What's?
- AWS Fargateに対するデプロイ方法は3種類あり、そのうちローリング更新を試してみたい
- 環境と更新操作は、Terraformで行う
というのをやってみたいという記事です。
AWS Fargateでのデプロイ方法
Fargateというか、ECSでのデプロイ方法ですが。
ドキュメントによると、3つがあるようです。
- ローリング更新
- CodeDeployを使用したBlue/Greenデプロイ
- 外部デプロイ
AWS内でやろうとすると、ローリング更新かBlue/Greenデプロイ(CodeDeploy利用)の2つになるということですね。
ローリング更新
ローリング更新に関するドキュメントはこちら、ですが、あんまり書いてないですね…。
AWSのマネージメントコンソールでECSサービスを作成する時に、ローリング更新に関する記述があるのはドキュメントのうち次の2つのページです。
[ローリング更新] デプロイタイプを使用している場合は、次のデプロイ設定パラメータを入力します。これらのパラメータの使用方法の詳細については、「デプロイ設定」を参照してください。
Minimum healthy percent: デプロイ時に RUNNING 状態に保つ必要のあるサービスのタスクの下限数をサービスのタスクの必要数のパーセント値 (最も近い整数に切り上げ) で指定します。
Maximum percent: デプロイ時に RUNNING または PENDING 状態にできるサービスのタスクの上限数をサービスの必要数のタスクのパーセント値 (最も近い整数に切り下げ) で指定します。
こちらは、主にヘルスチェックに関する話ですね。
ローリング更新に関するパラメーターのリファレンスは、こちらを見ることになります。
deploymentController
をデフォルトのECS
とした場合、ローリング更新になるということですね。
ドキュメントを読んでいくと、ローリング更新というのはサービス内のタスクを順次更新していくもののようです。
どのくらいのタスクを残して更新していくかは、minimumHealthyPercent
とmaximumPercent
で調整することになります。
環境
利用するTerraformと、AWS Providerのバージョンです。
$ terraform version
Terraform v0.12.26
+ provider.aws v2.65.0
構築内容
まっさらなところから、VPCを作ってAWS Fargateクラスタを構築、簡単な動作確認を行うところまでやります。
- マルチAZ
- 動作させるコンテナは、nginxとApache(ローリングアップデートで切り替える)
- ALBはHTTP
TerraformとAWS Providerのバージョン指定
こんな感じで。
terraform {
required_version = "= 0.12.26"
}
provider "aws" {
version = "2.65.0"
}
AWSのクレデンシャルは、環境変数で指定します。
export AWS_ACCESS_KEY_ID=.....
export AWS_SECRET_ACCESS_KEY=.....
export AWS_DEFAULT_REGION=.....
VPC〜ALBまで
このあたりは、コミュニティモジュールを使って作っていきます。
VPC。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "2.39.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
map_public_ip_on_launch = true
enable_nat_gateway = true
single_nat_gateway = false
one_nat_gateway_per_az = true
}
ALBとFargateに付与する、Security Group。
AWS EC2-VPC Security Group Terraform module
module "load_balancer_sg" {
source = "terraform-aws-modules/security-group/aws//modules/http-80"
version = "3.10.0"
name = "load-balancer-sg"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = ["0.0.0.0/0"]
}
module "http_server_sg" {
source = "terraform-aws-modules/security-group/aws//modules/http-80"
version = "3.10.0"
name = "http-server-sg"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = ["10.0.0.0/16"]
}
ALB。
AWS Application and Network Load Balancer (ALB & NLB) Terraform module
module "load_balancer" {
source = "terraform-aws-modules/alb/aws"
version = "5.6.0"
name = "http-server"
vpc_id = module.vpc.vpc_id
load_balancer_type = "application"
internal = false
subnets = module.vpc.public_subnets
security_groups = [module.load_balancer_sg.this_security_group_id]
target_groups = [
{
backend_protocol = "HTTP"
backend_port = 80
target_type = "ip"
health_check = {
interval = 20
}
}
]
http_tcp_listeners = [
{
port = 80
protocol = "HTTP"
}
]
}
ここまでのモジュール定義のOutputの利用と、Fargateでのコンテナ定義
ここまでで使用したモジュールのOutputのうち、この後のFargateで使うものをローカル変数としてまとめておきます。
対象は、VPC ID、プライベートサブネットのID、Fargate用のセキュリティグループ、そしてALBのターゲットグループです。
locals {
vpc_id = module.vpc.vpc_id
private_subnets = module.vpc.private_subnets
ecs_service_security_groups = [module.http_server_sg.this_security_group_id]
load_balancer_target_group_arn = module.load_balancer.target_group_arns[0]
nginx_container_definition = <<JSON
[
{
"name": "http-server",
"image": "nginx:1.19.0",
"essential": true,
"portMappings": [
{
"protocol": "tcp",
"containerPort": 80
}
],
"healthCheck": {
"command": [
"CMD-SHELL",
"curl -f localhost || exit 1"
],
"interval": 5
}
}
]
JSON
apache_container_definition = <<JSON
[
{
"name": "http-server",
"image": "httpd:2.4.43",
"essential": true,
"portMappings": [
{
"protocol": "tcp",
"containerPort": 80
}
],
"healthCheck": {
"command": [
"CMD-SHELL",
"test -e /proc/1"
],
"interval": 5
}
}
]
JSON
}
また、コンテナ定義も2つ用意しておき、こちらを変更することでFargateのサービス内で稼働するコンテナイメージを切り替えていきます。
ローリング更新と言いつつも、今回はnginxとApacheを切り替えることにしました。
コンテナのヘルスチェックですが、Apacheのイメージにはcurl
が入っていなかったので、だいぶ微妙な感じになっていますが…。
コンテナのヘルスチェックの設定については、こちらを参照。
Fargateクラスタの定義
では、Fargateクラスタを定義しましょう。
- クラスタ定義
- タスク定義
- サービス定義
以上を行います。
resource "aws_ecs_cluster" "http_server" {
name = "http-server-cluster"
}
resource "aws_ecs_task_definition" "http_server" {
family = "http-server-task-definition"
cpu = "256"
memory = "512"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = local.nginx_container_definition
#container_definitions = local.apache_container_definition
}
resource "aws_ecs_service" "http_server" {
name = "http-server-service"
cluster = aws_ecs_cluster.http_server.arn
task_definition = aws_ecs_task_definition.http_server.arn
desired_count = 5
launch_type = "FARGATE"
platform_version = "1.4.0"
deployment_minimum_healthy_percent = 50
deployment_controller {
type = "ECS" # default
}
network_configuration {
assign_public_ip = false
security_groups = local.ecs_service_security_groups
subnets = local.private_subnets
}
load_balancer {
target_group_arn = local.load_balancer_target_group_arn
container_name = "http-server"
container_port = 80
}
}
ローリング更新を行う場合、deployment_controller
のtype
はECS
にする必要があります。といっても、これはデフォルト値のようですが。
deployment_controller {
type = "ECS" # default
}
minimumHealthyPercent
は、50%に設定。
ここ、注意ですね。
レプリカサービスの minimumHealthyPercent のデフォルト値は 100% です。DAEMON サービススケジュールを使用しているデフォルトの minimumHealthyPercent 値は、AWS CLI、AWS SDK、API では 0%、AWS マネジメントコンソール では 50% です。
タスク定義は、まずはnginxを利用。
resource "aws_ecs_task_definition" "http_server" {
family = "http-server-task-definition"
cpu = "256"
memory = "512"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
container_definitions = local.nginx_container_definition
#container_definitions = local.apache_container_definition
}
確認
まずは、terraform apply
。
$ terraform apply -auto-approve
curl
で1度確認してみます。
$ curl [ALBのDNS名]
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
nginxコンテナのレスポンスが返ってきました。
この時に、ecs-cli ps
でコンテナの一覧を見てみます。
$ ecs-cli ps --cluster http-server-cluster --desired-status RUNNING
Name State Ports TaskDefinition Health
2a3da558-0dc6-4d26-9304-a5370520a4a4/http-server RUNNING 10.0.1.226:80->80/tcp http-server-task-definition:12 HEALTHY
6b6f5927-80b9-46ec-a6ff-8c4c85553804/http-server RUNNING 10.0.2.220:80->80/tcp http-server-task-definition:12 HEALTHY
8f0d5f0d-7321-4b75-9bea-b1423260f170/http-server RUNNING 10.0.1.19:80->80/tcp http-server-task-definition:12 HEALTHY
a6930cca-75ca-474e-9033-bfb1d889d14f/http-server RUNNING 10.0.1.193:80->80/tcp http-server-task-definition:12 HEALTHY
d4b0c60c-737c-42b3-9a74-4f1b7a5ae013/http-server RUNNING 10.0.2.79:80->80/tcp http-server-task-definition:12 HEALTHY
では、ここでコンテナ定義をnginxからApacheに変更してみます。
resource "aws_ecs_task_definition" "http_server" {
family = "http-server-task-definition"
cpu = "256"
memory = "512"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
#container_definitions = local.nginx_container_definition
container_definitions = local.apache_container_definition
}
$ terraform apply -auto-approve
すると、次のコンテナが立ち上がり始めます。
$ ecs-cli ps --cluster http-server-cluster
Name State Ports TaskDefinition Health
2a3da558-0dc6-4d26-9304-a5370520a4a4/http-server RUNNING 10.0.1.226:80->80/tcp http-server-task-definition:12 HEALTHY
3efbc3b2-6618-4ebd-86d2-138ea0b75529/http-server PENDING 10.0.1.203:80->80/tcp http-server-task-definition:13 UNKNOWN
6b6f5927-80b9-46ec-a6ff-8c4c85553804/http-server RUNNING 10.0.2.220:80->80/tcp http-server-task-definition:12 HEALTHY
7c0694c2-21aa-49a0-b0dd-04da880bfd34/http-server PENDING 10.0.1.85:80->80/tcp http-server-task-definition:13 UNKNOWN
8f0d5f0d-7321-4b75-9bea-b1423260f170/http-server RUNNING 10.0.1.19:80->80/tcp http-server-task-definition:12 HEALTHY
a6930cca-75ca-474e-9033-bfb1d889d14f/http-server RUNNING 10.0.1.193:80->80/tcp http-server-task-definition:12 HEALTHY
bd842250-6b40-4b5c-91fa-9cdaf2c7d30c/http-server PENDING 10.0.2.205:80->80/tcp http-server-task-definition:13 UNKNOWN
d4b0c60c-737c-42b3-9a74-4f1b7a5ae013/http-server RUNNING 10.0.2.79:80->80/tcp http-server-task-definition:12 HEALTHY
de437ee4-afbd-4ef1-95eb-3d9eeb0f189d/http-server PENDING 10.0.2.65:80->80/tcp http-server-task-definition:13 UNKNOWN
e684fa00-e7cb-4765-966b-92b15f1e008f/http-server PENDING 10.0.2.239:80->80/tcp http-server-task-definition:13 UNKNOWN
curl
の方ではnginxとApacheのレスポンスが交互に表示されたりもしますが、そのうちApacheになります。
$ curl [ALBのDNS名]
<html><body><h1>It works!</h1></body></html>
ecs-cli ps
の結果。
$ ecs-cli ps --cluster http-server-cluster
Name State Ports TaskDefinition Health
2a3da558-0dc6-4d26-9304-a5370520a4a4/http-server RUNNING 10.0.1.226:80->80/tcp http-server-task-definition:12 HEALTHY
3efbc3b2-6618-4ebd-86d2-138ea0b75529/http-server RUNNING 10.0.1.203:80->80/tcp http-server-task-definition:13 HEALTHY
6b6f5927-80b9-46ec-a6ff-8c4c85553804/http-server RUNNING 10.0.2.220:80->80/tcp http-server-task-definition:12 HEALTHY
7c0694c2-21aa-49a0-b0dd-04da880bfd34/http-server RUNNING 10.0.1.85:80->80/tcp http-server-task-definition:13 HEALTHY
8f0d5f0d-7321-4b75-9bea-b1423260f170/http-server RUNNING 10.0.1.19:80->80/tcp http-server-task-definition:12 HEALTHY
a6930cca-75ca-474e-9033-bfb1d889d14f/http-server RUNNING 10.0.1.193:80->80/tcp http-server-task-definition:12 HEALTHY
bd842250-6b40-4b5c-91fa-9cdaf2c7d30c/http-server RUNNING 10.0.2.205:80->80/tcp http-server-task-definition:13 HEALTHY
d4b0c60c-737c-42b3-9a74-4f1b7a5ae013/http-server RUNNING 10.0.2.79:80->80/tcp http-server-task-definition:12 HEALTHY
de437ee4-afbd-4ef1-95eb-3d9eeb0f189d/http-server RUNNING 10.0.2.65:80->80/tcp http-server-task-definition:13 HEALTHY
e684fa00-e7cb-4765-966b-92b15f1e008f/http-server RUNNING 10.0.2.239:80->80/tcp http-server-task-definition:13 HEALTHY
さらにしばらく待っていると、nginx側のコンテナが全部終了します。
$ ecs-cli ps --cluster http-server-cluster
3efbc3b2-6618-4ebd-86d2-138ea0b75529/http-server RUNNING 10.0.1.203:80->80/tcp http-server-task-definition:13 HEALTHY
7c0694c2-21aa-49a0-b0dd-04da880bfd34/http-server RUNNING 10.0.1.85:80->80/tcp http-server-task-definition:13 HEALTHY
bd842250-6b40-4b5c-91fa-9cdaf2c7d30c/http-server RUNNING 10.0.2.205:80->80/tcp http-server-task-definition:13 HEALTHY
de437ee4-afbd-4ef1-95eb-3d9eeb0f189d/http-server RUNNING 10.0.2.65:80->80/tcp http-server-task-definition:13 HEALTHY
e684fa00-e7cb-4765-966b-92b15f1e008f/http-server RUNNING 10.0.2.239:80->80/tcp http-server-task-definition:13 HEALTHY
2a3da558-0dc6-4d26-9304-a5370520a4a4/http-server STOPPED ExitCode: 0 80->80/tcp http-server-task-definition:12 HEALTHY
6b6f5927-80b9-46ec-a6ff-8c4c85553804/http-server STOPPED ExitCode: 0 80->80/tcp http-server-task-definition:12 HEALTHY
8f0d5f0d-7321-4b75-9bea-b1423260f170/http-server STOPPED ExitCode: 0 80->80/tcp http-server-task-definition:12 HEALTHY
a6930cca-75ca-474e-9033-bfb1d889d14f/http-server STOPPED ExitCode: 0 80->80/tcp http-server-task-definition:12 HEALTHY
d4b0c60c-737c-42b3-9a74-4f1b7a5ae013/http-server STOPPED ExitCode: 0 80->80/tcp http-server-task-definition:12 HEALTHY
簡単にですが、確認できた感じですね。