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?

Terraform + user_data で EC2 に「apply 1発」自動デプロイ。手で踏んだ罠を、二周目は先回りで全部避けた

0
Posted at

はじめに

Spring Boot + React の小さなアプリを勉強で作っています。

一度、手作業で EC2 にデプロイしました。SSHして、Docker入れて、EC2の中でビルドして…と、その道中でメモリ不足やらビルドツール不足やらの罠を踏みまくりました。そこで二周目は Terraform + user_data で「terraform apply 1発」の自動デプロイに作り直すことに。

面白かったのは、一周目で踏んだ罠の在り処を全部知っていたこと。おかげで二周目は、同じ穴の手前で全部よけられました。遠回りに見えた一周目が、そのまま二周目の地図になった記録です🗺️

※「apply 1発」と言っても、事前に イメージのビルド → ECRへpushterraform initAWS認証の設定 は済ませてあります。ここでの「1発」は、ECRにイメージがある状態から、EC2の構築〜起動までを自動化した、という意味です。

環境

  • Java 21 / Spring Boot 3.5系(Docker化済み)
  • AWS(EC2 / ECR / IAM)
  • Terraform 1.x
  • ローカルは Apple Silicon(arm64)の Mac

設計の核:EC2では「ビルドしない」

一周目は EC2 の中で Gradle ビルドをして、1GBメモリのインスタンスでメモリ不足寸前になりました。二周目は発想を変えます。

  • 先に手元でイメージをビルド → ECR(AWSのプライベートな倉庫)に push
  • EC2 は起動時に ECR から pull して動かすだけ(=EC2ではビルドしない)

これで「重いビルドをEC2でやる」悩みが丸ごと消えます。

罠①:CPUアーキの違い(手元で焼くなら最重要)

手元の Mac は arm64、EC2 は x86_64。何も考えずにビルドすると arm64 のイメージができ、EC2 では exec format error で動きません。

→ ビルド時に --platform linux/amd64 を付けて x86_64 向けに焼く。

docker build --platform linux/amd64 \
  -t <ECRのURL>:latest ./backend
 => [backend build 4/5] RUN ./gradlew bootJar
 => => # BUILD SUCCESSFUL in 59s
 => exporting to image
 => => naming to <ECRのURL>:latest   ✔ DONE

x86_64 で焼けたイメージを ECR に push しておきます。

罠②:コンテナ内の「localhost」はDBに届かない

今回は EC2上の Docker Compose で、アプリコンテナと DBコンテナ(PostgreSQL)を同じネットワークで動かしています(DBはRDSではなく同居コンテナ)。この構成だと、アプリの接続先が localhost だとコンテナ自身を指してDBに届きません。

→ compose の環境変数で DBの宛先をサービス名(db)に上書き

backend:
  image: <ECRのURL>:latest
  environment:
    SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/appdb   # localhost ではなく db
  depends_on: [db]

罠③:鍵を機械に置きたくない

EC2 が ECR から pull するには認証が要りますが、アクセスキーを user_data に直書きするのは危険。

IAMロールを EC2 に付ける。一時資格が自動で配られ、長期の鍵を機械に置かずに済みます(Terraform では aws_iam_roleaws_iam_instance_profile、ポリシーは ECR 読み取り専用)。

罠④:user_data の中の YAML が崩れる

user_data に compose(YAML)を直書きすると、Terraform のヒアドキュメントで字下げが崩れがち(YAMLは字下げが命)。

別のテンプレートファイルに出して templatefile() で読み込む。

user_data = templatefile("${path.module}/user_data.sh.tftpl", {
  image = "${aws_ecr_repository.backend.repository_url}:latest"
})

user_data は初回起動時に走ります。うまく立ち上がらない時は、EC2 内の /var/log/cloud-init-output.log で出力とエラーを確認しました。

動かしてみる

terraform applyyes。作られるのはEC2・SG・IAMロール一式:

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Outputs:
public_ip = "<公開IP>"

この時点ではEC2が起動しただけで、user_data はまだ裏で走っています(Docker導入→pull→compose)。1分ほど待って curl

curl -s http://<公開IP>:8080/posts
[]

200[](新しいDBなので中身は空)。SSHを一度もしていないのに、サーバーが立ち上がってアプリまで動いている。これは気持ちいい。

⚠️ ここで開けている 8080 は学習用です。本番ではセキュリティグループで公開範囲を絞り、ALB や nginx での HTTPS 化を検討します。

使い終わったら片付け:

Destroy complete! Resources: 6 destroyed.

ただし AWS では destroy しても、ECRのイメージ保存料・EBSスナップショット・確保したままのElastic IP・ログなどが残ることがあります。料金を抑えるには「不要なものが残っていないか」も確認します(今回は ECR を force_delete で一緒に消す設定にしました)。

学び

  • EC2でビルドしない(事前ビルド→ECRからpull)だけで、メモリ不足やビルドツールの悩みが消える
  • arch違いは --platform で先回り(忘れると exec format error
  • 権限は鍵でなく IAMロールでマシンに与える
  • 片付けは「destroyして終わり」ではなく、残りリソースも確認する
  • 手で一度やってからコード化すると、罠の位置が分かっていて先回りできる

おわりに

エラーに突っ込んでから直す一周目も勉強になりますが、罠の地図を持って二周目を走るのは、また別の気持ちよさがありました。急がば回れ、を地で行った感じです。

同じく「自動デプロイ、何から…」で止まっている人の最初の一歩になれば嬉しいです🙌

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?