自动化运维-Ansible04-Playbook

1、Playbook入门

  • Playbook说明文档:https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html
  • Ansible使用YAML语法描述配置文件,YAML语法以简洁明了、结构清晰著称。
  • Ansible的任务配置文件被称为Playbook,可以称之为“剧本”。
    • 每一个剧本(Playbook)中都包含一系列的任务,这每个任务在Ansible中又被称为“戏剧”(play)。一个剧本(Playbook)中包含多出戏剧(play)。
  • Playbook语法具有如下一些特性。
    • 1)需要以“---”(3个减号)开始,且需顶行首写。
    • 2)次行开始正常写Playbook的内容,但笔者建议写明该Playbook的功能。
    • 3)使用#号注释代码。
    • 4)缩进必须是统一的,不能将空格和Tab混用。
    • 5)缩进的级别必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的。
    • 6)YAML文件内容和Linux系统大小写判断方式保持一致,是区别大小写的,k/v的值均需大小写敏感。
    • 7)k/v的值可同行写也可换行写。同行使用“:”分隔,换行写需要以“-”分隔。
    • 8)一个完整的代码块功能需最少元素,需包括nam:task。
    • 9)一个name只能包括一个task。

1.1、Ansible-playbook命令

  • ansible-playbook:是日常应用中使用频率最高的命令,其工作机制是,通过读取预先编写好的playbook文件实现批量管理。
  • Ansible-playbook的命令使用格式如下:
ansible-playbook playbook.yml
  • Ansible-playbook的参数:
    • -i INVENTORY, --inventory INVENTORY:指定主机清单文件,或以逗号分隔的主机列表。
    • -b, --become:使用在远程主机上切换到root用户去执行命令(不提示密码)。要在远程主机提升sudo权限(例如centoshh ALL=(ALL) NOPASSWD:ALL)。
    • -f FORKS, --forks FORKS:并发管控主机的数量(default=5)。
    • -C, --check:测试执行,不会做任何更改。
    • --syntax-check:检查Playbook中的语法,但不执行。
    • --list-hosts:列出匹配的主机列表,不执行任何操作。
    • --list-tasks:列出所有要执行的任务。
    • --list-tags:列出所有可用的tags。
    • -l SUBSET, --limit SUBSET:-l SUBSET, --limit SUBSET:指定运行的主机。
    • -t TAGS, --tags TAGS:只执行指定的tags任务。
    • --skip-tags SKIP_TAGS:跳过指定的tags任务。
    • --start-at-task START_AT_TASK:从第几条任务开始执行。
    • --step:逐步执行Playbook定义的任务,并经人工确认后继续执行下一步任务。
    • -e EXTRA_VARS, --extra-vars EXTRA_VARS:在Playbook中引入外部变量。
    • -D,--diff:当更新的文件数及内容较少时,该选项可显示这些文件不同的地方,该选项结合-C用会有较好的效果。
    • --ask-vault-pass:使用加密playbook文件时提示输入密码。
    • --force-handlers:即使任务失败,也要运行处理程序
    • --flush-cache:清除缓存。
    • --version:显示程序版本号,配置文件位置,配置模块搜索路径,模块位置,可执行位置和退出

1.2、YAML语法

  • 见:https://www.cnblogs.com/maiblogs/p/14811614.html的“1、YAML数据”

1.3、Shell脚本与Playbook的转换

1、shell脚本

#!/bin/bash
#安装Apache
yum install --quiet -y httpd httpd-devel
#复制配置文件
cp /path/to/config/httpd.conf /etc/httpd/conf/httpd.conf
cp /path/to/httpd-vhosts.conf /etc/httpd/conf/httpd-vhosts.conf
#启动Apache,并设置开机启动
service httpd start
chkconfig httpd on

2、Playbook

  • 将Shell脚本转换为Playbook。
    • 第1行,“---”,这个是YAML语法中注释的用法,就像shell脚本中的“#”号一样。
    • 第2行,“- hosts: all”,告诉Ansible要在哪些主机上运行剧本(Playbook),在本例中是all,即所有主机。
    • 第3行,“tasks:”,指定一系列将要运行的任务。
      • 每一个任务(play)以“- name:”开头。
      • “- name:”字段并不是一个模块,不会执行任务实质性的操作,它只是给“task”一个易于识别的名称。
    • 第二个任务(play)同样是“-name”字符开头。使用copy模块来将“src”定义的源文件(必须是Ansible所在服务器上的本地文件)复制到“dest”定义的目的地址(远程主机上的地址)去。在传递文件的同时,还定义了文件的属主、属组和权限。
      • 在这个play中,用数组的形式给变量赋值,使用{var1:value,var2:value}的格式来赋值,变量的个数可以任意多,不同变量间以逗号分隔,使用{{item.var1}}的形式来调用变量。
    • 第三个任务(play)使用了同样的结构,调用了service模块,以保证服务的正常启动。
---
- hosts: all
  tasks:
  - name: 安装Apache
    yum: 
      state: present
      name:
      - httpd
      - httpd-devel
  - name: 复制配置文件
    copy:
      src: "{{ item.src }}"
      dest: "{{ item.dest }}"
      owner: root
      group: root
      mode: 0644
    with_items:
    - src: "/tmp/httpd.conf"
      dest: "/etc/httpd/conf/httpd.conf"
    - src: "/tmp/httpd-vhosts.conf"
      dest: "/etc/httpd/conf/httpd-vhosts.conf"
  - name: 检查Apache运行状态,并设置开机启动
    service: name=httpd state=started enabled=yes
  • 测试运行playbook
]# ansible-playbook ./test.yaml -C

PLAY [all] ******************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
ok: [10.1.1.13]
ok: [10.1.1.12]

TASK [安装Apache] *************************************************************************************************************************************************************************************************
changed: [10.1.1.12]
changed: [10.1.1.13]

TASK [复制配置文件] ***************************************************************************************************************************************************************************************************
changed: [10.1.1.12] => (item={u'dest': u'/etc/httpd/conf/httpd.conf', u'src': u'/tmp/httpd.conf'})
changed: [10.1.1.13] => (item={u'dest': u'/etc/httpd/conf/httpd.conf', u'src': u'/tmp/httpd.conf'})
changed: [10.1.1.12] => (item={u'dest': u'/etc/httpd/conf/httpd-vhosts.conf', u'src': u'/tmp/httpd-vhosts.conf'})
changed: [10.1.1.13] => (item={u'dest': u'/etc/httpd/conf/httpd-vhosts.conf', u'src': u'/tmp/httpd-vhosts.conf'})

TASK [检查Apache运行状态,并设置开机启动] *************************************************************************************************************************************************************************************
changed: [10.1.1.12]
changed: [10.1.1.13]

PLAY RECAP ******************************************************************************************************************************************************************************************************
10.1.1.12                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
10.1.1.13                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

2、Playbook的核心元素

  • Playbook的核心元素:
    • Hosts:执行操作的远程主机列表。
    • Tasks:任务列表。
    • Tags(标签):对某个任务设定一个或多个标签。在执行playbook时,可以只执行(跳过)带有指定标签的任务。
    • Handlers(触发器):和notity结合使用。可以对某个任务设定一个或多个notity。某任务在运行后,当其状态为changed时,可通过“notify”通知给相应的handlers。
    • Variables(变量):内置变量或自定义变量,可以在playbool中调用。
    • Templates(模板):包含可以被替换变量的文本文件。

2.1、主机列表(hosts)

  • hosts用于指定要执行任务的主机,必须事先定义在主机清单中。
//hosts的值可以是如下形式
all                #主机清单中的所有主机
one.example.com    #域名
10.1.1.12          #IP地址
10.1.1.*           #IP网络
web1:web2          #两个组的并集
web1:&web2         #两个组的交接
web1:!web2         #在web1中,但不在wb2中

2.2、任务列表(tasks)

  • 每一个剧本(Playbook)中都包含一系列的任务,这每个任务在Ansible中又被称为“戏剧”(play)。
  • “tasks:”指定一系列将要运行的任务
    • 每一个任务(play)以“- name:”头。
    • “- name:”字段并不是一个模块,不会执行任务实质性的操作,它只是给“task”一个易于识别的名称。
  • tasks格式有两种:
    • 注意:shell和command模块后面直接跟命令,而非key=value类的参数列表。
(1)action: module arguments    #动作: 模块名 模块参数
(2)module: arguments           #模块名: 模块参数(建议使用)
  • (1)某任务在运行后,当其状态为changed时,可通过“notify”通知给相应的handlers。
  • (2)任务可以通过“tags”打标签,而后可在ansible-playbook命令上使用-t指定进行调用;

示例:

  • 安装httpd,并启动
---
- hosts: all
  tasks:
  - name: 安装Apache
    yum: state=present name=httpd
  - name: 复制配置文件
    copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/httpd.conf owner=root group=root mode=0644
  - name: 检查Apache运行状态,并设置开机启动
    service: name=httpd state=started enabled=yes
  • 应用playbook
ansible-playbook test.yaml

2.3、标签(tags)

  • 对某个任务设定一个或多个标签。在执行playbook时,可以只执行(跳过)带有指定标签的任务。

示例:

---
- hosts: all
  tasks:
  - name: 安装Apache
    yum: state=present name=httpd
    tags:    #添加标签
    - install-apache
    - haha
  - name: 复制配置文件
    copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/httpd.conf owner=root group=root mode=0644
    tags:    #添加标签
    - config
    - haha
  - name: 检查Apache运行状态,并设置开机启动
    service: name=httpd state=started enabled=yes
    tags:    #添加标签
    - start
  • 应用playbook
//只运行带有指定标签的任务
ansible-playbook test.yaml -t haha

//不运行(跳过)带有指定标签的任务
ansible-playbook test.yaml --skip-tags config

2.4、触发器(handlers)

  • Handlers也是task列表,与前述的task并没有本质上的不同。只是用于当关注的资源发生变化时,才会执行的操作。
  • 可以对某个任务设定一个或多个notity。某任务在运行后,当其状态为changed时,可通过“notify”通知给相应的handlers。

示例:

---
- hosts: all
  tasks:
  - name: 安装Apache
    yum: state=present name=httpd
    tags:
    - install-apache
  - name: 复制配置文件
    copy: src=/tmp/httpd.conf dest=/etc/httpd/conf/httpd.conf owner=root group=root mode=0644
    notify: restart httpd    #当触发handlers,调用restart httpd
    tags:
    - config
  - name: 检查Apache运行状态,并设置开机启动
    service: name=httpd state=started enabled=yes
    tags:
    - start
  handlers:
  - name: restart httpd
    service: name=httpd state=restarted
  • 应用playbook
    • 修改httpd配置文件,然后只执行playbook指定的任务。
//修改配置,并重启服务
ansible-playbook test.yaml -t config

3、变量(Variables)

1、环境变量

  • 系统中的环境变量。

2、ansible变量

  • 变量名:由字母、数字和下划线组成,且只能以字母开头。
  • 变量来源:
    • (1)在命令行中使用的变量。
      • 在ansible-playbook命令行中,使用“-e EXTRA_VARS, --extra-vars EXTRA_VARS”引入外部变量。
    • (2)在/etc/ansible/hosts文件中定义的变量
      • 主机变量:对主机单独定义的变量,作用域是单个主机。
      • 组变量:对组定义的变量,作用域是整个组。
      • 注意invertory参数用于ansible连接远程目标主机时使用的参数,而非传递给playbook的变量。
        • ansible_user
        • ansible_password
    • (3)在playbook中定义的变量。
    • (4)setup模块提供的所有变量(内置变量),playbook都可以自动调用。
    • (5)在role中定义的变量。
  • 变量引用:{{ variable_name }}。
  • 优先级:命令行中的变量 > playbook中的变量 > 主机清单中的变量(主机变量 > 组变量)

3、注册变量

  • 在Playbook中使用register将操作的结果(包括标准输出和标准错误输出)保存到变量中,该变量被称为注册变量。

3.1、环境变量

  • 在某些情况下,若所有任务都运行在一个持久的或准高速缓存的SSH会话上的话,如果不重读环境变量配置文件,那么定义的新环境变量ENV_VAR可能就不会生效。

示例:

---
- hosts: all
  tasks:
  - name: 为远程主机上的用户指定环境变量
    lineinfile: dest=~/.bash_profile regexp='^ENV_VAR=' line='ENV_VAR=3.14'
  - name: 获取刚刚指定的环境变量,并将其保存到自定义变量foo中
    shell: 'source ~/.bash_profile && echo $ENV_VAR'
    register: foo
  - name: 打印出环境变量
    debug: msg="The variable is {{ foo.stdout }}"

3.2、在ansible命令行中定义变量

  • 定义playbook,并引用变量
---
- hosts: all
  tasks:
  - name: 安装{{ apps }}
    yum: state=present name={{ apps }}
    tags:
    - install-{{ apps }}
  - name: 检查{{ apps }}运行状态,并设置开机启动
    service: name={{ apps }} state=started enabled=yes
    tags:
    - start

1、在命令行中定义变量

  • 应用playbook,并定义变量
ansible-playbook test.yaml -e "apps=httpd" -C

2、在文件中定义变量

  • 定义变量
]# vim var.yaml
---
apps: httpd
  • 应用playbook
ansible-playbook test.yaml --extra-vars "@var.yaml" -C

3.3、在ansible主机清单中定义变量

  • 在Inventory文件中直接定义变量方法虽然简单直观,但当所需要定义的变量多,并且在被多台主机同时应用的时候,这种方法就会显得非常麻烦。事实上,Ansible的官方手册中也并不建议人们把变量直接定义在Hosts文件中。
  • 在执行Ansible命令时,Ansible默认会从/etc/ansible/host_vars/和/etc/ansible/group_vars/两个目录下读取变量定义,如果/etc/ansible下面没有这两个目录,可以直接手动创建,并且可以在这两个目录中创建与Hosts文件中主机名组名同名的文件来定义变量。

示例:

//定义主机变量和组变量
]# vim /etc/ansible/hosts
[web]
10.1.1.12 node_name=hh12    #定义主机变量“node_name=hh12”
10.1.1.13 node_name=hh13
[web:vars]
domain_name=com             #定义组变量“domain_name=com”
[all:vars]
ansible_user=centoshh       #invertory参数
ansible_password=centoshh

//定义playbook
]# vim test.yaml
---
- hosts: all
  tasks:
  - name: set hostname
    hostname: name={{ node_name }}.{{ domain_name }}
  • 应用playbook
ansible-playbook test.yaml -C

3.4、在ansible playbook中定义变量

1、在playbook中定义变量

  • 定义一个playbook,并定义变量
---
- hosts: all
  vars:
  - apps1: httpd
  - apps2: httpd-devel
  tasks:
  - name: 安装 {{ apps1 }}
    yum: state=present name={{ apps1 }}
    tags:
    - install-{{ apps1 }}
  - name: 安装 {{ apps2 }}
    yum: state=present name={{ apps2 }}
    tags:
    - install-{{ apps2 }}
  - name: 检查{{ apps1 }}运行状态,并设置开机启动
    service: name={{ apps1 }} state=started enabled=yes
    tags:
    - start
  • 应用playbook
ansible-playbook test.yaml -C

2、在文件中定义变量

  • 定义变量
]# vim var.yaml
---
apps1: httpd
apps2: httpd-devel
  • 定义一个playbook,并引用变量文件
---
- hosts: all
  vars_files:
  - var.yml
  tasks:
  - name: 安装 {{ apps1 }}
    yum: state=present name={{ apps1 }}
    tags:
    - install-{{ apps1 }}
  - name: 安装 {{ apps2 }}
    yum: state=present name={{ apps2 }}
    tags:
    - install-{{ apps2 }}
  - name: 检查{{ apps1 }}运行状态,并设置开机启动
    service: name={{ apps1 }} state=started enabled=yes
    tags:
    - start
  • 应用playbook
ansible-playbook test.yaml -C

3.5、内置变量

1、Facts信息

  • 在运行任何一个Playbook之前,Ansible默认会先抓取Playbook中所指定的所有主机的系统信息,这些信息称之为Facts(内置变量)。

  • 在某些用不到Facts信息的Playbook任务中,可以在Playbook中设置“gather_facts:no”来暂时让Ansible在执行Playbook任务之前跳过收集远程主机Facts信息这一步,这样可以为任务节省几秒钟的时间,如果主机数量多的话,就能节省更多的时间。
  • 在Playbook中设置gather_facts的方法如下:
- hosts: db
  gather_facts: no
  • 如果远程主机上安装了Facter或Ohai,那么Ansible将会把这两个软件所生成的Facts信息也给收集回来,Facts变量名分别以facter_和ohai_开头进行标示。

2、使用内置变量

  • 查看内置变量
ansible 10.1.1.12 -m setup
  • 定义playbook
---
- hosts: all
  tasks:
  - name: copyfile
    copy: content={{ ansible_default_ipv4.address }} dest=/tmp/ansible.ip
  • 应用playbook
ansible-playbook test.yaml

3、自定义本地内置变量

  • 在远程主机本地定义Facts变量。
]# vim /etc/ansible/facts.d/settings.fact
[users]
admin=jane,john
normal=jim
  • 查看自定义内置变量
]# ansible 10.1.1.12 -m setup | grep users -A 5
                "users": {
                    "admin": "jane,john", 
                    "normal": "jim"
                }
...
  • 如果在一个Playbook中,只有部分Playbook任务用到了远程主机自定义的本地Facts,那么可以在该任务中明确地指明只显示本地Facts。
- name: 重新获取本地Facts
  setup: filter=ansible_local

3.6、注册变量

  • 注册变量,其实就是将操作的结果,包括标准输出和标准错误输出,保存到变量中,然后再根据这个变量的内容来决定下一步的操作,在这个过程中用来保存操作结果的变量就叫注册变量。
  • 在Playbook中使用register来声明一个变量为注册变量。
  • 如果想查看一个注册变量都有哪些属性,可以在运行Playbook的时,使用-v选项来检查Playbook的运行结果,通常会使用一下4种运行结果。
    • changed:任务是否对远程主机造成的变更
    • delta:任务运行所用的时间
    • stdout:正常的输出信息
    • stderr:错误信息

示例:

---
- hosts: all
  tasks:
  - name: 为远程主机上的用户指定环境变量
    lineinfile: dest=~/.bash_profile regexp='^ENV_VAR=' line='ENV_VAR=3.14'
  - name: 获取刚刚指定的环境变量,并将其保存到自定义变量foo中
    shell: 'source ~/.bash_profile && echo $ENV_VAR'
    register: foo
  - name: 打印出环境变量
    debug: msg="The variable is {{ foo.stdout }}"

3.7、变量优先级

  • Ansible官方给出的变量优先级如下由高到低排序:
    • (1)在命令行中定义的变量(即用-e定义的变量)。
    • (2)在Inventory中定义的连接变量(比如ansible_ssh_user)。
    • (3)大多数的其他变量(命令行转换、play中的变量、included的变量、role中的变量等)。
    • (4)在Inventory定义的其他变量。
    • (5)由系统通过gather_facts方法发现的Facts。
    • (6)“Role默认变量”,这个是默认的值,很容易丧失优先权。
  • 变量定义方面的小技巧如下:
    • Role中的默认变量应设置得尽可能的合理,因为它优先级最低,以防这些亦是在其他地方都没被定义,而Role的默认亦是又定义的不合理而产生问题。
    • Playbook中应尽量少地定义变量,Playbook中用的变量应尽量定义在专门的变量文件中,通过vars_files引用,或定义在Inventory文件中。
    • 只有真正与主机或主机组强相关的变量才定义在Inventory文件中。
    • 应尽量少地在动态或静态的Inventory源文件中定义变量,尤其是不要定义那些很少在Playbook中被用到的变量。
    • 应尽量避免在命行中使用-e选项来定义变量。只有在我们不用去关心项目的可维护性和任务幂等性的时候,才建议使用这种变量定义方式。比如只是做本地测试,或者运行一个一次性的Playbook任务。

示例:

  • 定义主机清单
    • port1在主机变量、组变量、playbook变量中定义。
    • port2在主机变量、组变量中定义。
]# vim /etc/ansible/hosts
[web]
10.1.1.12 port1=8011 port2=8012
[web:vars]
port1=8021
port2=8022
[all:vars]
ansible_user=centoshh
ansible_password=centoshh
  • 定义playbook
]# vim test.yaml
---
- hosts: all
  vars:
  - port1: 8031
  tasks:
  - name: copyfile1
    copy: content={{ port1 }} dest=/tmp/ansible.port1
  - name: copyfile2
    copy: content={{ port2 }} dest=/tmp/ansible.port2
  • 应用playbook
    • 优先级:命令行中的变量 > playbook中的变量 > 主机清单中的变量(主机变量 > 组变量)
//ansible.port1中是8031,说明playbook中的变量的优先级高于主机清单中的变量。ansible.port3中是8012,说明主机变量的优先级高于组变量
ansible-playbook test.yaml

//ansible.port1中是8010,说明命令行中的变量的优先级高于playbook和主机清单中的变量
ansible-playbook test.yaml -e "port1=8010"

4、模板(templates)

  • Ansible在配置模板文件和任务进行条件判断时都会用到Jinja2语法以及一些Ansible也能够使用的Python内置函数
  • templates模块可以将一个本地模板处理后保存于远程服务器之上。
    • 模板文件,嵌套有脚本(使用Jinja2语言编写)。
  • 建议将模板放在playbook文件的同级目录templates中,且模板以.j2结尾。
    • 否则,引用模板时要使用绝对路径。
]# tree ./playbook
./playbook
├── nginx-playbook.yaml
└── templates
    └── nginx.conf.js

4.1、Jinja2基本语法

  • Jinja2说明文档:http://jinja.pocoo.org/docs/
  • 由python的python-jinja2模块提供。
yum info python-jinja2
  • Jinja2与php类似,是嵌入式语言,他只执行文本中的Jinja2代码,对文本的其他内容不改变。
    • 数据类型有:
      • 字符串:使用单引号或双引号。
      • 数字:整数,浮点数。
      • 列表:[item1, item2, ...]
      • 元组:(item1, item2, ...)
      • 字典:{key1:value1, key2:value2, ...}
      • 布尔型:true/false
    • 算术运算有:
      • +, -, *, /, //, %, **
    • 比较操作有:
      • ==, !=, >, >=, <, <=
    • 逻辑运算有:
      • and, or, not
    • 使用defined或undefined判断对象是否被定义过了。
      • foo is defined(foo被定义过为真)
      • foo is undefined(foo没有被定义过为真)
    • 使用even判断对象是否是偶数。
    • 使用iterable判断对象是否可迭代。
  • 可以使用Python的内置方法,比如:string.split和[number].is_signed()等。
  • Jinja2.8版本的内置变量:
    • loop.index:当前循环的迭代次数(默认从1开始)。
    • loop.index0:当前循环的迭代次数(默认从0开始)。
    • loop.revindex:到循环结束需要迭代的次数(默认从1开始)。
    • loop.revindex0:到循环结束需要迭代的次数(默认从0开始)。
    • loop.first:如果是第一次迭代,为True。
    • loop.last:如果是最后一次迭代,为True。
    • loop.length:序列中的项目数。
    • loop.depth:显示渲染的递归循环的层级数(默认从1开始)。
    • loop.depth0:显示渲染的递归循环的层级数(默认从0开始)。
    • loop.cycle:在一串序列间期取值的辅助函数。
  • Jinja2的语法示例:
//下列表达式的运算结果都为'true'
1 in [1, 2, 3]
'see' in 'Can you see me?'
foo != bar
(1 < 2) and ('a' not in 'best')
//下列表达式的运算结果都为'false'
4 in [1, 2, 3]
foo == bar
(foo != foo) or (a in [1, 2, 3])

4.2、模板的基本使用

1、主机清单

[web]
10.1.1.12 web_port=8088
10.1.1.13 web_port=8089
[all:vars]
ansible_user=centoshh
ansible_password=centoshh

2、创建模板

  • 以nginx.conf为例。
]# cat templates/nginx.conf.j2 
worker_processes {{ ansible_processor_vcpus * 2 }};                       #工作进程的数量是CPU数量的两倍(CPU数量引用内置变量)
..
http {
    server {
        listen       {{ ansible_default_ipv4.address }}:{{ web_port }};    #IP地址引用内置变量,端口引用主机变量
        ...
    }
    ...
}

3、创建playbook

]# cat nginx-playbook.yaml
---
- hosts: all
  tasks:
  - name: 安装Nginx
    yum: state=present name=nginx
    tags:
    - install-nginx
  - name: 复制配置文件
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    notify: restart nginx    #当触发handlers,调用restart nginx
    tags:
    - config
  - name: 检查nginx运行状态,并设置开机启动
    service: name=nginx state=started enabled=yes
    tags:
    - start
  handlers:
  - name: restart nginx
    service: name=nginx state=restarted

4、应用playbook

ansible-playbook nginx-playbook.yaml -b

4.3、控制语句(for和if)

  • 控制语句格式:
//for控制语句
{% for i in var %}
  server{
    listen {{ i }}
  }
{% endfor %}

//if控制语句
{% if i is defined %}
  server_name {{ i }}
{% endif %}

示例1:

  • 创建模板
]# cat templates/template.conf.j2
{% for port in ports %}    #引用playbook中可以迭代的变量
server{
  listen {{ port }}
}
{% endfor %}
  • 创建playbook
]# cat nginx-playbook.yaml
---
- hosts: all
  vars:
    ports:    #定义一个可以迭代的变量
    - 81
    - 82
    - 83
  tasks:
  - name: 复制配置文件
    template: src=template.conf.j2 dest=/etc/nginx/nginx.conf    #引用模板
  • 查看结果
]# cat /etc/nginx/nginx.conf
server{
  listen 81
}
server{
  listen 82
}
server{
  listen 83
}

示例2:

  • 创建模板
]# cat templates/template.conf.j2
{% for config in ports %}    #引用playbook中可以迭代的变量
server{
  listen {{ config.web_port }}
{% if config.name is defined %}
  servername {{ config.name}}
{% endif %}
  documentroot {{ config.rootdir }}
}
{% endfor %}
  • 创建playbook
]# cat nginx-playbook.yaml
---
- hosts: all
  vars:
    ports:    #定义可以迭代的变量
    - name: web1.hengha.com
      web_port: 81
      rootdir: /data/web1
    - web_port: 82
      rootdir: /data/web1
    - name: web1.hengha.com
      web_port: 83
      rootdir: /data/web1
  tasks:
  - name: 复制配置文件
    template: src=template.conf.j2 dest=/etc/nginx/nginx.conf    #引用模板
  • 查看结果
]# cat /etc/nginx/nginx.conf
server{
  listen 81
  servername web1.hengha.com
  documentroot /data/web1
}
server{
  listen 82
  documentroot /data/web1
}
server{
  listen 83
  servername web1.hengha.com
  documentroot /data/web1
}

5、任务(play)控制流程

5.1、条件语句(when)

  • when语句可以让任务只有在特定条件下执行。
  • when语句和注册变量结合使用,可以让某个任务根据前面任务的结果进行判断是否执行。

示例1:

  • 在ansible中,可以使用的比较运算符
    • ==:比较两个对象是否相等
    • !=:比较两个对象是否不等
    • > :比较两个值的大小
    • >=:比较两个值的大小
    • < :比较两个值的大小
    • <=:比较两个值的大小
---
- hosts: test
  tasks:
  - debug:
      msg: "System release is redhat7"
    when: ansible_distribution == "RedHat" and ansible_distribution_major_version == "7"

示例2:

  • 在ansible中,可以使用的逻辑运算符
    • and:逻辑与,当左边与右边同时为真,则返回真
    • or :逻辑或,当左边与右边有任意一个为真,则返回真
    • not:取反,对一个操作体取反
    • () :组合,将一组操作体包装在一起,形成一个较大的操作体
---
- hosts: test
  tasks:
  - debug:
      msg: "System release is redhat7 or redhat6"
    when: ansible_distribution == "RedHat" and (ansible_distribution_major_version == "7" or ansible_distribution_major_version == "6")

示例3:

  • 根据shell模块的命令返回值,判断任务是否执行
---
- hosts: test
  remote_user: root
  tasks:
  - name: task1
    shell: "date"
    register: returnmsg
  - name: task2
    debug:
      var: returnmsg
  • 可以看到,当task1正常执行时,返回的结果rc为0
]# ansible-playbook test.yaml
PLAY [all] *****************************************************************************

TASK [Gathering Facts] *****************************************************************************
ok: [10.1.1.13]

TASK [task1] *****************************************************************************
changed: [10.1.1.13]

TASK [task2] *****************************************************************************
ok: [10.1.1.13] => {
    "returnmsg": {
        "changed": true, 
        "cmd": "date", 
        "delta": "0:00:00.002620", 
        "end": "2022-12-28 00:03:28.189568", 
        "failed": false, 
        "rc": 0, 
        "start": "2022-12-28 00:03:28.186948", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "2022年 12月 28日 星期三 00:03:28 CST", 
        "stdout_lines": [
            "2022年 12月 28日 星期三 00:03:28 CST"
        ]
    }
}

PLAY RECAP *****************************************************************************
10.1.1.13                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
  • 使用命令返回值(判断远程主机上目录是否存在)
---
- hosts: test
  remote_user: root
  tasks:
  - name: task1
    shell: "ls /test"
    register: returnmsg
    ignore_errors: true
  - name: task2
    debug:
      msg: "目录存在"
    when: returnmsg.rc == 0
  - name: task3
    debug:
      msg: "目录不存在"
    when: returnmsg.rc != 0

示例4:

  • 判断ansible主机上的路径:(该方法不能判断远程主机上的目录是否存在)
    • file:判断路径是否是一个文件,如果路径是一个文件则返回真
    • directory:判断路径是否是一个目录,如果路径是一个目录则返回真
    • link:判断路径是否是一个软链接,如果路径是一个软链接则返回真
    • mount:判断路径是否是一个挂载点,如果路径是一个挂载点则返回真
    • exists:判断路径是否存在,如果路径存在则返回真
  • 根据ansible主机上的目录是否存在,判断任务是否执行
    • 判断不存在的方法是when: testpath is not exists
---
- hosts: test
  gather_facts: no
  vars:
    testpath: /testdir
  tasks:
  - debug:
      msg: "file exist"
    when: testpath is exists

示例5:

  • 判断变量
    • defined :判断变量是否已经定义,已经定义则返回真
    • undefind:判断变量是否已经定义,未定义则返回真
    • none :判断变量值是否为空,如果变量已经定义,但是变值为空,则返回真
---
- hosts: test
  gather_facts: no
  vars:
    testvar1: "test"
    testvar2:
  tasks:
  - debug:
      msg: "variable is defined"
    when: testvar1 is defined
  - debug:
      msg: "variable is undefined"
    when: testvar2 is undefined
  - debug:
      msg: "variable is defined but there is no value"
    when: testvar3 is none

示例6:

  • 根据任务的执行状态进行判断
    • success或succeeded:通过任务的返回信息判断任务的执行状态,任务执行成功则返回真
    • failure或failed:通过任务的返回信息判断任务的执行状态,任务执行失败则返回真
    • change或changed:通过任务的返回信息判断任务的执行状态,任务执行状态为changed则返回真
    • skip或skipped:通过任务的返回信息判断任务的执行状态,当任务没有满足条件,而被跳过执行时,则返回真
---
- hosts: test
  gather_facts: no
  vars:
    doshell: "yes"
  tasks:
  - shell: "cat /testdir/123"
    register: returnmsg
    ignore_errors: true
  - debug:
      msg: "success"
    when: returnmsg is success
  - debug:
      msg: "failed"
    when: returnmsg is failure
  - debug:
      msg: "changed"
    when: returnmsg is change
  - debug:
      msg: "skip"
    when: returnmsg is skipped

示例7:

  • 判断字母包含的字符串是否是纯大/小写
    • lower:判断包含字母的字符串中的字母是否是纯小写,字符串中的字母全部为小写则返回真
    • upper:判断包含字母的字符串中的字母是否是纯大写,字符串中的字母全部为大写则返回真
---
- hosts: test
  gather_facts: no
  vars:
    str: "abc"
  tasks:
  - debug:
      msg: "This string is all lowercase"
    when: str is lower

示例8:

  • 根据数据的奇偶性进行判断:
    • even:判断数值是否是偶数,是偶数则返回真
    • odd:判断数值是否是奇数,是奇数则返回真
    • divisibleby(num):判断是否可以整除指定的数值,如果除以指定的值以后余数为0,则返回真
---
- hosts: test
  gather_facts: no
  vars:
    num1: 4
    num2: 9
  tasks:
  - debug:
      msg: "an even number"
    when: num1 is even
  - debug:
      msg: "can be divided exactly by"
    when: num2 is divisibleby(3)

示例9:

  • string:判断是否是字符串(注意,数字加上引号就是字符串了)
  • number:判断是否是数字(注意,数字加上引号就不是数值了,小数也是数值)
---
- hosts: test
  gather_facts: no
  vars:
    testvar1: 1
    testvar2: a
  tasks:
  - debug:
      msg: "This variable is a number"
    when: testvar1 is number
  - debug:
      msg: "This variable is a string"
    when: testvar2 is string

示例10:

  • 检查一个应用的运行状态,并判断返回的状态值,当状态为“ready”时,再执行下一步操作。
- command: my-app --status
  register: myapp_result
- command: do-something-to-my-app
  when: "'ready' in myapp_result.stdout"

示例:

  • 当centos版本不同时,nginx使用不同的配合模板
]# cat nginx-playbook.yaml
---
- hosts: all
  tasks:
  - name: 安装Nginx
    yum: state=present name=nginx
    tags:
    - install-nginx
  - name: 复制配置文件 centos7
    template: src=nginx-7.conf.j2 dest=/etc/nginx/nginx.conf
    when: ansible_distribution_major_version == "7"    #使用内置变量,判断centos的版本
    notify: restart nginx    #当触发handlers,调用restart nginx
    tags:
    - config
  - name: 复制配置文件 centos6
    template: src=nginx-6.conf.j2 dest=/etc/nginx/nginx.conf
    when: ansible_distribution_major_version == "6"
    notify: restart nginx    #当触发handlers,调用restart nginx
    tags:
    - config
  - name: 检查nginx运行状态,并设置开机启动
    service: name=nginx state=started enabled=yes
    tags:
    - start
  handlers:
  - name: restart nginx
    service: name=nginx state=restarted

5.2、迭代(with_items)

  • 当需要重复执行某类任务时,可以使用with_items指定要迭代的元素。
    • 并使用固定变量名"item"引用with_items指定的元素。

示例:

---
- hosts: all
  tasks:
  - name: 创建目录
    file: state=directory path=/{{ item }}
    with_items:
    - test1
    - test2
  - name: 复制文件
    copy: src={{ item.src }} dest={{ item.dest }}
    with_items:
    - {src: "/etc/ansible/ansible.cfg", dest: "/test1/"}
    - src: "/etc/ansible/hosts"
      dest: "/test2/"

5.3、ignore_errors条件判断

  • 在有些情况下,一些必须运行的命令或脚本会报一些错误,而这些错误并不一定真的说明有问题,但是会给接下来要运行的任务造成困扰,甚至直接导致Playbook运行中断。
  • 可以在相关任务中添加“ignore_errors: true”来屏蔽所有错误信息,Ansible也将视该任务运行成功,不再报错,这样就不会对接下来要运行的任务造成额外困扰。
  • 注意,不要过度依赖ignore_errors,因为它会隐藏所有的报错信息。

5.4、changed_when和failed_when

  • 对于Ansible来说,很难判断一个命令的运行结果是否符合我们的实际预期,尤其是command模块和shell模块,返回的状态changed永远是true。
  • 可以使用changed_when语句和failed_when语句对来对命令运行的结果进行判断。

1、changed_when

  • 当命令执行成功:
    • 当changed_when为真时,则通知ansible该任务改变了机器的现有状态。
    • 当changed_when为假时,则通知ansible该任务没有改变了机器的现有状态。

示例:

  • 当任务执行成功,且'bytes from'不在stdout(返回信息)中时,通知ansible该任务改变了机器的现有状态。
---
- hosts: all
  tasks:
  - name: 复制文件
    shell: ping -c 1 10.1.1.12
    register: out
    changed_when: "'bytes from' not in out.stdout"

2、failed_when

  • 当命令执行失败:
    • 当failed_when为假时,则通知ansible该任务运行成功。
    • 当failed_when为真时,则通知ansible该任务运行失败。

示例:

  • 当任务执行失败,且'无法创建普通文件'不在stderr(返回信息)中时,通知ansible该任务运行失败。
- hosts: all
  tasks:
  - name: 复制文件
    shell: cp /etc/passwd /test/
    register: err
    failed_when: err.stderr and '无法创建普通文件' not in err.stderr

5.5、任务间流程控制

1、任务委托

  • 默认情况下,playbook中的所有任务会在所有指定的机器(主机清单)上面执行,但有时候需要某个任务在特定的主机上运行,而非一开始指定的所有主机。
    • 例如给某个机器发送通知或向监控服务器中添加被监控主机,这个时候任务就需要在特定的主机上运行,而非一开始指定的所有主机。
  • 可以使用“delegate_to”关键字让任务在指定的机器上执行,而其他任务还是在所有指定的机器上执行。
    • 注意,该任务依然会执行多次(如果开始时指定了多台机器),每一台机器执行该任务时都会转向委托的机器。
  • “delegate_to”可以结合“run_once: true”使用,该任务仅执行一次。
    • 当仅指定了“run_once: true”,没有指定“delegate_to”时,只在第一台机器上执行。
  • 默认情况下, 委托任务的facts是inventory_hostname中主机的facts, 而不是被委托机器的facts。在ansible 2.0中, 可以设置“delegate_facts: true”让任务去收集被委托机器的facts。

示例1:

---
- hosts: all
  tasks:
  - name: 添加到所有指定的机器
    shell: 'echo "10.1.1.11 test.hengha11.com" >> /etc/hosts'
  - name: 只添加到10.1.1.13
    shell: 'echo "10.1.1.12 test.hengha12.com" >> /etc/hosts'
    delegate_to: 10.1.1.11
    run_once: true
    delegate_facts: true

示例2:

  • “delegate_to: 127.0.0.1”指定ansible主机。
    • 还可以使用local_action方法代替。
- name: Remove server from load balancer.
  command: remove-from-lb {{ inventory_hostname }}
  delegate_to: 127.0.0.1
- name: Remove server from load balancer.
  local_action: command remove-from-lb {{ inventory_hostname }}

2、任务暂停

  • 在某些情况下,某些任务需要等待一些状态的恢复,比如某一台机器或者应用刚刚重启,需要等待其端口开启,此时不得不将正在运行的任务暂停,等待其条件满足。

 示例:

  • 这个任务将会每10s检查一次主机10.1.1.13的80端口是否开启,如果超过300s,80端口仍未开启,将会返回失败信息。
---
- hosts: all
  tasks:
  - name: Wait for port 80 to become open
    wait_for:
      host: 10.1.1.13
      port: 80
      delay: 10
      timeout: 30
    run_once: true

1

#                                                                                                                         #
posted @ 2022-11-02 14:47  麦恒  阅读(255)  评论(0编辑  收藏  举报