Amazon ECS でソフトウェアバージョンの一貫性が強制されるようになりました[Amazon ECS + AWS CloudFormation]

本記事は 夏休みクラウド自由研究 8/18付の記事です

こんにちは。SCSKのふくちーぬです。

2024/7/11に Amazon ECS のアップデートが発表されました。今回は、Amazon ECS においてローリングアップデート時のソフトウェアの一貫性が保証されるようになりましたので紹介します。

Amazon ECS でコンテナ化されたアプリケーションにソフトウェアバージョンの一貫性が強制されるようになりました

以前まではECSにおいてlatestタグを利用していた場合、latestタグの更新・タスクのスケールアウトのタイミング次第で、latestタグが参照するコンテナイメージが異なるため、サービス内でコンテンツが異なるタスクがデプロイされてしまう事象が発生していました。

2024/7/11のアップデートにより、イメージタグが更新された場合において、サービス内のすべてのタスクが同一であることを保証し、ユーザーに一貫性のあるアプリケーションを提供することが可能です。

以前までのデプロイ方式

実際にどのようなデプロイ方式であったか説明します。

以前の方式では、各タスク内でその都度コンテナイメージタグのイメージダイジェスト解決をしていました。

そのため、例えば以下のようなシナリオにおいてスケールアウトのタイミングでコンテンツが異なるタスクがユーザーに公開される可能性があります。

①タスク定義内のlatestタグに従い,2つのタスクが起動

②開発者がlatestタグを更新(latestタグを新しいコンテナイメージへ参照先を変更する)

③タスクのスケールアウトが発生し,タスク定義内のlatestタグに従い,1つのタスクが新規起動

このように、サービス内でタスク間で異なるイメージダイジェストを参照することで、意図しないWebコンテンツの公開が行われてしまいます。

最新のデプロイ方式

最新のデプロイ方式では、一連のデプロイメントにおいて、ECSサービス内でコンテナイメージタグがイメージダイジェストに解決されて保存されます。よって、同じタスクをユーザーに公開することが可能になりました。

先程と同様のシナリオで違いをみてみます。

①タスク定義内のlatestタグに従い,2つのタスクが起動

②開発者がlatestタグを更新(latestタグを新しいコンテナイメージへ参照先を変更する)

③タスクのスケールアウトが発生し,タスク定義内のlatestタグに従い,1つのタスクが新規起動

検証

今回のアップデートを早速検証してみます。イメージダイジェストに基づいてタスクが起動し、一連のデプロイでタスク間で同一のイメージダイジェストを参照していることを確認することがゴールとなります。

事前準備

VPC、サブネット、ルートテーブル、インターネットゲートウェイ、ネットワークACL、ECRが作成済みであることを確認してください。

ECRへコンテナイメージをpush

nginx-helloリポジトリにイメージをpushします。

下記のdockerfileを利用して、コンテナイメージを作成します。

# ベースイメージとしてnginxの公式イメージを使用
FROM nginx:latest

# "Hello, world!" を返すHTMLファイルを作成
RUN echo "Hello, world!" > /usr/share/nginx/html/index.html

#80番ポートで公開
EXPOSE 80 

Cloud9やCloudShell等のdockerインストール済みのサーバを用意して、上記のdockerfileをビルドします。

サーバのIAMロールには、下記の権限を付与しておいてください。

・arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser

docker build . -t <AWSアカウントId>.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-hello:latest

ECRへログインした後に、ECRのリポジトリにコンテナイメージをpushします。

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWSアカウントId>.dkr.ecr.ap-northeast-1.amazonaws.com
docker push <AWSアカウントId>.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-hello:latest

以下のように、ECRのリポジトリ内にコンテナイメージが1つ格納されます。

CloudFormationテンプレート

以下のテンプレートを使用して、デプロイします。

AWSTemplateFormatVersion: 2010-09-09
# ---------------------------------
# パラメータ
# ---------------------------------
Parameters: 
  Env:
    Type: String 
  VpcId:
    Type: String    
  PublicSubnetA:
    Type: String  
  PublicSubnetC:
    Type: String  
  MyIp:
    Type: String    

Resources:
  # ================================
  # ECS (Cluster)
  # ================================
  ECSCluster:
    Type: "AWS::ECS::Cluster"
    Properties:
      ClusterName: !Sub "ECS-${Env}-helloworld-cluster"
      ServiceConnectDefaults:
        Namespace: !Sub "ECS-${Env}-helloworld-cluster"
      CapacityProviders:
        - FARGATE
  # ================================
  # ECS (Task Difinition)
  # ================================          
  ECSTaskDefinition:
    Type: "AWS::ECS::TaskDefinition"
    UpdateReplacePolicy: Retain
    Properties:
      Cpu: 256
      ExecutionRoleArn: !Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
      Family: !Sub "ECS-${Env}-helloworld-taskdef"
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        - Name: helloworld
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/nginx-hello:latest"
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Sub "/ecs/ECS-${Env}-helloworld-service" 
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: latest
          PortMappings:
            - AppProtocol: http
              HostPort: 80
              Protocol: tcp
              ContainerPort: 80
              Name: helloworld-8080-tcp
          ReadonlyRootFilesystem: false
      RuntimePlatform: 
        CpuArchitecture: X86_64
        OperatingSystemFamily: LINUX
# ------------------------------------------------------------#
#  Security Group
# ------------------------------------------------------------#        
  ECSServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties: 
      GroupDescription: ecs security group 
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: !Ref MyIp
      SecurityGroupEgress:
        - IpProtocol: -1
          FromPort: -1
          ToPort: -1
          CidrIp: "0.0.0.0/0"     
# ------------------------------------------------------------#
#  ECS Service
# ------------------------------------------------------------#
  ECSService:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !Ref ECSCluster
      DesiredCount: 2
      DeploymentConfiguration: 
        DeploymentCircuitBreaker: 
          Enable: TRUE
          Rollback: TRUE
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DeploymentController:
        Type: ECS
      LaunchType: FARGATE
      NetworkConfiguration:
        AwsvpcConfiguration:
            AssignPublicIp: ENABLED
            SecurityGroups:
              - !Ref ECSServiceSecurityGroup
            Subnets:
              - !Ref PublicSubnetA 
              - !Ref PublicSubnetC 
      PlatformVersion: 1.4.0
      ServiceName: !Sub "ECS-${Env}-helloworld-service"
      TaskDefinition: !Ref ECSTaskDefinition  
# ------------------------------------------------------------#
#  ECS LogGroup
# ------------------------------------------------------------#
  ECSLogGroup:
    Type: "AWS::Logs::LogGroup"
    Properties:
      LogGroupName: !Sub "/ecs/ECS-${Env}-helloworld-service"  

Webサーバのコンテンツを確認

デプロイ済みのタスクのパブリックIPアドレスにアクセスして、コンテンツを確認します。

2つのコンテナどちらも”Hello, world!”が表示されます。

ECRへコンテナイメージを再push

下記のdockerfileを利用して、コンテナイメージを作成します。

# ベースイメージとしてnginxの公式イメージを使用
FROM nginx:latest

# "Hello, world! Welcome" を返すHTMLファイルを作成
RUN echo "Hello, world! Welcome" > /usr/share/nginx/html/index.html

#80番ポートで公開
EXPOSE 80 

上記のdockerfileをビルドします。

docker build . -t <AWSアカウントId>.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-hello:latest

ECRのリポジトリにコンテナイメージをpushします。

docker push <AWSアカウントId>.dkr.ecr.ap-northeast-1.amazonaws.com/nginx-hello:latest

以下のように、ECRのリポジトリ内のlatestタグの参照先が更新されます。

タスクの再起動

今回は検証目的のため、タスクを手動停止することで新規タスクを起動させます。もしAuto Scalingが設定されていれば、スケールアウトでも同様に新規タスクを起動させることができます。

タスクを1つ選択後に、「選択されたものを停止」を押下します。

「停止」を押下します。

すぐにタスクが停止され、新規タスクの再起動が始まります。

Webサーバのコンテンツを再確認

新規に起動したタスクのパブリックIPアドレスにアクセスして、コンテンツを確認します。

こちらも”Hello, world!”が表示されます。コンテナイメージとしては、古いのバージョンのものを参照していますね。タスクの詳細を確認してみます。

新規に起動したタスクのイメージURIを確認すると、古いバージョンのもの(以前のイメージダイジェスト)を参照していることが分かります。

ソフトウェアバージョンの一貫性の挙動を確かめました。これによってユーザーには、同一のコンテンツを提供することが可能になります。

注意事項

・実環境でECSプラットフォームバージョン1.3.0についても、ソフトウェアバージョンの一貫性がサポートされていました。つまり、現AWS環境ではECSプラットフォームバージョン1.3.0,1.4.0が利用できるため、全てソフトウェアバージョンの一貫性がサポートされることに注意してください。

※2024/8/13にて、本検証を行ったところ、1.3.0以上でもサポートされている挙動を確認したため、AWSサポートに問い合わせ、AWSドキュメントを修正いただいています。日本語ドキュメントでは、1.4.0以上でのみサポートされていると記述されておりますが、英語ドキュメントでは1.3.0以上でサポートしている旨に修正されています。

 

最後に

いかがだったでしょうか。

コンテナでlatestタグを利用している場合でも、ECSでのデプロイがより快適なものになりました。

最新アップデートを鵜吞みにするだけではなくきちんと動作検証をすることで、AWSドキュメントの不備にも気づくことができました。

本記事が皆様のお役にたてば幸いです。

ではサウナラ~🔥

タイトルとURLをコピーしました