はじめに
個人開発アプリをAWSに公開するためのTerraform Packを作っている中で、今回はHTTPS公開まわりの構成について書きます。
前回は、EC2へSSHせずにSSM Session Managerで接続する構成について書きました。
今回はその続きとして、Webアプリを外部公開するために必要になる以下の構成を扱います。
- ALBによる外部公開
- ACMによるHTTPS化
- Route53による独自ドメイン設定
個人開発でWebアプリを公開する場合、最初はEC2に直接アクセスさせる構成でも動きます。
ただ、外部に見せるアプリとして考えると、HTTPS化や独自ドメイン対応はかなり重要です。
そこで、ヒトリ開発では以下のような構成を基本にしています。
今回やりたいこと
今回やりたいことはシンプルです。
独自ドメインでHTTPSアクセスできるようにする
具体的には、以下のような状態を目指します。
この構成にすることで、ユーザーは独自ドメインでWebアプリへアクセスできます。
また、HTTPでアクセスされた場合もHTTPSへリダイレクトします。
個人開発でも、外部に公開するならHTTPS化はほぼ必須だと思っています。
外部公開なのにHTTPのままだと「大丈夫かこれ?」って思われそうですしね(笑)
ALBを使う理由
EC2単体でもWebアプリを公開することはできます。
ただ、ヒトリ開発ではALBを前段に置く構成にしています。
理由は以下です。
- HTTPS終端やリダイレクトをALB側で扱える
- ヘルスチェックでアプリの状態を確認できる
- 通信をALBに集約し、Security Groupの管理をシンプルにできる
- 将来的にEC2追加やECS移行へつなげやすい
個人開発の初期段階では、ALBは少し大げさに見えるかもしれません。
ただ、独自ドメイン + HTTPS + 将来的な拡張を考えると、最初からALBを使う方が整理しやすいと感じました。
ACMを使う理由
HTTPS化には証明書が必要です。
AWS上でALBを使う場合、証明書はACMで発行してALBに紐づけるのが扱いやすいです。
ヒトリ開発では、ACMで証明書を発行し、ALBのHTTPS Listenerに設定する構成にしています。
ACMを使うことで、証明書管理をAWS側に寄せることができます。
手動で証明書ファイルをEC2に置いたり、更新作業を自分で頑張ったりする構成は、個人開発ではできるだけ避けたいです。
何より更新作業忘れがちなので…笑
Route53を使う理由
独自ドメインでアクセスするためにRoute53を使います。
Route53では、取得したドメインやHosted Zoneを管理し、ALBへ向けるレコードを作成します。
今回の構成では、Route53のAlias Recordを使ってALBへ向けます。
ALBのDNS名を直接ユーザーに見せるのではなく、独自ドメインでアクセスできるようにします。
全体構成
今回の構成は以下です。
ポイントは、80番と443番の扱いを分けることです。
- 80番はHTTPSへリダイレクト
- 443番はACM証明書を使って受ける
- 受けた通信はTarget Group経由でEC2へ転送する
この構成にすることで、HTTPでアクセスされてもHTTPSへ寄せることができます。
Terraform構成
ここからは、Terraformの構成例を書いていきます。
実際の環境では、変数化やmodule化をしていますが、この記事では分かりやすさ優先でシンプルに書きます。
ALB
まずはALBを作成します。
resource "aws_lb" "app" {
name = "sample-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
]
tags = {
Name = "sample-app-alb"
}
}
internal = false にすることで、インターネット向けのALBになります。
ALBはPublic Subnetに配置します。
Target Group
次に、EC2へ転送するためのTarget Groupを作成します。
resource "aws_lb_target_group" "app" {
name = "sample-app-tg"
port = 8080
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/"
protocol = "HTTP"
matcher = "200"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
tags = {
Name = "sample-app-tg"
}
}
ここでは、EC2上のWebアプリが 8080 番で動いている想定です。
アプリケーションによって、80 や 3000 などに変えて使います。
Target Group Attachment
作成したTarget GroupにEC2を登録します。
resource "aws_lb_target_group_attachment" "app" {
target_group_arn = aws_lb_target_group.app.arn
target_id = aws_instance.app.id
port = 8080
}
これで、ALBからEC2へトラフィックを流せるようになります。
ACM
HTTPS用の証明書をACMで作成します。
resource "aws_acm_certificate" "app" {
domain_name = "example.com"
validation_method = "DNS"
lifecycle {
create_before_destroy = true
}
tags = {
Name = "sample-app-cert"
}
}
DNS検証を使う想定です。
create_before_destroy = true を入れておくと、証明書の作り直し時に既存証明書を先に削除しないようにできます。
ACM DNS検証用レコード
ACMでDNS検証をする場合、Route53に検証用レコードを作成します。
resource "aws_route53_record" "app_cert_validation" {
for_each = {
for dvo in aws_acm_certificate.app.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
type = dvo.resource_record_type
record = dvo.resource_record_value
}
}
zone_id = aws_route53_zone.main.zone_id
name = each.value.name
type = each.value.type
records = [each.value.record]
ttl = 60
}
ACMで発行した証明書を有効化するために、DNS検証用のCNAMEレコードを作ります。
ACM Certificate Validation
検証完了をTerraform上で待つために、aws_acm_certificate_validation を使います。
resource "aws_acm_certificate_validation" "app" {
certificate_arn = aws_acm_certificate.app.arn
validation_record_fqdns = [
for record in aws_route53_record.app_cert_validation : record.fqdn
]
}
これにより、証明書のDNS検証が完了してからHTTPS Listenerで利用できるようになります。
HTTP Listener
HTTPアクセスはHTTPSへリダイレクトします。
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.app.arn
port = 80
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
HTTPで来たリクエストは、HTTPSへ301リダイレクトします。
HTTPS Listener
HTTPS Listenerでは、ACM証明書を設定し、Target Groupへ転送します。
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.app.arn
port = 443
protocol = "HTTPS"
certificate_arn = aws_acm_certificate_validation.app.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
これで、HTTPSアクセスをALBで受けて、EC2へ転送できます。
Route53 Alias Record
最後に、独自ドメインをALBへ向けます。
resource "aws_route53_record" "app" {
zone_id = aws_route53_zone.main.zone_id
name = "example.com"
type = "A"
alias {
name = aws_lb.app.dns_name
zone_id = aws_lb.app.zone_id
evaluate_target_health = true
}
}
通常のAレコードではなく、ALBに向けるAlias Recordを使います。
これで、example.com からALBへアクセスできるようになります。
Security Groupで意識したこと
HTTPS公開まわりでは、Security Groupの分け方も重要です。
ヒトリ開発では、ALB用とEC2用でSecurity Groupを分ける考え方にしています。
大まかな考え方は以下です。
- ALBはインターネットから80/443を受ける
- EC2はインターネットから直接受けない
- EC2はALB Security Groupからの通信だけ許可する
ALB側のSecurity Group例です。
resource "aws_security_group" "alb" {
name = "sample-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"]
}
}
EC2側は、ALB Security Groupからの通信だけを許可します。
resource "aws_security_group" "app" {
name = "sample-app-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
}
この構成にすることで、EC2を直接インターネットに晒さず、ALB経由でアクセスさせる形にできます。
ハマりやすいポイント
ACM証明書のリージョン
ALBで使うACM証明書は、ALBと同じリージョンで作成する必要があります。
CloudFront用の証明書はバージニア北部で作ることがありますが、ALBで使う場合はALBと同じリージョンです。
ここは地味に混乱しやすいです。
DNS検証が終わるまでHTTPS Listenerを作れない
ACM証明書は、DNS検証が完了するまで有効になりません。
Terraformで一気に作る場合は、aws_acm_certificate_validation を使って検証完了を待つ構成にしておくと扱いやすいです。
Target Groupのヘルスチェック
ALBがHealthyにならない場合、Target Groupのヘルスチェック設定を確認します。
見るポイントは大きく以下です。
- ヘルスチェックのpath / port / protocol
- EC2側のアプリ起動状態
- Security Groupの許可設定
ALB自体が正常でも、Target GroupがUnhealthyだとアプリには到達できません。
ここで「ALB作ったのに見れない!」ってなりがちです。私はなりました。
HTTPとHTTPSのListenerを分ける
HTTPとHTTPSはListenerを分けて考えます。
80番はHTTPSへリダイレクト。
443番は証明書を使ってTarget Groupへ転送。
この分け方をしておくと、構成がかなり整理しやすいです。
個人開発でこの構成にしてよかったこと
ALB + ACM + Route53の構成にしてよかったことは以下です。
- 独自ドメインでHTTPS公開できる
- EC2を直接公開せずに済む
- ヘルスチェックや将来のECS移行につなげやすい
個人開発の初期段階では、少し構成が大きく見えるかもしれません。
ただ、公開して人に見てもらうWebアプリであれば、最初からこのあたりは整えておいてよいと感じました。
今回のTerraform構成で意識したこと
今回のTerraform構成では、以下を意識しました。
- 独自ドメインでHTTPSアクセスできる
- HTTPはHTTPSへリダイレクトする
- EC2はALB経由でのみアクセスさせる
個人開発だからこそ、公開時の不安を減らしたいです。
「とりあえず動いた」だけではなく、外部に見せやすい状態までTerraformで整えておくことを意識しました。
まとめ
今回は、ALB + ACM + Route53で個人開発アプリをHTTPS公開するTerraform構成について書きました。
EC2単体でもWebアプリは公開できます。
ただ、独自ドメイン、HTTPS、HTTP → HTTPSリダイレクト、ヘルスチェック、将来的な拡張を考えると、ALBを前段に置く構成はかなり扱いやすいです。
今回の構成では、以下のような方針にしました。
- Route53で独自ドメインを管理する
- ACMで証明書を発行する
- ALBでHTTPSを受け、Target Group経由でEC2へ転送する
個人開発でも、外部公開するならHTTPS化はかなり重要です。
今後は、Security Groupの分け方やCloudWatchでの最低限監視なども、少しずつ切り出して書いていこうと思います。
補足
ここまでHTTPS化について書いてきましたが、実際のパッケージではHTTP構成も選べるようにしています(笑)
個人開発向けAWS公開環境の構成図や、検証環境で利用できる無料版TerraformのGitHubリンクを以下にまとめていますので、よければ参考にしてみてください。
Free版を触ってみた感想をもらえたらめちゃくちゃ喜びます(笑)