Edited at

ElasticBeanstalk環境下のALBで301リダイレクトさせる

本エントリは下書きにしたまま放置してあったんですが、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が反映されなかったのが原因でした。

https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-options.html#configuration-options-precedence:embed:cite

以下の名前空間を削除して置き換えました。

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を出したいと思っています。

image.png

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/" "-"