論理的に同一の一連の処理だけど、実行ホストがコロコロ変わる場合、delegate_toを使うことで、ターゲットホストの切り替えのためのplayを区切ることなく、一つのplayで処理できます。
delegate_toって
解説記事とか見ると「処理を実行するホストを移譲する」と書かれており、タスクを実行するノードを一時的に切り替える機能があることはわかるんですが、使いどころがよくわかってませんでした。
というのも、「play区切ってターゲットノード変えればいいし、、と思ってた」ので。。
delegate_toの使いどころ
よくあるパターンとして
- ターゲットノードAで秘密鍵・公開鍵キーペアを作成する
- ターゲットノードB,C,D,E,...に公開鍵を配布する
というのをAnsibleで実現しようとすると、ターゲットノード基準で考えると
- ターゲットノードA
- キーペア作成
- コントロールノードへ公開鍵コピー
- all
- 公開鍵を配布
と、2つのターゲットノードで処理が行われるので、(delegate_toを使わないのであれば)playを分割する必要があります。
--- - hosts: target-a tasks: - openssh_keypair: - fetch: - hosts: all tasks: - authorized_key:
こんな感じですね。 で、これ、playbookに全部書いてあれば、それほど見通しも悪くないしまぁ良いかなーという感じですが、roleに分割すると
--- - hosts: target-a roles: - create_ssh_keypair_by_targeta - hosts: all roles: - authorized_key_to_target
こんな感じですね。
これみたいにroleが2つでplayも2分割ならまだ何とかなるけど、鍵作成にlocalhostだけ足したいとか、ターゲットの中で一部だけ追加のroleが必要になったとか、要件が追加されるたびにplayが増殖、roleも増加(=sshの鍵設定という 目的は一つなのにrole毎のディレクトリが増えて見通しが悪くなる )してしまいます。
やりたいことは「sshの鍵設定」という論理的に一つのことなので、出来ればplayからは「sshの鍵設定というrole」を一つ呼んで終わりにしたい、という発想です。
(呼んだroleから先が細分化されてるのはroleの内部実装的な話なのでplay的には気にしない、という考え)
そこでdelegate_to
の「ターゲットノードを一時的に切り替える」という機能を使えば、「sshの鍵設定」という1つのroleを作り、その中で要件に応じてターゲットノードを切り替えることができるので、全体の構成をスッキリさせることができます。
delegate_to おためし
単体で使用
inventory
[master] master02 ansible_host=192.168.0.122 [worker] worker03 ansible_host=192.168.0.127 worker04 ansible_host=192.168.0.128
role
- name: create directory for ssh keypair file: path: "{{ lookup('env','HOME') }}/.ssh/" state: directory mode: 0700 delegate_to: localhost
これをターゲットノードall
のplayから実行すると
TASK [configure_ssh : create directory for ssh keypair] ******** changed: [worker04 -> localhost] ok: [worker03 -> localhost] ok: [master02 -> localhost]
となります。
「all
のノード毎にtaskが実行されるけど実行ノードはlocalhost
になってるので、all
のノード数3回分が実行されている」状態。
Ansibleの冪等性によって2度目以降はok
(変化なし)となってるけど、これはさすがに無駄が多いので、こういう時はrun_once
を使います。
run_once: trueと併用
- name: create directory for ssh keypair file: path: "{{ lookup('env','HOME') }}/.ssh/" state: directory mode: 0700 delegate_to: localhost run_once: true
実行結果
TASK [configure_ssh : create directory for ssh keypair] ************* changed: [worker03 -> localhost]
これでall
のノード数と関係なく、1回の実行になりました。
(余談: ターゲットノードの内1台で実行するならplayのhosts:
でhoge_groupnodes[0]
で行けるからrun_once
っていつ使うんだろうと思ってたけど、なるほど、delegate_to
と組み合わせてtask/role内で一時的に動作変更するときに使えば良かったんだな)
複数の実行ノードで動かしたい
上の例ではlocalhost
1台で実行しましたが、特定ノードグループで実行したい場合はどうするか。
以下、ダメな例
- name: publickey copy to target-nodes authorized_key: user: "{{ ansible_env.USER }}" key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/id_rsa.pub' )}}" delegate_to: worker
delegate_to
にinventoryに定義してるノードグループ名worker
を指定。
これを実行すると
TASK [configure_ssh : publickey copy to target-nodes] ************ fatal: [worker03]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: Could not resolve hostname worker: Name or service not known' unreachable: true fatal: [worker04]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: Could not resolve hostname worker: Name or service not known' unreachable: true fatal: [master02]: UNREACHABLE! => changed=false msg: 'Failed to connect to the host via ssh: ssh: Could not resolve hostname worker: Name or service not known' unreachable: true
worker
ってホスト名無いよ、と怒られる。
つまりdelegate_to
に指定するのはノードグループ名でなく、単にホスト名を指定する必要がある。
じゃあどうするかというと、loop
を使ってループ処理にすればOK
- name: publickey copy to target-nodes authorized_key: user: "{{ ansible_env.USER }}" key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/id_rsa.pub' )}}" delegate_to: "{{ item }}" run_once: true loop: "{{groups['worker']}}"
これで、各worker
ノードグループで実行されるようになる。
run_once: true
が無いと、(taskのターゲットホストの)all
の1ホスト毎にloopによるworkerノードの処理が実行されるので注意。(元のターゲットホスト x delegate_to+loopの掛け算になる)(伝われ)
実装例
まさにこのお試しの例で1roleにまとめてスッキリできました。
その他の用途
やっぱり証明書関連は特定ノードで作成して、それを各ノードに配布という処理が多いので、delegate_to
使うとすっきりするパターンは多そう。
また、Kubernetes環境だと、ノードの処理は各ターゲットノードだけど、処理の結果コンテナクラスタ上の状態を見る場合にmasterノードのうち1台だけで参照処理、みたいなのもありそう。
(平たく言うとmasterでkubeadm
で鍵作成、workerノードでkubeadm join
でクラスタ参加、masterでReadyになるのを監視、みたいな。)
去年仕事で初めて書いたplaybook/role、delegate_to知らなくてまさにターゲットノード毎に分割しちゃってたので、書き直したいなぁ(既にPJから離任してるけど)
playbookがmain()関数、roleは処理単位の関数、playbookはroleの詳細は触らない、という発想なので、「いや、Ansibleってそういうんじゃないよ?」という話だったらいろいろ変わってくるかも😅