そういう場面でTerraformを使えば、ALBの構成全体をHCLで宣言し、
terraform apply一発で再現できます。この記事では、TerraformでALBを設計するときに欠かせない aws_lb・aws_lb_target_group・aws_lb_listener の3リソースの関係を整理し、HTTP→HTTPSリダイレクト・ヘルスチェック設計・path-basedルーティングまでを実例のHCLで解説します。AWS provider 5.x・Terraform 1.9で動作を確認しています。
この記事のポイント
・ALBはaws_lb・aws_lb_target_group・aws_lb_listenerの3リソースで構成される
・HTTP→HTTPSリダイレクトはaws_lb_listenerのredirectアクションで1本宣言できる
・ヘルスチェックのthreshold値を絞りすぎると正常インスタンスが外れ続ける
・path-basedルーティングはaws_lb_listener_ruleのpriority順で評価される
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜALBをTerraformで管理するのか
AWS Management ConsoleでALBを作ると、「どのセキュリティグループを付けたか」「ヘルスチェックのパスは何だったか」「どのルーティングルールを追加したか」といった設定が画面の奥深くに埋もれていきます。変更履歴は残らず、メンバーが別のメンバーの設定を上書きしても気づきにくい。TerraformでALBを管理すると、構成全体がHCLファイルとして残ります。
terraform planでどこが変わるかを事前に確認でき、git diffでレビューも通せます。コンソール手作業の暗黙的な設定が全て可視化されるのが最大のメリットです。aws_lb・aws_lb_target_group・aws_lb_listenerの構成関係
TerraformでALBを作るとき、最低限必要なリソースは次の3つです。・aws_lb:ALB本体。DNSエントリやIPアドレスはここに紐づく
・aws_lb_target_group:リクエストを転送する先のグループ。ヘルスチェック設定もここに書く
・aws_lb_listener:ポートとプロトコルのルール。どのターゲットグループへ転送するかを決める
3つの参照関係は「aws_lb_listenerがaws_lbのARNを参照し、default_actionの中でaws_lb_target_groupのARNを参照する」流れになります。aws_lb_target_groupはaws_lbのARNを参照しない点に注意してください。ターゲットグループは単独で作れます。どのALBに紐づくかはリスナー側が決めます。
ALB本体(aws_lb)の設計
1. internet-facingとinternalの選択基準
internal = falseがインターネット向け(パブリックサブネット)、internal = trueがVPC内専用(プライベートサブネット)です。外部からリクエストを受けるWebアプリケーションはinternal = false一択です。バックエンドAPIやマイクロサービス間通信にだけ使うALBはinternalにして、インターネットからアクセスできない構成にします。# ALB本体 resource "aws_lb" "main" { name = "app-alb" internal = false load_balancer_type = "application" security_groups = [aws_security_group.alb.id] subnets = [ aws_subnet.public_a.id, aws_subnet.public_c.id, ] # アクセスログをS3へ保存(本番では有効化推奨) # access_logs { # bucket = aws_s3_bucket.alb_logs.bucket # prefix = "app-alb" # enabled = true # } }
2. セキュリティグループとサブネットの指定
ALBに付けるセキュリティグループには、受け付けるポートへのインバウンドを開けます。外部からHTTP(80)とHTTPS(443)を受ける場合は次のように書きます。resource "aws_security_group" "alb" { name = "alb-sg" vpc_id = aws_vpc.main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }
ターゲットグループのヘルスチェック設計(aws_lb_target_group)
1. ヘルスチェックのパスとthresholdを決める
ヘルスチェックで見落としがちなのがthreshold(正常判定に必要な連続成功回数)の設定です。デフォルト(3回)のままにすると、デプロイ直後にインスタンスがヘルシーになるまで時間がかかりすぎます。逆に1回にするとノイズでインスタンスが外れやすくなります。現場ではhealthy_threshold = 2、unhealthy_threshold = 3に調整することが多いです。resource "aws_lb_target_group" "app" { name = "app-tg" port = 8080 protocol = "HTTP" vpc_id = aws_vpc.main.id health_check { path = "/health" protocol = "HTTP" matcher = "200" interval = 30 timeout = 5 healthy_threshold = 2 unhealthy_threshold = 3 } }
matcherには期待するHTTPステータスコードを書きます。"200"のように単一指定も、"200-299"のように範囲指定もできます。アプリケーションが302を返すパスをヘルスチェックに使うと永遠にUnhealthyになるため注意してください。2. ターゲットタイプ:instanceとipの使い分け
target_typeのデフォルトはinstanceです。EC2インスタンスIDで登録する通常のケースはこれで問題ありません。ECSのFargateタスクに転送したい場合はipを指定します。Fargateはインスタンスを持たないため、コンテナのIPアドレス直接で登録する必要があります。# Fargate向けターゲットグループ resource "aws_lb_target_group" "fargate" { name = "fargate-tg" port = 3000 protocol = "HTTP" vpc_id = aws_vpc.main.id target_type = "ip" # Fargateはipを指定 health_check { path = "/healthz" matcher = "200" } }
リスナーの設計(aws_lb_listener)
1. HTTP→HTTPSリダイレクト
ポート80(HTTP)へのアクセスを全てポート443(HTTPS)へ301リダイレクトする設定は、redirectアクションで1本書けます。resource "aws_lb_listener" "http_redirect" { load_balancer_arn = aws_lb.main.arn port = "80" protocol = "HTTP" default_action { type = "redirect" redirect { port = "443" protocol = "HTTPS" status_code = "HTTP_301" } } }
status_codeは"HTTP_301"(恒久リダイレクト)が一般的です。テスト用途で"HTTP_302"(一時的リダイレクト)を使う場合もありますが、本番では301に固定してください。ブラウザが302をキャッシュしないため、毎回リダイレクトを踏む無駄が生じます。2. HTTPSリスナーとACM証明書の参照
HTTPSリスナーにはACM証明書のARNを渡します。証明書がすでに発行済みであればdataブロックで参照し、新規発行が必要ならaws_acm_certificateリソースで作ります。# 既存証明書をdata sourceで参照する場合 data "aws_acm_certificate" "main" { domain = "example.com" statuses = ["ISSUED"] most_recent = true } resource "aws_lb_listener" "https" { load_balancer_arn = aws_lb.main.arn port = "443" protocol = "HTTPS" ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" certificate_arn = data.aws_acm_certificate.main.arn default_action { type = "forward" target_group_arn = aws_lb_target_group.app.arn } }
ssl_policyには最新のTLS 1.3対応ポリシーELBSecurityPolicy-TLS13-1-2-2021-06を指定するのが2024年以降の標準です。古いELBSecurityPolicy-2016-08はTLS 1.0/1.1を許可するため、PCI DSS準拠環境では使えません。
>> Terraform実践セミナーの詳細はこちら
ターゲットグループへのEC2登録(aws_lb_target_group_attachment)
固定のEC2インスタンスをターゲットグループに登録するにはaws_lb_target_group_attachmentを使います。resource "aws_lb_target_group_attachment" "app_a" { target_group_arn = aws_lb_target_group.app.arn target_id = aws_instance.app_a.id port = 8080 } resource "aws_lb_target_group_attachment" "app_c" { target_group_arn = aws_lb_target_group.app.arn target_id = aws_instance.app_c.id port = 8080 }
aws_lb_target_group_attachmentではなく、aws_autoscaling_groupのtarget_group_arnsにターゲットグループのARNを渡す方法を使います。固定登録とAuto Scalingを混在させると、Auto Scalingが起動したインスタンスとattachmentで登録したインスタンスが重複する場合があるため注意してください。path-basedルーティングで複数サービスを振り分ける
1つのALBに複数のサービスを乗せるときはaws_lb_listener_ruleでpath-basedルーティングを設定します。HTTPSリスナーに対してルールを追加し、/api/*はAPIサーバー、それ以外はWebサーバーに転送する構成は次のように書けます。# APIサーバー用ターゲットグループ resource "aws_lb_target_group" "api" { name = "api-tg" port = 8081 protocol = "HTTP" vpc_id = aws_vpc.main.id health_check { path = "/api/health" matcher = "200" } } # /api/* をAPIサーバーへ転送するルール resource "aws_lb_listener_rule" "api" { listener_arn = aws_lb_listener.https.arn priority = 100 # 低い数値が先に評価される action { type = "forward" target_group_arn = aws_lb_target_group.api.arn } condition { path_pattern { values = ["/api/*"] } } }
priorityは1~50000の範囲で指定します。数値が小さいほど先に評価されます。デフォルトアクション(aws_lb_listenerのdefault_action)は全ルールを評価した後に動くフォールバックです。priorityの重複はAPIエラーになるため、チームで番号帯を決めて管理してください(例:フロントエンド=100番台、API=200番台)。トラブルシュート|503エラー・ヘルスチェック失敗の切り分け
ALBが503を返す場合、原因は大きく2つに分かれます。原因1:全ターゲットがUnhealthy
ターゲットグループのコンソール、またはAWS CLIでターゲットの状態を確認します。
$ aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/app-tg/abcd1234 # 出力例 { "TargetHealthDescriptions": [ { "Target": { "Id": "i-0a1b2c3d4e5f67890", "Port": 8080 }, "TargetHealth": { "State": "unhealthy", "Reason": "Target.ResponseCodeMismatch", "Description": "Health checks failed with these codes: [302]" } } ] }
Target.ResponseCodeMismatchが出ている場合は、ヘルスチェックパスが302を返しています。matcherを"200,302"にするか、リダイレクトしない専用のヘルスチェック用パスを作ってください。原因2:セキュリティグループがバックエンドへの通信をブロック
ALBからEC2インスタンスへの通信は、EC2のセキュリティグループ側で許可する必要があります。ALBのセキュリティグループからのインバウンドが開いていないと、ALBがターゲットに到達できずにUnhealthyになります。
# EC2のセキュリティグループ — ALBのSGからのアクセスを許可 resource "aws_security_group_rule" "ec2_from_alb" { type = "ingress" from_port = 8080 to_port = 8080 protocol = "tcp" security_group_id = aws_security_group.ec2.id source_security_group_id = aws_security_group.alb.id }
source_security_group_idでALBのSGを指定するのがIaC設計のベストプラクティスです。CIDR指定では将来のALB IP変更で壊れますが、SG参照なら変更に追従します。本記事のまとめ
TerraformでALBを設計するときの重要ポイントをまとめます。| 設計要素 | リソース | 実務上のポイント |
|---|---|---|
| ALB本体 | aws_lb |
internet-facing/internalを用途で使い分ける。サブネットは2AZ以上必須 |
| ターゲットグループ | aws_lb_target_group |
healthy/unhealthy_thresholdを絞る。FargateはtargetType=ipに変更 |
| HTTPリダイレクト | aws_lb_listener(port=80) |
type="redirect"でHTTP→HTTPS 301を1本で宣言できる |
| HTTPSリスナー | aws_lb_listener(port=443) |
ssl_policyはTLS13対応ポリシーを指定。古い2016-08は非推奨 |
| EC2固定登録 | aws_lb_target_group_attachment |
Auto Scalingと混在禁止。ASGはtarget_group_arnsで連携 |
| pathルーティング | aws_lb_listener_rule |
priorityは番号帯を管理。低い数値が先に評価される |
| 503の切り分け | TargetHealth確認 | ResponseCodeMismatchはヘルスチェックパスのstatusコードを確認 |
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:TerraformでACMの証明書発行とDNS検証を自動化する設計|aws_acm_certificate_validationの依存関係と発行待ちの実践
- この記事の属するカテゴリ:Terraformへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます