zoukankan      html  css  js  c++  java
  • Ansible-playbook 运维笔记

    之前详细介绍了Ansible的安装, 配置, 以及Ansible常用模块的使用. 下面对Ansible的playbook用法做一小结。

    为什么引入playbook?
    一般运维人员完成一个任务, 比如安装部署一个httpd服务会需要多个模块(一个模块也可以称之为task)提供功能来完成。而playbook就是组织多个task的容器,它的实质就是一个文件,有着特定的组织格式,它采用的语法格式是YAML(Yet Another Markup Language)。YAML语法能够简单的表示散列表,字典等数据结构。简单来说, playbook是由一个或多个模块组成的,使用多个不同的模块,完成一件事情。 

    Ansible核心功能
    -  pyYAML用于ansible编写剧本所使用的语言格式(saltstack---python);
    -  rsync-ini语法, sersync-xml语法, nsible-pyYAML语法;
    -  paramiko远程连接与数据传输;
    -  Jinja2用于编写ansible的模板信息;

    YAML三板斧
    缩进: YAML使用一个固定的缩进风格表示层级结构,每个缩进由两个空格组成, 不能使用tabs;
    冒号: 以冒号结尾的除外,其他所有冒号后面所有必须有空格;
    短横线: 表示列表项,使用一个短横杠加一个空格。多个项使用同样的缩进级别作为同一列表;

    YAML基本语法
    Ansible-playbook采用YAML语法编写。连续的项目(即列表)用 -减号来表示,key/value(字典)用冒号:分隔。

    列表:每一个列表成员前面都要有一个短横线和一个空格

    fruits:
        - Apple
        - Orange
        - Strawberry
        - Mango
    
    或者:
    fruits: ['Apple', 'Orange', 'Strawberry', 'Mango']
    

    字典:每一个成员由键值对组成,注意冒号后面要有空格

    martin:
        name: Martin D'vloper
        job: Developer
        skill: Elite
    或者
    martin: {name: Martin D'vloper, job: Developer, skill: Elite}
    

    列表和字典可以混合使用

    -  martin:
        name: Martin D'vloper
        job: Developer
        skills:
          - python
          - perl
          - pascal
    -  tabitha:
        name: Tabitha Bitumen
        job: Developer
        skills:
          - lisp
          - fortran
          - erlang

    示例如下:
    [root@localhost ~]# cat httpd.yaml

    ---
    - hosts: control-node    #将要执行任务的主机,已经在hosts文件中定义好了,可是单个主机或主机组
      remote_user: root      #在目标主机上执行任务时的用户身份
      vars:
        - pkg: httpd
      tasks:
        - name: "install httpd package."
          yum: name={{ pkg }}  state=installed
        - name: "copy httpd configure file to remote host."
          copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
          notify: restart httpd     #当这个任务执行状态发生改变时,触发handlers执行.
        - name: "boot httpd service."
          service: name=httpd state=started
      handlers:                     #handlers与tasks是同一级别
        - name: restart httpd
          service: name=httpd state=restarted
    

    playbook语法特性
    1.  以 --- (三个减号)开始,必须顶行写;
    2. 次行开始写Playbook的内容,但是一般要求写明该playbook的功能;
    3. 严格缩进,并且不能用Tab键缩进;
    4. 缩进级别必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的;
    5. K/V的值可同行写,也可换行写。同行使用 :分隔,换行写需要以 - 分隔;

    playbook基础组件
    Hosts:运行执行任务(task)的目标主机
    remote_user:在远程主机上执行任务的用户
    tasks:任务列表
    handlers:任务,与tasks不同的是只有在接受到通知时才会被触发
    templates:使用模板语言的文本文件,使用jinja2语法。
    variables:变量,变量替换{{ variable_name }}

    整个playbook是以task为中心,表明要执行的任务。hosts和remote_user表明在远程主机以何种身份执行,其他组件让其能够更加灵活。下面介绍插件:

    1.  variable
    变量定义在资产 (inventory) 中, 默认就是/etc/ansible/hosts文件中

    主机变量:
    192.168.200.136 http_port=808 maxRequestsPerChild=808
    192.168.200.137 http_port=8080 maxRequestsPerChild=909
    
    主机组变量:
    [websers]
    192.168.200.136
    192.168.200.137
    
    [websers:vars]  
    ntp_server=ntp.exampl.com
    proxy=proxy.exampl.com
    

    变量定义在playbook中

    - hosts: webservers
      vars:
        http_port: 80
    

    使用facts变量

    facts变量是由setup模块获取远程主机的信息。
    
    用法:
    # ansible 192.168.200.136 -m setup
    

    在roles中定义变量, 这个后面会介绍到.

    ansible-playbook 命令中传入参数

    使用 -e选项传入参数
    # ansible-playbook 192.168.200.136 -e "httpd_port=808" httpd04.yml
    

    变量的引用

    {{ var_name }}
    

    2.  templates
    它是一个模块功能,与copy不同的是他的文本文件采用了jinga2语法,jinga2基本语法如下:

    字面量:
      字符串:使用单引号或双引号
      数字:整型,浮点数
      列表:{item1,item2,...}
      字典:{key1:value1,key2:value2,...}
      布尔型:true/false
    算术运算:
      +,-,*,/,//,%,**
    比较运算:
      ==,!=,>,>=,<,<=
    逻辑运算:
      and,or,not
    

    注意:template只能在palybook中使用。

    3.  tasks
    执行的模块命令

    格式:
      action:模块参数(此种方式只在较新的版本中出现)
      module:参数(已键值对的形式出现)
    
    每一个task都有一个名称,用于标记此任务。任务示例:
      name: install httpd
      yum: name=httpd state=present
    
    注意:shell和command没有参数,可在后面直接跟命令
      shell: ss -tnl | grep :80
    
    1)某任务的运行状态为changed后,可通过相应的notify通知相应的handlers
    2)任务可以通过tags打标签,然后通过palybook命令-t选项调用.

    playbook命令及调用方式

    用法:
    ansible-playbook  <filename.yml> ... [options]

    <filename.yml>: yaml格式的playbook文件路径,必须指明
    [options]: 选项

    Options:
    --ask-vault-pass     
                 #ask for vault password
                 #加密playbook文件时提示输入密码
      -C, --check          
                 #don't make any changes; instead, try to predict some of the changes that may occur
                 #模拟执行,不会真正在机器上执行(查看执行会产生什么变化)。即并不在远程主机上执行,只是测试。
      -D, --diff           
                 #when changing (small) files and templates, show the differences in those files; works great with --check
                 #当更新的文件数及内容较少时,该选项可显示这些文件不同的地方,该选项结合-C用会有较好的效果
      -e EXTRA_VARS, --extra-vars=EXTRA_VARS
                 #set additional variables as key=value or YAML/JSON
                 #在Playbook中引入外部参数变量
      --flush-cache        
                 #clear the fact cache
                 #清理fact缓存,将fact清除到的远程主机缓存
      --force-handlers     
                 #run handlers even if a task fails
                 #强制运行handlers的任务,即使在任务失败的情况下
      -f FORKS, --forks=FORKS
                 #specify number of parallel processes to use(default=5)
                 #并行任务数。FORKS被指定为一个整数,默认是5
      -h, --help           
                 #show this help message and exit
                 #打开帮助文档API
      -i INVENTORY, --inventory-file=INVENTORY
                 #specify inventory host path (default=/etc/ansible/hosts) or comma separated host list.
                 #指定要读取的Inventory清单文件
      -l SUBSET, --limit=SUBSET
                 #further limit selected hosts to an additional pattern
                 #限定执行的主机范围
      --list-hosts         
                 #outputs a list of matching hosts; does not execute anything else
                 #列出执行匹配到的主机,但并不会执行任何动作。
      --list-tags          
                 #list all available tags
                 #列出所有可用的tags
      --list-tasks         
                 #list all tasks that would be executed
                 #列出所有即将被执行的任务
      -M MODULE_PATH, --module-path=MODULE_PATH
                 #specify path(s) to module library (default=None)
                 #要执行的模块的路径
      --new-vault-password-file=NEW_VAULT_PASSWORD_FILE
                 #new vault password file for rekey
                 #
      --output=OUTPUT_FILE 
                 #output file name for encrypt or decrypt; use - for stdout
                 #
      --skip-tags=SKIP_TAGS
                 #only run plays and tasks whose tags do not match these values
                 #跳过指定的tags任务
      --start-at-task=START_AT_TASK
                 #start the playbook at the task matching this name
                 #从第几条任务(START_AT_TASK)开始执行
      --step               
                 #one-step-at-a-time: confirm each task before running
                 #逐步执行Playbook定义的任务,并经人工确认后继续执行下一步任务
      --syntax-check       
                 #perform a syntax check on the playbook, but do not execute it
                 #检查Playbook中的语法书写,并不实际执行
      -t TAGS, --tags=TAGS 
                 #only run plays and tasks tagged with these values
                 #指定执行该tags的任务
      --vault-password-file=VAULT_PASSWORD_FILE
                 #vault password file
                 #
      -v, --verbose        
                 #verbose mode (-vvv for more, -vvvv to enable connection debugging)
                 #执行详细输出
      --version            
                 #show program's version number and exit
                 #显示版本
     
      ############Connection Options,即下面时连接权限############
        control as whom and how to connect to hosts
     
        -k, --ask-pass     
                 #ask for connection password
                 #
        --private-key=PRIVATE_KEY_FILE, --key-file=PRIVATE_KEY_FILE
                 #use this file to authenticate the connection
                 #
        -u REMOTE_USER, --user=REMOTE_USER
                 #connect as this user (default=None)
                 #指定远程主机以USERNAME运行命令
        -c CONNECTION, --connection=CONNECTION
                 #connection type to use (default=smart)
                 #指定连接方式,可用选项paramiko (SSH)、ssh、local,local方式常用于crontab和kickstarts
        -T TIMEOUT, --timeout=TIMEOUT
                 #override the connection timeout in seconds(default=10)
                 #SSH连接超时时间设定,默认10s
        --ssh-common-args=SSH_COMMON_ARGS
                 #specify common arguments to pass to sftp/scp/ssh (e.g.ProxyCommand)
                 #
        --sftp-extra-args=SFTP_EXTRA_ARGS
                 #specify extra arguments to pass to sftp only (e.g. -f, -l)
                 #
        --scp-extra-args=SCP_EXTRA_ARGS
                 #specify extra arguments to pass to scp only (e.g. -l)
                 #
        --ssh-extra-args=SSH_EXTRA_ARGS
                 #specify extra arguments to pass to ssh only (e.g. -R)
                 #
     
      ############Privilege Escalation Options, 即下面时权限提升权限############
        control how and which user you become as on target hosts
     
        -s, --sudo         
                 #run operations with sudo (nopasswd) (deprecated, use become)
                 #相当于Linux系统下的sudo命令
        -U SUDO_USER, --sudo-user=SUDO_USER
                 #desired sudo user (default=root) (deprecated, use become)
                 #使用sudo,相当于Linux下的sudo命令
        -S, --su           
                 #run operations with su (deprecated, use become)
                 #
        -R SU_USER, --su-user=SU_USER
                 #run operations with su as this user (default=root)(deprecated, use become)
        -b, --become       
                 #run operations with become (does not imply password prompting)
                 #
        --become-method=BECOME_METHOD
                 #privilege escalation method to use (default=sudo),valid choices: [ sudo | su | pbrun | pfexec | doas |dzdo | ksu | runas ]
                 #
        --become-user=BECOME_USER
                 #run operations as this user (default=root)
                 #
        --ask-sudo-pass    
                 #ask for sudo password (deprecated, use become)
                 #传递sudo密码到远程主机,来保证sudo命令的正常运行
        --ask-su-pass      
                 #ask for su password (deprecated, use become)
                 #
        -K, --ask-become-pass
                 #ask for privilege escalation password
                 #

    ansible-playbook需要注意的两个命令
    1)检查语法,只检查是否是yaml语法格式。并不做逻辑校验。(记住这个要经常使用, 它是判断语法是否正确!!!)
    # ansible-playbook --syntax-check kevin.yml
    2)模拟执行(不是真的执行)
    # ansible-playbook -C kevin.yml

    关闭Facts
    如果不需要使用主机的任何fact数据,可以选择关闭fact数据的获取,这样有利于增强Ansible面对大量系统的push模块。
    在playbook中关闭Facts方法(gather_facts: no):

    ---
    - hosts: webserver
      gather_facts: no

    palybook书写格式

    ---                                   # 也可以不使用这一行。可以省略。
    - hosts: 172.16.60.211                #处理指定服务器.   - (空格)hosts:(空格)172.16.20.211
      task:                               #剧本所要干的事情; (空格)(空格)task:
      - name:                             #(两个空格)-(空格)name。
        command: echo hello clsn linux    #(四个空格)command:(空格) 
    
    需要注意:
    Task任务里的name可以省略不写,将-(空格)放到下一行模块墙面。例如:
    ---                                   
    - hosts: 172.16.60.211               
      task:                             
      - command: echo hello clsn linux
    
    小示例:
    [root@localhost ansible]# cat haha.yaml 
    ---
    - hosts: test_host
      remote_user: root
      gather_facts: no
      tasks:
         - file: path=/opt/task1.txt state=touch

    palybook格式示例

    [root@ansible-server ~]# vim /etc/ansible/test.yaml
    - hosts: 172.16.60.213
      tasks:
        - name: Install Rsync
          yum: name=rsync state=installed
    
    playbook检查方法
    [root@ansible-server ~]# ansible-playbook --syntax-check /etc/ansible/test.yaml
    
    playbook: /etc/ansible/test.yaml
    [root@ansible-server ~]# ansible-playbook -C /etc/ansible/test.yaml
    
    PLAY [172.16.60.213] *******************************************************************************************************************
    
    TASK [Gathering Facts] *****************************************************************************************************************
    ok: [172.16.60.213]
    
    TASK [Install Rsync] *******************************************************************************************************************
    ok: [172.16.60.213]
    
    PLAY RECAP *****************************************************************************************************************************
    172.16.60.213              : ok=2    changed=0    unreachable=0    failed=0  
    
    上面两个检查命令, 第一个是进行playbook剧本配置信息语法检查; 第二个是模拟playbook剧本执行(彩排)
    

    palybook剧本文件示例

    ansible-playbook编写内容扩展剧本任务编写多个任务

    - hosts: all
      tasks:
        - name: restart-network
          cron: name='restart network' minute=00 hour=00 job='/usr/sbin/ntpdate time.nist.gov >/dev/null 2>&1'
        - name: sync time
          cron: name='sync time' minute=*/5 job="/usr/sbin/ntpdate pool.ntp.com >/dev/null 2>&1"

    剧本编写内容扩展:剧本任务编写多个主机

    - hosts: 172.16.60.7
      tasks:
        - name: restart-network
          cron: name='restart network' minute=00 hour=00 job='/usr/sbin/ntpdate time.nist.gov >/dev/null 2>&1'
        - name: sync time
          cron: name='sync time' minute=*/5 job="/usr/sbin/ntpdate pool.ntp.com >/dev/null 2>&1"
    
    - hosts: 172.16.60.31
      tasks:
        - name: show ip addr to file
          shell: echo $(hostname -i) >> /tmp/ip.txt

    playbook剧本编写方式
    -  多主机单任务编写方式
    -  多主机多任务编写方式
    -  不同主机多任务编写方式

    来看一个比较完整的ansible的yml文件写法:

    ---
    - host: webservers       ###要管理的远程服务器组名称, 服务器地址维护在/etc/ansible/hosts 里, 也可以直接写地址
      vars:
        port: 8081           ###定义了一个变量 端口号
      remote_user: root      ###远程登录后用什么用户执行
     
      pre_tasks:             ###执行正式 task 之前执行的任务
      - name: pre task       ###任务名称
        shell: echo 'execute pre task'      ###执行一行 shell 命令, 支持 >> 等符号
     
      roles:            ###引入 roles, 可以理解为引用了一个其他项目 ansible 包, 引用的 roles 可以是另一个完整的 ansible 脚本
      - role: my_role   ###要引用的 role 名称
        when: "ansible_os_family == 'RedHat'"   ###判断条件, ansible_os_family 是一个内置变量, 可直接使用
     
      tasks:           ###按顺序执行以下 task
      - include: my_tasks/some_task.yml       ###可以引入其他 yml 文件
      - name: get hostname        ###这是一个 task, 名称
        command: cat log.log      ###执行一行 command , 和 shell 类似, 但是不支持 >> 等操作符
        register: result          ###执行的结果, 设到 result 这个变量中, 后面可以使用
      - name: set hostname 
        shell: cat {{result.stdout}} >> host.text
      - name: task1
        command: echo 'execute task1'
      - name: task2 start apache
        service:             ###启动 httpd 服务器, service 是一个 ansible 内置模块, 读者可以自行查看更多模块, 包括下载复制等等
          name: httpd
          state: started
          tags:
            - apache        ###这是一个标签, 可以用 ansible-playbook main.yml --tags "apache" 指定只执行这个任务
      - name: copy and set value of index.html
        template:           ###这是一个复制方法, 也叫模块, 并且.j2文件中可以使用{{}}来设置需要替换的变量 
          src: templates/index.html.j2
          dest: /etc/httpd/index.html
        notify:             ###唤醒执行后面的 handlers 中名字叫 restart apache 的任务
        - restart apache
     
      post_tasks:           ###最后需要执行的任务
      - name: posy task
        shell: echo 'execute post task'
     
      handlers:
      - name: restart apache
        debug:              ###这是一个打印模块
          msg: start restart apche

    palybook剧本中的方法

    1.  handlers 任务触发
    在需要被监控的任务(tasks)中定义一个notify,只有当这个任务被执行时,才会触发notify对应的handlers去执行相应操作。例如配置文件被修改后,有可能需要重启程序,此时我们可以配置一个handlers,类似触发器。注意:handlers下的name名称必须要和它对应的notify名称相同!否则不会执行!!

    [root@localhost ~]# cat httpd.yaml 
    ---
    - hosts: control-node
      remote_user: root
      vars:
        - pkg: httpd
      tasks:
        - name: "install httpd package."
          yum: name={{ pkg }}  state=installed
        - name: "copy httpd configure file to remote host."
          copy: src=/root/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
          notify: restart httpd
        - name: "boot httpd service."
          service: name=httpd state=started
      handlers:
        - name: restart httpd
          service: name=httpd state=restarted
    

    ########  在使用handlers的过程中,需要注意下面几点  ########
    1. handlers只有在其所在的任务被执行完时,它才会被运行;如果一个任务中定义了notify调用Handlers,但由于条件判断等原因,该任务未被执行,则Handlers同样不会被执行。
    2. handlers只会在Play的末尾运行一次;如果想在一个Playbook的中间运行handlers,则需要使用meta模块来实现,例如:-meta: flush_handlers。
    3. 可以直接在Handlers中使用notify选项,实现Handlers调用Handlers。
    4. 可以使用listen关键字,在一个tasks任务中一次性notify多个handler。即将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify。
    5. 如果一个Play在运行到调用handlers的语句之前失败了,那么这个handlers将不会被执行。但是可以使用mega模块的--force-handlers选项来强制执行handlers,即使在handlers所在Play中途运行失败也能执行。需要注意:--force-handlers参数主要针对即使playbook执行失败,也要执行代码块成功了的handlers(即执行成功的task任务), 如果代码块本身执行失败(即执行失败的task任务),那么它所对应的handlers应当不会被执行!

    handlers可以理解成另一种tasks,handlers是另一种"任务列表",可以理解handlers和tasks是"平级关系",所以他们的缩进相同。handlers的任务会被tasks中的任务进行"调用",但是,被"调用"并不意味着一定会执行,只有当tasks中的任务"真正执行"以后,handlers中被调用的任务才会执行,如果tasks中的任务并没有做出任何实际的操作,那么handlers中的任务即使被"调用",也并不会执行。handlers中可以有多个任务,被tasks中不同的任务notify。

    场景1:headlers在所有tasks任务被执行完时才执行。

    [root@localhost ansible]# cat haha.yaml
    ---
    - hosts: test_host
      remote_user: root
      become: yes
      become_method: sudo
      tasks:
        - name: make file task1
          file: path=/opt/task1.txt state=touch
          notify: task1
        - name: make file task2
          file: path=/opt/task2.txt state=touch
          notify: task2
      handlers:
        - name: task1
          file: path=/opt/task1.txt mode=777 owner=root group=root
        - name: task2
          file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes
    
    执行结果:
    [root@localhost ansible]# ansible-playbook haha.yaml 
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.233]
    ok: [172.16.60.234]
    
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    TASK [make file task2] ***************************************************************************************************************************
    changed: [172.16.60.233]
    changed: [172.16.60.234]
    
    RUNNING HANDLER [task1] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task2] **************************************************************************************************************************
    changed: [172.16.60.233]
    changed: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=5    changed=4    unreachable=0    failed=0   
    172.16.60.234              : ok=5    changed=4    unreachable=0    failed=0

    从上面运行结果看出,Handlers执行的顺序与Handlers在playbook中定义的顺序是相同的,与"handler"被notify的顺序无关。

    场景2:使用meta模块,headlers会在它所对应的task任务执行完后立即被触发并执行,即在playbook的中间环节运行。
    默认情况下,所有的task执行完毕后,才会执行各个handles,并不是执行完某个task后,立即执行相应的handler,如果想要在执行完某些task以后立即执行对应的handlre,那么需要使用meta模块。

    [root@localhost ansible]# cat haha.yaml
    ---
    - hosts: test_host
      remote_user: root
      become: yes
      become_method: sudo
      tasks:
        - name: make file task1
          file: path=/opt/task1.txt state=touch
          notify: task1
        - meta: flush_handlers
        - name: make file task2
          file: path=/opt/task2.txt state=touch
          notify: task2
      handlers:
        - name: task1
          file: path=/opt/task1.txt mode=777 owner=root group=root
        - name: task2
          file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes
    
    执行结果:
    [root@localhost ansible]# ansible-playbook haha.yaml 
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.234]
    ok: [172.16.60.233]
    
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task1] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    TASK [make file task2] ***************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task2] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=5    changed=4    unreachable=0    failed=0   
    172.16.60.234              : ok=5    changed=4    unreachable=0    failed=0 
    

    上面使用了meta模块后,注意它的执行顺序于场景1做下对比!

    场景3:Handlers调用Handlers
    若实现Handlers调用Handlers,则直接在Handlers中使用notify选项即可以。

    [root@localhost ansible]# cat haha.yaml
    ---
    - hosts: test_host
      remote_user: root
      become: yes
      become_method: sudo
      tasks:
        - name: make file task1
          file: path=/opt/task1.txt state=touch
          notify: task1
        - name: make file task2
          file: path=/opt/task2.txt state=touch
           
      handlers:
        - name: task1
          file: path=/opt/task1.txt mode=777 owner=root group=root
          notify: task2
        - name: task2
          file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes
     
    执行结果:
    [root@localhost ansible]# ansible-playbook haha.yaml             
     
    PLAY [test_host] *********************************************************************************************************************************
     
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.234]
    ok: [172.16.60.233]
     
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.233]
    changed: [172.16.60.234]
     
    TASK [make file task2] ***************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
     
    RUNNING HANDLER [task1] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
     
    RUNNING HANDLER [task2] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=5    changed=4    unreachable=0    failed=0  
    172.16.60.234              : ok=5    changed=4    unreachable=0    failed=0
    
    
    注意:上面执行的顺序是:make file task1 > make file task2 > task1 > task2
    
    ====================================================================
    也可以改成下面的方式:实现Handlers调用Handlers
    
    [root@localhost ansible]# cat haha.yaml 
    ---
    - hosts: test_host
      remote_user: root
      become: yes
      become_method: sudo
      tasks:
        - name: make file task1
          file: path=/opt/task1.txt state=touch
          notify: task1
    
      handlers:
        - name: task1
          file: path=/opt/task1.txt mode=777 owner=root group=root
          notify: task2
        - name: task2
          file: path=/opt/task2.txt state=touch
          notify: task3
        - name: task3
          file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes
    
    执行结果:
    [root@localhost ansible]# ansible-playbook haha.yaml 
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.233]
    ok: [172.16.60.234]
    
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.233]
    changed: [172.16.60.234]
    
    RUNNING HANDLER [task1] **************************************************************************************************************************
    changed: [172.16.60.233]
    changed: [172.16.60.234]
    
    RUNNING HANDLER [task2] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task3] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=5    changed=4    unreachable=0    failed=0   
    172.16.60.234              : ok=5    changed=4    unreachable=0    failed=0  
    
    注意:上面的执行顺序是:make file task1 > task1 > task2 > task3

    场景4:使用listen关键字,在一个tasks任务中一次性notify多个handler
    怎么才能一次性notify多个handler呢?如果尝试将多个handler使用相同的name呢?其实这样并不可行!因为当多个handler的name相同时,只有一个handler会被执行。要想实现一次notify多个handler,需要借助一个关键字,它就是"listen",可以把listen理解成"组名",可以把多个handler分成"组",当需要一次性notify多个handler时,只要将多个handler分为"一组",使用相同的"组名"即可,当notify对应的值为"组名"时,"组"内的所有handler都会被notify。需要注意:listen的名称要和notify名称保持一致!

    [root@localhost ansible]# cat haha.yaml
    ---
    - hosts: test_host
      remote_user: root
      become: yes
      become_method: sudo
      tasks:
        - name: make file task1
          file: path=/opt/task1.txt state=touch
          notify: group1_handler
      handlers:
        - name: task1
          listen: group1_handler
          file: path=/opt/task1.txt mode=777 owner=root group=root
        - name: task2
          listen: group1_handler
          file: path=/opt/task1.txt src=/opt/task1.txt dest=/opt/heihei state=link force=yes
        - name: task3
          listen: group1_handler
          shell: echo "this is test,haha...." >> /opt/task1.txt
    
    执行结果:
    [root@localhost ansible]# ansible-playbook haha.yaml 
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.233]
    ok: [172.16.60.234]
    
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task1] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task2] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    RUNNING HANDLER [task3] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=5    changed=4    unreachable=0    failed=0   
    172.16.60.234              : ok=5    changed=4    unreachable=0    failed=0

    场景5:使用--force-handlers选项来强制执行handlers
    当playbook剧本执行失败以后,handlers可能并没有被触发,也就不会执行了!如果想不管task任务是否成功执行,都强制执行handlers。在这个时候,可以在执行playbook的时候,添加--force-handlers来强制执行handlers!但是必须要注意的是:--force-handlers参数主要针对即使playbook执行失败,也要执行代码块成功了的handlers(即执行成功的task任务), 如果代码块本身执行失败(即执行失败的task任务),那么它所对应的handlers应当不会被执行!

    [root@localhost ansible]# cat haha.yaml 
    ---
    - hosts: test_host
      remote_user: root
      become: yes
      become_method: sudo
      tasks:
        - name: make file task1
          file: path=/opt/task1.txt state=touch
          notify: task1
        - name: make file task2
          file: path=/opt/kevin/task2.txt state=touch
          notify: task2
      handlers:
        - name: task1
          file: path=/opt/task1.txt mode=777 owner=root group=root
        - name: task2
          shell: ln -s /opt/task1.txt /opt/task2.txt
    
    执行结果:
    [root@localhost ansible]# ansible-playbook haha.yaml 
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.234]
    ok: [172.16.60.233]
    
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.233]
    changed: [172.16.60.234]
    
    TASK [make file task2] ***************************************************************************************************************************
    fatal: [172.16.60.234]: FAILED! => {"changed": false, "module_stderr": "", "module_stdout": "Traceback (most recent call last):
      File "/tmp/ansible_iNMDpU/ansible_module_file.py", line 474, in <module>
        main()
      File "/tmp/ansible_iNMDpU/ansible_module_file.py", line 448, in main
        open(b_path, 'wb').close()
    IOError: [Errno 2] No such file or directory: '/opt/kevin/task2.txt'
    ", "msg": "MODULE FAILURE", "rc": 0}
    fatal: [172.16.60.233]: FAILED! => {"changed": false, "module_stderr": "", "module_stdout": "Traceback (most recent call last):
      File "/tmp/ansible_OvTacW/ansible_module_file.py", line 474, in <module>
        main()
      File "/tmp/ansible_OvTacW/ansible_module_file.py", line 448, in main
        open(b_path, 'wb').close()
    IOError: [Errno 2] No such file or directory: '/opt/kevin/task2.txt'
    ", "msg": "MODULE FAILURE", "rc": 0}
    
    RUNNING HANDLER [task1] **************************************************************************************************************************
            to retry, use: --limit @/etc/ansible/haha.retry
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=2    changed=1    unreachable=0    failed=1   
    172.16.60.234              : ok=2    changed=1    unreachable=0    failed=1 
    
    如上执行结果,由于/opt/kevin目录不存在,导致task的第二个任务执行失败,这个时候handler根本没有被触发,也就不会执行。
    即使第一个任务执行成功,但是它对应的第一个handler也不会被执行!!
    
    ###################################################################################
    接下来使用--force-handlers选项来强制执行handlers(强制执行的是:成功执行的task对应的handler)
    [root@localhost ansible]# ansible-playbook haha.yaml --force-handlers
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.234]
    ok: [172.16.60.233]
    
    TASK [make file task1] ***************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
    
    TASK [make file task2] ***************************************************************************************************************************
    fatal: [172.16.60.233]: FAILED! => {"changed": false, "module_stderr": "", "module_stdout": "Traceback (most recent call last):
      File "/tmp/ansible_rEJQHm/ansible_module_file.py", line 474, in <module>
        main()
      File "/tmp/ansible_rEJQHm/ansible_module_file.py", line 448, in main
        open(b_path, 'wb').close()
    IOError: [Errno 2] No such file or directory: '/opt/kevin/task2.txt'
    ", "msg": "MODULE FAILURE", "rc": 0}
    fatal: [172.16.60.234]: FAILED! => {"changed": false, "module_stderr": "", "module_stdout": "Traceback (most recent call last):
      File "/tmp/ansible_7CDxpp/ansible_module_file.py", line 474, in <module>
        main()
      File "/tmp/ansible_7CDxpp/ansible_module_file.py", line 448, in main
        open(b_path, 'wb').close()
    IOError: [Errno 2] No such file or directory: '/opt/kevin/task2.txt'
    ", "msg": "MODULE FAILURE", "rc": 0}
    
    RUNNING HANDLER [task1] **************************************************************************************************************************
    changed: [172.16.60.234]
    changed: [172.16.60.233]
            to retry, use: --limit @/etc/ansible/haha.retry
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.233              : ok=3    changed=2    unreachable=0    failed=1   
    172.16.60.234              : ok=3    changed=2    unreachable=0    failed=1 
    
    如上执行结果,即使playbook执行中有task任务执行失败,但是执行成功的task任务所调用的handler依然会被强制触发并执行!但是执行失败的task任务所调用的handler依然不会被执行。
    即handlers中的task1会被执行,task2不会被执行!

    2.  tags任务标签
    tags用于让用户选择运行playbook中的部分代码。ansible具有幂等性,因此会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时如果确信其没有变化,就可以通过tags跳过此些代码片断。tags可以看作是ansible的任务控制!

    ansible的标签(Tags)功能可以给角色(Roles)、文件、单独的任务,甚至整个Playbook打上标签,然后利用这些标签来指定要运行Playbook中的个别任务,或不执行指定的任务。如果有一个很大的playbook剧本,而只想运行playbook其中的某个或部分task任务,而不是运行playbook中所有的任务,这个时候tags是你的最佳选择。

    2.1  ansible支持"tags:"属性,执行playbook时,可以通过两种方式根据"tags"过滤任务:
    1. 在命令行上,使用或选项"--tags--skip-tags",后面使用空格或"="都可以。
    2. 在ansible配置设置中,使用和选项"TAGS_RUNTAGS_SKIP";
    3. 可以使用"--list-tags"查看playbook中有哪些tags会被执行;

    2.2  ansible系统中内置的特殊tags(目前有5个特殊的tags)
    到ansible 2.5版本以后,目前系统内置的tags有以下几个:
    always: 除非--skip-tags指定这个标签,否则该标记为always的task一直都会执行。"--tags always"只执行标记了always的tasks;
    never: 除非--tags指定了这个标签,否则该标记为never的task一直都不会执行。"--tags never"执行标记了always和never的tasks;
    tagged: --tags tagged表示执行所有有tags标签的tasks任务,但不包括tags标签是never的tasks任务;--skip-tags tagged表示所有有tags标签的tasks任务都跳过,即不会执行。
    untagged: --tags untagged表示执行所有没有tags标签的tasks任务和tags标签为always的tasks任务;--skip-tags untagged效果相反!
    all:--tags all表示执行所有的tags标签为非never的task,包括有tags标签和无tags标签的tasks。

    执行ansible-playbook命令时,使用下面两个参数的含义(自定义的tags可以是单个,也可以是多个,多个之间使用逗号隔开):
    "--tags 自定义的tag" 表示执行tags为指定的标签名的tasks和tags为always的tasks。如果执行命令ansible-playbook site.yml 时不指定tags,则会执行所有tags为非never的tasks
    "--skip-tags 自定义tag" 表示执行所有非指定tag和非never的tasks

    2.3  tags标签配置语法有下面三种:

    语法一:
    tags:
      - tag_test
     
    语法二:
    tags: tag_test
     
    语法三:
    tags: ['tag_test']

    2.3  ansible的tags使用
    1)最常见的使用形式。一个task任务添加一个tags标签。

    官方示例如下:
    [root@localhost ansible]# vim example.yml
    ---
    - hosts: all
      remote_user: root
      gather_facts: no
      tasks:
        - yum: name={{ item }} state=installed
          with_items:
             - httpd
             - memcached
          tags:
             - packages
        - template: src=templates/src.j2 dest=/etc/foo.conf
          tags:
             - configuration

    此时如果希望只run其中的某个task,则run的时候指定tags即可。可以运行多个tags,中间使用逗号隔开;也可以运行单个tags。

    [root@localhost ansible]# ansible-playbook example.yml --tags "configuration,packages"   
    [root@localhost ansible]# ansible-playbook example.yml --tags configuration   
    [root@localhost ansible]# ansible-playbook example.yml --tags packages
    或者
    [root@localhost ansible]# ansible-playbook example.yml --tags="configuration,packages"   
    [root@localhost ansible]# ansible-playbook example.yml --tags=configuration   
    [root@localhost ansible]# ansible-playbook example.yml --tags=packages

    相反,也可以使用--skip-tags跳过某个task任务。

    [root@localhost ansible]# ansible-playbook example.yml --skip-tags configuration
    或者
    [root@localhost ansible]# ansible-playbook example.yml --skip-tags=configuration

    具体看下面示例(tags三种语法都用上):

    [root@localhost ansible]# cat haha.yaml
    ---
    - hosts: test_host
      remote_user: root
      gather_facts: no
      tasks:
        - name: task1
          file: path=/opt/task1.txt state=touch
          tags: make_task1
        - name: task2
          file: path=/opt/task2.txt state=touch
          tags:
             - make_task2
        - name: task3
          file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes
          tags: ['link_task3']
    
    只运行make_task1标签的task任务
    [root@localhost ansible]# ansible-playbook haha.yaml --tags make_task1
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [task1] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.234              : ok=1    changed=1    unreachable=0    failed=0   
    
    运行多个tags
    [root@localhost ansible]# ansible-playbook haha.yaml --tags make_task1,make_task2
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [task1] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    TASK [task2] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.234              : ok=2    changed=2    unreachable=0    failed=0
    
    跳过make_task2标签的任务,其他任务正常执行
    [root@localhost ansible]# ansible-playbook haha.yaml --skip-tags make_task2
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [task1] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    TASK [task3] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.234              : ok=2    changed=2    unreachable=0    failed=0 
    

    2)一个task任务添加多个tags标签。
    上面是一个task任务添加一个tags标签,其实一个task任务可以添加多个标签,而且不同的task任务可以使用相同的tags标签。

    一个任务添加多个tags标签的语法仍然也有三种:
    语法1:
    tags:
      - tag1
      - tag2
    
    语法2:
    tags: tag1,tag2
    
    语法3:
    tags: ['tag1,tag2']
    
    ========================================================
    具体示例如下:
    [root@localhost ansible]# vim https.yml
    ---
    - hosts: test_host
      remote_user: root
      tasks:
         - name: install httpd package
           tags: 
             - httpd
             - package
           yum:
             name=httpd
             state=latest
     
         - name: start up httpd service
           tags: httpd,service
           service:
             name: httpd
             state: started
    
    
    上面例子中每个任务都有多个标签,而且上例中两个任务都有一个共同的标签,就是httpd标签。
    所以当执行"ansible-playbook httpd.yml --tags=httpd"时,上面两个task任务都会被执行。
    
    由于上面例子中的所有任务都有共同的httpd标签,所以像这种情况,可以把httpd标签提取出来并写在play剧本中,示例如下:
    [root@localhost ansible]# vim https.yml
    ---
    - hosts: test_host
      remote_user: root
      tags:httpd
      tasks:
         - name: install httpd package
           tags: 
              - package
           yum:
             name=httpd
             state=latest
     
         - name: start up httpd service
           tags: ['service']
           service:
             name: httpd
             state: started
    

    需要注意:当tags写在play剧本中而非写在task任务中时,play中的所有task任务都会继续当前paly中的tags,就像上例中,两个任务都会继承httpds的tag标签,同时还拥有自己的tag标签。

    3)内置的特殊tags的用法
    上面已经介绍了5个内置的特殊的tags,每个都有其自身的用意。如下以always关键字的tags为例:如果把任务的tags值指定为always时,那么这个任务就总是被执行,除非使用"--skip-tags"选项明确指定不执行对应任务的tags标签。

    [root@localhost ansible]# cat haha.yaml 
    ---
    - hosts: test_host
      remote_user: root
      gather_facts: no
      tasks:
        - name: task1
          file: path=/opt/task1.txt state=touch
          tags: make_task1
        - name: task2
          file: path=/opt/task2.txt state=touch
          tags:
             - always
        - name: task3
          file: path=/opt/task2.txt src=/opt/task2.txt dest=/opt/heihei state=link force=yes
          tags: ['link_task3']
    
    执行1:如下,虽然tags指定了执行标签为make_task1的任务,但是由于任务2的标签有关键字always,所以任务2也会被执行,这就是always的作用!
    [root@localhost ansible]# ansible-playbook haha.yaml --tags=make_task1
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [task1] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    TASK [task2] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.234              : ok=2    changed=2    unreachable=0    failed=0  
    
    执行2: 只执行标签为always的任务
    [root@localhost ansible]# ansible-playbook haha.yaml --tags always
    或者
    [root@localhost ansible]# ansible-playbook haha.yaml --tags=always
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [task2] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.234              : ok=1    changed=1    unreachable=0    failed=0
    
    执行3: 跳过标签为always关键字的任务,这里明确指出跳过执行always标签。
    [root@localhost ansible]# ansible-playbook haha.yaml --skip-tags always    
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [task1] *************************************************************************************************************************************
    changed: [172.16.60.234]
    
    TASK [task3] *************************************************************************************************************************************
    ok: [172.16.60.234]
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.234              : ok=2    changed=1    unreachable=0    failed=0   
    
    其他四个特殊的tags标签在这里就不做示例说明了。特殊tags标签可以在ansible-playbook命令执行时直接使用。
    

    4)tags标签可以和role 结合使用

    [root@localhost ansible]# cat test.yml
    ---
    roles:
      - { role: webserver, port: 5000, tags: [ 'web', 'foo' ] }

    5)tags和include结合使用。

    [root@localhost ansible]# cat test.yml
    ---
    - include: kevin.yml tags=web,foo

    如上,对一个include任务打了两个tags标签,直接执行"ansible_playbook test.yml" 或 "ansible_playbook test.yml --tags=web" 或 "ansible_playbook test.yml --tags=foo" 命令则会将kevin.yml文件中所有task任务都执行。

    再来看看一个include结合tags的示例:通过指定标签(tags),来说明是安装tomcat7还是tomcat8

    tomcat.yml文件

    ---
    - include: install_tomcat7.yml
      tags: tomcat7
    - include: install_tomcat8.yml
      tags: tomcat8
    

    install_tomcat7.yml文件

    ---
    - name: "复制文件到远程主机"
      copy:
        src={{ item.src }}
        dest={{ item.dest }}
      with_items:
        - src: jdk-7u79-linux-x64.rpm
          dest: /usr/local/src/
        - src: java17.sh
          dest: /etc/profile.d/
    - name: "安装jdk"
      yum:
        name: /usr/local/src/jdk-7u79-linux-x64.rpm
        state: present
    - name: "重新加载环境变量"
      shell: "source /etc/profile.d/java17.sh"
    - name: "复制tomcat文件到远程服务器并解压"
      unarchive:
        src=apache-tomcat-7.0.64.zip
        dest=/data/
        copy=yes
        owner=staplesapp
        group=admin
    - name: "对解压后的文件重命名"
      shell: mv /data/apache-tomcat-7.0.64 /data/tomcat7
    - name: "对tomcat进行相关配置"
      shell: find /data/tomcat7/bin -name "*.sh" | xargs chmod +x
    - name: "启动tomcat"
      shell: 'nohup /data/tomcat7/bin/startup.sh &'
    

    install_tomcat8.yml文件

    ---
    - name: "复制文件到远程主机"
      copy:
        src={{ item.src }}
        dest={{ item.dest }}
      with_items:
        - src: jdk-8u111-linux-x64.rpm
          dest: /usr/local/src/
        - src: java18.sh
          dest: /etc/profile.d/
    - name: "安装jdk"
      yum:
        name: /usr/local/src/jdk-8u111-linux-x64.rpm
        state: present
    - name: "配置java环境变量"
      shell: "source /etc/profile.d/java18.sh"
    - name: "安装tomcat"
      unarchive:
          src=apache-tomcat-8.0.30.tar.gz
          dest=/data/
          copy=yes
          owner=staplesapp
          group=admin
    - name: "对解压后的文件重命名"
      shell: mv /data/apache-tomcat-8.0.30 /data/tomcat8
    - name: "启动tomcat"
      shell: 'nohup /data/tomcat8/bin/startup.sh &'
    

    下面开始执行命令:

    安装tomcat7:
    [root@localhost ansible]# ansible-playbook tomcat.yml --tags tomcat7
    
    安装tomcat8:
    [root@localhost ansible]# ansible-playbook tomcat.yml --tags tomcat8
    

    这里需要特别注意:
    在之前ansible版本中使用include 整合多个roles至统一入口结合tags标签来管理roles剧本,但在ansible2.8版本之后将会删除include语法,更改为import_playbook。如果还使用include语法也可以,只不过ansible-playbook执行结果中会有告警信息:"DEPRECATION WARNING]:'include' for playbook includes. You should use 'import_playbook' instead. This feature will be removed in version 2.8. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg."。所以,最好将上面tomcat.yml文件中的include语法改成import_playbook,如下:

    [root@localhost ansible]# cat tomcat.yml
    ---
    - import_playbook: install_tomcat7.yml
      tags: tomcat7
    - import_playbook: install_tomcat8.yml
      tags: tomcat8

    3.  include用法
    如果想在playbook中重复使用任务列表,则可以使用include文件来执行此操作。 使用include的任务列表是定义系统将要实现的角色的好方法。主要清楚:ansible2.8版本之后include语法变成了import_playbook。如果还是使用include,则不会影响执行结果,只不过是有告警信息。ansible也可以将变量传递给include。示例如下:

    示例1: 通过Include,可以在playbook中引用另一个playbook或者tasks
    ==============================================================
    [root@localhost ansible]# cat install_MysqlAndPhp.yml
    - yum:
        name: mysql
        state: present
    - yum:
        name: php-fpm
        state: present
    
    [root@localhost ansible]# cat lamp.yml
    ---
    - hosts: test_host
      remote_user: root
      gather_facts: no
      tasks:
      - include: install_MysqlAndPhp.yml
      - yum:
          name: httpd
          state: present
     
    [root@localhost ansible]# cat lnmp.yml
    ---
    - hosts: test_host
      remote_user: root
      gather_facts: no
      tasks:
        - include: install_MysqlAndPhp.yml
        - yum:
            name: nginx
            state: present
    
    
    示例2: 可以在handler中引用include
    ==============================================================
    [root@localhost ansible]# cat test_include.yml
    ---
    - hosts: test_host
      remote_user: root
      gather_facts: no
      tasks:
        - file:
            path: /opt/ttt
            state: touch
          notify: test include handlers
     
      handlers:
        - name: test include handlers
          include: include_handler.yml
     
    [root@localhost ansible]# cat include_handler.yml
    - debug:
        msg: "task1 of handlers"
    - debug:
        msg: "task2 of handlers"
    - debug:
        msg: "task3 of handlers"
    
    示例3: when在include中使用
    ==============================================================
    [root@localhost ansible]# cat /etc/ansible/hosts
    [db]
    192.168.24.10
    [app]
    192.168.24.11
    
    [root@localhost ansible]# cat install_client.yml
    ---
    - hosts: '` hosts `'
      user: ansible
      sudo: yes
      sudo_user:root
      roles:
        - install_client
    
    [root@localhost ansible]# cat roles/install_client/tasks/main.yml  
    ---
    - include: db.yml
      when: "hosts == 'db'"
    - include: app.yml
      when: "hosts == 'app'"
    
    [root@localhost ansible]# cat roles/install_client/tasks/db.yml
    ---
      - name: Touchdb file
        shell: touch /tmp/db.txt
    
    [root@localhost ansible]# cat roles/install_client/tasks/app.yml
    ---
      - name: Touchdb file
        shell: touch /tmp/db.txt
    
    执行命令:
    [root@localhost ansible]# ansible-playbook -i hosts install_client.yml --extra-vars "hosts=db"
    [root@localhost ansible]# ansible-playbook -i hosts install_client.yml --extra-vars "hosts=app"
    
    示例4: 可以在include中使用tags标签,这个在上面已经介绍过了
    ==============================================================
    

    4.  role用法
    角色(roles)是ansible自1.2版本开始引入的新特性,用于层次性,结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用include指令即可。简单的说,roles就是通过分别将变量、文件、任务、模块及处理器放置于单独的目录中、并可以便捷地include他们的一种机制。角色一般用于基于主机构建服务的场景中、但也可以是用于构建守护进程等场景中。role主要作用是重用playbook,例如无论安装什么软件都会安装时间同步服务,那么每个playbook都要编写ntp task,可以将ntp task写好,等到用的时候再调用就行了。ansible中将其组织成role,它有着固定的组织格式,以便playbook调用。

    4.1  role层级目录结构
    role以特定的层级目录结构进行组织的tasks、variables、handlers、templates、files等;相当于函数的调用把各个功能切割成片段来执行。

    roles/
        role_name/:定义的role的名字
        file/:     用于存放copy或script等模块调用的函数
        tasks/:     用于定义各种task,此目录一定要有main.yml;其他文件需要main.yml包含调用
        handlers/: 用于定义各种handlers,此目录一定要有main.yml;其他文件需要main.yml包含调用
        vars/:      用于定义variables,此目录一定要有main.yml;其他文件需要main.yml包含调用
        templates/:存储由template模块调用的模板文本;
        meta/:     定义当前角色的特殊设定及其依赖关系,此目录中至少应该有一个名为main.yml的文件;其它的文件需要由main.yml进行"包含"调用;
        default/:  此目录中至少应该有一个名为main.yml的文件,用于设定默认变量;
    
    [root@localhost ansible]# ll roles/
    total 40
    drwkebor-kebor-kebo 8 root root 4096 Jul 29 22:13 web_Deploy
    drwkebor-kebor-kebo 8 root root 4096 May  7  2019 web_Deploy_af
    
    [root@localhost ansible]# ll roles/web_Deploy                   
    total 25
    -rw-r--r-- 1 root root   45 May  7  2019 web_Deploy.yml
    drwkebor-kebor-kebo 2 root root 4096 Jul 10 19:09 defaults
    drwkebor-kebor-kebo 2 root root 4096 May  7  2019 handlers
    drwkebor-kebor-kebo 2 root root 4096 May  7  2019 meta
    drwkebor-kebor-kebo 2 root root 4096 Dec 26 19:42 tasks
    drwkebor-kebor-kebo 2 root root 4096 May  7  2019 templates
    drwkebor-kebor-kebo 2 root root 4096 May  7  2019 vars
    
    [root@localhost ansible]# ll roles/web_Deploy/tasks/
    total 35
    -rwkebor-kebor-kebo 1 root root 1542 Jun 24  2019 Auth.yml
    -rwkebor-kebor-kebo 1 root root 1482 Oct 11 16:13 StartService.yml
    -rwkebor-kebor-kebo 1 root root  963 Jun 18  2019 main.yml
    -rwkebor-kebor-kebo 1 root root 1415 May  7  2019 StopService.yml
    
    [root@localhost ansible]# cat roles/web_Deploy/tasks/main.yml 
    ---
    - include_tasks: Auth.yml
      tags: userauth
      
    - include_tasks: StopService.yml 
      tags: stopservice
    - include_tasks: StartService.yml 
      tags: startservice 
    
    [root@localhost ansible]# cat roles/web_Deploy/web_Deploy.yml 
    ---
    - hosts: all
      roles:
        - web_Deploy
    
    ===================================================================================
    再如下一个项目的role目录结构:
    site.yml
    webservers.yml
    fooservers.yml
    roles/
       common/
         files/
         templates/
         tasks/
         handlers/
         vars/
         defaults/
         meta/
       webservers/
         files/
         templates/
         tasks/
         handlers/
         vars/
         defaults/
         meta/
    
    
    再看下目录解释:
    yml文件:用于定义此角色用到的各handler:在handler中使用include包含的其他的handler文件也应该位于此目录中;
    files目录:存放由copy或script等模块调用的文件;
    templates目录:templates模块会自动在此目录中寻找Jinja2模板文件;
    tasks目录:至少应该包含一个名为main.yml的文件,其定义了此角色的任务列表;此文件可以使用include包含其他的位于此目录中的task文件;
    handlers目录:此目录中应当包含一个main;
    vars目录:应当包含一个main.yml文件,用于定义此角色用到的变量;
    meta目录:应当包含一个main.yml文件,用于定义此角色的特殊设定及其依赖关系;ansible 1.3及其以后的版本才支持
    default目录:为当前角色设定默认变量时使用此目录;应当包含一个main.yml文件;
    
    
    那么一个playbook就可以这样写:
    ---
     - hosts: webservers
      roles:
         - common
         - webservers
    
    这个playbook为一个角色"kebo"指定了如下的行为:
    如果 roles/kebo/tasks/main.yml 存在, 其中列出的tasks将被添加到play中
    如果roles/kebo/handlers/main.yml 存在, 其中列出的handlers将被添加到play中
    如果roles/kebo/vars/main.yml 存在, 其中列出的variables将被添加到play中
    如果roles/kebo/meta/main.yml 存在, 其中列出的 "角色依赖"将被添加到roles列表中 (1.3 andlater)
    所有 copy tasks 可以引用 roles/kebo/files/ 中的文件,不需要指明文件的路径。
    所有 scripttasks 可以引用 roles/kebo/files/ 中的脚本,不需要指明文件的路径。
    所有 template tasks 可以引用roles/kebo/templates/ 中的文件,不需要指明文件的路径。
    所有 include tasks 可以引用roles/kebo/tasks/ 中的文件,不需要指明文件的路径。
    
    如果roles目录下有文件不存在,这些文件将被忽略。比如 roles目录下面缺少了"vars/"目录,这也没关系。
    
    需要注意:
    仍然可以在playbook中松散地列出tasks,vars_files 以及 handlers,这种方式仍然可用,但是roles是一种很好的具有组织性的功能特性,强烈建议使用它。
    如果在playbook中同时使用roles和tasks,vars_files 或者 handlers,roles 将优先执行。
    
    而且也可以使用参数化的roles,这种方式通过添加变量来实现,比如:
    --
    - hosts: webservers
      roles:
        - common
        - { role: foo_app_instance, dir: '/opt/a',  port: 5000 }
        - { role: foo_app_instance, dir: '/opt/b',  port: 5001 }
    
    当一些事情不需要频繁去做时,也可以为 roles 设置触发条件,像这样:
    ---
    - hosts: webservers
      roles:
        - { role: some_role, when: "ansible_os_family == 'RedHat'" }
    它的工作方式是:将条件子句应用到 role 中的每一个 task 上。
    
    也可以给role分配指定的标签,比如:
    ---
    - hosts: webservers
      roles:
        - { role: foo, tags: ["bar", "baz"] }
    
    如果play仍然包含有 "tasks" section,这些 tasks 将在所有 roles 应用完成之后才被执行。也可定义一些tasks,让它们在roles之前以及之后执行,可以这样做:
    ---
    - hosts: webservers
      pre_tasks:
        - shell: echo 'hello'
      roles:
        - { role: some_role }
      tasks:
        - shell: echo 'still busy'
      post_tasks:
        - shell: echo 'goodbye'
    
    注意:
    pre_tasks: 执行正式 task 之前执行的任务
    post_tasks:最后需要执行的任务

    4.2  在playbook中调用role

    role存放的路径在配置文件/etc/ansible/ansible.cfg中定义。如下,发现ansible的roles目录定义到/root/app/script/ansible/roles路径下了!!
    [root@localhost ansible]# cat /etc/ansible/ansible.cfg |grep roles_path
    roles_path    = /etc/ansible/roles:/root/app/script/ansible/roles
    
    在playbook中调用role的方式有三种,如下:
    第一种:
    - hosts: HOSTS
      remote_user: root
      roles:
        - ROLE_NAME1
        - ROLE_NAME2
    
    第二种:除了字典第一个元素指明调用的role,后面是传递给role的变量
    - hosts: HOSTS
      remote_user: root
      roles:
      - { role: ROLE_NAME1, VARIABLE1: VALUE1, ... }
    
    第三种:when指明role调用的条件
    - hosts: HOSTS
      remote_user: root
      roles:
      - { role: ROLE_NAME1, when: CONDITIONS }
    

    4.3  调用role示例

    0) 先来看看role的路径定义
    [root@localhost ansible]# cat /etc/ansible/ansible.cfg|grep roles_path
    roles_path    = /etc/ansible/roles:/etc/ansible/roles
    
    1)目录结构
    [root@localhost ansible]# tree /etc/ansible/roles
    /etc/ansible/roles
    └── httpd                   #palybook调用时role的名称
        ├── defaults
        ├── files
        └── handlers
        │   └── main.yml       #所有的目录文件,并不一定要有,用时才创建
        └── mata
        └── tasks
        │   └── main.yml
        └── tamplates
            └── httpd.conf.c6.j2  #centos6,centos7的配置文件
            └── httpd.conf.c7.j2
    
    2)tasks文件
    [root@localhost ansible]# cat /etc/ansible/roles/httpd/tasks/main.yml
    - name: install httpd package
      yum: name=httpd state=present
    - name: install configure file
      template: src=httpd.conf.c{{ ansible_distribution_major_version }}.j2 dest=/etc/httpd/conf/httpd.conf
      tags: instconf
      notify: restart httpd service
    - name: start httpd service
      service: name=httpd state=started enabled=true
    
    3) handlers文件
    [root@localhost ansible]# cat /etc/ansible/roles/httpd/handlers/main.yml
    - name: restart httpd service
      service: name=httpd state=restarted
    
    4) 模板文件
    [root@localhost ansible]# grep ^Listen /etc/ansible/roles/httpd/templates/httpd.conf.c6.j2
    Lister {{ httpd_port }}
    
    5) 变量
    [root@localhost ansible]# cat /etc/ansible/roles/httpd/vars/main.yml
    httpd_port: 8088
    
    6) playbook文件
    [root@localhost ansible]# cat /etc/ansible/httpd_conf.yml
    ---
    - hosts: webservers
      remote_user: root
      roles:
      - { role: httpd }
    
    7) 执行playbook文件,并查看httpd端口
    [root@localhost ansible]# ansible-playbook -i /etc/ansible/hosts /etc/ansible/httpd_conf.yml
    [root@localhost ansible]# ansible -i /etc/ansible/hosts webservers -m shell -a "ss -tnlp|grep :80"
    
    ========================================================================================================
    再来看一例:
    1.group: 创建用户组nginx
    2.user: 创建用户nginx
    3.yum: 安装nginx
    4.template: 配置文件更新nginx.conf
    5.service: 启动nginx
    
    [root@localhost ~]# cat /etc/ansible/ansible.cfg|grep roles_path
    roles_path    = /etc/ansible/roles:/root/ansible/roles
    
    [root@localhost ~]# cd /root/ansible/roles/nginx
    [root@localhost nginx]# mkdir tasks templates
    [root@localhost nginx]# cd tasks
    
    [root@localhost tasks]# vim group.yml
    - name: create group nginx
        group: name=nginx gid=80
    
    [root@localhost tasks]# vim user.yml
    -name: create user nginx
        user: name=nginx uid=80 group=nginx system=yes shell=/sbi/nologin
    
    [root@localhost tasks]# vim install.yml
    - name: install package
        yum: name=nginx
       
    [root@localhost tasks]# vim start.yml
    - name: start service
        service: name=nginx state=started enabled=yes
        
    [root@localhost tasks]# vim restart.yml
    - name: restart service
        service: name=nginx state=restarted
        
    [root@localhost tasks]# vim templ.yml
    - name: copy conf
        template: src=nginx.conf.j2 dest=/etc/nginx/conf/nginx.conf
        
    [root@localhost tasks]# vim main.yml
    - include: group.yml
    - include: user.yml
    - include: install.yml
    - include: templ.yml
    - include: start.yml
          
    [root@localhost tasks]# cd ../templates  && ls
    nginx.conf.j2
    
    [root@localhost tasks]# cd /root/ansible
    [root@localhost ansible]# vim nginx_role.yml
    - hosts: websrvs
      remote_user: root
      roles:
        - role: nginx
    
    执行命令:
    [root@localhost ansible]# ansible-playbook nginx_role.yml
    

    5. loop列表循环用法
    在ansible 2.5版本之前,大多数人习惯使用"with_X"风格的关键字操作循环,从ansible 2.6版本开始,官方开始推荐使用"loop"关键字代替"with_X"风格关键字。下面通过一些小示例来说明使用loop关键字进行的列表循环操作。[loop、with_items、with_list 三者等同,效果是一样的!]。ansible的循环使用,可以参考下面"循环变量"以及参考这里

    playbook中的变量设置

    ########   变量的优先级   ########
    1.  extra vars变量(在命令行中使用 -e);优先级最高
    2.  在inventory中定义的连接变量(比如ansible_ssh_user);优先级第二
    3.  大多数的其他变量(命令行转换,play中的变量,include的变量,role的变量等);优先级第三
    4.  在inventory定义的其他变量;优先级第四
    5.  有系统发现的facts;优先级第五
    6.  "role默认变量",这个是最默认的值,很容易丧失优先权。优先级最小。

    另外:在inventory清单列表里定义的变量:单个主机定义的变量优先级高于主机组定义的变量
    经过实验,ansible使用inventory定义变量的优先级顺序从高到低为:
    1. host_vars下定义变量
    2. inventory中单个主机定义变量
    3. group_vars下定义变量
    4. inventory中组定义变量

    playbook中定义变量,如下:

    - hosts: webservers
      vars:
        http_port: 80
    

    YAML陷阱
    YAML语法要求如果值以{{ foo }}开头的话,那么就需要将整行用双引号包起来,这是为了确认你不是想声明一个YAML字典。
    如下面配置是不行的!!!

    ---
    - hosts: app_servers
      vars:
        app_path: {{ base_path }}/data/web
    

    应该改成下面这样:

    ---
    - hosts: app_servers
      vars:
        app_path: "{{ base_path }}/data/web"
    

    ########   Ansbile-playbook变量配置方法   ########

    1.  在inventory主机清单文件中定义变量
    可以直接定义在主机清单文件/etc/ansible/hosts中,表明该变量只对对应的主机或者组有效,对其余的主机和组无效。

    有针对单个主机定义变量和组定义变量两种方式。
    1)向不同的单个主机传递不同的变量;
    IP/HOSTNAME  var1=value1 var2=value2
    
    2)向组中的主机传递相同的变量;
    [groupname:vars]
    var1=value1
    var2=value2
    
    但是注意:
    组定义变量的作用范围是组下的所有主机。
    当两种定义方式同时存在时,ansible会优先采用单个主机定义的变量值!
    
    [root@ss-server ansible]# pwd
    /etc/ansible
    [root@ss-server ansible]# cat hosts|egrep -v "^#|^$"
    [kevin]
    172.16.60.20 key=20180101
    172.16.60.22 ansible_ssh_port=22288 key="niubility"
     
    [root@ss-server ansible]# cat ansi.yml
    ---
    - hosts: kevin
      gather_facts: False
      tasks:
        - name: haha
          debug: msg="the {{ inventory_hostname }} value is {{ key }}"
     
    执行结果(注意inventory_hostname代表inventory列表列表里被控节点的主机名):
    [root@ss-server ansible]# ansible-playbook ansi.yml
     
    PLAY [kevin] *************************************************************************************************************************************
     
    TASK [haha] **************************************************************************************************************************************
    ok: [172.16.60.20] => {
        "msg": "the 172.16.60.20 value is 20180101"
    }
    ok: [172.16.60.22] => {
        "msg": "the 172.16.60.22 value is niubility"
    }
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20             : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
    172.16.60.22             : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
    
    再看下面一示例
    [root@ss-server ansible]# tail -n 10 /etc/ansible/hosts
    [webserver]
    172.16.60.51 dir=/root/node2
    172.16.60.80 dir=/root/node1
    
    [node1]
    172.16.60.80
    
    [webserver:vars]
    file=hostname.txt
    
    [root@ss-server ansible]# cat playbook.yml
    ---
    - hosts: webserver
      remote_user: root
      tasks:
        - name: Create New Folder
          file: name={{ dir }} state=directory
        - name: Create New Folder
          shell: echo `hostname` > {{dir}}/{{ file }}
    
    执行结果:
    [root@ss-server ansible]# ansible-playbook playbook.yml    
    
    PLAY [webserver] ************************************************************************
    
    TASK [Gathering Facts] ***************************************************************
    ok: [172.16.60.80]
    ok: [172.16.60.51]
    
    TASK [Create New Folder] *************************************************************
    changed: [172.16.60.51]
    changed: [172.16.60.80]
    
    TASK [Create New Folder] *************************************************************
    changed: [172.16.60.51]
    changed: [172.16.60.80]
    
    PLAY RECAP ***************************************************************************
    172.16.60.51              : ok=3    changed=2    unreachable=0    failed=0   
    172.16.60.80              : ok=3    changed=2    unreachable=0    failed=0 
    

    此外:ansible还内置了一些固定的主机变量名,在inventory中定义其值, 在另一篇文章中介绍过。

    2.  通过host_vars和group_vars目录来定义变量
    /etc/ansible/目录是linux系统上ansible默认的配置文件目录(Mac系统上的话,其默认配置目录是在/usr/local/etc/ansible/),在该目录下创建host_vars和group_vars两个目录用来存放定义变量的文件。

    1)针对单个主机的变量  
    [root@ss-server ansible]# pwd
    /etc/ansible
    [root@ss-server ansible]# cat host_vars/172.16.60.20
    ---
    user: root
    pass: root@123
    
    2)针对test组的变量 
    [root@ss-server ansible]# cat group_vars/kevin
    ---
    user: work
    pass: work@123
    
    ############ 在inventory清单列表文件里,单个主机定义的变量优先级高于主机组定义的变量 ############
    ############ 经过实验,ansible使用变量的优先级顺序从高到低为 #############
    1. host_vars下定义变量
    2. inventory中单个主机定义变量
    3. group_vars下定义变量
    4. inventory中组定义变量
    
    示例如下:
    [root@ss-server ~]# cat /root/ansible/hosts
    [kevin]
    172.16.60.20 ansible_ssh_port=22222
    172.16.60.21 ansible_ssh_port=22288
    
    [root@ss-server ~]# cat /root/ansible/group_vars/kevin
    key=20191010
    
    [root@ss-server ~]# cat /root/ansible/bo.yml
    ---
    - hosts: kevin
      remote_user: root
      tasks:
        - debug: msg='The key is {{key}} '
    
    [root@ss-server ~]# ansible-playbook /root/ansible/bo.yml
     
    PLAY [kevin] *************************************************************************************************************************************
     
    TASK [haha] **************************************************************************************************************************************
    ok: [172.16.60.20] => {
        "msg": "The key is 20191010"
    }
    ok: [172.16.60.22] => {
        "msg": "The key is 20191010"
    }
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20             : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  
    172.16.60.22             : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
    

    3.  通过var_files定义变量

    [root@ss-server ansible]# cat laoji.yml
    ---
    key: jiayou
    
    [root@ss-server ansible]# cat bo.yml
    ---
    - hosts: kevin
      gather_facts: False
      vars_files:
          - laoji.yml
      tasks:
        - name: display
          debug: msg="the {{ inventory_hostname }} valus is {{ key }}"
     
    执行结果:
    [root@ss-server ansible]# ansible-playbook bo.yml
    PALY [kevin] ****************************************************
     
    TASK [display] ****************************************************
    ok: [172.16.60.20] => {
        "changed": false
        "msg":"the 172.16.60.20 value is jiayou"
    }
     
    PLAY RECAP ****************************************************
    172.16.60.20         : ok=1  changed=0  unreachable=0  failed=0

    4.  通过vars_prompt交互式传入变量
    在playbook中定义vars_prompt的变量名和交互式提示信息,就可以实现在运行playbook时,通过交互的传入变量值。
    private字段:用来定义交互时是否回显输入的值,默认private为yes;
    default字段:用来定义变量的默认值。

    [root@ss-server ansible]#  cat prom.yml
    ---
    - hosts: kevin
      remote_user: root
      vars_prompt:
          - name: "var1"
            prompt: "please input you name"
            private: no
          - name: "var2"
            prompt: "please input you age"
            private: yes
            default: 'kevin var'
      tasks:
          - name: display var1
            debug: msg="your name of var1 is {{ var1 }}"
          - name: display var2
            debug: msg="you age of var2 is {{ var2 }}"
     
    执行结果:
    [root@ss-server ansible]# ansible-playbook prom.yml
    your name of var1: wangzhaojun       #把输入的内容传递给变量var1。输入的值显示出来!!
    you age of var2 [kevin var]:         #把输入的内容传递给默认变量kevin var。但是输入的值不显示出来!! 比如这里输入的30
     
    PALY [kevin] ****************************************************
     
    TASK [Gathering Facts] ****************************************************
    OK: [172.16.60.20]
     
    TASK [display var1] ****************************************************
    ok: [172.16.60.20] =>{
        "msg": "youn name of var1 is wangzhaojun"
    }
     
    TASK [display var2] ****************************************************
    ok: [172.16.60.20] =>{
        "msg": "youn name of var2 is 30"
    }
     
    PLAY RECAP ****************************************************
    172.16.60.20         : ok=3  changed=0  unreachable=0  failed=0
     
    接下来再来看一个"ansible 中prompt 交互变量的使用"的示例
    [root@ss-server ansible]# cat haha.yml
    ---
    - hosts: kevin
      remote_user: root
      vars_prompt:
         - name: "your_name"
           prompt: "what is your name"
           private: no
         - name: "your_age"
           prompt: "how old are you"
           private: no
         - name: "solution"
           prompt: "Choose the solution you want 
    
           A: solutionA
    
           B: solutionB
    
           C: solutionC
    "
           private: no
           default: A
         - name: "user_name"
           prompt: "Enter user name"
           private: no
         - name: "user_password"
           prompt: "Enter user password"
           private: no
           encrypt: "sha512_crypt"
           confirm: yes
      tasks:
         - name: "output vars"
           debug: msg="your name is {{ your_name }},you are {{ your_age }} years old"
         - name: "output solution"
           debug: msg="the final solution is {{solution}}"
         - name: "create_user"
           user:
             name: "{{user_name}}"
             password: "{{user_password}}"
         - name: "debug_create user"
           debug: msg="create user {{user_name}} in"
     
    执行结果为:
    [root@ss-server ansible]# ansible-playbook haha.yml
    what is your name: wangshibo
    how old are you: 29
    Choose the solution you want
     A: solutionA
     B: solutionB
     C: solutionC
     [A]: C
    Enter user name: bobo
    Enter user password: bobo123
    confirm Enter user password: bobo123
     
    PLAY [kevin] *************************************************************************************************************************************
     
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.20]
     
    TASK [output vars] *******************************************************************************************************************************
    ok: [172.16.60.20] => {
        "msg": "your name is wangshibo,you are 29 years old"
    }
     
    TASK [output solution] ***************************************************************************************************************************
    ok: [172.16.60.20] => {
        "msg": "the final solution is C"
    }
     
    TASK [create_user] *******************************************************************************************************************************
    changed: [172.16.60.20]
     
    TASK [debug_create user] *************************************************************************************************************************
    ok: [172.16.60.20] => {
        "msg": "create user bobo in"
    }
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20                  : ok=5    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    

    5.  通过ansible-playbook命令行定义变量!即参数传入变量
    除了"vars_prompt"和"vars_files",也可以通过Ansible命令行发送变量。如果想要编写一个通用的发布playbook时则特别有用!你可以传递应用的版本以便部署。例如下面命令(注意: --extra-vars 相等于 -e)

    [root@ss-server ~]# ansible-playbook release.yml --extra-vars "version=1.78.34 other_variable=fos"
     
    其他场景中,通过参数传入变量也是很有用的。比如为playbook设置主机群组或用户。如下:
    [root@ss-server ~]# vim exap.yml
    ---
    - hosts: '{{hosts}}'
      remote_user: '{{user}}'
      tasks:
        - name: "一个测试"
          debug: msg="your hosts is {{hosts}}, user is {{user}}"
     
    执行的时候,通过参数传入变量(-e)。变量{{hosts}}可以是主机群组名称,也可以是主机ip。
    [root@ss-server ansible]# ansible-playbook exap.yml -e "hosts=kevin user=root"   
    [WARNING]: Found variable using reserved name: hosts
     
     
    PLAY [kevin] *************************************************************************************************************************************
     
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.20]
     
    TASK [一个测试] **************************************************************************************************************************************
    ok: [172.16.60.20] => {
        "msg": "your hosts is kevin, user is root"
    }
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
     
    在命令行里面传值的方法:
    [root@ss-server ansible]# ansible-playbook amp.yml --extra-vars "hosts=webserver user=root"
     
    还可以使用json格式传递参数:
    [root@ss-server ansible]# ansible-playbook amp.yml --extra-vars "{'hosts':'webserver', 'user':'root'}"
     
    还可以将参数放在文件里面进行传递(注意命令行里要是用"@文件名"):
    [root@ss-server ansible]# ansible-playbook amp.yml --extra-vars "@anhui.yml"
    [root@ss-server ansible]# cat anhui.yml
    ---
    hosts: webserver
    user: root
    
    例如下面这个添加用户的plakbook剧本配置,用传递了json文件:
    [root@ss-server ansible]# cat useradd.yml
    ---
    - hosts: "{{ host }}"
        gather_facts: no
        remote_user: root
        vars:
          UserName: "{{ user }}"
          UserPassword: "{{ pass }}"
        tasks:
          - name: create new user {{ UserName }}
            user: name={{ UserName }} shell=/bin/bash  password={{ UserPassword |password_hash('sha512') }} update_password=always append=yes
          - name: Config /etc/sudoers
            lineinfile: dest=/etc/sudoers state=present  line='{{ item }}' validate='visudo -cf %s'
            with_items:
              - "{{ user }} ALL=(ALL) NOPASSWD:ALL,!/bin/rm,!/bin/su,!/usr/bin/passwd,!/usr/sbin/visudo,!/sbin/shutdown,!/sbin/reboot,!/sbin/halt"
              - "Defaults: {{ user }}  !requiretty"
    
    [root@ss-server ansible]# ansible-playbook useradd.yml -e "host=172.16.60.20 user=kevin_bo pass=kevin@bo123"
    
    在上例中,变量pass是密码,如果变量pass里有特殊的字符,或者变量pass是一串数组的话,它将被转义。若不想被转义,可以使用如下方法:
    [root@ss-server ansible]# cat user.json
    host: webserver                     #ansible里面定义的主机组或者直接配置主机ip,如172.16.60.20
    user: kevin_bo                      #添加的用户名
    pass: 'Fxx6unM$R%I$Jna&'            #添加的用户的密码,可以用百度上随机密码生成器生成
    
    指定user,json文件执行剧本 (使用JSON格式的文件即可,-e 传递变量,优先级最高) 
    [root@ss-server ansible]# ansible-playbook useradd.yml -e "@user.json"        
    
    删除web组中所有用户
    [root@ss-server ansible]# ansible webserver -m user -a 'name=zhangsan state=absent remove=yes'
    

    6.  在playbook剧本中定义变量

    在playbook中定义变量需要用到Ansible的vars模块,可以将所有需要用到的变量统一在vars模块下定义,定义格式需要遵循YAML语言格式:
    vars:
      - var1: value1
      - var2: value2
      - var3: value3
      - ....: .....
    
    示例如下:
    [root@ss-server ansible]# cat playbook.yml
    ---
    - hosts: kevin
      remote_user: root
      vars:
        - dir1: /root/Ansible
        - dir2: /root/Ansible/test1
        - dir3: /root/Ansible/test2
      tasks:
        - name: Create New Folder
          file: name={{ dir1 }} state=directory
        - name: Create New Folder
          file: name={{ dir2 }} state=directory
        - name: Create New Folder
          file: name={{ dir3 }} state=directory
    
    [root@ss-server ansible]# ansible-playbook playbook.yml    
    
    PLAY [kevin] *************************************************************************
    
    TASK [Gathering Facts] ***************************************************************
    ok: [172.16.60.20]
    
    TASK [Create New Folder] *************************************************************
    changed: [172.16.60.20]
    
    TASK [Create New Folder] *************************************************************
    changed: [172.16.60.20]
    
    TASK [Create New Folder] *************************************************************
    changed: [172.16.60.20]
    
    PLAY RECAP ***************************************************************************
    [172.16.60.20]              : ok=4    changed=3    unreachable=0    failed=0 

    7.  通过roles角色定义变量

    在Ansible的roles中定义变量,需要将变量及值的键值对形式写到roles的vars目录下的main.yml文件中,同样适用YAML语言格式,格式如下:
    var1: value1
    var2: value2
    var3: value3
    
    但是请注意:
    通过Roles定义的变量只适用于当前roles。如下是roles文件组织结构:
    [root@ss-server roles]# tree test/                  
    test/
    ├── files
    ├── handlers
    ├── playbook.retry
    ├── playbook.yml
    ├── tasks
    │   └── main.yml
    ├── templates
    └── vars
        └── main.yml
    5 directories, 4 files
    
    [root@ss-server roles]# cat test/tasks/main.yml         #任务文件
    - name: Get IP Address
      shell: echo `{{ cmd }}` >> {{ dir }}/{{ file }}
    
    [root@ss-server roles]# cat test/vars/main.yml          #定义变量cmd
    cmd: hostname -I
    
    [root@ss-server roles]# cat test/playbook.yml
    ---
    - hosts: webserver
      remote_user: root
      roles:
        - test
    
    hosts清单列表里定义的变量,适用于所有roles。
    [root@ss-server roles]# cat /etc/ansible/hosts
    [webserver]
    172.16.60.51 dir=/root/node2
    172.16.60.80 dir=/root/node1
    
    [node1]
    172.16.60.80
    
    [webserver:vars]
    file=hostname.txt
    
    playbook调用Roles,执行结果为:
    [root@Centos7T test]#ansible-playbook playbook.yml    
    
    PLAY [websvr] ************************************************************************
    
    TASK [Gathering Facts] ***************************************************************
    ok: [172.16.60.80]
    ok: [172.16.60.51]
    
    TASK [test : Get IP Address] *********************************************************
    changed: [172.16.60.51]
    changed: [172.16.60.80]
    
    PLAY RECAP ***************************************************************************
    172.16.60.51              : ok=2    changed=1    unreachable=0    failed=0   
    172.16.60.80              : ok=2    changed=1    unreachable=0    failed=0 

    8.  使用Facts获取的信息
    还有其它地方可以获取变量, 这些变量是自动发现的,而不是用户自己设置的。Facts通过访问远程系统获取相应的信息,一个很好的例子就是远程主机的IP地址或者操作系统是什么。

    使用以下命令可以查看哪些信息是可用的(kevin是上面在/etc/ansible/hosts列表文件中配置的主机群组):
    [root@ss-server ansible]# ansible kevin -m setup
     
    [root@ss-server ansible]# ansible kevin -m setup|grep "ansible_python_version"
            "ansible_python_version": "2.7.5",
    可以在playbook中这样引用上面被控制主机的python版本: {{ ansible_python_version }}
     
    [root@ss-server ansible]# ansible kevin -m setup|grep "ansible_nodename"
            "ansible_nodename": "ss-server",
    可以在playbook中这样引用上面被控制主机的主机名: {{ ansible_nodename }}
     
    [root@ss-server ansible]# ansible kevin -m setup|grep "ansible_hostname"
            "ansible_hostname": "ss-server",
    被控制主机的主机名变量还可以是: {{ ansible_hostname }}
    
    访问复杂变量数据
    有些提供的facts, 比如网络信息等, 是一个嵌套的数据结构。访问它们使用简单的 {{ foo }} 语法并不够用,
    但是也仍然很容易.如下所示:
    {{ ansible_eth0["ipv4"]["address"] }}
    
    或者这样写:
    {{ ansible_eth0.ipv4.address }}
     
    ############ 如果关闭Facts,可以大大提高ansible的执行速度 ###########
    关闭方法如下:
    [root@ss-server ansible]# cat anhui.yml
    ---
    - hosts: kevin
      gather_facts: no
    

    9.  register注册变量
    变量的另一个主要用途是在运行命令时,把命令结果存储到一个变量中,不同模块的执行结果是不同的。运行playbook时使用-v选项可以看到可能的结果值,ansible执行任务的结果值可以保存在变量中,以便稍后使用它。register方式主要用于在task之间传递变量。

    示例如下:
    [root@ss-server ansible]# cat /etc/ansible/hosts
    [kevin]
    172.16.60.237
    172.16.60.238
     
    [root@ss-server ansible]# cat /root/register.yml
    ---
    - hosts: kevin
      remote_user: root
      tasks:
          - name: register bo_test
            shell: hostname -I
            register: info
          - name: display info
            debug: msg="this host ip is {{ info }}"
     
    [root@ss-server ansible]# ansible-playbook /root/register.yml
     
    PLAY [kevin] *************************************************************************************************************************************
     
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.238]
    ok: [172.16.60.237]
     
    TASK [register bo_test] **************************************************************************************************************************
    changed: [172.16.60.238]
    changed: [172.16.60.237]
     
    TASK [display info] ******************************************************************************************************************************
    ok: [172.16.60.237] => {
        "msg": "this host ip is {'stderr_lines': [], u'changed': True, u'end': u'2019-12-15 22:07:18.431549', 'failed': False, u'stdout': u'172.16.60.237 ', u'cmd': u'hostname -I', u'rc': 0, u'start': u'2019-12-15 22:07:18.408235', u'stderr': u'', u'delta': u'0:00:00.023314', 'stdout_lines': [u'172.16.60.237 ']}"
    }
    ok: [172.16.60.238] => {
        "msg": "this host ip is {'stderr_lines': [], u'changed': True, u'end': u'2019-12-15 22:07:18.430108', 'failed': False, u'stdout': u'172.16.60.238 ', u'cmd': u'hostname -I', u'rc': 0, u'start': u'2019-12-15 22:07:18.407184', u'stderr': u'', u'delta': u'0:00:00.022924', 'stdout_lines': [u'172.16.60.238 ']}"
    }
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.237              : ok=3    changed=1    unreachable=0    failed=0  
    172.16.60.238              : ok=3    changed=1    unreachable=0    failed=0
     
    这里注意下:
    register定义的info变量在第二个task中用来查看前一个task中执行的hostname命令的结果。
    可以看到playbook运行后的结果中,info返回的是一段python字典数据,如果只想看到stdout部分的信息的话,可以通过info[‘stdout’]来引用。
     
    [root@ss-server ansible]# cat /root/register.yml
    ---
    - hosts: kevin
      remote_user: root
      tasks:
          - name: register bo_test
            shell: hostname -I
            register: info
          - name: display info
            debug: msg="this host ip is {{ info['stdout'] }}"
     
    [root@ss-server ansible]# ansible-playbook /root/register.yml
     
    PLAY [kevin] *************************************************************************************************************************************
     
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.238]
    ok: [172.16.60.237]
     
    TASK [register bo_test] **************************************************************************************************************************
    changed: [172.16.60.237]
    changed: [172.16.60.238]
     
    TASK [display info] ******************************************************************************************************************************
    ok: [172.16.60.237] => {
        "msg": "this host ip is 172.16.60.237 "
    }
    ok: [172.16.60.238] => {
        "msg": "this host ip is 172.16.60.238 "
    }
     
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.237              : ok=3    changed=1    unreachable=0    failed=0  
    172.16.60.238              : ok=3    changed=1    unreachable=0    failed=0
    

    10.  hostvars 变量
    该变量用于引用其他主机上收集的facts中的数据,或者引用其他主机的主机变量、主机组变量。即从一台远程主机获取另一台远程主机的变量。

    如下示例:
    [root@ss-server ansible]# cat /etc/ansible/hosts
    [kevin]
    172.16.60.237 addr=beijing 
    172.16.60.238 user=shibo age=39
    
    [root@ss-server ansible]# cat test.yml
    ---
    - hosts: kevin
      remote_user: root
      gather_facts: False
      tasks:
        - name: this is test1
          debug: msg="She is come from {{ hostvars['172.16.60.237']['addr'] }}"
        - name: this is test2
          debug: msg="I am {{ hostvars['172.16.60.238']['user'] }}, and age is {{ hostvars['172.16.60.238']['age'] }}"
    
    或者将test.yml文件配置如下,即由 "[变量]"" 改为 ".变量"
    两种配置的执行结果都是一样的!
    [root@ss-server ansible]# cat test.yml 
    ---
    - hosts: kevin
      remote_user: root
      gather_facts: False
      tasks:
        - name: this is test1
          debug: msg="She is come from {{ hostvars['172.16.60.237'].addr }}"
        - name: this is test2
          debug: msg="I am {{ hostvars['172.16.60.238'].user }}, and age is {{ hostvars['172.16.60.238'].age }}"
    
    执行结果为:
    [root@ss-server ansible]# ansible-playbook test.yml             
    
    PLAY [kevin] *************************************************************************************************************************************
    
    TASK [this is test1] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "msg": "She is come from beijing"
    }
    ok: [172.16.60.238] => {
        "msg": "She is come from beijing"
    }
    
    TASK [this is test2] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "msg": "I am shibo, and age is 39"
    }
    ok: [172.16.60.238] => {
        "msg": "I am shibo, and age is 39"
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.237              : ok=2    changed=0    unreachable=0    failed=0   
    172.16.60.238              : ok=2    changed=0    unreachable=0    failed=0  
    
    
    ############################# 这里需要注意下 ###########################
    除了上面的配置,还可以如下配置,但是请注意:
    1)debug后面必须使用"var"字段,即hostvar变量传递给var字段的变量。
    2)var=hostvars[....],等于两边不能有空格!
    
    [root@ss-server ansible]# cat test.yml 
    ---
    - hosts: kevin
      remote_user: root
      gather_facts: False
      tasks:
        - name: this is test1
          debug: var=hostvars['172.16.60.237']['addr']
        - name: this is test2
          debug: var=hostvars['172.16.60.238']['user']
    
    或者将test.yml文件配置如下,这两个配置方法的执行结果是一样的!
    [root@ss-server ansible]# cat test.yml
    ---
    - hosts: kevin
      remote_user: root
      gather_facts: False
      tasks:
        - name: this is test1
          debug: var=hostvars['172.16.60.237'].addr
        - name: this is test2
          debug: var=hostvars['172.16.60.238'].user
    
    执行结果为:
    [root@ss-server ansible]# ansible-playbook test.yml 
    
    PLAY [kevin] *************************************************************************************************************************************
    
    TASK [this is test1] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "hostvars['172.16.60.237']['addr']": "beijing"
    }
    ok: [172.16.60.238] => {
        "hostvars['172.16.60.237']['addr']": "beijing"
    }
    
    TASK [this is test2] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "hostvars['172.16.60.238']['user']": "shibo"
    }
    ok: [172.16.60.238] => {
        "hostvars['172.16.60.238']['user']": "shibo"
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.237              : ok=2    changed=0    unreachable=0    failed=0   
    172.16.60.238              : ok=2    changed=0    unreachable=0    failed=0
    
    注意:如果同一个name里配置了多个debug,则默认执行最后一个debug内容! (多个task的话,也是执行最后一个task)
    [root@ss-server ansible]# cat test.yml
    ---
    - hosts: kevin
      remote_user: root
      gather_facts: False
      tasks:
        - name: this is test1
          debug: var=hostvars['172.16.60.237']['addr']
        - name: this is test2
          debug: var=hostvars['172.16.60.238']['user']
          debug: var=hostvars['172.16.60.238']['age']
    
    执行结果:
    [root@ss-server ansible]# ansible-playbook test.yml 
     [WARNING]: While constructing a mapping from /etc/ansible/test.yml, line 8, column 7, found a duplicate dict key (debug). Using last defined
    value only.
    
    
    PLAY [kevin] *************************************************************************************************************************************
    
    TASK [this is test1] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "hostvars['172.16.60.237']['addr']": "beijing"
    }
    ok: [172.16.60.238] => {
        "hostvars['172.16.60.237']['addr']": "beijing"
    }
    
    TASK [this is test2] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "hostvars['172.16.60.238']['age']": "39"
    }
    ok: [172.16.60.238] => {
        "hostvars['172.16.60.238']['age']": "39"
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.237              : ok=2    changed=0    unreachable=0    failed=0   
    172.16.60.238              : ok=2    changed=0    unreachable=0    failed=0 

    11. 列表变量、循环变量、字典变量

    1)ansible的变量不仅可以是单个的值,也可以为列表,即ansible传列表作为变量。如下示例:
    #################################################################################################################
    [root@ss-server ansible]# vim test.yml
    ---
    - hosts: test_host
      gather_facts: no
      vars: 
        - list: [1,2,3]
      tasks:
        - name: echo
          debug: msg="{{ list }}"
    
    执行结果:
    [root@ss-server ansible]# ansible-palybook test.yml
    TASK [echo] ********************************************************************
    ok: [172.16.60.220] => {
        "msg": [
            1, 
            2, 
            3
        ]
    }
    
    再来看一例:
    linux创建用户,需要获取的变量有用户名,用户密码,用户组,有时候需要创建多个用户,那么传递给ansible的用户肯定是列表,但每一组又有用户名、密码、组这些变量值。
    做法如下:
    [root@ss-server ~]# cat /etc/ansible/test.yml 
    ---
    - hosts: test_host
      gather_facts: no
      tasks:
       - name: create or update account
         user: name={{ item.user }} password={{ item.password }} groups={{ item.group }} system=no
         with_items:
           -  '{{ user_list }}'
    
    执行结果:
    [root@ss-server ansible]# ansible-playbook /etc/ansible/test.yml -e '{"user_list":[{"user":"user1","password":"*******","group":"user1"}]}'
    
    #################################################################################################################
    2)循环列表
    结合循环,这个特性就变得很有用;以参数传递列表给playbook,不用在playbook中固定循环的次数与内容。如下示例:
    [root@ss-server ansible]# vim test.yml
    ---
    - hosts: test_host
      gather_facts: no
      vars: 
        - list: [1,2,3]
      tasks:
        - name: this is loop
          debug: msg="{{ item }}"
          with_items: '{{list}}'
    
    执行结果:
    [root@ss-server ansible]# ansible-palybook test.yml
    TASK [this is loop] ********************************************************************
    ok: [172.16.60.220] => (item=1) => {
        "item": 1, 
        "msg": 1
    }
    ok: [localhost] => (item=2) => {
        "item": 2, 
        "msg": 2
    }
    ok: [localhost] => (item=3) => {
        "item": 3, 
        "msg": 3
    }
    
    接着看下面一例:
    loop 关键字表示循环,去读循环体里的变量固定使用{{item}},item是个字典对象item.key=value。
    需要注意:下面test.yml文件中的"loop"关键字 改为 "with_items"关键字,效果是一样的!
    ansible的循环用法:在ansible 2.5版本之前,大多数人习惯使用"with_X"风格的关键字操作循环,
    从ansible 2.6版本开始,官方开始推荐使用"loop"关键字代替"with_X"风格关键字。
    [root@ss-server ~]# cat /etc/ansible/test.yml
    ---
    - name: this is test
      hosts: test_host
      connection: local
      gather_facts: no
    
      tasks:
        - name: debug loop
          debug:
            msg: "{{item.A1}}"
          loop:                      #这里将"loop"关键字 改为 "with_items"关键字,效果是一样的!
            - A: a
              A1: a1
              A2: a2
            - B: b
              A1: b1
              A2: b2
            - C: c
              A1: c1
              A2: c2
            - D: d
              A1: d1
              A2: d2
    
    执行结果:以上loop下的四个变量分别称为一块,即一个item,符号"-"为循环体块的标志,{{item.A1}}的值,即分别为a1,b1,c1,d1
    [root@ss-server ~]# ansible-playbook /etc/ansible/test.yml
    
    PLAY [this is test] *********************************************************************************************************************************
    
    TASK [debug loop] ********************************************************************************************************************************
    ok: [172.16.60.20] => (item={u'A': u'a', u'A1': u'a1', u'A2': u'a2'}) => {
        "msg": "a1"
    }
    ok: [172.16.60.20] => (item={u'A1': u'b1', u'B': u'b', u'A2': u'b2'}) => {
        "msg": "b1"
    }
    ok: [172.16.60.20] => (item={u'A1': u'c1', u'C': u'c', u'A2': u'c2'}) => {
        "msg": "c1"
    }
    ok: [172.16.60.20] => (item={u'A1': u'd1', u'A2': u'd2', u'D': u'd'}) => {
        "msg": "d1"
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
    
    再来看一例:
    [root@ss-server ~]# cat /etc/ansible/test.yml
    ---
    - name: this is test
      hosts: test_host
      connection: local
      gather_facts: no
      vars:
        my_list:
          - a
          - b
          - c
          - 1
    
      tasks:
        - name: debug loop output
          debug:
            msg: "The {{index}} one is {{item}}"
          loop: "{{my_list}}"               # 或者使用with_items: "{{my_list}}"  或者 with_list: "{{my_list}}" 
          loop_control:
            index_var: index
    
    执行结果:
    [root@ss-server ~]# ansible-playbook /etc/ansible/test.yml
    
    PLAY [this is test] ******************************************************************************************************************************
    
    TASK [debug loop output] *************************************************************************************************************************
    ok: [172.16.60.20] => (item=a) => {
        "msg": "The 0 one is a"
    }
    ok: [172.16.60.20] => (item=b) => {
        "msg": "The 1 one is b"
    }
    ok: [172.16.60.20] => (item=c) => {
        "msg": "The 2 one is c"
    }
    ok: [172.16.60.20] => (item=1) => {
        "msg": "The 3 one is 1"
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    
    #################################################################################################################
    3)字典变量
    变量也可以为字典。如下示例, 用到了ansible的循环,循环关键字是"with_X", 改为loop关键字,效果是一样的!
    注意:在变量中使用循环时,vars下必须要有"-",符号"-"为循环体块的标志!!
    [root@ss-server ansible]# vim test.yml
    ---
    - hosts: test_host
      gather_facts: no
      vars: 
        - lists: 
            list1: [1,2,3]
            list2: [4,5]
      tasks: 
        - name: loop
          debug: msg="{{ item }}"
          with_items: '{{lists["list1"]}}'        #替换为 loop: '{{lists["list1"]}}', 或者 with_list: '{{lists["list1"]}}' 效果是一样的
    
    执行结果:
    [root@ss-server ansible]# ansible-palybook test.yml
    TASK [loop] ********************************************************************
    ok: [172.16.60.220] => (item=1) => {
        "item": 1, 
        "msg": 1
    }
    ok: [localhost] => (item=2) => {
        "item": 2, 
        "msg": 2
    }
    ok: [localhost] => (item=3) => {
        "item": 3, 
        "msg": 3
    }
    
    接着看下面一例:
    [root@ss-server ~]# cat /etc/ansible/test.yml
    ---
    - hosts: test_host
      gather_facts: no
      vars: 
        - users: 
            name: [xiaoming]
            address: [anhui]
            age: [28]
      tasks: 
        - name: this is loop
          debug: msg="{{ item }}"
          loop: '{{users["name"]}}'        
    [root@ss-server ~]# ansible-playbook /etc/ansible/test.yml
    
    PLAY [test_host] *************************************************************************************************************************************
    
    TASK [this is loop] **************************************************************************************************************************************
    ok: [172.16.60.20] => (item=xiaoming) => {
        "msg": "xiaoming"
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
    
    上面的示例,如果要想打印多个字典变量,修改如下:
    [root@ss-server ~]# cat /etc/ansible/test.yml
    ---
    - hosts: test_host
      gather_facts: no
      vars: 
        - users: 
            name: [xiaoming]
            address: [anhui]
            age: [28]
      tasks: 
        - name: this is loop
          debug: msg="{{ item }}"
          loop:                         #这里用loop,with_items,with_list都可以
             - '{{users["name"]}}'    
             - '{{users["address"]}}' 
             - '{{users["age"]}}' 
    
    执行结果:
    [root@ss-server ~]# ansible-playbook /etc/ansible/test.yml
    
    PLAY [test_host] *********************************************************************************************************************************
    
    TASK [this is loop] ******************************************************************************************************************************
    ok: [172.16.60.20] => (item=[u'xiaoming']) => {
        "msg": [
            "xiaoming"
        ]
    }
    ok: [172.16.60.20] => (item=[u'anhui']) => {
        "msg": [
            "anhui"
        ]
    }
    ok: [172.16.60.20] => (item=[28]) => {
        "msg": [
            28
        ]
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.20                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

    ######   可以在一个yaml文件里放置多个hosts,将多个tasks任务一起执行   ########

    [root@ss-server ansible]# cat /etc/ansible/hosts
    172.16.60.237 
    [kevin]
    172.16.60.238 
    [grace]
    172.16.60.236
    
    [root@ss-server ansible]# cat bo.yml            
    ---
    - hosts: kevin
      remote_user: root
      gather_facts: False
      tasks:
        - name: this is test1
          shell: hostname -I
    
    - hosts: 172.16.60.237
      remote_user: root
      tasks:
        - name: this is test2
          debug: msg="this server is {{ info }} "
    
    - hosts: 172.16.60.238
      remote_user: root
      tasks:
        - name: this is test3
          debug: msg="this key is {{ haha }} "
    
    执行结果:
    [root@ss-server ansible]# ansible-playbook bo.yml -e "{'info':'my server','haha':'123123213123'}"  
    
    PLAY [kevin] *************************************************************************************************************************************
    
    TASK [this is test1] *****************************************************************************************************************************
    changed: [172.16.60.238]
    
    PLAY [172.16.60.237] *****************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.237]
    
    TASK [this is test2] *****************************************************************************************************************************
    ok: [172.16.60.237] => {
        "msg": "this server is my server "
    }
    
    PLAY [172.16.60.238] *****************************************************************************************************************************
    
    TASK [Gathering Facts] ***************************************************************************************************************************
    ok: [172.16.60.238]
    
    TASK [this is test3] *****************************************************************************************************************************
    ok: [172.16.60.238] => {
        "msg": "this key is 123123213123 "
    }
    
    PLAY RECAP ***************************************************************************************************************************************
    172.16.60.237              : ok=2    changed=0    unreachable=0    failed=0   
    172.16.60.238              : ok=3    changed=1    unreachable=0    failed=0 
    
    
    ############# 再来看下面一个测试环境用过的部署脚本配置 #########################
    [root@localhost ansible]# cat /opt/ansible_cfg/deploy.yml
    ---
    - hosts: webservers
      tasks:
        - name: Installed Httpd Server
          yum: name=httpd state=present
    
        - name: Start Httpd Server
          systemd: name=httpd state=started enabled=yes
    
        - name: Start Firewalld Server
          systemd: name=firewalld state=started enabled=yes
    
        - name: Configure Firewalld Server
          firewalld: service=http immediate=yes permanent=yes state=enabled
    
    - hosts: web01
      tasks:
        - name: Configure web01 Website
          copy: content='This is Web01' dest=/var/www/html/index.html
    
    - hosts: web02
      tasks:
        - name: Cofnigure webi-2 weisite
          copy: content='This is Web02' dest=/var/www/html/index.html
    
    - hosts: nfs01
      tasks:
        - name: Install NFS-utils Server
          yum: name=nfs-utils state=present
    
        - name: Configure Nfs-utils Server
          copy: src=./exports.j2 dest=/etc/exports owner=root group=root mode=0644
    
        - name: Create NFS Group
          group: name=www gid=666
    
        - name: Create NFS User
          user: name=www uid=666 group=www create_home=no shell=/sbin/nologin
    
        - name: Create Data Directory
          file: path=/data state=directory owner=www group=www mode=0755 recurse=yes
    
        - name: Start NFS Server
          systemd: name=nfs state=started enabled=yes
    
    - hosts: nfs01
      tasks:
        - name: Mount NFS Server
          mount: path=/opt src=172.16.60.23:/data fstype=nfs opts=defaults state=mounted

    ######   Ansible-playbook如何正确获取ip   ######

    [root@ansible ~]# vim /etc/ansible/hosts_all.yaml
    ---
    - hosts: all 
      tasks:       
        - name: 将原有的hosts文件备份         
          shell: mv /etc/hosts /etc/hosts_bak       
        - name: 将ansible端的hosts复制到各自机器上         
          copy: src=/root/hosts dest=/etc/ owner=root group=root mode=0644       
        - name: 在新的hosts文件后面追加各自机器内网ip和hostname         
          lineinfile: dest=/etc/hosts line="`ansible_all_ipv4_addresses`  `ansible_hostname`"
     
    但是执行完ansible-playbook之后,ansible客户机器上的/etc/hosts文件里ip和主机名对应关系如下(cat /etc/hosts):
     
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    [u'172.16.60.210'] redis-fun01.kevin.cn
     
    实际想要的结果是(cat /etc/hosts):
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
    ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
    172.16.60.210 redis-fun01.kevin.cn
     
    解决办法:
    调整ansible服务端的hosts_all.yaml文件中获取ip的变量
    变量用 IP: "{{ ansible_eth0['ipv4']['address'] }}",而不是`ansible_all_ipv4_addresses`
     
    修改后的yaml文件配置如下:
    [root@ansible ~]# vim /etc/ansible/hosts_all.yaml
    ---
    - hosts: all 
      vars:       
        IP: "{{ ansible_eth0['ipv4']['address'] }}" 
      tasks:       
        - name: 将原有的hosts文件备份         
          shell: mv /etc/hosts /etc/hosts_bak       
        - name: 将ansible端的hosts复制到各自机器上         
          copy: src=/root/hosts dest=/etc/ owner=root group=root mode=0644       
        - name: 在新的hosts文件后面追加各自机器内网ip和hostname         
          lineinfile: dest=/etc/hosts line="`IP`  `ansible_hostname`" 

    ########  获取ansible清单列表里对应组的ip、指定机器执行操作 [ --list-hosts--limit ]  ########

    注意下面两个参数的使用:
    1)获取hosts清单文件里指定组内的ip,使用"--list-hosts"
    2)指定hosts清单文件里的ip执行操作,使用"--limit"
     
    如下是ansible的一个清单文件:
    [root@localhost ~]# cat /opt/kevin-bo.cfg
    [kevin-bo_F]
    172.16.60.65
     
    [kevin-bo_A]
    172.16.60.140
    172.16.60.141
     
    [kevin-bo:children]
    kevin-bo_D
    kevin-bo_F
    kevin-bo_A
     
    [kevin-bo:vars]
    deploy_path=/opt/web/kevin-bo/
    start_time_out=90
    stop_time_out=60
    module=kevin-bo
     
    [kevin-bo_D]
    172.16.60.195
     
    现在需要获取"kevin-bo_A" 和 "kevin-bo_D" 组下的ip,--list-hosts后面加不加引号都可以。
    [root@localhost ~]# ansible -i /opt/kevin-bo.cfg --list-hosts kevin-bo_A
      hosts (2):
        172.16.60.140
        172.16.60.141
    [root@localhost ~]# ansible -i /opt/kevin-bo.cfg --list-hosts "kevin-bo_A"|grep -v "hosts"
        172.16.60.140
        172.16.60.141
    [root@localhost ~]# ansible -i /opt/kevin-bo.cfg --list-hosts kevin-bo_D|grep -v "hosts"
        172.16.60.195
    
    获取多个机器组内的ip,中间使用逗号隔开
    [root@localhost ~]# ansible -i /opt/kevin-bo.cfg --list-hosts "kevin-bo_A,kevin-bo_D"
      hosts (3):
        172.16.60.140
        172.16.60.141
        172.16.60.195
    
    [root@localhost ~]# ansible -i /opt/kevin-bo.cfg --list-hosts "kevin-bo_A,kevin-bo_D"|grep -v "hosts"
        172.16.60.140
        172.16.60.141
        172.16.60.195
     
    在执行ansible-playbook时,可以指定单个ip或group,也可以指定多个ip或group,也可以一起指定ip和group,多个中间使用逗号隔开!
    --limit后面加不加引号都可以。
    # ansible-playbook -i 清单文件 yml文件 --limit ip
    # ansible-playbook -i 清单文件 yml文件 --limit ip1,iP2,ipn
    # ansible-playbook -i 清单文件 yml文件 --limit group
    # ansible-playbook -i 清单文件 yml文件 --limit group1,group2,groupn
    # ansible-playbook -i 清单文件 yml文件 --limit ip1,ip2,group1,groupn
    还可以将制定的ip或group放在一个文件里,如ip.txt,然后--limit后面跟"@ip.txt"
    # ansible-playbook -i 列表文件 yml文件 --limit @ip.txt   
    # cat ip.txt
    172.16.60.140
    172.16.60.141
    kevin-bo_F
     
    如上,需要对清单文件"kevin-bo_A"组下的机器进行发布操作,由于发布过程中涉及服务启停,为了保证发布中整体服务不中断,需要一台一台的执行,不能所有机器一起执行。如下:
    [root@localhost ~]# vim deploy.sh
    #!/bin/bash
    #设置变量,传参等
    BRANCH=$1 
    MODULE_NAME=$2 
    PRODUCT_PATH=$3
    USER=$4
    APP_NANE=${Deploy_App}
    .....
     
    for Next_Deploy_IP in $(ansible -i /opt/kevin-bo.cfg --list-hosts kevin-bo_A|grep -v "hosts")
    do
       ansible-playbook -i /opt/kevin-bo.cfg /root/ansible/web_deploy.yml --limit "${Next_Deploy_IP}" -e "user=${USER} app_name=${APP_NANE} package_name=... ..."
       if [ $? -eq 0 ];then
          echo "[`date +%Y%m%d-%H%M%S`]++++++++++++++++++++++++${Next_Deploy_IP}部署成功!++++++++++++++++++++++++++++++"
          sleep 10
       else
          echo "[`date +%Y%m%d-%H%M%S`]++++++++++++++++++++++++${Next_Deploy_IP}部署失败!++++++++++++++++++++++++++++++"
          exit 1
       fi
    done
  • 相关阅读:
    TF中的自定义正则项
    [工具]多线程下载 axel
    [算法]kv-memory 表示dense特征
    [code]tensorflow分桶
    [code]Keras API的用法记录
    vim显示下划线不高亮问题
    [代码] kv2sparse
    [代码]并发执行python的例子
    AI算法手册
    [工具]soundflower
  • 原文地址:https://www.cnblogs.com/kevingrace/p/5569648.html
Copyright © 2011-2022 走看看