1.让’青蛙’说出你的任务
# ubuntu/debian: apt install cowsay
$ yum -y install cowsay
$ export ANSIBLE_NOCOWS=0 ANSIBLE_COW_SELECTION=bud-frogs
执行任意playbook,你将在每个任务得到…
$ ansible-playbook test.yml
__________________
< PLAY [localhost] >
------------------
\
\
oO)-. .-(Oo
/__ _\ /_ __\
\ \( | ()~() | )/ /
\__|\ | (-___-) | /|__/
' '--' ==`-'== '--' '
________________________
< TASK [Hello bud-frogs] >
------------------------
\
\
oO)-. .-(Oo
/__ _\ /_ __\
\ \( | ()~() | )/ /
\__|\ | (-___-) | /|__/
' '--' ==`-'== '--' '
ok: [localhost] => {
"msg": "Hello"
}
____________
< PLAY RECAP >
------------
\
\
oO)-. .-(Oo
/__ _\ /_ __\
\ \( | ()~() | )/ /
\__|\ | (-___-) | /|__/
' '--' ==`-'== '--' '
localhost : ok=1 changed=0 unreachable=0 failed=0
2. 在shell模块中使用脚本写法与jinja2
# 我们知道,任何能写变量的地方都能嵌套jinja2表达式
# 而shell模块中的内容到客户端渲染后也是一个可执行脚本
# 所以我们可以在shell内编写比平时更复杂的脚本
---
- name: Run shell
shell: |-
{% if foo is defined %}
mkdir -p /tmp/{{ foo }}
{% else %}
if [[ ! ${foo} ]];then
mkdir -p /var/lib/{{ bar }}
echo 'success'
else
echo 'falure'
fi
{% endif %}
3.展开jinja2中的一行的表达式
# 在使用jiaja2 if判断一个变量的时候,为了防止空格跟换行,我们经常会写在一行判断,但是展示却不太友好
---
- set_fact:
GET_REPO: "{% if ENV == 'UAT' %}[email protected]:xnph-devops/conf-test.git{% else %}[email protected]:xnph-devops/conf.git{% endif %}"
# 一行冗长的表达式看着很不直观,我们可以借助jinja2的‘-’实现换行写,并忽略换行符
# 其中:
# |- 为块标签忽略换行
# {%- -%} 为忽略换行
---
- set_fact:
GIT_REPO: |-
{%- if ENV == 'UAT' -%}
[email protected]:xnph-devops/conf-test.git
{%- else -%}
[email protected]:xnph-devops/conf.git
{%- endif -%}
4.简易的jinja2 ‘if else’表达式
# 我们在编写jinja2 if else的表达式时,通常都是像这样的:
# 下面例子,索引ID小于3的都分配为master
---
- set_fact:
role: >-
{%- if ansible_play_hosts.index(inventory_hostname) < 3 -%}
master
{%- else -%}
slave
{%- endif -%}
# 我们利用python特性,可以简写如下:
---
- set_fact:
role: "{{ ['slave','master'][ansible_play_hosts.index(inventory_hostname) < 3] }}"
# 即,先判断ansible_play_hosts.index(inventory_hostname) < 3是否成立,
# 如果成立,即true,失败为false
# 而python中true==>1,false==>0
# 再取['slave','master']列表索引
# 其中ansible_play_hosts为当前任务执行的所有主机
5.获版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作! 取当前时间
# 在setup中其实有时间字段,但是被拆分,或者格式问题不好处理,我们通过内置方法能更便捷的获取
# 格式可以自定义
- set_fact:
datetime: "{{ '%Y-%d-%m:%H' | strftime() }}"
# 但是这样取的时间在客户端时间出现不一致的时候可能会出现问题,所以,我们通过另外一个办法
# 即:通过lookup插件(lookup插件工作在控制端本机),统一获取本机时间
- set_fact:
datetime: "{{ lookup('pipe','date +%Y-%d-%m:%H') }}"
6.单独变量版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作! 嵌套
# 假设我们有如下playbook:
---
- hosts: group_a
vars:
foo_var: foo
bar_var: bar
tasks:
- debug: msg="I like the {{ {{ key }}_var }}"
# 以上执行必然是回报错的,
# 其中{{ key }}是我们想通过动态传入foo或者bar来获取foo_var或者bar_var的值
# 为此我们可以通过lookup的vars插件来查到目的,改造如下:
---
- hosts: group_a
vars:
foo_var: foo
bar_var: bar
tasks:
- debug: msg="I like the {{ lookup('vars', key + '_var') }}"
# 执行传入key=foo
ansible-playbook test.yml -e 'key=foo'
7.字典变量嵌套
# 假设我们有如下playbook
---
- hosts: group_a
vars:
project:
foo: foo_value
bar: bar_value
tasks:
- debug: msg="I like the {{ project.{{ key }} }}"
# 当然,以上执行是会报错的,
# 其中{{ key }}即为我想通过动态传入foo或者bar来获取project下的键值
# 为此我们可以这么写:
---
- hosts: group_a
vars:
project:
foo: foo_value
bar: bar_value
tasks:
- debug: msg="I like the {{ project[key] }}"
# 执行传入key=foo
ansible-playbook test.yml -e 'key=foo'
8.限制必须指定至少一个tags才能运行任务
# 我们对多个任务做了tags,而我们运行任务而不指定-t时,默认会运行所有的任务
# 有时候这并不是我们所期望的,所以想当不指定tags时候不运行后面的任务
---
- hosts: localhost
gather_facts: false
tasks:
- fail:
msg: "你必须指定一个tags[-t]"
when: "ansible_run_tags == ['all']"
run_once: true
- debug: msg='hello {{ ansible_run_tags }}'
tags:
- abc
# 即,只要ansible_run_tags默认值为['all']时,任务就直接失败
$ ansible-playbook abc.yml
PLAY [localhost] **********************************************************************************************************************************************************
TASK [fail] ***************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "你必须指定一个tags[-t]"}
PLAY RECAP ****************************************************************************************************************************************************************
localhost : ok=0 changed=0 unreachable=0 failed=1
# 其中:
# ansible_run_tags为ansible内置变量,默认值为['all']
9.访问其他主机的变量
# A主机有变量foo=archive,B主机想使用A主机的{{ foo }}怎么办呢?
---
- hosts: A:B
tasks:
- debug: msg={{ hostvars['A']['foo'] }}
# 大部分的变量对象都存放在hostvars中
10.计算不同主机中相同变量的和
# 什么意思呢,即,有N主机,有相同的变量score,但是值是不一样的,现在想计算它们的score和
# 假定inventory如下:
$ cat inventory.ini
[public]
10.18.12.213 score=77
10.18.16.166 score=100
10.18.16.167 score=81
10.18.16.168 score=56
# 计算score的和
---
- hosts: localhost
tasks:
- debug: msg={{ ansible_play_hosts_all | map('extract',hostvars,'score') | sum }}
# 执行结果
$ ansible-playbook -i inventory.ini score_sum.yml
PLAY [public] *************************************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************
ok: [10.18.16.167]
ok: [10.18.16.166]
ok: [10.18.16.168]
ok: [10.18.12.213]
TASK [debug] **************************************************************************************************************************************************************
ok: [10.18.12.213] => {
"msg": "314"
}
ok: [10.18.16.166] => {
"msg": "314"
}
ok: [10.18.16.167] => {
"msg": "314"
}
ok: [10.18.16.168] => {
"msg": "314"
}
PLAY RECAP ****************************************************************************************************************************************************************
10.18.12.213 : ok=2 changed=0 unreachable=0 failed=0
10.18.16.166 : ok=2 changed=0 unreachable=0 failed=0
10.18.16.167 : ok=2 changed=0 unreachable=0 failed=0
10.18.16.168 : ok=2 changed=0 unreachable=0 failed=0
# 其他例子:
# 获取所有主机的序列号,并以','连接
- debug: msg={{ ansible_play_hosts_all | map('extract',hostvars,'ansible_product_serial') | join(',') }}
# 获取所有主机内存的总和
- debug: msg={{ ansible_play_hosts_all | map('extract',hostvars,'ansible_memtotal_mb') | sum }}
# 其中:
# ansible_play_hosts_all为当前任务执行的所有主机
# map方法参考python map
# sum为计算list的和
11.使用本地命令的值作为循环参数
# 使用ls /tmp的输出作为loop循环的值
# 其中:q == query
# query与lookup都工作在本机
---
- hosts: localhost
gather_facts:
tasks:
- debug: msg={{ item }}
loop: "{{ q('lines','ls /tmp/') }}"
12.版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作! 嵌套两个列表的循环任务
# 假定有2个列表
# a_list: [1,2]
# b_list: ['jack', 'green', 'apple']
# 需要嵌套循环输出:
# jack-1,,green-1,apple-1,jack-2,green-2...
---
- hosts: localhost
vars:
a_list: [1,2]
b_list: ['jack', 'green', 'apple']
gather_facts: false
tasks:
- debug: msg="{{ item[1] }}-{{ item[0] }}"
loop: "{{ a_list | product(b_list) | list }}"
$ ansible-playbook loops.yml
PLAY [localhost] **********************************************************************************************************************************************************
TASK [debug] **************************************************************************************************************************************************************
ok: [localhost] => (item=[1, u'jack']) => {
"msg": "jack-1"
}
ok: [localhost] => (item=[1, u'green']) => {
"msg": "green-1"
}
ok: [localhost] => (item=[1, u'apple']) => {
"msg": "apple-1"
}
ok: [localhost] => (item=[2, u'jack']) => {
"msg": "jack-2"
}
ok: [localhost] => (item=[2, u'green']) => {
"msg": "green-2"
}
ok: [localhost] => (item=[2, u'apple']) => {
"msg": "apple-2"
}
PLAY RECAP ****************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
13.全局任务滚动执行
# 对于全局任务滚动执行
# 设置serial为1,或者其他的数值,会按这个数量的主机去滚动执行任务
# 每个任务结尾会停顿5s,再下一个主机
- hosts: public
serial: 1
gather_facts: false
tasks:
- name: I\'m the first task
debug: msg="hello {{ inventory_hostname }}"
- name: Sleep task
wait_for:
delay: 5
when: (ansible_play_hosts_all.index[-1] != inventory_hostname)
# 对于最后一台主机,是不需要执行wait_for任务的,所以判断最后一台主机跳过wait_for
# 其中:
# serial为滚动执行的关键字,可以是数值或者百分比(%)
# ansible_play_hosts_all为当前任务执行的所有主机
# inventory_hostname为各个主机自己
14.部分任务滚动执行
# 把需要滚动执行的任务放入include_tasks中,
# 利用loop循环所有当前执行的主机(ansible_play_hosts_all),即:
# 只有放入other.yml的任务会一台一台执行
# 设置run_once避免每个主机都多次重复执行
---
- hosts: public
tasks:
- debug: msg="I'm the Top public task"
- include_tasks: other.yml
loop: "{{ ansible_play_hosts_all }}"
loop_control:
loop_var: target_host
run_once: true
# other.yml
---
- block:
- shell: hostname -I
register: res
- debug: msg="I'm {{ res.stdout }}"
delegate_to: "{{ target_host }}"
# 其中:
# ansible_play_hosts_all为前期执行的所有host
# loop_control的loop_var把item赋值给target_host,即,target_host==item,防止other里其他任务调用loop,item变量冲突
# run_once:每个循环对象只允许一次,不设置的话,会重复运行
# delegate_to委派任务
# 另外,该方法有个缺点是由于委派者都是第一台主机,对应的facts变量都是该委派者的,所以没法使用其他主机的内置变量
# 故案例中也是使用register+shell来获取的主机名,而不是{{ inventory_hostname }}
# 执行结果:
$ ansible-playbook rolling.yml
PLAY [public] ************************************************************************************************************************************************************
TASK [Gathering Facts] ***************************************************************************************************************************************************
ok: [10.18.16.167]
ok: [10.18.16.166]
ok: [10.18.16.168]
ok: [10.18.12.213]
TASK [debug] *************************************************************************************************************************************************************
ok: [10.18.12.213] => {
"msg": "I'm the Top public task"
}
ok: [10.18.16.167] => {
"msg": "I'm the Top public task"
}
ok: [10.18.16.166] => {
"msg": "I'm the Top public task"
}
ok: [10.18.16.168] => {
"msg": "I'm the Top public task"
}
TASK [include_tasks] *****************************************************************************************************************************************************
included: /tmp/other.yml for 10.18.12.213
included: /tmp/other.yml for 10.18.12.213
included: /tmp/other.yml for 10.18.12.213
included: /tmp/other.yml for 10.18.12.213
TASK [shell] *************************************************************************************************************************************************************
changed: [10.18.12.213 -> 10.18.12.213]
TASK [debug] *************************************************************************************************************************************************************
ok: [10.18.12.213 -> 10.18.12.213] => {
"msg": "I'm 10.18.12.213 "
}
TASK [shell] *************************************************************************************************************************************************************
changed: [10.18.12.213 -> 10.18.16.166]
TASK [debug] *************************************************************************************************************************************************************
ok: [10.18.12.213 -> 10.18.16.166] => {
"msg": "I'm 10.18.16.166 "
}
TASK [shell] *************************************************************************************************************************************************************
changed: [10.18.12.213 -> 10.18.16.167]
TASK [debug] *************************************************************************************************************************************************************
ok: [10.18.12.213 -> 10.18.16.167] => {
"msg": "I'm 10.18.16.167 "
}
TASK [shell] *************************************************************************************************************************************************************
changed: [10.18.12.213 -> 10.18.16.168]
TASK [debug] *************************************************************************************************************************************************************
ok: [10.18.12.213 -> 10.18.16.168] => {
"msg": "I'm 10.18.16.168 "
}
PLAY RECAP ***************************************************************************************************************************************************************
10.18.12.213 : ok=14 changed=4 unreachable=0 failed=0
10.18.16.166 : ok=2 changed=0 unreachable=0 failed=0
10.18.16.167 : ok=2 changed=0 unreachable=0 failed=0
10.18.16.168 : ok=2 changed=0 unreachable=0 failed=0
15.在when里使用jinja2表达式
# 在when里是可以使用jinja2表达式的,只要格式规范即可。
# 一行表达式:
---
- hosts: target_host
tasks:
- name: Show debug
debug: msg='条件满足你就会看到我。'
when: '{% if def_a_var|d("abc") == "abc" %}{{ foo | d("true") }}{% endif %}'
# 等价的展开表达式
---
- hosts: target_host
tasks:
- name: Show debug
debug: msg='条件满足你就会看到我。'
when: |-
{%- if def_a_var|d("abc") == "abc" -%}
{{ foo | d("true") }}
{%- endif -%}
# 执行(会有警告提示):
ansible-playbook test.yml
PLAY [localhost] ********************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************************
ok: [localhost]
TASK [Show debug] *******************************************************************************************************************************************
[WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {% if def_a_var|d("abc") == "abc" %}{{ foo | d("true") }}{% endif %}
ok: [localhost] => {
"msg": "条件满足你就会看到我。"
}
PLAY RECAP **************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
16.变量使用锚定和别名
# 通常我们在定义多个类似变量时,不同变量有定义相同的内容
# 比如:
---
- hosts: localhost
vars:
project_1:
port: 8080
jvm: '-Xms1G -Xmx1G'
run_user: devops
root: /data/project_1
project_2:
port: 8080
jvm: '-Xms1G -Xmx1G'
run_user: devops
root: /data/project_2
tasks:
- debug: var=project_1
- debug: var=project_2
# 可以看到`project_2`中的很多键值都跟`project_1`中是一样的,
# 那么我们有什么办法简化呢?
# 对此,yaml中提供了'&'来描定一个变量,并给描定的变量设置一个别名,
# 其他变量要引用锚定变量的值,需要再使用'<<'来引用并用*指向设置的别名
# 另外,具有相同键的变量会被后者覆盖
# 即:project_2的root会覆盖引用project_1的root
# 修改如下:
---
- hosts: localhost
vars:
project_1: &conf_anchor
port: 8080
jvm: '-Xms1G -Xmx1G'
run_user: devops
root: /data/project_1
project_2:
<<: *conf_anchor
root: /data/project_2
tasks:
- debug: var=project_1
- debug: var=project_2
# 其中:
# & 为锚定标识
# conf_anchor为设置的别名
# << 为合并键
# * 为引用符
# 执行如下(变量覆盖会有警告):
$ ansible-playbook anchor.yml
[WARNING]: While constructing a mapping from /tmp/anchor.yml, line 10, column 7, found a duplicate dict key (root). Using last defined value only.
PLAY [localhost] ********************************************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************************************
ok: [localhost]
TASK [debug] ************************************************************************************************************************************************
ok: [localhost] => {
"project_1": {
"jvm": "-Xms1G -Xmx1G",
"port": 8080,
"root": "/data/project_1",
"run_user": "devops"
}
}
TASK [debug] ************************************************************************************************************************************************
ok: [localhost] => {
"project_2": {
"jvm": "-Xms1G -Xmx1G",
"port": 8080,
"root": "/data/project_2",
"run_user": "devops"
}
}
PLAY RECAP **************************************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
# 其他示例
# 引用锚定变量
---
- hosts: localhost
vars:
app:
version: &my_version 1.1.1
name:
- "app_name"
- *my_version
tasks:
debug:
msg: app version is "{{ app.name | join('-') }}".
# 执行
$ ansible-playbook app.yml
PLAY [localhost] *******************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************************************************************************************
ok: [localhost] => {
"msg": "app version is \"app_name-1.1.1\"."
}
PLAY RECAP *************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
# 合并多个字典
---
- hosts: localhost
vars:
center: &C
x: 1
y: 2
round: &R
r: 3.1415
big:
<<: [*C,*R]
g: 3x10e
tasks:
- debug: var=big
# 执行:
ansible-playbook muti.yml
PLAY [localhost] *******************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************************************************************************************
ok: [localhost] => {
"big": {
"g": "3x10e",
"r": 3.1415,
"x": 1,
"y": 2
}
}
PLAY RECAP *************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
17.定义带空格的key
# yaml是直接可以定义带空格的变量的
# 或者你可以用?:的方式
# 两者完全等价的。
- hosts: localhost
vars:
my_vars:
# 直接定义
'this is a key1': 'this is a value1'
# 或者使用?:的方式
? 'this is a key2'
: 'this is a value2'
tasks:
- debug: var=my_vars
# 执行
$ ansible-playbook space_var.yml
PLAY [localhost] **************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ******************************************************************************************************************************************************************
ok: [localhost] => {
"my_vars": {
"this is a key1": "this is a value1",
"this is a key2": "this is a value2"
}
}
PLAY RECAP ********************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
18.定义变量时,设置类型
# 其实在定义变量时,我们是可以设置类型的。
# 当然,你在加引号与不加引号时就能解决大多的定义问题
- hosts: localhost
vars:
type_vars:
is_str: !!str 9111
is_int: !!int '8081'
is_float: !!float 2019
is_bool: !!bool 'yes'
tasks:
- debug: var=type_vars
# 执行:
$ ansible-playbook type_vars.yml
PLAY [localhost] **************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ******************************************************************************************************************************************************************
ok: [localhost] => {
"type_vars": {
"is_bool": true,
"is_float": 2019.0,
"is_int": 8081,
"is_str": "9111"
}
}
PLAY RECAP ********************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
19.数字精度
# 利用format可以保留精度
$ ansible localhost -m debug -a "msg={{ '%0.2f -- %0.3f -- %d'| format(123,2.7,4.44) }}"
localhost | SUCCESS => {
"msg": "123.00 -- 2.700 -- 4"
}
20.循环(多实例)任务触发handlers重启任务
# 我们在使用ansible部署多实例任务时
# 多个实例完成后需要触发handler任务来重启各个实例(实例名称通常是动态/不固定的),
# 而通常实例的服务名称是有区别的,而且是按需重启(满足状态变化),逐个重启
# 我们可以借助json_query的filter特性来获取changed状态满足条件的实例来循环重启
# 如,实例变量如下:
---
vars:
redis_ins_port:
- 6379
- 6380
- 6381
# 我们要循环复制实例模板到客户端,触发handler后只重启发生变化的实例
# 复制模板并将任务状态注册到变量
- name:
template: src=redis.conf.j2 dest=/etc/redis/redis_{{ server_port }}.conf"
loop_control:
loop_var: server_port
loop: "{{ redis_ins_port }}"
register: task_status
notify:
- Restart redis
# handler任务
# 其中,json_query为查询task_status中changed的状态为true的所有server_port
---
- name: Restart redis
systemd: name=redis_{{ item }} state=restarted"
loop: "{{ task_status | json_query('results[?changed].server_port') }}
21.修改列表内的所有字段值,生成新的列表
# 我们在创建例如mongodb replicasets时,使用模块示例如下:
# Create a replicaset called 'rs0' with the 3 provided members
- name: Ensure replicaset rs0 exists
mongodb_replicaset:
login_host: localhost
login_user: admin
login_password: admin
replica_set: rs0
members:
- mongodb1:27017
- mongodb2:27017
- mongodb3:27017
when:ansible_play_hosts.index(inventory_hostname) == 0
# 对于其中的member如何能不固定通过方法自动创建呢?
# 试想,我们已经有了执行的主机列表ansible_play_hosts
# (虽然里面的值可能是别名,即:inventory_hostname)
# 但是这个列表么有对应的端口,即我们需要修改列表里的每一项,添加一个端口即可
# 像这样:['10.18.1.190:27017','10.18.1.191:27017','10.18.1.192:27017']
# 改动如下:
- name: Ensure replicaset rs0 exists
mongodb_replicaset:
login_host: localhost
login_user: admin
login_password: admin
replica_set: rs0
members: >-
{{ anible_play_hosts | map('extract', hostvars, 'ansible_default_ipv4') | list | json_query('[*].address') |map('regex_replace','(.*)','\\1:' + '27017' | list }}
when: ansible_play_hosts.index(inventory_hostname) == 0
# 其中:
# ansible_play_hosts为当前执行的所有主机列表
# 第一个map extract为提取hostvars中每个ansible_play_hosts的值作为键,并取ansible_default_ipv4,就像:hostvars[inventory_hostname]['ansible_default_ipv4']
# list把map对象转换成列表
# json_query把列表内每个address提取出来变成新的列表,到此是为了提取IP地址列表,当然,你ansible_play_hosts是IP地址的话可以不用这么麻烦
# 第二个map regex_replace把每个IP地址变成IP+:27017,
# 最后再把我们的map对象转成list,得到我们想要的:['10.18.1.190:27017','10.18.1.191:27017','10.18.1.192:27017']
22.待续…
如果你有更好的姿势跟建议,欢迎补充