RHEL 実践 #5 Ansible で RHEL を自動化: RHCE トラックへつなぐ
RHEL 実践トラック の #1〜#4 で、nginx Web サーバ、PostgreSQL、Podman コンテナ、モニタリングを手作業で立ち上げました。1 台をそうやって扱う作業は十分に身につけました。ところが同じ構成を 2 台目、3 台目のサーバに載せる段になると話が変わります。同じ dnf コマンド、同じ systemctl、同じ firewalld ルール、同じ SELinux boolean を手で打ち直していると、必ず 1 つ 2 つの手順が抜け落ちます。この記事ではその手作業を Ansible に移し、同じ結果をコード一式で再現する 全体像を整理します。
この記事の目的は Ansible の文法を深く掘ることではありません。「なぜ自動化へ移るのか」、そして RHEL の実務で身につけた手作業がどうやって playbook 一行に変わるかを見せることです。本格的な Ansible の文法と RHCE 試験範囲は RHCE シリーズ で別途扱います。
手作業の限界 #
#1 で nginx を立ち上げた手順を思い出してみます。パッケージをインストールし、サービスを enable にし、firewalld で http・https を開け、非標準ポートを使うなら SELinux port ラベルを登録しました。1 台なら 5 分で終わります。ところがこの作業には 3 つの弱点があります。
1 つ目は 再現が難しい ことです。2 台目で firewalld の --reload を抜かしたり、setsebool に -P を付け忘れたりすると、そのサーバだけ微妙に違う動きをします。2 つ目は 記録が残らない ことです。半年後に「このサーバはどの boolean を入れたっけ」と思い出すには、コマンド履歴を漁るしかありません。3 つ目は 検証する手段がない ことです。今の状態が意図した状態と同じか確かめるには、結局手で 1 つずつ確認することになります。
Ansible はこの 3 つを一度に解決します。手順ではなく 望む状態をコードで書いておけば、Ansible が現在の状態と比べて足りない部分だけを揃えます。コードそのものが記録であり、同じコードを何度実行しても結果は変わりません。
Ansible のインストールと最小構成 #
RHEL では Ansible は制御ノード (コマンドを出す側) にだけインストールします。対象サーバには Python と SSH さえあればよく、別途エージェントを入れない点が Ansible の大きな利点です。
# 制御ノードに ansible-core をインストール
sudo dnf install -y ansible-core
# バージョン確認
ansible --versionansible-core はエンジンと基本モジュールだけを収めた軽量パッケージです。より多くのコレクションが束ねられた ansible パッケージもありますが、RHEL の作業では ansible-core に必要なコレクションを別途足す方式のほうがすっきりします。次に、作業ディレクトリに inventory と ansible.cfg を置きます。
# inventory: 管理するサーバ一覧
[web]
web1.example.com
web2.example.com
[db]
db1.example.com# ansible.cfg: プロジェクトのデフォルト値
[defaults]
inventory = ./inventory
remote_user = ansible
host_key_checking = False
[privilege_escalation]
become = True
become_method = sudoinventory は管理対象のサーバをグループにまとめたファイルで、ansible.cfg は毎回オプションを付けずに済むようデフォルト値を集めたファイルです。become = True は対象ホストで sudo を使って権限を昇格させるという意味なので、RHEL でパッケージインストールやサービス制御のような作業には必須です。接続できるかは一行で確認します。
# すべてのホストに ping (SSH・Python 接続点検)
ansible all -m pingping モジュールは ICMP ではなく SSH で接続して Python が動くかを確認します。ここで pong が返ってくれば playbook を回す準備が整っています。
冪等性: 同じ結果を保証する核 #
Ansible を理解するうえで最も重要な概念が 冪等性 です。同じ playbook を何度回しても、結果が一度回したものと同じになるという性質です。手で dnf install を 2 回打つと 2 回目は「すでにインストール済み」と出ますが、スクリプトで闇雲にコマンドを並べると、2 回目の実行で意図しない副作用が生じやすくなります。
Ansible モジュールはコマンドではなく 状態 を扱います。state: present と書けば「このパッケージがあるべきだ」という意味であって「今インストールしろ」ではありません。すでにあれば何もせず ok で通り、ないときだけインストールして changed で報告します。だから初回の実行では複数の項目が changed で出ますが、すぐに再度回せばすべて ok になります。この「2 回目の実行で全部 ok」が、冪等性が守られたという合図です。
この性質のおかげで、playbook は「一度きりのインストールスクリプト」ではなく「いつ回してもサーバを意図した状態に揃える定義書」になります。サーバが何かの拍子にずれても、同じ playbook を再度回せば元の位置に戻ります。
手作業を playbook へ #
では #1 の nginx の作業を playbook 1 つに移してみます。パッケージインストール、サービス enable、firewalld の開放、SELinux boolean まで、手でやった 4 つを 1 ファイルに収めます。
# web.yml: nginx 1 サイクル
- name: Web サーバ構成
hosts: web
become: true
tasks:
- name: nginx インストール
ansible.builtin.dnf:
name: nginx
state: present
- name: nginx サービス enable + start
ansible.builtin.systemd:
name: nginx
enabled: true
state: started
- name: firewalld で http・https を許可
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
immediate: true
state: enabled
loop:
- http
- https
- name: reverse proxy 用 SELinux boolean
ansible.posix.seboolean:
name: httpd_can_network_connect
state: true
persistent: true手で打ったコマンドと一行ずつ並べてみると、対応がくっきりします。dnf install -y nginx は dnf モジュールの state: present へ、systemctl enable --now nginx は systemd モジュールの enabled: true + state: started へ、firewall-cmd --add-service ... --permanent + --reload は firewalld モジュールの permanent: true + immediate: true へ、setsebool -P は seboolean モジュールの persistent: true へ移っています。#1 で手でやった作業がそのままコードになったわけです。
実行は一行です。
# 実際に適用する前に、変更予定だけをプレビュー
ansible-playbook web.yml --check
# 実際に適用
ansible-playbook web.yml--check は実際には変えず、何が変わるかだけを見せるモードです。本番サーバに適用する前にこのモードで一度回して影響範囲を確認する習慣が、事故を減らします。inventory の [web] グループに web1、web2 が入っているので、この playbook 一度で 2 台のサーバが同じように構成されます。サーバが 10 台に増えても同じコマンド一行です。手作業の 3 つの弱点がここですべて消えます。
rhel-system-roles で抽象化 #
上の playbook は firewalld と SELinux をモジュールで直接扱いました。Red Hat はここからもう一段抽象化した rhel-system-roles を公式に提供しています。ファイアウォール、SELinux、時刻同期、ストレージといった RHEL の運用領域を、あらかじめ検証された role に束ね、モジュール呼び出しの代わりに「望む結果」だけを変数で書けばよいようにしたものです。
# システム role コレクションをインストール
sudo dnf install -y rhel-system-rolesたとえば時刻同期は、timesync role に NTP サーバだけ渡せば chrony 設定とサービスまで自動で揃えます。
# timesync.yml: 時刻同期 role
- name: 時刻同期構成
hosts: all
become: true
roles:
- role: rhel-system-roles.timesync
vars:
timesync_ntp_servers:
- hostname: 0.jp.pool.ntp.org
- hostname: 1.jp.pool.ntp.org同じやり方で、selinux role は boolean・port・fcontext を変数で受け取って #1 で手でやった semanage・setsebool の作業を代行し、firewall role はサービス・ポートの開放を変数で受け取ります。モジュールを直接使うより抽象化のレベルが高いので、RHEL 標準構成に近い作業は system-roles で、細かい制御が必要な作業はモジュールで分けて使うのが現実的な運用です。system-roles は Red Hat が RHEL バージョンに合わせて保守しているため、自分でモジュールを組むときよりも OS アップグレードに伴う破損が少なくなります。
運用ポイント #
自動化へ移るとき、実務で押さえる点を整理します。
- 手作業を先に理解してから自動化します。#1〜#4 で手で詰まってみた経験があってこそ、playbook が何をするかが読めます。手作業を飛ばして playbook から写すと、詰まったとき手が出せません。
--checkで先に回します。本番サーバにいきなり適用せず、変更予定を確認する段階を踏むと大きな事故を防げます。- 冪等性を確認します。playbook を 2 回回して、2 回目が全部
okで終わるかを見ます。2 回目にもchangedが出続けるなら、その task は冪等に組まれていないので直す必要があります。 - 標準は system-roles、細かいものはモジュールで分けます。RHEL 標準構成に近い作業は検証済みの role を使い、特殊な要件だけモジュールで直接制御します。
- playbook をバージョン管理に置きます。inventory と playbook を Git に入れれば、コードがそのままサーバ構成の記録になります。
まとめ #
この記事で押さえたこと:
- なぜ自動化か。手作業の弱点は再現の難しさ、記録の不在、検証の不能です。Ansible は状態をコードで書いてこの 3 つを一度に解決します。
- 最小構成。制御ノードに
ansible-coreだけインストールし、inventory で対象をまとめ、ansible.cfg にデフォルト値を置きます。対象にはエージェントがありません。 - 冪等性。モジュールはコマンドではなく状態を扱うので、何度回しても結果が同じです。2 回目の実行が全部
okかどうかで確認します。 - 手作業の移行。#1 の nginx 4 段階が
dnf・systemd・firewalld・sebooleanモジュールの playbook 1 つにそのまま移ります。 - 抽象化。rhel-system-roles で selinux・firewall・timesync を変数だけで扱います。
次回: トラック総まとめ #
手で 1 サイクルを回し、それをコードで束ねるところまで来ました。トラックの最後は、ここまで扱った断片を 1 枚の絵に合わせる作業です。
#6 トラック総まとめ: リファレンスアーキテクチャ では、Web・DB・コンテナ・モニタリング・自動化を 1 枚のリファレンスアーキテクチャにまとめ、実践トラック全体をどう 1 つのシステムとして運用するかを整理します。そしてさらに深い自動化が必要なら、RHCE シリーズ で Ansible の文法と試験範囲を本格的に扱います。