こんにちは、CTO室のldrです。 GitHub CI/CD実践ガイドの著者が提唱するグッドプラクティスを実践し、1つ追加してみました。
グッドプラクティス11
- タイムアウトを常に指定する
- デフォルトシェルでBashのパイプエラーを拾う
- 「actionlint」ですばやく構文エラーをチェックする
- Concurrencyで古いワークフローを自動キャンセルする
- 不要なイベントで起動しないようにフィルタリングする
- 最安値のUbuntuランナーを優先する
- GITHUB_TOKENのパーミッションはジョブレベルで定義する
- アクションはコミットハッシュで固定する
- Bashトレーシングオプションでログを詳細に出力する
- workflow_dispatchイベントで楽に動作確認する
- ワークフローの背景情報をコメントへ書き残す
+1
- OIDCとAWSSTSを使用した認証を使用する
PR作成時にterraform [validate,plan]を実行するworkfllowを作成
1.タイムアウトを常に指定する
すばやく失敗を検出し、効果的にフィードバックを得ることが大切
GitHub Actionsのデフォルトタイムアウトは360分なので、意図しない通信障害に気づかないと半日実行し続けてしまいますね。VPC含むNetworkをまるっと作成するPlanも発生するのでタイムアウトは10分にしました。
ジョブに記述
timeout-minutes: 10
2.デフォルトシェルでBashのパイプエラーを拾う
パイプ処理中のエラーを無視します。エラー時に意図せず処理を継続するため、結果として不具合の温床になります。GitHub ActionsではBashの利用を明示的に宣言すると、なぜかpipefailオプションが有効化されます
- パイプ(|)を使ったコマンドの実行中に、途中のコマンドでエラーが発生しても、そのまま処理が続行されてしまうのですが、Bashの利用を明示的に宣言しておくことで、pipefailオプションが有効化になってパイプ処理中のエラーを確実に検出し、意図しない処理の継続を防ぎます。 例:bashスクリプトでのpipefailオプションの有効化
set -o pipefail
- ジョブに記述
defaults: run: shell: bash
3.「actionlint」ですばやく構文エラーをチェックする
.github/workflowsディレクトリ配下のYAMLファイルをまとめてチェックし、構文エラーや非推奨構文などを検出します - .github/workflowsディレクトリ配下にactionlintを作成します
Actionlint Check
name: 'Actionlint Check' on: push: branches: - '**' pull_request: branches: - 'develop' jobs: actionlint: name: 'Actionlint' runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout Repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # docker runを使用してactionlintを実行する - name: RunActionlint run: | set -x docker run --rm -v "${{ github.workspace }}:${{ github.workspace }}" -w "${{ github.workspace }}" rhysd/actionlint:latest
4.Concurrencyで古いワークフローを自動キャンセルする
プルリクエストで起動するワークフローは、最新コード以外での実行がたいてい不要です。自動テストや静的解析はその典型で、古いコードで実行されるワークフローはムダです。
- プルリクエストが更新されるたびにワークフローが実行されると、古いコードに対してもワークフローが動いてしまいます。しかし、自動テストや静的解析の目的は「最新のコードが問題ないかを確認すること」なので、過去のコミットに対するワークフローの実行は無駄になります。
- ワークフロー全体に対して汎用的な設定を記述
# PRで最新のコードだけを実行対象 group: pr-${{ github.event.pull_request.number }} # pushも含む汎用的 group: ${{ github.workflow }}-${{ github.ref }}
# 古いワークフローを自動キャンセルし、リソースを節約 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
5.不要なイベントで起動しないようにフィルタリングする
pull_requestイベントやpushイベントなら、ファイルパスやブランチで起動条件を調整可能です
- イベントに記述
on: pull_request: paths: '**.tf'
6.最安値のUbuntuランナーを優先する
GitHub Actionsは使用時間に応じて課金されます。この使用時間はワークフローの実行時間へ、ランナーごとに設定されている乗率をかけて計算します。 「使用時間」=「実行時間」×「ランナーごとに異なる乗率」 この乗率はUbuntuランナーがもっとも小さいです。
- ジョブに記述
runs-on: ubuntu-latest
7.GITHUB_TOKENのパーミッションはジョブレベルで定義する
GITHUB_TOKENの権限はパーミッションで制御し、最小権限での運用が鉄則です。
- ワークフロー全体に対して記述
permissions: contents: read
8.アクションはコミットハッシュで固定する
Gitタグは可変です。攻撃者がコードを改ざんしてGitタグも上書きすれば、アクション経由でワークフローを侵害できます
- stepに記述
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
9.Bashトレーシングオプションでログを詳細に出力する
デバッグの第一歩は、なにが起きているか把握することです。そこで役立つのが、Bashのトレーシングオプションです。
- stepに記述
set -x
10.workflow_dispatchイベントで楽に動作確認する
workflow_dispatchイベントは起動タイミングが自由なだけでなく、任意のブランチで起動可能です。つまりデフォルトブランチへマージする前に、ワークフローの動作確認ができます
- 手動実行が必要なため、CI/CDパイプライン全体の自動化フローが乱れる可能性もあると思ったので採用を見送りました。
11.ワークフローの背景情報をコメントへ書き残す
あなたが実装したワークフローは、他人にとって自明ではありません。
- 拝承いたします。
+1.OIDCとAWS STSを使用した認証を使用する
- シークレット変数でアクセスキーとシークレットキーを運用するのではなく、OIDCを使用することでAWS STSと連携する。
permissions: id-token: write - name: Configure AWS Credentials via OIDC uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0 with: role-to-assume: arn:aws:iam::xxxxxxxxx:role/github-actions-role aws-region: ap-northeast-1
グッドプラクティス11+1を適用したワークフロー
Terraform PR
name: 'Terraform PR Validate' on: pull_request: paths: '**.tf' branches: - 'develop' permissions: contents: read id-token: write # OIDCトークン発行権限 # 古いワークフローを自動キャンセルし、リソースを節約 concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: terraform: name: 'TerraformValidateonChangedDirectories' runs-on: ubuntu-latest timeout-minutes: 10 env: AWS_DEFAULT_REGION: 'ap-northeast-1' defaults: run: shell: bash steps: # 1. PRの最新コミットをチェックアウト - name: CheckoutRepository uses: actions/checkout@@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2. with: ref: ${{ github.head_ref }} # 2. AWS認証情報の設定(OIDC経由でIAMロールを引き受ける) - name: Configure AWS Credentials via OIDC uses: aws-actions/configure-aws-credentials@ececac1a45f3b08a01d2dd070d28d111c5fe6722 # v4.1.0 with: role-to-assume: arn:aws:iam::xxxxxxxxx:role/github-actions-role aws-region: ap-northeast-1 # 3. Terraform CLIのセットアップ - name: SetupTerraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: terraform_version: '1.11.0' # 4. PRのターゲットブランチをフェッチ - name: FetchBaseBranch run: | set -x git fetch origin ${{ github.base_ref }} # 5. 変更された*.tfファイルが含まれるディレクトリを抽出 - name: ListChangedTerraformDirectories id: list_dirs run: | set -x changed_dirs=$(git diff --name-only origin/${{ github.base_ref }} HEAD | grep '\.tf$' | xargs -n1 dirname | sort -u) echo "ChangeddirectorieswithTerraformfiles:" echo "$changed_dirs" { echo "dirs<<EOF" echo "$changed_dirs" echo "EOF" } >> "$GITHUB_OUTPUT" # 6. 変更された各ディレクトリでTerraformの初期化と検証を実行(-backend=falseで状態取得をスキップ) - name: TerraformValidateinChangedDirectories run: | set -x IFS=$'\n' read -r -a dirs <<< "${{ steps.list_dirs.outputs.dirs }}" || true if [ ${#dirs[@]} -eq 0 ]; then echo "NochangedTerraformdirectoriesfound." else for d in "${dirs[@]}"; do echo "Processingdirectory:$d" (cd "$d" && terraform init -backend=false && terraform validate) done fi