Multi-Cluster Multi-Account Multi-Region (M3) Observability¶
Architecture¶
The following figure illustrates the architecture of the pattern we will be deploying for Multi-Account Multi-Region Mixed Observability (M3) Accelerator using both AWS native tooling such as: CloudWatch ContainerInsights, CloudWatch logs and Open source tooling such as AWS Distro for Open Telemetry (ADOT), Amazon Managed Service for Prometheus (AMP), Amazon Managed Grafana :
Objective¶
- Deploying two production grade Amazon EKS cluster with control plane logging across two AWS Accounts (Prod1, Prod2 account) in two different regions through a Continuous Deployment infrastructure pipeline triggered upon a commit to the repository that holds the pipeline configuration in another AWS account (pipeline account).
- Deploying ADOT add-on, AMP add-on to Prod 1 Amazon EKS Cluster to remote-write metrics to AMP workspace in Prod 1 AWS Account.
- Deploying ADOT add-on, CloudWatch add-on to Prod 2 Amazon EKS Cluster to write metrics to CloudWatch in Prod 2 AWS Account.
- Configuring GitOps tooling (Argo CD add-on) to support deployment of ho11y and yelb sample applications, in a way that restricts each application to be deployed only into the team namespace, by using Argo CD projects.
- Setting up IAM roles in Prod 1 and Prod 2 Accounts to allow an AMG service role in the Monitoring account (mon-account) to access metrics from AMP workspace in Prod 1 account and CloudWatch namespace in Prod 2 account.
- Setting Amazon Managed Grafana to visualize AMP metrics from Amazon EKS cluster in Prod account 1 and CloudWatch metrics on workloads in Amazon EKS cluster in Prod account 2.
- Installing Grafana Operator in Monitoring account (mon-account) to add AWS data sources and create Grafana Dashboards to Amazon Managed Grafana.
- Installing External Secrets Operator in Monitoring account (mon-account) to retrieve and Sync the Grafana API keys.
GitOps configuration¶
- For GitOps, this pattern bootstraps Argo CD add-on and points to sample applications in AWS Observability Accelerator.
- You can find the team-geordie configuration for this pattern in the workload repository under the folder
team-geordie
. - GitOps based management of Amazon Grafana resources (like: Datasources and Dashboards) is achieved using Argo CD application
grafana-operator-app
. Grafana Operator resources are deployed usinggrafana-operator-chart
.
Prerequisites¶
Ensure following tools are installed in host machine:
AWS Accounts¶
- AWS Control Tower deployed in your AWS environment in the management account. If you have not already installed AWS Control Tower, follow the Getting Started with AWS Control Tower documentation, or you can enable AWS Organizations in the AWS Management Console account and enable AWS SSO.
- An AWS account under AWS Control Tower called Prod 1 Account(Workloads Account A aka
prodEnv1
) provisioned using the AWS Service Catalog Account Factory product AWS Control Tower Account vending process or AWS Organization. - An AWS account under AWS Control Tower called Prod 2 Account(Workloads Account B aka
prodEnv2
) provisioned using the AWS Service Catalog Account Factory] product AWS Control Tower Account vending process or AWS Organization. - An AWS account under AWS Control Tower called Pipeline Account (aka
pipelineEnv
) provisioned using the AWS Service Catalog Account Factory product AWS Control Tower Account vending process or AWS Organization. - An AWS account under AWS Control Tower called Monitoring Account (Grafana Account aka
monitoringEnv
) provisioned using the AWS Service Catalog Account Factory product AWS Control Tower Account vending process or AWS Organization. - An existing Amazon Managed Grafana Workspace in
monitoringEnv
region ofmonitoringEnv
account. Enable Data sources AWS X-Ray, Amazon Managed Service for Prometheus and Amazon Cloudwatch. - If you are bringing new AWS accounts to deploy this pattern, then create a free-tier EC2 instance and let it run for 15-30 minutes in order to complete validation of account.
NOTE: This pattern consumes multiple Elastic IP addresses, because 3 VPCs with 3 subnets are created in
ProdEnv1
,ProdEnv2
andmonitoringEnv
AWS accounts. Make sure your account limits for EIP are increased to support additional 3 EIPs per account.
Clone Repository¶
Clone cdk-aws-observability-accelerator
repository, if not done already.
git clone https://github.com/aws-observability/cdk-aws-observability-accelerator.git
cd cdk-aws-observability-accelerator
Pro Tip: This document is compatible to run as Notebook with RUNME for VS Code. There's no need to manually copy and paste commands. You can effortlessly execute them directly from this markdown file. Feel free to give it a try.
Here is a sample usage of this document using RUNME:
SSO Profile Setup¶
- You will be accessing multiple accounts during deployment of this pattern. It is recommended to configure the AWS CLI to authenticate access with AWS IAM Identity Center (successor to AWS Single Sign-On). Let's configure Token provider with automatic authentication refresh for AWS IAM Identity Center. Ensure Prerequisites mentioned here are complete before proceeding to next steps.
- Create and use AWS IAM Identity Center login with
AWSAdministratorAccess
Permission set assigned to all AWS accounts required for this pattern (prodEnv1, prodEnv2, pipelineEnv and monitoringEnv). - Configure AWS profile with sso for
pipelineEnv
account:
aws configure sso --profile pipeline-account
# sample configuration
# SSO session name (Recommended): coa-multi-access-sso
# SSO start URL [None]: https://d-XXXXXXXXXX.awsapps.com/start
# SSO region [None]: us-west-2
# SSO registration scopes [sso:account:access]:sso:account:access
# Attempting to automatically open the SSO authorization page in your default browser.
# If the browser does not open or you wish to use a different device to authorize this request, open the following URL:
# https://device.sso.us-west-2.amazonaws.com/
# Then enter the code:
# XXXX-XXXX
# There are 7 AWS accounts available to you.
# Using the account ID 111122223333
# There are 2 roles available to you.
# Using the role name "AWSAdministratorAccess"
# CLI default client Region [None]: us-west-2
# CLI default output format [None]: json
# To use this profile, specify the profile name using --profile, as shown:
# aws s3 ls --profile pipeline-account
- Then, configure profile for
ProdEnv1
AWS account.
aws configure sso --profile prod1-account
# sample configuration
# SSO session name (Recommended): coa-multi-access-sso
# There are 7 AWS accounts available to you.
# Using the account ID 444455556666
# There are 2 roles available to you.
# Using the role name "AWSAdministratorAccess"
# CLI default client Region [None]: us-west-2
# CLI default output format [None]: json
# To use this profile, specify the profile name using --profile, as shown:
# aws s3 ls --profile prod2-account
- Then, configure profile for
ProdEnv2
AWS account.
aws configure sso --profile prod2-account
- Then, configure profile for
monitoringEnv
AWS account.
aws configure sso --profile monitoring-account
- Login to required SSO profile using
aws sso login --profile <profile name>
. Let's now log in topipelineEnv
account. When SSO login expires, you can use this command to re-login.
export AWS_PROFILE='pipeline-account'
aws sso login --profile $AWS_PROFILE
- Export required environment variables for further use. If not available already, you will be prompted for name of Amazon Grafana workspace in
monitoringEnv
region ofmonitoringEnv
account. And, then its endpoint URL, ID, Role ARN will be captured as environment variables.
source `git rev-parse --show-toplevel`/helpers/multi-acc-new-eks-mixed-observability-pattern/source-envs.sh
- Create SSM SecureString Parameter
/cdk-accelerator/cdk-context
inpipelineEnv
region ofpipelineEnv
account. This parameter contains account ID and region of all four AWS accounts used in this Observability Accelerator pattern.
aws ssm put-parameter --profile pipeline-account --region ${COA_PIPELINE_REGION} \
--type "SecureString" \
--overwrite \
--name "/cdk-accelerator/cdk-context" \
--description "AWS account details of different environments used by Multi-Account Multi-Region Mixed Observability (M3) Accelerator pattern" \
--value '{
"context": {
"pipelineEnv": {
"account": "'$COA_PIPELINE_ACCOUNT_ID'",
"region": "'$COA_PIPELINE_REGION'"
},
"prodEnv1": {
"account": "'$COA_PROD1_ACCOUNT_ID'",
"region": "'$COA_PROD1_REGION'"
},
"prodEnv2": {
"account": "'$COA_PROD2_ACCOUNT_ID'",
"region": "'$COA_PROD2_REGION'"
},
"monitoringEnv": {
"account": "'$COA_MON_ACCOUNT_ID'",
"region": "'$COA_MON_REGION'"
}
}
}'
Amazon Grafana Configuration¶
-
Run
helpers/multi-acc-new-eks-mixed-observability-pattern/amg-preconfig.sh
script to -
create SSM SecureString parameter
/cdk-accelerator/amg-info
inpipelineEnv
region ofpipelineEnv
account. This will be used by CDK for Grafana Operator resources configuration. - create Grafana workspace API key.
- create SSM SecureString parameter
/cdk-accelerator/grafana-api-key
inmonitoringEnv
region ofmonitoringEnv
account. This will be used by External Secrets Operator.
eval bash `git rev-parse --show-toplevel`/helpers/multi-acc-new-eks-mixed-observability-pattern/amg-preconfig.sh
GitHub Sources Configuration¶
-
Following GitHub sources used in this pattern:
-
Apps Git Repo - This repository serves as the source for deploying and managing applications in
prodEnv1
andprodEnv2
using GitOps by Argo CD. Here, it is configured to sample-apps from aws-observability-accelerator. - Source for CodePipeline - This repository serves as the CodePipeline source stage for retrieving and providing source code to downstream pipeline stages, facilitating automated CI/CD processes. Whenever a change is detected in the source code, the pipeline is initiated automatically. This is achieved using GitHub webhooks. We are using CodePipeline to deploy multi-account multi-region clusters.
- Source for
monitoringEnv
Argo CD - This repository serves as the source for deploying and managing applications in themonitoringEnv
environment using GitOps by Argo CD. Here, it is configured to grafana-operator-app from aws-observability-accelerator, using which Grafana Datasoures and Dashboards are deployed.
NOTE: Argo CD source repositories used here for
prodEnv1
,prodEnv2
andmonitoringEnv
are public. If you need to use private repositories, create secret calledgithub-ssh-key
in respective accounts and region. This secret should contain your GitHub SSH private key as a JSON structure with fieldssshPrivateKey
andurl
in AWS Secrets Manager. Argo CD add-on will use this secret for authentication with private GitHub repositories. For more details on setting up SSH credentials, please refer to Argo CD Secrets Support.
- Fork
cdk-aws-observability-accelerator
repository to your GitHub account. -
Create GitHub Personal Access Token (PAT) for your CodePipeline GitHub source. For more information on how to set it up, please refer here. The GitHub Personal Access Token should have these scopes:
-
repo - to read the repository
-
admin:repo_hook - to use webhooks
-
Run
helpers/multi-acc-new-eks-mixed-observability-pattern/gitsource-preconfig.sh
script to -
create SSM SecureString Parameter
/cdk-accelerator/pipeline-git-info
inpipelineEnv
region ofpipelineEnv
account which contains details of CodePipeline source. This parameter contains GitHub owner name where you forkedcdk-aws-observability-accelerator
, repository name (cdk-aws-observability-accelerator
) and branch (main
). - create AWS Secret Manager secret
github-token
inpipelineEnv
region ofpipelineEnv
account to hold GitHub Personal Access Token (PAT).
eval bash `git rev-parse --show-toplevel`/helpers/multi-acc-new-eks-mixed-observability-pattern/gitsource-preconfig.sh
Deployment¶
- Fork
cdk-aws-observability-accelerator
repository to your CodePioeline source GitHub organization/user. - Install the AWS CDK Toolkit globally on host machine.
npm install -g aws-cdk
- Install project dependencies.
cd `git rev-parse --show-toplevel`
npm i
make build
- Bootstrap all 4 AWS accounts using step mentioned for different environment for deploying CDK applications in Deploying Pipelines. If you have bootstrapped earlier, please remove them before proceeding with this step. Remember to set
pipelineEnv
account number in--trust
flag. You can also refer to commands mentioned below:
# bootstrap pipelineEnv account WITHOUT explicit trust
env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap --profile pipeline-account \
--cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
aws://${COA_PIPELINE_ACCOUNT_ID}/${COA_PIPELINE_REGION}
# bootstrap prodEnv1 account with trust access from pipelineEnv account
env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap --profile prod1-account \
--cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
--trust ${COA_PIPELINE_ACCOUNT_ID} \
aws://${COA_PROD1_ACCOUNT_ID}/${COA_PROD1_REGION}
# bootstrap prodEnv2 account with trust access from pipelineEnv account
env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap --profile prod2-account \
--cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
--trust ${COA_PIPELINE_ACCOUNT_ID} \
aws://${COA_PROD2_ACCOUNT_ID}/${COA_PROD2_REGION}
# bootstrap monitoringEnv account with trust access from pipelineEnv account
env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap --profile monitoring-account \
--cloudformation-execution-policies arn:aws:iam::aws:policy/AdministratorAccess \
--trust ${COA_PIPELINE_ACCOUNT_ID} \
aws://${COA_MON_ACCOUNT_ID}/${COA_MON_REGION}
- Once all pre-requisites are set, you are ready to deploy the pipeline. Run the following command from the root of cloned repository to deploy the pipeline stack in
pipelineEnv
account. This step may require approximately 20 minutes to finish.
export AWS_PROFILE='pipeline-account'
export AWS_REGION=${COA_PIPELINE_REGION}
cd `git rev-parse --show-toplevel`
make pattern multi-acc-new-eks-mixed-observability deploy multi-account-COA-pipeline
-
Check status of pipeline that deploys multiple Amazon EKS clusters through CloudFromation stacks in respective accounts. This deployment also creates
-
ampPrometheusDataSourceRole
with permissions to retrieve metrics from AMP inProdEnv1
account, cloudwatchDataSourceRole
with permissions to retrieve metrics from CloudWatch inProdEnv2
account and- Updates Amazon Grafana workspace IAM role in
monitoringEnv
account to assume roles inProdEnv1
andProdEnv2
accounts for retrieving and visualizing metrics in Grafana
This step may require approximately 50 minutes to finish. You may login to pipelineEnv
account and navigate to AWS CodePipeline console at pipelineEnv
region to check the status.
# script to check status of codepipeline
dots=""; while true; do status=$(aws codepipeline --profile pipeline-account list-pipeline-executions --pipeline-name multi-account-COA-pipeline --query 'pipelineExecutionSummaries[0].status' --output text); [ $status == "Succeeded" ] && echo -e "Pipeline execution SUCCEEDED." && break || [ "$status" == "Failed" ] && echo -e "Pipeline execution FAILED." && break || printf "\r" && echo -n "Pipeline execution status: $status$dots" && dots+="." && sleep 10; done
Post Deployment¶
-
Once all steps of
multi-acc-stages
inmulti-account-COA-pipeline
are complete, run script to -
create entries in kubeconfig with contexts of newly created EKS clusters.
- export cluster specific and kubecontext environment variables (like:
COA_PROD1_CLUSTER_NAME
andCOA_PROD1_KUBE_CONTEXT
). - get Amazon Prometheus Endpoint URL from
ProdEnv1
account and export to environment variableCOA_AMP_ENDPOINT_URL
.
source `git rev-parse --show-toplevel`/helpers/multi-acc-new-eks-mixed-observability-pattern/post-deployment-source-envs.sh
- Then, update parameter
AMP_ENDPOINT_URL
of Argo CD bootstrap app inmonitoringEnv
with Amazon Prometheus endpoint URL fromProdEnv1
account (COA_AMP_ENDPOINT_URL
) and sync Argo CD apps.
if [[ `lsof -i:8080 | wc -l` -eq 0 ]]
then
export ARGO_SERVER=$(kubectl --context ${COA_MON_KUBE_CONTEXT} -n argocd get svc -l app.kubernetes.io/name=argocd-server -o name)
export ARGO_PASSWORD=$(kubectl --context ${COA_MON_KUBE_CONTEXT} -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
echo "ARGO PASSWORD:: "$ARGO_PASSWORD
kubectl --context ${COA_MON_KUBE_CONTEXT} port-forward $ARGO_SERVER -n argocd 8080:443 > /dev/null 2>&1 &
argocdPid=$!
echo pid: $argocdPid
sleep 5s
argocd --kube-context ${COA_MON_KUBE_CONTEXT} login localhost:8080 --insecure --username admin --password $ARGO_PASSWORD
argocd --kube-context ${COA_MON_KUBE_CONTEXT} app set argocd/bootstrap-apps --helm-set AMP_ENDPOINT_URL=$COA_AMP_ENDPOINT_URL
argocd --kube-context ${COA_MON_KUBE_CONTEXT} app sync argocd/bootstrap-apps
echo -e '\033[0;33m' "\nConfirm update here.. You should see AMP endpoint URL and no error message." '\033[0m'
kubectl --context ${COA_MON_KUBE_CONTEXT} get -n grafana-operator grafanadatasources grafanadatasource-amp -o jsonpath='{.spec.datasource.url}{"\n"}{.status}{"\n"}'
kill -9 $argocdPid
else
echo "Port 8080 is already in use by PID `lsof -i:8080 -t`. Please terminate it and rerun this step."
fi
NOTE: You can access Argo CD Admin UI using port-forwading. Here are commands to access
prodEnv1
Argo CD:
export PROD1_ARGO_SERVER=$(kubectl --context ${COA_PROD1_KUBE_CONTEXT} -n argocd get svc -l app.kubernetes.io/name=argocd-server -o name)
export PROD1_ARGO_PASSWORD=$(kubectl --context ${COA_PROD1_KUBE_CONTEXT} -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d)
echo "PROD1 ARGO PASSWORD:: "$PROD1_ARGO_PASSWORD
nohup kubectl --context ${COA_PROD1_KUBE_CONTEXT} port-forward $PROD1_ARGO_SERVER -n argocd 8081:443 > /dev/null 2>&1 &
sleep 5
curl localhost:8081
- Datasource
grafana-operator-amp-datasource
created by Grafana Operator needs to reflect AMP Endpoint URL. There is a limitation with Grafana Operator (or Grafana) which doesn't sync updatedgrafana-datasources
to Grafana. To overcome this issue, we will simply delete Datasource and Grafana Operator syncs up with the latest configuration in 5 minutes. This is achieved using Grafana API and key stored in SecureString parameter/cdk-accelerator/grafana-api-key
inmonitoringEnv
account.
export COA_AMG_WORKSPACE_URL=$(aws ssm get-parameter --profile pipeline-account --region ${COA_PIPELINE_REGION} \
--name "/cdk-accelerator/amg-info" \
--with-decryption \
--query Parameter.Value --output text | jq -r ".[] | .workspaceURL")
export COA_AMG_API_KEY=$(aws ssm get-parameter --profile monitoring-account --region ${COA_MON_REGION} \
--name "/cdk-accelerator/grafana-api-key" \
--with-decryption \
--query Parameter.Value --output text)
export COA_AMP_DS_ID=$(curl -s -H "Authorization: Bearer ${COA_AMG_API_KEY}" ${COA_AMG_WORKSPACE_URL}/api/datasources \
| jq -r ".[] | select(.name==\"grafana-operator-amp-datasource\") | .id")
echo "Datasource Name:: grafana-operator-amp-datasource"
echo "Datasource ID:: "$COA_AMP_DS_ID
curl -X DELETE -H "Authorization: Bearer ${COA_AMG_API_KEY}" ${COA_AMG_WORKSPACE_URL}/api/datasources/${COA_AMP_DS_ID}
- Then, deploy ContainerInsights in
ProdEnv2
account.
prod2NGRole=$(aws cloudformation describe-stack-resources --profile prod2-account --region ${COA_PROD2_REGION} \
--stack-name "coa-eks-prod2-${COA_PROD2_REGION}-coa-eks-prod2-${COA_PROD2_REGION}-blueprint" \
--query "StackResources[?ResourceType=='AWS::IAM::Role' && contains(LogicalResourceId,'NodeGroupRole')].PhysicalResourceId" \
--output text)
aws iam attach-role-policy --profile prod2-account --region ${COA_PROD2_REGION} \
--role-name ${prod2NGRole} \
--policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
aws iam list-attached-role-policies --profile prod2-account --region ${COA_PROD2_REGION} \
--role-name $prod2NGRole | grep CloudWatchAgentServerPolicy || echo 'Policy not found'
FluentBitHttpPort='2020'
FluentBitReadFromHead='Off'
[[ ${FluentBitReadFromHead} = 'On' ]] && FluentBitReadFromTail='Off'|| FluentBitReadFromTail='On'
[[ -z ${FluentBitHttpPort} ]] && FluentBitHttpServer='Off' || FluentBitHttpServer='On'
curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed 's/{{cluster_name}}/'${COA_PROD2_CLUSTER_NAME}'/;s/{{region_name}}/'${COA_PROD2_REGION}'/;s/{{http_server_toggle}}/"'${FluentBitHttpServer}'"/;s/{{http_server_port}}/"'${FluentBitHttpPort}'"/;s/{{read_from_head}}/"'${FluentBitReadFromHead}'"/;s/{{read_from_tail}}/"'${FluentBitReadFromTail}'"/' | kubectl --context ${COA_PROD2_KUBE_CONTEXT} apply -f -
Validating Grafana Dashboards¶
- Run the below command in
ProdEnv1
cluster to generate test traffic to sample application and let us visualize traces to X-Ray and Amazon Managed Grafana Console out the sampleho11y
app :
frontend_pod=`kubectl --context ${COA_PROD1_KUBE_CONTEXT} get pod -n geordie --no-headers -l app=frontend -o jsonpath='{.items[*].metadata.name}'`
loop_counter=0
while [ $loop_counter -le 5000 ] ;
do
kubectl exec --context ${COA_PROD1_KUBE_CONTEXT} -n geordie -it $frontend_pod -- curl downstream0.geordie.svc.cluster.local;
echo ;
loop_counter=$[$loop_counter+1];
done
- Let it run for a few minutes and look in Amazon Grafana Dashboards > Observability Accelerator Dashboards > Kubernetes / Compute Resources / Namespace (Workloads)
Please also have a look at other Dashboards created using Grafana Operator under folder Observability Accelerator Dashboards.
- Run the below command in
ProdEnv2
cluster to generate test traffic to sample application.
frontend_pod=`kubectl --context ${COA_PROD2_KUBE_CONTEXT} get pod -n geordie --no-headers -l app=frontend -o jsonpath='{.items[*].metadata.name}'`
loop_counter=0
while [ $loop_counter -le 5000 ] ;
do
kubectl exec --context ${COA_PROD2_KUBE_CONTEXT} -n geordie -it $frontend_pod -- curl downstream0.geordie.svc.cluster.local;
echo ;
loop_counter=$[$loop_counter+1];
done
- Let it run for a few minutes and look in Amazon Grafana Administration > Datasources > grafana-operator-cloudwatch-datasource > Explore. Set values as highlighted in the snapshot and 'Run query'.
- Then, let us look at X-Ray traces in Amazon Grafana Administration > Datasources > grafana-operator-xray-datasource > Explore. Set Query Type = Service Map and 'Run query'.
Clean up¶
- Run this command to destroy this pattern. This will delete pipeline.
export AWS_PROFILE='pipeline-account'
aws sso login --profile $AWS_PROFILE
cd `git rev-parse --show-toplevel`
source `git rev-parse --show-toplevel`/helpers/multi-acc-new-eks-mixed-observability-pattern/source-envs.sh
make pattern multi-acc-new-eks-mixed-observability destroy multi-account-COA-pipeline
- Next, run this script to clean up resources created in respective accounts. This script deletes Argo CD apps, unsets kubeconfig entries, initiates deletion of CloudFormation stacks, secrets, SSM parameters and Amazon Grafana Workspace API key from respective accounts.
eval bash `git rev-parse --show-toplevel`/helpers/multi-acc-new-eks-mixed-observability-pattern/clean-up.sh
- In certain scenarios, CloudFormation stack deletion might encounter issues when attempting to delete a nodegroup IAM role. In such situations, it's recommended to first delete the relevant IAM role and then proceed with deleting the CloudFormation stack.
- Delete Dashboards and Data sources in Amazon Grafana.