benmo | 发布于 2018-06-26 17:48:16 | 阅读量 1389 | ? Ansible ?
发布于 2018-06-26 17:48:16 | ? Ansible ?



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)常用的输出关键字

keyvalue说明
changedtrue/false任务执行是否发生变化
failedtrue/false任务是否失败
skippedtrue/false任务是否跳过
rcnumber任务返回码
stdoutstring标准输出
stderrstring错误输出
msgstring一般信息
。。。。。。。。。

注:这里列举了一部分常用的,除了这些,还可以自定义其他任意输出

参考文档:

https://docs.ansible.com/ansible/latest/reference_appendices/common_return_values.html#common-return-values


    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)编不下去了,不想写了。。。

 


内容更新于: 2019-02-14 11:05:55
链接地址: http://blog.leanote.com/post/benmo/%E4%BB%8E0%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8shell%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AAansible%E6%A8%A1%E5%9D%97

上一篇: Ansible如何给集群主机自动分配ID/角色?

下一篇: ansible连接客户端selinux问题

1389 人读过
立即登录, 发表评论.
没有帐号? 立即注册
0 条评论
文档导航