Ansible-Playbook详解

Ansible-Playbook详解

  • [1 Playbook介绍](#1 Playbook介绍)
    • [1.1 Playbook概念](#1.1 Playbook概念)
    • [1.2 Playbook组成](#1.2 Playbook组成)
  • [2 Playbook组件](#2 Playbook组件)
    • [2.1 hosts组件](#2.1 hosts组件)
    • [2.2 tasks列表和task组件](#2.2 tasks列表和task组件)
    • [2.3 remote_user组件](#2.3 remote_user组件)
    • [2.4 ignore_errors组件](#2.4 ignore_errors组件)
    • [2.5 handlers和notify组件](#2.5 handlers和notify组件)
    • [2.6 tags组件](#2.6 tags组件)
  • [3 Variables变量](#3 Variables变量)
    • [3.1 使用 setup 模块中变量](#3.1 使用 setup 模块中变量)
    • [3.2 register注册变量](#3.2 register注册变量)
    • [3.3 在playbook命令行中定义变量](#3.3 在playbook命令行中定义变量)
    • [3.4 在playbook文件中定义变量](#3.4 在playbook文件中定义变量)
    • [3.5 playbook公共的变量文件](#3.5 playbook公共的变量文件)
    • [3.6 在主机清单中定义主机和主机组的变量](#3.6 在主机清单中定义主机和主机组的变量)
    • [3.7 针对当前项目的主机和主机组的变量](#3.7 针对当前项目的主机和主机组的变量)
  • [4 Templates模板](#4 Templates模板)
  • [5 流程控制](#5 流程控制)
    • [5.1 循环迭代 loop](#5.1 循环迭代 loop)
    • [5.2 条件判断 when](#5.2 条件判断 when)
    • [5.3 分组 block](#5.3 分组 block)
    • [5.4 changed_when](#5.4 changed_when)
    • [5.5 滚动执行](#5.5 滚动执行)
    • [5.6 委派至其它主机执行](#5.6 委派至其它主机执行)
    • [5.7 只执行一次 run_once](#5.7 只执行一次 run_once)
    • [5.8 yaml文件的相互调用](#5.8 yaml文件的相互调用)
  • [6 Roles角色](#6 Roles角色)

1 Playbook介绍

1.1 Playbook概念

在 ansible 中,较简单的任务,我们可以直接调用单个模块来完成,但是,如果遇到复杂的需求,需要调用大量模块才能完成一个需求,或多个任务间有依赖的时候,使用单条命令就特别不方便,这种情况下,我们就可以使用 playbook 来实现这种需求

在 ansible 中,我们写好 playbook,服务器作演员,由服务器根据我们编排的剧本,完成环境安装,服务部署,系统搭建,状态检测等各种各样的功能。

playbook 文件规范

  • 扩展名为 yaml 或 yml
  • 第一行用 "---" (英文输入法下的中划线) 表示 yaml 的开始,一般不写,如果有多个 yaml 文件合并,则可用此标识每个 yaml 文件内容,使用 # 作为注释
  • 大小写敏感
  • 缩进符要统一,用空格键缩进,不要用 tab
  • 缩进空格的数量不重要,但是,相同层级的左边缩进,要保持一致,要对齐,就是说,同样的缩进,代表同样的级别
  • 使用 "-"(英文输入法下的中划线)+一个空格 来表示单个列表项
  • 使用 ":"(冒号)+空格 来表示一个键值对
  • 使用 "{}"(花括号) 来表示一个键值表
  • 一个 name 只能有一个 task

1.2 Playbook组成

  • 一个 playbook(剧本)文件是一个YAML语言编写的文本文件
  • 通常一个playbook只包括一个play,但可以包括多个Play
  • 一个play的主要包括两部分: 主机和tasks. 即实现在指定一组主机上执行一个tasks定义好的任务列表。
  • 一个tasks中可以有一个或多个task任务
  • 每一个Task本质上就是调用ansible的一个module
  • 在复杂场景中,一个playbook中也可以包括多个play,实现对多组不同的主机执行不同的任务

2 Playbook组件

playbook 是由一个或多个 "play" 组成的列表。playbook 的主要功能在于,将多个 play 组织在一个playbook 文件中,让多台预定义的主机按照 playbook 中编排的内容来完成一系列复杂的功能。一个 playbook 至少要包含 name 和 tasks 两部份。

2.1 hosts组件

Hosts:playbook中的每一个play的目的都是为了让特定主机以某个指定的用户身份执行任务。hosts用于指定要执行指定任务的主机列表,须事先定义在主机清单中,其值可以是通配符,主机或组,可以用 -i 选项指定自定义的主机清单文件

yaml 复制代码
- hosts: 10.0.0.206 			#IP写法
- hosts: 192.168.1.*			#通配符
- hosts: node.linux-magedu.com 	#主机名写法
- hosts: group1 				#分组写法
- hosts: group1:group2 			#分组写法,或者关系,两个分组并集
- hosts: group1:&group2 		#分组写法,与关系,两个分组交集
- hosts: group1:!group2 		#分组写法,非关系,两个分组差集

2.2 tasks列表和task组件

tasks:定义要在远程主机上执行的任务列表,play的主体部分,各任务按顺序在 hosts 中指定的主机上执行,即所有主机做完当前任务,才会开始下一个任务

task:执行指定模块,后面跟上预定的参数,参数中可以使用变量。每个task是一个字典,一个完整的代码块功能需最少元素需包括 name 和 task,一个name只能包括一个task

  • 模块的执行是幂等的,这意味着多次执行是安全的,因为其结果均一致,如果在执行过程中发生错误,则会全部回滚(包括前面己执行成功的)
  • 每个 task 都应该定义name,用于playbook的执行结果输出,建议其内容能清晰地描述任务执行步骤
  • 如果没有指定 name ,则 action 的结果将用于输出
yaml 复制代码
- hosts: group
  tasks:
  - name: test
    command: id

2.3 remote_user组件

remote_user:指定任务在远程主机上执行时所使用的用户,可以是任意用户,前提是用户是存在的,默认 root

  • 可用于 Host 和 task 中。也可以通过指定其通过sudo的方式在远程主机上执行任务,其可用于play全局或某任务;此外,甚至可以在sudo时使用 sudo_user 指定sudo时切换的用户
  • 可以全局指定,也可以在特定 task 中指定,切换用户执行需要先完成登录校验或者用 -k 选项手动输入ssh 密码
yaml 复制代码
- hosts: group1 			#指定主机分组
  gather_facts: no 			#不收集主机信息
  remote_user: tom 			#全局设置远程执行用户
  tasks: 					#task列表
	- name: task1 			#task名称,此task以tom身份认行
  	  shell: id 			#具体执行的模块和参数
 
	- name: task2 			#task名称,此task以jerry身份连接主机执行
  	  shell: id 			#具体执行的模块和参数  
      remote_user: jerry
  
 	- name: task3 			#task名称,此task以jerry身份运行sudo到root执行
  	  shell: id 			#具体执行的模块和参数  
  	  remote_user: jerry
  	  sudo: yes				#默认sudo为root
  
 	- name: task4 			#task名称,此task以jerry身份运行sudo到tom执行
      shell: id 			#具体执行的模块和参数  
   	  remote_user: jerry
  	  sudo: yes 			#默认sudo为root
  	  sudo_user: tom		#sudo为tom

2.4 ignore_errors组件

在同一个 playbook中,如果一个task出错,默认将不会继续执行后续的其它 task

利用 ignore_errors: yes 可以忽略此task的错误,继续向下执行playbook其它 task,此项也可以配置为全局选项

yaml 复制代码
- hosts: group
  ignore_errors: yes  # 全局配置
  tasks:
  - name: task-1
    command: id1
    ignore_errors: yes  # 忽略此task的错误

2.5 handlers和notify组件

notify 和 handlers:配合使用,某任务的状态在运行后为changed时,可通过"notify"通知给相应的 handlers 任务,可以达到一种触发调用的效果,(类似于函数调用或触发器),由特定条件触发的操作,满足条件方才执行,否则不执行

  • handlers本质上也是 tasks,其中的 task 与前述的 task 并没有本质上的不同,只不过 handlers 中定义的 task,不会主动执行,只有notify关注的资源(task)发生变化时, notify去通知相应的handelrs,才会调用handler中定义的 task,没有改变则不会触发handlers
  • handers中的 task,是在playbook的 tasks 中所有的 task 都执行完成之后才调用,就算多个task触发了相同的handlers, 此handlers也仅会在所有task结束后运行一次,这样是为了避免多次触发同一个hander
  • handlers是在所有前面的 tasks 都成功执行才会执行,如果前面任何一个task失败,会导致handler跳过执行

范例:当文件发生变化时,重启服务

yaml 复制代码
- hosts: rocky
  tasks:
  - name: config file
    copy: src=/root/nginx_v2/linux.magedu.com.conf dest=/etc/nginx/conf.d/
    notify: restart service 	#当配置文件发生了变化时,通知重启服务,与handlers的name相匹配
  handlers: 
  - name: restart service
   	service: name=nginx state=restarted

在 task 中使用 notify 来调用 handlers 中的任务,如果该 task 执行成功,但在该 task 之后的其它 task 执行失败,则会导致 notify 通知的 handlers 中的任务不会被调用,如果不论后面的task成功与否,都希望handlers能执行, 可以使用 force_handlers: yes 强制执行handler

force_handlers 是以整个 playbook 的角度来理解的,在 playbook 中,如果有 task 执行失败,那整个 playbook 也执行失败,就算后面有task,也不会继续执行,那么对应的handler也就不会执行了,所以force_handlers保证的是己成功执行的 task 对应的 handlers 一定会被执行。

yaml 复制代码
- hosts: localhost
  force_handlers: yes  #force_handlers 可以保证handlers-1被执行,因为task-1任务执行成功
  tasks:
  - name: task-1
    shell: echo "task-1"
    notify: handlers-1

  - name: task-2
    shell: echoooooo "task-2"
    notify: handlers-2  #task-2执行失败,handlers-2不会执行

  - name: task-3
    shell: echo "task-3"
    notify: handlers-3  #因为task-2执行失败,playbook不会继续执行task-3,所以不论成功与否,handers-3都不会执行

  handlers:
  - name: handlers-1
    debug: msg="handlers-1"

  - name: handlers-2
    debug: msg="handlers-2"

  - name: handlers-3
    debug: msg="handlers-3"

2.6 tags组件

tags:还可以通过"tags"给 task 打标签,可在ansible-playbook命令上使用 -t 指定进行调用,定义哪些代码内容可以被忽略。ansible虽然具有幂等性,会自动跳过没有变化的部分,即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过tags执行特定的 task

  • 可以一个task对应多个tag,也可以多个task对应同一个tag
  • 还有另外3个特殊关键字用于标签,tagged,untagged 和 all,tagged 表示所有被 tag 标记的任务,untagged 表示所有没有被标记的任务,all 表示所有任务。
yaml 复制代码
- hosts: all
  tasks:
  - name: configure file
    copy:
      src: files/httpd.conf
      dest: /etc/httpd/conf/
    tags:
    - conf

3 Variables变量

Playbook中同样也支持变量,通过 variables 定义 playbook 运行时需要的使用的变量,有多种定义方式,内置变量或自定义变量

  • 变量名:仅能由字母、数字和下划线组成,区分大小写,且只能以字母开头
  • 变量定义:variable=valuevariable: value
  • 变量调用方式:通过 {``{ variable_name }} 调用变量,且变量名前后建议加空格,有时用 "{``{ variable_name }}" 才生效

变量优先级(从高到低)):-e 选项定义变量 --> playbook中vars_files --> playbook中vars变量定义 --> host_vars/主机名文件 --> 主机清单中主机变量--> group_vars/主机组名文件 --> group_vars/all文件 --> 主机清单组变量

3.1 使用 setup 模块中变量

setup:默认会自动调用的模块,获取远程主机的一些信息都是放在 facts 变量中的,在playbook中,可以直接使用 gather_facts 选项来获得目标主机的信息,默认是yes

facts中所有可用变量请参考官方文档:ansible_facts

yaml 复制代码
- hosts: rocky
  gather_facts: yes
  tasks:
  - name: show-facts
    debug: msg={{ ansible_facts }}
  - name: show-facts-hostname
    debug: msg={{ ansible_hostname }}
  - name: show-facts-ipv4-A
    debug: msg={{ ansible_facts["eth0"]["ipv4"]["address"] }}--{{ ansible_facts.eth0.ipv4.address }}
  - name: show-facts-ipv4-B
    debug: msg={{ ansible_eth0["ipv4"]["address"] }}--{{ ansible_eth0.ipv4.address }}
  - name: show-facts-ipv4-C
    debug: msg={{ ansible_default_ipv4["address"] }}--{{ ansible_default_ipv4.address }}

每次执行playbook,默认会收集每个主机的所有facts变量,每次执行都需要消耗一定的资源和时间,在不需要facts变量时可以禁用该项 gather_facts: no 来加快 playbook 的执行效率。

如果又需要使用 facts 中的内容,还希望执行的速度快,这时候可以设置 facts 的缓存,可以在 ansible 的配置文件中设置在本地做缓存,也可以将 facts 变量信息存在 redis 服务器中,以便在频繁执行时减少资源消耗

bash 复制代码
[defaults]
gathering=smart|implicit|explicit 		  #facts策略,默认smart,新版默认implicit
fact_caching_timeout=86400 			      #本地缓存时长,默认86400S
fact_caching=memeory|jsonfile|redis 	  #缓存类型,默认memory
fact_caching_connection=/path|REDIS       #缓存路径或redis信息

gathering 参数说明

  • smart:远程主机信息如果有本地缓存,则使用缓存,如果没有,就去抓取,再存到缓存中,下次就直接使用缓存信息
  • implicit:不使用缓存,每次都抓取新的,可以使用 gather_facts: no 来限制不去抓取新的
  • explicit:不收集主机信息,除非在playbook中用 gather_facts: yes 显式指定

fact_caching 参数说明

  • memeory:只在当前有效,下次还是要重新抓取
  • jsonfile:本地缓存,fact_caching_connection 需要指定本地缓存路径
  • redis:使用 redis 缓存,fact_caching_connection 的格式为 REDIS_IP:REDIS_PORT:REDIS_DB_NUMBER[:REDIS_PASSWORD]

3.2 register注册变量

在playbook中可以使用register将捕获命令的输出保存在临时变量中,方便后续调用此变量,比如可以使用debug模块进行显示输出

yaml 复制代码
- hosts: dbsrvs
  tasks:
  - name: get variable
    shell: hostname
    register: name
      
  - name: "print variable"
    debug:
      msg: "{{ name }}"          		 #输出register注册的name变量的全部信息,注意变量要加" "引起来
      #msg: "{{ name.cmd }}"       		 #显示命令
      #msg: "{{ name.rc }}"       		 #显示命令成功与否,相当于$?
      #msg: "{{ name.stdout }}"     	 #显示命令的输出结果为字符串形式,所有结果都放在一行里显示,适合于结果是单行输出
      #msg: "{{ name.stdout_lines }}"  	 #显示命令的输出结果为列表形式,逐行标准输出,适用于多行显示
      #msg: "{{ name['stdout_lines'] }}" #显示命令的执行结果为列表形式,和效果上面相同
      #msg: "{{ name.stdout_lines[0] }}" #显示命令的输出结果的列表中的第一个元素

范例:修改主机名形式为 web_<随机字符>

yaml 复制代码
- hosts: webservers
  tasks:
  - name: generate random
    shell: openssl rand -base64 12 | tr -dc '[:alnum:]'
    register: num

  - name: show random
    debug:
      msg: "{{ num }}"

  - name: change hostname
    hostname:
      name: web-{{ num.stdout }}

3.3 在playbook命令行中定义变量

bash 复制代码
#直接在命令行中指定  
[root@ubuntu ~]# ansible-playbook -e uname=tom -e age=18 -e gender=M var1.yaml

#多个变量一次定义
[root@ubuntu ~]# ansible-playbook -e "uname=tom age=18 gender=M" var1.yaml

#从文件中读取
[root@ubuntu ~]# cat var.txt
uname: jerry
age: 20
gender: F

#文件前面要加 @
[root@ubuntu ~]# ansible-playbook -e "@/root/var.txt" var1.yaml
[root@ubuntu ~]# ansible-playbook -e "@./var.txt" var1.yaml

3.4 在playbook文件中定义变量

此方式定义的是私有变量,即只能在当前playbook中使用,不能被其它Playbook共用

yaml 复制代码
- hosts: webservers
  remote_user: root
  vars:
    username: user1
    groupname: group1
  tasks:
  - name: create group {{ groupname }}
    group: name={{ groupname }} state=present
      
  - name: create user {{ username }}
    user: name={{ username }} group={{ groupname }} state=present

范例:变量的相互调用

yaml 复制代码
- hosts: webservers
  vars:
    suffix: "txt"
    file: "{{ ansible_nodename }}.{{suffix}}"
  tasks:
  - name: test var
    file: path="/data/{{file}}" state=touch

3.5 playbook公共的变量文件

可以在一个独立的 yaml 文件中定义公共变量,在其它的playbook文件中可以引用变量文件中的变量

此方式比playbook中定义的变量优化级高

yaml 复制代码
[root@ansible ~]#cat vars.yml
var1: httpd
var2: nginx
   
[root@ansible ~]#cat var.yml
- hosts: web
  vars_files:
    - vars.yml  # 可以使用相对路径和绝对路径的写法
  tasks:
  - name: create httpd log
    file: name=/app/{{ var1 }}.log state=touch
      
  - name: create nginx log
    file: name=/app/{{ var2 }}.log state=touch

3.6 在主机清单中定义主机和主机组的变量

主机变量:在 inventory 主机清单文件中为指定的主机定义变量以便于在playbook中使用

bash 复制代码
[webservers]
www1.wang.org http_port=80 maxRequestsPerChild=808
www2.wang.org http_port=8080 maxRequestsPerChild=909

主机组变量:在inventory 主机清单文件中赋予给指定组内所有主机上的在playbook中可用的变量,如果和主机变量是同名,优先级低于主机变量

bash 复制代码
[webservers]
10.0.0.8 
10.0.0.18

[webservers:vars]	# webservers组所拥有
ntp_server=ntp.wang.org
nfs_server=nfs.wang.org

[all:vars]	# 所有主机所拥有
ntp_server=ntp.wang.org
nfs_server=nfs.wang.org

3.7 针对当前项目的主机和主机组的变量

上面的方式是针对所有项目都有效,而官方更建议的方式是使用ansible特定项目的主机变量和组变量

生产建议在每个项目对应的目录中创建额外的两个变量目录,分别是host_vars和group_vars

  • host_vars下面的文件名和主机清单主机名一致,针对单个主机进行变量定义
    • 格式:host_vars/hostname
  • group_vars下面的文件名和主机清单中组名一致,针对单个组进行变量定义
    • 格式:group_vars/groupname
  • group_vars/all 文件内定义的变量对所有组都有效
yaml 复制代码
[root@ubuntu ~]#cat hostname.yaml
- hosts: all
  tasks:
  - name: change hostname
    hostname: name=web-{{ id }}.{{ domain }}
      
[root@ubuntu ~]#cat /etc/ansible/hosts
[webserver]
10.0.0.8
10.0.0.18

[appserver]
10.0.0.28
10.0.0.38

[root@ubuntu ~]#cat host_vars/10.0.0.8
id: 8
[root@ubuntu ~]#cat host_vars/10.0.0.18
id: 18
[root@ubuntu ~]#cat host_vars/10.0.0.28
id: 28
[root@ubuntu ~]#cat host_vars/10.0.0.38
id: 38

[root@ubuntu ~]#cat group_vars/webserver
domain: wang.org

#不属于webserver组的使用
[root@ubuntu ~]#cat group_vars/all
domain: wu.org

[root@ubuntu ~]#ansible-playbook hostname.yaml

#最终结果是
[[email protected] ~]#hostname
web-8.wang.org
[[email protected] ~]#hostname
web-18.wang.org
[[email protected] ~]#hostname
web-28.wu.org
[[email protected] ~]#hostname
web-38.wu.org

4 Templates模板

templates:模板模块,配合变量使用的模板文件,可以实现批量执行时,在不同主机上用同一个模板生成不同配置文件的功能,可替换模板文件中的变量并实现一些简单逻辑的文件,用于根据每个主机的不同环境而为生成不同的文件。模板文件中支持嵌套jinja2语言的指令,来实现变量、条件判断、循环等功能。

template文件建议存放在和 playbook 文件同级目录的 templates 目录下,且以 .j2 结尾,这样在 playbook 中使用模板文件时,就不需要指定模板文件的路径了。

范例:利用template生成不同的nginx配置文件

yaml 复制代码
[root@ansible ~]#mkdir templates
[root@ansible ~]#vim templates/nginx.conf.j2
......
worker_processes {{ ansible_processor_vcpus*2 }};  #根据不同主机的CPU核数生成不同的配置
......

[root@ansible ~]#vim temnginx.yml
---
- hosts: webservers
  remote_user: root
  tasks:
  - name: install nginx
    yum: name=nginx
      
  - name: template config to remote hosts
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf	
      
  - name: start service
    service: name=nginx state=started enabled=yes

template中也可以使用流程控制 for 循环和 if 条件判断,实现动态生成文件功能

for 循环格式

python 复制代码
{% for i in EXPR %}
	...
{% endfor %}

if 条件判断格式

python 复制代码
#单分支
{% if EXPR %}
...
{% endif %}

#双分支
{% if EXPR %}
...
{% else %}
...
{% endif %}

#多分支
{% if EXPR %}
...
{% elif EXPR %}
...
{% else %}
...
{% endif %}

playbook 文件 templnginx5.yml

yaml 复制代码
- hosts: webservers
  remote_user: root
  vars:
    nginx_vhosts:
    - listen: 8080
      root: "/var/www/nginx/web1/"
    - listen: 8080
      server_name: "web2.wang.org"
      root: "/var/www/nginx/web2/"
    - listen: 8080
      server_name: "web3.wang.org"
      root: "/var/www/nginx/web3/"
  tasks:
  - name: template config
    template:
      src: nginx.conf5.j2
      dest: /data/nginx5.conf

模版文件 templates/nginx.conf5.j2

python 复制代码
{% for vhost in nginx_vhosts %}
server {
  listen {{ vhost.listen }};
  {% if vhost.server_name is defined %}
server_name {{ vhost.server_name }};
  {% endif %}
root {{ vhost.root }};
}
{% endfor %}

生成结果

bash 复制代码
server {
  listen 8080;
  root /var/www/nginx/web1/;
}
server {
  listen 8080;
  server_name web2.wang.org;
  root /var/www/nginx/web2/;
}
server {
  listen 8080;
  server_name web3.wang.org;
  root /var/www/nginx/web3/;
}

5 流程控制

5.1 循环迭代 loop

ansible 的 task 中,如果要重复执行相同的模块,则可以使用循环的方式来实现

对于迭代项的引用,要使用固定内置变量 item 来引用,这是固定写法。

范例:安装多个安装包

yaml 复制代码
- hosts: webservers
  tasks:
    - name: Install Httpd and Php-fpm Package
      yum:
        name: "{{ item }}"
        state: latest
      loop:
        - httpd
        - php-fpm

在迭代中,还可以嵌套子变量,关联多个变量在一起使用

范例:批量修改用户密码

yaml 复制代码
- hosts: ssh-host
  tasks:
    - name: change user passwd
      user:
        name: "{{ item.name }}"
        password: "{{ item.chpass | password_hash('sha512') }}"
        update_password: always
      loop:
        - { name: 'root', chpass: '123456' }
        - { name: 'app', chpass: '654321' }

5.2 条件判断 when

可以实现条件测试。例如可以根据变量、facts或此前任务的执行结果来做为某task执行与否的前提,通过在task后添加when子句即可使用 jinja2 的语法格式条件测试

范例: 判断服务状态决定是否重新启动

yaml 复制代码
- hosts: webservers
  tasks:
    - name: Check nginx Service
      command: systemctl is-active nginx
      ignore_errors: yes
      register: check_nginx

    - name: debug var
      debug:
        var: check_nginx

    - name: Httpd Restart
      service:
        name: nginx
        state: restarted
      when: check_nginx.rc == 0  #如果nginx服务没启动,就重新启动
      #failed_when: check_nginx.rc != 0 	#条件不成立才执行,和when相反

范例: 多条件判断写法

yaml 复制代码
- hosts: all
  tasks:
  - name: "shut down CentOS 6 and Debian 7 systems"
    shell:
      cmd: /sbin/shutdown -h now
    when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "6") or (ansible_facts['distribution'] == "Debian" and ansible_facts['distribution_major_version'] == "7")
    
  - name: "shut down CentOS 7 systems"
    shell: /sbin/shutdown -h now
    when:  # when的列表形式表示 and 关系
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "7"

其他判断条件

  • 操作系统版本判断的第二种写法:ansible_distribution_file_variety == "Debian"
  • 操作系统主版本号判断的第二种写法:ansible_distribution_major_version == "7"
  • 判断变量是否被定义:name is undefined,name指定要判断的变量名
  • 对主机名进行条件判断:ansible_fqdn is match ("web*")

5.3 分组 block

当想在满足同样条件下,执行多个任务时,就需要分组。而不再针对每个任务都用相同的when来做条件判断

使用 block 可以对 task 任务进行分组,将多个 task 任务放到一个 block 下,可以在写一个 when 判断的情况下调用多个 task 任务

yaml 复制代码
- hosts: localhost
  tasks:
  - block:
      - debug: msg="first"
      - debug: msg="second"
    when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "8"
    
   
#相当于下面写法
- hosts: localhost
  tasks:
  - debug: msg="first"
   	when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "8"
 
  - debug: msg="second"
   	when:
      - ansible_facts['distribution'] == "CentOS"
      - ansible_facts['distribution_major_version'] == "8"

5.4 changed_when

第一个作用:关闭 changed 状态

只有在 task 的执行结果返回状态为 changed 的时候,我们才认为该 task 是真实执行了,在远程主机上产生了数据变化,但是在 ansible 中,不是所有模块都具有幂等性,对于某些不会产生数据变化的 task,ansible 也会给出 changed 输出。所以当确定某个task不会对被控制端产生数据变化时,而不想显示的是黄色的changed状态,可以通过 changed_when: false 来避免这一情况

yaml 复制代码
- hosts: localhost
  tasks:
  - name: task-1
    shell: id
    changed_when: false

第二个作用:自定义条件判断

Ansible 在执行任务之后,会依据任务的执行状况判断该任务是否使系统状态发生了改变,并且在输出结果里标记为 changed 或者 ok。不过,有时候 Ansible 默认的判断规则不符合我们的需求,这时就可以使用 changed_when 来自定义判断逻辑。

yaml 复制代码
- hosts: webservers
  tasks:
    - name: check config
      shell: /usr/sbin/nginx -t
      register: check_nginx_config
      changed_when: "'successful' in check_nginx_config.stdout"  #自定义条件判断任务是否发生了变更

5.5 滚动执行

默认情况下,Ansible将尝试并行管理playbook中所有的机器,如果一个 playbook 中有多个 task,在有多台远程主机的情况下,需要在所有远程主机上执行完当前的 task 之后才执行下一个 task,如果主机过多,或者需要执行的task 比较消耗时间,则会导致所有主机都处于一个执行中状态。如下图,先在 A,B,C 三台主机上执行 Task-1,再在三台主机上执行 Task-2

这样的话,在有些场景中是不利于的。比如升级场景,我们希望是先A主机执行完所有task,再到B主机依次执行,就不会导致所有主机都处于一个执行中状态,都无法对外提供服务。所以对于滚动更新用例,可以使用 serial 关键字定义Ansible一次应管理多少主机,还可以将 serial 关键字指定为百分比,表示每次并行执行的主机数占总数的比例

yaml 复制代码
- hosts: all
  serial: 2  #每次只同时处理2个主机
  #serial: "20%"  #每次只同时处理20%的主机
  gather_facts: false
  tasks:
    - name: task one
      command: hostname

    - name: task two
      command: hostname

5.6 委派至其它主机执行

利用委托技术,可以在非当前被控主机的其它主机上执行指定操作,即可以在非指定的主机上执行 task,委派时必须要有key验证

范例:将任务委派给指定的主机执行

yaml 复制代码
#在10.0.0.8上执行hostname -I,而非当前主机localhost
- hosts: localhost
  tasks:
    - name: show ip address
      command: hostname -I
      delegate_to: 10.0.0.8  #指定当前任务被委派给的目标主机
      delegate_facts: true  #收集被委派的目标主机的facts信息

范例: 将任务被委派给控制端ansible主机执行

yaml 复制代码
#在本地执行ifconfig,而非10.0.0.8,下面展示三种实现方式
- hosts: 10.0.0.8
  tasks:
    - name: show ip address
      local_action:  #旧语法,委派给控制端ansible主机执行
        module: command
        args: ifconfig

    - name: show hostname
      shell: hostname
      connection: local  #委派给控制端ansible主机执行

    - name: kernel version
      shell: uname -r
      delegate_to: localhost  #委派给控制端ansible主机执行

5.7 只执行一次 run_once

利用 run_once 指令可以只执行一次,而非在所有被控主机都执行,通常会在第一个匹配的主机上执行该任务

yaml 复制代码
- hosts: webservers
  tasks:
    - name: Run hostname command once
      command: hostname
      run_once: true

5.8 yaml文件的相互调用

利用 include 或 include_tasks 可以在某个 task 中调用其它的只有 task 内容的 yaml 文件,include 在 2.16 版本之后被弃用,建议使用 include_tasks 来实现包含。include_tasks 一次只能引用一个 yaml 文件

注意:一个task只有一个include_tasks生效,也可以配合when和变量一起使用

yaml 复制代码
#a.yml
- hosts: webservers
  tasks:
    - name: run a job
      command: wall run a job

    - name: excute b.yml
      include_tasks: b.yml  #调用b.yml执行task
    
#b.yml
- name: run b job
  command: wall run b job

import_playbook 合并多个 playbook 文件,将多个包含完整内容的 yaml 文件交由一个 yaml 统一调用

yaml 复制代码
[root@ansible ansible]#cat main.yml
- import_playbook: tasks1.yml
- import_playbook: tasks2.yml

[root@ansible ansible]#cat tasks1.yml
- hosts: webservers
  tasks:
  - name: run task1 job
  	command: wall run task1 job
  		
[root@ansible ansible]#cat tasks2.yml
- hosts: dbsrvs
  tasks:
  - name: run task2 job
  	command: wall run task2 job

6 Roles角色

角色是ansible自1.2版本引入的新特性,用于层次性、结构化地组织playbook。roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles只需要在playbook中使用 include 指令即可。简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,分门别类的管理起来,使其看起来更像一个项目,并可以便捷地 include 它们的一种机制,主要用来解决多文件之间的相互包含,引用,组合等问题

在Playbook中调用角色

一、直接调用

yaml 复制代码
- hosts: webservers
  remote_user: root
  roles:
	- mysql
	- memcached
	- nginx

二、传参

键role用于指定角色名称,后续的k/v用于传递变量给角色

yaml 复制代码
- hosts: all
  remote_user: root
  roles:
 	- role: mysql
  	  username: mysql

三、结合条件测试调用

yaml 复制代码
- hosts: all
  remote_user: root
  roles:
 	- role: nginx
 	  username: nginx
 	  when: ansible_distribution_major_version == '7'

四、结合tags调用

yaml 复制代码
- hosts: appsrvs
  remote_user: root
  tags: app
  roles:
    - role: nginx
      tags:
        - nginx
        - web

五、依赖其它角色,在meta目录下编写与该role相关的依赖角色

yaml 复制代码
dependencies:
  - role: nginx
  - role: php-fpm
  - role: mysql
相关推荐
chairon8 天前
Ansible:playbook的高级用法
linux·运维·服务器·ansible·apache
树下一少年8 天前
ansible+docker+docker-compose快速部署4节点高可用minio集群
docker·容器·ansible·docker-compose·minio集群
千航@abc9 天前
Ansible 实战:Roles,运维的 “魔法函数”
ansible·playbook·roles·角色·模版·剧本
一个高效工作的家伙9 天前
ansible可视化自动化平台-semaphore
ansible
树下一少年9 天前
通过ansible+docker-compose快速安装一主两从redis+三sentinel
redis·docker·ansible·sentinel·docker-compose
独隅10 天前
针对Ansible执行脚本时报错“可执行文件格式错误”,以下是详细的解决步骤和示例
运维·开发语言·ansible·lua·lua5.4
大小科圣10 天前
Ansible playbook
ansible
大小科圣11 天前
ansible条件判断及循环
ansible
狂奔solar12 天前
ansible-playbook 写arm版达梦7数据库的一键安装脚本
ansible