Fargateタスクのコンテナ内でシェルを起動する

Fargateタスクのコンテナ内でシェルを起動する方法のメモ。

あらかじめアクティベーションを作成してコンテナの起動時にインスタンスの登録を行う方法と、コンテナの起動時にアクティベーションの作成とインスタンス登録を両方行う方法がある。

前者の場合はタスクロールは不要だが、後者の場合はタスクロールでアクティベーションの作成が可能な権限を与える必要が必要がある。

参考リンク

サービスロールの作成

AmazonEC2RunCommandRoleForManagedInstancesが存在しない場合は作成する。マネジメントコンソールでアクティベーションを作成するのが簡単。

自分でサービスロールを作成する場合は以下のようにする。

ロールを作成する。

cat <<EOF > SSMService-Trust.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ssm.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
aws iam create-role \
    --role-name SSMServiceRole \
    --assume-role-policy-document file://SSMService-Trust.json

AWS管理ポリシーをアタッチする。

aws iam attach-role-policy \
    --role-name SSMServiceRole \
    --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

あらかじめアクティベーションを作成する場合

あらかじめアクティベーションをマネジメントコンソールまたはCLIで作成し、環境変数経由でコンテナに渡してあげる場合は以下のようにする。

イメージの作成

entrypoint.shを作成する。

cat <<"EOF" > entrypoint.sh
#!/bin/bash

set -eux
set -o pipefail

env

amazon-ssm-agent -register -id "${ACTIVATION_ID}" -code "${ACTIVATION_CODE}" -region "${AWS_REGION}"
amazon-ssm-agent
EOF

Dockerfileを作成する。

cat <<EOF > Dockerfile
FROM ubuntu:latest

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y \
      curl \
      dnsutils \
      iproute2 \
      iputils-ping \
      jq \
      language-pack-ja \
      net-tools \
      stress \
      tcpdump \
      tzdata \
      unzip \
      vim \
      wget \
      awscli \
    && rm -rf /var/lib/apt/lists/* \
    && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
    && dpkg-reconfigure -f noninteractive tzdata

RUN curl https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/debian_amd64/amazon-ssm-agent.deb -o /tmp/amazon-ssm-agent.deb \
    && dpkg -i /tmp/amazon-ssm-agent.deb \
    && cp /etc/amazon/ssm/seelog.xml.template /etc/amazon/ssm/seelog.xml

COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

ENV LANG ja_JP.UTF-8
ENV TZ Asia/Tokyo
EOF

イメージをビルドする。

docker build -t fargate-ssm:latest .

ECRにプッシュする。

aws ecr create-repository --repository-name fargate-ssm
repo=$(aws ecr describe-repositories --repository-names fargate-ssm --query 'repositories[0].repositoryUri' --output text)
aws ecr get-login-password | docker login --username AWS --password-stdin ${repo%%/*}
docker tag fargate-ssm:latest ${repo}:latest
docker push ${repo}:latest

アクティベーションの作成

アクティベーションを作成し、IDとコードを保管しておく。

aws ssm create-activation \
  --default-instance-name "DockerSSM" \
  --iam-role "SSMServiceRole" \
  --registration-limit 1 | tee activation.json

タスク定義の作成

タスク定義のjsonを作成する。環境変数にActivation IDとActivation Codeを入れる。

activation_id=$(cat activation.json | jq -r '.ActivationId')
activation_code=$(cat activation.json | jq -r '.ActivationCode')
cat <<EOF > task-definition.json
{
  "family": "fargate-ssm",
  "executionRoleArn": "ecsTaskExecutionRole",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "fargate-ssm",
      "image": "${repo}",
      "essential": true,
      "environment": [
        {
          "name": "ACTIVATION_ID",
          "value": "${activation_id}"
        },
        {
          "name": "ACTIVATION_CODE",
          "value": "${activation_code}"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/fargate-ssm",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024"
}
EOF

タスク定義を登録する。

aws ecs register-task-definition --cli-input-json file://task-definition.json

ロググループを作成する。

aws logs create-log-group --log-group-name "/ecs/fargate-ssm"

サービスの作成

サービスは手動で作成し、起動したらマネージドインスタンスに登録されていることを確認する。

コンテナの起動時にアクティベーションを作成する場合

コンテナ起動時にアクティベーションの作成とインスタンスの登録を両方行わせる場合は以下のようにする。

イメージの作成

entrypoint.shを作成する。

cat <<"EOF" > entrypoint.sh
#!/bin/bash

set -eux
set -o pipefail

env

activation=$(aws ssm create-activation \
  --default-instance-name "DockerSSM" \
  --iam-role "SSMServiceRole" \
  --registration-limit 1)
activation_id=$(echo ${activation} | jq -r .ActivationId)
activation_code=$(echo ${activation} | jq -r .ActivationCode)

amazon-ssm-agent -register -id "${activation_id}" -code "${activation_code}" -region "${AWS_REGION}"
amazon-ssm-agent
EOF

Dockerfileは先ほどと同じ。

cat <<EOF > Dockerfile
FROM ubuntu:latest

RUN apt-get update \
    && DEBIAN_FRONTEND=noninteractive apt-get install -y \
      curl \
      dnsutils \
      iproute2 \
      iputils-ping \
      jq \
      language-pack-ja \
      net-tools \
      stress \
      tcpdump \
      tzdata \
      unzip \
      vim \
      wget \
      awscli \
    && rm -rf /var/lib/apt/lists/* \
    && ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
    && dpkg-reconfigure -f noninteractive tzdata

RUN curl https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/debian_amd64/amazon-ssm-agent.deb -o /tmp/amazon-ssm-agent.deb \
    && dpkg -i /tmp/amazon-ssm-agent.deb \
    && cp /etc/amazon/ssm/seelog.xml.template /etc/amazon/ssm/seelog.xml

COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

ENV LANG ja_JP.UTF-8
ENV TZ Asia/Tokyo
EOF

イメージをビルドする。

docker build -t fargate-ssm-standalone:latest .

ECRにプッシュする。

aws ecr create-repository --repository-name fargate-ssm-standalone
repo=$(aws ecr describe-repositories --repository-names fargate-ssm-standalone --query 'repositories[0].repositoryUri' --output text)
aws ecr get-login-password | docker login --username AWS --password-stdin ${repo%%/*}
docker tag fargate-ssm-standalone:latest ${repo}:latest
docker push ${repo}:latest

タスクロールの作成

ロールを作成する。

cat <<EOF > ecs-tasks-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
aws iam create-role \
    --role-name MyFargateSSMStandaloneTaskRole \
    --assume-role-policy-document file://ecs-tasks-trust-policy.json

管理ポリシーを作成する。PassRoleが必要。

ACCOUNT_ID=$(aws sts get-caller-identity --output text --query Account)
cat <<EOF > ecs-tasks-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:PassRole"
      ],
      "Resource": "arn:aws:iam::${ACCOUNT_ID}:role/SSMServiceRole"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ssm:CreateActivation"
      ],
      "Resource": "*"
    }
  ]
}
EOF
aws iam create-policy \
    --policy-name MyFargateSSMStandaloneTaskPolicy \
    --policy-document file://ecs-tasks-policy.json
PolicyArn=$(aws iam list-policies | jq -r '.Policies[] | select( .PolicyName | test("MyFargateSSMStandaloneTaskPolicy") ) | .Arn')

ロールに管理ポリシーをアタッチする。

aws iam attach-role-policy \
    --role-name MyFargateSSMStandaloneTaskRole \
    --policy-arn ${PolicyArn}
RoleArn=$(aws iam list-roles | jq -r '.Roles[] | select( .RoleName | test("MyFargateSSMStandaloneTaskRole") ) | .Arn')

タスク定義の作成

タスク定義のjsonを作成する。

cat <<EOF > task-definition.json
{
  "family": "fargate-ssm-standalone",
  "taskRoleArn": "${RoleArn}",
  "executionRoleArn": "ecsTaskExecutionRole",
  "networkMode": "awsvpc",
  "containerDefinitions": [
    {
      "name": "fargate-ssm-standalone",
      "image": "${repo}",
      "essential": true,
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/fargate-ssm-standalone",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024"
}
EOF

タスク定義を登録する。

aws ecs register-task-definition --cli-input-json file://task-definition.json

ロググループを作成する。

aws logs create-log-group --log-group-name "/ecs/fargate-ssm-standalone"

サービスの作成

サービスは手動で作成し、起動したらマネージドインスタンスに登録されていることを確認する。

掃除

コンテナの停止時に自動的に登録解除するようにはなっていないので、マネージドインスタンスアクティベーションCLIまたはマネジメントコンソールで実行する。