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)