プレイブック — 命令ではなく状態を書く
一言でいうと
プレイブックの各タスクは、「このコマンドを実行せよ」ではなく、「終わったときにこの状態になっていなければならない」という宣言です。
なぜ必要なのか
シェルスクリプトでサーバーをセットアップしたことがある人は、スクリプトがだんだん長くなる経験をします。最初はmkdir /opt/appの1行だったのが、2回目の実行で「すでに存在する」というエラーが出てmkdir -pに変え、パーミッションが違うことがあるのでchmodを付け、それがすでに合っているなら、わざわざやらなくてもよいのでifで囲みます。結局、スクリプトの半分が、今の状態がどうなっているかを確認するコードになります。
Ansibleのモジュールは、その半分を代わりに担います。fileモジュールにpath、state=directory、mode=0755を渡すと、なければ作成し、あればそのままにし、パーミッションが違えば修正します。そして、実際に何かを変更したときにだけ、changedとして報告します。この報告が、次のモジュールで学ぶ冪等性とハンドラーの土台になります。
どう動くのか
プレイブックはプレイの一覧であり、プレイは「どのホストに、どのタスクを、順番に」を含みます。
- name: 웹 서버 기본 설정 # 플레이 이름
hosts: web # 대상 (인벤토리의 그룹/호스트/패턴)
gather_facts: true # 대상의 정보를 먼저 수집할지
tasks:
- name: 앱 디렉터리 준비 # 태스크 이름
ansible.builtin.file: # 모듈
path: /opt/app
state: directory
mode: "0755"
覚えておくべきルールは3つです。
- タスクは上から下へ順番に実行されます。ただし、複数のホストに対しては、タスク1つがすべてのホストで終わってから、次のタスクへ進みます。
- すべてのタスクに
nameを付けます。名前がないと、ログにモジュール名と引数がそのまま出力されて読みにくく、タグで選ぶのも難しくなります。 command/shellは最後の手段です。この2つは状態を知らないので、毎回changedと報告します。専用のモジュールがあるならそれを使い、どうしてもシェルを使う必要があるなら、createsのようなガードを付けます。
実行前に確認できる手段もあります。--syntax-checkはYAMLとプレイの構造を検査し、--checkは、実際には変更せず、「何が変わるか」だけを報告します。--diffを一緒に付けると、ファイル内容の差分まで表示してくれます。本番サーバーで初めて実行するプレイブックは、必ず--check --diffを先に通します。
現場での姿
1つ目は、タグの2つの顔です。タグを付けると、--tags configで設定だけをもう一度適用できて便利です。ところが、タグで一部だけを実行する習慣が定着すると、「全体を実行したことのないプレイブック」ができます。そのようなプレイブックは、ある日、全体の実行で初めて見るエラーを出します。タグは、デバッグ用の加速装置であり、通常の実行経路ではありません。
2つ目は、チェックモードの限界です。--checkは、モジュールがそのモードをサポートしている場合にのみ正確です。commandで作った結果に依存する後ろのタスクは、チェックモードで見当違いの結果を出すことがあります。そのため、チェックモードがクリーンであることと、実際の実行が安全であることは、別の命題です。
3つ目は、名前がそのままドキュメントになることです。事故が起きたとき、人が読むのはコードではなく実行ログです。「Install nginx」よりも、「nginxのインストール。設定ファイルは次のタスクで上書きする」のほうが、はるかに役に立ちます。
失敗したとき、どこで止まるのか
プレイブックが途中で失敗すると、すでに適用されたものと、まだ適用されていないものが混在した状態が残ります。スクリプトと違って、Ansibleは複数のホストを同時に扱うので、この状態がより複雑になり、デフォルトの動作を知っておかないと、事故のあとの判断が難しくなります。
デフォルトは、そのホストだけが外れることです。タスクがあるホストで失敗すると、そのホストは以降のタスクから除外され、残りのホストは続行します。そのため、10台のうち1台だけが失敗すると、残りの9台は最後まで進み、クラスターが2つのバージョンに分かれた状態になります。
この動作を変える仕組みが、いくつかあります。
any_errors_fatal: 1台でも失敗すると、全体をただちに止めます。半分だけが変わってはいけない作業に使います。max_fail_percentage: 失敗したホストの割合がしきい値を超えると止めます。数台は許容しつつ、大量の失敗は防ぎたいときに使います。serial: 一度に数台ずつだけを処理します。順次デプロイの基本的な仕組みで、serial: 1で始めて、1台が成功するのを見てから増やす方式が安全です。
serialとハンドラーの関係は、特に忘れられがちです。ハンドラーは、デフォルトではプレイが終わるときに1回動きますが、serialを使うと、各バッチが終わるたびに動きます。そのため、再起動がバッチ単位で起き、これが、実は順次デプロイで望む動作です。
そして、失敗したあとにもう一度実行するのが安全かどうかを、あらかじめ判断しておく必要があります。すべてのタスクが冪等なら、そのまま再実行すればよいですが、途中に冪等でないものが1つでもあると、再実行が状況を悪化させます。前のモジュールで、冪等性をテストで強制するよう述べた理由が、ここで明らかになります。再実行してよいかどうか確信できない自動化は、失敗したときに、人が手作業で収拾しなければならない自動化です。
次のラボですること
/root/ans/play/site.ymlを作成して、ディレクトリの作成・設定ファイルの配置・既存ファイルの1行の修正を、タスクとして表現し、タグとチェックモードを自分で使ってみます。最後に、エラーが4か所仕込まれたプレイブックを直して、通過させます。