こんにちは、香田です。
今回はECS on FargateでBastion用のコンテナをスケジュール起動、停止する方法について紹介していきます。
AWS環境を構築するにあたって、terraformを利用し各リソースを作成していくので必要に応じてインストールしてください。
ECS on Fargateで稼働させるBastionコンテナについて
今回構築するBastion(踏み台ホスト)ですが、下記のようにECS on Fargate上でコンテナとして構築していきます。
BastionへのログインにはECS Exec
を使用し、Application Auto Scaling
で指定したスケジュールで起動停止するように設定する流れとなります。
ECS Execの詳細については下記を参照してみてください。
Bastionコンテナイメージの作成
はじめにBastionコンテナ用のDockerfileを作成します。
Bastionコンテナへ必要なパッケージは下記のDockerfileへ追加しインストールしていきます。
FROM amazonlinux:2
RUN amazon-linux-extras install -y
RUN yum update -y \
&& yum install \
systemd \
tar \
unzip \
sudo \
jq \
less \
-y
RUN curl -OL https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip \
&& unzip awscli-exe-linux-x86_64.zip \
&& ./aws/install
RUN useradd "ec2-user" && echo "ec2-user ALL=NOPASSWD: ALL" >> /etc/sudoers
CMD ["/sbin/init"]
Terraform AWSプロバイダの設定
AWSのリソース作成にterraformを利用するため、AWSプロバイダを設定します。
provider "aws" {
region = "ap-northeast-1"
}
ECR リポジトリの作成
作成したコンテナイメージの保存先としてECRを作成します。
イメージは5世代まで保持するようにライフサイクルポリシーを設定します。
resource "aws_ecr_repository" "bastion" {
name = "bastion"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
resource "aws_ecr_lifecycle_policy" "bastion" {
repository = aws_ecr_repository.bastion.name
policy = jsonencode({
"rules" : [
{
"rulePriority" : 1,
"description" : "最新のイメージを5つ保持",
"selection" : {
"tagStatus" : "any",
"countType" : "imageCountMoreThan",
"countNumber" : 5
},
"action" : {
"type" : "expire"
}
}
]
})
}
CloudWatch Logsの作成
BastionコンテナとECS Exec実行時のログ出力先としてCloudWatch Logsを作成します。
resource "aws_cloudwatch_log_group" "ecs_exec" {
name = "/ecs/ecs-exec"
retention_in_days = 14
}
resource "aws_cloudwatch_log_group" "bastion" {
name = "/ecs/bastion"
retention_in_days = 14
}
ECS クラスターの作成
ECS クラスターを作成します。
resource "aws_ecs_cluster" "bastion" {
name = "bastion-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
configuration {
execute_command_configuration {
logging = "OVERRIDE"
log_configuration {
cloud_watch_log_group_name = aws_cloudwatch_log_group.ecs_exec.name
}
}
}
}
IAM ロールの作成
ECSで利用するIAM ロールとして、「ECS タスク実行 IAM ロール」と「タスク用のIAM ロール」を作成します。
ECS タスク実行 IAM ロールを作成します。
resource "aws_iam_role" "ecs_task_execution_role" {
name = "ecs-task-execution-role"
path = "/"
assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Action" : "sts:AssumeRole",
"Principal" : {
"Service" : "ecs-tasks.amazonaws.com"
},
"Effect" : "Allow"
}
]
})
}
resource "aws_iam_role_policy" "ecs_task_execution_role" {
name = "ecs-task-execution-role"
role = aws_iam_role.ecs_task_execution_role.name
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource" : "*"
}
]
})
}
タスク用のIAM ロールを作成します。
resource "aws_iam_role" "ecs_task_role" {
name = "ecs-task-role"
path = "/"
assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Action" : "sts:AssumeRole",
"Principal" : {
"Service" : "ecs-tasks.amazonaws.com"
},
"Effect" : "Allow"
}
]
})
}
resource "aws_iam_role_policy" "ecs_task_role" {
name = "ecs-task-role"
role = aws_iam_role.ecs_task_role.name
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel",
],
"Resource" : "*"
},
{
"Effect" : "Allow",
"Action" : [
"logs:DescribeLogGroups",
"logs:CreateLogStream",
"logs:DescribeLogStreams",
"logs:PutLogEvents"
],
"Resource" : "*"
}
]
})
}
ECS タスク定義の作成
Bastionコンテナ用のタスク定義を作成します。
data "aws_region" "current" {}
resource "aws_ecs_task_definition" "bastion" {
family = "bastion"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
task_role_arn = aws_iam_role.ecs_task_role.arn
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([
{
"name" : "bastion",
"image" : "${aws_ecr_repository.bastion.repository_url}:latest",
"command" : [
"sleep",
"86400"
],
"essential" : true,
"linuxParameters" : {
"initProcessEnabled" : true
}
"logConfiguration" : {
"logDriver" : "awslogs",
"options" : {
"awslogs-group" : aws_cloudwatch_log_group.bastion.name,
"awslogs-stream-prefix" : "bastion",
"awslogs-region" : data.aws_region.current.name
}
}
}
])
}
ECS サービスの作成
Bastionコンテナ用のECS サービスを作成します。
ECS サービスで利用するサブネットは、デフォルトVPCのサブネットを利用しています。
Security Groupはアウトバウンド ルールのみ追加し作成しています。
resource "aws_default_vpc" "default" {}
resource "aws_security_group" "bastion" {
name = "bastion"
vpc_id = aws_default_vpc.default.id
}
data "aws_subnet_ids" "default" {
vpc_id = aws_default_vpc.default.id
}
resource "aws_security_group_rule" "bastion_egress_all" {
security_group_id = aws_security_group.bastion.id
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_ecs_service" "bastion" {
name = "bastion"
cluster = aws_ecs_cluster.bastion.name
task_definition = aws_ecs_task_definition.bastion.arn
launch_type = "FARGATE"
desired_count = "0"
enable_execute_command = true
network_configuration {
subnets = data.aws_subnet_ids.default.ids
security_groups = [
aws_security_group.bastion.id
]
assign_public_ip = true
}
lifecycle {
ignore_changes = [
desired_count,
]
}
}
Application Auto Scalingでスケジュール設定
Application Auto Scalingを利用し、ECS サービスをスケジュールにて起動、停止するように設定します。
スケジュール設定として月曜〜金曜日で9時に起動し、21時に停止するように設定しています。
resource "aws_appautoscaling_target" "bastion" {
max_capacity = 0
min_capacity = 0
resource_id = "service/${aws_ecs_cluster.bastion.name}/${aws_ecs_service.bastion.name}"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
lifecycle {
ignore_changes = [
min_capacity,
max_capacity
]
}
}
resource "aws_appautoscaling_scheduled_action" "bastion_scale_out" {
name = "bastion-scale-out"
service_namespace = aws_appautoscaling_target.bastion.service_namespace
resource_id = aws_appautoscaling_target.bastion.resource_id
scalable_dimension = aws_appautoscaling_target.bastion.scalable_dimension
schedule = "cron(00 09 ? * MON-FRI *)"
timezone = "Asia/Tokyo"
scalable_target_action {
min_capacity = 1
max_capacity = 1
}
}
resource "aws_appautoscaling_scheduled_action" "bastion_scale_in" {
name = "bastion-scale-in"
service_namespace = aws_appautoscaling_target.bastion.service_namespace
resource_id = aws_appautoscaling_target.bastion.resource_id
scalable_dimension = aws_appautoscaling_target.bastion.scalable_dimension
schedule = "cron(00 21 ? * MON-FRI *)"
timezone = "Asia/Tokyo"
scalable_target_action {
min_capacity = 0
max_capacity = 0
}
}
AWSリソースの作成
terraform applyを実行しAWSリソースを作成します。
$ terraform init
$ terraform apply
ECRへイメージ登録
イメージをビルドしECRへ登録していきます。
ECRへログインします。
$ export ECR_URI_BASE=$(aws sts get-caller-identity --query Account --output text).dkr.ecr.ap-northeast-1.amazonaws.com
$ aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI_BASE
ビルドしECR リポジトリへ登録します。
$ docker build -t bastion:latest .
$ docker tag bastion:latest $ECR_URI_BASE/bastion:latest
$ docker push $ECR_URI_BASE/bastion:latest
Bastionコンテナへログイン
Application Auto Scalingのcron設定を適宜調整し、Bastionコンテナを起動させます。
ECS Execでコンテナへログインします。
$ export ECS_CLUSTER=bastion-cluster
$ export TASK_ID=$(aws ecs list-tasks --cluster ${ECS_CLUSTER} | jq -r ".taskArns[]" | cut -d "/" -f 3)
$ aws ecs execute-command --cluster ${ECS_CLUSTER} --task ${TASK_ID} \
--container bastion --command "/bin/bash" --interactive
コンテナよりexit
でログアウトすると、CloudWatch Logsに下記のように操作履歴のログが出力されているのが確認できるはずです。
さいごに
ECS on FargateでBastion用のコンテナをスケジュール起動、停止する方法如何でしたでしょうか?
Application Auto Scalingでスケジュール起動、停止することで、業務時間以外などは基本停止運用し利用料金を抑えることが可能となります。
またECS Execを利用することで、SSH接続が不要となる為SSHキーやOSユーザの管理が不要となり、IAMユーザ側に集約できるので管理コストの面でもメリットがあるのではないでしょうか。
Bastionコンテナの配置先サブネットもECS Execによって、プライベートネットワークに配置可能となりセキュアに運用できるのではないでしょうか。
最後までご覧いただきありがとうございます。