Ansible 中的 Templates和流程控制

Ansible 中的 Templates

Ansible 中的模板

狭义来讲,就是一个特定后缀的文本文件,在使用时,可以根据此文件,将部份关健内容进行替换,生 成新的文件,以达到在不同主机中,使用不同配置的作用,其中的逻辑部份或动态代码,用 jinja2 来实 现。

广义来讲,要模板文件,变量,变量文件,参数,条件判断,流程控制,playbook 中的 template 模块 调用时相互配合,来实现预期的效果。

Jinjia2 语言

Jinja2 是 Python 下一个被广泛应用的模版引擎,他的设计思想来源于 Django 的模板引擎,并扩展了其 语法和一系列强大的功能。

Jinjia2 的特点

  • 沙箱中执行

  • 强大的 HTML 自动转义系统保护系统免受 XSS

  • 模板继承

  • 及时编译最优的 python 代码

  • 可选提前编译模板的时间

  • 易于调试,异常的行数直接指向模板中的对应行

  • 可配置的语法

jinjia2 中的数据类型

数据类型 说明
字符串 使用单引号或双引号引起来的
数值型 包括整数和浮点数,有小数点表示浮点数,在 Python中,12 和 12.0 的含义是不一样的
列表 [a,b,c,d]
元组 (a,b,c,d)
字典 {key1:val1,key2:val2,key3:val3}
布尔 true/false

jinjia2 中的运算符

bash 复制代码
#算术运算符
+ #加法
- #减法
* #乘法,两个数值相乘,也可用于字符串重复,{{ 'a'*10 }} 表示10个a
/ #除法,会保留小数
// #除法,只保留整数 {{ 20 // 7 }} 的值为2
% #求余
** #次方运算 {{ 2**3 }}表示 2的3次方,值为8


#比较运算符
== 	#等于
!= 	#不等于
> 	#大于
>= 	#大于或等于
< 	#小于
<= 	#小于或等于

#逻辑运算符
or 	#或运算
and #且运算
not #取反
true #真 
false #假

jinjia2 中的条件判断

If 语句

bash 复制代码
{% if EXPR %}
...
{% endif %}

{% if EXPR %}
...
{% else %}
...
{% endif %}

{% if EXPR %}
...
{% elif EXPR %}
...
{% else %}
...
{% endif %}

jinjia2 中的流程控制

For 循环

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

Template 的使用规范

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

bash 复制代码
.
├── templates
│   └── test.j2 #模板文件
└── test.yaml #playbook文件

Template 的基本替换

bash 复制代码
.
├── templates
│   └── test-1.j2
└── test-1.yaml


cat test-1.yaml

- hosts: group1
  gather_facts: yes
  vars:
   var1: 'abcd'
   var2: 3
   var3: 3.14
  tasks:
    - name: template-task-1
     template: src=test-1.j2 dest=/tmp/ansible-test-1.txt
 
 
cat templates/test-1.j2

string------{{ var1 }}---{{ var1*3 }}
int---------{{ var2 }}---{{ var2+10 }}---{{ var2-10 }}---{{ var2*10 }}---{{ var2/10 }}---{{ var2//10 }}---{{ var2**2 }}
float-------{{ var3 }}---{{ var3+10 }}---{{ var3-10 }}---{{ var3*10 }}---{{ var3/10 }}---{{ var3//10 }}---{{ var3**2 }}
facts-------{{ ansible_default_ipv4.address }}


ansible-playbook test-1.yaml

#查看远程主机
cat /tmp/ansible-test-1.txt

string------abcd---abcdabcdabcd
int---------3---13----7---30---0.3---0---9
float-------3.14---13.14----6.859999999999999---31.400000000000002---0.314---0.0---9.8596
facts-------10.0.0.213

Template 的 for 循环和 if 判断

bash 复制代码
cat test-2.yaml 

- hosts: rocky
  gather_facts: no
  vars:
   var1: 'abcd'
   var2: 3
   var3: 3.14
   var4: 
      - tom
      - jerry
  
   var5: [ {name: tom, age: 123},{name: jerry, age: 456} ]
   var6:
      - {id: 1, ip: 1.1.1.1, port: 11, master: 1}
      - {id: 2, ip: 2.2.2.2, port: 22, master: 0, node: slave-2}
      - {id: 3, ip: 3.3.3.3, port: 33, master: 0, node: slave-3}
 tasks:
    - name: template-task-2
      template: src=test-2.j2 dest=/tmp/ansible-test-2.txt


cat templates/test-2.j2 

{% if var1 == 'abcd' %}
var1 is abcd
{% endif %}
===========================
{% if var1 != 'abcd' %}
var1 is not abcd
{% else %}
var1 is abcd
{% endif %}
===========================
{% if var2 > 3 %}
var2 > 3
{% elif var2 == 3 %}
var2 == 3
{% else %}
var2 < 3
{% endif %}
============================
{% for i in range(1,4) %}
---{{ i }}---{{ i+10 }}
{% endfor %}
============================
{% for i in var4 %}
---{{ i }}
{% endfor %}
===========================
{% for i in var5 %}
---{{ i.name }}---{{ i.age }}
{% endfor %}
============================
{% for i in var6 %}
---{{ i.ip }}:{{ i.port }}----{% if i.master == 1 %}master{% else %}slave{% endif %}{% if i.node is defined %}----node-{{ i.node }}{% endif %} 
{% endfor %}


ansible-playbook test-2.yaml

#查看远程主机
cat /tmp/ansible-test-2.txt

var1 is abcd
===========================
var1 is abcd
===========================
var2 == 3
============================
---1---11
---2---12
---3---13
============================
---tom
---jerry
===========================
---tom---123
---jerry---456
============================
---1.1.1.1:11----master 
---2.2.2.2:22----slave----node-slave-2 
---1.1.1.1:11----slave----node-slave-3

Ansible 中的流程控制

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

loop (with_items) 迭代

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

迭代元素使用 with_items 来锚定列表,列表中可以是单项元素,也可以是字典。

从 ansible2.5 以后的版本中,使用 loop 来代替 with_items。

bash 复制代码
cat loop-1.yaml 

- hosts: 10.0.0.166
  gather_facts: no
  tasks:
   - name: with_items-task
     debug: msg={{ item }}
     with_items:
      - tom
      - jerry
      - spike
     
   - name: loop-task
     debug: msg={{ item.name }}--{{ item.age }}
     loop: [ {name: tom, age: 10},{name: jerry, age: 20},{name: spike, age: 30}]
     

ansible-playbook loop-1.yaml

PLAY [10.0.0.166] 
********************************************************************************
*
TASK [with_items-task] 
****************************************************************************
ok: [10.0.0.166] => (item=tom) => {
    "msg": "tom"
}
ok: [10.0.0.166] => (item=jerry) => {
    "msg": "jerry"
}
ok: [10.0.0.166] => (item=msg-3) => {
    "msg": "spike"
}
TASK [loop-task] 
********************************************************************************
**
ok: [10.0.0.166] => (item={'name': 'tom', 'age': 10}) => {
    "msg": "tom--10"
}
ok: [10.0.0.166] => (item={'name': 'jerry', 'age': 20}) => {
    "msg": "jerry--20"
}
ok: [10.0.0.166] => (item={'name': 'spike', 'age': 30}) => {
    "msg": "spike--30"
}
PLAY RECAP 
********************************************************************************
********
10.0.0.166 : ok=2    changed=0    unreachable=0    failed=0    skipped=0   rescued=0    ignored=0

until 循环

使用 until 也可以控制一个 task 重复执行,until 后面的值或表达式为 true 的时候,才退出重试,即在 task 没有获得预期值的情况下,会一直重复执行,直到得到预期结果。

until 默认重试三次,每次重试之间间隔 5S,可自定义修改。

bash 复制代码
 cat until-2.yaml
 
- hosts: 10.0.0.166
  gather_facts: no
  tasks:
   - shell: cat /tmp/ansible-until
     register: rs  #注册变量rs
     until: rs.stdout=="123"    #rs.stdout就是cat命令的标准输出
     retries: 5  #重试 5次,默认值为 3
     delay: 2    #重试间隔时间,默认 5S

with_lines 逐行处理

with_lines 可以将一条命令的执行结果逐行调用同一个 task 进行处理

bash 复制代码
cat with_line.yaml

- hosts: localhost
  gather_facts: no
  tasks:
   - name: with_lines-1
     debug: msg={{ item }}
     with_lines: cat /etc/fstab
     
     
[root@ubuntu24 ~]# ansible-playbook with_line.yaml

PLAY [localhost] ***********************************************************************************************************************************************************************

TASK [with_lines-1] ********************************************************************************************************************************************************************
ok: [localhost] => (item=# /etc/fstab: static file system information.) => {
    "msg": "# /etc/fstab: static file system information."
}
ok: [localhost] => (item=#) => {
    "msg": "#"
}
ok: [localhost] => (item=# Use 'blkid' to print the universally unique identifier for a) => {
    "msg": "# Use 'blkid' to print the universally unique identifier for a"
}
ok: [localhost] => (item=# device; this may be used with UUID= as a more robust way to name devices) => {
    "msg": "# device; this may be used with UUID= as a more robust way to name devices"
}
ok: [localhost] => (item=# that works even if disks are added and removed. See fstab(5).) => {
    "msg": "# that works even if disks are added and removed. See fstab(5)."
}
ok: [localhost] => (item=#) => {
    "msg": "#"
}
ok: [localhost] => (item=# <file system> <mount point>   <type>  <options>       <dump>  <pass>) => {
    "msg": "# <file system> <mount point>   <type>  <options>       <dump>  <pass>"
}
ok: [localhost] => (item=# / was on /dev/ubuntu-vg/ubuntu-lv during curtin installation) => {
    "msg": "# / was on /dev/ubuntu-vg/ubuntu-lv during curtin installation"
}
ok: [localhost] => (item=/dev/disk/by-id/dm-uuid-LVM-H5gHRIOORHBs4Od7xVmGBfQE2ZJSd1kxqhie9niuE1re5q1XVMJmVvoQ7stlUKe2 / ext4 defaults 0 1) => {
    "msg": "/dev/disk/by-id/dm-uuid-LVM-H5gHRIOORHBs4Od7xVmGBfQE2ZJSd1kxqhie9niuE1re5q1XVMJmVvoQ7stlUKe2 / ext4 defaults 0 1"
}
ok: [localhost] => (item=# /boot was on /dev/sda2 during curtin installation) => {
    "msg": "# /boot was on /dev/sda2 during curtin installation"
}
ok: [localhost] => (item=/dev/disk/by-uuid/8a0cc0fa-cc21-4fe4-ab25-4a43540d9f02 /boot ext4 defaults 0 1) => {
    "msg": "/dev/disk/by-uuid/8a0cc0fa-cc21-4fe4-ab25-4a43540d9f02 /boot ext4 defaults 0 1"
}
ok: [localhost] => (item=/swap.img	none	swap	sw	0	0) => {
    "msg": "/swap.img\tnone\tswap\tsw\t0\t0"
}

PLAY RECAP *****************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

条件判断 when

when 语句可以实现选择执行,即根据条件判断的结果决定是否执行 task,条件判断的数据来源可以是 变量,前面的 task 的执行结果等

bash 复制代码
#判断变量是否被定义
- hosts: localhost
  gather_facts: no
  tasks:
   - debug: msg="undefined"
     when: tom is undefined
     
#循环判断     
- hosts: localhost
  gather_facts: no
  tasks:
   - debug: msg={{ item }}
     with_items: [1,2,3,4,5]
     when: item > 3
     
#根据不同的系统安装不同的软件
- hosts: 10.0.0.166
  #gather_facts: yes
  tasks:
   - name: redhat-yum-task
     yum: name=httpd state=present
     when: ansible_distribution_file_variety == "RedHat"
   - name: debian-apt-task
     apt: name=apache2 state=present update_cache=yes
     when: ansible_distribution_file_variety=="Debian"

fail_when 取反

使用 when 是保证让条件成立时执行 task,使用 fail_when 表示满足时 task 是执行失败状态

block 分组

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

bash 复制代码
- hosts: localhost
  tasks:
   - name: task-1
     debug: msg=task-1
     when: ansible_distribution_file_variety == 'RedHat'
     
   - name: task-2
     debug: msg=task-2
     when: ansible_distribution_file_variety == 'RedHat'


#使用分组写法,一个block 中可以有多个task
- hosts: localhost
  tasks:
   - block:
      - debug: msg=task-1
      - debug: msg=task-2
     when: ansible_distribution_file_variety == 'RedHat'

changed_when

只有在 task 的执行结果返回状态为 changed 的时候,我们才认为该 task 是真实执行了,在远程主机上 产生了数据变化,但是在 ansible 中,不是所有模块都具有幂等性,对于某些不会产生数据变化的 task ,ansible 也会给出 changed 输出,我们可以使用 changed_when 来避免这一情况

bash 复制代码
#对于此 playbook,task-1 不会在远程主机上产生任何变化,task-2 总会产生变化,但每次执行,都会产生 changed 的提示
- hosts: localhost
  tasks:
   - name: task-1
     shell: id
   - name: task-2
     file: path=/tmp/changed-when state=touch

#对于确定不会发生 change 的 task,可以使用 changed_when 来关闭changed 提示
- hosts: localhost
  tasks:
   - name: task-1
     shell: id
     changed_when: false
   - name: task-2
     file: path=/tmp/changed-when state=touch

滚动执行

默认情况下, ansible 从上到下执行,如果一个 playbook 中有多个 task,在有多台远程主机的情况 下,需要在所有远程主机上执行完当前的 task 之后才执行下一个 task,如果主机过多,或者需要执行的 task 比较消耗时间,则会导致所有主机都处于一个执行中状态

滚动执行,深度优先

bash 复制代码
- hosts: group1
  serial: 1 #每次在一台机上执行完所有task,可以写成百分比,如 "20%" 先执行 20% 的主机
  tasks:
   - name: task-1
     debug: msg=task-1
   - name: task-2
     debug: msg=task-2

委派执行

利用委派执行可以在非指定的主机上执行 task

bash 复制代码
- hosts: rocky
  tasks:
   - name: task-1
     shell: hostname -I
     delegate_to: localhost  #委派给当前主机执行
     register: rs
   - name: task-2
     debug: msg={{ rs.stdout }}

run_once 只执行一次

利用 run_once 指令可以让 task 只执行一次,而非在所有被控主机都执行。

bash 复制代码
- hosts: rocky  
  tasks:
   - name: task-1
     debug: msg=task-1
   - name: task-2
     debug: msg=task-2
     run_once: true

修改环境变量

使用 environment 选项可以修改目标主机的环境变量,environment 配置的环境变量只在当前的 task 中有效

bash 复制代码
- hosts: "10.0.0.150"
 gather_facts: yes
 tasks:
   - name: task-1
     shell: echo $PATH
     register: rs
     environment: 
       PATH: /usr/test:{{ ansible_env.PATH }} #ansible_env 从 gather_facts 中收集而来
   - name: task-2
     debug: msg={{ rs.stdout }}
   - name: task-3
     shell: echo $PATH
     register: rs2
   - name: task-4
     debug: msg={{ rs2.stdout }}
[root@ubuntu ~]# ansible-playbook env.yaml 
PLAY [10.0.0.150] 
********************************************************************************
*******************
TASK [Gathering Facts] 
********************************************************************************
*******************
ok: [10.0.0.150]
TASK [task-1] 
********************************************************************************
*******************
changed: [10.0.0.150]
TASK [task-2] 
********************************************************************************
*******************
ok: [10.0.0.150] => {
    "msg": "/usr/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
}
TASK [task-3] 
********************************************************************************
*******************
changed: [10.0.0.150]
TASK [task-4] 
********************************************************************************
*******************
ok: [10.0.0.150] => {
    "msg": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
}
PLAY RECAP 
********************************************************************************
*********************
10.0.0.150 : ok=5    changed=2    unreachable=0    failed=0    skipped=0   rescued=0    ignored=0

Yaml 文件互相调用

include_tasks 包含

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

bash 复制代码
#a.yaml
- hosts: localhost
  tasks:
   - name: task-1
     debug: msg=task-1
   - name: task-2
     include_tasks: b.yaml
   - name: task-3
     include_tasks: c.yaml
     
#b.yaml
- name: "b.yaml-task-1"
  debug: msg="b.yaml-task-1"
- name: "b.yaml-task-2"
  debug: msg="b.yaml-task-2"
 
#c.yaml
- name: "c.yaml-task-1"
  debug: msg="c.yaml-task-1"

注意:一个task 只有一个 include_tasks 生效

import_playbook 合并多个 playbook 文件

import_playbook 可以将多个包含完整内容的 yaml 文件交由一个 yaml 统一调用

bash 复制代码
cat main.yaml 
- import_playbook: task1.yaml
- import_playbook: task2.yaml
相关推荐
风清再凯11 小时前
自动化工具ansible,以及playbook剧本
运维·自动化·ansible
IT乌鸦坐飞机12 小时前
ansible部署数据库服务随机启动并创建用户和设置用户有完全权限
数据库·ansible·centos7
遇见火星14 天前
如何使用Ansible一键部署MinIO集群?
ansible
粥周粥14 天前
ANSIBLE
ansible
码农101号14 天前
Linux中ansible模块补充和playbook讲解
linux·运维·ansible
码农101号14 天前
Linux的Ansible软件基础使用讲解和ssh远程连接
ansible
烟雨书信15 天前
ANSIBLE运维自动化管理端部署
运维·自动化·ansible
碎碎-li15 天前
ANSIBLE(运维自动化)
运维·自动化·ansible
@donshu@18 天前
Linux运维-ansible-python开发-获取inventroy信息
linux·运维·ansible
Kendra91921 天前
Ansible
ansible