AWS EC2の新インスタンス「A1」("Graviton"/ARM64)をECSで使ってみた

# はじめに

前回投稿した記事「[AWS EC2の新インスタンス「A1」("Graviton"/ARM64)でDockerを動かしてみる](https://qiita.com/hideaki_aoyagi/items/877e8316cbb35293dfb0)」で、「A1インスタンスはまだECSやEKSに対応していない」と書きました。

しかし、よく調べてみると実は既にECSには対応していましたので、実際に試してみた内容を書き記したいと思います。


# ECSマネジメントコンソールは、まだ対応していないが・・・

ECSのマネジメントコンソールから「クラスターの作成」ウィザードを使うと、ECSクラスターと同時にコンテナインスタンス(EC2)も作成することができます。

ただ現時点では、EC2インスタンスタイプの選択肢に「A1インスタンス」シリーズは入っておらず選択できません。
![インスタンスタイプの選択](https://qiita-image-store.s3.amazonaws.com/0/273982/6fea8e62-6a31-88f3-6cf6-fd7cca4ea855.png)

「Manually enter desired instance type」にチェックを入れてインスタンスタイプを手入力(a1.medium等)すれば一見指定できそうですが、AMI IDがx86用で固定されているためEC2の起動に失敗します。
![インスタンスタイプの直接指定](https://qiita-image-store.s3.amazonaws.com/0/273982/496e4f2d-3b0b-ea7e-cb1c-45cae1e3ad0e.png)

このような状況を確認したため、前回の記事では「ECSには対応していない」と書きました。


# AWSドキュメント(英語版)には既に情報が掲載されていた

英語版のAWSドキュメントで下記ページを見ると、ARM64アーキテクチャの「ECS最適化Amazon Linux 2 AMI」が用意されていることが確認できます。
[Amazon ECS-Optimized Amazon Linux 2 AMI](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/al2ami.html)
※ 日本語版のドキュメントは更新されていませんので、言語を「English」にして参照して下さい。

現在、ARM64版「ECS最適化Amazon Linux 2 AMI」が提供されているのは下記のリージョンのみです。A1インスタンスの提供リージョンと同じですね。(当然ですが)

* us-east-2 (オハイオ)
* us-east-1 (バージニア北部)
* us-west-2 (オレゴン)
* eu-west-1 (アイルランド)


# 実際にECSで「A1インスタンス」を使ってみる

これはARM64版に限ったことではありませんが、ECSマネジメントコンソールの「クラスターの作成」ウィザードを使わずにコンテナインスタンスを作成する方法がちゃんとあります。
流れは以下の通りです。

1. 「空の」ECSクラスターを作成する。
2. コンテナインスタンス用の「IAMロール」を作成する。
3. EC2インスタンスを作成する。  
その際、クラスターから「コンテナインスタンス」として認識してもらうために必要な各種設定を行う。
4. EC2インスタンスが起動すると、自動的にECSクラスターへ組み込まれる。

実際にやってみましょう。


## 1. 「空の」ECSクラスターを作成する

ECSマネジメントコンソールの「クラスターの作成」ウィザードを起動します。
「クラスターテンプレートの選択」では「EC2 Linux + ネットワーキング」を選択します。
![クラスターテンプレートの選択](https://qiita-image-store.s3.amazonaws.com/0/273982/28d77cd1-80ed-38b1-5ec3-55adbf031c4d.png)

「クラスターの設定」ページで、クラスター名を入力後、「空のクラスターの作成」にチェックを入れます。
![クラスターの設定](https://qiita-image-store.s3.amazonaws.com/0/273982/b750d17f-28d2-f608-f6f0-3292247908b3.png)

これで「作成」を押すと、「空の」クラスターの作成完了です。


## 2. コンテナインスタンス用の「IAMロール」を作成する

EC2インスタンスがECSサービスにアクセスする権限を得るために、IAMロールが必要です。
「クラスターの作成」ウィザードを使ってコンテナインスタンスを作成する場合には自動的にIAMロールを作成してくれますが、今回の手順では個別にIAMロールを作成する必要があります。

まず、IAMマネジメントコンソールで「ロール」を選択し、「ecsInstanceRole」という名前のロールがあるかどうかを確認します。
![IAMロールの存在確認](https://qiita-image-store.s3.amazonaws.com/0/273982/f5f0f71b-ba3b-c54c-76a9-344b879e9325.png)

一度でもECSを使用したことがあれば既にロールが作成されていると思いますが、まだ存在しない場合には「ロールの作成」を押してIAMロールを作成します。

IAMロール作成ウィザードの1ページ目で、以下の通り選択します。

| 項目 | 設定値 |
| :--- | :--- |
| 信頼されたエンティティの種類を選択 | AWSサービス |
| このロールを使用するサービスを選択 | Elastic Container Service |
| ユースケースの選択 | EC2 Role for Elastic Container Service |

![IAMロールの作成1](https://qiita-image-store.s3.amazonaws.com/0/273982/0c28b7cb-813a-ebdc-f9e1-174ee71ec616.png)

2ページ目で、アクセス権限ポリシーに「AmazonEC2ContainerServiceforEC2Role」が表示されていることを確認して、次へ進みます。
![IAMロールの作成2](https://qiita-image-store.s3.amazonaws.com/0/273982/a98f5406-dcf7-ac80-4a54-e7f0e281da8a.png)

3ページ目はタグの設定ですが、特に設定の必要がなければそのまま次へ進みます。
![IAMロールの作成3](https://qiita-image-store.s3.amazonaws.com/0/273982/91fc7f63-3a4a-c084-ff60-19a2ba860be4.png)

最後の4ページ目で、ロール名に「ecsInstanceRole」と入力して、「ロールの作成」を押します。
![IAMロールの作成4](https://qiita-image-store.s3.amazonaws.com/0/273982/5cf5b514-0350-a3cc-5a85-4bc613bcc585.png)

これでIAMロールの準備ができました。


## 3. EC2インスタンスを作成する

事前に、前述のAWSドキュメントページ [Amazon ECS-Optimized Amazon Linux 2 AMI](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/al2ami.html) で、ARM64版「ECS最適化Amazon Linux 2 AMI」のAMI IDを確認しておきます。

リージョン毎の最新のAMI IDを確認する手順は下記ページにあります。
[Retrieving Amazon ECS-Optimized AMI Metadata](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/retrieve-ecs-optimized_AMI.html)

具体的には以下のコマンドを実行します：

```
$ aws ssm get-parameters --names /aws/service/ecs/optimized-ami/amazon-linux-2/arm64/recommended --region us-east-1
```

AMI IDを確認しましたら、EC2マネジメントコンソールの「インスタンスの作成」ウィザードを使ってインスタンスを作成します。
いくつかポイントがありますので、確認しながら進めて下さい。

**ステップ1: Amazonマシンイメージ(AMI)**

検索文字列に「ECS Optimized ARM」等を指定して検索する方法もありますが、検索結果が複数ヒットして分かり辛いため、AMI IDを直接指定する方法をお勧めします。

「コミュニティAMI」を選択した状態で、事前に確認したAMI IDを検索欄に入力してEnterキーを押します。
![AMIの選択](https://qiita-image-store.s3.amazonaws.com/0/273982/ea5be70e-4e6b-6dc8-9321-3aa8b22a6a56.png)

**ステップ2: インスタンスタイプの選択**

「A1インスタンス」シリーズの中から任意のインスタンスタイプを選択します。
![インスタンスタイプの選択](https://qiita-image-store.s3.amazonaws.com/0/273982/e4b412a0-ef02-4d9d-16fd-900ad22e0ce9.png)

**ステップ3: インスタンスの詳細の設定**

このページでは重要なポイントが2点あります。

1点目は、「IAMロール」欄で、手順2で用意した「ecsInstanceRole」を選択する点です。

2点目は、一番下の「高度な設定」を展開して、「ユーザーデータ」欄に以下のテキストを設定する点です。

```
#!/bin/bash
echo ECS_CLUSTER=TestCluster >> /etc/ecs/ecs.config
```

※ 「TestCluster」の部分は、手順1で作成した「空の」クラスターの名前を指定します。

これらの2点を正しく設定しないと、ECSクラスターがEC2インスタンスをコンテナインスタンスと認識してくれません。
![インスタンスの詳細設定](https://qiita-image-store.s3.amazonaws.com/0/273982/b37ce0ee-bdb2-9407-c28b-893f7d2a1509.png)

**ステップ4: ストレージの追加**

x86の場合とボリュームの構成が異なることに注意して下さい。

| アーキテクチャ | ボリューム構成 |
| :--- | :--- |
| x86 | ルートボリューム(8GiB)＋Docker用ボリューム(22GiB)で構成 |
| ARM64 | ルートボリューム(30GiB)のみで構成 |

最低容量は30GiBです。必要に応じて増量します。
![ストレージの追加](https://qiita-image-store.s3.amazonaws.com/0/273982/2be106c1-72c8-9a6d-b9ef-e228bb7d54e1.png)

**ステップ5: タグの追加**

必要に応じてタグを設定します。
![タグの追加](https://qiita-image-store.s3.amazonaws.com/0/273982/662f0ce9-ea20-6453-0ce9-fd707598a11a.png)

**ステップ6: セキュリティグループの設定**

一般的には、以下の2つのインバウンドアクセス許可を設定します。

* 外部に公開するサービスポート： HTTP(80)、HTTPS(443)等
* インスタンスの管理用： SSH(22)

![セキュリティグループの設定](https://qiita-image-store.s3.amazonaws.com/0/273982/44220114-0689-3e92-898d-3b1daace7910.png)

**ステップ7: インスタンス作成の確認**

入力した内容を確認して「作成」を押します。
![インスタンス作成の確認](https://qiita-image-store.s3.amazonaws.com/0/273982/f531ed1a-7481-9f9d-662d-6d05cfd8ae76.png)

これでEC2インスタンスが作成できました。


## 4. EC2インスタンスが起動し、自動的にECSクラスターへ組み込まれることを確認する

ECSマネジメントコンソールへ移動し、手順1で作成したクラスターを選択して、「ECSインスタンス」タブを選択します。

EC2インスタンスが起動してしばらく経つと、コンテナインスタンス欄にインスタンスが表示されます。
「状況」欄が「Active」になれば組み込み完了です。
![追加されたコンテナインスタンス](https://qiita-image-store.s3.amazonaws.com/0/273982/d018b724-1e0b-ff6e-a271-ca5d66125432.png)

あとは、x86の場合と同様に「タスク定義」や「サービス」を作成してコンテナを動作させることができます。


# (参考) Auto Scaling Groupの作成

ここまでで、単体のEC2インスタンスをコンテナインスタンスに組み込む手順を説明しました。

Auto Scaling Groupを作成する場合においても、単体EC2と同様に「IAMロールを指定する」「ユーザーデータにECSクラスター名を設定する」という点に注意すれば、コンテナインスタンスへの組み込みを行うことができます。

マネジメントコンソールで一つ一つ設定するのは大変なので、CloudFormationのスタックテンプレートを作成しました。

<details>
<summary>CloudFormationスタックテンプレート</summary>
<div>

```yaml:cfn-ecs-cluster-arm64.yaml
---
AWSTemplateFormatVersion: "2010-09-09"
Description: "Stack for creating an ARM64-based ECS cluster environment"

Parameters:
  ResourceNamePrefix:
    Type: String

  CidrBlockVpc:
    Type: String
    Default: 10.0.0.0/16

  CidrBlockSubnet1:
    Type: String
    Default: 10.0.0.0/24

  CidrBlockSubnet2:
    Type: String
    Default: 10.0.1.0/24

  CidrBlockSubnet3:
    Type: String
    Default: 10.0.2.0/24

  RemoteAccessAllowIpAddress:
    Type: String

  EcsInstanceProfile:
    Type: String
    Default: ecsInstanceRole

  Ec2ImageId:
    Type: AWS::EC2::Image::Id
    Default: ami-0e851dee3d33f685e  # Amazon ECS-optimized Amazon Linux 2 (arm64) (@us-east-1)

  Ec2KeyName:
    Type: AWS::EC2::KeyPair::KeyName

  Ec2InstanceType:
    Type: String
    Default: a1.medium

  Ec2VolumeType:
    Type: String
    Default: gp2

  Ec2VolumeSize:
    Type: String
    Default: 30

  AutoScalingDesiredCapacity:
    Type: String
    Default: 3

  AutoScalingMinSize:
    Type: String
    Default: 2

  AutoScalingMaxSize:
    Type: String
    Default: 4

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
    - Label:
        default: "General Information"
      Parameters:
      - ResourceNamePrefix
    - Label:
        default: "Network Configuration"
      Parameters:
      - CidrBlockVpc
      - CidrBlockSubnet1
      - CidrBlockSubnet2
      - CidrBlockSubnet3
      - RemoteAccessAllowIpAddress
    - Label:
        default: "IAM Configuration"
      Parameters:
      - EcsInstanceProfile
    - Label:
        default: "EC2 Launch Template Configuration"
      Parameters:
      - Ec2ImageId
      - Ec2KeyName
      - Ec2InstanceType
      - Ec2VolumeType
      - Ec2VolumeSize
    - Label:
        default: "EC2 Auto Scaling Configuration"
      Parameters:
      - AutoScalingDesiredCapacity
      - AutoScalingMinSize
      - AutoScalingMaxSize

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock:  !Ref CidrBlockVpc
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-VPC"

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-IGW"

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

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select 
      - 0
      - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnet1
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-Subnet1"

  Subnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select 
      - 1
      - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnet2
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-Subnet2"

  Subnet3:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select 
      - 2
      - Fn::GetAZs: !Ref AWS::Region
      CidrBlock: !Ref CidrBlockSubnet3
      MapPublicIpOnLaunch: true
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-Subnet3"

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-RTB"

  RouteIGW:
    DependsOn: VPCGatewayAttachment
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  SubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet1
      RouteTableId: !Ref RouteTable

  SubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet2
      RouteTableId: !Ref RouteTable

  SubnetRouteTableAssociation3:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref Subnet3
      RouteTableId: !Ref RouteTable

  SecurityGroupEc2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${ResourceNamePrefix}-SG-EC2"
      GroupDescription: "Security Group for EC2"
      VpcId: !Ref VPC
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 22
        ToPort: 22
        CidrIp: !Ref RemoteAccessAllowIpAddress
      - IpProtocol: tcp
        FromPort: 80
        ToPort: 80
        CidrIp: 0.0.0.0/0
      Tags:
      - Key: Name
        Value: !Sub "${ResourceNamePrefix}-SG-EC2"

  EcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub "${ResourceNamePrefix}-ECS-Cluster"

  Ec2LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub "${ResourceNamePrefix}-EC2-LaunchTemplate"
      LaunchTemplateData:
        InstanceType: !Ref Ec2InstanceType
        ImageId: !Ref Ec2ImageId
        KeyName: !Ref Ec2KeyName
        NetworkInterfaces:
        - DeviceIndex: 0
          AssociatePublicIpAddress: true
          Groups:
          - !Ref SecurityGroupEc2
        BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: !Ref Ec2VolumeSize
            VolumeType: !Ref Ec2VolumeType
        IamInstanceProfile:
          Name: !Ref EcsInstanceProfile
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            echo ECS_CLUSTER=${EcsCluster} >> /etc/ecs/ecs.config
        TagSpecifications:
        - ResourceType: instance
          Tags: 
          - Key: Name
            Value: !Sub "${ResourceNamePrefix}-EC2"
        - ResourceType: volume
          Tags: 
          - Key: Name
            Value: !Sub "${ResourceNamePrefix}-EC2"

  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub "${ResourceNamePrefix}-EC2-AutoScalingGroup"
      VPCZoneIdentifier:
      - !Ref Subnet1
      - !Ref Subnet2
      - !Ref Subnet3
      LaunchTemplate:
        LaunchTemplateId: !Ref Ec2LaunchTemplate
        Version: !GetAtt Ec2LaunchTemplate.LatestVersionNumber
      DesiredCapacity: !Ref AutoScalingDesiredCapacity
      MinSize: !Ref AutoScalingMinSize
      MaxSize: !Ref AutoScalingMaxSize
      HealthCheckType: EC2
      HealthCheckGracePeriod: 300

Outputs:
  VpcId:
    Value: !Ref VPC

  SubnetId1:
    Value: !Ref Subnet1

  SubnetId2:
    Value: !Ref Subnet2

  SubnetId3:
    Value: !Ref Subnet3

  SecurityGroupIdEc2:
    Value: !Ref SecurityGroupEc2

  EcsClusterName:
    Value: !Ref EcsCluster

  Ec2LaunchTemplateId:
    Value: !Ref Ec2LaunchTemplate

  AutoScalingGroupArn:
    Value: !Ref AutoScalingGroup
```
</div>
</details>

<details>
<summary>CloudFormationスタック作成スクリプト</summary>
<div>

```shell:setup.sh
#!/bin/sh

# デフォルトリージョンを「バージニア北部」に設定
aws configure set region us-east-1

# 操作PCのグローバルIPアドレスを取得
MY_IP=$(curl https://checkip.amazonaws.com/ | tr -d "\n\r")

# キーペアを作成
KEYPAIR_NAME=arm64ecs-KeyPair
aws ec2 create-key-pair \
  --key-name ${KEYPAIR_NAME} \
  --query "KeyMaterial" \
  --output text > ~/.ssh/${KEYPAIR_NAME}.pem
chmod 400 ~/.ssh/${KEYPAIR_NAME}.pem

# ARM64版「ECS最適化Amazon Linux 2 AMI」の最新のAMI IDを取得 (@us-east-1)
ECS_OPTIMIZED_AMI_ID=$(aws ssm get-parameters \
  --names /aws/service/ecs/optimized-ami/amazon-linux-2/arm64/recommended/image_id \
  --region us-east-1 \
  --query "Parameters[0].Value" \
  | tr -d '"')

# EC2インスタンスタイプ (a1.medium, a1.large, a1.xlarge, a1.x2large, a1.x4largeのいずれか)
EC2_INSTANCE_TYPE=a1.medium

# ECS用インスタンスプロファイル名(＝IAMロール名)
ECS_INSTANCE_PROFILE=ecsInstanceRole

# CloudFormationスタックを作成
RESOURCE_NAME_PREFIX=arm64ecs
STACK_NAME=arm64ecs-stack
aws cloudformation create-stack \
  --stack-name ${STACK_NAME} \
  --template-body file://cfn-ecs-cluster-arm64.yaml \
  --parameters ParameterKey=ResourceNamePrefix,ParameterValue=${RESOURCE_NAME_PREFIX} \
               ParameterKey=CidrBlockVpc,ParameterValue=10.0.0.0/16 \
               ParameterKey=CidrBlockSubnet1,ParameterValue=10.0.0.0/24 \
               ParameterKey=CidrBlockSubnet2,ParameterValue=10.0.1.0/24 \
               ParameterKey=CidrBlockSubnet3,ParameterValue=10.0.2.0/24 \
               ParameterKey=RemoteAccessAllowIpAddress,ParameterValue=${MY_IP}/32 \
               ParameterKey=EcsInstanceProfile,ParameterValue=${ECS_INSTANCE_PROFILE} \
               ParameterKey=Ec2ImageId,ParameterValue=${ECS_OPTIMIZED_AMI_ID} \
               ParameterKey=Ec2KeyName,ParameterValue=${KEYPAIR_NAME} \
               ParameterKey=Ec2InstanceType,ParameterValue=${EC2_INSTANCE_TYPE} \
               ParameterKey=Ec2VolumeType,ParameterValue=gp2 \
               ParameterKey=Ec2VolumeSize,ParameterValue=30 \
               ParameterKey=AutoScalingDesiredCapacity,ParameterValue=3 \
               ParameterKey=AutoScalingMinSize,ParameterValue=2 \
               ParameterKey=AutoScalingMaxSize,ParameterValue=4

# スタック作成完了を待ち合わせ
aws cloudformation wait stack-create-complete --stack-name ${STACK_NAME}
```
</div>
</details>


# おわりに

現時点では、ECSのマネジメントコンソールの「クラスターの作成」ウィザードからARM64アーキテクチャのコンテナインスタンスを作成することはできませんが、いずれ行えるようになるでしょう。

さあ、あとはEKSの対応を待つばかりだ・・・