AWSコンテナ設計読んでみる
コンテナオーケストレータとは、複数のコンテナを自動的に管理したり運用するソフトウェアのこと。
オーケストレータを使うと、以下を簡単に実現できる
- コンテナの配置管理の自動制御
- コンテナの負荷分散
- コンテナの状態監視と自動復旧
- コンテナのデプロイ
- アプリケーションをアップデートする際に、既に起動しているコンテナを停止して、新しいコンテナに置き換える必要がある。オーケストレータを活用することで、アプリケーションの稼働を正常に保ちつつ、新旧のコンテナを自動で入れ替えることができる(コンテナ全体が停止しないようにローリングアップデートしたりする)
オーケストレータはDockerレジストリを通して、イメージを取得して、コンテナをデプロイしてる
コンテナオーケストレータの事実上のデファクトスタンダードはKubernetes。
KubernetesはOSSなので、一般企業が使うとなると、クリティカルな課題が発生した際に対処がめんどくさい。
パブリッククラウドによっては、独自のオーケストレータを提供していて、
代表的なのがAmazon ECS
Kubernetesと比べて、各種AWSサービスとの親和性が高いのが特徴。
ECSはAWSの完全なサポートが受けられる。
分散システムとは、ネットワークで接続された複数のコンピューターが、それぞれに作業を分担し、連携して一つの大きなシステムとして機能する仕組みです。個々のコンピューターは独立していますが、全体としては1台の高性能なコンピューターのように動作し、単一のコンピューターでは処理しきれないような複雑なタスクも効率的に処理できます。
なるほど
分散システムを前提とした場合、コンテナ間の疎結合性や移植性を考慮した方式を検討す流必要がある
コンテナオーケストレーションは、コンテナのデプロイ、スケーリング、設定、ネットワーク接続、セキュリティ保護のプロセスを効率化し、エンジニアが他の重要なタスクに集中できるようにします。オーケストレーションはまた、コンテナの障害や停止を自動的に検知し対応することで、コンテナ化されたアプリケーションの高可用性を確保するのに役立ちます。
様々なコンテナオーケストレーターは自動化を異なる方法で実装しますが、いずれも「コントロールプレーン」と呼ばれる共通のコンポーネント群に依存しています。コントロールプレーンは、中央コントローラーから各コンテナへポリシーを適用する仕組みを提供します。これは本質的に運用エンジニアの役割を自動化し、コンテナに接続して様々な管理機能を実行するソフトウェアインターフェースを提供するものです。
https://www.datadoghq.com/knowledge-center/container-orchestration/#:~:text=Container orchestration streamlines the process,%2C network%2C and compute resources. https://www.datadoghq.com/knowledge-center/container-orchestration/#:~:text=Container orchestration streamlines the process,%2C network%2C and compute resources.
ECSはフルマネージドなコンテナオーケストレータ
コンテナが動作するコンポーネントをECSでは「タスク」と読んでいる。
タスクは1つ以上のコンテナから構成されるアプリケーションの実行単位。
タスク定義はタスクを定義するテンプレートのこと
タスク定義で割り当てリソース(CPU&メモリ)も定義できるのか
サービスは、指定した数だけタスクを維持するスケジューラのこと
オーケストレータのコア機能にあたる要素
ECSのクラスターとは、サービスとタスクを実行する論理グループ
EKSもコンテナオーケストレータの一つ
データプレーンとは、コンテナが実際に稼働するリソース環境を指す
ECS は、主に「クラスター」、「タスク」、「サービス」の 3 つのコンポーネントで構成されています。
なるほど
CloudFormation テンプレートは、AWS上に構築するインフラストラクチャを説明する JSON または YAML 言語でフォーマットされたテキストファイルです。要は設計書ですね。
なるほど
AWS CloudFormationは、AWSのクラウドサービスを操作するための指示を書いたコードを使って、自動的にインフラを構築したり管理したりするツールです。JSONやYAML形式でリソース情報をコードで記述(テンプレート化)することで、これを使って自動的にAWSリソース(スタック)が構築ができます。また、インフラの設計や変更内容がコードによって管理されるため、AWSリソースの作成、変更、削除が容易になる。
AWS CloudFormation: AWSリソースを、コードを用いて自動的に構築/管理するためのツール
CloudFormationを理解する上でテンプレートとスタックを理解する
テンプレート
- AWSリソースのパラメータをまとめた設計図
- どのリソースをどう起動するかすべて記述されている
- JSON/YAML形式で記述
スタック
- テンプレートによって作成されるリソースの集まり
- スタック単位でリソースの管理(作成、更新、削除)が可能
- スタックの作成・削除を行うと、スタックに紐づくリソースの作成・削除が一括で行われる(リソース単位で手を加える必要がない)
- 使用するリソースやリソースの構築順は、テンプレートの依存関係を見てCloudFormationが自動的に判断(こちら側で考える必要Nothing)
スタックはテンプレートによって作成されるリソースの集まり
CloudFormationテンプレートは10のセクションで構成されている。
ECS構築するときに参考になる
AWSTemplateFormatVersion: "2010-09-09"
Description:
ECS Creation
Metadata:
"AWS::CloudFormation::Interface": # MEMO: パラメータをグルーピングして表示したいときに使う
ParameterGroups:
- Label:
default: "Project Name Prefix"
Parameters:
- ProjectName
- Label:
default: "InternetALB Configuration"
Parameters:
- InternetALBName
- TargetGroupName
- Label:
default: "ECS Configuration"
Parameters:
- ECSClusterName
- ECSTaskName
- ECSTaskCPUUnit
- ECSTaskMemory
- ECSContainerName
- ECSImageName
- ECSServiceName
- ECSTaskDesiredCount
- Label:
default: "Scaling Configuration"
Parameters:
- ServiceScaleEvaluationPeriods
- ServiceCpuScaleOutThreshold
- ServiceCpuScaleInThreshold
- TaskMinContainerCount
- TaskMaxContainerCount
- Label:
default: "Network Configuration"
Parameters:
- VPCCIDR
- PublicSubnetACIDR
- PublicSubnetCCIDR
ParameterLabels: # MEMO: パラメータを別名で表示したいときに使う
InternetALBName:
default: "InternetALBName"
TargetGroupName:
default: "TargetGroupName"
ECSClusterName:
default: "ECSClusterName"
ECSTaskName:
default: "ECSTaskName"
ECSTaskCPUUnit:
default: "ECSTaskCPUUnit"
ECSTaskMemory:
default: "ECSTaskMemory"
ECSContainerName:
default: "ECSContainerName"
ECSImageName:
default: "ECSImageName"
ECSServiceName:
default: "ECSServiceName"
ECSTaskDesiredCount:
default: "ECSTaskDesiredCount"
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters: # MEMO: スタック作成時にテンプレートに対してカスタム値を受け渡すことができる
ProjectName:
Default: circleci-book
Type: String
ECSImageName:
Type: String
InternetALBName:
Type: String
Default: "alb"
TargetGroupName:
Type: String
Default: "tg"
ECSClusterName:
Type: String
Default: "cluster"
ECSTaskName:
Type: String
Default: "task"
ECSTaskCPUUnit:
AllowedValues: [ 256, 512, 1024, 2048, 4096 ]
Type: String
Default: "256"
ECSTaskMemory:
AllowedValues: [ 256, 512, 1024, 2048, 4096 ]
Type: String
Default: "512"
ECSContainerName:
Type: String
Default: "container"
ECSImageName:
Type: String
ECSServiceName:
Type: String
Default: "service"
ECSTaskDesiredCount:
Type: Number
Default: 1
ServiceScaleEvaluationPeriods:
Description: The number of periods over which data is compared to the specified threshold
Type: Number
Default: 2
MinValue: 2
ServiceCpuScaleOutThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling out
Default: 50
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
ServiceCpuScaleInThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling in
Default: 25
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
TaskMinContainerCount:
Type: Number
Description: Minimum number of containers to run for the service
Default: 1
MinValue: 1
ConstraintDescription: Value must be at least one
TaskMaxContainerCount:
Type: Number
Description: Maximum number of containers to run for the service when auto scaling out
Default: 2
MinValue: 1
ConstraintDescription: Value must be at least one
VPCCIDR:
Type: String
Default: "10.1.0.0/16"
PublicSubnetACIDR:
Type: String
Default: "10.1.10.0/24"
PublicSubnetCCIDR:
Type: String
Default: "10.1.20.0/24"
Resources:
# ------------------------------------------------------------#
# VPC
# ------------------------------------------------------------#
# VPC Create
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: !Ref VPCCIDR # MEMO: parametersのパラメータを参照してる
EnableDnsSupport: "true"
EnableDnsHostnames: "true"
InstanceTenancy: default
Tags:
- Key: Name
Value: !Sub "${ProjectName}-vpc"
# InternetGateway Create
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: Name
Value: !Sub "${ProjectName}-igw"
# IGW Attach
InternetGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment" # MEMO: internet gatewayをvpcにアタッチする
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ------------------------------------------------------------#
# SecurityGroup
# ------------------------------------------------------------#
# SecurityGroup Create
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${ProjectName}-sg"
GroupDescription: "Security Group for CircleCI Book"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: '0.0.0.0/0'
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: '0'
ToPort: '65535'
CidrIp: '0.0.0.0/0'
VpcId: !Ref VPC
# ------------------------------------------------------------#
# Subnet
# ------------------------------------------------------------#
# Public SubnetA Create
PublicSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1a"
CidrBlock: !Ref PublicSubnetACIDR
VpcId: !Ref VPC # MEMO: リソースの論理名を指定すると、そのリソースを識別するために使用する値が返される
Tags:
- Key: Name
Value: !Sub "${ProjectName}-public-subnet-a" # MEMO: !Subはパラメータをを展開して文字列にできる
# Public SubnetC Create
PublicSubnetC:
Type: "AWS::EC2::Subnet"
Properties:
AvailabilityZone: "ap-northeast-1c"
CidrBlock: !Ref PublicSubnetCCIDR
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${ProjectName}-public-subnet-c"
# ------------------------------------------------------------#
# RouteTable
# ------------------------------------------------------------#
# Public RouteTableA Create
PublicRouteTableA:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${ProjectName}-public-route-a"
# Public RouteTableC Create
PublicRouteTableC:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${ProjectName}-public-route-c"
# ------------------------------------------------------------#
# Routing
# ------------------------------------------------------------#
# PublicRouteA Create
PublicRouteA:
Type: "AWS::EC2::Route" # MEMO: ルートテーブルのルートを定義しテーブルのルートを定義しているルートを定義している
Properties:
RouteTableId: !Ref PublicRouteTableA
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
# PublicRouteC Create
PublicRouteC:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTableC
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------#
# PublicRouteTable Associate SubnetA
PublicSubnetARouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation" # SubnetとRouteTableを関連付ける
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicRouteTableA
# PublicRouteTable Associate SubnetC
PublicSubnetCRouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetC
RouteTableId: !Ref PublicRouteTableC
# ------------------------------------------------------------#
# Target Group
# ------------------------------------------------------------#
TargetGroup:
Type: "AWS::ElasticLoadBalancingV2::TargetGroup"
Properties:
VpcId: !Ref VPC
Name: !Sub "${ProjectName}-${TargetGroupName}"
Protocol: HTTP
Port: 80
TargetType: ip
# ------------------------------------------------------------#
# Internet ALB
# ------------------------------------------------------------#
InternetALB:
Type: "AWS::ElasticLoadBalancingV2::LoadBalancer"
Properties:
Name: !Sub "${ProjectName}-${InternetALBName}"
Tags:
- Key: Name
Value: !Sub "${ProjectName}-${InternetALBName}"
Scheme: "internet-facing"
LoadBalancerAttributes:
- Key: "deletion_protection.enabled"
Value: false
- Key: "idle_timeout.timeout_seconds"
Value: 60
SecurityGroups:
- !Ref InstanceSecurityGroup
Subnets:
- !Ref PublicSubnetA
- !Ref PublicSubnetC
ALBListener:
Type: "AWS::ElasticLoadBalancingV2::Listener"
Properties:
DefaultActions:
- TargetGroupArn: !Ref TargetGroup
Type: forward
LoadBalancerArn: !Ref InternetALB
Port: 80
Protocol: HTTP
# ------------------------------------------------------------#
# ECS Cluster
# ------------------------------------------------------------#
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: !Sub "${ProjectName}-${ECSClusterName}"
# ------------------------------------------------------------#
# ECS LogGroup
# ------------------------------------------------------------#
ECSLogGroup:
Type: "AWS::Logs::LogGroup" # MEMO: ロググループは、Amazon CloudWatch Logs内でログデータを論理的にまとめた単位
Properties:
LogGroupName: !Sub "/ecs/logs/${ProjectName}-ecs-group"
# ------------------------------------------------------------#
# ECS Task Execution Role
# ------------------------------------------------------------#
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy"
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# ------------------------------------------------------------#
# ECS TaskDefinition
# ------------------------------------------------------------#
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
Cpu: !Ref ECSTaskCPUUnit
ExecutionRoleArn: !Ref ECSTaskExecutionRole
Family: !Sub "${ProjectName}-${ECSTaskName}"
Memory: !Ref ECSTaskMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
#ContainerDefinitions
ContainerDefinitions:
- Name: !Sub "${ProjectName}-${ECSContainerName}"
Image: !Ref ECSImageName
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref ECSLogGroup
awslogs-region: !Ref "AWS::Region"
awslogs-stream-prefix: !Ref ProjectName
MemoryReservation: 128
PortMappings:
- HostPort: 80
Protocol: tcp
ContainerPort: 80
# ------------------------------------------------------------#
# ECS Service
# ------------------------------------------------------------#
ECSService:
Type: AWS::ECS::Service
DependsOn: ALBListener
Properties:
Cluster: !Ref ECSCluster
DesiredCount: !Ref ECSTaskDesiredCount
LaunchType: FARGATE
LoadBalancers:
-
TargetGroupArn: !Ref TargetGroup
ContainerPort: 80
ContainerName: !Sub "${ProjectName}-${ECSContainerName}"
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref InstanceSecurityGroup
Subnets:
- !Ref PublicSubnetA
- !Ref PublicSubnetC
ServiceName: !Sub "${ProjectName}-${ECSServiceName}"
TaskDefinition: !Ref ECSTaskDefinition
# ------------------------------------------------------------#
# Auto Scaling Service
# ------------------------------------------------------------#
ServiceAutoScalingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: application-autoscaling.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: !Sub "${ProjectName}-${ECSContainerName}-autoscaling"
PolicyDocument:
Statement:
- Effect: Allow
Action:
- application-autoscaling:*
- cloudwatch:DescribeAlarms
- cloudwatch:PutMetricAlarm
- ecs:DescribeServices
- ecs:UpdateService
Resource: '*'
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId: !Sub
- service/${EcsClusterName}/${EcsDefaultServiceName}
- EcsClusterName: !Ref ECSCluster
EcsDefaultServiceName: !Sub "${ProjectName}-${ECSServiceName}"
RoleARN: !GetAtt ServiceAutoScalingRole.Arn
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
DependsOn:
- ECSService
- ServiceAutoScalingRole
ServiceScaleOutPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutPolicy"
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: 1
MetricIntervalLowerBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleInPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInPolicy"
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: -1
MetricIntervalUpperBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleOutAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleOutAlarm"
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleOutThreshold
AlarmDescription: Alarm to add capacity if CPU is high
Period: 60
AlarmActions:
- !Ref ServiceScaleOutPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value: !Ref ECSCluster
- Name: ServiceName
Value: !Sub "${ProjectName}-${ECSServiceName}"
ComparisonOperator: GreaterThanThreshold
MetricName: CPUUtilization
DependsOn:
- ECSService
- ServiceScaleOutPolicy
ServiceScaleInAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${ProjectName}-${ECSServiceName}-ScaleInAlarm"
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleInThreshold
AlarmDescription: Alarm to reduce capacity if container CPU is low
Period: 300
AlarmActions:
- !Ref ServiceScaleInPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value: !Ref ECSCluster
- Name: ServiceName
Value: !Sub "${ProjectName}-${ECSServiceName}"
ComparisonOperator: LessThanThreshold
MetricName: CPUUtilization
DependsOn:
- ECSService
- ServiceScaleInPolicy