なになれ

IT系のことを記録していきます

Terraform AWS Cloud Control Providerを用いてAWS Chatbotを作成する

AWSとSlackを使っている場合、AWS上でのイベントをSlackに通知したいことが多々あると思います。最近ではAWS Chatbotを使うとコーディングなしでSlackに通知できるので、よっぽどのことがない限りはAWS Chatbotを使うのではないかと思います。
ただ自分の知る限りでは、これまでTerraformはAWS Chatbotに対応していませんでした。
そのため、マネージメントコンソールを使って手作業でAWS Chatbotを作成してしまっていました。CloudFormationであれば対応しているのですが、そこだけCloudFormationを使うというのも面倒でした。

ところが、本日たまたまTerraform AWS ProviderのリポジトリにあるAWS Chatbot関連のGitHub Issueをみると、AWS Cloud Control Providerを使えばできるよという投稿が!!!

github.com

これはTerraformでいい感じに完結してますし、とても良さそうなやり方と思い、自分でも試してみました。
ぜひ全Terraform AWS Providerユーザーに知ってもらいたい内容です!

今回のコードは以下にあります。
github.com

これ以降で、コードを解説します。

解説

AWS Cloud Control Providerについて

利用するTerraformのProviderを指定します。hashicorp/awsccAWS Cloud Control API向けのProviderです。

...
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.25.0"
    }
    awscc = {
      source  = "hashicorp/awscc"
      version = "0.29.0"
    }
  }
  required_version = ">= 1.2.0"
}
...
provider "awscc" {
  region = "ap-northeast-1"
}
...

hashicorp/awsccはHashiCorpがメンテナンスしているProviderなので安心だと思います。
AWS Cloud Control APIについては以下の公式ドキュメントに詳しい説明があります。

docs.aws.amazon.com

AWS Cloud Control APIを使うと、CloudFormationのリソースをAPIを介して作成できます。AWS Cloud Control ProviderはそのAPIに対応したProviderです。

AWS Chatbotのリソース作成

CloudFormationのAWS::Chatbot::SlackChannelConfigurationリソースを作成します。
AWS::Chatbot::SlackChannelConfiguration - AWS CloudFormation

AWS Cloud Control Providerを使うと以下の記述になります。

...
resource "awscc_chatbot_slack_channel_configuration" "example" {
  configuration_name = "example"
  iam_role_arn       = aws_iam_role.chatbot.arn
  slack_channel_id   = "example"
  slack_workspace_id = "XXXXXXXXX"
  guardrail_policies = [
    aws_iam_policy.chatbot.arn,
    local.readonly_policy_arn
  ]
  sns_topic_arns = [aws_sns_topic.chatbot.arn]
}
...

iam_role_arnには、IAM RoleのARNを指定します。AWS Chatbotができることには制限があります。AWSのリソースを参照したり、Lambda関数を実行するといったことなどです。
それに沿ったIAM Policyを付与したIAM Roleにします。例えば、以下のとおりです。

...
locals {
  readonly_policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}
...
resource "aws_iam_role" "chatbot" {
...
}

resource "aws_iam_policy" "chatbot" {
  name   = "chatbot"
  policy = file("./iam/chatbot.json")
}

resource "aws_iam_role_policy_attachment" "chatbot" {
  policy_arn = aws_iam_policy.chatbot.arn
  role       = aws_iam_role.chatbot.name
}

resource "aws_iam_role_policy_attachment" "chatbot_readonly" {
  policy_arn = local.readonly_policy_arn
  role       = aws_iam_role.chatbot.name
}
...

./iam/chatbot.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "iam:*",
        "s3:GetBucketPolicy",
        "ssm:*",
        "sts:*",
        "kms:*",
        "cognito-idp:GetSigningCertificate",
        "ec2:GetPasswordData",
        "ecr:GetAuthorizationToken",
        "gamelift:RequestUploadCredentials",
        "gamelift:GetInstanceAccess",
        "lightsail:DownloadDefaultKeyPair",
        "lightsail:GetInstanceAccessDetails",
        "lightsail:GetKeyPair",
        "lightsail:GetKeyPairs",
        "redshift:GetClusterCredentials",
        "storagegateway:DescribeChapCredentials"
      ],
      "Resource": [
        "*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "lambda:invokeAsync",
        "lambda:invokeFunction"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

AWSの管理ポリシーであるReadOnlyAccessを付与するのとAWS Chatbotでは利用できないサービスを拒否するのとLambda関数の実行を許可しています。このあたりは利用者自身の事情に合わせてAWS Chatbotでどこまでの操作を許可するかを指定する必要があります。

slack_workspace_idAWS ChatbotにSlackのワークスペースを設定する際に発行されます。AWSのマネージメントコンソール上でワークスペースのIDを確認することができます。ワークスペースのIDを取得するために、事前にマネージメントコンソールのAWS Chatbot画面でワークスペースの設定が必要になります。

guardrail_policiesはその名のとおり、ガードレールとなるIAMポリシーを設定します。このIAMポリシーの範囲が全体の許可の範囲になります。今回はIAM Roleと同じ許可の範囲にしています。本来は適切な権限範囲に設定すべきです。

sns_topic_arnsでは、SNS TopicのARNを指定します。以下のように作成したSNS TopicのARNを参照するだけで対応完了です。

...
resource "aws_sns_topic" "chatbot" {
  name = "chatbot"
}

ここまでで準備ができたら、Terraformを実行することで、AWS ChatbotのSlackチャンネル向けの設定リソースが作成できます。

まとめ

AWS Cloud Control Providerを使ってAWS Chatbotをシンプルに作成することができました。
AWS Cloud Control APIですが、発表された時にはAWSをそのまま使う人には関係ないだろうくらいの認識でした。
HashiCorp社でAWS Cloud Control APIへの対応が進められていて、このような形で便利になるものだったとは驚きました。
今回のAWS ChatbotのようにAWS SDKでは対応していないけど、AWS Cloud Control APIでは対応できることを知ることができました。
今後はAWS Cloud Control Providerも定期的にチェックしなければという気持ちになりました。

いつの間にかデータ基盤が使えなくなるのを防ぐ GitHub ActionsによるDBマイグレーションに気づく仕組み

データエンジニアリングにおいて、初手の処理として、各システムからデータを抽出する処理があります。よく言われるETLやELTと呼ばれる最初のExtractの処理です。
そこから始まり、利用者にデータを使ってもらえるように可視化や分析のプロセスを構成するかと思います。

本記事は、データエンジニアリングにおいてデータを抽出する対象システムのデータ定義変更をどうやって事前に気づくかのTipsです。
最初のデータの抽出処理がエラーとなると、後続のデータフローが止まってしまうこともあるため、個人的には重要なトピックであると考えています。
今回の仕組みはGitHubやDBのマイグレーションファイルを管理するフレームワークを前提としたものになりますが、考え方は技術スタックが変わっても補える部分があるのではないかと思います。

課題

現在扱っているデータ基盤では、各種システムからデータの抽出を行いデータレイクに蓄積する部分と、データレイクからデータを分析、可視化するというデータ処理とがあります。
そのあたりの詳細は下記の過去記事にあります。

hi1280.hatenablog.com

データを抽出する各種システムの例の一つとして、自社サービスを構成するシステムがあるかと思います。
自社サービスのデータを分析することで、サービスの改善に繋げることが期待されるからです。
自社サービスのシステムは日々機能追加やリファクタリングを重ねながら作られていくものです。それに伴い、データ定義の変更が発生する可能性があります。
データエンジニアリング側でこれに気づかない場合、いつの間にかデータの処理がエラーになってしまうといったことがあり得ます。

また、サービスのシステムを開発するエンジニアにとっては、データ基盤のことなど知る由もなく、自分たちの必要に応じてデータ定義を変更している状態かと思います。
そのため、データ基盤はいつの間にか壊れる危険性があります。
一方でデータ基盤のことも気にしながら、開発するというのはサービスの改善スピードを妨げることにもなり、このようなアプローチは破綻すると考えます。
やはりサービスのシステムを開発するエンジニアにはそのシステムのことだけを考えて開発してもらう方が全体にとっては効果的なはずです。

解決策

これらの課題を踏まえて、データエンジニアリングの仕組みとしては、事前にデータを抽出するシステムでのデータ定義の変更に気づく必要があります。
幸い、現環境のサービスのシステム開発において、DBのマイグレーションを行う場合には、GitHubリポジトリマイグレーションファイルをコミットするプロセスになっていました。
そのため、このマイグレーションファイルのコミットを監視することができれば、データ定義の変更に気づくことができると考えました。

実際の実装内容としては次の例のように、GitHub Actionsのワークフローファイルを作成しました。
内容はとてもシンプルで、マイグレーションファイルが配置されるディレクトリ配下のファイルを対象としたPRが作られた場合にSlackに通知するといった仕組みです。

.github/workflows/db-migration-notice.yml

name: database migration notice
on:
  pull_request:
    branches:
      - main
    paths:
      - "src/migration/**"
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: Post to a Slack channel
        id: slack
        uses: slackapi/slack-github-action@v1.19.0
        with:
          channel-id: 'XXXXXXX'
          payload: |
            {
              "text": "Database migration notice: <${{ github.event.pull_request.html_url || github.event.head_commit.url }}|${{ github.event.pull_request.title }}>",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "Database migration notice: <${{ github.event.pull_request.html_url || github.event.head_commit.url }}|${{ github.event.pull_request.title }}>"
                  }
                },
                {
                  "type": "context",
                  "elements": [
                    {
                      "type": "mrkdwn",
                      "text": "Author: <https://github.com/${{ github.event.sender.login }}|${{ github.event.sender.login }}>"
                    }
                  ]
                }
              ]
            }
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} 

Slackに通知する処理はSlackアプリを作成して実行しています。以下の内容が参考になります。
qiita.com

通知の様子は以下のようになります。PRの詳細はメッセージ内のリンクを開くことで分かるようになっています。

このように、データ定義の変更を事前に分かっていなくても各サービスの開発プロセスに組み込む形でデータ定義の変更を検知することができます。
もちろん、全てのデータ定義の変更がデータ基盤に影響するわけではないので、そこは人がチェックするしかないです。
このあたりは現状どうしようもなく、いつの間にかデータ基盤が使えなくなってしまうリスクを考えると許容できる運用かと思います。

まとめ

GitHub Actionsを使って、データエンジニアリングで起こるであろうデータ抽出の問題をある程度解決しました。
データ基盤はサービスのシステムがあってのものです。サービスのシステム開発に影響がない形で仕組みを作る必要があると思います。
今回はそれを踏まえて、現実的な解決策を実現できたのではないかと考えています。

クラスタマイグレーション方式を用いてAmazon EKSの複数マイナーバージョンを一度に更新する

この頃はEKSのバージョン更新を1年に1回くらいを目処に実施しています。今回、EKSのバージョンを1.19から1.22に更新しました。
1.19は2022年6月にサポート終了するので、ギリギリのタイミングでした。。。

複数のマイナーバージョンを一度に更新する場合には、新バージョンのクラスタを事前に作成して、旧バージョンへの向き先を新バージョンに切り替えることで更新を行います。
ここではこのやり方をクラスタマイグレーション方式と呼びます。
EKSでは、1マイナーバージョンずつであればインプレースで更新できます。ただ更新頻度が高いのが作業負荷的に辛いので、このアップデート方法は現在の運用では使っていません。

この記事では、上記の内容を踏まえたEKSのバージョン更新方法とハマりポイントを共有できればと思います。
EKSを運用し始めた方に参考になる内容かと思います。また、これからEKSを採用しようという方でもこんなハマりポイントがあるという事前の情報として役立つかと思います。

要約

内容

クラスタマイグレーション方式について

クラスタマイグレーション方式は、以下のイメージでALBのトラフィックの重み付けを徐々に変えていき、クラスタを切り替える方式です。

トラフィックの重み付けを変えるのはALBの機能を使えばできます。 ALBに関連付くターゲットグループを各クラスタごとに作成し、ターゲットグループへのトラフィックの重みを変更していきます。
下記の内容が参考になります。
aws.amazon.com

移行作業時に気をつけるポイントとして、重複を許容しないリソースがある場合、旧クラスタと新クラスタが並行稼働している状態だとどちらかのクラスタでそのリソースが起動している状態にする必要があります。
クラスタから新クラスタへの移行作業時にそのリソースを移すことで、ただ一つリソースがある状態にします。

クラスタマイグレーションでは、クラスタを新しく作成する必要があります。
本環境ではTerraformのEKSモジュールを使って、クラスタを作成しています。
このモジュールですが、ここ1年の間にリリースされたv18で大きな変更があり、クラスタの作り方に修正が必要でした。

github.com

EKSの更新とは直接関係ないですが、EKSのバージョン更新の機会に周辺ツールのバージョンも更新しておくことになるかと思います。
そのため、このあたりのクラスタ作成方法に変更がないかも気にしていく必要があります。

更新時のハマりポイント

あまりに特有の問題なので再現性はないかもしれませんが、EKSの運用ではこのような運用作業が発生することがあるというくらいの認識を持ってもらえると良いと思います。

Datadog Agentの更新

土台となるKubernetesのバージョンが大きく変わるため、Kubernetes上にインストールするインフラ系のツールを更新します。
インフラ系のツールは例えば、Datadog AgentやCluster Autoscalerなどです。それら全てのKubernetesによるインフラを動作させるために必要なツールのバージョンをその時点での最新版に更新しました。
その作業の中で、Datadog Agentの更新ではいくつか問題がありました。

具体的には、Datadog Agentがクラスタを認識しないという事象が発生しました。
旧バージョンだとクラスタを自動的に認識している動作をしているようでした。この原因はよく分からなかったのですが、最終的にはクラスタ名をAgentのパラメータに直で指定することで解決しました。
下記のコマンドのようにdatadog.clusterNameのパラメータを指定します。

$ helm install datadog-agent -f values.yaml  --set datadog.apiKey=[apikey],datadog.tags=env:production,datadog.clusterName=[clustername],datadog.apm.portEnabled=true datadog/datadog --set targetSystem=linux --namespace=[namespace]

トラブルシューティングの過程で、Datadog Agentに下記コマンドを実行するとAgentの状態が確認できることを知りました。

$ kubectl exec -it <POD_NAME> agent status

このコマンドを使うと、Agentがクラスタを認識しているかなどがわかります。トラブルシューティングに役立ちます。

もう一つの問題として、Kubernetesのメトリクスを収集するのに変更が必要でした。
Datadog AgentではKubernetesのメトリクスを収集するのにkube-state-metricsを使用します。
kube-state-metricsを使うには2つの方法があり、kube-state-metricsを別途デプロイするレガシーな方法と、Datadog Agentに含まれるKubernetes State Metrics Coreを使う方法があります。
kube-state-metricsを別途デプロイする方法だと1.9.8という古いバージョンを使うことになってしまいます。
今まではkube-state-metricsを利用していましたが、kubernetesの1.22に対応するKubernetes State Metrics Coreを使うように変更しました。
Helmの場合は以下のように変更します。

values.yaml

...
  # datadog.kubeStateMetricsEnabled -- If true, deploys the kube-state-metrics deployment
  ## ref: https://github.com/kubernetes/kube-state-metrics/tree/kube-state-metrics-helm-chart-2.13.2/charts/kube-state-metrics
  kubeStateMetricsEnabled: false
...
  kubeStateMetricsCore:
    # datadog.kubeStateMetricsCore.enabled -- Enable the kubernetes_state_core check in the Cluster Agent (Requires Cluster Agent 1.12.0+)
    ## ref: https://docs.datadoghq.com/integrations/kubernetes_state_core
    enabled: true
...

どちらもやることは同じなので、被らないようにkubeStateMetricsEnabledをfalseにして無効にし、kubeStateMetricsCoreを有効にします。
Kubernetes State Metrics Coreはkube-state-metricsのバージョン2以上に対応した仕組みのようです。
docs.datadoghq.com Kubernetes State Metrics Coreに変更したことに伴い、Datadogでメトリクスを扱う際に利用するタグの名称を変更する必要がありました。
上記のリンク先にタグの変更に関する詳細があります。

containerdを使う

EKSでは1.21からコンテナランタイムにcontainerdを使うためのサポートが追加されました。
Kubernetesでは1.24でdockershimが削除されており、できるならばcontainerdへの移行をしたいところです。
EKSでは、AMIのbootstrap scriptを実行する時に以下のようにオプションを指定するだけでcontainerdを有効化できます。

--container-runtime containerd

ただ今回はcontainerdを試した結果、containerdの採用は見送りました。
理由はdockershimではできていたディスク使用量のメトリクスを取得することがcontainerdではできなかったためです。
以下にissueがあります。

github.com

1.21ではdockershimはまだ非推奨という段階のため、引き続きdockershimを利用することにしました。
containerdを使う際にはこのように取得したいメトリクスが取得できるかも確認が必要でした。

まとめ

クラスタマイグレーションをするとEKSの複数マイナーバージョンを一度に更新することができます。
containerdはメトリクスが取得できないことがあるので、dockershimが削除されるからといってそのままcontainerdに移行してよいかは現時点では考える必要がありそうです。
非推奨ではありますが、EKSではまだしばらくはサポートされる状況です。
インフラ系ツールを色々インストールすることでKubernetes自体は便利になりますが、Kubernetesの更新に追従してツールを更新するのが辛くなってきます。
やはりコンテナを動かすだけならEKSはオーバースペックだなぁと感じてしまいます。