🚥

CloudWatch Application SignalsでAPM

2024/01/19に公開

CloudWatchを中心にAWSのObservabilityソリューションを幅広く学ぶことができるOne Observability WorkshopApplication 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でした。

このエラーの詳細はこちらを見ていただければと思います。
https://zenn.dev/p0n/articles/96dcf916a11507

Cloud9環境のセットアップを完了して次ページに進むと、「Cloud9 IAM権限の設定」に続いていて、この流れで進めてしまうとPetClinicではなくPetSiteアプリケーションをデプロイしてしまうので注意してください。

必須かどうかわからないのですが、自分はCloud9の一時的な認証情報の削除とAWS CLIのリージョンの設定まで行いました。推測になりますが、後からカスタムリソースでアタッチしたインスタンスプロファイルに含まれるインスタンスロールにもとづいたクレデンシャルを使用するよう、一旦現在のものを削除しているのかもしれません。

PetClinicのセットアップ

先ほどセットアップしたCloud9のターミナルで、デモアプリケーションのPetClinicをセットアップしていきます。いきなりですが、ワークショップの日本語の手順には抜けている手順があり、手順通りにやっても動きません。英語では正しい手順になっているので、翻訳時の見落としで、そのうち修正されるのではないかと思います。 修正されたようです。

  1. ソース等をGitHubのリポジトリからクローンします。

    git clone https://github.com/aws-observability/application-signals-demo.git
    
  2. EKSのデモを使用するので、EKS用のスクリプトディレクトリに移動します。

    cd application-signals-demo/scripts/eks/appsignals
    
  3. 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
    
  4. 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 ACTIVE
    

    CloudWatch Observability EKSアドオンのインストールとテレメトリをCloudWatchに送信するためのアクセス許可の設定が行われます。用意されたスクリプトでは、CloudWatchAgentServerPolicyポリシーとAWSXrayWriteOnlyAccessポリシーがアタッチされたIAMサービスアカウントロールを作成し、テレメトリの送信に利用していました。ワーカーノードのインスタンスロールを使用するやり方もあり、公式ドキュメントで説明されています。

  5. 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
    
  6. 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   37m
    

    deploy-sample-app.shの出力の最後に書かれているURLにアクセスしてみましょう。
    PetClinic

    スクショ撮り忘れてSLOの設定済みの画面になってますが、この時点で自動的にServiceが検出されています。
    service-dashboard

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のデプロイをやってしまった名残です。
Canaries

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が設定されていることがわかります。
PetClinicFrontEndSlo1
PetClinicFrontEndSlo2

マネコン等から追加のSLI/SLOを作成することも可能です。SLIのタイプにはサービスオペレーションとCloudWatchメトリクスが選択できます。サービスオペレーションを選択すると、検出されたサービスのエンドポイントについて可用性とレイテンシをSLIとして定義できます。CloudWatchメトリクスを選択すると、CloudWatchに送信される様々なメトリクスを用いてSLIを定義することができます。CloudWatchにメトリクスを集約しておけば、よりビジネスKPIとリンクしたSLI/SLOへ改善していくこともできそうです。

SLOの測定間隔は、カレンダーベースとローリングウィンドウが選択できるようになっています。従来はCloudWatch職人でもローリングウィンドウでのSLO監視は難しかったと思うので、これはうれしい。

一方アラームはちょっと残念で、エラーバジェットの残量が指定した閾値を切った時にアラームを送信することはできそうですが、バーンレートに応じたアラームはサポートされていないようです。残量だけだとアラートが送信された時にはすでに遅いって場合があるので、ぜひ正式リリースではサポートして欲しいです。
SloAlarms

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

Service Map

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

おわりに

まだプレビューですが、基本的なサービスレベルの管理がAWSだけで完結できるようになりそうで正式リリースが楽しみです。
ワークショップを楽しんだあとのお掃除はくれぐれもお忘れなく。Retainになってるリソース(S3バケット等)もあるので、よく確認して掃除漏れのないようにしましょう。

最後に、クリーンアップの時に気づかれたかもしれませんが、実はPetClinicのデプロイからSLOの作成までを一発でやれるone-stepというスクリプトがあります。PetClinicはEKSを使うので少々お高くつくのでずっと動かし続けるのは難しいと思いますが、Application Signalsの設定の流れを把握した後、またPetClinicを動かしてみたいという時はone-stepが便利だと思います。

Discussion