レッドハットでコンサルタントをしている id:nanodayo です。 赤帽エンジニア Advent Calendar 2019の8日目のエントリーです。
ご存知、Ansible のplaybookはYAML形式で記述しますが、YAMLのAnchor と Aliasも適用できるのでご紹介します。 なお、多用すると複雑なplaybookになり保守しにくくなるので強くはおすすめはしません。
YAMLのAnchorとAlias
同じような記述をたくさん書くときに便利な記法です。 以下のような仮想マシンの変数定義(vars)があり、殆ど同じパラメータを設定するので、共通設定として一箇所で書きたいとします。 (説明用に作った物なのでデータ構造は適当です。)
vars:
vms:
- hostname: "test0.example.com"
image: "cirros"
flavor: "m1.tiny"
az: "az1"
nics:
- name: "net0"
- name: "net1"
- hostname: "test1.example.com"
image: "cirros"
flavor: "m1.tiny"
az: "az1"
nics:
- name: "net0"
- name: "net1"
- hostname: "test2.example.com"
image: "cirros"
flavor: "m1.tiny"
az: "az1"
nics:
- name: "net3"
このような定義の場合、例えばimage の定義を全台違うものにするには3箇所に変更が必要なってしまうので 雛形を決めて、1箇所だけで管理できるようにしたいケースでAnchorとAliasを使います。
---
- name: yaml anchor and alias test
hosts: all
gather_facts: no
vars:
vms:
# Anchorに登録
- &test
hostname: "test0.example.com"
image: "cirros"
flavor: "m1.tiny"
az: "az1"
nics:
- name: "net0"
- name: "net1"
# 全く同じ内容を参照
- *test
# 参照先で部分的に上書き
- hostname: "test1.example.com"
<<: *test
# 参照先で部分的に上書き2
- hostname: "test2.example.com"
# list はlistごと上書き
nics:
- name: "net3"
<<: *test
tasks:
- name: display vms data
debug:
var: vms
実行すると以下のようなデータとして扱われていることがわかります。
TASK [display vms data] **********************************************************************************************************************
ok: [localhost] => {
"vms": [
{
"az": "az1",
"flavor": "m1.tiny",
"hostname": "test0.example.com",
"image": "cirros",
"nics": [
{
"name": "net0"
},
{
"name": "net1"
}
]
},
{
"az": "az1",
"flavor": "m1.tiny",
"hostname": "test0.example.com",
"image": "cirros",
"nics": [
{
"name": "net0"
},
{
"name": "net1"
}
]
},
{
"az": "az1",
"flavor": "m1.tiny",
"hostname": "test1.example.com",
"image": "cirros",
"nics": [
{
"name": "net0"
},
{
"name": "net1"
}
]
},
{
"az": "az1",
"flavor": "m1.tiny",
"hostname": "test2.example.com",
"image": "cirros",
"nics": [
{
"name": "net3"
}
]
}
]
}
test0.example.comの内容をひな形にして参照し、test1.example.com, test2.example.comのように参照先で定義している値で上書きされてる事がわかります。
データ構造全部まるごとAnchorに登録ではなく、部分的に使うこともできます。 部分的なAnchorを追加したサンプルは以下。
vars:
vms:
# Anchorに登録。部分的なものも追加
- &test
hostname: "test0.example.com"
image: &image "cirros"
flavor: "m1.tiny"
az: &az "az1"
nics: &nics
- name: "net0"
- name: "net1"
# 全く同じ内容を持ってくる
- *test
# 参照先で部分的に上書き
- hostname: "test1.example.com"
<<: *test
# 参照先で部分的に上書き2
- hostname: "test2.example.com"
# list はlistごと上書き
nics:
- name: "net3"
<<: *test
# 部分的なAnchor を参照
- hostname: "test3.example.com"
image: *image
flavor: "m1.tiny"
az: *az
nics: *nics
vars に限らず task の記述に対しても適用ができます。
---
- name: yaml anchor and alias test
hosts: all
gather_facts: no
tasks:
- name: sample task block
block: &task_block
- &task
name: sample task in block 1
debug: &module
msg: "sample message 1"
ignore_errors: yes
- name: sample task in block 2
debug:
msg: "sample message 2"
<<: *task
- name: sample task out of block
debug:
<<: *module
- name: sample task block(alias)
block: *task_block
とまぁ一見便利な記法なのですが、Ansible playbookで使う場合に関しては
- 同じ値を一箇所で定義するだけであれば、varsの値に変数を定義すれば可能
- 特定のホスト群でのデフォルト値を設定したい場合は group vars や role default 等が登録できる。
- 複数task をまとめたものを再利用したいならファイルを分けて include_playbook などで読み出せる
と、Ansible側の機能で事足りる場合もあります。
GitLab CIでAnchor Aliasを使う
Ansible以外ですと、GitLab CIのYAMLファイルを記載する際、パイプラインの処理で共通の内容が多く定義するのにお勧めできます。 以下、GitLab CIのパイプラインの定義例です。 細かな説明はしませんが、パイプラインに必要な前処理(before_script)等が、どのパイプラインでも同じというケースが多いです。
.Unit_test_template: &unit_test_template
stage: unit_test
only:
- staging
- master
image:
name: unittest_container
before_script:
- eval $(ssh-agent -s)
- echo "${CICD_SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add - > /dev/null
- echo -e "Host *\n\tPort 22\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- ansible -c local tests/unit_test_presetup.yml
after_script:
- ansible -c local tests/unit_test_cleanup.yml
tags:
- container-server
Unit_test1:
<<: *unit_test_template
script:
- bash unit_test1.sh
Unit_test2:
<<: *unit_test_template
script:
- bash unit_test2.sh
なお、GitLab側のドキュメントにもっと良いサンプルが載っています。
https://docs.gitlab.com/ce/ci/yaml/#special-yaml-features
おまけ
Anchor / Alias以外のYAMLの特徴で、Ansibleで使えるか試してみたもの紹介します。
変数の値の型を指定する
通常、シングルクォートやダブルクォート でくくっていれば文字列、そうでなければ数値として解釈されるので、あまり明示的に指定するケースはないと思われますが、以下のように値の前に !!str 等を書けば指定ができるようです。
---
- name: yaml test
hosts: all
gather_facts: no
vars:
string_test: !!str 1234
int_test: !!int 1234
tasks:
- name: step1 string_test
debug:
msg: "{{ string_test }} is {{ string_test | type_debug }}"
- name: step2 int_test
debug:
msg: "{{ int_test }} is {{int_test | type_debug}}"
...
実行結果
# ansible-playbook -i hosts yaml-test.yml
PLAY [yaml test] *****************************************************************************************************************************
TASK [step1 string_test] *********************************************************************************************************************
ok: [localhost] => {
"msg": "1234 is AnsibleUnicode"
}
TASK [step2 int_test] ************************************************************************************************************************
ok: [localhost] => {
"msg": "1234 is int"
}
PLAY RECAP ***********************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
... を使って好きな場所で終了(はできない)
YAMLの文法上、--- が開始 ... が終了となっています。
省略も可能なので、特にplaybookでは ... を書かないケースが多いです。
「playbookのデバッグ時に、止めたい箇所に ... を入れれば exit命令的に使えるのでは」と思って試しましたが、... より後にコメントではないものを書くとsyntax errorになります。
# これは動きません
---
- name: yaml test
hosts: all
gather_facts: no
vars:
string_test: !!str 1234
int_test: !!int 1234
tasks:
- name: step1 string_test
debug:
msg: "{{ string_test }} is {{ string_test | type_debug }}"
...
- name: step2 int_test
debug:
msg: "{{ int_test }} is {{int_test | type_debug}}"
yamllintでも同様にNGだったので、YAML的はそういう使い方を想定してないのでしょう。