APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

AWS CLI で再入門:Amazon ECS + Fargate

目次

はじめに

こんにちは、クラウド事業部の梅本です。

今回は再入門ということで Amazon ECS を使ってコンテナを立てたいと思います。

普段は CloudFormation や Terraform 等の IaC で構築したり、Web コンソールからポチポチで作成するのですが、今回はステップバイステップで改めて関連サービスを意識しながら構築していこうと思います。

環境情報

  • クライアント:AWS CloudShell
  • AWS CLI:2.16.8

Role作成

最初に Role を作成します。作成する Role の種類は以下の通りです。

  • タスクロール
    • コンテナ内のアプリがAWSリソースにアクセスするためのロール
  • タスク実行ロール
    • ECSエージェントがタスクを実行する際に必要なリソースにアクセスするためのロール
    • 例)
      • ECRからコンテナイメージをプルする
      • CloudWatch Logsにログを送信する
      • Secret Manager、System Manager Parameter Storeからシークレットを取得

それでは早速作っていきます。

# 作成するリソース名に付与するプレフィックス
prefix=ume
# Role の URL で利用するためにアカウント ID を取得しておく
account_id=$(aws sts get-caller-identity --query 'Account' --output text)

# タスクロール作成
aws iam create-role \
  --role-name "$prefix-ecs-task-role" \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ecs-tasks.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }
    ]
  }'

# タスクロール作成確認
aws iam get-role \
  --role-name "$prefix-ecs-task-role" \
  --query 'Role.{RoleName:RoleName, CreateDate:CreateDate}' \
  --output table

----------------------------------------------------
|                      GetRole                     |
+----------------------------+---------------------+
|         CreateDate         |      RoleName       |
+----------------------------+---------------------+
|  2024-06-17T15:38:16+00:00 |  ume-ecs-task-role  |
+----------------------------+---------------------+

# タスクロールに付与するポリシーの作成
# 今回は作成したコンテナに shell でアクセスするためにコマンド実行許可のポリシーを追加
aws iam create-policy \
  --policy-name "$prefix-ecs-task-policy" \
  --policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "ssmmessages:CreateControlChannel",
          "ssmmessages:CreateDataChannel",
          "ssmmessages:OpenControlChannel",
          "ssmmessages:OpenDataChannel"
        ],
        "Resource": "*"
      }
    ]
  }'

# タスクロールに作成したポリシーをアタッチ
aws iam attach-role-policy \
  --role-name "$prefix-ecs-task-role" \
  --policy-arn "arn:aws:iam::$account_id:policy/$prefix-ecs-task-policy"

# タスクロールのポリシーを確認
aws iam list-attached-role-policies \
  --role-name "$prefix-ecs-task-role" \
  --output table

-----------------------------------------------------------------------------------
|                            ListAttachedRolePolicies                             |
+---------------------------------------------------------------------------------+
||                               AttachedPolicies                                ||
|+-------------------------------------------------------+-----------------------+|
||                       PolicyArn                       |      PolicyName       ||
|+-------------------------------------------------------+-----------------------+|
||  arn:aws:iam::XXXXXXXXXXXX:policy/ume-ecs-task-policy |  ume-ecs-task-policy  ||
|+-------------------------------------------------------+-----------------------+|

# タスク実行ロールの作成
aws iam create-role \
  --role-name "$prefix-ecs-task-execution-role" \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "ecs-tasks.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }
    ]
  }'

# タスク実行ロール作成確認
aws iam get-role \
  --role-name "$prefix-ecs-task-execution-role" \
  --query 'Role.{RoleName:RoleName, CreateDate:CreateDate}' \
  --output table

--------------------------------------------------------------
|                           GetRole                          |
+----------------------------+-------------------------------+
|         CreateDate         |           RoleName            |
+----------------------------+-------------------------------+
|  2024-06-17T15:41:48+00:00 |  ume-ecs-task-execution-role  |
+----------------------------+-------------------------------+

# タスク実行ロールに AWS 側で用意しているポリシーをアタッチ
aws iam attach-role-policy \
  --role-name "$prefix-ecs-task-execution-role" \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

# タスク実行ロールのポリシーを確認
aws iam list-attached-role-policies \
  --role-name "$prefix-ecs-task-execution-role" \
  --output table

-----------------------------------------------------------------------------------------------------------------
|                                           ListAttachedRolePolicies                                            |
+---------------------------------------------------------------------------------------------------------------+
||                                              AttachedPolicies                                               ||
|+------------------------------------------------------------------------+------------------------------------+|
||                                PolicyArn                               |            PolicyName              ||
|+------------------------------------------------------------------------+------------------------------------+|
||  arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy |  AmazonECSTaskExecutionRolePolicy  ||
|+------------------------------------------------------------------------+------------------------------------+|

ECSタスク定義作成

次に ECS で実行するコンテナの定義である タスク定義 を作成します。

今回は Web ブラウザで動作する VSCode のコンテナイメージ code-server を利用していきます。

github.com

設定は以下の通りです。

  • コンテナは Fargate で起動する
    • Fargate を指定した場合、NetworkMode は awsvpc で固定
  • CPU/Memory は少し余裕をもって設定
  • 作成したタスクロール、タスク実行ロールを指定
  • コンテナの設定は --container-definitions で設定
    • code-server のイメージを指定
    • essential で必須なコンテナかどうかを指定
    • 外部からコンテナにアクセスする際に開放するポートを指定
      • 今回は http プロトコルの 8080
# タスク定義作成
aws ecs register-task-definition \
--family "$prefix-task-def" \
--network-mode awsvpc \
--requires-compatibilities FARGATE \
--cpu "1024" \
--memory "2048" \
--task-role-arn "arn:aws:iam::$account_id:role/$prefix-ecs-task-role" \
--execution-role-arn "arn:aws:iam::$account_id:role/$prefix-ecs-task-execution-role" \
--container-definitions '[
  {
    "name": "code-server",
    "image": "codercom/code-server:4.89.1",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 8080,
        "hostPort": 8080,
        "protocol": "tcp",
        "appProtocol": "http"
      }
    ]
  }
]'

# タスク定義作成確認
aws ecs describe-task-definition \
  --task-definition "$prefix-task-def" \
  --query 'taskDefinition.{Family:family, RegisterDate:registeredAt}' \
  --output table

------------------------------------------------------
|               DescribeTaskDefinition               |
+---------------+------------------------------------+
|    Family     |           RegisterDate             |
+---------------+------------------------------------+
|  ume-task-def |  2024-06-17T15:44:06.623000+00:00  |
+---------------+------------------------------------+

VPC、SecurityGroup 作成

続いて起動するコンテナのネットワーク準備を行います。

今回は簡易な構成にするためパブリックのセグメントのみで構成しています。

# VPC の作成
vpc_id=$(aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --tag-specifications "ResourceType=vpc,Tags=[{Key=Name,Value=$prefix-vpc}]" \
  --query 'Vpc.VpcId' \
  --output text)

# Subnet の作成
subnet_id=$(aws ec2 create-subnet \
  --vpc-id "$vpc_id" \
  --cidr-block 10.0.1.0/24 \
  --tag-specifications "ResourceType=subnet,Tags=[{Key=Name,Value=$prefix-subnet}]" \
  --query 'Subnet.SubnetId' \
  --output text)

# InternetGateway の作成
igw_id=$(aws ec2 create-internet-gateway \
  --tag-specifications "ResourceType=internet-gateway,Tags=[{Key=Name,Value=$prefix-igw}]" \
  --query 'InternetGateway.InternetGatewayId' \
  --output text)

# InternetGateway を VPC にアタッチ
aws ec2 attach-internet-gateway \
  --vpc-id "$vpc_id" \
  --internet-gateway-id "$igw_id"

# RouteTable の作成
route_table_id=$(aws ec2 create-route-table \
  --vpc-id "$vpc_id" \
  --tag-specifications "ResourceType=route-table,Tags=[{Key=Name,Value=$prefix-route-table}]" \
  --query 'RouteTable.RouteTableId' \
  --output text)

# RouteTable にデフォルトルートを追加
aws ec2 create-route \
  --route-table-id "$route_table_id" \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id "$igw_id"

# Subnet に RouteTable に関連付ける
aws ec2 associate-route-table \
  --subnet-id "$subnet_id" \
  --route-table-id "$route_table_id"

# SecurityGroup の作成
security_group_id=$(aws ec2 create-security-group \
  --group-name "$prefix"-sg \
  --description "Allow access for code-server" \
  --vpc-id "$vpc_id" \
  --output text \
  --query 'GroupId')

# SecurityGroup にルールを追加
# 今回は code-server の 8080 ポートを許可
aws ec2 authorize-security-group-ingress \
  --group-id "$security_group_id" \
  --protocol tcp \
  --port 8080 \
  --cidr "0.0.0.0/0"

NW関連は今回の本題ではないのでサクッと作成します。

ECS クラスター、サービス作成

準備が整ったのでコンテナを起動していきます。

まずは起動のための ECS クラスターを作成します。

# ECS クラスター作成
aws ecs create-cluster \
  --cluster-name "$prefix-cluster"

# ECS クラスター作成確認
aws ecs describe-clusters \
  --clusters "${prefix}-cluster" \
  --output table

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                                        DescribeClusters                                                                                        |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
||                                                                                           clusters                                                                                           ||
|+---------------------+---------------------------------------------------------------+--------------+--------------------+------------------------------------+--------------------+----------+|
|| activeServicesCount |                          clusterArn                           | clusterName  | pendingTasksCount  | registeredContainerInstancesCount  | runningTasksCount  | status   ||
|+---------------------+---------------------------------------------------------------+--------------+--------------------+------------------------------------+--------------------+----------+|
||  0                  |  arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:cluster/ume-cluster  |  ume-cluster |  0                 |  0                                 |  0                 |  ACTIVE  ||
|+---------------------+---------------------------------------------------------------+--------------+--------------------+------------------------------------+--------------------+----------+|

クラスタの作成ができたので、コンテナを起動します。ECS でコンテナを起動するためにサービスを作成します。

設定は以下の通りです。

  • 作成したクラスター、タスク定義、VPC/SecurityGroup を指定
  • Fargate で起動を指定
  • --desired-count で起動するコンテナの数を指定
  • assignPublicIp で Public IP 付与を指定
# サービス作成
aws ecs create-service \
  --cluster "$prefix-cluster" \
  --service-name "$prefix-service" \
  --task-definition "$prefix-task-def" \
  --desired-count 1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={
    subnets=[$subnet_id],
    securityGroups=[$security_group_id],
    assignPublicIp=ENABLED
  }"

# サービス作成確認
aws ecs describe-services \
  --cluster "$prefix-cluster" \
  --services "$prefix-service" \
  --query "services[*].{Name:serviceName,Status:status}" \
  --output table

---------------------------
|    DescribeServices     |
+--------------+----------+
|     Name     | Status   |
+--------------+----------+
|  ume-service |  ACTIVE  |
+--------------+----------+

動作確認

サービスを作成したことで、コンテナが起動しました。 起動したコンテナに shell 及び Web ブラウザでアクセスしてみます。

shell でアクセスするためにはもう1つ設定とコンテナの再起動が必要です。

# 作成したサービスでコマンドが実行できるように設定を更新
aws ecs update-service \
  --cluster "$prefix-cluster" \
  --service "$prefix-service" \
  --enable-execute-command

# 更新した設定を確認
aws ecs describe-services \
  --cluster "$prefix-cluster" \
  --services "$prefix-service" \
  --query "services[*].{Name:serviceName,ExecuteCommand:enableExecuteCommand}" \
  --output table

-----------------------------------
|        DescribeServices         |
+-----------------+---------------+
| ExecuteCommand  |     Name      |
+-----------------+---------------+
|  True           |  ume-service  |
+-----------------+---------------+

# 設定した内容でコンテナを起動させるため、新しいコンテナを起動
# 新しいコンテナを起動させるため、既存コンテナの Task ID を取得
task_id=$(aws ecs list-tasks --cluster "$prefix-cluster" --service-name ${prefix}-service --query "taskArns[0]" --output text)

# 起動しているコンテナを停止し、新しいコンテナを起動する
aws ecs stop-task --cluster "$prefix-cluster" --task "$task_id"

Shell でアクセスできる準備ができたので、アクセス確認してみます。

# アクセスするために Task ID を取得
task_id=$(aws ecs list-tasks --cluster ${prefix}-cluster --service-name ${prefix}-service --query "taskArns[0]" --output text)

# bash をコンテナ内で実行して、コンテナ内にアクセスする
aws ecs execute-command \
    --cluster "$prefix-cluster" \
    --task "$task_id" \
    --container code-server \
    --interactive \
    --command "/bin/bash"

# ここからコンテナ内

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.

Starting session with SessionId: ecs-execute-command-hp565njqk7cd4obq3sdko2hhlm

# コンテナの OS 情報を出力
root@b7a3b13147df47dc9c9e2efc5757e547-119361880:/home/coder# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
NAME="Debian GNU/Linux"
VERSION_ID="12"
VERSION="12 (bookworm)"
VERSION_CODENAME=bookworm
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

# code-server のバージョンを出力
root@b7a3b13147df47dc9c9e2efc5757e547-119361880:/home/coder# code-server --version
[2024-06-17T16:07:46.385Z] info  Wrote default config file to /root/.config/code-server/config.yaml
4.89.1 effc6e95b4ad1c5ac5f9083ec06663ba4a2e005c with Code 1.89.1

# 確認できたので、コンテナ内から離れる
root@b7a3b13147df47dc9c9e2efc5757e547-119361880:/home/coder# exit

# ここまでコンテナ内

最後に Web UI にアクセスします。アクセスのための Public IP を取得します。

aws ec2 describe-network-interfaces \
  --network-interface-ids $(aws ecs describe-tasks --cluster "$prefix-cluster" --tasks "$task_id" --query "tasks[0].attachments[0].details[?name=='networkInterfaceId'].value" --output text) \
  --query "NetworkInterfaces[0].Association.PublicIp" --output text

アクセスする URL は http://<取得した Public IP>:8080 です。

まとめ

AWS CLI を使って、ECS でコンテナを起動してみました。

ステップバイステップでコマンドを実行することで何を作成し、指定したオプションでどんな設定がなされているのかの理解にもなり私にとって復習にちょうど良かったです。

引き続き ECS と他サービスの連携について投稿できればと思ってます。

お知らせ

APCはAWS Advanced Tier Services(アドバンストティアサービスパートナー)認定を受けております。

その中で私達クラウド事業部はAWSなどのクラウド技術を活用したSI/SESのご支援をしております。

www.ap-com.co.jp

また、AWSの運用自動化ツールも展開しております。

www.ap-com.co.jp

一緒に働いていただける仲間も募集中です!
今年もまだまだ組織規模拡大中なので、ご興味持っていただけましたらぜひお声がけください。

www.ap-com.co.jp

本記事の投稿者: t-umemoto
コンテナや k8s をメインにインフラ系のご支援を担当しています。
AWS は現在学び直し中! 普段は QiitaZenn に k8s を中心とした記事を投稿しております。よろしければ。