はじめに
Ansible初心者のたわごとです。
詳しい方がみれば何を当たり前のことをと思われるかも知れませんがご容赦ください。
Ansibleとは
- ansibleはタスクを定義して複数のホストでそれを実行させるプラットフォームです。
- 複数のタスクをまとめて Playbook とすることで、大量のホストに複雑なことをやらせることができます。
- 対象ホストにsshでログインできて、そこにPythonが入っていれば、あとはなんとかなるというものです。
- タスクは手続き型の記述ではなく、あるべき状態を示すので、正しく動作すればべき等性が保証されます。
- タスクはベタッとPlaybookの中に書くこともできますが、それを分割することもできます。C言語のようにincludeで引っ張り込むこともできますが、役割(role)ごとに分割するのが一般的です。
変数を使ったタスクの実行
Ansibleでは変数を定義することで、同様のタスクをいろんなホストにていろんなパラメータて実行することができます。一番単純な例として、変数を一つ定義して参照してみます。(認証周りのセットアップは省略してます、ご了承ください)
hosts: all
vars:
foo: "this is test."
tasks:
- name: test
debug:
msg: "{{ foo }}"
これを実行してみます。
$ echo localhost > host.local
$ ansible-playbook -i host.local test.yml
PLAY [all] ******************************************************************************************************************************************************
TASK [Gathering Facts] ******************************************************************************************************************************************
ok: [localhost]
TASK [test] *****************************************************************************************************************************************************
ok: [localhost] => {
"msg": "this is test."
}
PLAY RECAP ******************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
これはあちこちのブログでも例として掲示されています。これだけですべてわかるなら苦労はしないわけで、次行ってみます。
Ansibleは変数がキモ
Ansibleを使ってるとうまく動かないことがあってデバッグするわけですが、全変数の内容を吐き出せれば、自分が何を間違ってるのかの把握がしやすくなります。全変数の吐き出し方は下記のようにします。
- hosts: all
tasks:
- name: Dump all variables
debug:
msg: "{{ vars }}"
実行すると山盛りもりもり吐き出されます。
多すぎるのでリダイレクトするなりしてファイルに落としてから眺めるほうがいいと思います。
なお、システムが用意する変数だけを出力させる場合は下記のコマンドが使えるようです。
ansible -i hosts -m setup ホスト名
あちこちで変数を定義して、どこに差し込まれるか観察してみた
Ansibleではいろんなところに変数を定義することができます。どこに定義して何がどうなるのか把握せずに書いてると訳がわからなくなるので、調べてみました。
- Playbookに直接
-
vars_files
のファイルの中 group_vars/all
host_vars/ホスト名
- hosts: all
vars:
foo1: "this is test.(vars)"
vars_files:
- vars.yml
tasks:
- name: test
debug:
msg: "{{ vars }}"
foo2: "this is test (vars.yml)"
foo3: "this is test (group_vars/all)"
foo4: "this is test (host_vars/localhost)"
実行して吐かれた内容を確認してみます。全部だと果てしないので、かなーーり省略してます。
{
"ansible_all_ipv4_addresses": [
...
},
"environment": [],
"foo1": "this is test (vars)",
"foo2": "this is test (vars.yml)",
"foo3": "this is test (group_vars/all)",
"foo4": "this is test (host_vars/localhost)",
...
"hostvars": {
"localhost": {
"ansible_all_ipv4_addresses": [
...
},
"discovered_interpreter_python": "/usr/bin/python3",
"foo3": "this is test (group_vars/all)",
"foo4": "this is test (host_vars/localhost)",
...
}
},
}
{{ foo4 }}
と書いているtasks
やroles
が参照してるのは、上のほう(ホストによらないグローバルな変数)を参照していると思われます。
複数グループ、複数ホストを用意してみる
推測ですが、ホストによらないグローバル変数の値がホストごとに書き換わっているのではないかと思います。同じ変数名で値を変えてあちこちで定義して、どうなるか観察してみます。
[GroupA]
hostA
hostB
[GroupB]
hostC
hostD
[GroupC]
hostE
hostF
- hosts: all
vars:
foo: "this is test.(vars)"
vars_files:
- vars.yml
tasks:
- name: test
debug:
msg: "{{ vars }}"
foo: "this is test (vars.yml)"
foo: "this is test (group_vars/all)"
foo: "this is test (group_vars/GroupA)"
foo: "this is test (group_vars/GroupB)"
foo: "this is test (host_vars/hostA)"
foo: "this is test (host_vars/hostB)"
foo: "this is test (host_vars/hostC)"
foo: "this is test (host_vars/hostE)"
想像としては、グローバルの値が下記ではないかと考えています。
-
hostA
: hostAの定義 -
hostB
: hostBの定義 -
hostC
: hostCの定義 -
hostD
: GroupBの定義 -
hostE
: hostEの定義 -
hostF
: group_vars/allの定義
では実行。
実行してみた(1)
hostA
の結果から見てみます。
{
...
"environment": [],
"foo": "this is test (vars.yml)",
...
}
あれ? そうなの? じゃあ `msg: "{{ foo }}" にしてみたらどうなるか見てみます。
ok: [hostA] => {
"msg": "this is test (vars.yml)"
}
あらー。ってことは、ホストごとに違う値を入れたい変数と同じ名前の変数をvars.yml
で定義しちゃいけないと。vars.yml
をどけたらどうなるか見てみます。
ok: [hostA] => {
"msg": "this is test (vars)"
}
ですよねー。vars
からも削除してみます。
実行してみた(2)
- hosts: all
tasks:
- name: test
debug:
msg: "{{ foo }}"
結果は下記です。
TASK [test] *****************************************************************************************************************************************************
ok: [hostA] => {
"msg": "this is test (host_vars/hostA)"
}
ok: [hostB] => {
"msg": "this is test (host_vars/hostB)"
}
ok: [hostC] => {
"msg": "this is test (host_vars/hostC)"
}
ok: [hostD] => {
"msg": "this is test (group_vars/GroupB)"
}
ok: [hostE] => {
"msg": "this is test (host_vars/hostE)"
}
ok: [hostF] => {
"msg": "this is test (group_vars/all)"
}
想像していたとおりの結果が得られました。
出力内容を {{ vars }}
に変更して覗いてみると、それぞれのホストでの実行時にグローバルの"foo"
の値が書き換わっていることがわかります。そしてそれはhostvars[ホスト名].foo
の値と一致しています。
まとめ
- ホストごとに違う値の定義は
host_vars/ホスト名
に書きましょう。他では書けません。 - 他ホストと共通の値はグループなり
vars:
なりで定義すればよいです。 - 優先順位にしたがってホストごとにグローバルにコピーした状態でタスクが実行されます。
共通の値をグループに書いておき、特定ホストだけ例外扱いすることもできるということで、優先順位を書くと下記になります。
-
vars_files
に書かれた定義があればそれが使われる。 - 上記の定義がなければ
vars
に書かれた定義が使われる。 - 上記の定義がなければ
host_vars/ホスト名
から定義がコピーされる。 - 上記の定義がなければ
group_vars/所属グループ
から定義がコピーされる。 - 上記の定義がなければ
group_vars/all
から定義がコピーされる。
変数の定義のしかたとしては上記の他にコマンドラインで指定したりインベントリに書いたりhost_vars/ホスト名
をディレクトリにしてその下のファイルに書いたりとかいろいろありますが、そこは省略。それらを含めた細かい優先度が気になる方は公式ドキュメントをご参照ください。
ホストごとに定義される変数は無理やり{{ hostvars[inventory_hostname].foobar }}
などとして参照することも可能ですが、各々のホストに合わせた値がグローバルに設定されるのでそれを参照すればよく、つまり単に{{ foobar }}
と書けば値を得られる、ということでした。
だから(他の人が作ったroleなどと併用することを考えたとき)変数名が重複しないよう気をつける必要がある、と。なるほど納得です。
さいごに
公式ドキュメントを読んでも世間様のブログや解説記事を読んでもなにがなんだかよくわからなかったので調べたのですが、世間の皆様はそういうのでするっと理解できているんでしょうか、皆さんすごいなあと思います。