Ansible の仕組み
Ansible は NW 機器や各種サーバーの設定を管理する仕組みです。
playbook と呼ばれるファイル (人間で言う手順書に該当するもの) に従って、対象の機器に接続し、playbook の通りに設定作業をしてくれます。playbook は 1 つ以上の task (一番小さい処理の単位) が存在し、その task を順番にこなしていきます。
つまり、機器が ansible に対応しているかどうかは、その機器用のモジュールがあるかどうか次第となります。
Ansible における冪等性の考え方
Ansible は「冪等性」という考え方を重視します。同じ内容の playbook であれば、何度実行しても同じ結果になる、ということです。(例えば実行するたびに、とある設定ファイルに行が増える、という動作をする場合、これは冪等性が無い、と言えます。)
Ansible では処理を実行しエラーが無い場合は「ok」、エラーがある場合は「failed」となりますが、ok の中でも何かしらの変更処理を実施したかどうか、ということを示す「changed」というステータスが返ってきます。
また、モジュールを動かす前提条件を設定した場合は、その条件に合致せず処理をスキップしたことを示す「skipped」というステータスもあります。
「冪等性がある」という評価の単純な方法の 1 つとしては、「ある playbook を 2 連続で実行したとき、2 回目の処理については changed が無い (skipped は有っても OK)」になることです。
Ansible には task で使うためのモジュールが用意されており、多くは冪等性が担保されています。例えば ansible.builtin.apt では Ubuntu パッケージインストールを実行してくれますが、既にインストール済の場合は changed は返ってきません。(インストールされていない場合はインストールに成功したら「changed」が返ってくる)
ですが、モジュールによっては冪等性が担保できないものもあります。
例えば Linux 用のモジュールとして ansible.builtin.shell というモジュールがありますが、これは任意の Linux コマンドを実行するモジュールであり、実行が成功した場合は「changed」になり、単体では冪等性を担保できません。
後のサンプルで示しますが、例えばロケール設定を日本にする場合、update-locale LANG=ja_JP.UTF-8 コマンドを実行しますが、これを毎回実行してしまうと ansible としては changed と判断されてしまい、ansible の考え方としては好ましいものではありません。
ロケールについては、ただ changed を気にしなければいい問題とも言えますが、これが例えば postgreSQL をインストールした後の initdb (/usr/bin/postgresql-setup initdb) コマンドで考えた場合、1 回目は設定が通るけど 2 回目はエラーにより処理が停止する、といった実害が発生します。
このようなケースを回避するためには、以下のようにスキップする条件を自分で考えて設定する必要があります。
- ロケールの場合: まずは状態を表示するためのコマンド localectl status を (changed とならないオプション changed_when: false を付けて) 実行し、このタスクの標準出力の結果に ja_JP.UTF-8 という文字列が含まれているかどうかを確認し、含まれれている場合は処理を実行しない
- initdb の場合: /var/lib/pgsql/data/PG_VERSION というファイルが存在する場合は実行しない
構成例
以降では Ubuntu サーバ#1 (ubuntu-1) を ansible サーバとし、Ubuntu サーバ #2 (ubuntu-2) を管理することを考えます。
前提として人が操作するためのユーザー test が両サーバーに存在する状態から始めます。ansible サーバ側にも専用のユーザー ansible を作っても良いです。
事前準備
Ubuntu サーバ #2 : ansible で接続されるための最低限の Setup
サーバ #2 の本格的な設定は ansible で行いますが、その ansible での管理ができるように最低限の Setup を実施します。
サーバ #2 に ansible ユーザーを作成し、公開鍵を配置するディレクトリ .ssh を作成し、sudo でのパスワード変更不要に設定します。
test@ubuntu-2:~$ sudo useradd -m ansible -s /bin/bash test@ubuntu-2:~$ sudo passwd ansible test@ubuntu-2:~$ sudo install -d -m 0700 -o ansible -g ansible /home/ansible/.ssh test@ubuntu-2:~$ sudo visudo ~~~ ansible ALL=(ALL:ALL) NOPASSWD: ALL # 行のどこかに追加 (%sudo から始まる行の直後など) ~~~
#2 へパスワード無しで ssh 接続できるよう公開鍵認証の設定
サーバ#1 の任意のユーザー (ここでは test) にて ssh 鍵ペアを作成 (途中のオプションはデフォルトでよいので Enter)
test@ubuntu-1:~$ ssh-keygen
公開鍵をサーバ#1 送り込み、パーミッションを変更します。
test@ubuntu-1:~$ scp ~/.ssh/id_rsa.pub ansible@10.1.1.5:~/.ssh/authorized_keys test@ubuntu-1:~$ ssh ansible@10.1.1.5 chmod 0600 /home/ansible/.ssh/authorized_keys
これで公開鍵認証で (パスワード入力無しで) ansible ユーザーとして ssh 接続ができるようになります。
test@ubuntu-1:~$ ssh ansible@10.1.1.5
ansible@ubuntu-2:~$
動作確認ができたので exit で ubuntu-1 に戻ります。
ansible@ubuntu-2:~$ exit
test@ubuntu-1:~$
ansible の事前セットアップ
Ubuntu サーバ #1 : ansible のインストール
まずはサーバ#1 へ ansible をインストールをします。インストール方法はいくつかありますが、今回は一般的な手法の 1 つである python の仮想環境 (venv) を用意し、その上で python パッケージから ansible をインストールします。
まずは仮想環境用のパッケージ python3.10-venv をインストールします。
test@ubuntu-1:~$ sudo apt -y install python3.10-venv
仮想環境を作ります。
test@ubuntu-1:~$ python3.10 -m venv .venv310
仮想環境に入り、pip を最新化します。
test@ubuntu-1:~$ source .venv310/bin/activate (.venv310) test@ubuntu-1:~$ pip install --upgrade pip
ansible をインストールします。
(.venv310) test@ubuntu-1:~$ pip install ansible
Ubuntu サーバ #1 : ansible.cfg, hosts ファイルを作成
ansible 作業用のディレクトリを作成し、移動します。
(.venv310) test@ubuntu-1:~$ mkdir ansible (.venv310) test@ubuntu-1:~$ cd ansible
環境セットアップとして最低限、2 つのファイルを作成するとよいでしょう。
1 つ目は ansible.cfg です。vi 等で以下の内容を記述します。
(.venv310) test@ubuntu-1:~/ansible$ vi ansible.cfg
[defaults]
inventory = hosts
remote_user = ansible
private_key_file = ~/.ssh/id_rsa
host_key_checking = False
次に hosts ファイルです。これは上記の .cfg ファイルの inventory = hosts に該当する部分です。ファイル名は合わせてファイルを作成します。同様に vi 等で以下のように記述します。
(.venv310) test@ubuntu-1:~/ansible$ vi hosts
all:
hosts:
ubuntu-1:
ansible_host: localhost
ansible_connection: local
ubuntu-2:
ansible_host: 10.1.1.5
今回の例では ubuntu-2 だけを管理するので、ubuntu-1 の部分は不要ですが、この設定を入れることによりローカルホストである ubuntu-1 も管理可能になります。
これで最低限のセットアップは完了です。
疎通確認をしてみましょう。ansible all -m ping で以下のように返ってくれば OK です。
(.venv310) test@ubuntu-1:~/ansible$ ansible all -m ping ubuntu-1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ubuntu-2 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" }
Ubuntu サーバ #1 : playbook ファイルを作成
[task1.yml] というプレイブックファイルを作成し、日本語環境を整えます。hosts に ubuntu-2 を指定し、tasks としてタスクを 5 つセットしています。
(.venv310) test@ubuntu-1:~/ansible$ vi task1.yml
- name: Common Setup
hosts: ubuntu-2
gather_facts: false
become: true
become_user: root
tasks:
- name: Install apt Package
ansible.builtin.apt:
name:
- language-pack-ja-base
- language-pack-ja
- ibus-kkc
- name: Gen Locale
locale_gen:
name: ja_JP.UTF-8
- name: Check locale
ansible.builtin.shell: localectl status
register: chk_locale
changed_when: false
- name: Set Locale
ansible.builtin.shell: update-locale LANG=ja_JP.UTF-8
when:
- "'LANG=ja_JP.UTF-8' not in chk_locale.stdout"
- name: Set timezone to Asia/Tokyo
community.general.timezone:
name: Asia/Tokyo
ansible-playbook を実行
これを以下のように ansible-playbook で実行します。このとき、ansible.cfg は自動的に読み込まれるため、-i hosts は不要となり、ssh 接続時の秘密鍵も .cfg で指定されたものを使います。
(.venv310) test@ubuntu-1:~/ansible$ ansible-playbook task1.yml
手元の環境では ok=5 , changed=3 となりました。つまり 5 つのタスクが全て成功し、そのうち 3 つが変更を伴った、ということを意味します。
これをもう 1 回実行すると ok=4 , skipped=1 となるはずです。changed が現れません。これが前述した、冪等性が担保された状態の判断基準の 1 つです。
タスクの Check Locale にて localectl status を実行し、(register: chk_locale オプションにより) その標準出力を chk_locale という変数に格納しています。このコマンドは変更要素の無いコマンドですので、changed_when: false にすることで、実行しても毎回 changed にならないようになります。
そして Set Locale タスクについては、when: で指定している通り、この chk_locale 変数内に 'LANG=ja_JP.UTF-8' という文字列が無い場合にのみ実行するようになります。これにより 2 回目以降は skipped が返ってくるようになります。
その他
become と become_user
become は ssh 接続時のユーザー ansible から sudo でユーザー変更するかどうかを指定します。become: true の場合は become_user で指定されたユーザーに変更します。become_user が指定されていない場合は root ユーザーになります。なので今回は敢えて become_user を指定していますが、省略可能です。
gather_facts
接続先の情報を ansible 変数として収集するときに使います。例えば接続先のホスト名を確認し、それによって条件分岐を書くときは gather_facts: true にすれば ansible_hostname という変数に接続先のホスト名が格納されますので、この変数を使って条件分岐を書きます。
デフォルトで true ですが、収集にちょっと時間がかかるため、もし使わないなら false が良いです。
コメント