{"msg":"操作成功","code":200,"data":{"createBy":"admin","createTime":"2020-07-18 16:04:26","updateBy":"admin","updateTime":"2020-07-18 16:04:26","remark":null,"id":31,"articleTitle":"Ansible（四）Playbook格式与写法","articleUrl":"ansible_playbook","articleThumbnail":"https://www.asumimoe.com/imgfiles/20220908/5969ef61cf754d1aa6e0f5c28219aefa.jpg","articleFlag":"1","draftStatus":"1","reprintStatement":"0","articleSummary":"Playbooks 是 Ansible的配置,部署,编排语言.他们可以被描述为一个需要希望远程主机执行命令的方案,或者一组IT程序运行的命令集合。如果 Ansible 模块你是工作室中的工具,那么 playbooks 就是你设置的方案计划。","articleContent":"## YAML语法\n\n### 简单说明\n\n以一个简单的playbook为例说明yaml的语法。\n\n```yml\n---\n    - hosts: 192.168.100.59,192.168.100.65\n        remote_user: root\n        pre_tasks:\n            - name: set epel repo for Centos 7\n              yum_repository:\n                name: epel7\n                description: epel7 on CentOS 7\n                baseurl: http://mirrors.aliyun.com/epel/7/$basearch/\n                gpgcheck: no\n                enabled: True\n        tasks:\n            # install nginx and run it\n            - name: install nginx\n              yum: name=nginx state=installed update_cache=yes\n            - name: start nginx\n              service: name=nginx state=started\n              post_tasks:\n            - shell: echo \"deploy nginx over\"\n              register: ok_var\n            - debug: msg=\"{{ ok_var.stdout }}\"\n```\n\n1. yaml⽂件以--- 开头，以表明这是⼀个yaml⽂件，就像xml⽂件在开头使⽤<?xml version=\"1.0\" encoding=\"utf-8\"?> 宣称它是xml⽂件⼀样。但即使没有使⽤--- 开头，也不会有什么影响。\n2. yaml中使⽤\"#\"作为注释符，可以注释整⾏，也可以注释⾏内从\"#\"开始的内容。\n3. yaml中的字符串通常不⽤加任何引号，即使它包含了某些特殊字符。但有些情况下，必须加引号，最常见的是\n   在引⽤变量的时候。具体见后⽂。\n4. 关于布尔值的书写格式，即true/f alse的表达⽅式。其实playbook中的布尔值类型⾮常灵活，可分为两种情\n   况：\n\n - 模块的参数： 这时布尔值作为字符串被ansible解析。接受yes/on/1/true/no/of f /0/f alse，这时被ansible解\n   析。例如上⾯⽰例中的update_cache=yes 。\n - ⾮模块的参数： 这时布尔值被yaml解释器解析，完全遵循yaml语法。接受不区分⼤⼩写的\n   true/yes/on/y/f alse/no/of f /n。例如上⾯的gpgcheck=no 和enabled=True 。\n\n建议遵循ansible的官⽅规范，模块的布尔参数采⽤yes/no，⾮模块的布尔参数采⽤True/False。\n\n### 列表\n\n使用\"- \"（减号加一个或多个空格）作为列表项，也就是json的属组。\n\n例如：\n    \n\n```yml\n- zhangsan\n- lisi\n- wangwu\n```\n\n还支持内联写法：使用中括号。\n\n```yml\n[zhangsan,lisi,wangwu]\n```\n\n等价于json格式的：\n\n```json\n[\n\"zhangsan\",\n\"lisi\",\n\"wangwu\"\n]\n```\n\n具体在ansible playbook中，列表所描述的是局部环境，它不⼀定要有名称，不⼀定要从同⼀个属性开始，只要使\n⽤\"- \"，它就表⽰圈定⼀个范围，范围内的项都属于该列表。例如：\n\n```yml\n---\n    - name: list1           # 列表1，同时给了个名称\n      hosts: localhost      # 指出了hosts是列表1的⼀个对象\n      remote_user: root     # 列表1的属性\n      tasks:                # 还是列表1的属性\n    \n    - hosts: 192.168.10.11  # 列表2，但是没有为列表命名，⽽是直⼊主题11\n      remote_user: root\n      sudo: yes\n      tasks:\n```\n\n唯⼀要注意的是，每⼀个playbook中必须包含\"hosts\"和\"tasks\"项。更严格地说，是每个play的顶级列表必须包含\n这两项。就像上⾯的例⼦中，就表⽰该playbook中包含了两个play，每个play的顶级列表都包含了hosts和\ntasks。其实绝⼤多数情况下，⼀个playbook中都只定义⼀个play，所以只有⼀个顶级列表项。**顶级列表的各项，\n其实可以将其看作是ansible-playbook运⾏时的选项。**\n\n### 字典\n\n官⽅⼿册上这么称呼，其实就是key=value的另⼀种写法。使⽤\"冒号+空格\"分隔，即key: value 。它⼀般当作列表\n项的属性。\n\n例如：\n\n```yml\n- hosts: localhost # 列表1\n  remote_user: root\n  tasks:\n    - name: test1                   # ⼦列表，下⾯是shell模块，是⼀个动作，所以定义为列表，只不过加了个name\n      shell: echo /tmp/a.txt\n      register: hi_var\n    - debug: var=hi_var.stdout      # 调⽤模块，这是动作，所以也是列表\n    - include: /tmp/nginx.yml       # 同样是动作，包含⽂件\n    - include: /tmp/mysql.yml\n    - copy:                         # 调⽤模块，定义为列表。但模块参数是虚拟性内容，应定义为字典⽽⾮列表\n      src: /etc/resolv.conf         # 模块参数1\n      dest: /tmp                    # 模块参数2\n\n- hosts: 192.168.10.13              # 列表2\n  remote_user: root\n  vars:\n    nginx_port: 80                  # 定义变量，是虚拟性的内容，应定义为字典⽽⾮列表\n    mysql_port: 3306\n  vars_files:\n    - nginx_port.yml                # ⽆法写成key/value格式，且是实体⽂件，因此定义为列表\n  tasks:\n    - name: test2\n      shell: echo /tmp/a.txt\n      register: hi_var              # register是和最近⼀个动作绑定的\n    - debug: var=hi_var.stdout\n```\n\n模块的参数是虚拟性内容，也能使用字典的方式定义。\n字典格式也支持内联格式写法：\n\n```yml\n{nginx_port: 80,mysql_port: 3306}\n```\n\n### 分行\n\n- 在\"key: \"的后⾯使⽤⼤于号。\n\n- 在\"key: \"的后⾯使⽤竖线。这种⽅式可以像脚本⼀样写很多⾏语句。\n\n- 多层缩进。\n\n  ```yml\n  hosts: localhost\n  tasks:\n  \n    - shell: echo 2 >>/tmp/test.txt\n      creates=/tmp/haha.txt           # ⽐模块shell缩进更多\n    - shell: >                          # 在\"key: \"后使⽤⼤于号\n      echo 2 >>/tmp/test.txt\n      creates=/tmp/haha.txt\n    - shell: |                          # 指定多⾏命令\n      echo 2 >>/tmp/test.txt\n      echo 3 >>/tmp/test.txt\n      args:\n        creates: /tmp/haha.txt\n  ```\n\n### 向模块传递参数\n\n模块参数一般是key=value格式的，有三种传递方式：\n\n  - 直接写在模块后，此时要求使⽤\"key=value\"格式。这是让ansible内部去解析字符串。因为可分⾏写，所以有\n    多种写法。\n  - 写成字典型，即\"key: value\"。此时要求多层缩进。这是让yaml去解析字典。\n  - 使⽤内置属性args，然后多层缩进定义参数列表。这是让ansible明确指定⽤yaml来解析。\n\n例如：\n\n```yml\n- hosts: localhost\n  tasks:\n    - yum: name=unix2dos state=installed    # key=value直接传递\n    - yum:\n        name: unxi2dos\n        state: installed                    # \"key: value\"字典格式传递\n    - yum:\n        args:                               # 使⽤args传递\n        name: unix2dos\n        state:installed\n```\n\n但要注意，当模块的参数是free_form 时，即格式不定，例如shell和command模块指定要执⾏的命令，它⽆法写成\nkey/value格式，此时不能使⽤上⾯的第⼆种⽅式。也就是说，下⾯第⼀个模块是正确的，第⼆个模块是错误的，因\n为shell模块的命令\"echo haha\"是⾃由格式的，⽆法写成key/value格式。\n\n```yml\n- hosts: localhost\n  tasks:\n    - yum:\n        name: unxi2dos\n        state: installed\n    - shell:\n        echo haha\n        creates: /tmp/haha.txt\n```\n\n调用一个模块的方式有了多种方式，例如：\n\n```yml\n- hosts: localhost\n  tasks:\n    - shell: echo 1 >/tmp/test.txt creates=/tmp/haha.txt\n    - shell: echo 2 >>/tmp/test.txt\n        creates=/tmp/haha.txt\n    - shell: echo 3 >>/tmp/test.txt\n        args:\n        creates: /tmp/haha.txt\n    - shell: >\n        echo 4 >>/tmp/test.txt\n        creates=/tmp/haha.txt\n    - shell: |\n        echo 5.1 >>/tmp/test.txt\n        echo 5.2 >>/tmp/test.txt\n      args:\n        creates: /tmp/haha.txt\n    - yum:\n        name: dos2unix\n        state: installed\n```\n\n## playbook应用示例\n\n### playbook中什么时候使用引号\n\nplaybook中定义的大多数是列表和字典。绝大多数时候都不需要引号，除两个特殊情况外：\n\n- 出现⼤括号\"{}\"。\n- 出现冒号加空格时\": \"。\n\n大括号要使用引号包围，是因为不使用引号时会被yaml解析成内联字典。例如要使用大括号引用变量时，以及想输出大括号符的时候。\n\n```yml\n- hosts: localhost\n  tasks:\n    - shell: echo \"{{inventory_hostname}}:haha\"\n```\n\n冒号尾随空格时要使用引号包围，是因为它会被解析为\"key:value\"的形式。而且包围冒号的引号还要更严格。例如下面的debug模块中即使使用了引号也是错误的。\n\n```yml\n- hosts: localhost\n  tasks:\n    - shell: echo \"{{inventory_hostname}}:haha\"\n      register: hello\n    - debug: msg=\"{{hello.stdout}}: heihei\"\n```\n\n因为它把{{...}}当成key，heihei当成了value了。因此，必须将整个debug模块的参数都包围起来，显示指定这一段是模块的参数。但这样会和原来的引号冲突，因此使用单引号。\n\n```yml\n- hosts: localhost\n  tasks:\n    - shell: echo \"{{inventory_hostname}}:haha\"\n      register: hello\n    - debug: 'msg=\"{{hello.stdout}}: heihei\"'\n```\n\n但是，如果将shell模块中的冒号后也尾随上空格，即写成echo \"{{inventory_hostname}}: haha\" ，那么shell模块也会报错。因此也要使用多个引号，正确示例如下：\n\n```yml\n- hosts: localhost\n  tasks:\n    - shell: 'echo \"{{inventory_hostname}}: haha\"'\n      register: hello\n    - debug: 'msg=\"{{hello.stdout}}: heihei\"'\n```\n\n### ansible-playbook命令使用\n\n如下为一个简单的playbook\n\n```yml\n- hosts: centos7\n  tasks:\n    - name: execute date cmd\n      command: /bin/date\n    - name: copy fstab to /tmp\n      copy: src=/etc/fstab dest=/tmp\n```\n\n共执行两个任务：第一个任务是执行一个/bin/date命令；第二个任务是复制/etc/fstab文件到目标主机的/tmp下。\n\n用来执行playbook的命令为ansible-playbook，已下是该命令的帮助信息。\n\n```yml\nansible-playbook --help\nUsage: ansible-playbook playbook.yml\n\nOptions:\n    -e EXTRA_VARS,--extra-vars=EXTRA_VARS       # 设置额外的变量，格式为key/value。-e \"key=KEY\"，\n                                                # 如果是⽂件⽅式传⼊变量，则-e \"@param_file\"\n    --flush-cache                       # 清空收集到的fact信息缓存\n    --force-handlers                    # 即使task执⾏失败，也强制执⾏handlers\n    --list-tags                         # 列出所有可获取到的tags\n    --list-tasks                        # 列出所有将要被执⾏的tasks\n    -t TAGS,--tags=TAGS                 # 以tag的⽅式显式匹配要执⾏哪些tag中的任务\n    --skip-tags=SKIP_TAGS               # 以tag的⽅式忽略某些要执⾏的任务。被此处匹配的tag中的任务都不会执⾏\n    --start-at-task=START_AT_TASK       # 从此task开始执⾏playbook\n    --step                              # one-step-at-a-time:在每⼀个任务执⾏前都进⾏交互式确认\n    --syntax-check                      # 检查playbook语法\n```\n\n- 默认情况下，ansible-playbook和ansible是⼀样的，都是同步阻塞模式，需要先在\n  所有主机上执⾏完⼀个任务，才会继续下⼀个任务；\n- 在执⾏前会⾃动收集fact信息；\n- 从显⽰结果中可以判断出任务是否真的执⾏了，抑或者是因为幂等性⽽没有执⾏。\n- 每⼀个play都包含数个task，且都有响应信息play recap。\n\n## playbook内容\n\n### hosts和remote_user\n\n对于playbook中的每⼀个play，使⽤hosts选项可以定义要执⾏这些任务的主机或主机组，还可以使⽤\nremote_user指定在远程主机上执⾏任务的⽤户，实际上remote_user是ssh连接到被控主机上的⽤户，⾃然⽽然\n执⾏命令的⾝份也将是此⽤户。\n\n```yml\n- hosts: centos6,centos7,192.168.100.59\n  remote_user: root\n  tasks: XXXXXX\n```\n\n虽然在hosts处可以使⽤\",\"分隔主机或主机组，但官⽅⼿册上并没有介绍该⽅法。除此之外，有以下⼏种指定主机\n和主机组的⽅式：\n\n- all 或* ：表⽰inventory中的所有主机。\n- : ：取并集。例如\"host1:host2:group1\"表⽰2台主机加⼀个主机组。\n- :& ：取交集。例如\"group1:&group2\"表⽰两个主机组中都有的主机。\n- :! ：排除。例如\"group1:!host1\"表⽰group1中排除host1主机的剩余主机。\n- 通配符：例如\"web*.baidu.com\"。\n- 数字范围：例如\"web[0-5].baidu.com\"。\n- 字母范围：例如\"web[a-d].baidu.com\"。\n- 正则表达式：以\"~\"开头。例如\"~web\\d\\.baidu\\.com\"。\n\n在命令行或playbook命令行中，可以使用-l选项指定执行任务的主机，如：\n\n```yml\nansile centos -l host[1:5] -m ping    # 表示在centos主机组中只有host1到host5才执行ping模块\n```\n\n也可以在某个task上单独指定执行该task的身份，这将覆盖全局的定义。\n\n```yml\n- hosts: centos6,centos7,192.168.100.59\n  remote_user: root\n  tasks:\n    - name: run a command\n      shell: /bin/date\n    - name: copy a file to /tmp\n      copy: src=/etc/fstab dest=/tmp\n      remote_user: myuser\n```\n\n### task list\n\n#### 1. 特性\n\n- 每个play都包含⼀个hosts和⼀个tasks，hosts定义的是inventory中待控制的主机，tasks下定义的是⼀系列task\n  任务列表，⽐如调⽤各个模块。这些task按顺序⼀次执⾏⼀个，直到所有被筛选出来的主机都执⾏了这个task之后\n  才会移动到下⼀个task上进⾏同样的操作。\n\n- 需要注意的是，虽然只有被筛选出来的主机会执⾏对应的task，但是所有主机(此处的所有主机表⽰的是，hosts选\n  项所指定的那些主机)都会收到相同的task指令，所有主机收到指令后，ansible主控端会筛选某些主机，并通过ssh\n  在远程执⾏任务。也就是说，如果查看ansible-playbook -vvvv 的信息，将会发现临时任务⽂件会通过sftp发送到所\n  有的被控主机上，但是只有⼀部分被筛选(如果进⾏了筛选)的主机才会ssh过去并远程执⾏命令。\n\n- 当某⼀台被控主机执⾏某个任务出错或失败时，它将会被移除出任务轮询列表。也就是说，对于某主机来说，某任\n  务执⾏失败，后续的所有任务都不会再去执⾏。当然，这不会影响其他的主机执⾏任务(除⾮主机的任务之间有依赖\n  关系)。\n\n- 最重要的是，ansible中的task是幂等性的，多次执⾏不会影响那些成功执⾏过的任务。另外幂等性还表现在执⾏失\n  败后如果修正了playbook再次执⾏，将不会影响那些原本已经执⾏成功的任务，即使是不同主机也不会影响。仅这\n  ⽅⾯⽽⾔，ansible对于排错来说是极其友好的。当然，某些特殊的模块或者特殊定义的task并不⼀定总是幂等的，\n  例如最简单的，执⾏⼀个command或者shell模块的命令，它会重复执⾏。但也有办法使其变得幂等，以command\n  和shell模块为例，它们有两个选项：creates和removes，它们分别表⽰被控主机上指定的⽂件存在(不存在)时就不\n  执⾏命令。\n\n#### 2. 定义task的细节\n\n- 可以为每个task加上name项，也可以多个task依赖一个name。\n\n```\ntasks:\n  - name: do something to initialize mariadb\n    file: path=/mydata/data state=directory owner=mysql group=mysql mode=0755\n  - shell: /usr/bin/mysql_install_db --datadir=/mydata/data --user=mysql creates=/mydata/data/ibdata1\n```\n\n-  既然task，必然要执行一个或多个任务，其本质是加载并执行ansible对应的模块。在playbook中，每调用一个模块都称为一个action。\n\n例如，定义一个确保服务是启动状态的task，有以下三种方法\n\n    tasks:\n      - name: be sure the sshd is running\n        service: name=sshd state=started    # ⽅法⼀： 定义为key=value，直接传递参数给模块\n        \n        service:                            # ⽅法⼆： 定义为key: value⽅式\n            name: sshd\n            state: started\n        service:                            # ⽅法三： 使⽤args内置关键字，然后定义为key: value⽅式\n        args:\n            name: sshd\n            state: started\n\n注意：，ping模块、command和shell模块是不需要key=value 格式的。对于ping命令，可以直接省略key=value 。\n对于command和shell，只需要给定命令路径和要接上去的选项或参数即可，且⽆法使⽤上⾯的⽅法⼆。例如下⾯\n定义的是⼀个ntpdate命令，只需给定它的参数即可。\n\n    tasks:\n      - name: execute command ntpdate\n        shell: /usr/sbin/ntpdate ntp1.aliyun.com\n      - name: ping host\n        ping:\n\n对于command或shell模块来说，有时候要考虑命令的返回状态码。如果要忽略⾮0状态码继续执⾏任务，可以使⽤\n以下两种⽅式：\n\n    tasks:\n      - name: ignore non_zero return code\n        shell: /usr/sbin/ntpdate ntp1.aliyun.com || /bin/true\n    \n    tasks:\n      - name: another way to ignore the non_zero return code\n        shell: /usr/sbin/ntpdate ntp1.aliyun.com\n        ignore_errors: true\n\n- 如果action的key=value太多，导致内容太长，可以在上⼀⾏的缩进级别基础上继续缩进表⽰续⾏。例如，下⾯的owner⽐src多缩进了4个空格。\n\n\n    tasks:\n      - name: Copy ansible inventory file to client\n        copy: src=/etc/fstab dest=/tmp\n        owner=root group=root mode=0644\n\n- 在action的value部分，可以引⽤已经定义的变量，可以是已定义好的⾃定义的变量，也可以是内置变量。\n\n- 使⽤include指令，可以将其他的playbook⽂件包含到此playbook⽂件中。\n\n### notify和handler\n\nansible中⼏乎所有的模块都具有幂等性，这意味着被控主机的状态是否发⽣改变是能被捕捉的，即每个任务的\nchanged=true或changed=false。ansible在捕捉到changed=true时，可以触发notify组件(如果定义了该组件)。\nnotify是⼀个组件，并⾮⼀个模块，它可以直接定义action，其主要⽬的是调⽤handler。例如：\n\n    tasks:\n      - name: copy template file to remote host\n        template: src=/etc/ansible/nginx.conf.j2 dest=/etc/nginx/nginx.conf\n        notify:\n          - restart nginx\n          - test web page\n        copy: src=nginx/index.html.j2 dest=/usr/share/nginx/html/index.html\n        notify:\n          - restart nginx\n\n这表⽰当执⾏template模块的任务时，如果捕捉到changed=true，那么就会触发notify，如果分发的index.html改\n变了，那么也重启nginx(当然这是没必要的)。notify下定义了两个待调⽤的handler。handler主要⽤于重启服务或\n者触发系统重启，除此之外很少使⽤handler。\n\n    handlers:\n      - name: restart nginx\n        service: name=nginx state=restarted\n      - name: test web page\n        shell: curl -I http://192.168.10.10/index.html | grep 200 || /bin/false\n\nhandler的定义和tasks的定义完全⼀样，唯⼀需要限定的是handler中task的name必须和notify中定义的名称相\n同。\n注意，notify是在执⾏完⼀个play中所有task后被触发的，在⼀个play中也只会被触发⼀次。意味着如果⼀个play中\n有多个task出现了changed=true，它也只会触发⼀次。例如上⾯的⽰例中，向nginx复制配置⽂件和复制\nindex.html时如果都发⽣了改变，都会触发重启apache操作。但是只会在执⾏完play后重启⼀次，以避免多余的重启\n\n### 标签tag\n\n可以为playbook中的每个任务都打上标签，标签的主要作⽤是可以在ansible-playbook中设置只执⾏哪些被打上\ntag的任务或忽略被打上tag的任务。\n\n    tasks:\n      - name: make sure apache is running\n        service: name=httpd state=started\n        tags: apache\n      - name: make sure mysql is running\n        service: name=mysqld state=started\n        tags: mysql\n\n以下是ansible-playbook命令关于tag的选项\n\n    --list-tags                 # list all available tags\n    -t TAGS, --tags=TAGS        # only run plays and tasks tagged with these values\n    --skip-tags=SKIP_TAGS       # only run plays and tasks whose tags do not match these values\n\n参考自：<https://www.cnblogs.com/f-ck-need-u/p/7576137.html#ansible>","categoryId":3,"viewCount":2319,"categoryName":"Ansible","author":"球接子","authorAvatar":null,"tagIds":[3],"tagNames":["Ansible"]}}