はじめに
想定している読者は次のような方です。
- VPCやEC2等の設定、構築をした事がある方。
- コンテナは触ったことがあるが、ECS はこれからという方。
- ECS Fargateを使用して、ECSの基礎知識を確認しておきたい方。
- 昔ECSを使ったが久しぶりのため、改めて作業方法の再確認をしたい方、復習をして知識を維持したい方。
前回について
ALB + ECS Fargate + ECR の基本構成から、CodePipeline、ECS ネイティブ Blue/Green、GitHub Actions までを段階的に作りました。ただし連載では学習のしやすさを優先し、タスクをパブリックサブネットに置いてパブリック IP を付ける構成にしていました。
応用編の今回は、本番でそのまま使える形に近づけます。
- タスクはプライベートサブネットに置き、パブリックIPも NAT ゲートウェイも使わない
- ECRなどのAWS サービスにはVPC エンドポイント経由でアクセスする
- RDS for PostgreSQLに接続し、アプリはパスワードを一切持たない(IAMデータベース認証)
- GitHub Actionsでビルドし、コミットIDを変数としてCodePipelineに渡してデプロイする
- ECRのタグはイミュータブル(上書き不可)にする
- ECSネイティブBlue/Green で、テストリスナーで確認 → 人が承認 → ベイク時間中も人が監視
この記事だけで完結するよう、VPCからゼロで作ります。ECSやBlue/Greenの基本的な説明についてはこれまでの各回やAWSマニュアルをご参照ください。
| 第何回 | テーマ | ゴール |
| 第1回 | コンテナと ECS の基礎、手動構築 | ブラウザに「Hello from Amazon ECS」 が表示される |
| 第2回 | 手動リリースと CodePipeline による自動リリース | ECR への push でローリングアップデート |
| 第3回 | ECS ネイティブ Blue/Green デプロイ | テストリスナーで確認してから本番切り替え |
| 第4回 | GitHub Actions で ECR への push を自動化 |
“git push”だけでリリースが完走する |
| 第5回 | プライベートサブネットを使用した構成 |
プライベートサブネットとエンドポイントを利用した環境構築の検証をする |
本記事について
- 本記事ではALBやFargateなどの有料サービスを使用するため、費用が発生します。ALBとFargateは起動している時間に応じた課金のため、数時間で完了すれば数USドル程度ですみますが、削除し忘れて1か月放置すると40USドル程度になります。検証後は必ずリソースの削除をお願いします(手順は記事末尾に記載しています)。
- AWSマネジメントコンソールの画面構成は、随時更新されます。本記事は2026年9月時点の画面をもとにしています。項目名や配置が異なる場合は、適宜読み替えてください。
- 掲載している手順は、実際にAWS上で検証した内容です。ただし、学習しやすさを優先してセキュリティ設定を簡略化している箇所があります(タスクをパブリックサブネットに配置する、セキュリティグループで広い範囲からのアクセスを許可する、など)。そのため、検証用のAWSアカウントでの実施を推奨します。
- 本記事では、GitHubとAWSを連携させる設定(OIDCのIDプロバイダー、GitHubアプリの連携など)を行います。これらは他のプロジェクトと共有される設定のため、検証後に削除する際はご注意ください。同じアカウントやGitHub組織で他にも利用しているものがあると、そちらの連携まで止まってしまう可能性があります。削除対象かどうか分からない場合は、無理に消さずに残しておくほうが安全です(残しておいても費用は発生しません)。
- 共用アカウントや業務で使用しているアカウントで実施する場合は、組織のルールを確認したうえで設定内容を見直してから、本記事を参考にしたり、手順の実施をしてください。
今回の構成

前提と準備
環境
| 項目 | 設定値 |
| リージョン | ap-northeast-1 |
| 起動タイプ | AWS Fargate |
| 作業方法 | AWS マネジメントコンソール + GitHub の Web 画面 |
| 所要時間 | 約3〜4時間 |
検証用のAWS アカウントと、GitHubアカウント(リポジトリを1つ作成します)を用意してください。
本記事の構成料金目安
以下は2026年9月時点の東京リージョンでのおおよその目安です。正確な金額は必ず各サービスの公式料金ページで確認してください。
| リソース | 概算 |
| Interface エンドポイント 4 種 × 2 AZ | 約 0.11 USD / 時間(1 本 1 AZ あたり約 0.014 USD / 時間) |
| Application Load Balancer | 約 0.025 USD / 時間 + LCU 従量 |
| RDS for PostgreSQL db.t4g.micro(シングル AZ) | 約 0.03 USD / 時間 + ストレージ |
| Fargate 0.25 vCPU / 0.5 GB × 2 | 約 0.025 USD / 時間 |
| Secrets Manager | 1シークレットあたり月 0.40 USD(日割り) |
| S3 Gateway エンドポイント | 無料 |
合計すると1時間あたり0.2USD前後、1日放置すると5USD前後になります。本構成を試す場合、途中で数日空けるのはおすすめしません。1日で最後まで進めて、その日のうちに後片付け(後半にあるクリーンアップの章)まで実施していただきますようお願いいたします。
1.VPCを作る
1-1. VPC ウィザードで作成する
VPC コンソール →「VPCを作成」。以下の内容で設定します。
| 項目 | 設定値 |
| 作成するリソース | VPCなど |
| 名前タグの自動生成 | ecs-private |
| IPv4 CIDR ブロック | 10.0.0.0/16 |
| アベイラビリティーゾーン (AZ) の数 | 2 |
| パブリックサブネットの数 | 2 |
| プライベートサブネットの数 | 2 |
| NAT ゲートウェイ | なし |
| VPC エンドポイント | S3ゲートウェイ |
| DNS ホスト名を有効化 | チェックをつける |
| DNS 解決を有効化 | チェックをつける |
2.セキュリティグループを作る
今回の構成概要です。

EC2 コンソール →「セキュリティグループ」→「セキュリティグループを作成」で、次の4つを上から順に作ります(後ろの SGが前のSGを参照するため)。VPCはすべて事前に作成したVPC(今回の名称は、ecs-private-vpc)を選びます。アウトバウンドルールはデフォルト(すべて許可)のままで構いません。
2-1.ALB用
セキュリティグループ名:ecs-private-sg-alb
| タイプ | ポート | ソース | 説明 |
| HTTP | 80 | 0.0.0.0/0 | Listener for production environment |
| カスタムTCP | 8080 |
マイIP |
Listener for test environment |
2-2.ECSタスク用
セキュリティグループ名:ecs-private-sg-app
| タイプ | ポート | ソース | 説明 |
| カスタムTCP | 8000 | ecs-private-sg-alb | from ALB |
8000 と 8080 を取り違えないように:8000 はアプリ(gunicorn)が待ち受けるポートで、ALB → タスクの通信に使います。8080 は ALB のテストリスナーのポートで、インターネット → ALB の通信に使います。ここを 8080 にしてしまうと、ALB のヘルスチェックが失敗します。
2-3.RDS用
セキュリティグループ名:ecs-private-sg-db
| タイプ | ポート | ソース | 説明 |
| PostgreSQL | 5432 | ecs-private-sg-app | from ECS task |
2-4.VPCエンドポイント用
セキュリティグループ名:ecs-private-sg-vpce
| タイプ | ポート | ソース | 説明 |
| HTTPS | 443 | ecs-private-sg-app | from ECS task |
3.VPCエンドポイントを作る
3-1.作成エンドポイント
| エンドポイント | 種類 | 使用用途 |
| com.amazonaws.ap-northeast-1.ecr.api | Interface | ECR の認証、イメージ情報の取得 |
| com.amazonaws.ap-northeast-1.ecr.dkr | Interface | イメージの pull(Docker Registry API) |
| com.amazonaws.ap-northeast-1.s3 | Gateway |
イメージのレイヤー本体の取得(ECRの実体は S3) |
| com.amazonaws.ap-northeast-1.logs | Interface | CloudWatch Logsへのログ送信 |
| com.amazonaws.ap-northeast-1.secretsmanager | Interface | タスク起動時のシークレット取得 |
ECR のイメージは、メタデータは ECR、レイヤーの実体は S3 に置かれています。ecr.apiとecr.dkrだけ作ってS3を忘れると、認証は通るのにレイヤーのダウンロードでタイムアウトしCannotPullContainerErrorになります。
今回は、RDSのIAM 認証用のエンドポイントは不要です。アプリが RDS に接続するときに使う認証トークンは、SDKが手元で署名計算をして作るもので、AWSのAPIは呼びません。また、タスクが IAM ロールの認証情報を受け取る仕組み(タスクメタデータエンドポイント)も VPC の外には出ません。
3-2.Interfaceエンドポイントを作る
VPC コンソール →「エンドポイント」→「エンドポイントを作成」
以下の内容で設定します。
| 項目 | 設定値 |
| 名前タグ | ecs-private-vpce-ecr-api |
| タイプ | AWS のサービス |
| サービス | com.amazonaws.ap-northeast-1.ecr.api |
| VPC | ecs-private-vpc |
| 追加設定 → プライベートDNS名を有効化 |
チェックがついていること(デフォルト設定) |
| サブネット |
2つのAZで、それぞれプライベートサブネット(ecs-private-subnet-private1-…/ecs-private-subnet-private2-…)を選択 |
| セキュリティグループ | ecs-private-sg-vpce(defaultは使用しないため、チェックは外します。) |
| ポリシー | フルアクセス |
同様に、次の3つも名前タグとサービス名以外は同じ設定で作成します。
| 名前タグ | サービス |
| ecs-private-vpce-ecr-dkr | com.amazonaws.ap-northeast-1.ecr.dkr |
| ecs-private-vpce-logs | com.amazonaws.ap-northeast-1.logs |
| ecs-private-vpce-secretsmanager | com.amazonaws.ap-northeast-1.secretsmanager |
合計4つ作成し、ステータスが使用可能になることを確認してください。

4.RDS for PostgreSQLを作る
4-1. DB サブネットグループを作る
RDS コンソール →「サブネットグループ」→「DB サブネットグループを作成」
| 項目 | 設定値 |
| 名前 | ecs-private-db-subnet |
| 説明 | private subnets for ecs-private |
| VPC | ecs-private-vpc |
| アベイラビリティゾーン | ap-northeast-1a, ap-northeast-1c |
| サブネット |
2 つのプライベートサブネット(CIDRが10.0.128.0/20と10.0.144.0/20) |

4-2.DBインスタンスを作る
RDS コンソール → データベースの作成 → メニューからフル設定をクリック

| 項目 | 設定値 |
| エンジンのタイプ | PostgreSQL |
| データベース作成方法を選択 | フル設定 |
| テンプレート | 開発/テスト |

| 項目 | 設定値 |
| 可用性と耐久性 | シングル AZ DB インスタンスのデプロイ |

| 項目 | 設定値 |
| マルチAZ DBクラスターをサポートするバージョンのみを表示 | OFF |
| エンジンバージョン | PostgreSQL 18.3-R2 (作業時の最新バージョンを選択してください) |
| RDS延長サポートの有効化 | チェックなし |
| DB インスタンス識別子 | ecs-private-db |
| マスターユーザー名 | postgres(デフォルト) |
| 認証情報管理 | AWS Secrets Manager で管理 |
| 暗号化キー | aws/secretsmanager(デフォルト) |
| データベース認証 | パスワードと IAM データベース認証 |

| 項目 | 設定値 |
| インスタンスの設定 | バースト可能クラス → db.t4g.micro |
| ストレージタイプ | 汎用 SSD (gp3) |
| ストレージ割り当て | 20 GiB |
| ストレージの自動スケーリング | ストレージの自動スケーリングを有効にするチェックを外す |

| 項目 | 設定値 |
| コンピューティングリソース | EC2 コンピューティングリソースに接続しない |
| VPC | ecs-private-vpc |
| DB サブネットグループ | ecs-private-db-subnet |
| パブリックアクセス | なし |
| VPC セキュリティグループ | 既存の選択→ecs-private-sg-db(default は外す) |
| データベース認証 | パスワードと IAM データベース認証 |
| モニタリング | Database Insights – 標準 (今回はその他モニタリング設定等はしません) |
| 追加設定 → 最初のデータベース名 | appdb |
| 追加設定 → バックアップ保持期間 | 1日 |
| 追加設定 → 削除保護 | 削除保護の有効化チェックを外す |

4-3.設定の確認
| 設定内容 | 設定方針 |
| パブリックアクセスなし + ecs-private-sg-db | ECSタスク以外からは、ネットワーク的に到達できない。 |
| Secrets Managerで管理 | マスターパスワードを人間が決めず、誰の手元にも残さない。自動ローテーションによるパスワード更新。 |
| IAMデータベース認証 | アプリ用ユーザーはパスワードを持たず、IAM ロールで認証する。 |
4-4.作成後設定値を控える
作成が完了したら、ecs-private-dbを開いて次の値をメモします。
| 値 | 設定箇所 | 例 |
| エンドポイント |
接続とセキュリティタブ |
ecs-private-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com |
| リソース ID | 設定タブ | db-ABCDEFGHIJKLMNOPQRSTUVWXYZ |
| マスター認証情報の ARN | 設定タブ→マスター認証情報 | arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:rds!db-xxxx-AbCdEf |
リソース IDは、インスタンス識別子(ecs-private-db)とは違います。後ほど、IAMポリシーの設定で使用します。
5.ECRとアプリケーション、GitHub Actions
5-1.ECRリポジトリ
ECR コンソール →「リポジトリを作成」
| 項目 | 設定値 |
| リポジトリ名 | ecs-private-app |
| イメージタグの変更可能性 | Immutable(イミュータブル) |
| 暗号化 | AES-256(デフォルト) |
5-2.アプリケーション
今回使用するアプリは、Python(Flask)で書いた小さな Web アプリです。アクセスのたびに DB に 1 行記録し、次の情報を表示します。
- アプリのバージョン(コミット ID)と、応答したタスク
- PostgreSQL のバージョン
- 接続しているユーザーと、接続が TLS かどうか
- これまでのアクセス回数
GitHub で新しいリポジトリを名称ecs-private-handsonで作ります。設定は、PrivateでREAD MEを追加しておきます。
「Add file」→「Create new file」または、VisualStudioCodeで次の4ファイルを作ります。(gitの操作は記載しませんので、マニュアル等をご参照ください。)
- Python
- app.py
- init_db.py
- txt
- requirements.txt
- Dockerfile
- Dockerfile
app.py
import os
import socket
import boto3
import psycopg
from flask import Flask
# ▼ リリースのたびにここを書き換えます(11 章)
APP_MESSAGE = "v1: Hello from private subnet!"
THEME_COLOR = "#e8f0fe"
DB_HOST = os.environ["DB_HOST"]
DB_PORT = int(os.environ.get("DB_PORT", "5432"))
DB_NAME = os.environ.get("DB_NAME", "appdb")
DB_USER = os.environ.get("DB_USER", "app_user")
REGION = os.environ.get("AWS_REGION", "ap-northeast-1")
APP_VERSION = os.environ.get("APP_VERSION", "local")
CA_BUNDLE = "/app/global-bundle.pem"
app = Flask(__name__)
rds = boto3.client("rds", region_name=REGION)
def connect():
# IAM 認証トークンを生成する(手元での署名計算のみ。AWS の API は呼ばない)
token = rds.generate_db_auth_token(
DBHostname=DB_HOST, Port=DB_PORT, DBUsername=DB_USER, Region=REGION
)
# トークンをパスワードとして使い、TLS + サーバー証明書検証ありで接続する
return psycopg.connect(
host=DB_HOST,
port=DB_PORT,
dbname=DB_NAME,
user=DB_USER,
password=token,
sslmode="verify-full",
sslrootcert=CA_BUNDLE,
connect_timeout=5,
)
@app.route("/health")
def health():
# ALB のヘルスチェック用。DB には触らない(理由は 12-3 のコラム)
return "ok", 200
@app.route("/")
def index():
with connect() as conn, conn.cursor() as cur:
cur.execute(
"INSERT INTO access_log (app_version) VALUES (%s)", (APP_VERSION,)
)
cur.execute("SELECT count(*) FROM access_log")
count = cur.fetchone()[0]
cur.execute("SELECT version()")
pg_version = cur.fetchone()[0].split(" on ")[0]
cur.execute(
"SELECT current_user, ssl, version FROM pg_stat_ssl "
"WHERE pid = pg_backend_pid()"
)
user, ssl, tls_version = cur.fetchone()
return f"""<!doctype html>
<html><head><meta charset="utf-8"><title>ecs-private-handson</title></head>
<body style="font-family:sans-serif;background:{THEME_COLOR};padding:2rem">
<h1>{APP_MESSAGE}</h1>
<table border="1" cellpadding="8" style="border-collapse:collapse;background:#fff">
<tr><th align="left">アプリのバージョン</th><td>{APP_VERSION}</td></tr>
<tr><th align="left">応答したタスク</th><td>{socket.gethostname()}</td></tr>
<tr><th align="left">DB</th><td>{pg_version}</td></tr>
<tr><th align="left">接続ユーザー</th><td>{user}</td></tr>
<tr><th align="left">TLS</th><td>{ssl} ({tls_version})</td></tr>
<tr><th align="left">アクセス回数</th><td>{count}</td></tr>
</table>
</body></html>"""
init_db.py
"""マスターユーザーで接続し、アプリ用ユーザーとテーブルを用意する。
何度実行しても同じ結果になる(冪等)ように書いている。"""
import os
import psycopg
conn = psycopg.connect(
host=os.environ["DB_HOST"],
port=int(os.environ.get("DB_PORT", "5432")),
dbname=os.environ.get("DB_NAME", "appdb"),
user=os.environ["DB_MASTER_USER"],
password=os.environ["DB_MASTER_PASSWORD"],
sslmode="verify-full",
sslrootcert="/app/global-bundle.pem",
connect_timeout=5,
autocommit=True,
)
with conn, conn.cursor() as cur:
# アプリ用ユーザー(パスワードなし)を作り、IAM 認証を許可する
cur.execute("""
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN
CREATE ROLE app_user LOGIN;
END IF;
END $$;
""")
cur.execute("GRANT rds_iam TO app_user")
# アプリが使うテーブル
cur.execute("""
CREATE TABLE IF NOT EXISTS access_log (
id bigserial PRIMARY KEY,
accessed_at timestamptz NOT NULL DEFAULT now(),
app_version text NOT NULL
)
""")
# アプリ用ユーザーには必要最小限の権限だけを渡す
cur.execute("GRANT SELECT, INSERT ON access_log TO app_user")
cur.execute("GRANT USAGE ON SEQUENCE access_log_id_seq TO app_user")
print("init_db: done")
requirements.txt
flask==3.1.*
gunicorn==23.*
psycopg[binary]==3.2.*
boto3==1.*
Dockerfile
FROM public.ecr.aws/docker/library/python:3.12-slim
WORKDIR /app
# RDS のサーバー証明書を検証するための CA 証明書(全リージョン共通バンドル)
ADD https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem /app/global-bundle.pem
RUN chmod 644 /app/global-bundle.pem
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py init_db.py ./
ARG GIT_SHA=local
ENV APP_VERSION=${GIT_SHA}
# root 以外のユーザーで実行する
RUN useradd --system --uid 10001 app
USER app
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "2", "app:app"]
-
CA 証明書をイメージに入れる理由
-
sslmode=”verify-full”は、「接続先が本物のRDSであること」を証明書で確かめる設定です。これがないと、TLSで暗号化はされていても、なりすましたサーバーに接続してしまう可能性を排除できません。プライベートサブネットのタスクは実行時にインターネットから証明書を取ってこられないので、ビルド時に(インターネットに出られるGitHub Actions上で)取り込んでおきます。
-
- ベースイメージをpublic.ecr.awsから取る理由
-
Docker Hubには未認証のpull回数制限があります。ECR Public GalleryにあるDocker公式イメージのミラーを使い、さらにワークフローでECR Publicにログインしてからpullすることで、制限にかかりにくくしています。
-
5-3. GitHub Actions用のOIDCとIAMロール
IDプロバイダーを作る(既に存在する場合は、スキップしてください)
IAM コンソール → ID プロバイダ → プロバイダを追加
| 項目 | 設定値 |
| プロバイダのタイプ | OpenID Connect |
| プロバイダのURL | https://token.actions.githubusercontent.com |
| 対象者 | sts.amazonaws.com |
ロールの作成
IAM コンソール →「ロール」→「ロールを作成」→「カスタム信頼ポリシー」で、次を貼り付けます。ACCOUNT_IDとGITHUB_OWNERは自身の値に置き換えてください。
- ロール名ecs-private-github-actions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:GITHUB_OWNER/ecs-private-handson:ref:refs/heads/main"
}
}
}
]
}
許可ポリシーは付けずに次へ進み、ロール名ecs-private-github-actionsで作成します。
作成後、ロールを開いて「許可を追加」→「インラインポリシーを作成」→ JSON で次を貼り付けecs-private-github-actions-policyという名前で保存します。
- ecs-private-github-actions-policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EcrLogin",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
},
{
"Sid": "EcrPush",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:DescribeImages"
],
"Resource": "arn:aws:ecr:ap-northeast-1:ACCOUNT_ID:repository/ecs-private-app"
},
{
"Sid": "EcrPublicLogin",
"Effect": "Allow",
"Action": [
"ecr-public:GetAuthorizationToken",
"sts:GetServiceBearerToken"
],
"Resource": "*"
},
{
"Sid": "StartPipeline",
"Effect": "Allow",
"Action": "codepipeline:StartPipelineExecution",
"Resource": "arn:aws:codepipeline:ap-northeast-1:ACCOUNT_ID:ecs-private-pipeline"
}
]
}
パイプラインはまだ作っていませんが、ARNは名前から決まるので先に書いておけます。
5-4.ワークフローを作る
GitHubのリポジトリ →「Settings」→「Secrets and variables」→「Actions」→「Variables」タブで、変数を1つ追加します。
| Name | Value |
| AWS_ROLE_ARN | arn:aws:iam::ACCOUNT_ID:role/ecs-private-github-actions |
PIPELINE_NAMEは、パイプラインを作った後に追加します。

Repository variablesに登録したところの画面

GitHubのリポジトリに.github/workflows/build-and-deploy.ymlを作ります。
- .github/workflows/build-and-deploy.yml
- ワークフローで使用しているactions(actions/checkout@v4、aws-actions/amazon-ecr-login@v2)は、最新のバージョンを使用するようにお願いいたします。
name: build-and-deploy
on:
push:
branches: [main]
workflow_dispatch:
permissions:
id-token: write # OIDC トークンの取得に必要
contents: read
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: ecs-private-app
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Login to Amazon ECR Public
uses: aws-actions/amazon-ecr-login@v2
with:
registry-type: public
env:
AWS_REGION: us-east-1
AWS_DEFAULT_REGION: us-east-1
- name: Build and push image
id: push
env:
REGISTRY: ${{ steps.ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
IMAGE=${REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}
docker build --build-arg GIT_SHA=${IMAGE_TAG::7} -t ${IMAGE} .
docker push ${IMAGE}
# push したイメージのダイジェストを取得
DIGEST=$(aws ecr describe-images \
--repository-name ${ECR_REPOSITORY} \
--image-ids imageTag=${IMAGE_TAG} \
--query 'imageDetails[0].imageDigest' --output text)
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
echo "### Pushed image" >> "$GITHUB_STEP_SUMMARY"
echo "- tag: \`${IMAGE_TAG}\`" >> "$GITHUB_STEP_SUMMARY"
echo "- digest: \`${DIGEST}\`" >> "$GITHUB_STEP_SUMMARY"
- name: Start CodePipeline
if: vars.PIPELINE_NAME != ''
run: |
aws codepipeline start-pipeline-execution \
--name ${{ vars.PIPELINE_NAME }} \
--variables name=IMAGE_TAG,value=${{ github.sha }} \
--source-revisions actionName=Source,revisionType=IMAGE_DIGEST,revisionValue=${{ steps.push.outputs.digest }}
コミットすると(mainへのpushで)ワークフローが起動します。「Actions」タブで緑になり、ECR コンソールのecs-private-appに40桁のコミットIDのタグが付いたイメージが 1 つ入っていれば成功です。
このタグ(フルのコミット ID)をメモしておきます。ECSのタスク定義で使用します。

CodePipelineで、最後のステップが「スキップ」になっているのは正常です。 パイプラインを作るまでは起動のしようがないため、変数の有無で実行を切り替えています。
6.ECS用のIAMロールを作る
6-1. 「タスク実行ロール」と「タスクロール」
ECSのタスクには、性格の違う2種類のロールがあります。
| タスク実行ロール | タスクロール | |
| 使用する | ECS(Fargate)自身 | コンテナ内のアプリ |
| 使用タイミング | タスクの起動時(コンテナが動く前) | コンテナが動いている間 |
| 権限 |
ECRからのpull、ログ送信、シークレットの取得 |
アプリが呼ぶAWS サービス(RDSへのIAM認証など) |
今回は次の 4 つのロールを作ります
| ロール | 種類 | 誰が使うか | 割り当てたい権限 |
| ecs-private-execution-role | タスク実行ロール | アプリのタスク | ECR pull、ログ |
| ecs-private-init-execution-role | タスク実行ロール | 初期化タスク |
ECR pull、ログ、マスターパスワードの取得 |
| ecs-private-app-task-role | タスクロール | アプリのコンテナ | rds-db:connect(app_userのみ) |
| ecsInfrastructureRoleForLoadBalancers | ECSのインフラストラクチャロール | ECS(Blud/Green) | ALB のルール書き換え |
アプリのタスクはマスターパスワードを取得できないようにすることで、秘密情報が漏れないようにしています。マスターパスワードを読めるのは、初期化タスクの実行ロールだけです。
6-2.ecs-private-execution-role
IAM コンソール →「ロール」→「ロールを作成」
| 項目 | 設定値 |
| 信頼されたエンティティタイプ | AWSのサービス |
| サービスまたはユースケース |
Elastic Container Service → Elastic Container Service Task |
| 許可ポリシー | AmazonECSTaskExecutionRolePolicy |
| ロール名 | ecs-private-execution-role |
ユースケースの選び直しに注意:「Elastic Container Service」を選ぶと、ユースケースは一番上の「Elastic Container Service」が最初から選択された状態になります。これは ECS サービス自身(ecs.amazonaws.com)用のもので、タスク用ではありません。下にある「Elastic Container Service Task」(ecs-tasks.amazonaws.com)を選び直してください。作成後、ロールの「信頼関係」タブでecs-tasks.amazonaws.comになっていれば正しく作成できています。
作成後、ロールを開いて「許可を追加」→「インラインポリシーを作成」→ JSON で次を追加します。ポリシー名は、create-log-groupで作成します。
ACCOUNT_IDは自身のAWSアカウントIDを入力します。
- インラインポリシー:create-log-group
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:ap-northeast-1:ACCOUNT_ID:log-group:/ecs/ecs-private-*"
}
]
}
6-3.ecs-private-init-execution-role
IAM コンソール →「ロール」→「ロールを作成」
| 項目 | 設定値 |
| 信頼されたエンティティタイプ | AWSのサービス |
| サービスまたはユースケース |
Elastic Container Service → Elastic Container Service Task |
| 許可ポリシー | AmazonECSTaskExecutionRolePolicy |
| ロール名 | ecs-private-init-execution-role |
作成後、ロールを開いて「許可を追加」→「インラインポリシーを作成」→ JSON で次を追加します。6-2と同じポリシーcreate-log-groupのインラインポリシーを付けます。追加で、read-db-master-secretという名前で追加します。Resourceには 4-4 でメモしたマスター認証情報の ARNを、末尾の 6 文字(-AbCdEfの部分)まで含めてそのまま入れます。
ACCOUNT_IDは自身のAWSアカウントIDを入力します。
- インラインポリシー:create-log-group
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "logs:CreateLogGroup",
"Resource": "arn:aws:logs:ap-northeast-1:ACCOUNT_ID:log-group:/ecs/ecs-private-*"
}
]
}
- インラインポリシー:read-db-master-secret
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:ap-northeast-1:ACCOUNT_ID:secret:rds!db-xxxx-AbCdEf"
}
]
}
シークレットは AWS マネージドキー(aws/secretsmanager)で暗号化されているため、KMS の権限を別途付ける必要はありません。
作成後、ロールの「許可」タブに次の 3 つが並んでいることを確認してください
| ポリシー | 種類値 |
| AmazonECSTaskExecutionRolePolicy | AWS管理 |
| create-log-group | インライン |
| read-db-master-secret | インライン |

6-4. ecs-private-app-task-role
IAM コンソール →「ロール」→「ロールを作成」
| 項目 | 設定値 |
| 信頼されたエンティティタイプ | AWSのサービス |
| サービスまたはユースケース |
Elastic Container Service → Elastic Container Service Task |
| 許可ポリシー | 選択しない |
| ロール名 | ecs-private-app-task-role |
作成後、インラインポリシーrds-iam-connectを追加します。ACCOUNT_IDは自身のID、RESOURCE_IDは 4-4 でメモしたリソースID(db-で始まる値)になります。
- インラインポリシー:rds-iam-connect
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:ap-northeast-1:ACCOUNT_ID:dbuser:RESOURCE_ID/app_user"
}
]
}
ARN の形に注意:rds-db:connectのARNは、サービス名がrdsではなくrds-db、インスタンスの指定がインスタンス識別子ではなくリソースID、末尾がDBユーザー名です。ここを間違えると、接続時エラーになります。
6-5.ecsInfrastructureRoleForLoadBalancers
ECS ネイティブ Blue/Green で、ECS が ALB のリスナールールの重みを書き換えるためのロールです。
IAM コンソール →「ロールを作成」→「カスタム信頼ポリシー」で次を貼り付けます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ecs.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
許可ポリシーにAmazonECSInfrastructureRolePolicyForLoadBalancersを選びます。

ロール名ecsInfrastructureRoleForLoadBalancersで作成します。

7.ECSを作る
7-1.ECSクラスターを作る
ECS コンソール →「クラスター」→「クラスターの作成」
| 項目 | 設定値 |
| クラスター名 | ecs-private-cluster |
| インフラストラクチャ | AWS Fargate(サーバーレス)のみ |
| モニタリング | デフォルト(Container Insightsはオフ) |
7-2.初期化用のタスク定義を作る
ECS コンソール →「タスク定義」→「新しいタスク定義の作成」
以下の設定で作成します。
- タスク定義
| 項目 | 設定値 |
| タスク定義ファミリー | ecs-private-init |
| 起動タイプ | AWS Fargate |
| OS/アーキテクチャ | Linux/X86_64 |
| CPU/メモリ | 0.25 vCPU / 0.5 GB |
| タスクロール | なし(初期化タスクは AWS の API を呼ばない) |
| タスク実行ロール | ecs-private-init-execution-role |

- コンテナ – 1
ACCOUNT_IDは自身のAWSアカウントIDを入力します。
| 項目 | 設定値 |
| 名前 | init |
| イメージURI | <ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-private-app:<5-4 でメモしたコミット ID> |
| 必須コンテナ | はい |
| ポートマッピング |
削除する(Web サーバーとして動かさないため) |

環境変数
環境変数 – オプションを展開し、環境変数を追加ボタンを押下して変数の追加をします。(値のタイプを間違えないようにお願いいたします。)
| キー | 値のタイプ | 設定値 |
| DB_HOST | 値 | 4-4 でメモしたエンドポイント |
| DB_NAME | 値 | appdb |
| DB_MASTER_USER | ValueFrom | <マスター認証情報のARN>:username:: |
| DB_MASTER_PASSWORD | ValueFrom | <マスター認証情報のARN>:password:: |
DB_HOSTに入れるのは、ecs-private-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.comの形式の値
DB_MASTER_USERとDB_MASTER_PASSWORDに入れる、マスター認証情報のARNはarn:aws:secretsmanager:ap-northeast-1:123456789012:secret:rds!db-xxxx-AbCdEfの形式の値です。
どちらも、事前の作業4-4でメモしているはずです。
ValueFromの書き方:Secrets ManagerのシークレットはJSONで{“username”: “…”, “password”: “…”}の形をしています。ARN の後ろに「:キー名::」を付けると、JSONの中の特定のキーだけを環境変数に入れられます。末尾の :: は「バージョンは最新」の意味です。値はタスク起動時に実行ロールの権限で取得され、タスク定義にはARNしか残りません。

ログ収集設定
基本的にはデフォルト設定で問題ありません。念のため、設定されていることを確認します。
ログ収集を展開します。ログ収集の使用にチェックがついていること、送信先をAmazon CloudWatchが設定されていることを確認します。
各設定が、以下のように設定されていることを確認します。
| キー | 値のタイプ | 設定値 |
| awslogs-group | 値 | /ecs/ecs-private-init |
| awslogs-create-group | 値 | true |
| awslogs-region | 値 | ap-northeast-1 |
| awslogs-stream-prefix | 値 | ecs |
Docker設定
Docker設定 – オプションを展開します。以下の設定入れます。
| 項目 | 設定値 |
| コマンド | python,init_db.py |

7-3.初期化タスクを実行する
ECSコンソール → ecs-private-cluster → タスクタブ → 新しいタスクの実行
| 項目 | 設定値 |
| タスク定義ファミリー | ecs-private-init(最新リビジョン) |
| 必要なタスク | 1 |
| タスクグループ | ブランク(設定しない) |
| コンピューティングオプション | 起動タイプ |
| 起動タイプ | Fargate |
| プラットフォームバージョン | LATEST |
| ネットワーキング→VPC | ecs-private-vpc |
| サブネット | プライベートサブネットを2つ選択(パブリックは選択しない) |
| セキュリティグループ | ecs-private-sg-app (defaultは設定から外しておく) |
| パブリックIP | オフ |

「作成」を押し、タスクのステータスが「停止」になるまで待ちます(1〜2 分)。
タスクタブを開いて、次の 3 点を確認します。
| 確認する項目 | 確認したい結果 |
| ログのタブ | init_db: done |
| コンテナの終了コード | 0 |
| 停止理由 | Essential container in task exited (文字が青色であること。) |
タスクライフサイクルの「実行中」に赤い × が付いていても、終了コードが0(ゼロ)なら問題ありません。初期化タスクは処理が終われば止まるのが正常としています。



8.ALBとターゲットグループ
8-1.ターゲットグループの作成
EC2 コンソール →「ターゲットグループ」→「ターゲットグループの作成」
ターゲットグループを ecs-private-tg-blueとecs-private-tg-greenの2つ作成します。
- ecs-private-tg-blue
| 項目 | 設定値 |
| ターゲットタイプ | IPアドレス |
| ターゲットグループ名 | ecs-private-tg-blue |
| プロトコル : ポート | HTTP:8000 |
| VPC | ecs-private-vpce |
| ヘルスチェックパス | /health |
- ecs-private-tg-green (設定内容はblueと同じです。)
| 項目 | 設定値 |
| ターゲットタイプ | IPアドレス |
| ターゲットグループ名 | ecs-private-tg-green |
| プロトコル : ポート | HTTP:8000 |
| VPC | ecs-private-vpce |
| ヘルスチェックパス | /health |
ヘルスチェックの詳細設定等は、デフォルトのままで問題ありません。
「次へ」→ ターゲットは何も登録せずに「ターゲットグループの作成」

8-2.ALBを作る
EC2 コンソール →「ロードバランサー」→「ロードバランサーの作成」→ロードバランサータイプ「Application Load Balancer」
| 項目 | 設定値 |
| 名前 | ecs-private-alb |
| スキーム | インターネット向け |
| VPC | ecs-private-vpc |
| マッピング | 2つのAZで、それぞれパブリックサブネットを選択 |
| セキュリティグループ |
ecs-private-sg-alb(default設定は外します。) |
リスナーとルーティング
| 項目 | 設定値 |
| プロトコル | HTTP |
| ポート | 80 |
| アクションのルーティング | ターゲットグループへ転送 |
| ターゲットグループ | ecs-private-tg-blue 重み:100 |
| ecs-private-tg-green 重み: 0 |
リスナーの追加ボタンを押下し、つづけてポート8080番を作成します。
| 項目 | 設定値 |
| プロトコル | HTTP |
| ポート | 8080 |
| アクションのルーティング | ターゲットグループへ転送 |
| ターゲットグループ | ecs-private-tg-blue 重み:100 |
| ecs-private-tg-green 重み: 0 |


ロードバランサーの作成ボタンを押下します。作成したら、ALBの詳細タブにあるDNS名をメモして控えておきます。
9.ECSサービスを作る
9-1.アプリ用のタスク定義を作る
ECS コンソール →「タスク定義」→「新しいタスク定義の作成」
| 項目 | 設定値 |
| タスク定義ファミリー | ecs-private-app |
| 起動タイプ | AWS Fargate |
| オペレーティングシステム/アーキテクチャ | Linux/X86_64 |
| タスクサイズ CPU | 0.25 vCPU |
| タスクサイズ メモリ | 0.5 GB |
| タスクロール | ecs-private-app-task-role |
| タスク実行ロール | ecs-private-execution-role |

コンテナ – 1 の設定
- ACCOUNT_IDは自身のAWSアカウントIDを入力します。
- コミットIDは確認した5-4 でメモしたコミット IDを入力します。
| 項目 | 設定値 |
| 名前 | app |
| イメージURI | <ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-private-app:<コミットID> |
| ポートマッピング |
コンテナポート:8000、プロトコル:TCP、ポート名:app-8000、アプリケーションプロトコル:HTTP |

環境変数
| キー | 値のタイプ | 値 |
| DB_HOST | 値 | 4-4 でメモしたエンドポイント 例:ecs-private-db.xxxxxxxx.ap-northeast-1.rds.amazonaws.com |
| DB_NAME | 値 | appdb |
| DB_USER | 値 | app_user |
| AWS_REGION | 値 | ap-northeast-1 |

パスワードに関する環境変数設定はいれません。
ログ収集の設定などは、そのままデフォルト設定で問題ありません。
確認したら作成ボタンを押下します。
9-2.サービスを作る
ECS コンソール → クラスター → ecs-private-cluster →「サービス」タブ →「作成」
サービスの詳細
| 項目 | 設定値 |
| タスク定義ファミリー | ecs-private-app |
| タスク定義のリビジョン | 最新リビジョン |
| サービス名 | ecs-private-service |
環境
| 項目 | 設定値 |
| コンピューティングオプション | 起動タイプ |
| 起動タイプ | Fargate |
| プラットフォームバージョン | LATEST |
デプロイ設定
| 項目 | 設定値 |
| スケジューリング戦略 | レプリカ |
| 必要なタスク | 2 |
| アベイラビリティゾーンのリバランスを有効にする | チェックをつける |
| ヘルスチェックの猶予期間 | 特に設定しません |
デプロイオプション
| 項目 | 設定値 |
| デプロイコントローラのタイプ | ECS |
| デプロイ戦略 | ブルー/グリーン |
| デプロイベイク時間 | 5 |
| トラフィック移行 | 全てのトラフィックを更新済みのAmazon ECSコンテナに一度に移行 |
デプロイライフサイクルフック
一時停止フック
| 項目 | 設定値 |
| ライフサイクルステージ | テストトラフィック移行後 |
| タイムアウト | 30 |
| タイムアウトアクション | ロールバック |
デプロイ不具合の検出
| 項目 | 設定値 |
| Amazon ECS デプロイサーキットブレーカーを使用する | チェックをつける |
| 失敗時のロールバック | チェックをつける |
| 希望するカウントの上限・加減付き割合 | 選択する |
| しきい値 | 50 |
ネットワーキング
| 項目 | 設定値 |
| VPC | ecs-private-vpc |
| サブネット |
プライベートサブネット 2 つ |
| セキュリティグループ | ecs-private-sg-app (defaultは外す) |
| パブリックIP | オフ |
ロードバランシング
| 項目 | 設定値 |
| ロードバランサーの種類 | Application Load Balancer |
| コンテナ | app 8000:8000 |
| ロードバランサー | 既存のロードバランサーを使用 → ecs-private-alb |
| リスナー |
既存のリスナーを使用 → HTTP:80 |
| 本番リスナールール | 優先度:default |
| テストリスナールール | 空欄にします |
| ターゲットグループ | 2 つの既存のターゲットグループを使用 |
| ブルーターゲットグループ | ecs-private-tg-blue |
| グリーンターゲットグループ | ecs-private-tg-green |
| ロール | ecsInfrastructureRoleForLoadBalancers |
※ テストリスナールールは作成後の「サービスを更新」で追加します。

9-3.サービスを更新してテストリスナーを追加
タスクが2つRUNNINGになり、サービスの「デプロイ」タブでデプロイが完了していることを確認したら、テストリスナーを追加します。
ECS → クラスター → ecs-private-cluster → サービス → ecs-private-app-serviceを選択 → サービスを更新ボタンを押下 → ecs-private-app-service を更新画面が表示されるので、ロードバランシングセクションまでスクロールします。
| 項目 | 設定値 |
| テストリスナー | HTTP:8080 |
| テストリスナールール | 優先度:default |

他は変更せずに「更新」を押します。
更新そのものでデプロイが始まります。 中身は同じイメージのまま、Blue/Greenの流れ(Green起動 → テストトラフィック移行 → 一時停止)が1周します。デプロイが「テストトラフィック移行後」で一時停止したら、次の2つを開いて確認します。
| URL | 環境 | 表示 |
| http://<ALB の DNS 名>/ | 本番 | v1(Blue が応答) |
| http://<ALB の DNS 名>:8080/ | テスト | v1(Green が応答) |
どちらも同じv1ですが、「応答したタスク」の値が本番とテストで異なることを確認してください。確認できたら、デプロイ詳細の「アクションを実行する」→「続行」を押します。ベイク時間(5分)が過ぎれば、このデプロイは完了です。
動作確認
ブラウザで、http://<ALB の DNS 名>/を開いて(httpも含めて、明示的にhttpと入力してください。ブラウザによってはhttpsに補完される場合があります。)確認します。
| 項目 | 想定される表示結果 |
| アプリのバージョン | コミットIDの先頭7桁 |
| 応答したタスク | ip-10-0-1xx-xx.ap-northeast-1.compute.internal |
| DB | PostgreSQL 18.x |
| 接続ユーザー | app_user |
| TLS | True (TLSv1.3) |
| アクセス回数 | 1 |
テストが正常に表示されて、本番が一時停止フックで500番台が表示されていると思います。確認したら、アクションを実行するで処理を続行し、テストも本番も両方のサイトが正しく表示されることを確認します。

リロードするたびにアクセス回数が増え、「応答したタスク」が 2 つの値で切り替わります。RDSへの接続ユーザーがapp_userで、TLSがTrueになっていることが、IAM認証とTLS接続が機能しているとなります。
もし’/’を開いて500 エラーになる場合は、DBへの接続で失敗しています。タスクのログを確認して原因を確認していく必要があります。
ここで本番(80)だけ 503 になった場合:503 Service Temporarily Unavailableは、「本番リスナーの転送先(Blue)に正常なターゲットが1つもない」ことを意味します。9-2の時点でセキュリティグループなどの設定を誤っていて、最初のデプロイが一度も正常にならなかった場合に起きます。
「ロールバックに失敗」「No rollback candidate was found」と表示された場合:Greenがヘルスチェックに通らずサーキットブレーカーが作動したものの、戻り先となる「正常に動いた実績のあるリビジョン」が存在しないときの表示です。上と同じく、初回構築時に設定を誤っていた場合に起きます。原因(多くはセキュリティグループにあります。)を直してから、「サービスを更新」で「新しいデプロイの強制」にチェックを入れて更新すれば、デプロイをやり直せます。
10.CodePipelineを作る
10-1.パイプライン全体像

今回作成するパイプラインは、パイプラインが自分で変更を検知して動くのではなく、GitHub Actionsから「このイメージを出して」と指定されて動くことです。パイプラインは変数IMAGE_TAGを受け取り、それをもとにデプロイするイメージを決めます。
10-2.パイプラインを作る
ステップ 1:作成オプションを選択する
CodePipeline コンソール →「パイプラインを作成する」→「カスタムパイプラインを構築する」

ステップ 2:パイプラインの設定
| 項目 | 設定値 |
| パイプライン名 | ecs-private-pipeline |
| 実行モード | キュー |
| サービスロール | 新しいサービスロール |
| ロール名 | ロール名は表示されたロール名を使用します。 |
| AWS CodePipelineがサービスロールを作成できるようになるため、この新しいパイプラインでの使用が可能になります。にチェックをつける | |
| アーティファクトストア | デフォルトのロケーション |
| 暗号化キー | デフォルトのAWSマネージド型キー |
| 変数 | 名前 :IMAGE_TAG、デフォルト値:空欄、説明:空欄 |

ステップ3:ソースステージ
| 項目 | 設定値 |
| ソースプロバイダー | Amazon ECR |
| リポジトリ名 | ecs-private-app |
| 画像(イメージ)タグ | 空欄 |
それ以外はデフォルト設定にします。

ステップ 4:ビルドステージ
プロバイダーを構築するでコマンドを選択します。以下のコマンドを、コマンド欄に記載します。(testコマンド始まるのと、catコマンドで始まる2行になります。)
test -n “#{variables.IMAGE_TAG}” || (echo “IMAGE_TAG is empty” && exit 1)
printf ‘[{“name”:”app”,”imageUri”:”%s”}]’ “<ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/ecs-private-app:#{variables.IMAGE_TAG}” > imagedefinitions.json
cat imagedefinitions.json
<ACCOUNT_ID>は自分のアカウント ID に置き換えてください。1 行目は「変数が渡されていなければ止める」ための安全装置です。

ステップ5: テストステージ
ビルドステージをスキップボタンを押下して、スキップします。

ステップ6:デプロイステージを追加する
導入段階をスキップボタンを押下して、スキップします。後ほど、デプロイステージを追加します。

ステップ7: レビュー
内容に問題がなければ、パイプラインを作成するボタンを押下します。
10-3.出力ファイルとデプロイステージを追加する
作成したパイプラインを開き、「編集する」を押します。
ステップ1:Buildステージのアクションを編集する
「ステージを編集する」→ Commands アクションの編集アイコン
| 項目 | 設定値 |
| 出力アーティファクト | BuildArtifact |
| 出力ファイル | imagedefinitions.json(入力後「ファイルパスに追加」を押して登録する) |
補足:ステージを編集するボタンを押下すると、下記画像のようになるのでペンのアイコンをクリックすると編集できます。

出力アーティファクトは下の方にあります。

完了ボタンを押下します。編集:Buildの画面も完了ボタンを押下して、設定を保存します。
ステップ2:Deploy ステージを追加する
Build ステージの下の「+ ステージを追加する」をクリックします→ ステージを追加するが表示されるので、Deployと入力し、ステージを追加するボタンを押下します →「+ アクショングループを追加する」をクリックします

アクションを編集する画面が表示されるので、以下のように設定します。
| 項目 | 設定値 |
| アクション名 | Deploy |
| アクションプロバイダー | Amazon ECS |
| リージョン |
アジアパシフィック(東京) |
| 入力アーティファクト | BuildArtifact |
| クラスター名 | ecs-private-cluster |
| サービス名 | ecs-private-service |
| イメージ定義ファイル | imagedefinitions.json |
| デプロイのタイムアウト | 60分 |
デプロイのタイムアウトを長めにする理由:このアクションは、ECSのデプロイが完了するまで待ちます。今回は「一時停止フック(最大30 分)+ベイク時間(5分)」があるので、既定値では足りなくなる可能性があります。実際の業務では、業務要件に合わせてください。

入力したら「完了」ボタンを押下→ 編集:Deployの「完了」をクリック→画面上部の「保存」を押下→パイプラインの変更を保存する画面が表示されるので「保存」ボタンを押下します。

10-4. パイプラインのロールに ECS の権限を追加する
ウィザードでデプロイステージの作成をスキップしたため、自動作成されたサービスロールにはECSを操作する権限がありません。
IAM コンソール →「ロール」→ AWSCodePipelineServiceRole-ap-northeast-1-ecs-private-pipeline →「許可を追加」→「インラインポリシーを作成」→ JSON で次を貼り付け、ポリシー名をecs-deployという名前で保存します。
- ポリシー名:ecs-deploy
ACCOUNT_IDは自身のAWSアカウントIDを入力します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EcsDeploy",
"Effect": "Allow",
"Action": [
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:DescribeTasks",
"ecs:ListTasks",
"ecs:RegisterTaskDefinition",
"ecs:TagResource",
"ecs:UpdateService"
],
"Resource": "*"
},
{
"Sid": "PassTaskRoles",
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": [
"arn:aws:iam::ACCOUNT_ID:role/ecs-private-execution-role",
"arn:aws:iam::ACCOUNT_ID:role/ecs-private-app-task-role"
],
"Condition": {
"StringEquals": { "iam:PassedToService": "ecs-tasks.amazonaws.com" }
}
}
]
}

11.GitHub Actionsからリリースする
11-1. パイプラインの起動を有効にする
GitHubのリポジトリ →「Settings」→「Secrets and variables」→「Actions」→「Variables」→「New repository variables」で、変数を追加します。ワークフローの最後のステップ(Start CodePipeline)がスキップされなくなります。
| Name | Value |
| PIPELINE_NAME | ecs-private-pipeline |


11-2. v2 をリリースする
GitHubでapp.pyを編集し、先頭の2行を変更し(以下の箇所の内容を変更します)mainにマージします。(鉛筆アイコンで直接編集とコミットをしても今回は問題ありません。)
APP_MESSAGE = "v2: Released via GitHub Actions + CodePipeline"
THEME_COLOR = "#e6f4ea"
11-3. 連鎖を観察する
コミットから数分で、次の連鎖が起きます。
| 順番 | 発生箇所 | 内容 |
| 1 | GitHub Actions | イメージをビルドし、コミット ID のタグで ECR に push |
| 2 | GitHub Actions | ダイジェストを取得し、IMAGE_TAG変数付きでパイプラインを起動 |
| 3 | CodePipeline Source | 指定されたダイジェストのイメージを取得 |
| 4 | CodePipeline Build | imagedefinitions.jsonを生成 |
| 5 | CodePipeline Deploy | 新しいタスク定義を登録し、サービスを更新 |
| 6 | ECS |
Green タスクを起動 → テストリスナーを Green に向ける → 一時停止 |
CodePipeline の実行履歴を開くと、「変数」の欄にIMAGE_TAGの値(コミット ID)が表示されます。どのコミットのデプロイなのかが、パイプライン側にも記録として残ります。
11-4. テストリスナーで Green を確認する
ECS コンソール → ecs-private-service →「デプロイ」タブで、デプロイがテストトラフィック移行後で一時停止していることを確認します。

この状態で、ブラウザで2つのURLを開き比べます。
| URL | 環境 | 表示 |
| http://<ALB の DNS 名>/ | 本番 | v1(背景色:青) |
| http://<ALB の DNS 名>:8080/ | テスト | v2(背景色:緑) |

本番とテストのどちらを開いても、同じカウンターが増えていきます。BlueとGreenは、同じ1 つのデータベースを共有しているからです。これは便利な一方で、Blue/Green で DB を扱うときの最大の注意点でもあります。後ほど解説します。
11-5. 承認して本番に出す
確認して問題なければ、デプロイ詳細の「アクションを実行する」→一時停止フックアクションを実行するで「続行」を選択し、「確認」ボタンを押下します。
本番トラフィック移行まで進んだら、本番(ポート番号:80)をブラウザで開き直してv2が表示されれば、本番の切り替えは完了です。

11-6. ベイク時間中に「人が判断する」
本番を切り替えた直後から、5分間のベイク時間が始まります。この間、Blue(v1)のタスクは停止されずに残っています。ベイク時間中での確認したい点は以下になります。
| 確認先 | 確認すること |
| ブラウザ(本番) | 画面が正しく表示されるか |
| CloudWatch Logs /ecs/ecs-private-app | エラーログが出ていないか |
| ALB の「モニタリング」タブ | 500番台のエラーが増えていないか |
| ターゲットグループ | Green のターゲットがすべてhealthyになっているか |
問題を見つけた場合は、ECSコンソール → サービス →「デプロイ」タブ → 対象のデプロイを開き、「ロールバック」を押します。ALBの重みがBlueに戻り、残っていたv1のタスクがそのまま本番を引き継ぎます。新しくタスクを起動し直す必要がないので、数秒で元に戻ります。
何もしなければ、5 分後に Blue のタスクが停止され、デプロイは完了します。CodePipelineのDeployアクションも、このタイミングで「成功」になります。
11-7. ロールバックを練習する
実業務でいきなりロールバックする前に、ここで練習します。
- app.pyをAPP_MESSAGE = “v3: This release will be rolled back” 、 THEME_COLOR = “#fce8e6″に変更してコミットします。コードパイプラインが動いて、テスト側にリリースされることを確認します。
- ブラウザでテストリスナー側がv3であることを確認したら、デプロイ詳細の「アクションを実行する」→ 一時停止フックアクションを実行するで「続行」を選択し、「確認」ボタンを押下し本番側にリリースされることを確認します。
- ベイク時間に入るので、ベイク時間中に「ロールバック」ボタンを押下します。サービスデプロイをロールバックしますか?と聞かれるので、「ロールバック」ボタンを押下します。
- 本番側がロールバックされることを確認するため、ブラウザで本番側リスナーを開いてv2に戻ることを確認します。
- CodePipelineの実行は、Deployアクションが「失敗」として記録されることを確認します。「リリースしたが戻した」ことをパイプラインの履歴に残ることを確認しておきます。


2 段階の判断ポイント:この構成では、人が判断する場面が 2 回あります。
| タイミング | 見るもの | 判断を誤ったときの影響 |
| 一時停止中(本番切り替え前) | テストリスナー(自分だけ) | なし(まだ本番に出ていない) |
| ベイク時間中(本番切り替え後) | 本番のトラフィック、メトリクス | 数分間、利用者に影響が出る |
一時停止では「出してよいか」を、ベイク時間では「出してよかったか」を判断します。一時停止のタイムアウトを「ロールバック」にしているのは、「誰もOKを出さなかったものは出さない」ためでした。一方、ベイク時間は「誰もNOを出さなければ完了する」仕組みになります。本番では、ベイク時間の監視を人の目だけに頼らず、CloudWatchアラームによる自動ロールバック(デプロイ設定の「アラーム」)を組み合わせると、夜間や見落としにも強くなります。
参考リンク:Amazon ECS で手動ロールバック(デプロイメントの手動停止)がサポートされました
11-8. 修正版をリリースして、正しい状態に戻す
ロールバックで本番は v2 に戻りましたが、これで終わりにしてはいけません。今の状態を整理すると、次のようになっています。
| 対象サービス | 状態 |
| 本番(ECS) | v2 (ロールバックで戻った) |
| GitHubのmainブランチ | v3 (問題のあるコード) |
| ECR | v3のイメージも残っている |
ロールバックは「本番を戻す」操作で、ソースコードまでは戻りません。 この状態のまま、別の人が関係のない修正をmainにpushすると、v3の問題を抱えたコードがそのまま本番に出てしまいます。ロールバックの後は、必ずソースコードを直してリリースし直すところまでをセットで行います。
直し方には 2 通りあります。
| 方法 | やり方 | 向いている場面 |
| 修正して前に進む | 問題を直したコードを新しいバージョンとして出す | 原因が分かっていて、すぐ直せる |
| 元に戻す | git revertで v3 のコミットを打ち消すコミットを作る | 原因の調査に時間がかかる。まず確実にv2の内容に戻したい |
どちらの場合も、新しいコミットを作ってリリースする点は同じです。ECR のタグはイミュータブルなので、v2 のコミットをもう一度 GitHub Actions で流すことはできません(同じタグの push が失敗します)。常に「新しいコミット = 新しいタグ」で前に進むのが、この構成の考え方です。
今回は「修正して前に進む」方法で、v4 を出します。
ステップ1: 修正版をコミットする
app.pyの先頭2行を以下のように変更し、mainにマージします。(鉛筆アイコンで直接編集とコミットをしても今回は問題ありません。)
APP_MESSAGE = "v4: Fixed after rollback"
THEME_COLOR = "#fff4e5"
ステップ2:リリースする
ここまで実施してきた内容と、同じになります。
- GitHub Actions がビルドし、CodePipelineを起動する
- デプロイが「テストトラフィック移行後」で一時停止する
- テスト側(8080番ポート)で、ブラウザにてv4を確認します。本番側(80番ポート)はまだv2のままです。
- 「アクションを実行する」→ 一時停止フックアクションを実行するで「続行」を選択し、「確認」ボタンを押下し本番側にリリースされることを確認します。
-
今回はロールバックせずに、ベイク時間(5分)が過ぎるのを待ちます。
ステップ3: 最終状態を確認する
ベイク時間が過ぎたら、次の 3 か所を確認します。
| 確認 | 期待される状態 |
| 本番側のhttp://<ALB の DNS 名>/をブラウザで確認 | v4が表示される |
| ECS サービスの「デプロイ」タブ | 最新のデプロイが完了する |
| CodePipeline の実行履歴 | v2 = 成功、v3 = 失敗(ロールバック)、v4 = 成功 |
画面の「アクセス回数」も見てみてください。v1 から v4 まで、何度デプロイしてもロールバックしても、カウンターは途切れずに増え続けています。アプリのタスクは何度も入れ替わりましたが、データは RDS に残り続けていることの確認にもなります。
これで本記事は完了です。 おつかれさまでした。料金がかかり続けるので、クリーンアップ章の手順で後片付けを行ってください。12章のコラムは、後片付けの後に読んでいただいて構いません。
12.コラム
12-1. DB を共有する Blue/Green の落とし穴 ―「expand / contract」
11-4 で見たとおり、BlueとGreenは同じ DB を同時に使います。ベイク時間中も、ロールバック後も同じです。そのため、スキーマ変更を伴うリリースでは注意が必要です。
たとえば、v2でaccess_log.app_version列をversionに名前変更したとします。
- Green(v2)が起動する前に列名を変えると、本番で動いている Blue(v1)が壊れる
- 列名を変えた後にロールバックすると、戻った v1 が動かない
これを避ける定番の考え方が expand/contract(拡張と縮小)です。
| ステップ | リリース内容 | DB | 動くバージョン |
| 1.expand | 新しい列versionを追加。アプリは両方の列に書き込む。 | 古い列・新しい列が共存 | 旧アプリも新アプリも動く |
| 2.移行 | 既存データを新しい列にコピー。アプリは新しい列を読む。 | 共存 | 同上 |
| 3.contract | 十分に安定したら、古い列を削除 | 新しい列のみ | 新アプリのみ |
ポイントは、どのリリースの時点でも「1つ前のバージョンのアプリ」が動くDBであり続けることです。こうしておけば、ベイク時間中のロールバックが常に安全に行えます。
今回の初期化タスクは、このマイグレーションを流す場所としてそのまま使えます。本番では「マイグレーション用のタスク定義をCodePipelineのDeployの前にRunTaskで実行する」ステージを足す構成もよく見られます。
12-2. DB 認証情報の管理方式を比較する
DB の認証情報の置き場所として、よく使われる方式を比較します。
| 観点 | Parameter Store(SecureString) | Secrets Manager |
IAM DB 認証(今回使用した方法) |
| アプリが持つもの | 固定パスワード | パスワード(ローテーション可) | なし(15 分有効のトークンをその場で生成) |
| ローテーション | 自前で作る | 自動 | 不要 |
| 漏えい時の影響 | 変更するまで使われ続ける | 次のローテーションまで | トークンは 15 分で失効 |
| 権限の管理 | パラメータの読み取り権限 | シークレットの読み取り権限 | タスクロールのrds-db:connect |
| 料金 | 標準パラメータは無料 | 1シークレットあたり月額課金 | 追加料金なし |
| 注意点 | パスワードを知っている人が増えやすい | ECSのsecrets
による注入はタスク起動時のみ |
TLS 接続が必須。新規接続の頻度が非常に高い用途には不向き。 |
-
Secrets Managerの「起動時のみ」問題は、本番でよく踏む落とし穴です。ECSはタスクの起動時にシークレットを環境変数へ入れますが、その後ローテーションでDBのパスワードが変わっても、動いているタスクの環境変数は古いままです。結果として、コネクションを張り直した瞬間に接続できなくなります。対策としては、アプリが接続のたびにSDK でシークレットを取り直す、タスクを定期的に入れ替える、などがあります。
-
IAM 認証ではこの問題自体が起きません。今回の構成でマスターパスワードを Secrets Manager で管理しているのは、人も日常のアプリも使わない、非常用の鍵という位置づけだからです。初期化タスクは実行のたびに最新の値を取得するので、ローテーションの影響も受けません。
12-3. ヘルスチェックで DB を見ない理由
今回の”/health”は、DBに接続せずにokを返すだけにしています。「DBに接続できるかまで確認したほうが安全では?」と思うかもしれません。
ヘルスチェックでDBを見ると、DBが一時的に不調になっただけで、ALBが全タスクをunhealthyと判断します。ECSはそのタスクを停止して新しいタスクを起動しますが、DBが不調なのは変わらないので、新しいタスクもすぐにunhealthyになります。結果として、アプリは「DBを使わないページすら返せない」状態になり、タスクの入れ替えが延々と続きます。
ヘルスチェックは「このタスクを入れ替えれば直るか」を判断するためのもの、と考えると分かりやすいです。DBの不調はタスクを入れ替えても直らないので、ヘルスチェックの対象外にし、代わりにアラームで検知します。
12-4. IAM 認証を本番で使うときの補足
-
トークンが必要なのは接続を張るときだけです。一度つながったコネクションは、トークンの有効期限(15分)が過ぎても切れません。コネクションプールを使う場合は、「新しいコネクションを張るたびにトークンを生成する」ように書けば問題ありません。
-
今回のアプリはリクエストごとに接続しています(学習のための単純化です)。本番ではコネクションプールを使い、接続数の多い構成ではRDS Proxyの導入も検討してください。RDS ProxyもIAM認証に対応しています。
12-5.ECR Public へのログインだけ、なぜ us-east-1 なのか
ワークフローでは、東京リージョンで作業しているのに、「Login to Amazon ECR Public」のステップだけAWS_REGION: us-east-1を指定しています。「東京に揃えるべきでは?」と感じた方もいると思います。理由は、ECR Public の仕組みにあります。
| ECR(プライベート) | ECR Public | |
| アドレス | <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com | public.ecr.aws |
| リージョン | リポジトリごとに特定のリージョンに属する |
特定のリージョンに属さない(グローバル) |
| ログイン(認証)のAPI | 各リージョンにある |
一部のリージョンにしかない(us-east-1とus-west-2) |
ECR Public は、世界中どこからでも同じアドレス(public.ecr.aws)で使えるレジストリです。その一方で、ログインのための API(ecr-public:GetAuthorizationToken)は us-east-1とus-west-2にしか用意されていません。ap-northeast-1を指定してログインしようとすると、東京リージョンには ECR Public の API がないため失敗します。
一度ログインしてしまえば、取得した認証情報は public.ecr.aws 全体に対して有効です。後続の docker build でのベースイメージの取得は、リージョンを意識せずにそのまま動きます。
リージョンの切り替えがほかのステップに影響しないか: ワークフローの中で、「env:」をステップの中に書いているので、us-east-1 になるのはこのステップだけです。
| ステップ | リージョン | 相手 |
| Login to Amazon ECR | ap-northeast-1 | 自分のアカウントの ECR |
| Login to Amazon ECR Public | us-east-1 | ECR Public |
| Build and push image | ap-northeast-1 | 東京の ECR に push |
| Start CodePipeline | ap-northeast-1 | 東京のパイプライン |
参考リンク:Amazon ECR Public エンドポイントとクォータ
12-6:ECR Public のレート制限ついて
ベースイメージにpublic.ecr.awsを使用しているのは、Docker Hub の匿名pull制限を避けるためですが、ECR Publicにも制限はあります。
| pull の種類 | 上限 |
| 未認証 | 1 回/秒 |
| 認証済み | 10 回/秒 |
| ECS・Fargate・EC2 からの pull | 10 回/秒(未認証でも) |
※参考URL Amazon ECR Public endpoints and quotas
1 回のビルドでbaseイメージを1回pullするだけなら、未認証でも問題ありません。それでもワークフローでログインしているのは、CIの同時実行が増えたときに効いてくるためです。複数のブランチでビルドが同時に走ると、未認証の1 回/秒はすぐに頭打ちになります。また、ECSやFargateからのpullは、未認証でも認証済みと同じ上限になります。
クリーンアップ
今回の構成は時間課金のリソースが多いので、必ずその日のうちに削除してください。 依存関係があるので、上から順に実施します。
| 順番 | リソース | 操作 |
|---|---|---|
| 1 | ECS サービス | ecs-private-service →「サービスを削除」(強制削除にチェック) |
| 2 | ECS クラスター | ecs-private-cluster →「クラスターの削除」 |
| 3 | タスク定義 | ecs-private-appとecs-private-init それぞれをクリック、全リビジョンを「登録解除」します。タスク定義画面にもどり、更新ボタンで削除されていることを確認します。 |
| 4 | CodePipeline | ecs-private-pipelineを削除 |
| 5 | S3 | CodePipeline のアーティファクト用バケット(codepipeline-ap-northeast-1-…)を空にして削除 注意:バケットの中をみて、他の人や別プロジェクトで使用している場合は、バケットを削除せずに自分が使用したフォルダのみ削除してください。フォルダ名や実施した日付及び時間で検討がつくはずです。 |
| 6 | ECR | ecs-private-appを削除 |
| 7 | RDS |
ecs-private-dbを削除 ・最終スナップショットは作成しない → チェックを外す マスター認証情報のシークレットも自動で削除されます |
| 8 | RDS |
DB サブネットグループecs-private-db-subnetを削除(すぐに削除できない場合は、DBインスタンス削除が完了するまで待ってから再度実行します。) |
| 9 | EC2 |
・ALB ecs-private-alb を削除 |
| 10 | VPC |
Interface エンドポイント 4 つを削除(削除完了まで数分待つ) 対象となるインターフェイスエンドポイント |
| 11 | VPC |
DSとInterfaceエンドポイントの削除完了を待ってから実行します。 ecs-private-vpcを削除(サブネット、ルートテーブル、IGW、S3 Gateway エンドポイント、SG も一緒に削除される) |
| 12 | IAM | ロール ecs-private-execution-role、ecs-private-init-execution-role、ecs-private-app-task-role、ecsInfrastructureRoleForLoadBalancers、ecs-private-github-actions、AWSCodePipelineServiceRole-ap-northeast-1-ecs-private-pipeline を削除 |
| 13 | IAM | 他の人や別プロジェクトで使用している場合は、絶対に削除しないでください。 ID プロバイダtoken.actions.githubusercontent.comを削除 |
| 14 | CloudWatch Logs | /ecs/ecs-private-app、/ecs/ecs-private-init、/aws/codepipeline/ecs-private-pipeline を削除 |
| 15 | GitHub | リポジトリの Variables を削除、またはリポジトリごと削除 |
VPC が削除できないとき:VPC の中にネットワークインターフェイスが残っていると削除できません。Interface エンドポイント、ALB、RDS、ECS タスクのいずれかが削除中のことが多いので、数分待ってから再実行してください。
まとめ
- プライベートサブネット = インターネットへの経路がないサブネット。ルートテーブルに 0.0.0.0/0 がないことで実現している
- Fargate の起動には ECR(api/dkr)、S3、Logs のエンドポイントが必要。S3はECRのレイヤーの実体があるため、忘れるとpullに失敗する
- 通信の許可はセキュリティグループの連鎖で表現する。ALB → アプリ → DB / エンドポイントの順に、IPではなく SG を参照する
- アプリはパスワードを持たない。IAMデータベース認証で、タスクロールのrds-db:connectだけで接続する
- タスク実行ロールとタスクロールを分ける。マスターパスワードを読めるのは初期化タスクの実行ロールだけ
- DBの初期化・マイグレーションはECSの単発タスクで行う。プライベートな DB に届く場所で、ログを残して実行できる
- ECRをイミュータブルにし、GitHub Actions がコミットIDを渡してパイプラインを起動する。どのコミットがデプロイされたかが、ECR・パイプライン・画面のすべてで一致する
- 人の判断は2回。一時停止で「出してよいか」、ベイク時間で「出してよかったか」を判断する
- BlueとGreen は同じDBを共有する。スキーマ変更は expand / contract で、常に1つ前のバージョンが動くようにする
- ロールバックは本番を戻すだけで、ソースコードは戻らない。ロールバックの後は、新しいコミットで修正版をリリースするところまでをセットで行う
以上で全て完了です。最後まで読んでいただき、ありがとうございました。
本記事でトラブル発生時の参考
| 症状 | 原因と対処 |
| Actions のログにAssuming role with OIDCが何行も繰り返され、最後に失敗する |
信頼ポリシーのsubが一致していない。CloudTrail のAssumeRoleWithWebIdentityイベントから実際の値をコピーする(5-3)。リポジトリ名・ユーザー名の綴りも確認。 |
| 初期化タスクがecr:GetAuthorizationTokenのAccessDeniedExceptionで起動前に停止 | 実行ロールにAmazonECSTaskExecutionRolePolicy
が付いていない(6-3)。タイムアウトではなく AccessDenied なら、エンドポイントまでは到達できている |
| 初期化タスクのログにpassword authentication failed for user “postgres” | DB_MASTER_PASSWORD の値のタイプが「値」のままで、ARN の文字列がパスワードとして渡っている(7-2)。新しいリビジョンで ValueFrom に修正する。 |
| ターゲットグループのターゲットがRequest timed outでunhealthy | sg-appのインバウンドのポートが 8000 になっているか(8080 と取り違えやすい) サービスのセキュリティグループがdefaultのままになっていないか |
| サービス作成画面でテストリスナールールが選べない | 作成画面では、選んだリスナー(HTTP:80)のルールしか候補に出ない。空欄で作成し、9-3 の「サービスを更新」で 8080 を追加する |
|
デプロイが「ロールバックに失敗」 |
初回のデプロイから一度も正常になっていないため、戻り先がない。原因を直してから「新しいデプロイの強制」で更新する(9-3) |
| テスト(8080)は表示されるが、本番(80)が 503 Service Temporarily Unavailable |
本番の転送先(Blue)に正常なターゲットがない。初回構築時のトラブルから復旧する途中で起きる。一時停止中なら「続行」で本番が Green に切り替わり解消する(9-3) |
| タスクが CannotPullContainerError (タイムアウト)で停止する |
① ecr.api / ecr.dkr のエンドポイントがない、または「DNS 名を有効化」がオフ ② sg-vpce のインバウンドに sg-app からの 443 がない ③ S3 Gateway エンドポイントがプライベートサブネットのルートテーブルに関連付いていない |
| ResourceInitializationError: unable to retrieve secret |
① Secrets Manager のエンドポイントがない ② 実行ロールにシークレットの読み取り権限がない(cs-private-init-execution-roleを選んだか確認) ③ ValueFrom の末尾の :username::/ :password:: の書き方 |
| ResourceInitializationError: failed to validate logger args / ロググループ作成エラー |
① Logs のエンドポイントがない ② 実行ロールにlogs:CreateLogGroupがない(6-2) |
| 初期化タスクのログにconnection timeout |
sg-dbのインバウンドにsg-appからの 5432 がない。初期化タスクのセキュリティグループが sg-app になっているか確認 |
| “/”が500エラー。ログにcertificate verify failed |
DB_HOSTがエンドポイント名になっているか(IP アドレスだと証明書の検証に失敗する) |
| “/”が500エラー。ログにdatabase “appdb” does not exist |
RDS 作成時に「最初のデータベース名」を入れ忘れた。DB_NAMEをpostgresにして初期化タスクからやり直す。 |
| サービス作成画面で green のターゲットグループが選べない |
ALB のリスナーに、blue と green の両方を重み付きで設定していない(8-2) |
| Actions の Start CodePipeline が AccessDenied |
ecs-private-github-actionsのポリシーのパイプライン ARN(名前・リージョン・アカウント ID)を再確認する。 |
| パイプラインの Source が失敗する |
手動の「変更をリリース」や作成直後の実行では、 latest を探しに行って失敗する。GitHub Actions から起動する。 |
| パイプラインの Build が IMAGE_TAG is empty で失敗 |
変数なしで起動された。GitHub Actions から起動する。 |
| パイプラインの Deploy が権限エラー |
10-4 のインラインポリシー。特に「iam:PassRole」の対象ロール名を再確認する。 |
| デプロイが一時停止のまま進まない |
「アクションを実行する」→「続行」を押していない。30 分放置するとロールバックされる。 |