playbookのテストで利用できるオプションやlinterについて

Ansibleで書いたplaybookのテストを実行できる方法をまとめています。


環境情報


シンタックスチェック

構文エラーを調べてくれるオプション --syntax-check が用意されているので、これを利用できます。

ヘルプコマンドより。

$ ansible-playbook -help
Usage: ansible-playbook [options] playbook.yml [playbook2 ...]

Options:
  --syntax-check        perform a syntax check on the playbook, but do not execute it

サンプル。

$ ansible-playbook -i hosts site.yml --syntax-check

playbookのdry run(チェックモード)

作成したplaybookをdry runしてくれるオプション --check が用意されています。

ヘルプコマンドより。

$ ansible-playbook -help
Usage: ansible-playbook [options] playbook.yml [playbook2 ...]

Options:
  -C, --check           don't make any changes; instead, try to predict some of the changes that may occur

サンプル。

$ ansible-playbook -i hosts site.yml --check

Ansilbe - Check Mode

チェックモードの問題点1:依存関係のあるタスクでの利用

例えば以下の様なタスクの場合。Nginxのyum repoをインストール後、Nginxをinstallする処理ですが、当然repoがなければNginxはインストールできないので、dry runであるチェックモードの処理では、Nginxのインストールするタスクで失敗してしまいます。

- name: nginx用yumリポジトリの用意
  yum_repository:
    name: nginx
    description: nginx repo
    baseurl: http://nginx.org/packages/mainline/centos/7/$basearch/
    owner: root
    gpgcheck: no
    enabled: yes
    state: present

- name: Nginxのインストール
  yum:
    name: nginx
    state: present

この回避策として、 ignore_errors オプションを利用して、チェックモード時のエラーを回避する方法が公式では紹介されています。以下のように、Nginxインストール用タスクに ignore_errors オプションを追加することで、チェックモード時のエラーを無視してくれるようなります。

- name: Nginxのインストール
  yum:
    name: nginx
    state: present
  ignore_errors: "{{ ansible_check_mode }}"

チェックモードの問題点2:チェックモードをスキップするタスクモジュールがいる

commandshell モジュールでは、デフォルトではチェックモードをスキップする仕様となっています。( createsremoves オプションを付与されている場合、チェックモードで処理される)そのため、以下のようなタスクでは、チェックモードは必ず失敗してしまいます。command モジュールで情報を取得し register で変数に代入、後続のタスクにて、変数の値により処理を実行するものです。

- name: システムロケールの確認
  command: localectl status
  register: localectl_result
  changed_when: false

- name: ロケールの日本語化
  block:
  - name: 日本語ロケールのモジュールをインストール
    yum:
      name: glibc-common
  - name: ロケールの変更
    command: localectl set-locale LANG=ja_JP.utf8
  when: "'LANG=ja_JP.utf8' not in localectl_result.stdout"

command モジュールは、チェックモードではスキップされてしまうため変数の設定がなされず、後続の処理がエラーとなってしまうのです。この回避策として、 check_mode オプションを付与して、チェックモード時の動作を制御する方法があります。

  • check_mode: yes
    • Force a task to run in check mode, even when the playbook is called without --check .
  • check_mode: no
    • Force a task to run in normal mode and make changes to the system, even when the playbook is called with --check .

check_mode: no オプションを付与することで、チェックモードであっても必ず処理が実行されるため、下記のように記載することで、 register の処理が必ず実行されるようできます。

- name: システムロケールの確認
  command: localectl status
  register: localectl_result
  changed_when: false
  check_mode: no

ただ全ての依存関係を考慮して各タスクに、こういった回避策を設けていくのは非常にめんどくさく、チェックモードが利用できる範囲は限定的になってしまうのでは、と思っています。


AnsibleのLinter

AnsibleではLinterが用意されいるので、これを利用することができます。

Ansible - Lint

インストール

pipにてインストールできます。

$ sudo pip3 install ansible-lint
$ ansible-lint --version
ansible-lint 4.1.0

デフォルトのルール

デフォルトのルールは、以下のコマンドより確認できます。

$ ansible-lint -L -R
101: Deprecated always_run
  Instead of ``always_run``, use ``check_mode``
102: No Jinja2 in when
  ``when`` lines should not include Jinja2 variables
()

デフォルトルールのOS上配置パスは、(僕の環境の場合)、以下となっていました。

/usr/local/lib/python3.6/dist-packages/ansiblelint/rules/

利用方法

テストしたいplaybookを指定して、ansible-lintを実行するだけです。

$ ansible-lint --help
Usage: ansible-lint [options] playbook.yml [playbook2 ...]

サンプル。こんな感じで指摘してくれます。

$ ansible-lint site.yml
[201] Trailing whitespace
/mnt/c/_/ansible/sample/roles/postgresql/tasks/main.yml:43
  lineinfile:

独自ルールの作成

lintで利用できる独自のルールを作成することができます。詳しくは、以下の公式資料を見てもらうとして、

Ansible Lint - Creating Custom Rule

以下は timezone モジュールを利用禁止とする独自ルールの例になります。

ルールを格納しているデフォルトパスに、ルールを定義したpythonプログラムを置いてあげると、

/usr/local/lib/python3.6/dist-packages/ansiblelint/rules/NotUseTimezone.py

import ansiblelint.utils
from ansiblelint import AnsibleLintRule

class NotUseTimezone(AnsibleLintRule):
  id = 'CUSTOMRULE001'
  shortdesc = 'should not use timezone'
  description = 'should not use timezone'
  tags = ['prohibition']
  modules = ['timezone']

  def matchtask(self, file, task):

    if task['action']['__ansible_module__'] in self.modules:
      return True

    return False

linterを実行時に、timezoneを利用している場合に指摘してくるようなります。

$ ansible-lint site.yml
[CUSTOMRULE001] should not use timezone
/mnt/c/_/ansible/sample/roles/postgresql/tasks/main.yml:23
Task/Handler: タイムゾーンの変更