クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

コードとしてのパイプライン

このレッスンのあとで: 最初の失敗で止まるステージのパイプラインを書ける。

60分のレッスンコマンド12件OpenTofu のラボ:プラン、apply、ステートで学ぶ Infrastructure as Code を1台の VM で

金曜日の壊れた変更

誰かが4時にファイルを直し、アップロードして帰宅しました。チェックは誰も実行しておらず、次の apply は途中で失敗します。

パイプラインは、どの変更に対しても同じチェックを同じ順で実行します。失敗したところで止まるので、悪い変更が apply まで届きません。

このレッスンのゴール

メモ

最初の失敗で止まるステージのパイプラインを書ける。

重要な言葉

ことばいみれい
パイプライン決まった順に実行される手順
ステージパイプラインの1つの手順
終了コードコマンドが返す数。0 は成功echo $?
ゲートチェックが失敗したら止める手順
プランファイルapply がそのまま実行する、保存したplanplan.bin

学生が入力するセルは、このプレビューでは空白です。

どのコマンドも終了コードで終わる

コマンドは終わるとき、数を1つ返します。これが終了コードです。0 は成功、ほかの数は失敗を表します。

シェルは直前の終了コードを $? に覚えています。パイプラインが読むのは終了コードだけです。0 以外を返すチェックは、実行を止めるチェックです。

パイプライン用のフォルダを作る

コマンド
mkdir -p ~/iac-demo/pipe && cd ~/iac-demo/pipe

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

リソース1つのプロジェクトを書く

コマンド
printf '%s\n' 'variable "shop" {' '  type    = string' '  default = "駅前の店"' '}' '' 'resource "terraform_data" "door" {' '  input = format("%s: 営業中", var.shop)' '}' > main.tf

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

フォルダを準備する

コマンド
tofu init -no-color | grep successfully

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

字下げが乱れた2つ目のファイルを書く

コマンド
printf '%s\n' 'resource "terraform_data" "sign" {' '    input = "営業中"' '}' > sign.tf

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

整形を調べて終了コードを読む

コマンド
tofu fmt -check; echo "終了コード $?"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

パイプラインは最初の失敗で止まる

スクリプトは、各ステージを実行し、失敗したら止まることができます。ここでは小さな関数 stage がコマンドを実行し、0 以外が返ったら終了します。

ステージは、手で実行する順に並べます。format、validate、plan、ゲート、apply です。失敗したステージがあれば、あとのステージは始まりません。

ゲートを書く

コマンド
printf '%s\n' '[.resource_changes[].change.actions[]] | index("delete") | not' > gate.jq

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

パイプラインのスクリプトを書く

コマンド
printf '%s\n' '#!/bin/bash' 'stage() { echo "== ステージ: $1"; name=$1; shift; "$@" > /dev/null || { echo "ステージ $name が失敗したので停止"; exit 1; }; }' 'gate() { tofu show -json plan.bin | jq -e -f gate.jq; }' 'stage format tofu fmt -check -no-color' 'stage validate tofu validate -no-color' 'stage plan tofu plan -no-color -out=plan.bin' 'stage gate gate' 'stage apply tofu apply -no-color plan.bin' 'echo "パイプライン成功"' > ci.sh

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

sign.tfが乱れたまま実行する

コマンド
bash ci.sh; echo "パイプラインの終了コード $?"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

整形を直して、もう一度実行する

コマンド
tofu fmt -no-color; bash ci.sh; echo "パイプラインの終了コード $?"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

パイプラインが成功なら、変更は安全だと考える人が多くいます。成功が示すのは、書いたチェックがすべて通ったことだけです。変更が正しい形で、計画され、適用されても、間違った変更のことがあります。安全を守るのは、ゲートと、planを読む人です。

ゲートはapplyの前にplanを読む

plan ステージは、planを plan.bin に保存しました。apply ステージは、そのファイルだけを実行します。ゲートが認めたものが、そのまま実行されます。

tofu show -json は、planをデータに変えます。ゲートのフィルタは、削除されるリソースが1つでもあれば「だめ」と答えます。

リソースを消して実行する

コマンド
rm sign.tf; bash ci.sh; echo "パイプラインの終了コード $?"; tofu state list

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ホスト型のパイプラインはファイル

パイプラインを提供するサービスでは、ステージをリポジトリ内のファイルに書き、サービスが自分のマシンで実行します。ジョブは needs で別のジョブを待てます。

ここにはホスト型のランナーがないので、実行はできません。書いて、読むことはできます。

ワークフローファイルを書く

コマンド
mkdir -p .github/workflows && printf '%s\n' 'name: infra' 'on:' '  pull_request:' '  push:' '    branches: [main]' 'jobs:' '  check:' '    runs-on: ubuntu-latest' '    steps:' '      - uses: actions/checkout@v4' '      - run: tofu fmt -check' '      - run: tofu validate' '      - run: tofu plan -out=plan.bin' '  apply:' '    needs: check' "    if: github.ref == 'refs/heads/main'" '    runs-on: ubuntu-latest' '    steps:' '      - run: tofu apply plan.bin' > .github/workflows/infra.yml

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

順番とコマンドを探す

コマンド
grep -nE "^  (check|apply):|needs:|run:" .github/workflows/infra.yml

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

実行結果をPythonで計算

コード: python
# 前のステージに依存するステージのパイプラインを実行します。
jobs = {"check": [], "plan": ["check"], "apply": ["plan"]}
failing = {"plan"}
state = {}
for name in ["check", "plan", "apply"]:
    blocked = False
    for need in jobs[name]:
        if state[need] != "passed":
            blocked = True
    if blocked:
        state[name] = "skipped"
    elif name in failing:
        state[name] = "failed"
    else:
        state[name] = "passed"
    print(name, state[name])
# failing を空にして、もう一度見てみましょう。

このラボとソースについて: OpenTofu のラボ:プラン、apply、ステートで学ぶ Infrastructure as Code を1台の VM で

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。