はじめに
今回は前回AWS CDKを使ってコード化した内容を、そのままTerraformに置き換えてみました。
Terraformの基礎に関しては、こちらの記事をぜひご参照ください。
完成したTerraformフォルダ構成
プロジェクトルートにインフラのデプロイ用フォルダterraform/を作成します。
terraform/
├── .gitignore
├── .terraform-version ① # tfenv 用(Terraform 1.11.4)
├── modules/
│ ├── github-oidc-provider/ # アカウントベース singleton(CDK BaseStack 相当)
│ │ ├── main.tf ②
│ │ ├── variables.tf ③
│ │ ├── outputs.tf ④
│ │ └── versions.tf ⑤
│ ├── static-site/ # S3 + CloudFront(OAC)(CDK FrontendStack 相当)
│ │ ├── main.tf ⑥
│ │ ├── variables.tf ⑦
│ │ ├── outputs.tf ⑧
│ │ └── versions.tf ⑨
│ └── github-oidc-role/ # GitHub Actions Deploy Role(CDK GithubOidcStack 相当)
│ ├── main.tf ⑩
│ ├── variables.tf ⑪
│ ├── outputs.tf ⑫
│ └── versions.tf ⑬
└── envs/
├── dev/
│ ├── backend.tf ⑭ # S3 backend 設定(dev アカウント側)
│ ├── providers.tf ⑮ # provider "aws" + アカウントガード data source
│ ├── versions.tf ⑯ # terraform { required_version / required_providers }
│ ├── main.tf ⑰ # module 呼び出しのみ
│ ├── variables.tf ⑱
│ ├── outputs.tf ⑲
│ └── terraform.tfvars ⑳ # dev 固有値
├── stg/
│ └── ... (同上)
└── prod/
└── ... (同上、force_destroy=false を validation で強制)
各ファイル内容
※前回の記事で作成した内容を、AIを使用してコード化しています(微修正/動作確認済み)
※ベストプラクティスというわけではないため、あくまで参考にしていただけると幸いです
※各ファイルの詳細な説明は省略いたしますが、気になった方はAIに投げてみてください
※機密情報を扱う際は、別途.envやSecretManagerなどと連携する必要があります
① terraform/.terraform-version
「このディレクトリ」で使う Terraformコマンドのバージョンを固定する宣言ファイルです。
セットアップ(後述)で使用するtfenv(Terraform 用バージョンマネージャ、Node.js でいう nvm相当)が読み込む規約ファイルです。
1.11.4
② terraform/modules/github-oidc-provider/main.tf
GitHub ActionsのOIDCトークンをAWSで受け入れるための「OIDC Provider」をAWS IAMに登録するTerraformモジュールです。CDKのcdk/lib/stacks/base-stack.tsと1:1で対応しています。
# =============================================================================
# GitHub Actions 用 OIDC Provider
#
# 同一 URL(token.actions.githubusercontent.com)の OIDC Provider は
# AWS アカウントあたり 1 つしか作れない singleton リソース。
# 本プロジェクトは「マルチアカウント前提(dev/stg/prod が別アカウント)」なので、
# 各アカウントごとに 1 度だけ作成すれば良い。
#
# AWS Provider 5.x 以降は thumbprint_list を省略しても自動で取得される。
# https://github.com/hashicorp/terraform-provider-aws/issues/32976
# =============================================================================
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
tags = {
Name = "${var.prefix}-${var.env_name}-github-oidc-provider"
}
}
③ terraform/modules/github-oidc-provider/variables.tf
このモジュールが「外から受け取る入力値」を宣言するファイルです。
variable "prefix" {
description = "リソース名のプレフィックス(タグ補助用)"
type = string
}
variable "env_name" {
description = "環境名(dev / stg / prod)"
type = string
}
④ terraform/modules/github-oidc-provider/outputs.tf
このモジュールが「外に返す値」を宣言するファイルです。
上記のvariables.tfがモジュールの入力(引数)なら、outputs.tfは戻り値であり、関数で言うreturnに相当します。
output "provider_arn" {
description = "GitHub Actions OIDC Provider の ARN(同一アカウント内の Deploy Role が trust する)"
value = aws_iam_openid_connect_provider.github.arn
}
⑤ terraform/modules/github-oidc-provider/versions.tf
このモジュールを動かすために必要な「Terraform本体のバージョン」と「Providerのバージョン」の制約を宣言するファイルです。
terraform {
required_version = ">= 1.11.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80.0, < 7.0.0"
}
}
}
⑥ terraform/modules/static-site/main.tf
# =============================================================================
# S3(プライベート)+ CloudFront(OAC)の静的サイト構成。SPA ホスティングに最適。
#
# - Bucket: BlockPublicAccess、SSE-S3、BucketOwnerEnforced、TLS 強制
# - Distribution: Managed CachingOptimized、redirect-to-https、HTTP/2、IPv6
# - SPA エラーマッピング: 403/404 → /index.html(200 で応答)
# =============================================================================
data "aws_caller_identity" "current" {}
locals {
bucket_name = "${var.prefix}-${var.env_name}-static-${data.aws_caller_identity.current.account_id}"
oac_name = "${var.prefix}-${var.env_name}-oac"
}
# -----------------------------------------------------------------------------
# S3 バケット本体
# -----------------------------------------------------------------------------
resource "aws_s3_bucket" "site" {
bucket = local.bucket_name
force_destroy = var.force_destroy
# prod では terraform destroy を構造的に拒否したい。
# ただし lifecycle.prevent_destroy は static にしか書けないため、
# ここでは設定せず、運用ガード(README / force_destroy=false)に委ねる。
}
# Public Access を完全にブロック。
resource "aws_s3_bucket_public_access_block" "site" {
bucket = aws_s3_bucket.site.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# Object Ownership: BucketOwnerEnforced(ACL 無効化)。
resource "aws_s3_bucket_ownership_controls" "site" {
bucket = aws_s3_bucket.site.id
rule {
object_ownership = "BucketOwnerEnforced"
}
}
# SSE-S3(AES256)でサーバ側暗号化。
resource "aws_s3_bucket_server_side_encryption_configuration" "site" {
bucket = aws_s3_bucket.site.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
# bucket_key_enabled は SSE-KMS 向けの KMS 呼び出し削減機能であり、
# SSE-S3 (AES256) では意味を持たないためここでは指定しない。
}
}
# `aws s3 sync` 失敗時に残る未完了 multipart upload を自動回収する。
# 設定しないと孤児パートが残り続けてストレージ課金が止まらない。
resource "aws_s3_bucket_lifecycle_configuration" "site" {
bucket = aws_s3_bucket.site.id
rule {
id = "AbortIncompleteMultipartUploads"
status = "Enabled"
filter {}
abort_incomplete_multipart_upload {
days_after_initiation = var.abort_incomplete_multipart_upload_days
}
}
}
# -----------------------------------------------------------------------------
# CloudFront Origin Access Control(OAC)
# OAI ではなく OAC を使う(OAI は非推奨)。SigV4 always で署名を強制。
# -----------------------------------------------------------------------------
resource "aws_cloudfront_origin_access_control" "site" {
name = local.oac_name
description = "${var.prefix}-${var.env_name} S3 origin access control"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# -----------------------------------------------------------------------------
# CloudFront Distribution
# -----------------------------------------------------------------------------
resource "aws_cloudfront_distribution" "site" {
enabled = true
is_ipv6_enabled = true
http_version = "http2"
comment = "${var.prefix}-${var.env_name} static site"
default_root_object = "index.html"
price_class = "PriceClass_All"
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = "s3-${aws_s3_bucket.site.id}"
origin_access_control_id = aws_cloudfront_origin_access_control.site.id
}
default_cache_behavior {
target_origin_id = "s3-${aws_s3_bucket.site.id}"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
compress = true
# AWS Managed-CachingOptimized
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6"
}
# SPA エラーマッピング: S3 が 403/404 を返したら / index.html を 200 で返す。
# ttl=0 にしておかないと、デプロイ直後にエラーがキャッシュされたままになる。
custom_error_response {
error_code = 403
response_code = 200
response_page_path = "/index.html"
error_caching_min_ttl = 0
}
custom_error_response {
error_code = 404
response_code = 200
response_page_path = "/index.html"
error_caching_min_ttl = 0
}
viewer_certificate {
# CloudFront のデフォルト証明書 (*.cloudfront.net) を使う構成。
# この場合 minimum_protocol_version は AWS 側で固定(TLSv1)となり、
# `TLSv1.2_2021` 等を明示すると `terraform apply` 時に ValidationError で拒否される。
# カスタムドメイン + ACM 証明書 (us-east-1) を導入する際に、acm_certificate_arn / aliases
# と合わせて minimum_protocol_version = "TLSv1.2_2021" を再指定すること。
cloudfront_default_certificate = true
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
}
# -----------------------------------------------------------------------------
# S3 Bucket Policy
# - CloudFront(OAC) からの GetObject 許可
# - 非 TLS リクエストを全 Deny(enforceSSL 相当)
# -----------------------------------------------------------------------------
data "aws_iam_policy_document" "site" {
# OAC からの読み取りのみ許可。AWS:SourceArn で当該 Distribution に限定。
statement {
sid = "AllowCloudFrontServicePrincipalReadOnly"
effect = "Allow"
actions = ["s3:GetObject"]
resources = [
"${aws_s3_bucket.site.arn}/*",
]
principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [aws_cloudfront_distribution.site.arn]
}
}
# 非 TLS を Deny(CDK の enforceSSL=true 相当)。
statement {
sid = "DenyInsecureTransport"
effect = "Deny"
actions = ["s3:*"]
resources = [
aws_s3_bucket.site.arn,
"${aws_s3_bucket.site.arn}/*",
]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
}
}
resource "aws_s3_bucket_policy" "site" {
bucket = aws_s3_bucket.site.id
policy = data.aws_iam_policy_document.site.json
depends_on = [
aws_s3_bucket_public_access_block.site,
]
}
⑦ terraform/modules/static-site/variables.tf
# リソース名のプレフィックス(例: "<project-name>-liff")。
variable "prefix" {
description = "リソース名のプレフィックス(例: <project-name>-liff)"
type = string
validation {
condition = can(regex("^[a-z0-9][a-z0-9-]{1,30}[a-z0-9]$", var.prefix))
error_message = "prefix は 3〜32 文字の小文字英数字とハイフンで、英数字で始まり終わる必要があります。"
}
}
# 環境名(dev / stg / prod)。バケット名・OAC 名・コメント等の suffix として使う。
variable "env_name" {
description = "環境名(dev / stg / prod)"
type = string
validation {
condition = contains(["dev", "stg", "prod"], var.env_name)
error_message = "env_name は dev / stg / prod のいずれかである必要があります。"
}
}
# `aws s3 sync` 失敗時に残る未完了 multipart upload を回収するまでの日数。
variable "abort_incomplete_multipart_upload_days" {
description = "未完了 multipart upload を自動 abort するまでの日数"
type = number
default = 7
}
# 本番のバケット誤削除を阻止する。dev/stg では true、prod では false に設定する想定。
# false の場合、バケットに中身が残っていると Terraform destroy はエラーになる。
variable "force_destroy" {
description = "バケット削除時に中身も一緒に削除するか(dev/stg のみ true 推奨)"
type = bool
default = false
}
⑧ terraform/modules/static-site/outputs.tf
output "bucket_name" {
description = "S3 バケット名"
value = aws_s3_bucket.site.id
}
output "bucket_arn" {
description = "S3 バケット ARN"
value = aws_s3_bucket.site.arn
}
output "distribution_id" {
description = "CloudFront Distribution ID"
value = aws_cloudfront_distribution.site.id
}
output "distribution_arn" {
description = "CloudFront Distribution ARN"
value = aws_cloudfront_distribution.site.arn
}
output "distribution_domain_name" {
description = "CloudFront のデフォルトドメイン(例: dxxxx.cloudfront.net)"
value = aws_cloudfront_distribution.site.domain_name
}
⑨ terraform/modules/static-site/versions.tf
terraform {
required_version = ">= 1.11.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80.0, < 7.0.0"
}
}
}
⑩ terraform/modules/github-oidc-role/main.tf
# =============================================================================
# 環境別の GitHub Actions Deploy Role
#
# Trust:
# federated = token.actions.githubusercontent.com の OIDC Provider
# aud = sts.amazonaws.com
# sub = github_subjects 各要素について repo:<owner>/<repo>:<subject>
#
# 権限:
# - 環境の S3 バケットへの sync(List/Get/Put/Delete object + multipart 系)
# - 環境の CloudFront Distribution の invalidation
#
# `sub` には StringLike ではなく StringEquals を使う。これにより
# wildcard が解釈されない。github_subjects は完全一致の subject claim 一覧
# (例: 'environment:prod')を想定しており、将来 '*' を含むエントリが
# 紛れ込んでも StringLike のように暗黙的に権限が広がる事故を防げる。
# =============================================================================
locals {
role_name = "${var.prefix}-${var.env_name}-github-deploy"
# 各 subject に repo:<owner>/<repo>: のプレフィックスを付ける。
subject_claims = [
for s in var.github_subjects :
"repo:${var.github_owner}/${var.github_repo}:${s}"
]
}
data "aws_iam_policy_document" "assume_role" {
statement {
sid = "GitHubActionsAssumeRole"
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [var.oidc_provider_arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:sub"
values = local.subject_claims
}
}
}
resource "aws_iam_role" "deploy" {
name = local.role_name
description = "GitHub Actions deploy role for ${var.prefix} ${var.env_name}"
max_session_duration = var.max_session_duration_seconds
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
# -----------------------------------------------------------------------------
# Deploy Policy
# -----------------------------------------------------------------------------
data "aws_iam_policy_document" "deploy" {
# S3 バケットレベル操作。
# `aws s3 sync` が実行中 multipart upload を列挙して abort/resume を判断するために
# ListBucketMultipartUploads が必要。
statement {
sid = "S3BucketLevel"
effect = "Allow"
actions = [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads",
]
resources = [var.bucket_arn]
}
# S3 オブジェクトレベル操作。
# `aws s3 sync` は約 8 MB を超えるファイルで multipart upload に切り替わる。
# AbortMultipartUpload / ListMultipartUploadParts が無いと、アップロード失敗時に
# 孤児パートが残ってストレージ課金が発生し続けるうえ、デプロイ Role 自身では掃除できない。
statement {
sid = "S3ObjectLevel"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:GetObjectVersion",
"s3:AbortMultipartUpload",
"s3:ListMultipartUploadParts",
]
resources = ["${var.bucket_arn}/*"]
}
# CloudFront invalidation。
statement {
sid = "CloudFrontInvalidate"
effect = "Allow"
actions = [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
"cloudfront:GetDistribution",
]
resources = [var.distribution_arn]
}
}
resource "aws_iam_role_policy" "deploy" {
name = "${local.role_name}-policy"
role = aws_iam_role.deploy.id
policy = data.aws_iam_policy_document.deploy.json
}
⑪ terraform/modules/github-oidc-role/variables.tf
variable "prefix" {
description = "リソース名のプレフィックス(例: <project-name>-liff)"
type = string
}
variable "env_name" {
description = "環境名(dev / stg / prod)"
type = string
}
variable "oidc_provider_arn" {
description = "GitHub Actions OIDC Provider の ARN(github-oidc-provider モジュールから渡す)"
type = string
}
variable "github_owner" {
description = "GitHub Organization / User 名"
type = string
}
variable "github_repo" {
description = "リポジトリ名"
type = string
}
# OIDC sub claim のリスト。例:
# - "environment:dev" : GitHub Environment 'dev' 発行のトークン
# - "ref:refs/heads/main" : main ブランチへの push で発行されたトークン
# - "pull_request" : pull_request ワークフロー発行のトークン
# 複数指定した場合は OR 結合される(StringEquals なので完全一致のみ)。
variable "github_subjects" {
description = "AssumeRole を許可する GitHub OIDC sub claim 一覧"
type = list(string)
validation {
condition = length(var.github_subjects) > 0
error_message = "github_subjects には最低 1 件の subject claim を指定してください。"
}
}
variable "bucket_arn" {
description = "この Role が sync 対象とする S3 バケット ARN"
type = string
}
variable "distribution_arn" {
description = "この Role が invalidation を許可される CloudFront Distribution ARN"
type = string
}
variable "max_session_duration_seconds" {
description = "AssumeRole の最大セッション時間(秒)"
type = number
default = 3600
}
⑫ terraform/modules/github-oidc-role/outputs.tf
output "role_arn" {
description = "GitHub Actions に AWS_DEPLOY_ROLE_ARN として渡す値"
value = aws_iam_role.deploy.arn
}
output "role_name" {
description = "Deploy Role 名"
value = aws_iam_role.deploy.name
}
⑬ terraform/modules/github-oidc-role/versions.tf
terraform {
required_version = ">= 1.11.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80.0, < 7.0.0"
}
}
}
⑭ terraform/envs/dev/backend.tf
「Terraformのstateファイルをどこに保存するか」を宣言するファイルです。
※後述のデプロイ手順で最初に作成するs3バケット名を設定します。
# =============================================================================
# Remote backend (S3 + S3 native locking)
#
# - Terraform 1.11+ の use_lockfile = true により、DynamoDB を使わず S3 単独で
# 排他制御できる(state file と並んで <key>.tflock が作られる)。
# - 各環境は別アカウント・別 state なので、bucket / key を環境ごとに完全分離する。
# - bucket は事前に作成しておく必要がある(README の "Bootstrap" 参照)。
# =============================================================================
terraform {
backend "s3" {
bucket = "<project-name>-liff-dev-tfstate-<your-aws-account-id>"
key = "<project-name>-liff/dev/terraform.tfstate"
region = "ap-northeast-1"
encrypt = true
use_lockfile = true
}
}
⑮ terraform/envs/dev/providers.tf
# =============================================================================
# Provider 設定(HashiCorp Style Guide に従い main.tf から分離)
#
# - allowed_account_ids: 誤アカウントへの terraform apply を構造的に拒否するガード。
# CDK の bin/app.ts における CDK_DEFAULT_ACCOUNT との突合と同じ役割。
# 認証情報のアカウントが var.account_id と一致しない場合、provider 初期化に失敗する。
# - default_tags: App 配下のすべてのリソースに統一タグを付与。
# =============================================================================
provider "aws" {
region = var.region
allowed_account_ids = [var.account_id]
default_tags {
tags = var.tags
}
}
# 認証情報のアカウントが var.account_id と一致することを再確認する二重防御。
# allowed_account_ids でも止まるが、postcondition のほうがメッセージが読みやすい。
data "aws_caller_identity" "current" {
lifecycle {
postcondition {
condition = self.account_id == var.account_id
error_message = "認証情報のアカウント (${self.account_id}) は var.account_id (${var.account_id}) と一致しません。--profile を確認してください。"
}
}
}
⑯ terraform/envs/dev/versions.tf
terraform {
required_version = ">= 1.11.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.80.0, < 7.0.0"
}
}
}
⑰ terraform/envs/dev/main.tf
# =============================================================================
# dev 環境のルート構成(modules の呼び出しのみを記述)
#
# 構築リソース(同一 state):
# 1. github-oidc-provider : アカウントベース singleton(IAM OIDC Provider)
# 2. static-site : S3 + CloudFront(OAC)
# 3. github-oidc-role : GitHub Actions Deploy Role
#
# 注:
# - provider 設定とアカウントガード data source は providers.tf に分離。
# - terraform / required_providers ブロックは versions.tf に分離。
# - backend 設定は backend.tf に分離。
# =============================================================================
# -----------------------------------------------------------------------------
# 1. アカウントベース(GitHub OIDC Provider)
# -----------------------------------------------------------------------------
module "github_oidc_provider" {
source = "../../modules/github-oidc-provider"
prefix = var.prefix
env_name = var.env_name
}
# -----------------------------------------------------------------------------
# 2. 静的サイト(S3 + CloudFront OAC)
# -----------------------------------------------------------------------------
module "static_site" {
source = "../../modules/static-site"
prefix = var.prefix
env_name = var.env_name
force_destroy = var.force_destroy
}
# -----------------------------------------------------------------------------
# 3. GitHub Actions Deploy Role
# -----------------------------------------------------------------------------
module "github_oidc_role" {
source = "../../modules/github-oidc-role"
prefix = var.prefix
env_name = var.env_name
oidc_provider_arn = module.github_oidc_provider.provider_arn
github_owner = var.github_owner
github_repo = var.github_repo
github_subjects = var.github_subjects
bucket_arn = module.static_site.bucket_arn
distribution_arn = module.static_site.distribution_arn
}
⑱ terraform/envs/dev/variables.tf
# 各環境ディレクトリで上書きする変数群。値は terraform.tfvars に書く。
variable "account_id" {
description = "この環境を構築する AWS アカウント ID。provider の allowed_account_ids にも指定する。"
type = string
validation {
condition = can(regex("^[0-9]{12}$", var.account_id))
error_message = "account_id は 12 桁の数字である必要があります。"
}
}
variable "region" {
description = "AWS リージョン"
type = string
}
variable "prefix" {
description = "リソース名のプレフィックス(例: <project-name>-liff)"
type = string
}
variable "env_name" {
description = "環境名(dev / stg / prod)"
type = string
validation {
condition = contains(["dev", "stg", "prod"], var.env_name)
error_message = "env_name は dev / stg / prod のいずれかである必要があります。"
}
}
variable "github_owner" {
description = "GitHub Organization / User 名"
type = string
}
variable "github_repo" {
description = "GitHub リポジトリ名"
type = string
}
variable "github_subjects" {
description = "AssumeRole を許可する GitHub OIDC sub claim(例: [\"environment:dev\"])"
type = list(string)
}
variable "tags" {
description = "App 配下のすべてのリソースに付与するタグ"
type = map(string)
}
variable "force_destroy" {
description = "S3 バケットを Terraform destroy 時に空にしてから削除するか(dev/stg のみ true 推奨)"
type = bool
}
# terraform/envs/prod/variables.tfであれば以下のようにする
variable "force_destroy" {
description = "S3 バケットを Terraform destroy 時に空にしてから削除するか。prod は必ず false。"
type = bool
validation {
condition = var.force_destroy == false
error_message = "prod 環境では force_destroy は必ず false である必要があります(誤削除防止)。"
}
}
⑲ terraform/envs/dev/outputs.tf
# CDK の CfnOutput に対応する出力。
# GitHub Actions の Environment Variables にそのまま登録する。
output "github_oidc_provider_arn" {
description = "GitHub Actions OIDC Provider ARN(アカウントベース、同アカウント内で共有)"
value = module.github_oidc_provider.provider_arn
}
output "bucket_name" {
description = "S3_BUCKET_NAME に登録"
value = module.static_site.bucket_name
}
output "distribution_id" {
description = "CLOUDFRONT_DISTRIBUTION_ID に登録"
value = module.static_site.distribution_id
}
output "distribution_domain_name" {
description = "CloudFront のデフォルトドメイン(例: dxxxx.cloudfront.net)"
value = module.static_site.distribution_domain_name
}
output "deploy_role_arn" {
description = "AWS_DEPLOY_ROLE_ARN に登録"
value = module.github_oidc_role.role_arn
}
⑳ terraform/envs/dev/terraform.tfvars
account_id = "<your-aws-account-id>"
region = "ap-northeast-1"
prefix = "<project-name>-liff"
env_name = "dev"
github_owner = "<your-organization-name>"
github_repo = "<your-repo-name>"
github_subjects = ["environment:dev"]
tags = {
Project = "<project-name>-liff"
Environment = "dev"
ManagedBy = "terraform"
}
force_destroy = true
# 本番では必ず false(誤削除防止。variables.tf の validation で強制)。
# terraform/envs/prod/terraform.tfvarsであれば以下のようにする
force_destroy = false
実行方法
①terraformコマンドのセットアップ
以下の記事を参考に、セットアップを行います。
②設定ファイルの編集
次に、terraform/envs/のdev/stg/prodのterraform.tfvarsファイルを編集し、必要な値を入力します(AWSアカウントID等)
③SSOログイン
デプロイ対象のAWSアカウントでSSOログインします。
aws sso login --profile <your-profile-name>
以下のコマンドを実行し、出力結果のAWSアカウントIDが、terraform.tfvarsのファイルに設定したIDと一致していることを確認します。
aws sts get-caller-identity --profile <your-profile-name>
④Bootstrap
tfstate保存用バケットのBootstrapを行います(各アカウント × 各リージョンで初回 1 回のみ)
以下のコマンドを順に実行します。
※スクリプトやIaC化すると楽かもしれません
※暗号化やパブリックアクセス全ブロックなど、デフォルトの設定で問題ない場合もあります
# S3バケットを作成(ここでバケット名を指定)
aws s3api create-bucket \
--profile <project-name>-liff-dev \
--bucket <project-name>-liff-dev-tfstate-<aws-account-id> \
--region ap-northeast-1 \
--create-bucket-configuration LocationConstraint=ap-northeast-1
# バージョニングの有効化
aws s3api put-bucket-versioning \
--profile <project-name>-liff-dev \
--bucket <project-name>-liff-dev-tfstate-<aws-account-id> \
--versioning-configuration Status=Enabled
# AWS管理鍵による暗号化
aws s3api put-bucket-encryption \
--profile <project-name>-liff-dev \
--bucket <project-name>-liff-dev-tfstate-<aws-account-id> \
--server-side-encryption-configuration '{"Rules":[{"ApplyServerSideEncryptionByDefault":{"SSEAlgorithm":"AES256"}}]}'
# パブリックアクセス全ブロック
aws s3api put-public-access-block \
--profile <project-name>-liff-dev \
--bucket <project-name>-liff-dev-tfstate-<aws-account-id> \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
⑤AWSリソースのデプロイ
以下のコマンドを実行し、実際にAWSリソースをデプロイします。
cd terraform/envs/dev
AWS_PROFILE=<your-profile-name> terraform init
AWS_PROFILE=<your-profile-name> terraform plan
AWS_PROFILE=<your-profile-name> terraform apply
⑥GitHub Actions連携
ターミナル上に「Outputs:」として出力された値を、デプロイ対象のGitHubリポジトリのEnvironmentsに登録していきます。
| 変数名 | 取得元 (Terraform Output) | 例 |
|---|---|---|
AWS_DEPLOY_ROLE_ARN |
<project-name>-liff-<env>-github-oidc.DeployRoleArn |
arn:aws:iam::<env-account>:role/<project-name>-liff-dev-github-deploy |
AWS_REGION |
固定 | ap-northeast-1 |
S3_BUCKET_NAME |
<project-name>-liff-<env>-frontend.BucketName |
<project-name>-liff-dev-static-<env-account> |
CLOUDFRONT_DISTRIBUTION_ID |
<project-name>-liff-<env>-frontend.DistributionId |
EXXXXXXXXXXXX |
LIFF_ID |
LINE Developers から | 2001234567-asdfghjk |
その後、GitHub Actionsのワークフローを実行してフロントエンドのコンテンツをアップロードすれば完了です!
このあたりは手動での設定が必要となるため、前回の記事をご参照ください(スクリプトで自動化するのもありです)
⑦リソースの削除
以下のコマンドを実行します。
AWS_PROFILE=<your-profile-name> terraform destroy
dev/stg環境の場合、tfstate保存用バケットを除く、「今回のterraformによって作成されたすべてのAWSリソース」が削除されます。
今回は以上になります!
