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

#最终结果是
[root@10.0.0.8 ~]#hostname
web-8.wang.org
[root@10.0.0.18 ~]#hostname
web-18.wang.org
[root@10.0.0.28 ~]#hostname
web-28.wu.org
[root@10.0.0.38 ~]#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
相关推荐
风清再凯2 天前
自动化工具ansible,以及playbook剧本
运维·自动化·ansible
IT乌鸦坐飞机2 天前
ansible部署数据库服务随机启动并创建用户和设置用户有完全权限
数据库·ansible·centos7
遇见火星15 天前
如何使用Ansible一键部署MinIO集群?
ansible
粥周粥15 天前
ANSIBLE
ansible
码农101号15 天前
Linux中ansible模块补充和playbook讲解
linux·运维·ansible
码农101号15 天前
Linux的Ansible软件基础使用讲解和ssh远程连接
ansible
烟雨书信17 天前
ANSIBLE运维自动化管理端部署
运维·自动化·ansible
碎碎-li17 天前
ANSIBLE(运维自动化)
运维·自动化·ansible
@donshu@20 天前
Linux运维-ansible-python开发-获取inventroy信息
linux·运维·ansible
Kendra91923 天前
Ansible
ansible