Ansibleの冪等性とPlaybook

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

渡辺です。

Ansibleを利用する時、避けて通れない概念が「冪等性(べきとうせい/idempotence)」です。 冪等性は数学方面の用語で、大雑把に言えば「1回だけ操作を行っても、不数回(N回)行っても結果が変わらない特性」のことを指します。

例えば、有理数の乗算であれば1と0には冪等性があります。 1は、Nに何回かけても結果はNです。 同様に、0は、Nに何回かけても結果は0です。 とはいえ、Ansibleの冪等性は、あくまで構成管理を行う上でのことなので、数学的な冪等性については「そんな概念なんだ」程度の理解で良いと思います。

Ansibleによる冪等性はサーバの状態を保つこと

サーバの定義を記述したAnsibleのPlaybookを実行すると、定義に合わせたミドルウェアなどがサーバにインストールされ、サーバの状態が変更されます。 そして、サーバの定義、すなわちPlaybookを変更しないのであれば、Playbookを何回実行してもサーバの状態が変更されないことが自然です。 この自然な状態を「Playbookが冪等性を保っている」と定義します。 言い換えれば、冪等性を持たないPlaybookは、実行する度にサーバの状態が変更されるのです。

例えば、素のAmazonLinuxのAMIからサーバを立ち上げ、この状態を「初期状態」とします。 ここに次のPlaybookを流しました。

---
- hosts: all
  tasks:
    - name: apache24 installed
      yum:
        name: httpd24

Ansibleのyumモジュールにより、Apache2.4がインストールされます。 この時、サーバの状態が変更されています。

サーバの状態が変更された時、Ansibleの実行結果は次のようにchangedを示します。

TASK [apache24 installed] ******************************************************
changed: [Web]

さて、再度、同じPlaybookを流します。 サーバには既にApache2.4がインストールされているので、yumモジュールはApache2.4をインストールしません。 この時、サーバの状態は変更されません。

サーバの状態が変更されなかった時、Ansibleの実行結果は次のようにokを示します。

TASK [apache24 installed] ******************************************************
ok: [Web]

すなわち、すべての実行結果がokであるならば、サーバの状態が変更されていないということです。

さらに、3回目・4回目と同じPlaybookを流しても、サーバの状態は変更されません。 このPlaybookは冪等性を保っています

Ansibleの冪等性と範囲

Ansibleの冪等性とは無関係にサーバの状態は変化することも現実です。

例えば、Apacheにアクセスがあればアクセスログやエラーログが出力されるでしょう。 Ansibleで構成管理を行っていたとしても、サーバの状態が厳密に変更されないことは、まずありません。 手動でPHPをインストールした場合も同様です。 この時、サーバの状態は変化しますが、Ansibleを実行する上ではサーバの状態は変更がないとみなされます。

つまり、Ansibleで保たれる冪等性はPlaybookで定義した範囲に限定されます。 言い換えると、なるべくAnsibleで管理する範囲を広くしておいた方が、サーバの再構築などが簡単にできますし、構成管理に冪等性の概念を活用する事ができることになります。

冪等性を破壊するcommandモジュール

AnsibleはサーバにSSH接続を行い、各種のコマンドを実行することで構成管理を行います。 Playbookはその定義を記述したものであり、本質的にはセットアップスクリプトやセットアップ手順書と同じモノです。

Ansibleでは、構成管理したい内容によって様々なモジュールが提供されています。 そして、モジュールではカバーできない作業を行う最終手段としてcommandモジュールが提供されています。 commandモジュールを使うと、文字通りサーバ上で任意のコマンドを実行できます。

例えば、次のようなPlaybookでApacheをインストール可能です。

---
- hosts: all
  tasks: 
    - name: apache24 installed
      command: "yum -y install httpd24"

ところが、commandモジュールは常にchangedを返します。 実際に実行されたコマンドがサーバの状態を変更しているとみなされます。 したがって、commandモジュールを使ったPlaybookは冪等性を保つことができません。

冪等性を保つPlaybookに必要なこと

Ansibleで構成管理を行う時、サーバの定義であるPlaybookに変更がなければ、サーバの状態を変更させない、すなわちPlaybookに冪等性を持たせることが重要です。 このためにはAnsibleのモジュールの特性を理解し、サブ構文を駆使する必要があります。

commandモジュールを利用しない

commandモジュールは強力です。 しかし、常にchangedを返すため、Playbookの冪等性を破壊します。 一方、yumモジュールでは内部的に対象がインストールされているかをチェックし、インストールの必要がなければokを返し、必要があればインストールしてchangedを返します。 つまり、他の冪等性を保てるモジュールを利用できる場合に、commandモジュールを利用すべきではありません。

changed_whenで制御する

サブ構文のひとつchanged_whenは文字どおり、changedの条件を記述できます。 changed_when: Falseとすれば、どのようなモジュールが実行されてもサーバに変更を与えないタスクとなります。

例えば、次のタスクはcommandモジュールでjavaコマンドを実行し、javaがインストールされているかをチェックします。 commandモジュールなのでjavaコマンドはサーバの状態を変更するとみなされ、cahngedを返します。 しかし、changed_when: Falseがあるため、このタスクは常にokを返すのです。

- name: "check '{{ java_home }}/bin/java' if exist"
  command: "{{ java_home }}/bin/java -version"
  failed_when: False
  changed_when: False
  register: java_result

このようにチェックのためにコマンドを実行するような場合、registerと組み合わせ、commandを使う事があります。

やっぱりcommandモジュールを利用しない

なお、パスの判定であれば、 stat モジュールを利用すれば次のように書くこともできます。

- name: "check '{{ java_home }}/bin/java' if exist"
  stat:
    path: "{{ java_home }}/bin/java"
  register: java_result

statモジュールはpathで指定されたパスが存在するかを判定する専用モジュールです。 このモジュールはパスが存在しても存在しなくともokを返しますので、changed_whenfailed_whenに依存しなくて済むでしょう。

changed_whenが必要になったならば、一度モジュールの一覧を確認し、代用モジュールがないか確認してください。

まとめ

今回はAnsibleで重要な概念である「冪等性」について、commandモジュールやchanegd_whenを交えつつ解説しました。 Playbookで冪等性を保てれば、Playbookでの修正がサーバの変更と同等となります。 サーバの構築手順書はすべてPlaybookになると、gitなどのバージョン管理システムでの管理とも相性が良いです。 手間はかかりますが、それだけの価値があるのです。