通过容器解决 Ansible 版本兼容性问题
Ansible 和 Python 之前存储版本依赖,不同版本的 Python 支持的 Ansible 版本不一样。
Ansible 与 Python 版本不兼容导致执行失败的问题,比方说 Ansible 版本为 3.18.2,被控节点 Python 分别为 3.12.0 和 3.6.8,对 3.6.8 Python 版本的被控节点执行任务时会有 Python 报错导致任务执行失败,为了解决这个问题,Ansible 提出了通过容器执行 Ansible 任务的方式。
Ansible 执行环境演示
假设有两台服务器:
[root@study ansible]# ansible all --list-hosts
hosts (2):
servera
serverb
这两台服务器的 python 版本不一致,其中 servera
为当前 Ansible 控制节点:
[root@study ansible]# ansible --version
ansible [core 2.18.2]
config file = /root/ansible1/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/python/lib/python3.12/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/python/bin/ansible
python version = 3.12.0 (main, Oct 8 2023, 15:41:59) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/usr/local/python/bin/python3.12)
jinja version = 3.1.2
libyaml = True
[root@study ansible]# ssh servera
root@servera's password:
Activate the web console with: systemctl enable --now cockpit.socket
Last login: Sun Feb 9 16:52:29 2025 from 10.0.164.64
[root@localhost ~]# python3 --version
Python 3.6.8
[root@localhost ~]# python
python3 python3.6m python-argcomplete-tcsh
python3.6 python-argcomplete-check-easy-install-script
- Ansible 的 版本为 2.18.2
servera
的 Python 版本为 3.12.0serverb
的 Python 版本为 3.6.8
执行 ansible 命令测试:
[root@study ansible]# ansible servera -m ping
[WARNING]: Platform linux on host servera is using the discovered Python interpreter at /usr/local/python/bin/python3.12, but future installation of another Python
interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-core/2.18/reference_appendices/interpreter_discovery.html for more
information.
servera | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/local/python/bin/python3.12"
},
"changed": false,
"ping": "pong"
}
[root@study ansible]# ansible serverb -m ping
[WARNING]: Unhandled error in Python interpreter discovery for host serverb: Expecting value: line 1 column 1 (char 0)
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: SyntaxError: future feature annotations is not defined
[WARNING]: Platform linux on host serverb is using the discovered Python interpreter at /usr/bin/python3, but future installation of another Python interpreter could
change the meaning of that path. See https://docs.ansible.com/ansible-core/2.18/reference_appendices/interpreter_discovery.html for more information.
serverb | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"module_stderr": "Shared connection to serverb closed.\r\n",
"module_stdout": "Traceback (most recent call last):\r\n File \"/root/.ansible/tmp/ansible-tmp-1739091843.7380877-172497-249791526669330/AnsiballZ_ping.py\", line 107, in <module>\r\n _ansiballz_main()\r\n File \"/root/.ansible/tmp/ansible-tmp-1739091843.7380877-172497-249791526669330/AnsiballZ_ping.py\", line 99, in _ansiballz_main\r\n invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\r\n File \"/root/.ansible/tmp/ansible-tmp-1739091843.7380877-172497-249791526669330/AnsiballZ_ping.py\", line 44, in invoke_module\r\n from ansible.module_utils import basic\r\n File \"<frozen importlib._bootstrap>\", line 971, in _find_and_load\r\n File \"<frozen importlib._bootstrap>\", line 951, in _find_and_load_unlocked\r\n File \"<frozen importlib._bootstrap>\", line 894, in _find_spec\r\n File \"<frozen importlib._bootstrap_external>\", line 1157, in find_spec\r\n File \"<frozen importlib._bootstrap_external>\", line 1131, in _get_spec\r\n File \"<frozen importlib._bootstrap_external>\", line 1112, in _legacy_get_spec\r\n File \"<frozen importlib._bootstrap>\", line 441, in spec_from_loader\r\n File \"<frozen importlib._bootstrap_external>\", line 544, in spec_from_file_location\r\n File \"/tmp/ansible_ping_payload_3__qh04p/ansible_ping_payload.zip/ansible/module_utils/basic.py\", line 5\r\nSyntaxError: future feature annotations is not defined\r\n",
"msg": "MODULE FAILURE: No start of json char found\nSee stdout/stderr for the exact error",
"rc": 1
}
可以看到 serverb
执行失败了,因为 Python 版本太低了。
制作了一个 Ansible 的容器执行环境:
[root@study ansible]# podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/ansible_ee_rocky 8.10 516702fcfaf2 32 minutes ago 364 MB
检查容器执行环境的版本:
[root@study ansible]# ansible-navigator exec -- ansible --version
ansible [core 2.15.13]
config file = /root/ansible/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.9.20 (main, Oct 23 2024, 13:02:27) [GCC 8.5.0 20210514 (Red Hat 8.5.0-22)] (/usr/bin/python3)
jinja version = 3.1.5
libyaml = True
可以看到容器内执行环境的 Ansible 版本为 2.15.13,Python 版本为 3.9.20。
使用容器执行环境执行命令测试:
[root@study ansible]# ansible-navigator exec -- ansible servera -m ping
servera | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
[root@study ansible]# ansible-navigator exec -- ansible serverb -m ping
serverb | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"ping": "pong"
}
可以看到这样能够执行了,所以针对多种系统版本的环境时,可以通过容器执行环境来规避版本兼容性的问题,而且可以针对不同的 Ansible 集合制作容器执行环境,便于不同模式的管理,比方说一个容器执行环境用于系统初始化,安装系统初始化使用的模块,另一个容器执行环境安装 Ldap 集合,用于 Ldap 的管理。
配置 Ansible 容器执行环境
安装 Ansible 容器执行环境
配置 Ansible 执行环境需要安装 ansible-navigator
和容器环境,如果是 RHEL 系统且有 RHEL 订阅,都会有相应的 RPM 包,如果是开源环境,ansible-navigator
可以用 pip
安装,容器环境可以使用 podman
或 docker
。
因为是通过容器执行,所以需要容器镜像,开源的
ansible-navigator
默认使用的镜像是ghcr.io/ansible/community-ansible-dev-tools:latest
。
# 安装 ansible-navigator
[root@ansible-controller ~]# python3 -m pip install ansible-navigator
# 安装容器环境
[root@ansible-controller ~]# dnf install podman
# 测试
[root@ansible-controller ~]# cd ansible-navigator/
[root@ansible-controller ansible-navigator]# ansible-navigator welcome
-------------------------------------------------------------------------------------
Execution environment image and pull policy overview
-------------------------------------------------------------------------------------
Execution environment image name: ghcr.io/ansible/community-ansible-dev-tools:latest
Execution environment image tag: latest
Execution environment pull arguments: None
Execution environment pull policy: tag
Execution environment pull needed: True
-------------------------------------------------------------------------------------
Updating the execution environment
-------------------------------------------------------------------------------------
Running the command: podman pull ghcr.io/ansible/community-ansible-dev-tools:latest
Trying to pull ghcr.io/ansible/community-ansible-dev-tools:latest...
# 执行 ansible-navigator welcome 会出现一个交互页面,页面如下
0│Welcome
1│————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
2│
3│Some things you can try from here:
4│- :collections Explore available collections
5│- :config Explore the current ansible configuration
6│- :doc <plugin> Review documentation for a module or plugin
7│- :help Show the main help page
8│- :images Explore execution environment images
9│- :inventory -i <inventory> Explore an inventory
10│- :log Review the application log
11│- :lint <file or directory> Lint Ansible/YAML files (experimental)
12│- :open Open current page in the editor
13│- :replay Explore a previous run using a playbook artifact
14│- :run <playbook> -i <inventory> Run a playbook in interactive mode
15│- :settings Review the current ansible-navigator settings
16│- :quit Quit the application
17│
18│happy automating,
19│
20│-winston
在上边这个交互页面下,输入 :collections
可以查看可使用的集合(就像 VIM 时输入 :wq
保存文件一样)。
ansible-navigator
更详细的使用后边再写。
通过容器来执行 Ansible 任务
--pp
设置镜像拉取策略--eei
设置使用什么容器镜像
[root@ansible-controller ~]# mkdir ansible-navigator/
[root@ansible-controller ~]# cd ansible-navigator/
[root@ansible-controller ansible-navigator]# ansible-navigator exec --pp missing --eei quay.io/ansible/awx-ee:24.6.1 -- ansible --version
ansible [core 2.15.12]
config file = /root/ansible-navigator/ansible.cfg
configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.9/site-packages/ansible
ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/local/bin/ansible
python version = 3.9.19 (main, Jun 11 2024, 00:00:00) [GCC 11.4.1 20231218 (Red Hat 11.4.1-3)] (/usr/bin/python3)
jinja version = 3.1.4
libyaml = True
# 准备配置文件和主机清单用于测试
[root@ansible-controller ansible-navigator]# cat ansible.cfg
[defaults]
inventory=./inventory
host_key_checking=False
[root@ansible-controller ansible-navigator]# cat inventory
master1 ansible_ssh_host=192.168.221.142 HOSTNAME=master1.example.com
worker1 ansible_ssh_host=192.168.221.143 HOSTNAME=worker1.example.com
[all:vars]
ansible_ssh_user=ansible
ansible_ssh_password=redhat
被控节点系统为 Rocky 8.10,Python 版本为 3.6.8。
ansible-navigator
执行任务有两种方式:
- 通过
exec
选项执行 Ansible Ad-Hoc - 通过
run
选项执行 Ansible Playbook
通过容器执行 Ansible Ad-Hoc
-m stdout
设置输出模式为非交互模式
[root@ansible-controller ansible-navigator]# ansible-navigator exec --pp missing --eei quay.io/ansible/awx-ee:24.6.1 -- 'ansible all -m command -a "python3 --version"'
worker1 | CHANGED | rc=0 >>
Python 3.6.8
master1 | CHANGED | rc=0 >>
Python 3.6.8
通过容器执行 Playbook
-m stdout
设置输出模式为非交互模式
[root@ansible-controller ansible-navigator]# ansible-navigator --pp missing --eei quay.io/ansible/awx-ee:24.6.1 -m stdout run test.yml
PLAY [Ansible-navigator test!] *************************************************
TASK [Gathering Facts] *********************************************************
ok: [master1]
ok: [worker1]
TASK [Print Hello World!] ******************************************************
ok: [master1] => {
"msg": "Hello World!"
}
ok: [worker1] => {
"msg": "Hello World!"
}
PLAY RECAP *********************************************************************
master1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
worker1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0