無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

Playbook 側から Ansible のバージョンを指定する方法(あるいは Ansible 2.x.0 を絶対使わせない方法)

f:id:muziyoshiz:20160331232512p:plain:w300

もうすぐ Ansible 2.5 がリリースされますね。僕もそろそろ Ansible 2.5 Porting Guide とか読み始めました。

ところで、僕は Ansible のバージョンが上がった最初のリリースでつまづくことが多くて、どうしてもしばらく様子見してしまいます。例えば、Ansible 2.4.0 では aws_s3 モジュールに不具合があって、既存の playbook が動かなくなったりしました(下記)。

github.com

しかし、いくら自分が注意していても、そういう経験の無い人が気軽に Ansible のバージョンを上げてしまい、あとから「動かなくなったんだけど」と言われることもあります。そんなわけで、playbook 側から実行環境の Ansible のバージョンを指定できないか?と考えてみたら、うまく動いたので紹介します。

対象読者

  • Ansible 2.x.0 を基本的に信用していない
  • 同じ Ansible playbook を操作・編集する人が自分以外にもいる
  • ある程度動作確認が終わってから、Ansible のバージョンを上げたい

実現方法

Ansible では、always という特別なタグを付けたタスクは「毎回必ず呼ばれるタスク」として扱われます。そこで、Ansible のバージョンをチェックするタスクを、この「毎回必ず呼ばれるタスク」として playbook に登録すれば、今回やりたいことを実現できます。

There is a special always tag that will always run a task, unless specifically skipped (--skip-tags always) Tags — Ansible Documentation

実行環境の Ansible のバージョン番号は、ansible_version という変数から参照できます。例えば、debug モジュールでこの変数を参照すると、以下のように出力されます。

- debug: msg="{{ ansible_version }}"
TASK [common : debug] ************************************************************************************************
ok: [localhost] => {
    "msg": {
        "full": "2.4.3.0",
        "major": 2,
        "minor": 4,
        "revision": 3,
        "string": "2.4.3.0"
    }
}

バージョン番号の4番目は個別に取得できませんが、そこまで確認する機会は考えにくいので大丈夫でしょう。

具体例

どの playbook からも常に呼ばれるロールを作ります。この例では "common" という名前にします。

まず、固定したいバージョンを表す変数 expected_ansible_version を作ります。今回は、Ansible 2.4.0 だけは使われたくないので、2.4.1 以降なら許すということにします。

roles/common/vars/main.yml

---
expected_ansible_version:
  major: 2
  minor: 4
  revision: 1

そして、この変数と ansible_version を比較するタスクを作ります。

roles/common/tasks/main.yml

---
- name: Ansible major & minor version check
  fail:
    msg:
      - Expected Ansible version is
        {{ expected_ansible_version.major }}.{{ expected_ansible_version.minor }},
        but actual version is {{ ansible_version.major }}.{{ ansible_version.minor }}
  when: not (expected_ansible_version.major == ansible_version.major
             and expected_ansible_version.minor == ansible_version.minor)
  run_once: True
  tags: always

- name: Ansible revision check
  fail:
    msg:
      - Expected Ansible version is
        {{ expected_ansible_version.major }}.{{ expected_ansible_version.minor }}.{{ expected_ansible_version.revision }}+,
        but actual version is {{ ansible_version.full }}
  when: not (expected_ansible_version.major == ansible_version.major
             and expected_ansible_version.minor == ansible_version.minor
             and expected_ansible_version.revision <= ansible_version.revision)
  run_once: True
  tags: always

この例では、revision(バージョン番号の3番目)が想定より大きい場合は許しています。また、エラーメッセージをわかりやすくするために、タスクを2個に分けていますが、Ansible revision check の方だけでも十分です。

あとは、その環境のすべての playbook で、ロールの先頭にこの common を追加すれば OK です。

playbook1.yml

---
- hosts: all
  roles:
    - common

こうすると、ansible-playbook コマンドでタグを指定してもしなくても、必ずバージョンチェックが実行されます。when でチェックを実行しているので、チェックに成功すると "skipping" と表示されます(この表示は若干わかりにくいので、他にいい方法があったら教えてください)。

$ ansible-playbook -i inventory -c local playbook1.yml

PLAY [all] ***********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

TASK [common : Ansible major & minor version check] ******************************************************************
skipping: [localhost]

TASK [common : Ansible revision check] *******************************************************************************
skipping: [localhost]

TASK [common : Example task 1] ***************************************************************************************
ok: [localhost] => {
    "msg": "Example task 1 is executed."
}

TASK [common : Example task 2] ***************************************************************************************
ok: [localhost] => {
    "msg": "Example task 2 is executed."
}

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0

$ ansible-playbook -i inventory -c local playbook1.yml --tags=tag1

PLAY [all] ***********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

TASK [common : Ansible major & minor version check] ******************************************************************
skipping: [localhost]

TASK [common : Ansible revision check] *******************************************************************************
skipping: [localhost]

TASK [common : Example task 1] ***************************************************************************************
ok: [localhost] => {
    "msg": "Example task 1 is executed."
}

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

メジャーバージョンかマイナーバージョンが合わないと処理が止まります。例えば、Ansible 2.4.3 以上を指定したのに Ansible 2.5 で実行されると、以下のように表示されます。

$ ansible-playbook -i inventory -c local playbook1.yml --tags=tag1

PLAY [all] ***********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

TASK [common : Ansible major & minor version check] ******************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": ["Expected Ansible version is 2.4, but actual version is 2.5"]}
    to retry, use: --limit @/Users/myoshiz/devel/ansible_version/playbook1.retry

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

同じく、リビジョンが小さすぎても止まります。例えば、Ansible 2.4.3 以上を指定したのに Ansible 2.4.0 で実行されると、以下のように表示されます。

$ ansible-playbook -i inventory -c local playbook1.yml --tags=tag1

PLAY [all] ***********************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

TASK [common : Ansible major & minor version check] ******************************************************************
skipping: [localhost]

TASK [common : Ansible revision check] *******************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": ["Expected Ansible version is 2.4.3+, but actual version is 2.4.0.0"]}
    to retry, use: --limit @/Users/myoshiz/devel/ansible_version/playbook1.retry

まとめ

Ansible は頻繁にバージョンアップされるので、特に問題がないなら、最新版に追従したほうがいいのは確かです。Ansible 2.5.0 を安心して使う気になれるまでの一時しのぎとして、よかったら試してみてください。

余談:Ansible 2.5 での仕様変更

Ansible 2.5 Porting Guide の冒頭に書いてありますが、include_tasks に付けられた属性(タグなど)の扱いが変わるようです。以下の記事で紹介した事象は、Ansible 2.4 でのみ発生する一時的な問題だったみたいですね。

muziyoshiz.hatenablog.com

2018-03-13追記

Ansible のバージョンチェックは1回で十分なので、run_once: True を付けました(参考)。