Terraform AWSクラウド:健全なインフラストラクチャ管理
公開: 2022-03-11アプリケーションの作成は話の一部にすぎません。 価値のあるものにするためには、拡張可能な場所にデプロイする必要があり、高可用性で実行する必要があり、バックアップが必要です。
少なくともこの展開プロセスを把握する必要がある開発者はますます増えています。 これは、たとえば、システムの複雑さが増すにつれて、DevOpsが最近頻繁に要求される役割になることで明らかになります。 これらの変化を無視することはできず、簡単に展開できるようにアプリケーションを設計する方法を知っておく必要があります。
これは私たちのクライアントの利益にもなります。彼らは私たちを私たちの分野の専門家として雇い、私たちが製品全体を、多くの場合最初から最後まで提供することを期待しています。 彼らには要件があり、多くの場合、ビジネスソリューションが実行されているスタックに気づいていません。 結局のところ、重要なのは製品のビジネス価値です。
Terraformの紹介
導入とインフラストラクチャの管理は単純なプロセスではありません。 絶えず変化する多くのドメインの専門知識に加えて、さらに別のツールまたは新しいワークフローを学ぶ必要があります。 これを延期している場合、この記事は、インフラストラクチャ管理への1つのアプローチに精通するチャンスです。 最終的に、Terraformの使用に自信が持てるようになり、考えられるアプローチと課題について詳しく知ることができれば幸いです。 このツールを使用して、少なくともクラウドインフラストラクチャの一部の管理を開始できるはずです。
Terraformは抽象化レイヤーです、そうです、そして抽象化は漏れやすいです、私は同意します。 しかし、最終的には、問題を解決し、抽象化を管理するビジネスを行っています。 これは、私たちの日常業務においてより正気を提供することを目的としています。
目標
Terraformとは何か、それがエコシステム全体にどのように適合するか、そして他の同様のツールとどのように比較されるかを説明します。 次に、チーム用にマルチ環境で本番環境に対応したTerraformセットアップを構成するために必要な手順を示します。 Terraform構成の記述の基本、つまり共有可能なモジュールを使用して複雑さと重複コードを管理する方法について説明します。
例はすべて、Amazon Web Services(AWS)という1つのクラウドプロバイダーに焦点を当てています。 これは私が最も経験を積んだクラウドですが、すべての情報は他のクラウドにも適用されるはずです。
最後に、始めたときに知っていたらよかったと思うメモをいくつか示します。構文の落とし穴、癖、Terraformが私の選択ツールではない場合などです。
構文の本質的な詳細には焦点を当てません。 HashiCorpがTerraformに提供している素晴らしいドキュメントとガイドを読むことで、すぐにそれを理解することができます。 最初からわかりにくいかもしれないことや、Terraformを使い始める前に知っていたらよかったことに焦点を当てたいと思います。 うまくいけば、これはあなたを正しい方向に導き、Terraformを将来のプロジェクトで使用するためのツールと見なすことができるようになるでしょう。
インフラストラクチャ管理は難しい
クラウドでアプリの環境を設定するには、複数の手順が必要です。 それらをすべて詳細なチェックリストとして書き留めて、それらに注意深く従わない限り、常に間違いを犯します。 結局のところ、私たちは人間です。 これらの手順を共有するのは困難です。 多くの手動手順を文書化する必要があり、ドキュメントはすぐに古くなる可能性があります。 次に、これらすべてに、単一のアプリの環境の総数( dev 、 test / qa 、 stage 、およびprod )を掛けます。 また、それぞれのセキュリティについても考慮する必要があります。
すべてのツールに、複雑さを忘れて使用できるUIが備わっているわけではありませんか?
UIはあります—AWSマネジメントコンソールがその代表的な例です。 しかし、これらのツールは内部で多くのことを行います。 UIを1回クリックすると、実際には把握しにくい一連の変更が呼び出される場合があります。 通常、UIで行ったことを元に戻す方法はありません(通常の「Areyousure?」プロンプトでは不十分なことがよくあります)。 さらに、変更を確認するために2つ目の目を持つことは常に良い考えですが、UIを使用する場合は、この人と一緒に座ったり、変更が行われた後に変更を確認したりする必要があります。これは、レビュー。 各クラウドプロバイダーには、マスターする必要のある独自のUIがあります。 UIは使いやすいように設計されているため(必ずしも単純ではありません!)、「これはほんの小さな調整です」などの妄想的なアプローチや、48時間で忘れてしまうクイックプロダクションホットフィックスになりがちです。 このような手動クリックも自動化が非常に困難です。
CLIツールはどうですか?
ユースケースでは、UIツールよりも優れています。 ただし、手作業で変更を加えたり、手に負えなくなってしまう可能性のあるbashスクリプトを記述したりする傾向があります。 さらに、各プロバイダーには独自のCLIツールがあります。 これらのツールを使用すると、コミットする前に変更を確認できません。 ありがたいことに、これは新しい問題ではなく、これを支援するツールがあります。
オーケストレーションと構成管理
インフラストラクチャの管理に使用されるツールとプラクティスのいくつかのカテゴリを定義します。 これらは、オーケストレーションと構成管理です。 それらは大まかに宣言型および命令型プログラミングモデルと考えることができます(PrologとPythonを考えてみてください)。 それぞれに長所と短所がありますが、それらすべてを理解し、特定の仕事に最適なツールを適用するのが最善です。 また、オーケストレーションには通常、構成管理よりも高い抽象化レベルが含まれます。
オーケストレーション
オーケストレーションは、宣言型プログラミングパラダイムに似ています。 私はそれをオーケストラの指揮者であると考えるのが好きです。 指揮者の仕事は、ウィキペディア(リンク)によって「ジェスチャーを使用して複数のプレーヤーまたは歌手の同時演奏を指示する芸術」としてうまく要約されています。 指揮者は音楽のビートとテンポ、強弱法、手がかりを伝えますが、実際の作業は楽器演奏の専門家である個々のミュージシャンによって行われます。 そのような調整がなければ、彼らは完璧な作品を演奏することができません。
これがTerraformが適している場所です。 これを使用して、ITインフラストラクチャの一部を実行します。何をデプロイするかを指示すると、Terraformはそれをすべてリンクし、必要なすべてのAPI呼び出しを実行します。 この分野にも同様のツールがあります。 最もよく知られているのはAWSクラウドフォーメーションです。 Terraformよりもエラーが発生した場合のリカバリとロールバックのサポートが優れていますが、私の意見では、学習曲線が急になっています。 また、クラウドに依存しません。AWSでのみ機能します。
構成管理
これらのプラクティスの補完的な側面は、構成管理アプローチです。 このパラダイムでは、特定の目的の構成に到達するためにツールが実行する必要のある正確な手順を指定します。 手順自体は小さく、複数のオペレーティングシステムをサポートしている場合がありますが、実行の順序について積極的に検討する必要があります。 彼らは(あなたがそれで彼らをプログラムしない限り)現在の状態と周囲を認識しておらず、そのため、あなたが彼らに与えるどんなステップでも盲目的に実行します。 これにより、構成のドリフトと呼ばれる問題が発生する可能性があります。この問題では、特に手動で変更を加えた場合に、リソースが最初に表現しようとしていたものとゆっくりと非同期になります。 これらは、個々のインスタンスでのサービスの管理とプロビジョニングに優れています。 このワークフローで優れているツールの例は、Chef、Puppet、Ansible、Saltです。
オーケストレーションは、リソースをペットではなく牛として扱うインフラストラクチャへのアプローチを実施します。 各VPSを手動で「育成」する代わりに、問題が発生したときに正確なコピーに置き換えることができます。 私はあなたが単に気にしないで、最高のものを期待して物事を再開するという意味ではありません。
代わりに、コードの問題を調査して修正してからデプロイする必要があります。
Ansible(およびその他のCMツール)を使用してAWSインフラストラクチャを管理できますが、これには多くの作業が必要であり、特にインフラストラクチャが頻繁に変更されて複雑さが増す場合は、エラーが発生しやすくなります。
覚えておくべき重要なことの1つは、オーケストレーションと構成管理のアプローチが互いに競合しないことです。 それらは互換性があります。 AutoScalingグループにEC2(VPS)インスタンスのグループをTerraformで管理し、ディスクのスナップショットであるAWSアプリケーションイメージ(AMI)を実行することは、まったく問題ありません。これは、Ansibleなどの必須の手順で準備されたものです。 。 Terraformには、マシンの起動後に外部のプロビジョニングツールを実行できる「プロバイダー」の概念もあります。
Terraformのドキュメントは、これをさらに説明し、Terraformをエコシステム全体に配置するのに役立ちます。
Terraformとは何ですか?
HashiCorpによって作成されたオープンソースツールであり、バージョン管理および共有され、レビュー可能な宣言型構成ファイルとしてインフラストラクチャを体系化できます。
HashiCorpの名前はベルを鳴らす必要があります。これにより、Nomad、Vault、Packer、Vagrant、Consulも作成されます。 これらのいずれかを使用したことがある場合は、ドキュメントの品質、活気のあるコミュニティ、およびソリューションに期待できる有用性をすでに知っているツールです。
コードとしてのインフラストラクチャ
Terraformはプラットフォームに依存しません。 これを使用して、ベアメタルサーバーまたはAWS、Google Cloud Platform、OpenStack、Azureなどのクラウドサーバーを管理できます。 Terraformの用語では、これらはプロバイダーと呼ばれます。サポートされているプロバイダーの完全なリストを読むことで、スケールの感覚をつかむことができます。 複数のプロバイダーを同時に使用できます。たとえば、プロバイダーAがVMを構成しているが、プロバイダーBがDNSレコードを構成して委任している場合です。
これは、構成ファイルを1回変更するだけでクラウドプロバイダーを切り替えることができるということですか? いいえ、少なくとも自動化された方法では、あなたがそれを望んでいるとは思いません。 問題は、プロバイダーごとに機能、提供内容、フロー、アイデアなどが異なる可能性があることです。つまり、同じ概念を表現するには、プロバイダーごとに異なるリソースを使用する必要があります。 ただし、これはすべて、使い慣れた単一の構成構文で実行でき、まとまりのあるワークフローの一部にすることができます。
Terraformセットアップの重要な部分
- Terraformバイナリ自体、インストールする必要があります
- ソースコードファイル、つまり構成
- Terraformが管理するリソースを表す状態(ローカルまたはリモート)(詳細は後で説明します)
Terraform構成の記述
Terraform構成コードは、HCL言語を使用して*.tf
ファイルに記述します。 JSON形式( *.tf.json
)を使用するオプションがありますが、これは人間ではなくマシンと自動生成を対象としています。 HCLを使用することをお勧めします。 HCL言語の構文については詳しく説明しません。 公式ドキュメントは、HCLの記述方法、変数と補間の使用方法を説明する素晴らしい仕事をしています。 例を理解するために必要な最低限のことだけを述べます。
Terraformファイル内では、主にリソースとデータソースを扱っています。 リソースは、AWS EC2インスタンス、RDSインスタンス、Route53 DNSレコード、セキュリティグループのルールなどのインフラストラクチャのコンポーネントを表します。 クラウドアーキテクチャ内でプロビジョニングおよび変更できます。
Terraformを設定したとすると、 terraform apply
を発行すると、以下のコードで完全に機能するEC2インスタンスが作成されます(特定のプロパティのみが表示されます)。
resource "aws_instance" "bastion" { ami = "ami-db1688a2" # Amazon Linux 2 LTS Candidate AMI 2017.12.0 (HVM), SSD Volume Type - ami-db1688a2 instance_type = "t2.nano" key_name = "${var.key_name}" subnet_ vpc_security_group_ids = ["${aws_security_group.bastion.id}"] monitoring = "false" associate_public_ip_address = "true" disable_api_termination = "true" tags = { Name = "${var.project_tag}-bastion-${var.env}" Env = "${var.env}" Application ApplicationRole = "Bastion Host" Project = "${var.project_tag}" } }
一方、特定のコンポーネントに関するデータを変更せずに読み取ることができるデータソースがあります。 ACMが発行した証明書のAWSID(ARN)を取得したいですか? データソースを使用します。 違いは、構成ファイルでデータソースを参照するときに、データソースの前にdata_
が付いていることです。
data "aws_acm_certificate" "ssl_cert" { domain = "*.example.com" statuses = ["ISSUED"] }
上記は、AWSALBと一緒に使用できる発行済みのACMSSL証明書を参照しています。 ただし、それをすべて行う前に、環境をセットアップする必要があります。
フォルダ構造
Terraform環境(およびその状態)は、ディレクトリで区切られています。 Terraformは、ディレクトリ内のすべての*.tf
ファイルを1つの名前空間にロードするため、順序は重要ではありません。 次のディレクトリ構造をお勧めします。
/terraform/ |---> default_variables.tf (1) /stage/ (2) |---> terraform.tfvars (3) |---> default_variables.tf (4) |---> terraform.tf (5) |---> env_variables.tf (6) /prod/ /<env_name>/
-
default_variables.tf
–すべての最上位変数とオプションでそれらのデフォルト値を定義します。 それらは、シンボリックリンクを使用して各環境(ネストされたディレクトリ)で再利用できます。 -
/stage/
–完全に別個の環境の構成を保持するディレクトリー(ここではstage
と名付けられていますが、何でもかまいません)。 このフォルダー内で行われた変更は、他の環境(env-prod
など)から完全に独立しています。これは、stage
に加えられた変更で本番環境を台無しにしないために必要なものです。 -
terraform.tfvars
–変数値を定義します。.tfvars
ファイルは、定義された変数のkey=val
ペアを保持するという点で.env
ファイルに似ています。 たとえば、これは私が使用するAWSprofile
、AWSkey_name
、およびkey_path
を指定します。 Gitでは無視できます。 -
default_variables.tf
–これはファイル(2)へのシンボリックリンクであり、環境に依存しない変数を繰り返すことなく共有できます。 -
terraform.tf
–これは各環境の主な構成です。 バックエンドを構成するterraform {}
ブロックを保持します。 ここでプロバイダーも構成します。 -
env_variables.tf
–このファイルはenv固有の変数を保持します。 AWSではすべてのリソースにEnv=<env_name>
のタグを付けているため、このファイルは通常、env
という1つの変数のみを定義します。
もちろん、これが環境を構築する唯一の方法ではありません。 これは、関心の分離を明確にすることで、私にとってうまく機能したものです。
バックエンド構成
Terraformの状態についてはすでに説明しました。 これは、Terraformワークフローの重要な部分です。 状態が実際に必要かどうか疑問に思うかもしれません。 Terraformは、インフラストラクチャの実際の状態を取得するために、常にAWS APIにクエリを実行することはできませんか? 考えてみれば、Terraformは、宣言型構成ファイルで管理するものと、それらのファイルが実際に対応するもの(クラウドプロバイダーの環境)との間のマッピングを維持する必要があります。 Terraform構成ファイルを作成するときは、たとえば、公開するセキュリティグループ用に作成される個々のEC2インスタンスやARNのIDは気にしないことに注意してください。 ただし、内部的には、Terraformは、特定のリソースブロックがID/ARNを持つ具体的なリソースを表すことを知る必要があります。 これは、変更を検出するために必要です。 さらに、状態はリソース間の依存関係を追跡するために使用されます(これも通常は考える必要はありません!)。 これらは、(通常は)並列化して実行できるグラフを作成するために使用されます。 いつものように、Terraformの状態とその目的に関する優れたドキュメントを読むことをお勧めします。
状態はアーキテクチャの唯一の正しい情報源であるため、自分とチームが常に最新バージョンで作業していること、および状態への非同期アクセスによって競合が発生しないことを確認する必要があります。 状態ファイルでのマージの競合を解決したくないと思います。
デフォルトでは、Terraformは、(各envの)現在の作業ディレクトリにあるディスク上のファイルに状態をterraform.tfstate
ファイルとして保存します。 これは、あなたがその仕事の唯一の開発者であることがわかっている場合、またはTerraformを学習して実験しているだけの場合は問題ありません。 技術的には、状態をVCSリポジトリにコミットできるため、チームで機能させることができます。 ただし、その場合は、全員が常に最新バージョンの状態で作業していること、および誰も同時に変更を行わないことを確認する必要があります。 これは一般的に大きな頭痛の種であり、私は強くお勧めします。 さらに、誰かが単一開発操作に参加した場合でも、状態の代替の場所を構成する必要があります。
幸いなことに、これはTerraformに組み込まれている優れたソリューション(いわゆるリモートステート)の問題です。 リモート状態を機能させるには、使用可能なバックエンドプロバイダーの1つを使用してバックエンドを構成する必要があります。 次のバックエンドの例は、AWSS3とAWSDynamoDB(AWS NoSQLデータベース)に基づいています。 S3しか使用できませんが、状態ロックと整合性チェックのメカニズムが失われます(推奨されません)。 以前にローカル状態のみを使用していた場合、リモートバックエンドを構成すると、状態を最初に移行するオプションが提供されるため、何も失われません。 バックエンド構成の詳細については、こちらをご覧ください。
残念ながら、鶏が先か卵が先かという問題があります。S3バケットとDynamoDBテーブルを手動で作成する必要があります。 まだ状態がないため、Terraformはそれらを自動的に作成できません! https://github.com/gruntwork-io/terragruntのように、AWS CLIを使用して自動化するソリューションがいくつかありますが、このブログ投稿のメイントピックから逸脱したくありません。
S3およびDynamoDBバックエンド構成について知っておくべき重要なことは次のとおりです。
- S3バケットのバージョン管理を有効にして、人為的エラーやマーフィーの法則から安全にします。
- DynamoDBテーブルには、読み取りと書き込みのレート制限(容量と呼ばれる)があります。 リモート状態に多くの変更を加える場合は、そのテーブルに対してDynamoDB AutoScalingを有効にするか、十分に高いR/W制限を設定してください。 そうしないと、Terraformは、多くの呼び出しを実行するときにAWSAPIからHTTP400エラーを取得します。
まとめると、次のバックエンド構成をterraform.tf
に配置して、S3とDynamoDBのリモート状態を構成できます。
terraform { # Sometimes you may want to require a certain version of Terraform required_version = ">= 0.11.7" # Stores remote state, required for distributed teams # Bucket & dynamoDB table have to be created manually if they do not exist # See: https://github.com/hashicorp/terraform/issues/12780 backend "s3" { bucket = "my-company-terraform-state" key = "app-name/stage" region = "eu-west-1" # 5/5 R/W Capacity might not be enough for heavy, burst work (resulting in 400s). Consider enabling Auto Scaling on the table. # See: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ProvisionedThroughput.html dynamodb_table = "terraform-state-lock-table" } }
これは一度に取り入れることがたくさんありますが、これを環境ごとに1回行うと、忘れてしまう可能性があることを忘れないでください。 状態のロックをさらに制御する必要がある場合は、HashiCorp Terraform Enterpriseがありますが、ここでは取り上げません。
プロバイダー
このバックエンドにアクセスでき、クラウドプロバイダーと通信できるようにするには、いわゆるプロバイダーを構成する必要があります。 次のブロックは、 terraform.tf
ファイル(環境ごと)に配置できます。
provider "aws" { profile = "${var.profile}" region = "${var.region}" version = "~> 1.23.0" }
profile
とregion
の変数はterraform.tfvars
ファイルに保存されますが、無視してもかまいません。 profile
変数は、標準のクレデンシャルファイルを使用してAWSクラウドのセキュリティクレデンシャルを保持する名前付きプロファイルを参照します。 いくつかのバージョン制約も設定していることに注意してください。 すべてのバックエンドの初期化に関する知識がなくても、Terraformがプロバイダープラグインをアップグレードすることは望ましくありません。 特に、慎重なアップグレードが必要なAWSプロバイダーのバージョン2.xがあります。
バックエンドの初期化
各バックエンド構成では、最初に初期化ステップが必要であり、変更が加えられるたびに必要です。 初期化ではTerraformモジュールも構成されるため(これらについては後で詳しく説明します)、それらを追加するときに、initステップも再実行する必要があります。 この操作は、複数回実行しても安全です。 バックエンドの初期化中に、Terraformがすべての変数を読み取って状態を構成できるわけではなく、セキュリティ上の理由(秘密鍵など)のために読み取ることもできないことに注意してください。 これを克服し、この場合はデフォルトとは異なるAWSプロファイルを使用するために、変数のk=v
ペアを受け入れる-backend-config
オプションを使用できます。 これは部分構成と呼ばれます。
terraform init -backend-config=profile=<aws_profile_name>
Terraformファイルは、特定のディレクトリに制限されているスコープを共有します。 これは、サブフォルダが親ディレクトリコードに直接接続されていないことを意味します。 ただし、コードの再利用、複雑さの管理、および共有を可能にするモジュールを定義しています。
ワークフロー
Terraformコードを使用する場合の一般的なワークフローは次のとおりです。
- インフラストラクチャの構成を記述します。
- 実際にどのような変更が行われるかを確認します(
terraform plan
)。 - 必要に応じて、手順2で見た正確な変更を実行します(
terraform apply
)。 - GOTO 1
テラフォームプラン
Terraform plan
コマンドは、 apply
コマンドの発行時にインフラストラクチャに加えられる変更のリストを表示します。 プラン自体は何も変更しないため、 plan
複数回発行しても安全です。
プランの読み方
Terraform内のオブジェクト(リソースおよびデータソース)は、完全修飾名で簡単に識別できます。
- リソースの場合、IDは次のようになります:
<resource_type>.<resource_name>
—例:aws_ecs_service.this
。 - モジュール内のリソースの場合、追加のモジュール名があります:
module.<module_name>.<resource_type>.<resource_name>
—例:module.my_service_ecs_service_task.aws_ecs_service.this
。 - データソース(モジュールの内部と外部):(
(module.<module_name>).data.<resource_type>.<resource_name>
—例:module.my_service_ecs_service_task.data.aws_ecs_task_definition.this
。
リソースタイプは特定のプロバイダーに固有であり、通常はその名前( aws_…
)が含まれます。 AWSで利用可能なリソースの全リストは、ドキュメントにあります。
特定のリソースに対してプランが表示するアクションは5つあります。
-
[+]
追加–新しいリソースが作成されます。 -
[-]
破壊–リソースは完全に破壊されます。 -
[~]
インプレースで変更–リソースが変更され、1つ以上のパラメーターが変更されます。 これは一般的に安全です。 Terraformは、変更されるパラメーターとその方法(可能な場合)を示します。 -
[- / +]
–リソースが削除され、新しいパラメーターで再作成されます。 これは、インプレースで変更できないパラメータに変更が加えられた場合に発生します。 Terraformは、どの変更がリソースの再作成を強制するかを示し、次のコメントが赤で表示されます:((forces new resource)
。 リソースがまったく存在しない期間があるため、これは潜在的に危険です。 また、他の接続された依存関係を壊す可能性があります。 結果がどうなるかわからない場合、またはダウンタイムを気にしない場合を除いて、このような変更を回避することをお勧めします。 -
[<=]
–データソースが読み取られます。 これは読み取り専用の操作です。

上記は計画例です。 私が行った変更は次のとおりです。
- 最初の要塞インスタンスの
instance_type
を変更しました - 2番目の要塞インスタンスを追加しました
- セキュリティグループの名前を変更しました
最後の変更は、親グループの名前の変更を自動的に検出したセキュリティグループルールであることに注意してください。 私の意見では、全体の計画は非常に読みやすいです。 一部のパラメーター値は<computed>
として表示されます。これは、変更されることを意味するのではなく、この段階で取得および表示できないことを意味します(まだ作成されていないSG名など)。 plan
コマンドでは、変更されたリソースのみが表示されることに注意してください。 省略されたものは変更されません。
通常、Terraformコマンドは追加のパラメーターを受け入れます。 planコマンドの最も重要なパラメーターは-out
オプションです。これにより、プランがディスクに保存されます。 この保存されたプランは、applyコマンドによって(保存されたとおりに)実行できます。 これは非常に重要です。そうしないと、 plan
の発行とapply
の発行の間に同僚などによって変更が加えられる可能性があるためです。 例:
-
plan
を発行し、見栄えがよいことを確認します。 - 誰かがあなたに知られていない状態を変更します。
- 問題
apply
—おっと、それは計画されたもの以外の何かをしました!
ありがたいことに、このワークフローはTerraformv0.11.0で改善されました。 このバージョン以降、 apply
コマンドは、承認する必要のある計画を自動的に提示します(明示的にyes
と入力します)。 利点は、このプランを適用すると、提示されたとおりに実行されることです。
もう1つの便利なオプションは-destroy
。これは、Terraformが管理するすべてのリソースを破棄する結果となる計画を示します。 -target
オプションを使用して、特定のリソースを破棄の対象にすることもできます。
Terraformアプライ
与えられた計画を適用すると、Terraformは外に出て、排他性を確保するために状態をロックします。 次に、リソースの変更に進み、最終的に、更新された状態をプッシュします。 注意すべき点の1つは、一部のリソースは他のリソースよりも終了に時間がかかることです。 たとえば、AWS RDSインスタンスの作成には12分以上かかる場合があり、Terraformはこれが完了するのを待ちます。 明らかに、Terraformは、これによって他のすべての操作をブロックしないほど賢いです。 要求された変更の有向グラフを作成し、相互依存関係がない場合は、並列処理を使用して実行を高速化します。
リソースのインポート
多くの場合、Terraformおよびその他の「コードとしての構成」ソリューションは、既存の環境に徐々に導入されます。 移行は本当に簡単です。 基本的に、Terraform内で定義されていないものはすべて管理されず、変更されません。 Terraformは、管理対象のみに関心があります。 もちろん、これにより問題が発生する可能性があります。たとえば、Terraformが構成外に存在するエンドポイントに依存していて、手動で破棄された場合などです。 Terraformはこれを認識していないため、状態の変更中にこのリソースを再作成できません。これにより、プランの実行中にAPIからエラーが発生します。 これは私が心配することではありません。 Terraformを導入することのメリットは、デメリットをはるかに上回ります。
導入プロセスを少し簡単にするために、Terraformにはimport
コマンドが含まれています。 これは、既存の外部リソースをTerraform状態に導入し、それらのリソースを管理できるようにするために使用されます。 残念ながら、Terraformは、少なくとも執筆時点では、これらのインポートされたモジュールの構成を自動生成することはできません。 この機能の計画はありますが、今のところ、最初にTerraformでリソース定義を記述し、次にこのリソースをインポートして、Terraformに管理を開始するように指示する必要があります。 状態にインポートされると(そして実行の計画中に) .tf
ファイルに書き込んだ内容と実際にクラウドに存在する内容とのすべての違いがわかります。 このようにして、構成をさらに微調整できます。 理想的には、変更が表示されないようにする必要があります。これは、Terraform構成がすでにクラウド上にあるものを1:1で反映していることを意味します。
モジュール
モジュールはTerraform構成の重要な部分であり、モジュールを採用して頻繁に使用することをお勧めします。 特定のコンポーネントを再利用する方法を提供します。 例としては、Dockerコンテナを実行するために使用されるAWSECSクラスターがあります。 このようなクラスターを機能させるには、クラスター自体、個別のEC2インスタンスを管理する起動構成、イメージのコンテナーリポジトリ、自動スケーリンググループとポリシーなど、多くの個別のリソースを構成する必要があります。 通常、個別の環境やアプリケーション用に個別のクラスターを用意する必要があります。
これを克服する1つの方法は、構成をコピーして貼り付けることですが、これは明らかに近視眼的な解決策です。 最も単純な更新を行うとエラーが発生します。
モジュールを使用すると、これらすべての個別のリソースを1つの構成ブロック(モジュールと呼ばれる)にカプセル化できます。 モジュールは、「外界」(または呼び出し元のコード)と通信するためのインターフェースである入力と出力を定義します。 さらに、モジュールを他のモジュール内にネストできるため、個別の環境全体をすばやく起動できます。
ローカルおよびリモートモジュール
状態と同様に、ローカルモジュールまたはリモートモジュールを持つことができます。 ローカルモジュールは、Terraform構成と一緒に(各環境の外部の同じリポジトリにある個別のディレクトリに)保存されます。 単純なアーキテクチャを使用していて、それらのモジュールを共有していない場合、これは問題ありません。
ただし、これには制限があります。 それらのモジュールをバージョン管理して共有することは困難です。 ECSモジュールのv1.0.0を本番環境で使用したいが、ステージング環境でv1.1.0を試してみたい場合があるため、バージョン管理は重要です。 モジュールがコードと一緒に保存されている場合、モジュールコードへのすべての変更は、通常は望ましくないすべての環境に反映されます( apply
が実行されると)。
モジュールをバージョン管理するための便利なアプローチの1つは、モジュールをすべて別のリポジトリに配置することです。たとえば、your-company/terraform-modulesです。 次に、Terraform構成内でこれらのモジュールを参照するときに、VCSリンクをソースとして使用できます。
module "my-module" { source = "[email protected]:your-company/terraform-modules.git//modules/my-module?ref=v1.1.0" ... }
ここでは、異なる環境で同じモジュールの他のバージョンから独立してテストできるmy-module(特定のパス)のv1.1.0を参照しています。
それ以外に、モジュールの発見可能性と共有可能性の問題があります。 十分に文書化された再利用可能なモジュールを作成するように努める必要があります。 通常、アプリごとに異なるTerraform構成があり、それらの間で同じモジュールを共有したい場合があります。 それらを別のリポジトリに抽出しないと、これは非常に困難になります。
モジュールの使用
モジュールは、特別なモジュールブロックを定義することにより、Terraform環境で簡単に参照できます。 これは、架空のECSモジュールのそのようなブロックの例です。
module "my_service_ecs_cluster" { source = "../modules/ecs_cluster" cluster_name = "my-ecs-service-${var.env}" repository_names = [ "my-ecs-service-${var.env}/api", "my-ecs-service-${var.env}/nginx", "my-ecs-service-${var.env}/docs", ] service_name = "my-ecs-service-${var.env}" ecs_instance_type = "t2.nano" min_size = "1" max_size = "1" use_autoscaling = false alb_target_group_arn = "${module.my_alb.target_group_arn}" subnets = "${local.my_private_subnets}" security_groups = "${aws_security_group.my_ecs.id}" key_name = "${var.key_name}" env_tag = "${var.env}" project_tag = "${var.project_tag}" application_tag = "${var.api_app_tag}" asg_tag = "${var.api_app_tag}-asg" }
渡されるすべてのオプション( source
などの一部のグローバルオプションを除く)は、このモジュールの構成内で入力(変数)として定義されています。
モジュールレジストリ
最近、HashiCorpは公式のTerraformモジュールレジストリを立ち上げました。 すでにバトルテスト済みのモジュールを開発しているコミュニティの知識から引き出すことができるので、これは素晴らしいニュースです。 さらに、それらのいくつかには「HashiCorp Verified Module」バッジが付いています。これは、それらが精査され、積極的に保守されていることを意味し、あなたにさらなる自信を与えます。
以前は、独自のモジュールを最初から作成する(そして間違いから学ぶ)か、GitHubやその他の場所で公開されているモジュールを使用する必要がありましたが、その動作は保証されていませんでした(コードを読むことは別として)。
環境間でのデータの共有
理想的には、異なるAWSアカウントを使用する場合でも、環境は完全に分離されている必要があります。 実際には、あるTerraform環境が別の環境で情報を使用する場合があります。 これは、Terraformを使用するようにアーキテクチャを徐々に変換している場合に特に当てはまります。 One example might be that you have a global
env that provides certain resources to other envs.
Let's say env global
shares data with stage
. For this to work, you can define outputs at the main level of the environment like so:
output "vpc_id" { value = "${module.network.vpc_id}" }
Then, in the stage
environment, you define a datasource that points to the remote state of global
:
data "terraform_remote_state" "global" { backend = "s3" config { bucket = "my-app-terraform-state" key = "terraform/global" region = "${var.region}" dynamodb_table = "terraform-state-lock-table" profile = "${var.profile}" } }
Now, you can use this datasource as any other and access all the values that were defined in global
's outputs:
vpc_
Words of Caution
Terraform has a lot of pros. I use it daily in production environments and consider it stable enough for such work. Having said that, Terraform is still under active development. Thus, you will stumble on bugs and quirks.
Where to Report Issues and Monitor Changes
First of all, remember: Terraform has a separate core repo and repositories for each provider (eg, AWS). If you encounter issues, make sure to check both the core repo and the separate provider repositories for issues and/or opened pull requests with fixes. GitHub is really the best place to search for bugs and fixes as it is very active and welcoming.
This also means that provider plugins are versioned separately, so make sure you follow their changelogs as well as the core one. Most of the bugs I have encountered were resolved by upgrading the AWS provider which already had a fix.
Can't Cheat Your Way out of Cloud Knowledge
You cannot use Terraform to configure and manage infrastructure if you have no knowledge of how a given provider works. I would say this is a misconception and not a downside, since Terraform has been designed to augment and improve the workflow of configuration management and not to be some magic dust that you randomly sprinkle around and—poof! Environments grow! You still need a solid knowledge of a security model of each cloud, how to write, eg, AWS policies, what resources are available, and how they interact.
Prefer Separate Resources That Are Explicitly linked
There are certain resources—for example, the AWS security group or AWS route table—that allow you to configure the security rules and routes respectively, directly inside their own block. This is tempting, as it looks like less work but in fact will cause you trouble. The problems start when you are changing those rules on subsequent passes. The whole resource will be marked as being changed even if only one route/security rule is being introduced. It also gives implicit ordering to those rules and makes it harder to follow the changes. Thankfully, mixing those both approaches is not allowed now (see the note).
Best-practice example, with explicitly linked resources:
resource "aws_security_group" "my_sg" { name = "${var.app_tag}-my-sg" ... } resource "aws_security_group_rule" "rule_one" { security_group_ ... } resource "aws_security_group_rule" "rule_two" { security_group_ ... }
Terraform plan
Doesn't Always Detect Issues and Conflicts
I already mentioned this in the case where you were managing resources with Terraform that were relying on other, unmanaged infrastructure. But there are more trivial examples—for example, you will get an error if your EC2 instance has Termination Protection enabled, even though plan
would show you it's OK to destroy it. You can argue that this is what Termination Protection has been designed for, and I agree, but there are more examples of things you can do in theory/on plan but when executed will deadlock or error out. For example, you cannot remove a network interface if something is using it—you get a deadlock without an option to gracefully recover.
Syntax Quirks
There are also quirks related to how HCLv1 (the syntax language Terraform uses) has been designed. It has a couple of frustrating quirks. There is work underway to provide an improved version of the parser for HCLv2. The best way to read on the current limitations and the plan to overcome them is this fantastic blog series. In the meantime, there are workarounds for most of those issues. They are not pretty and they will fail once v0.12 comes out, but hey, it is what it is.
When State Update Fails
It sometimes happens that Terraform is not able to correctly push an updated state. This is usually due to underlying network issues. The solution is to retry the state update instead of running apply again, which will fork the state .
Another issue might happen when state lock (the synchronization primitive that prevents multiple users to update the same state) fails to be taken down by Terraform. This involves running terraform force-unlock
with the lock ID to take it down manually.
Thankfully, in case of such problems, Terraform provides you with a good description and steps you need to make to fix it.
Not Everything Is Fun to Manage Through Terraform
There are certain cases where Terraform is not my tool of choice. For example, configuring AWS CodePipeline and CodeBuild projects (AWS equivalent of CI/CD pipeline) is cumbersome when done through Terraform. You need to define each step through very verbose configuration blocks and things like “Login via GitHub” are a lot more complicated than using the UI. Of course, it's still possible if you prefer to have it codified. Well, I guess it's a good candidate for a well-written module!
Same thing goes for managing AWS API Gateway endpoints. In this case, using a dedicated serverless framework would be a better option.
When configuring AWS resources with Terraform, you will find yourself writing a lot of policies. Policies that would otherwise often be auto-generated for you (when using the UI). For those, I'd recommend the AWS Visual Editor and then copying the resulting policy JSON into Terraform.
結論
Using Terraform has been fun and I'll continue doing so. Initial steps can be a bumpy ride, but there are more and more resources that help to ease you in.
I'd definitely recommend taking Terraform for a spin and simply playing with it. Remember, though—be safe and test it out on a non-essential account. If you are eligible for AWS Free Tier, use it as a 12-month free trial. Just be aware it has limitations as to what you can provision. Otherwise, just make sure you spin the cheapest resources, like t3.nano instances.
I highly recommend extensions for Terraform support in various code editors. For Visual Studio Code, there is one with syntax highlighting, formatting, validation and linting support.
It's always valuable to learn new things and evaluate new tools. I found that Terraform helped me immensely in managing my infrastructure. I think working with Terraform will only get easier and more fun, especially once v0.12.0 ships with a major upgrade to the HCL syntax and solve most of the quirks. The traction and community around Terraform are active and vibrant. You can find a lot of great resources on things I didn't manage to cover in a single blogs post, eg, a detailed guide on how to write modules.