2016.03.11

実サービスで利用中のAnsible 1系をAnsible 2.0にアップグレードすると、何が起こるか?


Ansible 2.0

次世代システム研究室の DevOps ネタ担当の M. Y. です。

今年の1月に、構成管理ツール Ansible のメジャーバージョンアップ版である Ansible 2.0 がリリースされました。

この Ansible 2.0 では今後の機能追加を容易にするために、内部構造がほぼ一から書き直されています。その成果として、複数のタスクをまとめる block 構文や、200個を超える新規モジュールなど、多数の機能が追加されました。その一方、既存の playbook(処理内容が書かれた YAML ファイル)との後方互換性を維持することも、Ansible 2.0 の大きなゴールとして謳われています。

私たちも、いくつかのサービスで Ansible を実際に使用しており、そのうちの一つで Ansible を 1.8.2 から 2.0.1 へとアップグレードしました。今回は、このアップグレードの前に行った準備や、アップグレード時に起こったことをご紹介します。これから Ansible 2 系に移行される方の参考になれば幸いです。

Ansible 2.0 に関する情報源


Ansible 2.0 に関しては、主に Ansible 公式のドキュメントを参考にしました。また、Ansible 2.0 の紹介スライドや、既に Ansible 2.0 に移行された方の記事も参考にさせていただきました。

事前の動作確認


気になる後方互換性:include されたファイルに書かれているタグやハンドラは 2.0 でも動くのか?


Ansible 2.0 は既存の playbook との後方互換性を目指したものの、互換性を維持できなかった部分が一部残っているとのことで、その内容は Ansible 2.0 Has Arrived の “Known Issues with Ansible 2.0” の節にまとめられています。このなかで個人的に特に気になったのは “Dynamic Include Problems” の部分でした。以下にその部分を抜粋します。

Dynamic Include Problems

今回のリファクタリングを通じ、includeされるタスクは実行時に評価されることになり、Ansibleはincludeして実行されるタスクを事前に知ることができなくなりました。
このことはいくつかの状況において問題(将来のバージョンでは修正される予定です)を引き起こしてきます。
  • タスクに設定されたタグはincludeが実行されるまで見られることがないため、includeされる個々のタスクではなくincludeに対してタグを設定しないと評価されません。–list-tags オプションについても同様です。
  • include内部のHandlerからはnotifyが機能しません。そのため、includeとHandlerの同時利用を避ける必要があります。

Ansible 2.0 Has Arrived日本語訳より抜粋)

この部分については原文を読んでも、include されたファイルに書かれているタグやハンドラは実行時に評価されるのか、実行時にも無視されてしまうのか、いまいちよく分かりませんでした。そこで、Ansible 2.0 へ移行する前に、簡単な playbook を書いて、Ansible 2.0 での include タスクの動作を検証してみました。

include タスクの検証:Ansible 1.9.4 と 2.0.1 で同じ playbook を動かしてみる


以下の3種類の playbook を用意して、それぞれ Ansible 1.9.4 と 2.0.1 で実行しました。

Playbookタグハンドラ
playbook1.ymltasks/main.yml 内の include タスクに tags を付与handlers/main.yml 内で定義
playbook2.ymltasks/main.yml から include されるファイル内の各タスクに tags を付与handlers/main.yml 内で定義
playbook3.ymltasks/main.yml 内の include タスクに tags を付与handlers/main.yml から include されるファイル内で定義


playbook1.yml から読み込む main.yml は、以下のように application.yml を include します。
- name: Install our web application
  include: application.yml
そして application.yml のなかで、以下のように tags を付与します。今回調べたかったのは、これが Ansible 2.0.1 でも問題なく動作するのかどうか?という点です。
- name: Install our web application 1
  command: echo "Web application 1 is installed." 
  notify:
    - Restart web application
  tags: application1
- name: Install our web application 2
  command: echo "Web application 2 is installed." 
  notify:
    - Restart web application
  tags: application2

一方、playbook2.yml から読み込む main.yml では、以下のように include タスク自体に tags を付与しました。これは Ansible 2.0.1 でも動作するはずです。
- name: Install our web application 1
  include: application1.yml
  tags: application1
- name: Install our web application 2
  include: application2.yml
  tags: application2

playbook3.yml は、ハンドラを handlers/main.yml から include される handlers/application.yml のなかで定義しました。task ディレクトリ以下は playbook1.yml と同一にしました。

今回の検証に使った playbook は GitHub で公開しましたので、詳細に興味のある方は ansible-2.0-sample: Sample playbooks for Ansible 2.0 をご覧ください。

include タスクの検証結果


検証にあたっては、以下の3種類のコマンドを実行しました。
  • –list-tag オプションを指定して実行
    • $ ansible-playbook playbook1.yml -c local -i inventory --list-tags
    • 期待される動作:2個のタグ “application1”, “application2” が表示される
  • タグを指定せずにタスクを実行
    • $ ansible-playbook playbook1.yml -c local -i inventory
    • 期待される動作:application1, application2 のインストール後に、handler で定義された Web サーバの再起動が行われる
  • タグ “application1” を指定してタスクを実行
    • $ ansible-playbook playbook1.yml -c local -i inventory -t application1
    • 期待される動作:application1 のインストール後に、handler で定義された Web サーバの再起動が行われる

実行結果は以下の通りです。

Ansible のバージョンPlaybook--list-tagsタグを指定せずにタスクを実行タグ "application1" を指定してタスクを実行
Ansible 1.9.4playbook1.yml[application1, application2]期待通りの動作期待通りの動作
playbook2.yml[application1, application2]期待通りの動作期待通りの動作
playbook3.yml[application1, application2]期待通りの動作期待通りの動作
Ansible 2.0.1playbook1.yml[]期待通りの動作期待通りの動作
playbook2.yml[application1, application2]期待通りの動作期待通りの動作
playbook3.yml[]ハンドラはエラーなしで無視されたハンドラはエラーなしで無視された


結論としては、include されるファイル内の各タスクに tags を付与しても、実用上は問題ないようです。その一方で、include されるファイル内で定義されたハンドラはエラーも出さずに無視されました。こちらは、移行前の書き換えが必須のようです。

今回の移行対象のサービスを調べたところ、前者のタスクの事例はあったものの、後者のハンドラの事例はありませんでした。また、今回の移行対象には Ansible 2.0 Has Arrived に書かれているバックスラッシュの問題などもありませんでした。そのため、まずは playbook を何も修正せず、Ansible 2.0 への移行を試してみることにしました。

Ansible 2.0 への移行


今回の移行対象


Playbook の事前確認も終わったので、いよいよ移行です。今回は、CentOS 6.5 および Ansible 1.8.2 の組合せで動作中の CI サーバを、Ansible 2.0.1 にアップグレードしました。

また、今回はこのサービスの playbook のうち、開発環境と本番環境で共通して用いている主要な playbook 6件を対象に、アップグレード前後の動作確認を行いました。各 playbook に含まれるタスク数は、数件~100件程度でした。

移行手順


以下の手順で、Ansible を 1.8.2 → 1.9.4 → 2.0.1 の順にアップグレードしました。Ansible 1.9.4 を経由させたのは、2.0.1 がうまく動作しなかった場合の原因切り分けのためです。

  1. 現在インストールされている Ansible 1.8.2 で、移行対象に選んだ playbook を一通り実行し、実行結果をテキストファイルに記録する
  2. Ansible 1.9.4 にアップグレードする
  3. Ansible 1.9.4 で、移行対象に選んだ playbook を一通り実行し、実行結果をテキストファイルに記録する
  4. Ansible 2.0.1 にアップグレードする
  5. Ansible 2.0.1 で、移行対象に選んだ playbook を一通り実行し、実行結果をテキストファイルに記録する
  6. Ansible 2.0.1 で FAILED または WARNING が出るタスクがあったら、出なくなるまで playbook を修正する

個々のタスクの実行結果を自動的にテストする準備はさすがにできなかったので、テキストファイルに出力した実行結果を、あとから比較しました(原始的な方法ではありますが……)。また、実行結果を細かく調べるために、今回は ansible-playbook コマンドに -v (–verbose) オプションを付けて実行しました。

Ansible 1.8.2 と 1.9.4 の結果の比較


CentOS では、以下のコマンドで Ansible 1.9.4 にアップグレードできます。

# yum install ansible
(中略)
# ansible --version
ansible 1.9.4
  configured module search path = None

Playbook の実行を試してみた結果、Ansible 1.8.2 と 1.9.4 では、実行時刻以外の出力はほとんど同じになりました。両方とも Ansible 1 系なので当然といえば当然なのですが、とりあえずこれで安心して Ansible 2 系を試せることがわかりました。

Ansible 1.9.4 と 2.0.1 の結果の比較


CentOS では、以下のコマンドで Ansible 2.0.1 にアップグレードできます。Ansible 2 系をインストールしたい場合は、--enablerepo=epel-testing オプションが必要です。

# yum install ansible --enablerepo=epel-testing
(中略)
# ansible --version
/usr/lib64/python2.6/site-packages/pycrypto-2.6.1-py2.6-linux-x86_64.egg/Crypto/Util/number.py:57: PowmInsecureWarning: Not using mpz_powm_sec.  You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.
  _warn("Not using mpz_powm_sec.  You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.", PowmInsecureWarning)
ansible 2.0.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides

インストールには成功したのですが、実行時に警告が出てしまいました。以下のように python-crypto2.6 のアップグレードも行うことで、この警告は出なくなりました。

# yum update --enablerepo=epel-testing python-crypto2.6-2.6.1-2.el6
(中略)
# ansible --version
ansible 2.0.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides

この状態で playbook の実行を試してみたところ、Ansible 1 系と 2 系では標準出力が若干変わっているようで、出力の diff がほとんど一致しない、という結果になってしまいました……。仕方ないので Ansible 2.0.1 の出力を1系に似た形に変換し、再度 diff を取ったところ、すべての playbook の実行に成功していました。素晴らしい!

しかし、実行は成功したものの、Ansible 1 系では出なかった WARNING が大量に出てきてしまいました。仕方ないので、WARNING の内容を1個1個確認していくことに。

Playbook を修正して解決した WARNING


  • その1. 廃止予定の sudo/sudo_user に関する WARNING
  • その2. when 行が2行続いていた場合の WARNING
  • その3. command/shell モジュールに関する WARNING

その1. 廃止予定の sudo/sudo_user に関する WARNING


まず、誰もが出くわしそうな WARNING として、以下の WARNING が発生しました。

【2.0 実行時の WARNING】
[DEPRECATION WARNING]: Instead of sudo/sudo_user, use become/become_user and 
make sure become_method is 'sudo' (default). This feature will be removed in a 
future release. Deprecation warnings can be disabled by setting 
deprecation_warnings=False in ansible.cfg.

これは、playbook の冒頭で sudo を使っていることが原因で、以下の sudo: yes を単純に become: yes に書き換えることで解決しました。become/become_user は Ansible 1.9 から導入されているのですが、2.0 から WARNING を出すようになったみたいです。

【修正前】
- hosts: batch_servers
  roles:
   - batch 
  sudo: yes

その2. when 行が2行続いていた場合の WARNING


普通あまりこういう書き方はしないと思いますが、今回の移行対象では、when を2行に分けて書いている箇所がありました。例えば以下のように書いたタスクは、sample_flag という変数が定義されていて、かつ値が true の場合に実行されます。

【修正前】
- name: create sample configuration
  template: src=sample_conf.j2 dest=/etc/sample_conf
  tags: sample
  when: sample_flag is defined
  when: sample_flag

しかし、Ansible 2.0 では、以下のように「最後に定義された when しか有効にならない」旨の WARNING が表示されました。

【2.0 実行時の WARNING】
[WARNING]: While constructing a mapping from
/home/provision/honeybee/ansible/site/roles/batch/tasks/main.yml, line 86,
column 3, found a duplicate dict key (when).  Using last defined value only.

playbook を以下のように書き換えることで、この WARNING は回避できました。

【修正後】
- name: create sample configuration
  template: src=sample_conf.j2 dest=/etc/sample_conf
  tags: sample
  when: sample_flag is defined and sample_flag

その3. command/shell モジュールに関する WARNING


他のモジュールで書き換え可能なコマンドを command または shell モジュールで実行していると、WARNING が表示されるようになりました(この command_warnings 機能自体は Ansible 1.8 から入ったようですが、2.0 でデフォルトの設定が変わった?)。

今回試した範囲では、例えば以下のような WARNING が表示されました。

【2.0 実行時の WARNING】
[WARNING]: Consider using file module with mode rather than running chmod
[WARNING]: Consider using file module with state=absent rather than running rm
[WARNING]: Consider using service module rather than running service
[WARNING]: Consider using unarchive module rather than running tar
[WARNING]: Consider using git module rather than running git

メッセージに書かれているように、command/shell モジュールを使わないようにタスクを書き換えることで、これらの警告は出なくなりました。例えば、

【修正前】
    - template: src="templates/nginx/nginx.sh" dest="/etc/init.d/nginx" owner=root group=root
    - command: chmod 755 /etc/init.d/nginx

と書いてあった部分を、template の mode パラメータでパーミッション変更するように書き換えると、chmod に関する警告は出なくなりました。

【修正後】
    - template: src="templates/nginx/nginx.sh" dest="/etc/init.d/nginx" owner=root group=root mode=0755

一般的に、command/shell モジュールで書かれたタスクを別のモジュールに書き直すと、本当に変更があった場合だけ “changed” とカウントされるようになります。例えば上に書いた template の例で言うと、変更前は command モジュールの結果が常に “changed” になってしまいますが、変更後はテンプレートの内容が変わった場合だけ “changed” になります。

ansible.cfg に command_warnings = False と書けばこの警告は抑止できますが、playbook をリファクタリングする良い機会だと思って、Ansible 2.0 への移行時に直してしまうことをお勧めします。

Playbook を修正せず、解決を保留した WARNING


番外. command/shell モジュールで root ユーザ以外でのコマンド実行


以下のような、shell モジュールで su コマンドを実行している箇所で WARNING が表示されました。

【修正前】
  shell: su - sample_user -c 'sample_command arg1 arg2 arg3'

【2.0 実行時の WARNING】
 [WARNING]: Consider using 'become', 'become_method', and 'become_user' rather
than running su

しかし、この環境では Ansible 実行用のユーザから root 以外のユーザ(この例では sample_user)に直接 su できない設定にしていたため、以下のように書き換えると動かなくなってしまいます。

【修正後(これは動かない)】
  shell: sample_command arg1 arg2 arg3
  become: yes
  become_user: sample_user

/etc/sudoers を変更するという手もありますが、今回は以下のように warn=no を付けて、このタスクだけ警告を抑止するようにしました。warn オプションは command_warnings と同じく 1.8 から追加されたようです。

【修正後(これは動く)】
  shell: su - sample_user -c 'sample_command arg1 arg2 arg3' warn=no

まとめ


実サービスで利用中のAnsible 1系をAnsible 2.0にアップグレードすると、何が起こるか? 私たちの環境に関しては、Ansible 2.0 Has Arrived に書かれている範囲のことを対策しておけば、既存 playbook はちゃんと動く(ただし WARNING は大量に出る)、という結果になりました。

Ansible はエージェントレスな構成管理ツールのため、アップグレード対象は CI サーバのみでした。そのため、今回は後方互換性に影響するメジャーアップグレードでしたが、「いざ問題発生しても、すぐ元に戻せる」という気楽さがありました。

とはいえ、本番利用しているなら Ansible 1 系と Ansible 2 系の動作は、一応比較してみる必要があるでしょう。Ansible 1 と 2 では出力が若干変わっているため、diff での単純比較は難しいです。1個の playbook にタスクを詰め込んでいると動作の比較が大変なので、巨大な playbook(例えばタスク100個~)は事前に分割しておくことをお勧めします。

今回の結果を見る限り、Ansible 2.0 の後方互換性は高そうです。システムが小規模で、Ansible 2.0 に興味がある方は、移行にチャレンジしてみても良いのではないでしょうか。私も、これから Ansible 2.0 の新機能・新モジュールを実サービスで活用してみて、また何か面白いネタが出てきたらご紹介したいと思います。


次世代システム研究室では、グループ全体のインテグレーションを支援してくれるアーキテクトを募集しています。インフラ設計、構築経験者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。