まえがき
Ansibleを巡ってこんなことがありました。
- Webインスタンスの作り分けに苦心していた
- Ansible 2.0に救いを求めた
- 幸せが訪れた
Webインスタンスの作り分け
やりたかったこと
- AnsibleでWebサーバ構築をしたい (今回はMW部分)
- Webサーバは複数のWebインスタンスを稼働させる可能性がある
- Webインスタンスは複数のWebサーバで稼働する可能性がある
- 各インスタンス情報は一元管理、Webサーバ間で共用可能なようにしたい
- 特定のインスタンスに限った更新ができるようにしたい
アイデア1 : インスタンスのgroup_vars化
インスタンスごとにグループを作り、インスタンス情報をgroup_varsに集約する。
具体的なイメージ
[instanceA]
Web_Server_#1
[instanceB]
Web_Server_#1
Web_Server_#2
[instanceC]
Web_Server_#2
このように組むことで
- 各インスタンス情報の一元管理、Webサーバ間の共用
-
特定のインスタンスに限った更新 ( ← グループ指定の実行)
が可能になると考えていました。
まずかったこと
- サーバがどのインスタンスを持っているのかが分かりにくい
インベントリを見たときに、
インスタンス → サーバ
の関係はよくわかりますが、
サーバ → インスタンス
の関係がイマイチわかりません。
一般論はさておき、1つのサーバ上には多い場合に10以上のインスタンスを乗せることも想定していたため、Web_Server_#1なんてのを10個以上も書くのはスマートではありません。
また、Webサーバ構築の類似ケースとして、既存Webサーバへのインスタンス追加もあったりするので追加しようとするサーバにどんなインスタンスが乗っかっているのかパッと確認したい気持ちもありました。
- そもそもAnsibleはホストが複数のグループに属することを想定していない
上記のインベントリでansible-playbookを実行すると下記のようなWarningが表示されます。
[WARNING]: While constructing a mapping from /ansible/test.yml, line 27, column 3, found a
duplicate dict key (instance). Using last defined value only.
んんん?と思ったのでソースの該当部分を確認するととあるコメントが...。
# Most of this is from yaml.constructor.SafeConstructor. We replicate
# it here so that we can warn users when they have duplicate dict keys
# (pyyaml silently allows overwriting keys)
(略)
display.warning(
While constructing a mapping from {1}, line {2}, column {3},
found a duplicate dict key ({0}). Using last defined value only.
.format(key, *mapping.ansible_pos)
)
(意訳) pyyamlがkeyの値を勝手に上書いちゃうので最後の値しか使えませんがWarningは怠りません!
つまり、上のようにhostsを書いた場合、上から評価されるので Web_Server_#1は最後に出てくるinstanceBのgroup_varsでしか動かない ということになります。
気持ち的にはホストが所属しているグループ毎にタスクが動いてくれるものと思っていたので、結構焦ってしまいました。
先人の悩み
焦る気持ちでググったらこんな人がいました。
質問は2014年11月、Ansible 1.8の時代です。
Best Practice Question: Multiple non-overlapping groups for one host - best alternative?
(要約) ホストが複数グループに属していると、1つのグループを指定して実行したとしても、最後のグループのgroup_varsで実行されて困っている。
状況的には自分の場合とほぼ同じで、DBサーバ上に複数のDBインスタンスを運用する例を挙げていました。
1つ目のアドバイスは「単一のグループ内にインスタンス情報をまとめるといいよ」、という内容でしたが、これに対する返答の中で
thanks for the tip. I think the approach is not totally suitable for my
situation. I would rather not define all the databases for one server in one
place for two reasons:
というように集約したくないという点で完全にマッチしない前置きし、
I would like to have it very easy to "move" the database to a different server.
Also quite important is that I want to be able to run a playbook that installs
just db01 and not db02 to save development time.
という点を挙げています。
この「容易に異なるサーバへ動かせるようにしたい」「db02インスタンスを触らず、db01のみを構築したい」
というモチベーションは自分もWebインスタンスに対して同様に抱いていたことなので、投稿者の悲しみに思いを馳せながら興味深くスレッドを追っていきました。
しかし、スレッドの最後は
「考え得るDBインスタンスの組み合わせごとに親グループ化しておこう」
というアドバイスで終わっていました。
例は2つのDBインスタンスだけでしたからパターンは「db01のみ」「db02のみ」「db01, db02」の3パターンで済みましたが、例えば10インスタンスを扱う場合では1023パターンも用意しなければならないことになります。
( $n$ インスタンスを扱う場合、組み合わせとしては $2^n-1$ パターンが必要)
やめてください、しんでしまいます ^q^
そんなわけで、有効な手立てが見つからず、悩んでいました。
Ansible 2.0
Ansible DocumentやらGoogle先生やらにお伺いを立てていましたが、ピンとくるアイデアが見つからず。。。
正式リリースでないことはわかっていながらも、「実は2.0だったら画期的な機能でなんとかなるんじゃね?」と思い、Ansible 2.0を調べてみることに。
主な変更点
こちらに2.0における変更一覧があります。
たださすがにメジャーが上がるともあって非常に項目が多いです。
しかしありがたいことに2.0での変更をまとめて下さっている方々がいらっしゃいました。
-
Ansible 2.0
こちらは僕もお世話になった「入門Ansible」の作者若山さん( @r_rudi )さんのAnsible Meetup Tokyo 2015用スライド。わかりやすくて涙でます。 -
Ansible 2.0 変更点まとめ
こちらは @yuta_h3 さんのQiita記事。
いい感じに変更点がカテゴライズされていて、わかりやすくて涙でます。
とにかく皆様のすばらしさに涙が出るのです。
include + with_*
数ある変更の中で目に止まったのがこれ。
むしろこれまでできなかったのか、という思いがありますが。
**CHAGELOG**でいくとこの部分ですね。
- "with_" loops can now be used with task includes since they are dynamic.
include を with_* で回せるようになった、というもの。
ちなみにこの include + with_* の書式、1.9などの2.0以前でやろうとすると
ERROR: [DEPRECATED]: include + with_items is a removed deprecated feature. Please update your playbooks.
というメッセージが出て実行できません。
調べてみると、昔は実行できていたがバグにより 取り外されていた機能 らしいです。
とはいえどう考えても便利そうな機能なので、質問者にせよ回答者にせよ絶望している人が多数いたようですね。
So looks like this feature has been deprecated, i really don't understand why
(なくなるとかマジ意味分かんないんですけど~~~)
This has been covered about eleventy billion times on the mailing list in the past
(この質問何回目かわからんでござる~~~)
アイデア2 : インスタンスのvars化とhost_varsによる保持インスタンスの明示
include + with_* をありがたく取り入れ、こんな風にしました。
具体的なイメージ
ansible/
├─inventory/
│ └─hosts
├─group_vars/
│ └─Web_Servers.yml
├─host_vars/
│ ├─Web_Server_#1.yml
│ └─Web_Server_#2.yml
├─vars/
│ ├─instanceA.yml
│ ├─instanceB.yml
│ └─instanceC.yml
├─webserver.yml
└─instance.yml
上のように、インスタンス情報を共用可能なvarsとして集約 しています。
[Web_Servers]
Web_Server_#1
Web_Server_#2
web_instances:
- instanceA
- instanceB
host_varsを使うことで、サーバ→インスタンスの関係もわかりやすく なりました。
- host: Web_Servers
...
- task:
- include: instance.yml
with_items: web_instances
メインとなるPlaybookの中では、インスタンスを作るタスクを with_itemsで回しながらinclude します。
- include_vars: {{ item }}.yml
- name: create instance
...
includeされるタスクの先頭で with_itemsから渡されたインスタンス名のvarsをinclude_vars することで各ホストはインスタンス情報を変えながら同じ手順で各々のインスタンスを作り分けます。
また、host_varsの書き換えを避ければ特定のインスタンスに限った更新は、--extra-vars による上書き で可能になります。
--extra-vars に関しては
- extra vars (-e in the command line) always win
と最強の変数なので扱いも楽といえば楽そうです。
(意図しない変数上書きを防止するために Ansible’s Zen philosophy の徹底が必要ですが)
Ansible’s Zen philosophy
上で引用した公式ドキュメント(Variables)には唐突に Ansible’s Zen philosophy という文句が出現します。
なんだこれ?と思ってググッてみても出典らしきものが見つかりません。
Avoid defining the variable “x” in 47 places and then ask the question “which x gets used”.
(変数"x"を47ヶ所で使っておきながら、「どれが使われるんだっけ?」なんて言わないで下さい)
Why? Because that’s not Ansible’s Zen philosophy of doing things.
(なぜかって?それは "Ansible’s Zen philosophy" のやり方ではないからですよ)
というような公式ドキュメントの文章を頼りにすると、
- ユーザが変数の使用場所を明確に把握できていること
が Ansible’s Zen philosophy の重要な思想、ということでしょうか?
ちなみにこれを調べている最中に プログラマが持つべき心構え (The Zen of Python) なんてものを見つけました。Python好きになりそうです。
I'm Happy ?
何はともあれ、2.0で include + with_* の書き方が復活したおかげで 幸せな気持ち になることができました。
今回の内容を考えていて思っていたのは、「よくありそうな話なのにググっても出てこない」ということでした。
確かに最近はVMやらコンテナやらが発達しているので、サーバに複数のインスタンスが相乗りというのは少ないのかもしれませんが。。。
もしかしたらとてもシンプルで簡単な話なのに、うまく整理できていないだけなんじゃないかと悩んだ結果 、Advent Calendarに晒してみようと思った次第です。
さて、私は本当に幸せになれたのでしょうか?
おわり。