Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
リソースは少数、外部依存なし。単一のAWSアカウント。単一のリージョン。単一の環境。
完了
複数のAWSアカウントと環境、Terraformを使用した既製のインフラモジュール。
完了
多数のAWSアカウントと複数のリージョン、コピーペーストの削減が急務、カスタムインフラモジュール、コンポジションの多用。Terraformを使用。
進行中
超大規模
複数のプロバイダー(AWS、GCP、Azure)。マルチクラウド展開。Terraformを使用。
未着手
中規模
複数のAWSアカウントと環境、既製のインフラモジュール、Terragruntを用いたコンポジションパターン。
未着手
大規模
多数のAWSアカウントと複数のリージョン、コピーペーストの削減が急務、カスタムインフラモジュール、コンポジションの多用。Terragruntを使用。
未着手
超大規模
複数のプロバイダー(AWS、GCP、Azure)。マルチクラウド展開。Terragruntを使用。
未着手
公式のTerraformドキュメントには、設定のすべての側面が詳細に説明されています。次のセクションを理解するために、注意深くお読みください。 このセクションでは、本書で使用される重要な概念について説明します。
リソースとは、aws_vpcやaws_db_instanceなどを指します。リソースはプロバイダーに属し、引数を受け取り、属性を出力し、ライフサイクルを持っています。リソースは作成、取得、更新、削除が可能です。
リソースモジュールは、相互に接続されたリソースの集まりで、共通のアクションを実行します(例:は、VPC、サブネット、NATゲートウェイなどを作成します)。プロバイダー設定に依存し、それはモジュール内またはより上位の構造(例:インフラモジュール)で定義できます。
インフラモジュールは、リソースモジュールの集まりで、論理的には接続されていない場合もありますが、現在の状況やプロジェクト、設定において同じ目的を果たします。プロバイダーの設定を定義し、それが下位のリソースモジュールやリソースに渡されます。通常、論理的な区切りごと(例:AWSリージョン、Googleプロジェクト)に1つのエンティティで機能するよう制限されています。
例えば、 モジュールは、 や のようなリソースモジュールを使用して、 上で を実行するために必要なインフラを管理します。
もう一つの例は、 モジュールです。ここでは、複数の によるモジュールが一緒に使用され、インフラを管理するとともに、Dockerリソースを使用してDockerイメージをビルド、プッシュ、デプロイします。すべてが一つのセットで実行されます。
コンポジションとは、複数のインフラモジュールの集まりで、複数の論理的に分離された領域(例:AWSリージョン、複数のAWSアカウント)にまたがることができます。コンポジションは、組織全体やプロジェクト全体に必要なインフラを記述するために使用されます。
コンポジションはインフラモジュールで構成され、そのモジュールはリソースモジュールで構成され、リソースモジュールは個々のリソースを実装しています。
データソースは読み取り専用の操作を実行し、プロバイダー設定に依存しています。リソースモジュールやインフラストラクチャモジュールで使用されます。
データソースterraform_remote_stateは、上位レベルのモジュールやコンポジションを結びつける役割を果たします。
を使用すると、外部プログラムがデータソースとして機能し、Terraformの設定内で他の場所で使用する任意のデータを提供できます。以下は、モジュールの例で、外部のPythonスクリプトを呼び出してファイル名を計算しています。
データソースは、指定されたURLに対してHTTP GETリクエストを行い、レスポンスに関する情報をエクスポートします。これは、ネイティブなTerraformプロバイダーが存在しないエンドポイントから情報を取得する際に便利です。
インフラモジュールやコンポジションは、をリモートの場所に保存し、他のユーザーが制御可能な方法で取得できるようにするべきです(例:ACLの指定、バージョニング、ロギングの設定)。
プロバイダーやプロビジョナー、その他いくつかの用語については公式ドキュメントで非常に詳しく説明されており、ここで繰り返す必要はありません。私の意見では、これらは良いTerraformモジュールを書くことにあまり関係がないと考えています。
個々のリソースがインフラの「原子」のようなものである一方、リソースモジュールは「分子」(原子から構成される)に相当します。モジュールは、バージョン管理され共有可能な最小の単位です。特定の引数リストがあり、この単位に必要な機能を実行するための基本的なロジックが実装されています。例えば、モジュールは、入力に基づいてaws_security_groupやaws_security_group_ruleリソースを作成します。このリソースモジュール自体は、他のモジュールと組み合わせてインフラストラクチャモジュールを作成するために使用できます。
「分子」(リソースモジュールやインフラモジュール)間でのデータアクセスは、モジュールのアウトプットやデータソースを使って行います。
コンポジション間のアクセスは、リモートステートデータソースを使用することが多いです。あります。
上記の概念を擬似的な関係に当てはめると、次のようになります。
このドキュメントでは、Terraformのベストプラクティスを体系的に説明し、Terraformユーザーが最も頻繁に経験する問題に対する推奨事項を提供します。
Terraformは非常に強力(現在では最も強力なものかもしれません)で、インフラをコードとして管理できるツールの中でも最も広く使用されています。開発者に多くのことを可能にし、サポートや統合が困難になる方法で作業することを制限しません。
本書で説明されている情報の一部は、ベストプラクティスとは見えないかもしれません。そのため、読者が確立されたベストプラクティスと単なる一つの意見に基づくやり方を区別できるように、時折ヒントを使って文脈を提供し、各サブセクションに関連するベストプラクティスの成熟度レベルを示すアイコンを使用しています。
本書は2018年、晴れたマドリードで書き始められ、無料で以下のサイトから入手可能です: https://www.terraform-best-practices.com/
数年後、Terraform 1.0で利用可能な最新のベストプラクティスで更新されました。最終的には、Terraformユーザーにとって疑いのないベストプラクティスと推奨事項のほとんどを本書に収めることを目指しています。
Please contact me if you want to become a sponsor.
他言語への翻訳にご協力いただける方はご連絡ください。
常にフィードバックを求め、この本をコミュニティの成熟や新しいアイデアの実装・検証に応じて更新していきたいと考えています。
特定のトピックに興味がある場合は、を立てるか、取り上げてほしいリクエストに「いいね」をしてください。また、コンテンツを提供したい場合は、ドラフトを書いてプルリクエストを送ってください(現時点で文章の完成度を気にする必要はありません!)。
この本は、 と様々な寄稿者や翻訳者の協力によって管理されています。
この作品はApache 2ライセンスの下で提供されています。詳細はLICENSEをご確認ください。
このコンテンツの著者および寄稿者は、ここで提供される情報の正確性を保証するものではありません。ここで提供される情報は自由に提供されており、このコンテンツやプロジェクトに関わるいかなる人との間にも、契約や合意が成立しないことをご理解ください。 著者および寄稿者は、このコンテンツに含まれる、関連する、またはリンクされている情報の誤りや不足に起因して、いかなる当事者に生じた損失、損害、または混乱に対しても一切の責任を負わないことをここに明記します。これには、過失、事故、その他の理由に起因する誤りや不足が含まれます。
Copyright © 2018-2023 Anton Babenko.
関連するリソースの数
Terraformプロバイダーの数 (下記の「論理的なプロバイダー」に関する注記を参照)
インフラの変更頻度は?
月1回/週1回/1日1回
継続的 (新しいコミットがある度に毎回)
誰がコードを変更しますか? 新しいアーティファクトがビルドされた時にCIサーバーがリポジトリを更新することを許可していますか?
開発者のみがインフラのリポジトリにプッシュ可能
誰でも(CIサーバー上で実行される自動化されたタスクを含む)PRをオープンすることで何かしらの変更を提案可能
どのデプロイメントプラットフォームまたはデプロイメントサービスを使用していますか?
AWS CodeDeploy、Kubernetes、OpenShiftは少し異なるアプローチが必要
環境はどのようにグループ化されていますか?
環境、リージョン、プロジェクトごと
すべてのコードを main.tf に配置することは、初めて始める時やサンプルコードを書く時には良い方法です。それ以外の場合は、以下のように論理的に複数のファイルに分割する方が良いでしょう:
main.tf - モジュール、ローカル変数、データソースを呼び出してすべてのリソースを作成します
variables.tf - main.tfで使用される変数の宣言を含みます
outputs.tf - main.tfで作成されたリソースからの出力を含みます
versions.tf - Terraformとプロバイダーのバージョン要件を含みます
terraform.tfvars は、 composition 以外では使用すべきではありません。
より少ないリソース数で作業する方が容易で速い
terraform planと terraform apply はともに、リソースの状態を確認するためにクラウドAPIを呼び出します
インフラ全体を単一の構成にまとめると時間がかかる可能性がある
セキュリティ侵害時の影響範囲はリソースが少ない方が小さい
関連のないリソースを別々の構成に配置することで、問題が発生した場合のリスクを軽減
プロジェクトはリモートステートを使用して開始してください。その理由は:
あなたのラップトップは、インフラの信頼できる情報源として適切な場所ではありません
gitでtfstateファイルを管理することは悪夢のようなものです
後にインフラレイヤーが複数の方向(依存関係やリソースの数)に成長した時、制御しやすい
一貫した構造と規則を実践してください
手続き型コードと同様に、Terraformコードは最初に人が読むことを考えて書かれるべきです。一貫性があれば、6ヶ月後に変更が必要になった時に役立ちます
Terraformステートファイル内でリソースを移動することは可能ですが、構造や命名に一貫性がない場合、移動が困難になる可能性があります
リソースモジュールはできるだけシンプルに保ってください
変数として渡せる値や、データソースを使用して検出できる値は、ハードコードしないでください
データソースと terraform_remote_state は、特に構成内のインフラモジュール間の「接着剤」として使用してください
本書では、サンプルプロジェクトが複雑さによって小規模から大規模インフラまでグループ分けされています。この区分は厳密なものではないので、他の構造も確認してください。
小規模なインフラストラクチャとは、依存関係が少なく、リソースも少ないことを意味します。プロジェクトが成長するにつれて、Terraformの設定の実行を連鎖させ、異なるインフラストラクチャモジュールを接続し、構成内で値を受け渡す必要性が明らかになってきます。
開発者が使用するオーケストレーションソリューションには、少なくとも5つの異なるグループがあります:
Terraformのみ:非常に直接的なアプローチで、開発者は仕事を完了するためにTerraformだけを知って必要があります。
Terragrunt:インフラ全体のオーケストレーションと依存関係の処理が可能な純粋なオーケストレーションツールです。Terragruntはインフラストラクチャモジュールと構成をネイティブに扱うため、コードの重複を減らすことができます。
自社開発スクリプト:多くの場合、これはオーケストレーションへの最初のステップとして、Terragruntを発見する前に行われます。
Ansibleやそれに類似する汎用自動化ツール:通常、TerraformがAnsible採用後に導入される場合や、Ansible UIが積極的に使用される場合に使用されます。
やその他のKubernetesに触発されたソリューション:Kubernetesエコシステムを活用し、reconciliation loop機能を使用してTerraform設定の望ましい状態を達成することが意味を持つ場合があります。詳細については、「 」 の動画を参照
これを踏まえて、本書では最初の2つのプロジェクト構造、TerraformのみとTerragruntについて検討します。
次の章で Terraform または Terragrunt のコード構造の例を確認してください。
Compliance.tf — Terraform Compliance Simplified. Make your Terraform modules compliance-ready.
—
ソース:
この例は、大規模インフラストラクチャ向けのTerraform構成を整理するコード例で、以下の内容を使用しています。
2つのAWSアカウント
2つのリージョン
2つの独立した環境(プロダクションとステージング、共有は一切なし)。各環境は異なるAWSアカウント内にあり、2つのリージョン間でリソースを展開する
composition-1 {
infrastructure-module-1 {
data-source-1 => d1
resource-module-1 {
data-source-2 => d2
resource-1 (d1, d2)
resource-2 (d2)
}
resource-module-2 {
data-source-3 => d3
resource-3 (d1, d3)
resource-4 (d3)
}
}
}各環境は、Terraform Registryから取得した既製のインフラモジュール(alb)の異なるバージョンを使用
各環境は、ローカルディレクトリから取得される内部モジュールmodules/networkの同じバージョンを使用
インフラが論理的に分離されているプロジェクトに最適(AWSアカウントが分かれている場合)
AWSアカウント間で共有されるリソースを変更する必要がない場合に適している(一つの環境=一つのAWSアカウント=一つの状態ファイル)
環境間での変更のオーケストレーションが不要な場合に適している
環境ごとにインフラリソースが異なり、一般化できない場合に適している(例:ある環境またはリージョンに存在しないリソースがある場合)
プロジェクトが成長するにつれ、これらの環境を互いに最新の状態に保つことが難しくなります。繰り返し行われるタスクには、既製または内部のインフラモジュールの使用を検討してください。
var.website が空のマップでない場合は、Required引数である index_document が設定されている必要があります。
Optional引数の error_document は省略可能です。
variable "website" {
type = map(string)
default = {}
}
resource "aws_s3_bucket" "this" {
# omitted...
dynamic "website" {
for_each = length(keys(var.website)) == 0 ? [] : [var.website]
content {
index_document = website.value.index_document
error_document = lookup(website.value, "error_document", null)
}
}
}website = {
index_document = "index.html"
}
FTP (Frequent Terraform Problems)
Terragrunt - オーケストレーションツール
- コードリンター
- バージョンマネージャー
- バージョンマネージャー用のHashiCorpプラグイン
- プルリクエストの自動化
- で使用するTerraform用のGitフックコレクション
- プルリクエストでのTerraformのクラウドコスト見積もり。Terragrunt、Atlantis、pre-commit-terraformとも連携可能。
リソースとインフラモジュールのバージョンは指定されるべきです。プロバイダーはモジュールの外部で、コンポジション内でのみ設定されるべきです。プロバイダーとTerraformのバージョンもロックすることができます。
マスターとなる依存関係管理ツールは存在しませんが、依存関係の指定をより問題の少ないものにするためのヒントがいくつかあります。例えば、を使用して依存関係の更新を自動化することができます。Dependabotは、依存関係を安全かつ最新の状態に保つためのプルリクエストを作成します。DependabotはTerraformの設定をサポートしています。
ソース: https://github.com/antonbabenko/terraform-best-practices/tree/master/examples/small-terraform
この例には、外部依存がない小規模インフラストラクチャのためのTerraform構成を整理するコードが含まれています。
初めての導入や進めながらのリファクタリングに最適
小規模なリソースモジュールに最適
小規模かつ直線的なインフラストラクチャモジュールに適している(例: )
リソースが少ない(20〜30未満)の場合に適している
すべてのリソースに対して単一の状態ファイルを使用すると、リソースが増え、Terraformの操作が遅くなる可能性があります(-target引数を使ってリソース数を制限することを検討してください)
ソース:
この例は、中規模インフラストラクチャのためのTerraform構成を整理するコード例で、次の内容を使用しています。
2つのAWSアカウント
2つの独立した環境(プロダクションとステージング、共有は一切なし)。各環境は異なるAWSアカウント内に存在する
各環境は、から取得した既製のインフラモジュール(ALB)の異なるバージョンを使用
各環境は、ローカルディレクトリから取得される内部モジュールmodules/networkの同じバージョンを使用
インフラが論理的に分離されているプロジェクトに最適(AWSアカウントが分かれている場合)
AWSアカウント間で共有されるリソースを変更する必要がない場合に適している(一つの環境=一つのAWSアカウント=一つの状態ファイル)
環境間での変更のオーケストレーションが不要な場合に適している
環境ごとにインフラリソースが異なり、一般化できない場合に適している(例:ある環境またはリージョンに存在しないリソースがある場合)
プロジェクトが成長するにつれ、これらの環境を互いに最新の状態に保つことが難しくなります。繰り返し行われるタスクには、既製または内部のインフラモジュールの使用を検討してください。
は、マルチランゲージのプリコミットフックを管理および維持するためのフレームワークです。Pythonで書かれており、コードがGitリポジトリにコミットされる前に、開発者のマシン上で自動的に何かしらの処理を行うための強力なツールです。通常は、リンターを実行したり、コードをフォーマットしたりするために使用されます(を参照)。
Terraformの構成では、pre-commitを使用してコードをフォーマットし、検証し、ドキュメントを更新することができます。
をチェックして、使い方を把握し、すでに使用されている既存のリポジトリ(例:)を確認してください。
は、さまざまな出力形式でTerraformモジュールからドキュメントを生成するツールです。手動で実行することもできます(プリコミットフックなしで)、または を使用してドキュメントを自動的に更新することもできます。
@todo: モジュールのバージョン、リリース、GHアクションを文書化する
Blog post by :
ワークショップ
このガイドで説明されている内容を練習したい人のためのワークショップもあります。
コンテンツはこちらから - https://github.com/antonbabenko/terraform-best-practices-workshop

https://twitter.com/antonbabenko/lists/terraform-experts - Terraformを非常に活発に使用しており、尋ねると多くのことを教えてくれる人々のリスト
https://github.com/shuaibiyy/awesome-terraform - HashiCorpのTerraformに関するキュレーションされたリソースのリスト
http://bit.ly/terraform-youtube - Anton Babenkoによる"Your Weekly Dose of Terraform"というYouTubeチャンネル。レビュー、インタビュー、Q&A、ライブコーディング、そしてTerraformを使用したハッキングなどのライブストリームを提供しています。
https://weekly.tf - Anton Babenkoによる"Terraform Weekly"ニュースレター。Terraformの世界における様々なニュース(プロジェクト、アナウンス、ディスカッション)を提供しています。
リソース名、データソース名、変数名、出力など、すべての場所で -(ダッシュ)の代わりに _(アンダースコア)を使用してください。
UTF-8がサポートされていても、小文字とアルファベットを使用することを推奨します。
リソースとデータソースの引数
リソース名にリソースタイプを(部分的にも、完全にも)繰り返さないでください:
より説明的で一般的な名前が利用できない場合、またはリソースモジュールがこのタイプのリソースを1つだけ作成する場合(例えば、では aws_nat_gateway タイプのリソースは1つだけで、aws_route_table タイプのリソースは複数あるため、aws_nat_gateway は this という名前にし、aws_route_table には private、public、database のようなより説明的な名前をつけるべき)、リソース名は this にすべきです。
resourceのコード例countとfor_eachの使用方法tags の配置count内の条件リソースモジュールで車輪の再発明をしないでください:作業しているリソースの "Argument Reference" セクションで定義されている通りに、変数のname、description、default値を使用してください。
変数のバリデーションサポートはかなり限定的です(例:他の変数へのアクセスや参照ができません)。多くの場合この機能は役に立たないので、それを考慮して計画してください。
型がlist(...)またはmap(...)
出力はスコープ外でも一貫性があり理解しやすいものにしてください(モジュールを使用するユーザーにとって、返される値の型と属性が明らかであるべきです)。
出力名は、含まれるプロパティを説明するものであり、通常望むよりも自由度は低くすべきです。
出力名の良い構造は{name}_{type}_{attribute}のようになります。ここで:
{name}はリソースまたはデータソース名です
outputのコード例セキュリティグループのIDを最大1つ返す場合:
同じタイプの複数のリソースがある場合、出力名ではthisを省略すべきです:
名前には常に単数名詞を使用してください。
引数の値の中や、人が目にする場所(例:RDSインスタンスのDNS名)では、-(ハイフン)を使用してください。
リソースまたはデータソースのブロック内で、count/for_each引数を最初の引数として一番上に記述し、その後に改行を入れて区切ってください。
リソースでサポートされている場合はtags引数を実質的な最後の引数として記述し、必要に応じてその後にdepends_onとlifecycleを続けてください。これらはすべて空行1行で区切ってください。
count/for_each引数で条件を使用する場合は、lengthやその他の式を使用するのではなく、ブール値を使用することを推奨します。
変数ブロック内のキーは次の順序で並べてください:description、type、default、validation
明白だと思える場合でも、将来必要になるので、すべての変数に必ずdescriptionを含めてください。
各キーに厳密な制約が必要な場合を除き、object()のような特定の型よりも、シンプルな型(number、string、list(...)、map(...)、any)の使用を推奨します。
マップのすべての要素が同じ型(例:string)を持つ場合、または変換可能な場合(例:number型はstringに変換可能)は、map(map(string))のような特定の型を使用してください。
特定の深さから型バリデーションを無効にする場合や、複数の型をサポートする必要がある場合は、any型を使用してください。
値{}は時にマップであり、時にオブジェクトです。オブジェクトを作成する方法がないため、マップを作成するにはtomap(...)を使用してください。
data "aws_subnet" "private"の{name}はprivateです
resource "aws_vpc_endpoint_policy" "test"の{name}はtestです
{type}はプロバイダーのプレフィックスを除いたリソースまたはデータソースの型です
data "aws_subnet" "private"の{type}はsubnetです
resource "aws_vpc_endpoint_policy" "test"の{type}はvpc_endpoint_policyです
{attribute}は出力によって返される属性です
例を参照してください。
出力が補間関数と複数のリソースを使用した値を返す場合、{name}と{type}はできるだけ一般的にすべきです(プレフィックスとしてのthisは省略すべき)。例を参照してください。
返される値がリストの場合は、複数形の名前にすべきです。例を参照してください。
明白だと思える場合でも、すべての出力に必ずdescriptionを含めてください。
すべてのモジュールのすべての場所でその出力の使用を完全に制御できない限り、sensitive引数の設定は避けてください。
(0.13以前のバージョンでの従来のアプローチである)element(concat(...))よりも(Terraform 0.13以降で利用可能な)try()を推奨します。
`resource "aws_route_table" "public" {}``resource "aws_route_table" "public_route_table" {}``resource "aws_route_table" "public_aws_route_table" {}`resource "aws_route_table" "public" {
count = 2
vpc_id = "vpc-12345678"
# ... 残りの引数は省略
}
resource "aws_route_table" "private" {
for_each = toset(["one", "two"])
vpc_id = "vpc-12345678"
# ... 残りの引数は省略
}resource "aws_route_table" "public" {
vpc_id = "vpc-12345678"
count = 2
# ... 残りの引数は省略
}resource "aws_nat_gateway" "this" {
count = 2
allocation_id = "..."
subnet_id = "..."
tags = {
Name = "..."
}
depends_on = [aws_internet_gateway.this]
lifecycle {
create_before_destroy = true
}
} resource "aws_nat_gateway" "this" {
count = 2
tags = "..."
depends_on = [aws_internet_gateway.this]
lifecycle {
create_before_destroy = true
}
allocation_id = "..."
subnet_id = "..."
}resource "aws_nat_gateway" "that" { # 最適
count = var.create_public_subnets ? 1 : 0
}
resource "aws_nat_gateway" "this" { # 良い
count = length(var.public_subnets) > 0 ? 1 : 0
}output "security_group_id" {
description = "The ID of the security group"
value = try(aws_security_group.this[0].id, aws_security_group.name_prefix[0].id, "")
}output "this_security_group_id" {
description = "The ID of the security group"
value = element(concat(coalescelist(aws_security_group.this.*.id, aws_security_group.web.*.id), [""]), 0)
}output "rds_cluster_instance_endpoints" {
description = "A list of all cluster instance endpoints"
value = aws_rds_cluster_instance.this.*.endpoint
}