自动化运维-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中定义的变量。
- (1)在命令行中使用的变量。
- 变量引用:{{ 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
# #