Open28

AWSコンテナ設計読んでみる

ハガユウキハガユウキ

コンテナオーケストレータとは、複数のコンテナを自動的に管理したり運用するソフトウェアのこと。

ハガユウキハガユウキ

オーケストレータを使うと、以下を簡単に実現できる

  • コンテナの配置管理の自動制御
  • コンテナの負荷分散
  • コンテナの状態監視と自動復旧
  • コンテナのデプロイ
    • アプリケーションをアップデートする際に、既に起動しているコンテナを停止して、新しいコンテナに置き換える必要がある。オーケストレータを活用することで、アプリケーションの稼働を正常に保ちつつ、新旧のコンテナを自動で入れ替えることができる(コンテナ全体が停止しないようにローリングアップデートしたりする)
ハガユウキハガユウキ

オーケストレータはDockerレジストリを通して、イメージを取得して、コンテナをデプロイしてる

ハガユウキハガユウキ

コンテナオーケストレータの事実上のデファクトスタンダードはKubernetes。
KubernetesはOSSなので、一般企業が使うとなると、クリティカルな課題が発生した際に対処がめんどくさい。
パブリッククラウドによっては、独自のオーケストレータを提供していて、
代表的なのがAmazon ECS
Kubernetesと比べて、各種AWSサービスとの親和性が高いのが特徴。
ECSはAWSの完全なサポートが受けられる。

ハガユウキハガユウキ

分散システムとは、ネットワークで接続された複数のコンピューターが、それぞれに作業を分担し、連携して一つの大きなシステムとして機能する仕組みです。個々のコンピューターは独立していますが、全体としては1台の高性能なコンピューターのように動作し、単一のコンピューターでは処理しきれないような複雑なタスクも効率的に処理できます。

https://hnavi.co.jp/knowledge/blog/distribution_system/

なるほど

ハガユウキハガユウキ

分散システムを前提とした場合、コンテナ間の疎結合性や移植性を考慮した方式を検討す流必要がある

ハガユウキハガユウキ

コンテナオーケストレーションは、コンテナのデプロイ、スケーリング、設定、ネットワーク接続、セキュリティ保護のプロセスを効率化し、エンジニアが他の重要なタスクに集中できるようにします。オーケストレーションはまた、コンテナの障害や停止を自動的に検知し対応することで、コンテナ化されたアプリケーションの高可用性を確保するのに役立ちます。
様々なコンテナオーケストレーターは自動化を異なる方法で実装しますが、いずれも「コントロールプレーン」と呼ばれる共通のコンポーネント群に依存しています。コントロールプレーンは、中央コントローラーから各コンテナへポリシーを適用する仕組みを提供します。これは本質的に運用エンジニアの役割を自動化し、コンテナに接続して様々な管理機能を実行するソフトウェアインターフェースを提供するものです。

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では「タスク」と読んでいる。
タスクは1つ以上のコンテナから構成されるアプリケーションの実行単位。

ハガユウキハガユウキ

タスク定義はタスクを定義するテンプレートのこと
タスク定義で割り当てリソース(CPU&メモリ)も定義できるのか

ハガユウキハガユウキ

サービスは、指定した数だけタスクを維持するスケジューラのこと
オーケストレータのコア機能にあたる要素

ハガユウキハガユウキ

ECSのクラスターとは、サービスとタスクを実行する論理グループ

ハガユウキハガユウキ

データプレーンとは、コンテナが実際に稼働するリソース環境を指す

ハガユウキハガユウキ

AWS Fargateとは、ECSとEKSの両方で動作するコンテナ向けサーバレスコンピューティングエンジンである

ハガユウキハガユウキ

Fargateは単体では利用できず、ECSやEKSとセットで利用する必要がある
Fargateの最大のメリットはホスト管理が不要となる点

ハガユウキハガユウキ

FargateはEC2を使うよりも高いと言われている。
ビジネスとして差別化しにくいOSのセキュリティパッチ適用や各種ライブラリのアップデート作業のために作業者を確保する必要がなくなったので、そこが良い

ハガユウキハガユウキ

CloudFormation テンプレートは、AWS上に構築するインフラストラクチャを説明する JSON または YAML 言語でフォーマットされたテキストファイルです。要は設計書ですね。

https://www.isoroot.jp/blog/6760/
なるほど

ハガユウキハガユウキ

AWS CloudFormationは、AWSのクラウドサービスを操作するための指示を書いたコードを使って、自動的にインフラを構築したり管理したりするツールです。JSONやYAML形式でリソース情報をコードで記述(テンプレート化)することで、これを使って自動的にAWSリソース(スタック)が構築ができます。また、インフラの設計や変更内容がコードによって管理されるため、AWSリソースの作成、変更、削除が容易になる。

AWS CloudFormation: AWSリソースを、コードを用いて自動的に構築/管理するためのツール

CloudFormationを理解する上でテンプレートとスタックを理解する

テンプレート

  • AWSリソースのパラメータをまとめた設計図
  • どのリソースをどう起動するかすべて記述されている
  • JSON/YAML形式で記述

スタック

  • テンプレートによって作成されるリソースの集まり
  • スタック単位でリソースの管理(作成、更新、削除)が可能
  • スタックの作成・削除を行うと、スタックに紐づくリソースの作成・削除が一括で行われる(リソース単位で手を加える必要がない)
  • 使用するリソースやリソースの構築順は、テンプレートの依存関係を見てCloudFormationが自動的に判断(こちら側で考える必要Nothing)

スタックはテンプレートによって作成されるリソースの集まり
CloudFormationテンプレートは10のセクションで構成されている。
https://www.isoroot.jp/blog/6587/

ハガユウキハガユウキ

ヒューマンエラーの解決、環境の再現性の高さが特にメリットだなと個人的に感じた

ハガユウキハガユウキ

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