Ansible を使って yum update を実行する

Ansibleコントロールノード を Dockerコンテナ でビルドし、リモートサーバに公開鍵認証でSSH接続してインストール済みのパッケージの一覧を取得するまで の続きです。

手動でサーバに接続して yum update するのは面倒なので Ansible の力を借りようと思った次第です。

試験環境については上述記事を参照。

設定

ディレクトリ階層

PROJECT_ROOT/
  ├ workspace/                            // データ永続化領域
  │    ├ entrypoint.sh                    // Dockerコンテナ起動時に実行するシェルスクリプト
  │    └ ansible/                         // Ansible 用ディレクトリ
  │         ├ targets/                    // リモートホストの情報を収めるディレクトリ
  │         │    └ hosts                  // リモートサーバの一覧 (今回は1つのみ)
  │         │
  │         ├ tasks/                      // Ansible のタスクを収めるディレクトリ
  │         │    ├ get_packages.yml       // リモートサーバのパッケージとバージョンの一覧を取得
  │         │    ├ output_packages.yml    // get_packages.yml で取得したパッケージの一覧を package_list.j2 を使って出力
  │         │    └ yum_update.yml         // yum update を実行
  │         │
  │         ├ templates/                  // 出力用テンプレートを収めるディレクトリ
  │         │    └ package_list.j2        // パッケージとバージョンの一覧のリストのテンプレート
  │         │
  │         └ main.yml                    // Ansible の playbook
  │
  ├ docker-compose.yml      // Docker Compose 設定ファイル
  └ Dockerfile              // Dockerfile

前回の記事の内容をベースに修正。 Ansible のタスクが多くなったのでタスクごとにファイルを分割したこと、それに併せて Ansible 関係のファイルを整理するためにディレクトリを切りました。

前回同様一揃いを Github に置きました。

Dockerfile, docker-compose.yml, entrypoint.sh

これらは前回のままです。

Ansible

今回の主眼はココ。

全体を通してやっている内容は以下の通りとなります。

  1. 自PC内の Dockerコンテナ から Ansible 実行
  2. XXX.XXX.XXX.XXX にSSH接続
  3. XXX.XXX.XXX.XXX に現時点でインストールされているパッケージ一覧を取得、 データ永続化領域 にファイルとして書き出し
  4. XXX.XXX.XXX.XXXyum update を実行
  5. 3.と同様 XXX.XXX.XXX.XXX にインストールされているパッケージ一覧の取得・ファイル書き出し (yum update でアップデートが実行されたのでパッケージの内容やバージョンが3.とは差分が生じるはず)

main.yml

- name: Get packages from hosts and do yum update

  become: yes
  become_user: ADMIN_USER
  become_method: su

  hosts:
    - update_servers

  tasks:
    - name: Get packages from hosts before update
      include_tasks: ./tasks/get_packages.yml
    - name: Output packages from hosts before update
      include_tasks: ./tasks/output_packages.yml
      vars:
        flag: before
    - name: Do yum update
      include_tasks: ./tasks/yum_update.yml
    - name: Get packages from hosts after update
      include_tasks: ./tasks/get_packages.yml
    - name: Output packages from hosts after update
      include_tasks: ./tasks/output_packages.yml
      vars:
        flag: after
  • タスクを各ファイルに分割
    • vars: flag./tasks/output_packages.yml で出力されるファイル名を変更しています
    • yum update 前は XXX.XXX.XXX.XXX_packages_before, yum update 後は XXX.XXX.XXX.XXX_packages_after となります

get_packages.yml

- name: Get packages
  package_facts:
    manager: auto
  become: true

極めてシンプル。ちなみに分割したタスクの yml ファイルの中で tasks: は使えなさそう(確かに分割しているのにさらに読み込めたら依存関係の解決が面倒なことになりそう)ので前回はパッケージ一覧取得→ファイルに書き出しが1つの yml ファイルに記述されていましたが今回は分割しました。

output_packages.yml

- name: Output packages
  template:
    src: ../templates/package_list.j2
    dest: "/workspace/{{ inventory_hostname }}_packages_{{ flag }}"
    mode: '0644'
  delegate_to: localhost

上述の yml で取得したパッケージの一覧を ../templates/package_list.j2 のテンプレートファイルを使ってファイルに書き出し。

{{ flag }}main.yml で既述した変数が渡ってきて、ファイル名が変更されるというわけですね。

yum_update.yml

- name: Yum update at hosts
  yum:
    name: '*'
    state: latest
  async: 1000
  poll: 1
  register: yum_sleeper

- name: Yum check on fire and forget task
  async_status:
    jid: "{{ yum_sleeper.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 30
  delay: 10

本題の yum update を実行する部分。

ちなみに Ansible のSSH接続はデフォルトだと5分で切断されてしまうようで、アップデート対象のパッケージが大量にあって yum update の実行に時間がかかると、途中で切断されてしまうので注意。

今回それに気付くまでに何回か失敗していましたが、パッケージチェック中の切断などタイミング的に良かったのか、幸運なことに特に支障は起きませんでした。

不幸にも切断されてしまうと不整合パッケージのチェックが終わらずどうにもならない、なんていうケースも見受けられたので注意。

そのため、自動切断を回避するために async, poll 指定、 async_status モジュールの使用など細工を色々と加えています。

hosts

[update_servers]
XXX.XXX.XXX.XXX

ここは前回と変わらずグループとIPアドレスの指定を記述。

package_list.j2

# {{ inventory_hostname }}
{% if ansible_facts.os_family == 'RedHat' %}
{%   for package_name in ansible_facts.packages.keys()|sort %}
{%     for package in ansible_facts.packages[package_name] %}
{{       package['name'] }}-{{package['version']}}-{{package['release']}}.{{package['arch']}}
{%     endfor %}
{%   endfor %}
{% elif ansible_facts.os_family == 'Debian' %}
{%   for package_name in ansible_facts.packages.keys()|sort %}
{%     for package in ansible_facts.packages[package_name] %}
{{       [package['name'], package['version']] | join('_') }}
{%     endfor %}
{%   endfor %}
{% endif %}

こちらも前回と変わらず。

動作確認

# ansible-playbook -i /workspace/ansible/targets/hosts /workspace/ansible/main.yml -u SSH_REMOTEUSER --private-key="/root/.ssh/PRIVATE_KEY" -K
BECOME password:

PLAY [Get packages from hosts and do yum update] *******************************************************************

TASK [Gathering Facts] *********************************************************************************************
ok: [XXX.XXX.XXX.XXX]

TASK [Get packages from hosts before update] ***********************************************************************
included: /workspace/ansible/tasks/get_packages.yml for XXX.XXX.XXX.XXX

TASK [Get packages] ************************************************************************************************
ok: [XXX.XXX.XXX.XXX]

TASK [Output packages from hosts before update] ********************************************************************
included: /workspace/ansible/tasks/output_packages.yml for XXX.XXX.XXX.XXX

TASK [Output packages] *********************************************************************************************
changed: [XXX.XXX.XXX.XXX]

TASK [Do yum update] ***********************************************************************************************
included: /workspace/ansible/tasks/yum_update.yml for XXX.XXX.XXX.XXX

TASK [Yum update at hosts] *****************************************************************************************
changed: [XXX.XXX.XXX.XXX]

TASK [Yum check on fire and forget task] ***************************************************************************
changed: [XXX.XXX.XXX.XXX]

TASK [Get packages from hosts after update] ************************************************************************
included: /workspace/ansible/tasks/get_packages.yml for XXX.XXX.XXX.XXX

TASK [Get packages] ************************************************************************************************
ok: [XXX.XXX.XXX.XXX]

TASK [Output packages from hosts after update] *********************************************************************
included: /workspace/ansible/tasks/output_packages.yml for XXX.XXX.XXX.XXX

TASK [Output packages] *********************************************************************************************
changed: [XXX.XXX.XXX.XXX]

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


#

OK、上手く行きました。

失敗時: UNREACHABLE! => {“changed”: false, “msg”: “Failed to connect to the host via ssh: Shared connection to XXX.XXX.XXX.XXX closed.”, “unreachable”: true}

- name: Yum update at hosts
  yum:
    name: '*'
    state: latest

上述の通り、 yum update で時間がかかった場合、 yum_update.yml がシンプルな内容だと途中でSSH接続が切れてしまいます。

# ansible-playbook -i /workspace/ansible/targets/hosts /workspace/ansible/main.yml -u SSH_REMOTEUSER --private-key="/root/.ssh/PRIVATE_KEY" -K
BECOME password:

PLAY [Get packages from hosts and do yum update] *******************************************************************

TASK [Gathering Facts] *********************************************************************************************
ok: [XXX.XXX.XXX.XXX]

TASK [Get packages from hosts before update] ***********************************************************************
included: /workspace/ansible/tasks/get_packages.yml for XXX.XXX.XXX.XXX

TASK [Get packages] ************************************************************************************************
ok: [XXX.XXX.XXX.XXX]

TASK [Output packages from hosts before update] ********************************************************************
included: /workspace/ansible/tasks/output_packages.yml for XXX.XXX.XXX.XXX

TASK [Output packages] *********************************************************************************************
ok: [XXX.XXX.XXX.XXX]

TASK [Do yum update] ***********************************************************************************************
included: /workspace/ansible/tasks/yum_update.yml for XXX.XXX.XXX.XXX

TASK [Yum update at hosts] *****************************************************************************************
fatal: [XXX.XXX.XXX.XXX]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Shared connection to XXX.XXX.XXX.XXX closed.", "unreachable": true}

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

fatal: [XXX.XXX.XXX.XXX]: UNREACHABLE! => {“changed”: false, “msg”: “Failed to connect to the host via ssh: Shared connection to XXX.XXX.XXX.XXX closed.”, “unreachable”: true}

結果、上のようなエラーで失敗してしまいます。

対処については上述の通り、 yum_update.yml に細工を施します。

結果

XXX.XXX.XXX.XXX_packages_before

# XXX.XXX.XXX.XXX
acl-2.2.51-15.el7.x86_64
acl-2.2.51-14.el7.x86_64
aic94xx-firmware-30-6.el7.noarch
alsa-firmware-1.0.28-2.el7.noarch
alsa-lib-1.1.6-2.el7.x86_64
alsa-tools-firmware-1.1.0-1.el7.x86_64
apr-1.4.8-3.el7_4.1.x86_64
apr-1.4.8-5.el7.x86_64

## 略

vsftpd-3.0.2-25.el7.x86_64
webmin-1.900-1.noarch
which-2.20-7.el7.x86_64
wpa_supplicant-2.6-12.el7.x86_64
xfsprogs-4.5.0-18.el7.x86_64
xmlsec1-1.2.20-7.el7_4.x86_64
xmlsec1-openssl-1.2.20-7.el7_4.x86_64
xz-5.2.2-1.el7.x86_64
xz-devel-5.2.2-1.el7.x86_64
xz-libs-5.2.2-1.el7.x86_64
yum-3.4.3-167.el7.centos.noarch
yum-3.4.3-161.el7.centos.noarch
yum-metadata-parser-1.1.4-10.el7.x86_64
yum-plugin-fastestmirror-1.1.31-50.el7.noarch
yum-plugin-fastestmirror-1.1.31-54.el7_8.noarch
yum-utils-1.1.31-54.el7_8.noarch
zlib-1.2.7-18.el7.x86_64
zlib-devel-1.2.7-18.el7.x86_64

XXX.XXX.XXX.XXX_packages_after

# XXX.XXX.XXX.XXX
acl-2.2.51-15.el7.x86_64
acl-2.2.51-14.el7.x86_64
aic94xx-firmware-30-6.el7.noarch
alsa-firmware-1.0.28-2.el7.noarch
alsa-lib-1.1.8-1.el7.x86_64
alsa-tools-firmware-1.1.0-1.el7.x86_64
apr-1.4.8-3.el7_4.1.x86_64
apr-1.4.8-5.el7.x86_64

## 略

vsftpd-3.0.2-27.el7.x86_64
webmin-1.900-1.noarch
which-2.20-7.el7.x86_64
wpa_supplicant-2.6-12.el7.x86_64
xfsprogs-4.5.0-20.el7.x86_64
xmlsec1-1.2.20-7.el7_4.x86_64
xmlsec1-openssl-1.2.20-7.el7_4.x86_64
xz-5.2.2-1.el7.x86_64
xz-devel-5.2.2-1.el7.x86_64
xz-libs-5.2.2-1.el7.x86_64
yum-3.4.3-167.el7.centos.noarch
yum-3.4.3-161.el7.centos.noarch
yum-metadata-parser-1.1.4-10.el7.x86_64
yum-plugin-fastestmirror-1.1.31-50.el7.noarch
yum-plugin-fastestmirror-1.1.31-54.el7_8.noarch
yum-utils-1.1.31-54.el7_8.noarch
zlib-1.2.7-18.el7.x86_64
zlib-devel-1.2.7-18.el7.x86_64

かなり省略していますが、それでも alsa-lib, vsftpd, xfsprogs がアップデートされたことが確認できます。

参考

yum update

タスクの分割

(最終的に未使用) ansible.cfg のパラメータ

yum update 中のタイムアウトを防ぐ

(今回は reboot までしていないので未使用) Ansible で reboot


将来的には Raspberry Pi を自動で焼いていきたいですね。

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。