TT Lab
はじめる
学ぶ 学習パス コース

Ansible基礎

タグと部分実行 — 切り取った実行は最終状態を約束しない

TT Labで続きを見る

一言でいうと

タグは「今回はこれだけ」を選ぶ仕組みであり、付けた場所から下へ継承されます。ところが、タグで絞り込んだ実行は、プレイブックが約束した最終状態を保証しません。その事実を知ることが、タグの文法よりも重要です。

なぜ必要なのか

プレイブックは成長します。最初はタスクが5個だったのに、半年後には80個になります。そのうち、設定ファイルの1行だけを直したい日が来ます。全体を実行すると、ファクトの収集からパッケージの確認まで15分かかり、その間に、触れたくないタスクが20個、通り過ぎます。

タグは、この問題への答えです。タスクにラベルを付けておき、--tags configで、そのラベルが付いたものだけを実行します。逆に、--skip-tags slowで、遅いものだけを除くこともできます。文法は簡単で、そのため、人々は文法を学んだ翌日から、本番で使います。

ここで事故が起きます。タグは実行を切り分ける刃物ですが、プレイブックは切り分けられてもよいようには設計されていません。8番目のタスクが作成したディレクトリを12番目のタスクが使い、20番目のタスクが変更した設定を、ハンドラーが受け取って、サービスに再読み込みさせます。--tags configで12番目だけを実行すると、ディレクトリがなくて失敗するか、もっと悪い場合には、成功したふりをしながら、半分だけ合った状態を残します。

そのため、このモジュールのテーマは2つの層です。タグの文法と継承が1つの層で、切り分けても大丈夫なプレイブックを、どうやって見分けるのかが、もう1つの層です。

どう動くのか

タグは4つの場所に付き、下へ継承されます。

付く場所 継承範囲
タスク そのタスク1つ
ブロック ブロック内のすべてのタスク
プレイ そのプレイのすべてのタスク
ロール / import_tasks / include_tasks 下で説明します

--list-tasksを実行してみると、継承が目に見えます。プレイにplaytagを付けておくと、すべてのタスクのタグ一覧にplaytagがついてきます。ブロックにblockyを付けると、ブロック内の2つのタスクが、それぞれblockyを持ちます。

import_tasksとinclude_tasksは、ここで分かれます。import_tasksは静的です。プレイブックを読み込むときにタスクがその場所に展開され、import文に付けたタグが、展開されたタスク1つ1つに継承されます。include_tasksは動的です。実行中になって初めて、タスクが割り込みます。そのため、include文に付けたタグは、include文自体にだけ付き、内部には継承されません。

この違いが生む結果は、初めて見ると戸惑います。--tags includedを指定すると、include文はタグが合うので実行され、その中で割り込んだタスクたちは、includedタグがないので、すべて絞り込まれます。何のエラーもなく、何も起こりません。公式ドキュメントは、includeの中のタスクにもタグを付けたい場合は、applyキーワードを使うか、いっそimport_tasksを使うよう案内しています。

特殊なタグが5つあります。

タグ 意味
always --skip-tags alwaysで明示的に除かない限り、常に実行される
never 名前を直接指定しない限り、絶対に実行されない
tagged タグが1つでも付いているタスクすべて
untagged タグが1つもないタスクすべて
all すべて(デフォルト)

alwaysは、インベントリのファクト収集や、共通変数の設定のように、どの選択からも抜けてはいけないタスクに付けます。neverは、危険なタスク(データベースを削除したり、全体を再デプロイしたりするもの)を、プレイブックに書いておきながら、手が滑って実行されないようにします。neverが付いたタスクにdangerのようなラベルを一緒に付けておけば、--tags dangerで呼んだときだけ実行されます。

注意すべき点が1つあります。プレイにタグを付けると、そのプレイのすべてのタスクが「タグがある」状態になります。そうすると、--tags untaggedは何も選ばず、--tags taggedはすべてを選びます。継承は、このように特殊なタグの意味まで変えてしまいます。

実行する前に、一覧を見ます。--list-tagsは、このプレイブックにどのようなタグがあるかを表示し、--list-tasksは、今回の選択で何が実行されるかを、順番に表示します。どちらも対象に接続せず、何も変更しません。--tagsを付けて一緒に指定すると、その選択がそのまま反映された一覧が出力されます。本番でタグを初めて使うときは、これを先に見る習慣が、事故を半分に減らします。

途中から実行する方法もあります。--start-at-task "태스크 이름"(プレースホルダーはタスク名です)は、その名前のタスクから開始します。長いプレイブックが40番目で落ちたときに、1番目から再実行しないために使います。--stepは、タスクごとに人に尋ねながら進みます。対話式なので自動化には使えませんが、初めて見るプレイブックを手でたどるときに役に立ちます。

--start-at-taskは、タグよりも危険です。前のタスクが作成したものも、前のタスクが送ったnotifyも、すべてない状態で、途中から始めるからです。終わったときにfailed=0が出ても、システムはプレイブックが約束した状態ではありません。

部分実行が危険な2つの方式を整理すると、次のとおりです。

1つ目は、依存するタスクが抜けることです。3番目のタスクのset_factが決めた値を7番目のタスクが使うのに、--tagsで7番目だけを選ぶと、その変数は未定義のままです。運がよければすぐに失敗し、運が悪ければ、デフォルト値で見当違いのものが入ります。

2つ目は、ハンドラーが動かないことです。ハンドラーは、notifyを受け取って動きます。設定を変更するタスクが選択から抜けると、ハンドラーは呼ぶ人がいなくなって、静かに通り過ぎます。逆に、設定のタスクだけを選んだ実行では、ハンドラーが正常に動きます。つまり、ハンドラーが実行されるかどうかは、「誰がnotifyしたか」で決まり、「タグ」では決まりません。この2つを混同すると、「タグを付けたらサービスが再起動されなかった」原因を、見当違いの場所で探すことになります。

現場での姿

1つ目は、--tagsで最初のデプロイをすることです。新しいサーバーに--tags configだけをかけると、ディレクトリがなくて失敗します。タグは、すでに一度全体が実行されたシステムに使う道具です。最初の収束は、全体で行います。

2つ目は、タグがドキュメントの代わりになってしまうことです。タグが30個になると、どれを付ければ安全か、誰にもわかりません。タグは5個前後に保ち、セットで使う組み合わせは、スクリプトやMakefileで名前を付けておきます。

3つ目は、neverを付けなかったせいで事故が起きることです。「全体の再インストール」のタスクをコメントアウトしておくチームがあります。コメントは、いつか外されます。neverタグを付ければ、コードとして残しつつ、誤って実行されません。

4つ目は、途中から実行したあとで、緑のランプを信じることです。--start-at-taskでよみがえらせたデプロイが成功で終わっても、前のタスクが残すべきだったものはありません。復旧のあとは、全体をもう一度実行して、changed=0を確認することが、実際の終了条件です。

5つ目は、このラボ環境の正直な限界です。--stepは、人がキーを押さなければ進まない対話式のモードで、採点ツールが判定できないので、ラボから外しました。ロールにタグを付ける話も、ロール自体がansible-advancedのテーマなので、ここでは扱いません。

参考ドキュメント

次のラボですること

タスクにタグを付けて--tagsで絞り込み、--list-tagsと--list-tasks --tagsで、実行する前に何が実行されるかを先に見て、--skip-tagsで除外してみます。ブロックとプレイにタグを付けて、継承が一覧にどのように表れるかを確認し、alwaysとneverを付けて、特殊なタグの意味を自分で計測し、import_tasksとinclude_tasksを並べて、タグが継承される側と継承されない側を、一覧で分けます。--start-at-taskで途中から実行したあと、設定が古い値のままで、ハンドラーが動かなかったことを確認し、最後に、どのタグの選択なら最後まで完走できるかを判定するツールを、自分で作成します。