CloudWatch Application SignalsでAPM
CloudWatchを中心にAWSのObservabilityソリューションを幅広く学ぶことができるOne Observability WorkshopにApplication Signalsのワークショップが追加されていたのでやってみました。
ワークショップの概要
「Application SignalsによるApplication Performance Monitoring」と銘打っていて、PetClinicというアプリケーションをデプロイして、自動計装で収集されたメトリクスやトレース、Synthetics Canaryの情報、そして目玉のサービスレベルを統合的に把握できることを体験するものとなっています。
One Observability Workshopの大部分はPetSiteというアプリケーションを題材としていますが、Application SignalsのワークショップではPetClinicという別のアプリケーションを利用します。このPetClinicというアプリケーションはApplication Signals公式ドキュメントでもサンプルとして言及されているものでGitHubで公開されています。PetClinicはEKSとEC2の両方のデモを提供していますが、ワークショップではEKSのデモが対象になっています。
リージョンの確認
CloudWatch Application Signalsはまだパブリックプレビューのサービスであり、使用できるリージョンがかなり限られています。公式アナウンスによると、米国東部 (バージニア北部)、米国東部 (オハイオ)、欧州 (アイルランド)、アジアパシフィック (シドニー)、アジアパシフィック (東京) で利用可能です。
詳細は次の「Cloud9のセットアップ」で後述しますが、私が普段使用しているオレゴンが珍しく対象外となっていて、思わぬ時間ロスにつながってしまったので、利用可能なリージョンはよく確認しましょう。
Cloud9環境のセットアップ
PetClinicのREADMEやApplication Signalsの公式ドキュメントはローカルのCLI等を用いてセットアップすることを念頭においているようですが、ワークショップはCloud9を使用することを推奨しています。日頃の開発で使うかどうかはさておき、Cloud9はGameDayでも頻繁に使用するサービスなので慣れておいて損はないサービスなので、Cloud9を利用して進めることにしました。
といっても、Cloud9のセットアップはCloudFormationのテンプレートが提供されているので、スタックを作成するだけです。ワークショップのサイトの手順ではCloudShellからCloudFormationのスタックを作成していますが、テンプレートはGitHubで公開されているのでマネコンでもCLIでも好きな方法でどうぞ。
本題からそれますが、実はこのCFnテンプレートが結構興味深い。
- カスタムリソースを使用してCloud9で使用するEC2インスタンスにインスタンスプロファイルを適用している。
- EC2インスタンスの初期設定をSSMでやってる。
- インスタンスタイプがt3.small, t3.medium, m5.largeから選択できる(デフォルトはt3.medium)
- VPCを新規作成するか既存のVPCを利用するか選択できる(デフォルトは新規作成)
- ボリュームサイズを指定できる(デフォルトは30GB)
- パッチの適用是非(デフォルトは非適用)※OSはUbuntu 22.04
結構凝った作りかと思いきや、Cloud9のEC2インスタンスロールにAdministratorAccessをアタッチしてたりw
 ManagedPolicyArns:
      - !Sub arn:${AWS::Partition}:iam::aws:policy/AdministratorAccess  #Pending least privilege update
      Path: "/"
中でも、AWS::Cloud9::EnvironmentEC2は素のEC2インスタンスのようにIamInstanceProfileでインスタンスプロファイルを指定できないので、カスタムリソースを使用してLambdaでIAMインスタンスプロファイルを適用しているのだと思いますが、とても勉強になります。
ただ、このLambdaが原因でセットアップに失敗したんですよね…。予約済同時実行数が指定されていて、わずか1なのですが、非予約済同時実行用の同時実行数の最小値を下回ったためLambda関数の作成に失敗していました。
普段使いのリージョンであれば同時実行数のクォータも余裕がある値になっているのではないかと思いますが、東京リージョンを使わない私の場合、クォータはわずか10でした。
このエラーの詳細はこちらを見ていただければと思います。
Cloud9環境のセットアップを完了して次ページに進むと、「Cloud9 IAM権限の設定」に続いていて、この流れで進めてしまうとPetClinicではなくPetSiteアプリケーションをデプロイしてしまうので注意してください。
必須かどうかわからないのですが、自分はCloud9の一時的な認証情報の削除とAWS CLIのリージョンの設定まで行いました。推測になりますが、後からカスタムリソースでアタッチしたインスタンスプロファイルに含まれるインスタンスロールにもとづいたクレデンシャルを使用するよう、一旦現在のものを削除しているのかもしれません。
PetClinicのセットアップ
先ほどセットアップしたCloud9のターミナルで、デモアプリケーションのPetClinicをセットアップしていきます。いきなりですが、ワークショップの日本語の手順には抜けている手順があり、手順通りにやっても動きません。英語では正しい手順になっているので、翻訳時の見落としで、そのうち修正されるのではないかと思います。 修正されたようです。
- 
ソース等をGitHubのリポジトリからクローンします。 git clone https://github.com/aws-observability/application-signals-demo.git
- 
EKSのデモを使用するので、EKS用のスクリプトディレクトリに移動します。 cd application-signals-demo/scripts/eks/appsignals
- 
EKSクラスタを作成します。 REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r '.region') echo $REGION CLUSTER_NAME="PetClinic" #お好みで ./create-cluster.sh $CLUSTER_NAME $REGION
- 
Amazon CloudWatch Observability EKSアドオンをセットアップします。 ./enable-app-signals.sh $CLUSTER_NAME $REGION作成されるサービスリンクロール{ "Role": { "Path": "/aws-service-role/application-signals.cloudwatch.amazonaws.com/", "RoleName": "AWSServiceRoleForCloudWatchApplicationSignals", "RoleId": "***", "Arn": "arn:aws:iam::***:role/aws-service-role/application-signals.cloudwatch.amazonaws.com/AWSServiceRoleForCloudWatchApplicationSignals", "CreateDate": "2024-01-04T06:02:29+00:00", "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" ], "Effect": "Allow", "Principal": { "Service": [ "application-signals.cloudwatch.amazonaws.com" ] } } ] } } }出力2024-01-04 06:03:34 [ℹ] will create IAM Open ID Connect provider for cluster "PetClinic" in "ap-northeast-1" 2024-01-04 06:03:35 [✔] created IAM Open ID Connect provider for cluster "PetClinic" in "ap-northeast-1" Creating ServiceAccount 2024-01-04 06:03:36 [ℹ] 1 iamserviceaccount (amazon-cloudwatch/cloudwatch-agent) was included (based on the include/exclude rules) 2024-01-04 06:03:36 [!] metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set 2024-01-04 06:03:36 [ℹ] 1 task: { 2 sequential sub-tasks: { create IAM role for serviceaccount "amazon-cloudwatch/cloudwatch-agent", create serviceaccount "amazon-cloudwatch/cloudwatch-agent", } }2024-01-04 06:03:36 [ℹ] building iamserviceaccount stack "eksctl-PetClinic-addon-iamserviceaccount-amazon-cloudwatch-cloudwatch-agent" 2024-01-04 06:03:36 [ℹ] deploying stack "eksctl-PetClinic-addon-iamserviceaccount-amazon-cloudwatch-cloudwatch-agent" 2024-01-04 06:03:36 [ℹ] waiting for CloudFormation stack "eksctl-PetClinic-addon-iamserviceaccount-amazon-cloudwatch-cloudwatch-agent" 2024-01-04 06:04:06 [ℹ] waiting for CloudFormation stack "eksctl-PetClinic-addon-iamserviceaccount-amazon-cloudwatch-cloudwatch-agent" 2024-01-04 06:04:06 [ℹ] created namespace "amazon-cloudwatch" 2024-01-04 06:04:06 [ℹ] created serviceaccount "amazon-cloudwatch/cloudwatch-agent" Checking amazon-cloudwatch-observability add-on An error occurred (ResourceNotFoundException) when calling the DescribeAddon operation: No addon: amazon-cloudwatch-observability found in cluster: PetClinic Installing amazon-cloudwatch-observability add-on { "addon": { "addonName": "amazon-cloudwatch-observability", "clusterName": "PetClinic", "status": "CREATING", "addonVersion": "v1.2.0-eksbuild.1", "health": { "issues": [] }, "addonArn": "arn:aws:eks:ap-northeast-1:***:addon/PetClinic/amazon-cloudwatch-observability/***", "createdAt": "2024-01-04T06:04:09.304000+00:00", "modifiedAt": "2024-01-04T06:04:09.327000+00:00", "tags": {} } } Current status: CREATING Waiting for addon to become ACTIVE... EKS amazon-cloudwatch-observability add-on is now ACTIVECloudWatch Observability EKSアドオンのインストールとテレメトリをCloudWatchに送信するためのアクセス許可の設定が行われます。用意されたスクリプトでは、CloudWatchAgentServerPolicyポリシーとAWSXrayWriteOnlyAccessポリシーがアタッチされたIAMサービスアカウントロールを作成し、テレメトリの送信に利用していました。ワーカーノードのインスタンスロールを使用するやり方もあり、公式ドキュメントで説明されています。 
- 
PetClinicアプリをビルドしてイメージをECRにPUSHします。 日本語の手順ではこのステップが抜けています。ここをスキップしてしまうと、ECRにイメージが登録されないので、イメージがPULLできずPodを起動できません。作業中、イメージをどこからとってくるのか気になってはいたのですが、どこかの公開リポジトリからPULLするんだろうと思って進めていたら、ただの手順漏れでした。修正されました。cd ~/environment/application-signals-demo/ ./mvnw clean install -P buildDocker export ACCOUNT=$(aws sts get-caller-identity --output text --query Account) export REGION=$REGION ./push-ecr.sh
- 
PetClinicアプリケーションをEKSクラスタにデプロイします。 cd scripts/eks/appsignals ./deploy-sample-app.sh $CLUSTER_NAME $REGION出力Creating ServiceAccount 2024-01-04 06:06:07 [ℹ] 1 existing iamserviceaccount(s) (amazon-cloudwatch/cloudwatch-agent) will be excluded 2024-01-04 06:06:07 [ℹ] 1 iamserviceaccount (default/visits-service-account) was included (based on the include/exclude rules) 2024-01-04 06:06:07 [!] metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set 2024-01-04 06:06:07 [ℹ] 1 task: { 2 sequential sub-tasks: { create IAM role for serviceaccount "default/visits-service-account", create serviceaccount "default/visits-service-account", } }2024-01-04 06:06:07 [ℹ] building iamserviceaccount stack "eksctl-PetClinic-addon-iamserviceaccount-default-visits-service-account" 2024-01-04 06:06:08 [ℹ] deploying stack "eksctl-PetClinic-addon-iamserviceaccount-default-visits-service-account" 2024-01-04 06:06:08 [ℹ] waiting for CloudFormation stack "eksctl-PetClinic-addon-iamserviceaccount-default-visits-service-account" 2024-01-04 06:06:38 [ℹ] waiting for CloudFormation stack "eksctl-PetClinic-addon-iamserviceaccount-default-visits-service-account" 2024-01-04 06:06:38 [ℹ] created serviceaccount "default/visits-service-account" deployment.apps/admin-server created service/admin-server created deployment.apps/pet-clinic-frontend created service/pet-clinic-frontend created deployment.apps/config-server created service/config-server created deployment.apps/customers-service created service/customers-service created deployment.apps/discovery-server created service/discovery-server created deployment.apps/vets-service created service/vets-service created deployment.apps/visits-service created service/visits-service created namespace/ingress-nginx created namespace/ingress-nginx unchanged service/ingress-nginx created deployment.apps/default-http-backend created service/default-http-backend created configmap/nginx-configuration created configmap/tcp-services created configmap/udp-services created serviceaccount/nginx-ingress-serviceaccount created clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created role.rbac.authorization.k8s.io/nginx-ingress-role created rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created deployment.apps/nginx-ingress-controller created Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead ingress.networking.k8s.io/petclinic-nginx-ingress created Visit the following URL to see the sample app running: ***-1220759577.ap-northeast-1.elb.amazonaws.comアプリが起動するとこんな感じになります。 $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE admin-server ClusterIP 10.100.222.85 <none> 9090/TCP 38m config-server ClusterIP 10.100.208.58 <none> 8888/TCP 38m customers-service ClusterIP 10.100.233.223 <none> 8081/TCP 38m discovery-server ClusterIP 10.100.231.55 <none> 8761/TCP 38m kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 52m pet-clinic-frontend ClusterIP 10.100.242.241 <none> 8080/TCP 38m vets-service ClusterIP 10.100.158.153 <none> 8083/TCP 38m visits-service ClusterIP 10.100.70.223 <none> 8082/TCP 37mdeploy-sample-app.shの出力の最後に書かれているURLにアクセスしてみましょう。
  スクショ撮り忘れてSLOの設定済みの画面になってますが、この時点で自動的にServiceが検出されています。 
  
Synthetics Canariesの設定
./create-canaries.sh $REGION
出力
REGION is ap-northeast-1
OPERATION is create
ACCOUNT_ID is ***
Setting up the canary execution role
Start creating IAM role CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1
{
    "Role": {
        "Path": "/service-role/",
        "RoleName": "CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
        "RoleId": "***",
        "Arn": "arn:aws:iam::***:role/service-role/CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
        "CreateDate": "2024-01-04T06:10:50+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
creating IAM policy
{
    "Policy": {
        "PolicyName": "CloudWatchSyntheticsPolicy-PetClinic-***-ap-northeast-1",
        "PolicyId": "ANPAVJSWBDW7VDOIXNMOV",
        "Arn": "arn:aws:iam::***:policy/service-role/CloudWatchSyntheticsPolicy-PetClinic-***-ap-northeast-1",
        "Path": "/service-role/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2024-01-04T06:10:53+00:00",
        "UpdateDate": "2024-01-04T06:10:53+00:00"
    }
}
Setting up S3 code buckets
Creating S3 code bucket aws-synthetics-code-petclinic-***-ap-northeast-1
make_bucket: aws-synthetics-code-petclinic-***-ap-northeast-1
Creating S3 artifact bucket cw-syn-results-petclinic-***-ap-northeast-1
make_bucket: cw-syn-results-petclinic-***-ap-northeast-1
  adding: nodejs/ (stored 0%)
  adding: nodejs/node_modules/ (stored 0%)
  adding: nodejs/node_modules/pc-add-visit.js (deflated 69%)
Uploading canary script for canary pc-add-visit to S3
{
  "ETag": "\"fd279fffcf1cd4e2d339177a1ca8c538\"",
  "ServerSideEncryption": "AES256"
}
ENDPOINT is http://***-1220759577.ap-northeast-1.elb.amazonaws.com
Creating/updating canary pc-add-visit
{
  "Canary": {
    "Id": "***",
    "Name": "pc-add-visit",
    "Code": {
      "Handler": "pc-add-visit.handler"
    },
    "ExecutionRoleArn": "arn:aws:iam::***:role/service-role/CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
    "Schedule": {
      "Expression": "rate(1 minute)",
      "DurationInSeconds": 0
    },
    "RunConfig": {
      "TimeoutInSeconds": 60,
      "MemoryInMB": 1000,
      "ActiveTracing": true
    },
    "SuccessRetentionPeriodInDays": 31,
    "FailureRetentionPeriodInDays": 31,
    "Status": {
      "State": "CREATING",
      "StateReasonCode": "CREATE_PENDING"
    },
    "Timeline": {
      "Created": "2024-01-04T06:11:09.913000+00:00",
      "LastModified": "2024-01-04T06:11:09.913000+00:00"
    },
    "ArtifactS3Location": "cw-syn-results-petclinic-***-ap-northeast-1",
    "RuntimeVersion": "syn-nodejs-puppeteer-4.0",
    "Tags": {}
  }
}
  adding: nodejs/ (stored 0%)
  adding: nodejs/node_modules/ (stored 0%)
  adding: nodejs/node_modules/pc-create-owners.js (deflated 81%)
Uploading canary script for canary pc-create-owners to S3
{
  "ETag": "\"637656eef2095facea04d4e48976b481\"",
  "ServerSideEncryption": "AES256"
}
ENDPOINT is http://***.ap-northeast-1.elb.amazonaws.com
Creating/updating canary pc-create-owners
{
  "Canary": {
    "Id": "***",
    "Name": "pc-create-owners",
    "Code": {
      "Handler": "pc-create-owners.handler"
    },
    "ExecutionRoleArn": "arn:aws:iam::***:role/service-role/CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
    "Schedule": {
      "Expression": "rate(1 minute)",
      "DurationInSeconds": 0
    },
    "RunConfig": {
      "TimeoutInSeconds": 60,
      "MemoryInMB": 1000,
      "ActiveTracing": true
    },
    "SuccessRetentionPeriodInDays": 31,
    "FailureRetentionPeriodInDays": 31,
    "Status": {
      "State": "CREATING",
      "StateReasonCode": "CREATE_PENDING"
    },
    "Timeline": {
      "Created": "2024-01-04T06:11:16.892000+00:00",
      "LastModified": "2024-01-04T06:11:16.892000+00:00"
    },
    "ArtifactS3Location": "cw-syn-results-petclinic-***-ap-northeast-1",
    "RuntimeVersion": "syn-nodejs-puppeteer-4.0",
    "Tags": {}
  }
}
  adding: nodejs/ (stored 0%)
  adding: nodejs/node_modules/ (stored 0%)
  adding: nodejs/node_modules/pc-visit-pet.js (deflated 59%)
Uploading canary script for canary pc-visit-pet to S3
{
  "ETag": "\"4d7b629cf75d2afd9b7ebe54bc65bfab\"",
  "ServerSideEncryption": "AES256"
}
ENDPOINT is http://***.ap-northeast-1.elb.amazonaws.com
Creating/updating canary pc-visit-pet
{
  "Canary": {
    "Id": "***",
    "Name": "pc-visit-pet",
    "Code": {
      "Handler": "pc-visit-pet.handler"
    },
    "ExecutionRoleArn": "arn:aws:iam::***:role/service-role/CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
    "Schedule": {
      "Expression": "rate(1 minute)",
      "DurationInSeconds": 0
    },
    "RunConfig": {
      "TimeoutInSeconds": 60,
      "MemoryInMB": 1000,
      "ActiveTracing": true
    },
    "SuccessRetentionPeriodInDays": 31,
    "FailureRetentionPeriodInDays": 31,
    "Status": {
      "State": "CREATING",
      "StateReasonCode": "CREATE_PENDING"
    },
    "Timeline": {
      "Created": "2024-01-04T06:11:23.841000+00:00",
      "LastModified": "2024-01-04T06:11:23.841000+00:00"
    },
    "ArtifactS3Location": "cw-syn-results-petclinic-***-ap-northeast-1",
    "RuntimeVersion": "syn-nodejs-puppeteer-4.0",
    "Tags": {}
  }
}
  adding: nodejs/ (stored 0%)
  adding: nodejs/node_modules/ (stored 0%)
  adding: nodejs/node_modules/pc-visit-vet.js (deflated 55%)
Uploading canary script for canary pc-visit-vet to S3
{
  "ETag": "\"c9a6a3643cc77119452769c6a28c20eb\"",
  "ServerSideEncryption": "AES256"
}
ENDPOINT is http:/***.ap-northeast-1.elb.amazonaws.com
Creating/updating canary pc-visit-vet
{
  "Canary": {
    "Id": "***",
    "Name": "pc-visit-vet",
    "Code": {
      "Handler": "pc-visit-vet.handler"
    },
    "ExecutionRoleArn": "arn:aws:iam::***:role/service-role/CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
    "Schedule": {
      "Expression": "rate(1 minute)",
      "DurationInSeconds": 0
    },
    "RunConfig": {
      "TimeoutInSeconds": 60,
      "MemoryInMB": 1000,
      "ActiveTracing": true
    },
    "SuccessRetentionPeriodInDays": 31,
    "FailureRetentionPeriodInDays": 31,
    "Status": {
      "State": "CREATING",
      "StateReasonCode": "CREATE_PENDING"
    },
    "Timeline": {
      "Created": "2024-01-04T06:11:30.793000+00:00",
      "LastModified": "2024-01-04T06:11:30.793000+00:00"
    },
    "ArtifactS3Location": "cw-syn-results-petclinic-***-ap-northeast-1",
    "RuntimeVersion": "syn-nodejs-puppeteer-4.0",
    "Tags": {}
  }
}
  adding: nodejs/ (stored 0%)
  adding: nodejs/node_modules/ (stored 0%)
  adding: nodejs/node_modules/pet-clinic-traffic.js (deflated 64%)
Uploading canary script for canary pet-clinic-traffic to S3
{
  "ETag": "\"4a83189520480b3bd4c4e001faf33dae\"",
  "ServerSideEncryption": "AES256"
}
ENDPOINT is http://***.ap-northeast-1.elb.amazonaws.com
Creating/updating canary pet-clinic-traffic
{
  "Canary": {
    "Id": "***",
    "Name": "pet-clinic-traffic",
    "Code": {
      "Handler": "pet-clinic-traffic.handler"
    },
    "ExecutionRoleArn": "arn:aws:iam::***:role/service-role/CloudWatchSyntheticsRole-PetClinic-***-ap-northeast-1",
    "Schedule": {
      "Expression": "rate(1 minute)",
      "DurationInSeconds": 0
    },
    "RunConfig": {
      "TimeoutInSeconds": 60,
      "MemoryInMB": 1000,
      "ActiveTracing": true
    },
    "SuccessRetentionPeriodInDays": 31,
    "FailureRetentionPeriodInDays": 31,
    "Status": {
      "State": "CREATING",
      "StateReasonCode": "CREATE_PENDING"
    },
    "Timeline": {
      "Created": "2024-01-04T06:11:37.631000+00:00",
      "LastModified": "2024-01-04T06:11:37.631000+00:00"
    },
    "ArtifactS3Location": "cw-syn-results-petclinic-***-ap-northeast-1",
    "RuntimeVersion": "syn-nodejs-puppeteer-4.0",
    "Tags": {}
  }
}
Waiting a minute for canaries to finish creating or updating.
Done waiting. Starting canaries.
++ aws synthetics get-canary --name pc-add-visit --region ap-northeast-1
++ set +x
Starting canary pc-add-visit
++ aws synthetics get-canary --name pc-create-owners --region ap-northeast-1
++ set +x
Starting canary pc-create-owners
++ aws synthetics get-canary --name pc-visit-pet --region ap-northeast-1
++ set +x
Starting canary pc-visit-pet
++ aws synthetics get-canary --name pc-visit-vet --region ap-northeast-1
++ set +x
Starting canary pc-visit-vet
++ aws synthetics get-canary --name pet-clinic-traffic --region ap-northeast-1
++ set +x
Starting canary pet-clinic-traffic
このスクリプトでは5つのCanaryが作成されます。本ワークショップではサービスレベルの評価のために、これらのCanaryで継続的にトラフィックを発生させます。Successの値が低いのは、コンテナのイメージをECRにPUSHする前にPodのデプロイをやってしまった名残です。

SLOの設定
./create-slo.sh $CLUSTER_NAME $REGION
出力
Wait 10 minutes for canaries to generate traffic
Creating Service Level Objectives
Reference_Id
SERVICE_ARN_THROUGH_ATTRIBUTES_SET
SERVICE_ARN_THROUGH_ATTRIBUTES
arn:aws:cloudwatch:ap-northeast-1:***:service/pet-clinic-frontend-e3b4f131441c918bfc74636c2397868b3604cf0ac51ee475393fb70979408059
arn:aws:cloudwatch:ap-northeast-1:***:service/pet-clinic-frontend-e3b4f131441c918bfc74636c2397868b3604cf0ac51ee475393fb70979408059
{
    "Slo": {
        "Arn": "arn:aws:cloudwatch:ap-northeast-1:***:slo/Availability for Searching an Owner",
        "Name": "Availability for Searching an Owner",
        "Description": "Availability larger than 99 for Get Owner operation",
        "CreatedTime": "2024-01-04T06:51:46.812000+00:00",
        "LastUpdatedTime": "2024-01-04T06:51:46.812000+00:00",
        "Sli": {
            "StandardMetrics": {
                "ServiceId": "arn:aws:cloudwatch:ap-northeast-1:***:service/pet-clinic-frontend-e3b4f131441c918bfc74636c2397868b3604cf0ac51ee475393fb70979408059",
                "OperationName": "GET /api/customer/owners",
                "MetricName": "AVAILABILITY",
                "PeriodSeconds": 60,
                "Metrics": [
                    {
                        "Id": "availability",
                        "Expression": "(1 - fault / volume) * 100",
                        "ReturnData": true,
                        "Period": 60
                    },
                    {
                        "Id": "fault",
                        "MetricStat": {
                            "Metric": {
                                "Namespace": "AppSignals",
                                "MetricName": "Fault",
                                "Dimensions": [
                                    {
                                        "Name": "Operation",
                                        "Value": "GET /api/customer/owners"
                                    },
                                    {
                                        "Name": "Service",
                                        "Value": "pet-clinic-frontend"
                                    },
                                    {
                                        "Name": "HostedIn.K8s.Namespace",
                                        "Value": "default"
                                    },
                                    {
                                        "Name": "HostedIn.EKS.Cluster",
                                        "Value": "PetClinic"
                                    }
                                ]
                            },
                            "Period": 60,
                            "Stat": "Sum"
                        },
                        "ReturnData": false
                    },
                    {
                        "Id": "volume",
                        "MetricStat": {
                            "Metric": {
                                "Namespace": "AppSignals",
                                "MetricName": "Latency",
                                "Dimensions": [
                                    {
                                        "Name": "Operation",
                                        "Value": "GET /api/customer/owners"
                                    },
                                    {
                                        "Name": "Service",
                                        "Value": "pet-clinic-frontend"
                                    },
                                    {
                                        "Name": "HostedIn.K8s.Namespace",
                                        "Value": "default"
                                    },
                                    {
                                        "Name": "HostedIn.EKS.Cluster",
                                        "Value": "PetClinic"
                                    }
                                ]
                            },
                            "Period": 60,
                            "Stat": "SampleCount"
                        },
                        "ReturnData": false
                    }
                ]
            },
            "MetricThreshold": 99.0,
            "ComparisonOperator": "GreaterThan"
        },
        "Goal": {
            "Interval": {
                "RollingInterval": {
                    "DurationUnit": "DAY",
                    "Duration": 1
                }
            },
            "AttainmentGoal": 99.9,
            "WarningThreshold": 60.0
        }
    }
}
{
    "Slo": {
        "Arn": "arn:aws:cloudwatch:ap-northeast-1:***:slo/Latency for Searching an Owner",
        "Name": "Latency for Searching an Owner",
        "Description": "Latency P99 less than 200 ms for Get Owner operation",
        "CreatedTime": "2024-01-04T06:51:48.267000+00:00",
        "LastUpdatedTime": "2024-01-04T06:51:48.267000+00:00",
        "Sli": {
            "StandardMetrics": {
                "ServiceId": "arn:aws:cloudwatch:ap-northeast-1:***:service/pet-clinic-frontend-e3b4f131441c918bfc74636c2397868b3604cf0ac51ee475393fb70979408059",
                "OperationName": "GET /api/customer/owners",
                "MetricName": "LATENCY",
                "Statistic": "p99",
                "PeriodSeconds": 60,
                "Metrics": [
                    {
                        "Id": "latency",
                        "MetricStat": {
                            "Metric": {
                                "Namespace": "AppSignals",
                                "MetricName": "Latency",
                                "Dimensions": [
                                    {
                                        "Name": "Operation",
                                        "Value": "GET /api/customer/owners"
                                    },
                                    {
                                        "Name": "Service",
                                        "Value": "pet-clinic-frontend"
                                    },
                                    {
                                        "Name": "HostedIn.K8s.Namespace",
                                        "Value": "default"
                                    },
                                    {
                                        "Name": "HostedIn.EKS.Cluster",
                                        "Value": "PetClinic"
                                    }
                                ]
                            },
                            "Period": 60,
                            "Stat": "p99",
                            "Unit": "Milliseconds"
                        },
                        "ReturnData": true
                    }
                ]
            },
            "MetricThreshold": 200.0,
            "ComparisonOperator": "LessThan"
        },
        "Goal": {
            "Interval": {
                "RollingInterval": {
                    "DurationUnit": "DAY",
                    "Duration": 1
                }
            },
            "AttainmentGoal": 99.9,
            "WarningThreshold": 60.0
        }
    }
}
{
    "Slo": {
        "Arn": "arn:aws:cloudwatch:ap-northeast-1:***:slo/Availability for Registering an Owner",
        "Name": "Availability for Registering an Owner",
        "Description": "Availability larger than 99 for Post Owner operation",
        "CreatedTime": "2024-01-04T06:51:49.496000+00:00",
        "LastUpdatedTime": "2024-01-04T06:51:49.496000+00:00",
        "Sli": {
            "StandardMetrics": {
                "ServiceId": "arn:aws:cloudwatch:ap-northeast-1:***:service/pet-clinic-frontend-e3b4f131441c918bfc74636c2397868b3604cf0ac51ee475393fb70979408059",
                "OperationName": "POST /api/customer/owners",
                "MetricName": "AVAILABILITY",
                "PeriodSeconds": 60,
                "Metrics": [
                    {
                        "Id": "availability",
                        "Expression": "(1 - fault / volume) * 100",
                        "ReturnData": true,
                        "Period": 60
                    },
                    {
                        "Id": "fault",
                        "MetricStat": {
                            "Metric": {
                                "Namespace": "AppSignals",
                                "MetricName": "Fault",
                                "Dimensions": [
                                    {
                                        "Name": "Operation",
                                        "Value": "POST /api/customer/owners"
                                    },
                                    {
                                        "Name": "Service",
                                        "Value": "pet-clinic-frontend"
                                    },
                                    {
                                        "Name": "HostedIn.K8s.Namespace",
                                        "Value": "default"
                                    },
                                    {
                                        "Name": "HostedIn.EKS.Cluster",
                                        "Value": "PetClinic"
                                    }
                                ]
                            },
                            "Period": 60,
                            "Stat": "Sum"
                        },
                        "ReturnData": false
                    },
                    {
                        "Id": "volume",
                        "MetricStat": {
                            "Metric": {
                                "Namespace": "AppSignals",
                                "MetricName": "Latency",
                                "Dimensions": [
                                    {
                                        "Name": "Operation",
                                        "Value": "POST /api/customer/owners"
                                    },
                                    {
                                        "Name": "Service",
                                        "Value": "pet-clinic-frontend"
                                    },
                                    {
                                        "Name": "HostedIn.K8s.Namespace",
                                        "Value": "default"
                                    },
                                    {
                                        "Name": "HostedIn.EKS.Cluster",
                                        "Value": "PetClinic"
                                    }
                                ]
                            },
                            "Period": 60,
                            "Stat": "SampleCount"
                        },
                        "ReturnData": false
                    }
                ]
            },
            "MetricThreshold": 99.0,
            "ComparisonOperator": "GreaterThan"
        },
        "Goal": {
            "Interval": {
                "RollingInterval": {
                    "DurationUnit": "DAY",
                    "Duration": 1
                }
            },
            "AttainmentGoal": 99.9,
            "WarningThreshold": 60.0
        }
    }
}
{
    "Slo": {
        "Arn": "arn:aws:cloudwatch:ap-northeast-1:***:slo/Latency for Registering an Owner",
        "Name": "Latency for Registering an Owner",
        "Description": "Latency P99 less than 2000 ms for Post Owner operation",
        "CreatedTime": "2024-01-04T06:51:50.874000+00:00",
        "LastUpdatedTime": "2024-01-04T06:51:50.874000+00:00",
        "Sli": {
            "StandardMetrics": {
                "ServiceId": "arn:aws:cloudwatch:ap-northeast-1:***:service/pet-clinic-frontend-e3b4f131441c918bfc74636c2397868b3604cf0ac51ee475393fb70979408059",
                "OperationName": "POST /api/customer/owners",
                "MetricName": "LATENCY",
                "Statistic": "p99",
                "PeriodSeconds": 60,
                "Metrics": [
                    {
                        "Id": "latency",
                        "MetricStat": {
                            "Metric": {
                                "Namespace": "AppSignals",
                                "MetricName": "Latency",
                                "Dimensions": [
                                    {
                                        "Name": "Operation",
                                        "Value": "POST /api/customer/owners"
                                    },
                                    {
                                        "Name": "Service",
                                        "Value": "pet-clinic-frontend"
                                    },
                                    {
                                        "Name": "HostedIn.K8s.Namespace",
                                        "Value": "default"
                                    },
                                    {
                                        "Name": "HostedIn.EKS.Cluster",
                                        "Value": "PetClinic"
                                    }
                                ]
                            },
                            "Period": 60,
                            "Stat": "p99",
                            "Unit": "Milliseconds"
                        },
                        "ReturnData": true
                    }
                ]
            },
            "MetricThreshold": 2000.0,
            "ComparisonOperator": "LessThan"
        },
        "Goal": {
            "Interval": {
                "RollingInterval": {
                    "DurationUnit": "DAY",
                    "Duration": 1
                }
            },
            "AttainmentGoal": 99.9,
            "WarningThreshold": 60.0
        }
    }
}
このスクリプトでは4つのSLOが作成されます。例えばpet-clinic-frontendというサービスの/api/customer/ownersエンドポイントに対して次のようなSLOが設定されていることがわかります。


マネコン等から追加のSLI/SLOを作成することも可能です。SLIのタイプにはサービスオペレーションとCloudWatchメトリクスが選択できます。サービスオペレーションを選択すると、検出されたサービスのエンドポイントについて可用性とレイテンシをSLIとして定義できます。CloudWatchメトリクスを選択すると、CloudWatchに送信される様々なメトリクスを用いてSLIを定義することができます。CloudWatchにメトリクスを集約しておけば、よりビジネスKPIとリンクしたSLI/SLOへ改善していくこともできそうです。
SLOの測定間隔は、カレンダーベースとローリングウィンドウが選択できるようになっています。従来はCloudWatch職人でもローリングウィンドウでのSLO監視は難しかったと思うので、これはうれしい。
一方アラームはちょっと残念で、エラーバジェットの残量が指定した閾値を切った時にアラームを送信することはできそうですが、バーンレートに応じたアラームはサポートされていないようです。残量だけだとアラートが送信された時にはすでに遅いって場合があるので、ぜひ正式リリースではサポートして欲しいです。

エラーバジェット自体はApp SignalsのAttainmentRateというメトリクスから算出されているので、CloudWatch職人ならMetric Mathを使ったりしてそれっぽいものは作れるかも。

Service Map
サービスマップからもSLOが定義できるようになっていました。せっかくなのでserviceを1個壊して見た画面です。

おわりに
まだプレビューですが、基本的なサービスレベルの管理がAWSだけで完結できるようになりそうで正式リリースが楽しみです。
ワークショップを楽しんだあとのお掃除はくれぐれもお忘れなく。Retainになってるリソース(S3バケット等)もあるので、よく確認して掃除漏れのないようにしましょう。
最後に、クリーンアップの時に気づかれたかもしれませんが、実はPetClinicのデプロイからSLOの作成までを一発でやれるone-stepというスクリプトがあります。PetClinicはEKSを使うので少々お高くつくのでずっと動かし続けるのは難しいと思いますが、Application Signalsの設定の流れを把握した後、またPetClinicを動かしてみたいという時はone-stepが便利だと思います。



Discussion