DMMグループ Advent Calendar 2021 4日目の記事です。
DMMの動画配信におけるインフラを担当している菅野です。
普段はオンプレで物理サーバ買ったりいじったりしてますが、最近ではGCPをいろいろ触ったりもしてます。
今回はオンプレサーバを管理するためにansibleを触っていたとき少しハマってしまった内容を記事にします。
事の起こり
サーバに入れてるミドルウェアのアップデートしたいので、ansibleでミドルウェアインストール用に切り出した role
にフラグ判定で latest
を入れる処理を入れてみました。
nginx.yml
--
tasks:
# nginxを入れるtask
- name: install nginx
yum:
name: nginx
state: present
# nginx.updateがtrueならlatestを入れることで最新版に上げる処理
- name: update nginx
yum:
name: nginx
state: latest
when: ( nginx.update | default(false) | bool )
group_vars/all.yml
--
nginx:
update: true
これで nginx.update
に true
が入り本来であれば latest
の方も実行されて最新版に更新される想定でした。
しかし、実際の実行結果では nginx.update != true
と判定され skip
になりました。
PLAY [all] ********************************************************************************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************************************************************************
ok: [server01]
TASK [install nginx] **********************************************************************************************************************************************************************************************************************************************************************************
changed: [server01]
TASK [update nginx] ***********************************************************************************************************************************************************************************************************************************************************************************
skipping: [server01]
PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************************************
server01 : ok=3 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
原因
ansibleの変数は基本的にjinja2のテンプレートで処理が実施されるのですが、中身的にはpythonのdict型なため、pythonのdict型に組み込まれているメソッドがそのまま組み込まれています。
そして、そのメソッド名と同じ変数名をつけると外から見る分には見れるのですが、実際に参照しようと思うとメソッド呼び出し扱いになり、想定外の挙動になりました。
試しに hoge
というdictを用意してその下に update, upgrade, var1
というkeyで値を設定し、 hoge
全体と各key毎の値を出力してみました。
group_vars/all.yml
--
hoge:
update: "update"
upgrade: "upgrade"
var1: "var1"
echo-vars.yml
--
- hosts: all
tasks:
- name: debug hoge
debug:
var: hoge
- name: debug hoge.var1
debug:
var: hoge.var1
- name: debug hoge.upgrade
debug:
var: hoge.upgrade
- name: debug hoge.update
debug:
var: hoge.update
上記の playbook
を実行した結果が下記になります。
PLAY [all] ********************************************************************************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************************************************************************
ok: [server01]
TASK [debug hoge] *************************************************************************************************************************************************************************************************************************************************************************************
ok: [server01] => {
"hoge": {
"update": "update",
"upgrade": "upgrade",
"var1": "var1"
}
}
TASK [debug hoge.var1] ********************************************************************************************************************************************************************************************************************************************************************************
ok: [server01] => {
"hoge.var1": "var1"
}
TASK [debug hoge.upgrade] *****************************************************************************************************************************************************************************************************************************************************************************
ok: [server01] => {
"hoge.upgrade": "upgrade"
}
TASK [debug hoge.update] ******************************************************************************************************************************************************************************************************************************************************************************
ok: [server01] => {
"hoge.update": "<built-in method update of dict object at 0x7f559978d240>"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************************************
server01 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
見て分かる通り update
だけが built-in method
扱いになっていることが分かります。
しかし、hoge
全体で参照しようと思うと見れてしまうことに加えて when
で処理する際もエラー等にはならず処理自体は通ってしまうため、デバッグでも見落としやすいです。
他のbuilt-in method
pythonのドキュメントから見て判断可能だった組み込みメソッドは下記の通りです。
Built-in Types|docs.python.org
- clear
- copy
- get
- items
- keys
- pop
- popitem
- setdefault
- update
- values
試しに上記の一覧全てを同様に変数出力してみました。
group_vars/all.yml
--
hoge:
update: "update"
upgrade: "upgrade"
var1: "var1"
PLAY [all] *************************************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************************************
ok: [server01]
TASK [debug hoge] ******************************************************************************************************************************************************
ok: [server01] => {
"hoge": {
"update": "update",
"upgrade": "upgrade",
"var1": "var1"
}
}
TASK [clear] ***********************************************************************************************************************************************************
ok: [server01] => {
"hoge.clear": "<built-in method clear of dict object at 0x7feaab32b240>"
}
TASK [copy] ************************************************************************************************************************************************************
ok: [server01] => {
"hoge.copy": "<built-in method copy of dict object at 0x7feaab2c2678>"
}
TASK [get] *************************************************************************************************************************************************************
ok: [server01] => {
"hoge.get": "<built-in method get of dict object at 0x7feaac09d480>"
}
TASK [items] ***********************************************************************************************************************************************************
ok: [server01] => {
"hoge.items": "<built-in method items of dict object at 0x7feaab827090>"
}
TASK [keys] ************************************************************************************************************************************************************
ok: [server01] => {
"hoge.keys": "<built-in method keys of dict object at 0x7feaab2c3510>"
}
TASK [pop] *************************************************************************************************************************************************************
ok: [server01] => {
"hoge.pop": "<built-in method pop of dict object at 0x7feaab2d9a20>"
}
TASK [popitem] *********************************************************************************************************************************************************
ok: [server01] => {
"hoge.popitem": "<built-in method popitem of dict object at 0x7feaab760e58>"
}
TASK [setdefault] ******************************************************************************************************************************************************
ok: [server01] => {
"hoge.setdefault": "<built-in method setdefault of dict object at 0x7feaab795a68>"
}
TASK [update] **********************************************************************************************************************************************************
ok: [server01] => {
"hoge.update": "<built-in method update of dict object at 0x7feaab2dab88>"
}
TASK [values] **********************************************************************************************************************************************************
ok: [server01] => {
"hoge.values": "<built-in method values of dict object at 0x7feaab273090>"
}
PLAY RECAP *************************************************************************************************************************************************************
server01 : ok=12 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
定義していないkeyについてもメソッドとして定義があるため参照することができ、全てが組み込みメソッド扱いとしてメッセージ表示される結果となりました。
まとめ
社内では結構クラウド寄りな記事が多そうだったので、あえてAnsibleを題材にヒトネタ書いてみました。
jinja2に潜む意外な罠でしたが、考えてみると「そうだよなあ」となりそうなものなのに、dict全体であれば変数が見れてしまうためハマってしまうと意外とすぐに気付けず苦労をしました。
また、ansibleだとデバッグ方法もtaskの間にdebugモジュールの処理を混ぜるぐらいしかできず、結構しづらいこともあるかと思います。それもまたハマりどころの一因かもしれないので良い方法があれば採用していきたいですね。
今回の記事の検証は下記のリポジトリにもまとめていますので、気になる方は試してみてください。