TECH PLAY

SCSKクラウドソリューション

SCSKクラウドソリューション の技術ブログ

1226

こんにちは、SCSKでAWSの内製化支援『 テクニカルエスコートサービス 』を担当している貝塚です。 前記事では、EC2への接続記録を必ず取得するという観点から、 Session Managerのログ記録 Fleet ManagerのRDP録画 ネットワーク経由の従来型ログイン を考慮する必要があることを説明し、1. Session Managerのログ記録 について設定方法等を解説しました。 AWS Systems Manager Session Managerでの監査ログ取得 - セッションログ設定編 Systems Manager Session Managerで監査証跡を取るためにCLIの入出力ログ取得とRDP録画の導入を検討しました。 blog.usize-tech.com 2026.03.09 本記事では 2. Fleet ManagerのRDP録画設定方法について解説します。 RDP接続記録の設定 RDP接続記録とは Fleet Manager Remote Desktopを使用したRDP接続を画面録画し、S3バケットに保存する機能です。GUI操作の証跡を動画として残すことができます。 RDP接続記録を有効化するには、Just-in-time(JIT)ノードアクセスを有効化する必要があります。JITノードアクセスは、動的で時間制限付きのアクセス制御を提供する機能で、承認ポリシーベースのアクセス管理を実現します。 Just-in-time ノードアクセスの概要 Just-in-time(JIT)ノードアクセスは、以下の特徴を持つアクセス制御機能です。 動的なアクセス制御: アクセス要求時に承認ポリシーを評価 時間制限付きアクセス: 一定時間後に自動的にアクセス権限が失効 承認ポリシーベース: Cedar言語(Amazonが開発したオープンソースのポリシー言語)で記述された承認ポリシーで制御 RDP接続記録の前提条件: RDP接続記録機能を有効化するために必須 JITノードアクセスを有効化することで、RDP接続記録機能が利用可能になります。 Quick Setupの初回セットアップ JITノードアクセスを有効化する前に、Quick Setupの初回セットアップを実行する必要があります。 この記事では単一アカウント環境の場合のみを説明しています。Organizations環境には対応していないのでご注意ください。 quicksetup-init.sh スクリプト(本記事で使用したスクリプトは、記事の最後に掲載しています)を実行します。 bash quicksetup-init.sh --profile default --region ap-northeast-1 –profileにはAWS CLIを実行するプロファイル名を指定します。 このスクリプトは、CloudFormation StackSets用のIAMロール2件を作成します。 AWS-QuickSetup-StackSet-Local-AdministrationRole AWS-QuickSetup-StackSet-Local-ExecutionRole JIT有効化の手順 Quick Setupの初回セットアップ完了後、 enable-jit-node-access.sh スクリプトを実行してJITノードアクセスを有効化します。 bash enable-jit-node-access.sh --profile default --region ap-northeast-1 –profileにはAWS CLIを実行するプロファイル名を指定します。 このスクリプトは以下の処理を実行します。 1. Configuration Managerを作成 2. JITノードアクセスを有効化 3. ホームリージョン、ターゲットアカウント、ターゲットリージョンを設定 スクリプト実行後、Configuration ManagerのステータスがSUCCEEDEDになることを確認してください。 RDP接続記録の設定 JITノードアクセスの有効化後、 configure-rdp-recording.sh スクリプトを実行してRDP接続記録を設定します。 bash configure-rdp-recording.sh --profile default --region ap-northeast-1 --stack-name my-session-manager-stack –profileにはAWS CLIを実行するプロファイル名、–stack-name2には 前の記事の「AWS CloudFormationでの設定」のところで作成したスタック名  を指定します。 このスクリプトは以下の処理を実行します。 1. CloudFormationスタックからKMSキーARNとS3バケット名を取得 2. ssm-guiconnect update-connection-recording-preferences APIを使用してRDP接続記録を設定 設定完了後、以下のコマンドで設定内容を確認できます。 aws ssm-guiconnect get-connection-recording-preferences --region ap-northeast-1 承認ポリシーの作成 JITノードアクセスでは、Cedar言語で記述された承認ポリシーによってアクセス制御を行います。承認ポリシーの作成にはSystems Manager統合エクスペリエンスの有効化が必要です。Systems Managerコンソールを開き、画面上部に「新しいエクスペリエンスを有効にする」バナーが表示されている場合はクリックして有効化してください。 有効化後、承認ポリシーの作成はAWSマネジメントコンソールから実施します。 1. Systems Manager >ジャストインタイムノードアクセス に移動 2. 承認ポリシー タブを選択 3. 自動承認ポリシーを作成 をクリック 4. ポリシーステートメントを入力 5. 自動承認ポリシーを公開 ボタンをクリック 「3. 自動承認ポリシーを作成」の画面 自動承認ポリシーによるアクセスの有効期間は1時間で、変更できません。また、AWSアカウントとリージョンごとに1つの自動承認ポリシーのみ作成可能です。 以下は、特定のタグを持つノードへのアクセスを自動承認するポリシーステートメントの例です。 permit ( principal, action == AWS::SSM::Action::"getTokenForInstanceAccess", resource ) when { resource.hasTag("Project") && resource.getTag("Project") == "windows-ec2-private" }; permitステートメントの各要素は以下の通りです。 principal: アクセスを要求するユーザー。”principal,” と書くとすべてのユーザーが対象になる。’principal in AWS::IdentityStore::Group::”グループID”,’ と書くと特定のIAM Identity Centerグループに限定できる action: JITノードアクセスでは常に AWS::SSM::Action::”getTokenForInstanceAccess” resource: アクセス対象のノード when句: 自動承認の条件。resource.hasTag()やresource.getTag()でノードのタグを評価できる 設計上の留意点 1. KMSキーの要件 RDP録画の暗号化に使用するKMSキーには、以下の要件があります。 対称キー(Symmetric key)であること 暗号化と復号化(Encrypt and decrypt)用途であること 必須タグ: SystemsManagerJustInTimeNodeAccessManaged: true このタグがないと、RDP接続記録の設定時にエラーが発生します。 2. S3バケットポリシー RDP録画ファイルを保存するS3バケットのバケットポリシーには、ssm-guiconnect.amazonaws.com サービスプリンシパルが、RDP録画ファイルをS3バケットに書き込むための権限(PutObject)が必要です。 { "Sid": "ConnectionRecording", "Effect": "Allow", "Principal": { "Service": ["ssm-guiconnect.amazonaws.com"] }, "Action": "s3:PutObject", "Resource": [ "arn:aws:s3:::bucket-name", "arn:aws:s3:::bucket-name/*" ], "Condition": { "StringEquals": { "aws:SourceAccount": "account-id" } } } aws:SourceAccount条件により、特定のAWSアカウントからのアクセスのみを許可します。 3. IAMロールの権限 RDP接続を開始するユーザーには、以下の権限が必要です。(IAMロール作成は、本記事・前記事のCloudFormationテンプレートやスクリプトには含まれていません) ssm-guiconnect:StartConnection ssm-guiconnect:CancelConnection ssm-guiconnect:GetConnection kms:CreateGrant RDP接続とRDP録画の確認 ここまで完了していれば、RDP録画の準備はできています。Fleet Managerを使ってEC2インスタンスに接続(テストインスタンスが必要な場合は 前の記事 でCloudFormationを使って作成したEC2インスタンスを使用)してください。 リモートデスクトップ画面の上部に表示されている設定タブをクリックし、「S3への接続を記録」が有効になっていること、S3バケット に録画保存先のS3名が表示されることを確認してください。 リモートデスクトップ接続を終了すると、指定されたS3バケットに録画データが置かれていることを確認できるはずです。 まとめ RDP録画有効化手順についてまとめました。 次の記事では、ネットワークの面から検討・考察を行います。   スクリプト類 本記事に出てきたスクリプト類を掲載します。 quicksetup-init.sh #!/bin/bash # Quick Setup 初回セットアップスクリプト # このスクリプトは、AWS Systems Manager Quick Setupの初回セットアップを # AWS CLIを使用してプログラマティックに実行します。 # # 使用方法: # 単一アカウント環境: bash scripts/quicksetup-init.sh # Organizations環境: bash scripts/quicksetup-init.sh --enable-organizations set -e # 設定 REGION="ap-northeast-1" PROFILE="" ENABLE_ORGANIZATIONS=false # コマンドライン引数の解析 while [[ $# -gt 0 ]]; do case $1 in --profile) PROFILE="$2" shift 2 ;; --enable-organizations) ENABLE_ORGANIZATIONS=true shift ;; --region) REGION="$2" shift 2 ;; *) echo "不明なオプション: $1" echo "使用方法: $0 [--profile PROFILE] [--enable-organizations] [--region REGION]" exit 1 ;; esac done # プロファイルオプションの設定 PROFILE_OPT="" if [ -n "${PROFILE}" ]; then PROFILE_OPT="--profile ${PROFILE}" fi ACCOUNT_ID=$(aws sts get-caller-identity ${PROFILE_OPT} --query Account --output text) echo "==========================================" echo "Quick Setup 初回セットアップスクリプト" echo "==========================================" echo "アカウントID: ${ACCOUNT_ID}" echo "リージョン: ${REGION}" echo "Organizations統合: ${ENABLE_ORGANIZATIONS}" echo "" # Organizations統合の有効化(オプション) if [ "${ENABLE_ORGANIZATIONS}" = true ]; then echo "Organizations統合を有効化します..." echo "" # ステップ1: CloudFormationの信頼アクセスを有効化 echo "ステップ1: CloudFormationの信頼アクセスを有効化中..." if aws cloudformation activate-organizations-access ${PROFILE_OPT} --region "${REGION}" 2>&1 | grep -q "already activated"; then echo " 既に有効化されています" else echo " 有効化しました" fi echo "" # ステップ2: Systems ManagerとOrganizationsの統合を有効化 echo "ステップ2: Systems ManagerとOrganizationsの統合を有効化中..." if aws organizations enable-aws-service-access ${PROFILE_OPT} --service-principal ssm.amazonaws.com 2>&1 | grep -q "already enabled"; then echo " 既に有効化されています" else echo " 有効化しました" fi echo "" else echo "単一アカウントモードで実行します(Organizations統合はスキップ)" echo "" fi # ステップ3: Explorer用のIAMロールを作成(単一アカウント環境では不要) if [ "${ENABLE_ORGANIZATIONS}" = true ]; then echo "ステップ3: Explorer用のIAMロールを作成中..." else echo "ステップ3: Explorer用のIAMロールを作成中(単一アカウント環境ではスキップ)..." fi if [ "${ENABLE_ORGANIZATIONS}" = true ]; then EXPLORER_ROLE_NAME="AWS-QuickSetup-SSM-RoleForEnablingExplorer" if aws iam get-role ${PROFILE_OPT} --role-name "${EXPLORER_ROLE_NAME}" --region "${REGION}" >/dev/null 2>&1; then echo " ${EXPLORER_ROLE_NAME}: 既に存在します" else aws iam create-role \ ${PROFILE_OPT} \ --role-name "${EXPLORER_ROLE_NAME}" \ --assume-role-policy-document file://scripts/quicksetup-init/explorer-role-trust-policy.json \ --region "${REGION}" >/dev/null echo " ${EXPLORER_ROLE_NAME}: 作成しました" fi fi echo "" # ステップ4: Quick Setupサービス設定を更新 echo "ステップ4: Quick Setupサービス設定を更新中..." aws ssm update-service-setting \ ${PROFILE_OPT} \ --setting-id "arn:aws:ssm:${REGION}:${ACCOUNT_ID}:servicesetting/ssm/opsitem/ssm-patchmanager" \ --setting-value "Enabled" \ --region "${REGION}" 2>&1 | grep -q "." || echo " 設定を更新しました" echo "" # ステップ5: CloudFormation StackSets用のIAMロールを作成 echo "ステップ5: CloudFormation StackSets用のIAMロールを作成中..." # Administration Role ADMIN_ROLE_NAME="AWS-QuickSetup-StackSet-Local-AdministrationRole" if aws iam get-role ${PROFILE_OPT} --role-name "${ADMIN_ROLE_NAME}" --region "${REGION}" >/dev/null 2>&1; then echo " ${ADMIN_ROLE_NAME}: 既に存在します" else aws iam create-role \ ${PROFILE_OPT} \ --role-name "${ADMIN_ROLE_NAME}" \ --assume-role-policy-document file://scripts/quicksetup-init/administration-role-trust-policy.json \ --region "${REGION}" >/dev/null aws iam attach-role-policy \ ${PROFILE_OPT} \ --role-name "${ADMIN_ROLE_NAME}" \ --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" \ --region "${REGION}" echo " ${ADMIN_ROLE_NAME}: 作成しました" echo " IAMの整合性を待機中(10秒)..." sleep 10 fi # Execution Role用のTrust Policyを動的に生成 EXEC_ROLE_NAME="AWS-QuickSetup-StackSet-Local-ExecutionRole" EXEC_TRUST_POLICY=$(cat </dev/null 2>&1; then echo " ${EXEC_ROLE_NAME}: 既に存在します" else echo "${EXEC_TRUST_POLICY}" > execution-role-trust-policy.json aws iam create-role \ ${PROFILE_OPT} \ --role-name "${EXEC_ROLE_NAME}" \ --assume-role-policy-document file:///execution-role-trust-policy.json \ --region "${REGION}" >/dev/null aws iam attach-role-policy \ ${PROFILE_OPT} \ --role-name "${EXEC_ROLE_NAME}" \ --policy-arn "arn:aws:iam::aws:policy/AdministratorAccess" \ --region "${REGION}" rm /execution-role-trust-policy.json echo " ${EXEC_ROLE_NAME}: 作成しました" fi echo "" echo "==========================================" echo "Quick Setup 初回セットアップが完了しました" echo "==========================================" echo "" echo "作成されたリソース:" if [ "${ENABLE_ORGANIZATIONS}" = true ]; then echo " - IAMロール: ${EXPLORER_ROLE_NAME}" fi echo " - IAMロール: ${ADMIN_ROLE_NAME}" echo " - IAMロール: ${EXEC_ROLE_NAME}" echo "" echo "次のステップ:" echo " 1. JIT有効化スクリプトを実行: bash enable-jit-node-access.sh" echo "" enable-jit-node-access.sh  #!/bin/bash # Just-in-time Node Access有効化スクリプト # このスクリプトは、AWS Systems Manager Quick SetupのAPIを使用して # Just-in-time (JIT) ノードアクセスを有効化します。 set -e # 設定 REGION="ap-northeast-1" PROFILE="" # コマンドライン引数の解析 while [[ $# -gt 0 ]]; do case $1 in --profile) PROFILE="$2" shift 2 ;; --region) REGION="$2" shift 2 ;; *) echo "不明なオプション: $1" echo "使用方法: $0 [--profile PROFILE] [--region REGION]" exit 1 ;; esac done # プロファイルオプションの設定 PROFILE_OPT="" if [ -n "${PROFILE}" ]; then PROFILE_OPT="--profile ${PROFILE}" fi ACCOUNT_ID=$(aws sts get-caller-identity ${PROFILE_OPT} --query Account --output text) CONFIG_MANAGER_NAME="JITNodeAccess-${ACCOUNT_ID}" S3_BUCKET_NAME="windows-ec2-private-session-logs-${ACCOUNT_ID}" KMS_KEY_ALIAS="alias/windows-ec2-private-rdp-recording-key" echo "==========================================" echo "Just-in-time Node Access 有効化スクリプト" echo "==========================================" echo "アカウントID: ${ACCOUNT_ID}" echo "リージョン: ${REGION}" echo "S3バケット: ${S3_BUCKET_NAME}" echo "KMSキーエイリアス: ${KMS_KEY_ALIAS}" echo "" # KMSキーIDを取得 echo "KMSキーIDを取得中..." KMS_KEY_ID=$(aws kms describe-key ${PROFILE_OPT} --key-id "${KMS_KEY_ALIAS}" --region "${REGION}" --query 'KeyMetadata.KeyId' --output text) echo "KMSキーID: ${KMS_KEY_ID}" echo "" # 必要なIAMロールが存在するか確認 echo "必要なIAMロールを確認中..." ADMIN_ROLE_NAME="AWS-QuickSetup-StackSet-Local-AdministrationRole" EXEC_ROLE_NAME="AWS-QuickSetup-StackSet-Local-ExecutionRole" # Administration Roleの存在確認 if aws iam get-role ${PROFILE_OPT} --role-name "${ADMIN_ROLE_NAME}" --region "${REGION}" >/dev/null 2>&1; then echo " ${ADMIN_ROLE_NAME}: 存在します" else echo " ${ADMIN_ROLE_NAME}: 存在しません。作成が必要です。" echo "" echo "エラー: 必要なIAMロールが存在しません。" echo "Quick Setupを使用する前に、以下のロールを作成する必要があります:" echo " - ${ADMIN_ROLE_NAME}" echo " - ${EXEC_ROLE_NAME}" echo "" echo "これらのロールは、AWS Systems Manager Quick Setupの初回セットアップ時に自動作成されます。" echo "Systems Managerコンソールから「Quick Setup」を開き、初回セットアップを完了してください。" exit 1 fi # Execution Roleの存在確認 if aws iam get-role ${PROFILE_OPT} --role-name "${EXEC_ROLE_NAME}" --region "${REGION}" >/dev/null 2>&1; then echo " ${EXEC_ROLE_NAME}: 存在します" else echo " ${EXEC_ROLE_NAME}: 存在しません。作成が必要です。" echo "" echo "エラー: 必要なIAMロールが存在しません。" exit 1 fi ADMIN_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/${ADMIN_ROLE_NAME}" echo "" # Configuration Definitionの作成 echo "Configuration Definitionを作成中..." CONFIG_DEF=$(cat <<EOF { "Type": "AWSQuickSetupType-JITNA", "LocalDeploymentAdministrationRoleArn": "${ADMIN_ROLE_ARN}", "LocalDeploymentExecutionRoleName": "${EXEC_ROLE_NAME}", "Parameters": { "HomeRegion": "${REGION}", "TargetAccounts": "${ACCOUNT_ID}", "TargetRegions": "${REGION}" } } EOF ) echo "Configuration Definition:" echo "${CONFIG_DEF}" | jq . echo "" # Configuration Managerの作成 echo "Configuration Managerを作成中..." set +e RESPONSE=$(aws ssm-quicksetup create-configuration-manager \ ${PROFILE_OPT} \ --name "${CONFIG_MANAGER_NAME}" \ --description "Just-in-time Node Access configuration for Windows EC2 instances" \ --configuration-definitions "${CONFIG_DEF}" \ --region "${REGION}" \ --output json 2>&1) EXIT_CODE=$? set -e echo "終了コード: ${EXIT_CODE}" echo "レスポンス:" echo "${RESPONSE}" echo "" if [ ${EXIT_CODE} -eq 0 ]; then echo "成功: Configuration Managerが作成されました" MANAGER_ARN=$(echo "${RESPONSE}" | jq -r '.ManagerArn') echo "Manager ARN: ${MANAGER_ARN}" else echo "エラー: Configuration Managerの作成に失敗しました" exit 1 fi echo "" echo "==========================================" echo "Just-in-time Node Access の有効化が完了しました" echo "==========================================" echo "" echo "次のステップ:" echo "1. Systems Manager コンソールで設定を確認" echo "2. 承認ポリシーを作成(自動承認または手動承認)" echo "3. EC2インスタンスへのアクセスをテスト" echo "" configure-rdp-recording.sh #!/bin/bash # RDP接続記録設定スクリプト(オプトインログ記録機能用) # このスクリプトは、Fleet Manager RDP接続の記録設定を行います # 新しいスタック(windows-ec2-private-vpc-opt-in)用 set -e # 設定 REGION="ap-northeast-1" STACK_NAME="windows-ec2-private-vpc-opt-in" PROFILE="" # コマンドライン引数の解析 while [[ $# -gt 0 ]]; do case $1 in --profile) PROFILE="$2" shift 2 ;; --region) REGION="$2" shift 2 ;; --stack-name) STACK_NAME="$2" shift 2 ;; *) echo "不明なオプション: $1" echo "使用方法: $0 [--profile PROFILE] [--region REGION] [--stack-name STACK_NAME]" exit 1 ;; esac done # プロファイルオプションの設定 PROFILE_OPT="" if [ -n "${PROFILE}" ]; then PROFILE_OPT="--profile ${PROFILE}" fi echo "==========================================" echo "RDP接続記録設定スクリプト(オプトインログ記録機能用)" echo "==========================================" echo "スタック名: ${STACK_NAME}" echo "リージョン: ${REGION}" echo "" # CloudFormationスタックから情報を取得 echo "CloudFormationスタックから情報を取得中..." ACCOUNT_ID=$(aws sts get-caller-identity ${PROFILE_OPT} --query Account --output text) KMS_KEY_ARN=$(aws cloudformation describe-stacks \ ${PROFILE_OPT} \ --stack-name "${STACK_NAME}" \ --region "${REGION}" \ --query 'Stacks[0].Outputs[?OutputKey==`RDPRecordingKMSKeyArn`].OutputValue' \ --output text) S3_BUCKET_NAME=$(aws cloudformation describe-stacks \ ${PROFILE_OPT} \ --stack-name "${STACK_NAME}" \ --region "${REGION}" \ --query 'Stacks[0].Outputs[?OutputKey==`SessionLogsBucketName`].OutputValue' \ --output text) echo "アカウントID: ${ACCOUNT_ID}" echo "S3バケット: ${S3_BUCKET_NAME}" echo "KMSキーARN: ${KMS_KEY_ARN}" echo "" # 値の検証 if [ -z "${KMS_KEY_ARN}" ] || [ -z "${S3_BUCKET_NAME}" ]; then echo "エラー: CloudFormationスタックから必要な情報を取得できませんでした" echo "スタック名が正しいか確認してください: ${STACK_NAME}" exit 1 fi # 既存の設定を確認 echo "既存のRDP接続記録設定を確認中..." set +e EXISTING_PREFS=$(aws ssm-guiconnect get-connection-recording-preferences \ ${PROFILE_OPT} \ --region "${REGION}" \ --no-cli-pager 2>&1) EXIT_CODE=$? set -e if [ ${EXIT_CODE} -eq 0 ]; then echo "既存の設定が見つかりました:" echo "${EXISTING_PREFS}" | jq . echo "" echo "既存の設定を上書きしますか? (y/N)" read -r RESPONSE if [[ ! "${RESPONSE}" =~ ^[Yy]$ ]]; then echo "処理を中止しました" exit 0 fi echo "" fi # RDP接続記録設定を更新 echo "RDP接続記録設定を更新中..." # JSONペイロードを作成 PAYLOAD=$(cat <&1) EXIT_CODE=$? if [ ${EXIT_CODE} -eq 0 ]; then echo "成功: RDP接続記録設定が更新されました" echo "${RESPONSE}" | jq . else echo "エラー: RDP接続記録設定の更新に失敗しました" echo "${RESPONSE}" exit 1 fi echo "" echo "==========================================" echo "RDP接続記録設定が完了しました" echo "==========================================" echo "" echo "設定内容:" echo " - S3バケット: ${S3_BUCKET_NAME}" echo " - KMSキー: ${KMS_KEY_ARN}" echo "" echo "次のステップ:" echo " 1. Fleet Manager > Managed nodes に移動" echo " 2. 対象のEC2インスタンスを選択" echo " 3. Node actions > Connect > Remote Desktop を選択" echo " 4. RDP接続をテスト" echo " 5. 接続終了後、S3バケットに記録が保存されることを確認" echo "" echo "S3バケット内の記録を確認:" echo " aws s3 ls s3://${S3_BUCKET_NAME}/ --recursive --region ${REGION}" echo ""
アバター
こんにちは、SCSKの松岡です🔗 データ連携の実装でAWS Glue (Python Shell Job)を導入した際の試行錯誤を整理しました。 RDSからデータレイクであるS3 Tablesに連携する際に、横展開可能な軽量なデータ連携ジョブを実現するために気にしたポイントについて紹介します。 背景 データ活用基盤を構築するにあたり、「データをどのように集めるか」は重要なテーマの一つです。 仮に収集元のシステムが単一であっても、対象となるテーブルが複数存在する場合、テーブルごとに連携方法を検討し、ジョブとして実装していく必要があります。そのため、連携対象のテーブル数が多い場合には、テーブル単位での開発工数をいかに抑えつつ、効率的に横展開できるかが重要なポイントとなります。 また、データ連携方式を検討する際には、データ量だけでなく実行頻度も重要な判断軸となります。1回あたりのデータ量が少量であっても、高頻度で実行する必要がある場合、実行回数に比例してコストが増加するため、想定以上にコストが膨らむ可能性があります。 連携元はRDS(PostgreSQL)のみだが、連携対象テーブルが多数 処理1回あたりのデータ量は少量だが、毎時で何度も差分連携しなければならない 開発コストと運用コストの両方をなるべく抑えたい データレイクとしてIceberg (S3 Tables)にデータを連携したい このような前提のもと、開発コストと運用コストの双方を抑えつつ、シンプルに実現できるデータ連携方式を検討しました。   構成と選定理由 Why AWS Glue(Python Shell)? データ連携方式として、AWSサービスを利用することを前提に、以下から比較検討しました。 AWS Glue (Python Shell) AWS Glue (Spark) AWS DMS AWS Lambda その結果、今回は AWS Glue (Python Shell) を採用しました。 主な理由は以下の通りです。 AWS Glue (Python Shell)は起動時間が短く、小規模かつ高頻度なデータ連携を効率的に実行できる AWS Glue (Python Shell)は、PythonベースでSQL抽出や変換処理を柔軟に制御できる AWS Glue (Spark)は大規模データ処理に適しているが、要件に対して過剰スペックになる AWS DMSには差分レプリケーション機能があるが、S3 Tablesに対応していない(※2025調査時点) AWS Lambdaで柔軟な処理開発が可能だが、15分の実行時間制約がある 参考:AWS Glue の料金 構成 データソースからRDS(PostgreSQL)に集約したデータを、AWS Glue(Python Shell)ジョブによりAmazon S3へ連携し、未加工データとしてS3 Tablesに蓄積します。その後、AthenaやRedshiftから参照・集計し、BIツール(Power BI)で可視化する構成としています。 構成図では簡略化していますが、ジョブの起動はGlueトリガーによりスケジュール実行し、実行結果の監視や通知についてはEventBridgeおよびAmazon SNSを組み合わせて実現しています。 ※本記事ではGlueジョブ中の変換処理は扱わず、未加工データをそのまま連携するシンプルなデータパイプラインにフォーカスしています。   気にしたポイント 差分更新の設計 Glueの処理にフォーカスしたデータ連携の概要は、以下の通りです。 Glueジョブを動かすための要素として、RDS側で管理・履歴テーブル、S3側に各種ファイル配置先を設定しています。 本構成では、RDS(PostgreSQL)に格納された業務データを、AWS Glue(Python Shell)ジョブによりS3へ連携します。 差分更新を実現するため、RDS側にはジョブ実行日時を管理する「管理テーブル」と「履歴テーブル」を配置し、S3側にはデータ出力先に加えて、Glueで利用するソースコードやドライバ、ライブラリを格納するバケットを用意しています。 Glueジョブは実行時に管理テーブルを参照し、前回処理日時をもとに差分データを抽出します。抽出したデータはS3へ出力され、その後、処理結果を管理テーブルへ反映することで、次回実行時の差分基準として利用されます。 また、GLueジョブの実行結果は履歴テーブルにも記録されるように設計しています。具体的には、各実行ごとに「連携対象日時(FROM/TO)」「処理件数」「実行成否」などをRDSに記録することで、過去の実行状況を追跡可能としています。 さらに、実行結果はCloudWatch Logsにも出力し、個々のジョブ実行の詳細を確認できるようにしています。 参考:AWS Glue ジョブのログ記録 ジョブのパラメータ設定 Glueジョブでは、パラメータを指定することで、ジョブごとに処理対象や抽出条件を動的に制御することができます。 このパラメータ設計は、複数テーブルのジョブ実装対応を効率化する上で重要なポイントとなります。テーブルごとに個別のジョブを1から作成すると開発負荷が高くなるため、ジョブをパラメータ化し、共通のジョブ定義をベースとして複製することで、複数テーブルに対応できるようにしました。 (GlueジョブはClone機能により簡単に複製が可能です) 具体的には、以下のようなパラメータを定義しています。 table_name :対象テーブル名を指定し、スクリプト内で参照するテーブルを動的に切り替える primary_key :主キー項目を指定し、更新処理時のキー条件を動的に制御する last_update_column :差分抽出に利用する列を指定し、当該列をもとにフィルタ条件を動的に生成する これにより、ジョブ定義の共通化と再利用性を確保しつつ、テーブル数が増加した場合でも効率的に展開できる構成としています。 参考:AWS Glue の Python パラメータの受け渡しとアクセス PostgreSQL → Icebergのデータ型変換 出力先をIcebergテーブルとして利用する前提のため、データ型の整合性を意識した設計を行いました。 PostgreSQLとIcebergでは型の扱いに差異があるため、スキーマをもとに列ごとの型変換を動的に適用する方式としています。これにより、テーブルごとに個別対応することなく、共通ロジックで型整合性を担保できる構成としています。 また、今回のケースに限らず、システム間連携においては、各システムで扱うデータ型の仕様を正しく理解しておくことが重要です。特に、型の不一致による連携漏れや桁あふれなどの問題が発生しないよう、事前に設計段階で考慮しておく必要があります。 参考:Icebergのデータ型の仕様 ライブラリ管理 Glue Python Shellでは、処理内容に応じて必要なPythonライブラリを自前で準備する必要があります。 今回の構成では、PostgreSQL接続用にpg8000、S3 Tables / Iceberg操作用にpyicebergなどのライブラリを利用しており、これらをwhlファイルとしてS3に配置して使用しています。 これらのライブラリを事前に配置しておくことで、実行時に外部リポジトリへアクセスして取得する必要がなく、インターネット接続に依存しない安定した実行環境を構築することができます。 また、Glue Python Shellにはあらかじめ利用可能なライブラリが含まれている一方で、用途によっては追加でライブラリを用意する必要があります。そのため、利用するライブラリが事前インストール済かどうかを確認した上で、必要に応じて追加対応を行うことが重要です。 さらに、ライブラリによってはGlueの実行環境との互換性によりそのまま利用できない場合もあるため、事前の検証やビルド方法の調整が必要となる点には注意が必要です。 参考:AWS Glue Pythonジョブにおけるライブラリ管理   まとめ AWS Glue Python Shellは、起動時間が短く、コストを抑えながらPythonで柔軟に実装できる点が特徴であり、軽量なデータ連携基盤として非常に有効な選択肢です。特に、SQL主体のシンプルなETL処理や高頻度のバッチにおいては、その特性を活かしやすく、効率的なデータ連携を実現できました。 一方で、ライブラリの準備や実行環境との互換性、差分更新の設計などは自前で考慮する必要があり、構成によっては設計・運用面での工夫が求められます。また、ジョブのパラメータ設計やログ出力などを含めた運用設計も重要なポイントとなります。そのため、処理内容やデータ量、運用要件に応じて、Glue SparkやDMS、リアルタイム連携であればKinesis Data Firehose、またはTROCCOのようなサードパーティ製ETLツールも含めて、適切に使い分けることが重要だと思いました。 本記事のような軽量かつ柔軟なデータ連携のユースケースにおいては、Glue Python Shellは実用性の高い選択肢であると感じました。   (宣伝) クラウドデータ活用サービス 今回ご紹介した内容は、SCSKで提供しているクラウドデータ活用サービスの中で扱っているテーマの一部になります。 お客様のデータ活用状況に応じて、基盤構築から可視化、データ連携、データマネジメント、高度データ活用までを段階的にご支援しています! ご関心あれば、以下のサービスページもご参照ください。 クラウドデータ活用サービス|サービス|企業のDX戦略を加速するハイブリッドクラウドソリューション クラウドデータ活用サービスは、お客様のデータ活用状況に合わせ、適切なご支援が可能です。 www.scsk.jp
アバター
SCSKの畑です。 今回もデータベース関連の話題ですが、若干毛色の異なる内容となります。 要件とその背景 本案件における MySQL (RDS/Aurora) の各種ログは Cloudwatch Logs に出力されているような設計となっているのですが、ログの一部をマスキングできないかという相談を受けました。具体的には以下のような要件です。 本番環境用 AWS アカウントの Cloudwatch Logs に出力されたログはマスキングしない 運用保守用 AWS アカウントの Cloudwatch Logs に本番環境用 AWS アカウントで出力されたログを転送し、その際にログの一部をマスキングしたい 上記要件の背景として、お客さんが本番環境用 AWS アカウントでオペレーションする場合は、本番運用ルーム(特権区画)に入室の上、各種制約の下で作業する必要があります。よって、一般区画でもアクセスできる AWS アカウント上で本番環境の RDS/Aurora ログ確認・分析やそれに基づく各種調査を行えるようにすることで、より円滑に運用・保守を行えるようにしたいという意図がありました。 ただし、MySQL ログにはいわゆる PII のような機微な情報が含まれる可能性があることから、運用保守用 AWS アカウントの Cloudwatch Logs にログを転送する際は 100% 確実にマスキングを行う必要があります。 ここが方式検討時におけるポイントでした。   補足:マスキング対象の MySQL ログについて そもそも、PII のような機微な情報が含まれる可能性がある MySQL ログがあるのか?と思われる方もいるかもしれません。その疑問自体は正しいと思っていて、実質的に「データベース上で実行された SQL 文がそのままログに出力される」ようなケースのみが該当します。例えば SQL 文の where 句にそのような機微な情報が含まれているようなケースですね。 つまり、データベース上で実行された SQL 文がそのままログに出力され得る MySQL ログのみをマスキング対象とすれば OK ということになります。具体的には以下 3 種類となりますが、MySQL エラーログについては極めて限定的なケースであるため、実質的にはほぼスロークエリログ及び監査ログのみが対象となります。 MySQL エラーログ スロークエリログ 監査ログ(クエリ情報を出力する場合) 本案件では監査ログにクエリ情報を出力しない方針であったため、まずはスロークエリログを対象に検討を進めることになりました。 スロークエリの出力例は以下の通りです。ヘッダとして実行時間やユーザ、クエリ実行時の各種統計情報が含まれており、実行された SQL 文の情報は SET timestamp 文の直後に出力されます。 # Time: 2025-05-13T05:36:51.377085Z # User@Host: admin[admin] @ [10.10.51.21] Id: 6102 # Query_time: 1.192364 Lock_time: 0.000002 Rows_sent: 0 Rows_examined: 4999999 use test; SET timestamp=1747113033; select * from regex_test where col1 > 100 and col2 = "abc"   実装案1:Bedrock or マスキングに適したマネージドサービスの使用 元々のお客さんからの要望としては、生成AI(LLM)や機械学習などを使用して、非決定的なルールで PII のような情報を検知してマスキングを実現できないか?という内容でした。正規表現などを使用してログマスキング用のスクリプトを実装するようなアプローチでは決定的なルールとなるため、未知のパターンにそのまま対応できないこと、対応に際して相応のコストがかかることなどが理由でした。AWS 上でそのような仕組みの実装を検討するとなると、当然ですが Bedrock が候補となります。 一方で、生成 AI の原理・特性上、PII のような情報を含むログを入力しても 100% マスキングできるという保証はありません。生成 AI が非決定的なルール(プロンプト)を解釈して処理ができる以上、その振る舞いも非決定的になることは現状避けられないとも言えます。いずれにせよ、先述の通り運用保守用 AWS アカウントの Cloudwatch Logs にログを転送する際は 100% 確実にマスキングを行う必要があり、 この要件を満たすことができなかったため今回は見送りとなり、決定的なルールによるマスキング方式を採用することとなりました。 対応時期は Bedrock Guardrails が日本語対応する少し前だったのですが、もし今であればこの機能の使用を前提とした上でもう少し真剣に Bedrock の使用を検討したかもしれません。いずれにせよ非決定的な要素は排除できないので、他のサービス/方式と組み合わせる、Bedrock のマスキング処理対象を限定するなどの検討が必要であろうなとは思います。 Amazon Bedrock Guardrails が日本語に対応しました | Amazon Web Services 本記事では、日本語が扱えるようになった Amazon Bedrock Guardrails の機能と利用手順についてご紹介します。今回のアップデートで、コンテンツフィルターと拒否トピックについて Standard Tier を選択することで... aws.amazon.com なお、元々のお客さんの要件としては必ずしも生成 AI(LLM)の使用が前提ではなく、例えば Cloudwatch Logs のマネージドデータ保護ポリシーや Macie、Comprehend などのマネージドサービスを使用して PII 情報のマスキングをすることも並行して検討していました。残念ながらいずれのサービスも日本語に対応していない情報が多かったため、いずれも候補から外さざるを得なかったというのが正直なところです。(Macie は PII 情報の検知までが対象となるため、その先のマスキングをどうするかはまた別問題となりますが・・) Amazon Comprehend でサポートされている言語 - Amazon Comprehend Amazon Comprehend でサポートされている言語について説明します。 docs.aws.amazon.com PII 向けマネージドデータ識別子 - Amazon Macie Amazon Macie が組み込み型の基準と手法を使用して Amazon S3 オブジェクトから検出できる個人を特定できる情報のタイプを説明します。 docs.aws.amazon.com   ログマスキング方針及びルールの検討 さて、先述の通り決定的なルールを定めることになりましたが、そのルールを定めるためにスロークエリログに含まれる SQL 文のどの部分をどうマスキングするのかの方針を検討する必要がありました。 当初は PII に該当する情報が含まれるテーブルやカラム及び具体的な PII 情報についてお客さんに調査頂いた上で、その対象が SQL 文内に含まれる場合のみ該当情報をマスキングするようなルールを検討する、という進め方を検討していました。SQL 文におけるマスキング範囲が広くなればなるほど当然ながらログ(情報)の価値は相対的に下がってしまうためです。 ただ、お客さん側で調査に割ける工数が限定的であり、ログマスキング対象を十分に特定かつ網羅できるだけの情報をプロジェクト期間内に整理するのが難しそうということが分かったため、お客さんとも協議の上、最終的には 「MySQL の SQL 構文上 PII 情報が含まれ得る箇所を全て網羅的にマスキングする」 という方針でマスキングを行うことに決定しました。また、網羅的にマスキングすることを最優先とし、本来対象外の部分がマスキングのルール・仕組み上マスキングされてしまうことは許容する旨も合わせて決定しました。 その方針を踏まえて、SQL 構文を MySQL のドキュメントから調査した結果、 MySQL におけるリテラル値 をマスキングすれば、必然的に SQL 構文上 PII 情報が含まれ得る箇所を全て網羅的にマスキングできるのではないかと考えました。 fw_error_www dev.mysql.com 検討の結果、上記 URL に示されているリテラル値の一覧から、 「数値」 と 「文字列リテラル」 の 2 種類を対象にマスキングするようなルールであれば上記要件を満たせるのではないかと最終的に判断しました。数値リテラルではなく数値を対象とすることで、日付リテラルや 16 進数リテラルの一部を合わせてマスキングできるためです。booleanリテラルや NULL 値は対象外となりますが、一旦は対象外として良いということでお客さんと合意しました。必要になった場合は追加すればよいという判断です。 ちなみに一応補足ですが、オブジェクト定義自体に PII 情報が含まれていないことが前提です。もちろんそのようなことは原則まずあり得ないものと思いますが、念のため。。   実装案2:Cloudwatch Logs のカスタムデータ識別子の使用 上記決定を踏まえてまず考えたのが Cloudwatch Logs のカスタムデータ識別子の使用です。Cloudwatch Logs 内でログマスキングを完結できるシンプルな構成となりますし、ドキュメントを見る限りは正規表現も使用できるので「数値」と「文字列リテラル」の 2 種類を対象にマスキングするようなルールも実装できそうと考えたためです。 カスタムデータ識別子 - Amazon CloudWatch Logs CloudWatch Logs でマスクするカスタムデータのタイプを指定するために使用するカスタムデータ識別子を作成する方法について説明します。 docs.aws.amazon.com ということで早速試してみたのですが以下のような制約が発覚したため、結論として今回は使用できないという判断になりました。 使用できる正規表現の記法が限定されている 正規表現パターンにマッチした部分が全てマスクされるような挙動となる 1つのロググループに対して設定できるカスタムデータ識別子が最大 10 個 200 文字以上の正規表現パターンは使用できない 今回、特にネックとなったのは 1、2 点目でしたので、もう少し掘り下げて説明します。 なお、上記 AWS ドキュメント上では正規表現内で使用できる記号が以下の通り限定されているように見受けられ、それはそれで困るので AWS サポートに問い合わせていたのですが、回答としてはドキュメントの表記が間違っており、基本的にはどの記号も使用できるとのことでした。(本日時点ではまだドキュメントは直っていないようです) 記号: ( ‘_’ | ‘#’ | ‘=’ | ‘@’ |’/’ | ‘;’ | ‘,’ | ‘-‘ | ‘ ‘ )   使用できる正規表現の記法が限定されている 私の検証した範囲ですが、最小マッチや前方/後方参照、否定先読みといった正規表現パターンが使用できませんでした。特に、否定先読みのような複雑な正規表現パターンについては「regex too complex」のようなエラーが表示されてしまい、カスタムデータ識別子として登録できませんでした。 また、「文字列リテラル」にマッチングさせる正規表現パターンに最小マッチが使用できないのも実用上問題がありました。例えば、ダブルクォートで囲われた文字列リテラルは一例として “[^”]*?” のような正規表現でマッチングできますが、最小マッチ (?) が使えないので “[^”]*” となってしまいます。ダブルクォートで囲われた任意の文字列が最大マッチとなってしまうため、本来マスキング対象として意図していない部分までマスキングされてしまいます。 具体例を挙げると、以下のような SQL 文の場合は select * from regex_test where col1 > 100 and col2 = "abc" 以下のようにほぼ想定通りマスキングできます。 select * from regex_test where col1 > 100 and col2 = ***** 一方で、このように文字列リテラルが複数現れるような SQL 文の場合は select * from regex_test where col1 > 100 and col2 = "abc" and col3 = "edf" 最大マッチの影響で、col2 への問い合わせ条件に指定された文字列リテラルから、col3 への問い合わせ条件に指定された文字列リテラルまでが丸々正規表現パターンにマッチングしてしまうため、より広い範囲がマスキングされてしまいます。この SQL の場合だと col3 への問い合わせ条件が完全にマスキングされてしまい、元の SQL 文の構造的な情報が失われてしまいます。 select * from regex_test where col1 > 100 and col2 = ****************** この SQL 文は単純な分まだマシですが、サブクエリや複数表の結合などが含まれる複雑な SQL 文の場合は文字列リテラルの最大マッチによってマスキングされてしまう範囲が更に膨大になることも考えられます。先述した通り、本来対象外の部分がマスキングのルール・仕組み上マスキングされてしまうことは許容するという方針があるとしても、SQL 文の原型を留めないような広範囲をマスキングされてしまうと運用保守用 AWS アカウント上のログから有意な調査ができなくなってしまうということになります。   正規表現パターンにマッチした部分が全てマスクされるような挙動となる こちらは数値のマスキングで問題となりました。先述した通り、スロークエリログのヘッダには様々な情報が含まれていますが、単純に数値をマスクすると以下の通り、重要な情報が概ねマスクされてしまうことになります。 # Time: ****-**-**T**:**:**.******Z # User@Host: admin[admin] @ [**.**.**.**] Id: **** # Query_time: *.****** Lock_time: *.****** Rows_sent: * Rows_examined: ******* use test; SET timestamp=**********; select * from regex_test where col* > *** and col* = "abc" 数値は文字列リテラルと違いシングルクォート/ダブルクォートによる囲み文字がないため、シンプルな正規表現で SQL 文内の数値にのみマッチさせることが困難です。よって、現実的な方法としては SET timestamp 文以降の SQL 文のみを正規表現によるマスキングの対象とすることが最も簡単な解決策なのですが、残念ながらカスタムデータ識別子ではパターンマッチした部分が全てマスクされてしまうような仕様のためこのような対応が取れませんでした。 ちなみに、1点目については例えば \s+”[^”]*”\s* のように文字列リテラル前後の空白文字もパターンに追加することで、col2 の条件句と col3 の条件句を別々にマスキングすることは可能です。(2点目の場合も似たような工夫ができなくはない)また、試せていませんが単語の境界を示す \b を \s の代わりに使用しても良いと思います。 ただ、こういうパターンを検討し出すと最終的にスロークエリログの出力仕様を正確に把握しないとマスキングの抜け漏れが発生し得ることになるため、調査/実装コストの観点も鑑みるとある意味きりがなくなってしまいます。よって、今回は上記 2 点の制約が判明した段階で、他の方法を採用することにしました。   実装案3:Lambda(Python)の使用 ということでより幅広い正規表現の記法を扱えるソリューションとしては、やはり Lambda(Python)の使用がベターではないかという結論となりました。先述したログマスキングルールを実装する分にはある意味どうとでもなるため、Lambda を使用したログマスキングをどのようなアーキテクチャで実現するのかの検討に入りました。 アーキテクチャとしては大まかに以下 2 つの案をお客さんと検討しましたが、スロークエリログの性質上大量に出力されるようなケースは少ないため、アーキテクチャのシンプルさを取って結論としては案 1 を採用しました。もし今後ログ出力が増大したり、出力量の多いログがマスキング対象に追加された場合は、案 2 の方が筋が良さそうではあります。   案1:Cloudwatch Logs のサブスクリプションフィルタ経由でログマスキング用の Lambda をリアルタイム実行 先述した通りのシンプルな構成で、マスキングされたログを運用保守用 AWS アカウントにリアルタイム出力できます。ログ出力量に応じてログマスキング用 Lambda の呼び出し回数が多くなるためコスト面への影響が考えられますが、反面 Lambda のマスキング対象が単一のログエントリ(スロークエリログ)になるため、Lambda の実装をよりシンプルにすることができます。また、サブスクリプションフィルタ経由でログマスキング対象の Lambda を呼び出す構成上、一時的なエラーで Lambda の実行に失敗した場合のリトライが難しいのがデメリットではあります。   案2:Firehose で Cloudwatch Logs のログを S3 に出力し、EventBridge 経由でログマスキング用の Lambda をバッチ実行 Firehose で Cloudwatch Logs のログを S3 に出力しておき、EventBridge 経由でログマスキング用の Lambda をバッチ実行する構成です。案 1 と比較するとログマスキング処理失敗時のリトライが容易なこと、及びバッチ実行が可能なこと(= Lambda の実行回数を相対的に抑えられる)の 2 点がメリットになります。最も、これらのメリットを得るためには Lambda の実装が相応に複雑になるため、アーキテクチャの複雑性と合わせてトレードオフになる部分だと思います。バッチ実行が必須要件となる場合はこのようなアーキテクチャを採用する必要が出てきますが。 なお、S3 のイベントトリガー経由で Lambda を起動するような構成にすればリアルタイム出力も可能になりますが、今回のケースでは案 1 と比較するとこのアーキテクチャを取るメリットがないため、ここでは取り上げていません。   案 1 におけるログマスキング用 Lambda の実装例 最後に、案 1 におけるログマスキング用 Lambda の実装例を紹介します。ただこの実装例はクロスアカウントの Cloudwatch Logs 出力に対応していないので、もし時間があれば改訂するかもしれません。。 import json import time import base64 import gzip import re import boto3 # ロググループとログストリームの設定(サンプル) LOG_GROUP_NAME = '/custom/mysql-masked-log' LOG_STREAM_NAME = 'slowquery' def lambda_handler(event, context): # base64でデコード decoded_data = base64.b64decode(event['awslogs']['data']) # gzipで解凍 decompressed_data = gzip.decompress(decoded_data) json_data = json.loads(decompressed_data) # 文字列リテラル置換用の正規表現 regex_str = r'(["\'])((?:\\.|(?!\1).)*?)(\1)' # SET timestamp文以降のSQL文を抽出するための正規表現 settimestamp_pattern = r'(?s)(.*?SET\s+timestamp.*?\n)(.*)' # 数値リテラル置換用関数 def replace_numbers(match): before_settimestamp = match.group(1) # SET TIMESTAMPまでの部分 after_settimestamp = match.group(2) # SET TIMESTAMP以降の部分 # 数値リテラル(整数・小数)のみを「?」に置換(識別子中の数字は対象外) masked_numbers = re.sub(r'\b\d+(\.\d+)?\b', '?', after_settimestamp) return before_settimestamp + masked_numbers # 全logEventsを処理(複数件対応) log_events = json_data.get('logEvents', []) masked_events = [] for log_event in log_events: cwl_msg = log_event['message'] # 文字列リテラルを「*」に置換 masked_msg = re.sub(regex_str, r'\1*\1', cwl_msg, flags=re.DOTALL) # SET timestamp文以降の数値リテラルを「?」に置換 # SET timestampパターンにマッチしなかった場合はこのコードでは考慮していない masked_msg = re.sub(settimestamp_pattern, replace_numbers, masked_msg) masked_events.append({ 'timestamp': log_event.get('timestamp', int(round(time.time() * 1000))), 'message': masked_msg }) # CWLにマスクしたスロークエリログを出力 log_to_cwl(masked_events) return { 'statusCode': 200, 'body': json.dumps(f'Processed {len(masked_events)} log events') } def log_to_cwl(log_events: list): """ 指定したロググループとログストリームにテキスト形式でログを出力する Args: log_events: {'timestamp': int, 'message': str} のリスト """ # CloudWatch Logs クライアントを初期化 logs_client = boto3.client('logs') # ロググループが存在しない場合は作成 try: logs_client.create_log_group(logGroupName=LOG_GROUP_NAME) except logs_client.exceptions.ResourceAlreadyExistsException: pass # ログストリームが存在しない場合は作成 try: logs_client.create_log_stream( logGroupName=LOG_GROUP_NAME, logStreamName=LOG_STREAM_NAME ) except logs_client.exceptions.ResourceAlreadyExistsException: pass # ログを出力 logs_client.put_log_events( logGroupName=LOG_GROUP_NAME, logStreamName=LOG_STREAM_NAME, logEvents=log_events ) 先述のログマスキングルールを Python で実装している以上の内容がないのであまり言及することはないのですが、一応ポイントだけ説明して終わりたいと思います。 サブスクリプションフィルタ経由で Lambda に渡されるログは圧縮されているので、最初にデコード・解凍して取得  以下3種類の正規表現パターンを用意 文字列リテラルを「*」に置換 数値を「?」に置換 SET timestamp 文以降の部分(=SQL文)と、それより前の部分を別々に抽出 取得したスロークエリログの内容から、まず文字列リテラルを「*」に置換した後、SET timestamp 文以降の部分に含まれる数値を「?」に置換 Cloudwatch Logs 出力時に対象のロググループ/ログストリームが存在しない場合は Lambda 側で作成   まとめ 要件上最終的には無難な方式に落ち着きましたが、そこに至るまで色々と考えさせられたトピックだったなと思います。 Cloudwatch Logs のマネージドデータ識別子などのプリセットに定義されているルールがそのまま使用できれば一番良かったのですが、そのあたりはいわゆる言語の壁を感じたところです。一昔前はそもそも日本語における自然言語処理の観点でこの手の話が色々あったことを思い出すというか、そういうのが生成 AI(LLM)の隆盛で大分取っ払われたと思いきやこういうところにまだ残っているのだなと感じた次第です。別の機会があれば、Bedrock Guardrails も今後是非試してみたいところです。 本記事がどなたかの役に立てば幸いです。
アバター
こんにちは、SCSKでAWSの内製化支援『 テクニカルエスコートサービス 』を担当している貝塚です。 先日、顧客内製開発中のWebシステムの認証について、こんなご相談をいただきました。 社内のAD(Active Directory)で管理しているユーザーIDとパスワードで、クラウド上のWebアプリケーションにログインさせたい ただし、社員IDはメールアドレスではなくsAMAccountName(Active Directoryでユーザーを一意に識別するログイン名属性。例: testuser01)を使用している 本記事では、AWS上にADFS(Active Directory Federation Services)を構築し、Amazon Cognitoと連携してSAML 2.0ベースのSSO認証を実現する構成について説明します。さらに、ADFSをインターネットに安全に公開するため、Web Application Proxy(WAP)を導入した最終的なアーキテクチャも紹介します。   CognitoとADを連携させるには Cognito User Poolは外部のSAML 2.0 Identity Provider(IdP)と連携することで、SSOを実現できます。 しかし、AD自体はLDAP/Kerberosベースのディレクトリサービスであり、SAML IdPの機能を持ちません。そのため、CognitoとADを連携させるには、ADの認証情報をSAML Assertionに変換して発行できるIdPを別途用意する必要があります。 AWS Directory Service AD Connectorは使えるか このケースでAWS Directory Service AD Connectorは使えるでしょうか?AD ConnectorはオンプレミスADへのプロキシとして動作し、AWSマネジメントコンソールへのSSO、Amazon WorkSpaces、RDSのWindows認証などに利用できます。 しかし、AD ConnectorはSAML IdPではありません。Cognito User PoolのSAMLフェデレーションに必要なSAMLメタデータやSAML Assertionを発行する機能を持たないため、CognitoとADの連携には使用できないということが分かります。 結論: AWS上にADFSを構築する AD Connectorの検討を経て、AWS上にADFSサーバーを構築する方式を選択しました。 ADFSはWindows Serverの役割(ロール)の一つで、ADの認証情報をSAML 2.0やWS-Federationといったフェデレーションプロトコルで外部に提供します。つまり、ADをSAML IdPとして機能させるのがADFSの中核的な役割です。 今回のケースでは、このADFSをSAML IdPとしてCognitoと連携させます。   全体アーキテクチャの検討 ADFSとCognitoを連携させるアーキテクチャを検討します。 当初案: ALB → ADFS → AD 当初は、Internet → ALB → ADFS → AD という構成を検討していました。ADFSサーバはSSL証明書を持つので、ALBでHTTPSを終端した後、再びHTTPSでADFSサーバと通信する想定です。ALBにAWS WAFをアタッチすればセキュリティ面も安心です。 ALBからADFSへの通信 ALBはHTTPSターゲットグループを使えばバックエンドへの再暗号化自体は可能です。 しかし、ADFSはSNI(Server Name Indication)の処理が特殊で、ALBのようなL7ロードバランサーの背後に配置すると、ヘルスチェックやプロキシ動作が正しく機能しないことが分かりました。 修正案: NLB → ADFS → AD そこで、NLBを使い Internet → NLB → ADFS → AD という 構成に変更しました。 NLBはL4(TCP)パススルーで動作するため、TLS終端はADFSが行います。SNIの問題も発生しません。ただし、NLBにはAWS WAFを直接アタッチできないという制約があります。   暫定アーキテクチャ インターネットからADFSへの通信のセキュリティ確保は別途考えることにして、まずは以下の構成としました。 こちらは完全に検証用の構成であることにご注意ください。実際はウェブサイトと同じところ(またはその「奥」)に認証サイトがあるべきですが、Cognito、ADFS、ウェブサーバ間の通信要件が明確になるようにあえてVPCを分けました。また実際には可用性要件も踏まえて構成する必要があります。 認証データフロー 認証データの流れとしては クライアントPC → Cognito → ADFS → AD → ADFS → Cognito → Client ですが、実際の通信はクライアントPCのブラウザがCognitoとADFSの間をHTTPリダイレクトで仲介する形になります。CognitoとADFSの間に直接の通信は発生しません。 通信フロー 具体的な通信フローは以下のとおりです。 Client → ALB(Webアプリ): Webアプリにアクセス ALB → Client: Cognitoの認証エンドポイントへリダイレクト(HTTP 302) Client → Cognito: 認証リクエスト Cognito → Client: ADFS(SAML IdP)へリダイレクト(HTTP 302、SAMLRequestを含む) Client → ADFS: SAMLRequestを送信(ADFSのログイン画面が表示される) ADFS ←→ AD: ADFSがADに対してユーザー認証を実行(サーバー間通信) ADFS → Client: SAMLResponseを返却(認証成功時) Client → Cognito: SAMLResponseをCognitoのACSエンドポイントにPOST Cognito → Client: 認証トークンを発行し、ALBのコールバックURLへリダイレクト Client → ALB(Webアプリ): 認証済みとしてWebアプリにアクセス 重要なのは、CognitoとADFSの間に直接の通信は発生しないという点です。唯一の例外は、CognitoがADFSのメタデータXMLを取得する際のみ、Cognito → ADFSの直接通信が発生します。ただしメタデータXML取得は後述の通り構築時にADFSで出力されたメタデータをCognitoにアップロードすることで代替が可能です。 自己署名証明書とCognitoのメタデータ取得 今回の検証環境ではADFSに信頼できる第三者機関認証局(CA)の証明書は準備できず自己署名証明書を使用しました。 CognitoがADFSのメタデータURLにアクセスする方式では自己署名証明書のSSL検証に失敗するためエラーが発生し連携ができません。この問題を回避するため、メタデータのファイルをCognitoにアップロードする方式を採用しました。ADFSのメタデータXMLをS3バケットに格納し登録するようにしています。 sAMAccountNameでの認証 今回の要件では、メールアドレスではなくsAMAccountName(Active Directoryでユーザーを一意に識別するログイン名属性)でログインする必要がありました。 ADFSの Claim Rulesで sAMAccountNameを Name IDとして発行するよう設定し、Cognito側でこの値をユーザー識別子として使用しています。ウェブサイト側では認証済みリクエストの x-amzn-oidc-data ヘッダー(JWT形式)をデコードすることで、sAMAccountName を取得できます。   アーキテクチャ修正:Web Application Proxy (WAP)の導入 上記構成で一旦動作を確認した後、インターネットにさらされることになったADFSのセキュリティ強化を検討することになりました。 Web Application Proxy (WAP)はMicrosoft推奨のADFS公開方式で、ADFSのSNI処理を正しくハンドリングするリバースプロキシです。WAPはドメイン非参加で運用するため、万が一WAPが侵害されてもADドメインへの影響を限定できます。WAPが侵害された場合は、ADFS側で「プロキシ信頼の取り消し」を実行することで、侵害されたプロキシからのリクエストを即座に拒否できます。 本案件はクライアントPCのグローバルIPアドレスが限定できるためセキュリティグループに頼る選択肢もありましたが、ネットワーク層以外の防御を組み合わせるという視点からWAPを導入する方向で検討を進めました。とはいえ顧客運用対象のEC2サーバが増えるため手放しでは喜べない結果となりました。 まとめ 本記事では、CognitoとオンプレミスADをSAML連携するために、AWS上にADFSを構築し、WAPを経由して安全にインターネットに公開する構成を紹介しました。 ADFSの前にALBを置けないことからNLBを採用し、さらにWAPを導入することで、セキュリティと可用性を両立しています。自己署名証明書環境でのCognitoメタデータ取得の回避策や、sAMAccountNameによる認証の実現方法など、実装時に直面した課題とその解決策も共有しました。これらのノウハウが皆様のお役に立てば幸いです。   実装手順 本検証構成をデプロイするためのCloudFormationテンプレートとコマンドを以下に掲載します。 デプロイは5つのCloudFormationテンプレートと、テンプレート以外で必要な対応手順から構成されます。 [Step 1] CloudFormation: スタック1 (擬似オンプレミスVPC) デプロイ [Step 2] CloudFormation: スタック2 (AD/ADFSサーバ) デプロイ [自動実行] UserDataスクリプト(ADドメイン作成、ADFSドメイン参加) [Step 3] CloudFormation: スタック3 (WAP) デプロイ [Step 4] SSM Run Command: ADテストユーザー作成 [Step 5] 手動: ADFSファーム作成(Fleet Manager 経由) [Step 6] ADFSメタデータXMLのS3アップロード [Step 7] CloudFormation: スタック4 (ウェブアプリ用VPC) デプロイ [Step 8] CloudFormation: スタック5 (Cognito/ウェブアプリ) デプロイ [Step 9] WAP構成(Fleet Manager 経由) Step 1: スタック1(擬似オンプレミスVPC)のデプロイ コマンド aws cloudformation create-stack \ --stack-name adfs-pseudo-vpc \ --template-body file://cfn-pseudo-onprem-vpc.yaml \ --parameters file://cfn-pseudo-onprem-vpc_adfs-pseudo-vpc.json \ --region ap-northeast-1 cfn-pseudo-onprem-vpc.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 1: Pseudo On-Premises VPC with NLB for ADFS access' Parameters: VpcCidr: Type: String Description: 'CIDR block for the VPC' Default: '10.0.0.0/16' PublicSubnet1aCidr: Type: String Description: 'CIDR block for the public subnet in AZ 1a' Default: '10.0.10.0/24' PublicSubnet1cCidr: Type: String Description: 'CIDR block for the public subnet in AZ 1c' Default: '10.0.11.0/24' WapSubnet1aCidr: Type: String Description: 'CIDR block for the WAP subnet in AZ 1a' Default: '10.0.20.0/24' WapSubnet1cCidr: Type: String Description: 'CIDR block for the WAP subnet in AZ 1c' Default: '10.0.21.0/24' InternalNlbSubnet1aCidr: Type: String Description: 'CIDR block for the Internal NLB subnet in AZ 1a' Default: '10.0.30.0/24' InternalNlbSubnet1cCidr: Type: String Description: 'CIDR block for the Internal NLB subnet in AZ 1c' Default: '10.0.31.0/24' PrivateSubnet1aCidr: Type: String Description: 'CIDR block for the private subnet in AZ 1a' Default: '10.0.40.0/24' PrivateSubnet1cCidr: Type: String Description: 'CIDR block for the private subnet in AZ 1c' Default: '10.0.41.0/24' Resources: # VPC PseudoOnPremVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-vpc' # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-igw' InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref PseudoOnPremVpc InternetGatewayId: !Ref InternetGateway # Public Subnet 1a PublicSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PublicSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-1a' # Public Subnet 1c PublicSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PublicSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-1c' # WAP Subnet 1a WapSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref WapSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-subnet-1a' # WAP Subnet 1c WapSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref WapSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-subnet-1c' # Internal NLB Subnet 1a InternalNlbSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref InternalNlbSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-internal-nlb-subnet-1a' # Internal NLB Subnet 1c InternalNlbSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref InternalNlbSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-internal-nlb-subnet-1c' # Private Subnet 1a PrivateSubnet1a: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PrivateSubnet1aCidr AvailabilityZone: 'ap-northeast-1a' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-1a' # Private Subnet 1c PrivateSubnet1c: Type: AWS::EC2::Subnet Properties: VpcId: !Ref PseudoOnPremVpc CidrBlock: !Ref PrivateSubnet1cCidr AvailabilityZone: 'ap-northeast-1c' MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-1c' # Elastic IP for NAT Gateway NatGatewayEIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-eip' # NAT Gateway NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet1a Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-gateway' # Public Route Table PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-rt' PublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref InternetGateway PublicSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1a RouteTableId: !Ref PublicRouteTable PublicSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1c RouteTableId: !Ref PublicRouteTable # Private Route Table PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-rt' PrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: '0.0.0.0/0' NatGatewayId: !Ref NatGateway PrivateSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1a RouteTableId: !Ref PrivateRouteTable PrivateSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1c RouteTableId: !Ref PrivateRouteTable # WAP Route Table WapRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-rt' WapRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref WapRouteTable DestinationCidrBlock: '0.0.0.0/0' NatGatewayId: !Ref NatGateway WapSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref WapSubnet1a RouteTableId: !Ref WapRouteTable WapSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref WapSubnet1c RouteTableId: !Ref WapRouteTable # Internal NLB Route Table (VPC local only, no internet route) InternalNlbRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref PseudoOnPremVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-internal-nlb-rt' InternalNlbSubnet1aRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref InternalNlbSubnet1a RouteTableId: !Ref InternalNlbRouteTable InternalNlbSubnet1cRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref InternalNlbSubnet1c RouteTableId: !Ref InternalNlbRouteTable # Security Group for AD ADSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for Active Directory server' VpcId: !Ref PseudoOnPremVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-ad-sg' # Security Group for ADFS ADFSSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for ADFS server' VpcId: !Ref PseudoOnPremVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-adfs-sg' # Security Group Ingress Rules (defined separately to avoid circular dependency) ADSecurityGroupIngressFromADFS: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref ADSecurityGroup IpProtocol: -1 SourceSecurityGroupId: !Ref ADFSSecurityGroup Description: 'Allow all traffic from ADFS Security Group' ADFSSecurityGroupIngressFromAD: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref ADFSSecurityGroup IpProtocol: -1 SourceSecurityGroupId: !Ref ADSecurityGroup Description: 'Allow all traffic from AD Security Group' ADFSSecurityGroupIngressAllTraffic: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref ADFSSecurityGroup IpProtocol: -1 CidrIp: '0.0.0.0/0' Description: 'Allow all traffic (test environment)' # Security Group for WAP WapSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for WAP server' VpcId: !Ref PseudoOnPremVpc SecurityGroupIngress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Description: 'Allow all traffic (test environment)' SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-sg' # External Network Load Balancer ExternalNetworkLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}-ext-nlb' Type: network Scheme: internet-facing Subnets: - !Ref PublicSubnet1a - !Ref PublicSubnet1c Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-ext-nlb' # External NLB Target Group (WAP Target) ExternalNlbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub '${AWS::StackName}-wap-tg' VpcId: !Ref PseudoOnPremVpc Port: 443 Protocol: TCP TargetType: instance HealthCheckProtocol: TCP HealthCheckPort: '443' TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: 'false' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-wap-tg' # External NLB Listener ExternalNlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref ExternalNetworkLoadBalancer Port: 443 Protocol: TCP DefaultActions: - Type: forward TargetGroupArn: !Ref ExternalNlbTargetGroup # Internal Network Load Balancer InternalNetworkLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}-int-nlb' Type: network Scheme: internal Subnets: - !Ref InternalNlbSubnet1a - !Ref InternalNlbSubnet1c Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-int-nlb' # Internal NLB Target Group (ADFS Target) InternalNlbTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub '${AWS::StackName}-adfs-tg' VpcId: !Ref PseudoOnPremVpc Port: 443 Protocol: TCP TargetType: instance HealthCheckProtocol: TCP HealthCheckPort: '443' TargetGroupAttributes: - Key: preserve_client_ip.enabled Value: 'false' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-adfs-tg' # Internal NLB Listener InternalNlbListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref InternalNetworkLoadBalancer Port: 443 Protocol: TCP DefaultActions: - Type: forward TargetGroupArn: !Ref InternalNlbTargetGroup Outputs: VpcId: Description: 'VPC ID' Value: !Ref PseudoOnPremVpc Export: Name: !Sub '${AWS::StackName}-VpcId' PublicSubnet1aId: Description: 'Public Subnet 1a ID' Value: !Ref PublicSubnet1a Export: Name: !Sub '${AWS::StackName}-PublicSubnet1aId' PrivateSubnet1aId: Description: 'Private Subnet 1a ID' Value: !Ref PrivateSubnet1a Export: Name: !Sub '${AWS::StackName}-PrivateSubnet1aId' WapSubnet1aId: Description: 'WAP Subnet 1a ID' Value: !Ref WapSubnet1a Export: Name: !Sub '${AWS::StackName}-WapSubnet1aId' ADSecurityGroupId: Description: 'AD Security Group ID' Value: !Ref ADSecurityGroup Export: Name: !Sub '${AWS::StackName}-ADSecurityGroupId' ADFSSecurityGroupId: Description: 'ADFS Security Group ID' Value: !Ref ADFSSecurityGroup Export: Name: !Sub '${AWS::StackName}-ADFSSecurityGroupId' WapSecurityGroupId: Description: 'WAP Security Group ID' Value: !Ref WapSecurityGroup Export: Name: !Sub '${AWS::StackName}-WapSecurityGroupId' ExternalNlbTargetGroupArn: Description: 'External NLB Target Group ARN' Value: !Ref ExternalNlbTargetGroup Export: Name: !Sub '${AWS::StackName}-ExternalNlbTargetGroupArn' InternalNlbTargetGroupArn: Description: 'Internal NLB Target Group ARN' Value: !Ref InternalNlbTargetGroup Export: Name: !Sub '${AWS::StackName}-InternalNlbTargetGroupArn' ExternalNlbDnsName: Description: 'External NLB DNS Name' Value: !GetAtt ExternalNetworkLoadBalancer.DNSName Export: Name: !Sub '${AWS::StackName}-ExternalNlbDnsName' cfn-pseudo-onprem-vpc_adfs-pseudo-vpc.json [ { "ParameterKey": "VpcCidr", "ParameterValue": "10.0.0.0/16" }, { "ParameterKey": "PublicSubnet1aCidr", "ParameterValue": "10.0.10.0/24" }, { "ParameterKey": "PublicSubnet1cCidr", "ParameterValue": "10.0.11.0/24" }, { "ParameterKey": "WapSubnet1aCidr", "ParameterValue": "10.0.20.0/24" }, { "ParameterKey": "WapSubnet1cCidr", "ParameterValue": "10.0.21.0/24" }, { "ParameterKey": "InternalNlbSubnet1aCidr", "ParameterValue": "10.0.30.0/24" }, { "ParameterKey": "InternalNlbSubnet1cCidr", "ParameterValue": "10.0.31.0/24" }, { "ParameterKey": "PrivateSubnet1aCidr", "ParameterValue": "10.0.40.0/24" }, { "ParameterKey": "PrivateSubnet1cCidr", "ParameterValue": "10.0.41.0/24" } ] Step 2: スタック2(AD/ADFS)のデプロイ ADサーバ、ADFSサーバを作成します。ADFSDomainNameに、ADFSを外部公開するためのFQDNを指定し、Route 53でそのDNS名をStep1で作成される外側NLBのFQDNに向けるレコードを作成しておいてください。 コマンド aws cloudformation create-stack \ --stack-name adfs-ad \ --template-body file://cfn-ad-adfs.yaml \ --parameters file://cfn-ad-adfs_adfs-ad.json \ --capabilities CAPABILITY_IAM \ --region ap-northeast-1 cfn-ad-adfs.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 2: AD/ADFS Servers for Cognito ADFS SSO Integration' Parameters: PseudoOnPremVpcStackName: Type: String Description: 'Name of the Pseudo On-Premises VPC stack' Default: 'adfs-pseudo-vpc' ADInstanceType: Type: String Description: 'Instance type for AD server' Default: 't3.medium' AllowedValues: - t3.medium - t3.large - t3.xlarge ADFSInstanceType: Type: String Description: 'Instance type for ADFS server' Default: 't3.medium' AllowedValues: - t3.medium - t3.large - t3.xlarge ADAdminPassword: Type: String Description: 'Administrator password for AD domain' NoEcho: true MinLength: 8 ADDomainName: Type: String Description: 'AD domain name (e.g., corp.local)' Default: 'corp.local' ADFSDomainName: Type: String Description: 'ADFS federation service name (e.g., adfs.example.com)' Default: 'adfs.example.com' WindowsAMI: Type: AWS::SSM::Parameter::Value Description: 'Latest Windows Server 2022 AMI from SSM Parameter Store' Default: '/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base' Resources: # IAM Role for SSM SSMInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' Tags: - Key: Name Value: !Sub '${AWS::StackName}-ssm-role' - Key: Cost Value: 'cognitoadfs' # Instance Profile for SSM SSMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref SSMInstanceRole # AD Server Instance ADInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref WindowsAMI InstanceType: !Ref ADInstanceType IamInstanceProfile: !Ref SSMInstanceProfile SubnetId: Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-PrivateSubnet1aId' SecurityGroupIds: - Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-ADSecurityGroupId' Tags: - Key: Name Value: !Sub '${AWS::StackName}-ad-server' - Key: Cost Value: 'cognitoadfs' PropagateTagsToVolumeOnCreation: true UserData: Fn::Base64: !Sub | # Get instance metadata $instanceId = Invoke-RestMethod -uri http://169.254.169.254/latest/meta-data/instance-id # Install AD DS role Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools # Disable Network Level Authentication for RDP (for Fleet Manager access) Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 # Set local Administrator password before creating domain # This password will become the domain Administrator password after domain creation $AdminUser = [ADSI]"WinNT://./Administrator,user" $AdminUser.SetPassword("${ADAdminPassword}") # Create AD domain (computer name will be the default AWS name) $SafeModePassword = ConvertTo-SecureString "${ADAdminPassword}" -AsPlainText -Force Install-ADDSForest -DomainName "${ADDomainName}" -DomainNetbiosName "CORP" -SafeModeAdministratorPassword $SafeModePassword -InstallDns -Force -NoRebootOnCompletion:$false # Server will restart automatically after domain creation # ADFS Server Instance ADFSInstance: Type: AWS::EC2::Instance DependsOn: ADInstance Properties: ImageId: !Ref WindowsAMI InstanceType: !Ref ADFSInstanceType IamInstanceProfile: !Ref SSMInstanceProfile SubnetId: Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-PrivateSubnet1aId' SecurityGroupIds: - Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-ADFSSecurityGroupId' Tags: - Key: Name Value: !Sub '${AWS::StackName}-adfs-server' - Key: Cost Value: 'cognitoadfs' PropagateTagsToVolumeOnCreation: true UserData: Fn::Base64: Fn::Sub: - | # Get instance metadata $instanceId = Invoke-RestMethod -uri http://169.254.169.254/latest/meta-data/instance-id # Install ADFS role Install-WindowsFeature -Name ADFS-Federation -IncludeManagementTools # Disable Network Level Authentication for RDP (for Fleet Manager access) Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 # Generate self-signed certificate $cert = New-SelfSignedCertificate -DnsName "${ADFSDomainName}" -CertStoreLocation "cert:\LocalMachine\My" -KeySpec KeyExchange # Wait for AD server to be ready (20 minutes for domain creation and restart) Start-Sleep -Seconds 1200 # Get AD server IP $ADServerIP = "${ADInstancePrivateIp}" # Set DNS server to AD server # Get the first active network adapter (dynamically) $adapter = Get-NetAdapter | Where-Object {$_.Status -eq 'Up'} | Select-Object -First 1 Set-DnsClientServerAddress -InterfaceIndex $adapter.ifIndex -ServerAddresses $ADServerIP # Clear DNS cache after changing DNS server Clear-DnsClientCache # Wait for DNS resolution with retry (max 10 attempts, 10 seconds interval) $maxRetries = 10 $retryCount = 0 do { Start-Sleep -Seconds 10 $result = Resolve-DnsName ${ADDomainName} -ErrorAction SilentlyContinue $retryCount++ Write-Output "DNS resolution attempt $retryCount : $($result.IPAddress)" } while (-not $result -and $retryCount -lt $maxRetries) if (-not $result) { Write-Error "DNS resolution for ${ADDomainName} failed after $maxRetries attempts" exit 1 } # Join domain with error handling $password = ConvertTo-SecureString "${ADAdminPassword}" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential("CORP\Administrator", $password) try { Add-Computer -DomainName "${ADDomainName}" -Credential $credential -Force -ErrorAction Stop Write-Output "Domain join successful" } catch { Write-Error "Domain join failed: $_" exit 1 } Restart-Computer -Force - ADInstancePrivateIp: !GetAtt ADInstance.PrivateIp # Lambda execution role for NLB target registration NLBTargetRegistrationRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: 'ELBv2TargetRegistration' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'elasticloadbalancing:RegisterTargets' - 'elasticloadbalancing:DeregisterTargets' Resource: '*' Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-role' - Key: Cost Value: 'cognitoadfs' # Lambda function for NLB target registration NLBTargetRegistrationFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-nlb-target-reg' Runtime: python3.12 Handler: index.handler Role: !GetAtt NLBTargetRegistrationRole.Arn Timeout: 60 Code: ZipFile: | import json import boto3 import cfnresponse def handler(event, context): try: target_group_arn = event['ResourceProperties']['TargetGroupArn'] instance_id = event['ResourceProperties']['InstanceId'] client = boto3.client('elbv2') if event['RequestType'] in ['Create', 'Update']: client.register_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'InstanceId': instance_id}) elif event['RequestType'] == 'Delete': try: client.deregister_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) except Exception: pass cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception as e: print(f'Error: {e}') cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-func' - Key: Cost Value: 'cognitoadfs' # Custom resource to register ADFS instance to Internal NLB target group NLBTargetRegistration: Type: Custom::NLBTargetRegistration DependsOn: ADFSInstance Properties: ServiceToken: !GetAtt NLBTargetRegistrationFunction.Arn TargetGroupArn: Fn::ImportValue: !Sub '${PseudoOnPremVpcStackName}-InternalNlbTargetGroupArn' InstanceId: !Ref ADFSInstance Outputs: ADInstanceId: Description: 'AD Server Instance ID' Value: !Ref ADInstance Export: Name: !Sub '${AWS::StackName}-ADInstanceId' ADFSInstanceId: Description: 'ADFS Server Instance ID' Value: !Ref ADFSInstance Export: Name: !Sub '${AWS::StackName}-ADFSInstanceId' ADInstancePrivateIp: Description: 'AD Server Private IP Address' Value: !GetAtt ADInstance.PrivateIp Export: Name: !Sub '${AWS::StackName}-ADInstancePrivateIp' ADFSInstancePrivateIp: Description: 'ADFS Server Private IP Address' Value: !GetAtt ADFSInstance.PrivateIp Export: Name: !Sub '${AWS::StackName}-ADFSInstancePrivateIp' cfn-ad-adfs_adfs-ad.json [ { "ParameterKey": "PseudoOnPremVpcStackName", "ParameterValue": "adfs-pseudo-vpc" }, { "ParameterKey": "ADInstanceType", "ParameterValue": "t3.medium" }, { "ParameterKey": "ADFSInstanceType", "ParameterValue": "t3.medium" }, { "ParameterKey": "ADAdminPassword", "ParameterValue": "ChangeMe123!" }, { "ParameterKey": "ADDomainName", "ParameterValue": "corp.local" }, { "ParameterKey": "ADFSDomainName", "ParameterValue": "adfs.example.com" }, { "ParameterKey": "WindowsAMI", "ParameterValue": "/aws/service/ami-windows-latest/Windows_Server-2022-English-Full-Base" } ] スタック2のデプロイ完了後、UserDataスクリプトの実行完了まで約25-30分の待機が必要です。UserDataではADドメインの作成とADFSサーバーのドメイン参加が自動実行されます。 Step 3: スタック3(WAP)のデプロイ コマンド aws cloudformation create-stack \ --stack-name adfs-wap \ --template-body file://cfn-wap.yaml \ --parameters file://cfn-wap_adfs-wap.json \ --capabilities CAPABILITY_IAM \ --region ap-northeast-1 cfn-wap.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 3: WAP Server for ADFS Proxy Security Enhancement' Parameters: VpcStackName: Type: String Description: 'Name of the Pseudo On-Premises VPC stack' Default: 'adfs-pseudo-vpc' InstanceType: Type: String Description: 'Instance type for WAP server' Default: 't3.medium' AllowedValues: - t3.medium - t3.large - t3.xlarge LatestWindowsAmiId: Type: AWS::SSM::Parameter::Value Description: 'Latest Windows Server 2019 Japanese AMI from SSM Parameter Store' Default: '/aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base' KeyPairName: Type: String Description: 'EC2 Key Pair name for WAP server (optional)' Default: '' Conditions: HasKeyPair: !Not [!Equals [!Ref KeyPairName, '']] Resources: # IAM Role for SSM (Fleet Manager) WapSSMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' Tags: - Key: Name Value: !Sub '${AWS::StackName}-ssm-role' - Key: Cost Value: 'cognitoadfs' # Instance Profile for SSM WapSSMInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref WapSSMRole # WAP Server Instance WapInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref LatestWindowsAmiId InstanceType: !Ref InstanceType KeyName: !If [HasKeyPair, !Ref KeyPairName, !Ref 'AWS::NoValue'] IamInstanceProfile: !Ref WapSSMInstanceProfile SubnetId: Fn::ImportValue: !Sub '${VpcStackName}-WapSubnet1aId' SecurityGroupIds: - Fn::ImportValue: !Sub '${VpcStackName}-WapSecurityGroupId' Tags: - Key: Name Value: !Sub '${AWS::StackName}-wap-server' - Key: Cost Value: 'cognitoadfs' PropagateTagsToVolumeOnCreation: true UserData: Fn::Base64: | # Install WAP role Install-WindowsFeature Web-Application-Proxy -IncludeManagementTools # Disable Network Level Authentication for RDP (for Fleet Manager access) Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name 'UserAuthentication' -Value 0 # Lambda execution role for NLB target registration WapNLBTargetRegistrationRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: 'sts:AssumeRole' ManagedPolicyArns: - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: 'ELBv2TargetRegistration' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'elasticloadbalancing:RegisterTargets' - 'elasticloadbalancing:DeregisterTargets' Resource: '*' Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-role' - Key: Cost Value: 'cognitoadfs' # Lambda function for NLB target registration WapNLBTargetRegistrationFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-nlb-target-reg' Runtime: python3.12 Handler: index.handler Role: !GetAtt WapNLBTargetRegistrationRole.Arn Timeout: 60 Code: ZipFile: | import json import boto3 import cfnresponse def handler(event, context): try: target_group_arn = event['ResourceProperties']['TargetGroupArn'] instance_id = event['ResourceProperties']['InstanceId'] client = boto3.client('elbv2') if event['RequestType'] in ['Create', 'Update']: client.register_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) cfnresponse.send(event, context, cfnresponse.SUCCESS, {'InstanceId': instance_id}) elif event['RequestType'] == 'Delete': try: client.deregister_targets( TargetGroupArn=target_group_arn, Targets=[{'Id': instance_id}] ) except Exception: pass cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception as e: print(f'Error: {e}') cfnresponse.send(event, context, cfnresponse.FAILED, {'Error': str(e)}) Tags: - Key: Name Value: !Sub '${AWS::StackName}-nlb-target-reg-func' - Key: Cost Value: 'cognitoadfs' # Custom resource to register WAP instance to External NLB target group WapNLBTargetRegistration: Type: Custom::NLBTargetRegistration DependsOn: WapInstance Properties: ServiceToken: !GetAtt WapNLBTargetRegistrationFunction.Arn TargetGroupArn: Fn::ImportValue: !Sub '${VpcStackName}-ExternalNlbTargetGroupArn' InstanceId: !Ref WapInstance Outputs: WapInstanceId: Description: 'WAP Server Instance ID' Value: !Ref WapInstance Export: Name: !Sub '${AWS::StackName}-WapInstanceId' WapInstancePrivateIp: Description: 'WAP Server Private IP Address' Value: !GetAtt WapInstance.PrivateIp Export: Name: !Sub '${AWS::StackName}-WapInstancePrivateIp' cfn-wap_adfs-wap.json [ { "ParameterKey": "VpcStackName", "ParameterValue": "adfs-pseudo-vpc" }, { "ParameterKey": "InstanceType", "ParameterValue": "t3.medium" }, { "ParameterKey": "LatestWindowsAmiId", "ParameterValue": "/aws/service/ami-windows-latest/Windows_Server-2019-Japanese-Full-Base" }, { "ParameterKey": "KeyPairName", "ParameterValue": "" } ] Step 4: ADテストユーザーの作成 SSM Run Commandを使用して、ADサーバー上にテストユーザーを作成します。 ユーザ名 パスワード testuser01@corp TestPass123! testuser02@corp TestPass123! testuser03@corp TestPass123! aws ssm send-command \ --instance-ids "<ADインスタンスID>" \ --document-name "AWS-RunPowerShellScript" \ --parameters file://ad-create-test-users.json \ --region ap-northeast-1 ad-create-test-users.json { "commands": [ "Import-Module ActiveDirectory", "$password = ConvertTo-SecureString 'TestPass123!' -AsPlainText -Force", "New-ADUser -Name 'testuser01' -SamAccountName 'testuser01' -UserPrincipalName 'testuser01@corp.local' -AccountPassword $password -Enabled $true -PasswordNeverExpires $true -Path 'CN=Users,DC=corp,DC=local'", "New-ADUser -Name 'testuser02' -SamAccountName 'testuser02' -UserPrincipalName 'testuser02@corp.local' -AccountPassword $password -Enabled $true -PasswordNeverExpires $true -Path 'CN=Users,DC=corp,DC=local'", "New-ADUser -Name 'testuser03' -SamAccountName 'testuser03' -UserPrincipalName 'testuser03@corp.local' -AccountPassword $password -Enabled $true -PasswordNeverExpires $true -Path 'CN=Users,DC=corp,DC=local'", "Write-Output 'Test users created. Verifying...'", "Get-ADUser -Filter * | Select-Object Name,SamAccountName,Enabled | Format-Table -AutoSize" ] } Step 5: ADFSファームの作成 Fleet Manager でADFSサーバーに接続し、PowerShellでADFSファームを作成します。adfs.example.comのところ(2か所)はADFSを外部公開するFQDNに置き換えてください。 ユーザ名 パスワード CORP\Administrator ChangeMe123! # 証明書サムプリント取得 $cert = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {$_.Subject -like "*adfs.example.com*"} # ADFSファーム作成 $password = ConvertTo-SecureString "ChangeMe123!" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential( "CORP\Administrator", $password) Install-AdfsFarm ` -CertificateThumbprint $cert.Thumbprint ` -FederationServiceName "adfs.example.com" ` -ServiceAccountCredential $credential ` -OverwriteConfiguration Step 6: ADFSメタデータXMLのS3アップロード 自己署名証明書環境では、CognitoのMetadataURL方式が使えないため、メタデータXMLをS3にアップロードします。 # メタデータXML取得。ファイル名 adfs-metadata.xml として保存 curl -sk https://<ADFSのFQDN>/FederationMetadata/2007-06/FederationMetadata.xml \ -o /tmp/adfs-metadata.xml # S3バケット作成 aws s3 mb "s3://<バケット名>" --region ap-northeast-1 # S3にアップロード aws s3 cp tmp/adfs-metadata.xml "s3://<バケット名>/adfs-metadata.xml" \ --region ap-northeast-1 Step 7: スタック4(本番VPC)のデプロイ デプロイには、ウェブサーバの前に配置するALBにインストールするACMサーバ証明書のARNが必要になります。 コマンド aws cloudformation create-stack \ --stack-name adfs-prod-vpc \ --template-body file://cfn-production-vpc.yaml \ --parameters file://cfn-production-vpc_adfs-prod-vpc.json \ --region ap-northeast-1 cfn-production-vpc.yaml AWSTemplateFormatVersion: '2010-09-09' Description: 'Stack 4: Production VPC with ALB for Web Application' Parameters: VpcCidr: Type: String Description: 'CIDR block for the VPC' Default: '10.1.0.0/16' PublicSubnet1Cidr: Type: String Description: 'CIDR block for the first public subnet' Default: '10.1.1.0/24' PublicSubnet2Cidr: Type: String Description: 'CIDR block for the second public subnet' Default: '10.1.2.0/24' PrivateSubnet1Cidr: Type: String Description: 'CIDR block for the first private subnet' Default: '10.1.3.0/24' PrivateSubnet2Cidr: Type: String Description: 'CIDR block for the second private subnet' Default: '10.1.4.0/24' AvailabilityZone1: Type: String Description: 'First Availability Zone' Default: 'ap-northeast-1a' AvailabilityZone2: Type: String Description: 'Second Availability Zone' Default: 'ap-northeast-1c' ALBCertificateArn: Type: String Description: 'ARN of the ACM certificate for ALB HTTPS listener' Resources: # VPC ProductionVpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-vpc' # Internet Gateway InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-igw' InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref ProductionVpc InternetGatewayId: !Ref InternetGateway # Public Subnet 1 PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PublicSubnet1Cidr AvailabilityZone: !Ref AvailabilityZone1 MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-1' # Public Subnet 2 PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PublicSubnet2Cidr AvailabilityZone: !Ref AvailabilityZone2 MapPublicIpOnLaunch: true Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-subnet-2' # Private Subnet 1 PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PrivateSubnet1Cidr AvailabilityZone: !Ref AvailabilityZone1 MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-1' # Private Subnet 2 PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref ProductionVpc CidrBlock: !Ref PrivateSubnet2Cidr AvailabilityZone: !Ref AvailabilityZone2 MapPublicIpOnLaunch: false Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-subnet-2' # Elastic IP for NAT Gateway NatGatewayEIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-eip' # NAT Gateway (in Public Subnet 1) NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGatewayEIP.AllocationId SubnetId: !Ref PublicSubnet1 Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-nat-gateway' # Public Route Table PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref ProductionVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-public-rt' PublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: '0.0.0.0/0' GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable # Private Route Table PrivateRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref ProductionVpc Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-private-rt' PrivateRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: '0.0.0.0/0' NatGatewayId: !Ref NatGateway PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable # Security Group for ALB ALBSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for Application Load Balancer' VpcId: !Ref ProductionVpc SecurityGroupIngress: - IpProtocol: tcp FromPort: 443 ToPort: 443 CidrIp: '0.0.0.0/0' Description: 'Allow HTTPS from Internet' SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-alb-sg' # Security Group for Web Application WebAppSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: 'Security group for Web Application server' VpcId: !Ref ProductionVpc SecurityGroupEgress: - IpProtocol: -1 CidrIp: '0.0.0.0/0' Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-webapp-sg' # Security Group Ingress Rule for WebApp (from ALB) WebAppSecurityGroupIngressFromALB: Type: AWS::EC2::SecurityGroupIngress Properties: GroupId: !Ref WebAppSecurityGroup IpProtocol: tcp FromPort: 80 ToPort: 80 SourceSecurityGroupId: !Ref ALBSecurityGroup Description: 'Allow HTTP from ALB' # Application Load Balancer ApplicationLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${AWS::StackName}-alb' Type: application Scheme: internet-facing IpAddressType: ipv4 SecurityGroups: - !Ref ALBSecurityGroup Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-alb' # ALB Target Group ALBTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Sub '${AWS::StackName}-alb-tg' VpcId: !Ref ProductionVpc Port: 80 Protocol: HTTP TargetType: instance HealthCheckEnabled: true HealthCheckPath: /health HealthCheckProtocol: HTTP HealthCheckPort: traffic-port HealthyThresholdCount: 5 UnhealthyThresholdCount: 2 HealthCheckIntervalSeconds: 30 HealthCheckTimeoutSeconds: 6 Tags: - Key: Cost Value: cognitoadfs - Key: Name Value: !Sub '${AWS::StackName}-alb-tg' # ALB HTTPS Listener ALBListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: LoadBalancerArn: !Ref ApplicationLoadBalancer Port: 443 Protocol: HTTPS SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06 Certificates: - CertificateArn: !Ref ALBCertificateArn DefaultActions: - Type: forward TargetGroupArn: !Ref ALBTargetGroup Outputs: VpcId: Description: 'VPC ID' Value: !Ref ProductionVpc Export: Name: !Sub '${AWS::StackName}-VpcId' PrivateSubnet1Id: Description: 'Private Subnet 1 ID' Value: !Ref PrivateSubnet1 Export: Name: !Sub '${AWS::StackName}-PrivateSubnet1Id' PrivateSubnet2Id: Description: 'Private Subnet 2 ID' Value: !Ref PrivateSubnet2 Export: Name: !Sub '${AWS::StackName}-PrivateSubnet2Id' PublicSubnet1Id: Description: 'Public Subnet 1 ID' Value: !Ref PublicSubnet1 Export: Name: !Sub '${AWS::StackName}-PublicSubnet1Id' PublicSubnet2Id: Description: 'Public Subnet 2 ID' Value: !Ref PublicSubnet2 Export: Name: !Sub '${AWS::StackName}-PublicSubnet2Id' ALBTargetGroupArn: Description: 'ALB Target Group ARN' Value: !Ref ALBTargetGroup Export: Name: !Sub '${AWS::StackName}-ALBTargetGroupArn' ALBListenerArn: Description: 'ALB Listener ARN' Value: !Ref ALBListener Export: Name: !Sub '${AWS::StackName}-ALBListenerArn' WebAppSecurityGroupId: Description: 'Web Application Security Group ID' Value: !Ref WebAppSecurityGroup Export: Name: !Sub '${AWS::StackName}-WebAppSecurityGroupId' ALBDnsName: Description: 'ALB DNS Name' Value: !GetAtt ApplicationLoadBalancer.DNSName Export: Name: !Sub '${AWS::StackName}-ALBDnsName' cfn-production-vpc_adfs-prod-vpc.json [ { "ParameterKey": "VpcCidr", "ParameterValue": "10.1.0.0/16" }, { "ParameterKey": "PublicSubnet1Cidr", "ParameterValue": "10.1.1.0/24" }, { "ParameterKey": "PublicSubnet2Cidr", "ParameterValue": "10.1.2.0/24" }, { "ParameterKey": "PrivateSubnet1Cidr", "ParameterValue": "10.1.3.0/24" }, { "ParameterKey": "PrivateSubnet2Cidr", "ParameterValue": "10.1.4.0/24" }, { "ParameterKey": "AvailabilityZone1", "ParameterValue": "ap-northeast-1a" }, { "ParameterKey": "AvailabilityZone2", "ParameterValue": "ap-northeast-1c" }, { "ParameterKey": "ALBCertificateArn", "ParameterValue": "<ACMで作成したウェブアプリ用サーバ証明書のARN>" } ] Step 8: スタック5(Cognito/WebApp)のデプロイ Step 6で作成したADFSメタデータXMLファイルを格納したS3バケット名をパラメータファイルで指定する必要があります。 ウェブサイトのDNS名を決定し、Route 53で名前解決設定(CNAMEまたはALIASをStep 7で作成したALBに向ける)をしてから実行してください。またそのDNS名をパラメータファイルで指定する必要があります。 パラメータファイルで指定するCognitoのドメインプレフィックスはAWSアカウント横断でグローバルに一意である必要があります。”AlreadyExists”エラーが発生した場合は、プレフィックスを変更してください。 コマンド aws cloudformation create-stack \ --stack-name adfs-webapp \ --template-body file://cfn-cognito-webapp.yaml \ --parameters file://cfn-cognito-webapp_adfs-webapp.json \ --capabilities CAPABILITY_NAMED_IAM \ --region ap-northeast-1 cfn-cognito-webapp.yaml 表示崩れるため別掲します。 cfn-cognito-webapp_adfs-webapp.json [ { "ParameterKey": "ProductionVpcStackName", "ParameterValue": "adfs-prod-vpc" }, { "ParameterKey": "PseudoOnPremVpcStackName", "ParameterValue": "adfs-pseudo-vpc" }, { "ParameterKey": "ADFSMetadataS3Bucket", "ParameterValue": "your-adfs-metadata-xml-bucket-name" }, { "ParameterKey": "ADFSMetadataS3Key", "ParameterValue": "adfs-metadata.xml" }, { "ParameterKey": "CognitoDomainPrefix", "ParameterValue": "your-cognito-domain-prefix" }, { "ParameterKey": "ALBDnsName", "ParameterValue": "<ウェブサーバのDNS名>" }, { "ParameterKey": "WebAppInstanceType", "ParameterValue": "t3.micro" }, { "ParameterKey": "WebAppAmiId", "ParameterValue": "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64" } ] Step 9: WAP構成 Fleet Manager でWAPサーバーに接続し、以下の手順でWAPを構成します。 9-1. DNS解決設定 WAPサーバーのhostsファイルに、ADFS FQDNとInternal NLBのIPアドレスのマッピングを追加します。WAPはADFS FQDNに対してHTTPS通信を行いますが、通常のDNS解決ではExternal NLBに解決されてしまうため、hostsファイルでInternal NLB経由に向ける必要があります。 $internalNlbDns = "<Internal NLB DNS名>" $adfsIps = [System.Net.Dns]::GetHostAddresses($internalNlbDns) | Select-Object -ExpandProperty IPAddressToString $hostsEntry = "$($adfsIps[0]) adfs.example.com" Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value $hostsEntry 9-2. ADFS証明書のインポート ADFSサーバーから証明書をPFXファイルとしてエクスポートし、S3経由でWAPサーバーに転送してインポートします。 # WAPサーバーで証明書をインポート $password = ConvertTo-SecureString -String "<エクスポートパスワード>" ` -Force -AsPlainText Import-PfxCertificate -FilePath "C:\adfs-cert.pfx" ` -CertStoreLocation Cert:\LocalMachine\My -Password $password 9-3. Proxy Trust確立 WAPとADFS間の信頼関係を確立します。 $cred = Get-Credential # CORP\Administrator Install-WebApplicationProxy ` -FederationServiceName "adfs.example.com" ` -FederationServiceTrustCredential $cred ` -CertificateThumbprint "<証明書Thumbprint>" 9-4. ADFSアプリケーション公開 ADFSをWAP経由で外部に公開します。 Add-WebApplicationProxyApplication ` -Name "ADFS" ` -ExternalUrl "https://adfs.example.com/adfs/ls/" ` -ExternalCertificateThumbprint "<証明書Thumbprint>" ` -BackendServerUrl "https://adfs.example.com/adfs/ls/" ` -ExternalPreauthentication PassThrough 以上
アバター
本記事は 新人ブログマラソン2025 の記事です。 こんにちは。新人のtknです。 温かい日差しと春の風を感じることが多くなり、大量の花粉が舞い踊る…そんな日々ですが、皆さまいかがお過ごしでしょうか。 赤々とした杉を見るとついつい睨みそうになりますが、杉は二酸化炭素の吸収効率が良く、地球温暖化防止に役立つそうなので、それなら、しょうがないですかね……。 さて本日は、私が学生時代に ChatGPTとちまちま作ったコードをKiro先生に添削させるとどれくらいの時間・コストがかかるのか? を調査していきたいと思います。 はじめに 2年前、学生の間でもChatGPTがすっかり身近になった頃、私の研究室では研究のお供にChatGPTを使うことが多くなりました。 用途としては、研究テーマや実験の案出し、参考文献の翻訳・要約、システムの実装サポート、実験データの分析サポート、論文の添削、自身の論文の英語化などに使用していました。 その中でも私は「 システムの実装サポート 」にChatGPTを最も活用していました。実際に使用していた方法は以下の通りです。 システムの概要(目的や機能、使用言語など)と実装したい機能を指示して、コード案を出してもらう エラー文と状況を送って、エラーの原因と改善案を教えてもらう 実装中のコードを添付して、そのコードの説明をしたうえで改善案を出してもらう お察しいただけたでしょうか。 ChatGPTはIDEに統合されているサービスではない ため、私はChatGPTを実装サポートに使用する際、 プロンプトに 一定の情報を記載 したうえで指示を与える+必要に応じて コードを添付 する ChatGPTが結果を出力する 出力結果を見て必要なものを コピーしてIDE上のコードファイルにペースト する というように 無駄な往復作業を繰り返していました 。 結果として、システム全体の構築には3か月くらいかかっていた気がします。 また、その他には以下のような問題が発生していました。 GPTはシステムの全体像や入力した内容以外のコードを把握していない ため、目的に沿わないコード案を出すことがある GPTがシステムの内容を忘れる ことがあり、時々一から説明する必要がある そこで、現在の私は「IDE統合型のKiroのSpecモードで学生時代のコードを読み込んだら、 あの頃の無駄な手間がどれくらいスマートになるのか? 」という疑問を持ちました。 そのため今回は、実際にKiroにコードを読み込ませ、その理解から修正まで行った結果について、 かかった時間とコストの観点 を含めてお届けしたいと思います。   Kiro Specモードの実行 実際にKiroを使用してシステムの改修を行う前に、Kiroについて簡単にご説明したいと思います。 Kiroとは KiroとはAWSが開発したエージェント型統合開発環境(Agentic IDE)であり、プロトタイプから本番品質のアプリケーションまで構築することができます。KiroはVS CodeをベースとしたIDEであり、既存のVS CodeユーザはVS Code上の設定をKiroに簡単に連携することが可能です。 Kiroの画面(右のチャット部ではVibe/Specモードを指定してAIエージェントとの対話を始められる) また、KiroはVibe Coding(自然言語のプロンプトからAIがコードを生成・修正する開発手法)にも優れていますが、その強みとしてSpecモードとAgent Hooksが挙げられます。 Specモード:仕様駆動開発 (事前に定義した仕様書に基づいて設計・実装・テストなどを進める開発手法)を実現する機能 プロンプトを入力すると要件定義書の作成を開始し、ユーザの確認と許可に応じて要件定義フェーズ→設計フェーズ→実装フェーズと遷移して実装を進めていく Agent Hooks:特定のイベントに基づいてアクションを自動で行わせることができるツール コードの変更時にREADMEファイルを自動で更新するなどのアクションを設定でき、コードの一貫性や品質を保持するのに役立ちます このように、Kiroは従来の人手による開発を支援する機能がたくさん詰まっている画期的なIDEなのです。 Kiro のご紹介 – プロトタイプからプロダクションまで、あなたと共に働く新しい Agentic IDE | Amazon Web Services コンセプトからプロダクションまで、AI エージェントとの作業を簡素化した開発者体験を通じて開発を支援する AI IDE(統合開発環境)、Kiro の発表を嬉しく思います。Kiro は Vibe Coding "も" 得意ですが、それをはるか... aws.amazon.com Kiro の AI エージェントフックで開発ワークフローを自動化する | Amazon Web Services ソフトウェアプロジェクトが成長するにつれ、ドキュメントやテスト、コードの可読性とパフォーマンスを同期させ続けるのは難しくなります。Kiro のエージェントフックは、こうした重要な作業をバックグラウンドで自動化し、テスト更新やドキュメント同期... aws.amazon.com ①KiroのSpecモードでコードを理解させる それでは、早速Kiroに過去のコードを読み込ませたいと思います。コードの特徴は以下の通りです。 構成:フロントエンド(React)+ バックエンド(Node.js/Express)+ MySQLをDockerで動かす 総ファイル数:69ファイル、総行数:約5,600行 私が最初に行った指示は以下の通りです。 これからこのアプリを改修したい。まずはアプリの内容や構成・機能を理解して、その結果を設計書として出力して このとても雑な指示を受け、Kiroはなんと 2分6秒 で私の過去のコードを全て解読しました!そのコストは 0.39クレジット です! Kiroは毎月50クレジットが無料で付与され、初回利用では期間限定で500クレジット付与されるため、0.39クレジットはほぼ無いに等しいと言えます。また、有料版でも1クレジットあたり0.04ドルで追加購入できるため、0.04×0.39×156円/ドル≒ 2.4円 となり、これぞ実質無料ですね! また、Kiroは分析と同時に、アプリについて記載した573行のmdファイルを出力しました。 そこにはアプリの機能やデータの流れ、コンポーネント構成などについて詳細に記載されており、たった2分でアプリの強力な理解者を1人得ることができました。ファイルには以下のように図を用いた説明もあり、 自身の理解だけでなく、他者に見せた際にも理解しやすいドキュメント となっているのが良い点です。 ②KiroのSpecモードでコードの修正案を出力させる さて、3か月かけて作成したコードを2分で理解されたところで、Kiroに次の指示を出しました。 次はこのアプリの改善点や問題点を挙げたレポートを作成して 過去に一生懸命作った自分には悪いですが自ら斬られにいったところ、Kiroは 2分9秒で33項目もの指摘ポイントを挙げました 。 学会の質疑だったらと思うとぞっとする量です。 先ほどと同様にKiroが出力したレポートを見ると、エラーハンドリング不足やバックエンドでの認証検証不足などが指摘されていました。 当時は内部での実験用アプリとして作成したため、優しいユーザたちにより重大な問題は起きていませんでしたが、本番利用するとなるとこれだけの改修が必要なことが2分で分かりました。 また、Kiroは問題を挙げるだけでなく、その優先度や改修にかかる時間、改修のロードマップ例も示してくれるため、ただこちらを絶望させるだけでなく 今後どうするべきか?を前向きに考えるポイントをくれる ことも良い点です。 ③KiroのSpecモードでコードの重要修正タスクを実行させる Kiroは33項目の指摘を挙げてくれましたが、ロードマップを見ると最長で6か月とあり、今対処するには負担が大きいように感じます。 そこで、まずは 最重要の改修だけを行う ことにしました。 今回は緊急対処が必要な項目だけ対応したい。そのための計画を立てて その結果、Kiroは3分15秒で EMERGENCY_FIX_PLAN を作成してくれました。 しかし、1つあたりの課題の工数やかかる日数が極端に多いように感じたため、そこについて修正を依頼し、最終的には全体で5~7.5時間で終わる見積もりとなりました。 コードに基づいた緊急対応課題の洗い出しは正確でしたが、各課題対応の 細かいタスクについて自動で洗い出しまで行っていないため、表記からの憶測で時間を多めに見積もってしまった ようでした。 適切な修正プランができたところで、KiroのSpecモードを使用して実際に修正を行う準備を整えることにしました。 ブログ前半で示したように、KiroのSpecモードではユーザの指示から 要件定義書の作成→設計書の作成→実装タスク定義書の作成 を行います。今回は「要件定義書・デザイン~を作成してください」と指示していますが、Specモードを選択して「○○という改修を行いたい」などのプロンプトを入力するだけでも、自動で要件定義書の作成から開始してくれます! さて、要件定義書の作成を指示すると、Kiroがこちらに「 今回の改修は、新しい機能の追加または既存のバグの修正のどちらか? 」と質問をしてきました。ここもKiroを利用する際のありがたいポイントです! Kiroは、こちらからの 指示の中で不足している情報がある場合や、実行の選択肢が複数ある場合などに追加の情報を求めてくれます 。 そのため、全く意図と異なる仕様で計画書を完成させて、クレジットを無駄にしたという場合を減らすことができます。クレジットを最適に使用するには、ある程度のプロンプトの工夫が必要になってきますが、凝っていないプロンプトでもエージェント側が最適になるように判断して動いてくれるのは、AI初心者にもありがたい仕様ですね。 また、Specモードでは次のフェーズに移行する際には、 必ずユーザの許可が必要 になります。 次のフェーズに進んでから前のフェーズのドキュメントを修正した場合も、自動で以降のドキュメントに修正を反映してくれるため、こちらの意図が反映された一貫性のあるドキュメントを手軽に作成できるのも良いポイントです。 さて、各フェーズで少しの修正を加えた結果、24分12秒で3つのドキュメントが完成しました。 最終的に作成されたtasks.mdは以下のようになっており、Startボタンを押すだけでエージェントがタスク定義に基づいて実装を開始してくれます。 早速、1つ目のタスク1.1から順に実行していきます。 すると、上記のTask.mdにおいて定義したように、バグの存在をテストコードで確認し、バグの根本的な原因を確認してくれました。 また、タスクが定義通りに終了したかをKiroが確認したうえで判定をしてくれるため、少しの安心感がある一方で、Kiroへの指示書(ドキュメント)の精度が問われる場面とも言えるかと思います。 上記のように全てのタスクを順に実行していった結果、Kiroが行った修正以外に何もすることなく、以下のようにローカル環境でシステムを立ち上げて一通りの機能を動かすことができました! 今回は大幅な修正が無かったことからKiroの修正のみで起動できましたが、新規機能追加などではエラー対応が必要な場合があります さて、全ての修正が完了したということで、最終的にかかった時間とコストは以下の通りになりました。 時間:ドキュメント作成 1,776s + タスク実行 14,555s ≒ 4時間30分   (※Kiroの動作時間) プロンプトの作成やドキュメントの確認時間などを含めると、 合計7時間 くらいかと思われます コスト:142.72 Credits ≒ $2.85(453円) Kiroは無料プランで50Credits/月、PROプラン($20/月)で1000Credits/月利用可能(参考: Kiro | Pricing ) 今回は$0.02/Creditsとして換算していますが、 実際に利用する場合にはPROプランに入る必要があります 2年前のChatGPTとKiroを用いたコード開発の比較 各AIツールでの作業結果をまとめた表は以下の通りです。   Web版 ChatGPT (2年前) Kiro サービス形式 Web上でのAIチャット IDE統合型AIエージェント 利用用途 システム開発 システム改修 使用コスト $66(10,494円) ※$22/月プランを3か月利用 $20(3,180円) ※$20/月プランを1か月利用 使用時間 30時間 ※動作時間30分/日で3か月間平日のみ利用 5時間 プロンプトの構成 参照してほしい情報は全て含めたうえで、 指示文を入力する 参照してほしい情報は確認を依頼したうえで、指示文を入力する 前提として、両者には2年もの歳月差があり、AIツールとしての位置づけや利用用途も異なるため、対照に比較することは難しい点をご了承ください。 それでもKiroでは、 IDE統合型というユーザ環境を理解しやすい特性と、AIエージェントによるタスク実行などの利点 により、時間やコストを大幅に抑えられたことがお分かりいただけるのではないでしょうか。 何より、 事前に把握してほしい情報を「確認して」の一言で理解してきてくれること が、私には非常に便利に感じられました。前提の共有に時間を取られず、開発そのものに集中できるのはエンジニアのたまごとして非常にありがたい限りです。 もっとも、Kiroを実際の開発業務に使用するにはまだまだ工夫が必要となります。その具体的なノウハウについては、今後の試行錯誤を通じて改めて共有できればと思います。   おわりに いかがでしたでしょうか。厳密な比較ができないものを並べて語ってしまいましたので、理系畑の方の視線が怖くはありますが、KiroのSpecモードの便利さが少しでも伝わっていましたら幸いです。 私はKiroを利用し始めて早4か月、すっかりKiro先生に懐いています。Kiro先生のおかげで初めて知ったことも多くありますので、新人には嬉しい強力なパートナーです。 初めて利用する方は、期間限定でボーナス500Creditsがもらえます ので、少しでも興味がある方は始めてみることをおすすめいたします!私の場合ですが、開発で頻繫に利用していても500Creditsの利用には半月ほどかかるため、十分な機能検証ができるのではないかと思います。 今回も最後までお付き合いいただきありがとうございました。 企業の技術ブログらしくかっこよく技術情報をお届けしたいのですが、毎度やかましい文章になってしまうのはなぜなんでしょうか。。次回頑張りたいと思います。 暦の上では春分を迎え、春らしい日差しが増える季節となってまいりました。まだまだ寒い日もありますので、皆さまお身体ご自愛ください。
アバター
こんにちは。SCSKの岡尾です。 今回は、AWS Glueを利用したETL処理を実装していた中でハマったポイントを紹介したいと思います。 私自身、ETLの実装は初めてでした。これからGlueを使ったETL処理実装していこうとしている方が同じようにつまずかないようにハマりポイントをご紹介できればと思います。   目次 はじめに ハマりどころ ネットワーク:Glueセキュリティグループの「自己参照」 トランザクション:Commit Failed Exception PySpark:メモリ不足エラー まとめ   1. はじめに 今回のプロジェクトでは、Amazon RDS上の業務データをS3 Tablesで構築したデータレイクへ同期するパイプラインを構築しました。 システム構成を簡略化した図が以下の通りです。ポイントとしてはRDSはVPC内のプライベートサブネットに配置されているというところです。 一見シンプルな構成ですが、実際に構築してみると思わぬ落とし穴がありました。   2.1【ハマりどころ①】 ネットワーク:Glueセキュリティグループの「自己参照」 最初のハマりどころは、GlueでのRDSへの接続設定です。 VPC内にあるRDSへGlueから接続する場合、Glue Connection(接続情報)を作成し、VPC・サブネット・セキュリティグループ(SG)を指定する必要があります。 ここで、Glue特有の要件として 自己参照ルール というものがあります。Glueジョブは、内部的にドライバーとワーカーノード間で通信を行います。この通信はVPC内に作成されたENIを経由して行われます。 そのため、 Glueに割り当てたセキュリティグループ自身が、そのセキュリティグループからの全TCP通信を許可している 必要があります。 そのため、Glueにアタッチするセキュリティグループのインバウンドルールには以下を追加する必要があります。 タイプ : すべてのTCP ポート範囲 : 0 – 65535 ソース : カスタム(自分自身のセキュリティグループID sg-xxxxxx) この設定がないと、Glueジョブの実装ができないようです。   2.2 【ハマりどころ②】トランザクション:Commit Failed Exceptionエラー 続いてのハマりどころは、icebergテーブルの書き込み競合です。 今回の実装では、Glueジョブは連携するテーブルの数だけ作成し同時に複数のジョブが起動するような構成としていました。このとき、偶発的にCommitの競合を示すような以下のエラーが発生しました。 「pyiceberg.exceptions.CommitFailedException: CommitFailedException: Request doesn’t meet the requirement condition: Requirement failed: branch main has changed: expected id …..」 調べてみると、以下の公式ブログにもある通り、共通のカタログを利用していると異なるテーブルであってもCommitFailedException が発生する仕様となっているようでした。 Manage concurrent write conflicts in Apache Iceberg on the AWS Glue Data Catalog これを回避するためには、このエラーが発生した場合にリトライ処理を実施するような実装が必要でした。異なるテーブルであれば同時にジョブ実行しても問題ないと思っていましたが、カタログが共通だと書き込みの競合が発生してしまうんですね。   2.3 【ハマりどころ③】PySpark:メモリ不足エラー 最後のハマりどころは、Glueジョブの処理性能です。 最初はコスト効率のいい Python Shell でpythonのpyicebergライブラリを利用した実装をしていました。 しかし、データ量が増え、数万行レベルになった際に、データをDataFrameとしてメモリに展開しようとした際に落ちてしまうMemoryErrorが発生 しました。   Python Shellで利用したpyicebergライブラリのupsert処理では、一度に処理できるデータの件数に制約があるようです。 そこで、 Python Shellでの戦いを諦め、分散処理が可能な Glue ETL (Spark) へ切り替えました。これにより、Sparkの分散処理により数十万件のデータも一度にupsertできるようになりました。また、 Worker Typeの選定も柔軟になり、DPUサイズも調整することで安定してデータ連携が可能になりました。 最初は小さくPythonで、将来的なデータ増加に応じてSpark構成を検討するというのがいいのではないかなと感じました。   3. まとめ 今回は、RDSのデータをGlueジョブを使ってS3 Tablesへ連携する際のハマりどころを紹介しました。 Glueはサーバレスのサービスであるために便利な側面が多い反面、そこで利用される仕組みを理解した上での実装が必要になると勉強になりました。 皆様もGlueを使う際にはぜひ参考にしてみてください!
アバター
近年、AIやデータサイエンスの急速な発展に伴い、HPC(ハイパフォーマンス・コンピューティング)の需要が爆発的に増加しています。しかし、需要が拡大する一方で、「現場の計算待ちが常態化している」「費用対効果を把握できず投資に踏み切れない」といった課題を抱える企業も少なくありません。 この記事では、HPCを取り巻く現状の課題を紐解きながら、リソースの最適化と全社的な可視化を実現するSCSKのソリューション「HPC View(エイチピーシー ビュー)」の魅力と機能についてご紹介します。 HPC市場の急成長と多様化するニーズ 世界的なHPCの市場規模は、2034年までに915億ドル(約14兆6,000億円)に達すると予測されており、需要が急速に伸びています。日本国内に目を向けても、2025年時点で約4,300億円規模に達するとされています。 Source: Global Growth Insights 2020年頃までのHPCは、主に自動車や重工業など製造業におけるCAE(コンピュータ活用エンジニアリング)需要が大半を占めていました。しかし現在は、AI・機械学習(ML)が台頭し主役になりつつあります。さらにクラウドHPCの普及により、金融、ライフサイエンス、メディアなど業界がさらに多様化しており、従来の運用方法では対応しきれない新たな課題が生まれています。 HPC管理に潜む「悪循環」とは? HPCの運用において、現場と経営層の間で以下のようなギャップが生じていませんか? 現場の課題 :計算待ちの常態化、急な計算に対応できない、利用者の偏り、運用・管理の属人化(引き継ぎ不備や運用に時間がかかる) 経営の課題 :投資効果が見えないため、有効な戦略が立てられない このように、「現場の課題が可視化されない ➡ 経営が判断できない ➡ 投資されない ➡ 現場が改善しない」という悪循環が起きています。これを断ち切るためには、 「全社で利用状況を一元的に可視化すること」 が不可欠です。 部門ごとではなく全社的に一元管理できれば、「計算待ちの短縮」「フェアユースの実現(リソースの公平な利用)」「説明可能なHPC運用(経営層への数値提示)」「他部門とのスムーズな連携」が可能となり、全社最適が促されます。 リソース最適化を実現する「HPC View」とは? こうした課題を解決するためにSCSKが提供しているのが、HPCの利用状況を可視化し、リソースを最適に活用するためのサービスである「HPC View」です。業務やシミュレーションのスピードを落とすことなく、最適なHPC運用を実現します。 HPC Viewの3つの特長 HPC View(ポータル)の主な機能 HPC Viewのポータル機能は、直感的に利用可能なGUIベースで提供されます。オンプレミスとクラウドのジョブ管理を統合し、最適なリソースの選択が可能です。以下に主な5つの機能をご紹介します。 ① ダッシュボード機能 利用者は、現在のリソース使用状況やジョブの混雑状況を、グラフィカルな画面で一目で把握できます。全体の稼働率や空きリソースをリアルタイムで確認可能です。 ② ジョブ実行制御 事前に定義済みのテンプレートから、GUIベースで簡単にジョブを実行できます。リストからテンプレートを選択するだけで設定可能なパラメータが表示されるため、ジョブ発行の手間を大幅に軽減します。 ③ ジョブ状況確認 実行中および過去の完了済みジョブの状況をポータルから一覧で確認できます。ジョブをクリックすれば詳細情報が表示され、便利なフィルタ機能により、大量のジョブの中から対象のものを素早く見つけることができます。 ④ モニタリング / BI機能 計算ノードの状態、メモリ・CPUの使用状況、ジョブやキューの混雑状況を確認できます。最大6年間分のデータを参照でき、将来の投資判断やフェアユースの実現を支援します。各レポートデータはCSVでのダウンロードも可能です。 ⑤ セルフ管理機能 管理者向けに、ユーザー情報の作成・変更やジョブテンプレートのメンテナンス機能を提供します。また、お知らせ管理機能により、ユーザー向けのマニュアルやURLリンクを簡単に設置できます。 まとめ HPCは単なる計算機から、ビジネスの成長を牽引する重要なインフラへと変化しています。しかし、そのリソースがブラックボックス化していては、十分なパフォーマンスを発揮できません。 「HPC View」を導入し、現場の利用状況を可視化・一元管理することで、無駄のないリソース活用と納得感のあるIT投資が実現します。 ■ HPC運用のお悩み、SCSKにご相談ください 「現場の計算待ちを解消したい」「HPCの利用実態を可視化したい」など、HPC運用に関する課題をお持ちではありませんか? 本記事でご紹介した「HPC View」の詳細機能や、お客様の環境に合わせた最適なソリューションについては、以下の製品サイトよりお気軽にお問い合わせください。 🔗 HPC View(エイチピーシービュー) | SCSK株式会社
アバター
こんにちは。 今回はAmazon MSK(Managed Streaming for Apache Kafka)上のデータを、S3 Sink Connectorによって Amazon S3 へ出力するパターンを解説します。 MSKはクラスター内のトピックにデータを保管しますが、そのデータをS3へバックアップすることができます。 前提条件 本記事で触れている内容は、以下の構成を前提としています。 ブローカータイプ:標準ブローカー(kafka.m5.large など) クラスタタイプ:プロビジョンドクラスタ メタデータ管理:Apache Zookeeper モード AWS公式ドキュメントに記載されている下記の内容を参考にしています。 Amazon S3 シンクコネクタを設定する 準備 今回、下記リソースは準備済みとして進めます。 MSK クラスター(プロビジョンド、Zookeeper モード) S3 バケット(クラスターと同リージョン) MSK Connect 用 IAM ロール まず、S3 Sink Connectorを用意するためドキュメントに記載されているリンクから、Confluent S3 Sink Connector を入手します。 一番左の Self-Hosted をダウンロードし、ZIP ファイルを取得します。 次に、MSK クラスターをデプロイした同じリージョンの S3 上に ZIP ファイルを配置しておきます。 ここまでで事前準備は完了です。 カスタムプラグインの作成 カスタムプラグインは、MSK Connect を通じて利用する外部 Kafka Connector の ZIP アーカイブです。S3 Sink Connector の Kotlin/Java クラスを含むバイナリパッケージを事前に登録します。 先ほど配置した ZIP ファイルの S3 パスを取得し、カスタムプラグインの作成画面で指定します。 カスタムプラグイン名を任意で設定し、「カスタムプラグインを作成」をクリックします。 作成が完了すると、ステータスがアクティブになります。 次に、このプラグインを用いてコネクターを作成します。 コネクターの作成 MSK Connector は、MSK Connect における Kafka Connect ジョブの単位です。既存のカスタムプラグインを指定して、接続先(MSK クラスター)・タスク数・出力先(S3)等を設定します。 コネクタ作成画面で、先ほど作成したカスタムプラグインを指定します。 画面に従い、設定を進めます。 任意のコネクター名 バックアップする対象となるMSKクラスター コネクタ設定は、 公式ドキュメント を参考に設定します。今回は下記の通り設定しました。 補足として、1つの MSK Connector で複数 Topic を S3 へバックアップするために `topics.regex` を利用しています。 このパラメータを利用することで、`topic` から始まるトピック名のみを対象とすることができます。 {   "connector.class": "io.confluent.connect.s3.S3SinkConnector",   "flush.size": "1",   "format.class": "io.confluent.connect.s3.format.json.JsonFormat",   "partitioner.class": "io.confluent.connect.storage.partitioner.DefaultPartitioner",   "s3.bucket.name": "msk-connector-demo-s3bucket",   "s3.region": "us-east-1",   "schema.compatibility": "NONE",   "storage.class": "io.confluent.connect.s3.storage.S3Storage",   "tasks.max": "1",   "topics.regex": "topic.*" } (補足) プロパティについて 今回は同アカウントのS3に出力するため、下記の通り指定しています。 s3.bucket.name: 出力先バケット名 s3.region: 出力先バケットのリージョン また、公式ドキュメントの例だと、単一トピックを指定していますが、これだと「バックアップ対象のトピック」とConnectorが同じ数必要となります。 Connectorの数が増えると、下記のような問題につながります。 Connectorの利用料金が増える Connectorの数によりサブネットのIP数が足りなくなり、MSK Clusterのスケールアウトができなくなる等の影響がある 今回は1つのS3 Sink Connectorで複数TopicのデータをS3へ出力するため、「topics.regex」を利用しています。 残りの設定を行います。 。 最後に、IAMロールを指定します。 ここで重要なのが、警告にある通り「AWSServiceRoleforKafkaConnect」ロールを指定できないことです。 必ず、別途IAMロールを用意しましょう。 公式ドキュメント に必要なポリシーがまとめられています。 はまりやすいのが、今回のケースで利用できるMSK Connect用の管理ポリシーは現時点で存在しません。MSKFullAccessという管理ポリシーはありますが、MSK ConnectのIAMロールとして使うには権限が足りていません。 もし権限が不足している場合は、MSK Connectの作成途中に下記エラーが発生します。 コード: KafkaConnect.BrokerAuthenticationFailure メッセージ: MSK Connect was unable to connect to the Kafka Broker due to authentication issues. Please verify the authentication needed to connect to the Kafka broker and the related permissions and retry the operation. こうなったときは一度Connectorを削除し、再作成する必要があります。 IAMロールを指定し、「次へ」をクリックします。 セキュリティの画面はそのまま「次へ」とし、ログは必要に応じて設定します。 MSK Connectの作成が失敗した場合などのトラブルシューティングに役立つため、ログは有効化することをお勧めします。 各設定を確認し、「コネクタを作成」をクリックします。 しばらく時間がかかりますが、作成が完了するとステータスが「実行中」になります。 ここまでで Connector の作成は完了です。次はバックアップ対象として扱う Topic を用意し、正しく S3 へ出力されるかを確認します。 トピック作成 S3 に Topic 内のデータが出力されることを確認するため、まずいくつか Topic を作成します。今回は下記トピックを作成してみました。 demo-topic topic-demo-dev01 topic-demo-stg01 Connector作成時の設定により、「topic」から始まるトピックのみバックアップされることを確認します。 EC2などに配置した Kafka CLI で、トピックへメッセージを格納します。いったん、現在の Topic を一覧化します。 ./bin/kafka-topics.sh --list --bootstrap-server <bootstrap-server> --command-config config/client.properties __amazon_msk_canary __amazon_msk_connect_configs_S3SinkConnector_4ab1ac36-1695-4b03-9e22-dcf79cee7218-2 __amazon_msk_connect_configs_S3SinkConnector_63bdb115-000a-4432-8668-b8c70bba0b34-2 __amazon_msk_connect_configs_S3SinkConnector_a6910ee0-12fc-4c43-ad89-784a9a5136ca-2 __amazon_msk_connect_configs_S3SinkConnector_b293673e-999e-4f0e-a616-e8eaed5206c2-2 __amazon_msk_connect_configs_S3SinkConnector_fb60d2ef-8ae7-4621-ba7d-1b891d1602dd-2 __amazon_msk_connect_offsets_S3SinkConnector_4ab1ac36-1695-4b03-9e22-dcf79cee7218-2 __amazon_msk_connect_offsets_S3SinkConnector_63bdb115-000a-4432-8668-b8c70bba0b34-2 __amazon_msk_connect_offsets_S3SinkConnector_a6910ee0-12fc-4c43-ad89-784a9a5136ca-2 __amazon_msk_connect_offsets_S3SinkConnector_b293673e-999e-4f0e-a616-e8eaed5206c2-2 __amazon_msk_connect_offsets_S3SinkConnector_fb60d2ef-8ae7-4621-ba7d-1b891d1602dd-2 __amazon_msk_connect_status_S3SinkConnector_4ab1ac36-1695-4b03-9e22-dcf79cee7218-2 __amazon_msk_connect_status_S3SinkConnector_63bdb115-000a-4432-8668-b8c70bba0b34-2 __amazon_msk_connect_status_S3SinkConnector_a6910ee0-12fc-4c43-ad89-784a9a5136ca-2 __amazon_msk_connect_status_S3SinkConnector_b293673e-999e-4f0e-a616-e8eaed5206c2-2 __amazon_msk_connect_status_S3SinkConnector_fb60d2ef-8ae7-4621-ba7d-1b891d1602dd-2 __consumer_offsets demo-topic topic-demo-dev01 topic-demo-stg01 今回手動で作成したTopic以外にいくつか存在していますが、こちらはMSK内部で自動作成されるものです。 メッセージ送信と検証 kafka-console-producer を利用して、適当なメッセージを送ります。 topic-demo-dev01 にメッセージ送信 ./bin/kafka-console-producer.sh --bootstrap-server <bootstrap-server> --topic topic-demo-dev01 --producer.config config/client.properties message1 message2 message3 topic-demo-stg01 にメッセージ送信 ./bin/kafka-console-producer.sh --bootstrap-server <bootstrap-server> --topic topic-demo-stg01 --producer.config config/client.properties message1 message2 message3 demo-topic にメッセージ送信(対象外で確認) ./bin/kafka-console-producer.sh --bootstrap-server <bootstrap-server> --topic demo-topic --producer.config config/client.properties message1 message2 message3 次に、S3を確認します。 トピック名のオブジェクトが作成されています。 配下に移動すると、JSONオブジェクトとして出力されています。 このように、S3へTopic内のデータを出力することができました。 ちなみにオブジェクト名は、トピック名+パーティション数+オフセット番号から構成されています。 また、ここでわかるように「topic」から始まるトピックのみが S3 へ出力されています。demo-topic に関しては出力されていません。 まとめ MSK Connect の S3 Sink Connector を活用し、複数Topic のバックアップをする方法をご紹介しました。 MSKクラスターのデータをバックアップする手段はいくつかありますが、今回はS3 Sink Connectorを使っています。MSK Connector自体はクラスター同様、MSK ConnectorはMSKクラスターが配置されているサブネット上に作成されます。 サブネットのIPアドレスに空きが十分でないと、クラスターのスケールアウトなどにも影響があります。 MSKクラスターの設計と合わせて検討することをおすすめします。
アバター
Amazon MSKクラスターのKafkaに関する構成は「クラスター構成」として管理されます。   クラスター構成の設定変更には2段階の適用が必要となるため、その手順を理解しておくことが重要です。 前提条件 本記事で触れている内容は、以下の構成を前提としています。 ブローカータイプ:標準ブローカー(kafka.m5.large など) クラスタタイプ:プロビジョンドクラスタ メタデータ管理:Apache Zookeeper モード あくまで参考資料となります。環境によって最適解は変わるため、必要に応じて検討してください。 MSKクラスター構成 MSKクラスター構成には下記のようなプロパティを設定することができます。 default.replication.factor min.insync.replicas auto.create.topics.enable デフォルトで設定されるプロパティのほかに、利用者側でカスタムすることが可能です。プロパティの一覧は 公式ドキュメント をご覧ください。 変更手順 変更する手順は大きく下記のような流れとなります。 `AWS::MSK::Configuration`の `ServerProperties` を編集して新リビジョンを作成 `AWS::MSK::Cluster` の `ConfigurationInfo` を最新リビジョンに切り替え クラスタが `ACTIVE` になるまで待機し、設定反映を確認 1段階目:`MSKClusterConfig`を変更 1. `AWS::MSK::Configuration`リソースにある対象のKafka設定(`server.properties`)を確認 2. 任意のプロパティを追加もしくは、既存のプロパティを変更 3. CloudFormationスタックを更新すると、`AWS::MSK::Configuration`の新しいリビジョンが作成される。(もし最新のリビジョンが1の場合、スタック更新後に2が作成される) 変更前のClusterConfigを下記例として、説明します。 MSKClusterConfig:   Type: AWS::MSK::Configuration   Properties:     Name: msk-demo-config     ServerProperties: |       default.replication.factor=3       min.insync.replicas=2       auto.create.topics.enable=false     log.retention.hours=168 コンソール上で確認すると、それぞれ下記の通りです。 ・MSK Configuration 変更前のリビジョン ・MSK Configuration の現行設定 ・クラスターに適用されている現行設定(現行リビジョン) 今回は、下記の通り変更してみます。 MSKClusterConfig:   Type: AWS::MSK::Configuration   Properties:     Name: msk-demo-config     ServerProperties: |       default.replication.factor=3       min.insync.replicas=2       auto.create.topics.enable=true       log.retention.hours=72 1. 変更セット作成 2. MSK Configuration 変更後のリビジョン 変更セットによる変更が完了すると、上記の通りリビジョンがインクリメントされて最新が追加されます。 3. 変更後のリビジョン確認画面 インクリメントされた最新リビジョンを確認すると、変更したパラメータが適用されていることがわかります。 ポイント 更新時は常に新しいリビジョンが作成される リビジョン番号は自動的にインクリメントされるため、1つ前のリビジョンは保持される(ロールバック時に有用) 2段階目:クラスターに新リビジョンを適用 1. `AWS::MSK::Cluster` の `ConfigurationInfo` に、作成済みの `AWS::MSK::Configuration` リソースの最新リビジョンを指定 2. CloudFormationスタックを更新 3. クラスタの `Cluster Operation` タブで `UPDATE` 完了を確認 MSKCluster: Type: 'AWS::MSK::Cluster' DependsOn: MSKClusterConfig Properties: BrokerNodeGroupInfo: NumberOfBrokerNodes: 3 InstanceType: kafka.t3.small ClientAuthentication: ConfigurationInfo: Arn: !GetAtt MSKClusterConfig.Arn Revision: 2 1. 変更セット作成(クラスタリビジョンを最新に指定) 2. 変更セットの内容確認 リビジョンを最新のバージョンに設定し。変更セットを作成します。 3. 変更セット実行 変更セットが実行されると、MSKクラスターの「クラスターオペレーション」タブにて更新が開始されていることが確認できます。 更新が完了するまで、一定時間待機します。 4. クラスターオペレーションの完了 ブローカーやトピックによって所要時間は前後しますが、一定時間が経過するとクラスターオペレーションが完了します。 この時点で最新のリビジョンが適用されました。 5. 変更反映後のクラスタ状態 クラスターの詳細を見ると、リビジョンが変更されていることが確認できます。これで作業は完了となります。 押さえるポイント `AWS::MSK::Configuration`更新後も、クラスタ側の `ConfigurationInfo` を切り替えるまで新設定は適用されない ロールバックに備え、古いリビジョンは保持されていることを確認しておく `ConfigurationRevision` がインクリメントされた後、明示的にMSKクラスターが参照するリビジョンを切り替えないと反映されません。 まとめ MSKクラスタ設定の変更は、CloudFormation上で「設定リビジョン作成」と「クラスタ設定切替」の2段階処理になることを前提に運用します。もし最新のリビジョンで問題が発生した場合、古いリビジョンへ切り替えることも可能です。
アバター
Amazon MSKをCloudFormationで管理する場合、スタック更新時の制約や依存関係を把握しておくことが重要です。特にMSKリソース(`AWS::MSK::Cluster`など)は、複数のプロパティを一度に変更できない場合があります。 本書では、CloudFormationを使ったMSK運用で遭遇しやすいケースとエラーについて触れます。 前提条件 本記事で触れている内容は、以下の構成を前提としています。 ブローカータイプ:標準ブローカー(kafka.m5.large など) クラスタタイプ:プロビジョンドクラスタ メタデータ管理:Apache Zookeeper モード あくまで参考資料となります。環境によって最適解は変わるため、必要に応じて検討してください。 CloudFormationでのリソース変更 CloudFormationは、テンプレート(コード)を更新すると、差分検出に基づいて必要なリソース更新を自動実行します。一般的なAWSリソースでは、依存関係がなければ複数フィールドをまとめて変更しても問題なく適用されます。 一方で、MSK `AWS::MSK::Cluster`では、同時に変更できないパラメータが存在します。1回のスタック更新で複数項目をまとめて書いても、MSK側で順序依存や不可一致が発生して失敗するケースがあります。 そのため、運用上は1度に全項目を更新せず、複数回に分割した変更(段階的適用)が必要となることがあります。 (補足)マネジメントコンソールでの変更 マネジメントコンソール上でももちろん変更できますが、下記のようなメニューとなります。 それぞれ構成を変更すると、MSKクラスター側に変更が完了するまで他の構成は変更できません。 複数の設定を同時に変更する 例えば次のような変更を同時に行おうとすると、クラスター更新が失敗するケースがあります。 `BrokerNodeGroupInfo` の `InstanceType` 変更 (ブローカーのスケールアップ) `NumberOfBrokerNodes` の増減 (ブローカーのスケールアウト) `EBSVolumeInfo` の `VolumeSize` 変更 (ストレージサイズの変更) 変更セット適用時のエラーと原因 CloudFormationの変更セット実行時に、以下のようなエラーメッセージが発生することがあります。 Error: You can’t update multiple attributes of the cluster in same request. Use a different request for each update. このエラーは、MSKクラスターの同じリクエスト内に複数の変更を含めた場合に発生します。この場合、プロパティを分割して複数回の変更セット作成/適用する運用が必要です。 今回の例以外にもクラスタ更新が失敗するケースは多数存在します。必ず事前に検証環境で検証を実施してください。 また、複数の変更を1つずつ実施すると、想定以上の時間がかかることがあります。特にブローカー再起動が伴う場合は、ダウンタイムやパフォーマンス影響を見積もり、変更内容に応じたスケジュールを確保してください。 運用フロー例 ここまでの内容を踏まえて、変更したい内容次第ではありますが下記のような運用フローとなります。 変更は極力1つの要素ずつ行う 例: まず`KafkaVersion`を更新して安定確認してから、次に`NumberOfBrokerNodes`を変更する 変更計画とスケジュールを明示する 複数回に分ける必要がある場合、各変更に必要な時間・リードタイム(検証、変更セット作成、適用、監視)を見積もる ブローカー再起動が伴う変更は、業務影響が少ない時間帯に実施する 検証/テスト環境で事前に再現確認 本番環境で行う前に、同様の変更をステージング環境で実施して失敗パターンを確認 クラスタ設定変更は段階的に公開 設定変更後は時間を置いて安定性を確認し、問題なければ次の変更を適用 まとめ ご紹介した通り、MSKのCloudFormation運用では、複数プロパティを同時に変更するとスタック更新ができない場合があります。 分割して段階的に変更するなどを検討しつつ、ログやステータスを確認しながら進めることが推奨されます。
アバター
こんにちは。 今回は、Amazon MSKにおけるスケールアップとスケールアウトについてフォーカスします。   Amazon MSK はフルマネージド Kafka であり、クラスターを停止せずに スケールアップ(ブローカー性能の向上)やスケールアウト(ブローカー台数の増加)を行える点が魅力です。   この記事では、MSK のスケールアップ/スケールアウトの基礎や注意点の一部をまとめています。 前提条件 本記事で触れている内容は、以下の構成を前提としています。 ブローカータイプ:標準ブローカー(kafka.m5.large など) クラスタタイプ:プロビジョンドクラスタ メタデータ管理:Apache Zookeeper モード Serverless や Express ブローカーなどはユースケースに応じて検討してください。 あくまで参考資料となります。環境によって最適解は変わるため、必要に応じて検討してください。 スケールアップとスケールアウトの違い Amazon MSKのスケーリングには 2 つの方向があります。 スケールアップ(ブローカー性能を上げる方法) ブローカーの vCPU / メモリ / ストレージ IOPSを大きくし、既存のクラスタ構成のまま性能を引き上げるアプローチです。 ブローカー数は変化なし 性能が向上し、スループットやメタデータ処理が改善   パーティション再配置などのデータ移動が不要   ローリングアップデートのためクラスタの可用性を維持しやすい 接続文字列(bootstrap.servers)が変わらないため、クライアント側に影響が少ない AWS の 公式ドキュメント でも、Kafka の特性と MSK の運用観点から、以下の理由で スケールアウトよりスケールアップをまず検討することが推奨されています。 パーティション再割り当てが不要 ローリングリスタートのためクラスターのI/O停止なし(高可用性の構成になっていることが前提) クライアント側の設定変更が不要なため、全体的な運用コストが低い 特に、ブローカー追加後にパーティション再割り当てをトピックごとに行う必要があります。トピック数が多い場合、運用作業の負荷が高くなります。 スケールアウト(ブローカー台数を増やす方法)と注意点 ブローカー数を増やしてクラスタ容量やパーティション分散を強化する方法です。 ただし、運用上の注意点が多くスケールアップより負荷が高い操作です。 サブネットの IP アドレスを消費する MSK のブローカーは サブネット内に EC2 と同様に配置されるため、スケールアウトに応じてサブネットのIPアドレスを使用します。 また、ブローカーの追加はAZごとになるため、下記の通り「3AZを利用するMSKクラスター」において「1つのブローカー」を追加すると合計で6個のブローカーとなります。 試しにMSKクラスターのスケールアウトを実施してみます。3AZを利用するMSKクラスターの場合、下記のENIが作成されています。Apache Zookeeperノードとブローカーの合計で6個となります。 次に、1ブローカーをAZごとに追加した場合です。 下記の通り、9個となっています。ブローカー数が3つ追加されているためです。 このように、ENIが増えていくためサブネットのIPアドレスの空きには十分注意してください。 スケールアウト時はクライアント設定も更新が必要 追加されたブローカーを活用するには、  bootstrap.servers に “全ブローカー” を含める必要があります。 bootstrap.servers=b-1.example:9092,b-2.example:9092,b-3.example:9092,b-4.example:9092 更新しないと… 新ブローカーが障害時のフェイルオーバー先にならない   リーダーが移動しても接続先が偏る   クラスタ拡張が性能向上につながらない   スケールアウトしたら、必ず全クライアント(Producer / Consumer)の設定を更新しましょう。スケールアップでは接続文字列が変わらないため、この作業は不要です。 スケールアウトの料金インパクト スケールアウトは ブローカー数に比例して料金が増加します。3AZ 構成では 3の倍数での増加となるため注意が必要です。 参考例として、「m5.xlarge の MSK クラスターをスケールアウト」した場合の料金です。 構成 計算式 月額料金 現在(3ブローカー) 3 × $0.543/時間 × 730時間 $1,189.17 スケールアウト後(6ブローカー) 6 × $0.543/時間 × 730時間 $2,378.34 ※東京リージョンを利用した場合。またストレージサイズなど他のプロパティでも変動有。 3AZ 構成ではブローカーを最小 3 個単位で追加する必要があるため、スケールアウト時はコスト面の検討も必ず実施してください。 参考資料 公式にてスケールアップとスケールアウトについてまとめられた記事が公開されています。  より詳細なユースケースに応じた選択を下記から確認ください。 Best practices for right-sizing your Apache Kafka clusters to optimize performance and cost
アバター
こんにちは。 AWSでマネージドなKafkaを扱うとき、Amazon MSK(Managed Streaming for Apache Kafka)は選択肢の一つです。 この記事では、構築・運用の中で気づいた押さえておきたいポイントの一部を紹介します。 前提条件 本記事で触れている内容は、以下の構成を前提としています。 ブローカータイプ:標準ブローカー(kafka.m5.large など) クラスタタイプ:プロビジョンドクラスタ メタデータ管理:Apache Zookeeper モード Serverlessや Express ブローカーなどはユースケースに応じて検討してください。また、あくまで参考資料となります。環境によって最適解は変わるため、必要に応じて検討してください。 可用性のベストプラクティス Amazon MSK(Managed Streaming for Apache Kafka)で高可用性を実現する際の基本は レプリケーションファクタ(RF) と min.insync.replicas(ISR) の設定です。 基本の考え方 MSKにおいても、複数 AZ へ跨がって配置するのがベストプラクティスとなります。   MSKはブローカーと呼ばれるノードの数やスペックを指定し、クラスターを作成します。   そのクラスター構築では、下記を設定するのが一般的です。 設定項目 推奨値 レプリケーションファクタ(default.replication.factor) AZ 数と同じ値(3AZ の場合は 3 min.insync.replicas RF – 1(3AZ の場合は 2) この設定により、以下のような障害パターンに対応できる構成になります。 ブローカー単体のダウン(AZ 障害) メンテナンスによるローリングアップデート ネットワーク一時障害によるブローカー切断  デフォルト設定の活用 この設定はAmazon MSKの デフォルト設定 を選択すると自動で適用されます。カスタム設定を利用する場合は、明示的に設定しましょう。  MSK コンフィグ画面(例) 3AZに分散したクラスター上で作成したトピック詳細 Topic: MSKTutorialTopic TopicId: 8TdcHHTZRgaoF9JmsPS1yg PartitionCount: 1       ReplicationFactor: 3    Configs: min.insync.replicas=2,message.format.version=3.0-IV1,unclean.leader.election.enable=true コスト面とサブネットへの注意   可用性を向上させるためには重要な設定ですが、下記も考慮しておく必要があります。 ブローカー数が増えるほど MSK の利用料金も線形に上昇する ブローカー数が増えると、サブネットの空き IP アドレスを消費する クライアント接続文字列には “全ブローカー” を含める 複数 AZ に跨ってクラスターを構築したら、それを正しく活かすために重要なのが クライアント側の接続設定です。 ベストプラクティスとしては、各 AZ のブローカーを最低1つ以上、bootstrap.servers に含めることを推奨します。全ブローカーを列挙しておくと、フェイルオーバーの信頼性が高まります。 bootstrap.servers=b-1.example:xxxx,b-2.example:xxxx,b-3.example:xxxx この設定の利点: どれか 1 台のブローカーが停止しても、他のブローカーへ自動フェイルオーバーできる AZ 障害発生時でも、残存するブローカーに切り替えられる 接続先の取得方法  MSKを構築したら、AWS コンソールから接続先をコピーすることで、全ての AZ(ブローカー)を含めることができます。 ブートストラップサーバー(接続先)の取得例 スケールアウト時に見落としやすい “AZ 数 × ブローカー数”  MSK では、ブローカーは AZ に均等配置される仕様になっています。  そのため、スケールアウトすると以下のように AZ 数の倍数で増えていきます。 AZ 構成 ブローカー数の遷移 3AZ 3台 → 6台 → 9台 → … 2AZ 2台 → 4台 → 6台 → … スケールアウトによるブローカーの追加 「ちょっと性能上げたいから ブローカーを 1 台だけ増やそう」ということはできません。 「追加するブローカー数」を1に設定すると、利用している全てのAZに1つずつ追加されます。 図:ブローカー追加時の AZ ごとの挙動イメージ この仕様が影響するポイントは次の通りです。 影響項目 詳細 コスト上昇 ブローカー数が想定以上に増えて、利用料金が大きく増大する IP 枯渇 サブネットの IP 消費量が AZ × ブローカー数で増えていき、デプロイ不可能に パーティション再配置 増やしたブローカーに合わせてパーティション再配置が必要になる コスト例(東京リージョン、m5.xlarge): ブローカー数 月額コスト 3台 1,189.17 USD 6台 2,378.34 USD ポイント  “IP 枯渇” は後戻りが難しい部分なので、MSKで使用するサブネットを小さく切りすぎない設計が重要です。 ネットワークインターフェースの画面から、各ブローカーの状況を確認できます。(Apache ZooKeeperノードも含まれる) 図:ENI(ネットワークインターフェース)状況の例 ブローカーのスケールアウトとスケールアップ及びパーティション再配置については、別の機会に改めて触れる予定です。 MSK のモニタリングレベル MSK では、クラスタの状態を監視するために モニタリングレベルが 4 種類用意されています。   どのレベルを選ぶかによって取得できるメトリクスの粒度やコストが変わるため、運用目的に応じて選択することが大切です。 MSK のモニタリングレベル 4 種類 レベル 特徴 コスト DEFAULT クラスター・コンシューマー・プロデューサーの基本的な性能を把握できる 無料 PER_BROKER ブローカー単位でより詳細なメトリクスを取得できる 別途料金が必要 PER_TOPIC_PER_BROKER ブローカー及びトピック単位でのメトリクスを取得可能 別途料金が必要 PER_TOPIC_PER_PARTITION ブローカーごと、トピックごと、パーティションレベルで詳細なメトリクスを取得可能。 別途料金が必要 4つのレベルからモニタリングレベルを選択できますが、DEFAULT レベルでも運用に必要な主要指標はほぼ揃っています。 監視の方針次第ではありますが、利用料金に影響するため、まずは DEFAULT で要件を満たせているか確認することをおすすめします。   ブローカータイプは“性能”+“パーティション上限”で選ぶ MSK のブローカータイプは単純に性能だけで選ぶのではなく、  ブローカーあたりのパーティション数の推奨値や、更新オペレーションをサポートするパーティション上限 も考慮する必要があります。 特に、大量のトピック(数百~数千)を作成する場合は、必ず考慮に入れましょう。 パーティション数の目安 MSKではブローカーサイズに応じて推奨のパーティション数が決まっています。詳しくは 公式ドキュメント をご確認ください。 ブローカーサイズ 推奨パーティション数 kafka.m5.large 1000 kafka.m5.2xlarge 2000 大量のトピック(数百~数千)を作成する場合、ブローカーあたりのパーティション数が増えるため、高スペックのブローカーを選ぶ必要があることを忘れないようにしてください。 例えば、下記のようなクラスターを用意しトピックを作成したとします。 MSKクラスターを 1 台用意 環境ごと(dev / stg / prd)に同じトピックを複製 トピック例(XXXは連番) SampleTopicXXX-dev SampleTopicXXX-stg 最終的にクラスターで 数百~千単位のトピックが生成され、これに伴ってブローカーのスペックが十分でないと CPU やメモリが不足する場合があります。 ブローカーサイズごとにパーティション上限が決まっている インスタンスタイプの性能のみに着目していると、気づきにくいポイントですが、運用時に困ることになります。   上限を超過した場合、 更新オペレーションができなくなる点 に十分注意すべきです。 この点を考慮せずにリリースした場合、MSKクラスターに対する様々な更新ができないという状態になってしまいます。 パーティション上限 MSK には ブローカーごとのパーティション上限があり、トピック作成やパーティション追加、ブローカー設定変更などの更新オペレーションを安全に行うための制約です。 例:kafka.t3.small ブローカーあたり300パーティションが上限 上限を超えてクラスターの負荷が高い、パーティション追加やクラスター設定変更時に エラーが発生 例えば、パーティション数が1のトピックを300個超作成した場合を考えてみます。 このような状態で MSK クラスターの設定変更を実施しようとすると、下記のようなエラーが発生します。 The number of partitions per broker is above the recommended limit. Add more brokers and rearrange the partitions per broker to be below the recommended limit, then retry the request このように、ブローカー当たりのパーティション数が上限を超えると、設定変更などのオペレーションができなくなります。 上限に余裕を持たせてパーティションを設計し、定期的にメトリクスを監視することが重要です。 スケールアウトに耐えるサブネット MSK クラスターを作成する際、サブネット設計は意外と見落とされがちなポイントです。   スケールアウトのたびに IP を消費する MSK のブローカーは EC2 と同じで、サブネットの IP を消費します。   スケールアウトを何度か行うと、IP が枯渇してデプロイできないケースがあります。 ポイント MSK クラスター作成後、サブネットを変更することはできません。 そのため、最初に十分な IP アドレスの余裕を持たせてサブネットを設計することが重要です。 例えば、ブローカーを1つ追加した際、各 AZ に 1 つずつ追加されるため、ネットワークインターフェースの数が急増します。Apache ZooKeeperノードも含めて管理する必要があります。 データ移行 MSK はクラスター内に作成したトピック内にデータを保持する仕組みのため、別クラスターを作成した場合はデータを移行する手間が発生します。 設計時のコツ サブネット設計時には 「将来的に別クラスターを作らずに済むための IP アドレス余裕」を確保することを推奨します。 またMSK Connect を使う場合、ワーカーが 自動的に同じサブネットにデプロイされます。 MSK Connector の構築画面(例) MSK Connect 構築時のENI ワーカーもブローカーと同じサブネットにデプロイされるため、IP アドレスの消費が増加します。 MSK クラスター単体でも十分に IP を消費しますが、Connect を追加するとさらに圧迫される可能性があります。 ストレージ:増やせるけど減らせない ブローカーのストレージは後から増やすことはできますが、減らすことはできません。 MSK の EBS ストレージは”スケールアップのみ” 現在設定されているストレージが初期値かつ最小値になっています。 コスト面も考慮し、初期設定は慎重に見積もりましょう。またMSK では retention 設定がない限り、無期限でデータを保存します。 無制限のままだと、いつの間にかディスクスペースがなくなっていた、という状況に陥ります。必ず適切な保持期間を設定しましょう。 IAM 認証で“運用しやすい”アクセス制御にする MSK は IAM を使った認証(IAM Access Control)をサポートしており、AWS の IAM ポリシーと同じ考え方で権限管理ができるのが大きなメリットです。 トピック単位でのアクセス拒否など、細かい制御が可能 IAM 認証では、IAM ポリシーにより柔軟なアクセス権限を表現できます。 アクセス制御の例 あるアプリには「特定トピックの読み取り・書き込みだけ許可」 別のアプリには「特定トピックへの読み取りだけ許可」 管理用ユーザーには「全トピック管理操作を許可」 IAMポリシー例 末尾が「-dev」のトピックへの操作のみを許可するポリシーです。 { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "kafka-cluster:*Topic*", "kafka-cluster:ReadData", "kafka-cluster:WriteData" ], "Resource": "arn:aws:kafka:*:123456789000:topic/*/*/*-dev" } 「-dev」トピックへのproducer実行例(成功) sh-5.2$ ./bin/kafka-console-producer.sh --bootstrap-server <bootstrap-server>--topic SampleTopic001-dev --producer.config ./config/client.properties >message1 「-stg」トピックへのproducer実行例(権限エラー) sh-5.2$ ./bin/kafka-console-producer.sh --bootstrap-server <bootstrap-server>--topic SampleTopic001-stg --producer.config ./config/client.properties >message1 [2025-11-28 09:15:09,879] WARN [Producer clientId=console-producer] Error while fetching metadata with correlation id 5215 : {SampleTopic001-stg=TOPIC_AUTHORIZATION_FAILED} (org.apache.kafka.clients.NetworkClient) [2025-11-28 09:15:09,885] ERROR [Producer clientId=console-producer] Topic authorization failed for topics [SampleTopic001-stg] (org.apache.kafka.clients.Metadata) [2025-11-28 09:15:09,887] ERROR Error when sending message to topic SampleTopic001-stg with key: null, value: 8 bytes with error: (org.apache.kafka.clients.producer.internals.ErrorLoggingCallback) org.apache.kafka.common.errors.TopicAuthorizationException: Not authorized to access topics: [SampleTopic001-stg] 末尾が「-stg」のトピックへ操作する権限はないため、権限エラーが発生します。 このように、IAMポリシーを利用することで、トピックレベルでの権限制御も可能です。 Kafka バージョンアップは“短いサイクル”を前提に Amazon MSK で利用する Kafka バージョンは、他のサービスと同様に常に一定の更新サイクルを意識しておく必要があります。 MSK のサポート終了 利用するバージョンにもよりますが、おおよそ 2 年ほどでサポートが終了します。  詳細はAWS公式ドキュメントをご確認ください。   サポート対象の Apache Kafka バージョン サポート終了後も同バージョンを使い続けることはできず、AWS によって自動的にバージョンアップが行われます。そのため、サポート期限を把握し、事前にバージョンアップの影響を確認しておくことが重要です。 互換性の確認 Kafka のバージョンを上げると、プロデューサー/コンシューマーのクライアントにも影響します。 ライブラリの互換性 API の変更 クライアントの再接続の挙動 新機能による設定パラメータの追加 これらは事前に検証しておきましょう。アプリ側のクライアントが古いバージョンのままで、MSK だけ先にアップグレードしてしまい、動作不具合が発生してしまう恐れがあります。 MSK のバージョンアップはローリング方式 MSK のメジャーアップデートはブローカー単位でローリング方式により実施されます。 クラスターを複数 AZ に配置しておけば、アップデート中も処理を継続できます。 クライアント側も、全てのブローカーを接続先に含めておくことで、切替時の影響を最小化できます。 bootstrap.servers=b-1.example:xxxx,b-2.example:xxxx,b-3.example:xxxx まとめ Amazon MSK は高可用性と優れた管理機能を備えたマネージドサービスですが、設計段階での決定が運用に大きく影響します。 本記事で紹介した内容は、Amazon MSK設計時に注意すべき項目の一部をまとめたものです。MSK のベストプラクティスはこれだけではなく、チーム規模やユースケースに応じて、さらに細かい調整が必要になる場合があります。あくまで参考資料として、自組織の要件に合わせて検討してください。 最後に、プロビジョンドクラスターは一時停止できません。ブローカータイプによっては利用料金が高額になる場合もあります。検証目的で構築し、不要になった場合は必ずクラスターを削除することを忘れないようにしましょう。 本記事が皆さんの MSK 構築・運用の一助となれば幸いです。
アバター
こんにちは。SCSKの石田です。 本記事より、次世代APIプラットフォームとして世界中で注目を集めている「Kong API Gateway」についてのブログを開始したいと思います。初めてブログを投稿するため、至らない点もありますがご容赦ください。 昨今のエンタープライズシステムにおいて、クラウドネイティブ化やマイクロサービス化が進む中、システム同士をつなぐ「API」の数は爆発的に増加しています。第1回目となる今回は、なぜ今エンタープライズ企業においてAPIマネジメントが重要視されているのか、そして「Kong API Gateway」がどのようにその課題を解決するのか、概要を説明します。   爆発的に増加するAPIトラフィックと新たな課題 エンタープライズにおけるAPIマネジメントの重要性を語る上で外せないのが、APIトラフィックの圧倒的な増加です。 近年の複数のグローバル調査データ( ※1 )によると、現在の Webトラフィック全体の約70%以上がAPI経由の通信 であると報告されています。さらに特筆すべきは、AI技術の普及に伴う変化です。Postman社の「2025 State of the API Report」等によれば、 AI主導のAPI呼び出し(マシン間通信)が前年比で40%以上も急増 しており、APIは単なるアプリケーションの連携口から「AIエージェントの実行レイヤー」へと進化しつつあります。 ※1 参考: Postman「2025 State of the API Report」 、 The State of API Security in 2024 | Resource Library 等の各調査レポートより このように、人間が操作する端末からの通信だけでなく、システムやAIによる自動化された大量のリクエストが飛び交う中、各システム(社内システム、SaaS、パブリッククラウド上のサービスなど)が個別にAPIを公開・管理したままでは、以下のような課題に直面します。 セキュリティのガバナンス低下: 認証・認可の仕組み(OIDCやmTLSなど)が各システムでバラバラになり、脆弱性の温床になる。 トラフィック制御の複雑化: AI等によるリクエストの急増や攻撃から、バックエンドシステムを保護する仕組みが統合されていない。 運用負荷の増大: どのAPIが、誰に(どのシステムに)、どれくらい利用されているのかを一元的に把握できない。 これらの課題を解決し、増え続けるすべてのAPIトラフィックを安全かつ効率的に統合管理する仕組みこそが「APIマネジメント」であり、その入り口となるのがAPIゲートウェイです。   Kong API Gatewayとは?その特徴と強み Kong API Gatewayは、世界で最も利用されているオープンソースベースのAPIゲートウェイの一つです。エンタープライズ環境でKongが選ばれるのには、大きく3つの理由があります。 1. 圧倒的なパフォーマンスと軽量さ NGINXをベースに構築された軽量なアーキテクチャにより、極めて低いレイテンシで大量のAPIリクエストを処理できます。第三者評価機関であるGigaOm社のAPIマネジメントベンチマーク調査( ※2 )においても、他の製品と比較して圧倒的な高スループット(1ノードあたり毎秒5万件以上のトランザクション)と、サブミリ秒(1ミリ秒以下)の低レイテンシを記録し、その卓越したパフォーマンスが実証されています。AWSなどのクラウド環境やコンテナ環境との親和性が非常に高く、モダンなインフラ上でも軽快に動作します。 ※2 参考: GigaOm「API and Microservices Management Benchmark」 より 2. 豊富なプラグインエコシステム Kongの最大の魅力は、APIのルーティング機能だけでなく、高度な要件を「プラグイン」として簡単に追加できる点です。例えば、トラフィック制御(レート制限)、高度な認証・認可(OIDC、OAuth2.0、SAML、OPA連携)、ログ転送などを、バックエンドのコードを改修することなくAPIゲートウェイ層で一元的に実装・自動化できます。Kongにて一元的にこれらの機能を集めることで、API開発者の負荷を下げることができます。また、プラグインはノーコード・ローコードで実装できる点も強みです。 3. あらゆる環境に対応する柔軟性 Kongはコンテナとして動作するため、オンプレミス、マルチクラウド、ハイブリッドなど、どこにでもデプロイ可能です。SaaS型の管理基盤である「Kong Konnect」を利用すれば、グローバルに分散したAPIゲートウェイ群を単一のコントロールプレーンで統合管理することも可能です。   SCSKとKongのパートナーシップ 私たちSCSKは、Kong Inc.の公式パートナーとして、商用版Kongにおけるライセンスの提供からシステム構築・導入支援まで、エンタープライズ企業様向けのサービスを展開しています。 多数のAPIが乱立する大規模環境への導入や、既存のレガシーシステムからモダンアーキテクチャへの移行に伴うAPI基盤の刷新など、お客様の課題に合わせた最適な構成をご提案可能です。「自社のAPI管理に課題を感じている」「Kongの導入を検討したい」といったご相談があれば、ぜひお気軽に「kong-sales@scsk.jp」までお問い合わせください。   まとめ・今後の連載について 今回は第1回のため、APIトラフィック増加の背景とAPIマネジメントの重要性、そしてKong API Gatewayの概要についてご紹介しました。APIを安全かつ効率的に公開・管理することが、これからの開発スピードを左右する重要な要素となります。 次回以降は、Kongの基本的なAPIのルーティングや、プラグインの具体的な「やってみた」、さらに今や欠かせない「Kong AI Gateway」の紹介まで、エンジニア目線でより詳細な技術情報をお届けしていく予定です。 次回もぜひご期待ください!
アバター
生成AIの ChatGPT (OpenAI)、 Gemini (Google)、 Claude (Anthropic)などの進化が激しいですが、それぞれのサービスの最新情報を収集し、新たにできることになったことを理解し、複雑なプロンプト(指示文)を使いこなすのに、そろそろ疲れてきていませんか? 生成AI疲れと、実際の一般の利用者視点、実際の業務の効率化視点におけるAI活用のひとつとして、 Genspark をご紹介します。   Genspark(ジェンスパーク)とは Genspark ( ジェンスパーク )は、ChatGPTのような対話型AIと異なる エージェント型AI となり、検索から資料作成までを自動化する次世代型のAIオールインワンワークスペースとうたわれています。2026年3月時点では Genspark AI ワークスペース 3.0 がリリースされています。 ちなみに、Gensparkで「Gensparkとは?」を尋ねると、以下の回答が返ってきます。 Gensparkは「オールインワンのAIワークスペース/AIコパイロット」を掲げ、ブラウザ上(拡張機能のサイドバー)でページ要約・Q&A・タスク自動化までまとめて行えるサービスです。 中核となる”スーパーエージェント(Super Agent)”を中心に「考えて、計画して、実行する」自律型AIとなり、調査・コンテンツ作成・データ分析・電話・メールなどを「1つのプロンプト」で実行します。 30+モデル・150+ツール・700+ MCP連携を組み合わせ、スライド(Slides)、シート(Sheets)、ドキュメント(Docs)、画像(Designer)、デベロッパー(Developer)等の用途別の専門エージェントと協調して動作する仕組みになっています。Genspark AI ワークスペース以前は、Genspark スーパーエージェントと言う名前でした。 直近の2026年3月に新機能の自律型エージェント Genspark Claw もリリースされ、メール送信、カレンダー調整、Slack連絡など、複雑なマルチタスクを指示だけで自動化することも可能となっています。 Gensparkは、「調べる」→「まとめる」→「成果物(資料/文章/表/デザイン)作成」→「レビュー(ファクトチェック)」→「連絡やスケジュール調整」というような一連の皆さんが普段実施している業務をすべてAIで自動に実施することが最大の強みとなり、また複雑なプロンプトではなく、やりたい内容をひとつの指示で投げられるのが特徴です(例:〇×市場の調査して10枚のスライド、PDFを集めて要約して、など)   Genspark 運営企業 MainFunc について Gensparkの運営元は、 MainFunc です。アメリカ カリフォルニア州パロアルトを本拠地とする企業で、創業者はBaidu(百度)出身のEric Jing(エリック・ジン氏)で、Microsoft、Google、Meta、Pinterest などの出身者によって2023年に設立されました。アメリカ以外には、シンガポールと東京にオフィスがあります。 現在、2億7,500万ドルのシリーズB資金調達ラウンドを経て、12.5億ドルの評価を受けています。 “MainFunc”という名前の由来は、コンピュータプログラミングの基本概念である「main function」に由来しており、プログラミングにおける main function(=主機能)はあらゆるソフトウェアアプリケーションの出発点として機能し、すべての操作がここから始まるのと同様に、MainFuncは技術分野において重要なプレーヤーとしての位置づけとなり、革新的なAI駆動製品の発信点となることを目指しています。   オプトアウト(学習拒否)について まず最初に、企業で利用する場合に問題になるのが オプトアウト(学習拒否) です。 Gensparkに限らず、AIチャットボットへ利用者が機密情報や個人情報などを誤って入力してしまった場合でも、入力したデータや検索履歴がAI学習に利用されないようにするのはオプトアウト(学習拒否)ですが、Gensparkでは、オプトアウト設定は、以下の手順で行えます。 左サイドバーの最下部のアイコンより[設定]を選択。 [アカウント]タブの「 AIデータ保持 」のスイッチをOffにします。(デフォルトではOnになっています)   AIチャット、AI画像、AI音楽、AI動画 まず、ChatGPTと同じ対話型AIである AIチャット(チャットボット) をご紹介します。 ここで見ていただいて分かるように、AIチャットを選ぶと、利用する生成AIを自由に選ぶことが可能になっています。 現時点では、 ChatGPT は、GPT-5.4、GPT-5.4 Mini、GPT-5.4 Nano、GPT-5.4 Pro、o3-pro、 Claude は、Sonnet 4.6、Sonnet 4.5、Opus 4.6、Opus 4.5、Haiku 4.5、 Gemini は、2.5 Pro、3 Flash Preview、3.1 Pro Preview、3 Pro Preview、また、イーロンマスクが率いる xAI社 のGrok4 0709が利用することができます。過去には、DeepSeek、Mistralなども含まれていました。 つまり、Gensparkを契約するだけで、ChatGPT、Claude、Gemini、Grokを個別に契約することなく、それぞれを利用することが可能になります。 AIチャットでは、生成AIを選択することが可能ですが、通常問い合わせ(スーパーエージェント)においては、プロンプトで支持されたタスクに最適な生成AIをGenspark側が自動で組み合わせて利用するため、利用者側で生成AIを選ぶ必要はなく、生成AI毎の得意・不得意を理解している必要もありません。また、最新の生成AIがリリースされると自動的にGensparkに組み込まれることになります。 次に、 AI画像生成 も同じです。 見ていただいて分かるように、画像生成するAIを選択することができます。 最近話題の Geminiの Nano Banana Pro 、Nano Banana 2から、ByteDance Seedream v5.0 Lite、Flux 2、Flux 2 Pro、GPT-Image 5.0、Recraft v3、Ideogram v3、Qwen Image 2、Recraft Clarity Upscale、Baria Background Remover、Text Removal から選択が可能です。Baria Background Remover、Text Removal については入力画像が必須となります。 AI音楽生成 も、ElevenLabs Music、MiniMax Music 2.5、Mureka Song、Lyria2 Music Generator、ElevenLabs Sound Effects、CassetteAI、Mureka Instrumentalから選択が可能です。 AI動画(ビデオ)生成 も、 Gemini Veo 3.1 、Gemini Veo 3.1 Ref、Gemini Veo 3.1 First-Last Frame、 Sora 2 、Sora 2 Pro、Gemini Veo 3、 Kling V3 、Kling V3 Motion Control、Kling O3 Image-to-Video、Kling O3 Refelence-to-Video、Seedance v1.5 Pro、Grok Imagine Video、MiniMax Hailuo-2.3 Standard、PixVerse V5、Seedance Pro Fast、Fal Lipssync V2、Wan V2.6、Vidu Q3、Runway、ByteDance Video Upscalerから選択が可能です。 AIチャットと同じですが、AI画像生成、AI音楽生成、AI動画(ビデオ)生成についても、Gensparkを契約するだけで、Nano Bananaを始め様々は専門の生成AIを利用することが可能になります。 ちなみに現在キャンペーンを実施しており、有償契約を行うと2026年末まで AIチャットとAI画像生成は無制限に利用することができます。   実業務での利用シーン AIスライド、AIシート活用 次に、実際に「調べる」→「まとめる」→「成果物(資料/文章/表/デザイン)作成」→「レビュー(ファクトチェック)」→「連絡やスケジュール調整」と言った業務での利用をイメージした利用方法についてご紹介します。 利用シーンとして「オンラインストレージのマーケティング業務」を例としています 。 背景としては、 最近、オンラインストレージのニーズが再び高まってきている からとなります。 オンラインストレージとは、 Dropbox 、 OneDrive 、 Google Drive 、 Box と言ったサービスになります。DropboxやBoxは10年以上前からサービス利用されていますが、最近ニーズが高まっている背景としては、 ①各種システム(サーバ)がクラウドへ移行され、企業内にファイルサーバだけが残っており、ファイルサーバもクラウド移行したい 。 ②単純にWindowsファイルサーバをクラウドへ移行すると、バックアップの設定やソフトウェア(OS含む)のパッチ適用やバージョンアップなど運用管理の負荷が変わらない 。 ③さらに、ランサムウェア対策として容易にデータ復元が可能なファイルサーバのサービスを利用したい 、などが挙げられます。 これまでオンラインストレージは、社外とのデータ共有やファイル受け渡しにしか利用していなかったが、改めて社内ファイルサーバとしての活用を検討する企業が増えています。 SCSKは、2017年に 日本初のDropboxサービスパートナーに認定 (2026年3月時点で当社のみ)されており、単純にDropboxのライセンス販売をするだけではなく、Dropboxの導入支援(初期設定、ID/SSO連携)から、ファイルサーバからのデータ移行、トレーニングを含む活用支援などの様々なサービスをご提供しています。 ということで、 Genspark でオンラインストレージのマーケティング調査を実施 します。 まず、マーケティング調査報告書を Genspark の AIスライド で作成します。 デフォルトで様々なテンプレートが準備されており、自社向けの独自テンプレートで作成することも可能ですが、今回はデフォルトで準備されている「 マーケティング戦略 2025 」を利用し、プロンプト(指示分)は「 オンラインストレージの主要サービスを比較し、国内市場シェア、Dropboxの販売促進を行うためのマーケティング戦略をまとめてください 」としました。 プロンプトをより詳細に記述することで、アウトプットの精度を向上させることが可能ですが、今回は一般の利用者を前提として作りたいアウトプットをひとつの指示で行っています。 先ほどの指示で上記のスライドが作成されました。Genspark の追加の指示サンプル「3ページ目の市場シェアデータを円グラフまたは棒グラフに変換して視覚的にわかりやすくしてください」にあるように細かい調整を行うことも可能です。 また、[ ファクトチェック ]や[ AI編集 ]を行うことができ、[ 高度な編集 ]にて直接文字の修正をすることも可能です。 他の生成AIでは、アウトプットが画像イメージやPDFで出力され、直接アウトプットを編集することができないケースが多いですが、Genspark のAIスライドは、このように直接編集することが可能です。さらにエクスポートを行うことができます。 PDFはもちろんですが、Microsoft Powerpoint(PPT形式)やGoogle Slidesでエクスポートすることが可能です。 現状のAIスライドはきちんとPPTの枠内に収まっていないなどがよくありますが、細かい修正はPPTで実施した方が圧倒的に早いです。 完成したマーケティング報告書は、ここで掲載はしませんが、日本の市場規模と年成長率(CAGR)、2034年の市場規模、各サービスのシェア、主要4サービス比較表(Dropbox、Box、OneDrive、Google Drive)、Dropboxの勝機(勝ち筋)、DropboxのSWOT分析、最後にDropboxの販売戦略についてのアウトプットが行われています。特に、販売戦略については、ターゲット、差別化するメッセージ、重要KPIなども提示されています。 Genspark のインプットとなるデータは、一般に公開されているWebサイトのデータ以外にも、Genspark が独自に購入しているデータも含まれています(詳細は後述) 同様のマーケティング調査報告書を、自社(自分)で作成するには何時間、何日か掛かりますし、外部調査企業へ依頼すると時間以外にお金も掛かりますが、Genspark の AIスライドであれば数分で完成します。 Gemini(Canvas)で同様のことができますが、PPTにするには、Google Slidesで一旦エクスポートし、再エクスポートする必要があります。 これは、個人的な意見ですが、他の生成AIで作成すると、初回は期待値の40~60%の出来栄えからスタートし、その後プロンプトの調整で70~90%の出来栄えになるイメージですが、Genspark は、最初から70~80%の出来栄えが完成するイメージです。   先ほどのマーケティング戦略のターゲットのひとつに、 SMB(20から500名) 、 クリエーター(代理店・制作会社・動画編集者) とありましたので、ターゲット企業を調査します。 ターゲット企業調査に Genspark の AIスライド を利用します。 AIシートのプロンプトに「 日本国内で従業員数が20名から500名までのクリエーター会社、映像制作会社、コンテンツ制作会社をリストアップしてください 」としました。 数分で、約200社の企業のリスト(スプレッドシート)が制作されました(会社名はマスキングしています) このスプレッドシートも元にしてさらに AIシートで、都道府県分析などを行ったりすることも可能です。 AIシートは、Excelとしてエクスポートをすることも可能ですが、今回の企業調査においては、外部の有償の企業データベース「Crunchbase」を活用しているため、Excelでのエクスポートはできなくなっていました。 このスプレッドシートを元に企業で保有するハウスリストと突き合わせることも可能ですが、今回は、それとは別に、AIシートへ「 日本国内で従業員数が20名から500名までのクリエーター会社、映像制作会社、コンテンツ制作会社の参加者が多い2025年に開催された国内イベントをリストアップしてください 」を指示することにしました。 数分で、クリエーター会社・映像制作会社・コンテンツ制作会社(従業員20〜500名規模)の参加者が多いイベントがスプレッドシートにまとめられました。さらに、特におすすめのイベントとして、① コンテンツ東京2025(7月) 、② Inter BEE 2025(11月) 、③ VIDEOGRAPHERS TOKYO ONLINE 2025(10月) の3つが挙げられていました。 これらイベントのさらなる調査や、実際の広告・イベント企業への情報の裏どりが必須ですが、これらのイベントへ参加しターゲット企業のリードを獲得することも可能です。 今回のAIシートは問題なくExcleでエクスポートが可能でした。   まとめ Genspark(ジェンスパーク)とは?から、対話型AI(AIチャットボット、画像・音楽・動画生成)、エージェント型AIである AIスライド、AIシートの活用例を紹介しました。 AIスライドは、マーケティング調査だけではなく、提案書骨子を作成し、骨子をプロンプトで指示することで、数分で提案書の叩き台を作成することが可能です。大量のPDF資料を元にしてサマリ資料を作成することもできます。 AIシートは、Excelのマクロやピボットテーブル、グラフなど複雑な操作を知らなくても、Excelスプレッドシートを元にして、加工・分析し報告書を作成することが可能です。 対話型AIのAIチャットボットや画像・音楽・動画生成は、利用されていた方が多いと思いますが、エージェント型AIのAIスライドやAIシートは、普段の実業務を効率化するのに有効だと思います。 Genspark では、対話型AIを「 基本エージェント 」、エージェント型AIを「 高度なエージェント 」と定義しており、以下のように業務の効率化に寄与できる便利なエージェントがたくさんあります。 エージェント種別 エージェント名 概要 高度なエージェント Genspark スーパーエージェント 自動調査(旅行計画・予約、記事・動画生成など) AIスライド スライド生成 AIシート スプレッドシート生成 AIドキュメント ドキュメント生成 AIデベロッパー Webサイト・アプリ開発 AIデザイナー デザイン制作 フォトジーニアス 話して写真編集 クリップジーニアス 動画編集 AIポッドキャスト ポッド(音声番組)生成 深層研究 ディープリサーチ ファクトチェック 複数ソースを用いた検証 通話代行 電話アシスタント ダウンロードエージェント AIドライブへダウンロード 基本エージェント AIチャット AIチャットボット AI画像 AI画像生成 AIオーディオ AI音声変換 AI音楽 AI音楽生成 AIビデオ AI動画生成 翻訳 AI翻訳 ミーティングメモ AI会議議事録生成 上記エージェント以外に、 Speakly (音声入力)、 AIドライブ 、 AI Inbox (メール/カレンダー管理)などもありますので、また次の記事で紹介していこうと思います。  
アバター
こんにちは、SCSKの松岡です🪣 データ基盤の構築でIceberg (S3 Tables)を導入した際の試行錯誤を整理しました。 従来のS3によるファイル格納型のデータレイクと比較し、Icebergを採用することで得られたメリットや、それをマネージドで扱えるS3 Tablesの利便性について紹介します。   背景 データ活用基盤におけるデータレイクは、単にデータを蓄積するだけでなく、直接参照して検索・分析に活用したいというニーズが増えています。そのような用途では、複数テーブルのJOINや同時アクセスなどに対する性能も重要な観点となります。 従来は、データレイクのデータをRedshiftやSnowflakeといったDWHに取り込み、集計・加工したうえで活用する構成が一般的でした。 一方で、マルチクラウド環境では、組織や用途に応じて複数のツールから同一データを参照したいケースも増えています。 また、データオーナーの視点では、業務プロセスの変化に伴うデータ構造の変更に対して、データレイク側が柔軟に対応できるかが重要になります。列追加やデータ型変更などを、既存データに影響を与えずに実施できることが望まれます。 さらに、データ基盤管理者にとっては、従来のファイル形式のデータレイクでは、フォルダ構成やファイルサイズ、データ配置などの設計が必要となり、構築・運用のハードルが高いという課題がありました。 加えて、大量の履歴データを長期保管する特性上、データ量の増加に伴うストレージコストの増大や、運用負荷の増加も懸念されました。   構成と選定理由 Why Iceberg? Apache Iceberg – Apache Iceberg™ 「背景」で述べた課題を踏まえ、データレイクにIcebergの仕組みを採用しました。 Icebergはオープンソースのテーブルフォーマットであり、オブジェクトストレージ上のデータをテーブルのように扱える点が大きな特徴です。 メタデータや統計情報を活用することで、必要なデータファイルのみを読み込むことが可能となり、従来の単純なファイル型データレイクと比較して効率的な検索が可能です。 また、Icebergはオープンテーブルフォーマットであるため、特定のDWHサービスに依存せず、AthenaやRedshift、Snowflakeなど複数の分析エンジンから同一データを参照できます。これにより、用途に応じてBI・AI/MLなどのツールを柔軟に選択でき、ベンダーロックインを避けた構成を実現できます。 さらに、用途ごとにテーブルを分離し、加工データを段階的に保持するデータレイク構成にも適しています。 機能面では、「スキーマ進化(Schema Evolution)」により、既存データを書き換えることなくカラム追加などのスキーマ変更が可能であり、データ構造の変化にも柔軟に対応できます。 Why S3 Tables? 表形式データの大規模ストレージ – Amazon S3 Tables – AWS S3 TablesはIcebergをマネージドで利用できるAWSのサービスです。S3 Tablesを採用することで、Icebergのテーブル管理を簡素化することができました。 テーブル単位でデータを管理できるため、フォルダ構成やファイル設計を個別に検討する必要がなく、データ管理をシンプルに保つことが可能です。 また、スナップショットやメタデータ管理がマネージドで提供されるため、履歴管理に伴う運用負荷を軽減できます。 必要に応じてスナップショットの保持期間などを手動で制御することも可能です。 さらに、データファイルの最適化が自動で実施されるため、運用による性能維持の負担を抑えつつ、必要に応じてパーティション設定などのチューニングも行えます。 加えて、S3ベースのストレージを利用するため低コストでのデータ保管が可能であり、コストタグやAWS Cost Explorerを用いた可視化・管理にも対応しています。 構成 今回のケースでは、オンプレミスのサーバから収集したデータの蓄積先としてS3 Tablesを採用しています。 未加工データと一次加工データをテーブル単位で分離し、用途や処理段階に応じたデータレイク構成としています。 S3 Tablesに格納したデータは、AthenaやRedshift、BIツールなどから直接参照しています。   気にしたポイント Amazon S3 Tables とテーブルバケットの使用 – Amazon Simple Storage Service 基本的なテーブルの構築は、S3 Tablesユーザーガイドのチュートリアルに従うことで簡単に実現可能でした。 運用も考慮した場合、追加で以下のような観点を考慮しました。   テーブル設定に関する考慮 基本的にS3 Tables側がマネージドで制御されるので、初期作成時には以下の点だけ気にしました メンテナンスジョブに関する考慮事項と制限事項 – Amazon Simple Storage Service ・テーブルプロパティ(スナップショットを保持する世代数、保持する最長時間) Iceberg テーブルを作成する – Amazon Athena ・パーティション設定   テーブル定義に関する考慮 Iceberg テーブルスキーマを進化させる – Amazon Athena Iceberg 形式では、次のスキーマ進化の変更がサポートされています。 Add  – 新しい列をテーブルまたはネストされた  struct  に追加します。 Drop  – 既存の列をテーブルまたはネストされた  struct  から削除します。 Rename  – 既存の列またはネストされた  struct  のフィールドの名前を変更します。 順序変更  – 列の順序を変更します。 型昇格  – 列、 struct  フィールド、 map  キー、 map  値、または  list  要素の型を広げます。Iceberg テーブルでは、現時点で次のケースがサポートされています。 整数から大きな整数 浮動小数点から倍精度浮動小数点 10 進数型の精度を上げる Icebergでは、既存テーブルに対してカラム追加や型拡張などのスキーマ変更(スキーマ進化)が可能です。 一方で、スキーマ進化には制約があり、数値型から文字列型のような互換性のない型変更はサポートされていません。その場合は、列の追加やテーブル再作成による対応が必要になります。 このため、スキーマ変更に柔軟に対応できる一方で、初期段階でのテーブル定義設計は依然として重要となります。   テーブル定義変更方法の制約 AWS Glue を使用した Amazon S3 テーブルでの ETL ジョブの実行 – Amazon Simple Storage Service S3 Tablesのテーブルの作成はAWS AthenaやAWS CLIから可能です。 一方で、作成済テーブルに対するパーティション変更やソート順の変更など、一部のテーブル設定はAthenaやCLIからは実行できず、GlueのSparkジョブからSQLを実行する必要がありました(2025年時点)。   コストに関する考慮 Amazon S3 Tables で個々のテーブルのストレージコストを可視化できるように – AWS 2025年6月のアップデートにより、S3 TablesはCost Explorer からテーブルレベルのコストデータを参照できるようになりました。 他のAWSサービスと同様に、コストタグの設定も行えるようになりました。 比較的に新しいサービスなので、このような機能アップデートが活発に行われている状況です。   権限に関する考慮 Lake Formation を使用したテーブルまたはデータベースへのアクセスの管理 – Amazon Simple Storage Service S3 Tablesの権限制御はLakeFormationベースで行う必要があります。 従来のS3バケットの権限管理とは異なるため、導入時には注意が必要です。 一般的には、IAMユーザー/ロールごとに、S3 Tablesの名前空間やテーブル単位で権限を付与する形となります。 Simplified permissions for Amazon S3 Tables and Iceberg materialized views – AWS (追記) S3 Tablesに対して、IAM ベースの認証をサポートするというような記事も投稿されていました。   まとめ Icebergを採用することで、データ構造の変化に柔軟に対応しつつ、効率的な検索が可能なデータレイクを構築することができました。 また、S3 Tablesを利用することで、構築・運用における設計負荷を大きく軽減できました。 本構成は柔軟性・性能・運用負荷のバランスに優れたデータ基盤の選択肢であると考えています。 一方で、Icebergにはスキーマ進化の制約があり、またS3 Tablesについても一部のテーブル設定変更に制約があるなど、設計時に考慮すべきポイントが存在していました。 IcebergはIoTデータなどの大規模データの格納基盤としても注目されており、今後はリアルタイムデータやAI/ML活用など、さらなるユースケースへの適用も検討していきたいです。   (宣伝) クラウドデータ活用サービス 今回ご紹介した内容は、SCSKで提供しているクラウドデータ活用サービスの中で扱っているテーマの一部になります。 お客様のデータ活用状況に応じて、基盤構築から可視化、データ連携、データマネジメント、高度データ活用までを段階的にご支援しています! 私自身もこのサービスに関わっており、AWS Summitのブースやミニセッションでもご紹介してきました。 ご関心あれば、以下のサービスページもご参照ください。 AWS データ活用|サービス|企業のDX戦略を加速するハイブリッドクラウドソリューション
アバター
こんにちは、 AWS内製化支援「テクニカルエスコート」 担当の間世田です。 先日、セッションマネージャーでのEC2接続についてLTを行ったところ、VPCエンドポイントの要否についてアドバイスをいただきました。 従来、プライベートサブネット内のEC2へセッションマネージャーで接続するためには、以下の 3つのエンドポイント が必要でした。 ssm ssmmessages ec2messages ところが、 約2年前にアップデートが行われ、SSM Agent バージョン 3.3.40.0 以上では ec2messages の代わりに ssmmessages が使用される ため、以下の 2つのエンドポイントで十分 とのことです。 ssm ssmmessages 本件については、以下のブログでも解説が行われています。 SSM セッションマネージャーに必要なVPCエンドポイントが2つになっていた | DevelopersIO 新しいバージョンのSSM Agentではec2messagesエンドポイントが不要になりました dev.classmethod.jp さて、セッションマネージャー接続では ec2messagesエンドポイント が不要であることが示されましたが、従来 ec2messageエンドポイント が必要とされているサービスは他にもあります。その一つが、 インスタンスのパッチを管理する Patch Manager です。 現時点では、Patch Manager に必要なエンドポイントとして ec2messages も含めて紹介している技術ブログが多く見られます。 そこで今回は、 Patch Manager においても ec2messages が不要となったのかどうかを検証 していきます。 前準備 以下のような環境を構築します。 エンドポイント エンドポイント 種別 com.amazonaws.ap-northeast-1.ssm Interface com.amazonaws.ap-northeast-1.ssmmessages Interface com.amazonaws.ap-northeast-1.s3 Gateway 検証のため、ec2messagesエンドポイントは作成しません。 EC2 OSはRHEL 9.2とし、プライベートサブネットに配置します。 RHELは初期状態では SSM エージェントがインストールされていないため、EC2 起動時のユーザーデータでインストールしました。 #!/bin/bash cd /tmp sudo dnf --disablerepo="*" install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm sudo systemctl enable amazon-ssm-agent sudo systemctl start amazon-ssm-agent 【初心者向け】RHELでSession Managerを使うためにユーザーデータでSSMエージェントをインストールしてみた | DevelopersIO dev.classmethod.jp エージェントのバージョンを確認すると amazon-ssm-agent-3.3.3883.0-1.x86_64 でした。バージョン 3.3.40.0 以上であるため、前述のとおり ec2messages の代わりに ssmmessages が使用されます。 NAT Gateway RHEL では、パッチ適用時にインターネット上の RHUI(Red Hat Update Infrastructure)からパッケージを取得します。 第1章 Red Hat Update Infrastructure の概要 | システム管理者のガイド | Red Hat Update Infrastructure | 3.1 | Red Hat Documentation 第1章 Red Hat Update Infrastructure の概要 | システム管理者のガイド | Red Hat Update Infrastructure | 3.1 | Red Hat Documentation docs.redhat.com EC2 からのアウトバウンド通信が必要となるため、今回は Regional NAT Gateway および Internet Gateway を作成します。 【本題】Patch Managerの実行 今回は検証のため、「概要から開始」からパッチの適用を行います。 スキャンのみ それでは、最初に「スキャン」のみを行います。 結果、ec2messagesエンドポイント作成せずともスキャンが成功しました。 Patch Manager「スキャンのみ」でも、セッションマネージャと同様にec2messagesエンドポイントは不要で、ssmmessagesで代替されるようです。 解説 従来ec2messagesエンドポイントは、Systems Managerサービスへの API オペレーションのために使用されていました。 ec2messages API オペレーション Systems Manager は、Systems Manager Agent (SSM Agent) からクラウド上の Systems Manager サービスへの API オペレーションにこのエンドポイントを使用します。 リファレンス: ec2messages、ssmmessages およびその他の API オペレーション - AWS Systems Manager Systems Manager の内部オペレーションで使用される特殊な API オペレーションについて説明します。 docs.aws.amazon.com SSM Agentの内部には、ec2messagesエンドポイントと通信するMDSInteractorと、ssmmessagesエンドポイントとMGSInteractorという2つのコンポーネントが存在します。 SSM Agetntのソースコードには以下のコメントがあります。 // mdsSwitcher is responsible for turning on and off MDS based on MGS status. amazon-ssm-agent/agent/messageservice/interactor/mdsinteractor/mdsinteractor.go at mainline · aws/amazon-ssm-agent An agent to enable remote management of your EC2 instances, on-premises servers, or virtual machines (VMs). - aws/amazon... github.com つまり、ssmmessages(MGS)への接続が確立されると、ec2messages(MDS)のポーリングは自動的に停止する設計のようです。 では、/var/log/amazon/ssm/amazon-ssm-agent.logを確認してみましょう。 2026-03-23 03:21:25.3209 INFO [ssm-agent-worker] [MessageService] [MDSInteractor] Starting message polling 2026-03-23 03:21:25.3955 INFO [ssm-agent-worker] [MessageService] [MGSInteractor] SSM Connection channel status is set to ssmmessages 2026-03-23 03:22:25.3965 INFO [ssm-agent-worker] [MessageService] [MDSInteractor] Moving to stop poll job after a minute 2026-03-23 03:22:25.3965 INFO [ssm-agent-worker] [MessageService] [MDSInteractor] MDS Polling job stopped. ログでも SSM Connection channel status is set to ssmmessages の直後に MDS Polling job stopped が記録されており、この動作が確認できます。 スキャン&インストール 次に、「スキャンとインストール」を検証していきます。   ec2messagesエンドポイント作らずとも、パッチインストールについても無事成功しました。   結論 SSM Agent バージョン 3.3.40.0 以上では ec2messages の代わりに ssmmessages が使用されるため、Patch Managerのスキャン/インストールともにec2messagesエンドポイントは不要である。
アバター
組織改編(部署統合・分割・名称変更・異動)のたびに、Dropboxの権限周りで「誰が、どのチームフォルダにアクセスできるべきか」を整合させる作業は、Dropbox を運用している管理者にとって“事故が起きやすい”ポイントです。 実際、Dropboxグループが実組織と乖離したり、旧部署メンバーがチームフォルダに残り続けたり、新部署メンバーが必要なチームフォルダにアクセスできない、といった状態が発生し得ます。 組織改編時のDropbox運用では、アクセス権の棚卸し・更新を確実に行い、「適切な人が確実にフォルダにアクセスできること」「異動後も不要なフォルダにアクセスできてしまう状態を防ぐこと」を満たす必要があります。 本記事では、上記の課題に対し、弊社SCSKのDropbox環境で「どのように組織改編対応を現場負荷を抑えつつ権限整合を担保しているか」を、Dropbox管理者(情シス・部門IT担当者)向けにご紹介します。 1. 基本方針 まず、弊社SCSKのDropbox環境についてご紹介します。 Enterprise プラン Entra ID を介して、Dropboxユーザーの自動プロビジョニングを行っています。 人事情報をSmartdbxに取り込み、SmartdbxにてDropboxグループの作成、削除、およびグループメンバーの管理を行っています。 各チームフォルダの利用部署にてSmartdbxの共有フォルダ管理機能を使ってチームフォルダの作成、およびアクセス権限付与を実施しています。 SCSK Dropboxユーザーとグループの連携 SCSKテナントは、Smartdbxでチームフォルダの払い出し、およびアクセス権限付与を 行っており、チームフォルダ作成や共有メンバーの管理は、フォルダ利用部署の担当者の申請により実施されます。 チームフォルダの共有メンバーの管理は、利用者に委ねられています。 さて、Smartdbxとは何でしょうか?「Smartdbx」は、社内外の多くのDropboxプロジェクトで培ったSCSKの知見を活用して開発したサービスです。 Dropboxをより簡単に、便利に、安心して活用する Dropbox 統合管理ツールの「Smartdbx」は以前の投稿で紹介しています。こちらをご参照ください。 Smartdbxとは?~Dropbox統合管理ツール~ – TechHarmony 1.1 Dropbox チームフォルダはグループ単位に共有する まず押さえる前提として、共有フォルダは「チームフォルダ」を使っており、チームフォルダの共有先は個人のメンバー単位ではなく、Dropboxグループを指定しています。 そして、Dropboxグループには「課」単位の「組織グループ」があり、日次で人事情報をSmartdbxに取り込み、Dropboxグループが更新されています。 チームフォルダには主に「組織単位」と「業務単位」の2パターンがありますが、そのうち組織単位のチームフォルダの共有先は、主に「組織グループ」を指定しています。 1.2 組織改編時の作業フロー 組織改編時は、 「グループの更新」と「チームフォルダ共有先の付け替え」を混同せず、 ステップに分けて順番に作業を進めていきます。( 「グループの更新」と「チームフォルダ共有先の付け替え」の作業は、以降で説明します。) SCSK環境では、人事情報をSmartdbxに取り込み、組織グループが日次で更新される一方で、チームフォルダのアクセス権は、組織改編後も自動では新組織グループへ付与されません。そのため、改編前に利用部署側で影響範囲(チームフォルダの分類、共有先グループ、改編後も継続利用するか)を棚卸しし、改編後に「継続利用」対象のチームフォルダについて、旧組織グループから新組織グループへ共有先を付け替える作業を実施します。 2. 組織改編時の作業 2.1 組織グループの更新 Smartdbxにて自動で新組織に対応した Dropbox グループが作成・更新されます。そのため、「グループを新組織に合わせて作り直し、メンバーを移し替える」といった作業の実施は必要ありません。ただし、 組織改編の部署の変更パターン別に 、Dropboxの組織グループの挙動が変わるため、このルールを予め理解しておく必要があります。 【組織改編の部署の変更パターン】 部署は存続・部署のメンバーが変更される – 更新部署 新しい部署ができる – 新設部署 部署が廃止される – 廃止部署 部署は存続・部署のメンバーが変更される 更新部署 → 異動者をグループメンバーに追加・削除します Dropboxの組織グループは人事データと連携して、自動的に更新されます。組織グループは残り、旧年度メンバーから新年度メンバーへ 組織グループのメンバーの入れ替えが発生します。異動があった人は、異動元の組織グループからは除外され、異動先の組織グループに追加されます。 また、組織改編により「部署コード」に変更がなく部署名が変わった場合も、SmartdbxによってDropboxグループ名が新しい部署名に自動更新されます。 新しい部署ができる 新設部署 → 新組織グループ作成・所属する人をグループメンバーに追加します 新しい部署が新設された場合、その部署に所属する人をグループメンバーとした新しいDropbox組織グループが作成されます。 部署が廃止される 廃止部署 → 組織グループの名前を変更します 廃止された部署のグループ名は、Smartdbxによって自動で先頭に”OLD_”が付与されたDropboxグループ名に変更されます。廃止された部署の組織グループのメンバーは更新されず、そのまま残ります。 2.2 現行チームフォルダの棚卸し 前に記載したとおり、SCSKテナントはSmartdbxでチームフォルダの払い出しおよびアクセス権限付与を行っているため、基本的にはチームフォルダ作成や共有メンバーの管理は、フォルダ利用部署の担当者が実施します。チームフォルダの共有メンバーの管理は利用者に委ねられており、Dropbox管理者が棚卸しなどを実施することはありません。 組織改編前に、フォルダ利用部署の担当者は、管理するチームフォルダを一覧化し、組織改編による影響範囲の棚卸を行います。 チームフォルダの分類(組織単位 / 業務単位) チームフォルダの共有先グループ(どの組織グループに共有されているか) 組織改編後のチームフォルダ継続利用の要否(組織改編後も新しい組織グループで利用するか) 組織改編後も新組織グループにアクセス権を付与して継続利用するのか、もしくは、組織改編後は利用しない(現行の共有メンバーにアクセス権を付与した状態のまま残しておく)のかを確認します。 この棚卸しにて、「組織グループに共有」されている「継続利用」と判断したチームフォルダに対しては、フォルダ利用部署の担当者が組織改編後にアクセス権の変更を実施します。 2.3 チームフォルダのアクセス権を変更 組織改編後、チームフォルダのアクセス権は、”OLD_”が付与された、廃止部署のDropbox組織グループに付与されたままです。そして、チームフォルダのアクセス権は、自動で新しい組織グループに付与されません。 「組織グループに共有」されている「継続利用」と判断したチームフォルダに対して、チームフォルダの共有先を 旧組織グループから新組織グループへ付け替えます。 Smartdbx 共有フォルダ申請アクセス権を付与 対象のチームフォルダについて、新組織グループにアクセス権を付与し、必要に応じて旧組織グループのアクセス権を削除するとともに、編集/閲覧の権限の見直しを実施します。具体的には、各利用部署の担当者がSmartdbxから、新組織グループへのアクセス権付与や、必要に応じた旧組織グループのアクセス権削除など、共有メンバーの変更申請を行います。 申請されると、申請者の 上長(課長・部長)に承認フローが回り、上長が確認・承認した上で チームフォルダのアクセス権が 自動変更 されます。 以上により、弊社SCSKのDropbox環境は、組織改編時に起きやすい Dropbox 運用上の課題を解消しつつ、組織改編後も適切なアクセス権を付与したチームフォルダを利用しています。 まとめ 「Smartdbx」には管理者業務を自動化することで、管理者業務自体を極力なくし、管理者の運用負荷を軽減させる機能が用意されております。 チームフォルダを利用していても、管理者側で実施する作業を極力少なくしつつ、組織改編時に起きやすい権限課題を抑える ―― この考え方は、管理者が組織改編を年次イベントとして確実に対応するうえで有効です。 本記事の内容が、組織改編時の権限事故を避けるための運用設計・周知に役立てば幸いです。お問い合わせは本文記載の窓口をご活用ください。 本投稿に関するお問合せ先     :  Dropbox-sales@scsk.jp SCSKのDropbox取り組み紹介:    https://www.scsk.jp/product/common/dropbox/index.html   参照情報 Dropbox でグループを使用する方法 – Dropbox ヘルプ https://help.dropbox.com/ja-jp/account-access/groups チーム フォルダ マネージャー – Dropbox ヘルプ   https://help.dropbox.com/ja-jp/organize/team-folder-manager チーム フォルダの管理方法 – Dropbox ヘルプ   https://help.dropbox.com/organize/shared-folder-differences Dropbox 管理者になるためのガイド   https://learn.dropbox.com/ja/self-guided-learning/business-admin-course/dropbox-admin-guide Dropbox 運用ガイドライン(Dropbox NAVI) https://navi.dropbox.jp/dropbox-folder-operation-guideline
アバター
こんにちは。SCSKの福田です。 昨今、Microsoft 365 Copilot をはじめとする AI 技術や AI エージェントの進化が目覚ましく進んでいます。 2025 年 11 月に開催された Microsoft Ignite 2025 では「Agent 365」や「Entra Agent ID」など、エージェントを人の ID と同様にセキュアに管理できる新しい仕組みが発表され、ID 管理の重要性はこれまで以上に高まっています。 こうした背景の中で ID 管理の基盤となるのが Entra ID の条件付きアクセスポリシーです。 条件付きアクセスポリシーは、ユーザーやネットワーク、リスク状態などに応じて認証を柔軟に制御できる Entra ID の機能です。 本記事では、条件付きアクセスポリシーについて、基本的な考え方から実際のユースケースに基づく設定手順までを分かりやすく解説します。 今後も Entra ID に関するトピックを紹介していく予定ですので、ぜひ参考にしていただければ幸いです。   条件付きアクセスポリシーとは? 条件付きアクセスポリシーとは、指定した条件に合致したアクセスに対してアクセス制御(ブロック、MFA を要求、など)を実施する機能です。 ポイントは以下の通りです。 条件で指定できる項目 条件には接続ユーザー、接続元デバイス( OS )、接続元ネットワーク、接続先リソース、リスク状態などを指定できます。 また、条件で指定できる項目は一部を除き対象と対象外を設定可能です。 例えば、接続元ネットワークの対象に「すべてのネットワーク」、対象外に「社内のグローバル IP アドレス」を指定したブロックポリシーを作成することで、社外からのアクセスはブロックされます。 アクセス制御 「ブロック」または「アクセス権の付与」を指定できます。 「アクセス権の付与」は「MFAを要求する」など、アクセス許可するための制御方法を選択できます。 ブロックの扱い 複数のポリシーが同時に適用された場合は、すべてのポリシーが評価されます。 その結果、いずれかのポリシーで「ブロック」と判定された場合はアクセスがブロックされます。 ポリシーが適用されていないユーザーは制御がかからない ポリシーが適用されていないユーザーはアクセス制御がかからず、原則 ID・パスワードのみでアクセスできてしまいます。 ユーザー種別が多い環境だと、ポリシーの適用漏れがないように注意する必要があります。   必要なライセンス 条件付きアクセスポリシーを利用するには Microsoft Entra ID P1 ライセンス または Microsoft Entra ID P2 ライセンスが必要です。 尚、サインインリスクやユーザーリスクに基づいてアクセスを制御する「リスクベースの条件付きアクセスポリシー」を利用する場合は、Microsoft Entra ID P2 ライセンスが必要となります。 また、デバイスの準拠状態を条件としてアクセス制御を行うポリシーを構成する場合は、Microsoft Intune のライセンスも別途必要となります。 Microsoft Entra ID P1 と P2 の違いについては、以下のサイトをご参照ください。 Microsoft Entra のプランと価格 | Microsoft Security   今回の構成(ユースケース) 今回は架空の企業のセキュリティ要件を想定し、以下の構成でポリシー設定を行います。 グループ構成 一般ユーザー用セキュリティグループ:GeneralUsersGroup 管理者ユーザー用セキュリティグループ:AdminGroup ポリシー要件 一般ユーザー Windows または iOS の会社貸与デバイス( Intune 準拠)からのアクセスを許可し、それ以外のアクセスはブロックする。 管理者ユーザー 指定のネットワーク(運用保守用ネットワーク)からの Windows デバイスによるアクセスに対しては MFA を要求することでアクセスを許可し、それ以外のアクセスはブロックする。 その他のユーザー 上記2つのグループに属さないユーザーについてはすべてのアクセスをブロックする。 「何をもって準拠とするか」の定義は Microsoft Intune 側で設定となります。( Microsoft Intune ライセンスが必要) 本記事では割愛します。   設定手順:事前準備(場所の定義) まず、管理者ユーザーの制御で使用する「運用保守用ネットワーク」を定義するために、ネームドロケーションを登録します。 Microsoft Entra 管理センターにアクセスします。 左メニューより [条件付きアクセス] > [ネームド ロケーション] を開きます。 [+IP 範囲の場所] をクリックし、以下の通り登録します。 名前:運用保守用ネットワーク 信頼できる場所としてマークする:チェックを外す [+] をクリックし、グローバル IP アドレスを登録します。 [作成] をクリックします。 これでネームドロケーションの登録は完了です。   設定手順:各ポリシーの作成 ここからは実際のポリシー作成です。要件に合わせて順番に作成していきます。 一般ユーザー用ポリシー 一般ユーザー用のポリシーを作成します。 Microsoft Entra 管理センターにアクセスします。 左メニューより [条件付きアクセス] > [ポリシー] を開きます。 まずは、Windows、iOS からのアクセス時にデバイスの Intune 準拠を要求するポリシーを作成します。 [+新しいポリシー] をクリックします。 下表の通り設定します。 設定箇所 設定内容 名前 CA-General-DeviceCompliance ユーザーまたはエージェント (プレビュー) 対象 [ユーザーとグループの選択] > [ユーザーとグループ] を選択し、セキュリティグループ「GeneralUsersGroup」を指定する 対象外 チェックなし(デフォルト) 条件 > デバイスプラットフォーム ※ [構成] を [はい] に設定する 対象 [デバイス プラットフォームの選択] で Windows と iOS を選択する 対象外 チェックなし(デフォルト) アクセス制御 許可 [アクセス権の付与] を選択し、[デバイスは準拠しているとしてマーク済みである必要があります] にチェックを入れる [ポリシーの有効化] を [オン] に設定し、[作成] をクリックします。   同様の手順で、Windows、iOS 以外からのアクセスをブロックするポリシーを作成します。 設定箇所 設定内容 名前 CA-General-DeviceBlock ユーザーまたはエージェント (プレビュー) 対象 [ユーザーとグループの選択] > [ユーザーとグループ] を選択し、セキュリティグループ「GeneralUsersGroup」を指定する 対象外 チェックなし(デフォルト) 条件 > デバイスプラットフォーム ※ [構成] を [はい] に設定する 対象 [任意のデバイス] を選択する 対象外 Windows と iOS を選択する アクセス制御 許可 [アクセスのブロック] を選択する これにより、一般ユーザーが Windows、iOS からのアクセス時はデバイスの Intune 準拠を要求し、その他デバイスからのアクセスはブロックされます。   管理者ユーザー用ポリシー 管理者ユーザー用のポリシーを作成します。 まずは、Windows からのアクセス、かつ、運用保守用ネットワークからのアクセス時に MFA を要求するポリシーを作成します。 設定箇所 設定内容 名前 CA-Admin-MFA ユーザーまたはエージェント (プレビュー) 対象 [ユーザーとグループの選択] > [ユーザーとグループ] を選択し、セキュリティグループ「AdminGroup」を指定する 対象外 チェックなし(デフォルト) ネットワーク ※[構成] を [はい] に設定する 対象 [選択したネットワークと場所] で 「運用保守用ネットワーク」を指定する 対象外 選択なし(デフォルト) 条件 > デバイスプラットフォーム ※ [構成] を [はい] に設定する 対象 [デバイス プラットフォームの選択] で Windows を選択する 対象外 チェックなし(デフォルト) アクセス制御 許可 [アクセス権の付与] を選択し、[多要素認証を要求する] にチェックを入れる   次に、Windows 以外からのアクセスをブロックするポリシーを作成します。 設定箇所 設定内容 名前 CA-Admin-DeviceBlock ユーザーまたはエージェント (プレビュー) 対象 [ユーザーとグループの選択] > [ユーザーとグループ] を選択し、セキュリティグループ「AdminGroup」を指定する 対象外 チェックなし(デフォルト) 条件 > デバイスプラットフォーム ※ [構成] を [はい] に設定する 対象 [任意のデバイス] を選択する 対象外 Windows を選択する アクセス制御 許可 [アクセスのブロック] を選択する   次に、運用保守用ネットワーク以外からのアクセスブロックするポリシーを作成します。 設定箇所 設定内容 名前 CA-Admin-NetworkBlock ユーザーまたはエージェント (プレビュー) 対象 [ユーザーとグループの選択] > [ユーザーとグループ]を選択し、セキュリティグループ「AdminGroup」を指定する 対象外 チェックなし(デフォルト) ネットワーク ※[構成] を [はい] に設定する 対象 [任意のネットワークまたは場所] を選択する 対象外 [選択したネットワークと場所] で「運用保守用ネットワーク」を指定する アクセス制御 許可 [アクセスのブロック] を選択する これにより、管理者ユーザーは Windows からのアクセス、かつ、運用保守用ネットワークからのアクセス時に MFA を要求され、その他デバイス・ネットワークからのアクセスはブロックされます。   その他ユーザー向けポリシー 最後に、どのグループにも属していないユーザーや想定外のユーザーがアクセスできないよう、包括的なブロックポリシーを作成します。 設定箇所 設定内容 名前 CA-Unknown-Block ユーザーまたはエージェント (プレビュー) 対象 [すべてのユーザー] を選択する 対象外 [ユーザーとグループ] を選択し、セキュリティグループ「GeneralUsersGroup」「AdminGroup」を指定する アクセス制御 許可 [アクセスのブロック] を選択する 以上で条件付きアクセスポリシーの作成は完了です。   さいごに 今回は Entra ID の条件付きアクセスポリシーについて、基本的な考え方からユースケースに基づく設定例まで解説しました。 「適切なデバイスから」「適切な場所から」「多要素認証を経て」アクセスさせることは、ゼロトラストセキュリティの第一歩です。 この土台を整えることで、より安全で運用しやすい環境になります。 今後も Entra ID に関する役立つ情報を発信していきますので、ぜひチェックしてください!
アバター
こんにちは、SCSK林です! 様々なデータ活用が推進される中、データの蓄積場所(データレイク)と分析基盤を異なるクラウドで運用するようなケースもあると思います。一度AWS S3に溜まった膨大なデータを動かすことは容易ではありません。一方で、分析層ではGoogle CloudのBigQueryが持つクエリ性能や、マネージドなETLサービスを活用したいというニーズがあります。 私が担当した某プロジェクトでは、この「AWSにデータ、Google Cloudで分析」というハイブリッド構成を、完全閉域網で実現することが求められました。本記事では、100以上のインターフェースを抱える大規模なデータ連携基盤において、AWSのネットワーク機能をいかに駆使して「セキュア・低遅延・低運用コスト」を実現したか、その設計思想を解説します。 アーキテクチャ概要:Amazon S3 × Google Cloud Data Fusion 今回のアーキテクチャの主役は、Amazon S3とGoogle Cloud Data Fusionです。 システム構成の概要 Storage (AWS) : S3。数百万レコードにおよぶ日次の業務データが蓄積されるデータレイク。 ETL (Google Cloud) : Cloud Data Fusion。GUIベースでパイプラインを構築・管理。 Network : AWS Direct Connect ⇔ Partner Interconnect による専用線接続。 Security : インターネットを一切経由しない閉域網構成。 解決すべき技術的課題 接続性の確保 : 専用線経由でGCPからS3へどうやって「プライベートIP」でアクセスするか。 名前解決(DNS) : 異なるクラウド間で、複雑なインフラを立てずにどうやってS3のFQDNを解決するか。 スケーラビリティ : 100を超えるインターフェースのトラフィックをどう効率的に捌くか。 【技術的ポイント①】Gateway型を棄却し、PrivateLinkを選定 AWSでS3へのプライベートアクセスを考える際、まず頭に浮かぶのは「Gateway VPC Endpoint」でしょう。しかし、本プロジェクトでは「Interface VPC Endpoint」を使用しました。 Gateway Endpointの仕様的限界 S3 Gateway Endpointは、VPCのルートテーブルを書き換えることで機能します。しかし、この仕組みは「VPCの外部(Direct ConnectやVPNの向こう側)」からは利用できないという制約があります。Google CLoud側から専用線経由でアクセスしようとしても、Gateway Endpointへルーティングを飛ばすことはできません。 この制約を回避するためには、VPC内にForward Proxy(Squid等を搭載したEC2)を立てる必要がありますが、これは「サーバーレス・マネージド」というプロジェクトの方針に反し、運用コストと単一障害点(SPOF)のリスクを増大させます。 Interface VPC Endpoint (PrivateLink) の採用 今回使用したのが、Interface VPC Endpoint (AWS PrivateLink) です。 PrivateLinkは、VPC内のサブネットにENI(Elastic Network Interface)を払い出し、S3への通信をそのIPアドレス経由で行います。 メリット : 専用線(Direct Connect)越しに、Google CloudからS3のプライベートIPへ直接ルーティングが可能。 運用の排除 : EC2のようなOS管理が不要。AWSが提供するフルマネージドな高可用性をそのまま享受できる。 100以上のIFが集中する基盤において、インフラの保守をAWSにオフロードできるメリットは、処理量に応じた課金を十分に正当化できるものでした。 【技術的ポイント②】シンプルなマルチクラウドDNS設計 PrivateLinkを採用した際、次に問題となるのが「DNSの名前解決」です。Google Cloud上のData Fusionから、AWS S3のエンドポイント名をどう解決するか。 通常、ここでも「Route 53 Resolver Endpointを立てて、GCP Cloud DNSと条件付き転送(Forwarding)を設定する」という構成が検討されます。しかし、今回はシンプルで保守性の高い方式を採用しました。 PrivateLinkのDNS特性の活用 AWS PrivateLinkでS3エンドポイントを作成すると、 vpce-xxxx.s3.region.vpce.amazonaws.com のような専用のFQDNが払い出されます。このFQDNは、パブリックなDNSサーバから名前解決しても、VPC内のプライベートIPアドレスを返却するという特性を持っています。 Google Clooud の Cloud DNSでのCNAME変換 この特性を活かし、Google Cloud側の設定のみで名前解決を完結させました。 具体的には、Cloud DNSにおいて、Data Fusionが参照するS3の接続先ドメイン名を、AWSから払い出されたPrivateLink用のFQDNへCNAMEレコードとして登録したのです。 構成フロー: Data Fusionが s3.ap-northeast-1.amazonaws.com へアクセス。 Cloud DNSがそれを PrivateLinkのFQDN( vpce-xxxx... )にCNAME解決。 そのFQDNをパブリックDNS経由で解決すると、AWS VPC内のプライベートIPが返る。 専用線(Direct Connect)経由で、そのプライベートIPへ直接通信。 この設計により、AWS側にResolver Endpointという追加の有償リソースを立てることなく、また複雑なクロスクラウドのDNS転送設定も不要にしました。 まとめ 本プロジェクトを通じて、AWSとGoogle Cloudのいいとこ取りをしたハイブリッドデータ連携基盤が完成しました。 安定性 : 100以上のインターフェース、日次数百万レコードの転送において、専用線とPrivateLinkの組み合わせにより極めて低いエラー率と安定したスループットを維持。 コスト最適化 : 冗長化されたプロキシサーバやDNSフォワーダーの構築・運用工数を削減し、純粋なデータ処理に集中。 拡張性 : 今後インターフェースが増加しても、ネットワーク経路やDNS設定を変更することなく、Data Fusion上のパイプライン追加だけで対応可能な拡張性を確保。 AWSの各サービスは単体でも強力ですが、その特性を深く理解することで他クラウドとの連携において価値を発揮すると感じました。 この記事がどなたかのお役に立つと幸いです。
アバター
こんにちは、SCSK林です! モダンなシステムアーキテクチャにおいて、システム間を「疎結合」に保つことはもはや定番です。AWSにおいてその中心を担うのは、Amazon SQSやAmazon Managed Streaming for Apache Kafka (MSK)といったメッセージングサービス、あるいはAmazon S3を用いたバッファ層などかと思います。 ただ、実際のエンタープライズ領域におけるデータ連携案件、特にマルチクラウド構成やオンプレミスとの閉域網接続が絡むプロジェクトでは、単に「サービスを間に挟む」だけでは解決できない課題が多いと感じています。 「どのタイミングでデータの到達を保証すべきか」 「コストとスループットの妥協点をどこに置くか」 「リトライによって発生するデータの重複をどう制御するか」 本記事では、私が携わった、毛色の異なる2つのデータ連携プロジェクトを例に、アーキテクトが直面する「キューイング・バッファリング設計」のポイントについて解説していきたいと思います。 プロジェクト事例①:異種クラウド間連携における「Pull型」MSK設計 プロジェクトの背景と課題 最初にご紹介するのは、AWS上の基幹システムで発生する変更データを、Google Cloud上のDWH基盤(BigQuery)へリアルタイムに同期する案件です。 AWS側ではデータのハブとしてAmazon MSKを採用していました。当初の検討では、MSK Connectを利用してGoogle Cloud側のエンドポイントへデータをPush送信する構成が有力でした。しかし、精査を進めると以下の3つの大きな課題が浮上しました。 ネットワークの不確実性: AWSからGoogle Cloudへのクロスクラウド通信、かつ専用線経由という環境下で、ネットワーク瞬断時のエラーハンドリングをどこまでインフラ層に任せられるか。 コスト効率の悪化: 同期対象となるインターフェース(Topic)は20個以上存在しました。MSK Connectはコネクタ単位でのMCU(MSK Connect Unit)課金が発生するため、1日の流量が数千件程度の比較的小規模なインターフェース群に対して個別にコネクタを立てると、データ量に対して極めて割高な固定費が発生します。 責任分界点の曖昧さ: 送信側(AWS)が無理に押し込む「Push型」では、受信側(Google Cloud)の負荷状況に合わせた流量制御(バックプレッシャー)が難しく、受信失敗時の再送管理が複雑化します。 独自コンシューマーによる「Pull型」への転換 結果的には、マネージドサービスであるMSK Connectの採用を見送り、Google Cloud側のCloud RunからMSKへ「Pull型」でデータを取得しに行くカスタムコンシューマー構成を提案しました。 この設計は、「責任完了のポイント」をコンシューマー側に移譲したことにあります。 同期的なオフセット管理 : コンシューマーはMSKからメッセージを取得し、Google Cloud側のストレージ(Pub/Sub)への書き込みが完全に完了したことを確認してから、MSKに対して「Offset」をコミットします。これにより、処理の途中でコンシューマーがダウンしても、次回の起動時に未処理のデータから確実に再開できる「At-least-once(少なくとも一回)」を担保しました。 コストの最適化 : 20個以上の多くのインターフェースを単一または少数のCloud Runサービスに集約して処理することで、MSK Connectを利用した場合と比較してインフラコストを大幅に抑制しました。 重複排除の戦略的妥協 : At-least-once構成では、再送時にデータの重複が発生する可能性があります。これをインフラ層の複雑なロジックで排除しようとせず、最終的な格納先であるGoogle Cloud側(BigQuery)で、ユニークキーに基づいた「重複排除処理」を行う方針を策定しました。 技術的な「完璧さ」をインフラだけで追求するのではなく、エンドツーエンドでの整合性とコストのバランスを考慮した構成になっているかなと感じています。 ※詳細はこちらのブログも参照ください。 【AWS - Google Cloud】マルチクラウドでキューイングデータ連携 AWS MSKからGCPへのデータ連携において、MSK Connectの仕様制約に伴うコスト肥大化を回避するため、Cloud RunによるPull型アーキテクチャへと転換した事例を紹介します。コスト最適化と疎結合な設計により、大規模なマルチクラウド環境下で高効率かつ堅牢なデータパイプラインを実現した経緯を詳説します。 blog.usize-tech.com 2026.03.23   プロジェクト事例②:S3を「バッファ」と見立てた高耐久非同期アーキテクチャ プロジェクトの背景と課題 次にご紹介するのは、オンプレミス環境からAWSを経由し、データウェアハウスであるSnowflakeへデータをロードする基盤構築案件です。 この案件では、Direct Connect経由で送られてくるデータをAPI Gateway + Lambdaで受け取る構成をとりましたが、以下の制約が障壁となりました。 Lambdaのペイロード制限 : API GatewayおよびLambdaには数MBから数十MBのペイロード制限があり、将来的なデータ肥大化に対応できない懸念がありました。 Snowflakeへのロード遅延 : Snowflakeへの書き込み処理には、オーバーヘッドを含めて一定の時間が必要です。同期的な処理では、APIのタイムアウトや、オンプレミス側のクライアントを長時間待機させるリスクがありました。 性能要件の遵守 : 「データ発生から3分以内に分析可能にすること」という性能要件に対し、単一のプロセスで全てを完結させるのは可用性の観点から危険だと判断しました。 S3を「高耐久なキュー」として定義 私は、Amazon S3を単なるストレージではなく、「書き込みが極めて高速で、無限のキャパシティを持つキュー(バッファ)」として位置づける非同期アーキテクチャを採用しました。 取り込み層(受領)の軽量化: API Gatewayから起動されるLambdaの役割を「S3へのファイル保存」のみに限定しました。これにより、オンプレミス側に対しては数ミリ秒から数百ミリ秒という極めて短いレスポンスタイムで「受領完了」を返せます。 ロード層(処理)のデカップリング: S3へのファイル作成をトリガー(S3 Event Notifications)として、後続のLambdaがSnowflakeへのロードを実行します。この構成により、Snowflake側で一時的なメンテナンスや障害が発生しても、データはS3に「滞留(キューイング)」されるだけであり、取り込み層を止める必要がなくなります。 枯れた技術による信頼性: Snowflakeへのロードには、あえて最新のSnowpipeではなく「LambdaによるCOPYコマンド実行」を選択しました。これは既存の資産であるシェルスクリプトのロジックを流用しやすくするためであり、またエラー時の再実行制御をより細かくハンドリングできるようにするためでした。 結果としてのパフォーマンス この「S3バッファ」を介した非同期構成により、結果としてデータ発生からSnowflakeへの到達まで、平均して十数秒というパフォーマンスを実現しました。目標としていた「3分以内」という性能要件を大幅に上回る余裕を持った設計となりました。 ※詳細はこちらのブログも参照ください。 Amazon API Gateway + AWS Lambda + Snowflake によるニアリアルタイムデータ連携 オンプレミスからSnowflakeへのデータ連携において、API GatewayとLambdaを用いた非同期処理による、データ基盤構築の事例を解説します。S3を境界に「取り込み」と「ロード処理」を分離することで、閉域網での高いセキュリティと耐障害性を両立させた設計をご紹介します。 blog.usize-tech.com 2026.03.23   まとめ:キューイング設計における3つのポイント これら2つの案件を通じて、痛感した「キューイング設計のポイント」は以下の3点に集約されると感じました。 ① 責任分界点(Commit Point)をどこに置くか 「データを受け取った」とみなすタイミングをどこにするかは、システムの信頼性を左右する最も重要な決断です。事例①では、宛先システムが処理を終えたタイミング。事例②では、AWS側の高耐久ストレージ(S3)に書き込んだタイミング。 これを明確に定義することで、障害発生時に「どこからリトライすべきか」が自ずと決まります。 ② マネージドサービスとカスタム実装の天秤 マネージドサービスの利点は十分に理解していますが、事例①のように「インターフェース数が多いが、個々の流量が少ない」といった特殊な条件下では、マネージドサービスのコスト構造がボトルネックになることがあります。 「何でもマネージド」ではなく、ランニングコストと運用負荷(メンテナンス性)を天秤にかけ、時にはカスタムコンシューマー(手組のプログラム)を選択する勇気も必要です。 ③ 冪等性の確保 キューイングを導入する以上、リトライによる「重複」は避けられません。インフラ側で「Exact-once(正確に一回)」を実現しようとすると、アーキテクチャは極めて複雑になり、パフォーマンスも低下します。 「重複は発生するもの」と割り切り、アプリケーション層やデータベースのレイヤーで重複排除を行う設計にすることで、システム全体の堅牢性とシンプルさを両立させることができます。 おわりに AWSはじめ各クラウドサービスには、データ連携を支える強力なサービス群が揃っています。しかし、それらを組み合わせるだけで優れたシステムが出来上がるわけではないと改めて感じました。 今回の事例では、「あえてマネージドサービスを使わない」「あえて非同期にする」といった、ある種のデザインチョイス(選択と集中)です。ビジネス要件、コスト制約、そしてネットワークの物理的な限界を直視し、どこで妥協し、どこで妥協するか。その判断こそが難しいポイントだなと思いました。 今回の構成、事例がどなたかのお役に立つと幸いです。
アバター