Edited at

決めろ最強のECS! 〜起動タイプ × 負荷分散 × デプロイ方法で自分だけのアーキテクチャ〜


決めろ最強のECS! 〜起動タイプ × 負荷分散 × デプロイ方法で自分だけのアーキテクチャ〜

※このノートはJAWS DAYS 2019懇親会LTの発表の詳細です!



アジェンダ


  • 起動タイプ

  • 負荷分散

  • デプロイ方法

  • 実際のシナリオ

  • まとめ



起動タイプ


  • ec2

  • fargate



ec2

Pros:

* コスト面でfargateに勝る

Cons:

* インスタンスの死活を気にする必要が出てくる



fargate

Pros:

* EC2インスタンスを作成しないため、環境構築の手順を減らせる!

* インスタンスに物理的に入ることができないため、監査が楽(と思われます)!

Cons:

* 2019年2月時点ではドキュメントがそこまで多くなく、ecs-cliとの組み合わせなど応用的(?)なことを試すと事例が探しづらい。

* IPアドレスを固定できないため、何らかの負荷分散方法と組み合わせないとインターネットからアクセスできるようにできない



負荷分散


  • ALB

  • API Gateway + NLB

  • サービスディスカバリ



ALB

Pros:

* パスベースマッピングが重宝する

* WAFをセットできる

Cons:

* 普通はALBで十分だと思われるが...API Gatewayにある機能がこちらにも欲しくなる!



API Gateway + NLB

Pros:

* APIを構築する上ではとても便利!

* カスタムオーソライザーの提供

* ex) アクセストークンをみてリクエストヘッダーを書き換える)

* マネージドなAPIのバージョン管理

* VPC_LINKでNLBをプライベートサブネットに置くことができる。

* API Gatewayをプロキシ統合にすれば、ルーティングは実質アプリのフレームワークで握ることができる → 開発者に嬉しい!!!

Cons:

* パスベースのマッピングができない。

* 複数のECSサービスをインターネットと接続する場合、一つのNLBではそれらのサービスをインターネットに接続することができない。

* 内部にロードバランサーを置く、NLBより手前でポート番号を変換するなど方法は考えられる



サービスディスカバリ

Pros:

* あくまで名前解決なので、gRPCやWebSocketを使いたい場合にも便利

* 名前で参照するため、サーバー間の参照方法が環境・デプロイ結果に依存しない

Cons:

* 2019年2月時点ではドキュメントが多くない



デプロイ方法


  • CloudFrmation

  • ecs-cli

  • Terraform(今回は対象外)

  • CDK(今回は対象外)



CloudFormation

Pros:

* 先行事例が多い

* ロードバランサーやサブネットの指定時に!Refで参照できる

Cons:

* タスク定義を更新した場合、サービスを自前で再起動する必要がある

* justInCaseではsilinternational/ecs-deployを利用しています



ecs-cli

Pros:

* docker-compose.ymlを利用できる

* タスク定義を更新した場合のサービスの再起動について考慮が不要になる

* ECRとECSのサービスがクロススタック参照で依存しないので、スタックを削除するときにテンプレートをごにょごにょしないてOK

Cons:

* コマンドの癖が若干強い

* ex) サービス作成時と更新時でサービスディスカバリを利用するオプションが異なる

* ...もしかして、CIでヘビーにデプロイすることは想定されていない???

* CloudFormationではないので、クロススタック参照を使ってリソースを参照できない

ecs-cliでサブネットやロードバランサーを動的に指定するとちょっと大変でした。次のページを参照。



SAMPLE: ecs-cliでサブネットやロードバランサーを動的に指定する

AWS_ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)

export IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${ENV}-app"
export LOGS_GROUP="/aws/ecs/task/${ENV}-App"
export LOGS_REGION="${AWS_DEFAULT_REGION}"
export TASK_ROLE_ARN="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`TaskRoleArn`].OutputValue' --output text)"
export EXECUTION_ROLE_ARN="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`TaskExecutionRoleArn`].OutputValue' --output text)"
export TARGET_GROUP_ARN="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`TargetGroupArn`].OutputValue' --output text)"
export VPC_ID="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`VpcId`].OutputValue' --output text)"
export SUBNET1="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`ECSSubnet1aId`].OutputValue' --output text)"
export SUBNET2="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`ECSSubnet1cId`].OutputValue' --output text)"
export SECURITY_GROUP="$(aws cloudformation describe-stacks --stack-name ${ENV}-infrastracture --query 'Stacks[0].Outputs[?OutputKey==`ECSSecurityGroupId`].OutputValue' --output text)"
export MYSQL_HOST=${MYSQL_HOST}
export MYSQL_USERNAME=${MYSQL_USERNAME}
export MYSQL_PASSWORD=${MYSQL_PASSWORD}
[ $(aws ecs list-services --cluster ${ENV}-ECSCluster --query 'serviceArns' | grep \".*/app\") ] && export SERVICE_DISCOVERY_DEPLOYMENT_OPTION="--update-service-discovery" || export SERVICE_DISCOVERY_DEPLOYMENT_OPTION="--enable-service-discovery"
# ↑↑ ecs-cliでサービスディスカバリを有効にする場合、初回起動では `--enable-service-discovery`オプションを、更新では `--update-service-discovery`オプションを有効にする必要がある...
ecs-cli compose --project-name app -f ecs/docker-compose.yml --ecs-params ecs/ecs-params.yml service up \
${SERVICE_DISCOVERY_DEPLOYMENT_OPTION} --private-dns-namespace local --vpc ${VPC_ID} \
--container-name app --container-port 8080 --cluster ${ENV}-ECSCluster --target-group-arn ${TARGET_GROUP_ARN} --launch-type FARGATE --timeout 10



実際のシナリオ


  • toBでAPIを提供する

  • 認証はCognito Userpoolを使うが、認可サーバーは自前/サードパーティのものを使う

  • ECSサーバーはプライベートサブネットに置きたい

  • ECSサーバー同士の相互参照がありえる

  • 構築初期なので、スタックの削除もそこそこやる可能性がある

→ API Gateway + NLB + ECS on fargate w/ ecs-cliで構築



まとめ

要件によって起動タイプ x 負荷分散 x デプロイ方法 を使い分けて、自分だけのECSのアーキテクチャを見つけ出そう!