TerraformでS3,CloudFront環境をアカウント跨ぎの移行
今までの記事
- (2/2)静的webサイトをS3にデプロイ!claude codeとGithub Actionsを用いて自動デプロイ!
- (1/2)静的webサイトをS3にデプロイ!claude codeとGithub Actionsを用いて自動デプロイ!
目次
- 2025/08/13〜 Terraform backend-setupの作成
- 2025/08/13〜 モジュールとAssumeRoleを使用してのbackend-setupの作成へ変更
-
2025/08/14〜 静的Webサイトホスティングを本番環境に構築
- 構成内容
- DNS関連のお勉強
- モジュールの粒度
- フェーズ 1: 準備 — IAMロールとコードの骨格作り
- 寄り道(突然のgit基本操作お勉強)
- 寄り道終わり(突然のgit基本操作お勉強)
- フェーズ 2: 証明書の発行とS3バケットの作成
- 寄り道(for_eachのお勉強)
- 寄り道終わり(for_eachのお勉強)
- 寄り道(権威DNSはRoute 53、検証用CNAMEはレジストラ側にあるままなのを発見)
- 寄り道終わり(権威DNSはRoute 53、検証用CNAMEはレジストラ側にあるままなのを発見)
- フェーズ 3: CloudFrontディストリビューションのデプロイ
- フェーズ 4: 最終接続と本番リリース
-
構成図起こし
claude code
/infraを深く考えて調査し、AWS構成図を.drawioファイルで作成してください。
・アイコン図形はAWS 2025を利用
・リソース間の通信の流れ、データの流れ
・AWSアカウントや、リージョンなどの関係性
・Terraformデプロイの関係性
・軽い説明
Terraform使用してのインフラ構築お勉強
作りたいもの
前回ブログで作成した環境をアカウント跨ぎで、同環境を作成
2025/08/13〜 Terraform backend-setupの作成
まずbackend-setupが必要なのでprdの設定をします。
フェーズ 1:terraform.tf
git操作(ファイル編集始まり)
これからファイルの編集を行うので、何か間違えた時にある地点に戻せるようにgitのブランチを切ってファイルの編集を行う
$ git status #現在のgit状況を確認
$ git branch #現在のブランチの状況を確認
$ git checkout -b feature/terraform-backend-setup #ブランチの作成
Terraformのバージョンなどの取り決め
このファイルで、このディレクトリで使うTerrafromのバージョンを指定する
またAWSプロバイダのバージョンも指定する
# backend-setup/terraform.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
バージョン指定の演算子によって意味が変わる
演算子 | 意味 |
---|---|
= 1.5.0 | 1.5.0 のみ固定 |
>= 1.5.0 | 1.5.0 以上 |
~> 1.5.0 | 1.5.X(1.6.0は除外) |
>= 1.5, < 2.0 | .5以上2.0未満 |
フェーズ 2:provider.tf
awsのプロバイダのリージョンを指定
ここでどのプロバイダーをTerraformで動かすか記述する
provider "aws" {
region = var.aws_region # variables.tfで定義された変数を参照
}
例えば複数のAWSアカウントを使い分ける場合はプロファイルを指定出来る。
全リソースに自動適用させるデフォルトタグの作成も可能。
フェーズ 3:variables.tf
main.tfで使う変数を定義する
ここで変数に値を入力することもあれば、terraform.tfvars
で入力することもある
terraform.tfvars
で変数に値を入力する方が柔軟性がある
variable "aws_region" {
description = "The AWS region to create resources in."
type = string
default = "ap-northeast-1"
}
type
で型を指定することによって、意図しないデータの受け渡しを防ぐ
main.tfでの使用例:
name = "${var.project_name}-tfstate-lock"
フェーズ 4:main.tf
ここに記載されているリソースを作成する
variables.tf
で定義した変数を使うことも可能
モジュールを使う際はここで、使うモジュールを指定する
1. データソース:AWSアカウントID取得
data "aws_caller_identity" "current" {}
・現在のAWSアカウントIDを動的に取得
・ハードコーディングを避けれる
取得出来る情報は様々
# 使用例
data.aws_caller_identity.current.account_id # "123456789012"
data.aws_caller_identity.current.arn # "arn:aws:iam::123456789012:user/terraform"
data.aws_caller_identity.current.user_id # "AIDACKCEVSQ6C2EXAMPLE"
2. メインのS3バケット
resource "aws_s3_bucket" "terraform_state" {
bucket = "${var.project_name}-tfstate-${data.aws_caller_identity.current.account_id}"
lifecycle {
prevent_destroy = true
}
tags = var.tags
}
構文の分解
resource "aws_s3_bucket" "terraform_state"
① ② ③
resource
キーワード
①これからAWSリソースを作成すると言う宣言
resource # Terraformに新しいリソースを作ることを伝える
data # 既存リソースから情報を取得する場合
variable # 変数を定義する場合
output # 出力値を定義する場合
"aws_s3_bucket"
リソースタイプ
②"aws_s3_bucket"
↓
aws = プロバイダー(AWS)
s3 = サービス名(Simple Storage Service)
bucket = リソースの種類(バケット)
"terraform_state"
リソース名(ローカル名)
③Terraform内部での識別名(自由に命名可能)
# ✅ 良い例:用途が明確
resource "aws_s3_bucket" "terraform_state"
resource "aws_s3_bucket" "application_logs"
resource "aws_s3_bucket" "user_uploads"
# ❌ 悪い例:意味が不明確
resource "aws_s3_bucket" "bucket1"
resource "aws_s3_bucket" "test"
resource "aws_s3_bucket" "tmp"
terrafrom destroy
をしても削除されない
誤ってprevent_destroy = true
variables.tfで定義しているタグが付与される
tags = var.tags
4. バージョニング設定
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
バージョニングの設定を作成
resource "aws_s3_bucket_versioning" "terraform_state"
バージョニングの設定を行うバケットを指定
bucket = aws_s3_bucket.terraform_state.id
5. 暗号化設定
resource: 作成するリソース、ローカル名
bucket: 対象のバケットを指定
apply_server_side_encryption_by_default
はAWS APIで設定可能な項目
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
6. パブリックアクセスブロック
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true # パブリックACLをブロック
block_public_policy = true # パブリックポリシーをブロック
ignore_public_acls = true # 既存のパブリックACLを無視
restrict_public_buckets = true # パブリックバケットを制限
}
7. DynamoDBテーブル(ステートロック)
resource "aws_dynamodb_table" "terraform_lock" {
name = "${var.project_name}-tfstate-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = var.tags
}
resource
:リソース宣言
name = "${var.project_name}-tfstate-lock"
name
:テーブル名
${var.project_name}
:variables.tfで定義されてる変数を導入
課金モード
billing_mode = # 課金方式の設定
"PAY_PER_REQUEST" # オンデマンド課金(使った分だけ)
フェーズ 5:outputs.tf
出力させる値を決めるファイル
リソースが作成されないと決まらないもの(ID)などを出力させて
他のTerraformでのリソース作成の際に使用する
# backend-setup/outputs.tf
output "s3_bucket_id" {
description = "The ID (name) of the S3 bucket for Terraform state."
value = aws_s3_bucket.terraform_state.id
}
output "dynamodb_table_name" {
description = "The name of the DynamoDB table for Terraform state locking."
value = aws_dynamodb_table.terraform_lock.name
}
出力の宣言
output
:出力値の宣言キーワード
"s3_bucket_id"
:出力の名前(任意)
value = # 実際に出力する値
aws_s3_bucket.terraform_state.id # S3バケットのID(名前)を参照
↓ ↓ ↓
リソースタイプ リソース名 属性
DynamoDBテーブル名の出力
output "dynamodb_table_name" # 出力名
description = "..." # 説明文
value = aws_dynamodb_table.terraform_lock.name # テーブル名を参照
↓ ↓ ↓
リソースタイプ リソース名 属性
なぜOutputが必要?
この出力値をモジュール間の連携などで使用する
これから難しいことをしていく時にどんどん活用される
git操作(ファイル編集終わり)
一区切りのファイル編集が終わったので、その編集した変更をリモートのメインブランチへ反映させるため操作を行う
$ git status #現在のgit状況を確認
$ git branch #現在のブランチの状況を確認
$ cd <プロジェクトのルートディレクトリ>
$ git add . #gitの変更をステージングにあげる
$ git status #現在のgit状況を確認
$ git commit -m "terraform-backend-setup" #gitの変更をローカルブランチへ適用させる
$ git push origin feature/terraform-backend-setup #変更をリモートのブランチへ適用させる
# ここからGithubなどのリモート側の操作
# リモートでmainブランチとfeature/terraform-backend-setupブランチがあるので、変更をmainブランチへ反映させるためにプルリクエストを行う
# プルリクエストを受けた側(自学習なら自分)は変更差分を見て問題無いか確認して、問題なければマージしてfeature/terraform-backend-setupブランチを削除する
# Githubなどのリモート側の操作はここまで
$ git branch #現在のブランチの状況を確認
$ git checkout main #最新のブランチに変更。リモートではもうブランチが削除されてる
$ git branch #現在のブランチの状況を確認
$ git pull origin main # リモートで反映させた変更が、ローカルにも反映される
$ git branch -d feature/terraform-backend-setup #不要になったブランチを削除する
$ git branch #現在のブランチの状況を確認
2025/08/13〜 モジュールとAssumeRoleを使用してのbackend-setupの作成へ変更
こんな感じ
┌─────────────────────────────────┐
│ 管理/ツールアカウント (Hub) │
│ │
│ 作業者はまずここにログイン 🔐 │
│ │
│ Account: xxxxxxxxxxxx │
└────────────┬────────────────────┘
│
│ AssumeRole
│
┌────────┼────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Dev │ │ Stg │ │ Prd │
│ Account │ │ Account │ │ Account │
│ (Spoke) │ │ (Spoke) │ │ (Spoke) │
│ │ │ │ │ │
│ 111111 │ │ 222222 │ │ 333333 │
└─────────┘ └─────────┘ └─────────┘
フェーズ 1:“STS:AssumeRole” の許可
概要
IAM Identity Centerを作成しているので、IAM Identity Centerの管理アカウントから許可セットを作成し、AssumeRoleの踏み台にするアカウントに許可セットを割り当てる。
許可セットを作成
IAM Identity Centerから許可セットを作成
今回はprd,stg,devアカウントにAssumeRoleしたいので3つ記載
※追記: DNSアカウントを途中で追加したのでそのアカウントも追加が必要です
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::<dev-account-id>:role/TerraformExecutionRole",
"arn:aws:iam::<stg-account-id>:role/TerraformExecutionRole",
"arn:aws:iam::<prd-account-id>:role/TerraformExecutionRole",
"arn:aws:iam::<DNS-account-id>:role/TerraformExecutionRole",
]
}
]
}
許可セットの割り当て
IAM Identity CenterからAWSアカウント→踏み台にするアカウントを選択し
割り当てたいユーザーやグループを選択し許可セットの割り当てを行う
フェーズ 2:IAMロールを作成
IAMロールの作成
prdアカウントからIAMサービスに移動し、ロールの作成を行う
信頼されたエンティティはカスタム信頼ポリシーを選択し以下を記載
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "<許可セットの割り当てから作成された、踏み台アカウントのロールarn>"
},
"Action": "sts:AssumeRole"
}
]
}
次に、必要な権限を選択し作成
フェーズ 3:Terraformコード作成
git操作(ファイル編集始まり)
これからファイルの編集を行うので、何か間違えた時にある地点に戻せるようにgitのブランチを切ってファイルの編集を行う
$ git status #現在のgit状況を確認
$ git branch #現在のブランチの状況を確認
$ git checkout -b refactor/backend-setup-modules-assume-role #ブランチの作成
ディレクトリ構造
tano1:infra tano$ tree
.
├── backend-setup
│ ├── environments
│ │ └── prd
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ ├── terraform.tf
│ │ ├── terraform.tfstate
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ └── modules
│ └── backend
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
terraform.tf
terraformのバージョンとプロバイダのバージョンを指定
provider.tf
assume_roleを記載
AWS CLIのプロファイルが管理アカウントでも記載することによって、こちらのアカウントにリソースは作成される
provider "aws" {
region = "ap-northeast-1"
assume_role {
# prdアカウント(0025...)で作成したロールのARN
role_arn = "<prdアカウント(0025...)で作成したロールのARN>"
session_name = "tf-session-0603game-prd-backend"
}
}
variables.tf
変数の定義を行うことによって、色んなところで変数を使用出来る
variable "project_name" {
description = "プロジェクト名"
type = string
}
使用例
# S3バケット名で使用
resource "aws_s3_bucket" "state" {
bucket = "${var.project_name}-tfstate-${var.environment}-${var.account_id}"
# 結果: "0603game-tfstate-prd-333333333333"
}
outputs.tf
output "backend_s3_bucket_name" {
description = "Terraformステート用のS3バケット名"
value = module.backend.s3_bucket_name
}
output "backend_dynamodb_table_name" {
description = "Terraformステートロック用のDynamoDBテーブル名"
value = module.backend.dynamodb_table_name
}
output "backend_s3_bucket_name" { # ① 出力名の定義
description = "..." # ② 説明文
value = module.backend.s3_bucket_name # ③ 出力する値
↓ ↓ ↓
モジュール参照 モジュール名 モジュールの出力名
}
main.tf
module "backend" {
source = "../../modules/backend"
project_name = var.project_name
environment = var.environment
account_id = var.account_id
common_tags = var.common_tags
}
module "backend" {
source = "../../modules/backend"
作成するリソースのモジュールを指定し、そのファイルのパスも指定する
source
は必須属性
変数の受け渡し
variables.tfで変数定義した入れ物に、terraform.tfvarsで値を挿入して、その値をmain.tfで参照される。
moduleに変数を引き渡す場合は、project_nameがmoduleのvariables.tfに対応していて、そこにvariables.tfの値を挿入している
project_name = var.project_name
environment = var.environment
account_id = var.account_id
common_tags = var.common_tags
terraform.tfvars variables.tf main.tf module
の値 → で変数定義 → で変数参照 → に渡される
var.xxx 引数として
分かりやすいので。(生成AI)
Step 1: 値の定義
┌──────────────────────────────────────┐
│ environments/prd/terraform.tfvars │
│ │
│ project_name = "0603game" │
│ environment = "prd" │
│ account_id = "333333333333" │
│ common_tags = { │
│ Project = "0603game" │
│ Environment = "production" │
│ } │
└────────────────┬─────────────────────┘
│ 値を読み込み
▼
Step 2: 変数の宣言
┌──────────────────────────────────────┐
│ environments/prd/variables.tf │
│ │
│ variable "project_name" { │
│ type = string │
│ } │
│ variable "environment" { │
│ type = string │
│ } │
│ variable "account_id" { │
│ type = string │
│ } │
│ variable "common_tags" { │
│ type = map(string) │
│ } │
└────────────────┬─────────────────────┘
│ var.xxx で参照可能に
▼
Step 3: モジュールへの受け渡し
┌──────────────────────────────────────┐
│ environments/prd/main.tf │
│ │
│ module "backend" { │
│ source = "../../modules/backend" │
│ │
│ project_name = var.project_name │ ← "0603game"
│ environment = var.environment │ ← "prd"
│ account_id = var.account_id │ ← "333333333333"
│ common_tags = var.common_tags │ ← {Project="0603game"...}
│ } │
└────────────────┬─────────────────────┘
│ モジュールの引数として渡す
▼
Step 4: モジュール内での受け取り
┌──────────────────────────────────────┐
│ modules/backend/variables.tf │
│ │
│ variable "project_name" { │
│ description = "プロジェクト名" │
│ type = string │
│ } │
│ # ↑ モジュール内でvar.project_name │
│ # として使用可能に │
└────────────────┬─────────────────────┘
│
▼
Step 5: モジュール内での使用
┌──────────────────────────────────────┐
│ modules/backend/main.tf │
│ │
│ resource "aws_s3_bucket" "state" { │
│ bucket = "${var.project_name}-..." │ ← "0603game-..."
│ tags = var.common_tags │
│ } │
└──────────────────────────────────────┘
backend.tf
ブーストトラップでのbackend.tfなので、後ほど書き換えが必要
# 初回はローカルにtfstateファイルを作成します
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
terraform.tfvars
variables.tfの変数定義に値を挿入する
project_name = "0603game"
environment = "prd"
# prdアカウントのID
account_id = "002540791269"
common_tags = {
ManagedBy = "Terraform"
Project = "0603game"
Environment = "prd"
}
modules/backend/variables.tf
変数を定義することにより、prd/main.tfから受け渡した変数の値をmodules/backendで使える
variable "project_name" {
description = "プロジェクト名"
type = string
}
variable "environment" {
description = "環境名 (例: prd, stg)"
type = string
}
variable "account_id" {
description = "対象環境のAWSアカウントID"
type = string
}
variable "common_tags" {
description = "すべてのリソースに適用する共通のタグ"
type = map(string)
default = {}
}
modules/backend/main.tf
# S3 Bucket for Terraform State
resource "aws_s3_bucket" "tfstate" {
bucket = "${var.project_name}-${var.environment}-tfstate-${var.account_id}"
lifecycle {
prevent_destroy = true
}
tags = merge(
var.common_tags,
{
Name = "${var.project_name}-${var.environment}-tfstate-bucket"
}
)
}
# S3 Bucket Versioning
resource "aws_s3_bucket_versioning" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
versioning_configuration {
status = "Enabled"
}
}
# S3 Bucket Public Access Block
resource "aws_s3_bucket_public_access_block" "tfstate" {
bucket = aws_s3_bucket.tfstate.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# DynamoDB Table for Terraform State Lock
resource "aws_dynamodb_table" "tflock" {
name = "${var.project_name}-${var.environment}-tfstate-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
tags = merge(
var.common_tags,
{
Name = "${var.project_name}-${var.environment}-tfstate-lock-table"
}
)
}
variables.tfでの変数定義が効いて来る
resource "aws_s3_bucket" "tfstate" {
bucket = "${var.project_name}-${var.environment}-tfstate-${var.account_id}"
バケット名はproject-env-tfstate-account_id
形式でグローバルな一意を確保
bucket = "${var.project_name}-${var.environment}-tfstate-${var.account_id}"
↓ ↓ ↓
呼び出し元から 呼び出し元から 呼び出し元から
受け取る値 受け取る値 受け取る値
terraform destroy
での削除保護
lifecycle {
prevent_destroy = true
merge
複数のマップを1つに結合
今回の場合は、var.common_tags
にName = "${var.project_name}-${var.environment}-tfstate-bucket"
を結合
Name = "${var.project_name}-${var.environment}-tfstate-bucket"
はvariables.tfでの変数定義が効いて来る
tags = merge(
var.common_tags,
{
Name = "${var.project_name}-${var.environment}-tfstate-bucket"
}
)
S3 のバージョニング
S3 のパブリックアクセス遮断
DynamoDB(ロックテーブル)
上記は割愛
modules/backend/outputs.tf
何かで使う。
まだ不明
output "s3_bucket_name" {
description = "Terraformステート用のS3バケット名"
value = aws_s3_bucket.tfstate.bucket
}
output "dynamodb_table_name" {
description = "Terraformステートロック用のDynamoDBテーブル名"
value = aws_dynamodb_table.tflock.name
}
フェーズ 4:管理アカウントのSSOプロファイル作成
CLIでSSOプロファイル登録
CLIからコマンド入力すると、下記のように案内されるので従って登録する
tano1:0603game tano$ aws configure sso --profile <登録するプロファイル名>
SSO session name (Recommended): <セッション名>
There are 7 AWS accounts available to you.
Using the account ID <登録したいアカウント名>
There are 3 roles available to you.
Using the role name <登録したいロールを選択>
CLI default client Region [None]: ap-northeast-1 # リージョン指定
CLI default output format [None]: json # 出力形式
To use this profile, specify the profile name using --profile, as shown:
aws s3 ls --profile <プロファイル名が出てればOK>
tano1:0603game tano$
.envrc
を使用してる場合はプロファイル名を環境変数として記載する
export AWS_PROFILE=<プロファイル名>
フェーズ 5:リソース作成
terraform init
作成するリソースのディレクトリで
ざっくり実施されてること
・バックエンドを確定
・Providersを取得
・Modulesを取得
・必要なファイル作成
つまりterraformでリソースを作るにあたり必要な、設定が確認される的な。。。?(たぶん)
terraform plan
ざっくり実施されてること
・これらの設定ファイルを読み込む
・現在のtfstate状態を読み込み
・実際のAWSリソースを確認
・実際のリソースとtfstate+実行計画との差分を計算
・実行計画生成、表示
差分表示とよく言われる
ヘッダーと記号
作成のみ表示されている(変更や削除は無し)
Resource actions are indicated with:
+ create
意味
# module.backend.aws_dynamodb_table.tflock will be created
↓ ↓ ↓ ↓ ↓
モジュール名 リソースタイプ リソース名 アクション
確定している値は事前に記載されている
(known after apply) の意味
arn = (known after apply) # 作成後にAWSが割り当てる
id = (known after apply) # テーブル名と同じになる
read_capacity = (known after apply) # PAY_PER_REQUESTなのでN/A
arn: arnのアカウントIDやリージョンはAWS側で決定
id: 作成完了後に確定
read_capacity: オンデマンドなので該当無し
terraform apply
ざっくり実施されてること
・terraform planの再実行
・リソース作成開始
・tfstate更新
・完了メッセージ
様々な問題でterraform planでOKだったものが、terraform applyで無理だった例は多々ある
フェーズ 6:backend.tf書き換え
terraform {
backend "s3" {
# applyで作成されたS3バケット名
bucket = "<applyで作成されたS3バケット名>"
# このバックエンドリソース自体のStateファイルのパス
key = "backend/terraform.tfstate"
region = "ap-northeast-1"
# applyで作成されたDynamoDBテーブル名
dynamodb_table = "<applyで作成されたDynamoDBテーブル名>"
encrypt = true
# バックエンド操作もassume_roleで行う
role_arn = "<バックエンド操作もassume_roleで行うロール名>"
}
}
そもそもパスのことをkeyって言うのはS3が階層構造ではなくて、フラットな構造になっているからで、コンソールで見ると階層構造のように見せている
# このバックエンドリソース自体のStateファイルのパス
key = "backend/terraform.tfstate"
terraform init -migrate-state
-migrate-state
:バックエンド設定の変更を検出し、設定を変える
terraform init -migrate-state
git操作(ファイル編集終わり)
一区切りのファイル編集が終わったので、その編集した変更をリモートのメインブランチへ反映させるため操作を行う
$ git status #現在のgit状況を確認
$ git branch #現在のブランチの状況を確認
$ cd <プロジェクトのルートディレクトリ>
$ git add . #gitの変更をステージングにあげる
$ git status #現在のgit状況を確認
$ git commit -m "backend-setup-modules-assume-role" #gitの変更をローカルブランチへ適用させる
$ git push origin refactor/backend-setup-modules-assume-role #変更をリモートのブランチへ適用させる
# ここからGithubなどのリモート側の操作
# リモートでmainブランチとrefactor/backend-setup-modules-assume-roleブランチがあるので、変更をmainブランチへ反映させるためにプルリクエストを行う
# プルリクエストを受けた側(自学習なら自分)は変更差分を見て問題無いか確認して、問題なければマージしてrefactor/backend-setup-modules-assume-roleブランチを削除する
# Githubなどのリモート側の操作はここまで
$ git branch #現在のブランチの状況を確認
$ git checkout main #最新のブランチに変更。リモートではもうブランチが削除されてる
$ git branch #現在のブランチの状況を確認
$ git pull origin main # リモートで反映させた変更が、ローカルにも反映される
$ git branch -d refactor/backend-setup-modules-assume-role #不要になったブランチを削除する
$ git branch #現在のブランチの状況を確認
2025/08/14〜 静的Webサイトホスティングを本番環境に構築
構成内容
前回の記事で作成したものを別のアカウント(prd環境)で再現し、Terraformで管理する
・(1/2)静的webサイトをS3にデプロイ!claude codeとGithub Actionsを用いて自動デプロイ!
DNS関連のお勉強
Route53の設定などをしたアカウントから、prd環境で再現するのでこれらの設定などの再現はどうするべきか検討する
ベストプラクティス
専用のアカウントでDNSを一元管理するのがベストプラクティス??
Route 53
AWSのマネージドDNSサービス。
ドメイン登録、DNSレコード管理、ヘルスチェックやトラフィック制御までを提供
Route 53はAWSサービス名
様々なDNS関連のサービスを提供している
Route 53 公開ホストゾーン
インターネット全体から参照されるDNSゾーン
あるドメインの権威情報を保持し、世界へ応答する。
NSレコードに書かれたネームサーバーをレジストラ側のNSに設定してはじめて権威になる。
・Route 53でゾーンを作ると、4つのネームサーバーが(NS)が発行される
・でも世界中のDNSはレジストラ(お名前.comなど)に登録されているNSだけ見に行く
・だからレジストラ側の設定画面でその4つのNSに変更すると、『このドメインの答えはRoute 53が出します』と全インターネットに委任される
Aレコード,Alias A
Aレコード:IPv4アドレスを紐づける基本レコード
Alias A:IPではなくAWSリソースに向けれる
CNAME
www.example.com
はd3xxx.cloudfront.net
ですよと伝えるだけ
NS / SOA レコード
NSレコード
厳密には分からないけど、そのレコードが登録されてるところが強い
SOAレコード
Route 53では自動管理され、手で触ることはほぼ無いらしい
ACM証明書と検証CNAME
証明書やDNS関連は難しい。。。
ACMが発行したCNAMEをRoute 53に作成
ACMがCNAMEでの検証をし、本人か確認する的な流れ
CloudFrontで独自ドメインをHTTPS配信をしたくてACMに証明書を申請
ACMが『このドメインの所有者の確認が必要』とのことで検証用CNAMEを発行
そして、権威DNSにその検証用CNAMEでCNEMEレコードを作成するとACMがそれをDNSに確認してDNS検証が完了となる
モジュールの粒度
アンチパターン
細かすぎるマイクロ分割
# ❌ マイクロ分割
modules/s3-bucket/
modules/s3-versioning/ # 細かすぎる
モジュール間の横串参照
# ❌ モジュール間の横串参照
data "terraform_remote_state" "other" { } # 他モジュールの状態を直接参照
モジュール内でプロバイダー定義
# ❌ モジュール内でプロバイダー定義
provider "aws" { # モジュール内で定義しない
region = "us-east-1"
}
巨大な万能モジュール
# ❌ 巨大な万能モジュール
variable "create_s3" { }
variable "create_cloudfront" { }
variable "create_route53" { } # フラグ地獄
ベストプラクティス
役割ごとのモジュールがベストプラクティス???
使い倒すうちにメリットデメリットを実感してくる系??
フェーズ 1: 準備 — IAMロールとコードの骨格作り
git操作(ファイル編集始まり)
これからファイルの編集を行うので、何か間違えた時にある地点に戻せるようにgitのブランチを切ってファイルの編集を行う
$ git status #現在のgit状況を確認
$ git branch #現在のブランチの状況を確認
$ git checkout -b feature/prod-static-website-hosting #ブランチの作成
ディレクトリ構造
tano1:infra tano$ tree
.
├── backend-setup
│ ├── environments
│ │ └── prd
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ ├── terraform.tf
│ │ ├── terraform.tfstate
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ └── modules
│ └── backend
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── modules
│ ├── acm-certificate
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── variables.tf
│ │ └── versions.tf
│ ├── cloudfront-oac
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ └── variables.tf
│ ├── route53-records
│ │ ├── main.tf
│ │ └── variables.tf
│ └── s3-private-bucket
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── static-website
└── environments
└── prd
├── backend.tf
├── main.tf
├── outputs.tf
├── providers.tf
├── terraform.tf
├── terraform.tfvars
└── variables.tf
14 directories, 30 files
tano1:infra tano$
IAMロールの作成
TerraformからDNS管理アカウントを操作するので、DNS管理アカウントにポリシーを作成し、そのポリシーでprdのTerraform操作を引き受けるロールを作成
※追記: 最初はroute53の権限絞ってたけど、必要な権限が想定より多くて*にしました。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "route53:*",
"Resource": "*"
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<prdアカウントID>:role/TerraformExecutionRole"
},
"Action": "sts:AssumeRole"
}
]
}
寄り道(突然のgit基本操作お勉強)
・空ディレクトリを作成しただけでは変更にならない。ファイルの中身の変更だけ追跡している。
$ git pull #最新の変更を反映させる
$ git checkout -b <ブランチ名-で繋げる> #ブランチの作成
$ git branch #現在のブランチの状況を確認
$ git status #現在のgit状況を確認
$ git add <.> #gitの変更をステージングにあげる
$ git commit -m "<コミットコメント>" #gitの変更をローカルブランチへ適用させる
$ git push <リモートリポジトリ名> <ブランチ名> #変更をリモートのブランチへ適用させる
Github上でプルリクエストを作成
受けた側は確認してマージしてブランチ削除
$ git checkout <ブランチ名> #最新のブランチに変更。リモートではもうブランチが削除されてるため
$ git pull origin <ブランチ名> #最新の変更を最新を維持するブランチに変更を反映させる
$ git branch -d <ブランチ名> #不要になったブランチを削除する
寄り道終わり(突然のgit基本操作お勉強)
ディレクトリの作成
再利用したいので、infra直下にmodulesディレクトリを作成。
static-websiteが静的webサイトホスティングになる。
backend-setupの下のmodulesは専用モジュールなので、そのまま。
tano1:infra tano$ tree
.
├── backend-setup
│ ├── environments
│ │ └── prd
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ │ ├── provider.tf
│ │ ├── terraform.tf
│ │ ├── terraform.tfstate
│ │ ├── terraform.tfvars
│ │ └── variables.tf
│ └── modules
│ └── backend
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── modules
│ ├── acm-certificate
│ ├── cloudfront-oac
│ ├── route53-records
│ └── s3-private-bucket
└── static-website
└── environments
└── prd
14 directories, 11 files
tano1:infra tano$
フェーズ 2: 証明書の発行とS3バケットの作成
modules/acm-certificate/ の作成
機能
DNSゾーンを取得する
ACMの証明書をリクエスト
DNS検証用のCNAMEレコードを作成
DNS検証が完了するまで待機
modules/acm-certificate/main.tf
data: 既存リソースの情報を読み取る
aws_route53_zone: Terraformのデータソースの種類。AWS Route 53のホストゾーンを取得するためのデータソース。
main: ローカル識別名。data.aws_route53_zone.main.name
=ドメインnameとなる。
provider = aws.dns_master: dns_masterと言うprovidersを使用する
name = var.domain_name: ドメインnameを取得
resource: data
と違って新しいリソースを作成する。既存のものを参照するのではなくて、新規作成、更新、削除の管理対象となる。
aws_acm_certificate: リソースタイプ。証明書を作成する
provider = aws.us-east-1: CloudFrontで使うためリージョン固定
domain_name = var.domain_name: メインドメイン
subject_alternative_names = var.subject_alternative_names: 追加ドメイン
validation_method = "DNS": 検証方法を選ぶ
lifecycle { create_before_destroy = true: 新しい証明書を作成してから古いのを削除する
# DNSゾーンの情報を取得
data "aws_route53_zone" "main" {
provider = aws.dns_master
name = var.domain_name
}
# ACM証明書をリクエスト (DNS検証方式)
resource "aws_acm_certificate" "main" {
domain_name = var.domain_name
subject_alternative_names = var.subject_alternative_names
validation_method = "DNS"
tags = var.tags
lifecycle {
create_before_destroy = true
}
}
寄り道(for_eachのお勉強)
処理の流れ
Route 53にレコードを追加する処理を、専用アカウントで実施します。
その実施内容はfor_eachで与えたキー分実施します。
その要素の作成が必要で、ACM証明書からドメイン名、CNAME名、レコードタイプ、CNAME値が格納されてる配列を受け取ってるので、それをマップ形式に変換する際にキーに設定したCNAME名が重複してマップ内に存在するならfor_eachに渡します。
そしてその渡されたマップ要素分for_eachで繰り返しレコードを作成します。
# DNS検証用のCNAMEレコードを作成
resource "aws_route53_record" "validation" { #レコード作成
provider = aws.dns_master #provider指定
for_each = { #要素分繰り返しレコード作成します
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
# ACM証明書からの配列をdvoへ変数として格納して反復処理する:生成するのがマップ形式で、キー
#この時のマップに格納するときにキーの重複を参照して、重複があれば上書き
name = dvo.resource_record_name #マップ形式の値
record = dvo.resource_record_value #マップ形式の値
type = dvo.resource_record_type #マップ形式の値
}
}
以下がACMから渡される値
1ブロックで1要素
aws_acm_certificate.main.domain_validation_options = [
{#ここから
domain_name = "tanoyuusuke.com"
resource_record_name = "_xxxx.tanoyuusuke.com."
resource_record_type = "CNAME"
resource_record_value = "_token.acm-validations.aws."
},#ここまでで1要素
{
domain_name = "*.tanoyuusuke.com"
resource_record_name = "_xxxx.tanoyuusuke.com."
resource_record_type = "CNAME"
resource_record_value = "_token.acm-validations.aws."
}
]
これを変数としてdvoに格納し、マップを作成する
それを要素ごとに繰り返しなので、下記の状態からもう一度繰り返すが、キーであるdvo.resource_record_name
と同じキーが存在するので上書きして1ブロックのマップが完成する。
そのマップのキー分for_eachが繰り返して、レコード作成を実行する(今回は1回)
{
"_xxxx.tanoyuusuke.com." = {
name = "_xxxx.tanoyuusuke.com."
record = "_token.acm-validations.aws."
type = "CNAME"
}
寄り道終わり(for_eachのお勉強)
aws_route53_record: Route 53でレコードを操作する
provider = aws.dns_master: DNS専用アカウントを指定
for_each: 同じリソースを複数作成するためのTerraformのメタ引数
メタ引数: 通常の引数は、ami=ami-123456
のようなAWS CLIの形で指定。メタ引数はTerraform制御のためのもの。下記のようにTerraform内の独自制御のもの。
主要なメタ引数一覧(生成AI)
メタ引数 | 用途 | リソース | モジュール |
---|---|---|---|
count | 指定数のリソース作成 | ✅ | ✅ |
for_each | マップ/セットから複数作成 | ✅ | ✅ |
provider | 使用するプロバイダー指定 | ✅ | ❌ |
depends_on | 明示的な依存関係 | ✅ | ✅ |
lifecycle | ライフサイクル制御 | ✅ | ❌ |
provisioner | 作成後の処理(非推奨) | ✅ | ❌ |
for: 繰り返しの文を書きますとの宣言みたいなもの。for構文として書き方が決まっている
dvo: ループの変数名。各要素を一時的に入れる箱の名前
in: 次のリストやマップの中から1つずつ取り出す
aws_acm_certificate.main.domain_validation_options:
aws_acm_certificate
:リソースタイプ
main
:自分が付けた識別名、そのモジュール内のみに影響する
domain_validation_options
:acm証明書の検証CNAMEなど。下記のような形
aws_acm_certificate.main.domain_validation_options = [
{
domain_name = "tanoyuusuke.com"
resource_record_name = "_xxxx.tanoyuusuke.com."
resource_record_type = "CNAME"
resource_record_value = "_token.acm-validations.aws."
},
{
domain_name = "*.tanoyuusuke.com"
resource_record_name = "_xxxx.tanoyuusuke.com."
resource_record_type = "CNAME"
resource_record_value = "_token.acm-validations.aws."
}
]
dvo.domain_name: dvoに格納された上記のdomain_nameのこと
マップ包括表記: 下記のような形でマップを作成しますとのこと、キーと{値}で構成されて重複のキーがあればマップ構成の際に上書きされる。
dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
機能: ACMから検証CNAMEなどの値を取得して、重複無しでそのレコードを作成する
# DNS検証用のCNAMEレコードを作成
resource "aws_route53_record" "validation" {
provider = aws.dns_master
for_each = {
for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
属性: 下記の記載のようなものを一まとめに属性と呼ぶ。aws_route53_record
で設定出来るもの。今回の場合はレコード登録際の話。
allow_overwrite = true: 同名、同タイプのレコードがすでに存在していても上書きをすると言う意味
name = each.key: 作成するレコード名。for_each
の時のキーを指定。(今回はドメイン名)
records = [each.value.record]: for_each
の時の値オブジェクト全般の中のrecord
の値をレコード作成の値に使う
**ttl = 60:**TTL(キャッシュ寿命)。DNSキャッシュがこのレコードを何秒間保持するかの秒数
type = each.value.type: for_each
の時の値オブジェクト全般の中のtype
の値をレコード作成のレコードタイプに使う
zone_id = data.aws_route53_zone.main.zone_id: 登録先のホストゾーンを指定する
allow_overwrite = true
name = each.key
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.main.zone_id
}
aws_acm_certificate_validation: DNS検証の完了を待つためのTerraformリソース
certificate_arn = aws_acm_certificate.main.arn: どの証明書を検証するかを指定。上記で作成しているものを参照する。
aws_route53_record.validation: name、record、typeを指定したのにプラスしてTerraformが自動で作成するのもがリソースIDと完全修飾ドメイン名で、それらのプロパティも含んでる。
forの構文:
[for <変数> in <コレクション> : <出力式>]
# ↑
# コロンの後が「何を取り出すか」
: record.fqdn: for文の出力部分。record
は変数名、fqdn
はプロティ名。record
の中のfqdn
を取得すると言う意味
for record in aws_route53_record.validation : record.fqdn: aws_route53_record.validation
をrecord
と言う変数に入れて、その中の各要素の数だけ繰り返しfqdn
をvalidation_record_fqdns
へ代入する
[]: リストって意味、{}はマップ
リストとマップの違い: 分からない。一応生成AIの説明をメモ
2️⃣ 特徴の比較(Claude)
特徴 | リスト | マップ |
---|---|---|
順序 | ✅ 順序を保持 | ❌ 順序は保証されない |
重複 | ✅ 同じ値OK | ❌ キーの重複不可 |
アクセス | 数字(インデックス) | 文字列(キー) |
検索速度 | 遅い(全要素チェック) | 速い(キー直接アクセス) |
用途 | 同じ種類のデータ集合 | 名前付きデータの管理 |
# DNS検証が完了するまで待機
resource "aws_acm_certificate_validation" "main" {
certificate_arn = aws_acm_certificate.main.arn
validation_record_fqdns = [
for record in aws_route53_record.validation : record.fqdn
]
}
寄り道(権威DNSはRoute 53、検証用CNAMEはレジストラ側にあるままなのを発見)
前回作成したときにブログを記録として作成しててよかった
DNS検証を先に行なって、そのあとにネームサーバーをRoute 53に切り替えたのでCNAMEがレジストラ側で作成されたままになっている
次回の証明書更新の際にDNS検証が出来なくなる
ACMで発行されているCNAMEを確認
ACMで証明書を確認すると、ドメインの欄があって本来はそこのRoute 53でレコードを作成を押すと一発で作成出来ると思われるがグレーアウトになっててドメインを選択出来ない。
なのでCNAME名``CNAME値``レコードタイプ
を確認する
サブドメインもある場合でも値などが同じならレコード作成は1つでOK。
Route 53でCNAMEレコード作成
レコード作成から下記を入力しレコード作成
レコード名
=CNAME名<.ドメイン名を抜いたもの>
レコードタイプ
=レコードタイプ
値
=CNAME値
検証が成功するかはどうするの?
検証が成功するかどうかは実際に更新の時にならないと分からないが、CNAMEレコードとしてきちんと登録出来たかどうかをコマンドで確認出来る
tano1: tano$ dig +short _dca204859df78ca393858c4c8707c43c.tanoyuusuke.com CNAME
_68748e600a040003d680c5e0b4411482.xlfgrmvvlj.acm-validations.aws.
tano1: tano$ dig +short tanoyuusuke.com
寄り道終わり(権威DNSはRoute 53、検証用CNAMEはレジストラ側にあるままなのを発見)
modules/acm-certificate/variables.tf
variables.tf: 変数の定義。modulesのvariables.tfはprdのtfvarsの値が入るイメージ
subject_alternative_names: 証明書でカバーしたい追加のサブドメインを定義
variable "tags": 呼び出したルート側からタグが降ってくる
tano1:acm-certificate tano$ cat variables.tf
variable "domain_name" {
description = "証明書を発行するドメイン名"
type = string
}
variable "subject_alternative_names" {
description = "証明書に含める追加のドメイン名 (SANs)"
type = list(string)
default = []
}
variable "tags" {
description = "リソースに適用するタグ"
type = map(string)
default = {}
}
tano1:acm-certificate tano$
modules/acm-certificate/versions.tf
versions.tf: モジュール側の宣言情報。
configuration_aliases = [aws.dns_master]: aws.dns_master
はaliasだと伝える
tano1:acm-certificate tano$ cat versions.tf
# versions.tf(宣言あり)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
configuration_aliases = [aws.dns_master]
}
}
}
tano1:acm-certificate tano$
modules/acm-certificate/outputs.tf
outputs.tf: modules呼び出し側から参照する情報を出力する。もしCloudFrontなどで証明書のARNが必要であれば出力しとく必要がある。
certificate_arn: 識別名。以下使用例。
resource "aws_cloudfront_distribution" "cdn" {
viewer_certificate {
acm_certificate_arn = module.acm_cert.certificate_arn #←これ
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
tano1:acm-certificate tano$ cat outputs.tf
output "certificate_arn" {
description = "発行されたACM証明書のARN"
value = aws_acm_certificate.main.arn
}
tano1:acm-certificate tano$
modules/s3-private-bucket/ の作成
パブリックアクセスブロックなS3バケットの作成
modules/s3-private-bucket/main.tf
aws_s3_bucket: バケットを作成
bucket = aws_s3_bucket.main.id: どのバケットに適用するか設定
versioning_configuration: バージョニングの詳細設定
? "Enabled" : "Suspended": 条件式 ? 真の場合の値 : 偽の場合の値
id = "expire-old-versions": ルールの識別名
status = "Enabled": ルールを有効にする
filter {}: ライフサイクルルールにフィルターを指定しないと警告となる
noncurrent_version_expiration: 古いバージョンを削除する。何日で削除するか設定出来る。
abort_incomplete_multipart_upload: 不完全なマルチアップロードを削除
aws_s3_bucket_public_access_block: パブリックアクセスブロック設定
aws_s3_bucket_server_side_encryption_configuration: サーバーサイド暗号化設定
AES256: SSE-S3暗号化
tano1:s3-private-bucket tano$ cat main.tf
resource "aws_s3_bucket" "main" {
bucket = var.bucket_name
tags = var.tags
}
# バージョニング設定
resource "aws_s3_bucket_versioning" "main" {
bucket = aws_s3_bucket.main.id
versioning_configuration {
status = var.enable_versioning ? "Enabled" : "Suspended"
}
}
# ライフサイクルポリシー(常に作成)
resource "aws_s3_bucket_lifecycle_configuration" "main" {
bucket = aws_s3_bucket.main.id
rule {
id = "expire-old-versions"
status = "Enabled"
# 追加:バケット全体を対象にする
filter {}
# 古いバージョンの削除(バージョニング停止後も有効)
noncurrent_version_expiration {
noncurrent_days = var.noncurrent_version_expiration_days
}
# 不完全なマルチパートアップロードも削除
abort_incomplete_multipart_upload {
days_after_initiation = 7
}
}
}
resource "aws_s3_bucket_public_access_block" "main" {
bucket = aws_s3_bucket.main.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "main" {
bucket = aws_s3_bucket.main.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
tano1:s3-private-bucket tano$
modules/s3-private-bucket/variables.tf
variables.tf: main.tfで使う変数を定義する
tano1:s3-private-bucket tano$ cat variables.tf
variable "bucket_name" {
description = "作成するS3バケットの名前"
type = string
}
variable "tags" {
description = "リソースに適用するタグ"
type = map(string)
default = {}
}
variable "enable_versioning" {
description = "バージョニングを有効にする場合はtrue"
type = bool
default = true # デフォルトで有効にする
}
variable "noncurrent_version_expiration_days" {
description = "古いバージョンを自動削除するまでの日数"
type = number
default = 90 # デフォルトは90日
}
tano1:s3-private-bucket tano$
modules/s3-private-bucket/outputs.tf
outputs.tf: モジュール呼び出し側から参照する情報を出力する。
tano1:s3-private-bucket tano$ cat outputs.tf
output "bucket_id" {
description = "S3バケットのID (バケット名)"
value = aws_s3_bucket.main.id
}
output "bucket_arn" {
description = "S3バケットのARN"
value = aws_s3_bucket.main.arn
}
output "bucket_regional_domain_name" {
description = "S3バケットのリージョナルドメイン名"
value = aws_s3_bucket.main.bucket_regional_domain_name
}tano1:s3-private-bucket tano$
infra/static-website/environments/prd/の設定
パブリックアクセスブロックのS3バケットと、ACM証明書周りのドメイン設定をまずは設定
static-website/environments/prd/providers.tf
alias = "us-east-1": 呼び出すリソースによってリージョンやアカウント変えるための設定
tano1:prd tano$ cat providers.tf
# デフォルトプロバイダ (prdアカウントのap-northeast-1)
provider "aws" {
region = "ap-northeast-1"
assume_role {
role_arn = "arn:aws:iam::002540791269:role/TerraformExecutionRole"
session_name = "tf-session-static-website-prd"
}
}
# ACM証明書用のプロバイダ (prdアカウントのus-east-1)
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
assume_role {
role_arn = "Terraformからの操作用のロールARN"
session_name = "tf-session-static-website-prd-acm"
}
}
# DNS操作用のプロバイダ (DNS管理アカウントのRoute53)
provider "aws" {
alias = "dns_master"
region = "ap-northeast-1" # Route53はグローバルなのでリージョンはどこでもOK
assume_role {
role_arn = "Terraformからの操作用のロールARN"
session_name = "tf-session-dns-cross-account"
}
}
tano1:prd tano$
static-website/environments/prd/terraform.tf
TerraformのバージョンとTerraformAWSプロバイダーを指定:
tano1:prd tano$ cat terraform.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
tano1:prd tano$
static-website/environments/prd/backend.tf
backend.tf: backend-setupで使用したリソースを指定
tano1:prd tano$ cat backend.tf
terraform {
backend "s3" {
# backend-setupで作成したS3バケット
bucket = "<バケット名>"
# このスタック専用のStateファイルのパス
key = "static-website/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "0603game-prd-tfstate-lock"
encrypt = true
role_arn = "<Terraform操作用のARN>"
}
}
tano1:prd tano$
static-website/environments/prd/variables.tf
variables.tf: 変数を定義する
# 追記 2025/08/20
tano1:prd tano$ cat variables.tf
variable "project" {
description = "プロジェクト名"
type = string
}
variable "environment" {
description = "環境名(dev/stg/prd)"
type = string
}
# ここまで 2025/08/20
variable "domain_name" {
description = "ウェブサイトのドメイン名"
type = string
}
variable "aws_account_id" {
description = "この環境のAWSアカウントID"
type = string
}
variable "common_tags" {
description = "リソースに適用する共通のタグ"
type = map(string)
default = {}
}
tano1:prd tano$
static-website/environments/prd/terraform.tfvars
terraform.tfvars: 定義した変数に代入する値
tano1:prd tano$ cat terraform.tfvars
# 追記 2025/08/20
project = "0603game"
environment = "prd"
# ここまで 2025/08/20
domain_name = "tanoyuusuke.com"
aws_account_id = "002540791269"
common_tags = {
Project = "0603game"
Environment = "prd"
ManagedBy = "Terraform"
Owner = "tano" # 追記 2025/08/20
}
tano1:prd tano$
static-website/environments/prd/main.tf
module "acm": モジュールの宣言acm
は識別名
source = "../../../modules/acm-certificate": モジュールの場所を指定
aws = aws.us-east-1: aliasをus-east-1にしているので、us-east-1のプロバイダーを使用
bucket_name = "tano-0603game-bucket-${var.aws_account_id}": 変数を用いてバケット名を決めている。
tano1:prd tano$ cat main.tf
# ACM証明書モジュールを呼び出し
module "acm" {
source = "../../../modules/acm-certificate"
# providerをエイリアスで指定
providers = {
aws = aws.us-east-1 # このモジュール内のデフォルトはus-east-1
aws.dns_master = aws.dns_master # dns_masterプロバイダを渡す
}
domain_name = var.domain_name
subject_alternative_names = ["www.${var.domain_name}"]
tags = var.common_tags
}
# S3バケットモジュールを呼び出し
module "s3_website" {
source = "../../../modules/s3-private-bucket"
bucket_name = "tano-0603game-bucket-${var.aws_account_id}" # 重複しないようにアカウントIDを付与
tags = var.common_tags
}
tano1:prd tano$
static-website/environments/prd/outputs.tf
outputs.tf: 外部や他スタックなどで参照するために出力する。
tano1:prd tano$ cat outputs.tf
output "acm_certificate_arn" {
description = "発行されたACM証明書のARN"
value = module.acm.certificate_arn
}
output "s3_website_bucket_id" {
description = "作成されたS3バケットのID"
value = module.s3_website.bucket_id
}
tano1:prd tano$
Terraformの実行
AWS CLIの認証確認
このコマンドで現在のAWS CLIの認証情報が出力される
$ aws sts get-caller-identity
terraform init
新しいバックエンド設定、モジュール設定などを読み込む
terraform plan
実行計画生成。以下今回出たエラー
aliasで記載していたのにaws.us-east-1
を直接指定していた
│ Error: Provider configuration not present
│
│ To work with module.acm.aws_acm_certificate.main its original provider configuration at
│ module.acm.provider["registry.terraform.io/hashicorp/aws"].us-east-1 is required, but it has been
│ removed. This occurs when a provider configuration is removed while objects created by that provider
│ still exist in the state. Re-add the provider configuration to destroy
│ module.acm.aws_acm_certificate.main, after which you can remove the provider configuration again.
╵
╷
│ Error: Provider configuration not present
│
│ To work with module.acm.aws_acm_certificate_validation.main its original provider configuration at
│ module.acm.provider["registry.terraform.io/hashicorp/aws"].us-east-1 is required, but it has been
│ removed. This occurs when a provider configuration is removed while objects created by that provider
│ still exist in the state. Re-add the provider configuration to destroy
│ module.acm.aws_acm_certificate_validation.main, after which you can remove the provider configuration
│ again.
╵
ルールにfilter
かprefix
を指定してとの警告: 記載して解消
AssumeRole出来ないとのエラー: DNS管理アカウントのロールを後から追加したので、踏み台ロールの許可ポリシーにそのロールが記載漏れ。
またprdアカウントからDNS操作すると勘違いし、そのロールをDNS管理ロールで信頼されたエンティティで許可していた。踏み台ロールからに修正。
╷
│ Warning: Invalid Attribute Combination
│
│ with module.s3_website.aws_s3_bucket_lifecycle_configuration.main,
│ on ../../../modules/s3-private-bucket/main.tf line 15, in resource "aws_s3_bucket_lifecycle_configuration" "main":
│ 15: resource "aws_s3_bucket_lifecycle_configuration" "main" {
│
│ No attribute specified when one (and only one) of [rule[0].filter,rule[0].prefix] is required
│
│ This will be an error in a future version of the provider
│
│ (and one more similar warning elsewhere)
╵
╷
│ Error: Cannot assume IAM Role
│
│ with provider["registry.terraform.io/hashicorp/aws"].dns_master,
│ on providers.tf line 21, in provider "aws":
│ 21: provider "aws" {
│
│ IAM Role (arn:aws:iam::<DNS管理アカウントID>:role/Route53CrossAccountManagerRole) cannot be assumed.
│
│ There are a number of possible causes of this - the most common are:
│ * The credentials used in order to assume the role are invalid
│ * The credentials do not have appropriate permission to assume the role
│ * The role ARN is not valid
│
│ Error: operation error STS: AssumeRole, https response error StatusCode: 403, RequestID:
│ 16bf10ef-bbf3-440c-b74a-069d806b5e4b, api error AccessDenied: User:
│ arn:aws:sts::<踏み台アカウントID>:assumed-role/AWSReservedSSO_0603game-TerraformOperator_c6f75dbe8448194d/tano-sso-user
│ is not authorized to perform: sts:AssumeRole on resource:
│ arn:aws:iam::<DNS管理アカウントID>:role/Route53CrossAccountManagerRole
│
╵
Route 53を操作する権限が足りなくてエラー: 想定の権限では、足りなかった。権限を増やすことにより解消。
╷
│ Error: reading Route53 Hosted Zone (<ホストゾーンID>): operation error Route 53: GetHostedZone, https response error StatusCode: 403, RequestID: 055a21ec-360d-406d-8e26-f2aa7b2dbaa9, api error AccessDenied: User: arn:aws:sts::<DNS管理アカウントID>:assumed-role/Route53CrossAccountManagerRole/tf-session-dns-cross-account is not authorized to perform: route53:GetHostedZone on resource: arn:aws:route53:::hostedzone/<ホストゾーンID> because no identity-based policy allows the route53:GetHostedZone action
│
│ with module.acm.data.aws_route53_zone.main,
│ on ../../../modules/acm-certificate/main.tf line 2, in data "aws_route53_zone" "main":
│ 2: data "aws_route53_zone" "main" {
│
╵
CNAME名がキーだとリソースが作成されないと確定出来ずエラー: キーをドメイン名に変更で解消
╷
│ Error: Invalid for_each argument
│
│ on ../../../modules/acm-certificate/main.tf line 22, in resource "aws_route53_record" "validation":
│ 22: for_each = {
│ 23: for dvo in aws_acm_certificate.main.domain_validation_options : dvo.resource_record_name => {
│ 24: name = dvo.resource_record_name
│ 25: record = dvo.resource_record_value
│ 26: type = dvo.resource_record_type
│ 27: }
│ 28: }
│ ├────────────────
│ │ aws_acm_certificate.main.domain_validation_options is set of object with 2 elements
│
│ The "for_each" map includes keys derived from resource attributes that cannot be determined until apply, and so Terraform cannot determine the
│ full set of keys that will identify the instances of this resource.
│
│ When working with unknown values in for_each, it's better to define the map keys statically in your configuration and place apply-time results
│ only in the map values.
│
│ Alternatively, you could use the -target planning option to first apply only the resources that the for_each value depends on, and then apply
│ a second time to fully converge.
╵
terraform apply
リソースを作成する
Gitを忘れずに
terraform apply
したらstateファイルが変更されると思ったら、それはローカルバックエンド設定をしてたからだったので、リモートバックエンドならGit操作しなくても大丈夫。
フェーズ 3: CloudFrontディストリビューションのデプロイ
modules/cloudfront-oacの作成
modules/cloudfront-oac/variables.tf
呼び出し元から変数の値を受け取る変数を定義する
tano1:cloudfront-oac tano$ cat variables.tf
# infra/modules/cloudfront-oac/variables.tf
variable "s3_origin_domain_name" {
description = "オリジンとなるS3バケットのリージョナルドメイン名"
type = string
}
variable "s3_bucket_id" {
description = "S3バケットID(バケット名)"
type = string
}
variable "acm_certificate_arn" {
description = "使用するACM証明書のARN (us-east-1で作成されたもの)"
type = string
}
variable "domain_aliases" {
description = "ディストリビューションに設定するドメイン名のリスト (CNAME)"
type = list(string)
default = []
}
variable "tags" {
description = "リソースに適用するタグ"
type = map(string)
default = {}
}
tano1:cloudfront-oac tano$
modules/cloudfront-oac/main.tf
aws_cloudfront_origin_access_control
signing_behavior = "always": 署名リクエスト
signing_protocol = "sigv4": 署名方式
default_root_object = "index.html": ルートURLアクセス時に表示するファイル
aliases = var.domain_aliases: ドメイン設定
origin_access_control_id = aws_cloudfront_origin_access_control.main.id: ディストリビューションとOACを結ぶ設定
allowed_methods = ["GET", "HEAD", "OPTIONS"]: 許可されたHTTPメソッド設定
cached_methods = ["GET", "HEAD"]: キャッシュするメソッド設定
target_origin_id = "S3-${var.s3_origin_domain_name}": ターゲットのオリジン
viewer_protocol_policy = "redirect-to-https": HTTPはHTTPSへリダイレクトの設定
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6: AWSが推奨する一般的なキャッシュポリシー。キャッシュ最適化。
tano1:cloudfront-oac tano$ cat main.tf
# S3オリジン用のOrigin Access Control (OAC) を作成
# これがCloudFrontからS3へ安全にアクセスするための「鍵」の役割を果たします
resource "aws_cloudfront_origin_access_control" "main" {
name = "oac-${var.s3_bucket_id}"
description = "OAC for S3 bucket"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
# CloudFrontディストリビューションを作成
resource "aws_cloudfront_distribution" "main" {
enabled = true
default_root_object = "index.html" # ルートURLへのアクセス時に表示するファイル
aliases = var.domain_aliases # "tanoyuusuke.com" などを設定
is_ipv6_enabled = true # 追記2025/08/20
# オリジン(配信元)の設定
origin {
domain_name = var.s3_origin_domain_name
origin_id = "S3-${var.s3_origin_domain_name}" # オリジンのユニークなID
origin_access_control_id = aws_cloudfront_origin_access_control.main.id
}
# デフォルトのキャッシュ動作設定
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${var.s3_origin_domain_name}"
viewer_protocol_policy = "redirect-to-https" # HTTPアクセスはHTTPSにリダイレクト
compress = true # コンテンツを自動で圧縮して配信を高速化
# AWSが推奨する一般的なキャッシュ最適化ポリシーを利用
cache_policy_id = "658327ea-f89d-4fab-a63d-7e88639e58f6" # CachingOptimized
}
ssl_support_method = "sni-only": レガシーなブラウザは非対応
minimum_protocol_version = "TLSv1.2_2021": 一番推奨なHTTPS接続に使用するTLSプロトコル
restrictions: 制限設定の開始
geo_restriction: 地理的制限
restriction_type = "none": 地理的制限無し
price_class = "PriceClass_All": 料金によって配信出来る場所が変わる
# SSL/TLS証明書の設定
viewer_certificate {
acm_certificate_arn = var.acm_certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
# ← ここに追加(トップレベル)
restrictions {
geo_restriction {
restriction_type = "none" # 地理制限なし。whitelist/blacklist も選べる
locations = [] # none の場合は空配列
}
}
# 価格クラス (日本を含むアジア、北米、欧州に限定してコストを最適化)
price_class = "PriceClass_All"
tags = var.tags
}
tano1:cloudfront-oac tano$
modules/cloudfront-oac/outputs.tf
呼び出し元が参照する情報を出力する
tano1:cloudfront-oac tano$ cat outputs.tf
output "distribution_id" {
description = "CloudFrontディストリビューションのID"
value = aws_cloudfront_distribution.main.id
}
output "distribution_arn" {
description = "CloudFrontディストリビューションのARN"
value = aws_cloudfront_distribution.main.arn
}
output "domain_name" {
description = "CloudFrontディストリビューションのドメイン名"
value = aws_cloudfront_distribution.main.domain_name
}
output "hosted_zone_id" {
description = "CloudFrontディストリビューションのRoute53ホストゾーンID"
value = aws_cloudfront_distribution.main.hosted_zone_id
}
tano1:cloudfront-oac tano$
司令塔のprd/main.tfの作成
CloudFrontディストリビューションを作成
前回:パブリックアクセスブロックのS3バケットと、ACM証明書周りのドメイン設定をまずは設定
acm_certificate_arn = module.acm.certificate_arn: ACMのモジュールの出力を渡す。SSL/TLS証明書の設定で使用
s3_origin_domain_name = module.s3_website.bucket_regional_domain_name: OCAのNAMEに使用。オリジンの名前とIDで使用。キャッシュ動作の設定でターゲットオリジンに使用。
domain_aliases: 代替ドメイン名で使用。
prd/main.tf
# --- 以下を追記 ---
module "cloudfront_cdn" {
source = "../../../modules/cloudfront-oac"
# ACMモジュールの出力を入力として渡す
acm_certificate_arn = module.acm.certificate_arn
# S3モジュールの出力を入力として渡す
s3_origin_domain_name = module.s3_website.bucket_regional_domain_name
# ⭐ bucket_id を使用(既存の出力をそのまま使える!)
s3_bucket_id = module.s3_website.bucket_id
domain_aliases = ["tanoyuusuke.com", "www.tanoyuusuke.com"]
tags = var.common_tags
}tano1:prd tano$
prd/outputs.tf
# --- 以下を追記 ---
output "cloudfront_distribution_domain_name" {
description = "CloudFrontディストリビューションのドメイン名"
value = module.cloudfront_cdn.domain_name
}
tano1:prd tano$
自分が書き換える箇所は、tfvarsのみの方が良いなって思って固定名が書かれてる箇所を訂正
何が正解か分からないけど、訂正しました。
# infra/static-website/environments/prd/variables.tf
+ variable "project" {
+ description = "プロジェクト名"
+ type = string
+ }
+
+ variable "environment" {
+ description = "環境名(dev/stg/prd)"
+ type = string
+ }
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
# infra/static-website/environments/prd/terraform.tfvars
+ project = "0603game"
+ environment = "prd"
domain_name = "tanoyuusuke.com"
aws_account_id = "002540791269"
common_tags = {
Project = "0603game"
Environment = "prd"
ManagedBy = "Terraform"
+ Owner = "tano"
}
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
# infra/static-website/environments/prd/main.tf
# S3バケットモジュールを呼び出し
module "s3_website" {
source = "../../../modules/s3-private-bucket"
+ bucket_name = "bucket-${var.project}-${var.environment}-${var.aws_account_id}"
- bucket_name = "tano-0603game-bucket-${var.aws_account_id}"
tags = var.common_tags
}
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
# infra/static-website/environments/prd/main.tf
# S3モジュールの出力を入力として渡す
s3_origin_domain_name = module.s3_website.bucket_regional_domain_name
+ domain_aliases = ["${var.domain_name}", "www.${var.domain_name}"]
- domain_aliases = ["tanoyuusuke.com", "www.tanoyuusuke.com"]
tags = var.common_tags
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
Terraform実行
terraform init
モジュールを新しく作成したら読み込まれてないので、読み込むためにterraform init
を実行
terraform plan
モジュールを取得出来ていなくてエラー。terraform init
が必要。
tano1:prd tano$ terraform plan
╷
│ Error: Module not installed
│
│ on main.tf line 28:
│ 28: module "cloudfront_cdn" {
│
│ This module is not yet installed. Run "terraform init" to install all modules required by this configuration.
terraform apply
OACの名前が長いのでエラー。名前をバケット名にすることで解消。
したと思ったがバケットidの方がoutputsしていて変更が少ないのでそちらにすることに。
モジュールのvariablesでbucket.idを定義し、mainでnameをidに。prd/mainでbucket.idをモジュールで渡す設定に修正。
╷
│ Error: expected length of name to be in the range (1 - 64), got oac-for-bucket-0603game-prd-002540791269.s3.ap-northeast-1.amazonaws.com
│
│ with module.cloudfront_cdn.aws_cloudfront_origin_access_control.main,
│ on ../../../modules/cloudfront-oac/main.tf line 4, in resource "aws_cloudfront_origin_access_control" "main":
│ 4: name = "oac-for-${var.s3_origin_domain_name}"
│
╵
tano1:prd tano$
指定したaliases
がすでに別のリソースに関連付いてるためエラー。
前回ブログで書いたときのドメインをそのまま使ってたので、そちらを削除。
╷
│ Error: creating CloudFront Distribution: operation error CloudFront: CreateDistributionWithTags, https response error StatusCode: 409, RequestID: d249b6cd-fbc9-49df-bda5-d9cb700fcdf2, CNAMEAlreadyExists: One or more of the CNAMEs you provided are already associated with a different resource.
│
│ with module.cloudfront_cdn.aws_cloudfront_distribution.main,
│ on ../../../modules/cloudfront-oac/main.tf line 12, in resource "aws_cloudfront_distribution" "main":
│ 12: resource "aws_cloudfront_distribution" "main" {
│
╵
tano1:prd tano$
フェーズ 4: 最終接続と本番リリース
route53-recordsモジュールのコード作成
modules/route53-records/variables.tf
variable "records": DNSレコードを受け取るための入力変数の定義。配列として複数のレコードを受け取り、各要素はオブジェクトで1レコード分を表す。
optional: 値がモジュール呼び出し側で指定されなくてもエラーを返さず、デフォルトで入力される。これはalias
の時は入力無しになるため。モジュール側では、alias
の時は条件分岐で無視するようになっている。
}tano1:route53-records tano$ cat variables.tf
variable "zone_name" {
description = "対象のRoute 53ホストゾーン名"
type = string
}
variable "records" {
description = "作成するDNSレコードのリスト"
type = list(object({
name = string
type = string
ttl = optional(number, 300)
content = optional(list(string), [])
alias = optional(object({
name = string
zone_id = string
}), null)
}))
default = []
}
tano1:route53-records tano$
modules/route53-records/main.tf
for_each = { for record in var.records : "
dynamic: ブロックを作る、作らない、複数作るを制御する機能。
for_each = each.value.alias != null ? [each.value.alias] : []:
each.value.alias != null: aliasがnullでは無い。がtrue。そうでは無い(aliasがnull)がfalse
? [each.value.alias] : []: ?は真偽式で、trueなら'[each.value.alias]'を返す。falseなら[]を返す。
trueなら'[each.value.alias]'を返す。: contentを記述してるので、要素の数だけcontentが実行される。その返されたものをresource "aws_route53_recordのalias"
として設定される。
ttl = each.value.alias == null ? each.value.ttl : null: 設定されてるalias
がnull
ならtrue、そうじゃないならfalse。つまりaliasが設定されてるならnullとして値を設定しない。
ホストゾーンを参照して、レコードを複数作成する。
そのために、レコード名とレコードタイプをキーとして被りが起きないように繰り返し設定を入れる。
その繰り返し設定の中にalias
をするかどうかの分岐も入れていてそれをdynamicとfor_eachで実現している。
tano1:route53-records tano$ cat main.tf
# ホストゾーン参照。プロバイダーは専用のを用意。
data "aws_route53_zone" "main" {
provider = aws.dns_master
name = var.zone_name
}
# レコード名-レコードタイプをキーとしたレコード情報を繰り返しマップ作成し、その情報でキーの数だけレコードを作成。
resource "aws_route53_record" "main" {
provider = aws.dns_master
# キーを name と type の組み合わせにして、必ずユニークになるように修正
for_each = { for record in var.records : "${record.name}-${record.type}" => record }
zone_id = data.aws_route53_zone.main.zone_id
name = each.value.name
type = each.value.type
dynamic "alias" {
for_each = each.value.alias != null ? [each.value.alias] : []
content {
name = alias.value.name
zone_id = alias.value.zone_id
evaluate_target_health = false
}
}
ttl = each.value.alias == null ? each.value.ttl : null
records = each.value.alias == null ? each.value.content : null
}tano1:route53-records tano$
# versions.tf
プロバイダーaliasをモジュール側で定義する
tano1:route53-records tano$ cat versions.tf
# versions.tf(宣言あり)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
configuration_aliases = [aws.dns_master]
}
}
}
tano1:route53-records tano$
prd/main.tf) の更新
Version = "2012-10-17": AWSのポリシー言語バージョン、他に2008-10-17
が存在してて旧版となっている。
Effect = "Allow": このルールは許可
Principal: 誰に。CloudFrontに
Action = "s3:GetObject": 何を。s3のオブジェクト読み取り権限
Resource: どこの?S3BucketARN。(S3BucketARNのs3:GetObjectって意味)
Condition: 条件
StringEquals: AWS:SourceArn
が指定した文字列が完全に一致するかどうか
CloudFrontに対して。AWS:SourceArn
が指定した文字列が完全に一致したディストリビューションのみ特定のS3BucketARNのs3:GetObjectを許可します。
# 4. S3バケットポリシーの設定
# CloudFrontが作成された後に、その情報を使ってポリシーを適用
resource "aws_s3_bucket_policy" "website_bucket_policy" {
bucket = module.s3_website.bucket_id
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Principal = { Service = "cloudfront.amazonaws.com" },
Action = "s3:GetObject",
Resource = "${module.s3_website.bucket_arn}/*",
Condition = {
StringEquals = {
# CloudFrontモジュールのARNを参照してアクセスを制限
"AWS:SourceArn" = module.cloudfront_cdn.distribution_arn
}
}
}
]
})
}
records: モジュール側のvariablesで定義されている変数。
# 5. Route 53 レコードの作成 (本番リリース)
module "dns" {
source = "../../../modules/route53-records"
providers = {
aws.dns_master = aws.dns_master
}
zone_name = var.domain_name
records = [
# --- Aレコード (IPv4) ---
{
name = var.domain_name
type = "A"
alias = {
name = module.cloudfront_cdn.domain_name
zone_id = module.cloudfront_cdn.hosted_zone_id
}
},
{
name = "www.${var.domain_name}"
type = "A"
alias = {
name = module.cloudfront_cdn.domain_name
zone_id = module.cloudfront_cdn.hosted_zone_id
}
},
# --- AAAAレコード (IPv6) ---
{
name = var.domain_name
type = "AAAA"
alias = {
name = module.cloudfront_cdn.domain_name
zone_id = module.cloudfront_cdn.hosted_zone_id
}
},
{
name = "www.${var.domain_name}"
type = "AAAA"
alias = {
name = module.cloudfront_cdn.domain_name
zone_id = module.cloudfront_cdn.hosted_zone_id
}
}
]
tano1:prd tano$
Terraform 実行
terraform init
alias
のモジュール側に定義がなかったため警告。versions.tf
を追加し解消。
│ Warning: Reference to undefined provider
│
│ on main.tf line 76, in module "dns":
│ 76: aws.dns_master = aws.dns_master
│
│ There is no explicit declaration for local provider name "aws.dns_master" in module.dns, so Terraform is assuming
│ you mean to pass a configuration for "hashicorp/aws".
│
│ If you also control the child module, add a required_providers entry named "aws.dns_master" with the source
│ address "hashicorp/aws".
│
│ (and one more similar warning elsewhere)
terraform plan
問題無し
terraform apply
Route53にすでにレコード名と一致するものが存在するエラー。前回ブログで作成してたものが残ってたので削除して解消。
╷
│ Error: creating Route53 Record: operation error Route 53: ChangeResourceRecordSets, https response error StatusCode: 400, RequestID: b7032817-50c1-4aec-b00a-70a2999b355d, InvalidChangeBatch: [Tried to create resource record set [name='www.tanoyuusuke.com.', type='A'] but it already exists]
│
│ with module.dns.aws_route53_record.main["www.tanoyuusuke.com-A"],
│ on ../../../modules/route53-records/main.tf line 6, in resource "aws_route53_record" "main":
│ 6: resource "aws_route53_record" "main" {
│
╵
╷
│ Error: creating Route53 Record: operation error Route 53: ChangeResourceRecordSets, https response error StatusCode: 400, RequestID: 9adc302d-75d3-45d5-9b60-237fac79744a, InvalidChangeBatch: [Tried to create resource record set [name='tanoyuusuke.com.', type='A'] but it already exists]
│
│ with module.dns.aws_route53_record.main["tanoyuusuke.com-A"],
│ on ../../../modules/route53-records/main.tf line 6, in resource "aws_route53_record" "main":
│ 6: resource "aws_route53_record" "main" {
│
╵
tano1:prd tano$
git操作(ファイル編集終わり)
一区切りのファイル編集が終わったので、その編集した変更をリモートのメインブランチへ反映させるため操作を行う
$ git status #現在のgit状況を確認
$ git branch #現在のブランチの状況を確認
$ cd <プロジェクトのルートディレクトリ>
$ git add . #gitの変更をステージングにあげる
$ git status #現在のgit状況を確認
$ git commit -m "prod-static-website-hosting" #gitの変更をローカルブランチへ適用させる
$ git push origin feature/prod-static-website-hosting #変更をリモートのブランチへ適用させる
# ここからGithubなどのリモート側の操作
# リモートでmainブランチとfeature/prod-static-website-hostingブランチがあるので、変更をmainブランチへ反映させるためにプルリクエストを行う
# プルリクエストを受けた側(自学習なら自分)は変更差分を見て問題無いか確認して、問題なければマージしてfeature/prod-static-website-hostingブランチを削除する
# Githubなどのリモート側の操作はここまで
$ git branch #現在のブランチの状況を確認
$ git checkout main #最新のブランチに変更。リモートではもうブランチが削除されてる
$ git branch #現在のブランチの状況を確認
$ git pull origin main # リモートで反映させた変更が、ローカルにも反映される
$ git branch -d feature/prod-static-website-hosting #不要になったブランチを削除する
$ git branch #現在のブランチの状況を確認
構成図起こし
awslabs/diagram-as-code
github
インフラ構成図をコードで書くけどもなかなか簡単には難しい。。。
AWS構成図作成の悩みを一掃!Diagram as Codeで始める“コードで描く”インフラ設計
[ブースレポート]AWS構成図とAWS CloudFormationテンプレートを自動生成!Diagram-as-Codeのデモをブースで触ってみた #AWSSummit
Discussion