Ansible 命令入门
一、ansible
我们来深入的看下,ansible 命令的使用,语法如下:
ansible <pattern> -m <module_name> -a <arguments>
这里补充一个图示:

图片来源:Devops Junction,一家 IT 解决方案提供商,网站有很多 DevOps 技术栈的相关文章,非常推荐!
1.1 Patterns
首先看第一个参数,在Ansible中,Patterns 主要是用来匹配节点,应用特定配置,常见的使用方式:
1.1.1 通配符匹配主机
此前我们用过 all
关键字,其实还可以用 *
通配符
$ ansible '*' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
部分通配
# 通配 ecs 节点
$ ansible '*ecs*' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
# 通配 server 主机组
$ ansible '*server*' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.1.2 匹配特定主机组
$ ansible bj_server -m ping
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.1.3 匹配多个特定目标对象
语法:用:
分割多个目标对象(组、节点)
匹配多个主机组
$ ansible bj_server:sz_server -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
匹配多个节点
$ ansible sz-aliyun-ecs-1:bj-huawei-hecs-1 -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
匹配多个节点(带通配符)
$ ansible 'bj-huawei-hecs-1:sz*' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
匹配多个组(带通配符)
$ ansible 'ecs:*_server' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.1.4 取反排除特定目标对象
语法:!<target>
匹配 非 sz_server 组
的节点
$ ansible '!sz_server' -m ping
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
匹配 ecs 组中非 bj_server 组
的节点
$ ansible 'ecs:!bj_server' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.1.5 取两个组的交集节点
语法:<group>:&<group>
匹配同属于 ecs
云主机组 与 prod
环境组 的节点
$ ansible 'ecs:&prod' -m ping
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
再看个烧脑的,ecs:web_server:&prod:!maintenance
,我们把它拆开来看:
ecs:
匹配 ecs 组的节点:bj-huawei-hecs-1
、sz-aliyun-ecs-1
web_server:&prod
:匹配 同属于 web组 和 正式环境 的节点bj-huawei-hecs-1
:!maintenance
:匹配 非维护状态的节点bj-huawei-hecs-1
$ ansible 'ecs:web_server:&prod:!maintenance' -m ping
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.1.6 匹配分组中特定区间的节点
语法:group[n|n:m]
匹配 ecs
分组中第 1 个节点
ansible 'ecs[0]' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
匹配 ecs
分组中第 0:2
区间内节点
$ ansible 'ecs[0:2]' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
bj-huawei-hecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.1.7 正则表达式匹配节点
语法:~
匹配 深圳区域
的 阿里云
节点
ansible '~sz-aliyun.*' -m ping
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
1.2 Ad-Hoc
什么是 ad-hoc?,一句话就可以讲清楚,“一次性的 Ansible 指令”
Ansible 提供两种方式去完成任务,
ad-hoc
:适用于解决一些简单,且临时的任务playbook
:适用于处理较复杂的任务,且需要持久化的任务
ad-hoc
之于playbook
,等同command
之于bash scripts
在开始之前,我们先了解两个命令
查看 ansible 支持那些模块
bash$ ansible-doc -l fortios_router_community_list Configure community lists in Fortinet's FortiOS and FortiGate azure_rm_devtestlab_info Get Azure DevTest Lab facts ecs_taskdefinition register a task definition in ecs avi_alertscriptconfig Module for setup of AlertScriptConfig Avi RESTful Object tower_receive Receive assets from Ansible Tower netapp_e_iscsi_target NetApp E-Series manage iSCSI target configuration azure_rm_acs Manage an Azure Container Service(ACS) instance fortios_log_syslogd2_filter Filters for remote system server in Fortinet's FortiOS and FortiGate junos_rpc Runs an arbitrary RPC over NetConf on an Juniper JUNOS device na_elementsw_vlan NetApp Element Software Manage VLAN pn_ospf CLI command to add/remove ospf protocol to a vRouter pn_snmp_vacm CLI command to create/modify/delete snmp-vacm cp_mgmt_service_sctp Manages service-sctp objects on Check Point over Web Serv
查看 ansible 模块参数列表
bash$ ansible-doc -s shell - name: Execute shell commands on targets shell: chdir: # Change into this directory before running the command. cmd: # The command to run followed by optional arguments. creates: # A filename, when it already exists, this step will *not* be run. executable: # Change the shell used to execute the command. This expects an absolute path to the executable. free_form: # The shell module takes a free form command to run, as a string. There is no actual parameter named 'free form'. See the examples on how to use this module. removes: # A filename, when it does not exist, this step will *not* be run. stdin: # Set the stdin of the command directly to the specified value. stdin_add_newline: # Whether to append a newline to stdin data. warn: # Whether to enable task warnings.
1.2.1 命令执行
command 模块
顾名思义,command 模块可以在远程主机执行命令,需要特别说明的,命令不是经由 shell 执行的,所以并不支持诸如:管道、重定向、逻辑操作符等,如果需要这些功能,可以用下面的 shell
模块
例1:执行特定命令
执行命令
$ ansible 'ecs[0]' -m command -a "echo 123"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
123
例2:在特定目录执行命令
执行命令
$ ansible 'ecs[0]' -m command -a "chdir=/prodata ls"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
docker
gitbook
jupyterlab
leanote
mrdoc
scripts
web
wiz
例3:文件存在,则不执行命令
$ ansible 'ecs[0]' -m command -a "creates=/tmp/testfile ls"
sz-aliyun-ecs-1 | SUCCESS | rc=0 >>
skipped, since /tmp/testfile exists
例4:文件不存在,则执行命令
执行命令
$ ansible 'ecs[0]' -m command -a "removes=/tmp/testfile ls"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
anki
data
install-release.sh
node_exporter-0.16.0.linux-amd64
node_exporter-0.16.0.linux-amd64.tar.gz
prometheus.tar.gz
prometheus-webhook-dingtalk-1.4.0.linux-amd64.tar.gz
sqlite-autoconf-3350500
sqlite-autoconf-3350500.tar.gz
shell 模块
Shell 模块同样是用来远程执行命令,但区别是 命令 会经由 shell 执行,所以最常用的就是它
例1:执行特定命令
执行命令
$ ansible 'ecs[0]' -m shell -a "echo Hello > /tmp/testfile && cat /tmp/testfile"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
Hello
例2:调用特定 shell 执行命令
执行命令
$ ansible 'ecs[0]' -m shell -a 'executable=/bin/zsh echo zsh > /tmp/testfile && cat /tmp/testfile'
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
zsh
scrips 模块
必学的一个模块,不多说直接开始
创建一个测试脚本
$ cat t1.sh
#!/bin/bash
echo "This is t1 shell scripts"
例1:执行特定脚本
执行命令
$ ansible 'ecs[0]' -m script -a "chdir=/tmp t1.sh"
sz-aliyun-ecs-1 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to sz-aliyun-ecs-1 closed.\r\n",
"stderr_lines": [
"Shared connection to sz-aliyun-ecs-1 closed."
],
"stdout": "This is t1 shell scripts\r\n",
"stdout_lines": [
"This is t1 shell scripts"
]
}
例2:文件存在,则不执行脚本
执行命令
$ ansible 'ecs[0]' -m script -a "chdir=/tmp creates=/tmp/testfile t1.sh"
sz-aliyun-ecs-1 | SKIPPED
例3:文件存在,则不执行脚本
执行命令
$ ansible 'ecs[0]' -m script -a "chdir=/tmp removes=/tmp/testfile t1.sh"
sz-aliyun-ecs-1 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to sz-aliyun-ecs-1 closed.\r\n",
"stderr_lines": [
"Shared connection to sz-aliyun-ecs-1 closed."
],
"stdout": "This is t1 shell scripts\r\n",
"stdout_lines": [
"This is t1 shell scripts"
]
}
1.2.1 并行执行
串行执行任务
$ ansible ecs -a 'date "+%H:%M:%S"' -f 1
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
20:28:12
bj-huawei-hecs-1 | CHANGED | rc=0 >>
20:28:13
PS:当不指定
-m
模块时,将使用 ansible 的默认模块command
,它不会通过shell
进行处理,所以像$HOME
和像<
,>
,|
,;
和&
将不工作
并行执行任务
$ ansible ecs -a 'date "+%H:%M:%S"' -f 2
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
20:28:20
bj-huawei-hecs-1 | CHANGED | rc=0 >>
20:28:20
这里涉及到 2 个新的参数:
-a MODULE_ARGS, --args
:模块参数 ``,目前个人理解为所执行的命令及参数
-f FORKS, --forks
:并行进程数,最多 fork 出几个子进程去执行任务,默认为5
指明执行用户
有时,我们需要使用特殊的用户去运行程序,例如:安全场景要求下,我们需要用最小化权限的用户启动应用进程,针对这个需求,我们可以通过 -u
参数指明我们远程执行时使用那个用户
$ ansible ecs -a 'id' -u lotus -k
SSH password:
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
uid=1001(lotus) gid=1001(lotus) groups=1001(lotus)
bj-huawei-hecs-1 | CHANGED | rc=0 >>
uid=1000(lotus) gid=1000(lotus) groups=1000(lotus)
又是一个新参数:-k
-k, --ask-pass
:提示用户输入密码进行远程验证
根据官方文档的中文版本,尝试了下 --sudo
参数,目前看来是无法工作的
官方示例:
$ ansible atlanta -a "/usr/bin/foo" -u username --sudo [--ask-sudo-pass]
实验结果:
$ ansible ecs -a 'id' -u lotus --sudo
usage: ansible [-h] [--version] [-v] [-b] [--become-method BECOME_METHOD]
[--become-user BECOME_USER] [-K] [-i INVENTORY] [--list-hosts]
[-l SUBSET] [-P POLL_INTERVAL] [-B SECONDS] [-o] [-t TREE] [-k]
[--private-key PRIVATE_KEY_FILE] [-u REMOTE_USER]
[-c CONNECTION] [-T TIMEOUT]
[--ssh-common-args SSH_COMMON_ARGS]
[--sftp-extra-args SFTP_EXTRA_ARGS]
[--scp-extra-args SCP_EXTRA_ARGS]
[--ssh-extra-args SSH_EXTRA_ARGS] [-C] [--syntax-check] [-D]
[-e EXTRA_VARS] [--vault-id VAULT_IDS]
[--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES]
[-f FORKS] [-M MODULE_PATH] [--playbook-dir BASEDIR]
[-a MODULE_ARGS] [-m MODULE_NAME]
pattern
ansible: error: unrecognized arguments: --sudo
1.2.2 文件操作
Ansible 支持以并行的方式发送文件到多台节点上,对应的功能模块为 copy
、file
:
copy 模块发送文件
$ ansible ecs -m copy -a "src=/etc/hosts dest=/tmp/hosts"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"checksum": "188c9c72076e04a2ef823bf9dad28504469cd08c",
"dest": "/tmp/hosts",
"gid": 0,
"group": "root",
"md5sum": "e4bdcda443cdcad751b0253aebf2aed7",
"mode": "0644",
"owner": "root",
"size": 290,
"src": "/root/.ansible/tmp/ansible-tmp-1632056206.08-10548-194351796284871/source",
"state": "file",
"uid": 0
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"checksum": "188c9c72076e04a2ef823bf9dad28504469cd08c",
"dest": "/tmp/hosts",
"gid": 0,
"group": "root",
"md5sum": "e4bdcda443cdcad751b0253aebf2aed7",
"mode": "0644",
"owner": "root",
"size": 290,
"src": "/root/.ansible/tmp/ansible-tmp-1632056206.58-10550-120318189335979/source",
"state": "file",
"uid": 0
}
确认文件是否存在
$ ansible ecs -a 'ls -l /tmp/hosts' -f 2
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
-rw-r--r-- 1 root root 290 Sep 19 20:56 /tmp/hosts
bj-huawei-hecs-1 | CHANGED | rc=0 >>
-rw-r--r-- 1 root root 290 Sep 19 20:56 /tmp/hosts
file 模块操作文件
变更元数据
通过 file
模块,我们可以变更文件的属主/属组/权限
等元数据信息
$ ansible ecs -m file -a "dest=/tmp/hosts mode=600 owner=lotus group=lotus"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 1001,
"group": "lotus",
"mode": "0600",
"owner": "lotus",
"path": "/tmp/hosts",
"size": 290,
"state": "file",
"uid": 1001
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 1000,
"group": "lotus",
"mode": "0600",
"owner": "lotus",
"path": "/tmp/hosts",
"size": 290,
"state": "file",
"uid": 1000
}
查看文件信息
$ ansible ecs -a 'ls -l /tmp/hosts' -f 2
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
-rw------- 1 lotus lotus 290 Sep 19 20:56 /tmp/hosts
bj-huawei-hecs-1 | CHANGED | rc=0 >>
-rw------- 1 lotus lotus 290 Sep 19 20:56 /tmp/hosts
创建目录
通过参数 state=directory
表明我们要删除这个文件(目录)
ansible ecs -m file -a "dest=/tmp/ansible_temp_folder mode=755 owner=lotus group=lotus state=directory"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 1001,
"group": "lotus",
"mode": "0755",
"owner": "lotus",
"path": "/tmp/ansible_temp_folder",
"size": 4096,
"state": "directory",
"uid": 1001
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 1000,
"group": "lotus",
"mode": "0755",
"owner": "lotus",
"path": "/tmp/ansible_temp_folder",
"size": 4096,
"state": "directory",
"uid": 1000
}
查看目录信息
$ ansible ecs -a 'ls -la /tmp/ansible_temp_folder' -f 2
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
total 24
drwxr-xr-x 2 lotus lotus 4096 Sep 19 21:08 .
drwxrwxrwt. 30 root root 20480 Sep 19 21:16 ..
bj-huawei-hecs-1 | CHANGED | rc=0 >>
total 24
drwxr-xr-x 2 lotus lotus 4096 Sep 19 21:08 .
drwxrwxrwt. 11 root root 20480 Sep 19 21:16 ..
删除目录
通过参数 state=absent
表明我们要删除这个文件(目录)
$ ansible ecs -m file -a "dest=/tmp/ansible_temp_folder state=absent"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"path": "/tmp/ansible_temp_folder",
"state": "absent"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"path": "/tmp/ansible_temp_folder",
"state": "absent"
}
确认目录是否存在
$ ansible ecs -a 'ls -la /tmp/ansible_temp_folder' -f 2
sz-aliyun-ecs-1 | FAILED | rc=2 >>
ls: cannot access /tmp/ansible_temp_folder: No such file or directorynon-zero return code
bj-huawei-hecs-1 | FAILED | rc=2 >>
ls: cannot access /tmp/ansible_temp_folder: No such file or directorynon-zero return code
blockinfile 模块变更内容
blockinfile 模块可以帮助我们在指定的文件中插入”被标记的一段文本”,加标记是为了后续找到找到这段文本,然后修改或者删除它
相关参数一览:
参数 | 描述 |
---|---|
path | 指定要操作的文件 |
block | 此参数用于指定我们想要操作的那”一段文本”,此参数有一个别名叫”content”,使用 content 或 block 的作用是相同的 |
marker | 自定义开始结束标记,默认 ansible 会为文本前后添加开始结束标记 `# [BEGIN |
state | present(默认值) 、absent(删除内容) |
insertafter | 插入哪行的后面,可以用 正则 或 EOF/BOF(End/Begin of File) 关键字 设定插入位置,默认插入文件末尾 |
insertbefore | 插入哪行的前面,可以用 正则 或 EOF/BOF(End/Begin of File) 关键字 设定插入位置 |
backup | 更新时自动备份 |
create | 操作文件并不存在时,是否自动创建 |
我们将 /etc/rc.d/rc.local 文件复制到 /tmp/ 目录中,以做测试
$ ansible ecs -m shell -a "cp /etc/rc.d/rc.local /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
bj-huawei-hecs-1 | CHANGED | rc=0 >>
插入末尾
尝试在 /testdir/rc.local 文件尾部插入如下两行服务启动的命令
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="systemctl start ntpd\nsystemctl start docker" '
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
查看文件内容
$ ansible ecs -m shell -a "tail -5 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
touch /var/lock/subsys/local
# BEGIN ANSIBLE MANAGED BLOCK
systemctl start ntpd
systemctl start docker
# END ANSIBLE MANAGED BLOCK
bj-huawei-hecs-1 | CHANGED | rc=0 >>
touch /var/lock/subsys/local
# BEGIN ANSIBLE MANAGED BLOCK
systemctl start ntpd
systemctl start docker
# END ANSIBLE MANAGED BLOCK
定义内容标识
上面示例中,Ansible 会为添加的内容前后添加 “ANSIBLE MANAGED BLOCK” 标识,有时我们需要自己去定义附和需求的标识,如下:service <svcName> to start
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="systemctl start vsftpd" marker="#{mark} service vsftpd to start"'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
查看文件内容
$ ansible ecs -m shell -a "tail -5 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
systemctl start docker
# END ANSIBLE MANAGED BLOCK
#BEGIN service vsftpd to start
systemctl start vsftpd
#END service vsftpd to start
bj-huawei-hecs-1 | CHANGED | rc=0 >>
systemctl start docker
# END ANSIBLE MANAGED BLOCK
#BEGIN service vsftpd to start
systemctl start vsftpd
#END service vsftpd to start
更新指定内容
blockinfile 模块是基于特定标记实现更新的,简单来说,如果某一标记不存在,则插入内容;如果某一标记存在,则更新内容,如下:
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="systemctl start vsftpd-server" marker="#{mark} service vsftpd to start"'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
查看文件内容
$ ansible ecs -m shell -a "tail -5 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
systemctl start docker
# END ANSIBLE MANAGED BLOCK
#BEGIN service vsftpd to start
systemctl start vsftpd-server
#END service vsftpd to start
bj-huawei-hecs-1 | CHANGED | rc=0 >>
systemctl start docker
# END ANSIBLE MANAGED BLOCK
#BEGIN service vsftpd to start
systemctl start vsftpd-server
#END service vsftpd to start
vsftpd
更新为了 vsftpd-server
更新时备份
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="backup..." marker="#{mark} test backup" insertafter=EOF backup=yes'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
查看文件列表
$ ansible ecs -m shell -a "ls -l /tmp/rc.local* | grep rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
-rw-r--r-- 1 root root 663 Sep 24 19:54 /tmp/rc.local
-rw-r--r-- 1 root root 617 Sep 24 19:49 /tmp/rc.local.4872.2021-09-24@19:54:23~
bj-huawei-hecs-1 | CHANGED | rc=0 >>
-rw-r--r-- 1 root root 663 Sep 24 19:54 /tmp/rc.local
-rw-r--r-- 1 root root 617 Sep 24 19:49 /tmp/rc.local.16633.2021-09-24@19:54:18~
查看文件内容
$ ansible ecs -m shell -a "tail -3 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
#BEGIN test backup
backup...
#END test backup
bj-huawei-hecs-1 | CHANGED | rc=0 >>
#BEGIN test backup
backup...
#END test backup
查看备份文件
$ ansible ecs -m shell -a "tail -3 /tmp/rc.local.*"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
# that this script will be executed during boot.
touch /var/lock/subsys/local
bj-huawei-hecs-1 | CHANGED | rc=0 >>
# that this script will be executed during boot.
touch /var/lock/subsys/local
删除指定内容
老套路,还是基于 state=absent
属性
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="systemctl start vsftpd-server" marker="#{mark} service vsftpd to start" state=absent'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block removed"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block removed"
}
查看文件内容
$ ansible ecs -m shell -a "tail -5 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
touch /var/lock/subsys/local
# BEGIN ANSIBLE MANAGED BLOCK
systemctl start ntpd
systemctl start docker
# END ANSIBLE MANAGED BLOCK
bj-huawei-hecs-1 | CHANGED | rc=0 >>
touch /var/lock/subsys/local
# BEGIN ANSIBLE MANAGED BLOCK
systemctl start ntpd
systemctl start docker
# END ANSIBLE MANAGED BLOCK
OK,vsftpd 服务的内容删除掉了~
插入文首
通过 insertbefore=BOF
参数值,可以实现插入指定内容到文件开头
文首 BOF(Begin Of File)、 文末 EOF(End Of File)
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="systemctl start vsftpd-server" insertbefore=BOF marker="#{mark} temp test rc.local" '
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
查看文件内容
$ ansible ecs -m shell -a "head -5 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
#BEGIN temp test rc.local
systemctl start vsftpd-server
#END temp test rc.local
#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
bj-huawei-hecs-1 | CHANGED | rc=0 >>
#BEGIN temp test rc.local
systemctl start vsftpd-server
#END temp test rc.local
#!/bin/bash
# THIS FILE IS ADDED FOR COMPATIBILITY PURPOSES
插入正则匹配行后
某些场景下我们需要更精确的插入位置,这时可以使用 insertafter/insertbefore
+ 正则表达式
实现需求:
$ ansible ecs -m blockinfile -a 'path=/tmp/rc.local block="systemctl start vsftpd-server" marker="#{mark} test regex" insertafter="^#!/bin/bash" '
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "Block inserted"
}
查看文件内容
$ ansible ecs -m shell -a "head -10 /tmp/rc.local"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
#BEGIN temp test rc.local
systemctl start vsftpd-server
#END temp test rc.local
#!/bin/bash
#BEGIN test regex
systemctl start vsftpd-server
#END test regex
...
bj-huawei-hecs-1 | CHANGED | rc=0 >>
#BEGIN temp test rc.local
systemctl start vsftpd-server
#END temp test rc.local
#!/bin/bash
#BEGIN test regex
systemctl start vsftpd-server
#END test regex
...
自动创建并插入
当插入内容时,如果目标文件不存在,自动执行创建在插入内容
$ ansible ecs -m blockinfile -a 'path=/tmp/testfile7 block="backup..." marker="#{mark} test create" create=yes'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "File created"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"msg": "File created"
}
查看文件内容
$ ansible ecs -m shell -a "cat /tmp/testfile7"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
#BEGIN test create
backup...
#END test create
bj-huawei-hecs-1 | CHANGED | rc=0 >>
#BEGIN test create
backup...
#END test create
lineinfile 模块
blockinfile
模块 是基于通常是基于段的粒度来操作文本,lineinfile
则基于行的粒度操作文本,它可以实现 确保某行存在与文本中、删除特定行、以及根据表达式替换某行
相关参数一览:
参数 | 描述 |
---|---|
path | 指定要操作的文件 |
line | 此参数用于指定我们想要操作的那 ”一行文本” |
regexp | 使用正则表达式匹配对应的行,匹配到多行时,替换操作 只影响尾行、删除操作 影响所有匹配行 |
state | present(确保行存在) 、absent(删除行) |
backrefs | backrefs 为 yes,开启向引用,当使用正则匹配时,默认情况下不允许引用分组(即使存在) backrefs 为 yes,取消补插入,默认情况,若正则未匹配任何行,则插入 line 内容到文本末尾 |
insertafter | 插入哪行的后面,可以用 正则 或 EOF/BOF(End/Begin of File) 关键字 设定插入位置行后,默认插入文件末尾当使用 backrefs 参数时此参数失效 |
insertbefore | 插入哪行的前面,可以用 正则 或 EOF/BOF(End/Begin of File) 关键字 设定插入位置行前当使用 backrefs 参数时此参数失效 |
backup | 更新时自动备份 |
create | 操作文件并不存在时,是否自动创建 |
例1:插入确保行存在(没有即插入)
准备一个测试文件,内容如下:
$ cat testfile
Hello ansible,Hiiii
lineinfile -
Ensure a particular line is in a file,
lineinfile -
or replace an existing line using a back-referenced regular expression.
执行命令
$ ansible 'ecs[0]' -m lineinfile -a 'path=/tmp/testfile line="test line"'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup": "",
"changed": true,
"msg": "line added"
}
查看效果
$ hostname && cat /tmp/testfile
sz-aliyun-ecs-1
Hello ansible,Hiiii
lineinfile -
Ensure a particular line is in a file,
lineinfile -
or replace an existing line using a back-referenced regular expression.
test line
例2:正则匹配替换或插入
通过上一例子的内容也可以看到,有 2 行 lineinfile -
,我们通过正则去尝试替换,测试是否是尾行被替换掉了
执行命令
$ ansible 'ecs[0]' -m lineinfile -a 'path=/tmp/testfile regex="^line" line="test line"'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup": "",
"changed": true,
"msg": "line replaced"
}
执行效果
$ cat testfile
Hello ansible,Hiiii
lineinfile -
Ensure a particular line is in a file,
test line
or replace an existing line using a back-referenced regular expression.
test line
OK 符合预期!
例3:删除特定行
老套路 absent
,删除的时候可不管你匹配几行,只要匹配全都删除!
执行命令
$ ansible 'ecs[0]' -m lineinfile -a 'path=/tmp/testfile line="test line" state=absent'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup": "",
"changed": true,
"found": 2,
"msg": "2 line(s) removed"
}
执行效果
$ cat testfile
Hello ansible,Hiiii
lineinfile -
Ensure a particular line is in a file,
or replace an existing line using a back-referenced regular expression.
同样正则删除也是都删
执行命令
$ ansible 'ecs[0]' -m lineinfile -a 'path=/tmp/testfile regex='file' line="test line" state=absent'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup": "",
"changed": true,
"found": 2,
"msg": "2 line(s) removed"
}
执行效果
$ cat testfile
Hello ansible,Hiiii
or replace an existing line using a back-referenced regular expression.
例4:反向引用替换
通过设置 backrefs
参数为 yes
启用反向引用功能
执行命令
$ ansible 'ecs[0]' -m lineinfile -a 'path=/tmp/testfile regex="(\w+\s\w+),\w+" line="\1" backrefs=yes'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup": "",
"changed": true,
"msg": "line replaced"
}
执行效果
$ cat testfile
Hello ansible
or replace an existing line using a back-referenced regular expression.
其他部分都基本与 blockinfile 类似,暂不演示~
find 模块
顾名思义,类似于 find 命令
直接看几个小例子吧~
例1:在指定目录中查找包含特定内容的文件
几个点,指定目录、正则匹配文件内容、递归查找
执行命令
$ ansible 'ecs[0]' -m find -a "paths=/tmp contains='^Hel' recurse=yes"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 24,
"files": [
{
"atime": 1633103275.1560168,
"ctime": 1633103271.1926205,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1193873,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103271.1926205,
"nlink": 1,
"path": "/tmp/testfile",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 86,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
}
],
"matched": 1,
"msg": ""
}
例2:查找以 .sh
几位的文件,包括隐藏文件
几个点:等值匹配文件名、包括隐藏文件
执行命令
$ ansible 'ecs[0]' -m find -a "paths=/tmp patterns='*.py' hidden=yes"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 25,
"files": [
{
"atime": 1633103962.7277672,
"ctime": 1633103962.7277672,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189829,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103962.7277672,
"nlink": 1,
"path": "/tmp/.app.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
}
],
"matched": 1,
"msg": ""
}
查询所有类型的匹配文件
$ ansible 'ecs[0]' -m find -a "paths=/tmp patterns='*.py' file_type=any hidden=yes"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 26,
"files": [
{
"atime": 1633103962.7277672,
"ctime": 1633103962.7277672,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189829,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103962.7277672,
"nlink": 1,
"path": "/tmp/.app.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
},
{
"atime": 1633104055.3080244,
"ctime": 1633104055.3080244,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189847,
"isblk": false,
"ischr": false,
"isdir": true,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": false,
"issock": false,
"isuid": false,
"mode": "0755",
"mtime": 1633104055.3080244,
"nlink": 2,
"path": "/tmp/.django.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 4096,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": true,
"xoth": true,
"xusr": true
}
],
"matched": 2,
"msg": ""
}
例3:正则匹配文件名
开启 use_regex=yes
后,patterns
支持正则匹配
执行命令
$ ansible 'ecs[0]' -m find -a "paths=/tmp patterns='.[a-z]+.py' use_regex=yes file_type=any hidden=yes"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 26,
"files": [
{
"atime": 1633103962.7277672,
"ctime": 1633103962.7277672,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189829,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103962.7277672,
"nlink": 1,
"path": "/tmp/.app.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
},
{
"atime": 1633104055.3080244,
"ctime": 1633104055.3080244,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189847,
"isblk": false,
"ischr": false,
"isdir": true,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": false,
"issock": false,
"isuid": false,
"mode": "0755",
"mtime": 1633104055.3080244,
"nlink": 2,
"path": "/tmp/.django.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 4096,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": true,
"xoth": true,
"xusr": true
}
],
"matched": 2,
"msg": ""
}
例4:查找指定时间范围内的文件
age=-<num><单位hmswMY>
执行命令
ansible 'ecs[0]' -m find -a "paths=/tmp age=-1h hidden=yes"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 26,
"files": [
{
"atime": 1633103962.7277672,
"ctime": 1633103962.7277672,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189829,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103962.7277672,
"nlink": 1,
"path": "/tmp/.app.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
},
{
"atime": 1633103275.1560168,
"ctime": 1633103271.1926205,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1193873,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103271.1926205,
"nlink": 1,
"path": "/tmp/testfile",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 86,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
}
],
"matched": 2,
"msg": ""
}
例5:查找大小超过指定容量的文件
size=<num><单位>
先创建一个 100m 的测试文件
$ dd if=/dev/zero of=/tmp/testfile2 count=100 bs=1M
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 0.734994 s, 143 MB/s
执行命令
$ ansible 'ecs[0]' -m find -a "paths=/tmp size=100m"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 26,
"files": [
{
"atime": 1633103762.3407304,
"ctime": 1633104519.3404229,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1192944,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "1204",
"mtime": 1633104519.3404229,
"nlink": 1,
"path": "/tmp/testfile2",
"pw_name": "root",
"rgrp": false,
"roth": true,
"rusr": false,
"size": 104857600,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
}
],
"matched": 1,
"msg": ""
}
例6:查找文件并返回 sha1 校验码
$ ansible 'ecs[0]' -m find -a "paths=/tmp patterns='*.py' file_type=file hidden=yes get_checksum=yes"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"examined": 26,
"files": [
{
"atime": 1633104731.9406807,
"checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"ctime": 1633103962.7277672,
"dev": 64769,
"gid": 0,
"gr_name": "root",
"inode": 1189829,
"isblk": false,
"ischr": false,
"isdir": false,
"isfifo": false,
"isgid": false,
"islnk": false,
"isreg": true,
"issock": false,
"isuid": false,
"mode": "0644",
"mtime": 1633103962.7277672,
"nlink": 1,
"path": "/tmp/.app.py",
"pw_name": "root",
"rgrp": true,
"roth": true,
"rusr": true,
"size": 0,
"uid": 0,
"wgrp": false,
"woth": false,
"wusr": true,
"xgrp": false,
"xoth": false,
"xusr": false
}
],
"matched": 1,
"msg": ""
}
1.2.3 包管理
Ansible 提供了 yum
、apt
模块,以实现对常见包管理操作的支持,语法规则如下:
ansible ecs -m yum -a "name=<package>" state=<action>
yum 模块
安装软件
安装软件可以使用present
、latest
、installed
3 个动作
present
installed
:仅安装指定的软件包,不对其进行检查更新latest
:安装软件包的同时对其进行检查更新,确保使用的是最新版本
$ ansible ecs -m yum -a "name=iotop state=present" -f 2
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"changes": {
"installed": [
"iotop"
]
},
"msg": "",
"rc": 0,
"results": [
"...\n\nComplete!\n"
]
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"changes": {
"installed": [
"iotop"
]
},
"msg": "",
"rc": 0,
"results": [
"...\n\nComplete!\n"
]
}
卸载软件
卸载软件 可以使用absent
、remove
2 个动作
ansible ecs -m yum -a "name=iotop state=removed" -f 2
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"changes": {
"removed": [
"iotop"
]
},
"msg": "",
"rc": 0,
"results": [
"\n\nComplete!\n"
]
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"changes": {
"removed": [
"iotop"
]
},
"msg": "",
"rc": 0,
"results": [
"\n\nComplete!\n"
]
}
目前看来,没发现两个动作在执行效果上有什么区别~
yum_repo 模块
操作管理 yum 仓库文件的模块,直接上例子:
例1:创建 ali-epel 源,并配置开启
执行命令
$ ansible 'ecs[0]' -m yum_repository -a 'name=ali-epel description="Alibaba EPEL" baseurl=https://mirrors.aliyun.com/epel/7/$basearch enabled=yes'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"repo": "ali-epel",
"state": "present"
}
执行效果
$ ls /etc/yum.repos.d
ali-epel.repo CentOS-Base.repo docker-ce.repo epel.repo epel.repo.rpmnew epel-testing.repo
$ cat /etc/yum.repos.d/ali-epel.repo
[ali-epel]
baseurl = https://mirrors.aliyun.com/epel/7/$basearch
enabled = 1
name = Alibaba EPEL
刷新仓库
$ yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
ali-epel | 4.7 kB 00:00:00
(1/3): ali-epel/x86_64/group_gz | 96 kB 00:00:00
(2/3): ali-epel/x86_64/updateinfo | 1.0 MB 00:00:00
(3/3): ali-epel/x86_64/primary_db | 7.0 MB 00:00:03
repo id repo name status
ali-epel/x86_64 Alibaba EPEL 13,674
base/7/x86_64 CentOS-7 10,072
docker-ce-stable/7/x86_64 Docker CE Stable - x86_64 123
epel/x86_64 Extra Packages for Enterprise Linux 7 - x86_64 13,674
extras/7/x86_64 CentOS-7 500
updates/7/x86_64 CentOS-7 2,751
repolist: 40,794
例2:删除仓库
老套路,state=absent
执行命令
$ ls /etc/yum.repos.d
CentOS-Base.repo docker-ce.repo epel.repo epel.repo.rpmnew epel-testing.repo
$ ansible 'ecs[0]' -m yum_repository -a 'name=ali-epel state=absent'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"repo": "ali-epel",
"state": "absent"
刷新仓库
$ yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
repo id repo name status
base/7/x86_64 CentOS-7 10,072
docker-ce-stable/7/x86_64 Docker CE Stable - x86_64 123
epel/x86_64 Extra Packages for Enterprise Linux 7 - x86_64 13,674
extras/7/x86_64 CentOS-7 500
updates/7/x86_64 CentOS-7 2,751
repolist: 40,794
easy_install 模块
通过 easy_install
模块安装 Python
的 django
库
$ ansible ecs -s -m easy_install -a "name=django"
1.2.4 管理用户、组
生成密文
使用 debug
模块生成 密文格式的用户密码
$ ansible localhost -m debug -a "msg={{ 'Passw0rd' | password_hash('sha512', 'salt') }}"
localhost | SUCCESS => {
"msg": "$6$salt$N.PquVfbm9DyVhvrzy9pZ5yAfFM2ihCS9ZKzdllgl2ZPYIqcVYsVendbrH1Pa38eAfTLK0eBdQjjuSaS/dGVC."
}
创建用户
创建用户 foo
填入早先此前生成的密文,注意 $
符号需要转移处理
$ ansible ecs -m user -a 'name=foo shell=/bin/bash password='\$6\$salt\$N.PquVfbm9DyVhvrzy9pZ5yAfFM2ihCS9ZKzdllgl2ZPYIqcVYsVendbrH1Pa38eAfTLK0eBdQjjuSaS/dGVC.' create_home=yes'
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"comment": "",
"create_home": true,
"group": 1004,
"home": "/home/foo",
"name": "foo",
"password": "NOT_LOGGING_PASSWORD",
"shell": "/bin/bash",
"state": "present",
"system": false,
"uid": 1004
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"comment": "",
"create_home": true,
"group": 1001,
"home": "/home/foo",
"name": "foo",
"password": "NOT_LOGGING_PASSWORD",
"shell": "/bin/bash",
"state": "present",
"system": false,
"uid": 1001
}
确认用户创建情况
$ ansible ecs -a "id" -u foo -k
SSH password:
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
uid=1004(foo) gid=1004(foo) groups=1004(foo)
bj-huawei-hecs-1 | CHANGED | rc=0 >>
uid=1001(foo) gid=1001(foo) groups=1001(foo)
删除用户
通过参数 state=absent
删除特定用户
$ ansible ecs -m user -a "name=foo state=absent"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"force": false,
"name": "foo",
"remove": false,
"state": "absent"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"force": false,
"name": "foo",
"remove": false,
"state": "absent"
}
创建组
通过模块 group
参数 state=absent
创建组
$ ansible ecs -m group -a "name=develop state=present"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 1004,
"name": "develop",
"state": "present",
"system": false
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 1001,
"name": "develop",
"state": "present",
"system": false
}
查看创建情况
$ ansible ecs -a "getent group develop"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
develop:x:1004:
bj-huawei-hecs-1 | CHANGED | rc=0 >>
develop:x:1001:
删除组
老样子,state=absent
$ ansible ecs -m group -a "name=develop state=absent"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "develop",
"state": "absent"
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "develop",
"state": "absent"
}
确认删除情况
$ ansible ecs -a "getent group develop"
sz-aliyun-ecs-1 | FAILED | rc=2 >>
non-zero return code
bj-huawei-hecs-1 | FAILED | rc=2 >>
non-zero return code
1.2.6 Git 操作
代码拉取
拉取代码前,需要提前确保服务器对 Git 仓库有访问权限,如使用 SSH 拉取代码,别忘了将对应节点的 SSH 公钥添加到 Git 平台
$ ansible ecs -m git -a "repo=git@gitee.com:l0tusch1ng/hello.git dest=/tmp/hello version=HEAD accept_hostkey=yes"
sz-aliyun-ecs-1 | CHANGED => {
"after": "0be478d0f2be0f93823bcfb6af6f0c344ba7800d",
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"before": null,
"changed": true
}
bj-huawei-hecs-1 | CHANGED => {
"after": "0be478d0f2be0f93823bcfb6af6f0c344ba7800d",
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"before": null,
"changed": true
}
查看拉取下来的 Git 仓库
$ ansible ecs -a "ls /tmp/hello"
sz-aliyun-ecs-1 | CHANGED | rc=0 >>
LICENSE
README.en.md
README.md
bj-huawei-hecs-1 | CHANGED | rc=0 >>
LICENSE
README.en.md
README.md
1.2.7 服务管理
老套路,通过 service
模块中 state
参数进行服务管理
启动服务
$ ansible ecs -m service -a "name=ntpd state=started"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "ntpd",
"state": "started",
"status": {
# ...
}
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "ntpd",
"state": "started",
"status": {
# ...
}
}
重启服务
$ ansible ecs -m service -a "name=ntpd state=restarted"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "ntpd",
"state": "started",
"status": {
# ...
}
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "ntpd",
"state": "started",
"status": {
# ...
}
}
停止服务
ansible ecs -m service -a "name=ntpd state=stopped"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "ntpd",
"state": "stopped",
"status": {
# ...
}
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"name": "ntpd",
"state": "stopped",
"status": {
# ...
}
}
1.2.8 计划任务
cron 模块基本实现了 crontab 的功能,常用的使用范例如下
例1:创建计划任务每天8点执行
创建计划任务 test crontab,设置为 每天早上 8 点执行,任务是执行 /tmp/t1.sh
执行命令
$ ansible 'ecs[0]' -m cron -a "name='test crontab' hour=8 minute=0 job='/tmp/t1.sh'"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": [
"test crontab"
]
}
执行效果
$ crontab -l
#Ansible: test crontab
0 8 * * * /tmp/t1.sh
例2:创建计划任务每3天一次,每次8:00执行
执行命令
$ ansible 'ecs[0]' -m cron -a "name='test crontab demo2' hour=8 minute=0 day=*/3 job='/tmp/t1.sh'"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": [
"test crontab",
"test crontab demo2"
]
}
执行效果
$ crontab -l
#Ansible: test crontab
0 8 * * * /tmp/t1.sh
#Ansible: test crontab demo2
0 8 */3 * * /tmp/t1.sh
例2:创建计划任务 特殊时间执行
所谓特殊时间包括:reboot
、yearly
、monthly
、weekly
、daily
、hourly
执行命令
$ ansible 'ecs[0]' -m cron -a "name='test crontab demo3' special_time=reboot job='/tmp/t1.sh'"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"envs": [],
"jobs": [
"test crontab",
"test crontab demo2",
"test crontab demo3"
]
}
执行效果
$ crontab -l
#Ansible: test crontab
0 8 * * * /tmp/t1.sh
#Ansible: test crontab demo2
0 8 */3 * * /tmp/t1.sh
#Ansible: test crontab demo3
@reboot /tmp/t1.sh
例4:更新计划任务,并保存备份
执行命令
$ ansible 'ecs[0]' -m cron -a "name='test crontab' hour=8 minute=8 job='/tmp/t1.sh' backup=yes"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup_file": "/tmp/crontabU0yxKe",
"changed": true,
"envs": [],
"jobs": [
"test crontab",
"test crontab demo2",
"test crontab demo3"
]
}
执行效果
$ crontab -l
# 50 17 * * * systemctl stop node_exporter.service
#27 0 * * * /root/.pyenv/versions/3.9.0/bin/python /prodata/scripts/bmcc_user_check_in.py
* * * * * cd /prodata/gitbook/typora && git pull &> /dev/null
#Ansible: test crontab
8 8 * * * /tmp/t1.sh
#Ansible: test crontab demo2
0 8 */3 * * /tmp/t1.sh
#Ansible: test crontab demo3
@reboot /tmp/t1.sh
查看备份
$ cat /tmp/crontabU0yxKe
# 50 17 * * * systemctl stop node_exporter.service
#27 0 * * * /root/.pyenv/versions/3.9.0/bin/python /prodata/scripts/bmcc_user_check_in.py
* * * * * cd /prodata/gitbook/typora && git pull &> /dev/null
#Ansible: test crontab
0 8 * * * /tmp/t1.sh
#Ansible: test crontab demo2
0 8 */3 * * /tmp/t1.sh
#Ansible: test crontab demo3
@reboot /tmp/t1.sh
例5:禁用计划任务,并保存备份
无论是更新、禁用、删除,只要是操作变更计划任务,最好都要开启备份参数 backup=yes
执行命令
$ ansible 'ecs[0]' -m cron -a "name='test crontab' hour=8 minute=8 job='/tmp/t1.sh' disabled=yes backup=yes"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup_file": "/tmp/crontab9RjAvH",
"changed": true,
"envs": [],
"jobs": [
"test crontab",
"test crontab demo2",
"test crontab demo3"
]
}
执行效果
$ crontab -l
# 50 17 * * * systemctl stop node_exporter.service
#27 0 * * * /root/.pyenv/versions/3.9.0/bin/python /prodata/scripts/bmcc_user_check_in.py
* * * * * cd /prodata/gitbook/typora && git pull &> /dev/null
#Ansible: test crontab
#8 8 * * * /tmp/t1.sh
#Ansible: test crontab demo2
0 8 */3 * * /tmp/t1.sh
#Ansible: test crontab demo3
@reboot /tmp/t1.sh
可以看到,相关任务已被注释
例6:删除计划任务,并保存备份
执行命令
$ ansible 'ecs[0]' -m cron -a "name='test crontab' state=absent backup=yes"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"backup_file": "/tmp/crontabKiLobo",
"changed": true,
"envs": [],
"jobs": [
"test crontab demo2",
"test crontab demo3"
]
}
执行效果
$ crontab -l
#Ansible: test crontab demo2
0 8 */3 * * /tmp/t1.sh
#Ansible: test crontab demo3
@reboot /tmp/t1.sh
1.2.9 下载文件(todo)
$ ansible testserver -m get_url -a "url=https://nodejs.org/dist/v14.17.4/node-v14.17.4-linux-x64.tar.xz dest=/tmp mode=0755" -i prod-ansible-hosts
1.2.10 系统资源检测(采集)
setup 模块
例1:采集节点资源信息
跳过 setup 模块我们可以获取节点的资源信息,这个过程也叫 Gathering Facts,示例如下:
$ ansible 'ecs[0]' -m setup
内容太多,贴个地址吧
主要包括硬件、系统、网卡、CPU、磁盘、内存等,很多、很全面。但是,我们或许暂时用不到这么多信息,对此,我们可以通过关键字对信息进行过滤,比如:只获取内存相关的信息
例2:采集指定资源信息
$ ansible 'ecs[0]' -m setup -a 'filter=ansible_memory_mb'
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"ansible_memory_mb": {
"nocache": {
"free": 571,
"used": 1163
},
"real": {
"free": 73,
"total": 1734,
"used": 1661
},
"swap": {
"cached": 0,
"free": 0,
"total": 0,
"used": 0
}
},
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}
如果记不住关键字,我们还可以使用通配符,进行相对模糊的过滤,示例如下:
$ ansible 'ecs[0]' -m setup -a 'filter=*memory_mb'
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"ansible_memory_mb": {
"nocache": {
"free": 572,
"used": 1162
},
"real": {
"free": 74,
"total": 1734,
"used": 1660
},
"swap": {
"cached": 0,
"free": 0,
"total": 0,
"used": 0
}
},
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}
例3:自定义资源信息
除了节点本身信息外,我们还能够在远程主机中定义一些特殊的信息,用以标识更多的内容,例如地区、可用区、机柜、所属环境等等
ansible 默认会去目标主机的 /etc/ansible/facts.d
目录下查找以 .fact
为后缀的自定义信息,同时文件格式需要是 INI
或 JSON
创建测试文件
INI 风格
[testmsg]
region=aliyun-shenzhen
environment=development
JSON 风格
{
"testmsg": {
"region": "aliyun-shenzhen",
"environment": "development"
}
}
创建 fact 定义
$ mkdir -p /etc/ansible/facts.d
$ cat > /etc/ansible/facts.d/testmsg.fact << EOF
{
"testmsg": {
"region": "aliyun-shenzhen",
"environment": "development"
}
}
EOF
执行命令
$ ansible 'ecs[0]' -m setup -a 'filter=ansible_local'
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"testmsg": {
"testmsg": {
"environment": "development",
"region": "aliyun-shenzhen"
}
}
},
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}
当然,你也可以通过 fact_path=/dir
显示声明 fact 文件所属目录,使 ansible 到指定目录去查找 fact
$ mv /etc/ansible/facts.d/testmsg.fact /tmp
$ ansible 'ecs[0]' -m setup -a 'fact_path=/tmp filter=ansible_local'
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"testmsg": {
"testmsg": {
"environment": "development",
"region": "aliyun-shenzhen"
}
}
},
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false
}
listen_ports_facts 模块
端口检测
$ ansible testserver -m listen_ports_facts -i prod-ansible-hosts
1.2.11 后台执行(未理解)
后台执行,适用于执行 长时间 的任务,例如:编译安装软件包、初始化系统等
启动任务
-B
:异步执行任务-P
:任务状态信息拉取间隔,默认15s
$ ansible ecs -B 30 -P 0 -a "vmstat 5"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "129378104578.20485",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/129378104578.20485",
"started": 1
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "362800085138.19147",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/362800085138.19147",
"started": 1
}
查看任务状态
$ ansible sz-aliyun-ecs-1 -m async_status -a "jid=129378104578.20485"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "129378104578.20485",
"changed": false,
"finished": 0,
"started": 1
}
执行到这的时候,产生了一些疑惑,started
和 finished
不论多久,始终值保持不变,这是什么意思?状态信息未变更?
我试着调整了 -P
参数的值,再次执行
$ ansible ecs -B 10 -P 5 -a "vmstat 3"
sz-aliyun-ecs-1 | FAILED | rc=-1 >>
async task did not complete within the requested time - 10s
bj-huawei-hecs-1 | FAILED | rc=-1 >>
async task did not complete within the requested time - 10s
提示,任务未在 10s 内完成,当然这是肯定的,vmstat 是持续输出的,为什么任务突然变成前天执行了?这 -P
参数到底起到了个什么作用呢?
暂时摸不清,先撂这儿吧~
嗯,还在摸,测试个命令
vmstat
命令以每秒一次的间隔打印系统状态信息,一共 5 次-B 10
:只给任务 10 秒钟的时间运行-P 1
:以每秒的间隔,拉取任务的执行状态(包括命令打印到控制台的输出)
$ ansible ecs -B 10 -P 1 -a "vmstat 1 5"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_job_id": "604443018422.21970",
"changed": true,
"cmd": [
"vmstat",
"1",
"5"
],
"delta": "0:00:04.034279",
"end": "2021-09-19 23:38:02.394496",
"finished": 1,
"rc": 0,
"start": "2021-09-19 23:37:58.360217",
"stderr": "",
"stderr_lines": [],
"stdout": "procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----\n r b swpd free buff cache si so bi bo in cs us sy id wa st\n 1 0 0 84640 29188 597456 0 0 21 9 1 1 2 0 98 0 0\n 1 0 0 84568 29200 597392 0 0 0 152 2494 3712 8 6 85 0 0\n 0 0 0 78944 29200 597736 0 0 0 4 2525 3513 17 5 78 0 0\n 0 0 0 74148 29212 597920 0 0 68 284 3071 4366 14 8 78 0 0\n 0 0 0 78876 29212 597840 0 0 0 116 2829 4217 12 4 84 0 0",
"stdout_lines": [
"procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----",
" r b swpd free buff cache si so bi bo in cs us sy id wa st",
" 1 0 0 84640 29188 597456 0 0 21 9 1 1 2 0 98 0 0",
" 1 0 0 84568 29200 597392 0 0 0 152 2494 3712 8 6 85 0 0",
" 0 0 0 78944 29200 597736 0 0 0 4 2525 3513 17 5 78 0 0",
" 0 0 0 74148 29212 597920 0 0 68 284 3071 4366 14 8 78 0 0",
" 0 0 0 78876 29212 597840 0 0 0 116 2829 4217 12 4 84 0 0"
]
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_job_id": "58453485557.19502",
"changed": true,
"cmd": [
"vmstat",
"1",
"5"
],
"delta": "0:00:04.013519",
"end": "2021-09-19 23:38:03.612380",
"finished": 1,
"rc": 0,
"start": "2021-09-19 23:37:59.598861",
"stderr": "",
"stderr_lines": [],
"stdout": "procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----\n r b swpd free buff cache si so bi bo in cs us sy id wa st\n 1 0 0 132920 35908 385196 0 0 3 18 14 14 3 0 96 0 0\n 0 0 0 132664 35908 385224 0 0 0 12 361 607 8 0 92 0 0\n 2 0 0 123088 35920 385412 0 0 0 136 557 846 11 3 86 0 0\n 0 0 0 134284 35920 385352 0 0 0 0 394 631 10 2 88 0 0\n 0 0 0 132436 35936 385336 0 0 0 268 487 741 9 3 87 1 0",
"stdout_lines": [
"procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----",
" r b swpd free buff cache si so bi bo in cs us sy id wa st",
" 1 0 0 132920 35908 385196 0 0 3 18 14 14 3 0 96 0 0",
" 0 0 0 132664 35908 385224 0 0 0 12 361 607 8 0 92 0 0",
" 2 0 0 123088 35920 385412 0 0 0 136 557 846 11 3 86 0 0",
" 0 0 0 134284 35920 385352 0 0 0 0 394 631 10 2 88 0 0",
" 0 0 0 132436 35936 385336 0 0 0 268 487 741 9 3 87 1 0"
]
}
喏,可以看到,当我确保命令能在限期内完成时,前台执行的任务成功打印了返回,其中可以看到stdout_lines
输出了 5 次 vmstat 信息
可我还是很奇怪,为什么 -B
会失效,导致前台 Shell 终端发生阻塞?
有机会再摸索下…
似乎明白些了,示例如下:
vmstat
命令以每秒一次的间隔打印系统状态信息,一共 5 次-B 10
:只给任务 10 秒钟的时间运行-P 0
:不等待任务执行(启动并忽略)
$ ansible ecs -B 20 -P 0 -a "vmstat 1 5" -f 2
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "491593461135.22847",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/491593461135.22847",
"started": 1
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "317449108493.19704",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/317449108493.19704",
"started": 1
}
稍等几秒钟后…
$ ansible sz-aliyun-ecs-1 -m async_status -a "jid=491593461135.22847"
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "491593461135.22847",
"changed": true,
"cmd": [
"vmstat",
"1",
"5"
],
"delta": "0:00:04.034584",
"end": "2021-09-19 23:50:27.016328",
# 完成状态为 1,说明任务执行完成
"finished": 1,
"rc": 0,
"start": "2021-09-19 23:50:22.981744",
"stderr": "",
"stderr_lines": [],
"stdout": "procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----\n r b swpd free buff cache si so bi bo in cs us sy id wa st\n 1 0 0 123856 29672 597824 0 0 21 9 1 1 2 0 98 0 0\n 0 0 0 111608 29672 597724 0 0 0 0 2143 3455 8 3 89 0 0\n 0 0 0 175956 29672 597152 0 0 0 0 1974 2995 8 2 90 0 0\n 0 0 0 176120 29672 597152 0 0 0 0 1106 1698 2 1 98 0 0\n 0 0 0 176120 29672 597152 0 0 0 0 1027 1660 1 1 98 0 0",
"stdout_lines": [
"procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----",
" r b swpd free buff cache si so bi bo in cs us sy id wa st",
" 1 0 0 123856 29672 597824 0 0 21 9 1 1 2 0 98 0 0",
" 0 0 0 111608 29672 597724 0 0 0 0 2143 3455 8 3 89 0 0",
" 0 0 0 175956 29672 597152 0 0 0 0 1974 2995 8 2 90 0 0",
" 0 0 0 176120 29672 597152 0 0 0 0 1106 1698 2 1 98 0 0",
" 0 0 0 176120 29672 597152 0 0 0 0 1027 1660 1 1 98 0 0"
]
}
通过返回可以看到 "finished": 1
这是不是说明,当任务执行 成功 or 失败 finished
都会为 1,而执行时间超过 -B <timeout>
则显示为0?
验证任务执行失败
$ ansible ecs -B 5 -P 0 -a "ls -l /tmp/nofile" -f 2
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "227924770130.23660",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/227924770130.23660",
"started": 1
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "397730636493.19834",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/397730636493.19834",
"started": 1
}
查看状态信息
$ ansible sz-aliyun-ecs-1 -m async_status -a "jid=227924770130.23660"
sz-aliyun-ecs-1 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "227924770130.23660",
"changed": true,
"cmd": [
"ls",
"-l",
"/tmp/nofile"
],
"delta": "0:00:00.040787",
"end": "2021-09-19 23:59:36.361970",
"finished": 1,
"msg": "non-zero return code",
"rc": 2,
"start": "2021-09-19 23:59:36.321183",
"stderr": "ls: cannot access /tmp/nofile: No such file or directory",
"stderr_lines": [
"ls: cannot access /tmp/nofile: No such file or directory"
],
"stdout": "",
"stdout_lines": []
}
OK,符合预期~
验证任务执行超时
$ ansible ecs -B 5 -P 0 -a "vmstat 1" -f 2
sz-aliyun-ecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "90702411198.23358",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/90702411198.23358",
"started": 1
}
bj-huawei-hecs-1 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "679177421403.19797",
"changed": true,
"finished": 0,
"results_file": "/root/.ansible_async/679177421403.19797",
"started": 1
}
稍等片刻后…
$ ansible sz-aliyun-ecs-1 -m async_status -a "jid=90702411198.23358"
sz-aliyun-ecs-1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"ansible_job_id": "90702411198.23358",
"changed": false,
"finished": 0,
"started": 1
}
任务状态始终不变
啊…大致算是明白一点了,当然也仅限于 状态字段,其中执行原理还要在深入的摸索下…
1.3 扩展模块
ansible 为我们提供了各式各样丰富的模块,但是即便如此,有些时候它们不一定能满足我们的需求,为此我们可能需要自己去编写满足需求的模块
基于 ansible 灵活的设计,编写集成模块并不复杂,Shell 或 Python 都可以编写模块,下面我们会分别演示
1.3.1 Shell 编写模块
脚本编写 library/docker_sh
#!/bin/bash
set -e
source $1 $2 $3
IMAGE=$image
NAME=$name
TAG=$tag
if [ ! -z "$IMAGE" ] && [ ! -z "$NAME" ] && [ ! -z "$TAG" ]; then
container_id=$(/usr/bin/docker run -d --name ${NAME} ${IMAGE}:${TAG})
if [ -z "$id" ]; then
CHANGED="True"
echo {\"containerId\":\"$container_id\"}
exit 0
fi
else
echo {\"msg\": \"run docker container error.\"}
fi
执行命令
$ ansible 'ecs[0]' -M library -m docker_sh -a "name=ansible-nginx-demo image=nginx tag=latest"
ecs-1.aliyun.sz | SUCCESS => {
"changed": false,
"containerId": "4c3ad2ea664108e40f680c1d6a23faca7ea3eb7aed8533ec38798875db87231c"
}
执行效果
$ docker ps |grep ansible
4c3ad2ea6641 nginx:latest "/docker-entrypoint.…" 11 seconds ago Up 9 seconds 80/tcp ansible-nginx-demo
1.3.3 修改模块路径配置
为了避免重复填写 -M
参数,我们可以修改配置文件中的 library
配置项,声明 ansible 模块查找的路径
$ vim /etc/ansible/ansible.cfg
[defaults]
# some basic default values...
#inventory = /etc/ansible/hosts
#library = /usr/share/my_modules/
library = /usr/share/ansible-library
创建目录,并拷贝文件
$ mkdir /usr/share/ansible-library
$ cp library/docker_sh /usr/share/ansible-library
执行命令
$ ansible 'ecs[0]' -m docker_sh -a "name=ansible-nginx-demo2 image=nginx tag=latest"
ecs-1.aliyun.sz | SUCCESS => {
"changed": false,
"containerId": "7f84791d535ea56c1f740c63990af3409e926f7cebed414ea91ae3d644f97374"
}
执行效果
$ docker ps |grep ansible
7f84791d535e nginx:latest "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 80/tcp ansible-nginx-demo2
4c3ad2ea6641 nginx:latest "/docker-entrypoint.…" 7 minutes ago Up 7 minutes 80/tcp ansible-nginx-demo
二、ansible-console
ansible-console 命令在实际工作中用的不多,主要用来操作或命令的快速调试,毕竟 ad-hoc 手敲的话重复内容太多,用 playbook 又多少有些大材小用,下面我们看下具体的使用方法
ansible-console 为交互式命令执行方式,示例如下:
$ ansible-console ecs
Welcome to the ansible console.
Type help or ? to list commands.
root@ecs (2)[f:5]$ list
ecs-1.aliyun.sz
huawei
root@ecs (2)[f:5]$ forks 2
root@ecs (2)[f:2]$ ping
ecs-1.aliyun.sz | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
huawei | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
我们分解下上面 ansible-console 命令交互信息
首先看命令提示符,root@ecs (2)[f:5]:
- (2):括号内的数字为当前分组内的主机数,当前有两台主机
- [f:5]:并发线程数,默认为 5,可以通过 forks 命令修改
顺着往下看,list
命令表示列出当前分组内的主机
root@ecs (2)[f:5]$ list
ecs-1.aliyun.sz
huawei
forks 5
设置并发线程数
root@ecs (2)[f:5]$ forks 2
可以看到执行后,命令提示符中原本的 [f:5]
变为 [f:2]
,接着调用 ping
模块
root@ecs (2)[f:2]$ ping
ecs-1.aliyun.sz | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
huawei | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
调用 debug
模块,并传入 msg
参数及值
root@ecs (2)[f:2]$ debug msg="DaYo"
ecs-1.aliyun.sz | SUCCESS => {
"msg": "DaYo"
}
huawei | SUCCESS => {
"msg": "DaYo"
}
如果不知道如何运行某些模块,可以通过 help
变量获取帮助,例如:
root@ecs (2)[f:2]$ help debug
Print statements during execution
Parameters:
msg The customized message that is printed. If omitted, prints a generic message.
var A variable name to debug.
verbosity A number that controls when the debug is run, if you set to 3 it will only run debug when -vvv or above
三、ansible-doc
非常重要的命令,主要用来插件列表以及模块使用方法,示例如下
查看系统可用插件列表
$ ansible-doc -l
fortios_router_community_list Configure community lists in Fortinet's FortiOS and FortiGate
azure_rm_devtestlab_info Get Azure DevTest Lab facts
ecs_taskdefinition register a task definition in ecs
avi_alertscriptconfig Module for setup of AlertScriptConfig Avi RESTful Object
tower_receive Receive assets from Ansible Tower
netapp_e_iscsi_target NetApp E-Series manage iSCSI target configuration
azure_rm_acs Manage an Azure Container Service(ACS) instance
fortios_log_syslogd2_filter Filters for remote system server in Fortinet's FortiOS and FortiGate
junos_rpc Runs an arbitrary RPC over NetConf on an Juniper JUNOS device
na_elementsw_vlan NetApp Element Software Manage VLAN
pn_ospf CLI command to add/remove ospf protocol to a vRouter
...
# 省略下面内容
查看特定类别插件列表
由于 ansible 内置了非常多的插件,假如我指向看某一类的插件,可以这么做
$ ansible-doc -t become -l
ksu Kerberos substitute user
pbrun PowerBroker run
enable Switch to elevated permissions on a network device
sesu CA Privileged Access Manager
pmrun Privilege Manager run
runas Run As user
sudo Substitute User DO
su Substitute User
doas Do As user
pfexec profile based execution
machinectl Systemd's machinectl privilege escalation
dzdo Centrify's Direct Authorize
查看特定插件使用帮助
OK,这下清爽多了,可是如何使用呢?假如我们此时想要用 sudo 插件,这也不难
$ ansible-doc -t become sudo
执行效果
> SUDO (/usr/lib/python2.7/site-packages/ansible/plugins/become/sudo.py)
This become plugins allows your remote/login user to execute commands as another user via the sudo utility.
* This module is maintained by The Ansible Community
OPTIONS (= is mandatory):
- become_exe
Sudo executable
[Default: sudo]
set_via:
env:
- name: ANSIBLE_BECOME_EXE
- name: ANSIBLE_SUDO_EXE
ini:
- key: become_exe
section: privilege_escalation
- key: executable
section: sudo_become_plugin
vars:
- name: ansible_become_exe
- name: ansible_sudo_exe
- become_flags
Options to pass to sudo
[Default: -H -S -n]
set_via:
env:
- name: ANSIBLE_BECOME_FLAGS
- name: ANSIBLE_SUDO_FLAGS
ini:
- key: become_flags
section: privilege_escalation
- key: flags
section: sudo_become_plugin
vars:
- name: ansible_become_flags
- name: ansible_sudo_flags
- become_pass
Password to pass to sudo
[Default: (null)]
set_via:
env:
- name: ANSIBLE_BECOME_PASS
- name: ANSIBLE_SUDO_PASS
ini:
- key: password
section: sudo_become_plugin
vars:
- name: ansible_become_password
- name: ansible_become_pass
- name: ansible_sudo_pass
- become_user
User you 'become' to execute the task
[Default: root]
set_via:
env:
- name: ANSIBLE_BECOME_USER
- name: ANSIBLE_SUDO_USER
ini:
- key: become_user
section: privilege_escalation
- key: user
section: sudo_become_plugin
vars:
- name: ansible_become_user
- name: ansible_sudo_user
AUTHOR: ansible (@core)
METADATA:
status:
- preview
supported_by: community
我们可以参照着上述帮助信息,使用 sudo 插件
四、ansible-galaxy
Ansible 提供了一个名为 Galaxy 的 Role 分享平台,这个平台提供了包括大量官方、第三方组织、个人提交的扩展模块,ansible-galaxy 正是用于从 https://galaxy.ansible.com/ 站点下载第三方扩展的命令,基本类似于 python 的 pip、centos 的 yum 等
根据关键字搜索角色
虽然 search 可以搜,但还是建议网页端操作,毕竟信息更直观也更方便
$ ansible-galaxy search nginx
Found 1604 roles matching your search. Showing first 1000.
Name Description
---- -----------
0x0i.prometheus Prometheus - a multi-dimensional time-series data monitoring and alerting toolkit
0x5a17ed.ansible_role_netbox Installs and configures NetBox, a DCIM suite, in a production setting.
1davidmichael.ansible-role-nginx Nginx installation for Linux, FreeBSD and OpenBSD.
1it.sudo Ansible role for managing sudoers
1mr.zabbix_host configure host zabbix settings
1nfinitum.php PHP installation role.
2goobers.jellyfin Install Jellyfin on Debian.
2kloc.trellis-monit Install and configure Monit service in Trellis.
...
# 省略后续内容
安装指定角色
todo
创建 role 初始化模版
$ ansible-galaxy init nginx
- Role nginx was created successfully
查看目录结构
$ tree nginx
nginx
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
8 directories, 8 files
获取角色详细信息
$ ansible-galaxy info alexdzyoba.redis
Role: alexdzyoba.redis
description: Provision Redis for Debian as in official Docker image
active: True
commit: ab14252cc472fe433477f70099134e2058d8a2a0
commit_message: Initial
commit_url: https://github.com/alexdzyoba/ansible-redis/commit/ab14252cc472fe433477f70099134e2058d8a2a0
company:
created: 2017-12-02T20:45:35.049885Z
download_count: 68
forks_count: 0
github_branch: master
github_repo: ansible-redis
github_user: alexdzyoba
id: 22537
imported: 2017-12-02T15:45:39.121966-05:00
is_valid: True
issue_tracker_url: https://github.com/alexdzyoba/ansible-redis/issues
license: MIT
min_ansible_version: 2.4.0
modified: 2018-06-30T05:10:25.884196Z
open_issues_count: 0
path: (u'/root/.ansible/roles', u'/usr/share/ansible/roles', u'/etc/ansible/roles')
role_type: ANS
# 省略后续
五、ansible-pull
默认情况下,Ansible 工作方式是 PUSH,在主控端执行命令或剧本时,根据 ansible.cfg
中 forks
参数配置,生成目标主机数量的子进程,并发的向主机连接,执行 Ansible 生成的 Python 脚本,如下所示:
# 以执行剧本为例,剧本内 hosts 包含 3 台目标主机
$ ansible-playbook playbook-filters-demo7.yml
# 观察进程树信息
$ pstree -apsl `ps -ef|grep ansible-playbook|head -n1|awk '{print $2}'`
systemd,1 --switched-root --system --deserialize 22
└─sshd,1068 -D
└─sshd,3014
└─zsh,3017 # 当前 shell 终端
└─ansible-playboo,9734 /usr/bin/ansible-playbook playbook-filters-demo7.yml # 执行剧本父进程
├─ansible-playboo,9744 /usr/bin/ansible-playbook playbook-filters-demo7.yml 、
│ # 子进程执行剧本,并向目标主机发起远程 SSH 连接,执行 Python 脚本
│ └─ssh,9782 -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User="root" -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/1320de7946 -tt sz-aliyun-ecs-1 /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1678103105.08-9744-133134583078117/AnsiballZ_command.py && sleep 0'
├─ansible-playboo,9745 /usr/bin/ansible-playbook playbook-filters-demo7.yml
│ # 子进程执行剧本,并向目标主机发起远程 SSH 连接,执行 Python 脚本
│ └─ssh,9802 -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User="root" -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/65ae441d83 -tt bj-huawei-hecs-1 /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1678103105.16-9745-120153618180627/AnsiballZ_command.py && sleep 0'
├─ansible-playboo,9747 /usr/bin/ansible-playbook playbook-filters-demo7.yml
│ # 子进程执行剧本,并向目标主机发起远程 SSH 连接,执行 Python 脚本
│ └─ssh,9801 -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User="root" -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/17827b13d8 -tt bj-tencent-lhins-1 /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1678103105.16-9747-104995554240311/AnsiballZ_command.py && sleep 0'
└─{ansible-playboo},974
在大规模场景下,PUSH 执行性能会比较,所以 ansible-pull 的作用就体现出来了,它主要有以下特点
- 执行任务、剧本时不通过主控节点,而是通过远程 Git 仓库
- 触发方式通常为 Crontab 或 其他
接下来,看具体示例,创建 ansible-pull-demo 仓库并拉取到本地
$ git clone ansible-pull-demo
常见以下文件,local.yml
文件名是预定义的
$ cat > local.yml << EOF
---
- hosts: all
remote_user: root
gather_facts: no
tasks:
- name: "echo datetime to file"
shell: "echo $(date +'%Y-%m-%d %H:%M:%S') >> /tmp/ansible-pull-demo.txt"
- name: "sleep"
shell: "sleep 120"
EOF
主机清单
$ cat > host << EOF
[local]
127.0.0.1
EOF
提交推送远程
$ git add *
$ git commit -m "ansible pull demo"
$ git push origin master
接下来执行 git-pull
执行前需确保 本机 SSH 公钥已存储至远程 Git 仓库 或 Git 用户设置中
$ ansible-pull -vvvv -U git@e.coding.net:LotusChing/ansible/ansible-pull-demo.git -i hosts
$ pstree -aspl 12308
systemd,1 --system --deserialize 17
└─sshd,1618 -D
└─sshd,9190
└─zsh,9193
└─bash,9291
└─ansible-pull,12308 /usr/bin/ansible-pull -vvvv -U git@e.coding.net:LotusChing/ansible/ansible-pull-demo.git -i hosts
└─ansible-playboo,12367 /usr/bin/ansible-playbook -c local -vvvv /root/.ansible/pull/bj-huawei-hecs-1/local.yml -t all -l localhost,bj-huawei-hecs-1,127.0.0.1 -i hosts
├─ansible-playboo,12407 /usr/bin/ansible-playbook -c local -vvvv /root/.ansible/pull/bj-huawei-hecs-1/local.yml -t all -l localhost,bj-huawei-hecs-1,127.0.0.1 -i hosts
│ └─sh,12421 -c /usr/bin/python /root/.ansible/tmp/ansible-tmp-1678105005.07-12407-197308829181260/AnsiballZ_command.py && sleep 0
│ └─python,12422 /root/.ansible/tmp/ansible-tmp-1678105005.07-12407-197308829181260/AnsiballZ_command.py
│ └─sleep,12423 120
└─{ansible-playboo},12375
总结一下,应用场景很少,大致了解下即可
六、ansible-config
编辑管理 ansible 配置,主要用途有三个,查看配置文件、DUMP 配置、查看所有参数配置
$ ansible-config view
$ ansible-config dump
$ ansible-config list
七、ansible-inventory
查看主机清单信息
以文本图形展示主机(组)列表
$ ansible-inventory --export --graph
@all:
|--@bj_server:
| |--bj-huawei-hecs-1
|--@db_server:
| |--sz-aliyun-ecs-1
|--@dev:
| |--sz-aliyun-ecs-1
|--@ecs:
| |--ecs-1.aliyun.sz
| |--huawei
|--@maintenance:
| |--sz-aliyun-ecs-1
|--@prod:
| |--bj-huawei-hecs-1
|--@sz_server:
| |--sz-aliyun-ecs-1
|--@ungrouped:
|--@web_server:
| |--bj-huawei-hecs-1
展示指定清单文件中的主机列表
$ ansible-inventory -i /etc/ansible/hosts --export --list
{
"_meta": {
"hostvars": {
"ecs-1.aliyun.sz": {
"ansible_host": "sz-aliyun-ecs-1"
},
"huawei": {
"ansible_host": "bj-huawei-hecs-1"
}
}
},
"all": {
"children": [
"bj_server",
"db_server",
"dev",
"ecs",
"maintenance",
"prod",
"sz_server",
"ungrouped",
"web_server"
]
},
"bj_server": {
"hosts": [
"bj-huawei-hecs-1"
]
},
"db_server": {
"hosts": [
"sz-aliyun-ecs-1"
]
},
"dev": {
"hosts": [
"sz-aliyun-ecs-1"
]
},
"ecs": {
"hosts": [
"ecs-1.aliyun.sz",
"huawei"
],
"vars": {
"server_type": "ecs"
}
},
"maintenance": {
"hosts": [
"sz-aliyun-ecs-1"
]
},
"prod": {
"hosts": [
"bj-huawei-hecs-1"
]
},
"sz_server": {
"hosts": [
"sz-aliyun-ecs-1"
]
},
"web_server": {
"hosts": [
"bj-huawei-hecs-1"
]
}
}