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

Infrastructure as Code

命令型から宣言型へ

TT Labで続きを見る

一言でいうと

宣言的なアプローチは、望ましい最終状態を定義すると、ツールが現在の状態との差を計算して変更を実行する方式で、命令的なアプローチは、実行するステップを順番に記述する方式です。一文でいえば、インフラの「何を(What)」を定義すれば、「どうやって(How)」はツールが処理します。

なぜ必要なのか

手動管理の問題は、4つに要約されます。同じ環境を作り直せず(再現不可能)、誰が何をいつ変更したかが残らず(変更の追跡不可)、台数が増えると手が追いつかず、環境同士が少しずつ違ってきます。特に3つ目が決定的です。サーバー10台までは手動で管理できますが、100台以上は事実上不可能です。

そうして手で少しずつ手を入れたサーバーたちは、雪の結晶のようにすべてバラバラになります。スノーフレークサーバー(Snowflake Server)という比喩は、ここから生まれました。問題は、そのサーバーが死んだときです。誰もそのサーバーをまったく同じように作り直せません。設定がコードではなく、そのマシンのディスクにしかなかったからです。

命令的なスクリプトも、この問題を完全には解決しません。「パッケージをインストールして、設定ファイルをコピーして、サービスを再起動する」という順序は、最初の実行では正しいのですが、2回目の実行で何が起きるかは、スクリプトを全部読まなければわかりません。宣言的なアプローチは、この問いをそもそもなくします。目標の状態だけを書けば、現在の状態との差はツールが計算します。

どう動くのか

宣言的なツールは、常に3つの塊に分かれます。望ましい状態(コード)、現在の状態(実際のインフラを読み取ったもの)、そして両者の差(plan)です。差を人が読めるように記号で表示しますが、標準的な記号は次のとおりです。+ createは新しく作るもの、- destroyは削除するもの、~ updateはその場で属性だけを直すもの、-/+ replaceは削除後の再作成、<= readは読み取りだけを行う参照です。レビューで最も注目すべき記号は-/+です。属性を1つ変えるだけのつもりが、リソースが丸ごと再作成される場合が、ここで明らかになります。

自動化では、planの結果を終了コードで受け取ります。plan -detailed-exitcodeは、0なら変更なし、1ならエラー、2なら適用すべき変更が存在するという意味です。この3つの値があってはじめて、「変更があれば承認の段階へ、なければそのまま通過」のようなパイプラインを組めます。planの結果をファイルに保存して、そのファイルでapplyすると、planの時点とapplyの時点の間にコードが変更されても、planの時点の変更だけが適用されます。レビューしたものと適用されたものが同じだという保証は、ここから生まれます。

ドリフトを語るときは、3軸モデルが便利です。コードはDesired、状態ファイルはLast Known、実際のインフラはActualです。3つがすべて同じなら正常で、そのうちのどれか2つでも食い違えばドリフトです。GitOpsのツールは、この比較を回し続けて自己修復を行います。誰かがkubectlで直接replicasを変更すると、コントローラーが検知して、Gitに定義された値に戻します。

現場での姿

新人が最もよくやる間違いは、コンソールで急いで直して、コードに反映しないことです。その瞬間、コードは嘘になり、次の人のapplyが、その変更を静かに元に戻します。逆に、うまく回っているチームは、planの出力をPRに貼ってレビューします。人がレビューする対象が、コードではなく、コードが生み出す変更の一覧だという点が核心です。

状態ファイルこそ本当に重い資産

宣言的なツールで、最もよく事故が起きる場所は、コードではなく状態ファイルです。前の3軸モデルで「Last Known」を担当するものです。このファイルがない、あるいは壊れていると、ツールは今あるリソースを自分が作ったものと認識できず、そのままapplyすると、すでにあるものをもう一度作ったり、他人のものを消そうとしたりします。

そのため、いくつかのことが基本です。

共有のストレージに置いてロックします。 状態ファイルがそれぞれのノートパソコンにあると、2人が同時にapplyしたときに、お互いの記録を上書きします。リモートのストレージに置いて、applyの間ロックするのが標準で、ロックのない構成は、人が少ないときにたまたま安全なだけです。

秘密情報がその中に入ります。 データベースのパスワードのように、リソースを作るときに渡した値が、状態ファイルに平文で残る場合がよくあります。そのため、状態ファイルはコードのリポジトリにコミットせず、暗号化されるストレージに置き、アクセス権限をコードとは別に管理します。

手で直しません。 ツールが提供するコマンドだけで移動・削除します。テキストとして開いて編集すると、形式は合っていても内部の参照が食い違い、何回かのapplyのあとに、原因不明の再作成が起きます。

そして、範囲を分けることは、規模が大きくなるほど重要になります。インフラ全体を1つの状態で管理すると、planが数分かかり、小さな変更1つで全体がロックされます。寿命と担当が違うものどうしで分けるのが原則です。ネットワークのようにほとんど変わらないもの、クラスターのようにときどき変わるもの、アプリケーションのように毎日変わるものを1つの塊に置くと、毎日変わるもののせいで、ほとんど変わらないものまで、毎回リスクにさらされます。

次のラボですること

このPodには、terraformのバイナリがありません。そこで、YAMLで望ましい状態を宣言し、実際のコンテナの一覧を同じ形式のJSONとして読み取り、両者を比較して+ create / - destroy / ~ updateを出力するplanを、自分で作ります。そのあと、applyで収束させ、外からコンテナを削除してドリフトを作り、planがそれを検出できるかを確認します。ツールが画面に出力するあの記号がどう作られるかを知ると、他人のplanの出力も違って見えます。