この記事はフィジビリティが完全には取れていないので注意。
2018/03/16 追記 フィジビリティ取れました。
ElasticBeanstalkのImmutableデプロイでAutoScalingGroupが削除/新規作成されても新しい方のインスタンスをターゲットグループに自動追加してくれました。
背景
APIサーバとフロントエンドサーバがAWS上のElasticBeanstalkで動いていて、APIサーバはフロントエンドサーバからのアクセスのみを許可するためInternal ELBにPrivate DNSレコードを割り当てて運用しています。
特定のIPアドレスからのみ、特定のパスに対してAPIを直接実行させなければならない要件が出てきました。
フロントエンドをProxyにしてもいいですが、フロントエンドのSecurityGroupは0.0.0.0/0
を許可しているため、SecurityGroupの内側(Nginxなど)でIP制限をかけると責任範囲がAWSでなくなるし、セキュリティの担保が自前になるためやりたくありません。
特定のパスのみIP制限つきのPublic
にしたい、という要件を満たすためにALBを後から追加する方法を試しました。
ちなみに、AWS WAFを使えば似たようなことを簡単に実現できますが、全てのパスに対して毎回チェックが入ってオーバーヘッドが気になるのと、コスト面で選択しませんでした。
要は、これを
こうしたかったわけです。
概要手順
- ElasticBeanstalk内のEC2上でLISTENポートを増やす。
- 空(ターゲット未登録)の
TargetGroup
を作成する。 - ElasticBeanstalkの
AutoScalingGroup
をTargetGroup
に登録する。 -
ALB
を作成し、作成したTargetGroup
を紐付ける。 -
ALB
からのアクセス許可をElasticBeanstalkのSecurityGroupに追加する。
マネジメントコンソールやaws elb
のCLIリファレンスを見ると、ALBにはIPアドレス
もしくはインスタンスID
を紐付けなければいけないようにしか見えませんが、これが罠で、aws autoscaling
のCLIリファレンスを見ると、attach-load-balancer-target-groupsというコマンドがあります。
aws autoscaling attach-load-balancer-target-groups \
--auto-scaling-group-name <value> \
--target-group-arns <value>
このコマンドを使うとElasticBeanstalkがEC2インスタンスを増減させても自動的にALBの到達先も変わってくれます。超便利!!
詳細手順
前置きが長くなりましたが、実際にこれらを実装する手順は以下となります。
ElasticBeanstalkのEC2内でLISTENポートを増やす
ここでは、Java
のElasticBeanstalkを例として説明します。
ElasticBeanstalkはプラットフォーム言語によってEC2の内部構成がだいぶ異なるので、Rubyなど他のプラットフォームを利用している場合とNginxの設定が異なるので注意してください。
Java
版のElasticBeanstalkでは親切なことにソースディレクトリに .ebextensions/nginx
というディレクトリがあれば自動でその中身を/etc/nginx
に展開してくれます。
ElasticBeanstalk Javaの標準のnginx.conf
はこんな感じの設定になっていて、80番ポートからlocalhost
の5000番ポートにプロキシしています。
http {
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
include conf.d/elasticbeanstalk/*.conf;
}
}
なので、単純にserver
ディレクティブを追加してあげればポートを増やすことができます。
http {
server {
listen 80 default_server;
access_log /var/log/nginx/access.log main;
include conf.d/elasticbeanstalk/*.conf;
}
# もう1台のALB用LISTENポート
server {
listen 8080;
access_log /var/log/nginx/access-public.log main;
include conf.d/elasticbeanstalk/*.conf;
}
}
ただ、この方法だと 特定のパスのみ という要件にはマッチしないので、location
ディレクティブを追加してあげます。
# もう1台のALB用LISTENポート
server {
listen 8080;
access_log /var/log/nginx/access-public.log main;
# 外部からのアクセスを許可するパスを指定
location /public {
proxy_pass http://localhost:5000;
proxy_set_header xxxx ... ;
}
# それ以外は404にしちゃう
location / { return 404; }
}
空のTargetGroup
を作成
ポートをnginx.conf
に追加したポートを向くようにTargetGroup
を作成します。
CLIだとこんな感じ。
aws elbv2 create-target-group --name <value> --vpc-id <value> --protocol 'HTTP' \
--port 8080 --target-type 'instance' --health-check-path '/public/'
ポイント
- ポートにはNginxに追加したポート番号を指定する。
- ターゲットの種類では
instance
を選択する。 - ヘルスチェックパスでNginxで指定したパス配下を指定する。(
/
は404になるため)
TargetGroup
にElasticBeanstalkのAutoScalingGroup
をAttatchする
この操作はマネジメントコンソール上からはできません。なのでCLIを利用します。
まずはElasticBeanstalkが自動生成したAutoScalingGroup
のName
を取得する必要があります。
aws elasticbeanstalk describe-environment-resources --environment-name <value> \
--query 'EnvironmentResources.AutoScalingGroups' --output text
上記コマンドを実行すると、awseb-e-abcdefghij-stack-AWSEBAutoScalingGroup-123456789ABC
のようなAutoScalingGroupのNameが取得できます。(インタフェース上は複数返ってきますが、通常は1つのはずです)
こいつを、先に作成したTargetGroup
にアタッチしてあげます。
aws autoscaling attach-load-balancer-target-groups \
--auto-scaling-group-name '<取得したAutoScalingGroupのName>' \
--target-group-arns '<作成したTargetGroupのARN>'
コマンド出力はありません。うまくいったかどうかはマネジメントコンソールで確認します。
うまくいっていれば、登録済みターゲット
にElasticBeanstalk配下のインスタンスIDが表示されます。
(表示されるまで少し時間がかかります。)
ALB
を作成し、作成したTargetGroup
を紐付ける
普通にALBを作成して既存のターゲットグループを選択するだけなので手順は割愛します。
今回の目的であったIP制限は、このALBのSecurityGroupに対して指定したIPアドレス(CIDR)のIngressを追加することで実現可能です!!
あらかじめACMで証明書を発行しておいてHTTPS化したり、Route53でALIASレコードを作成してあげれば実運用可能な状態となります。
ALBからのアクセス許可をElasticBeanstalkのSecurityGroupに追加
これはElasticBeanstalkをどうやって構築したかによって追加の仕方がいくつもあるので詳しい手順は割愛しますが、ElasitcBeanstalkの汎用オプションを使った例では以下で指定したSecurityGroupに追加すればOKです。
aws:autoscaling:launchconfiguration:
SecurityGroups: sg-xxxxxxxxx # <- ここのSecurityGroupに追加
なお、マネジメントコンソールで環境を作成した場合はSecurityGroupは勝手に生成されてユーザ管理外となるため、手運用にならざるを得ません。(そもそも手運用であれば今回の要件も手運用で実現可能です)
ElasticBeanstalkの環境をマネジメントコンソールで作成するのはダメ!絶対!
Tips
ルートパスを特定のパスにProxyする
これはAWSは関係なく、Nginxの話ですが、以下のように設定すると/public
でアクセスしなくても、/
がProxy先の/public/
にアクセス可能になります。
# もう1台のALB用LISTENポート
server {
listen 8080;
access_log /var/log/nginx/access-public.log main;
location / {
proxy_pass http://localhost:5000/public/; # 末尾にスラッシュをつける
proxy_set_header xxxx ... ;
}
}
ヘルスチェックのエンドポイントを共有する
上記例では/public
配下のみをアクセスさせるようにしたので、既存のヘルスチェックエンドポイントが/public
配下にないと、新規でエンドポイントを作成する必要があります。
既存のエンドポイントを利用する場合はそこだけlocation
ディレクティブを追加します。
# もう1台のALB用LISTENポート
server {
listen 8080;
access_log /var/log/nginx/access-public.log main;
# ヘルスチェックだけ別パスにProxy
location /healthcheck {
proxy_pass http://localhost:5000;
proxy_set_header xxxx ... ;
}
location / {
proxy_pass http://localhost:5000/public/; # 末尾にスラッシュをつける
proxy_set_header xxxx ... ;
}
}
Terraformを利用した設定手順
# ElasticBeanstalk Environment
resource "aws_elastic_beanstalk_environment" "api" { /* 割愛 */ }
# ALB本体
resource "aws_alb" "public" { /* 割愛 */ }
# ALBのTargetGroup
resource "aws_alb_target_group" "public" {
load_balancer_arn = "${aws_alb.public.arn}"
# 割愛
}
# ElasticBeanstalkのAutoScalingGroupをALBのTargetGroupにAttatch
resource "aws_autoscaling_attachment" "asg_attachment" {
# リストで返ってくるので先頭のものを指定している。countを使っても良い。
autoscaling_group_name = "${element(aws_elastic_beanstalk_environment.api.autoscaling_groups, 0)}"
alb_target_group_arn = "${aws_alb_target_group.public.arn}"
}
注意
まだデプロイや環境のアップグレードまわりで問題ないことが確認できていません。自己責任で適用お願いします。
例えば、マネジメントコンソール上でAutoScalingGroupが置き換わるような変更をElasticBeanstalkに対して行うと、ElasitcBeanstalkが古いAutoScalingGroupを削除しようとして、TargetGroupに紐付いているため削除できずにゾンビ化する可能性があります。
これから実際に運用してみて問題がないか確認していきます。
2018/03/16 追記 フィジビリティ取れました。
ElasticBeanstalkのImmutableデプロイでAutoScalingGroupが削除/新規作成されても新しい方のインスタンスをターゲットグループに自動追加してくれました。