用了这么久的Ansible,今天想着研究下Ad-hoc命令的执行流程,从最简单的ping开始吧。
测试命令如下:
ansible 172.18.2.31 -m ping
先看看回显的结果
[root@bigdata-m-002 etc]# ansible 172.18.2.31 -m ping
172.18.2.31 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
内容很少,加上-vvv查看详细的信息:
[root@bigdata-m-002 etc]# ansible 172.18.2.31 -m ping -vvv
Using /etc/ansible/ansible.cfg as config file
host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
META: ran handlers
<172.18.2.31> Attempting python interpreter discovery
<172.18.2.31> ESTABLISH SSH CONNECTION FOR USER: None
<172.18.2.31> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/414e6c60fc 172.18.2.31 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"''
<172.18.2.31> (0, 'PLATFORM\nLinux\nFOUND\n/usr/bin/python\n/usr/bin/python2.7\n/usr/libexec/platform-python\n/usr/bin/python\nENDFOUND\n', '')
<172.18.2.31> ESTABLISH SSH CONNECTION FOR USER: None
<172.18.2.31> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/414e6c60fc 172.18.2.31 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<172.18.2.31> (0, '{"osrelease_content": "NAME=\\"CentOS Linux\\"\\nVERSION=\\"7 (Core)\\"\\nID=\\"centos\\"\\nID_LIKE=\\"rhel fedora\\"\\nVERSION_ID=\\"7\\"\\nPRETTY_NAME=\\"CentOS Linux 7 (Core)\\"\\nANSI_COLOR=\\"0;31\\"\\nCPE_NAME=\\"cpe:/o:centos:centos:7\\"\\nHOME_URL=\\"https://www.centos.org/\\"\\nBUG_REPORT_URL=\\"https://bugs.centos.org/\\"\\n\\nCENTOS_MANTISBT_PROJECT=\\"CentOS-7\\"\\nCENTOS_MANTISBT_PROJECT_VERSION=\\"7\\"\\nREDHAT_SUPPORT_PRODUCT=\\"centos\\"\\nREDHAT_SUPPORT_PRODUCT_VERSION=\\"7\\"\\n\\n", "platform_dist_result": ["centos", "7.8.2003", "Core"]}\n', '')
Using module file /usr/lib/python2.7/site-packages/ansible/modules/system/ping.py
Pipelining is enabled.
<172.18.2.31> ESTABLISH SSH CONNECTION FOR USER: None
<172.18.2.31> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/414e6c60fc 172.18.2.31 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
<172.18.2.31> (0, '\n{"invocation": {"module_args": {"data": "pong"}}, "ping": "pong"}\n', '')
172.18.2.31 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"invocation": {
"module_args": {
"data": "pong"
}
},
"ping": "pong"
}
META: ran handlers
META: ran handlers
上述的命令删除了一部分不必要的内容,主要是了解一下整个执行的过程,首先ansible会读取ansible.cfg作为配置文件,这个是默认的配置文件位置,随后ansible读取主机配置文件获得远程服务器的ip信息以及可能存在的变量信息。
接下来,ansible会尝试搜索远程的python解释器,也就是下面的命令:
SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/414e6c60fc 172.18.2.31 '/bin/sh -c '"'"'echo PLATFORM; uname; echo FOUND; command -v '"'"'"'"'"'"'"'"'/usr/bin/python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python3.5'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.7'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python2.6'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/libexec/platform-python'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'/usr/bin/python3'"'"'"'"'"'"'"'"'; command -v '"'"'"'"'"'"'"'"'python'"'"'"'"'"'"'"'"'; echo ENDFOUND && sleep 0'"'"''
command -v可以搜寻指定命令的绝对路径,此处搜索的命令清单定义在base.yml文件中:
INTERPRETER_PYTHON_FALLBACK:
name: Ordered list of Python interpreters to check for in discovery
default:
- /usr/bin/python
- python3.7
- python3.6
- python3.5
- python2.7
- python2.6
- /usr/libexec/platform-python
- /usr/bin/python3
- python
接下来Ansible会默认以拿到的第一个python路径作为运行远程脚本的解释器,并以此开启交互页,也就是这个命令的作用:
ssh -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/root/.ansible/cp/414e6c60fc 172.18.2.31 '/bin/sh -c '"'"'/usr/bin/python && sleep 0'"'"''
这里并没有打印出具体执行的逻辑,只有一个/usr/bin/python
,实际上这是一个交互窗口,接下来会在这个交互窗口中执行python脚本的逻辑,首先会执行executor/discovery/python_target.py
的逻辑,获取一些操作系统的基本信息:
[root@bigdata-m-002 etc]# python /usr/lib/python2.7/site-packages/ansible/executor/discovery/python_target.py
{"osrelease_content": "NAME=\"CentOS Linux\"\nVERSION=\"7 (Core)\"\nID=\"centos\"\nID_LIKE=\"rhel fedora\"\nVERSION_ID=\"7\"\nPRETTY_NAME=\"CentOS Linux 7 (Core)\"\nANSI_COLOR=\"0;31\"\nCPE_NAME=\"cpe:/o:centos:centos:7\"\nHOME_URL=\"https://www.centos.org/\"\nBUG_REPORT_URL=\"https://bugs.centos.org/\"\n\nCENTOS_MANTISBT_PROJECT=\"CentOS-7\"\nCENTOS_MANTISBT_PROJECT_VERSION=\"7\"\nREDHAT_SUPPORT_PRODUCT=\"centos\"\nREDHAT_SUPPORT_PRODUCT_VERSION=\"7\"\n\n", "platform_dist_result": ["centos", "7.8.2003", "Core"]}
随后就是准备执行模块的业务逻辑了,这里以ping模块为例,在执行具体的模块业务逻辑前,模块依赖的所有脚本文件都会被打包在一个python文件中,类似这样:
[root@bigdata-m-002 ansible-tmp-1699492620.36-27101-107096752350654]# ll
total 112
-rwx------. 1 root root 114480 Nov 9 14:10 AnsiballZ_ping.py
这个脚本的内容会通过定义的标准输入写进交互界面进行执行,如果手动执行就可以看到其回显的结果正是我们执行ansible的时候回显的结果,只不过ansible用自己的回调进行了处理:
[root@bigdata-m-002 ansible-tmp-1699492620.36-27101-107096752350654]# python AnsiballZ_ping.py
{"invocation": {"module_args": {"data": "pong"}}, "ping": "pong"}
[root@bigdata-m-002 ansible-tmp-1699492620.36-27101-107096752350654]#
而这个脚本中有个变量ZIP_DATA,就是把相关依赖文件被转换成了base64加密的一串字符,稍后ansible会将这些字符以文件流的方式再写入zip文件,然后进行解压,从而得到一系列依赖python:
[root@bigdata-m-002 ansible_ping_payload_zNTlb2]# tree
.
├── ansible
│ ├── __init__.py
│ ├── modules
│ │ ├── __init__.py
│ │ └── system
│ │ ├── __init__.py
│ │ └── ping.py
│ └── module_utils
│ ├── basic.py
│ ├── common
│ │ ├── _collections_compat.py
│ │ ├── collections.py
│ │ ├── file.py
│ │ ├── __init__.py
│ │ ├── _json_compat.py
│ │ ├── parameters.py
│ │ ├── process.py
│ │ ├── sys_info.py
│ │ ├── text
│ │ │ ├── converters.py
│ │ │ ├── formatters.py
│ │ │ └── __init__.py
│ │ ├── _utils.py
│ │ └── validation.py
│ ├── compat
│ │ ├── __init__.py
│ │ ├── _selectors2.py
│ │ └── selectors.py
│ ├── distro
│ │ ├── _distro.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── parsing
│ │ ├── convert_bool.py
│ │ └── __init__.py
│ ├── pycompat24.py
│ ├── six
│ │ └── __init__.py
│ └── _text.py
├── ansible_ping_payload.zip
└── sitecustomize.py
如果去看了ping的实现逻辑,我们就知道ansible的ping其实不是发送icmp包实现的,而是尝试在远程服务器上打印data,不过如果你的data给的值是crash,ansible会给你抛出一个boom
的错误:
from ansible.module_utils.basic import AnsibleModule
def main():
module = AnsibleModule(
argument_spec=dict(
data=dict(type='str', default='pong'),
),
supports_check_mode=True
)
if module.params['data'] == 'crash':
raise Exception("boom")
result = dict(
ping=module.params['data'],
)
module.exit_json(**result)
if __name__ == '__main__':
main()
可能这就是开发人员的恶趣味吧~