はじめに
前回投稿した記事「AWS EC2の新インスタンス「A1」("Graviton"/ARM64)でDockerを動かしてみる」で、「A1インスタンスはまだECSやEKSに対応していない」と書きました。
しかし、よく調べてみると実は既にECSには対応していましたので、実際に試してみた内容を書き記したいと思います。
ECSマネジメントコンソールは、まだ対応していないが・・・
ECSのマネジメントコンソールから「クラスターの作成」ウィザードを使うと、ECSクラスターと同時にコンテナインスタンス(EC2)も作成することができます。
ただ現時点では、EC2インスタンスタイプの選択肢に「A1インスタンス」シリーズは入っておらず選択できません。
「Manually enter desired instance type」にチェックを入れてインスタンスタイプを手入力(a1.medium等)すれば一見指定できそうですが、AMI IDがx86用で固定されているためEC2の起動に失敗します。
このような状況を確認したため、前回の記事では「ECSには対応していない」と書きました。
AWSドキュメント(英語版)には既に情報が掲載されていた
英語版のAWSドキュメントで下記ページを見ると、ARM64アーキテクチャの「ECS最適化Amazon Linux 2 AMI」が用意されていることが確認できます。
Amazon ECS-Optimized Amazon Linux 2 AMI
※ 日本語版のドキュメントは更新されていませんので、言語を「English」にして参照して下さい。
現在、ARM64版「ECS最適化Amazon Linux 2 AMI」が提供されているのは下記のリージョンのみです。A1インスタンスの提供リージョンと同じですね。(当然ですが)
- us-east-2 (オハイオ)
- us-east-1 (バージニア北部)
- us-west-2 (オレゴン)
- eu-west-1 (アイルランド)
実際にECSで「A1インスタンス」を使ってみる
これはARM64版に限ったことではありませんが、ECSマネジメントコンソールの「クラスターの作成」ウィザードを使わずにコンテナインスタンスを作成する方法がちゃんとあります。
流れは以下の通りです。
- 「空の」ECSクラスターを作成する。
- コンテナインスタンス用の「IAMロール」を作成する。
- EC2インスタンスを作成する。
その際、クラスターから「コンテナインスタンス」として認識してもらうために必要な各種設定を行う。 - EC2インスタンスが起動すると、自動的にECSクラスターへ組み込まれる。
実際にやってみましょう。
1. 「空の」ECSクラスターを作成する
ECSマネジメントコンソールの「クラスターの作成」ウィザードを起動します。
「クラスターテンプレートの選択」では「EC2 Linux + ネットワーキング」を選択します。
「クラスターの設定」ページで、クラスター名を入力後、「空のクラスターの作成」にチェックを入れます。
これで「作成」を押すと、「空の」クラスターの作成完了です。
2. コンテナインスタンス用の「IAMロール」を作成する
EC2インスタンスがECSサービスにアクセスする権限を得るために、IAMロールが必要です。
「クラスターの作成」ウィザードを使ってコンテナインスタンスを作成する場合には自動的にIAMロールを作成してくれますが、今回の手順では個別にIAMロールを作成する必要があります。
まず、IAMマネジメントコンソールで「ロール」を選択し、「ecsInstanceRole」という名前のロールがあるかどうかを確認します。
一度でもECSを使用したことがあれば既にロールが作成されていると思いますが、まだ存在しない場合には「ロールの作成」を押してIAMロールを作成します。
IAMロール作成ウィザードの1ページ目で、以下の通り選択します。
項目 | 設定値 |
---|---|
信頼されたエンティティの種類を選択 | AWSサービス |
このロールを使用するサービスを選択 | Elastic Container Service |
ユースケースの選択 | EC2 Role for Elastic Container Service |
2ページ目で、アクセス権限ポリシーに「AmazonEC2ContainerServiceforEC2Role」が表示されていることを確認して、次へ進みます。
3ページ目はタグの設定ですが、特に設定の必要がなければそのまま次へ進みます。
最後の4ページ目で、ロール名に「ecsInstanceRole」と入力して、「ロールの作成」を押します。
これでIAMロールの準備ができました。
3. EC2インスタンスを作成する
事前に、前述のAWSドキュメントページ Amazon ECS-Optimized Amazon Linux 2 AMI で、ARM64版「ECS最適化Amazon Linux 2 AMI」のAMI IDを確認しておきます。
リージョン毎の最新のAMI IDを確認する手順は下記ページにあります。
Retrieving Amazon ECS-Optimized AMI Metadata
具体的には以下のコマンドを実行します:
$ 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キーを押します。
ステップ2: インスタンスタイプの選択
「A1インスタンス」シリーズの中から任意のインスタンスタイプを選択します。
ステップ3: インスタンスの詳細の設定
このページでは重要なポイントが2点あります。
1点目は、「IAMロール」欄で、手順2で用意した「ecsInstanceRole」を選択する点です。
2点目は、一番下の「高度な設定」を展開して、「ユーザーデータ」欄に以下のテキストを設定する点です。
#!/bin/bash
echo ECS_CLUSTER=TestCluster >> /etc/ecs/ecs.config
※ 「TestCluster」の部分は、手順1で作成した「空の」クラスターの名前を指定します。
これらの2点を正しく設定しないと、ECSクラスターがEC2インスタンスをコンテナインスタンスと認識してくれません。
ステップ4: ストレージの追加
x86の場合とボリュームの構成が異なることに注意して下さい。
アーキテクチャ | ボリューム構成 |
---|---|
x86 | ルートボリューム(8GiB)+Docker用ボリューム(22GiB)で構成 |
ARM64 | ルートボリューム(30GiB)のみで構成 |
ステップ5: タグの追加
ステップ6: セキュリティグループの設定
一般的には、以下の2つのインバウンドアクセス許可を設定します。
- 外部に公開するサービスポート: HTTP(80)、HTTPS(443)等
- インスタンスの管理用: SSH(22)
ステップ7: インスタンス作成の確認
これでEC2インスタンスが作成できました。
4. EC2インスタンスが起動し、自動的にECSクラスターへ組み込まれることを確認する
ECSマネジメントコンソールへ移動し、手順1で作成したクラスターを選択して、「ECSインスタンス」タブを選択します。
EC2インスタンスが起動してしばらく経つと、コンテナインスタンス欄にインスタンスが表示されます。
「状況」欄が「Active」になれば組み込み完了です。
あとは、x86の場合と同様に「タスク定義」や「サービス」を作成してコンテナを動作させることができます。
(参考) Auto Scaling Groupの作成
ここまでで、単体のEC2インスタンスをコンテナインスタンスに組み込む手順を説明しました。
Auto Scaling Groupを作成する場合においても、単体EC2と同様に「IAMロールを指定する」「ユーザーデータにECSクラスター名を設定する」という点に注意すれば、コンテナインスタンスへの組み込みを行うことができます。
マネジメントコンソールで一つ一つ設定するのは大変なので、CloudFormationのスタックテンプレートを作成しました。
CloudFormationスタックテンプレート
---
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
CloudFormationスタック作成スクリプト
#!/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}
おわりに
現時点では、ECSのマネジメントコンソールの「クラスターの作成」ウィザードからARM64アーキテクチャのコンテナインスタンスを作成することはできませんが、いずれ行えるようになるでしょう。
さあ、あとはEKSの対応を待つばかりだ・・・