あしたのチーム Tech Blog

はたらくすべての人に「ワクワク」を届けるべく、日々奮闘するエンジニアの日常をお伝えします。

Terraformのモジュール分割

(この記事はあしたのクラウド のインフラ移行に関する連載記事です)

最近セサミ4を購入してスマートロックに感動している@hidenbaです。

今日は、あしたのクラウドのインフラ移行した際に利用したTerraformのモジュール分割について書いていこうと思います

ディレクトリ構成

Terraform用のリポジトリは作らずに、あしたのクラウドリポジトリ内に内包する形にしています。リポジトリのルートに iacディレクトリを作成しました。

iac 
  staging
    main
      main.tf
    variables.yml
  production
    main
      main.tf
    variables.yml
  demo    main
      main.tf
    variables.yml
  modules 
    alb
      main.tf
      outputs.tf
      variables.tf
    network
      ...
    ecs
      ...
    rds
      ...

ステージ毎に分割

あしたのクラウドのステージ構成は本番環境に加えてステージング環境とお客様が利用するデモ環境に分かれています。それぞれの環境で構成の差異があるのでワークスペースは利用せずにディレクトリを分けるようにしました。ワークスペースは各ステージのテスト用の環境を作成するために利用しています。

  • iac/staging : ステージング環境用
  • iac/demo : デモ環境用
  • iac/production : 本番環境用

各環境のmain.tfでは変数の読み込みとメインモジュールの実行を行うのですが、ステージング環境にだけベーシック認証を掛ける必要があるので差分として現れます。

差分の切り替え方法として変数を利用してcountでON/OFFを行う方法もあるのですが、条件分岐を作ることでコードの複雑性があがるのを避けるためにこの方法を採用しました。

本番環境

module "variables" {
  source = "../../modules/variables"
}

module "main" {
  source = "../../modules/main"

  input = module.variables.var
  suffix = module.variables.suffix
}

output "task_def" {
  value = module.main.ecs
}

ステージング環境

module "variables" {
  source = "../../modules/variables"
}

module "main" {
  source = "../../modules/main"

  input = module.variables.var
  suffix = module.variables.suffix
}

module "basic_auth" {
  source = "../../modules/basic_auth"

  for_each = toset(keys(module.variables.var.hosts))

  region = module.variables.var.region
  host = each.value
  target_group_basic_auth = module.main.alb[each.value].target_group_basic_auth[0]
  suffix = module.variables.suffix
}

output "task_def" {
  value = module.main.ecs
}

モジュール分割方針

iac/modules 配下にモジュールを作成して各ステージから利用するようにしています。各ステージで作成する構成は基本的には同じなので iac/modules/main モジュールにて必要なモジュールを呼び出します。各モジュールは利用するリソースとそれに付随するリソースをまとめて一つのモジュールとして利用するようにしています。ネットワークのモジュールだとVPC・サブネット等ネットワーク周りのリソースを束ねて作成しています。他のリソースへの依存や利用方法による差異は変数にて受け渡しを行うことで再利用性を高めるようにしています。

簡単な例としてSNSトピックの構成をみてみましょう

sns_topic/main.tf

resource "aws_sns_topic" "this" {
  name = "${var.name}_${var.host}_${var.type}"
}

data "aws_iam_policy_document" "this" {
  statement {
    actions = ["sns:Publish"]

    principals {
      type        = "Service"
      identifiers = ["codestar-notifications.amazonaws.com", "cloudwatch.amazonaws.com"]
    }

    resources = [aws_sns_topic.this.arn]
  }
}

resource "aws_sns_topic_policy" "this" {
  arn    = aws_sns_topic.this.arn
  policy = data.aws_iam_policy_document.this.json
}

resource "aws_sns_topic_subscription" "this" {
  count = var.type == "lambda" ? 1 : 0

  topic_arn = aws_sns_topic.this.arn
  protocol  = "lambda"
  endpoint  = var.lambda_endpoint
}

SNSトピックモジュールの本体です。IAMの設定も内包しています。サブスプリプションリソースのaws_sns_topic_subscription では変数を使いTypeが lambda の場合のみリソースを作成します。

main/main.tf

module "sns_topic" {
  source = "../sns_topic"

  for_each = toset(local.hosts)

  name = var.input.name
  host = each.value
  type = "alert"
}

module "sns_topic_lambda" {
  source = "../sns_topic"

  for_each = toset(local.hosts)

  name = var.input.name
  host = each.value
  type = "lambda"
  lambda_endpoint = var.input.lambda_endpoint
}

module "sns_topic_deploy" {
  source = "../sns_topic"

  for_each = toset(local.hosts)

  name = var.input.name
  host = each.value
  type = "deploy"
}

mainモジュールでSNSトピックモジュールを呼び出しています。SNSトピックは監視アラート用、LambdaFunctionの発火用、デプロイの通知用と用途別に作成しています。

今後の展望

同一リポジトリに収まっているが iac/modulesは別のリポジトリに切り出して再利用できるようにしていきたいと思っています!