Help us understand the problem. What is going on with this article?

Terraform設計・運用のノウハウ

概要

Terraformを使い始めて一年ほど経ったので、設計や運用上のノウハウを公開していこうと思います。
基本的にAWSを使うことが多いので、AWS寄りの話になります。

設計編

利用可能なプロバイダを調べる

TerraformはAWSやGCPといったクラウドサービスをコード化できることで有名ですが、オフィシャルだけでも様々なプロバイダに対応しています。
私はAWSのほか、GitHubやDatadogをコード化して運用してます。

ネットワーク設計

サービスを構築する上で本番環境のほかに開発やステージング環境を作りますが、環境の分け方は大きく分けて次のいずれかになることが多いと思います。

  1. 1つのVPCに全ての環境を入れる
  2. 環境ごとにVPCを分ける
  3. 環境ごとにAWSアカウントを分ける

2のパターンで設計する場合、Terraformで構成を組む際はVPCをdevelopmentstagingproductionに分離し、更に各VPCで共通となるサービスを配置するためのmanagementを作成します。managementには踏み台であったり、CI/CDを管理するインスタンスを配置しています。
ネットワーク上はmanagementから各環境にVPCペアリングで接続できますが、環境間(例えばdevelopmentからstagingstagingからproduction)の接続はルートテーブル上で制限しています。

開発基盤の概要-Page-3 (2).png

Workspaceを使う

Terraformで環境ごとのリソースを管理する方法は大きく分けて2つあります。

  1. 環境ごとにディレクトリを分けて管理
  2. Workspaceを使う

TerraformにはWorkspaceと呼ばれる環境ごとにリソースを管理する仕組みがあります。
先に上げたネットワーク構成にWorkspaceを適用することで、VPC単位のリソース構成が管理しやすくなります。

$ terraform workspace list
  default
* development
  production
  staging

managementVPCはWorkspace管理下から除外しています。

ディレクトリ設計

Terraformでリソースを管理する場合、悩むことになるのがディレクトリ構成かと思います。私は Code structure で紹介されている方法を元に次のような構成にしてます。

# 構築する対象サービス。EC2、Security Groupといった単位
{service}
  # resourceやmodule
  main.tf

  # main.tfやbackend.tfで参照する変数のリスト
  variables.tf

  # 出力パラメータのリスト
  outputs.tf

  # プロバイダ情報
  providers.tf

  # backendやdataのリスト
  backend.tf

インフラは複数のサービスから構成されるため、実際には次のようなディレクトリ構成となります。

providers
  aws
    # AWS運用上必須となる基盤サービス
    base
      # リージョンに依存しないサービス
      global
        iam
        route53
        ...
      # 特定のリージョンを構築するサービス
      ap-northeast-1
        cognito
        cloudtrail
        ...
    # アプリケーションを構成する上で必要となるサービス
    {APP NAME}
      # ワークスペースに依存しないサービス
      base
        route53
        ...
      # ワークスペースで構成されるサービス
      workspace
        ap-northeast-1
          global
            vpc
            ecs
            ...

Terraform Best Practiceを読む

Terraform Best Practiceを読みましょう。中でもNaming conventionsは非常に有用です。いくつか気になるポイントをまとめてみました。

  • Resource and data source arguments
    • リソース名や変数、出力名の区切り文字には-の代わりに_を使う
    • リソース名にはリソースタイプを含めない (例えば、aws_route_tableのリソース名にroute_tableなどのタイプを含めない)
    • 単一リソースや妥当な名前がない場合、リソース名はthisとする
    • tagsdepends_onlifecycleはリソースの最後に記述する
  • Variables
    • default = []がある場合、type = "list"は省略する
    • default = {}がある場合、type = "map"は省略する
    • listmapの変数名は複数形を使用する
  • Outputs
    • 名前は{name}_{type}_{attribute}形式が望ましい

リソース名の変更

terraform state mvコマンドを使います。

# リソース名をfooからbarに変更
$ terraform state mv aws_kms_key.foo aws_kms_key.bar

Workspaceを利用してる場合はWorkspace単位で実行が必要となります。

リソースをモジュール化

基本的にはリソース名の変更と同じです。接頭辞としてmodule.{MODULE_NAME}を付けるだけです。

# リソースfooをbarモジュール内のaws_kms_key.bazに変更
$ terraform state mv aws_kms_key.foo module.bar.aws_kms_key.baz

実装編

Docker版Terraformを使う

Docker版のTerraformを利用しましょう。

バージョンを指定する

利用するバージョンを明確化することで、無用なトラブルを回避することができます。

Terraform

terraform {
  required_version = "= 0.12.4"
}

Docker版Terraformを利用する場合、イメージのバージョンはlatestではなく、0.12.4のように番号で指定することをお勧めします。

プロバイダバージョン

provider "aws" {
  version = "= 2.19.0"
}

モジュールバージョン

module "foo" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "3.0.1"
}

尚、sourceがGitHubの場合はversionが使えないので、代わりにrefでバージョンを指定する必要がある。

module "foo" {
  source = "github.com/azavea/terraform-aws-acm-certificate?ref=1.1.0"
}

tfstateファイルの管理方法

リソースの状態を管理するtfstateファイルはローカルやGitで管理するべきではありません。TerraformのBackend機能を使うことで、tfstateをDynamoDBやS3で管理することができます。
AWSを利用しているのであれば、S3がお手軽かと思います。

terraform {
  backend "s3" {
    bucket = "mybucket"
    key    = "path/to/my/key"
    region = "us-east-1"
  }
}

尚、S3を使う場合、対象バケットのバージョニングの有効化を強くお勧めします。tfstateを誤って削除したり、Terraformのバージョンアップでファイル構成が書き換わっても、簡単にステートファイルを復元することができます。

Terraform Module Registryを使う

Terraform Module Registryでは、汎用的に使えるモジュールが公開されてます。AWSで言えばEC2やSecurity Groupなど、いくつかの変数を渡すだけでリソースを作成してくれます。
Registryには大きく分けて、Terraformコミュニティが作ったものと、HashiCorpによって検証されたVerifiedモジュールがあります。
運用で使う場合は、検索時にVerifiedにチェックを付けることをお勧めします。

Screen_Shot_2019-07-15_at_23_21_12.png

フォーマットを整形する

fmtコマンドでディレクトリ下のファイルに対しまとめて整形してくれます。

# 0.11まで
$ terraform fmt

# 0.12以降
$ terraform fmt -recursive

デバッグ

変数や関数の結果を確認したい場合はconsoleコマンドが便利です。

variables.tf
variable "foo" {
  default = "bar"
}
# 変数の確認
$ terraform console
> var.foo
bar

# 関数の実行結果の確認
> lookup({foo="bar"}, "foo", "baz")
bar

環境ごとに異なる値を読み込む方法

変数名はWorkspace名を含めて定義します。

variable "rds_cluster" {
  default = {
    "default.engine_version"    = "5.7.12"
    "production.engine_version" = "5.7.22"
  }
}

リソース作成時にlookup関数を使うことで、現在有効なWorkspaceがproductionであれば5.7.22、それ以外であれば5.7.11の値が設定されます。

resource "aws_rds_cluster" "this" {
  engine_version = lookup(
    var.rds_cluster,
    "${terraform.workspace}.engine_version",
    var.rds_cluster["default.engine_version"],
  )
  ...

lifecycleでリソースを保護する

リソースにlifecycleブロックを指定することで、リソース作成・更新時の動作を変更することができます。lifecycleにはいくつかのタイプがありますが、prevent_destroyを指定することで、強制的なリソース再作成が発生したときにエラーを発生するようになります。

main.tf
lifecycle {
  prevent_destroy = true
}

destroyを実行した結果。

$ terraform destroy
...
Error: Instance cannot be destroyed

  on main.tf line 55:
  55: resource "aws_eip" "this" {

Resource aws_eip.this has lifecycle.prevent_destroy set, but the plan calls
for this resource to be destroyed. To avoid this error and continue with the
plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan
using the -target flag

リソースを強制的に削除したい場合は、prevent_destroyfalseにするか、ブロックごと削除して、applyを実行します。

prevent_destroyはあくまでTerraform上で削除を保護するためのもので、AWSコンソールやCLIからは削除することが可能です。EC2やRDSなどいくつかのサービスには削除保護のオプションがあるので、別途有効化しておくことをお勧めします。

IAM PolicyのJSONはaws_iam_policy_documentで定義する

以下のリソースはIAMポリシーがヒアドキュメント形式で書かれており、可読性が低いです。

main.tf
resource "aws_iam_policy" "example" {
  # ... other configuration ...
  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": {
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }
}
POLICY
}

ヒアドキュメントはこのように置き換えることができます。HCLで書けるのでだいぶスッキリします。

main.tf
resource "aws_iam_policy" "example" {
  # ... other configuration ...
  policy = data.aws_iam_policy_document.example.json
}

data "aws_iam_policy_document" "example" {
  version = "2012-10-17"
  statement {
   effect = "Allow"
    actions = ["*"]
    resources = ["*"
  }
}

デバッグログを出力する

環境変数TF_LOGにログレベルを渡すことで、ログを出力することができます。ファイルに出力したい場合はTF_LOG_PATHも指定すると良いでしょう。

実行速度を改善する

Terraformのコマンドに--parallelismを渡すことで、APIの同時呼び出し数を変更することができます。

$ terraform plan --parallelism=20

デフォルト値は10です。パフォーマンスを見て数値を変えて見ると良さそうです。
尚、環境変数で指定する場合はTF_CLI_ARGS_planTF_CLI_ARGS_applyTF_CLI_ARGS_destroyを指定します。

拡張ライブラリ

metaps
メタップスは「テクノロジーでお金と経済のあり方を変える」というミッションのもと、テクノロジーをフル活用することで人々を現実世界における様々な制約から解放し、世界中の誰もが自由に価値創造できる社会を目指しています。
https://metaps.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away