ECS/Fargateでコンテナからタスクメタデータを取得する

ECS/Fargateでコンテナ起動時に初期処理でタスクメタデータを取得して、その値をもとにSSMパラメータストアおよびSecret Managerに格納された値を環境変数に設定する方法を確認したメモ。

コンテナイメージの作成

コンテナイメージを作成する。

サンプルアプリケーションの作成

環境変数を表示できるサンプルアプリケーションを作成する。以下のファイル群を作成する。

.
├── Dockerfile
├── app.py
└── requirements.txt

app.py

import os

from flask import Flask

app = Flask(__name__)


@app.route('/', methods=['GET'])
def top():
    return 'Hello World!'


@app.route('/env', methods=['GET'])
def env():
    return dict(os.environ)


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

requirements.txt

Flask

Dockerfile

FROM python:3-alpine

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py ./

ENV PYTHONUNBUFFERED 1
CMD [ "python", "./app.py" ]

パラメータの格納

SSMパラメータストアとSecret Managerにパラメータを格納する。

aws ssm put-parameter --name aurora-endpoint-ap-northeast-1a --type String --value aurora-ap-northeast-1a.example.com
aws ssm put-parameter --name aurora-endpoint-ap-northeast-1c --type String --value aurora-ap-northeast-1c.example.com

aws secretsmanager create-secret --name aurora-endpoint-ap-northeast-1a --secret-string aurora-ap-northeast-1a.example.local
aws secretsmanager create-secret --name aurora-endpoint-ap-northeast-1c --secret-string aurora-ap-northeast-1c.example.local

初期処理スクリプトの作成

タスクメタデータを取得し、その値をもとにパラメータを取得するスクリプトを作成する。

python:3-alpineのイメージをdocker inspectで確認するとENTRYPOINTは空になっている。 ENTRYPOINTスクリプトを実行し、このスクリプトに引数で渡されるCMDの処理を最後にexecで実行する。

entrypoint.sh

#!/bin/sh

# 起動直後のためか統計情報がとれないことがあったので、sleepを入れている
sleep 10

mkdir -p /usr/src/app/static/
curl ${ECS_CONTAINER_METADATA_URI}            > /usr/src/app/static/container-metadata.txt
curl ${ECS_CONTAINER_METADATA_URI}/task       > /usr/src/app/static/task-metadata.txt
curl ${ECS_CONTAINER_METADATA_URI}/stats      > /usr/src/app/static/container-stats.txt
curl ${ECS_CONTAINER_METADATA_URI}/task/stats > /usr/src/app/static/task-stats.txt

MYIP=$(cat /usr/src/app/static/task-metadata.txt | jq -r '.Containers[0].Networks[0].IPv4Addresses[0]')

# IPからAZを判断
# サブネットマスクが24でない場合は別のやり方を考える必要がある
if echo ${MYIP} | grep 10.1.3. > /dev/null 2>&1; then
  AZ=ap-northeast-1a
elif echo ${MYIP} | grep 10.1.4. > /dev/null 2>&1; then
  AZ=ap-northeast-1c
else
  exit 1
fi

# AWS CLIでSSMパラメータストアからパラメータを取得
AURORA_ENDPOINT_SSM=$(aws ssm get-parameters --name aurora-endpoint-${AZ} | jq -r '.Parameters[].Value')
# Secret Managerからパラメータを取得
AURORA_ENDPOINT_SECRET_MANAGER=$(aws secretsmanager get-secret-value --secret-id aurora-endpoint-${AZ} | jq -r '.SecretString')

export MYIP
export AZ
export AURORA_ENDPOINT_SSM
export AURORA_ENDPOINT_SECRET_MANAGER
exec "$@"

スクリプトに実行権限を付与する。

chmod +x entrypoint.sh

イメージのビルド

DockerfilecurljqawscliのインストールとENTRYPOINTを追加する。

FROM python:3-alpine

RUN apk --no-cache add curl
RUN pip install awscli

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py ./

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

ENV PYTHONUNBUFFERED 1
CMD [ "python", "./app.py" ]

イメージをビルドする。

docker build -t metadata-sample .

イメージのECRへの登録

イメージをECRに登録する。

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

ECS/Fargateへのデプロイ

ECS/Fargateでタスクを実行する。

VPCの作成

CloudFormationでパブリックサブネットとプライベートサブネットとを2つずつ持つVPCを作成する。

テンプレートを作成する。

AWSTemplateFormatVersion: "2010-09-09"

Parameters:
  VPCCIDR:
    Type: String
    Default: 10.1.0.0/16
  PublicSubnet1CIDR:
    Type: String
    Default: 10.1.1.0/24
  PublicSubnet2CIDR:
    Type: String
    Default: 10.1.2.0/24
  PrivateSubnet1CIDR:
    Type: String
    Default: 10.1.3.0/24
  PrivateSubnet2CIDR:
    Type: String
    Default: 10.1.4.0/24

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

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

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

  ##########
  # PublicSubnet1
  ##########
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnet1CIDR
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  PublicSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      SubnetId: !Ref PublicSubnet1

  PublicSubnet1DefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
    DependsOn:
      - InternetGateway

  NatGateway1EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet1

  ##########
  # PublicSubnet2
  ##########
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PublicSubnet2CIDR
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet2

  PublicSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet2

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      SubnetId: !Ref PublicSubnet2

  PublicSubnet2DefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnet2RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
    DependsOn:
      - InternetGateway

  NatGateway2EIP:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway2EIP.AllocationId
      SubnetId: !Ref PublicSubnet2
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PublicSubnet2

  ##########
  # PrivateSubnet1
  ##########
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PrivateSubnet1CIDR
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs ""]
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet1

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      SubnetId: !Ref PrivateSubnet1

  PrivateSubnet1DefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  ##########
  # PrivateSubnet2
  ##########
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref PrivateSubnet2CIDR
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs ""]
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet2

  PrivateSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-PrivateSubnet2

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      SubnetId: !Ref PrivateSubnet2

  PrivateSubnet2DefaultRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

Outputs:
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub ${AWS::StackName}-VPC
  PublicSubnet1:
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet1
  PublicSubnet2:
    Value: !Ref PublicSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-PublicSubnet2
  PrivateSubnet1:
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet1
  PrivateSubnet2:
    Value: !Ref PrivateSubnet2
    Export:
      Name: !Sub ${AWS::StackName}-PrivateSubnet2

パラメーターファイルを作成する。

[
  {
    "ParameterKey": "VPCCIDR",
    "ParameterValue": "10.1.0.0/16"
  },
  {
    "ParameterKey": "PublicSubnet1CIDR",
    "ParameterValue": "10.1.1.0/24"
  },
  {
    "ParameterKey": "PublicSubnet2CIDR",
    "ParameterValue": "10.1.2.0/24"
  },
  {
    "ParameterKey": "PrivateSubnet1CIDR",
    "ParameterValue": "10.1.3.0/24"
  },
  {
    "ParameterKey": "PrivateSubnet2CIDR",
    "ParameterValue": "10.1.4.0/24"
  }
]

スタックを作成する。

aws cloudformation create-stack \
  --stack-name MyVPCStack \
  --template-body file://vpc.yaml \
  --parameters file://vpc.parameter.json

VPCとサブネットのIDを確認する。

aws cloudformation describe-stacks --stack-name MyVPCStack | jq -r '.Stacks[].Outputs[]'

変数に入れておく。

VpcId=$(aws cloudformation describe-stacks --stack-name MyVPCStack | jq -r '.Stacks[].Outputs[] | select( .OutputKey | test("VPC") ) | .OutputValue')
PrivateSubnet1Id=$(aws cloudformation describe-stacks --stack-name MyVPCStack | jq -r '.Stacks[].Outputs[] | select( .OutputKey | test("PrivateSubnet1") ) | .OutputValue')
PrivateSubnet2Id=$(aws cloudformation describe-stacks --stack-name MyVPCStack | jq -r '.Stacks[].Outputs[] | select( .OutputKey | test("PrivateSubnet2") ) | .OutputValue')
PublicSubnet1Id=$(aws cloudformation describe-stacks --stack-name MyVPCStack | jq -r '.Stacks[].Outputs[] | select( .OutputKey | test("PublicSubnet1") ) | .OutputValue')
PublicSubnet2Id=$(aws cloudformation describe-stacks --stack-name MyVPCStack | jq -r '.Stacks[].Outputs[] | select( .OutputKey | test("PublicSubnet2") ) | .OutputValue')

セキュリティグループの作成

セキュリティグループを作成する。

aws ec2 create-security-group --group-name MyALBSecurityGroup --description MyALBSecurityGroup --vpc-id ${VpcId}
ALBSecurityGroupId=$(aws ec2 describe-security-groups | jq -r '.SecurityGroups[] | select( .GroupName | test("MyALBSecurityGroup") ) | .GroupId'); echo ${ALBSecurityGroupId}

80番ポートへのアクセスを許可する。

aws ec2 authorize-security-group-ingress --group-id ${ALBSecurityGroupId} --protocol tcp --port 80 --cidr 0.0.0.0/0

デフォルトのセキュリティグループを取得する。

DefaultSecurityGroupId=$(aws ec2 describe-security-groups | jq -r '.SecurityGroups[] | select( ( .VpcId | test("'${VpcId}'") ) and ( .GroupName | test("default") ) ) | .GroupId'); echo ${DefaultSecurityGroupId}

ALBの作成

ALBを作成する。

aws elbv2 create-load-balancer --name MyALB \
  --subnets ${PublicSubnet1Id} ${PublicSubnet2Id} --security-groups ${ALBSecurityGroupId} ${DefaultSecurityGroupId}
LoadBalancerArn=$(aws elbv2 describe-load-balancers --name MyALB | jq -r '.LoadBalancers[].LoadBalancerArn'); echo ${LoadBalancerArn}
DNSName=$(aws elbv2 describe-load-balancers --name MyALB | jq -r '.LoadBalancers[].DNSName'); echo ${DNSName}

ターゲットグループを作成する。

aws elbv2 create-target-group --name MyTargetGroup --protocol HTTP --port 80 \
  --target-type ip --vpc-id ${VpcId}
TargetGroupArn=$(aws elbv2 describe-target-groups --names MyTargetGroup | jq -r '.TargetGroups[].TargetGroupArn'); echo ${TargetGroupArn}

ターゲットグループにリクエストを転送するデフォルトルールを持つリスナーを作成する。

aws elbv2 create-listener --load-balancer-arn ${LoadBalancerArn} \
  --protocol HTTP --port 80  \
  --default-actions Type=forward,TargetGroupArn=${TargetGroupArn}

ECSクラスターの作成

Fargateクラスターを作成する。

aws ecs create-cluster --cluster-name fargate-cluster

タスクロールの作成

信頼ポリシーのjsonを作成する。

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 MetadataSampleTaskRole --assume-role-policy-document file://ecs-tasks-trust-policy.json

ポリシーの定義ファイルを作成する。

cat <<EOF > ecs-tasks-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ssm:DescribeParameters"],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": ["ssm:GetParameters"],
      "Resource": [
        "arn:aws:ssm:ap-northeast-1:XXXXXXXXXXXX:parameter/aurora-endpoint-*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "secretsmanager:ListSecrets",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX:secret:aurora-endpoint-*"
    }
  ]
}
EOF

ポリシーを作成し、ロールにアタッチする。

aws iam create-policy --policy-name MetadataSampleTaskPolicy --policy-document file://ecs-tasks-policy.json
PolicyArn=$(aws iam list-policies | jq -r '.Policies[] | select( .PolicyName | test("MetadataSampleTaskPolicy") ) | .Arn')
aws iam attach-role-policy --role-name MetadataSampleTaskRole --policy-arn ${PolicyArn}
RoleArn=$(aws iam list-roles | jq -r '.Roles[] | select( .RoleName | test("MetadataSampleTaskRole") ) | .Arn')

タスク定義の登録

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

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

タスク定義を登録する。

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

タスクが使用するロググループを作成する。

aws logs create-log-group --log-group-name "/ecs/metadata-sample"

サービスの作成

サービスのjsonを作成する。

cat <<EOF > service.json
{
  "cluster": "fargate-cluster",
  "serviceName": "metadata-sample",
  "taskDefinition": "metadata-sample",
  "loadBalancers": [
    {
      "targetGroupArn": "${TargetGroupArn}",
      "containerName": "metadata-sample",
      "containerPort": 5000
    }
  ],
  "desiredCount": 2,
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "subnets": ["${PrivateSubnet1Id}", "${PrivateSubnet2Id}"],
      "securityGroups": ["${DefaultSecurityGroupId}"],
      "assignPublicIp": "DISABLED"
    }
  }
}
EOF

サービスを作成する。

aws ecs create-service --cli-input-json file://service.json

稼働確認

環境変数出力ページを確認する。

$ curl -s http://${DNSName}/env
{
  "AURORA_ENDPOINT_SECRET_MANAGER": "aurora-ap-northeast-1c.example.local",
  "AURORA_ENDPOINT_SSM": "aurora-ap-northeast-1c.example.com",
  "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/v2/credentials/7e7d0c6e-7788-4c32-9b98-289866f8d150",
  "AWS_DEFAULT_REGION": "ap-northeast-1",
  "AWS_EXECUTION_ENV": "AWS_ECS_FARGATE",
  "AWS_REGION": "ap-northeast-1",
  "AZ": "ap-northeast-1c",
  "ECS_CONTAINER_METADATA_URI": "http://169.254.170.2/v3/875d420a-f2e5-494e-9284-0028203364bc",
  "GPG_KEY": "E3FF2839C048B25C084DEBE9B26995E310250568",
  "HOME": "/root",
  "HOSTNAME": "ip-10-1-4-45.ap-northeast-1.compute.internal",
  "LANG": "C.UTF-8",
  "MYIP": "10.1.4.45",
  "PATH": "/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
  "PWD": "/usr/src/app",
  "PYTHONUNBUFFERED": "1",
  "PYTHON_GET_PIP_SHA256": "421ac1d44c0cf9730a088e337867d974b91bdce4ea2636099275071878cc189e",
  "PYTHON_GET_PIP_URL": "https://github.com/pypa/get-pip/raw/d59197a3c169cef378a22428a3fa99d33e080a5d/get-pip.py",
  "PYTHON_PIP_VERSION": "20.0.2",
  "PYTHON_VERSION": "3.8.2",
  "SHLVL": "1",
  "WERKZEUG_RUN_MAIN": "true",
  "WERKZEUG_SERVER_FD": "3"
}

MYIPAZAURORA_ENDPOINT_SSMAURORA_ENDPOINT_SECRET_MANAGER環境変数にセットできている。

コンテナメタデータを確認する。違うタスクにロードバランスされるかもしれない。

$ curl -s http://${DNSName}/static/container-metadata.txt | jq .
{
  "DockerId": "74e471293a55db5ce564bf1a3e6dfbf3a912ec1c389db5d2ff1fc8ce3b3da71e",
  "Name": "metadata-sample",
  "DockerName": "ecs-metadata-sample-2-metadata-sample-eed8d2e8ddd793825000",
  "Image": "XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/metadata-sample",
  "ImageID": "sha256:d971d80d96bcefd3b9b9f3529c661c880e2eae721796eebc81df63f98e73a22c",
  "Labels": {
    "com.amazonaws.ecs.cluster": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/fargate-cluster",
    "com.amazonaws.ecs.container-name": "metadata-sample",
    "com.amazonaws.ecs.task-arn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task/31d29581-4211-4c80-9a1c-62aff14e50ca",
    "com.amazonaws.ecs.task-definition-family": "metadata-sample",
    "com.amazonaws.ecs.task-definition-version": "2"
  },
  "DesiredStatus": "RUNNING",
  "KnownStatus": "RUNNING",
  "Limits": {
    "CPU": 0,
    "Memory": 0
  },
  "CreatedAt": "2020-03-06T21:21:19.967864669Z",
  "StartedAt": "2020-03-06T21:21:23.462788118Z",
  "Type": "NORMAL",
  "Networks": [
    {
      "NetworkMode": "awsvpc",
      "IPv4Addresses": [
        "10.1.4.45"
      ]
    }
  ]
}

タスクメタデータを確認。

$ curl -s http://${DNSName}/static/task-metadata.txt | jq .
{
  "Cluster": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/fargate-cluster",
  "TaskARN": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task/31d29581-4211-4c80-9a1c-62aff14e50ca",
  "Family": "metadata-sample",
  "Revision": "2",
  "DesiredStatus": "RUNNING",
  "KnownStatus": "RUNNING",
  "Containers": [
    {
      "DockerId": "9471052b540ea3c6e38a3cc8d2a6036c8c77f66e7eab733f010236703da61121",
      "Name": "~internal~ecs~pause",
      "DockerName": "ecs-metadata-sample-2-internalecspause-84a9b0dbb4d0c09ba501",
      "Image": "fg-proxy:tinyproxy",
      "ImageID": "",
      "Labels": {
        "com.amazonaws.ecs.cluster": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/fargate-cluster",
        "com.amazonaws.ecs.container-name": "~internal~ecs~pause",
        "com.amazonaws.ecs.task-arn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task/31d29581-4211-4c80-9a1c-62aff14e50ca",
        "com.amazonaws.ecs.task-definition-family": "metadata-sample",
        "com.amazonaws.ecs.task-definition-version": "2"
      },
      "DesiredStatus": "RESOURCES_PROVISIONED",
      "KnownStatus": "RESOURCES_PROVISIONED",
      "Limits": {
        "CPU": 0,
        "Memory": 0
      },
      "CreatedAt": "2020-03-06T21:21:12.895668525Z",
      "StartedAt": "2020-03-06T21:21:13.938484458Z",
      "Type": "CNI_PAUSE",
      "Networks": [
        {
          "NetworkMode": "awsvpc",
          "IPv4Addresses": [
            "10.1.4.45"
          ]
        }
      ]
    },
    {
      "DockerId": "74e471293a55db5ce564bf1a3e6dfbf3a912ec1c389db5d2ff1fc8ce3b3da71e",
      "Name": "metadata-sample",
      "DockerName": "ecs-metadata-sample-2-metadata-sample-eed8d2e8ddd793825000",
      "Image": "XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/metadata-sample",
      "ImageID": "sha256:d971d80d96bcefd3b9b9f3529c661c880e2eae721796eebc81df63f98e73a22c",
      "Labels": {
        "com.amazonaws.ecs.cluster": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/fargate-cluster",
        "com.amazonaws.ecs.container-name": "metadata-sample",
        "com.amazonaws.ecs.task-arn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task/31d29581-4211-4c80-9a1c-62aff14e50ca",
        "com.amazonaws.ecs.task-definition-family": "metadata-sample",
        "com.amazonaws.ecs.task-definition-version": "2"
      },
      "DesiredStatus": "RUNNING",
      "KnownStatus": "RUNNING",
      "Limits": {
        "CPU": 0,
        "Memory": 0
      },
      "CreatedAt": "2020-03-06T21:21:19.967864669Z",
      "StartedAt": "2020-03-06T21:21:23.462788118Z",
      "Type": "NORMAL",
      "Networks": [
        {
          "NetworkMode": "awsvpc",
          "IPv4Addresses": [
            "10.1.4.45"
          ]
        }
      ]
    }
  ],
  "Limits": {
    "CPU": 0.25,
    "Memory": 512
  },
  "PullStartedAt": "2020-03-06T21:21:14.066094891Z",
  "PullStoppedAt": "2020-03-06T21:21:19.961957201Z"
}

コンテナ統計を確認する。起動直後に取ろうとすると値がnullになるかもしれない。

$ curl -s http://${DNSName}/static/container-stats.txt | jq .
{
  "read": "2020-03-06T21:21:33.154199168Z",
  "preread": "2020-03-06T21:21:32.151581707Z",
  "pids_stats": {
    "current": 2
  },
  "blkio_stats": {
    "io_service_bytes_recursive": [],
    "io_serviced_recursive": [],
    "io_queue_recursive": [],
    "io_service_time_recursive": [],
    "io_wait_time_recursive": [],
    "io_merged_recursive": [],
    "io_time_recursive": [],
    "sectors_recursive": []
  },
  "num_procs": 0,
  "storage_stats": {},
  "cpu_stats": {
    "cpu_usage": {
      "total_usage": 199585609,
      "percpu_usage": [
        13046685,
        186538924,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ],
      "usage_in_kernelmode": 10000000,
      "usage_in_usermode": 170000000
    },
    "system_cpu_usage": 175760000000,
    "online_cpus": 2,
    "throttling_data": {
      "periods": 0,
      "throttled_periods": 0,
      "throttled_time": 0
    }
  },
  "precpu_stats": {
    "cpu_usage": {
      "total_usage": 199585609,
      "percpu_usage": [
        13046685,
        186538924,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ],
      "usage_in_kernelmode": 10000000,
      "usage_in_usermode": 170000000
    },
    "system_cpu_usage": 173770000000,
    "online_cpus": 2,
    "throttling_data": {
      "periods": 0,
      "throttled_periods": 0,
      "throttled_time": 0
    }
  },
  "memory_stats": {
    "usage": 794624,
    "max_usage": 12771328,
    "stats": {
      "active_anon": 98304,
      "active_file": 0,
      "cache": 0,
      "dirty": 0,
      "hierarchical_memory_limit": 536870912,
      "hierarchical_memsw_limit": 9223372036854772000,
      "inactive_anon": 0,
      "inactive_file": 0,
      "mapped_file": 0,
      "pgfault": 1827,
      "pgmajfault": 0,
      "pgpgin": 3368,
      "pgpgout": 3344,
      "rss": 98304,
      "rss_huge": 0,
      "total_active_anon": 98304,
      "total_active_file": 0,
      "total_cache": 0,
      "total_dirty": 0,
      "total_inactive_anon": 0,
      "total_inactive_file": 0,
      "total_mapped_file": 0,
      "total_pgfault": 1827,
      "total_pgmajfault": 0,
      "total_pgpgin": 3368,
      "total_pgpgout": 3344,
      "total_rss": 98304,
      "total_rss_huge": 0,
      "total_unevictable": 0,
      "total_writeback": 0,
      "unevictable": 0,
      "writeback": 0
    },
    "limit": 4134825984
  },
  "name": "/ecs-metadata-sample-2-metadata-sample-eed8d2e8ddd793825000",
  "id": "74e471293a55db5ce564bf1a3e6dfbf3a912ec1c389db5d2ff1fc8ce3b3da71e"
}

タスク統計を確認。

$ curl -s http://${DNSName}/static/task-stats.txt | jq .
{
  "8e19921e19dcdc537d89758e56e1d5cab23265b0f709453b8daa5af41094ed38": {
    "read": "2020-03-06T21:21:27.704043015Z",
    "preread": "2020-03-06T21:21:26.701473202Z",
    "pids_stats": {
      "current": 2
    },
    "blkio_stats": {
      "io_service_bytes_recursive": [],
      "io_serviced_recursive": [],
      "io_queue_recursive": [],
      "io_service_time_recursive": [],
      "io_wait_time_recursive": [],
      "io_merged_recursive": [],
      "io_time_recursive": [],
      "sectors_recursive": []
    },
    "num_procs": 0,
    "storage_stats": {},
    "cpu_stats": {
      "cpu_usage": {
        "total_usage": 201698665,
        "percpu_usage": [
          79079868,
          122618797,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0
        ],
        "usage_in_kernelmode": 10000000,
        "usage_in_usermode": 170000000
      },
      "system_cpu_usage": 397570000000,
      "online_cpus": 2,
      "throttling_data": {
        "periods": 0,
        "throttled_periods": 0,
        "throttled_time": 0
      }
    },
    "precpu_stats": {
      "cpu_usage": {
        "total_usage": 201698665,
        "percpu_usage": [
          79079868,
          122618797,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0
        ],
        "usage_in_kernelmode": 10000000,
        "usage_in_usermode": 170000000
      },
      "system_cpu_usage": 395590000000,
      "online_cpus": 2,
      "throttling_data": {
        "periods": 0,
        "throttled_periods": 0,
        "throttled_time": 0
      }
    },
    "memory_stats": {
      "usage": 774144,
      "max_usage": 12685312,
      "stats": {
        "active_anon": 94208,
        "active_file": 0,
        "cache": 0,
        "dirty": 0,
        "hierarchical_memory_limit": 536870912,
        "hierarchical_memsw_limit": 9223372036854772000,
        "inactive_anon": 0,
        "inactive_file": 0,
        "mapped_file": 0,
        "pgfault": 1603,
        "pgmajfault": 0,
        "pgpgin": 3156,
        "pgpgout": 3133,
        "rss": 94208,
        "rss_huge": 0,
        "total_active_anon": 94208,
        "total_active_file": 0,
        "total_cache": 0,
        "total_dirty": 0,
        "total_inactive_anon": 0,
        "total_inactive_file": 0,
        "total_mapped_file": 0,
        "total_pgfault": 1603,
        "total_pgmajfault": 0,
        "total_pgpgin": 3156,
        "total_pgpgout": 3133,
        "total_rss": 94208,
        "total_rss_huge": 0,
        "total_unevictable": 0,
        "total_writeback": 0,
        "unevictable": 0,
        "writeback": 0
      },
      "limit": 4134825984
    },
    "name": "/ecs-metadata-sample-2-metadata-sample-9c9caf838f84dbe8a301",
    "id": "8e19921e19dcdc537d89758e56e1d5cab23265b0f709453b8daa5af41094ed38"
  },
  "9979db90a47dd9c7f89758f683da6f238ed4198486a84c6afa79c6cfb543fee0": {
    "read": "2020-03-06T21:21:27.702690833Z",
    "preread": "2020-03-06T21:21:26.70004246Z",
    "pids_stats": {
      "current": 7
    },
    "blkio_stats": {
      "io_service_bytes_recursive": [
        {
          "major": 202,
          "minor": 26368,
          "op": "Read",
          "value": 5922816
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Write",
          "value": 12288
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Sync",
          "value": 5935104
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Async",
          "value": 0
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Total",
          "value": 5935104
        }
      ],
      "io_serviced_recursive": [
        {
          "major": 202,
          "minor": 26368,
          "op": "Read",
          "value": 343
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Write",
          "value": 3
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Sync",
          "value": 346
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Async",
          "value": 0
        },
        {
          "major": 202,
          "minor": 26368,
          "op": "Total",
          "value": 346
        }
      ],
      "io_queue_recursive": [],
      "io_service_time_recursive": [],
      "io_wait_time_recursive": [],
      "io_merged_recursive": [],
      "io_time_recursive": [],
      "sectors_recursive": []
    },
    "num_procs": 0,
    "storage_stats": {},
    "cpu_stats": {
      "cpu_usage": {
        "total_usage": 580043158,
        "percpu_usage": [
          230190180,
          349852978,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0
        ],
        "usage_in_kernelmode": 250000000,
        "usage_in_usermode": 220000000
      },
      "system_cpu_usage": 397570000000,
      "online_cpus": 2,
      "throttling_data": {
        "periods": 0,
        "throttled_periods": 0,
        "throttled_time": 0
      }
    },
    "precpu_stats": {
      "cpu_usage": {
        "total_usage": 580013184,
        "percpu_usage": [
          230190180,
          349823004,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0,
          0
        ],
        "usage_in_kernelmode": 250000000,
        "usage_in_usermode": 220000000
      },
      "system_cpu_usage": 395580000000,
      "online_cpus": 2,
      "throttling_data": {
        "periods": 0,
        "throttled_periods": 0,
        "throttled_time": 0
      }
    },
    "memory_stats": {
      "usage": 12083200,
      "max_usage": 13115392,
      "stats": {
        "active_anon": 4009984,
        "active_file": 4476928,
        "cache": 5992448,
        "dirty": 12288,
        "hierarchical_memory_limit": 536870912,
        "hierarchical_memsw_limit": 9223372036854772000,
        "inactive_anon": 0,
        "inactive_file": 1515520,
        "mapped_file": 2129920,
        "pgfault": 6227,
        "pgmajfault": 52,
        "pgpgin": 7564,
        "pgpgout": 5122,
        "rss": 4009984,
        "rss_huge": 0,
        "total_active_anon": 4009984,
        "total_active_file": 4476928,
        "total_cache": 5992448,
        "total_dirty": 12288,
        "total_inactive_anon": 0,
        "total_inactive_file": 1515520,
        "total_mapped_file": 2129920,
        "total_pgfault": 6227,
        "total_pgmajfault": 52,
        "total_pgpgin": 7564,
        "total_pgpgout": 5122,
        "total_rss": 4009984,
        "total_rss_huge": 0,
        "total_unevictable": 0,
        "total_writeback": 0,
        "unevictable": 0,
        "writeback": 0
      },
      "limit": 4134825984
    },
    "name": "/ecs-metadata-sample-2-internalecspause-eca9c2c1c88ed48f3200",
    "id": "9979db90a47dd9c7f89758f683da6f238ed4198486a84c6afa79c6cfb543fee0"
  }
}