0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go(Gin)APIをECS Fargate・RDS・ALB・Route 53でマルチAZ構成にデプロイする

0
Posted at

はじめに

Go(Gin)で作成したWeb APIをAWSへデプロイしました。

コンテナはAmazon ECS on AWS Fargate、データベースはAmazon RDS for PostgreSQLで実行します。Application Load Balancer(ALB)、Route 53、AWS Certificate Manager(ACM)を組み合わせ、独自ドメインからHTTPSでアクセスできるマルチAZ構成にしました。

この記事では、Dockerfileの準備からECR、ECS、RDS、ALB、Route 53の設定までを順番にまとめます。

構築要件

  • ECS Fargateでアプリケーションを実行する
  • Amazon RDS for PostgreSQLを使用する
  • 2つのアベイラビリティゾーンを利用する
  • Application Load Balancerを使用する
  • Route 53で独自ドメインを管理する
  • ACMでSSL/TLS証明書を発行する
  • HTTPをHTTPSへリダイレクトする
  • ECSタスクのヘルスチェックを有効にする
  • RDSをインターネットへ直接公開しない

構成概要

項目 設定
リージョン 東京(ap-northeast-1)
VPC CIDR 10.0.0.0/24
AZ ap-northeast-1a / ap-northeast-1c
コンテナレジストリ Amazon ECR
コンテナ実行環境 Amazon ECS on AWS Fargate
ECSサービス Desired count 2
ロードバランサー Application Load Balancer
データベース Amazon RDS for PostgreSQL(Multi-AZ)
DNS Amazon Route 53
独自ドメイン my-golang-api.com
証明書 AWS Certificate Manager
アプリケーションポート 8080
PostgreSQLポート 5432
User
  ↓
Route 53
  ↓
Application Load Balancer
  ├─ HTTP:80 → HTTPS:443へ301リダイレクト
  └─ HTTPS:443 → ターゲットグループ
                    ↓
              ECS Fargate
                    ↓
            RDS for PostgreSQL

1. DockerfileをFargate向けに準備する

ビルド環境・開発環境・本番環境を分離するマルチステージビルドを使用しました。

FROM golang:1.26-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server .

FROM builder AS development

RUN go install github.com/air-verse/air@latest
CMD ["air", "-c", ".air.toml"]

FROM alpine:3.20 AS production

WORKDIR /app

RUN apk add --no-cache ca-certificates tzdata curl

COPY --from=builder /app/server /app/server

EXPOSE 8080

CMD ["/app/server"]

マルチステージビルド

Go SDKが必要なのはコンパイル時だけです。本番イメージにはコンパイル済みバイナリだけをコピーし、イメージサイズと不要な依存関係を減らします。

  • builder:Goアプリケーションをコンパイル
  • development:Airを利用するローカル開発環境
  • production:ECSで実行する本番環境

本番用は明示的にproductionを指定します。

docker build --target production -t my-golang-app:v⚪︎ .

⚪︎はビルドする数字を記入する。

OSとCPUアーキテクチャを一致させる

GOOS=linuxはFargateのLinux環境で実行するための指定です。CGO_ENABLED=0により、外部Cライブラリへの依存を減らします。

Apple SiliconのMacでECSタスク定義がX86_64の場合は、次のようにビルドします。

docker buildx build \
  --platform linux/amd64 \
  --target production \
  -t <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/my-golang-app:v2 \
  --push .
Dockerイメージ ECSタスク定義
linux/amd64 X86_64
linux/arm64 ARM64

不一致の場合、ECSでexec format errorなどが発生します。

ポートを統一する

設定箇所 ポート
Gin 8080
Dockerfile 8080
ECSタスク定義 8080
ターゲットグループ 8080
ECS用セキュリティグループ 8080

EXPOSE 8080だけで通信が許可されるわけではありません。実際の通信可否はECSのポートマッピング、セキュリティグループ、ターゲットグループで決まります。

Ginはコンテナ外部から接続できる状態で待ち受けます。

if err := router.Run(":8080"); err != nil {
    log.Fatal(err)
}

127.0.0.1:8080だけで待ち受けると、ALBから接続できません。

ヘルスチェックを用意する

router.GET("/health", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "ok"})
})

ECSとALBのヘルスチェックパスを/healthへ統一します。タスク定義のコンテナヘルスチェック例は次のとおりです。

{
  "command": [
    "CMD-SHELL",
    "curl -fsS http://localhost:8080/health || exit 1"
  ],
  "interval": 30,
  "timeout": 5,
  "retries": 3,
  "startPeriod": 30
}

ヘルスチェックで使用するcurlwgetが本番イメージに存在しないと、アプリケーションが動いていてもタスクはUNHEALTHYになります。

機密情報を含めない

DBパスワードやAWSアクセスキーをDockerfileのENVへ書いてはいけません。削除後もイメージレイヤーに残る可能性があります。本番のDB認証情報はSecrets Managerに保存し、ECSタスク定義から参照します。

.dockerignoreも用意します。

.git
.gitignore
.env
.env.*
README.md
tmp
coverage.out
*.log
.DS_Store

2. VPCとサブネットを作成する

  • VPC名:my-golang-app-vpc
  • IPv4 CIDR:10.0.0.0/24
  • DNS解決:有効
  • DNSホスト名:有効

用途ごとに6つのサブネットを作成します。

用途 AZ CIDR
Public subnet 1a 10.0.0.0/27
Public subnet 1c 10.0.0.32/27
ECS Private subnet 1a 10.0.0.64/27
ECS Private subnet 1c 10.0.0.96/27
DB Private subnet 1a 10.0.0.128/27
DB Private subnet 1c 10.0.0.160/27

ALBとNAT GatewayはPublicサブネット、ECSはECS用Privateサブネット、RDSはDB用Privateサブネットへ配置します。

3. Internet GatewayとNAT Gatewayを設定する

Internet GatewayをVPCへアタッチします。Publicルートテーブルには0.0.0.0/0 → Internet Gatewayを追加し、2つのPublicサブネットへ関連付けます。

PrivateサブネットのECSは、ECRからのイメージ取得やCloudWatch Logsへの送信にアウトバウンド通信が必要です。そのため、各AZのPublicサブネットへNAT GatewayとElastic IPを配置します。

  • ECS Private 1a → 1aのNAT Gateway
  • ECS Private 1c → 1cのNAT Gateway

DB用PrivateサブネットはVPC内部の通信だけでよいため、インターネット向けデフォルトルートを追加しません。すでに適切なルートテーブルが関連付けられている場合、重複して作成する必要はありません。

4. セキュリティグループを作成する

ALB用

プロトコル ポート ソース
HTTP 80 0.0.0.0/0
HTTPS 443 0.0.0.0/0

ECS用

プロトコル ポート ソース
TCP 8080 ALB用セキュリティグループ

RDS用

プロトコル ポート ソース
PostgreSQL 5432 ECS用セキュリティグループ

接続元にはIPアドレスではなくセキュリティグループを指定します。

5. RDS for PostgreSQLを作成する

異なるAZのDB用Privateサブネットを登録したDBサブネットグループを作成します。

  • 名前:my-golang-app-db-subnet-group
  • DB識別子:my-golang-app-db
  • エンジン:PostgreSQL
  • DBクラス:db.t4g.micro
  • Multi-AZ:有効
  • パブリックアクセス:なし
  • ストレージ暗号化:有効
  • ポート:5432
  • セキュリティグループ:RDS用

アプリケーションはRDSエンドポイントへ接続します。

6. Secrets ManagerへDB接続情報を登録する

{
  "host": "<RDS_ENDPOINT>",
  "port": "5432",
  "dbname": "appdb",
  "username": "<DB_USER>",
  "password": "<DB_PASSWORD>"
}

ブログやスクリーンショットへ実際の値を掲載しないよう注意します。

7. ECRへDockerイメージをpushする

ECRにmy-golang-appリポジトリを作成し、認証します。

aws ecr get-login-password --region ap-northeast-1 \
  | docker login \
      --username AWS \
      --password-stdin \
      <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com
docker buildx build \
  --platform linux/amd64 \
  --target production \
  -t <AWS_ACCOUNT_ID>.dkr.ecr.ap-northeast-1.amazonaws.com/my-golang-app:v2 \
  --push .

latestだけでなく、バージョンやGitコミットハッシュなど、デプロイ内容を識別できるタグを使います。

ECRイメージタグv2とECSタスク定義リビジョンmy-golang-app:2は別の概念です。

8. ECSクラスターとタスク定義を作成する

  • クラスター:my-golang-app-cluster
  • 起動タイプ:Fargate
  • タスク定義ファミリー:my-golang-app
  • リビジョン:2
  • ネットワークモード:awsvpc
  • コンテナポート:8080
  • ロググループ:/ecs/my-golang-app
  • DB認証情報:Secrets Manager
  • コンテナヘルスチェック:有効

タスク実行ロールには、ECRからの取得、CloudWatch Logsへの送信、対象シークレットの読み取りに必要な権限を付与します。

9. ターゲットグループを作成する

  • 名前:my-golang-app-tg
  • ターゲットタイプ:IP
  • プロトコル:HTTP
  • ポート:8080
  • ヘルスチェック:/health

Fargateのawsvpcでは、ターゲットタイプに「インスタンス」ではなく「IP」を指定します。

10. ALBを作成する

  • 名前:my-golang-app-alb
  • スキーム:インターネット向け
  • サブネット:Public subnet 1a / 1c
  • セキュリティグループ:ALB用

最初はHTTP:80からターゲットグループへ転送し、ALB経由でアプリケーションへ到達できることを確認します。

11. ECSサービスを作成する

  • サービス:my-golang-app-service
  • タスク定義:my-golang-app:2
  • Desired count:2
  • サブネット:ECS Private subnet 1a / 1c
  • パブリックIP:無効
  • セキュリティグループ:ECS用
  • ターゲットグループ:my-golang-app-tg
  • コンテナポート:8080

次の状態を確認します。

Desired tasks: 2
Running tasks: 2
Pending tasks: 0

ECSのHealth statusと、ターゲットグループの2つのIPターゲットがHealthyになれば正常です。

12. Route 53とACMを設定する

Route 53でmy-golang-api.comを取得し、Public Hosted Zoneを確認します。

ACMを東京リージョンへ切り替え、次のドメインで証明書をリクエストします。

my-golang-api.com
*.my-golang-api.com

DNS検証を選択し、Route 53へ検証用CNAMEを追加します。ALBとACM証明書は同じリージョンに作成する必要があります。

13. ALBにHTTPSリスナーを追加する

  • HTTPS:443 → my-golang-app-tgへ転送
  • 証明書:東京リージョンのACM証明書

HTTP:80リスナーはHTTPSへリダイレクトします。

項目 設定
アクション URLへリダイレクト
プロトコル HTTPS
ポート 443
ホスト #{host}
パス /#{path}
クエリ #{query}
ステータス HTTP 301

完全なURLを入力する画面では次の形式です。

https://#{host}:443/#{path}?#{query}

14. Route 53のA AliasをALBへ設定する

  • レコード名:空欄(ルートドメイン)
  • タイプ:A
  • エイリアス:有効
  • ルーティング先:Application Load Balancer
  • リージョン:東京
  • ALB:my-golang-app-alb
  • ポリシー:シンプル

ALBには通常の固定IPではなくAliasレコードを使用します。

15. 動作確認

curl -I http://my-golang-api.com

301 Moved PermanentlyとHTTPSのLocationが返れば正常です。

curl -i https://my-golang-api.com/albums

HTTP 200とAPIレスポンスが返れば、Route 53、ACM、ALB、ECS、RDSを経由する通信が成功しています。

トラブルシューティング

ECSタスクが起動しない

  • ECRのイメージURIとタグを確認する
  • イメージとタスク定義のCPUアーキテクチャを一致させる
  • タスク実行ロールを確認する
  • ECS PrivateサブネットからNAT Gatewayへ到達できるか確認する
  • CloudWatch Logsを確認する
  • Secrets Managerの読み取り権限を確認する

ターゲットがUnhealthyになる

  • アプリケーションが0.0.0.0:8080で待ち受けているか
  • ECS用SGがALB用SGからTCP 8080を許可しているか
  • ターゲットグループのポートが8080か
  • ヘルスチェックパスがHTTP 200を返すか
  • コンテナにヘルスチェックコマンドが存在するか

ECSのHealth statusがUNKNOWNになる

ALBのターゲットがHealthyでも、タスク定義にコンテナヘルスチェックがないとECSのHealth statusはUNKNOWNになることがあります。ヘルスチェックを追加した新しいタスク定義リビジョンでサービスを更新します。

RDSへ接続できない

  • RDS用SGの接続元がECS用SGか
  • PostgreSQLの5432番ポートが許可されているか
  • RDSエンドポイントとDB名が正しいか
  • ECSとRDSが同じVPCか
  • RDSがavailable

HTTPS証明書を選択できない

ALBとACM証明書のリージョンを確認します。今回は両方とも東京リージョンです。

まとめ

ALBだけをPublicサブネットへ配置し、ECSタスクとRDSをPrivateサブネットへ配置しました。

  • インターネット → ALB:80、443
  • ALB → ECS:8080
  • ECS → RDS:5432

ALB、ECSタスク、NAT Gateway、RDSを2つのAZへ分散し、独自ドメインからHTTPSでアクセスできる構成になりました。

Dockerfile、DockerイメージのCPUアーキテクチャ、コンテナポート、ヘルスチェックコマンドをAWS側の設定と一致させることが、ECRからECSへのデプロイをスムーズに進めるポイントです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?