Fargateでスタンドアロンタスクを動かす（CloudFormation、AWS CLI版）

# はじめに
前回、Fargateでスタンドアロンタスクを、コンソールから動かしました。

https://qiita.com/a_b_/items/36e6fb6f5ca416aa9fd3

今回はClouFormationで作って、CLIで動かしてみます。

# 概要

前回と同じ部分は以下です。
- 文字列をPrintするだけのタスクを動かします
    - CloudWatch Logsに出力されます
- Dockerコンテナを作り、ECRにPUSHして、それを実行します
    - 使うベースのイメージはUbuntu
        - Pythonをインストールするところからです

以下から、前回と異なります。
- ECSリソースをCFnで作ります
    - ECSクラスター
    - IAMロール
    - CloudWatchロググループ
    - ECSタスク定義
- 実行用のVPCも作ります。パブリックサブネットで動かします
- ECSタスク実行はAWS CLI


# やってみた

## 環境～コンテナをECRにPUSH　まで

前回とまるまる同じです。

- [環境](https://qiita.com/a_b_/items/36e6fb6f5ca416aa9fd3#%E7%92%B0%E5%A2%83)
- [コンテナ作成](https://qiita.com/a_b_/items/36e6fb6f5ca416aa9fd3#%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E4%BD%9C%E6%88%90)
- [ECRにPUSH](https://qiita.com/a_b_/items/36e6fb6f5ca416aa9fd3#ecr%E3%81%ABpush)


## VPC作成
前回はデフォルトVPCを使いましたが、今回はVPCを作ってみます。
サービスエンドポイントに接続できる必要があるため、パブリックサブネットにしました。

:::note
プライベートサブネットの場合は以下の記事が参考になりそうです。
https://dev.classmethod.jp/articles/vpc-endpoints-for-ecs-2022/
:::

以前の記事を参考に、VPC用のCFnテンプレートを作ります。

https://qiita.com/a_b_/items/4ab82e2da6f7b61536ac#cloudformation%E3%81%8B%E3%82%89%E4%BD%9C%E6%88%90

後続で使う、サブネットIDとセキュリティグループIDを出力しておきます。


```bash
touch createVpc.yaml
```

```yaml:createVpc.yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  VpcName:
    Type: String
    Default: forFargateTask
    Description: Name of the VPC
  VpcCIDR:
    Type: String
    Default: 10.70.0.0/16
    Description: CIDR block for the VPC

Resources:
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VpcName

  InternetGateway:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${VpcName}-igw

  VPCGatewayAttachment:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicSubnet:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPC
      CidrBlock: !Select [ 0, !Cidr [ !Ref VpcCIDR, 24, 8 ] ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VpcName}-subnet

  PublicRouteTable:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${VpcName}-rtb-public
  PublicRoute:
    Type: 'AWS::EC2::Route'
    DependsOn: VPCGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGateway

  SubnetRouteTableAssociation:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: "for Fargate Task"
  
Outputs:
  SubnetForFargateTask:
    Value: !Ref PublicSubnet

  SgForFargateTask:
    Value: !Ref SecurityGroup
```

```bash
# VPC作成
VPCSTACKNAME="create-vpc-fargatetask"
VPCNAME="forFargateTask"
VPCCIDR="10.70.0.0/16"

aws cloudformation create-stack --stack-name ${VPCSTACKNAME} \
  --template-body file://createVpc.yaml \
  --region ${REGION} \
  --parameters \
    ParameterKey=VpcName,ParameterValue=${VPCNAME} \
    ParameterKey=VpcCIDR,ParameterValue=${VPCCIDR}
```

## ECSリソース作成

使用するECSなどに関連するリソースをまとめて作ります。

- ECSクラスター
- タスク実行ロール
- CloudWatchロググループ
- ECSタスク

```bash
touch createStandaloneEcs.yaml
```

```yaml:createStandaloneEcs.yaml
AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  ClusterName:
    Type: String
    Default: clusterForStandalone
  TaskName:
    Type: String
    Default: taskForStandalone
  ImageUri:
    Type: String
    
Resources:
  ########################################################
  ### Ecs Cluster
  ########################################################
  EcsCluster:
    Type: 'AWS::ECS::Cluster'
    Properties:
      ClusterName: !Ref ClusterName
      CapacityProviders:
        - FARGATE
        - FARGATE_SPOT
      ClusterSettings:
        -
          Name: containerInsights
          Value: disabled
      DefaultCapacityProviderStrategy:
        - CapacityProvider: FARGATE
          Weight: 1
        - CapacityProvider: FARGATE_SPOT
          Weight: 1
      Configuration:
        ExecuteCommandConfiguration:
          Logging: DEFAULT
      ServiceConnectDefaults:
        Namespace: !Ref ClusterName

  ########################################################
  ### IAM Role
  ########################################################
  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      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
        
  ########################################################
  ### Log Group
  ########################################################
  TaskLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/ecs/${TaskName}"
      RetentionInDays: 3653

  ########################################################
  ### Ecs Task
  ########################################################
  EcsTask: 
    Type: AWS::ECS::TaskDefinition
    Properties: 
      ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: 256
      Memory: 512
      ContainerDefinitions: 
        - 
          Name: !Ref TaskName
          Image: !Ref ImageUri
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref TaskLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: ecs
Outputs:    
  EcsClusterArnForFargateTask:
    Value: !GetAtt EcsCluster.Arn
  EcsTaskDefArnForFargateTask:
    Value: !GetAtt EcsTask.TaskDefinitionArn
```

```bash
# 作成
ECSSTACKNAME="create-ecs-standalone"
CLUSTERNAME="clusterForStandalone"
TASKNAME="taskForStandalone"
IMAGEURI=`aws cloudformation describe-stacks --stack-name ${REPSTACKNAME} --query "Stacks[].Outputs[?OutputKey=='Task1RepositoryUri'].[OutputValue]"  --output text`:latest

aws cloudformation create-stack --stack-name ${ECSSTACKNAME} \
  --template-body file://createStandaloneEcs.yaml \
  --region ${REGION} \
  --parameters \
    ParameterKey=ClusterName,ParameterValue=${CLUSTERNAME} \
    ParameterKey=TaskName,ParameterValue=${TASKNAME} \
    ParameterKey=ImageUri,ParameterValue=${IMAGEURI} \
  --capabilities CAPABILITY_NAMED_IAM
```

## ECSタスク実行

以下のページを参考に、AWS CLIから実行します。

https://michimani.net/post/aws-run-ecs-task-using-aws-cli/

```bash
# ECSの情報
ECSCLUSTER_ARN=`aws cloudformation describe-stacks --stack-name ${ECSSTACKNAME} --query "Stacks[].Outputs[?OutputKey=='EcsClusterArnForFargateTask'].[OutputValue]"  --output text`
ECSTASKDEF_ARN=`aws cloudformation describe-stacks --stack-name ${ECSSTACKNAME} --query "Stacks[].Outputs[?OutputKey=='EcsTaskDefArnForFargateTask'].[OutputValue]"  --output text`

# VPCの情報
SUBNET_ID=`aws cloudformation describe-stacks --stack-name ${VPCSTACKNAME} --query "Stacks[].Outputs[?OutputKey=='SubnetForFargateTask'].[OutputValue]"  --output text`
SG_ID=`aws cloudformation describe-stacks --stack-name ${VPCSTACKNAME} --query "Stacks[].Outputs[?OutputKey=='SgForFargateTask'].[OutputValue]"  --output text`
NETWORK_CONFIG="awsvpcConfiguration={subnets=[${SUBNET_ID}],securityGroups=[${SG_ID}],assignPublicIp=ENABLED}"

aws ecs run-task \
  --cluster ${ECSCLUSTER_ARN} \
  --task-definition ${ECSTASKDEF_ARN} \
  --network-configuration "${NETWORK_CONFIG}" \
  --launch-type FARGATE
```

作成したクラスターのタスク情報を、コンソールから見てみると、ちゃんと実行されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/f84901ef-fc40-1068-add0-004314b49b06.png)

CloudWatchにも出力されていました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/0e4c73e0-c1d1-19da-612e-df6703435026.png)

# おわりに

サクッと動かすのであればコンソールを使うのが便利ですが、何度も動かしたり実行時の設定値を見直す場合はコード化しておくと大変便利です。必要に応じて使い分けたいと思います。

この記事がどなたかのお役に立てれば幸いです。