AWS CDKでECSサービスのオートスケーリングのリソースを作成

AWS CDK (Cloud Development Kit) は IaC ツールで、コードを書くことでインフラストラクチャのプロビジョニングを行います。 好きなプログラミング言語でインフラストラクチャを定義し、CloudFormation テンプレートを自動生成できます。抽象化レベルが高いので、コード量が少ないです。

CDK 開発環境の構築

  • nvm で node.js をインストール
    $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
    $ . ~/.nvm/nvm.sh
    $ nvm install 16
    
  • cdk コマンドラインツール AWS CDK Toolkit のインストール
    $ npm install -g aws-cdk
    
  • Python の CDK パッケージのインストール
    $ pip install aws-cdk-lib
    
  • AWS アカウントとリージョンを環境変数に設定
    $ echo "export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)" >> ~/.bashrc
    $ echo "export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)" >> ~/.bashrc
    

AWS CDK Toolkit (CLI) コマンド

  • CDK プロジェクトの作成
    $ cdk init app --language python
    
  • CDK アプリから CloudFormation テンプレートを生成する
    $ cdk synth
    
  • ブートストラップスタックのデプロイ。CDK アプリを AWS 環境(AWS アカウントとリージョン)に初めてデプロイする前に実行する必要がある。
    $ cdk bootstrap
    
  • CDK アプリのデプロイ
    $ cdk deploy
    

CDK でリソースを作成

EC2 起動タイプで ECS サービスのオートスケーリングを行います。

まずは、ECS クラスター、Auto Scaling グループとキャパシティープロバイダーの作成です。 以下のようになります。

import os

import boto3
from aws_cdk import (
    App,
    Tags,
    Stack,
    Duration,
    Environment,
    aws_ecs as ecs,
    aws_ec2 as ec2,
    aws_ecr as ecr,
    aws_iam as iam,
    aws_autoscaling as autoscaling,
    aws_cloudwatch as cloudwatch,
    aws_applicationautoscaling as appscaling
)
from constructs import Construct


class ECSAutoScaling(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # 既存のVPC、サブネット、セキュリティグループ、IAMロールを使用
        vpc = ec2.Vpc.from_lookup(self, 'vpc', vpc_id='vpc-xxx')
        security_group = ec2.SecurityGroup.from_security_group_id(
            self, 'SG', 'sg-xxx')
        subnet_ids = [
            'subnet-xxx',
            'subnet-yyy',
        ]
        subnets = [ec2.Subnet.from_subnet_id(
            self, f'subnet{idx}', sid) for idx, sid in enumerate(subnet_ids)]
        asg_role = iam.Role.from_role_name(
            self, 'AsgRole', 'role_name')
        task_role = iam.Role.from_role_name(
            self, 'TaskRole', 'role_name')

        # ECSクラスターの作成
        cluster = ecs.Cluster(
            self, 'Cluster',
            cluster_name='cluster_name',
            vpc=vpc)

        # AutoScalingGroupの作成
        asg = autoscaling.AutoScalingGroup(
            self, "ASG",
            auto_scaling_group_name='auto_scaling_group_name',
            vpc=vpc,
            role=asg_role,
            vpc_subnets=ec2.SubnetSelection(subnets=subnets),
            instance_type=ec2.InstanceType("c6g.16xlarge"),
            machine_image=ecs.EcsOptimizedImage.amazon_linux2(
                ecs.AmiHardwareType.ARM),
            min_capacity=0,
            max_capacity=5,
            desired_capacity=0,
            security_group=security_group
        )
        # キャパシティープロバイダーの作成とクラスターへの関連付け
        capacity_provider = ecs.AsgCapacityProvider(
            self, 'AsgCapacityProvider',
            capacity_provider_name='capacity_provider_name',
            auto_scaling_group=asg
        )
        cluster.add_asg_capacity_provider(capacity_provider)

タスク定義とサービスの作成。


# タスク定義の作成
task_def = ecs.Ec2TaskDefinition(
    self, "TaskDef",
    family='task_family_name',
    network_mode=ecs.NetworkMode.BRIDGE,
    task_role=task_role,
    execution_role=task_role,
)
# 既存のECRリポジトリを使用
repository = ecr.Repository.from_repository_name(
    self, 'Repository', 'repository_name')
task_def.add_container(
    'Container',
    container_name='container_name',
    image=ecs.ContainerImage.from_ecr_repository(
        repository, tag='latest'),
    memory_limit_mib=1024,
    logging=ecs.LogDrivers.aws_logs(stream_prefix='cdk')
)

# サービスのキャパシティープロバイダー戦略
capacity_provider_strategy = ecs.CapacityProviderStrategy(
    capacity_provider=capacity_provider.capacity_provider_name,
    weight=1,
    base=0
)
# ECSサービスの作成
ecs_service = ecs.Ec2Service(
    self, 'Service',
    service_name='service_name',
    cluster=cluster,
    task_definition=task_def,
    desired_count=0,
    capacity_provider_strategies=[capacity_provider_strategy]
)

CloudWatch メトリクスとアラーム、ステップスケーリングポリシーの作成。

メトリクスは、boto3 か AWS CLI を使って最初のデータポイントを発行した後に作成されます。

cw = boto3.client('cloudwatch', region_name=os.getenv('AWS_REGION'))
cw.put_metric_data(
    Namespace='Namespace',
    MetricData=[{
        'MetricName': 'MetricName',
        'Value': 0,
        'Unit': 'Count',
    }]
)

Metric クラスはメトリクスを参照します。メトリクス自体を作成しないです。

This class does not represent a resource, so hence is not a construct. Instead, Metric is an abstraction that makes it easy to specify metrics for use in both alarms and graphs.

参考:https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_cloudwatch/Metric.html

# カスタムメトリクスの参照
metric = cloudwatch.Metric(
    namespace='namespace',
    metric_name='metric_name',
    period=Duration.minutes(1),
    unit=cloudwatch.Unit.COUNT
)
# アラームを作成
alarm = metric.create_alarm(
    self, 'Alarm',
    alarm_name='alarm_name',
    evaluation_periods=1,
    threshold=0,
)

# ECSサービスのタスクスケーリングポリシーの追加
scalable_target = ecs_service.auto_scale_task_count(
    max_capacity=5,
    min_capacity=0
)
scaling_steps = [
    appscaling.ScalingInterval(change=0, upper=0),
    appscaling.ScalingInterval(change=1, lower=1, upper=10),
    appscaling.ScalingInterval(change=2, lower=10, upper=20),
    appscaling.ScalingInterval(change=5, lower=20),
]
scalable_target.scale_on_metric(
    'AutoScalingPolicy',
    metric=alarm.metric,
    scaling_steps=scaling_steps,
    adjustment_type=appscaling.AdjustmentType.EXACT_CAPACITY,
    cooldown=Duration.minutes(3)
)
app: App = App()
stack = ECSAutoScaling(
    app, "ECSAutoScaling",
    env=Environment(
        account=os.getenv('AWS_ACCOUNT_ID'),
        region=os.getenv('AWS_REGION')
    )
)
# リソースのタグ付け
Tags.of(stack).add('key', 'value', include_resource_types=['AWS::ECS::TaskDefinition'])

app.synth()

Tags:

Updated: