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)编不下去了,不想写了。。。
没有帐号? 立即注册