🌵

【terraform】GoogleStyleGuide咀嚼

2024/01/04に公開

概要

GoogleStyleGuide(terraform)
terrafomrを使用する為のベストプラクティス(Google公式)への自己解釈
特に留意するべきことを抜粋(意訳アリ・詳細はそれぞれリンク参照)

🪨スタイルと構造に関する一般的なガイドライン

🌵標準のモジュール構造に従う

  • terraform実行ディレクトリにはmain.tfを起因として、モジュールを呼び出す
  • モジュール配備ディレクトリにはREADME.mdを必ず配置
  • terraform実行ディレクトリにはexamples/ディレクトリを作成し、
    サンプルコードをとREADME.mdを配置
  • モジュールの*.tfは目的別に分け、長大化を避ける
  • モジュールディレクトリにはCHANGELOG.mdを配置し履歴を手書きで残す

🌵命名規則を採用する

  • リソース名には_を使用する。-を使用しない
  • 参照されるリソースについては名前を簡略化する
  • リソースタイプに含まれる文言をリソース名に含まない
    • aws_s3_bucketというリソース名に s3、bucket というワードは避ける

🌵変数を慎重に使用する

  • すべての変数を variables.tf で宣言する
  • 単位(MB・GB等)が影響する変数は、単位を変数名に組み込むとメンテナンス性が上がる
  • 必須でないオプショナルな変数は、接頭語に optional などを付与
    • requiredの場合は特に明記しないことで差別化を計る
  • ON・OFFの条件を判定する場合は、正(enable・true)の値を変数名に組み込む
  • 変数名のdescriptionは必須明記
  • 変数の型定義は必ず行う
  • 環境に依存しない変数についてはデフォルト値を設ける
  • 非公開のvalueについては、.gitignoreに含まれるファイルで管理する

🌵出力を公開する

  • output.tfを使用する際は、仕様についてREADME.mdに明記する
  • output.tfにて渡す変数は、descriptionを併せて記載する
  • variablesから、outputには直接変数を渡さないこと(依存関係が崩れる為)

🌵データソースを使用する

  • データソースにて外部値を参照する場合は、引用するリソースと一緒の箇所に記載する
    • 但し、データソースを多く参照する場合については専用のdata.tfを検討する

🌵カスタム スクリプトの使用を制限する

  • カスタムスクリプトは極力使用しないこと
    • terraformの挙動が対応していない場合のみ使用
    • カスタムスクリプトはscripts/ディレクトリを作成して配備する
    • README.mdに詳細を記載し、メンテナンス性を担保すること

🌵静的ファイルを別のディレクトリに配置する

  • terraformにて参照する静的ファイルはfiles/ディレクトリ配下に配備する
  • template関数を使用して参照するファイルはtemplates/ディレクトリ配下に配備する
    • templateファイルは拡張子.tftplを使用する

🌵ステートフル リソースを保護する

  • データベース・ディスクなどのステートフルリソースは削除保護を有効にする

🌵組み込みの書式設定を使用する

  • terraform fmtに準拠したコーディングをする(要はterraform fmtを実行しろ)

🌵式の複雑さを制限する

  • 1行のコードに関数を多用しない
    • local関数などを使用し、分割をこころがけ可読性をあげる
  • 1行のコードに3項演算子を複数用いない

🌵条件値に count を使用する

  • 条件付きでリソースをインスタンス化するには、count メタ引数を使用する
    variable "readers" {
    description = "..."
    type        = list
    default     = []
    }
    
    resource "resource_type" "reference_name" {
    // Do not create this resource if the list of readers is empty.
    count = length(var.readers) == 0 ? 0 : 1
    ...
    }
    

🌵反復されるリソースに対して for_each を使用する

  • 複数リソースを一括で作成する際は、for_each関数を使用する

🪨再利用可能なモジュール

🌵モジュールで必要な API を有効にする

  • GCPでterraformを使用する際は、project_servicesリソースを使用してAPIを有効化する

🌵オーナー ファイルを含める

  • すべての共有モジュールにOWNERS ファイルを含める
    • GitHub では CODEOWNERSが相当

🌵タグ付きバージョンをリリースする

  • バージョニングによって互換性を失うリソースについては、メジャーバージョンを固定する

🌵プロバイダやバックエンドを構成しない

  • 共有モジュールでbackendやproviderは指定せず、terraformコマンド実行ディレクトリにて指定する
  • providerバージョンを共有モジュールで記載する場合は、
    required_providersブロックに最低限のバージョンを記載する

🌵複雑なロジックにインライン サブモジュールを使用する

  • インラインサブモジュールとはモジュール内で更に呼び出す為のモジュールである
    • モジュール内は完結に記載できるようサブモジュールを使用するケースがある
    • インラインサブモジュールはモジュールからしか呼ばれなく、外部からは呼ばれない作りにする

🪨Terraform のルート モジュール

  • ルートモジュールとはterraform CLIを実行するディレクトリを指す

🌵各ルート モジュールのリソース数を最小限に抑える

  • ルートモジュールから呼び出されるリソースは100個未満に抑える

🌵環境固有のサブディレクトリにアプリケーションを分割する

  • サービスのディレクトリ配下には下記の2つのディレクトリを設ける
    • サービスモジュールmodules/
      • 再利用し、複数構築されるリソースはサービスモジュールと呼ぶ(EC2など)
      • 再利用し、環境を跨ぐ(Stage・Prod)リソースは参照モジュールと呼ぶ(ACMなど)
    • 環境ディレクトリenvironments/
      • environments/ディレクトリ配下は環境ディレクトリを掘る

🌵環境ディレクトリを使用する

  • サービスモジュールは共通入力をハードコーディングする
  • 環境毎の固有値は変数化して、ルートモジュールからの値を参照する構成にする
  • 環境ディレクトリ配下に下記ファイルを必須とする
    • tfstateを管理するbackend.tf
    • サービスモジュールをインスタンス化するmain.tf
  • terraformの機能である、workspaceは非推奨

🌵リモート状態から出力を公開する

  • terraform_remote_stateとは、別のルートモジュールに存在するtfstateファイルの情報を読み込むことが可能である
    • e.g.)別ディレクトリの参照モジュールにある、VPCのtfstate情報を読み込む
      参考サイト

🌵マイナー プロバイダのバージョンに固定する

  • versions.tfというファイルを設け、terraformブロックにrequired_providersを記載する

🌵変数を tfvars ファイルに格納する

  • terraform.tfvarsに変数の値を入力する
  • -var-fileはイレギュラーな為、使用しないこと

🪨バージョン管理

🌵デフォルトのブランチ戦略を使用する

  • 機能ブランチ名はfeature/$feature_name
  • バグ修正ブランチfix/$bugfix_name

🌵ルート構成に環境ブランチを使用する

  • 環境名・ブランチ名・ディレクトリ名は構成を考慮して設計する
    e.g.)dev環境構築
    devという名前の入ったブランチ名を使用し、環境ディレクトリ名はdevという名前で作成

🌵シークレットを commit しない

  • secret情報はSecretManagerや環境変数を利用する
  • terraformCLIを実行する際に、標準出力にアウトプットされるかも懸念すること

🪨運用

🌵常に計画してから始める

  • terraform plan事前に実行し、反映されるリソースについて計画通りであることを確認する

🌵既存のリソースのインポートを回避する

  • terraform import による既存リソースのインポートは極力避ける
    • 手動で作成したリソースやリソースの来歴を完璧に把握するのが困難な為

🌵Terraform の状態を手動で変更しない

  • terraform.tfstate は手動で変更せず、terraform stateで更新する

🌵バージョンの固定状態を定期的に確認する

  • dependbotを使いバージョンの固定状態の問題性を定期的に確認する

🌵Terraform にエイリアスを設定する

  • ローカル開発環境用にエイリアスを用意し、 コマンド短縮・タイポのフェイルセーフに万全を期す
    • alias tf="terraform"
    • alias terrafrom="terraform"

🪨セキュリティ

🌵リモート状態を使用する

  • 状態ファイル(tfstate)についてはリモートにて管理する
    • チームにての共同作業が制限されるので、チーム内での構築ルールを設ける
    • tfstateファイルについてはgitignoreに記載し、git管理外とする

🌵状態を暗号化する

  • 状態ファイル(tfstate)の保存の際に必ず暗号化を行う(AWS・GCP)
    • GOOGLE_ENCRYPTION_KEY やAWSのKMS

🌵シークレットを状態に保持しない

  • シークレット情報についてはterraformのコード管理からは外す
    • シークレットの箱はterraformで管理し、valueは手動で設定を行う

🌵機密出力をマークする

  • 標準出力に出したくない機密情報についてはsensitive = true をリソースに組み込む
    • planやapplyに出力されないので、README.mdなどに明記すること

🌵職掌分散を確保する

  • terraform実行の際に使用するプロファイルについては、権限を制限し必要最低限にする

🌵適用前のチェックを実行する

  • terraform vet planコマンドを事前に実行する
    • セキュリティ・コンプライアンスに準拠しているかを構築前にチェックし、
      出力が無い状態をキープする

🌵継続的な監査を実施する

  • terraformで構築したリソースに対して、
    terraform外部の自動セキュリティチェックを導入し安全性を担保する

🪨テスト

terraformCLIを実行する際の留意点して下記を挙げる

  • Terraform テストを実行すると、実際のインフラストラクチャが作成、変更、破棄されるため、テストに時間と費用がかかる可能性がある
  • エンドツーエンドの単体テストはterraformで完結しないが、ストランタイムの高速化・モジュール毎に反復開発が迅速に行えることを有効活用する
  • tfstateファイルについてテスト中は極力弄らない。テスト中にtfstateファイルを弄ると、予期せぬリソース変更が検出される為

🌵最初は低コストのテスト方法を使用する

  • 静的分析
    コンパイラ・リンター・ドライランを使用しリソースを構築せず、構文・構造をテストする
  • モジュール統合テスト
    サービス単位でモジュールを構築し、想定されるリソース群が作成されることを確認する
  • エンドツーエンドのテスト
    テスト環境にて全てのリソースを構築し、本番と同等の環境が構築されることを確認する

🌵小規模で始める

  • 小規模のテストを繰り返し実施した後に、複雑なテストを行いフェイルファストアプローチを実行する

🌵プロジェクト ID とリソース名をランダム化する

  • resource "random_id"を使用しリソース名の競合を避ける

🌵テストに別の環境を使用する

  • テスト用の環境は個別で用意すること
  • 構築・削除を前提とした環境とし、サービスアカウントやフォルダなどもテスト用を設ける

🌵すべてのリソースをクリーンアップする

  • リソースはクリーンアップするまでがフローである(構築しっぱなしだと課金される為)

🌵テスト ランタイムを最適化する

  • テストを並行して実行する
    • フレームワーク(terratest)などを使用することで、並行してリソース構築が可能である
      構築依存関係などを確認し、構築順が発生していないかを確認可能
  • 段階的にテストする
    • 別個にテストできるような構成に分離しておき、個別で反復開発できるよう独立させる

Discussion