追記: ここで紹介していることのほとんどがAnsible2系で解消しています。
この記事は古いバージョンであるAnsible1系時点での紹介であることをご注意ください。
タイトルは煽っていますが、Ansibleを使っていく中で
- できそうでできないので代替策を考えた
- できなくてちょっとハマった
ことをまとめます。
だからといって「Ansibleはクソだ」と言うつもりは全く無く、むしろ 不便さを補って余りあるほどメリットがあるので、今後もAnsibleを使い続けると思います。
自分が知らないだけかもなので、こんな風に解決してるよって話があればツッコミ大歓迎です。
ansibleのversionは1.9.4です。
不便なこと
変数の使い回しとdict
例えば、アプリケーションのホームディレクトリを指定しておいて、configディレクトリはホームディレクトリ配下のconf, binディレクトリはホームディレクトリ配下のbinとするときには、varsは以下のような書き方になると思います。
app1_basedir: "/opt/app1"
app1_confdir: "{{ app1_basedir }}/conf"
app1_bindir: "{{ app1_basedir }}/bin"
この場合は、app1_basedir
だけを変更すれば、その配下のディレクトリも全部参照してくれるので、一気に変更することができて便利です。
なら、アプリケーションが複数(例えば複数インスタンスとか)起動する場合に、ちょっとまとめてしまいたいとするとどうするか。
app:
app1:
basedir: "/opt/app1"
confdir: "ここに何を書けばいい?"
bindir: "ここに何を書けばいい?"
app2:
basedir: "/opt/app2"
confdir: "ここに何を書けばいい?"
bindir: "ここに何を書けばいい?"
ここで、ついうっかり以下のような書き方をしても、varsのconversionが通りません。
app:
app1:
basedir: "/opt/app1"
confdir: "{{ app.app1.basedir }}/conf"
bindir: "{{ app.app1.basedir }}/bin"
app2:
basedir: "/opt/app2"
confdir: "{{ app.app2.basedir }}/conf"
bindir: "{{ app.app2.basedir }}/bin"
以下のような感じで、無限ループで止まります。
- name: debug messages app1_dict
debug: msg={{ item.value.confdir }}
with_dict: "{{ app }}"
TASK: [role-test | debug messages app1_dict] **********************************
fatal: [192.168.33.11] => Failed to template msg={{ app.app1.basedir }}: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template {{ app.app1.basedir }}/conf: Failed to template (略): Failed to template {{ a
解決策としては、以下が考えられます。
-
basedir
を別のwithループに入れる
結局同じwithの中で賄ってしまおうとしているからこうなってしまうので、別のループで管理してしまいます。ただ、非常に美しくない…。というか見難い。 - 変数の使い回しをやめる
こっちも美しくないですが、/opt/app1
を全て直書きしてしまう。上記程度であればそれほど苦労しないだろうという逃げです。 - withループをやめてしまう
ネストする形式をやめる案です。今まで通り変数の使い回しができますが、上記の例でいうアプリケーションの数が変動するようなケースに弱いです。
私を2番目を選択していますが、もっといい方法がないものかと思っています。
結果が見づらい
上記のようなdict形式のものでmoduleを動かす場合。
例えば、以下のような構成で、実際にディレクトリを作ってみます。
- name: make directory
file: path={{ item.value.confdir }} state=directory
with_dict: "{{ app }}"
app:
app1:
basedir: "/opt/app1"
confdir: "/opt/app1/conf"
bindir: "/opt/app1/bin"
app2:
basedir: "/opt/app2"
confdir: "/opt/app2/conf"
bindir: "/opt/app2/bin"
実行結果は以下になります。
TASK: [role-test | make directory] ********************************************
changed: [192.168.33.11] => (item={'key': 'app2', 'value': {'confdir': '/opt/app2/conf', 'basedir': '/opt/app2', 'bindir': '/opt/app2/bin'}})
changed: [192.168.33.11] => (item={'key': 'app1', 'value': {'confdir': '/opt/app1/conf', 'basedir': '/opt/app1', 'bindir': '/opt/app1/bin'}})
これで何やってるか分かるでしょうか。自分には全く分かりません…。
実際にはconfdir
を作ってるだけなのですが、varsが全て表示されるため、何がなんだか分からないという状態です。
ちなみに、-v
オプションで少しだけ詳細表示にすると、なんとか分かります。
TASK: [role-test | make directory] ********************************************
changed: [192.168.33.11] => (item={'key': 'app2', 'value': {'confdir': '/opt/app2/conf', 'basedir': '/opt/app2', 'bindir': '/opt/app2/bin'}}) => {"changed": true, "gid": 0, "group": "root", "item": {"key": "app2", "value": {"basedir": "/opt/app2", "bindir": "/opt/app2/bin", "confdir": "/opt/app2/conf"}}, "mode": "0755", "owner": "root", "path": "/opt/app2/conf", "size": 4096, "state": "directory", "uid": 0}
changed: [192.168.33.11] => (item={'key': 'app1', 'value': {'confdir': '/opt/app1/conf', 'basedir': '/opt/app1', 'bindir': '/opt/app1/bin'}}) => {"changed": true, "gid": 0, "group": "root", "item": {"key": "app1", "value": {"basedir": "/opt/app1", "bindir": "/opt/app1/bin", "confdir": "/opt/app1/conf"}}, "mode": "0755", "owner": "root", "path": "/opt/app1/conf", "size": 4096, "state": "directory", "uid": 0}
おっ、どうやらpath
のディレクトリを作っているようだ。…分かります?
ansibleは、このwith_dict
形式の表示がすこぶる不親切なイメージがあります。(そうでなくても若干分かりづらいと思うのは自分だけ?)
特にChefを使ってた人間からすると、すごく見づらいと思っています…。
syntaxエラーが分かりづらい
例えば、tasksを書き間違えてしまいます。
---
# tasks file for role-test
- name: debug messages app1 confdir
debug: msg={{ app1_confdir }}
- name: make directory
file path={{ app1_confdir }} state=directory
- name: debug messages app1 bindir
debug: msg={{ app1_bindir }}
7行目で:
を入れ忘れています。
実行時は、こんなふうに怒られます。
ERROR: Syntax Error while loading YAML script, /vagrant/ansible-playbook/roles/role-test/tasks/main.yml
Note: The error may actually appear before this position: line 9, column 1
- name: debug messages app1 bindir
^
…どこ?
ただ、これはある程度はやむを得ない話かなぁと思います…。
includeとwithが併用できない
taskを全てmain.yml
に書いてしまうと利便性が低くなるので、ある程度taskファイルを分けて運用したくなります。例えば、以下のような感じです。
- include: app1.yml
- include: app2.yml
この場合に、状況によって読み込むincludeファイルを変えるために、vars
にincludeするymlファイルを書きたくなります。
include_tasks:
- "app1.yml"
- "app2.yml"
- include: "{{ item }}"
with_items: "{{ include_tasks }}"
が、そんなことはできません。
ERROR: [DEPRECATED]: include + with_items is a removed deprecated feature (in /vagrant/ansible-playbook/roles/role-test/tasks/main.yml). Please update your playbooks.
現時点では、inventoryファイルなどに有効/無効スイッチを書いて、それで判別するしかなさそうです。
[test-server]
192.168.33.11 app1=True app2=False
- include: app1.yml
when: app1 == True
- include: app2.yml
when: app2 == True
TASK: [role-test | debug messages app1] ***************************************
ok: [192.168.33.11] => {
"msg": "this is app1"
}
TASK: [role-test | debug messages app2] ***************************************
skipping: [192.168.33.11]
この例では、inventoryに対象サーバでapp1,app2を有効/無効として変数を設定し、その変数に従ってincludeするymlをtaskで切り替えています。
ただ、これはansible2.0で解消する見込みです。
これは Ansible Advent Calendar 2015 19日目の @t_nakayama0714 さんの以下の記事が詳しいです。
includeさんとwith_*さんが仲直りしていたので幸せが訪れた。
自分もAnsible2.0を心待ちにしています!
ansibleコマンドでvarsを取り込む方法がない
ansible-playbook
コマンドだとgroup_vars
下の変数などを読み込む方法はありますが、単なるansible
コマンドだとそんな大層なことはできません。
ansibleで運用作業アプリケーションのデプロイとかミドルウェアの再起動を行いたいときに、使いたい変数をそのまま流用できないのが辛い。inventoryは持ってるのに、変数が使えない…。
運用作業が型化されていれば、もうansibleではなくansible-playbookで(=つまりplaybookを書いてしまう)実現するほうが良さそうです。あるいは、そもそもansibleに任せない。
handlerを即動かす方法がない
例えば、
- configに差分があったらファイルを入れ替える
- configファイルを入れ替えた場合はミドルウェア再起動
という操作を行いたい場合、一連のタスクの中でミドルウェア再起動を「その時点ですぐに」行いたい場合があります。例えば、ミドルウェアを再起動したあとに後続のタスクを動かしたい場合などです。Chefでは、handlerに相当するnotifies
にimmediately
のオプションがありますが、Ansibleにはありません。
近いものに、 - meta: flush_handlers
という書き方がありますが、これはhandlerを全てその場で発火させるように見えるので、目的とは違うでしょう。
workaround的には、register
〜when
を使えということなんでしょうけれども、それってhandlerの範疇なんじゃないの…?と思ったりもします。
Ansible: notify と handlers の使い方について調べた | CUBE SUGAR CONTAINER
role間の依存関係を把握しづらい
role間に依存関係がある場合に、metaにdependenciesを書くケースがあります。
例えば、role-elasticsearch
はrole-jdk
に依存している、など。
dependencies:
- { role: 'role-test' }
上記の例では、role-test2
はrole-test
と依存関係を持っている事になります。
この場合、playbookファイルは、依存関係を勝手に解消してくれます。
- hosts: test-server
sudo: yes
roles:
# - role-test ← これは不要
- role-test2
TASK: [role-test | debug messages role-test] **********************************
ok: [192.168.33.11] => {
"msg": "this is role-test"
}
TASK: [role-test2 | debug messages role-test2] ********************************
ok: [192.168.33.11] => {
"msg": "this is role-test2"
}
これ自体は嬉しい挙動かなと思います。
ちなみに、上記の「これは不要」をコメントアウトしなかったら、思いっきり二重で動きます。
TASK: [role-test | debug messages role-test] **********************************
ok: [192.168.33.11] => {
- hosts: test-server
"msg": "this is role-test"
}
TASK: [role-test | debug messages role-test] **********************************
ok: [192.168.33.11] => {
"msg": "this is role-test"
}
TASK: [role-test2 | debug messages role-test2] ********************************
ok: [192.168.33.11] => {
"msg": "this is role-test2"
}
勝手にまとめてくれると嬉しいのですが、望み過ぎですかね…。
一方で、ansible-galaxy。
必要なroleを列挙さえすれば、そのroleを勝手にインストールしてくれるというスグレモノなのですが、role間の依存関係がクセモノで、どうやら公式ansible-galaxy上にリポジトリを登録した場合のみ、自動的に依存関係を解消して必要なroleを取得してくれるようです。
Advanced Control over Role Requirements Files | ansible-galaxy
To download a role with dependencies, and automatically install those dependencies, the role must be uploaded to the Ansible Galaxy website.
現在、私のところではgitのプライベートリポジトリを立てて、そこでansible roleを管理しているのですが、
- ansible-galaxyでプライベートリポジトリからroleをインストールする : 依存関係も含めて全部requirementsに記載する必要がある
- ansible-playbookで実際にプロビジョニングする : playbookにはdependencyの親だけ記載すればよい
という感じでアンマッチなのが気持ち悪いです。ansible-galaxyの公式リポジトリ使えってことなんでしょうか。
ということなので、少なくとも私の環境では結局dependencyを理解してansible-galaxyを実行する必要があり、片手落ちな感じが否めません。
ところで、Chefだとberkshelfが berks vis
というコマンドを提供しており、それによってcookbookの依存関係をグラフ化してくれるのですが、ansible-galaxyにもそんな機能があればいいなと思います。
berks viz # Visualize the dependency graph
fileモジュールなどで指定するpermissionの先頭は(4桁表示の場合は)必ず0である必要がある
上記のmode
オプションには、以下のようにあります。
For those used to /usr/bin/chmod remember that modes are actually octal numbers (like 0644). Leaving off the leading zero will likely have unexpected results.
例えば、以下のように記述する必要があるということです。
- name: make directory
file: path={{ app1_confdir }} state=directory mode=0644
先頭に0
を入れなさい、と。
普通、パーミッションの4桁目はsticky bitなどの特別な意味を持ちます。
今さら聞きづらい「ファイルパーミッション」について | fenrir Developer's Blog
具体的には、serverspec
に直接同じ値(例えば0644
とか)を利用してテストしようとすると、エラーになります。
ですので、もしテストで同じ変数を使う場合などは、0+{{ permission }}
みたいなことをする必要があります。(どちらかというとserverspec側の問題のような気もしますが)
ansible的には、8進数を判断するために先頭に0を付与しているので、0+3桁
、0+4桁
、4桁
のいずれかでもよさそうです。(コメントありがとうございます)
最後に
しつこいですが、それでもansibleには非常に多くのメリットがあります。私はこれからもansibleを使い続けます。いつかメリットも書きたいです。