Ansibleにおける変数の優先順位について
お久しぶりです。mnakamuraです。 過去に何度かこの技術ブログで取り上げておりますが、 今回はAnsibleについての記事を書こうかと思います。疑問
検証環境にて、AnsibleでPHPのバージョンを変数で指定しようとした時、 ふと思いました。 「同じ変数を複数のyamlで指定した場合、どこが優先されるのだろう?」と。 今回は「同じ変数を複数のyamlで指定した場合」と、 「site.ymlに異なる方法で記載した場合」の、2種類の優先順序について検証します。準備
早速試してみます。ターゲットノード側の情報は下記。 さくらのクラウドで、RockyLinux8を用意しました。 ApacheとPHPを導入予定ですが、現在はどちらも入っておりません。 また、記事執筆時点でAppStreamから導入可能なPHPのバージョンは 「7.2」「7.3」「7.4」「8.0」となっております。
[root@mnakamura-ansible-target ~]# uname -n mnakamura-ansible-target [root@mnakamura-ansible-target ~]# cat /etc/redhat-release Rocky Linux release 8.8 (Green Obsidian) [root@mnakamura-ansible-target ~]# php -v -bash: /usr/bin/php: そのようなファイルやディレクトリはありません [root@mnakamura-ansible-target ~]# dnf module list php メタデータの期限切れの最終確認: 0:23:16 前の 2023年09月21日 17時46分39秒 に実施しました。 Rocky Linux 8 - AppStream Name Stream Profiles Summary php 7.2 [d][e] common [d] [i], devel, minimal PHP scripting language php 7.3 common [d], devel, minimal PHP scripting language php 7.4 common [d], devel, minimal PHP scripting language php 8.0 common [d], devel, minimal PHP scripting language ヒント: [d]efault, [e]nabled, [x]disabled, [i]nstalled
次に、コントロールノード側の情報です。 同じくさくらのクラウド、こちらのOSはAlmaLinux8。ansibleはcore2.14。
[root@mnakamuraAlmaTest ansible]# uname -n mnakamuraAlmaTest [root@mnakamuraAlmaTest ansible]# cat /etc/redhat-release AlmaLinux release 8.8 (Sapphire Caracal) [root@mnakamuraAlmaTest ansible]# ansible --version ansible [core 2.14.2] config file = /etc/ansible/ansible.cfg configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/lib/python3.11/site-packages/ansible ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections executable location = /usr/bin/ansible python version = 3.11.2 (main, Jun 22 2023, 06:07:18) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/usr/bin/python3.11) jinja version = 3.1.2 libyaml = True
同じ変数を複数のyamlで指定した場合の検証
[root@mnakamuraAlmaTest ansible]# pwd /etc/ansible [root@mnakamuraAlmaTest ansible]# LANG=C tree . |-- ansible.cfg |-- group_vars | `-- all.yml |-- host_vars | `-- targetnode.yml |-- hosts |-- roles | `-- php | `-- tasks | `-- main.yml `-- site.yml 5 directories, 6 files
「/etc/ansible/group_vars/all.yml」でPHP7.2を、 「/etc/ansible/host_vars/targetnode.yml」でPHP7.3を、 それぞれ変数でバージョン指定しました。
[root@mnakamuraAlmaTest ansible]# cat /etc/ansible/group_vars/all.yml --- php_version: '7.2' [root@mnakamuraAlmaTest ansible]# cat /etc/ansible/host_vars/targetnode.yml --- php_version: '7.3'
尚、「host_vars」配下は「group_vars」配下と違い「all.yml」が指定出来ません。 これは、「hosts」ファイルにて指定した実行対象ホストを読み込んでいるからです。 そもそも全てのホストに適用させたい場合は、「group_vars」配下で事足ります。 ▼参考URL https://docs.ansible.com/ansible/7/tips_tricks/sample_setup.html#alternative-directory-layout https://docs.ansible.com/ansible/2.9_ja/user_guide/intro_inventory.html#splitting-out-vars よって、「hosts」に記載しているのと同じ名称の「targetnode.yml」にしています。
[root@mnakamuraAlmaTest ansible]# cat hosts [target] targetnode ansible_host=*.*.*.* ※「hosts」の中身のIPは *.*.*.* で表記を隠しております
site.ymlの内容は下記。 rolesにはphpのみを準備。
[root@mnakamuraAlmaTest ansible]# cat site.yml --- - hosts: all gather_facts: false remote_user: root roles: - php
そして、「/etc/ansible/roles/php/tasks/main.yml」にて、 php_versionの変数でバージョン指定し、 PHPをインストールするようタスクを用意しました。
[root@mnakamuraAlmaTest ansible]# cat /etc/ansible/roles/php/tasks/main.yml - name: 指定したバージョンのPHPをインストール ansible.builtin.dnf: name: '@php:{{ php_version }}' state: present - name: パッケージをインストール ansible.builtin.dnf: name: - php - php-cli - php-common - php-devel - php-gd - php-json - php-mbstring - php-mysqlnd - php-opcache - php-pdo - php-pear - php-process - php-xml - php-fpm state: present
さて、この状態でplaybookを実行した場合、どちらバージョンのPHPが ターゲットノードに導入されるのでしょうか…? やってみましょう。
[root@mnakamuraAlmaTest ansible]# ansible-playbook site.yml -i hosts PLAY [all] ********************************************************************************************************************************************************************************************************************************** TASK ****************************************************************************************************************************************************************************************** changed: [targetnode] TASK ******************************************************************************************************************************************************************************************************* changed: [targetnode] PLAY RECAP ********************************************************************************************************************************************************************************************************************************** targetnode : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
実行ログだけではどうなったのか分かりません…ターゲットノード側を確認してみましょう。
[root@mnakamura-ansible-target ~]# php -v PHP 7.3.20 (cli) (built: Jul 7 2020 07:53:49) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.3.20, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.3.20, Copyright (c) 1999-2018, by Zend Technologies
入っているのは「php7.3」、 つまり「/etc/ansible/host_vars/targetnode.yml」が優先されるという結果でした。 次に、コントロールノード側で 「/etc/ansible/host_vars/targetnode.yml」 の読み込みを無効化した上で再度playbookを実行してみましょう。
[root@mnakamuraAlmaTest ansible]# cat /etc/ansible/host_vars/targetnode.yml --- php_version: '7.3' [root@mnakamuraAlmaTest ansible]# vi /etc/ansible/host_vars/targetnode.yml [root@mnakamuraAlmaTest ansible]# cat /etc/ansible/host_vars/targetnode.yml #--- #php_version: '7.3' [root@mnakamuraAlmaTest ansible]# ansible-playbook site.yml -i hosts PLAY [all] ********************************************************************************************************************************************************************************************************************************** TASK ****************************************************************************************************************************************************************************************** changed: [targetnode] TASK ******************************************************************************************************************************************************************************************************* ok: [targetnode] PLAY RECAP ********************************************************************************************************************************************************************************************************************************** targetnode : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
同じようにターゲットノード側で確認してみます。 すると…
[root@mnakamura-ansible-target ~]# php -v
PHP 7.2.24 (cli) (built: Oct 22 2019 08:28:36) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.2.24, Copyright (c) 1999-2018, by Zend Technologies
入っているのは「php7.2」、つまり「/etc/ansible/group_vars/all.yml」が効いているようです。 この結果より、 host_vars/targetnode.yml > group_vars/all.yml の優先順位になっている事が確認出来ました。 同じ変数を指定した場合、「host_vars」配下の方が優先されるようです。 では次に、変数を使わない場合は、 site.yml上で優先順位はどのように決まるのでしょう?site.ymlに異なる方法で記載した場合
ディレクトリを移動し、もう一つの検証環境に切り替えます。
[root@mnakamuraAlmaTest ansible]# pwd /etc/ansible [root@mnakamuraAlmaTest ansible]# cd /etc/ansible2 [root@mnakamuraAlmaTest ansible2]# pwd /etc/ansible2 [root@mnakamuraAlmaTest ansible2]# LANG=C tree . |-- ansible.cfg |-- hosts |-- rocky8.yml |-- roles | |-- php7.4 | | `-- tasks | | `-- main.yml | `-- php8.0 | `-- tasks | `-- main.yml `-- site.yml 5 directories, 6 files : ok=8 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
今度は先程とは違い、「group_vars」や「host_vars」は存在せず、 代わりにrolesの配下で「php7.4」と「php8.0」を導入するタスクを作成しました。
[root@mnakamuraAlmaTest ansible2]# cat roles/php7.4/tasks/main.yml - name: 指定したバージョンのPHPをインストール(php7.4配下) ansible.builtin.dnf: name: '@php:7.4' state: present - name: パッケージをインストール(php7.4配下) ansible.builtin.dnf: name: - php - php-cli - php-common - php-devel - php-gd - php-json - php-mbstring - php-mysqlnd - php-opcache - php-pdo - php-pear - php-process - php-xml - php-fpm state: present [root@mnakamuraAlmaTest ansible2]# cat roles/php8.0/tasks/main.yml - name: 指定したバージョンのPHPをインストール(php8.0配下) ansible.builtin.dnf: name: '@php:8.0' state: present - name: パッケージをインストール(php8.0配下) ansible.builtin.dnf: name: - php - php-cli - php-common - php-devel - php-gd - php-json - php-mbstring - php-mysqlnd - php-opcache - php-pdo - php-pear - php-process - php-xml - php-fpm state: present
上記の通り、どちらのバージョンもディレクトリ構造や実行内容は、 バージョンやコメント以外は差異がありません。 ただし、「site.yml」にて、php8.0はそのままrolesで指定しており、 php7.4は「- import_playbook: rocky8.yml」にて、 一度「rocky8.yml」というyamlファイルを経由してから実行しています。
[root@mnakamuraAlmaTest ansible2]# cat site.yml --- - hosts: all gather_facts: false remote_user: root roles: - php8.0 - import_playbook: rocky8.yml [root@mnakamuraAlmaTest ansible2]# cat rocky8.yml --- - hosts: target become: yes roles: - php7.4
さてこの場合、site.ymlにて、どちらが優先されるでしょうか? playbookを実行してみましょう。
[root@mnakamuraAlmaTest ansible2]# ansible-playbook site.yml -i hosts PLAY [all] ********************************************************************************************************************************************************************************************************************************** TASK [php8.0 : 指定したバージョンのPHPをインストール(php8.0配下)] *************************************************************************************************************************************************************************** changed: [targetnode] TASK [php8.0 : パッケージをインストール(php8.0配下)] **************************************************************************************************************************************************************************************** ok: [targetnode] PLAY [target] ******************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************** ok: [targetnode] TASK [php7.4 : 指定したバージョンのPHPをインストール(php7.4配下)] *************************************************************************************************************************************************************************** changed: [targetnode] TASK [php7.4 : パッケージをインストール(php7.4配下)] **************************************************************************************************************************************************************************************** ok: [targetnode] PLAY RECAP ********************************************************************************************************************************************************************************************************************************** targetnode : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ログを見る限り、php7.4のインストールが後から実行されているように見えます。 ターゲットノード側を確認してみましょう。
[root@mnakamura-ansible-target ~]# php -v PHP 7.4.33 (cli) (built: Oct 31 2022 10:36:05) ( NTS ) Copyright (c) The PHP Group Zend Engine v3.4.0, Copyright (c) Zend Technologies with Zend OPcache v7.4.33, Copyright (c), by Zend Technologies
「php7.4」が入っています。 予想通り、「php7.4」が入っていますね。 …しかしこれ、実を言うとsite.ymlの記載順序の問題だったりします。 逆にしてみましょう。
[root@mnakamuraAlmaTest ansible2]# cat site.yml --- - hosts: all gather_facts: false remote_user: root roles: - php8.0 - import_playbook: rocky8.yml [root@mnakamuraAlmaTest ansible2]# vi site.yml [root@mnakamuraAlmaTest ansible2]# cat site.yml --- - import_playbook: rocky8.yml - hosts: all gather_facts: false remote_user: root roles: - php8.0
これでplaybook実行、すると…
[root@mnakamuraAlmaTest ansible2]# ansible-playbook site.yml -i hosts PLAY [target] ******************************************************************************************************************************************************************************************************************************* TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************** ok: [targetnode] TASK [php7.4 : 指定したバージョンのPHPをインストール(php7.4配下)] *************************************************************************************************************************************************************************** ok: [targetnode] TASK [php7.4 : パッケージをインストール(php7.4配下)] **************************************************************************************************************************************************************************************** ok: [targetnode] PLAY [all] ********************************************************************************************************************************************************************************************************************************** TASK [php8.0 : 指定したバージョンのPHPをインストール(php8.0配下)] *************************************************************************************************************************************************************************** changed: [targetnode] TASK [php8.0 : パッケージをインストール(php8.0配下)] **************************************************************************************************************************************************************************************** ok: [targetnode] PLAY RECAP ********************************************************************************************************************************************************************************************************************************** targetnode : ok=5 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
実行したログを確認すると、今度は「php8.0」が後から実行されています。 ターゲットノード側も見てみましょう。
[root@mnakamura-ansible-target ~]# php -v PHP 8.0.27 (cli) (built: Jan 3 2023 16:17:26) ( NTS gcc x86_64 ) Copyright (c) The PHP Group Zend Engine v4.0.27, Copyright (c) Zend Technologies with Zend OPcache v8.0.27, Copyright (c), by Zend Technologies
「php8.0」が入っています。 変数で指定していない場合は、単純にsite.ymlの内容は上から順に実行されます。 重複するような内容があった場合は、後から実行した内容で上書きされるのです。 …実はこれ、優先順位はドキュメントに記載されてたりします。 ▼参考URL https://docs.ansible.com/ansible/2.9_ja/user_guide/playbooks_variables.html?highlight=variable%20precedence#ansible-variable-precedence 同じ「group_vars」配下であっても、 それがinventory配下だったり、playbookだったりしても変わります。 今回は記載していませんが、「role defaults」よりは上の優先順位だったりしますので、 roles配下で個別設定をする際には、より上位の変数が効いていないか等、 しっかりチェックしておきましょう。 皆様もansibleで変数を定義する際には、 優先順位を意識し、より運用しやすいよう工夫してみて下さいね。 それではまた次の機会に。