運用エンジニアが Cloud Resume Challenge(AWS)で職務経歴サイトを作り、Terraform で既存リソースをコード化するまで
はじめに
運用保守の仕事(Windows Server/VMware、Linux・AWS 環境の障害一次対応)を約 2 年半続けてきましたが、AWS を「自分で構築した」経験がありませんでした。そこで Cloud Resume Challenge の AWS 版に取り組み、日英バイリンガルの職務経歴サイトを作りました。
この記事では、構成と工夫した点に加えて、新しい形態の AWS アカウントでつまずいたことと、コンソールで作ったリソースを Terraform に取り込んだら過剰権限が見つかった話をまとめます。
- サイト:https://d23dnziyuzdous.cloudfront.net
- リポジトリ:https://github.com/wangyuanpei8-del/cloud-resume-challenge
生成 AI を学習の補助に使いましたが、設定・実装・デプロイは自分で行っています。
構成
ブラウザ ──HTTPS──▶ CloudFront ──OAC──▶ S3(非公開バケット)
│
└─ counter.js ──▶ API Gateway (HTTP API, GET /count)
▼
Lambda (Python 3.14) ──▶ DynamoDB(アクセス数)
| サービス | 役割 |
|---|---|
| S3 + CloudFront(OAC) | 静的サイト配信。バケットは公開せず、CloudFront 経由のみ |
| API Gateway(HTTP API) |
GET /count。CORS はサイトのオリジンだけ許可 |
| Lambda + DynamoDB | アクセス数を ADD で原子的に 1 増やして返す |
| GitHub Actions |
frontend/ は S3 同期 + キャッシュ無効化、backend/ はテスト成功後に Lambda 更新 |
| Terraform | バックエンドの既存リソースを import してコード管理 |
工夫した点
S3 を公開しない
静的ウェブサイトホスティングは使わず、CloudFront の Origin Access Control(OAC)経由でのみ配信しています。
※ 推奨設定で作ると「デフォルトルートオブジェクト」が空のままで、/ にアクセスすると AccessDenied になりました。index.html を設定して解決。
アトミックカウンター
「読む → +1 → 書く」だと同時アクセスで値が飛ぶので、DynamoDB の更新式 ADD を使っています。count は予約語なので ExpressionAttributeNames で回避しました。
response = table.update_item(
Key={"id": "visitors"},
UpdateExpression="ADD #c :inc",
ExpressionAttributeNames={"#c": "count"},
ExpressionAttributeValues={":inc": 1},
ReturnValues="UPDATED_NEW",
)
テストが通ったときだけデプロイ
pytest + moto で DynamoDB をモックし、4 項目(応答形式、初回値、連続加算、保存結果)を確認。全部成功したときだけ aws lambda update-function-code を実行します。
つまずいたこと:新しい形態の AWS アカウント
今回使ったのは、新しいサインアップ方式で作ったアカウント(無料プラン、プロジェクト単位で管理される形態)です。通常のアカウントと違う制約があり、古い記事の手順がそのままでは通りませんでした。
| 制約 | 影響と対応 |
|---|---|
| ルートユーザーがない/IAM ユーザーでコンソールにログインできない | IAM ユーザーはプログラム用(アクセスキー)のみ |
| リージョンが固定(シドニー) | 東京リージョンは使えない |
SCP で iam:*Provider* が拒否される |
GitHub Actions の OIDC 連携が作れない → 最小権限の IAM ユーザー+アクセスキーを GitHub Secrets に保存(定期ローテーションが課題) |
| CloudShell が「アカウント確認中」で起動しない | Terraform は GitHub Codespaces で実行 |
OIDC が使えないのは悔しいですが、「なぜアクセスキーを選んだか」「そのリスクにどう対処しているか」を説明できるようにしました。
Terraform:作り直さずに import する
バックエンドはコンソールで手作業で作っていたので、Terraform 1.5 以降の import ブロックと -generate-config-out で既存リソースをコードに起こしました。
環境と権限
- 実行場所:GitHub Codespaces(ブラウザ上の Linux)
- 認証:ReadOnlyAccess + state バケットへの書き込みだけを許可した専用 IAM ユーザー。import と plan は読み取りだけで済むため
- state:専用の S3 バケット(バージョニング有効、
use_lockfile = trueで S3 ネイティブロック)
state をサイト用バケットに置かなかったのは、デプロイで aws s3 sync --delete を使っているからです。同じバケットに置くと、次のデプロイで state が消えます。
手順
# imports.tf(抜粋)
import {
to = aws_dynamodb_table.visitor_count
id = "visitor-count"
}
import {
to = aws_apigatewayv2_stage.default
id = "<api-id>/$default"
}
terraform init
terraform plan -generate-config-out=generated.tf
自動生成されたコードでは 2 つのエラーが出ました。
-
Lambda に
filenameがない:AWS からコードの zip 自体は取得できないため。コードは GitHub Actions がデプロイするので、Terraform は設定だけを管理するよう override ファイルで変更を無視しました。
# lambda_override.tf
resource "aws_lambda_function" "visitor_counter" {
filename = "lambda.zip"
lifecycle {
ignore_changes = [filename, source_code_hash]
}
}
-
DynamoDB の
recovery_period_in_days = 0:PITR を無効にしているテーブルで 0 が出力されるが、Terraform 側の許容範囲は 1〜35。該当行を削除して解決。
その後 terraform plan で
Plan: 9 to import, 0 to add, 0 to change, 0 to destroy.
となり、コードと実環境の差分ゼロを確認してから apply しました。
import で見つかった過剰権限
生成されたコードを読んで気づいたのが、Lambda の実行ロールに PowerUserAccess(IAM 以外のほぼ全操作)が付いていたことです。カウンターに必要なのは DynamoDB の UpdateItem とログ出力だけです。
そこで Terraform で専用ロールを作って置き換えました。
resource "aws_iam_role_policy" "visitor_counter_dynamodb" {
name = "visitor-count-update"
role = aws_iam_role.visitor_counter.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "dynamodb:UpdateItem"
Resource = aws_dynamodb_table.visitor_count.arn
}]
})
}
# 元のロールは削除せず、Terraform の管理対象から外すだけ
removed {
from = aws_iam_role.lambda_role
lifecycle {
destroy = false
}
}
- 変更は PR で出し、plan(
3 to add, 1 to change, 0 to destroy)を確認してから apply - apply のときだけ、対象のロールと Lambda に絞った書き込み権限を一時的に付与し、終わったら外しました
- apply 後に API を叩いてカウンターが動くことを確認
コンソールで作ったものは「何が設定されているか」が見えにくく、コード化して初めて気づけた問題でした。
まとめ・今後
- 運用で見ていた CloudWatch Logs、Lambda、API Gateway を自分で作ることで、「どこを見れば何が分かるか」の解像度が上がりました
- Terraform は新規構築より、既存環境の import → 差分ゼロ確認 → 安全に修正の流れが実務に近いと感じました
今後は CloudWatch アラームによる監視、S3・CloudFront の Terraform 化、AWS SAA の取得に取り組みます。