AWS X-Rayによる分散アプリケーション分析をFargateで試してみた #Fargate

こんにちは、コカコーラ大好きカジです。

AWS X-RayをFargate上でどのように動かすか不明だったため、調べてみたところ、以下の資料が見つかったので、東京リージョンで構築して試してみました。 構築時のコマンドの実行結果も貼っています。

GitHub - aws-samples/aws-xray-fargate: Code examples showing how to run AWS X-Ray as a sidecar container on Fargate for deep application insights.

AWS X-Rayによる分散アプリケーション分析(分散トレーシング)とは

マイクロサービスが増え、パフォーマンス状況やデバッグが煩雑になっており、サービス間の相互作用やそれぞれの待機時間を把握するのは面倒な問題です。 そんな問題を解決するのが、AWS X-Rayです。

AWS X-Rayでできること

  • パフォーマンスボトルネックとエラーの特定
  • アプリケーション内の特定サービスへの問題を特定
  • アプリケーションのユーザに対する問題のインパクトを特定
  • アプリケーションのサービスコールグラフの可視化

参考元 Introduction to AWS X-Ray

X-Rayの動作詳細は AWS Black Belt Online Seminar 2017 AWS X-Ray

Fargateとは

サーバーやクラスターの管理が不要なコンテナの実行 = EC2不要でコンテナが実行出来るサービスです。

AWS Fargate事始め〜CloudWatch Logsにこんにちは | DevelopersIO

構築する環境の構成図

事前準備

以下が事前に準備できている必要があります。

  • AWS CLIを実行できること
  • ECS CLIを実行できること(インストールはAmazon ECS CLI のインストール - Amazon Elastic Container Service
  • Dockerコマンドが実行できること
  • Fargateを載せるVPCが構築済みであること
  • Fargateを載せるサブネットが、ECRと通信できるようになっていること
  • envsubstコマンドが使えること(使えない場合は、MacOSXでは、brew upgrade gettextや、brew link --force gettextの実行が必要)

ECSクラスタの構築

作業用PCなどにgit cloneして作業します。

git clone https://github.com/aws-samples/aws-xray-fargate.git

ECSクラスタを作成します。 cluster_name=xray-fargate としました。

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

実行結果

{
    "cluster": {
        "clusterArn": "arn:aws:ecs:ap-northeast-1:000000000000:cluster/xray-fargate",
        "clusterName": "xray-fargate",
        "status": "ACTIVE",
        "registeredContainerInstancesCount": 0,
        "runningTasksCount": 0,
        "pendingTasksCount": 0,
        "activeServicesCount": 0,
        "statistics": [],
        "tags": [],
        "settings": [
            {
                "name": "containerInsights",
                "value": "disabled"
            }
        ]
    }
}

Task Roleを作成

以後は、git cloneしたフォルダのsrc下のREADME.mdをコピペしながら実施していきます。

<role_name><policy_name>を決めて実行します。

私は、以下にしてみました。

role_name = xray-fargate-taskrole
policy_name = xray-fargate-task-policy
cd <git clone下ディレクトリ>/aws-xray-fargate/src

export TASK_ROLE_NAME=$(aws iam create-role --role-name xray-fargate-taskrole --assume-role-policy-document file://ecs-trust-pol.json | jq -r '.Role.RoleName')
export XRAY_POLICY_ARN=$(aws iam create-policy --policy-name xray-fargate-task-policy --policy-document file://xray-pol.json | jq -r '.Policy.Arn')
aws iam attach-role-policy --role-name $TASK_ROLE_NAME --policy-arn $XRAY_POLICY_ARN

Task Execution Roleを作成

src下のREADME.mdを見ながら同じように実行していきます。 すでに1回でもFargateを起動しているとecsTaskExecutionRoleが作成済みのため別なTaskExecutionRole名にする必要があります。 私は、以下にしてみました。 <ecsTaskExecutionRole> = xray-fargate-ecsTaskExecutionRole

aws iam create-role --role-name xray-fargate-ecsTaskExecutionRole --assume-role-policy-document file://ecs-trust-pol.json
export ECS_EXECUTION_POLICY_ARN=$(aws iam list-policies --scope AWS --query 'Policies[?PolicyName==`AmazonECSTaskExecutionRolePolicy`].Arn' | jq -r '.[]')
aws iam attach-role-policy --role-name xray-fargate-ecsTaskExecutionRole --policy-arn $ECS_EXECUTION_POLICY_ARN

1行目だけ実行結果が出ます。

{
    "Role": {
        "Path": "/",
        "RoleName": "xray-fargate-ecsTaskExecutionRole",
        "RoleId": "AROAUVADBEKEJOK5IU3WL",
        "Arn": "arn:aws:iam::000000000000:role/xray-fargate-ecsTaskExecutionRole",
        "CreateDate": "2019-09-04T11:08:11Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "",
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "ecs-tasks.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

Task RoleとTask Execution RoleのARNを環境変数に出力

export TASK_ROLE_ARN=$(aws iam get-role --role-name xray-fargate-taskrole --query "Role.Arn" --output text)
export TASK_EXECUTION_ROLE_ARN=$(aws iam get-role --role-name xray-fargate-ecsTaskExecutionRole --query "Role.Arn" --output text)

Fargateを構築するVPCのサブネットを環境変数に指定

Fargate作成予定のVPC IDを確認しておきます。

vpc_id=vpc-00000000

Fargate構築するサブネットのIDを環境変数に指定します。 subnet-11111111subnet-22222222とします。

export SUBNET_ID_1=subnet-11111111
export SUBNET_ID_2=subnet-22222222

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

<group_name>=xray-fargate-tasksg にしました。

export SG_ID=$(aws ec2 create-security-group --group-name xray-fargate-sg --description xray-fargate-sg --vpc-id vpc-00000000 | jq -r '.GroupId')
aws ec2 authorize-security-group-ingress --group-id $SG_ID --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $SG_ID --protocol all --port all --source-group $SG_ID

service B用のALBを構築

私は以下のように変更して実行しました。 <load_balancer_name>=xray-fargate-b-alb <target_group_name>=xray-fargate-b-tg

export LOAD_BALANCER_ARN=$(aws elbv2 create-load-balancer --name xray-fargate-b-alb --subnets $SUBNET_ID_1 $SUBNET_ID_2 --security-groups $SG_ID --scheme internet-facing --type application | jq -r '.LoadBalancers[].LoadBalancerArn')
export TARGET_GROUP_ARN=$(aws elbv2 create-target-group --name xray-fargate-b-tg --protocol HTTP --port 8080 --vpc-id vpc-00000000 --target-type ip --health-check-path /health | jq -r '.TargetGroups[].TargetGroupArn')
aws elbv2 create-listener --load-balancer-arn $LOAD_BALANCER_ARN --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN

実行結果

{
    "Listeners": [
        {
            "ListenerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:000000000000:listener/app/xray-fargate-b-alb/cc80f26490abbeb3/bdf71758725d21a5",
            "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:000000000000:loadbalancer/app/xray-fargate-b-alb/cc80f26490abbeb3",
            "Port": 80,
            "Protocol": "HTTP",
            "DefaultActions": [
                {
                    "Type": "forward",
                    "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:000000000000:targetgroup/xray-fargate-b-tg/bf803125598e915a"
                }
            ]
        }
    ]
}

Service BのDNS名(Endpoint)を取得し環境変数に指定

export SERVICE_B_ENDPOINT=$(aws elbv2 describe-load-balancers --load-balancer-arn $LOAD_BALANCER_ARN | jq -r '.LoadBalancers[].DNSName')

ECRにService A/BのコンテナをPush

ecs-cliが正常にできない場合は、「スイッチロール先のAWSアカウントでecs-cliを実行するときに便利なスクリプト」を最後に記載しているので適応をしてから試してみてください。

cd ./service-b/
docker build -t service-b .
ecs-cli push service-b
cd ../service-a/
docker build -t service-a .
ecs-cli push service-a

実行結果

$ docker build -t service-b .
Sending build context to Docker daemon  8.704kB
Step 1/8 : FROM node:8-alpine
 ---> b9e61ad789af
Step 2/8 : RUN mkdir -p /usr/src/app
 ---> Using cache
 ---> ecdc0707193d
Step 3/8 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 491885cffc88
Step 4/8 : COPY package.json /usr/src/app/
 ---> Using cache
 ---> b46122624205
Step 5/8 : RUN npm install
 ---> Using cache
 ---> 091cf7c3a574
Step 6/8 : COPY . /usr/src/app
 ---> Using cache
 ---> 7bc2f297d1f5
Step 7/8 : EXPOSE 8080
 ---> Using cache
 ---> 2a762037c8cd
Step 8/8 : CMD [ "node", "server.js" ]
 ---> Using cache
 ---> 4e00fb1d0ea2
Successfully built 4e00fb1d0ea2
Successfully tagged service-b:latest

$ ecs-cli push service-b
INFO[0000] Getting AWS account ID...                    
INFO[0001] Tagging image                                 image=service-b repository=000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/service-b tag=
INFO[0001] Image tagged                                 
INFO[0001] Creating repository                           repository=service-b
INFO[0001] Repository created                           
INFO[0001] Pushing image                                 repository=000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/service-b tag=
INFO[0015] Image pushed                   

$ docker build -t service-a .
Sending build context to Docker daemon  11.26kB
Step 1/8 : FROM node:8-alpine
 ---> b9e61ad789af
Step 2/8 : RUN mkdir -p /usr/src/app
 ---> Using cache
 ---> ecdc0707193d
Step 3/8 : WORKDIR /usr/src/app
 ---> Using cache
 ---> 491885cffc88
Step 4/8 : COPY package.json /usr/src/app/
 ---> a95eab0a3236
Step 5/8 : RUN npm install
 ---> Running in a41cd518380f
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN xray-demo-service-a@1.0.0 No repository field.
npm WARN xray-demo-service-a@1.0.0 No license field.

added 85 packages from 115 contributors and audited 217 packages in 6.571s
found 0 vulnerabilities

Removing intermediate container a41cd518380f
 ---> a7f5e3006000
Step 6/8 : COPY . /usr/src/app
 ---> fdda478a4955
Step 7/8 : EXPOSE 8080
 ---> Running in 5569a53ae866
Removing intermediate container 5569a53ae866
 ---> 9fc0907cc380
Step 8/8 : CMD [ "node", "server.js" ]
 ---> Running in c3b88004ec36
Removing intermediate container c3b88004ec36
 ---> 57513ee25141
Successfully built 57513ee25141
Successfully tagged service-a:latest
HL00294:service-a kajihiroyuki$ ecs-cli push service-a
INFO[0000] Getting AWS account ID...                    
INFO[0000] Tagging image                                 image=service-a repository=000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/service-a tag=
INFO[0000] Image tagged                                 
INFO[0001] Creating repository                           repository=service-a
INFO[0001] Repository created                           
INFO[0001] Pushing image                                 repository=000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/service-a tag=
INFO[0013] Image pushed                                 

リポジトリのURLを取得

export REGISTRY_URL_SERVICE_B=$(aws ecr describe-repositories --repository-name service-b | jq -r '.repositories[].repositoryUri')
export REGISTRY_URL_SERVICE_A=$(aws ecr describe-repositories --repository-name service-a | jq -r '.repositories[].repositoryUri')

各サービス用のCloudwatch Logsのロググループを作成

aws logs create-log-group --log-group-name /ecs/service-b
aws logs create-log-group --log-group-name /ecs/service-a

Service Bを構築

東京リージョンで実行する場合は、docker-compose.yml内のリージョンをus-east-1から、ap-northeast-1へ修正が必要です。 東京リージョンで構築していると、us-east-1のCloudwatch Logsに投げられないので、タスクの起動停止を繰り返します。

cd ../service-b/
envsubst < docker-compose.yml-template > docker-compose.yml
#  docker-compose.yml内のリージョンをus-east-1から、ap-northeast-1へ修正が必要です。
envsubst < ecs-params.yml-template > ecs-params.yml
ecs-cli compose service up --deployment-max-percent 100 --deployment-min-healthy-percent 0 --target-group-arn $TARGET_GROUP_ARN --launch-type FARGATE --container-name service-b --container-port 8080 --cluster xray-fargate

実行結果 docker-compose.yml内の修正が漏れており、2回失敗しているので、service-b:3になっています。1回でできていれば、1になります。

$ ecs-cli compose service up --deployment-max-percent 100 --deployment-min-healthy-percent 0 --target-group-arn $TARGET_GROUP_ARN --launch-type FARGATE --container-name service-b --container-port 8080 --cluster xray-fargate
WARN[0000] Environment variable is unresolved. Setting it to a blank value...  key name=AWS_XRAY_DAEMON_ADDRESS
WARN[0000] Environment variable is unresolved. Setting it to a blank value...  key name=AWS_XRAY_DAEMON_ADDRESS
INFO[0011] Using ECS task definition                     TaskDefinition="service-b:3"
INFO[0011] Created an ECS service                        deployment-max-percent=100 deployment-min-healthy-percent=0 service=service-b taskDefinition="service-b:3"
INFO[0012] Updated ECS service successfully              deployment-max-percent=100 deployment-min-healthy-percent=0 desiredCount=1 force-deployment=false service=service-b
INFO[0027] (service service-b) has started 1 tasks: (task 5e5cdf10-81c5-47f3-bb0a-d3d952bcdfe6).  timestamp="2019-09-05 10:07:19 +0000 UTC"
INFO[0072] Service status                                desiredCount=1 runningCount=1 serviceName=service-b
INFO[0072] ECS Service has reached a stable state        desiredCount=1 runningCount=1 serviceName=service-b

Service A用のALBを構築

私は以下のように変更して実行しました。 <load_balancer_name>=xray-fargate-a-alb <target_group_name>=xray-fargate-a-tg

export LOAD_BALANCER_ARN=$(aws elbv2 create-load-balancer --name xray-fargate-a-alb --subnets $SUBNET_ID_1 $SUBNET_ID_2 --security-groups $SG_ID --scheme internet-facing --type application | jq -r '.LoadBalancers[].LoadBalancerArn')
export TARGET_GROUP_ARN=$(aws elbv2 create-target-group --name xray-fargate-a-tg --protocol HTTP --port 8080 --vpc-id vpc-00000000 --target-type ip --health-check-path /health | jq -r '.TargetGroups[].TargetGroupArn')
aws elbv2 create-listener --load-balancer-arn $LOAD_BALANCER_ARN --protocol HTTP --port 80 --default-actions Type=forward,TargetGroupArn=$TARGET_GROUP_ARN

Service AのFargateを構築

Service Bと同様に、東京リージョンで実行する場合は、docker-compose.yml内のリージョンをus-east-1から、ap-northeast-1へ修正が必要です。

cd ./service-a/
envsubst < docker-compose.yml-template > docker-compose.yml
## Service Bと同様に、東京リージョンで実行する場合は、docker-compose.yml内のリージョンをus-east-1から、ap-northeast-1へ修正が必要です。
envsubst < ecs-params.yml-template > ecs-params.yml
ecs-cli compose service up --deployment-max-percent 100 --deployment-min-healthy-percent 0 --target-group-arn $TARGET_GROUP_ARN --launch-type FARGATE --container-name service-a --container-port 8080 --cluster xray-fargate

実行結果

$ ecs-cli compose service up --deployment-max-percent 100 --deployment-min-healthy-percent 0 --target-group-arn $TARGET_GROUP_ARN --launch-type FARGATE --container-name service-a --container-port 8080 --cluster xray-fargate
WARN[0000] Environment variable is unresolved. Setting it to a blank value...  key name=AWS_XRAY_DAEMON_ADDRESS
WARN[0000] Environment variable is unresolved. Setting it to a blank value...  key name=AWS_XRAY_DAEMON_ADDRESS
INFO[0000] Using ECS task definition                     TaskDefinition="service-a:3"
INFO[0001] Created an ECS service                        deployment-max-percent=100 deployment-min-healthy-percent=0 service=service-a taskDefinition="service-a:3"
INFO[0002] Updated ECS service successfully              deployment-max-percent=100 deployment-min-healthy-percent=0 desiredCount=1 force-deployment=false service=service-a
INFO[0017] (service service-a) has reached a steady state.  timestamp="2019-09-05 10:11:41 +0000 UTC"
INFO[0047] (service service-a) has started 1 tasks: (task 9e827be2-47ad-4014-b5c6-411e7687b2ca).  timestamp="2019-09-05 10:12:14 +0000 UTC"
INFO[0078] Service status                                desiredCount=1 runningCount=1 serviceName=service-a
INFO[0078] ECS Service has reached a stable state        desiredCount=1 runningCount=1 serviceName=service-a

これで構築が完了します。

X-Rayのコンソールで確認

AWS X-Rayのコンソールを確認しますが、タスク分しかサービスマップに表示されません。

ApacheBenchでService AのALBへリクエスト送付

以下のコマンド

ab -n 100 -c 10 http://xray-fargate-a-alb-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com/

少し待つと、意図したサービス間の遅延が確認できるX-Rayのサービスマップが表示されました。

外部からのリクエストのレスポンスをTraceの詳細で確認できます。

まとめ

X-RayをFargateで構築する場合にどのようにすると良いのか学ぶことができました。 また、ecs-cliを初めて使い、ちょっとハマりましたが、試すことができました。 どなたかのお役に立てれば光栄です。

(補足)スイッチロール先のAWSアカウントでecs-cliを実行するときに便利なスクリプト

ecs-cliでスイッチロール先のAWSアカウントで実行するときに私の環境下ではうまくできませんでした。 そのため、Security Tokenを発行してecs-cliのプロファイルに登録してくれる以下のスクリプトを使って、ecs-cliを実行しました。

https://github.com/dimisjim/bash-scripts/blob/master/AWS/ecs-cli-auth.sh