Braindump

Docker コンテナで Ansible の reboot タスクを成功させる

April 29, 2023
ansible, docker

はじめに #

Ansible の reboot タスクは ansible.builtin.reboot module を用いて以下のように書く。

# playbook.yaml
---
- hosts: all
  gather_facts: false

  tasks:
    - name: This is Reboot task
      ansible.builtin.reboot:

一般的なマシンや VM に対しては上記の記述のみで所望の動作をする。

しかし、Docker コンテナでは Ansible がハングしてしまい次のタスクに進まなくなる。

本記事では Docker コンテナで Ansible の reboot タスクを成功させるための方法を説明する。 必要な設定は以下2ステップである。

  1. Docker の再起動ポリシーを設定する
  2. Ansible の reboot タスクの完了判定を設定する

検証環境 #

  • ansible: 7.1.0
  • Docker version: 23.0.3

Docker の再起動ポリシーを設定する #

コンテナ停止時の挙動は 再起動ポリシー を設定することで制御する。 再起動ポリシーの値は4種類ある (表は ここ から拝借)

フラグ説明
noコンテナを自動的に再起動しません(デフォルトです)。
on-failure終了コードがゼロ以外をエラーとみなし、エラーが発生時にコンテナを再起動します。
alwaysコンテナが停止すると常に再起動します。もしも手動でコンテナを停止した場合、再起動するのはDocker デーモンの再起動時やコンテナ自身を手動で再起動する時です( 再起動ポリシー詳細 の2つめのリストをご覧ください)。
unless-stoppedalways に似ていますが、コンテナの(手動または他の理由による)停止時は除外します。Docker デーモンを再起動しても再起動しません。

上記のとおり、Docker コンテナは停止時や再起動時にデフォルトで自動的に再起動しないようになっている。 つまり、コンテナ内部で reboot を実行するとそのまま停止してしまう。

reboot の挙動を実現するためには再起動ポリシーに always または unless-stopped を設定すれば良い。

コンテナに対する再起動ポリシーの設定方法は2種類ある

  • docker run コマンド実行時に設定する
  • docker-compose.yml で設定する

docker run コマンド実行時に再起動ポリシーを設定 #

docker run コマンド実行時に --restart フラグを使うことで再起動ポリシーを設定できる。

$ docker run -d --restart unless-stopped redis

docker-compose.yaml で再起動ポリシーを設定 #

docker-compose.yaml で再起動ポリシーを設定する場合は restart オプションを使う。

# docker-compose.yaml
services:
  redis:
    image: redis
    restart: unless-stopped

Ansible の reboot タスクの完了判定を設定する #

Docker コンテナで Ansible の reboot タスク を実現するためにはコンテナに対する再起動ポリシーの設定だけでなく、Ansible でも対応が必要。

reboot には ansible.builtin.reboot module (reboot module) を使う。

reboot module では reboot の完了判定に使用するコマンドを boot_time_command で設定している。 このコマンドの出力が reboot の前後で変化していた場合、reboot が完了したとみなし Ansible は次のタスクへと進む。

後述するが、Docker container の場合、デフォルトのコマンドの出力が reboot の前後で変化しない。 つまり、Docker container で reboot タスクを実行すると Ansible は次のタスクに進むことなくハングしてしまう。

この対策として、 boot_time_command をログインする度に値の変わるコマンドで置き換える。 以下の例では、システムに最後にログインしたユーザーの情報を表示する lastlog を reboot の完了判定に使用している。

# playbook.yaml
---
- host: container
  gather_facts: false

  tasks:
    - name: Reboot
      ansible.builtin.reboot:
        boot_time_command: lastlog

なぜ boot_time_command を変更する必要があるのか #

上述したように、 reboot module は reboot の完了判定に使用するコマンドを boot_time_command で定義している。 この出力が reboot の前後で変化していたら reboot タスクが完了したとみなし、次のタスクへと進む。

boot_time_command のデフォルトは cat /proc/sys/kernel/random/boot_id である。 /proc/sys/kernel/random/boot_id にはランダムに生成されたブートIDが保存されている。ブートIDはシステムが起動したときにランダムに生成され、システムが再起動するまで一意に保持される。

docker container の場合は reboot してもブートIDが同じであるため、reboot タスクでハングしてしまう。 そのため、 docker container で reboot タスクを通すためには boot_time_command を変更する必要があった。