本エントリは下書きにしたまま放置してあったんですが、ElasticBeanstalkとTerraformの記事って、ネットであまり見かけないしバッドノウハウは需要あると思い、掘り起こしてブラッシュアップしました。
https://youtu.be/e2W4BTR_9eA:embed:cite
ApplicationLoadBalancerのリスナーは下記をサポートしており、そのリスナーにはデフォルトのルールとオプションルールを定義することが可能です。
各ルールは優先度、1 つ以上のアクション、1 つ以上の条件で構成されます。
- プロトコル: HTTP,HTTPS
- ポート: 1 ~ 65535
ルールアクションの中にredirect
というタイプがあるので、これを使用することでHTTP=>HTTPSへリダイレクトさせることが可能になります。
(今から1年前くらいに実装された機能です)
https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#redirect-actions:embed:cite
そもそもElasticBeanstalkの標準の名前空間においてALBのリスナーのルールでリダイレクトを設定する機能が存在しません。そのため、.ebextensionsでALBにリダイレクトルールを入れようとしました。下記がその時の設定です。
`elastic_beanstalk_environment.tf`
(snip)
/* Network Tier */
#
# Load Balancing
#
// EnvironmentType
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "EnvironmentType"
value = "LoadBalanced"
}
// 環境のロードバランサーのタイプ
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "LoadBalancerType"
value = "application"
}
#
# process:default
#
// Elastic Load Balancing がアプリケーションの Amazon EC2 インスタンスの状態をチェックする間隔 (秒)
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckInterval"
value = "15"
}
// ヘルスチェックの HTTP リクエストを送信するパス
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckPath"
value = "/healthcheck"
}
// ヘルスチェック中のレスポンスの待機時間 (秒)
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckTimeout"
value = "5"
}
// インスタンスのヘルスステータスを変更するために必要な、連続して成功したリクエストの数
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthyThresholdCount"
value = "3"
}
// インスタンスが正常であることを示す HTTP コードのカンマ区切りのリスト
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "MatcherHTTPCode"
value = "200"
}
// プロセスがリッスンしているポート
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "Port"
value = "80"
}
// プロセスで使用するプロトコル
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "Protocol"
value = "HTTP"
}
// スティッキーセッション
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "StickinessEnabled"
value = "false"
}
// 内部ロードバランサーを作成する場合は internal を指定
setting {
namespace = "aws:ec2:vpc"
name = "ELBScheme"
value = "external"
}
setting {
namespace = "aws:elbv2:listener:80"
name = "ListenerEnabled"
value = "true"
}
// HTTP リスナーによって使用されるプロトコル
setting {
namespace = "aws:elbv2:listener:80"
name = "Protocol"
value = "HTTP"
}
// HTTPS リスナーによって使用されるプロトコル
setting {
namespace = "aws:elbv2:listener:443"
name = "Protocol"
value = "HTTPS"
}
// サーバ証明書
setting {
namespace = "aws:elbv2:listener:443"
name = "SSLCertificateArns"
value = "arn:aws:acm:ap-northeast-1:xxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
// セキュリティポリシー
setting {
namespace = "aws:elbv2:listener:443"
name = "SSLPolicy"
value = "ELBSecurityPolicy-2016-08"
}
// HTTP リスナーに適用するルールのリスト
setting {
namespace = "aws:elbv2:listener:80"
name = "DefaultProcess"
value = "default"
}
// HTTPS リスナーに適用するルールのリスト
setting {
namespace = "aws:elbv2:listener:443"
name = "DefaultProcess"
value = "default"
}
#
# default
#
// HTTP リスナーに適用するルールのリスト
setting {
namespace = "aws:elbv2:listener:80"
name = "Rules"
value = "default"
}
// HTTPS リスナーに適用するルールのリスト
setting {
namespace = "aws:elbv2:listener:443"
name = "Rules"
value = "default"
}
setting {
namespace = "aws:elbv2:listenerrule:default"
name = "Process"
value = "default"
}
// 一致するホスト名のリスト
setting {
namespace = "aws:elbv2:listenerrule:default"
name = "HostHeaders"
value = "xxxxx.jp"
}
// 複数のルールが一致する場合の、このルールの優先順位。低い番号が優先される
setting {
namespace = "aws:elbv2:listenerrule:default"
name = "Priority"
value = "1"
}
// アクセスログを保存する Amazon S3 バケット
setting {
namespace = "aws:elbv2:loadbalancer"
name = "AccessLogsS3Bucket"
value = "xxxxx"
}
// アクセスログストレージを有効
setting {
namespace = "aws:elbv2:loadbalancer"
name = "AccessLogsS3Enabled"
value = "true"
}
// アクセスログ名に追加するプレフィックス
setting {
namespace = "aws:elbv2:loadbalancer"
name = "AccessLogsS3Prefix"
value = "xxxx/production"
}
// クライアントとインスタンスへの接続を閉じる前に、リクエストの完了を待機する時間
setting {
namespace = "aws:elbv2:loadbalancer"
name = "IdleTimeout"
value = "60"
}
// EBでSGを新規作成せずに、既存SGを充てる
setting {
namespace = "aws:elbv2:loadbalancer"
name = "SecurityGroups"
value = aws_security_group.xxxxx_elb_sg.id
}
// EBでSGを新規作成せずに、既存SGを充てる
setting {
namespace = "aws:elbv2:loadbalancer"
name = "ManagedSecurityGroup"
value = aws_security_group.xxxxx_elb_sg.id
}
(snip)
`alb_redirect.config`
Resources:
AWSEBV2LoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
DefaultActions:
- Type: redirect
RedirectConfig:
Protocol: HTTPS
Port: 443
StatusCode: 'HTTP_301'
Port: 80
Protocol: HTTP
結果、デプロイは正常に終了しましたがALBにルールが適用されませんでした...
原因は設定オプションの優先順位に起因するものでした。
ElasticBeanstalk環境では、環境作成時にCreateEnvironmentAPIのoptionSettingsパラメータにて指定した設定が最優先されます。
Terraformから実行されたCreateEnvironmentAPIのリクエストパラメータoptionSettingsにおいて、名前空間aws:elbv2:listener:80に対する設定が優先されたため、.ebextensions配下のconfigが反映されなかったのが原因でした。
以下の名前空間を削除して置き換えました。
setting {
namespace = "aws:elbv2:listener:80"
name = "ListenerEnabled"
value = "true"
}
setting {
namespace = "aws:elbv2:listener:80"
name = "Protocol"
value = "HTTP"
}
setting {
namespace = "aws:elbv2:listener:80"
name = "DefaultProcess"
value = "default"
}
setting {
namespace = "aws:elbv2:listener:80"
name = "Rules"
value = "default"
}
setting {
namespace = "aws:elbv2:listener:443"
name = "Rules"
value = "default"
}
setting {
namespace = "aws:elbv2:listenerrule:default"
name = "Process"
value = "default"
}
setting {
namespace = "aws:elbv2:listenerrule:default"
name = "HostHeaders"
value = "xxxxx.jp"
}
setting {
namespace = "aws:elbv2:listenerrule:default"
name = "Priority"
value = "1"
}
- .ebextensionが優先されるように更新した設定ファイル
`elastic_beanstalk_environment.tf`
(snip)
/* Network Tier */
#
# Load Balancing
#
// EnvironmentType
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "EnvironmentType"
value = "LoadBalanced"
}
// 環境のロードバランサーのタイプ
setting {
namespace = "aws:elasticbeanstalk:environment"
name = "LoadBalancerType"
value = "application"
}
#
# process:default
#
// Elastic Load Balancing がアプリケーションの Amazon EC2 インスタンスの状態をチェックする間隔 (秒)
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckInterval"
value = "15"
}
// ヘルスチェックの HTTP リクエストを送信するパス
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckPath"
value = "/healthcheck"
}
// ヘルスチェック中のレスポンスの待機時間 (秒)
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthCheckTimeout"
value = "5"
}
// インスタンスのヘルスステータスを変更するために必要な、連続して成功したリクエストの数
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "HealthyThresholdCount"
value = "3"
}
// インスタンスが正常であることを示す HTTP コードのカンマ区切りのリスト
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "MatcherHTTPCode"
value = "200"
}
// プロセスがリッスンしているポート
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "Port"
value = "80"
}
// プロセスで使用するプロトコル
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "Protocol"
value = "HTTP"
}
// スティッキーセッション
setting {
namespace = "aws:elasticbeanstalk:environment:process:default"
name = "StickinessEnabled"
value = "false"
}
// 内部ロードバランサーを作成する場合は internal を指定
setting {
namespace = "aws:ec2:vpc"
name = "ELBScheme"
value = "external"
}
// HTTPS リスナーによって使用されるプロトコル
setting {
namespace = "aws:elbv2:listener:443"
name = "Protocol"
value = "HTTPS"
}
// サーバ証明書
setting {
namespace = "aws:elbv2:listener:443"
name = "SSLCertificateArns"
value = "arn:aws:acm:ap-northeast-1:xxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
// セキュリティポリシー
setting {
namespace = "aws:elbv2:listener:443"
name = "SSLPolicy"
value = "ELBSecurityPolicy-2016-08"
}
// HTTPS リスナーに適用するルールのリスト
setting {
namespace = "aws:elbv2:listener:443"
name = "DefaultProcess"
value = "default"
}
#
# default
#
setting {
namespace = "aws:elbv2:listener:443"
name = "Rules"
value = "tls"
}
setting {
namespace = "aws:elbv2:listenerrule:tls"
name = "HostHeaders"
value = "xxxxx.jp"
}
// アクセスログを保存する Amazon S3 バケット
setting {
namespace = "aws:elbv2:loadbalancer"
name = "AccessLogsS3Bucket"
value = "xxxxxx"
}
// アクセスログストレージを有効
setting {
namespace = "aws:elbv2:loadbalancer"
name = "AccessLogsS3Enabled"
value = "true"
}
// アクセスログ名に追加するプレフィックス
setting {
namespace = "aws:elbv2:loadbalancer"
name = "AccessLogsS3Prefix"
value = "xxxx/production"
}
// クライアントとインスタンスへの接続を閉じる前に、リクエストの完了を待機する時間
setting {
namespace = "aws:elbv2:loadbalancer"
name = "IdleTimeout"
value = "60"
}
// EBでSGを新規作成せずに、既存SGを充てる
setting {
namespace = "aws:elbv2:loadbalancer"
name = "SecurityGroups"
value = aws_security_group.xxxxx_elb.id
}
// EBでSGを新規作成せずに、既存SGを充てる
setting {
namespace = "aws:elbv2:loadbalancer"
name = "ManagedSecurityGroup"
value = aws_security_group.xxxxx_elb.id
}
(snip)
これでリスナールールが更新されるはず!と思ってapplyするも、正常終了しても既存のルールがそのまま残っており、優先順位が変更できない状態に陥りました。
さんざん悩んだ結果、環境を再作成することで.ebextensions配下の設定ファイルを適用することができました。このあたりはTerraformとCloudFormationの問題だと思うのですが、
今現在も解決していないため、ISSUEを出したいと思っています。
301でリダイレクトされています。
$ curl --head xxxxx.jp
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Tue, 23 Jul 2019 03:23:34 GMT
Content-Type: text/html
Content-Length: 150
Connection: keep-alive
Location: https://xxxxx.jp:443/
ALBのログにはredirect
されたことがわかるように出ています。
http 2019-07-23T04:31:30.708115Z app/awseb-AWSEB-************/*********** ***.***.***.***:49488 - -1 -1 -1 301 - 185 349 "GET http://***.***.***.***:80/ HTTP/1.1" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36" - - - "Root=1-5d368da2-89854ad4ee4a72105965a0c8" "-" "-" 0 2019-07-23T04:31:30.707000Z "redirect" "https://***.***.***.***:443/" "-"