🎰

【Terraform】ECSスロットリング検知とSlackへの自動通知(AWS CloudWatchとChatbotの活用)〜セキュリティ〜

2024/04/19に公開

はじめに

本シリーズのご紹介

このシリーズでは、AWSとSlackを連携させる方法や、Terraformを用いた自動化技術の基本について詳しく解説します。前回の記事の内容を踏まえつつ、さらに実用的なスキルを身につけることができるよう、具体的な手順やコード例を交えて進めていきます。

要件定義&全体のアーキテクチャ

  • ECS
  • CloudWatch
  • AWS SNS
  • AWS Chatbot
  • AWS Systems Management
  • Slack

https://zenn.dev/take_tech/articles/04707c6594e3fb

サンプルコード導入&各種オブジェクトの解説
https://zenn.dev/take_tech/articles/5029460000a0ed

各種API取得&Terrafrom連携
https://zenn.dev/take_tech/articles/ac0704b50684da

セキュリティの強化

秘密情報(Slack API)の保護

Slack Webhook URLの保護

  1. AWS Secrets Manager を利用して秘密情報を管理すること

上記について以下がソースです。

Important
Don't store sensitive data in a String or StringList parameter. For all sensitive data that must remain encrypted, use only the SecureString parameter type.

https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html

また以下の書籍にも記載されていました(以下は要約なのでご了承ください)
https://dev.classmethod.jp/articles/bookreview-introduction-guide-aws-container-design-and-construction/

  1. state ファイル(terraform.tfstate)に含める (⭕️最初はこちらで進めていきます)

https://engineering.mobalab.net/2021/03/25/handling-secrets-with-terraform/
以下のコマンドでdb.tfを作成します。

code db.tf

ここでの目的は、SlackのWebhook URL、Slack Channel ID、およびSlack Workspace IDを環境変数または外部ファイルを使用してTerraformで安全に管理することです。
以下を追加します。

variable "slack_webhook_url" {
  type = string
  description = "Webhook URL for sending notifications to Slack"
}

variable "slack_channel_id" {
  type = string
  description = "Slack channel ID where notifications will be sent"
}

variable "slack_workspace_id" {
  type = string
  description = "Slack workspace ID where the channel exists"
}

次にこれらの変数を使用するリソース設定を行います。
以下、SlackのWebhook URL、Slack Channel ID、およびSlack Workspace IDについて修正します。
chatbot_slack_config.tf

slack_channel_id    = var.slack_channel_id
slack_workspace_id  = var.slack_workspace_id

chatbot_slack_config.tf

endpoint  = var.slack_webhook_url

環境変数と外部ファイルの運用

nano~ /.zshrcで.zshrcファイルを開きます。

export TF_VAR_slack_webhook_url="https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXX"
export TF_VAR_slack_channel_id="***********"
export TF_VAR_slack_workspace_id="***********"

source ~/.zshrc で環境変数の変更を適用

外部ファイルの作成

以下で.tfvarsファイルを作成します。

code db.tfvars

秘密情報を追加して、.tfvarsファイルを.gitignore`へ追加します。

slack_webhook_url = "https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXX"
slack_channel_id = "************"
slack_workspace_id = "*************"

そのうえで、以下のコマンドを実行します。

terraform apply -var-file="db.tfvars"

これにより、秘密情報が安全に保管され、公開されるリスクを最小限に抑えられます。

AWS Systems Manager の利用方法とベストプラクティスの実践

こちらはAWS公式が網羅的に解説されている記事になります。
https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2024_AWS_KeyManagementService_0331_v1.pdf

目的

AWSのAWS Systems Managerのパラメーターストアに変数を格納したのちに、Terraformでそのデータを取得することです。

手順

  • AWS Management Console にログイン
  • Systems Manager サービスに移動

パラメータ名の例

***/***/***/***/***/config

階層化が推奨されている(主にスケーラビリティの確保と細かい粒度でアクセス制御を設定できるため)

Managing dozens or hundreds of parameters as a flat list is time consuming and prone to errors. It can also be difficult to identify the correct parameter for a task. This means you might accidentally use the wrong parameter, or you might create multiple parameters that use the same configuration data.

You can use parameter hierarchies to help you organize and manage parameters. A hierarchy is a parameter name that includes a path that you define by using forward slashes (/).

https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-paramstore-hierarchies.html

説明

  • パラメータの役割を記載する

利用枠

  • 標準

タイプ

  • 安全な文字列

繰り返しにはなりますが、ここは「文字列」ではなく、「安全な文字列」を選択しましょう。

https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html

KMSキーソース

  • 現在のアカウント

KMSキーID

  • 現在のアカウントを選択したら自動入力される

値(KMSキーで暗号化するパラメータ値)

  • 秘密情報(取得したSlack API情報など)

以下、パラメータストアに保存成功すると表示される例です。

(参考記事)
https://aws.plainenglish.io/automating-aws-ssm-parameter-store-a-python-based-approach-for-managing-application-parameters-7f333e69b52a

確信が持てない点

万が一、現在のアカウントが何らかの理由で使用不能になったら?

KMSキーIDは、AWSアカウントに紐づいているため、もしアカウントが何らかの理由でアクセスできなくなった場合、そのアカウントと関連付けられているKMSキーも使用できなくなる認識です。しかし、ルートユーザー側でもキーを管理できるようになっているため、通常はルートユーザーによる回復や管理が可能と解釈しています。(違ったらすみません、よろしければコメント頂けると幸いです🙇‍♂️)

Terrafrom側の修正

環境変数の削除

以下で構築した環境変数を削除します。先ほど環境変数を加えましたが、AWS Systems Managerのパラメーターストアに変数を格納したため、環境変数での定義は不要になります。

nano~ /.zshrcで.zshrcファイルを開きます。

- export
-TF_VAR_slack_webhook_url="https://hooks.slack.com/services/T0000/B0000/XXXXXXXXXX"
- export TF_VAR_slack_channel_id="***********"
- export TF_VAR_slack_workspace_id="***********"

Data Source: aws_ssm_parameterの定義

AWS Systems Manager Parameter Storeからシークレット(秘密情報)を取得するためのデータソースを設定する方法を記載します。これにより、SlackのWebhook URL、チャネルID、ワークスペースIDを安全に管理し、コードからこれらの値を直接参照するのではなく、AWS SSMから動的に取得することが実現できます。

before

variable "slack_webhook_url" {
  type = string
  description = "Webhook URL for sending notifications to Slack"
}

variable "slack_channel_id" {
  type = string
  description = "Slack channel ID where notifications will be sent"
}

variable "slack_workspace_id" {
  type = string
  description = "Slack workspace ID where the channel exists"
}

after
AWS SSM Parameter Storeから動的にデータを取得できるようになります

# データソース: AWS SSMからSlackの設定を取得
data "aws_ssm_parameter" "slack_webhook_url" {
  name = "/*******/*****/*********/************/slack_webhook_url"
  type = "SecureString"
}

data "aws_ssm_parameter" "slack_channel_id" {
  name = "/*******/*****/*********/************/slack_channel_id"
  type = "SecureString"
}

data "aws_ssm_parameter" "slack_workspace_id" {
  name = "/*******/*****/*********/************/slack_workspace_id"
  type = "SecureString"
}

Data Sourceを用いてssmから値を取得するように修正

channel   = resource.aws_ssm_parameter.channel.value
workspace = resource.aws_ssm_parameter.workspace.value

以下参考
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter

不要部分の削除

-resource "aws_sns_topic_subscription" "slack_notification" {
-  topic_arn = aws_sns_topic.ecs_throttling_notifications.arn
-  protocol  = "https"
-  endpoint  = var.slack_webhook_url
- }

AWS SNSトピックがSlackからの通知をサブスクリプションをする設定で、SNSがサブスクライブしているメッセージをSlackに対して通知するので不要となります。

すると、Slack webhook URLは必要ないのか?という疑問が生じますが、結論問題ないです。理由は、Slack webhook URLはあくまでSlackからのイベント(ユーザーからのフィードバック)をAWSなどに送るために使用されるものです。今回は、そのデータフロートは逆で、AWS側からSlackへ通知を送るため、Slack Webhook URLの選定は適切ではありません。(これまで通り、slackのchannelId とslackのworkspaceId は必要です。)
https://qiita.com/ik-fib/items/b4a502d173a22b3947a0

適切なmetric_nameの選定(詰まった点)

そもそもECSのスロットリングの認識:
タスクがリソースの制限に達したときにAWS ECSがタスクの起動を一時的に制限することとで、CPUやメモリなどのリソースが不足している場合に発生する可能性があると認識しています。

つまり、CPUのリソース欠如により、スロットリングが発生するという解釈のもと以下のmetric_nameを選択しました。

  • CPUUtilized

しかし、それだけでなく、コードにバグが含まれておりコンテナの立ち上げに失敗する際にもスロットリングが発生するそうです。

つまり、以下ECSのライフサイクルを表す添付画像で解説すると、「Running」への状態を目標にリソースが割かれるものの、「Stopped」の部分に留まっていること(この間にもリソースが消費されて費用が発生している)もスロットリングであるといえます。

そのため、例えばですが仮に1分間に1回立ち上がれば正常と判断されるタスクが、1分間に3回立ち上がろうとしていたら、スロットリングの疑いが出てきます。すなわち、RunningTaskCountの個数を監視する必要が出てきます

  • RunningTaskCount

(しつこくてすみません)しかし、再度検討しなおした結果、PendingTaskCountの個数をカウントした方が良さそうなことが判明しました。理由としては、正常ステータスをカウントした場合、正常に起動している場合と、スロットリングが起きつつも一瞬正常のステータスになった場合とで区別がつかないためです。一方で、PendingTaskCountが多い場合は、確実にスロットリングを捉えることができそうなためです。

結論(以下のmetric_nameを選定)

  • PendingTaskCount

https://speakerdeck.com/msato/amazon-ecsfalsenetutowakuguan-lian-kosutofalsehua?slide=7
https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-lifecycle-explanation.html

つづく

前回までの記事はこちら

要件定義&全体のアーキテクチャ

  • ECS
  • CloudWatch
  • AWS SNS
  • AWS Chatbot
  • AWS Systems Management
  • Slack

https://zenn.dev/take_tech/articles/04707c6594e3fb

サンプルコード導入&各種オブジェクトの解説
https://zenn.dev/take_tech/articles/5029460000a0ed

各種API取得&Terrafrom連携
https://zenn.dev/take_tech/articles/ac0704b50684da

Discussion