Ansible Dark Side
Ansibleは非常にシンプルかつ強力なツールですが、 様々な設定方法・書き方が許容されるがゆえにやり過ぎると収集がつかなくなります。 そんな若干やりすぎかもしれない設定の一部を晒してみます。 タイトルは若干大げさですが、お勧めするような設定ではないという自虐的な意味を込めてます。
production/staging environment variables
Ansibleである程度の規模のプロビジョニングをするようになると 公式DocumentにもあるBest Practices に倣うと思いますが、 この時課題の1つとなるのがproductionとstagingで異なる環境変数をどう管理するかです。
検索してみると何種類かのやり方がHitするのですが、うちでやっている方法も書いてみます。 このやり方はAnsible始めた頃に同僚から教えてもらった方法です。
inventory/ production/ group_vars/ all group1 group2 hosts staging/ group_vars/ all group1 group2 hosts roles/ site.yml
group_vars + hostsファイルという構成を
productionとstagingのそれぞれのディレクトリに作ります。
こうするとall
はproduction全体・staging全体を表すことになるので、
それぞれに適用したい変数を設定できますし、
group1、group2も環境毎の値を設定できます。
実行する際は、
ansible-playbook site.yml -i inventory/production/
デメリットはproduction/staging共通の設定であっても2箇所に書く必要があることです。 そのためパスは少し深くなりますがinventoryというディレクトリの中に両方を入れて、 ネストしてみせることで両方設定する必要がある、 というのを意識しやすくしています。 (並び順がバラけるのを防ぐ意味もある) 自分は更にlocalというディレクトリも作って 手元のvagrant上でレシピをテストする時に使ってます。 (.gitignoreで登録対象からは外す)
大外に共通用のgroup_vars作ればいいかもしれませんがgroup_varsが複数個所にあると 管理が更に煩雑になる気もするのでこのくらいが自分には限界の気がします。
large repository management
サービスがある程度の規模になってきてチームで運用が分かれたり、 小規模でも複数のサービスを同時に運用するようになると、 レシピの管理単位を分割したくなります。 一方で、共有したいRoleも当然あります。 以下を参考にしてディレクトリ構造を組みました。
参考にしつつ試行錯誤して自分なりの設定を加えています。
common-roles/ vars/ filter_plugins/ roles/ jdk/ nginx/ mackerel_agent/ team1/ inventory/ roles/ app1/ ansible.cfg site.yml team2/ inventory/ roles/ app2/ ansible.cfg site.yml
$ cat team1/site.yml - hosts: app sudo: yes vars_files: - ../common-roles/vars/xxx_vars.yml roles: - jdk - nginx - app1 - mackerel_agent
参考サイトと異なるのはcommon-rolesのRoleもteam1のRoleも同じように指定できるところ。 これを実現するためにansible.cfgを各実行ディレクトリに置いています。
$ cat team1/ansible.cfg [defaults] roles_path = ../common-roles/roles/ filter_plugins = ../common-roles/filter_plugins
関係ある設定はこんな感じでcommon-rolesのパスを設定すると、 設定したパスのRoleと実行ディレクトリ配下のRoleの両方読んでくれます。 filter_pluginsも同様に出来ました。 同様には出来なかったのがlibraryのパス設定で、 これは要するにmoduleを読むパスなんですが、 設定するとそのパスのみが有効になってしまい、 Ansible組み込みのmodule群が使えなくなってしまったので、 libraryに関しては実行ディレクトリ配下か、Role内にのみ置いて使っています。 それから共通で使うvars_fileみたいなのはそんなに数多くないし 相対パスで指定してます。
Ansibleは本来はもっとシンプルに運用可能なツールで、 library, group_vars, roles, xxx_plugins みたいな決まった名前のディレクトリを 実行ディレクトリ配下に置いていると勝手に読んでくれるので、 可能ならansible.cfgとか使わずに済んだ方がスッキリできるとは思います。 (接続性の設定とかは別)
Include various settings
mackerelとかtd-agentとか横断的に利用し、 かつ複数の設定をもつようなRoleがあります。 パターンの種類が少ないうちは種類ごとに分けたファイルをinclude + whenで 処理していました。
roles/ mackerel_agent/ tasks/ configure_a.yml configure_b.yml main.yml $ cat main.yml - include: configure_a.yml when: mackerel_agent_type == 'aaa' - include: configure_b.yml when: mackerel_agent_type == 'bbb'
この手法は分岐条件が少ないうちはまぁなんとかなりますが、 パターンが増えてくると徐々に扱いづらくなってきます。 ソースも見づらくなってきますし、実行時のログがwhenの場合は実行されない条件でも skippedとしてログに出てしまうので実行結果が読みにくいです。
解決策としては2種類考えました。
1つはinclude
するファイル名を変数化することです。
- include: configure_{{ mackerel_agent_type }}.yml
しかし課題もあり、group_varsでこの変数を指定しようとすると失敗します。 理由は、変数を読む順番のためだと思っています。
参考: Ansible の変数の優先順
group_varsよりplaybook role parameterの優先順の方が早いので、 playbook role parameterを読み取るためにincludeしようとして そんなファイルは無い、となります。 playbook vars (site.ymlの先頭にvars:で書く)で指定するとうまくいきます。 playbook毎に適用種類は決まる気がするので 今は一旦この方法でやろうと考えていています。
記事を書いているうちにもう1つの案が実はよりよく感じてきました。 もう1つの案とはRoleを分けることです。 Ansibleを運用する場合、多くの場合でRoleを適宜分割することは良い案です。 特にサービスに依存するような設定を行う場合、 ミドルウェアのインストールのような汎用的な部分と、 ミドルウェアの設定をサービスに合わせて行う部分は分けた方が良いです。 更に後者から前者にDependencyを設定しておけば後者のみ指定でよくなります。
roles/ mackerel_agent/ mackerel_agent_serviceA/ mackerel_agent_serviceB/ $ cat site.yml roles: - mackerel_agent_serviceA
デメリットはRoleが複数増えてしまうことと、共通の変数を設定しているような場合に 設定箇所が散らばってしまうことでしょうか。 共通のRoleがやたら増えるのも困るので今は1つ目の案で考えています。 (refactoring now...)
以上、3つほど運用パターンを晒してみました。 これらは決して勧めているわけではなく、 多分、ここまでやる前に構成や運用を見直した方が良い気がするのですが、 こういった事も出来るよ(やっちゃってるよ)という記事でした。
ちなみにちょっと気になって、 初心者Chefアンチパターン を久々に見返したらほぼ当てはまっていて今回の内容もだいぶやばいかも。。