Author: 本末丶 Ansible 交流群:372011984
一、前言
Ansibe是一个十分强大灵活的配置管理工具,有灵活多变(狗日)的API,又有各种自定义 module、action/callback plugin来编写自己需要的功能,但是对于一些不熟悉python的运维童鞋,可能就不太友好,不过,我这里要说的是,其实ansible的自定义模块是支持各种(解释型)语言的(狗日的官网信息真的少),这里我们看下如何从0开始使用shell编写一个Ansible 模块。
二、从原理出发
在编写模块之前,我们得先看下自定义模块的执行过程:
执行自定义模块-->ansible push模块(脚本)到远程服务器执行-->获取返回json(没错,就是这么简单,如果开启了pipelining会从管道中读取,而不是下发文件)
这么说可能很迷茫,那么我们看看它的一个执行过程就了然了:
1)开启自定义模块配置
# 首先要配置下自定义模块路径 vim /etc/ansible/ansible.cfg --- # 改这个路径到自己自定义模块路径,随便哪里 library = /etc/ansible/my_modules/
# 看下生效没(‘configured module search path’字段后的路径) # ansible --version ansible 2.5.5 config file = /etc/ansible/ansible.cfg configured module search path = ['/etc/ansible/my_modules'] ansible python module location = /usr/local/lib/python3.5/dist-packages/ansible executable location = /usr/local/bin/ansible python version = 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609]
2)创建一个空的模块
# 创建一个空的模块(脚本)文件(没错,就是空的) cd /etc/ansible/my_modules/ touch what.sh
3)探究模块执行过程
# ansible localhost -m what -a "data=123456" -vvv
ansible 2.5.5
config file = /etc/ansible/ansible.cfg
configured module search path = ['/etc/ansible/my_modules']
ansible python module location = /usr/local/lib/python3.5/dist-packages/ansible
executable location = /usr/local/bin/ansible
python version = 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609]
Using /etc/ansible/ansible.cfg as config file
Parsed /etc/ansible/hosts inventory source with ini plugin
META: ran handlers
Using module file /etc/ansible/my_modules/what.sh
localhost | FAILED! => {
"msg": "module (what) is missing interpreter line"
} 额?好像什么有用的信息都没有?嗯,没关系,我们在改下what.sh,添加一个头部的解析"#!/bin/bash",再执行看看:
# echo '#!/bin/bash' > what.sh
# ansible localhost -m what -a "data=123456" -vvv
ansible 2.5.5
config file = /etc/ansible/ansible.cfg
configured module search path = ['/etc/ansible/my_modules']
ansible python module location = /usr/local/lib/python3.5/dist-packages/ansible
executable location = /usr/local/bin/ansible
python version = 3.5.2 (default, Nov 23 2017, 16:37:01) [GCC 5.4.0 20160609]
Using /etc/ansible/ansible.cfg as config file
Parsed /etc/ansible/hosts inventory source with ini plugin
META: ran handlers
# 嗯哼,找的自定义的what.sh
Using module file /etc/ansible/my_modules/what.sh
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: root
<127.0.0.1> EXEC /bin/sh -c 'echo ~root && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /root/.ansible/tmp/
ansible-tmp-1529998837.5596042-3204284628154 `" && echo ansible-tmp-1529998837
.5596042-3204284628154="` echo /root/.ansible/tmp/ansible-tmp-1529998837.
5596042-3204284628154 `" ) && sleep 0'
# put的我们自定义的脚本到客户端
<127.0.0.1> PUT /root/.ansible/tmp/ansible-local-224268at5fv3k/tmp__7qj1bj TO
/root/.ansible/tmp/ansible-tmp-1529998837.5596042-3204284628154/what.sh
# 还上传了一个args?一会我们看看是什么
<127.0.0.1> PUT /root/.ansible/tmp/ansible-local-224268at5fv3k/tmpfd8vbmga TO
/root/.ansible/tmp/ansible-tmp-1529998837.5596042-3204284628154/args
# 这里可以看到给了what.sh跟args2个文件执行权限
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-
1529998837.5596042-3204284628154/ /root/.ansible/tmp/ansible-tmp-
1529998837.5596042-3204284628154/what.sh /root/.ansible/tmp/ansible-tmp-
1529998837.5596042-3204284628154/args && sleep 0'
# 执行了what.sh,后面还带了args文件作为位置参数
<127.0.0.1> EXEC /bin/sh -c '/bin/bash /root/.ansible/tmp/ansible-tmp-1529998837
.5596042-3204284628154/what.sh /root/.ansible/tmp/ansible-tmp-1529998837.
5596042-3204284628154/args && sleep 0'
# 执行完成后,删除了远程的临时目录
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /root/.ansible/tmp/ansible-tmp-1529998837
.5596042-3204284628154/ > /dev/null 2>&1 && sleep 0'
# 最终的结果,由于脚本里没有定义输出,所以这里异常了
localhost | FAILED! => {
"changed": false,
"module_stderr": "",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 0
} 附:理论上,只要是解释型语言,在头部声明脚本的解释程序都能编写ansible模块
接下来,我们看下args文件是什么?
# $1就是args文件,作为what.sh的位置参数传入,我们直接在what中看args里有啥
# echo ‘cat $1’ >> what.sh
# ansible localhost -m what -a "data=123456"
localhost | FAILED! => {
"changed": false,
"module_stderr": "",
"module_stdout": "_ansible_check_mode=False _ansible_selinux_special_fs='
['\"'\"'fuse'\"'\"', '\"'\"'nfs'\"'\"', '\"'\"'vboxsf'\"'\"',
'\"'\"'ramfs'\"'\"', '\"'\"'9p'\"'\"']' data=123456 _ansible_no_log=
False _ansible_version=2.5.5 _ansible_module_name=what _ansible_diff=False
_ansible_shell_executable=/bin/sh _ansible_verbosity=0
_ansible_syslog_facility=LOG_USER _ansible_debug=False _ansible_socket=None
_ansible_tmpdir=None ",
"msg": "MODULE FAILURE",
"rc": 0
} 从module_stdout中的内容,我们可以看到是一些ansible的内置参数,而且,还有一个参数: data=123456,咦!这个不就是我们命令行传入的参数么?
我想,到这里各位看官不难理解了,这个args文件就是包含ansible内置参数与模块传参,我们只要在自定义模块里source这个文件,就能引用、判断、约束传入的参数了!
三、定义模块返回
1)常用的输出关键字
| key | value | 说明 |
| changed | true/false | 任务执行是否发生变化 |
| failed | true/false | 任务是否失败 |
| skipped | true/false | 任务是否跳过 |
| rc | number | 任务返回码 |
| stdout | string | 标准输出 |
| stderr | string | 错误输出 |
| msg | string | 一般信息 |
| 。。。 | 。。。 | 。。。 |
注:这里列举了一部分常用的,除了这些,还可以自定义其他任意输出
参考文档:
2)模块输出测试
# cat what.sh
#!/bin/bash
source $1
# 返回的一定要是个json
# 可以借助jq这个工具验证输出的json是否正常
# 安装:yum -y install epel-release && yum -y install jq
# 例: bash what.sh | jq .
cat << EOF
{
"changed": true,
"failed": false,
"rc": "$?",
"msg": "${msg}",
"stdout": "Bash module testing!"
}
EOF 执行自定义模块,并传入一个msg的自定义参数:
# ansible localhost -m what -a "msg='F**k Ansible api'"
localhost | SUCCESS => {
"changed": true,
"msg": "F**k Ansible api",
"rc": 0,
"stdout": "Bash module testing!",
"stdout_lines": [
"Bash module testing!"
]
}
四、编写一个模块
1)编写一个添加/etc/hosts本地解析的模块
# cat add_etc_hosts.sh
---
#!/bin/bash
# 导入变量文件
source $1
# 定义一个全局的输出格式
_stdout() {
local changed=$1
local failed=$2
local rc=$3
local msg=$4
cat << EOF
{
"changed": ${changed},
"failed": ${failed},
"rc": ${rc},
"msg": "${msg}"
}
EOF
}
# 检查传参,没有就报异常
_check_args() {
if [[ x"$host" == x ]];then
_stdout false true 1 "Missing args host"
exit 1
fi
if [[ x"$domain" == x ]];then
_stdout false true 1 "Missing args domain"
exit 1
fi
}
# 检查是否已经存在行,并给出返回码
_check_line() {
grep -Eo "$host\s+$domain" /etc/hosts >/dev/null
return $?
}
# 开始添加行
add_line() {
_check_args
_check_line
res=$?
# 为了幂等,已存在的行不再添加
if [[ $res -eq 1 ]];then
echo "$host $domain" >> /etc/hosts
_stdout true false 0 "Add $host $domain"
elif [[ $res -eq 0 ]];then
_stdout false false 0 "$host $domain existing!"
fi
}
# 执行函数
add_line 附:当然你还可以写的更复杂些,比如添加一个state参数,控制添加还是删除,比如添加一个path指向hosts路径,等等...
2)执行自定义模块
①.测试参数有效性
# ansible localhost -m add_etc_hosts
localhost | FAILED! => {
"changed": false,
"msg": "Missing args host",
"rc": 1
}
# ansible localhost -m add_etc_hosts -a "host=10.10.1.1"
localhost | FAILED! => {
"changed": false,
"msg": "Missing args domain",
"rc": 1
}②.测试模块有效性与幂等
# ansible node04 -m add_etc_hosts -a "host=10.10.1.1 domain=www.abc.com"
localhost | SUCCESS => {
"changed": true,
"msg": "Add 10.10.1.1 www.abc.com",
"rc": 0
}
# 再跑一次,changed为false,代表任务状态没有发生变更
# ansible localhost -m add_etc_hosts -a "host=10.10.1.1 domain=www.abc.com"
localhost | SUCCESS => {
"changed": false,
"msg": "10.10.1.1 www.abc.com existing!",
"rc": 0
}
# 查看一下,只添加了一行
# cat /etc/hosts
127.0.0.1 localhost
127.0.0.1 pengjk-Lenovo-G480
127.0.0.1 Mint-G480
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.10.1.1 www.abc.com
3)编不下去了,不想写了。。。
benmo
没有帐号? 立即注册