TECH PLAY

MNTSQ

MNTSQ の技術ブログ

86

はじめに 構成 実装してみる EventBridge EventBridge API destinationsの設定 オートスケールイベントを拾うためのEventルールを作成する Datadog Slack連携の設定 Event Monitorの設定 おわりに はじめに ECSのオートスケールは、一度設定してしまえば非常に便利ですが、人の手を離れて安定運用に乗せるまでには様々な技術的なハードルがあります。安定運用に入るまでは、現在の設定は妥当なのかを判断するため、オートスケールが発生したことを何らかの方法で人間が把握し、日々改善を重ねていくことが必要不可欠です。そこで今回は、ECSのオートスケールイベントをEventBridgeで拾い、Datadogに連携してSlack通知する仕組みを実装してみます。 構成 全体の構成は以下の通りです。ECS クラスタ やサービスを限定せずに、オートスケールイベントが発生した時に通知を行うような汎用的な仕組みを作っていきます。 オートスケールのイベントをEventBridgeで拾う Datadog Event Management API からDatadogに連携する Datadog Event Monitorにてアラートの設定を行う Slackにメッセージを送信する 実装してみる EventBridge EventBridge API destinationsの設定 EventBridgeでは、 API destinations(API送信先) を設定することによって、 AWS 内のイベントを任意の API に連携することができます。 以下のサンプルコードにて、 API destinationsおよび必要なIAMリソースを作成します。DATADOG_ API _KEYはあらかじめSecrets Managerに登録されているものとしています。 サンプルコード # IAM data "aws_iam_policy_document" "eventbridge_sts" { statement { effect = "Allow" actions = [ "sts:AssumeRole" ] principals { type = "Service" identifiers = [ "events.amazonaws.com" ] } } } data "aws_iam_policy_document" "eventbridge_datadog" { statement { effect = "Allow" actions = [ "events:InvokeApiDestination" ] resources = [ aws_cloudwatch_event_api_destination.datadog.arn ] } statement { effect = "Allow" actions = [ "secretsmanager:DescribeSecret" , "secretsmanager:GetSecretValue" , ] resources = [ "arn:aws:secretsmanager:*:*:secret:events!connection/<DATADOG_API_KEYのシークレット名>/*" ] } } resource "aws_iam_role" "eventbridge_datadog" { name = "eventbridge_datadog_role" assume_role_policy = data.aws_iam_policy_document.eventbridge_sts.json } resource "aws_iam_role_policy" "eventbridge_datadog" { name = aws_iam_role.eventbridge_datadog.name role = aws_iam_role.eventbridge_datadog.id policy = data.aws_iam_policy_document.eventbridge_datadog.json } # あらかじめSecretsManagerにDATADOG_API_KEYを登録しておくこと data "aws_secretsmanager_secret_version" "datadog_api_key" { secret_id = "<DATADOG_API_KEYのシークレット名>" } # Connection resource "aws_cloudwatch_event_connection" "datadog" { name = "datadog-event-api" authorization_type = "API_KEY" auth_parameters { api_key { key = "DD-API-KEY" value = data.aws_secretsmanager_secret_version.datadog_api_key.secret_string } } } resource "aws_cloudwatch_event_api_destination" "datadog" { name = "datadog-event-api" connection_arn = aws_cloudwatch_event_connection.datadog.arn invocation_endpoint = "https://api.datadoghq.com/api/v1/events" http_method = "POST" invocation_rate_limit_per_second = 10 } EventBridgeのマネジメントコンソール左側のツリーから「 API の 送信先 」および「接続」を確認し、"datadog-event- api "のリソースが作成されていたらOKです。 オートスケールイベントを拾うためのEventルールを作成する ECSのオートスケールイベントは、一例としては以下のように、" aws .ecs"の"UpdateService"イベントを呼び元の"userIdentity"で絞ることによって判別することができます。" aws .application-autoscaling"のイベントもありますが、こちらで設定してもスケールの最大値に達した時しかトリガされないようです。ただでさえ動作確認が大変なところなので、気をつけましょう。 { " detail-type ": [ " AWS API Call via CloudTrail " ] , " source ": [ " aws.ecs " ] , " detail ": { " userIdentity ": { " invokedBy ": [ " ecs.application-autoscaling.amazonaws.com " ] } , " eventSource ": [ " ecs.amazonaws.com " ] , " eventName ": [ " UpdateService " ] } } ※今回やってはいけないイベントパターンの設定(スケール最大値に達した時しか発生しない) { " source ": [ " aws.application-autoscaling " ] " detail-type ": [ " Application Auto Scaling Scaling Activity State Change " ] } ターゲットには、先ほど作成した API destinationsを指定します。 また、拾ったイベントの json を API のリク エス トボディによしなに整形するため、 入力トランスフォーマ を以下のように設定します。 入力パスで使用したい変数をバインドし、入力テンプレートでリク エス トボディを作成しています。 ※ 入力パス { " newDesiredCount ": " $.detail.requestParameters.desiredCount ", " service ": " $.detail.requestParameters.service " } ※ 入力テンプレート { " title ": " ECS Cluster Auto Scaling ", " source_type_name ": " amazon ecs ", " alert_type ": " info ", " text ": " %%%`<service>` is auto scaled. \n New desiredCount: <newDesiredCount> %%% ", " tags ": [ " environment:development ", " source:amazon_ecs ", " aws_account:************ ", " new_desired_count:<newDesiredCount> ", " ecs_service_name:<service> " ] } これらの設定のサンプルコードです。 サンプルコード # ECSサービスのオートスケールを検知して通知するイベント resource "aws_cloudwatch_event_rule" "ecs_service_autoscale" { name = "ecs-service-autoscale" event_pattern = jsonencode ( { detail-type = [ "AWS API Call via CloudTrail" ] source = [ "aws.ecs" ] detail = { userIdentity = { invokedBy = [ "ecs.application-autoscaling.amazonaws.com" ] } eventSource = [ "ecs.amazonaws.com" ] , eventName = [ "UpdateService" ] } } ) } # API destinationsをターゲットに設定 resource "aws_cloudwatch_event_target" "ecs_service_autoscale" { rule = aws_cloudwatch_event_rule.ecs_service_autoscale.name arn = aws_cloudwatch_event_api_destination.datadog.arn role_arn = aws_iam_role.eventbridge_datadog.arn input_transformer { input_paths = { newDesiredCount = "$.detail.requestParameters.desiredCount" , service = "$.detail.requestParameters.service" } # jsonencodeが特殊文字をエスケープしてしまうので、ヒアドキュメントを使用する input_template = <<EOF { "title": "ECS Service Auto Scaling", "source_type_name": "amazon ecs", "alert_type": "info", "text": "%%%`<service>` is auto scaled.\n New desiredCount: <newDesiredCount> %%%", "tags": [ "environment:development", "source:amazon_ecs", "aws_account:**********", "event_name:ecs_service_autoscaled", "new_desired_count:<newDesiredCount>", "ecs_service_name:<service>" ] } EOF } } Eventルールが作成されたら、何らかの方法でECSサービスをオートスケールさせてみましょう。DatadogのEvent Explorer ( https :// .datadoghq.com/event/ explorer )にて以下のようなイベントが飛んできていることを確認します。"event_name:ecs_service_autoscaled "でクエリをすれば出てくるはずです。 ※ イベントがDatadogに飛んでこない時 イベントが飛ばない時は以下を行いましょう EventルールのターゲットにCloudWatchを設定 コンソールからEventルールを選択し、「ターゲット」のタブから「編集」でCloudWatchロググループを追加します。追加の権限などは不要なはずです。イベントが正しく拾えていれば、設定したロググループにログストリームが生成されているはずです。また、ロググループからイベントの詳細を確認できるので、入力テンプレートの情報を充実させたい場合などにもこちらを参照します。 Eventルールの API destinationsのターゲットにDLQを設定する コンソールからEventルールを選択し、「ターゲット」のタブから「編集」-> 「ターゲットを選択」のページに移動します。ターゲットは複数設定できますが、 API destinationsのものを選び、「追加設定」から以下のようにDLQを設定します。Datadogの API を読んだ時にエラーが返ってきていた場合は、設定したキューを確認することでレスポンスを確認できます。 Datadog Slack連携の設定 Slack通知を行うためには、あらかじめDatadogとSlack側で簡単な設定が必要です。本記事では設定方法の説明は割愛するので、公式ドキュメントを参考に設定を行ってください。 docs.datadoghq.com Event Monitorの設定 Datadogコンソールの左側のツリーから「Monitors」を選択し、遷移後画面の左上にある「+New Monitor」をクリックします。(もしくはブラウザに https :// .datadoghq.com/monitors/createを入力)作成画面では「Event」を選択します。 1. Define the search query を以下のように設定します クエリの欄に"event_name:ecs_service_autoscaled "を入力 "ecs_service_autoscaled"は入力テンプレートで独自定義したものなので、これで目的のイベントのみを拾います by句に"new_desired_count"と"ecs_service_name"を入力 サービス, スケールイベントごとに通知が飛ぶようにマルチモニター化します。また、ここでby句に指定したTagsのみが通知メッセージに埋め込み可能になります。 2. Set alert conditions を以下のように設定します Trigger when the evaluated value is " above or equal to "に設定 イベント発生を検知したいので データ点1 以上 でWarnアラートを飛ばせるように設定します Alert thresholdを適当な大きな値に設定 オートスケールイベントの通知はWarnレベルで行いたいので、Alertレベルの通知は行わないように、適当に大きな値にします。(本当はInfoレベルにしたいですが、2025/06現在ではDatadogの仕様上不可能と回答をサポートの方にいただきました) Warning thresholdを1に設定 イベントが起きる毎に通知を行いたいので、1に設定します 3. Configure notifications & automations をお好みで設定します 以下は設定例です。{{ }}で囲んである部分には、「EventのTagsに設定されている」かつ「by句で指定している」値のみ埋め込めます。通知をリッチにしたい場合は、 AWS のEventルール側で入力テンプレートの"tags"を充実させ、DatadogのEvent Monitor側のby句でも使用したいTagsを指定しましょう。また、メッセージ全体を{{#is_warning}}{{/is_warning}}で囲えば、Warn状態からの リカバリ 時の通知が飛ばなくなります。 以上で、オートスケールイベントが起きた時に、以下のようにSlackに通知が飛ぶようになりました。 おわりに とりあえずEventBridge -> Datadog -> Slackの通知ができそうだということで仕組みを作成してみましたが、思ったよりも通知がゴチャついてしまったなという印象があります。(通知タイトルに "on ecs_service_name:~"ってついてしまうなど)もしかしたら、以前紹介した Amazon Q Developer in chat applications(旧: AWS Chatbot)を使用した仕組み の方が、スマートに通知できたような気もしています。とはいえオートスケールイベントを通知するという目的は達成しており、監視・モニタリング系の管理をDatadogに集約することには運用上のメリットもあるので、そことの兼ね合いでもあると思います。ここら辺は今後のDatadogのアップデートにも期待ですね。 MNTSQ株式会社 SRE 西室
アバター
はじめに AWS Organizations にて複数の AWS アカウントを管理する場合において、各アカウントへどのようにアクセスするかは色々と検討の余地があると思います。 弊社では長らくこれを以下のような手法で運用していました。 踏み台的用途の AWS アカウントを1つ用意し、そこに作業者が使う IAM ユーザ及びスイッチ用 IAM ロールを用意 各 AWS アカウントにもスイッチ先 IAM ロールを用意 踏み台 AWS アカウント上の IAM ユーザから、作業対象 AWS アカウント上の IAM ロールへスイッチすることで、対象 AWS アカウントへアクセス 今回このあたりを全て IAM Identity Center を用いて以下のような運用に改めました。 IAM ユーザを考慮しなくてよくした IAM ロールも考慮しなくてよくした 全社的な ID 基盤(弊社では Entra ID )を IdP としての AWS への SSO ログイン及び各 AWS アカウントへのアクセスに寄せた これら作業にかかる考慮事項や実際の作業内容、設定完了後の利用感等、コード例を交えつつ解説します。 これまでの状況 構成 弊社における AWS アカウントの状況は以下の通りです。おおむね AWS のホワイトペーパー *1 に則った構成になっているはずです。 AWS Organizations で全ての AWS アカウントを一元管理 AWS アカウントの内訳は以下のとおり AWS Organizations 管理用 AWS アカウント 各 AWS アカウントへスイッチする為の IAM ユーザ / ロール等を管理する為の AWS アカウント プロダクトのワークロードが存在する AWS アカウント 開発環境 QA 環境 ステージング環境 本番環境 etc. 各種内部用途の為の AWS アカウント 全ての AWS アカウントへは 上述 2. に存在する IAM ユーザを使用し、 AWS アカウント間で IAM ロールをスイッチする構成をとっています。これを図にしたものが以下です。 スイッチロール構成。前述 2. を admin に、それ以外の AWS アカウントを member-X (X は適当な数)に、それぞれ置き換えている点に注意 課題 いっぽうで実際の運用上、弊社では以下のような課題感がありました。 IAM ユーザ管理コストが一定発生する 現存 IAM ユーザが在籍者に紐付くものか把握しておく必要がある 入退職や異動の際の IAM ユーザ管理作業が都度発生する IAM ロール(スイッチ元 / 先両方)の管理コストも一定生じる 現在は principal tag を使った制御 *2 にてスイッチ先 AWS アカウントおよび IAM ロールの制限を実施している 状況に応じ特殊な権限設定 / スイッチ先アカウント設定が必要になるケースがあり、タグ設計を検討する場合がある スイッチロール時の煩雑な操作を緩和するために外部ツールに頼る必要がある 実際に使っているもの Web ブラウザ: AWS Extended Switch Roles CLI : aws-vault 外部ツールを使うこと自体が問題なのではなく、ツールに依存性が生じる事(= コントールが難しい領域が発生する事)に課題感がある 3. については AWS が公式に提供 / メンテナンスをしているツール群のみの利用とできるのが理想です。 また 1. については、現在弊社では全社的な ID 基盤として Entra ID を使用しており、ここで管理されるユーザ情報が AWS で利用出来れば IAM ユーザを管理する必要がなくなるため、かなり嬉しくなれます。 ただし IAM ユーザのみを考慮した場合 2. がクリアできず、このあたりも吸収できるようなうまい策を考えなければなりません。 IAM Identity Center を導入する 前項で述べた課題感にほぼ対処できる策として IAM Identity Center があります。詳細はリンク先に譲るとして、これは早い話が AWS 上で利用できる ID 基盤です。 AWS Organizations とも特段複雑なことをせずとも連携できます。これを従来の IAM ユーザ / ロールの代わりに用いることで以下が達成できます。 外部 IdP をユーザ / グループの情報ソースとして利用が可能。当該ユーザを使用しての SAML 認証による SSO も実現可 IAM ユーザの管理が不要 IdP から連携されたユーザを用いることでユーザ管理の責務は IdP に移る IAM ロールの考慮も不要 IAM Identity Center 内には権限セット *3 という概念があり、どのような権限を認可するかを IAM ポリシドキュメントの形で設定可能 権限セットの効力範囲は AWS アカウント別に設定可能 実際に採った構成を以下に示します。 SSO 構成 作業 作業は以下の段取りで進める格好としました。 Entra ID と IAM Identity Center との連携設定投入 権限セットの整備 Entra ID から IAM Identity Center に連携されてきたユーザに対しての権限セット / AWS アカウント紐付け ユーザとグループが連携されているがグループは今回考慮していない。後述 1. のみ手作業での対応とし、他は Terraform にて IaC した状態で作業しています。 1. Entra ID と IAM Identity Center との連携設定投入 基本的には Configure SAML and SCIM with Microsoft Entra ID and IAM Identity Center に従うことで作業は完了となります。弊社では今回以下を前提としました。 Entra ID および AWS アカウントについては既存構成をそのまま使う Entra ID 側でのユーザ / グループへの変更が自動で IAM Identity Center へ連携されてくる構成 *4 とする 実際には前掲ドキュメント中の以下を適宜読み替えての実施としました。 Step 3: Configure and test your SAML connection Step 4: Configure and test your SCIM synchronization 本来このあたりも Terraform にて管理できるのが理想ですが、2025年5月現在 Terraform では IAM Identity Center 組織 インスタンス *5 の構成管理が出来ないため、本作業は手作業での実施としています。 2. 権限セットの整備 IAM Identity Center を利用してアクセスした各 AWS アカウント内でどういった操作を認可するかを定義するものが権限セット *6 です。利用者が IAM Identity Center 経由で各 AWS アカウントへアクセスする際に使う IAM ロール / ポリシの定義、という感覚で大方問題ないはずです(当方もその理解でいます)。 実際の定義方法も IAM Identity Center 側で事前に定義された権限セットから選択 IAM ポリシにおける AWS マネージドなものに大方対応する 既存 / 新設した IAM ポリシを割り当てる形式で設定 といった形で、概念としては IAM ロールの定義に似ています。弊社の事例では事前定義の権限セットで充分であった為、こちらを使っての設定としています。 ただしここには微妙に落とし穴があり、現在 IAM Identity Center が設定を推奨している identity-enhanced console session *7 を有効にする場合、事前定義の権限セットを素直に使うのみでは AWS アカウントへのアクセス時に HTTP 400 が発生して難儀することになります。 対処としては Granting permissions to use identity-aware console sessions で挙げられている IAM ポリシ ステートメント を権限セット側に設定すれば解決しますが、当該ドキュメントのコード例は resource に AWS アカウント ID を指定する厳格なもので、不特定多数の AWS アカウントへのアクセスが想定される IAM Identity Center とは食い合わせが悪いです。弊社では { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": " sts:SetContext ", " Resource ": " * " } ] } のように、少々ゆとりを持たせた ステートメント を使うようにしました。 3. Entra ID から IAM Identity Center に連携されてきたユーザに対しての権限セット / AWS アカウント紐付け Entra ID と IAM Identity Center との連携が無事に済むと、連携対象とした Entra ID 上のユーザとグループが IAM Identity Center 上で見えるようになります。これらに権限セットと操作対象 AWS アカウントとを設定してやれば晴れて Entra ID 側情報を利用しての AWS への SSO アクセスが可能となります。 ユーザとグループのどちらに権限セット / AWS アカウント割り当て設定を行うかは運用形態によって検討の余地があると思います。弊社では ユーザ単位での設定 としました。というのも以下事情が有ったためです。 Entra ID は社内の別部門が管理しており、IAM Identity Center と Entra ID とが密結合になるような状況は避けたかった Entra ID グループ単位での権限設計ではカバーが難しい例外的な設定が求められるケースが一部にあり Entra ID ユーザ単位での設定に利があった Entra ID ユーザ個別に権限セットや AWS アカウントの設定をおこなう煩雑さはコードレベルで可能な限り吸収するようにしました。 コード例 おまたせしました。IAM Identity Center で権限セットと AWS アカウントとを IAM Identity Center ユーザに紐付けする Terraform コード例を示します。実際に運用しているコードを元にしていますが適宜フィクションを交えての内容となる点はご容赦ください。 main.tf /* AWS Organizations で管理される AWS アカウント一覧を得るためのもの */ data "aws_organizations_organization" "main" {} /* IAM Identity Center インスタンスに対し必要な設定が行われる これは Terraform リソースでの管理が難しいので、設定は AWS マネジメントコンソール上から行い、Terraform からは参照に留める */ data "aws_ssoadmin_instances" "main" {} /* Identity-aware console sessions を有効にしている場合、以下を権限セットに設定する必要がある これが無いと IAM Identity Center 経由で AWS アカウントへログインができない */ data "aws_iam_policy_document" "identity_aware_sessions" { statement { actions = [ "sts:SetContext" ] resources = [ "*" # 不特定多数の AWS アカウントにログインするのでリソースを絞るのが難しい ] } } # 権限セットに割り当てる IAM ポリシ定義を取得 data "aws_iam_policy" "main" { for_each = local.permission_sets name = each.value.managed_policy_name } # 権限セットを定義する。権限セットへの IAM ポリシの割り当てもここでやる resource "aws_ssoadmin_permission_set" "main" { for_each = local.permission_sets name = each.key description = "Allow permissions defined as AdministratorAccess" instance_arn = tolist (data.aws_ssoadmin_instances.main.arns) [ 0 ] # セッションを8時間維持する。ISO 8601 の記法で記載 session_duration = "PT8H" } resource "aws_ssoadmin_managed_policy_attachment" "main" { for_each = local.permission_sets instance_arn = tolist (data.aws_ssoadmin_instances.main.arns) [ 0 ] managed_policy_arn = data.aws_iam_policy.main [ each.key ] .arn permission_set_arn = aws_ssoadmin_permission_set.main [ each.key ] .arn } resource "aws_ssoadmin_permission_set_inline_policy" "main" { for_each = local.permission_sets inline_policy = data.aws_iam_policy_document.identity_aware_sessions.json instance_arn = tolist (data.aws_ssoadmin_instances.main.arns) [ 0 ] permission_set_arn = aws_ssoadmin_permission_set.main [ each.key ] .arn } /* SCIM 経由で連携された Entra ID ユーザへ権限セットと AWS アカウントとを紐付ける 紐付け関係は locals.tf を参照 */ data "aws_identitystore_user" "main" { for_each = local.users identity_store_id = tolist (data.aws_ssoadmin_instances.main.identity_store_ids) [ 0 ] alternate_identifier { unique_attribute { attribute_path = "UserName" attribute_value = "$ { each.key } @$ { local.domain_name [ each.value.type ]} " } } } resource "aws_ssoadmin_account_assignment" "main" { # locals.tf 側 user_account_mapping も参照のこと for_each = tomap ( { for element in local.user_account_mapping : "$ { element.user } @$ { element.aws_account_id } " => element } ) instance_arn = tolist (data.aws_ssoadmin_instances.main.arns) [ 0 ] /* locals.tf 内で定義される local.users で admin = true が設定されている場合は Administrator 一択 そうでない場合は同 local.permission で定義されている権限セットを指定 */ permission_set_arn = ( try (each.value.admin, false ) ? aws_ssoadmin_permission_set.main [ "Administrator" ] .arn : aws_ssoadmin_permission_set.main [ local.permissions [ each.value.type ][ each.value.role ]] .arn ) principal_id = data.aws_identitystore_user.main [ each.value.user ] .id principal_type = "USER" target_id = each.value.aws_account_id target_type = "AWS_ACCOUNT" } locals.tf locals { permission_sets = { Administrator = { description = "Allow permissions defined as AdministratorAccess" managed_policy_name = "AdministratorAccess" } Developer = { description = "Allow permissions defined as PowerUserAccess" managed_policy_name = "PowerUserAccess" } ReadOnly = { description = "Allow permissions defined as ReadOnlyAccess" managed_policy_name = "ReadOnlyAccess" } } } locals { # 職責の一覧 roles = { sre = "sre" # SRE swe = "swe" # 開発(フロントエンド / バックエンド) algo = "algo" # Algo qa = "qa" # QA cre = "cre" # CRE pdm = "pdm" # PdM sales = "sales" # セールス cs = "cs" # CS } # 雇用区分の一覧 types = { employee = "employee" # 正社員 partner = "partner" # 業務委託 } /* Entra ID 上で設定されるユーザ名で使用されるドメイン部 もちろん実際に MNTSQ 内で使われるものとは一致しない */ domain_name = { employee = "example.com" partner = "partner.example.com" } } locals { environment = { production = [ /* プロダクトのワークロードが載っている AWS アカウントで本番環境として扱うべきものを列記 AWS アカウント名が期待される */ ] non_production = [ /* プロダクトのワークロードが載っている AWS アカウントで本番環境として扱う必要は無いものを列記 AWS アカウント名が期待される */ ] } aws_accounts = { # AWS Organizations 管理下にある全ての有効な AWS アカウント for account in data.aws_organizations_organization.main.accounts : account.name => account.id if account.status == "ACTIVE" } } /* 雇用区分と職責とによってどの権限セットをアサインするかを決定する 内訳 - permissions:職責と権限セットとの対応 - mappings:職責と AWS アカウントとの対応 - users:ユーザ(Entra ID 管理) / 雇用区分 / 職責の対応 */ locals { permissions = { /* 雇用区分と職責とで異なる権限セットを設定できるようにする: - 雇用区分:local.types を参照 - 職責:local.roles を参照 */ employee = { sre = "Administrator" swe = "Developer" algo = "Developer" qa = "Developer" cre = "Developer" pdm = "Developer" sales = "ReadOnly" cs = "ReadOnly" } partner = { sre = "Developer" swe = "Developer" algo = "Developer" qa = "Developer" } } mappings = { /* 職責によって IAM Identity Center 経由でアクセス可能な AWS アカウントを制御する 職責の定義は local.roles を参照 */ employee = { sre = values (local.aws_accounts) swe = concat ( local.environment.production, local.environment.non_production ) algo = concat ( local.environment.production, local.environment.non_production ) qa = concat ( local.environment.production, local.environment.non_production ) cre = concat ( local.environment.production, local.environment.non_production, ) pdm = concat ( local.environment.production, local.environment.non_production, ) sales = local.environment.production, cs = local.environment.production, } partner = { sre = local.environment.non_production, swe = local.environment.non_production algo = local.environment.non_production qa = local.environment.non_production cre = local.environment.non_production, } } users = { /* **Entra ID で** 管理されるユーザであって AWS を利用する者を宣言する ユーザ名はドメイン名を省く形で指定する ユーザ名をキーとし、値には以下をもつ map を宣言する: - 雇用区分 (type):local.types を参照 - 職責 (role):local.roles を参照 - 操作許可 AWS アカウント内で Administrator 権限が必要な場合は admin を true に設定 */ # # <名>.<姓> でユーザ名が定義されるとする "taro.sre" = { type = local.types.employee, role = local.roles.sre } , "hanako.swe" = { type = local.types.employee, role = local.roles.swe, admin = true } , "jiro.swe" = { type = local.types.partner, role = local.roles.swe } , "saburo.qa" = { type = local.types.employee, role = local.roles.qa, } , "shiro.algo" = { type = local.types.employee, role = local.roles.algo } , "goro.sales" = { type = local.types.employee, role = local.roles.sales } , } } /* IAM Identity Center 上でのユーザ / 権限セット / AWS アカウントとの紐付けには aws_ssoadmin_account_assignment というリソースを使う https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssoadmin_account_assignment このリソースはユーザと AWS アカウントを一度にそれぞれ1つしか設定できない 窮余の策として以下値をもつ list を定義し、当該リソースの for_each では "USERNAME@AWS_ACCOUNT_ID" をキーとする map として処理する: - ユーザ名 (user)(例:"hoge.fuga") - 職責 (role)(例:"sre") - 雇用区分 (type):"employee"(正社員)または "partner"(業務委託) - AWS アカウント ID (aws_account_id) 参考:https://developer.hashicorp.com/terraform/language/functions/flatten */ locals { user_account_mapping = flatten ( [ for user_key, user in local.users : [ for aws_account_id in local.mappings [ user.type ][ user.role ] : { user = user_key role = user.role type = user.type admin = try (user.admin, false ) aws_account_id = aws_account_id } ] ] ) } 使用感 Web ブラウザ IAM Identity Center のセットアップが無事に完了すると IAM Identity Center 設定時に指定した AWS アクセスポータル *8 の URL から SSO ログインが行えます。 権限セットや AWS アカウントが割り当ていない状態でもアクセスは可能ですが、 AWS アカウントへはアクセスできません。一方で両者が設定されている場合は SSO ログイン後に AWS アカウントの選択肢が表示され、設定された権限セットでもってのアクセスが可能となります。 実際に AWS アクセスポータルへアクセスした際の状況は以下のようになります。伏字が多い点はご寛容いただければと思います。 権限セット / AWS アカウント割当前 権限セット / AWS アカウント割当後 AWS アクセスポータルから各 AWS アカウントへアクセスした後はいつものマネジメントコンソール上での操作になります。アクセス先を切り替える為には AWS アクセスポータルへの都度アクセスが必要ですが、都度アクセスの面倒を避けたい向きに サードパーティ の拡張がいくつか提供されています。本稿筆者は AWS SSO Extender を使用しています *9 。 CLI 新しめの AWS CLI を使っている限りにおいて Configuring IAM Identity Center authentication with the AWS CLI に従い設定を済ませれば、以後は aws sso login --profile <プロファイル名> を都度実行することで認証が済み、 aws コマンドでの操作が可能です。これはクレデンシャル管理が必要であった IAM ユーザ利用に比べ大きなメリットと言えるでしょう。 AWS アカウントの切り替えも --profile オプションの指定ないしは 環境変数 AWS_PROFILE の宣言で済みます。 AWS CLI の設定内容については aws configure sso を使い都度設定してゆく方法もありますが、弊社では各職責毎にテンプレート化した設定ファイルを用意し、各作業者が利用している PC に設定を保存してもらう方針をとりました。 ほか Terraform に関しても AWS CLI 向けの設定が済んでいれば aws sso login が完了している前提にて特段支障なく操作が可能です。ただし Terraform 1.6.0 以前では AWS CLI 用の設定に改変を加える必要があり *10 、弊社でも当初この事象を踏みました。最終的には Terraform バージョンを最新版(作業時点で 1.11.4)にしてしまうことで対処としました。 おわりに 複数の AWS アカウントを管理する構成において IAM ロールをスイッチする運用を改め、IAM Identity Center による一元的かつ柔軟な AWS アカウント横断のアクセスを可能とするための作業とその後の所感について扱いました。 現在弊社では IAM Identity Center による AWS への SSO ログインの利用浸透を全社で行っており、これが完了した暁には個々人向けに払い出していた IAM ユーザおよびスイッチロール用の IAM ロールの削除をし、IAM Identity Center 経由での AWS アクセスに完全に移行するよう計画しています。 ユーザから見た場合に各 AWS アカウントへのアクセスが透過的になるのも IAM Identity Center 導入での嬉しさのひとつですが、IAM Identity Center 自体が IdP としての利用も可能であるという点から、SRE 主体での他のサービスへの SSO 化の試みについても着手し易くなってきました。今後は社内で独自のユーザ管理体系が存在する諸々のサービスを着実に SSO 化し、利用者の利便性向上やセキュアな体制構築に繋げてゆこうと計画しています。 IAM Identity Center と Entra ID との組み合わせ自体は然程珍しいものではなく、Web 上にも同様の事例が多数みられます。本稿がそれらのひとつとして数えられ、ひとつの事例としてお役に立てれば幸いです。 MNTSQ 株式会社 SRE 秋本 *1 : Organizing Your AWS Environment Using Multiple Accounts *2 : Controlling access to and for IAM users and roles using tags に詳細があります。弊社では IAM ユーザに所定のタグを設定し、スイッチ先 IAM ロールでは trust policy 内でタグを評価し、タグのキーと値とが上限に合致する場合にスイッチを認可する、といった設定で運用している *3 : Manage AWS accounts with permission sets *4 : 自動プロビジョニング。 Provisioning an external identity provider into IAM Identity Center using SCIM が詳しい *5 : ここ が詳しい。IAM Identity Center にはその設定や効力範囲を制御するための インスタンス という概念があり、 AWS Organizations 管理者アカウントにひもづく インスタンス を組織 インスタンス 、同メンバーアカウントにひもづく インスタンス をアカウント インスタンス という。組織 インスタンス で IdP 連携や SSO 時の設定等を管理する *6 : Manage AWS accounts with permission sets *7 : 本文中で後述している "identity-aware" の語が本記事にかかる設定作業を実際に行っていた2025年4月時点では使われていたが、2025年5月時点では "identity-enhanced" に変わっていた *8 : Using the AWS access portal *9 : 外部ツールに頼らなくしたいという動機はどこにいったのだというツッコミはその通りです…… *10 : https://sadayoshi-tada.hatenablog.com/entry/2023/10/06/001405 が詳しいです。事例公開多謝です
アバター
はじめに Redis ログについて slow ログ engine ログ 設定方針 構成 Datadog Logs 向けに slow ログを修正する インフラ構成 Datadog Logs の風景 おわりに 参考 はじめに 弊社では Web アプリケーションや非同期処理用ジョブの一時データなどを取り扱う際に ElastiCache Redis をインメモリデータベースとして利用しています。 これまで ElastiCache Redis 自体の稼動状況の把握には CloudWatch メトリクスを中心とした数値ベースの情報のみを利用していましたが、今回ここにログ情報を加えることにしました。ログ取得に際し必要となった作業について扱います。 簡単のため、以降は特に断わりなく "ElastiCache Redis" を単に "Redis" と呼びます。 Redis ログについて Redis ログには slow / engine の2種類があります。ElastiCache Redis の場合、ログのフォーマットは JSON およびプレーンテキストの2種類を選べますが、以下では JSON ログを前提とします。 なお slow ログは Datadog Logs へのログ送信に際し注意が必要 です(後述)。 slow ログ 「遅い処理」を記録するものです。詳細は Redis 公式のドキュメント を参照ください。 何をもって遅い処理とするかは ElastiCache Redis の場合、パラメタグループ内 slowlog-log-slower-than にてマイクロ秒単位で決定されます。また slow ログの収集は ElastiCache Redis 6.0 以上のバージョンから可能です。 ログ例は ElastiCache 公式ドキュメント で示される通り、以下のようになります。S3 へ保存されるログもこの形式になります。記録対象のログ1件に対し JSON オブジェクトが1件対応します。 { " CacheClusterId ": " logslowxxxxmsxj ", " CacheNodeId ": " 0001 ", " Id ": 296 , " Timestamp ": 1605631822 , " Duration (us) ": 0 , " Command ": " GET ... (1 more arguments) ", " ClientAddress ": " 192.168.12.104:55452 ", " ClientName ": " logslowxxxxmsxj## " } 各要素は以下のような意味合いになります。前掲ドキュメントの内容を意訳し、適宜補足を加えています。 CacheClusterId:Redis クラスタ 名 CacheNodeId:Redis ノード名(上記 クラスタ 内にあるはず) Id:各ログ固有の識別子 Timestamp: 当該ログが Redis 内で記録された際の 日時 Datadog でログが処理された時刻とは異なるので注意 Duration (us):ログで記録された処理に要した処理時間(マイクロ秒) Command:Redis で処理されたコマンドの内容 ClientAddress:Redis にコマンドを発行したクライアントの IP 通常これは ECS タスクなどが持つ VPC 内 IP になるはず ClientName:Redis にコマンドを発行したクライアントの名前 engine ログ Redis そのものの稼動状況を示すログになります。engine ログの収集は ElastiCache Redis 6.2 以上のバージョンから可能です。これも ElastiCache 公式ドキュメント から例を引用します。S3 側に保存される状況は slow ログと同様です。 例: { " CacheClusterId ": " xxxxxxxxxzy-engine-log-test ", " CacheNodeId ": " 0001 ", " LogLevel ": " VERBOSE ", " Role ": " M ", " Time ": " 12 Nov 2020 01:28:57.994 UTC ", " Message ": " Replica is waiting for next BGSAVE before synchronizing with the primary. Check back later " } 内容については以下となります。 CacheClusterId:Redis クラスタ 名 CacheNodeId:Redis ノード名(上記 クラスタ 内にあるはず) LogLevel:ログの重要性の度合い VERBOSE:雑多なやつ NOTICE:見ておくほうがよいやつ WARNING:真剣に見ておくほうがよいやつ Time: 当該ログが Redis 内で記録された際の 日時 Datadog でログが処理された時刻とは異なるので注意 Role:ログを出力した主体 M:プライマリノード S:レプリカ( セカンダリ )ノード C:ディスクにデータが書き出されたとき より詳細には RDB / AOF 処理が発生したとき( Redis 公式ドキュメントの "Redis persistence" が詳しい) X:フェイルオーバが発生したとき より詳細には Redis Sentinel が動いたとき( Redis 公式ドキュメントの "High availability with Redis Sentinel " が詳しい) 設定方針 既に稼動中の Redis クラスタ ( レプリケーション グループ)はそのまま利用し、ログ取得のための設定を追加で入れます。 ログの出力先については S3 バケット 及び Datadog Logs を選定しました。これは以下のような事情によります。 Datadog Logs:弊社ではログ管理(集約 / 検索 / 監視 etc.)を Datadog で一元化しており、Redis ログも Datadog 上で取り扱えると都合がよい S3:基本的には Datadog 上でログを取り扱うが、Datadog に万一の事態が生じた場合に Athena 等でログを扱えるようにしておきたい いっぽうで ElastiCache ログ取得に関する AWS のドキュメント によればログ出力先には Data Firehose と CloudWatch Logs の2つの選択肢があります。どちらが適切かという話になりますが、以下のとおり Data Firehose を使っておくのがベターなようです。 Redis ログ出力先 メリット デメリット Data Firehose ● S3 をバックアップ先に指定することで S3 へのログ転送も達成可( ドキュメント ) ● Datadog が公式にData Firehose によるログ転送を解説している( ドキュメント ) ● CloudWatch Logs へはログが飛ばない CloudWatch Logs ● S3 と Datadog Logs に加え CloudWatch Logs でのログ取扱が可能になる(CloudWatch Logs Insights が使える) ● S3 / Datadog Logs 向けに サブスクリプション フィルタと別途ログ送信用リソースの用意が必要になる よって本稿では Redis ログを Data Firehose へ出力し、Data Firehose から S3 および Datadog Logs へログを送信するような構成をとることとしました。 構成 構成図は以下の通りです。slow / engine 各ログは最終目的地が同一の S3 バケット / Datadog Logs であることから、Data Firehose ストリームは特に分けない構成としました。S3 バケット は Redis ログ用に新設したものを使います。 構成図 いきなり Data Firehose に Lambda 関数がくっつく構成となっていますが、これには事情があります。 Datadog Logs 向けに slow ログを修正する 当初、上記構成のうち Lambda 関数を設けず(つまり各ログを無加工の状態で) Redis ログを送信した際、slow ログのみ Datadog Logs でログとして取り扱えない状況となりました。Data Firehose から Datadog へのログ送信は問題無い様子だったので、Datadog がログを正しく処理できていない模様です。 トラブルシュートに難儀したのですが、ヒントは Datadog ドキュメントのうち Log Management -> Log Configuration -> Pipelines にありました。関連する箇所を同ドキュメントから要約します: JSON ログ中に Timestamp という attribute がある場合、Datadog Logs はその情報をログの時刻情報として扱う 取り扱える日付のフォーマットは ISO8601 / ミリ秒精度の UNIX 時間 / RFC3164 JSON ログ中に存在する Message の内容をログメッセージ本体として扱う ここで各ログの内容を見てみましょう。前掲のログ例を引用します。 slow ログ { "CacheClusterId": "logslowxxxxmsxj", "CacheNodeId": "0001", "Id": 296, "Timestamp": 1605631822, "Duration (us)": 0, "Command": "GET ... (1 more arguments)", "ClientAddress": "192.168.12.104:55452", "ClientName": "logslowxxxxmsxj##" } engine ログ { "CacheClusterId": "xxxxxxxxxzy-engine-log-test", "CacheNodeId": "0001", "LogLevel": "VERBOSE", "Role": "M", "Time": "12 Nov 2020 01:28:57.994 UTC", "Message": "Replica is waiting for next BGSAVE before synchronizing with the primary. Check back later" } 2ログを見比べると以下がわかります。 slow ログ Timestamp に UNIX 時間( ミリ秒精度ではない )と解釈できる値が入っている Message が無い Command に Redis へ発行されたコマンドが記録されている engine ログ Timestamp は無い Message がある engine ログが Datadog Logs で処理できているので、slow ログを engine ログと同様の内容にするのが手っ取り早いでしょう。つまり以下のようにすると光明が見えそうです。 { " CacheClusterId ": " logslowxxxxmsxj ", " CacheNodeId ": " 0001 ", " Id ": 296 , " Time ": 1605631822 , # 元 ` Timestamp ` " Duration (us) ": 0 , " Message ": " GET ... (1 more arguments) ", # 元 ` Command ` " ClientAddress ": " 192.168.12.104:55452 ", " ClientName ": " logslowxxxxmsxj## " } Data Firehose はストリーム内を流れるデータを Lambda 関数を使い処理できる機能があります。弊社では以下のような TypeScript コードを Node.js ランタイムを使う Lambda 関数として整備し、上記変換処理を行わせることにしました。 コードはこちらを参照 import { Buffer } from 'buffer' ; import { DateTime } from 'luxon' ; console . log ( 'Loading function' ); interface InputRecord { recordId : string ; data : string ; } interface Event { records : InputRecord [] ; } interface OutputRecord { recordId : string ; result : string ; data : string ; } interface Output { records : OutputRecord [] ; } export const handler = async ( event : Event ): Promise < Output > => { const output = event.records. map (( record , index ): OutputRecord => { const decoded = Buffer .from(record.data, 'base64' ). toString ( 'utf-8' ); console .log( `Decoded data: ${ decoded } ` ); /* * event として Data Firehose から受け取ったペイロードが JSON ログでない場合は後続処理をさせない * ElastiCache ログは Data Firehose へ JSON の形式で送るよう設定しているので通常はここに落ちないはず */ let parsed: any ; try { parsed = JSON . parse (decoded); } catch (error) { console .error( `Error parsing JSON for record ${ record.recordId } :` , error); return { recordId : record.recordId, result : 'Ok' , data : record.data, } ; } /* * Timestamp というエントリは Datadog Logs が処理する上で不適。以下のように直す * (参考:https://docs.datadoghq.com/logs/log_configuration/pipelines/?tab=date) * * - Time というエントリに改名 * - 元々 Timestamp として記録されていた epoch 秒を engine ログと同じような日付フォーマットの文字列に変換 * - engine ログと互換性のある内容にしたい */ if ( 'Timestamp' in parsed) { const dt = DateTime.fromSeconds(parsed.Timestamp, { zone : 'utc' } ); parsed.Time = dt.toFormat( "dd MMM yyyy HH:mm:ss.SSS 'UTC'" ); delete parsed.Timestamp; } /* * Message というエントリが無いと Datadog Logs 上でログとして受け付けてくれない * 拾いたい情報である "Command" の内容を Message に改名する * これも engine ログと同様の措置 * (参考:https://docs.datadoghq.com/logs/log_configuration/pipelines/?tab=message) */ if ( 'Command' in parsed) { parsed.Message = parsed.Command; delete parsed.Command; } console .log( `Transformed data: ${ JSON . stringify (parsed) } ` ); const encoded = Buffer .from( JSON . stringify (parsed)). toString ( 'base64' ); return { recordId : record.recordId, result : 'Ok' , data : encoded, } ; } ); console .log( `Processing completed. Successful records: ${ output. length} ` ); return { records : output } ; } ; インフラ構成 Terraform コード例を示します。実際に利用しているコードから要所を抜粋 / 省略したものとなります。 コードはこちらを参照 # Data Firehose 内でのログ処理に使用する Lambda 関数のソースコード # Terraform とは別の経路で事前に S3 バケットへアップロードしておく data "aws_s3_object" "lambda_source_archive" { bucket = var.bucket_name key = "transform-elasticache-logs/artifact.zip" } # Lambda 関数にアタッチされる IAM ロールで使用する IAM ポリシ # 当該関数内ではシンプルに JSON データをいじるのみで AWS サービスを触らない # 最低限の権限(CloudWatch Logs へのログ書き出し)をもつ IAM ポリシだけ参照できるようにする data "aws_iam_policy" "lambda_basic_role" { name = "AWSLambdaBasicExecutionRole" } # Lambda 関数本体 module "lambda_transform_elasticache_logs" { source = "terraform-aws-modules/lambda/aws" version = "7.20.2" function_name = "$ { var.env } -transform-elasticache-logs" description = "Lambda function for transforming slow log from Elasticache to appropriate format suitable for Datadog Logs" handler = "main.handler" runtime = "nodejs22.x" timeout = 60 publish = true attach_policy = true create_package = false policy = data.aws_iam_policy.lambda_basic_role.arn s3_existing_package = { bucket = var.bucket_name key = "transform-elasticache-logs/artifact.zip" version_id = data.aws_s3_object.lambda_source_archive.version_id } assume_role_policy_statements = { account_root = { effect = "Allow" actions = [ "sts:AssumeRole" ] principals = { account_principal = { type = "AWS" identifiers = [ "arn:aws:iam::$ { data.aws_caller_identity.self.account_id } :root" ] } } } } tags = { Name = "$ { var.env } -transform-elasticache-logs" } } # Data Firehose 向け principal を assume する data "aws_iam_policy_document" "elasticache_log_assuming" { statement { actions = [ "sts:AssumeRole" ] effect = "Allow" principals { type = "Service" identifiers = [ "firehose.amazonaws.com" ] } } } # Data Firehose が所定の S3 バケットにログを流し込めるようにする data "aws_iam_policy_document" "elasticache_log_permission" { statement { effect = "Allow" actions = [ "s3:AbortMultipartUpload" , "s3:GetBucketLocation" , "s3:GetObject" , "s3:ListBucket" , "s3:ListBucketMultipartUploads" , "s3:PutObject" , ] resources = [ aws_s3_bucket.main.arn, "$ { aws_s3_bucket.main.arn } /*" , ] } # ログ送信のためには CloudWatch Logs 関連の権限も必要そう statement { effect = "Allow" actions = [ "logs:CreateLogStream" , "logs:PutLogEvents" , "logs:CreateLogGroup" , ] resources = [ "*" ] } } # ElastiCache ログを Data Firehose 経由で配信する為の IAM ロール resource "aws_iam_role" "main" { name = "$ { var.env } -elasticache-log-role" assume_role_policy = data.aws_iam_policy_document.elasticache_log_assuming.json } # ElastiCache ログを Data Firehose 経由で配信する為の IAM ロールにポリシーをアタッチ resource "aws_iam_role_policy" "main" { name = "$ { var.env } -elasticache-log-policy" role = aws_iam_role.main.id policy = data.aws_iam_policy_document.elasticache_log_permission.json } # ログ処理用 Data Firehose ストリーム resource "aws_kinesis_firehose_delivery_stream" "main" { depends_on = [ aws_s3_bucket.main, ] name = "$ { var.env } -redis-log" # S3 と Datadog Logs とにログを送信する # S3 にログを送るのみであれば extended_s3_configuration が使えるが、その場合 Datadog 向けの設定と共存不可 destination = "http_endpoint" http_endpoint_configuration { url = "https://aws-kinesis-http-intake.logs.datadoghq.com/v1/input" name = "Datadog Log" # 以下 `DATADOG_API_KEY` は適切な値に置き換える access_key = DATADOG_API_KEY role_arn = aws_iam_role.main.arn # 今回は Datadog Logs と同内容のログを S3 にも置きたいという目的につき、全ログを S3 に置く設定とした s3_backup_mode = "AllData" request_configuration { content_encoding = "GZIP" # これら値を設定しておくことで Datadog Logs 上でタグが付与される common_attributes { name = "env" value = var.env } } s3_configuration { role_arn = aws_iam_role.main.arn bucket_arn = aws_s3_bucket.main.arn prefix = "redis/" } # ログ整形用 Lambda 関数定義 processing_configuration { enabled = "true" processors { type = "Lambda" parameters { parameter_name = "LambdaArn" parameter_value = module.lambda_transform_elasticache_logs.lambda_function_arn } } } } tags = { "LogDeliveryEnabled" = "true" } } # Redis クラスタ resource "aws_elasticache_replication_group" "main" { depends_on = [ aws_kinesis_firehose_delivery_stream.elasticache_log, ] replication_group_id = "$ { var.env } -redis" description = "Redis replication group" engine = var.elasticache.engine engine_version = var.elasticache.engine_version node_type = var.elasticache.instance_type port = var.elasticache.port subnet_group_name = var.elasticache.subnet_group_name parameter_group_name = aws_elasticache_parameter_group.main.name num_cache_clusters = var.elasticache.instance_num at_rest_encryption_enabled = true apply_immediately = true security_group_ids = var.elasticache.security_group_ids # slow ログ(後述) log_delivery_configuration { destination = aws_kinesis_firehose_delivery_stream.main.name destination_type = "kinesis-firehose" log_format = "json" log_type = "slow-log" } # engine ログ(後述) log_delivery_configuration { destination = aws_kinesis_firehose_delivery_stream.main.name destination_type = "kinesis-firehose" log_format = "json" log_type = "engine-log" } } # Redis パラメタグループ # ログ取得は Redis 6.x 系で利用出来る機能につき、パラメタグループも Redis 6.x 系を対象にする resource "aws_elasticache_parameter_group" "main" { name = "$ { var.env } -redis6" family = "redis6.x" /* どのくらい処理に時間が掛かれば slow な処理として slow log へ出すかを制御する マイクロ秒単位で設定する。デフォルトは 10000(= 10ミリ秒) */ parameter { name = "slowlog-log-slower-than" value = 10000 } } # ログ保存先 S3 バケット resource "aws_s3_bucket" "main" { bucket = "$ { var.env } -elasticache-log" } 必要になるリソースは以下の通りです。 当然用意が必要なもの S3 バケット Redis クラスタ ( レプリケーション グループ) Data Firehose ストリーム 注意が必要なもの Data Firehose が使用する IAM ロールと権限認可のための IAM ポリシ firehose.amazonaws.com な principal を assume できるよう設定 S3 バケット / オブジェクトを変更含め操作する為の権限を設定 Data Firehose は転送エラーを CloudWatch Logs へ記録するので CloudWatch Logs 向けの権限も設定 Redis パラメタグループ slowlog-log-slower-than というパラメタで slow ログに記録する対象となる処理時間の 閾値 を設定(マイクロ秒単位) ここで指定した時間以内で終わる処理はログに記録されないので注意 Lambda 関数 「Datadog Logs 向けに slow ログを修正する」の節で述べた通り slowlog-log-slower-than パラメタによって slow ログとしての記録がなされるか否かについては見落しがちなので注意が必要です。遅い処理が存在しない場合ログにも記録されません。Redis / Data Firehose / S3 各所の設定は問題ないはずなのに何故ログが出ないのだと悩む羽目にならないよう、適当な 閾値 を設定しておきましょう。 Datadog Logs の風景 実際に Datadog Logs 上で収集されたログをお見せできればと思います(所々伏せ字なのはご容赦ください) slow ログ engine ログ 既に扱った通り、slow / engine ログはログ中に自身が slow / engine ログである旨を示す情報を持っていません。各ログに固有の情報でログの種類を判定するのが手軽です。弊社では以下のように @Role の有無でログ判別を行うような view を Datadog Logs へそれぞれ追加し、運用しています。 slow ログ: service:aws -@Role:* engine ログ: service:aws @Role:* Data Firehose + Lambda によりログ内容の調整が可能な構成になっていることから、必要に応じてログ中へ @type: slow といったような値の盛り込みも検討できる状態ではあります。これは運用を続けてみてからの判断となるでしょう。 おわりに ここまで本稿を読んで頂き有り難うございました。Data Firehose を使用して ElastiCache Redis ログを S3 及び Datadog Logs で取り扱う為の設定について扱いました。 engine ログと slow ログとで Datadog Logs へのログ送信に際し検討すべきことに差が出るのは想定外でしたが、最終的には狙った結果を得ることが出来ました。 ログは取得を始めて直ちに何らか嬉しさが生じるようなものではありませんが、月日が経過し記録と実績が蓄積されゆく中で自ずとその価値や用途が見えてくるものとは成り得るはずです。不確定要素の多い将来への投資としての一助となれば幸いです。 MNTSQ 株式会社 SRE 秋本 参考 Log delivery - Amazon ElastiCache Send AWS Services Logs with the Datadog Amazon Data Firehose Destination Understand data delivery in Amazon Data Firehose - Amazon Data Firehose [アップデート] ElastiCache for RedisのスローログをCloudWatch LogsやKinesis Data Firehoseにパブリッシュできるようになりました! | DevelopersIO
アバター
SREチームマネージャーの藤原です。 LM Studio + Gemma 3 + Cline + VSCode の環境を自由研究的に試用したので、その報告エントリです。 モチベーション プライベートでコードを書く際も最近はClineなどを使ってLLMを使ってコーディングをすることが徐々に増えてきました。 VSCode とClineを組み合わせて外部サービスをつかってコードの変更作業を実施する場合、 何かコードの変更を依頼するたびに、財布の中身から少しずつお金が溢(こぼ)れていく感覚があるでしょう。 1回1回の額は少額とはいえ、多数回繰り返すとなかなかの金額になってきます。 会社では予算の範囲内であれば、利用できますが、個人開発の場合はなかなか躊躇してしまうこともあるでしょう。 また、先日 Google が公開したオープンなローカルLLMのGemma 3も話題になったりしています。 そこで API 課金に怯えることなくLLMを活用したコーディングができないか?ということでGemma3を使ったコーディングにチャレンジしてみました。 やったこと Clineからローカルマシンで動かしているGemma 3のモデルを利用してコードを作成させてみる。 試行に用いた環境 昨年くらいにインフルエンザにかかって熱に浮かされた際に購入したマシンで試してみます。 HP ZBook Fury 16 G9 CPU: Core i9 12950HX メモリ: 64GB GPU : NVIDIA A4500 Laptop GPU 16GB OS: Ubuntu 22.04 あらかじめ、GEMMA 3を動かす上で必要となる NVIDIA の プロプライエタリ ドライバや、CUDAは導入済みです。 また ファイアウォール 等も設定済みです。 環境構築 LM Studioの導入 ローカルLLMを動かすためのツールとしてLM Studioを導入します。 LM Studioの公式ページ にアクセスして、LM Studioをダウンロードします。 lmstudio.ai Linux 向けには、 AppImage 版が用意されており、libfuse2さえインストールされていればシングルバイナリで動作するようになっています。 Linux 向けLM Studioのダウンロード画面 ダウンロードしたAppImageファイルを実行できるようにファイルのプロパティを変更します。 ファイルに実行権限を付与しましょう。 ダウンロードしたファイルのアイコンを右クリックして、ファイルのプロパティを開きます。 アクセス権のタブを開いて、プログラムとして実行可能の チェックボックス にチェックを入れます。 ダウンロードした LM Studioのバイナリファイルのプロパティ これでLM Studioの導入は完了です。 それでは、アイコンをダブルクリックしてLM Studioを立ち上げてみましょう。 LM Studio Gemma 3の取得と設定 LM Studioの画面下部でDeveloperに設定します。 画面下部の設定項目 ウィンドウ左側の虫眼鏡アイコンを選択します。 モデルの検索画面が表示されるので、Gemma 3を検索します。 LM Studioのモデル検索画面 検索結果から Gemma3 4Bを選択してダウンロードします。 元の画面に戻りターミナル風のアイコンをクリックします。 Status: Stopped となっているトグルスイッチを切り替えるとCline等からアクセスするためのサーバーが起動します。 Settings ドロップダウンメニューから追加の設定ができます。 他のマシンからアクセスする際は ローカルネットワークでサービング を有効にします。 LM Studioのサーバー設定 次にサーバー経由で提供するモデルを読み込んでおきます。画面上部の 読み込むモデルを選択 ドロップダウンメニューを選択します。 ダウンロード済みのモデル一覧が表示されるので、Gemma 3 4B Instructを選択します。 読み込むモデルの一覧 モデルの読み込みが完了しましたが、このままではコンテキスト長が不足しているため、Cline経由で利用できません。 右側のモデルの設定画面から Load タブを開いて、コンテキスト長を設定します。 ここでは、50000を設定しました。 モデル利用の設定 設定を変更したので 変更の適用 ボタンをクリックして設定変更を反映します。 Info タブを開くと接続に必要な情報が表示されます。 LM Studioへの接続情報 VSCode (Cline)の設定 VSCode を起動し、 拡張機能 でClineを検索してインストールします。 拡張機能 の マーケットプレイス でClineを検索 VSCode の左側のClineのアイコンをクリックします。 VSCode 上のClineアイコン Clineの画面が開くので画面右上の歯車アイコンをクリックして設定画面を開きます。 Clineの画面抜粋 Clineの設定画面が表示されるので、LM Studioに接続するように設定します。 API Providerに LM Studio を選択します。 Base URLにはLM StudioでGamma 3を設定した際に表示された接続情報を設定します。 ここまで設定すると、利用可能なモデルが表示されるので gemma-3-4b-it を選択します。 Clineの設定画面 ここまで設定したら Done ボタンをクリックして設定完了です。 では試みに動かしてみましょう。 動かしてみる Clineを使って Fizz Buzz の bash スクリプト を書かせてみました。 Fizz Buzzを書かせてみる いくつかのプロンプトや何度かの指示出しを試してみましたが、残念ながら gemma-3-4b-it では正しい Fizz Buzzプログラムを出力するまでは、結構な手数が必要でした。 プロンプトの書き方についてはまだまだ改良の余地はありそうなのでこの辺りを磨いていくことで改善はできるかもしれません。 速度的には、外部のサービスを利用するよりもかなり高速に動作しました。試行錯誤する観点では、かなりストレス少なく利用できると思います。 また、(ハードがすでにあれば)電気代のみなので、その点ではコストを気にすることなく安心して利用できました。 モデルを変えた場合どうなるかを検証する目的で gemma-3-12b-it を使っての出力も試してみました。 デフォルトの設定のままでは十分な トーク ン数(Clineの要求するだけのもの)を設定することができませんでした。 一方で、 gemma-3-12b-it が動作するよう設定を変更した場合は正しく動作し、 Fizz Buzzプログラムを正しく生成することはできましたが、実用的とはいいがたい出力速度でした。 まとめ 今回は試みにGemma 3 + LM Studio + Cline + VS Code で生成AIをつかったコーディングができないかを検証してみました。 最低限は動作するところまでは確認できました。 今後もモデルの改良は続くでしょうし、将来的にはより使いやすいものになることは間違いないので、時間を見つけて新しいモデル活用なども含めて試行錯誤を重ねる価値はありそうです。
アバター
はじめに MinIOについて データ移行の要件 データ移行手順 帯域制御の方法 おわりに はじめに MNTSQでSREチームに所属している中岡です。 昨今ではコンテナ技術を使用してアプリケーションを稼働させることが一般的になっています。 コンテナが稼働する環境であれば、理論上は AWS などの クラウド 上でも、オンプレでも、自分の端末でも同じように動作するはずです。 そのため、開発者が自身の端末内に開発環境を構築し、そこで開発を進めた上で AWS 上の環境にデプロイするというケースも多いかと思います。 その際、アプリケーションで使用するデータ保持のため、 RDB やオブジェクトストレージが必要になります。 RDB であれば、 MySQL や PostgreSQL をコンテナで稼働させれば問題ありません。ではオブジェクトストレージはどうすれば良いでしょうか? AWS ではS3(Simple Storage Service)というオブジェクトストレージのマネージドサービスがあります。 ローカル上で AWS と同じ構成を模して開発するには、同じようにストレージを扱う必要があります。 MinIOについて 弊社では、MinIOというS3互換ストレージの OSS を使用しています。 MinIOは、 GNU Affero General Public License v3.0 に基づいてリリースされた高性能オブジェクト ストレージです。 Amazon S3 と API 互換性があります。 MinIO Object Storage for Container — MinIO Object Storage for Container 実際にMinIOをコンテナ環境で稼働させた印象として、安定しており、非常に使いやすいと感じています。 ※本稿では、MinIOの具体的な設定方法などには触れません データ移行の要件 さて、ここからが本題となります。 弊社では以下のような要件が発生しました ローカル環境やオンプレ環境で使用したデータを、そのまま AWS のS3上に移行したい 移行したデータに破損や漏れがないかをチェックしたい ローカルから AWS 上にデータを移行する時、ネットワークの帯域を使い切ってしまい、他の通信に悪影響を与えてしまう事を避けたい 検討の結果、MinIOのcommand line toolとして提供されている mc が優秀で、上記の問題は全てこのツールで対処できました。 MinIO Client — MinIO Object Storage for Linux なお、弊社にて動作実績のある環境は、CentOS7及びRHEL8.9となります。 データ移行手順 mc(minio client)のインストール 以下の手順でインストールします。 $ wget https://dl.min.io/client/mc/release/linux-amd64/mc -- https://dl.min.io/client/mc/release/linux-amd64/mc dl.min.io (dl.min.io) をDNSに問いあわせています... 138.68.11.125, 178.128.69.202 dl.min.io (dl.min.io)|138.68.11.125|:443 に接続しています... 接続しました。 HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 27496600 (26M) [application/octet-stream] `mc' に保存中 100%[==============================================================================================>] 27,496,600 4.83MB/s 時間 5.9s YYYY-MM-DD HH:MM:SS (4.47 MB/s) - `mc' へ保存完了 [27496600/27496600] $ sudo chmod +x mc $ sudo mv mc /usr/local/bin/mc $ mc --version mc version RELEASE.2024-10-08T09-37-26Z (commit-id=cf128de2cf42e763e7bd30c6df8b749fa94e0c10) Runtime: go1.22.8 linux/amd64 Copyright (c) 2015-2024 MinIO, Inc. License GNU AGPLv3 <https://www.gnu.org/licenses/agpl-3.0.html> これでmcがインストールできました。 mcの基本的な使用方法は、データ操作をしたい対象に エイリアス の設定をすることです。 以下は初期状態です。 $ mc alias ls mc: Configuration written to `/home/mntsq/.mc/config.json`. Please update your access credentials. mc: Successfully created `/home/mntsq/.mc/share`. mc: Initialized share uploads `/home/mntsq/.mc/share/uploads.json` file. mc: Initialized share downloads `/home/mntsq/.mc/share/downloads.json` file. gcs URL : https://storage.googleapis.com AccessKey : YOUR-ACCESS-KEY-HERE SecretKey : YOUR-SECRET-KEY-HERE API : S3v2 Path : dns local URL : http://localhost:9000 AccessKey : SecretKey : API : Path : auto play URL : https://play.min.io AccessKey : Q3AM3UQ867SPQQA43P2F SecretKey : zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG API : S3v4 Path : auto s3 URL : https://s3.amazonaws.com AccessKey : YOUR-ACCESS-KEY-HERE SecretKey : YOUR-SECRET-KEY-HERE API : S3v4 Path : dns このサンプルを見て分かる通り、MinIOで管理しているストレージだけでなく、 AWS のS3や Google Cloud Storage(以下GCS)もalias設定の対象にすることができます。その際、AccessKeyおよびSecretKeyを使ってアクセスしますが、aliasコマンドでkeyが表示されてしまうため、移行作業に限定したkeyを発行し、対象のS3Bucketを限定するなど、 AWS 側のアクセスキー設定には十分留意してください。 そして、データ移行が終わったら、速やかにkeyを削除しましょう。 では実際に エイリアス の設定をしていきます。MinIOはローカル上にDockerコンテナで起動している前提です。 また、S3は東京リージョンを使用しているため、URLも東京リージョン指定にしています。 移行元:MinIO(ローカルストレージ) 移行先: AWS のS3 mc mirror — MinIO Object Storage for Linux # エイリアスの登録 $ mc alias set mntsq-test-minio http://127.0.0.1:7100 <MINIO_ROOT_USER> <MINIO_ROOT_PASSWORD> Added `mntsq-test-minio` successfully. $ mc alias set mntsq-aws https://<S3BUCKET NAME>.s3.ap-northeast-1.amazonaws.com/ <ACCESS KEY> <SECRET KEY> Added `mntsq-aws` successfully. # エイリアスが登録されているかを確認 $ mc alias ls mntsq-aws URL : https://<S3BUCKET NAME>.s3.ap-northeast-1.amazonaws.com AccessKey : **************************** SecretKey : **************************** API : s3v4 Path : auto Src : /home/mntsq/.mc/config.json mntsq-test-minio URL : http://127.0.0.1:7100 AccessKey : ******** SecretKey : ******** API : s3v4 Path : auto Src : /home/mntsq/.mc/config.json これでデータ移行の準備ができました。 MinIOからS3にデータ移行をするには、mcの ミラーリング 機能を使います。 この例では、test配下のデータを ミラーリング します。 # ミラーコマンド実行 # 送信元、送信先の順番でエイリアス設定したバケットを指定 $ mc mirror mntsq-minio/test mntsq-aws/test 0 B / ? xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax ...xxxxxxxx.pdf: 1.02 GiB / 1.02 GiB xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax 10.31 MiB/s 1m41s $ ミラーリング が完了し、プロンプトが帰ってきたら完了です。 もし何かエラーが発生した場合は、その内容が標準出力されます。 また、途中で ミラーリング を停止した場合は、再度同じコマンドを実行すれば問題ありません。 最後に、データの移行がもれなく完了したかを確認します。 $ mc diff mntsq-minio/test mntsq-aws/test # 差分がない場合は、何も出力されずにプロンプトが返ってくる # diffの実行には時間がかかるため、sshでサーバに接続している場合などは途中でセッションが切れてしまう場合がある # データ量が多い場合は、以下のようにバックグランドで実行することを推奨 $ nohup mc diff mntsq-minio/test mntsq-aws/test > YYYYMMDD_mc_diff_log.txt 2>&1 & 帯域制御の方法 ここまででデータ移行は完了です。 ただし、この手順ではデータ移行でインターネット回線の帯域を占有し、他の通信に影響を及ぼす可能性があります。 mcにはアップロード、ダウンロードの帯域を制限するオプションもあり、これを併用することでその心配もなくなります。 mc mirror — MinIO Object Storage for Linux # ミラーコマンドにアップロードの帯域制限を指定 # この場合、30Mbpsの帯域制限をしてアップロードする $ mc mirror --limit-upload 3.57MiB mntsq-minio/test mntsq-aws/test ローカルから AWS へのアップロードになるため、上記のオプションとなります。方向が逆の場合は"--limit-download"を指定します。 おわりに ローカルでテストしたデータを使いたい、オンプレ環境のデータを クラウド 上に単純移行したいなど、オブジェクトストレージを扱う色々なケースがあるかと思います。そうした時に本稿の内容が参考になれば幸いです。
アバター
はじめに 要件の整理 構成 実装例 Stop Env (GitHub Actions) Control ECS (GitHub Actions) Update ECS Clusters (Lambda) おわりに はじめに 弊社MNTSQでは AWS 上にMNTSQ CLMをはじめとする複数のサービスを展開していますが、サービス運用が軌道に乗るにつれて、社内利用の環境( AWS アカウント)が開発環境、QA環境、ステージング環境と用途によって増えていき、コストの増加が無視できない問題となってきました。そこで、 GitHub Actionsを使用して、ECSサービスを夜間停止する仕組みを導入することにより、コストの削減を行いました。workflow_callを使用することにより、良い感じに再利用性のある仕組みが作れたと思うので、記事にしていきたいと思います。 ※ 以降 GitHub Actionsは単にワークフローと表記しています 要件の整理 弊社では、社内環境の停止を行うにあたって、以下のような要件を満たす必要がありました。 開発環境、QA環境、ステージング環境の3環境を対象に夜間停止を行う必要がある 平日は0時 ~ 8時, 休日は終日対象の環境を停止する 残業や休日稼働をする人がいるかもしれないので、手動での起動・停止ができる必要がある 構成 フローの全体図 この構成のポイントは以下です 上位の コンポーネント から「 AWS 環境」->「管理対象のECS クラスタ 」 -> 「ECS クラスタ に所属するECSサービス」と操作対象をスコープダウンしている フロー全体の基点となる"Start Env", "Stop Env"のワークフローを、スケジュール(自動)とworkflow_dispatch(手動)の両方でキックできる このような構成にすることにより各 コンポーネント を再利用性が高い形で作成することができ、管理しなければならないワークフローの数も少なくすることができました。また、以下のような形に拡張することにより、RDSやEC2などの他のコンピューティングリソースを停止対象に追加することも容易です。 対象のコンピューティングリソースを拡張した例 実装例 Stop Env ( GitHub Actions) フロー全体の基点となるワークフローです。スケジュール停止を行いたい場合は開発環境、QA環境、ステージング環境の3つの AWS 環境に対して操作を行いたいですが、手動で起動を行う場合は特定の AWS 環境のみを対象にしたいです。そのロジックを set-target のjobに内包し、このjobのoutputについて、後続の stop-ecs-clusters のjobをmatrixで起動することによって、ワークフローのトリガの種類による対象環境の差分を吸収しています。"Start Env"と"Stop Env"を分けているのは、 schedule をワークフローに記述する必要があったからです。workflow_dispatchで起動する際は環境を選択するだけなので、シンプルな使い心地になっていると思います。 Start Envはこのコードをコピぺして、操作を反転させればOKです。 # .github/workflows/control_stop_env.yml # 指定した環境を停止する # スケジュールで実行される場合は、development~stagingの環境を停止する # 手動で実行される場合は、inputで指定した環境を停止する name : "Stop Env" permissions : # wf_callを実行するためにはこの権限が必要 id-token : write contents : write on : schedule : - cron : '0 15 * * *' # JST 0:00 に自動実行 workflow_dispatch : inputs : environment : type : choice description : 'Environment to Stop' required : true default : 'development' options : - development - qa - staging jobs : # schedule起動の時は"development", "qa", "staging"を対象にする # workflow_dispatch起動の時は、inputで指定されたAWS環境を対象にする set-target : runs-on : ubuntu-latest outputs : target : ${{ steps.set.outputs.target }} steps : - name : Set Target Environment id : set run : | if [ "$GITHUB_EVENT_NAME" = "schedule" ] ; then target='["development", "qa" , "staging" ] ' else target=' [ "${{ inputs.environment }}" ] ' fi echo "target=$target" >> $GITHUB_OUTPUT # 管理対象のECSクラスタを一括管理するWFをCallする stop-ecs-clusters : name : "Stop ECS Clusters" uses : ./.github/workflows/control_ecs_clusters.yml needs : set-target strategy : # 操作対象のAWS環境ごとに並列で起動する matrix : target : ${{fromJson(needs.set-target.outputs.target)}} with : environment : ${{ matrix.target }} action : STOP # stop-ecs-clustersと並列に、別のリソースを管理するWFをcallするjobを追加して拡張できる # stop-rds-clusters: # name: "Stop RDS Clusters" # uses: ./.github/workflows/control_rds_clusters.yml # needs: set-target # strategy: # matrix: # target: ${{fromJson(needs.set-target.outputs.target)}} # with: # environment: ${{ matrix.target }} # action: STOP Control ECS ( GitHub Actions) 管理対象のECS クラスタ を一括停止するワークフローです。ただし、"管理対象のECS クラスタ "の部分の実態は、運用により大きく異なるかと思います。対象の判定ロジックをワークフロー, Lambdaのどちらに置くのが適切かは、ケースバイケースになるはずです。今回はシンプルに、ワークフロー内に管理対象のECS クラスタ を列挙する形の実装例を置いておきます。 ※ 弊社環境は開発環境内に複数の開発面を持っている都合上、管理すべきECS クラスタ の数が多く、対象判定ロジックをワークフローとLambdaで分割して持つ、もう少し複雑な実装になっております。このサンプルコードは弊社のコードから余分な処理を削ぎ落としたものであり、実際の動作を確認したわけではないので、参考程度にご覧ください。 inputには environment (対象AWS環境)と action を要求します。 action は STOP or START を渡し、後続のLambdaでECSサービスを停止するのか起動するのかを制御する変数です。別のワークフローから呼ばれるので、 workflow_call のブロックも記述しています。 # .github/workflows/control_ecs_clusters.yml # 指定した環境のECSクラスターを停止する name : "Control ECS Clusters" env : ASSUME_ROLE_ARN : "arn:aws:iam::%AWS_ACCOUNT_ID:role/oidc-gha-role" LAMBDA_UPDATE_ECS_CLUSTER_NAME : "update-ecs-cluster" permissions : id-token : write contents : write on : workflow_dispatch : inputs : environment : type : choice description : 'Environment to apply' required : true default : 'development' options : - development - qa - staging action : type : choice description : 'Action to apply' required : true default : 'STOP' options : - START - STOP workflow_call : inputs : environment : type : string required : true action : type : string required : true jobs : # 必要な変数を組み立てる # AWSアカウント毎の差分が.github/configs/aws/variables.ymlというファイルに記述されていることを前提としている setup-env : runs-on : ubuntu-latest outputs : ASSUME_ROLE_ARN : ${{ steps.setup-env.outputs.ASSUME_ROLE_ARN }} steps : - name : Checkout uses : actions/checkout@v4 - name : Setup ENV id : setup-env run : | ENV=${{ inputs.environment }} # 設定ファイルの<env>.AWS_ACCOUNT_IDというフィールドから、アカウントIDを取得している AWS_ACCOUNT_ID=$(yq -r ".[ \" $ENV \" ].AWS_ACCOUNT_ID" .github/configs/aws/variables.yml) ASSUME_ROLE_ARN=$(echo $ASSUME_ROLE_ARN | sed -e "s/%AWS_ACCOUNT_ID/$AWS_ACCOUNT_ID/g" -e "s/%ENV/$ENV/g" ) echo "ASSUME_ROLE_ARN=$ASSUME_ROLE_ARN" >> $GITHUB_OUTPUT # 対象のECSクラスタ毎に、所属するECSサービスに対して一括操作を行うLambdaを呼び出す control-ecs-clusters : needs : setup-env runs-on : ubuntu-latest strategy : matrix : # WF冒頭のenvブロックに記載するとfromJsonで展開できないので、ここに対象ECSクラスタを書く cluster : ${{ fromJson('["service1-cluster","service2-cluster"]') }} steps : - name : Configure AWS credentials uses : aws-actions/configure-aws-credentials@v4 with : aws-region : ap-northeast-1 role-to-assume : ${{ needs.setup-env.outputs.ASSUME_ROLE_ARN }} - name : Invoke Lambda UpdateEcsCluster run : | echo '{ "clusterName": "${{ matrix.cluster }}", "action": "${{ inputs.action }}" }' | jq -c > payload.json aws lambda invoke \ --function-name $LAMBDA_UPDATE_ECS_CLUSTER_NAME \ --payload file://payload.json \ --cli-binary-format raw-in-base64-out \ --invocation-type RequestResponse \ response.json Update ECS Clusters (Lambda) "clusterName"と"action"をinputとし、指定されたECS クラスタ の全サービスのタスク数を更新するLambdaのサンプルコードを置いておきます。こちらはnode.22.xで動作を確認しているものになります。 import { ECSClient , ListServicesCommand , UpdateServiceCommand } from "@aws-sdk/client-ecs" ; const ecsClient = new ECSClient ({ region : "ap-northeast-1" }) ; export const handler = async ( event ) => { try { const clusterName = event . clusterName ; const action = event . action ; const desiredCount = action === "STOP" ? 0 : 1 ; if ( ! clusterName ) { throw new Error ( "clusterName is required" ) ; } if ( action ! == "START" && action ! == "STOP" ) { throw new Error ( "action must be either START or STOP" ) ; } console . log ( `Processing cluster: ${ clusterName } ` ) ; // クラスタ内の全サービスを取得 let nextToken ; let serviceArns = [] ; do { const response = await ecsClient . send ( new ListServicesCommand ({ cluster : clusterName , nextToken })) ; serviceArns = serviceArns . concat ( response . serviceArns ) ; nextToken = response . nextToken ; } while ( nextToken ) ; if ( serviceArns . length === 0 ) { console . log ( `No services found in cluster: ${ clusterName } ` ) ; return { message : "No services found" , clusterName } ; } // 各サービスのタスク数を更新 await Promise . all ( serviceArns . map ( async ( serviceArn ) => { await ecsClient . send ( new UpdateServiceCommand ({ cluster : clusterName , service : serviceArn , desiredCount : desiredCount })) ; console . log ( `Updated service ${ serviceArn } to desiredCount: 0` ) ; })) ; return { message : "Successfully updated services" , clusterName , servicesUpdated : serviceArns . length } ; } catch ( error ) { console . error ( "Error updating services:" , error ) ; return { error : error . message } ; } } ; おわりに GitHub Actions を綺麗に実装するのはなかなか難しいですが、今回はシンプルで再利用性の高い形にできたと思うので紹介させていただきました。特に「操作対象をスコープダウンしながら設計する」という部分は、他のワークフローを作成する際にも役立つ考え方になるはずです。 SREのように内部改善やプラットフォーム維持を担うエンジニアは、直接的に売上を上げる機会が少ないからこそ、「コスト」に敏感である必要があります。ただし、コスト削減はそう単純ではなく、例えばテクニカルサポートや営業など、サービスを扱うすべての人が、将来に渡ってスムーズに業務を進められるかどうかも、見落としてはいけない「コスト」です。 業務全体を見渡してみると、もっと幅広い場面で GitHub Actions を活用できるはずです。そうした「小さな自動化の積み重ね」が、より良い運用環境を作っていくのだと思います。とりわけ、「夜間も環境が動いているのはもったいないよね」といったシンプルな課題は、落ちているチリ紙を拾うような気持ちでサクッと解決したいですね。 MNTSQ株式会社 SRE 西室
アバター
こんにちは!! SREチームマネージャーの藤原です。 2024年6月末から2025年2月頭にかけて、 入門 継続的デリバリー の読書会を実施し、完走したのでその報告エントリです。 www.oreilly.co.jp 勉強会の進め方 基本的な進め方としては、 過去エントリ にて解説した通りの進め方に則る形としました。 tech.mntsq.co.jp つまり、 事前に対象とする章を定める 参加者は対象の章を読む 参加者は気になった部分などを引用しながら所感をなどを Google Docs に記載する 当日はそれぞれ読んだ内容についてDocs記載内容について説明しながらディスカッションする の形で進めました。 勉強会のログサンプル 書籍の内容について 書籍の内容としては、架空のシステムを対象にストーリー仕立てでCI(Continuous Integration; 継続的インテグレーション )やCD(Continuous Delivery; 継続的デリバリー)においてよくある問題とその対処方針をまとめています。 流れとしては、事例を挙げた上で個々の状況においてどのような点に問題があるのかを解説しています。問題を抱えた現状を改善してより良い状況に持っていくにはどう考えるか?どう対処するか?を基本となる考え方を示しつつ、アクションを定めていくような形をとっています。 なぜバージョン管理が重要なのか?といった基本的なことについても当然のこととして切り捨てるのではなく改めて丁寧に確認するような形となっています。また、テストそのものやCI/CDパイプラインから得られるシグナルをどうとらえるか、シグナルに比してノイズが多すぎる状況はどのような問題を開発組織にもたらすか?などさまざまな観点からプロダクトの 開発プロセス におけるデリバリーに関わるプロセス上の課題について議論しています。 さらに、テストやビルドの技術的な問題を解決していく中で、DORAメトリクスなどを使ってデプロイに関連した組織パフォーマンスを測定することなども記述されています。 終盤では、CI/CDのパイプラインを構築運用していく上での考え方が述べられています。問題が発生した際の トラブルシューティング に必要なシグナルや、そもそもCI/CDのパイプラインを構成している スクリプト もコードであり、各種ソフトウェアエンジニアリングにおけるプ ラク ティスが適用できることなどが述べられています。 勉強会を通じての感想 事前に読んで気になった点をピックアップしたり、勉強会の中で気になった点などをコメントしたり、個々人が勉強会の中で発言した内容をコメントと残す中で、最終的に Google Docs 上では、A4で37ページ、コメント数は120-130程度の大作になりました。 個々の回では対象となっている章のどの部分に勉強会参加者が興味があるのか?が引用した部分やコメントから浮き彫りになりました。 書籍の内容としては、(藤原個人からみると)特別な内容はなく、よくある課題とそれらへの対応方針を丁寧に 言語化 してくれています。 議論の端緒として非常に有用な書籍でした。 コードを提示した上でどう直すか?といったことはほぼないので、写経して学ぶといったスタイルの書籍ではありません。 それよりは直面している問題をどう捉え、どう対処するかについての指針が多く述べられています。 このようなことから、 読み進めるに際しては、”書籍中で述べられているこの問題は自分たちの抱えているプロダクトにおいてどの部分に対応するだろう?”といった観点から、書籍で述べられている内容をベースに具体的なアクションを議論することでより得られる学びは多くなる と思いました。
アバター
はじめに DMSを使ってMySQLの移行をする際に気をつけたいこと7選! その1. DMSのログを出力するには決まった名前のIAMロールが必要である その2. CDCを有効にするにはソースDBでバイナリログを出す必要がある その3. GENERATEDカラムは移行対象から除外せよ その4. LOB型のカラムがある場合はターゲットDBでNOT NULL制約を一時解除せよ その5. 完全LOBモードの設定が必要か確認せよ その6. AUTO_INCREMENTは手動で移行する必要がある その7. 移行後の検証の設計は慎重に おわりに はじめに データベース移行というのは非常にセンシティブな作業であり、この使命を背負ってしまったエンジニアの皆様におかれましては、さぞ胃に優しくない日々を送っていることかと存じます。そんな私たちの心強い味方が AWS DMSです。 AWS Database Migration Service (以下DMS) は、 AWS が提供する フルマネージドのデータベース移行サービス であり、オンプレミスや クラウド 環境間のデータ移行を可能にします。 MySQL 、 PostgreSQL 、 Oracle など多様なデータベースをサポートし、移行元と移行先の異なるエンジン間の変換も自動化。フルロード、CDC (変更データキャプチャ) による継続的 レプリケーション も可能で、 最小限のダウンタイムでデータベースをスムーズに移行できる のが大きな魅力です! そんな便利なDMSですが、当然使用する際に気をつけなければいけないことはあります。 本記事では、DMSを使用して60回以上のデータベース移行を行なったMNTSQ SREチームから、「 MySQL の移行をする際に気をつけたいこと7選!」をお届けしたいと思います。 ※ なお、本記事はDMS自体の説明や利用方法の解説記事ではございません DMSを使って MySQL の移行をする際に気をつけたいこと7選! その1. DMSのログを出力するには決まった名前のIAMロールが必要である まずはじめにDMSの移行タスクなどを作成すると思いますが、ここで Terraformの公式サンプルコード を見てみましょう。IAMロールの定義として、このような記述があるかと思います。 resource "aws_iam_role" "dms-cloudwatch-logs-role" { assume_role_policy = data.aws_iam_policy_document.dms_assume_role.json name = "dms-cloudwatch-logs-role" } 他のリソースと 命名規則 を揃えたかったとしても、 このロールのnameは変更してはいけません。 aws_dms_replication_instance のリソースの記述を見るとわかりますが、DMS インスタンス にこのロールをアタッチするわけではないのです。DMS インスタンス は暗黙的に特定の 命名 のロールを使用します。何故なのかは知りません。とにかく、他のリソースのような感覚で名前を変更してしまうと、後々動作確認の際に、エラーになっても原因調査が進まないといったことになります。(一応サンプルコードの コメントアウト に注意書きがありますが......) ちなみに dms-access-for-endpoint , dms-vpc-role も同様の理由でnameを変更してはいけません。 その2. CDCを有効にするにはソースDBでバイナリログを出す必要がある DMSの強力な機能であるCDC (変更データキャプチャ) は、 レプリケーション 開始後にINSERTされたレコードも移行先DBに反映させることができる機能です。これがあるため、稼働中の環境でも無停止で レプリケーション を進め、最小のダウンタイムで新データベースに移行することができます。ただし、CDCはソースDBでバイナリログを有効化していないと利用できません。 Aurora MySQL の場合は、パラメータグループで binlog_format=ROW , binlog_row_image=full に設定しておけばOKです。ただし適用にはソースDBの再起動が必要です。 その3. GENERATEDカラムは移行対象から除外せよ GENERATEDカラムは、他のカラムの値を基に自動計算されるカラムです。便利な機能ですが、DMSはこのカラムにも律儀に値をINSERTしようとしてしまい、そのまま移行を実行するとエラーとなってしまいます。 ソースDBにGENERATEDカラムが存在するときは、 aws_dms_replication_task の table_mappings に、以下のようなルールを記述して、移行対象から除外しましょう。除外しても、ターゲットDBにも適切にGENERATEDカラムの制約が設定されていれば、自動で計算された値が再び入るはずです。カラムが複数ある場合は、除外ルールも複数書きます。 resource "aws_dms_replication_task" "mysql" { replication_task_id = "replication-mysql" migration_type = "full-load-and-cdc" ~~ 省略 ~~ table_mappings = jsonencode(local.table_mappings_mysql) } locals { table_mappings_mysql = { rules = [ # GENERATEDカラムを移行対象から除外するルール { rule-type = "transformation", rule-id = "1", rule-name = "skip_generated_column", rule-target = "column", object-locator = { schema-name = <schema_name>, table-name = <table_name>, column-name = <column_name> # GENERATED制約がついているカラム名 }, rule-action = "remove-column" } ] } } その4. LOB型のカラムがある場合はターゲットDBでNOT NULL制約を一時解除せよ DMSはLOB型のカラムを含む行を移行する際、以下の2つのステップで処理を行います。 LOB列をNULLにしたまま行を作成 LOB列をUPDATEしてデータを挿入 このため、LOB型のカラムにNOT NULL制約がついている場合、1の処理でエラーとなってしまうようです。NOT NULL制約がついたLOB型のカラムを持つデータベースの移行を行う際には、DMSの移行タスクを実行する前にターゲットDB側の対象カラムから制約を解除し、移行後に元に戻しましょう。 MySQL のLOB型には TINYBLOB , BLOB , MEDIUMBLOB , LONGBLOB , TINYTEXT , TEXT , MEDIUMTEXT , LONGTEXT , JSON などがあります。 ※ 参考: DMSのAWS公式ドキュメント その5. 完全LOBモードの設定が必要か確認せよ DMSによるLOB型カラムの移行オプションには、次の2つのモードがあります。 制限付きLOB モード すべての LOB 値をユーザー指定のサイズ制限 (デフォルトは 32 KB) で移行します。サイズを制限を超えるLOBは移行されず、手動で移行する必要があります。 完全LOB モード サイズに関係なくテーブル内のすべての LOB データを移行します。 DMSのデフォルトは"制限付きLOB モード" なので、データベースの完全な移行を行いたい場合は明示的に"完全LOBモード"を設定する必要があります。一応 AWS 的には、まず"制限付きLOB モード"を試し、必要なら"完全LOBモード"に切り替えるという戦略を推奨しているみたいです。その主な理由は、"完全LOBモード"だとパフォーマンスが極端に落ちるためのようです。本来LOBはS3などを使用して管理するのが AWS 的なベストプ ラク ティスであり、そのような方法を検討して欲しいのだと思いますが、既に移行の計画に入ってからの変更は厳しい箇所かなと思います。ですので、 結局ほとんどのケースで"完全LOB モード"を使用することになるのではないかと思っています。 LOBモードの設定は、terraformの場合だと aws_dms_replication_task リソースの replication_task_settings.TargetMetadata.FullLobMode にbooleanで定義されています。"完全LOB モード"を使用する場合には FullLobMode=true に設定しましょう。 AWS コンソールから移行タスクを編集して設定することも可能です。 その6. AUTO_INCREMENTは手動で移行する必要がある DMSがサポートするのはあくまでアプリケーションデータであり、 INFORMATION_SCHEMA や performance_schema などの移行は行えません。プライマリキーなどに AUTO_INCREMENT を使用している場合、DMSでの移行後に値がリセットされ、新たなレコードが挿入できなくなるなどのサービス障害の原因となってしまいます。 これを防ぐにはAUTO_INCREMENTの値を手動で移行する必要があります。以下の記事などを参考にし、弊社では移行用 SQL を作成する スクリプト などを用意して、移行手順に組み込みました。注意点として、AUTO_INCREMANTの値を取得する時は SHOW CREATE TABLE <table_name>; などを使用しましょう。 INFORMATION_SCHEMA へのクエリでは、最新の値が取れないことがありますし、物理削除が行われるテーブルではAUTO_INREMENTと最新レコードのidにはズレが生じます。 blog.tocyuki.com その7. 移行後の検証の設計は慎重に データベース移行後には必ず、移行前後で差分が出ていないかの検証を行うかと思います。当然、弊社でも検証を行なっていましたが、移行作業初期にはやはりトラブルに見舞われることはありました。原因は様々でしたが、 移行後検証の完全性が保証されていれば、全てメンテナンスウインドウ中に検知できた ものであり、サービスのインシデントにつながることな無かったはずのものばかりでした。最終的に弊社では、以下のチェックを行う スクリプト を導入した結果、データベース移行に関するトラブルは起きなくなりました。 全テーブルの SELECT COUNT(*) FROM <table>; の結果を移行前後で突き合わせる スクリプト 全テーブルの SHOW CREATE TABLE <table_name>; の結果を移行前後で突き合わせる スクリプト これにより、 レコード数に差分がないこと 、 テーブル構造に差分がないこと 、 AUTO_INCREMENTの差分がないこと が保証できました。 なお、これは弊社の事例に基づく例であり、いかなるケースにおいても上記の確認が移行の完全性を保証するものではございません! 弊社の場合は事前にCDCで レプリケーション を行い、移行日にはサービスのメンテナンス時間をとってこれらを確認しましたが、無停止での切り替えなどを計画している場合は、上記の項目の確認は難しくなります。また、データベースの設計によっては、確認項目が不足しているケースもあるかもしれません。加えて、制限付きLOBモードを使用していた場合、移行がスキップされたLOBがあったとしても、この方法では気づけません。あくまで「最低限ここは確認したほうが良い」程度にお受け取りください。 いずれにせよ、 移行後の検証は、移行作業そのもの以上に慎重に設計すべきです。 おわりに 弊社MNTSQでは、ここ2年間ほどかけて大規模な アーキテクチャ 変更を行い、その一環として計67回の顧客環境のデータ移行を行いました。最初の1回目の作業を行った時は「え、この作業あと60回以上するの?」と、その "永遠" に絶望したものですが、この度、ついにその作業も完了したので、区切りとして記事を書かせていただきました。 本記事では、移行作業を設計・検証する際に、実際に私がハマった箇所を「気をつけたいこと」という形で紹介させていただきました。「ここだけ気をつけていればあらゆるトラブルを回避できる」というものではありませんが、これからDMSを使用してデータベース移行を行うという方の助けになれば幸いです。 MNTSQ株式会社 SRE 西室
アバター
......のですが、かなり苦戦しました。この記事に辿り着いた人はすでにハマっている、もしくはこれからハマる運命(さだめ)にある人も多いと思うので、そのような人の助けになればと思い、記事にして残しておきます。 結論からお伝えすると、Lambdaを使わずに通知を行うことは可能ですが、設定は少し複雑かなという印象でした。 しかし、一度設定出来てしまえば、同じようなことをしたい時の実装コストをグッと抑えられる、とても良い仕組みだと思います。 構成について この構成のメリット この構成のデメリット AWS Chatbotの認証を行う terraformでデプロイしてみる SNS -> Chatbot -> Slackの部分 DynamoDB Stream -> EventBridgePipes -> EventBridgeCustomBusの部分 EventBridgeRule -> SNS の部分 おわりに 構成について Lambdaではなく、 AWS Chatbotを使用してSlackへの通知を行う構成です。 この構成のメリット Lambdaのランタイムやコードの管理から解放される 「 SNS -> Chatbot -> Slack」,「EventBridgeRule -> SNS 」 の部分の汎用性が高く、使いまわせる この構成のデメリット メッセージのカスタマイズ性に少しだけ欠ける 初めて設定する際はハマりどころが多い AWS Chatbotの認証を行う まずは、 AWS ChatbotからSlack ワークスペース に対しての認証を行います。 あらかじめSlack ワークスペース にChatbotのアプリをインストールしておく必要があります。 mntsq.slack.com ワークスペース にアプリをインストールしたら、Chatbotが通知を行いたいチャンネルにアプリを招待します。チャンネル詳細の「インテグレーション」タブから、「 AWS Chatbot」のアプリを招待します。 次に、 AWS コンソールからChatbotのページへ行き、Slackクライアントの設定を行います。 ブラウザでSlackログインしていた場合、Slackの認証ページにリダイレクトされるので、「承認」します。 その後「Slack チャネルを設定」という編集画面に遷移することがありますが、今回はterraformでデプロイを行うので、この画面は閉じてしまって大丈夫です。 terraformでデプロイしてみる 今回はこのような仕組みを作るものとします。 ファイルアップロード時にウイルススキャンを行い、感染ファイルが見つかった場合、DynamoDBのInfectedScanResultsというテーブルに書き込みを行う InfectedScanResultsに書き込みがあった場合、その内容をSlackのエラーチャンネルに通知する SNS -> Chatbot -> Slackの部分 まずは通知の起点となる SNS トピックとChatbotのチャンネル設定を作成します。 SNS # 後段のChatBotからSlackへの通知を行うためのSNSトピック resource "aws_sns_topic" "slack_notify_error" { name = "slack-notify-error" } # SNSにCloudWatchからのPublishを許可するポリシー data "aws_iam_policy_document" "allow_cloudwatch_to_publish_sns" { statement { actions = [ "sns:Publish" , ] effect = "Allow" resources = [ aws_sns_topic.slack_notify_error.arn, ] principals { type = "Service" identifiers = [ "cloudwatch.amazonaws.com" , ] } } } resource "aws_sns_topic_policy" "allow_cloudwatch_to_publish_sns" { arn = aws_sns_topic.slack_notify_error.arn policy = data.aws_iam_policy_document.allow_cloudwatch_to_publish_sns.json } Chatbot # chatbot用のIAMロール data "aws_iam_policy_document" "chatbot_assume_policy" { statement { effect = "Allow" principals { type = "Service" identifiers = [ "chatbot.amazonaws.com" ] } actions = [ "sts:AssumeRole" ] } } data "aws_iam_policy_document" "chatbot_slack_notify" { statement { effect = "Allow" actions = [ "sns:Subscribe" , "sns:ListSubscriptionsByTopic" , "sns:GetTopicAttributes" , "sns:Publish" ] resources = [ aws_sns_topic.slack_notify_error.arn ] } } resource "aws_iam_role" "chatbot_slack_notify_error" { name = "chatbot-slack-notify-error-role" assume_role_policy = data.aws_iam_policy_document.chatbot_assume_policy.json } resource "aws_iam_policy" "chatbot_slack_notify_error" { name = "chatbot-slack-notify-error-policy" description = "IAM policy for Chatbot to notify error to Slack" policy = data.aws_iam_policy_document.chatbot_slack_notify.json } # Chatbot チャンネル設定 resource "aws_chatbot_slack_channel_configuration" "slack_notify_error" { configuration_name = "slack-notify-error" slack_channel_id = "YOUR_CHANNEL_ID" slack_team_id = "YOUR_WORKSPACE_ID" logging_level = "INFO" sns_topic_arns = [ aws_sns_topic.slack_notify_error.arn ] iam_role_arn = aws_iam_role.chatbot_slack_notify_error.arn } terraform apply を実行したら、 AWS コンソールからリソースが作成されたことを確認してください。 Chatbotのコンソールから「テストメッセージの送信」を行い、Slackチャンネルに通知が届けばOKです。 ※弊社開発環境のものなので、微妙にリソース名が異なります なお、2024/12月現在、Chatbotのロググループは強制的に バージニア 北部に作成されるので、東京リージョンを彷徨わないようにご注意下さい。 DynamoDB Stream -> EventBridgePipes -> EventBridgeCustomBusの部分 DynamoDBに書き込みがあった時、それをCustomBusにEventとして送信するEventBridgePipesの設定を行います。 DynamoDB ## InfectedScanResultsテーブル resource "aws_dynamodb_table" "infected_scan_results" { name = "InfectedScanResults" billing_mode = "PAY_PER_REQUEST" hash_key = "ObjectPath" attribute { name = "ObjectPath" type = "S" } # EventBridge -> SNS -> Slack通知を行うためのストリーム stream_enabled = true stream_view_type = "NEW_IMAGE" } EventBridge # カスタムのイベントを記録するためのバス resource "aws_cloudwatch_event_bus" "notification" { name = "notification" } # PipeのためのIAMロール data "aws_iam_policy_document" "eventbridge_pipe_assume_role_policy" { statement { actions = [ "sts:AssumeRole" ] effect = "Allow" principals { type = "Service" identifiers = [ "pipes.amazonaws.com" ] } } } resource "aws_iam_role" "dynamodb_pipe_role" { name = "dynamodb-pipe-role" assume_role_policy = data.aws_iam_policy_document.eventbridge_pipe_assume_role_policy.json } data "aws_iam_policy_document" "dynamodb_pipe_policy" { statement { actions = [ "dynamodb:DescribeStream" , "dynamodb:GetRecords" , "dynamodb:GetShardIterator" , "dynamodb:ListStreams" , ] effect = "Allow" resources = [ aws_dynamodb_table.infected_scan_results.stream_arn ] } statement { actions = [ "events:PutEvents" ] effect = "Allow" resources = [ "*" ] } statement { actions = [ "logs:CreateLogStream" , "logs:PutLogEvents" ] effect = "Allow" resources = [ "*" ] } } resource "aws_iam_role_policy" "dynamodb_pipe_policy" { name = "dynamodb-pipe-policy" role = aws_iam_role.dynamodb_pipe_role.name policy = data.aws_iam_policy_document.dynamodb_pipe_policy.json } # ロググループ resource "aws_cloudwatch_log_group" "dynamodb_infected_scan_results_write" { name = "/aws/vendedlogs/pipes/dynamodb-infected-scan-results-write" } # InfectedScanResultsのストリームをEventBridgeに通知するPipe resource "aws_pipes_pipe" "dynamodb_infected_scan_results_write" { name = "dynamodb-infected-scan-results-write" role_arn = aws_iam_role.dynamodb_pipe_role.arn source = aws_dynamodb_table.infected_scan_results.stream_arn target = aws_cloudwatch_event_bus.notification.arn log_configuration { include_execution_data = [ "ALL" ] level = "INFO" cloudwatch_logs_log_destination { log_group_arn = aws_cloudwatch_log_group.dynamodb_infected_scan_results_write.arn } } source_parameters { dynamodb_stream_parameters { batch_size = 1 starting_position = "LATEST" } } target_parameters { eventbridge_event_bus_parameters { detail_type = "InfectedScanResultsWrite" source = "custom.dynamodb.infected-scan-results" } } } terraform apply を実行したら、DynamoDBにレコードを追加してみてください。ロググループ /aws/vendedlogs/pipes/dynamodb-infected-scan-results-write に dynamodb-infected-scan-results-write というストリームが作成され、いくつかログが出ているはずです。 # 最後のログがこんな感じだったらOK { "resourceArn": "arn:aws:pipes:ap-northeast-1:******:pipe/dynamodb-infected-scan-results-write", "timestamp": 1734671733500, "executionId": "8e3e7d3c-0c1e-4b47-a6b7-******", "messageType": "ExecutionSucceeded", "logLevel": "INFO" } ちなみにこのイベントはCroudTrailなどには記録されません。(最初はCroudTrailに記録されるものだと勘違いして時間を溶かしました)EventBridgeのコンソールからここら辺確認できるようになるとありがたいなぁ......と、しみじみ思います。 EventBridgeRule -> SNS の部分 最後に、CustomBusにイベントが送信された時に、それを拾って SNS にパブリッシュするRuleを作成します。Chatbotが受け取ることができる json の形式は決まっているので、EventBridgeの入力トランスフォーマを使用し、良い感じに整形します。 EventBridgeRule # busのイベントをSNSに通知するためのルール resource "aws_cloudwatch_event_rule" "dynamodb_infected_scan_results_write" { name = "dynamodb-infected-scan-results-write" description = "Send DynamoDB InfectedScanResults write events to EventBridge" event_bus_name = aws_cloudwatch_event_bus.notification.name event_pattern = jsonencode ( { source = [ "InfectedScanResultsPuts" ] detail-type = [ "custom.dynamodb.infected-scan-results" ] } ) } resource "aws_cloudwatch_event_target" "dynamodb_infected_scan_results_write_target" { rule = aws_cloudwatch_event_rule.dynamodb_infected_scan_results_write.name arn = aws_sns_topic.slack_notify_error.arn event_bus_name = aws_cloudwatch_event_bus.notification.name input_transformer { input_paths = { "ObjectPath" : "$.detail.dynamodb.NewImage.ObjectPath.S" , "ScannedAt" : "$.detail.dynamodb.NewImage.ScannedAt.S" , "Message" : "$.detail.dynamodb.NewImage.Message.S" } # jsonencodeを使用すると<, >などが文字コードに変換されてしまうのでTEMPLATEを使用する input_template = <<TEMPLATE { "version": "1.0", "source": "custom", "content": { "textType": "client-markdown", "title": "⚠️ウイルス感染ファイルが検出されました⚠️", "description": "<!subteam^YOUR_TEAM_ID>\n<ObjectPath>\n<ScannedAt>\n<Message>" } } TEMPLATE } } <!subteam^YOUR_TEAM_ID> の部分はメンションしたいSlackのTeamIDです。TeamIDの確認の仕方はここでは割愛します。Chatbotが解釈できる json の形式は、 こちらのドキュメント を参照しました。 これで全リソースの定義を作成できました。 terraform apply を実行して、もう一度DynamoDBにレコードを追加し、Slackに通知が飛んでくることを確認しましょう。 無事通知されました! おわりに 今回はLambdaを使用せず、DynamoDBへの書き込みをSlackに通知する方法について紹介させていただきました。EventBridge + SNS + Chatbotの構成は設定も簡単で再利用性が高く、監視モニタリングの整備をする際にはとても便利な仕組みですね。 本当はSlackのメッセージにカラーバーをつけたりとカスタマイズしたかったのですが、 AWS サポートに問い合わせたところ、2024/12現在ではそこまで細かい設定はできないようです。ただしEventBridgeRuleのターゲットにはHTTPエンドポイントも指定できるようなので、もっとこだわりたい人はこちらの方法を使ってみるのも良いかもしれません。 dev.classmethod.jp 以上、何かの助けになれば幸いです! MNTSQ株式会社 SRE 西室
アバター
こんにちは。MNTSQ( モンテスキュー )株式会社でQAエンジニアをしている坂本です。 今回は ソフトウェアテスト Advent Calendar 2024 の場をお借りして、 弊社の 自動テスト構築過程 をご紹介します。 QAメンバー4名だけでなく、PdMやSREにもご協力頂きながら進めており、 2025年1月からの稼働に向けた準備が大詰めの段階です。 品質と開発スピードの両立を目指したコラボレーションの様子が少しでも伝わればうれしいです。 テスト自動化の目的 MNTSQ社では アジャイル 開発を行っており、製品のリリースサイクルごとに新規開発機能のテストと、既存機能全体の リグレッション テストを実施しています。 リグレッション テストはテスト項目数が多いため、すこし早めにテスト実施担当者とスケジュール調整する必要があります。 そこで、テストスケジュールの自由度を高めることを目的として、 リグレッション テストの約9割を自動化することにしました。 テスト自動化ツールAutify 今回は、 Autify(オーティファイ) という自動テスト作成ツールを用いています。 主な機能は以下の通りです。 ノーコードのテストシナリオ作成 テストの定期実行 テストレポートの自動作成 テストの作成・管理・自動作成されたテストレポートの保管がWeb上で完結するため、URL一つで情報共有できるところがありがたいです。 テスト設計 手動 リグレッション テストをそのままAutifyシナリオにすることは、以下の理由で避けました。 製品の成長とともにテスト項目が膨大になっていた 徐々に継ぎ足されてきたため、テストの体系が見えにくくなってきていた その代わり、製品の性質を踏まえ、以下のテストを作成することにしました。 クリティカルパス の動作確認テスト CRUD テスト 権限テスト リグレッション テスト全体の1割についてはAutifyで実装しづらい動作であったため、手動テストとして継続することにしました。 テスト設計のレビュー テスト設計をQA内でピアレビューした後、特に重要な権限ごとの期待動作について各製品のPdMにもレビューを依頼しました。 レビューの過程で細かい仕様の認識違いも明らかになりましたので、色々な方にレビューをして頂けて大変助かりました。 テスト用データの準備 SREへ相談し、テスト用環境を特定のテスト用データで初期化できる仕組みを作って頂きました。 この仕組みはテスト実行の安定化に対して強力です。SREチームに大変感謝しています。 自動テストが途中で落ちた際にテスト過程で作成したデータが残ることがあるのですが、この仕組みがあれば毎回同じ条件でテストをスタートできます。 アカウント テストしたい権限を持つユーザーアカウントを作成します。 ファイル 処理結果をテストで確認したいファイルを用意します。 テスト用環境の設定値の調整 製品の実環境と同じ条件で作成されたテスト用環境に、一般的な利用場面を想定したユーザー設定値を追加していきます。 Autifyテストシナリオの実装 いよいよ、Autifyのシナリオを実装します。 Autifyレコーダーを起動させた状態で製品のUIを操作すると、その様子が記録され、Autifyの中にあるAIがステップに切り分け、テストシナリオ起こしてくれます。 このおかげで、テスト実装はコードを書かずに進めることができました。 頻度として多くはないのですが、HTMLと CSS の構成が特に複雑な画面では、 クリックしたい要素の特定方法をAutifyの中のAIがうまく割り出せないことがあります。 その場合は、人間が CSSセレクター や XPath で特定し直します。 大抵はブラウザの検証ツールからコピー&ペーストした CSS セレクタ で動くのですが、 もし画面の構成が複雑過ぎて、 CSS セレクタ ーをいい感じに簡略化できないときには、 Autifyのサポート窓口に相談すると、専門の方に手厚くフォローして頂けます。 リグレッション テストの9割を自動化する目標の元、 やることが多くて社内では細かい分析まで手が回らないことがありますので、 サポート頂けて本当に助かっています。 ひととおり実装を終えた感想 自動 リグレッション テストを設計する際に一番重視したのは、いかに安全にパターンを絞るかでした。 そのためには自社が提供するウェブアプリの特徴を捉え、必ず押さえなければいけないポイントを見極めることが重要でした。 このポイントの整理に一番力を注ぎました。 ポイントを整理してからテスト項目の骨格を組み立てることで、既存の手動 リグレッション テスト項目との比較もしやすくなり、 本当に移行して大丈夫か、既存のテスト項目から漏れているものはないか、追加すべきものはないかを検討する際の道筋を得られたと思います。 自動化したい内容の全体像を予め設計できたことで、目標の「9割」を実装したと表現しやすくなり、 チーム外とのコミュニケーションも取りやすくなりました。 実装後、自動テストの内容を、QAチームで日頃仕様している手動テスト項目書のフォーマットに書き起こし、 ドキュメントとして利用できるようにしています。ドキュメントを作成するとテストシナリオの粗が見えてくるので、 リファクタリング も同時に行っているのですが、最初にポイントを整理していたために迷わず作業できているように思います。 運用体制構築に向けて 来月から自動テストの運用フェーズに入ります。 ここまでは効率重視で、テスト自動化のための役割を分担して進めてきました。 プロジェクト管理 データ整備 テスト設計&実装 QAチームは比較的新しい組織で、4名のQA歴は長い方から1年3ヶ月、1年1ヶ月、5ヶ月、2ヶ月ですから、 よく協力して頑張っているのではないかと思います。 ここからは、QAチームの誰もが自動テストのメンテナンスが出来るようになろう! という目標に変わりますので、以下のような活動が始まります。 Autify勉強会 リグレッション テスト追加・削除基準のすり合わせ テストレポート作成方針のすり合わせ 年明けからの運用フェーズ立ち上げも頑張ろうという意気込みで、年末の振り返りとして自動テスト構築のまとめ記事を発信させていただきました。 ここまでお読み頂きありがとうございました!
アバター
こんにちは、MNTSQ でバックエンドエンジニアをやっております河久保です。 先日 Kaigi on Rails 2024 に参加してきたので、参加記をしたためます。 Kaigi on Rails は昨年に続き2回目の参加となります。 MNTSQ もアプリケーションのバックエンドは Ruby on Rails で実装されており、昨年に続き今年もスポンサーとして手を挙げさせていただきました。 個人としてプロポーザルを出したことや、担当しているシステムのより深いところまで触れる機会が増えたことで、昨年より主体的にカンファレンスに関われたと実感しています。 スポンサーボード 今回は以下の講演を聴講してきました kaigionrails.org 1日目 Hall Red Hall Blue 1 基調講演 2 Rails の仕組みを理解してモデルを上手に育てる - モデルを見つける、モデルを分割する良いタイミング - 3 そのカラム追加、ちょっと待って!カラム追加で増える ActiveRecord のメモリサイズ、イメージできますか? 4 モノリス でも使える!OpenTelemetryで Rails アプリのパフォーマンス分析を始めてみよう 5 cXML という 電子商取引 の トランザクション を支える プロトコル と向きあっている話 6 リリース8年目のサービスの1800個のERBファイルをViewComponentに移行した方法とその結果 7 ActionCableなら簡単? 生成 AIの応答をタイピングアニメーションで表示。実装、コスト削減、テスト、運用まで。 8 現実の Ruby / Rails アップグレード 9 (中抜け) (中抜け) 2日目 Hall Red Hall Blue 1 都市伝説バスターズ「Webアプリの ボトルネック はDBだから言語の性能は関係ない」 2 Cache to Your Advantage: フラグメントキャッシュの基本と応用 3 OmniAuthから学ぶOAuth 2.0 4 約9000個の自動テストの時間を50分から10分に短縮、 偽陽性 率(Flakyテスト)を1%以下に抑えるまでの道のり 5 Sidekiq vs Solid Queue 6 The One Person Framework 実践編 7 Data Migration on Rails 8 30万人が利用するチャットをFirebase Realtime DatabaseからActionCableへ移行する方法 9 サイロ化した金融システムを、packwerk を利用して無事故で リファクタリング した話 10 Identifying User Identity 11 基調講演 2日間通して振り返ると、それぞれの講演に連動があり Kaigi on Rails オーガナイザーからのメッセージを強く感じました。 One Person Framework Rails 7 から One Person Framework という旗を掲げて、 Rails 8 も当然その流れをくんだ機能・ コンポーネント を提供しています。 0(Idea) →→→→ 1( IPO ) というコンセプトは、 初期は資金もないし、人もいないから Rails Way に乗って最速で立ち上げる。 サービスが当たってグロースして IPO というころには資金もあるわけなので、そのときに顕在化した ボトルネック の アーキテクチャ を乗り換えれば使えばいいし、人も雇用できるでしょ。 と私は解釈しており、とても納得感のあるアプローチだなと思います。 『One Person』からは「じゃあ規模大きくなったらダメなの?」という疑問符浮かぶのも必然だと思います。 シンプルさ、 Rails Way の追求 小規模なアプリケーションであれば1人の脳に収まるでしょうが、サービスが成長するに従いそれは難しい問題となるでしょう。 加えて Ruby 言語の特性がゆえに避けられない問題(主に型の側面)もあります。 多人数が触るコードベースは時間とともに統制が取れなくなってくるでしょう。 そういった面のアプローチとして「シンプルに保つ」という話が、2つの基調講演で触れられていたのが印象的でした。 初日の palkan さんによる講演では、新しい層を持ち込むことへのアプローチについてサンプルコードを添えたレクチャーがありました。 ここではプレゼンテーション層へのインタフェースとなる Form オブジェクトを取り上げていたと記憶しています。 このアプローチは将来的なプロダクトコードのおぼろげながら抱いていたイメージに合っていたので、勇気づけられました。 2日目の 島田 さん講演で「オプションを手に入れよう」「シンプルさを維持するための修復」という点が響きました。 『オプション』とは何でも受けられる設計ということを指すのではなく、シンプルさを維持することで将来的に取れる択を残しておきましょうということ。 『修復』とは単に元の状態に戻すことではなく、損傷を直しつつそのときの環境、新しい技術を使ってより環境に適応した状態にすることで、これは創造的なことだよね。 と話されてました。 損傷を直しつつそのときの環境、新しい技術を使ってより環境に適応した状態にすること この部分が私の持つポリシー *1 とフィットして心震えました。 どちらも アーカイブ が公開されたらぜひとも見返したいです。 Solid Queue Rails 8 から ActiveJob のデフォルトバックエンドになるという「Solid Queue」からも初期は極力 ミドルウェア を排除するという強い意志を感じています。 willnet さんの講演を聞くまで DB ベースのキュー管理って大丈夫なんだろうか?という不安がありました。 しかし、事例紹介で 2,000万 job/日は捌けているよ(Sidekiq は 20,000 job/秒 !!)という言及されており、次回 rails new する際に触ってみようと思います。 Open Telemetry ちょうど Open Telemetry に興味を持ち始めたので、 ymtdzzz さんの講演を聞きました。 speakerdeck.com 今回は Rails アプリケーションという領域での紹介でしたが、トレースとアプリケーションログの連動のといったデモを見て、「これこれ、これがやりたいんよ」ってなりました。 弊社では Rails アプリケーションは DataDog の APM を導入しておりますが、 DataDog Log を掛け合わせて同様のこともできそうですが、 Open Telemetery でメトリクスとログをコレクトして、 DataDog なり他サービスなりに放り込んでリッチなビューアーとして利用するというアプローチは現実的だな思いました。 またフロントエンドからトレースを送ることで、ユーザーリク エス トから始まる各レイヤーのトレースが取得でき、サービス全体での ボトルネック の発見に向けて適切なアプローチをとることもできそうだという感覚を得られました。 ほかにもたくさん osyoyu さんの講演からも CPU バウンドなのか、 I/O バウンドなのか、その比率から適切な Puma スレッド数が変わってくるというはなし。 ohbarye さんの講演では、最近私達が db:migration のスキームに乗りつつ、手オペを減らしたのですが、それ以外のアプローチの紹介。 moro さんの講演の User モデルの拡張のアプローチはとても斬新(User テーブルは id だけもつ)で、提示されたコードもエレガント。 and more ... おわりに Kaigi on Rails 2024 を終えて色々とやりたいことが湧いてきて仕方がないです。 こういうワクワク感が出てくると目先のタスクがおざなりになりがちな性格なので、チームメンバーには「掛かり *2 気味になってたら、しっかり手綱を握って制御お願いします」と言ってまわっています💦。 2日目の各社の Drink Up から流れ着いた二次会、三次会、、、でも参加者と講演の話などで盛り上がりました。 懐がデカすぎる! @kawakubox 「ここのテーブルは全部俺が持つッ」 唐突なスケールにも対応できるMNTSQさんありがとうございます! #kaigionrails pic.twitter.com/arWbYF78ov — iberianpig(Kohei Yamada) (@nukumaro22) 2024年10月26日 弊社について「あぁ、もんてすきゅーさんね」と言われるくらいに認知はされ始めてきていると思いますが、プロダクトについてまったく知らないという方も多いと思います。 プロダクトについて知りたい!! Kaigi on Rails のことで話したい!! などありましたら、カジュアル面談でもよいですし、私の X アカウントでもよいので気軽にメッセージを送っていただけたらと思います。 careers.mntsq.co.jp Kaigi on Rails 2024 の クリエイティヴ は CC BY 4.0 のライセンス下で提供されております *1 : カジュアル面談でも飲みの場でもいくらでも話します *2 : 騎手と馬の呼吸が合わず、ちぐはぐな状態
アバター
みなさんこんにちは、SREチームメンバーの中岡です。 2024年10月16日に開催された「 Datadog Summit Tokyo 」に参加しましたので、そのレポートをお届けしたいと思います。 Datadogは SaaS で提供されている、 クラウド アプリケーションのためのモニタリングとセキュリティプラットフォームです。 弊社サービスの監視にもDatadogを使用しており、SREチームで取り組んでいるモニタリング改善の参考になればと思い、参加しました。 イベントの内容 Datadog Summit Tokyo Datadog Summitが東京で開催されるのは2019年以来、5年ぶりの開催とのこと。 参加者も多く、盛況なイベントでした。 Datadogセッション + お客様セッション 午前中はDatadogの歴史から始まり、日本国内にデータセンターを開設したことや、Datadog認定資格の日本語対応(まだ一部だけ)、日本法人の強化など、日本への対応に力を入れている事が伝わってきました。特に日本国内のデータセンターは、ログの保存先を国内にしたいといった需要が一定数あるのではないかと思います。 国内データセンター開設 お客様セッションは、Datadogのユーザによる活用事例の紹介です。 普段、他社でどのようにDatadogを利用しているかといった話を聞く機会がないため、このセッションは大変参考になりました。 以下、それぞれのセッションで聞いた内容をレポートします。 【Datadog ダッシュ ボードで 見える化 する、新たなビジネス価値創造のチャンス】 NTT DoCoMo 野部様 www.datadoghq.com d払いのサービス監視にDatadogを活用 DatadogのMonitor / APM 機能を活用 Anomaly機能(異常検知)も使っている ダッシュ ボード活用事例 新機能リリース時(d払いスタンプ機能) エラー数、レイテンシ、アクセス数をモニター(ユーザストーリごとに ダッシュ ボードを分ける) スタンプを集めた後の宝箱の 開封 率が低いと分かったので、UIを改善した ビジネス観点のモニター ダッシュ ボードとの向き合い方 必要なデータや見方を目的ごとに最適化 リク エス ト数が目標値に達しているか? 起動時間がどれくらいかかるか? 多く使われる 動線 はどちらか? 新機能がきちんとユーザに使われているのか?UIの 動線 が意図した通りに使われているか?といった分析に ダッシュ ボードを活用しているのが印象的でした。 【SLO監視文化の立ち上げジャーニー】 株式会社ワンキャリア SRE 渡邉様 www.datadoghq.com 短期間で複数サービス立ち上げ 2021年にSREチーム発足 当初はサービス監視のみ 2022年 パフォーマンスの維持をするためのSLO運用 始めてみたがうまくいかなかった SLO監視と違反対応の優先度が上がらない SREチームだけで定義し、それを開発チームに移管したのが原因 2023年 SLOの再構築(SREの 民主化 ) ナレッジの共有、運用負荷の低減、カルチャーの醸成、人事評価指標との連動 SLOに関する勉強会(SRE -> DEV) 開発チームと議論してSLI/SLOを決める Datadog SLO Dashboard を使った SLO Dayの開催(内部改善Dayのようなもの)/ SLO定例 / 経営陣への月次定期報告 人事評価にSLOの遵守の目標を導入する SLOの改善、パフォーマンス改善の効果があった 2024年 さらなるSLO改善 ユーザー体験を軸にしたSLO運用 アプリやフロントエンドのメトリクス計測 注力指標の選定(Critical User Journey:CUJ) CUJはPdMと議論しながら決める 重要度 x 頻度で決める SLO監視文化を根付かせる 同じSREとして、共感できる内容が多い講演でした。 SLOの運用をSREだけで進めても、開発チームは目の前の開発があり、なかなかSLO改善の優先度が上がらないというのは、私も過去実感しました。 SLO遵守を目標設定に組み込んだり、ナレッジ共有をしてDevOpsを進めたりといった活動など、SREに閉じずに活動をされているのが印象的でした。 【 クラウド マネージドサービスの挑戦:多様なSI/ SaaS 環境の共通基盤化】 東芝 デジタルソリューション 鹿野様 www.datadoghq.com アプリ寄りなSRE / インフラ寄りなSRE / PJ横断的なSRE -> 今回はインフラ寄りなSREの話 多数のプロジェクトのインフラを管理するSREチーム SREのミッション 共通基盤を開発、24/365の運用、安定稼働 多様なSI/ SaaS 環境の運用を共 通化 問題解決のアプローチ クラウド 、オンプレ、アプリそれぞれのエキスパートをSREチームに編成 何を共 通化 するか選定 閉域網を使って、複数の クラウド サービスを運用するためのネットワークを構築 共通基盤「 クラウド マネージドサービス」 クラウド 、 プライベートクラウド 、オンプレ環境を監視する共通基盤サービス 運用チーム立ち上げ 監視ツールは全てDatadogに統一し、学習コストを下げる アラート件名と本文の統一 -> 運用手順の共 通化 東芝 グループが提供する様々なソリューション・サービスの クラウド 環境を24時間体制で運用するための共通基盤として、監視ツールをDatadogで統一するという内容でした。 DatadogはIntegrationsを使用して、多様な クラウド サービスの監視を行えるので、確かに統合モニタリングとしても有用です。 また、アラートの件名や本文を統一するという運用ルールは、弊社の監視モニタリング改善でも取り組みたいと思いました。 【Workflow automation によるインシデント原因調査の自動化】Degica SRE 伊藤様 www.datadoghq.com 決済代行システム: KOMOJU サービス監視にDatadogを利用 インシデント復旧時間の改善 迅速に障害検知できること 問題の原因特定の時間をいかに短縮するか? インシデント発生時の例 -> 4xx / 5xxエラーが多発した 何から調べるか? 直近のデプロイ有無確認 LBのログ確認 APM のトレースログ 真因は、ユーザが誤ったカード情報を入力したことによりエラー数が増加した 偽陽性 アラート 優れたエンジニアであっても、 ドメイン 知識がないと解決に時間を要してしまう 対策:One Monitor, one runbook DatadogのWorkflow Automation アラートを契機に起動 条件によって処理を分岐 ユーザのカード情報に起因するエラーか判断 偽陽性 アラートか、要調査かをSlackに通知する ただし、ログの料金には注意 明日からできること 障害を迅速に検知できるようにする 監視モニターには必ずRunBookを用意 必要に応じてAutomation化 automation化する価値があるかどうかはよく考える このセッションでは、いかにインシデント発生時の復旧時間を改善するかといったテーマで、自社でサービスを運用していると必ず課題となる内容です。 Workflow automationは使用した事がないのですが、条件や対応手順が明確であれば、障害発生時に自動的にアクションまで実行されるのは便利ですね。 とはいえ、automationの作り込みは難しいと想像します。伊藤様も、「年に一回起きるアラートに対してautomationを作っても、価値は大きくない」とコメントされていました。 全体的に、登壇者にSREの方が多いのが印象的でした。 確かにこうしたモニタリングに関する業務はインフラ運用チームやSREが担う事が多いですが、SREという職種について国内での認知が広まったのも一因ではないかと思います。 ワークショップ 昼休みを挟み、午後はワークショップとパネルセッションに分かれます。 私はワークショップ「Datadog101:SRE」に参加しました。 午後のワークショップ、パネルセッション案内 ワークショップ用のDatadogアカウント、コンソール、 IDE などが用意され、設問に従ってDatadogの活用方法を学んでいきます。 Datadogは非常に機能が多いため、ワークショップでこれまで使ったことのない機能に触れる事ができるのは良いですね。 ワークショップは基本的に英語での提供ですが、今回のプログラムはDatadog日本法人によって翻訳されていました。 最後に クラウド 上でサービスを提供する事が当たり前となり、マイクロサービスによる コンポーネント 間の通信の複雑化などに対応するため、モニタリングも クラウド ネイティブである事が求められます。Datadogはそうした要求に対応するモニタリングツールの1つであり、弊社も活用していこうとしています。 今回のSummitでは特にユーザーのセッションが参考になり、モニタや ダッシュ ボード活用方法のヒントを得る事ができました。 また、ワークショップを通じてDatadogの機能理解も深める事ができました。
アバター
備忘録として残しておきます。 こんな感じで、IAMユーザーにStatement AとStatement Bを付与するインラインポリシーがあり、このインラインポリシーにStatement Cを追加しようとしました。 ところが、コードを変更してterraform applyをかけたところ、「LimitExceeded: Maximum policy size of 2048 bytes exceeded」と怒られてしまいました。 │ Error: putting IAM User (****) Policy (****): operation error IAM: PutUserPolicy, https response error StatusCode: 409, RequestID: ****, LimitExceeded: Maximum policy size of 2048 bytes exceeded for user **** どうやらポリシーの合計が2048 bytesをオーバーしてしまったようです。 仕方ないのでインラインポリシーを2つに分けました。 怒られました。 │ Error: putting IAM User (****) Policy (****): operation error IAM: PutUserPolicy, https response error StatusCode: 409, RequestID: ****, LimitExceeded: Maximum policy size of 2048 bytes exceeded for user **** 「言いがかりをつけるな!」と言いたい気持ちをSlackに吐き出しながら調べてみると、 こんなドキュメント を見つけました。 IAM ユーザー、ロール、またはグループに必要な数のインラインポリシーを追加できます。ただし、エンティティごとの総ポリシーサイズ (すべてのインラインポリシーの合計サイズ) は以下の制限を超えることはできません。 ・ ユーザーポリシーサイズは 2,048 文字を超えることはできません。 ・ロールポリシーサイズは 10,240 文字を超えることはできません。 ・ グループポリシーサイズは 5,120 文字を超えることはできません。 どうやらインラインポリシーを分けたとしても、合算されて制限がかかるようです (どうして) この方法では制限を回避することはできないようです。 結局それぞれを個別の管理ポリシーにし、IAMユーザーにアタッチすることによってこの問題を回避しました。 ユーザー、ロールに関わらず、インラインポリシーが太っていくような実装はしないほうが良さそうですね。 MNTSQ株式会社 SRE 西室
アバター
こんにちは、MNTSQでエンジニアをやっている平田です。 ありがたいことに生成AI関連のイベントでLTする機会を何度かいただいており、その発表資料の小ネタについて嬉しいコメントをいただきましたので、記事にすることにしました。 トーク ン節約の方法考えてたら、ぴったりのもの見つけた。 JSON を unpretty-print して、改行と空白を消す方法めちゃめちゃよい。 https://t.co/hGCW8AT9eL — Yuto (@YutoY3629) September 4, 2024 はじめに プロダクトで生成AIを活用する際、 トーク ン数(特に出力)の節約は重要です。 その理由は主に次のようなものです: 生成AIの課金体系が トーク ン数に依存する トーク ン数が多いとターンアラウンドタイムが長くなる また、プロダクトでの生成AI活用では、 JSON 形式で出力を構造化することが一般的です。 関連する話題として、ChatGPTやGeminiでは JSON 出力を強制するStructured Outputsが提供されていますね。 openai.com この記事では、プロダクトでの生成AI活用を念頭に置き、出力 JSON を圧縮(unpretty-print)することで トーク ン数を節約する方法をご紹介します。 やり方 やり方は非常に簡単で、プロンプトで「unpretty-printして」と指示するだけです。 次の例は、文章を JSON スキーマ に従って構造化するプロンプトで、unpretty-printの指示を加える前後の差分を示したものです。 {text} に文章、 {json_schema} に JSON スキーマ が入ります。 {text} -Transform the above text into a JSON according to the following JSON schema. Do not output anything other than the JSON. +Transform the above text into a JSON according to the following JSON schema. Output the JSON in an unpretty-printed format, and do not output anything other than the JSON. ```json {json_schema} ``` 日本語でも同じようにできます。 {text} -上記の文章を次のJSONスキーマに従って変換してください。JSON以外は何も出力しないでください。 +上記の文章を次のJSONスキーマに従って変換してください。JSONはunpretty-printedフォーマットで出力し、JSON以外は何も出力しないでください。 ```json {json_schema} ``` 効果 unpretty-printにより JSON から空白や改行が除去され、わたしが試した一例 *1 では 約43%の トーク ン節約 *2 になりました🎉 JSONのunpretty-printによるトークン節約効果 また、(少なくともわたしが試した限りでは) unpretty-print前後で空白や改行以外の差分はありません でした。 もちろん出力内容によっては差分が出ると思いますが、プロダクトとして許容可能な差分なら コスパ の高い手法だと思います。 ぜひ、試してみてください。 この記事を書いた人 Takumi Hirata MNTSQの アルゴリズム エンジニア。流離の なんでも屋 。 *1 : https://tech.mntsq.co.jp/entry/2024/06/17/110000 で紹介しているタスク *2 : tiktoken による計数
アバター
こんにちは! SREチームマネージャーの藤原です。 今回はライトな話題として、s3:ListBucketの プレフィックス 指定で時間を無駄にしたお話をしたいと思います。 よくあるs3リソースへのアクセス用ポリシー 特定のS3 バケット 内オブジェクトへの書き込み、読み込み、一覧表示としたい場合以下のようなIAMポリシーを作成することがよくあるでしょう。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject ", " s3:ListBucket " ] , " Resource ": [ " arn:aws:s3:::バケット名 ", " arn:aws:s3:::バケット名/* " ] } ] } 特定の バケット 内のオブジェクト全ての場合はこれで問題ありません。 では、 指定した バケット の特定 プレフィックス を持つオブジェクトのみを書き込み、読み込み、一覧表示したい場合 はどうなるでしょう。 意図した動作をしないパターン まずは意図したとおり動作せず失敗したパターンです。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject ", " s3:ListBucket " ] , " Resource ": [ " arn:aws:s3:::バケット名/aaaa/bbbb/* " ] } ] } 一見良さそうに見えます。 オブジェクトのアップロード、ダウンロード共に問題なく、 s3:PutObject 、 s3:GetObject は意図した通りに動作しています。 一方で AWS CLI を使って aws s3 ls s3://バケット名/aaaa/bbbb/ とすると以下のようなエラーが返ってきます。 An error occurred (AccessDenied) when calling the ListObjectsV2 operation: User: arn:aws:iam::アカウントID:user/IAMユーザー名 is not authorized to perform: s3:ListBucket on resource: "arn:aws:s3:::バケット名" because no identity-based policy allows the s3:ListBucket action 指定した バケット への s3:ListBucket は認可されてないと言われてしまいます。 原因の特定 原因の特定のために、 AWS の公式ドキュメント( Amazon S3 のアクション、リソース、条件キー )を見てみましょう 1 。 正常に動作している GetObject 、 PutObject を見てみましょう。 Resource type として object が必須指定となっています。 オブジェクトのARNは arn:${Partition}:s3:::${BucketName}/${ObjectName} の形で指定します。 ListBucket の行を眺めてみると、 Resource type として bucket が必須指定となっています。 バケット のARNの指定例は arn:${Partition}:s3:::${BucketName} となっており、先で失敗した内容では Resource で指定している内容が適切ではないことになります。 では、 ListBucket で特定 プレフィックス の配下だけを参照できるようにするにはどうしたら良いでしょう。 再度公式ドキュメントを眺めてみます。 ListBucket では、 Condition keys に s3:prefix が指定できることがわかります。 s3:prefix 条件キーを利用することで、キー名の プレフィックス を基にアクセスをフィルタリングすることができます。 最終的な形 s3:ListBucket はリソースとして バケット を指定しなければならず、 プレフィックス でアクセス制限をしたい場合は s3:prefix を条件キーとして指定する必要があることがわかりました。 最終的に 指定した バケット の特定 プレフィックス を持つオブジェクトのみを書き込み、読み込み、一覧表示 するためのポリシーは以下のようになりました。 { " Version ": " 2012-10-17 ", " Statement ": [ { " Effect ": " Allow ", " Action ": [ " s3:PutObject ", " s3:GetObject " ] , " Resource ": [ " arn:aws:s3:::バケット名/aaaa/bbbb/* " ] } , { " Effect ": " Allow ", " Action ": " s3:ListBucket ", " Resource ": " arn:aws:s3:::バケット名 ", " Condition ": { " StringLike ": { " s3:prefix ": " aaaa/bbbb/* " } } } ] } トラブルシュートの際は最終的には公式ドキュメントをしっかり読み込むことが重要ですね。 なお、本エントリで解説している内容は2024年9月時点のドキュメントをもとにしています。将来的には変更されている可能性がある点にはご留意ください。 ↩
アバター
こんにちは、MNTSQでエンジニアをやっている平田です。 先日、「生成AI時代のリーガルテック」という題目でお話させていただきました。 generative-ai-conf.connpass.com 合計240名の方にご参加いただいたとのことで、ご視聴いただいた方々、ありがとうございました。 本稿で簡単に内容を紹介させていただきます。 発表資料 speakerdeck.com パネルディスカッション テーマ1: リーガルを扱う上で難しいこと/それに対して工夫していること リーガルテック業界全体でいえば弁護士法72条との関係を取り上げられることが多いですが、MNTSQはサービスの性質上、弁護士法72条との関係で大きな問題は顕在化していません。MNTSQの生成AI活用で注目したいのは契約データの性質で、次のような観点があります。 契約データは機密情報なので、プロダクトは強固なセキュリティの上に実現する必要がある 契約データは長いので、生成AIを活用する上でコンテキストウィンドウが十分でなくchunkingが必要になったり、Lost in the Middle *1 の影響が大きくなったりすることがある 契約書の専門性が高いため、 アノテーション やモデル評価において ドメイン エキスパートとのコラボレーションが重要になる テーマ2: ドメイン 知識ある人とどう融合させているか ドメイン を横断するコラボレーションはMNTSQが創業当初から重視している文化の1つです。 https://speakerdeck.com/mntsq/mntsq-careersdeck?slide=22 コラボレーションを重視している点は変わりませんが、仕事の内容は変わります。 ML時代 ドメイン エキスパートによる アノテーション -> MLエンジニアによる モデリング -> 推論結果を一緒にエラー分析 生成AI時代 訓練用の アノテーション と モデリング がプロンプトエンジニアリングに置き換わった ドメイン エキスパートにプロンプトエンジニアリングを開放することで機能開発をスケール コードとプロンプトをうまく分離して管理する仕組みが重要 推論結果を一緒にエラー分析するところは変わらず、エンジニアはプロンプトの改善をサポート Slidoの質問 いくつか質問をいただいたのですが、時間の都合ですべてにお答えすることができませんでしたので、この場をお借りして回答させていただきます。 Q. 法律が変わったりしたときに、過去の事例が使えなくなったりするケースがあると思うんですがどうやって学習データの管理をされてますか? MNTSQで扱っている契約データは比較的時間依存性の低いデータということもあり、法律改正等によるデータ変化がアウトカムに著しく影響を及ぼすといった問題はまだ顕在化していません。問題が顕在化した際には、関連する法令等を契約データに メタデータ として持たせてフィルターするといった方法が考えられます。 Q. 品質はどうやって管理されてますか?テストもどうやって行われているのか?改善手法も。 タスクによって方法が変わります。テキストの分類・抽出といったタスクであれば アノテーション データを使った 定量 評価が可能ですので、MLOpsに近い仕組みで品質管理できます。一方で、自然文を出力するタスクは ドメイン エキスパートと意思決定者による定性評価になります。改善手法はまだベストプ ラク ティスがないので探索的です。プロンプトエンジニアリングの沼にハマらないように、 テストファースト で進めること、プロンプトを生成AIにレビューさせることを検討しています。 Q. リーガルテックならではのプロダクト開発で苦労した点や注意しなければならないことは何かありましたか? パネルディスカッションのテーマ1と同じ回答になります。 Q. RAGを活用されているとのことですが、fine-tuningモデルの開発などに取り組まれる可能性ありますか?また、リーガルテックにおけるfine-tuningとRAGのメリット・デメリットについてご意見あれば伺いたいです もちろんfine-tuningや(継続)事前学習は関心のある技術領域です。コストや体制の都合でまだ十分な検証はできていないですが、関連研究や事例を調査しながら今後チャレンジしていきたいと考えています。リーガルテックにおけるfine-tuningとRAGの関係に関しては関連研究 *2 でも報告されているように、うまく組み合わせることで高い性能を発揮するのではないかと期待しています。 Q. LLMのバージョンが上がったり、GPTに加えてGeminiその他の選択肢も増えてきたと思いますが、どのよう選定していますか。 次のような観点を重視しています。 タスクに対して十分な性能(コンテキストウィンドウ、精度、速度、スケーラビリティ等)かどうか ビジネスが成立するコストかどうか 日本リージョンで提供されているかどうか Q. 生成AIを活用した機能開発で、取り組んだけどうまくいかなかった事例は何かありますか?あれば理由も知りたいです 基盤モデルの知識をベースにして生成するタスク(例えばゼロから契約書を作る等)はあまりうまくいっていないです。理由は多分に推測を含みますが、基盤モデルが十分な契約データを学習していないこと、条文の機微を制御しきれないこと等が考えられます。 Q. 契約書を作成する過程で、契約当事者のレビューの後に契約内容の交渉、歩み寄り、妥協を検討する場面があると思いますが、交渉や歩み寄りの部分で生成AIにサポートを期待できることはありますか? あります。過去の契約データに付随する交渉のログや成果物を分析し、新たな契約の交渉に活用するという考え方はCLM(Contract Lifecycle Management)の本質であり、この分析や交渉アプローチの提案に生成AIは大きく寄与すると考えています。 Q. 生成AIを活用したプロダクトをユーザーに提供する場合、AIの性質を理解した上でサービスや機能設計を行うことが重要と考えますが、「これはリーガルテックならではの生成AIに関するサービス設計だろう」と思う部分があれば教えてください。 網羅的でなくて恐縮ですが、パッと思いつくものだと例えば次のような観点があると思います。 弁護士法72条に抵触しない(非弁行為にならない)ようにユーザー体験を設計 契約データを扱う上で、強固なセキュリティの上にプロダクトを実現 コードとプロンプトを分離しプロンプトエンジニアリングを ドメイン エキスパートに開放することでプロダクトデリバリーを高速化 Q. 他の業界の活用事例などをキャッチアップすることはありますか?具体的にどの業界が参考になるなどあれば教えていただきたいです なるべく業界を固定せずにリサーチすることを心掛けていますが、企業の業務プロセスに生成AIを組み込んだ事例はMNTSQの ユースケース にも近いので特に注目しています。 Q. リーガルテックでの生成AI活用はベストプ ラク ティスがなく各社検証中の状態かと思いますが、今現在で生成AIがフィットするための必要条件として見えてるものはありますか? ビジネスは意思決定の連続であり、業務プロセスは非構造化データのやりとりです。また生成AIをビジネスにフィットさせるには、ビジネス価値に直結させるのが良いと思います。必要条件とは少し違いますが、次のような使い方がフィットするのではないかと思います。 意思決定の根拠を提供することで、意思決定の精度を高める 生成AIの説明可能性は旧来のMLと比べて飛躍的に向上しています そのため生成AIの出力を意思決定に使うハードルが低くなっています 業務プロセスをシームレスに統合する 生成AIは非構造化データを構造化するのにとても適しています 例えばMNTSQの ユースケース では、契約データから メタデータ の抽出等に活用できます 抽出した メタデータ は契約書の検索・推薦・管理等で役立ちます もしもっと詳しい話を聞いてみたいという方がいらっしゃいましたらお気軽にDM *3 等でお問い合わせください。 カジュアル面談でもお待ちしています🙌 careers.mntsq.co.jp この記事を書いた人 Takumi Hirata MNTSQの アルゴリズム エンジニア。流離の なんでも屋 。 *1 : https://arxiv.org/abs/2307.03172 *2 : https://arxiv.org/abs/2403.01432v1 *3 : https://x.com/_hrappuccino
アバター