zaki work log

作業ログやら生活ログやらなんやら

[Ansible] ロール呼び出し時にプログラミングでいう引数指定の関数コールっぽいことをしてみる

ロールに分割した処理を呼び出すときに、ホストグループ等に依存しないロール内で使用する変数を呼び出し元で定義することで、プログラミングにおける関数コールの引数みたいなことをやってみました。

唯一無二のやり方というわけでなく、最初に思いついたのがこの方法だった、という内容です。

ベースの内容

プレイブック

playbook.ymlは以下の通り。

---
- hosts: localhost
  gather_facts: false
  roles:
  - sample_role

sample_roleというロールを呼び出すプレイブックです。

ロール

roles/sample_role/tasks/main.ymlは以下の通り。

---
- name: hello curry world
  debug:
    msg: "hello {{ args }}"

hello {{ args }}をprintします。
ただし、このプレイブックトロールだけだとargsが未定義なのでエラーになります。
(実行時に-e指定するという手もあるけど)

playbookで変数指定

ここで、ベースのプレイブックを次のように変更。

---
- hosts: localhost
  gather_facts: false
  vars:
    args: curry
  roles:
  - sample_role

すると実行結果は

TASK [sample_role : hello curry world] *****************************************
ok: [localhost] => 
  msg: hello curry

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[zaki@manager-dev sample-ansible]$ 

この通り、呼び出し元のplaybookでセットしたcurryという文字列が参照できている。
呼び出し元のplaybookで定義忘れに気を付ければ、ひとまずこれで実現はできました。

ロール毎に変数指定

プレイでなくロールに変数指定するには

- hosts: localhost
  gather_facts: false
  roles:
  - role: sample_role
    vars:
      args: curry
  - role: sample_role
    vars:
      args: carbonara

これでsample_roleは2回実行されるけど、引数はそれぞれcurrycarbonaraになる。

TASK [sample_role : hello curry world] *****************************************
ok: [localhost] => 
  msg: hello curry

TASK [sample_role : hello curry world] *****************************************
ok: [localhost] => 
  msg: hello carbonara

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

デフォルト値の定義

といってもロール使用時に変数定義し忘れることもあるし、未設定の場合はデフォルトの値を指定を使用するようにしてみるには。

roles/sample_role/defaults/main.ymlというパスでファイルを作成。

---
args: ramen

これで、argsがどこにも設定されてなかった場合に上記ファイルからramenという文字列が参照されるようになる。

プレイブックの

  vars:
    args: curry

を削除してansible-playbookを実行すると

TASK [sample_role : hello curry world] *****************************************
ok: [localhost] => 
  msg: hello ramen

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

このとおり。

ここで重要なのは、defaultsというディレクトリに定義するということ。
ロール用の変数を定義する場合は、普通はvarsというディレクトリ(roles/sample_role/vars/main.yml)に定義しますが、ロール変数に定義してしまうと、呼び出し元のプレイブックでvarsディレクティブで変数定義しても上書きができず、ロールroles/sample_role/vars/main.ymlの値が使われるため、上の例であれば必ず出力はhello ramenになります。

これはAnsibleの変数の優先順位が規定されており、抜粋すると以下の優先度(数字が大きいほど強い)になっているため。

優先度 変数種別
2 ロールのデフォルト変数 (defaults/main.yml)
12 プレイのvars変数
15 ロール変数 (vars/main.yml)

(※優先度20の「role (and include_role) params」が実は何者かよくわからなかった… → 後述)

docs.ansible.com

ちなみにリストを見ればわかるけど最強優先度の変数は、コマンドライン-eを使って指定するエクストラ変数。

$ ansible-playbook -i inventory.ini playbook.yml -e args=gohan

のように実行すれば、実行中に出現する{{ args }}gohanとなる。

で、これで何するの?

Kubernetes環境だと、実際の処理対象(kubectl実行とか)のターゲットノードはmasterノード1台で、入出力の引数がmaster全ノードだったりworker全ノードだったり、「処理は一緒だけど対象ノードが異なり、実行するノードはまたそれと別の1台」みたいなことがあって、色々考えた結果こんな処理を編み出したという話。

github.com


なんだかんだでYAMLマークアップで普通のプログラミングとは異なり、クラスの定義だとかCのヘッダファイルとかが無いので、ロールの実装を隠ぺいするのであればコメントやドキュメントなどで引数指定の仕様をどこかで明確にしておかないと、分かりづらくなる可能性が高いので注意。


入力チェックについて(5/19追記)

入力のチェックもあるほうが良いとコメントいただきました。ありがとうございます。
関数コールっぽくするなら確かに入力チェックも行う方が普通ですね。
本記事のプレイブック/ロールでは行ってませんが、assertを使えばよさそう。

docs.ansible.com


「role (and include_role) params」について(5/19追記)

記事内で「よくわからん」と書いてたところ、よこちさん( id:akira6592 )がまた調べてくださいました。いつもありがとうございます!

tekunabe.hatenablog.jp

前述のプレイブック例に使ってみると

  roles:
  - role: sample_role
    vars:               # この"vars:"以下に書いたら
      args: curry       # 優先度12のplay vars
  - role: sample_role
    args: carbonara     # ここが優先度20のrole params

とのことでした。
ロール内でロール変数を定義している状態で実行するとこの通り。

TASK [sample_role : hello curry world] *****************************************
ok: [localhost] => 
  msg: hello ramen

TASK [sample_role : hello curry world] *****************************************
ok: [localhost] => 
  msg: hello carbonara

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

確かにvarsを使ってると上書きはできず(ramenのまま)、varsを使わずroleと同じレベルで変数名: 値をセットすると上書きできている。

そーこーかーー!!!

roleに関するドキュメントは目を通したつもりだったけど見当たらない…どこに載ってるんだろう。

docs.ansible.com

qiita.com