権限 640 が 1204 で出ていった日 — ファイルを状態として扱う
目標
ファイルとディレクトリを、コマンドではなく状態として宣言します。引用符なしの8進数が残す権限を自分で計測し、バックアップと検証で、元に戻すための根拠と壊れた設定の遮断を付け、マーカー付きのブロック・リンク・状態レポート・パス点検ツールまで作成します。
なぜ重要なのか
シェルでmkdir・cp・chmod・sed -iの4行をそのまま移すと、自動化のように見えますが、3つのことが同時に崩れます。2回目の実行が1回目と異なるようになり、何を上書きしたのかがどこにも残らず、壊れた設定がそのまま上がります。Ansibleのファイルモジュールは、この3つに、それぞれstateとbackupとvalidateで答えます。ここに、複数行のブロックを安全に管理するblockinfileのマーカー、デプロイに使うリンクの2種類、そして何も変更せず事実だけを読み取るstatが加わると、設定デプロイの骨格が完成します。このラボは、その骨格を、一度に1片ずつ手で立て、最後には、「今このパスがどのような状態か」を判定するツールを、自分で作ります。
ステップ
/root/ans/files/hosts.iniに、webグループのホストweb1(ansible_host=127.0.0.1、ansible_port=2222、ansible_user=root)を書き、/root/ans/files/p01.ymlで、4つのディレクトリを作成してください。/root/ans/files/tree/confと/root/ans/files/tree/logsと/root/ans/files/outは0755、/root/ans/files/tree/secretsは0700です。/root/ans/files/p02.ymlで、3つのファイルを作成してください。/root/ans/files/tree/secrets/api.keyは"0600"、/root/ans/files/tree/conf/motd.txtは"0644"、そして/root/ans/files/scratch/decimal.txtは、引用符なしでmode: 644と書きます。そのあと、stat -c %aがdecimal.txtについて出力する値を、/root/ans/files/out/mode-trap.txtに1行で書いてください。/root/ans/files/p03.ymlで、/root/ans/files/tree/conf/app.confを配置してください。内容は、listen_port={{ app_port }}とenv=prodの2行で、app_portのデフォルト値は8080、パーミッションは"0644"、backupをオンにします。同じプレイブックが、/root/ans/files/tree/conf/deprecated.confと/root/ans/files/tree/logs/archive/old.logも作成するようにしてください。まず、デフォルト値で1回実行したあと、-e app_port=9090でもう一度実行して、バックアップができるようにし、そのバックアップのパスが、/root/ans/files/out/backup-path.txtに残るようにしてください。/root/ans/files/p04.ymlで、/root/ans/files/tree/conf/app.jsonを配置してください。内容はapp_json変数から来て、デフォルト値はserviceがweb、portが8080のJSONで、copyにvalidateを付けて、JSONの文法を検査させます。そのあと、同じプレイブックを-e "app_json=not json at all"でもう一度実行して、失敗した出力を/root/ans/files/out/validate-fail.txtに残してください。/root/ans/files/p05.ymlで、/root/ans/files/tree/conf/hosts.blockを扱ってください。ファイルがないときだけ、127.0.0.1 localhostの1行で作成し(すでにあれば上書きしません)、blockinfileで、マーカー# {mark} ANSIBLE MANAGED BLOCK: web poolを使うブロックに、10.10.0.11 web1と10.10.0.12 web2の2行を入れてください。プレイブックを2回実行しても、ブロックが1つである必要があります。/root/ans/files/p06.ymlで、4つのことを行ってください。ないときだけ/root/ans/files/tree/logs/app.logを作成し、/root/ans/files/tree/logs/latest.logがそのファイルを指すシンボリックリンクになるようにし、/root/ans/files/tree/conf/motd.hardが/root/ans/files/tree/conf/motd.txtのハードリンクになるようにし、最後に、latest.logのパスに"0640"を設定して、リンクをたどらせ、app.logの権限が変わるようにしてください。/root/ans/files/p07.ymlで、/root/ans/files/tree/conf/app.confの状態をstatで確認し、チェックサムはsha256で受け取って、/root/ans/files/out/stat-report.jsonに、path・exists・mode・size・checksumの5つのキーを含むJSONを残してください。続けて、/root/ans/files/tree/conf/app.jsonをfetchで取得して、/root/ans/files/fetched/app.jsonという1つのファイルとして配置されるようにしてください。/root/ans/files/p08.ymlで、/root/ans/files/tree/conf/deprecated.confと/root/ans/files/tree/logs/archiveを、存在しない状態にしてください。そして、パスの一覧ファイルを引数として受け取り、パスごとに、<경로> LINK <가리키는 곳>・<경로> DIR <권한>・<경로> FILE <권한>・<경로> ABSENTのうち1つを1行で出力する/root/ans/files/audit.shを書いてください(プレースホルダーはパス、指す先、権限です)。/root/ans/files/paths.txtに、上の2つのパスと、/root/ans/files/tree/conf/app.conf、/root/ans/files/tree/logs/latest.log、/root/ans/files/tree/secretsを書き、結果を/root/ans/files/out/state-report.txtに保存してください。
参考
- 作業ディレクトリは
/root/ans/filesです。インベントリのホストは、このPodの中の127.0.0.1:2222のsshdに接続するので、対象とコントローラーが同じファイルシステムを見ます。 - モジュールを探すときは、
ansible-doc ansible.builtin.fileのように呼ぶと、引数と例がその場で出力されます。 - よくある間違い:
modeを引用符なしで書くことです。ステップ2で、その結果を自分で計測します。 - よくある間違い: すでにある他人のファイルに
copyを上書きとして実行して、先に入れたブロックが、実行のたびに消えては付くことです。 - よくある間違い:
state: fileで、存在しないファイルを作成しようとすることです。その値は、すでにあるファイルの属性だけを合わせます。 - fileモジュール・copyモジュール・blockinfileモジュール・statモジュール・fetchモジュール
ディレクトリツリーを状態として宣言する
/root/ans/files/hosts.iniに、webグループのホストweb1(ansible_host=127.0.0.1、ansible_port=2222、ansible_user=root)を書き、/root/ans/files/p01.ymlで、4つのディレクトリを作成してください。/root/ans/files/tree/confと/root/ans/files/tree/logsと/root/ans/files/outは0755、/root/ans/files/tree/secretsは0700です。
ansible.builtin.fileで、ディレクトリを意味するstateの値1つがあれば、途中のパスまで一緒に作成してくれます。modeは、必ず引用符で囲んだ文字列で書いてください。次のステップで、なぜそうなのかを自分で計測します。採点ツールは、プレイブックをチェックモードでもう一度実行して、変更が0かどうかも確認します。
引用符なしの8進数が残す権限を自分で計測する
/root/ans/files/p02.ymlで、3つのファイルを作成してください。/root/ans/files/tree/secrets/api.keyは"0600"、/root/ans/files/tree/conf/motd.txtは"0644"、そして/root/ans/files/scratch/decimal.txtは、引用符なしでmode: 644と書きます。そのあと、stat -c %aがdecimal.txtについて出力する値を、/root/ans/files/out/mode-trap.txtに1行で書いてください。
YAMLは、先頭に0がない数字を10進数として読みます。その整数がそのまま権限ビットとして使われると、どんな値になるかは、頭で計算せず、実際に作成してstatで計測してみてください。scratchディレクトリも、同じプレイブックで一緒に作成します。
上書きする前の内容を残す
/root/ans/files/p03.ymlで、/root/ans/files/tree/conf/app.confを配置してください。内容は、listen_port={{ app_port }}とenv=prodの2行で、app_portのデフォルト値は8080、パーミッションは"0644"、backupをオンにします。同じプレイブックが、/root/ans/files/tree/conf/deprecated.confと/root/ans/files/tree/logs/archive/old.logも作成するようにしてください。まず、デフォルト値で1回実行したあと、-e app_port=9090でもう一度実行して、バックアップができるようにし、そのバックアップのパスが、/root/ans/files/out/backup-path.txtに残るようにしてください。
copyのバックアップ機能は、上書きの直前の内容を、同じディレクトリに残します。戻り値にそのパスが入るので、registerで受け取っておけば、次のタスクですぐに使えます。最初の実行では、上書きするものがないのでバックアップができないため、パスを記録するタスクには、is definedの条件を付けてください。
検査に通った設定だけを所定の場所に置く
/root/ans/files/p04.ymlで、/root/ans/files/tree/conf/app.jsonを配置してください。内容はapp_json変数から来て、デフォルト値はserviceがweb、portが8080のJSONで、copyにvalidateを付けて、JSONの文法を検査させます。そのあと、同じプレイブックを-e "app_json=not json at all"でもう一度実行して、失敗した出力を/root/ans/files/out/validate-fail.txtに残してください。
validate文字列の中の%sの場所に、一時ファイルのパスが入ります。検査コマンドは、JSONを読み込んでみて、問題があれば0以外の値で終了すればよいです。失敗した実行の出力を残すときは、標準エラー出力も一緒に受け取る必要があり、失敗したコマンドのせいでスクリプトが止まらないようにしてください。失敗したあとも、元のファイルが無事かどうかを確認してみてください。
マーカー付きのブロックだけを入れ替える
/root/ans/files/p05.ymlで、/root/ans/files/tree/conf/hosts.blockを扱ってください。ファイルがないときだけ、127.0.0.1 localhostの1行で作成し(すでにあれば上書きしません)、blockinfileで、マーカー# {mark} ANSIBLE MANAGED BLOCK: web poolを使うブロックに、10.10.0.11 web1と10.10.0.12 web2の2行を入れてください。プレイブックを2回実行しても、ブロックが1つである必要があります。
copyには、すでにあるファイルを上書きしないようにするスイッチがあります。それを抜くと、2回目の実行でブロックがまるごと消えて、また付きます。マーカー文字列の{mark}の場所に、BEGINとENDがそれぞれ入ります。この文言をあとで変えると、ブロックが2つになります。採点ツールは、プレイブックをチェックモードでもう一度実行して、変更が0かどうかも確認します。
シンボリックリンクとハードリンク、そしてfollow
/root/ans/files/p06.ymlで、4つのことを行ってください。ないときだけ/root/ans/files/tree/logs/app.logを作成し、/root/ans/files/tree/logs/latest.logがそのファイルを指すシンボリックリンクになるようにし、/root/ans/files/tree/conf/motd.hardが/root/ans/files/tree/conf/motd.txtのハードリンクになるようにし、最後に、latest.logのパスに"0640"を設定して、リンクをたどらせ、app.logの権限が変わるようにしてください。
シンボリックリンクとハードリンクは、stateの値が異なります。どちらもsrcとdestを受け取ります。最後のタスクは、すでにあるファイルの属性だけを合わせるstateを使い、リンクをたどるかどうかを決める引数を、明示的にオンにしてください。Linuxでは、シンボリックリンク自体の権限が意味を持たないので、権限はリンクではなく、その先のファイルにかかります。
状態を読み取ってレポートとして残し、コントローラーに取得する
/root/ans/files/p07.ymlで、/root/ans/files/tree/conf/app.confの状態をstatで確認し、チェックサムはsha256で受け取って、/root/ans/files/out/stat-report.jsonに、path・exists・mode・size・checksumの5つのキーを含むJSONを残してください。続けて、/root/ans/files/tree/conf/app.jsonをfetchで取得して、/root/ans/files/fetched/app.jsonという1つのファイルとして配置されるようにしてください。
statは、何も変更せず、事実だけを返します。チェックサムは、アルゴリズムを指定して初めて、戻り値に含まれます。fetchは、デフォルトでホスト名のディレクトリを作成して、元のパスをそのまま再現するので、ファイル1つとして配置するには、その動作をオフにする引数をオンにする必要があります。レポートは、辞書を作成してJSONに変換するフィルターを使えば、1つのタスクで終わります。
片付けも状態の宣言、そしてパス点検ツールの作成
/root/ans/files/p08.ymlで、/root/ans/files/tree/conf/deprecated.confと/root/ans/files/tree/logs/archiveを、存在しない状態にしてください。そして、パスの一覧ファイルを引数として受け取り、パスごとに、<경로> LINK <가리키는 곳>・<경로> DIR <권한>・<경로> FILE <권한>・<경로> ABSENTのうち1つを1行で出力する/root/ans/files/audit.shを書いてください(プレースホルダーはパス、指す先、権限です)。/root/ans/files/paths.txtに、上の2つのパスと、/root/ans/files/tree/conf/app.conf、/root/ans/files/tree/logs/latest.log、/root/ans/files/tree/secretsを書き、結果を/root/ans/files/out/state-report.txtに保存してください。
権限は、stat -c %aが出力するそのままを書きます。判定の順序が、このステップのすべてです。-dと-fは、シンボリックリンクをたどるので、リンクをあとで見ると、リンクがディレクトリやファイルに化けます。採点ツールは、自分で作成した別のパスの一覧でも、このスクリプトを実行してみるので、答えを書いておいただけのスクリプトは、通過できません。