Ansible Playbooks
如果说 -m <module> 模块是远程执行的工具,那 Playbook剧本定义就是一个远程执行的方案。与 ad-hoc 不同,playbooks 是专门为复杂任务的执行而设计的。
Playbooks 利于声明式配置编排执行过程,实现在在多组机器间,按步骤的有序执行,除此外还支持同步或异步的执行任务,接下来我们一点点的学习它
一、快速体验
第一个 Playbook:安装软件
Playbooks 的格式是 YAML,它的基本结构如下:
Playbook:整个执行方案,包含多个 Play,可理解为一个包含多集的影视剧本Play:一组执行动作,包含多个 Task,理解为一集电视剧有多个故事镜头Task:一个具体的任务(命令)

早先我们体验过 Ad-Hoc 执行远程,现在我们对照着学习下 Playbook,编写第一个 Playbook 定义:

代码如下:
---
# Play 名称
- name: install-iotop-playbook
# 主机组
hosts: ecs
# Task 列表
tasks:
# Task 名称
- name: install
# 使用 yum 模块
yum:
# 定义模块参数
name: iotop
state: latest
执行效果
$ ansible-playbook playbook-demo1.yaml
PLAY [install-iotop-playbook] *****
TASK [Gathering Facts] ************
ok: [sz-aliyun-ecs-1]
ok: [bj-huawei-hecs-1]
TASK [install] ********************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
当 Playbook 开始执行后,第一个动作是 Gathering Facts 收集 Facts,所谓 Facts 指的是被控节点的系统信息,这个动作是可选的,我们可以去禁用它,有关 Facts 的内容,我们后面留到 “**Facts 数据**” 小节再讨论
---
# Playbook 名称
- name: install-iotop-playbook
# 主机组
hosts: ecs
# 关闭自动采集 Facts
gather_facts: no
# Task 列表
tasks:
# Task 名称
- name: install
# 使用 yum 模块
yum:
# 定义模块参数
name: iotop
state: latest
执行效果
$ ansible-playbook playbook-demo1.yaml
PLAY [install-iotop-playbook] *****
TASK [install] ********************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
第二个 Playbook:部署 Redis 服务
上面,我们写了一个最基本的 Playbook,通过它我们完成了将 ecs 组的所有主机安装 iotop 软件包,不过像这样的功能,我们一个 ad-hoc 指令就可以搞定,Playbook 可以用来做更复杂的事情,来,我们再看一个更贴合实际的例子:部署一个 redis-server 服务
配置如下:
$ cat playbook-redis-demo1.yaml
---
# 一、[Target section] 定义远程主机组
- name: deploy-redis-server
hosts: db_server
# 远程执行用户
remote_user: root
# 不收集 Facts 信息
gather_facts: no
# 二、[Variable section] 定义 Jinjia2 模版所需变量
vars:
listen_host: 127.0.0.1
listen_port: 6380
# yes 不用双引会被转为 True
set_daemon: "yes"
# 三、[Task section] 定义执行任务列表
tasks:
# Step 1:安装 Redis
- name: Installing Redis
yum:
name: redis
state: latest
# Step 2:生成 Redis 配置
- name: Generate Config
template:
src: redis.conf.j2
dest: /etc/redis.conf
# Step 3:确保服务运行
- name: Ensure Service
service:
name: redis
state: started
# Play 执行完成后调用 restart redis 回调 handler
notify:
- check redis
# 四、[Handler section] 定义回调函数
handlers:
# 当 task 任务通过 notify 函数调用 check redis 检测 redis 是否可用
- name: check redis
shell: redis-cli -p {{ listen_port }} ping
# 显示设置不忽略错误,当执行遇到错误(返回码非 0),提示错误信息
ignore_errors: False
配置文件模版 redis.conf.j2
$ cat redis.conf.j2
bind {{ listen_host }}
protected-mode yes
port {{ listen_port }}
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize {{ set_daemon }}
supervised no
pidfile /var/run/redis_{{ listen_port }}.pid
loglevel notice
logfile /var/log/redis/redis_{{ listen_port }}.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
通过 Playbook 的定义,我们看到共分为 4 部分:
Target section: 定义将要执行 playbook 的远程主机组Variable section:定义 playbook 运行时需要使用的变量Task section:定义将要在远程主机上执行的任务列表Handler section:定义 task 执行完成以后需要调用的任务
执行效果
$ ansible-playbook playbook-redis-demo1.yaml
PLAY [deploy-redis-server] ********
TASK [Installing Redis] ***********
changed: [sz-aliyun-ecs-1]
TASK [Generate Config] ************
changed: [sz-aliyun-ecs-1]
TASK [Ensure Service] *************
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [check redis] *****
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=4 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
澄清误解,早先我对 handler 及服务管理的部分有些误解,做完下面的实验后,我才真正明白了为什么很多人在写服务的 playbook 时,习惯在变更配置的 tasks 后添加 notify,这是为了后续更改配置重启服务使用,废话不多说了,直接看例子
当前 nginx 主配置内容
$ cat /opt/nginx/conf/nginx.conf
worker_processes 2;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
include conf.d/*.conf;
}
最基础的 Nginx 配置管理剧本
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: moidfy nginx config
lineinfile:
path: "/opt/nginx/conf/nginx.conf"
regexp: "^(\\s+)server_tokens\\s+off;"
line: " server_tokens off;"
insertafter: "\\s+keepalive_timeout\\s+\\d+;"
backup: yes
- name: restart nginx service
shell: "/opt/nginx/sbin/nginx -s reload"
执行命令
$ ansible-playbook playbook-nginx-demo1.yml
PLAY [ecs[0]] *****
TASK [moidfy nginx config] ********
changed: [sz-aliyun-ecs-1]
TASK [restart nginx service] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
不难理解,两个任务,先修改、后重启~ 那假如我们再次执行一遍呢?
$ ansible-playbook playbook-nginx-demo1.yml
PLAY [ecs[0]] *****
TASK [moidfy nginx config] ********
ok: [sz-aliyun-ecs-1]
TASK [restart nginx service] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
moidfy nginx config 任务为 ok 状态、restart nginx service 任务为 changed 状态,之前讨论过 Ansible 的幂等性的问题,一句话简单来说,Ansible 是朝着剧本所定义的状态变更
既然如此,这就很好解释了,第一次,由于文本中没有对应的配置项,所以 moidfy nginx config 成功任务执行,而第二次 已经有了,所以直接返回个 OK,截至目前,都没啥问题,唯一的尴尬点在与,以目前剧本的配置,无论 Nginx 配置是否发生变更,重启服务的 Tasks 都会执行,这并不是我们期望看到的效果,所以 handler 出场了
调整后的 Nginx 配置管理剧本
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: moidfy nginx config
lineinfile:
path: "/opt/nginx/conf/nginx.conf"
regexp: "^(\\s+)server_tokens\\s+off;"
line: " server_tokens off;"
insertafter: "\\s+keepalive_timeout\\s+\\d+;"
backup: yes
notify:
restart nginx service
handlers:
- name: restart nginx service
command: "/opt/nginx/sbin/nginx -s reload"
删掉 server_tokens off; 配置项再测试
$ cat /opt/nginx/conf/nginx.conf
worker_processes 2;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
include conf.d/*.conf;
}
执行命令
$ ansible-playbook playbook-nginx-demo1-optimized.yml
PLAY [ecs[0]] *****
TASK [moidfy nginx config] ********
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [restart nginx service] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
再次尝试
$ ansible-playbook playbook-nginx-demo1-optimized.yml
PLAY [ecs[0]] *****
TASK [moidfy nginx config] ********
ok: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
二、剧本命令
此前我们只是简单的使用 ansible-playbook 命令执行 playbook 文件,现在来补充了解下更多的用法:
检查 playbook 语法
$ ansible-playbook playbook-demo1.yaml --syntax-check
playbook: playbook-demo1.yaml
没有问题,直接返回空值,否则会返回对应的错误信息
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: No JSON object could be decoded
Syntax Error while loading YAML.
did not find expected key
The error appears to be in '/prodata/scripts/ansibleLearn/playbook-demo1.yaml': line 16, column 6, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
name: iotop
state: latest
^ here
列出 目标主机
$ ansible-playbook playbook-demo1.yaml --list-hosts
playbook: playbook-demo1.yaml
play #1 (ecs): install-iotop-playbook TAGS: []
pattern: [u'ecs']
hosts (2):
sz-aliyun-ecs-1
bj-huawei-hecs-1
列出 任务列表
$ ansibleLearn ansible-playbook playbook-redis-demo1.yaml --list-tasks
playbook: playbook-redis-demo1.yaml
play #1 (db_server): deploy-redis-server TAGS: []
tasks:
Installing Redis TAGS: []
Generate Config TAGS: []
Ensure Service TAGS: []
三、handler 进阶
早先我们简单来说过 handler,并通过 nginx 配置、redis 剧本使用体验过,下面我们再多了解 handler 的其他功能及使用方法
大致内容

多 handler 定义
顾名思义,handler 是一种另类的任务列表,它同样支持定义多个
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: task1
file:
path: /tmp/testfile
state: `
notify: handler1
- name: task2
file:
path: /tmp/testfile
state: touch
notify: handler2
handlers:
- name: handler1
file:
path: /tmp/handler1
state: touch
- name: handler2
file:
path: /tmp/handler1
state: touch
执行效果
$ ansible-playbook playbook-handler-demo1.yml
PLAY [ecs[0]] *****
TASK [task1] ******
changed: [sz-aliyun-ecs-1]
TASK [task2] ******
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler1] ********
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler2] ********
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=4 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
通过执行返回可以看到大致执行顺序,先执行完所有 tasks 再执行 handlers,如果我们想实现某称某个(些) tasks 后执行 handler 可以通过 meta 模块实现需求
meta 模块改变执行顺序
这里我们使用 meta 模块,定义一种特殊的任务,meta 任务,通过 meta 任务 我们可以实现影响 ansible 的内部运行顺序,下例中,meta 任务的参数值为 flush_handlers,表示立即执行之前的 task 所对应 handler,剧本定义如下:
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: task1
file:
path: /tmp/testfile
state: touch
notify: handler1
# 执行 meta 任务
- meta: flush_handlers
- name: task2
file:
path: /tmp/testfile
state: touch
notify: handler2
handlers:
- name: handler1
file:
path: /tmp/handler1
state: touch
- name: handler2
file:
path: /tmp/handler1
state: touch
执行命令
$ ansible-playbook playbook-handler-demo2.yml
PLAY [ecs[0]] *****
TASK [task1] ******
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler1] ********
changed: [sz-aliyun-ecs-1]
TASK [task2] ******
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler2] ********
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=4 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
一(task)对多(handler)
我们可以在一个 task 中一次性 notify 多个 handler,主要有以下几种方法
方法一:列表包含要通知的 handler
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: task1
file:
path: /tmp/testfile
state: touch
notify:
- handler1
- handler2
handlers:
- name: handler1
file:
path: /tmp/handler1
state: touch
- name: handler2
file:
path: /tmp/handler1
state: touch
执行命令
$ ansible-playbook playbook-handler-demo3.yml
PLAY [ecs[0]] *****
TASK [task1] ******
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler1] ********
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler2] ********
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
方法二:分组 handler
把多个 handler 划分到一个”组”中,当我们需要一次性 notify 多个 handler 时,使用对应的”组名”即可
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: task1
file:
path: /tmp/testfile
state: touch
# 通知 hgroup1 组中的 handler
notify: hgroup1
handlers:
- name: handler1
# 定义监听 组名
listen: hgroup1
file:
path: /tmp/handler1
state: touch
- name: handler2
# 定义监听 组名
listen: hgroup1
file:
path: /tmp/handler1
state: touch
执行命令
$ ansible-playbook playbook-handler-demo4.yml
PLAY [ecs[0]] *****
TASK [task1] ******
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler1] ********
changed: [sz-aliyun-ecs-1]
RUNNING HANDLER [handler2] ********
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
四、tags 任务标识
假如我们写了一个很长的 playbook,其中有很多的任务,可在使用时我们只想执行其中的一部分任务而已,那么该如何做呢?答案是,tags!
大致内容

通过 tags 对任务进行打标签,在执行 playbook 时,借助标签,实现指定执行哪些任务,示例如下:
例1:指定执行某些任务
通过 --tags 执行指定任务,逗号分隔
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: task1
file:
path: /tmp/testfile
state: touch
tags: tag1
- name: task2
file:
path: /tmp/testfile
state: touch
tags: tag2
执行命令
# 多个 tags 逗号分隔
$ ansible-playbook --tags=tag2 playbook-tags-demo1.yml
PLAY [ecs[0]] *****
TASK [task2] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
例2:指定跳过某些任务
通过 --skip-tags 跳过指定任务,逗号分隔
执行命令
$ ansible-playbook --skip-tags=tag1 playbook-tags-demo1.yml
PLAY [ecs[0]] *****
TASK [task2] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
例3:继承 tags
假如,某个标签存在 play 中的所有 task 中,像这种情况,我们可以把 公共存在的标签提取出来,如下所示:
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tags: tags-demo
tasks:
- name: task1
file:
path: /tmp/testfile
state: touch
tags: tag1
- name: task2
file:
path: /tmp/testfile
state: touch
tags: tag2
执行带有 tags-demo 标签的任务
$ ansible-playbook --tags=tags-demo playbook-tags-demo3.yml
PLAY [ecs[0]] *****
TASK [task1] ******
changed: [sz-aliyun-ecs-1]
TASK [task2] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
跳过带有 tags-demo 标签的任务
$ ansible-playbook --skip-tags=tags-demo playbook-tags-demo3.yml
PLAY [ecs[0]] ************************************************************************************************
PLAY RECAP ***************************************************************************************************
例4:列出剧本中标签列表
在执行剧本前,如果想看下 playbook 中都有哪些标签,可以使用 –list-tags 选项
$ ansible-playbook --list-tags playbook-tags-demo3.yml
playbook: playbook-tags-demo3.yml
play #1 (ecs[0]): ecs[0] TAGS: [tags-demo]
TASK TAGS: [tag1, tag2, tags-demo]
例5:内置 tags
ansible 预置了 5 个特殊 tag:
| Tag | 描述 |
|---|---|
always |
任务总是会被执行,除非 --skip-tags 显式跳过 |
never |
任务总是会被跳过,除非 --tags 显式声明 |
tagged |
调用标签时使用,只执行有标签的任务 |
untagged |
调用标签时使用,只执行无标签的任务(always 任务也会被跳过) |
all |
调用标签时使用,所有任务会被执行,默认就是这个 |
比较好理解,暂不演示~
五、变量
大致内容

官方文档:https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.htm
vars 声明变量
示例1:
- hosts: test70
remote_user: ecs[0]
vars:
filename: testfile
- name: task1
file:
path: /tmp/{{ filename }}
state: touch
示例2:
---
- hosts: test70
remote_user: root
vars:
tempfile:
file1: /tmp/tempfile1
file2: /tmp/tempfile2
tasks:
- name: task1
file:
path: "{{ tempfile.file1 }}"
state: touch
- name: task2
file:
path: "{{ tempfile['file2']] }}"
state: touch
var_files 引入变量
变量文件分离的好处,主要有以下几点
- 符合工程思维,易于维护
- 分析脚本文件,敏感保护
变量文件
---
key1: var file demo k1
key2: var file demo k2
剧本文件
---
- name: vars_files-demo1
hosts: db_server
gather_facts: no
vars_files:
- vars/var_files-demo1.yml
tasks:
- debug:
msg: " 【Key 1】: {{ key1 }} 【Key 2】: {{ key2 }}"
执行命令
$ ansible-playbook playbook-var_files-demo1.yml
PLAY [vars_files-demo1] **************************************************************************************************************
TASK [debug] **************************************************************************************************************
ok: [sz-aliyun-ecs-1] => {
"msg": " 【Key 1】: var file demo k1 【Key 2】: var file demo k2"
}
PLAY RECAP **************************************************************************************************************
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
vars & vars_files 支持混用
---
- name: vars_files-demo2
hosts: db_server
gather_facts: no
vars:
- key3: var-k3
vars_files:
- vars/var_files-demo1.yml
tasks:
- debug:
msg: " 【Key 1 from file】: {{ key1 }} 【Key 3 from var】: {{ key3 }}"
执行命令
$ ansible-playbook playbook-var_files-demo2.yml
PLAY [vars_files-demo2] ***********
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": " 【Key 1 from file】: var file demo k1 【Key 3 from var】: var-k3"
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
include_vars 导入变量
include_vars(变量包含),通过该指令可以实现在 task 中动态加载 yaml 文件中的变量
变量文件
$ cat vars/vars-demo1.yml
---
filename: include-demo4-filename
text: include-demo4-text
常规包含
通过指定变量文件路径及名称,引入其中所定义的变量
剧本定义
---
- name: include-demo4
hosts: ecs
gather_facts: no
tasks:
- include_vars:
file: "vars/vars-demo1.yml"
- debug:
msg: "{{ filename }}---{{ text }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
ok: [bj-huawei-hecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "include-demo4-filename---include-demo4-text"
}
ok: [bj-huawei-hecs-1] => {
"msg": "include-demo4-filename---include-demo4-text"
}
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
字段管理所有变量
将所有包含进来的变量统一归纳到一个类似与字典的结构中,通过 <name>.<key> 即可获取
剧本定义
---
- name: include-demo4
hosts: ecs
gather_facts: no
tasks:
- include_vars:
file: "vars/vars-demo1.yml"
name: filedata
- debug:
msg: "{{ filedata }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
ok: [bj-huawei-hecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": {
"filename": "include-demo4-filename",
"text": "include-demo4-text"
}
}
ok: [bj-huawei-hecs-1] => {
"msg": {
"filename": "include-demo4-filename",
"text": "include-demo4-text"
}
}
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
获取某一单独变量
剧本定义
---
- name: include-demo4
hosts: ecs
gather_facts: no
tasks:
- include_vars:
file: "vars/vars-demo1.yml"
name: filedata
- debug:
msg: "{{ filedata.filename }}---{{ filedata.text }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
ok: [bj-huawei-hecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "include-demo4-filename---include-demo4-text"
}
ok: [bj-huawei-hecs-1] => {
"msg": "include-demo4-filename---include-demo4-text"
}
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
包含目录下所有变量文件(dir)
删除 vars 所有文件,重新定义:
$ cat vars/vars-demo1.yml
---
key1: include-demo4-key1
key2: include-demo4-key2#
$ cat vars/vars-demo2.yml
---
key3: include-demo4-key3
key4: include-demo4-key4#
$ ansibleLearn cat vars/filedata.yml
---
filename: "testfile5"
text: "include-demo4"
剧本定义
---
- name: include-demo4
hosts: db_server
gather_facts: no
tasks:
- include_vars:
dir: "vars/"
name: "variables_dict"
- debug:
msg: "{{ variables_dict }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": {
"filename": "testfile5",
"key1": "include-demo4-key1",
"key2": "include-demo4-key2",
"key3": "include-demo4-key3",
"key4": "include-demo4-key4",
"text": "include-demo4"
}
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
正则载入特定变量文件(files_matching)
有时我们只想要载入符合特定规则的变量文件,可以这样做
剧本定义
---
- name: include-demo4
hosts: db_server
gather_facts: no
tasks:
- include_vars:
dir: "vars/"
files_matching: "^vars-.*"
name: "variables_dict"
- debug:
msg: "{{ variables_dict }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": {
"key1": "include-demo4-key1",
"key2": "include-demo4-key2",
"key3": "include-demo4-key3",
"key4": "include-demo4-key4"
}
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
正则过滤特定变量文件(ignore_files)
Ansible 不仅支持通过正则去匹配需要加载的变量文件名,还可以指明哪些变量文件不能被加载
剧本定义
---
- name: include-demo4
hosts: db_server
gather_facts: no
tasks:
- include_vars:
dir: "vars/"
ignore_files: ["^file.*", "vars-demo2.yml"]
name: "variables_dict"
- debug:
msg: "{{ variables_dict }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": {
"key1": "include-demo4-key1",
"key2": "include-demo4-key2"
}
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
获取本次任务引入的变量文件列表
ansible(2.4 版本) 在执行 include_vars 模块后会将载入的变量文件列表写入到自己的返回值中,这个返回值的关键字为 ansible_included_var_files,所以,如果我们想要知道本次任务引入了哪些变量文件,则可以使用如下方法:
剧本定义
---
- name: include-demo4
hosts: db_server
gather_facts: no
tasks:
- include_vars:
dir: "vars/"
ignore_files: ["^file.*", "vars-demo2.yml"]
name: "variables_dict"
register: return_val
- debug:
msg: "{{ return_val.ansible_included_var_files }}"
执行效果
$ ansible-playbook playbook-include-demo4.yml
PLAY [include-demo4] **************
TASK [include_vars] ***************
ok: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": [
"/prodata/scripts/ansibleLearn/vars/vars-demo1.yml"
]
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
fact 信息变量
setup 模块的返回信息都存在对应的变量中,可以通过变量引用信息,在使用前我们先了解下 debug 模块
debug 模块是用来进行调试的,它可以把信息输出到 ansible 控制台上,以便我们能够定位问题,示例如下:
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
text: Hello, World!
tasks:
- name: debug demo
debug:
msg: "He say {{ text }}"
执行命令
$ ansible-playbook playbook-debug-demo1.yml
PLAY [ecs[0]] *****
TASK [debug demo] *****************
ok: [sz-aliyun-ecs-1] => {
"msg": "He say Hello, World!"
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
OK,大致了解后,我们尝试用 debug 模块获取 facts 信息,需要注意的是确保 gather_facts 参数未被设置为 no
---
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- name: debug demo
debug:
msg: "Memory info: {{ ansible_memory_mb }}"
执行命令
$ ansible-playbook playbook-debug-demo2.yml
PLAY [ecs[0]] *****
TASK [Gathering Facts] **************************************************************************************************************
ok: [sz-aliyun-ecs-1]
TASK [debug demo] **************************************************************************************************************
ok: [sz-aliyun-ecs-1] => {
"msg": "Memory info: {u'real': {u'total': 1734, u'free': 69, u'used': 1665}, u'swap': {u'cached': 0, u'total': 0, u'used': 0, u'free': 0}, u'nocache': {u'used': 1151, u'free': 583}}"
}
PLAY RECAP **************************************************************************************************************
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
还可以配置为只获取部分信息,例如:
- name: debug demo
debug:
msg: "Memory info: {{ ansible_memory_mb['real'] }}"
执行命令
TASK [debug demo] *****************
ok: [sz-aliyun-ecs-1] => {
"msg": "Memory info: {u'total': 1734, u'free': 69, u'used': 1665}"
}
获取我们自定义的 facts 信息
fact 文件及定义
$ cat /etc/ansible/facts.d/testmsg.fact
{
"testmsg": {
"region": "aliyun-shenzhen",
"environment": "development"
}
}
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- name: debug demo
debug:
msg: "所属地区:{{ ansible_facts['ansible_local']['testmsg']['testmsg']['region'] }} 所属环境:{{ ansible_facts.ansible_local.testmsg.testmsg.environment }}"
执行命令
$ ansible-playbook playbook-debug-demo3.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
TASK [debug demo] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "所属地区:aliyun-shenzhen 所属环境:development"
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
OK,获取 fact 通常是为了做逻辑判断,例如:如果远程节点环境为 development 那么我们部署时的步骤是如何如何,如果是生产环境,部署步骤又是如何如何…加油啊,尽快学习到逻辑判断内块!冲冲冲!
register 注册变量
ansible 模块在运行之后会产生返回值,只不过不会显示,这点可以参照 Python 中的函数返回值,我们可以手动把模块返回值写入某个变量中,在 ansible 中 这种操作叫 ”注册变量”,怎么做呢,我们看个示例:
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: create testfile
shell: "echo test > /tmp/testfile"
register: ret
- name: get ret
debug:
var: ret
执行命令
$ ansible-playbook playbook-register-demo1.yml
PLAY [ecs[0]] ******
TASK [create testfile] ******
changed: [sz-aliyun-ecs-1]
TASK [get ret] ******
ok: [sz-aliyun-ecs-1] => {
"ret": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"cmd": "echo test > /tmp/testfile",
"delta": "0:00:00.036262",
"end": "2021-10-02 15:43:47.636774",
"failed": false,
"rc": 0,
"start": "2021-10-02 15:43:47.600512",
"stderr": "",
"stderr_lines": [],
"stdout": "",
"stdout_lines": []
}
}
PLAY RECAP ******
sz-aliyun-ecs-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
返回内容是 json 格式,这意味这我们可以只获取其中部分内容,例如:
- name: get ret
debug:
msg: "命令: {{ ret.cmd }} 返回码:{{ ret.rc }}"
执行命令
TASK [get ret] ********************
ok: [sz-aliyun-ecs-1] => {
"msg": "命令: echo test > /tmp/testfile 返回码:0"
}
OK~ 同 facts 信息一样,我们会基于模块返回值做后续的逻辑处理。比如,通过模块的返回值决定之后的一些动作,模块执行成功,执行什么操作,模块执行失败,执行什么操作等
模块返回值解读:https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html
PS:官方文档中并非所有模块的返回值都有说明,最好在使用前自己先通过这个方法确认下
vars_prompt 交互式写入变量
有时,我们希望用户输入一些信息,以此作为脚本后续执行的动作,在 ansible 可以通过 vars_prompt 实现需求
---
- name: playbook-vars_promote-demo1
hosts: ecs[0]
gather_facts: no
vars_prompt:
- name: "username"
prompt: "Please input your username"
# 用户名可以回显
private: no
- name: "password"
prompt: "Please input your password"
- name: "shell"
prompt: "Please choose login shell\n
A: /bin/bash\n
B: /usr/bin/zsh\n"
private: no
default: A
tasks:
- debug:
msg: "【Username】: {{ username }} 【Password】: {{ password }} 【Shell】:{{ shell }}"
执行命令
$ ansible-playbook playbook-vars_prompt-demo1.yml
Please input your username: Da
Please input your password:
Please choose login shell
A: /bin/bash
B: /usr/bin/zsh
[A]: a
PLAY [playbook-vars_prompt-demo1] ******
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": " 【Username】: Da 【Password】: Yo 【Shell】:a"
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
接下来,我们尝试通过 交互式剧本 创建 linux 用户
---
- name: playbook-vars_promote-demo2
hosts: ecs[0]
gather_facts: no
vars_prompt:
- name: "username"
prompt: "Please input your username"
# 用户名可以回显
private: no
- name: "password"
prompt: "Please input your password"
confirm: yes
encrypt: "sha512_crypt"
tasks:
- debug:
msg: "【Username】: {{ username }} 【Password】: {{ password }}"
- name: create user
user:
name: "{{ username }}"
password: "{{ password }}"
执行命令
$ ansible-playbook playbook-vars_prompt-demo2.yml
Please input your username: dayo
Please input your password:
confirm Please input your password:
PLAY [playbook-vars_promote-demo2] ******
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【Username】: dayo 【Password】: $6$PP1xu7fGP0vwawb4$gBl./ToldxFp0QNEk3S0.mkJr1rW/tSNy/.wlzcw2pyS5dZGPXGhROvQv4IICtLEzCDK8kgdtP1yUBOdxQi6U."
}
TASK [create user] ******
changed: [sz-aliyun-ecs-1]
PLAY RECAP ******
sz-aliyun-ecs-1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
登录确认
$ ssh dayo@xx.xx.xx.xx w
dayo@47.115.121.119's password:
16:30:49 up 201 days, 4:56, 5 users, load average: 0.12, 0.08, 0.06
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root pts/0 223.104.3.211 16:05 9.00s 0.97s 0.00s ssh dayo@47.115.121.119 w
root pts/3 223.104.3.211 16:05 2:49 1.75s 1.72s -zsh
root pts/5 223.104.3.211 13:34 2:54m 0.15s 0.15s -zsh
root pts/6 223.104.3.211 13:34 44:09 6.91s 6.91s -zsh
dayo pts/7 223.104.3.211 16:29 1:48 0.00s 0.00s -bash
–extra-vars 命令行传递变量
ansible 支持在执行 playbook 时传递变量,示例如下
剧本定义
---
- name: playbook-cmd_pass-demo1
hosts: ecs[0]
gather_facts: no
vars:
- username: "yo"
- password: "passwd"
- group_list: []
tasks:
- debug:
msg: "【Username】: {{ username }} 【Password】: {{ password }} group_list: {{ group_list }} 1th group: {{ group_list[0] }}"
执行时通过 等号传值
$ ansible-playbook playbook-cmd_pass-demo1.yml --extra-vars '{"username": "da", password: "yo", "group_list": ["sa", "dev"]}'
PLAY [playbook-cmd_pass-demo1] ******
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【Username】: da 【Password】: yo group_list: [u'sa', u'dev'] 1th group: sa"
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
执行时通过 json 传值
$ ansible-playbook playbook-cmd_pass-demo1.yml -e '{"username": "da", password: "yo", "group_list": ["sa", "dev"]}'
PLAY [playbook-cmd_pass-demo1] ******
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【Username】: da 【Password】: yo group_list: [u'sa', u'dev'] 1th group: sa"
}
PLAY RECAP ******
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
执行时通过 变量文件传值
$ cat vars/cmd_vars.yml
---
username: da
password: yo
group_list: ["sa", "dev"]
执行命令,使用 @ 符号加上变量文件的路径,即可在命令行中传入对应的变量文件
$ ansible-playbook playbook-cmd_pass-demo1.yml -e '@vars/cmd_vars.yml'
PLAY [playbook-cmd_pass-demo1] ******
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【Username】: da 【Password】: yo group_list: [u'sa', u'dev'] 1th group: sa"
}
PLAY RECAP ******
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
主机清单 配置变量
当我们在清单文件配置远程主机时 /etc/ansible/hosts,可以在配置的同时为 主机 赋予变量,示例如下
[ecs]
sz-aliyun-ecs-1 provider="aliyun"
bj-huawei-hecs-1 provider="huaweiyun"
执行命令
$ ansible ecs -m shell -a "echo {{ provider }}"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
aliyun
bj-huawei-hecs-1 | CHANGED | rc=0 >>
huaweiyun
ini 格式的配置文件,书写很方便,但是在类型支持上并不是很好,比如列表
[ecs]
sz-aliyun-ecs-1 provider="aliyun" node_groups=dev,stage
bj-huawei-hecs-1 provider="huaweiyun" node_groups=pro
执行命令
$ ansible ecs -m shell -a "echo {{ provider }} {{ node_groups }} {{ node_groups.0 }}"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
aliyun dev,stage d
bj-huawei-hecs-1 | CHANGED | rc=0 >>
huaweiyun pro p
YAML 格式 inventory 配置
all:
hosts:
sz-aliyun-ecs-1:
ansible_host: sz-aliyun-ecs-1
ansible_port: 22
provider: aliyun
node_groups:
- dev
- stage
bj-huawei-hecs-1:
ansible_host: bj-huawei-hecs-1
ansible_port: 22
provider: huawei
node_groups:
- pro
执行命令
$ ansible all -m shell -a "echo {{ provider }} {{ node_groups }} {{ node_groups.0 }}" -i /etc/ansible/hosts.yml
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
aliyun [udev, ustage] dev
bj-huawei-hecs-1 | CHANGED | rc=0 >>
huawei [upro] pro
我们除了为 主机 赋予变量外,还可以为 主机组 赋变量,示例如下:
[ecs:vars]
server_type='ecs'
执行命令
$ ansible all -m shell -a "echo {{ server_type }}"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
ecs
bj-huawei-hecs-1 | CHANGED | rc=0 >>
ecs
YAML 格式 inventory 配置
all:
# 固定套路
children:
# 主机组名称
vmgroup:
# 主机组列表
hosts:
sz-aliyun-ecs-1:
ansible_host: sz-aliyun-ecs-1
ansible_port: 22
provider: aliyun
node_groups:
- dev
- stage
bj-huawei-hecs-1:
ansible_host: bj-huawei-hecs-1
ansible_port: 22
provider: huawei
node_groups:
- pro
# 主机组变量
vars:
server_type: vm
执行命令
$ ansible all -m shell -a "echo {{ server_type }}" -i hosts.yml
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
vm
bj-huawei-hecs-1 | CHANGED | rc=0 >>
vm
set_fact 定义变量
set_fact 是一个模块,可以用来定义变量,它定义的变量是可以跨 play,直接开始看示例
---
- name: play-1
hosts: ecs[0]
remote_user: root
gather_facts: yes
vars:
- username: Da
tasks:
- set_fact:
nickname: Yo
- debug:
msg: "【play-1】 [username]: {{ username }} [nickname]: {{ nickname }} "
- name: play-2
hosts: ecs[1]
remote_user: root
gather_facts: yes
tasks:
- name: get other play vars
debug:
msg: "【play-2】 [username]: {{ username }} [nickname]: {{ nickname }} "
执行命令
$ ansible-playbook playbook-set_fact-demo1.yml
PLAY [play-1] ******
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
TASK [set_fact] ******
ok: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【play-1】 [username]: Da [nickname]: Yo "
}
PLAY [play-2] ******
TASK [Gathering Facts] ******
ok: [bj-huawei-hecs-1]
TASK [get other play vars] ******
fatal: [bj-huawei-hecs-1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'username' is undefined\n\nThe error appears to be in '/prodata/scripts/ansibleLearn/playbook-set_fact-demo1.yml': line 19, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: get other play vars\n ^ here\n"}
PLAY RECAP ******
bj-huawei-hecs-1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
返回了错误信息,很明显由 vars 关键字声明的 username 变量是无法跨 play,而 set_fact 可以!
调整变量定义,都使用 set_fact
- set_fact:
nickname: Yo
username: Da
执行效果
TASK [get other play vars] ********
ok: [sz-aliyun-ecs-1] => {
"msg": "【play-2】 [username]: Da [nickname]: Yo "
}
顺便补充一句,register 注册变量也是可以跨 play 的,如下所示:
---
- name: play-1
hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- shell: "echo Da"
register: username
- shell: "echo Yo"
register: nickname
- debug:
msg: "【play-1】 [username]: {{ username.stdout }} [nickname]: {{ nickname.stdout }} "
- name: play-2
hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- name: get other play vars
debug:
msg: "【play-2】 [username]: {{ username.stdout }} [nickname]: {{ nickname.stdout }} "
执行命令
$ ansible-playbook playbook-register_vars_test-demo1.yml
PLAY [play-1] *****
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
TASK [shell] ******
changed: [sz-aliyun-ecs-1]
TASK [shell] ******
changed: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【play-1】 [username]: Da [nickname]: Yo "
}
PLAY [play-2] *****
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
TASK [get other play vars] ********
ok: [sz-aliyun-ecs-1] => {
"msg": "【play-2】 [username]: Da [nickname]: Yo "
}
PLAY RECAP ********
sz-aliyun-ecs-1 : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
但是,无论是 set_fact 或 register 都是不可以跨主机的
$ ansible-playbook playbook-register_vars_test-demo1.yml
PLAY [play-1] *****
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
TASK [shell] ******
changed: [sz-aliyun-ecs-1]
TASK [shell] ******
changed: [sz-aliyun-ecs-1]
TASK [debug] ******
ok: [sz-aliyun-ecs-1] => {
"msg": "【play-1】 [username]: Da [nickname]: Yo "
}
PLAY [play-2] *****
TASK [Gathering Facts] ******
ok: [bj-huawei-hecs-1]
TASK [get other play vars] ********
fatal: [bj-huawei-hecs-1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'username' is undefined\n\nThe error appears to be in '/prodata/scripts/ansibleLearn/playbook-register_vars_test-demo1.yml': line 19, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: get other play vars\n ^ here\n"}
PLAY RECAP ********
bj-huawei-hecs-1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ansible 内置变量
ansible 中内置了一些变量供用户使用,当然,我们在定义变量时尽量要避免与这些变量冲突
1. ansible_version
获取到 ansible 的版本号,示例命令
$ ansible 'ecs[0]' -m debug -a "msg={{ansible_version}}"
sz-aliyun-ecs-1 | SUCCESS => {
"msg": {
"full": "2.9.25",
"major": 2,
"minor": 9,
"revision": 25,
"string": "2.9.25"
}
}
2. hostvars
通过 hostvars 可以实现在操作当前主机(B)时获取到其他主机(A)中的信息,示例如下:
---
- name: "play 1: Gather facts of aliyun-ecs"
hosts: ecs[0]
remote_user: root
- name: "play 2: Get facts of aliyun-ecs when operating on huaweiyun-hecs"
hosts: ecs[1]
remote_user: root
tasks:
- debug:
msg: "Get OS: {{ hostvars['sz-aliyun-ecs-1'].ansible_eth0.ipv4 }}"
执行命令
$ ansible-playbook playbook-builtin-vars-demo1.yml
PLAY [play 1: Gather facts of aliyun-ecs] ******
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
PLAY [play 2: Get facts of aliyun-ecs when operating on huaweiyun-hecs] ******
TASK [Gathering Facts] ******
ok: [bj-huawei-hecs-1]
TASK [debug] ******
ok: [bj-huawei-hecs-1] => {
"msg": "Get OS: {u'broadcast': u'172.25.175.255', u'netmask': u'255.255.240.0', u'network': u'172.25.160.0', u'address': u'172.25.163.48'}"
}
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
之前我们提过,无论是 set_fact 亦或是 register 他们声明(注册)的变量都只能跨 play,无法跨节点,但是现在,我们可以基于 hostvars 实现 跨节点、跨 play 的变量共享,示例如下:
---
- name: "play 1: Gather facts of aliyun-ecs"
hosts: sz-aliyun-ecs-1
remote_user: root
tasks:
- set_fact:
provider: "aliyun"
- name: "play 2: Get facts of aliyun-ecs when operating on huaweiyun-hecs"
hosts: bj-huawei-hecs-1
remote_user: root
tasks:
- debug:
msg: "VM Provider: {{ hostvars['sz-aliyun-ecs-1'].provider }}"
执行命令
$ ansible-playbook playbook-builtin-vars-demo2.yml
PLAY [play 1: Gather facts of aliyun-ecs] ******
TASK [Gathering Facts] ******
ok: [sz-aliyun-ecs-1]
TASK [set_fact] ******
ok: [sz-aliyun-ecs-1]
PLAY [play 2: Get facts of aliyun-ecs when operating on huaweiyun-hecs] ******
TASK [Gathering Facts] ******
ok: [bj-huawei-hecs-1]
TASK [debug] ******
ok: [bj-huawei-hecs-1] => {
"msg": "VM Provider: aliyun"
}
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3. inventory_hostname
通过 inventory_hostname 变量可以获取当前 play 操作的主机名称(并非 linux 主机名,而是 ansible 清单主机名)
[ecs]
ecs-1.aliyun.sz ansible_host=sz-aliyun-ecs-1
huawei ansible_host=bj-huawei-hecs-1
xx.xx.xx.119
执行命令
$ ansible ecs -m debug -a "msg={{inventory_hostname}}"
huawei | SUCCESS => {
"msg": "huawei"
}
ecs-1.aliyun.sz | SUCCESS => {
"msg": "ecs-1.aliyun.sz"
}
xx.xx.xx.119 | SUCCESS => {
"msg": "xx.xx.xx.119"
}
以什么标识主机,inventory_hostname 的值 就是什么
4. inventory_hostname_short
与 inventory_hostname 类似,只不过 inventory_hostname_short 更加简短
使用之前的定义,执行命令
$ ansible ecs -m debug -a "msg={{inventory_hostname_short}}"
47.115.121.119 | SUCCESS => {
"msg": "47"
}
huawei | SUCCESS => {
"msg": "huawei"
}
ecs-1.aliyun.sz | SUCCESS => {
"msg": "ecs-1"
}
5. play_hosts
通过 play_hosts 可以获取到当前 play 所操作的主机名列表
---
- name: "play 1: get play hosts"
hosts: ecs
remote_user: root
tasks:
- debug:
msg: "{{ play_hosts }}"
执行命令
$ ansible-playbook playbook-builtin-vars-demo3.yml
PLAY [play 1: get play hosts] *****
TASK [Gathering Facts] ******
ok: [47.115.121.119]
ok: [ecs-1.aliyun.sz]
ok: [huawei]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": [
"ecs-1.aliyun.sz",
"huawei",
"47.115.121.119"
]
}
ok: [huawei] => {
"msg": [
"ecs-1.aliyun.sz",
"huawei",
"47.115.121.119"
]
}
ok: [47.115.121.119] => {
"msg": [
"ecs-1.aliyun.sz",
"huawei",
"47.115.121.119"
]
}
PLAY RECAP ********
47.115.121.119 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
huawei : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
虽然 inventory_hostname 与 play_hosts 主机名,但前者返回的是 正在操作(执行)的主机名,后者返回 play 中所有被操作主机的列表,两个区别:1.时机、2.范围
6. groups
通过内置变量 groups 可以获取到清单中”分组信息”,当前清单如下:
[ecs]
ecs-1.aliyun.sz ansible_host=sz-aliyun-ecs-1
huawei ansible_host=bj-huawei-hecs-1
47.115.121.119
[ecs:vars]
server_type='ecs'
[bj_server]
bj-huawei-hecs-1
[sz_server]
sz-aliyun-ecs-1
[prod]
bj-huawei-hecs-1
[dev]
sz-aliyun-ecs-1
[web_server]
bj-huawei-hecs-1
[db_server]
sz-aliyun-ecs-1
[maintenance]
sz-aliyun-ecs-1
执行命令,查看 groups 会返回什么
$ ansible 'ecs[0]' -m debug -a "msg={{groups}}"
ecs-1.aliyun.sz | SUCCESS => {
"msg": {
"all": [
"sz-aliyun-ecs-1",
"bj-huawei-hecs-1",
"ecs-1.aliyun.sz",
"huawei",
"47.115.121.119"
],
"bj_server": [
"bj-huawei-hecs-1"
],
"db_server": [
"sz-aliyun-ecs-1"
],
"dev": [
"sz-aliyun-ecs-1"
],
"ecs": [
"ecs-1.aliyun.sz",
"huawei",
"47.115.121.119"
],
"maintenance": [
"sz-aliyun-ecs-1"
],
"prod": [
"bj-huawei-hecs-1"
],
"sz_server": [
"sz-aliyun-ecs-1"
],
"ungrouped": [],
"web_server": [
"bj-huawei-hecs-1"
]
}
}
获取指定组的主机列表
$ ansible 'ecs[0]' -m debug -a "msg={{groups.ecs}}"
ecs-1.aliyun.sz | SUCCESS => {
"msg": [
"ecs-1.aliyun.sz",
"huawei",
"47.115.121.119"
]
}
7. group_names
通过内置变量 group_names 获取到当前主机所在分组
查看所有节点,它们分别属于那些组
$ ansible all -m debug -a "msg={{group_names}}"
sz-aliyun-ecs-1 | SUCCESS => {
"msg": [
"db_server",
"dev",
"maintenance",
"sz_server"
]
}
bj-huawei-hecs-1 | SUCCESS => {
"msg": [
"bj_server",
"prod",
"web_server"
]
}
huawei | SUCCESS => {
"msg": [
"ecs"
]
}
ecs-1.aliyun.sz | SUCCESS => {
"msg": [
"ecs"
]
}
47.115.121.119 | SUCCESS => {
"msg": [
"ecs"
]
}
查看特定节点(基于清单中的主机名称)所属的组
$ ansible sz-aliyun-ecs-1 -m debug -a "msg={{group_names}}"
sz-aliyun-ecs-1 | SUCCESS => {
"msg": [
"db_server",
"dev",
"maintenance",
"sz_server"
]
}
8. inventory_dir
通过 inventory_dir 变量可以获取到主机清单文件路径
$ ansible 'ecs[0]' -m debug -a "msg={{inventory_dir}}"
ecs-1.aliyun.sz | SUCCESS => {
"msg": "/etc/ansible"
}
六、循环
大致内容

with_items
循环列表元素
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 每迭代一个元素,所属的 task 就会执行一次
- debug:
msg: "{{ item }}"
# with_items 循环迭代 [1, 2, 3]
with_items: [ 1, 2, 3 ]
执行命令
$ ansible-playbook playbook-loop-with_item-demo1.yml
PLAY [ecs[0]] *****
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=1) => {
"msg": 1
}
ok: [ecs-1.aliyun.sz] => (item=2) => {
"msg": 2
}
ok: [ecs-1.aliyun.sz] => (item=3) => {
"msg": 3
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
循环内置变量
早先我们讨论过,内置变量中的 groups 是展示组及组内主机,现在我们遍历下主机列表看看
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 每迭代一个元素,所属的 task 就会执行一次
- debug:
msg: "{{ item }}"
# with_items 循环迭代 [1, 2, 3]
with_items: "{{ groups.ecs }}"
执行命令
$ ansible-playbook playbook-loop-with_item-demo2.yml
PLAY [ecs[0]] *****
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=ecs-1.aliyun.sz) => {
"msg": "ecs-1.aliyun.sz"
}
ok: [ecs-1.aliyun.sz] => (item=huawei) => {
"msg": "huawei"
}
ok: [ecs-1.aliyun.sz] => (item=47.115.121.119) => {
"msg": "47.115.121.119"
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
循环字典
示例如下
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 每迭代一个元素,所属的 task 就会执行一次
- debug:
msg: "{{ item.username }}---{{ item.nickname }}"
# with_items 循环迭代 {}
with_items:
- {"username": "Da", "nickname": "Dayo"}
- {"username": "Yo", "nickname": "yoDa"}
执行命令
$ ansible-playbook playbook-loop-with_item-demo3.yml
PLAY [ecs[0]] *****
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item={u'username': u'Da', u'nickname': u'Dayo'}) => {
"msg": "Da---Dayo"
}
ok: [ecs-1.aliyun.sz] => (item={u'username': u'Yo', u'nickname': u'yoDa'}) => {
"msg": "Yo---yoDa"
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
循环创建文件
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filelist:
- /tmp/tempfile1
- /tmp/tempfile2
- /tmp/tempfile3
tasks:
# 每迭代一个元素,所属的 task 就会执行一次
- file:
path: "{{ item }}"
state: touch
# with_items 循环迭代 {}
with_items: "{{ filelist }}"
执行命令
$ ansible-playbook playbook-loop-with_item-demo4.yml
PLAY [ecs[0]] *****
TASK [file] *******
changed: [ecs-1.aliyun.sz] => (item=/tmp/tempfile1)
changed: [ecs-1.aliyun.sz] => (item=/tmp/tempfile2)
changed: [ecs-1.aliyun.sz] => (item=/tmp/tempfile3)
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
循环注册变量
注册变量,存储模块执行结果的地方,既然 with_items 在迭代时会执行多次,那此时的 register 记录的返回值是什么样呢?我很好奇
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 每迭代一个元素,所属的 task 就会执行一次
- shell: "{{ item }}"
register: ret
# with_items 循环迭代
with_items: ["ls /prodata", "ls /root"]
- debug:
msg: "{{ ret }}"
执行命令
$ ansible-playbook playbook-loop-with_item-demo5.yml
PLAY [ecs[0]] *****
TASK [shell] ******
changed: [ecs-1.aliyun.sz] => (item=ls /prodata)
changed: [ecs-1.aliyun.sz] => (item=ls /root)
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_loop_var": "item",
"changed": true,
"cmd": "ls /prodata",
"delta": "0:00:00.037048",
"end": "2021-10-02 23:07:22.615961",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "ls /prodata",
# ...
}
},
"item": "ls /prodata",
"rc": 0,
"start": "2021-10-02 23:07:22.578913",
"stderr": "",
"stderr_lines": [],
"stdout": "docker\ngitbook\njupyterlab\nleanote\nmrdoc\nscripts\nweb\nwiz",
"stdout_lines": [
# ...
]
},
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "ls /root",
"delta": "0:00:00.035308",
"end": "2021-10-02 23:07:22.926602",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "ls /root",
# ...
}
},
"item": "ls /root",
"rc": 0,
"start": "2021-10-02 23:07:22.891294",
"stderr": "",
"stderr_lines": [],
"stdout": "anki\ndata\ninstall-release.sh\nnode_exporter-0.16.0.linux-amd64\nnode_exporter-0.16.0.linux-amd64.tar.gz\nprometheus.tar.gz\nprometheus-webhook-dingtalk-1.4.0.linux-amd64.tar.gz\nsqlite-autoconf-3350500\nsqlite-autoconf-3350500.tar.gz",
"stdout_lines": [
# ...
]
}
]
}
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
可以看到,每条命令的执行结果被存入 results 这个列表中,我们可以再使用 with_items 遍历它获取每条命令的执行结果
tasks:
# 逐个执行命令
- shell: "{{ item }}"
# with_items 循环迭代 命令列表
with_items: ["ls /prodata", "ls /root"]
# 命令返回值存入 ret
register: ret
- debug:
# 目前这里有些问题,无论怎么尝试,打印的都是完整的命令返回,无法实现只获取部分属性值 {{ item.stdout }}
msg: "{{ item }}"
with_items: "{{ret.results}}"
执行命令
$ ansible-playbook playbook-loop-with_item-demo5.yml
PLAY [ecs[0]] *****
TASK [shell] ******
changed: [ecs-1.aliyun.sz] => (item=ls /prodata)
changed: [ecs-1.aliyun.sz] => (item=ls /root)
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item={u'stderr_lines': [], u'ansible_loop_var': u'item', u'end': u'2021-10-02 23:18:36.030348', u'failed': False, u'stdout': u'docker\ngitbook\njupyterlab\nleanote\nmrdoc\nscripts\nweb\nwiz', u'changed': True, u'ansible_facts': {u'discovered_interpreter_python': u'/usr/bin/python'}, u'item': u'ls /prodata', u'delta': u'0:00:00.035269', u'cmd': u'ls /prodata', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'ls /prodata', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'stdout_lines': [u'docker', u'gitbook', u'jupyterlab', u'leanote', u'mrdoc', u'scripts', u'web', u'wiz'], u'start': u'2021-10-02 23:18:35.995079'}) => {
"msg": "ls /prodata"
}
ok: [ecs-1.aliyun.sz] => (item={u'stderr_lines': [], u'ansible_loop_var': u'item', u'end': u'2021-10-02 23:18:36.349611', u'failed': False, u'stdout': u'anki\ndata\ninstall-release.sh\nnode_exporter-0.16.0.linux-amd64\nnode_exporter-0.16.0.linux-amd64.tar.gz\nprometheus.tar.gz\nprometheus-webhook-dingtalk-1.4.0.linux-amd64.tar.gz\nsqlite-autoconf-3350500\nsqlite-autoconf-3350500.tar.gz', u'changed': True, u'item': u'ls /root', u'delta': u'0:00:00.034596', u'cmd': u'ls /root', u'stderr': u'', u'rc': 0, u'invocation': {u'module_args': {u'warn': True, u'executable': None, u'_uses_shell': True, u'strip_empty_ends': True, u'_raw_params': u'ls /root', u'removes': None, u'argv': None, u'creates': None, u'chdir': None, u'stdin_add_newline': True, u'stdin': None}}, u'stdout_lines': [u'anki', u'data', u'install-release.sh', u'node_exporter-0.16.0.linux-amd64', u'node_exporter-0.16.0.linux-amd64.tar.gz', u'prometheus.tar.gz', u'prometheus-webhook-dingtalk-1.4.0.linux-amd64.tar.gz', u'sqlite-autoconf-3350500', u'sqlite-autoconf-3350500.tar.gz'], u'start': u'2021-10-02 23:18:36.315015'}) => {
"msg": "ls /root"
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
循环拉平列表
当 with_items 遍历 2 个列表时,会自动将两个列表拉平(合并),以 元素 为单位逐个遍历,示例如下:
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 逐个打印元素
- debug:
msg: "{{ item }}"
# 多个一层列表会被拉平(合并)
with_items:
- [1, 2]
- [a, b]
执行命令
$ ansible-playbook playbook-loop-with_item-demo6.yml
PLAY [ecs[0]] *****
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=1) => {
"msg": 1
}
ok: [ecs-1.aliyun.sz] => (item=2) => {
"msg": 2
}
ok: [ecs-1.aliyun.sz] => (item=a) => {
"msg": "a"
}
ok: [ecs-1.aliyun.sz] => (item=b) => {
"msg": "b"
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_list
循环列表
当 with_list 遍历列表时,不会像 with_items 那般将两个列表拉平(合并),而是以 列表 为单位进行遍历,示例如下:
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 逐个打印子列表
- debug:
msg: "{{ item }}"
# 不拉平列表,以整个列表为单位遍历 == for lst in [[], []]: lst
with_list:
- [1, 2]
# - [a, b]
执行命令
$ ansible-playbook playbook-loop-with_list-demo1.yml
PLAY [ecs[0]] *****
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[1, 2]) => {
"msg": [
1,
2
]
}
ok: [ecs-1.aliyun.sz] => (item=[u'a', u'b']) => {
"msg": [
"a",
"b"
]
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
如上述信息所示,经过 with_list 处理后,每个列表都被当做一个整体存放在 item 变量中,最终被 debug 作为一个小整体输出了,没有像with_items ”拉平合并” 后一并将所有列表内的元素循环输出
需要注意,即便是一个列表,with_list 也不会按照列表内元素为单位进行遍历,如下所示
with_list:
- [1, 2]
执行命令
ok: [ecs-1.aliyun.sz] => (item=[1, 2]) => {
"msg": [
1,
2
]
}
顺便补充下嵌套列表的声明方式:
第一种:
with_list:
- [1, 2]
- [a, b]
第二种:
with_list:
-
- 1
- 2
-
- a
- b
执行效果是以一样的
ok: [ecs-1.aliyun.sz] => (item=[1, 2]) => {
"msg": [
1,
2
]
}
ok: [ecs-1.aliyun.sz] => (item=[u'a', u'b']) => {
"msg": [
"a",
"b"
]
}
with_flattened
压平循环列表
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 逐个打印元素
- debug:
msg: "{{ item }}"
# 多个一层列表会被拉平(合并)
with_flattened:
- [1, 2]
- [a, b]
执行
$ ansible-playbook playbook-loop-with_flattened-demo1.yml
PLAY [ecs[0]] *****
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=1) => {
"msg": 1
}
ok: [ecs-1.aliyun.sz] => (item=2) => {
"msg": 2
}
ok: [ecs-1.aliyun.sz] => (item=a) => {
"msg": "a"
}
ok: [ecs-1.aliyun.sz] => (item=b) => {
"msg": "b"
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_together
对齐合并元素
with_together 可以将两个列表中的元素 ”对齐合并”,可以理解为 Python 中的 zip 函数
>>> [l for l in zip([1, 2, 3], ['a', 'b', 'c'])]
[(1, 'a'), (2, 'b'), (3, 'c')]
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 逐个打印子列表
- debug:
msg: "{{ item }}"
# 不拉平列表,以整个列表为单位遍历 == for lst in [[], []]: lst
with_together:
-
- 1
- 2
-
- a
- b
执行命令
$ ansible-playbook playbook-loop-with_together-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[1, u'a']) => {
"msg": [
1,
"a"
]
}
ok: [ecs-1.aliyun.sz] => (item=[2, u'b']) => {
"msg": [
2,
"b"
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_cartesian
获取列表笛卡尔集
笛卡尔集,直接看示例:
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
# 笛卡尔集,[(1, a), (1, b), (2, a), (2, b)]
with_cartesian:
- [1, 2]
- [a, b]
执行命令
$ ansible-playbook playbook-loop-with_cartesian-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[1, u'a']) => {
"msg": [
1,
"a"
]
}
ok: [ecs-1.aliyun.sz] => (item=[1, u'b']) => {
"msg": [
1,
"b"
]
}
ok: [ecs-1.aliyun.sz] => (item=[2, u'a']) => {
"msg": [
2,
"a"
]
}
ok: [ecs-1.aliyun.sz] => (item=[2, u'b']) => {
"msg": [
2,
"b"
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
创建多级目录
为了更好的理解 with_cartesian 用法,我们做个小测试,如过要求你创建以下目录结构,你该怎么做?
$ tree
├── a
│ ├── 1
│ └── 2
└── b
├── 1
└── 2
如过用 Shell 命令
$ ansible 'ecs[0]' -m shell -a "mkdir -p /tmp/testdir/{a,b}/{1,2} && tree /tmp/testdir/"
ecs-1.aliyun.sz | CHANGED | rc=0 >>
/tmp/testdir/
├── a
│ ├── 1
│ └── 2
└── b
├── 1
└── 2
6 directories, 0 files
如过是用 剧本呢?OK,我们来看下
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- file:
state: directory
path: "/tmp/testdir2/{{ item.0 }}/{{ item.1 }}"
# 笛卡尔集,[(a, 1), (a, 2), (b, 1, (b, 2)]
with_cartesian:
- [a, b]
- [1, 2]
执行命令
$ ansible-playbook playbook-loop-with_cartesian-demo2.yml
PLAY [ecs[0]] ******
TASK [file] ******
changed: [ecs-1.aliyun.sz] => (item=[u'a', 1])
changed: [ecs-1.aliyun.sz] => (item=[u'a', 2])
changed: [ecs-1.aliyun.sz] => (item=[u'b', 1])
changed: [ecs-1.aliyun.sz] => (item=[u'b', 2])
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_nested
基本功能作用与 with_cartesian 等同,所以大致看下就行
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
# 笛卡尔集,[(1, a), (1, b), (2, a), (2, b)]
with_nested:
- [a, b]
- [1, 2]
执行命令
$ ansible-playbook playbook-loop-with_nested-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[u'a', 1]) => {
"msg": [
"a",
1
]
}
ok: [ecs-1.aliyun.sz] => (item=[u'a', 2]) => {
"msg": [
"a",
2
]
}
ok: [ecs-1.aliyun.sz] => (item=[u'b', 1]) => {
"msg": [
"b",
1
]
}
ok: [ecs-1.aliyun.sz] => (item=[u'b', 2]) => {
"msg": [
"b",
2
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_indexed_items
with_indexed_items 的作用类似于 Python 中的 enumrate 函数,可以为列表元素分配索引序号
In [3]: [i for i in enumerate(['a', 'b'])]
Out[3]: [(0, 'a'), (1, 'b')]
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "索引:{{ item.0 }} 值:{{ item.1 }}"
# 为元素分配索引序号 [(0, a), (1, b)]
with_indexed_items:
- [a, b]
执行命令
$ ansible-playbook playbook-loop-with_indexed_items-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[0, u'a']) => {
"msg": "索引:0 值:a"
}
ok: [ecs-1.aliyun.sz] => (item=[1, u'b']) => {
"msg": "索引:1 值:b"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_indexed_items 还支持多个列表,默认会把最外层拉平(合并),不过嵌套在内层的列表不会处理,如下所示:
with_indexed_items:
- [a, b]
- [c, d]
- [e, [f, g]]
执行命令
$ ansible-playbook playbook-loop-with_indexed_items-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[0, u'a']) => {
"msg": "索引:0 值:a"
}
ok: [ecs-1.aliyun.sz] => (item=[1, u'b']) => {
"msg": "索引:1 值:b"
}
ok: [ecs-1.aliyun.sz] => (item=[2, u'c']) => {
"msg": "索引:2 值:c"
}
ok: [ecs-1.aliyun.sz] => (item=[3, u'd']) => {
"msg": "索引:3 值:d"
}
ok: [ecs-1.aliyun.sz] => (item=[4, u'e']) => {
"msg": "索引:4 值:e"
}
ok: [ecs-1.aliyun.sz] => (item=[5, [u'f', u'g']]) => {
"msg": "索引:5 值:[u'f', u'g']"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_sequence
with_sequence 有些类似于 Python 中的 range() 函数,支持以特性的步长生成指定范围的数字,再交由 with_ 循环迭代
正序遍历
步长为1,打印 1-3
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_sequence: start=1 end=3 stride=1
执行命令
$ ansible-playbook playbook-loop-with_sequence-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=1) => {
"msg": "1"
}
ok: [ecs-1.aliyun.sz] => (item=2) => {
"msg": "2"
}
ok: [ecs-1.aliyun.sz] => (item=3) => {
"msg": "3"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
倒序遍历
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_sequence: start=6 end=0 stride=-2
执行命令
$ ansible-playbook playbook-loop-with_sequence-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=6) => {
"msg": "6"
}
ok: [ecs-1.aliyun.sz] => (item=4) => {
"msg": "4"
}
ok: [ecs-1.aliyun.sz] => (item=2) => {
"msg": "2"
}
ok: [ecs-1.aliyun.sz] => (item=0) => {
"msg": "0"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_random_choice
with_random_choice 类似于 Python 中的 random.choice() 函数
In[4]: random.choice([1, 2, 'a', 'b'])
Out[4]: 'a'
In[5]random.choice([1, 2, 'a', 'b'])
Out[5]: 'b'
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
# 从以下元素中随机返回一个
with_random_choice:
- a
- b
- c
- 1
- 2
- 3
执行命令
$ ansible-playbook playbook-loop-with_random_choice-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=c) => {
"msg": "c"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
with_dict
字典格式的 user_info 变量经过 with_dict 处理后,以字典中最外层的 键值对 为单位进行遍历,存入 item 变量中
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
user_info:
name: Da
gender: male
tasks:
- debug:
msg: "{{ item }}"
with_dict: "{{ user_info }}"
执行命令
$ ansible-playbook playbook-loop-with_dict-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item={u'key': u'gender', u'value': u'male'}) => {
"msg": {
"key": "gender",
"value": "male"
}
}
ok: [ecs-1.aliyun.sz] => (item={u'key': u'name', u'value': u'Da'}) => {
"msg": {
"key": "name",
"value": "Da"
}
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
理解了基本用法后,我们拓展下,将字典复杂度提高些
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
user_info:
Da:
name: Da
gender: male
Yo:
name: Yo
gdner: female
tasks:
- debug:
msg: "{{ item }}"
with_dict: "{{ user_info }}"
执行命令
$ ansible-playbook playbook-loop-with_dict-demo2.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item={u'key': u'Yo', u'value': {u'gdner': u'female', u'name': u'Yo'}}) => {
"msg": {
"key": "Yo",
"value": {
"gdner": "female",
"name": "Yo"
}
}
}
ok: [ecs-1.aliyun.sz] => (item={u'key': u'Da', u'value': {u'gender': u'male', u'name': u'Da'}}) => {
"msg": {
"key": "Da",
"value": {
"gender": "male",
"name": "Da"
}
}
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
OK,可以看到,with_dict 是以字典中最外层的键值对进行遍历的,最外层是啥,key 就是啥~
with_subelements
这里理解起来有抽象,直接看例子
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
userlist:
- name: Da
gender: male
skill:
- linux
- python
- name: Yo
gender: female
skill:
- golang
tasks:
- debug:
# item = [{dict_key: dict_value}, skill_item]
msg: "{{ item }}"
with_subelements:
# 遍历 userlist
- "{{ userlist }}"
# 基于 skill 循环迭代
# skill 字段值在最外层,其他属性会存放至字典中
- skill
执行命令
$ ansible-playbook playbook-loop-with_subelements-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[{u'gender': u'male', u'name': u'Da'}, u'linux']) => {
"msg": [
{
"gender": "male",
"name": "Da"
},
"linux"
]
}
ok: [ecs-1.aliyun.sz] => (item=[{u'gender': u'male', u'name': u'Da'}, u'python']) => {
"msg": [
{
"gender": "male",
"name": "Da"
},
"python"
]
}
ok: [ecs-1.aliyun.sz] => (item=[{u'gender': u'female', u'name': u'Yo'}, u'golang']) => {
"msg": [
{
"gender": "female",
"name": "Yo"
},
"golang"
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
如果不太好理解的话,我们格式化下输出
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
userlist:
- name: Da
gender: male
skill:
- linux
- python
- name: Yo
gender: female
skill:
- golang
tasks:
- debug:
# item = [{dict_key: dict_value}, skill_item]
msg: "用户:{{ item.0.name }} 性别:{{ item.0.gender }} 技能:{{ item.1 }}"
with_subelements:
# 遍历 userlist
- "{{ userlist }}"
# 基于 skill 循环迭代
# skill 字段值在最外层,其他属性会存放至字典中
- skill
执行命令
$ ansible-playbook playbook-loop-with_subelements-demo2.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=[{u'gender': u'male', u'name': u'Da'}, u'linux']) => {
"msg": "用户:Da 性别:male 技能:linux"
}
ok: [ecs-1.aliyun.sz] => (item=[{u'gender': u'male', u'name': u'Da'}, u'python']) => {
"msg": "用户:Da 性别:male 技能:python"
}
ok: [ecs-1.aliyun.sz] => (item=[{u'gender': u'female', u'name': u'Yo'}, u'golang']) => {
"msg": "用户:Yo 性别:female 技能:golang"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
现在基本能理解了吧~
with_file
获取 ansible 中控节点的文件内容,还是看例子吧
首先,在 ansible 节点上生成几个测试文件
$ cat t1.txt
t1 test1 test file 1
$ cat t2.txt
t2 test2 test file 2
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_file:
- /tmp/testdir/t1.txt
- /tmp/testdir/t2.txt
执行命令
$ ansible-playbook playbook-loop-with_file-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => (item=t1 test1 test file 1) => {
"msg": "t1 test1 test file 1"
}
ok: [ecs-1.aliyun.sz] => (item=t2 test2 test file 2) => {
"msg": "t2 test2 test file 2"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
PS:需要注意,无论这个任务在那个节点执行,最终获取文件都是从 ansible 主机读取
with_fileglob
with_file 用来获取文件内容 with_fileglob 是用来匹配文件名称,如下所示:
- hosts: ecs[1]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ file_item }}"
with_fileglob:
- /tmp/testdir/*.txt
loop_control:
loop_var: file_item
执行命令
$ ansible-playbook playbook-loop-with_fileglob-demo1.yml
PLAY [ecs[1]] ******
TASK [debug] ******
ok: [huawei] => (item=/tmp/testdir/t1.txt) => {
"msg": "/tmp/testdir/t1.txt"
}
ok: [huawei] => (item=/tmp/testdir/t2.txt) => {
"msg": "/tmp/testdir/t2.txt"
}
PLAY RECAP ******
huawei : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
PS:同样需要注意,无论这个任务在那个节点执行,最终获取文件都是从 ansible 主机尝试匹配
jinja2 for
循环命令返回
Jinja2 语法,它遍历出的结果是符合预期的
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
# 逐个执行命令
- shell: "{{ item }}"
# with_items 循环迭代 命令列表
with_items: ["ls /prodata", "ls /root"]
# 命令返回值存入 ret
register: ret
- debug:
# 目前这里有些问题,无论怎么尝试,打印的都是完整的命令返回,无法实现只获取部分属性值 {{ item.stdout }}
msg:
"{% for i in ret.results %}
命令输出:{{ i.stdout }}
{% endfor %}"
执行命令
$ ansible-playbook playbook-loop-jinja2-for-demo1.yml
PLAY [ecs[0]] *****
TASK [shell] ******
changed: [ecs-1.aliyun.sz] => (item=ls /prodata)
changed: [ecs-1.aliyun.sz] => (item=ls /root)
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " 命令输出:docker\ngitbook\njupyterlab\nleanote\nmrdoc\nscripts\nweb\nwiz 命令输出:anki\ndata\ninstall-release.sh\nnode_exporter-0.16.0.linux-amd64\nnode_exporter-0.16.0.linux-amd64.tar.gz\nprometheus.tar.gz\nprometheus-webhook-dingtalk-1.4.0.linux-amd64.tar.gz\nsqlite-autoconf-3350500\nsqlite-autoconf-3350500.tar.gz "
}
PLAY RECAP ********
ecs-1.aliyun.sz : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
七、判断
ansible 与大多数语言不同,它没有选择使用 if 作为条件判断的关键字,而是 when 不过是什么无所谓了,名字而已
大致内容

when
先看下 ansible 支持的比较运算符
| 符号 | 描述 |
|---|---|
== |
比较两个对象是否相等 |
!= |
比较两个对象是否不等 |
> |
比较两个值的大小 |
< |
比较两个值的大小 |
>= |
比较两个值的大小 |
<= |
比较两个值的大小 |
再看下逻辑运算符
| 符号 | 描述 |
|---|---|
and |
逻辑与 |
or |
逻辑或 |
not |
取反 |
() |
组合,将一组操作体包装在一起 |
OK,接下来我们看例子
判断列表元素大于指定数
遍历 1~3,只打印大于 1 的元素,算是简单复习下上面的
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- debug:
msg: "{{ item }}"
with_items:
- 1
- 2
- 3
# when 关键字 使用变量 不需要显式 {{}}
when: item > 1
执行命令
$ ansible-playbook playbook-when-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
skipping: [ecs-1.aliyun.sz] => (item=1)
ok: [ecs-1.aliyun.sz] => (item=2) => {
"msg": 2
}
ok: [ecs-1.aliyun.sz] => (item=3) => {
"msg": 3
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断当前系统平台及版本
fact 信息中包含系统平台,所以基于此我们可以实现判断系统版本从而执行对应任务,示例如下:
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- debug:
msg: "当前系统版本为:{{ ansible_distribution }} {{ ansible_distribution_major_version }}"
# when 关键字 使用变量 不需要显式 {{}}
when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "7"
执行命令
$ ansible-playbook playbook-when-demo2.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "当前系统版本为:CentOS 7"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断命令是否执行成功
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- name: tasks1
# shell: "ls /"
shell: "ls /not_exist_file"
register: ret
# 忽略错误,否则当前任务失败后,剧本停止运行
ignore_errors: true
- debug:
msg: "命令:{{ ret.cmd }} 执行成功!"
# when 关键字 使用变量 不需要显式 {{}}
when: ret.rc == 0
- debug:
msg: "命令:{{ ret.cmd }} 执行失败!"
# when 关键字 使用变量 不需要显式 {{}}
when: ret.rc != 0
执行命令
$ ansible-playbook playbook-when-demo3.yml
PLAY [ecs[0]] ******
TASK [tasks1] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "ls /not_exist_file", "delta": "0:00:00.036502", "end": "2021-10-03 19:41:39.178106", "msg": "non-zero return code", "rc": 2, "start": "2021-10-03 19:41:39.141604", "stderr": "ls: cannot access /not_exist_file: No such file or directory", "stderr_lines": ["ls: cannot access /not_exist_file: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring
TASK [debug] ******
skipping: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "命令:ls /not_exist_file 执行失败!"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=1
failed_when
见下面 [failed_when 变更任务状态](#failed_when 变更任务状态) 小节
changed_when
见下面 [changed_when 变更任务状态](#changed_when 变更任务状态) 小节
until
通过 until 指令,可以实现同一个任务多次执行,直到满足某条件才停止,例如:当前网络状态不佳,下载安装 Jenkins 插件很有可能出现异常,这是我们可以通过 until 实现失败重试,如下所示:
- name: Install Jenkins plugins using password.
jenkins_plugin:
name: "{{ item.name | default(item) }}"
version: "{{ item.version | default(omit) }}"
jenkins_home: "{{ jenkins_home }}"
# Jenkins 用户名密码
url_username: "{{ jenkins_admin_username }}"
url_password: "{{ jenkins_admin_password }}"
# state 'present' 安装插件
state: "{{ 'present' if item.version is defined else jenkins_plugins_state }}"
# 安装超时
timeout: "{{ jenkins_plugin_timeout }}"
# update-center.json 过期时间
updates_expiration: "{{ jenkins_plugin_updates_expiration }}"
# 插件更新中心 d
updates_url: "{{ jenkins_updates_url }}"
#
url: "http://{{ jenkins_hostname }}:{{ jenkins_http_port }}{{ jenkins_url_prefix }}"
with_dependencies: "{{ jenkins_plugins_install_dependencies }}"
# 遍历 插件列表 逐个安装
with_items: "{{ jenkins_plugins }}"
when: jenkins_admin_password | default(false)
notify: restart jenkins
tags: ['skip_ansible_lint']
register: plugin_result
until: plugin_result is success
# 失败重试测试
retries: 3
# 失败重试间隔(s)
delay: 2
基于这个思路可以做很多事情,诸如滚动发布啊,测试某个 HTTP 响应是否正常,等到正常后在进行后续逻辑
通常 until 会配合 retries 与 delay 进行使用
tests
linux 中 tests 通常用来测试文件的各种属性,如文件类型、权限、属主、属组等,如下所示:
# 测试是否为目录
$ test -d hosts.yml
$ echo $?
1
# 测试是否为存在
$ test -e hosts.yml
$ echo $?
0
# 测试是否为目录
$ test -d hosts.yml
$ echo $?
1
# 测试是否为存在
$ test -d vars
$ echo $?
0
ansible 使用 jinja2 中的 tests 实现类似 test 命令的功能,我们接下来慢慢看
判断文件是否存在
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filepath: "/tmp/testfile9"
tasks:
- debug:
msg: "文件:{{ filepath }} 存在!"
when: filepath is exists
- debug:
msg: "文件:{{ filepath }} 不存在!"
# 等同于 not filepath is exists
when: filepath is not exists
执行命令
$ ansible-playbook playbook-tests-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
skipping: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "文件:/tmp/testfile9 不存在!"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
判断变量定义
| 关键字 | 描述 |
|---|---|
defined |
变量是否定义 |
undefind |
变量是否未定义 |
none |
变量是否为空 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
var1: "/tmp/testfile9"
var2:
tasks:
- debug:
msg: "变量 var1:已定义 值为 {{ var1 }}!"
# when 关键字 使用变量 不需要显式 {{}}
when: var1 is defined and var1 is not none
- debug:
msg: "变量 var2:已定义 值为空!"
# when 关键字 使用变量 不需要显式 {{}}
when: var2 is none
- debug:
msg: "变量 var3 未定义!"
# when 关键字 使用变量 不需要显式 {{}}
when: var3 is undefined
执行命令
$ ansible-playbook playbook-tests-demo2.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量 var1:已定义 值为 /tmp/testfile9!"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量 var2:已定义 值为空!"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量 var3 未定义!"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断任务状态
| 关键字 | 描述 |
|---|---|
| `success | succeeded` |
| `failure | failed` |
| `change | changed` |
| `skip | skipped` |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
# 命令控制开关,也没啥特别的意义,就是练个手
enable_task: "yes"
tasks:
- name: success task
shell: "ls /tmp"
register: success_ret
ignore_errors: true
when: enable_task == "yes"
- name: failed task
shell: "ls /123123"
register: failed_ret
ignore_errors: true
when: enable_task == "yes"
- name: skip task
shell: "ls /asd"
register: skip_ret
when: enable_task == "no"
- debug:
msg: "命令:{{ success_ret.cmd }} 执行成功~"
when: success_ret is success
- debug:
msg: "命令:{{ success_ret.cmd }} 产生变更~"
when: success_ret is changed
- debug:
msg: "命令:{{ failed_ret.cmd }} 执行失败!"
when: failed_ret is failed
- debug:
msg: "failed task 已跳过..."
when: skip_ret is skipped
执行命令
$ ansible-playbook playbook-tests-demo3.yml
PLAY [ecs[0]] ******
TASK [success task] ******
changed: [ecs-1.aliyun.sz]
TASK [failed task] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"changed": true, "cmd": "ls /123123", "delta": "0:00:00.036857", "end": "2021-10-03 20:47:07.767571", "msg": "non-zero return code", "rc": 2, "start": "2021-10-03 20:47:07.730714", "stderr": "ls: cannot access /123123: No such file or directory", "stderr_lines": ["ls: cannot access /123123: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring
TASK [skip task] ******
skipping: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "命令:ls /tmp 执行成功~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "命令:ls /tmp 产生变更~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "命令:ls /123123 执行失败!"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "failed task 已跳过..."
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=6 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=1
判断路径类型
| 关键字 | 描述 |
|---|---|
file |
普通文件 |
directory |
目录 |
link |
软链接文件 |
mount |
挂载点 |
exists |
存在 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
file_path: /tmp/testfile
dir_path: /tmp/testdir
link_path: /root/.pyenv/bin/pyenv
mount_path: /sys/fs/cgroup
exists_path: /tmp/
tasks:
- debug:
msg: "路径:{{ file_path }} 为普通文件~"
when: file_path is file
- debug:
msg: "路径:{{ file_path }} 为目录 ~"
when: dir_path is directory
- debug:
msg: "路径:{{ file_path }} 为软链接!"
when: link_path is link
- debug:
msg: "路径:{{ file_path }} 为 挂载点!"
when: mount_path is mount
- debug:
msg: "路径:{{ file_path }} 存在!"
when: exists_path is exists
执行命令
$ ansible-playbook playbook-tests-demo4.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "路径:/tmp/testfile 为普通文件~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "路径:/tmp/testfile 为目录 ~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "路径:/tmp/testfile 为软链接!"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "路径:/tmp/testfile 为 挂载点!"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "路径:/tmp/testfile 存在!"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断字符串大小写
| 关键字 | 描述 |
|---|---|
lower |
小写 |
upper |
大写 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
lower_var: "abc"
upper_var: "ABC"
tasks:
- debug:
msg: "变量:{{ lower_var }} 为 小写字母~"
when: lower_var is lower
- debug:
msg: "变量:{{ upper_var }} 为 大写字母~"
when: upper_var is upper
执行命令
$ ansible-playbook playbook-tests-demo5.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:abc 为 小写字母~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:ABC 为 大写字母~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断数字奇偶
| 关键字 | 描述 |
|---|---|
odd |
是否为奇数 |
even |
是否为偶数 |
divisibleby(num) |
是否能整除 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
odd_var: 7
even_var: 8
num: 2
tasks:
- debug:
msg: "变量:odd_var {{ odd_var }} 为 奇数~"
when: odd_var is odd
- debug:
msg: "变量:even_var {{ even_var }} 为 偶数~"
when: even_var is even
- debug:
msg: "变量:even_var {{ even_var }} 可以被 {{ num }} 整除~"
# 判断数字能否整除 指定数字
when: even_var is divisibleby(num)
执行命令
$ ansible-playbook playbook-tests-demo6.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:odd_var 7 为 奇数~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:even_var 8 为 偶数~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:even_var 8 可以被 2 整除~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断版本大小
| 关键字|操作符 | 描述 |
|---|---|
version |
对比两个版本号的大小,version(版本号, 比较操作符) |
>, gt |
大于 |
>=, ge |
大于等于 |
<, lt |
小于 |
<=, le |
小于等于 |
==, =, eq |
等于 |
!=, <>, ne |
不等于 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- debug:
msg: "当前系统版本大于 6!"
when: ansible_distribution is version("6", "gt")
执行命令
$ ansible-playbook playbook-tests-demo7.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "当前系统版本大于 6!"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
列表父子集判断
| 函数 | 描述 |
|---|---|
subset() |
判断一个 list 是不是另一个 list 的子集 |
superset() |
判断一个 list 是不是另一个 list 的父集 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
list_a:
- 1
- 2
list_b: [1, 2, 3]
tasks:
- debug:
msg: "list_a {{ list_a }} 是 list_b {{ list_b }} 的子集~"
when: list_a is subset(list_b)
- debug:
msg: "list_b {{ list_a }} 是 list_a {{ list_b }} 的父集~"
when: list_b is superset(list_a)
执行命令
$ ansible-playbook playbook-tests-demo8.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "list_a [1, 2] 是 list_b [1, 2, 3] 的子集~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "list_b [1, 2] 是 list_a [1, 2, 3] 的父集~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
判断变量类型
| 函数 | 描述 |
|---|---|
string |
判断对象是否是字符串 |
number |
判断对象是否是数字 |
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
username: "Da"
age: 18
weight: 62.1
gender: "1"
tasks:
- debug:
msg: "变量:username 值 {{ username }} 是字符串~"
when: username is string
- debug:
msg: "变量:age 值 {{ age }} 是数字~"
when: age is number
- debug:
msg: "变量:weight 值 {{ weight }} 是数字~"
when: weight is number
- debug:
msg: "变量:gender 值 {{ gender }} 不是数字~"
when: not gender is number
执行命令
$ ansible-playbook playbook-tests-demo9.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:username 值 Da 是字符串~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:age 值 18 是数字~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:weight 值 62.1 是数字~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "变量:gender 值 1 不是数字~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
八、异常处理
Python 语言有 try、except、finally 关键字,它们用来不说异常,ansible 中同样也是如此,对应的是 block、rescue、always
大致内容

block 多个任务在 block 内运行
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filepath: "/tmp/testfile9"
tasks:
- debug:
msg: "我在 block 外执行~"
- block:
- debug:
msg: "我是第一个在 block 内执行哒~"
- debug:
msg: "我是第二个在 block 内执行哒~"
执行命令
$ ansible-playbook playbook-block-demo1.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我在 block 外执行~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我是第一个在 block 内执行哒~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我是第二个在 block 内执行哒~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
rescue 任务执行异常处理
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filepath: "/tmp/testfile9"
tasks:
- debug:
msg: "我在 block 外执行~"
- block:
- debug:
msg: "我是第一个在 block 内执行哒~"
- shell: "ls /nofile"
- debug:
msg: "执行不到我这里的~~"
rescue:
- debug:
msg: "我捕获到了一个异常!~"
执行命令
$ ansible-playbook playbook-block-demo2.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我在 block 外执行~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我是第一个在 block 内执行哒~"
}
TASK [shell] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "ls /nofile", "delta": "0:00:00.036600", "end": "2021-10-03 22:17:34.377571", "msg": "non-zero return code", "rc": 2, "start": "2021-10-03 22:17:34.340971", "stderr": "ls: cannot access /nofile: No such file or directory", "stderr_lines": ["ls: cannot access /nofile: No such file or directory"], "stdout": "", "stdout_lines": []}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我捕获到了一个异常!~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
当出现错误后,ansible 会中断 block 任务执行(除非显式忽略),切换到 rescue 块执行异常处理任务,当 rescue 处理完后,才会来到 always 块,具体示例看下面~
always 异常捕获完整处理
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filepath: "/tmp/testfile9"
tasks:
- debug:
msg: "我在 block 外执行~"
- block:
- debug:
msg: "我是第一个在 block 内执行哒~"
- shell: "ls /nofile"
# ignore_errors: true
- debug:
msg: "执行不到我这里的,除非你忽略出错的哥们~~"
rescue:
- debug:
msg: "我 rescue 捕获到了一个异常!~"
always:
- debug:
msg: "无论发生啥,我 always 都是我会被执行的~"
- debug:
msg: "哥们,我也是在 block 外执行,我应该是最后的吧..."
执行命令
$ ansible-playbook playbook-block-demo3.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我在 block 外执行~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我是第一个在 block 内执行哒~"
}
TASK [shell] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "ls /nofile", "delta": "0:00:00.034611", "end": "2021-10-03 22:20:12.016098", "msg": "non-zero return code", "rc": 2, "start": "2021-10-03 22:20:11.981487", "stderr": "ls: cannot access /nofile: No such file or directory", "stderr_lines": ["ls: cannot access /nofile: No such file or directory"], "stdout": "", "stdout_lines": []}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我 rescue 捕获到了一个异常!~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "无论发生啥,我 always 都是我会被执行的~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "哥们,我也是在 block 外执行,我应该是最后的吧..."
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
fail 主动触发异常中断
Python 有 raise 关键字,用来主动触发异常,ansible 同样也有类似的共, 那就是 fail 模块,fail 模块天生就是一个用来 执行失败 的模块,如下所示:
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filepath: "/tmp/testfile9"
tasks:
- block:
- debug:
msg: "我是第一个在 block 内执行哒~"
# 通过 fail 模块触发执行错误
- fail:
# ignore_errors: true
- debug:
msg: "执行不到我这里的,除非你忽略出错的哥们~~"
rescue:
- debug:
msg: "我 rescue 捕获到了一个异常!~"
always:
- debug:
msg: "无论发生啥,我 always 都是我会被执行的~"
执行命令
$ ansible-playbook playbook-block-demo4.yml
PLAY [ecs[0]] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我是第一个在 block 内执行哒~"
}
TASK [fail] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我 rescue 捕获到了一个异常!~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "无论发生啥,我 always 都是我会被执行的~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
fail 模块通常需要配置 when 来实现需求,例如:当命令执行返回中出现 error 单词,那么我们认为命令执行出现了问题,主动触发停止
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- block:
- shell: "echo 'test fail module error message...' "
register: ret
# 通过 fail 模块触发执行错误
- fail:
when: "'error' in ret.stdout"
- debug:
msg: "执行不到我这里的,除非你忽略出错的哥们~~"
rescue:
- debug:
msg: "我 rescue 捕获到了一个异常!~"
always:
- debug:
msg: "无论发生啥,我 always 都是我会被执行的~"
执行命令
$ ansible-playbook playbook-block-demo4.yml
PLAY [ecs[0]] ******
TASK [shell] ******
changed: [ecs-1.aliyun.sz]
TASK [fail] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"changed": false, "msg": "Failed as requested from task"}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我 rescue 捕获到了一个异常!~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "无论发生啥,我 always 都是我会被执行的~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
failed_when 变更任务状态
ansible 提供 failed_when 关键字,当关键字条件成立时,将对应任务的执行状态设置为失败,我们基于该功能可以进一步简化上面的剧本
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- block:
- shell: "echo 'test fail module error message...' "
register: ret
# 通过 failed_when 关键字 判断条件为真时,自动触发执行 fail 模块 抛出错误
failed_when: "'error' in ret.stdout"
- debug:
msg: "执行不到我这里的,除非你忽略出错的哥们~~"
rescue:
- debug:
msg: "我 rescue 捕获到了一个异常!~"
always:
- debug:
msg: "无论发生啥,我 always 都是我会被执行的~"
执行命令
$ ansible-playbook playbook-block-demo5.yml
PLAY [ecs[0]] ******
TASK [shell] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": true, "cmd": "echo 'test fail module error message...' ", "delta": "0:00:00.035564", "end": "2021-10-03 22:45:35.943006", "failed_when_result": true, "rc": 0, "start": "2021-10-03 22:45:35.907442", "stderr": "", "stderr_lines": [], "stdout": "test fail module error message...", "stdout_lines": ["test fail module error message..."]}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "我 rescue 捕获到了一个异常!~"
}
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "无论发生啥,我 always 都是我会被执行的~"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
changed_when 变更任务状态
changed_when 关键字的作用是在条件成立时,将对应任务的执行状态设置为 changed
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- shell: "echo 'test changed_when feature change message...' "
register: ret
# 通过 changed_when 关键字 判断条件为真时,自动触发执行 fail 模块 抛出错误
- debug:
msg: "变更任务状态为 changed"
changed_when: "'change' in ret.stdout"
执行命令
$ ansible-playbook playbook-block-demo6.yml
PLAY [ecs[0]] ******
TASK [shell] ******
changed: [ecs-1.aliyun.sz]
TASK [debug] ******
changed: [ecs-1.aliyun.sz] => {
"msg": "变更任务状态为 changed"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
九、过滤器
所谓过滤器其实就是将管道符前点的内容按照某种规则进行处理,例如: {{ lower_var | upper }} 将小写字母单词转为大写,Ansible 支持诸多过滤器,有的是 ansible 内置,有的来自于 jinja2,如果现有的过滤器不能满足需求还可以自定义
jinja2 内置过滤器:https://jinja.palletsprojects.com/en/3.0.x/templates/#builtin-filters
大致内容

字符串操作
也不一个一个试了,直接贴出所有常用的字符串相关过滤器
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: yes
vars:
var1: "abc123ABC 999"
var2: " DevOps "
var3: "0123456789"
var4: "1a2b!@#$%^&"
tasks:
- name: "upper 所有字母转大写"
debug:
msg: "【{{ var1 }}】 -> 【{{ var1 | upper }}】"
- name: "lower 所有字母转小写"
debug:
msg: "【{{ var1 }}】 -> 【{{ var1 | lower }}】"
- name: "首字母大写,其余小写"
debug:
msg: "【{{ var1 }}】 -> 【{{ var1 | capitalize }}】 "
- name: "字符串反转"
debug:
msg: "【{{ var3 }}】 -> 【{{ var3 | reverse }}】"
- name: "返回首字符"
debug:
msg: "【{{ var3 }}】 -> 【{{ var3 | first }}】"
- name: "返回尾字符"
debug:
msg: "【{{ var3 }}】 -> 【{{ var3 | last }}】"
- name: "去掉首尾空格"
debug:
msg: "【{{ var2 }}】 -> 【{{ var2 | trim }}】"
- name: "字符串居中,两边空格补齐"
debug:
msg: "【{{ var3 }}】 -> 【{{ var3 | center(width=30) }}】"
- name: "返回字符串长度"
debug:
msg: "【{{ var4 }}】 -> 【{{ var3 | length }}】"
- name: "字符串转列表,每个字符为一个元素"
debug:
msg: "【{{ var4 }}】 -> 【{{ var3 | list }}】"
- name: "字符串转列表,每个字符为一个元素,并且随机打乱顺序"
debug:
msg: "【{{ var4 }}】 -> 【{{ var3 | shuffle }}】"
- name: "使用自定义的随机因子打乱列表顺序"
debug:
# Fact 信息中的 命令执行的 UNIX 时间戳
msg: "【{{ var4 }}】 -> 【{{ var4 | shuffle(seed=ansible_date_time.epoch) }}】"
执行命令
$ ansible-playbook playbook-filters-demo1.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [ecs-1.aliyun.sz]
TASK [upper 所有字母转大写] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【abc123ABC 999】 -> 【ABC123ABC 999】"
}
TASK [lower 所有字母转小写] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【abc123ABC 999】 -> 【abc123abc 999】"
}
TASK [capitalize 首字母大写,其余小写] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【abc123ABC 999】 -> 【Abc123abc 999】 "
}
TASK [reverse 字符串反转] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0123456789】 -> 【9876543210】"
}
TASK [first 返回首字符] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0123456789】 -> 【0】"
}
TASK [last 返回尾字符] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0123456789】 -> 【9】"
}
TASK [trim 去掉首尾空格] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【 DevOps 】 -> 【DevOps】"
}
TASK [center() 字符串居中,两边空格补齐] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0123456789】 -> 【 0123456789 】"
}
TASK [length 返回字符串长度] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【1a2b!@#$%^&】 -> 【10】"
}
TASK [list 字符串转列表,每个字符为一个元素] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【1a2b!@#$%^&】 -> 【[u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9']】"
}
TASK [shuffle() 字符串转列表,每个字符为一个元素,并且随机打乱顺序] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【1a2b!@#$%^&】 -> 【[u'2', u'4', u'9', u'1', u'5', u'0', u'8', u'6', u'3', u'7']】"
}
TASK [shuffle(seed=v) 使用自定义的随机因子打乱列表顺序] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【1a2b!@#$%^&】 -> 【[u'@', u'^', u'a', u'b', u'#', u'$', u'&', u'2', u'!', u'1', u'%']】"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=13 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
数字操作
同样,直接贴出所有常用的数字相关过滤器
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- name: "int 字符数字 转为 int 整型"
debug:
# 等同于 Python 1 + int(1)
msg: "【'1' + 1】 -> 【{{ 1 + ('1'|int) }}】"
- name: "int 转换失败时使用默认值"
debug:
msg: "【'a'】 -> 【{{ 'a' | int(default=6) }}】"
- name: "float 转换为 浮点数"
debug:
msg: "【'10'---'a'】 -> 【{{ '10' | float }}---{{ 'a' | float(default=9.9) }}】"
- name: "abs 返回 绝对值"
debug:
msg: "【-6】 -> 【{{ -6 | abs }}】"
- name: "round 四舍五入(浮点数)"
debug:
msg: "【7---9.9】 -> 【{{ 7 | round }}---{{ 9.9 | round }}】"
- name: "random 返回 10 以内随机一个数字"
debug:
msg: "【0-10】 -> 【{{ 10 | random }}】"
- name: "random 返回 5-10 以内随机一个数字"
debug:
msg: "【5-10】 -> 【{{ 10 | random(start=5, step=1) }}】"
- name: "random 返回 10-20 以内随机一个数字,只能步长为 2 的值 10、12、14、16、18"
debug:
msg: "【10-20】 -> 【{{ 20 | random(start=10, step=2) }}】"
- name: "random 返回 0-10 以内随机一个数字,只能是 2 的倍数"
debug:
msg: "【0-10】 -> 【{{ 10 | random(step=2) }}】"
- name: "random 返回 0-10 以内随机一个数字,给定随机因子"
debug:
msg: "【0-10】 -> 【{{ 10 | random(seed=ansible_date_time.epoch) }}】"
执行命令
$ ansible-playbook playbook-filters-demo2.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [ecs-1.aliyun.sz]
TASK [int 字符数字 转为 int 整型] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【'1' + 1】 -> 【2】"
}
TASK [int 转换失败时使用默认值] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【'a'】 -> 【6】"
}
TASK [float 转换为 浮点数] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【'10'---'a'】 -> 【10.0---9.9】"
}
TASK [abs 返回 绝对值] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【-6】 -> 【6】"
}
TASK [round 四舍五入(浮点数)] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【7---9.9】 -> 【7.0---10.0】"
}
TASK [random 返回 10 以内随机一个数字] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0-10】 -> 【0】"
}
TASK [random 返回 5-10 以内随机一个数字] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【5-10】 -> 【7】"
}
TASK [random 返回 10-20 以内随机一个数字,只能步长为 2 的值 10、12、14、16、18] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【10-20】 -> 【18】"
}
TASK [random 返回 0-10 以内随机一个数字,只能是 2 的倍数] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0-10】 -> 【4】"
}
TASK [random 返回 0-10 以内随机一个数字,seed 给定随机因子] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【0-10】 -> 【0】"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=11 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
列表操作
以下是列表操作相关的过滤器
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: yes
tasks:
- name: "length 返回列表长度(等同 count)"
debug:
# 等同于 Python 1 + int(1)
msg: "【[1,2,3]】 -> 【length: {{ [1,2,3] | length }}---count: {{ [1,2,3] | length }}】"
- name: "first & last 返回首尾元素"
debug:
msg: "【[1,2,3]】 -> 【首: {{ [1,2,3] | first }}---尾: {{ [1,2,3] | last }}】"
- name: "max & min 返回最大最小元素"
debug:
msg: "【[1,2,3]】 -> 【最大: {{ [1,2,3] | max }}---最小: {{ [1,2,3] | min }}】"
- name: "sort 升序 & 降序(reverse=true) 排序列表元素"
debug:
msg: "【[9,5,2,7]】 -> 【升序: {{ [9,5,2,7] | sort }}---降序: {{ [9,5,2,7] | sort(reverse=true) }}】"
- name: "sum 计算元素累加总和"
debug:
msg: "【[1,2,3]】 -> 【总和: {{ [1,2,3] | sum }}】"
- name: "flatten 拉平子列表"
debug:
msg: "【[1,[2,[3,4]]]】 -> 【{{ [1,[2,[3,4]]] | flatten }}】"
- name: "flatten & max 拉平子列表,取出其中值最大的元素"
debug:
msg: "【[1,[2,[3,4]]]】 -> 【{{ [1,[2,[3,4]]] | flatten | max }}】"
- name: "join 列表元素合并为字符串"
debug:
msg: "【['D','a','Y','o']】 -> 【{{ ['D','a','Y','o'] | join }}】"
- name: "join 列表元素合并为字符串,元素间以特定字符隔开"
debug:
msg: "【['D','a','Y','o']】 -> 【{{ ['D','a','Y','o'] | join('.') }}】"
- name: "lower & upper 元素转为大小写"
debug:
msg: "【['D','a','Y','o']】 -> 【小写:{{ ['D','a','Y','o'] | lower }}---大写:{{ ['D','a','Y','o'] | upper }}】"
- name: "unique 列表去重"
debug:
msg: "【['a', 'a', 'b']】 -> 【{{ ['a', 'a', 'b'] | unique }}】"
- name: "union 取出两个列表的并集(去重)[a,b,c,d,e]"
debug:
msg: "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【{{ ['a', 'b', 'c'] | union(['c', 'd', 'e']) }}】"
- name: "intersect 取出两个列表的交集(去重)[c]"
debug:
msg: "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【{{ ['a', 'b', 'c'] | intersect(['c', 'd', 'e']) }}】"
- name: "difference 取出在左不在右的元素(去重)[a,b]"
debug:
msg: "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【{{ ['a', 'b', 'c'] | difference(['c', 'd', 'e']) }}】"
- name: "symmetric_difference 取出左右各自独有的元素(去重)[a,b,d,e]"
debug:
msg: "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【{{ ['a', 'b', 'c'] | symmetric_difference(['c', 'd', 'e']) }}】"
- name: "random 随机返回一个元素"
debug:
msg: "【[1,2,3]】 -> 【{{ [1,2,3] | random }}】"
- name: "random 随机返回一个元素,指定随机因子"
debug:
msg: "【[1,2,3]】 -> 【{{ [1,2,3] | random(seed=ansible_date_time.epoch) }}】"
- name: "shuffle 打乱列表顺序,给定随机因子打乱"
debug:
msg: "【['D','a','Y','o']】 -> 【普通打乱:{{ ['D','a','Y','o'] | shuffle }}---随机因子:{{ ['D','a','Y','o'] | shuffle(seed=ansible_date_time.epoch) }}】"
执行效果
$ ansible-playbook playbook-filters-demo3.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [ecs-1.aliyun.sz]
TASK [length 返回列表长度(等同 count)] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,2,3]】 -> 【length: 3---count: 3】"
}
TASK [first & last 返回首尾元素] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,2,3]】 -> 【首: 1---尾: 3】"
}
TASK [max & min 返回最大最小元素] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,2,3]】 -> 【最大: 3---最小: 1】"
}
TASK [sort 升序 & 降序(reverse=true) 排序列表元素] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[9,5,2,7]】 -> 【升序: [2, 5, 7, 9]---降序: [9, 7, 5, 2]】"
}
TASK [sum 计算元素累加总和] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,2,3]】 -> 【总和: 6】"
}
TASK [flatten 拉平子列表] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,[2,[3,4]]]】 -> 【[1, 2, 3, 4]】"
}
TASK [flatten & max 拉平子列表,取出其中值最大的元素] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,[2,[3,4]]]】 -> 【4】"
}
TASK [join 列表元素合并为字符串] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['D','a','Y','o']】 -> 【DaYo】"
}
TASK [join 列表元素合并为字符串,元素间以特定字符隔开] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['D','a','Y','o']】 -> 【D.a.Y.o】"
}
TASK [lower & upper 元素转为大小写] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['D','a','Y','o']】 -> 【小写:['d', 'a', 'y', 'o']---大写:['D', 'A', 'Y', 'O']】"
}
TASK [unique 列表去重] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['a', 'a', 'b']】 -> 【['a', 'b']】"
}
TASK [union 取出两个列表的并集(去重)[a,b,c,d]] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【['a', 'b', 'c', 'd', 'e']】"
}
TASK [intersect 取出两个列表的交集(去重)[c]] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【['c']】"
}
TASK [difference 取出在左不在右的元素(去重)[a,b]] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【['a', 'b']】"
}
TASK [symmetric_difference 取出左右各自独有的元素(去重)[a,b,d,e]] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['a', 'b', 'c'], ['c', 'd', 'e']】 -> 【['a', 'b', 'd', 'e']】"
}
TASK [random 随机返回一个元素] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,2,3]】 -> 【1】"
}
TASK [random 随机返回一个元素,指定随机因子] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【[1,2,3]】 -> 【2】"
}
TASK [shuffle 打乱列表顺序,给定随机因子打乱] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【['D','a','Y','o']】 -> 【普通打乱:['a', 'o', 'Y', 'D']---随机因子:['D', 'o', 'Y', 'a']】"
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=19 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
变量定义操作
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: "default 变量 未定义 则设置默认值"
debug:
msg: "{{ name | default('Dayo') }}"
- name: "default(boolean=true) 变量 未定义 或 为空字符串 则设置默认值"
debug:
msg: "{{ age | default(18, boolean=true) }}"
- name: "使用 quote 为变量添加引号,以此避免部分符号冲突的问题"
debug:
msg: 'echo {{ text | quote }} >> /tmp/testfile'
vars:
text: "It is work."
- name: "Mandatory 自定义 变量未定义出错信息"
debug:
# 修改后:Mandatory variable 'gender' not defined.
msg: "{{ gender | mandatory }}"
执行效果
$ $ ansible-playbook playbook-filters-demo4.yml
PLAY [ecs[0]] ******
TASK [default 变量 未定义 则设置默认值] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "Dayo"
}
TASK [default(boolean=true) 变量 未定义 或 为空字符串 则设置默认值] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "18"
}
TASK [使用 quote 为变量添加引号,以此避免部分符号冲突的问题] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "echo 'It is work.' >> /tmp/testfile"
}
TASK [Mandatory 自定义 变量未定义出错信息] ******
fatal: [ecs-1.aliyun.sz]: FAILED! => {"msg": "Mandatory variable 'gender' not defined."}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=3 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
关于 default 过滤器,这里再补充小例子,假如我们要创建一些文件,有的文件严格要去权限,有的则不要求,我们该如何做?
/tmp/secret_file 600
/tmp/t1.txt
/tmp/t2.txt
剧本定义
按照之前的思路,我们的写法大致是这一样的,首先,遍历 filelist 文件列表,而后判断 mode 属性是否是否定义,最后调用 file 按照预定义的权限,创建文件
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filelist:
- path: /tmp/secret_file
mode: 600
- path: /tmp/t1.txt
- path: /tmp/t2.txt
tasks:
- file: dest={{ item.path }} state=touch mode={{ item.mode }}
with_items: "{{ filelist }}"
when: item.mode is defined
- file: dest={{ item.path }} state=touch
with_items: "{{ filelist }}"
when: item.mode is undefined
执行效果
$ ansibleLearn ls -al /tmp/secret_file
-rw------- 1 root root 0 Oct 4 15:31 /tmp/secret_file
☁ ansibleLearn ls -al /tmp/t1.txt
-rw-r--r-- 1 root root 0 Oct 4 15:31 /tmp/t1.txt
☁ ansibleLearn ls -al /tmp/t2.txt
-rw-r--r-- 1 root root 0 Oct 4 15:31 /tmp/t2.txt
现在,我们转换下思路,使用 default 过滤器优化 剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
filelist:
- path: /tmp/secret_file
mode: 600
- path: /tmp/t1.txt
- path: /tmp/t2.txt
tasks:
- with_items: "{{ filelist }}"
# {{ item.mode | default(omit) }} 意为 如果 item 有 mode 属性,那就用
# 如果没有,file 模块就直接省略该参数(mode 参数)
file: dest={{ item.path }} state=touch mode={{ item.mode | default(omit) }}
执行效果
$ ansible-playbook playbook-filters-demo6.yml
PLAY [ecs[0]] ******
TASK [file] ******
changed: [ecs-1.aliyun.sz] => (item={u'path': u'/tmp/secret_file', u'mode': 600})
changed: [ecs-1.aliyun.sz] => (item={u'path': u'/tmp/t1.txt'})
changed: [ecs-1.aliyun.sz] => (item={u'path': u'/tmp/t2.txt'})
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
查看文件权限
$ ls -al /tmp/t2.txt /tmp/t1.txt /tmp/secret_file
-rw------- 1 root root 0 Oct 4 15:38 /tmp/secret_file
-rw-r--r-- 1 root root 0 Oct 4 15:38 /tmp/t1.txt
-rw-r--r-- 1 root root 0 Oct 4 15:38 /tmp/t2.txt
json 操作
我们知道 json 是 yaml 的子集,yaml 是json 的超集,所以呢,剧本编写的过程中有些字段直接可以用 json 格式。ansible 提供了解析 json 的功能,这里看个小例子
创建一个 json 文件
$ cat cdn_log.json
{"logs":[{"domainName":"asia1.cdn.test.com","files":[{"dateFrom":"2018-09-05-0000","dateTo":"2018-09-05-2359","logUrl":"http://log.testcd.com/log/zsy/asia1.cdn.test.com/2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz?wskey=XXXXX5a","fileSize":254,"fileName":"2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz","fileMd5":"error"}]},{"domainName":"image1.cdn.test.com","files":[{"dateFrom":"2018-09-05-2200","dateTo":"2018-09-05-2259","logUrl":"http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz?wskey=XXXXX1c","fileSize":10509,"fileName":"2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz","fileMd5":"error"},{"dateFrom":"2018-09-05-2300","dateTo":"2018-09-05-2359","logUrl":"http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz?wskey=XXXXXfe","fileSize":5637,"fileName":"2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz","fileMd5":"error"}]}]}
格式化输出
直接去看 json 文件可读性肯定是很差的,我们可以用 ansible 格式化下
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- include_vars:
file: "/prodata/scripts/ansibleLearn/cdn_log.json"
name: jsonfile
- debug:
msg: "{{ jsonfile }}"
执行效果
$ ansible-playbook playbook-filters-json-demo1.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": {
"logs": [
{
"domainName": "asia1.cdn.test.com",
"files": [
{
"dateFrom": "2018-09-05-0000",
"dateTo": "2018-09-05-2359",
"fileMd5": "error",
"fileName": "2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz",
"fileSize": 254,
"logUrl": "http://log.testcd.com/log/zsy/asia1.cdn.test.com/2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz?wskey=XXXXX5a"
}
]
},
{
"domainName": "image1.cdn.test.com",
"files": [
{
"dateFrom": "2018-09-05-2200",
"dateTo": "2018-09-05-2259",
"fileMd5": "error",
"fileName": "2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz",
"fileSize": 10509,
"logUrl": "http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz?wskey=XXXXX1c"
},
{
"dateFrom": "2018-09-05-2300",
"dateTo": "2018-09-05-2359",
"fileMd5": "error",
"fileName": "2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz",
"fileSize": 5637,
"logUrl": "http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz?wskey=XXXXXfe"
}
]
}
]
}
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
这部分我们要了解你的是 json 过滤器,日常工作中 json 是非常常见的请求响应格式,所以掌握 json 过滤器是很重要的,尤其是 ansible 频繁与企业内其他其他系统进行联动的场景下
获取列表数据对象的属性
假设,json 内容如下,如何获取 users 中各对象的名字呢?
splitext{
"users": [
{
"name": "Da",
"gender": "Male",
"age": 18
},
{
"name": "Yo",
"gender": "Female",
"age": 19
},
]
}
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- include_vars:
file: "/prodata/scripts/ansibleLearn/filters-json-demo2.json"
name: jsonfile
- debug:
# * 代表所有元素
msg: "{{ jsonfile | json_query('users[*].name') }}"
执行效果
$ ansible-playbook playbook-filters-json-demo2.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": [
"Da",
"Yo"
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
OK,json_query 过滤器帮助我们从所有元素对象中查到了 name 属性值
获取列表数据对象的列表属性
假设,每个用户擅长不同的技术栈,如何获取所有用户的所擅长的技能呢?
{
"users": [
{
"name": "Da",
"gender": "Male",
"age": 18,
"skill": ["Linux", "Unix", "Python"]
},
{
"name": "Yo",
"gender": "Female",
"age": 19,
"skill": ["Golang", "Python"]
},
]
}
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- include_vars:
file: "/prodata/scripts/ansibleLearn/filters-json-demo2.json"
name: jsonfile
- debug:
# * 代表所有元素
msg: "{{ jsonfile | json_query('users[*].skill[*]') }}"
执行效果
$ ansible-playbook playbook-filters-json-demo3.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": [
[
"Linux",
"Unix",
"Python"
],
[
"Golang",
"Python"
]
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
嗯,看到了各用户的技能都被拎了出来,不过分属不同的列表,我们处理下这个小瑕疵
剧本定义
- debug:
# 第一个 * 代表所有元素对象
# 第二个 * 代表所有技能
# flatten | unique 拉平(合并) 并 去重
msg: "{{ jsonfile | json_query('users[*].skill[*]') | flatten | unique }}"
执行效果
ok: [ecs-1.aliyun.sz] => {
"msg": [
"Linux",
"Unix",
"Python",
"Golang"
]
}
获取特定用户的技能列表
假如我们不想获取所有人的,只想拿到某个人的技能列表,可以这么做,users['filed'=='value'].list_field
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- include_vars:
file: "/prodata/scripts/ansibleLearn/filters-json-demo2.json"
name: jsonfile
- debug:
# users[?name==`Da`] 代表只获取 name 为 Da 的对象
# 第二个 * 代表所有技能
msg: "{{ jsonfile | json_query('users[?name==`Da`].skill[*]') }}"
执行效果
$ ansible-playbook playbook-filters-json-demo4.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": [
[
"Linux",
"Unix",
"Python"
]
]
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
上面的匹配条件部分我们使用的是 反引号 `,这是以为避免与 最外层的 msg 参数的双引号、json_query 函数参数的单引号 冲突,不过我们可以通过变量的方式处理这个问题
剧本定义
- debug:
# users[?name==`Da`] 代表只获取 name 为 Da 的对象
# 第二个 * 代表所有技能
# msg: "{{ jsonfile | json_query('users[?name==`Da`].skill[*]') }}"
msg: " {{ jsonfile | json_query(query_string) }} "
vars:
query_string: "users[?name=='Da'].skill[*]"
执行效果
$ ansible-playbook playbook-filters-json-demo4.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " [[u'Linux', u'Unix', u'Python']] "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
获取元素对象的多个属性值
假设我们要获取用户的姓名、技能列表,可以这么做
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- include_vars:
file: "/prodata/scripts/ansibleLearn/filters-json-demo2.json"
name: jsonfile
- debug:
# {new_field: field, ...}
# {username: name, user_skill_list: skill}
msg: " {{ jsonfile | json_query(query_string) }} "
vars:
query_string: "users[*].{username: name, user_skill_list: skill}"
执行效果
$ ansible-playbook playbook-filters-json-demo5.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " [{u'username': u'Da', u'user_skill_list': [u'Linux', u'Unix', u'Python']}, {u'username': u'Yo', u'user_skill_list': [u'Golang', u'Python']}] "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
获取 CDN JSON 日志中所有 LogUrl 信息
OK,做个小案例,再一次使用最初的那个 cdn json 日志,从中获取所有 LogUrl,该怎么做呢?
先观察数据格式
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": {
"logs": [
{
"domainName": "asia1.cdn.test.com",
"files": [
{
"dateFrom": "2018-09-05-0000",
"dateTo": "2018-09-05-2359",
"fileMd5": "error",
"fileName": "2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz",
"fileSize": 254,
"logUrl": "http://log.testcd.com/log/zsy/asia1.cdn.test.com/2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz?wskey=XXXXX5a"
}
]
},
{
"domainName": "image1.cdn.test.com",
"files": [
{
"dateFrom": "2018-09-05-2200",
"dateTo": "2018-09-05-2259",
"fileMd5": "error",
"fileName": "2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz",
"fileSize": 10509,
"logUrl": "http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz?wskey=XXXXX1c"
},
{
"dateFrom": "2018-09-05-2300",
"dateTo": "2018-09-05-2359",
"fileMd5": "error",
"fileName": "2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz",
"fileSize": 5637,
"logUrl": "http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz?wskey=XXXXXfe"
}
]
}
]
}
}
最外层字典结构中只有一个字段,logs 列表数据,其中以域名为单位,域名字典中 files 列表数据中的 文件对象包含 logUrl 属性,大致结构如下:logs[].files[].logUrl
按照需求,我们要获取所有域名中 URL 信息,那么就是 logs[*].files[*].logUrl,我们试下这个规则
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- include_vars:
file: "/prodata/scripts/ansibleLearn/cdn_log.json"
name: jsonfile
- debug:
# 第一个 * 代表所有域名
# 第二个 * 代表所有文件
# flatten | unique 拉平(合并) 并 去重
msg: " {{ jsonfile | json_query('logs[*].files[*].logUrl') }} "
执行效果
$ ansible-playbook playbook-filters-json-demo6.yml
PLAY [ecs[0]] ******
TASK [include_vars] ******
ok: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " [[u'http://log.testcd.com/log/zsy/asia1.cdn.test.com/2018-09-05-0000-2330_asia1.cdn.test.com.all.log.gz?wskey=XXXXX5a'], [u'http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2200-2230_image1.cdn.test.com.cn.log.gz?wskey=XXXXX1c', u'http://log.testcd.com/log/zsy/image1.cdn.test.com/2018-09-05-2300-2330_image1.cdn.test.com.cn.log.gz?wskey=XXXXXfe']] "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
路径操作
主要是围绕着 获取路径最终文件、最终目录、软链接指向等,直接看示例
剧本定义
- hosts: ecs[0]
remote_user: root
gather_facts: no
vars:
path_var1: "/tmp/testdir/t1.txt"
win_path_var2: "C:\\Program Files\\JetBrains\PyCharm 2021.1.1\\bin\\pycharm64.exe"
soft_link_var3: "/root/.pyenv/bin/pyenv"
tasks:
- name: "返回路径最终的文件名"
debug:
msg: "{{ path_var1 | basename }}"
- name: "返回路径最终的文件名(win)"
debug:
msg: "{{ win_path_var2 | win_basename }}"
- name: "返回路径最终目录"
debug:
msg: " {{ path_var1 | dirname }} "
- name: "返回路径最终目录(win)"
debug:
msg: " {{ win_path_var2 | win_dirname }} "
- name: "分割盘符与路径(win)"
debug:
msg: " {{ win_path_var2 | win_splitdrive }} "
- name: "返回软链接文件最终指向"
debug:
msg: " {{ soft_link_var3 | realpath }} "
- name: "分割路径最终文件的.后缀"
debug:
msg: " {{ path_var1 | splitext }} "
执行命令
$ ansible-playbook playbook-filters-path-demo1.yml
PLAY [ecs[0]] ******
TASK [返回路径最终的文件名] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "t1.txt"
}
TASK [返回路径最终的文件名(win)] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "pycharm64.exe"
}
TASK [返回路径最终目录] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " /tmp/testdir "
}
TASK [返回路径最终目录(win)] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " C:\\Program Files\\JetBrains
yCharm 2021.1.1\\bin "
}
TASK [分割盘符与路径(win)] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " (u'C:', u'\\\\Program Files\\\\JetBrains\\u2029yCharm 2021.1.1\\\\bin\\\\pycharm64.exe') "
}
TASK [返回软链接文件最终指向] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " /root/.pyenv/libexec/pyenv "
}
TASK [分割路径最终文件的.后缀] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " (u'/tmp/testdir/t1', u'.txt') "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
逻辑操作
主要是来两块,布尔处理、三元运算
先看布尔过滤器,根据字符串内容返回 true 或 false,这里举个小例子
bool 过滤器
剧本定义
---
- name: playbook-vars_promote-demo2
hosts: ecs[0]
gather_facts: no
vars_prompt:
- name: "username"
prompt: "Please input your username"
# 用户名可以回显
private: no
- name: "password"
prompt: "Please input your password"
confirm: yes
encrypt: "sha512_crypt"
- name: "verify"
prompt: "Are you sure? (yes/no)"
tasks:
- debug:
msg: "创建用户 【Username】: {{ username }} 【Password】: {{ password }}"
when: verify | bool
- debug:
msg: "取消创建 【Username】: {{ username }} 【Password】: {{ password }}"
when: not verify | bool
- name: create user
user:
name: "{{ username }}"
password: "{{ password }}"
when: verify | bool
执行效果
$ ansible-playbook playbook-filters-logical-demo1.yml
Please input your username: Da
Please input your password:
confirm Please input your password:
Are you sure? (yes/no):
PLAY [playbook-vars_promote-demo2] ******
TASK [debug] ******
skipping: [ecs-1.aliyun.sz]
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "取消创建 【Username】: Da 【Password】: $6$2k4UZm2au1Tv7o0Y$iN00KCxUjIUX.mNu/VexlvVc8UjokpFs2nfKiA/gGFq7vjBCNXfio9.1VZBfGAhTk3UIVxAET7n0.6hD42zna0"
}
TASK [create user] ******
skipping: [ecs-1.aliyun.sz]
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
输入 yes
$ ansible-playbook playbook-filters-logical-demo1.yml
Please input your username: DaYo
Please input your password:
confirm Please input your password:
Are you sure? (yes/no):
PLAY [playbook-vars_promote-demo2] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "创建用户 【Username】: DaYo 【Password】: $6$UuxK2JgYMnPRxKU2$OSXDhAy9Nn3qjf/TARrIDbv0NzR18v8V7YihEEKlyjEeUr0//7QAybdHJmU/7TJlU1Vb2YcuSMQz/M0Y3chhu1"
}
TASK [debug] ******
skipping: [ecs-1.aliyun.sz]
TASK [create user] ******
changed: [ecs-1.aliyun.sz]
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
ternary 三元运算
剧本定义
---
- name: playbook-vars_promote-demo2
hosts: ecs[0]
gather_facts: no
vars:
username: "Da"
tasks:
- debug:
# 如果 (username == 'Da') 条件为真,则返回 ternary 第一个参数、否则返回第二个
msg: " {{ (username == 'Da') | ternary('Mr', 'Ms') }} "
执行效果
$ ansible-playbook playbook-filters-logical-demo2.yml
PLAY [playbook-vars_promote-demo2] ******
TASK [debug] ******
ok: [ecs-1.aliyun.sz] => {
"msg": " Mr "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
大致是这么个意思,估计不会太常用~
日期时间操作
to_datetime 模块
剧本定义
---
- hosts: ecs[0]
gather_facts: no
tasks:
- name: "使用 to_datetime 将字符串类型时间转为日期时间类型,并计算两个日期时间的时间差"
debug:
msg: "【2021-10-01 12:00:00 与 2021-10-01 10:00:00 相差 [时:分:秒] {{ diff_time }}】"
vars:
# to_datetime 默认按照 %Y-%m-%d %H:%M:%S 格式进行处理,如不同则需要自定义说明规则
diff_time: "{{ ('2021-10-01 12:00:00' | to_datetime) - ('20211001 100000' | to_datetime('%Y%m%d %H%M%S')) }}"
- name: "使用 to_datetime 并计算两个日期时间的相隔几天"
debug:
msg: "【2021-10-01 12:00:00 与 2021-09-30 12:00:00 相差 {{ diff_days }} 天 {{ diff_seconds }} 秒 {{ diff_total_seconds }} 总计秒 "
vars:
- diff_days: "{{ (('2021-10-01 12:00:00' | to_datetime) - ('2021-09-30 12:00:00' | to_datetime)).days }}"
# .seconds 在计算相隔时间时,日期位不会纳入对比计算范围
- diff_seconds: "{{ (('2021-10-01 12:00:00' | to_datetime) - ('2021-09-30 12:00:00' | to_datetime)).seconds }}"
# .total_seconds() 获取到两个日期之间一共相差多少秒
- diff_total_seconds: "{{ (('2021-10-01 12:00:00' | to_datetime) - ('2021-09-30 12:00:00' | to_datetime)).total_seconds() }}"
执行命令
$ ansible-playbook playbook-filters-datetime-demo1.yml
PLAY [ecs[0]] ******
TASK [使用 to_datetime 将字符串类型时间转为日期时间类型,并计算两个日期时间的时间差] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【2021-10-01 12:00:00 与 2021-10-01 10:00:00 相差 [时:分:秒] 2:00:00】"
}
TASK [使用 to_datetime 并计算两个日期时间的相隔几天] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【2021-10-01 12:00:00 与 2021-09-30 12:00:00 相差 1 天 0 秒 86400.0 总计秒 "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
strftime 模块
剧本定义
---
- hosts: ecs[0]
gather_facts: yes
tasks:
- name: "strftime 返回年-月-日"
debug:
msg: "【%Y-%m-%d】 -> {{ '%Y-%m-%d' | strftime }} "
- name: "strftime 返回时:分:秒"
debug:
msg: "【%H-%M-%S】 -> {{ '%H-%M-%S' | strftime }} "
- name: "格式化字符串日期时间,这里用的是 ansible_date_time.epoch fact 信息"
debug:
msg: "【%H-%M-%S】 -> {{ '%H:%M:%S' | strftime(ansible_date_time.epoch) }} "
- name: "格式化 unix 时间戳为日期时间"
debug:
msg: "【%Y-%m-%d %H-%M-%S】 -> {{ '%Y-%m-%d %H:%M:%S' | strftime(1633587539) }} "
执行命令
$ ansible-playbook playbook-filters-strftime-demo1.yml
PLAY [ecs[0]] ******
TASK [Gathering Facts] ******
ok: [ecs-1.aliyun.sz]
TASK [strftime 返回年-月-日] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【%Y-%m-%d】 -> 2021-10-07 "
}
TASK [strftime 返回时:分:秒] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【%H-%M-%S】 -> 14-20-14 "
}
TASK [格式化字符串日期时间,这里用的是 ansible_date_time.epoch fact 信息] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【%H-%M-%S】 -> 14:20:14 "
}
TASK [格式化 unix 时间戳为日期时间] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "【%Y-%m-%d %H-%M-%S】 -> 2021-10-07 14:18:59 "
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
编解码加密相关
主要包括 b64_encode/b64_decode、hash/checksum、password_hash,使用示例如下:
剧本定义
---
- hosts: ecs[0]
remote_user: root
gather_facts: no
tasks:
- name: "b64encode 使用 base64 对字符串进行编码"
debug:
msg: "{{ 'hello' | b64encode }}"
- name: "b64decode 使用 base64 对字符串进行解码"
debug:
msg: "{{ 'aGVsbG8=' | b64decode }}"
- name: "checksum 获取字符串校验和"
debug:
msg: "{{ 'hello' | checksum }}"
- name: "hash('md5') 使用 md5 进行哈希计算"
debug:
msg: "{{ 'hello' | hash('md5') }}"
- name: "hash('sha256') 使用 sha256 进行哈希计算 进行加盐哈希计算"
debug:
msg: "{{ 'hello' | hash('sha256') }}"
- name: "password_hash('sha256', 'salt') 使用 sha256 进行哈希计算,并进行加盐哈希计算"
debug:
msg: "{{ 'hello' | password_hash('sha256', 'salt') }}"
- name: "password_hash 幂等的为每个主机生成对应加密串"
debug:
# 盐规则:65534 | random 获取 0-65534 内随机一个数字
# inventory_hostname 当前 play 操作的主机名称作为随机因子
# string 返回 unicode 字符串
# ->加密串
msg: "{{ 'Da' | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string ) }}"
执行效果
$ ansible-playbook playbook-filters-encode-encrypt-demo1.yml
PLAY [ecs[0]] ******
TASK [b64encode 使用 base64 对字符串进行编码] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "aGVsbG8="
}
TASK [b64decode 使用 base64 对字符串进行解码] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "hello"
}
TASK [checksum 获取字符串校验和] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
}
TASK [hash('md5') 使用 md5 进行哈希计算] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "5d41402abc4b2a76b9719d911017c592"
}
TASK [hash('sha256') 使用 sha256 进行哈希计算 进行加盐哈希计算] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
TASK [password_hash('sha256', 'salt') 使用 sha256 进行哈希计算,并进行加盐哈希计算] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "$5$salt$ZYXsK0pxpaRWBUweKuToC90TC/15c9Iz8u3SGLTaS4D"
}
TASK [password_hash 幂等的为每个主机的密码生成对应哈希串] ******
ok: [ecs-1.aliyun.sz] => {
"msg": "$6$17885$Z1OVvdPQA0xBooh2.MYzIcEKsdcfQ8qlrvjrKZ0P5QhoTtdPwRiGJnn.RgW6EoLD81g22a4zF9yGi4BytCE/2."
}
PLAY RECAP ******
ecs-1.aliyun.sz : ok=7 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Kubernetes 操作(todo)
使用前需要通过 ansible-galaxy 安装第三方模块(collection)
https://galaxy.ansible.com/kubernete
具体操作待后续补充
自定义过滤器【重要】
有些时候 ansible or jinja2 提供的过滤器不足以满足我们的需求,这时我们就需要自己动手开发过滤器了,倒也不难,这里以 Python 为例
首先,修改 ansible 配置文件,调整 filter_plugins 参数配置
$ vim /etc/ansible/ansible.cfg
#filter_plugins = /usr/share/ansible/plugins/filter
filter_plugins = /usr/share/ansible/plugins/filter
接下来开始 过滤器的编写
# coding: utf-8
class FilterModule(object):
# 实现 filters 方法,返回过滤器的名称及方法映射【必须】
# 所有过滤器必须注册到这里来,否则 ansible 调不到
def filters(self):
return {'a_filter': self.a_filter, 'b_filter': self.b_filter()}
# 过滤器方法:这就是后面我们使用时跟在 管道符 | 后面的
# 默认情况下 管道符前面的内容会作为 params 参数的值传递过来
def a_filter(self, params):
return '{} from a_filter.'.format(params)
# 还可以通过 可变参数及可变关键字参数接受所有参数
def b_filter(self, *args, **kwargs):
return 'args: {} --- '.format(args) + 'kwargs: {}'.format(kwargs)
拷贝 过滤器脚本到 filter_plugins 配置项所指定的目录中
$ cp my_filter.py /usr/share/ansible/plugins/filter/
再随便写个剧本测试下
---
- hosts: localhost
gather_facts: no
connection: local
tasks:
- name: Custom Filter Demo
debug:
msg: "{{ 'Da' | a_filter }}"
- name: Custom Filter Demo2
debug:
msg: "{{ 'Yo' | b_filter('Yo', 'like', 'Python', username='yo', gender='female') }}"
执行命令
$ ansible-playbook playbook-custom-filter-demo1.yml
PLAY [localhost] ******
TASK [Custom Filter Demo] ******
ok: [localhost] => {
"msg": "Da from a_filter."
}
TASK [Custom Filter Demo2] ******
ok: [localhost] => {
"msg": "args: ('Yo', 'Yo', 'like', 'Python') --- kwargs: {'username': 'yo', 'gender': 'female'}"
}
PLAY RECAP ******
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
其他操作
map 获取元素共有属性值
十、组织文件
在学习时,我们可以将所有 ansible 指令配置到一个 playbook 中,但从生产实践的角度上说,这是不利于维护的,且无法重用某些内容,所以接下来我们需要学习下,如何组织文件,主要涉及到三种方法,include、imports、roles,我们一个一个看
动态包含
include(include_tasks)
假如有一个 task 列表,它包含了很多通用的命令,我们想在多个 play 或多个 playbook 中重用它,避免大量重复性的 task 定义,这时应该怎么做呢?
答案就是,include files ,下面来看看如何做
首先,我们需要定一个包含 普通的 task 列表 的 task include file
$ cat tasks/tasks-demo1.yml
---
- name: print start time
command: date "+%H:%M:%S"
- name: print hostname
command: hostname
然后,在 playbook 中,我们可以在 tasks 字段使用 include 指令引入文件中的任务列表
$ cat playbook-include-demo1.yml
---
- name: include-demo1
hosts: ecs
gather_facts: no
tasks:
- include: tasks/tasks-demo1.yml
执行效果
$ ansible-playbook playbook-include-demo1.yml
PLAY [include-demo1] **************
TASK [print start time] ***********
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [print hostname] *************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
参数化的 include
有时,我们系统通过变量传值的方式,让 tasks 通用性更高些,对此 ansible 也是支持的
tasks file 定义
---
- name: create temp file
file:
path: /tmp/{{ filename }}
mode: 644
state: touch
- name: insert data to temp file
shell: "echo {{ text }} > /tmp/{{ filename }}"
playbook 定义
---
- name: include-demo2
hosts: ecs
gather_facts: no
tasks:
- include: tasks/tasks-demo2.yml
vars:
filename: testfile2
执行效果
$ ansible-playbook playbook-include-demo2.yml
PLAY [include-demo2] **************
TASK [create temp file] ***********
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [insert data to temp file] ********************************************************************************************************************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
查看文件及内容
sz-aliyun-ecs-1
$ cat /tmp/testfile2
include-demo2
bj-huawei-hecs-1
$ cat /tmp/testfile2
include-demo2
当然了,include 语句可以和其他非 include 的 tasks 混合使用
---
- name: include-demo2
hosts: ecs
gather_facts: no
tasks:
- name: test
command: hostname
- include: tasks/tasks-demo2.yml
vars:
filename: testfile2
text: include-demo2
执行效果
$ ansible-playbook playbook-include-demo2.yml
PLAY [include-demo2] **************
TASK [test] *******
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [create temp file] ***********
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [insert data to temp file] ********************************************************************************************************************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
除此 include 指令外,还可以用 include_tasks 指令
$ cat playbook-include-demo3.yml
---
- name: include-demo3
hosts: ecs
gather_facts: no
tasks:
- name: get system load
command: uptime
- name: include task file
include_tasks: "tasks/tasks-demo2.yml"
vars:
filename: testfile3
text: include-demo3
执行效果
$ ansible-playbook playbook-include-demo3.yml
PLAY [include-demo3] **************
TASK [get system load] ************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [include task file] **********
included: /prodata/scripts/ansibleLearn/tasks/tasks-demo2.yml for sz-aliyun-ecs-1, bj-huawei-hecs-1
TASK [create temp file] ***********
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [insert data to temp file] ********************************************************************************************************************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=4 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
查看文件及内容
# 节点:sz-aliyun-ecs-1
☁ ~ $ hostname && cat /tmp/testfile3
sz-aliyun-ecs-1
include-demo3
# 节点:bj-huawei-hecs-1
☁ ~ $ hostname && cat /tmp/testfile3
bj-huawei-hecs-1
include-demo3
使用 include_tasks 导入的方式,官方称之为 “动态导入”,与之对应自然就有 “静态导入”
include_vars
空
静态导入
import_tasks
用法并不难
$ cat playbook-import-demo1.yml
---
- name: import-tasks-demo1
hosts: ecs
gather_facts: no
tasks:
- name: import tasks file
import_tasks: tasks/tasks-demo2.yml
vars:
filename: testfile4
text: import-tasks-demo1
执行效果
$ ansible-playbook playbook-import-demo1.yml
PLAY [import-tasks-demo1] *********
TASK [create temp file] ***********
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
TASK [insert data to temp file] ********************************************************************************************************************
changed: [sz-aliyun-ecs-1]
changed: [bj-huawei-hecs-1]
PLAY RECAP ********
bj-huawei-hecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
sz-aliyun-ecs-1 : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
确认文件
# 节点:sz-aliyun-ecs-1
$ hostname && cat /tmp/testfile4
sz-aliyun-ecs-1
import-tasks-demo1
# 节点:bj-huawei-hecs-1
$ hostname && cat /tmp/testfile4
bj-huawei-hecs-1
import-tasks-demo1
动态 vs 静态(todo)
有关 静态导入与动态导入 的区别,就目前而言,我是不太能很好理解的,具体内容留待后续对 ansible 有进一步的理解后再去阅读官方文档
| 对比角度 | 静态包含 | 动态包含 |
|---|---|---|
| 处理时机 | 在 Playbook 解析时 预处理 所有静态包含 | 运行期间 遇到该任务时才会 触发处理动态包含 |
| 导入异常 | 在执行前导入配置文件,出现错误会 立即停止 | 在执行时导入配置文件,出现错误 不会停止 |
| … |
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse.html#imports-static-re-use
十一、执行顺序
先大致了解下 Playbook 的执行顺序,其中几个概念目前还不了解,例如:pre_tasks、role、post_tasks,不着急后续总会摸清楚的~
- 加载变量(Variable loading)
- 是否获取 fact 数据(Gathering Facts)
- 执行 pre_tasks 任务(The pre_tasks execution)
- 执行 pre_tasks 任务里的事件通知(Handlers notified from the pre_tasks execution)
- 执行 roles 角色(Roles execution)
- 执行 Tasks 任务(Tasks execution)
- 执行 tasks 任务里的事件通知(Handlers notified from roles or tasks execution)
- 执行 post_tasks 任务(The post_tasks execution)
- 执行 post_tasks 任务里的事件通知(Handlers notified from the post_tasks execution)