テックブログ

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で変数を定義する際には、
優先順位を意識し、より運用しやすいよう工夫してみて下さいね。

それではまた次の機会に。



この記事をシェアする

  • facebook
  • twitter
  • hatena
  • line
URLとタイトルをコピーする

実績数30,000件!
サーバーやネットワークなど
ITインフラのことならネットアシストへ、
お気軽にご相談ください