有时我们需要部署无法访问网络的服务器,这时候使用Ansible的command模块执行rpm命令。而使用rpm命令的最大缺点是第2次执行时会报错,因为该模块已被安装。介绍在Ansible上使用rpm命令安装软件包时,多次执行时不重复安装软件包的方法。

目录结构

编写Ansible代码时,基本使用Role进行开发。创建标准Role的目录结构的方法,参照 自动化工具Ansible入门
Role的目录结构如下。

oracle_client/
|-- README.md
|-- defaults
|   `-- main.yml
|-- files
|   |-- libaio-0.3.112-1.el8.x86_64.rpm
|   |-- libnsl-2.28-189.1.el8.x86_64.rpm
|   |-- oracle-instantclient19.18-basic-19.18.0.0.0-1.x86_64.rpm
|   |-- oracle-instantclient19.18-sqlplus-19.18.0.0.0-1.x86_64.rpm
|   `-- oracle-instantclient19.18-tools-19.18.0.0.0-1.x86_64.rpm
|-- handlers
|   `-- main.yml
|-- meta
|   `-- main.yml
|-- tasks
|   `-- main.yml
|-- templates
|-- tests
|   |-- inventory
|   `-- test.yml
`-- vars
    `-- main.ym

实际使用的目录为下面3个。
– defaults/main.yml 文件定义,需要安装的rpm信息
– files目录下保存将要安装的rpm文件
– tasks/main.yml 编写Ansible代码

defaults/main.yml文件

在这里定义需要安装的rpm package一览及oracle_client Role使用的变量。
安装的Oracle Client的版本为19.18(19c),需要安装的rpm package有5个,在这里将oracle_client_packages定义为字典类型。

---
# defaults file for oracle_client
oracle_client_version: "19.18"

oracle_client_packages:
  - package: libaio-0.3.112-1.el8.x86_64
  - package: libnsl-2.28-189.1.el8.x86_64
  - package: oracle-instantclient19.18-basic-19.18.0.0.0-1.x86_64
  - package: oracle-instantclient19.18-sqlplus-19.18.0.0.0-1.x86_64
  - package: oracle-instantclient19.18-tools-19.18.0.0.0-1.x86_64

tasks/main.yml文件

第1步使用 rpm -qa 命令查询服务器上安装rpm package一览,并将结果保存到installed_packages变量。
第2步可使用debug模块为了查看installed_packages的内容,实际运行时可注释掉。
第3步,使用 with_items 读取在 defaults/main.yml 上定义的oracle_client_packages变量。
使用 when 条件判定查看该模块是否已安装。installed_packages.stdout_lines保存着已安装的所有package名,当将要安装的package名不存在(not in)于该一览时,执行rpm命令。

- name: Gather all installed package facts
  command: rpm -qa
  register: installed_packages

- name: debug
  debug:
    var: installed_packages

- name: Install oracle client packages
  command: "rpm -ivh /tmp/{{ item['package'] }}.rpm"
  when: 'item["package"] not in installed_packages.stdout_lines'
  with_items: "{{ oracle_client_packages }}"

执行Ansible playbook(第1次)

未安装Oracle Client的rpm package时执行Ansible Playbook的结果如下。

# ansible-playbook -i hosts/test playbook.yml

PLAY [Setup oracle client server] *******************************************************************************

TASK [oracle_client : Gather all installed package facts] ****************************************************
changed: [XXX.XXX.XXX.XXX]

TASK [oracle_client : debug] *********************************************************************************
ok: [XXX.XXX.XXX.XXX] => {
    "installed_packages": {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/libexec/platform-python"
        },
        "changed": true,
        "cmd": [
            "rpm",
            "-qa"
        ],
        "delta": "0:00:00.701150",
        "end": "2023-01-28 12:53:44.500712",
        "failed": false,
        "msg": "",
        "rc": 0,
        "start": "2023-01-28 12:53:43.799562",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "python3-syspurpose-1.28.29-3.el8.x86_64\ngeolite2-country-20180605-1.el8.noarch\npython3-unbound-1.7.3-17.el8.x86_64~省略~",
        "stdout_lines": [
            "python3-syspurpose-1.28.29-3.el8.x86_64",
            ~省略~
            "polkit-pkla-compat-0.1-12.el8.x86_64"
        ]
    }
}

TASK [oracle_client : Install oracle client packages] ********************************************************
changed: [XXX.XXX.XXX.XXX] => (item={'package': 'libaio-0.3.112-1.el8.x86_64'})
changed: [XXX.XXX.XXX.XXX] => (item={'package': 'libnsl-2.28-189.1.el8.x86_64'})
changed: [XXX.XXX.XXX.XXX] => (item={'package': 'oracle-instantclient19.18-basic-19.18.0.0.0-1.x86_64'})
changed: [XXX.XXX.XXX.XXX] => (item={'package': 'oracle-instantclient19.18-sqlplus-19.18.0.0.0-1.x86_64'})
changed: [XXX.XXX.XXX.XXX] => (item={'package': 'oracle-instantclient19.18-tools-19.18.0.0.0-1.x86_64'})

PLAY RECAP ***************************************************************************************************
XXX.XXX.XXX.XXX             : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

执行结果里需要关注的内容如下。

  • stdout:debug输出的installed_packages内容,已被安装的rpm package一览(没有换行)
  • stdout_lines:debug输出的installed_packages内容,已被安装的rpm package一览(列表),可使用installed_packages.stdout_lines进行访问
  • PLAY RECAP:执行结果概要,
    • ok=3 3个步骤的执行结果为
    • changed=2 rpm -qa命令和rpm -ivh命令成功执行
    • unreachable=0 无法访问的服务器的数量
    • failed=0 执行失败的task
    • skipped=0 未满足条件而被skipped的task数量
    • rescued=0 block模块里可使用rescued和always,一般为0
    • ignored=0 在task里不写ignore_erros=True的话,一般为0

执行Ansible playbook(第2次)

rpm package已安装的情况下,再次执行该Ansible Playbook(debug模块除外)。

# ansible-playbook -i hosts/test playbook.yml

PLAY [Setup oracle client server] *******************************************************************************

TASK [oracle_client : Gather all installed package facts] ****************************************************
changed: [XXX.XXX.XXX.XXX]

TASK [oracle_client : Install oracle client packages] ********************************************************
skipping: [XXX.XXX.XXX.XXX] => (item={'package': 'libaio-0.3.112-1.el8.x86_64'})
skipping: [XXX.XXX.XXX.XXX] => (item={'package': 'libnsl-2.28-189.1.el8.x86_64'})
skipping: [XXX.XXX.XXX.XXX] => (item={'package': 'oracle-instantclient19.18-basic-19.18.0.0.0-1.x86_64'})
skipping: [XXX.XXX.XXX.XXX] => (item={'package': 'oracle-instantclient19.18-sqlplus-19.18.0.0.0-1.x86_64'})
skipping: [XXX.XXX.XXX.XXX] => (item={'package': 'oracle-instantclient19.18-tools-19.18.0.0.0-1.x86_64'})

PLAY RECAP ***************************************************************************************************
XXX.XXX.XXX.XXX             : ok=1    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

可确认到Oracle Client需要的5个package已安装,因此第2次执行Ansible Playbook时rpm package的安装task被skip了。

这么做的好处是,可保持Ansible的幂等性,同样task无论执行多少次都可以得到想要的结果。