はじめに (対象読者・この記事でわかること)

この記事は、Node.jsとAWS SDKを使ってアプリケーションを開発し、それをDocker環境で実行しようとしているエンジニア、特に「ローカルでは動くのにDockerコンテナ内ではAWSサービスへのアクセスがうまくいかない」という問題に直面している方を対象としています。

この記事を読むことで、Docker環境におけるAWS SDKの認証情報取得メカニズムの課題と、Cognitoユーザー情報取得に失敗する具体的な原因、そしてその解決策を理解し、同様の認証関連エラーを効率的にデバッグできるようになります。筆者自身もこの問題に直面し、解決までに時間を要した経験から、その知見を共有するためにこの記事を執筆しました。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - Node.jsの基本的な開発経験とAWS SDK for JavaScriptの利用経験 - Dockerの基本的なコマンド操作(docker build, docker runなど)とDockerfileの記述 - AWS Cognitoの基本的な概念と、ユーザー管理APIの利用経験

Docker環境におけるAWS SDKと認証情報の課題

Node.jsでAWS SDKを利用する際、SDKはAWSサービスへのアクセスに必要な認証情報(アクセスキーID、シークレットアクセスキー、セッションID)を特定の順序で探索します。この認証情報の探索順序は、開発環境とデプロイ環境で異なる挙動を示すことがあり、特にDockerコンテナ内でアプリケーションを実行する際に予期せぬ問題を引き起こすことがあります。

AWS SDKが認証情報を探す主な順序は以下の通りです。 1. 環境変数: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN 2. 共有認証情報ファイル: ~/.aws/credentials 3. SDK設定ファイル: ~/.aws/config 4. IAMロール: EC2インスタンスプロファイル、ECSタスクロール、EKS Pod IAMロールなど

ローカル開発環境では、通常aws configureコマンドで設定された~/.aws/credentialsファイルが利用されたり、あるいは開発者のPCに紐づいたIAMロールが暗黙的に利用されたりします。しかし、DockerコンテナはホストOSから独立した環境であるため、これらのローカル設定が自動的に引き継がれるわけではありません。コンテナ内部には独自のファイルシステムと環境変数が存在するため、明示的に認証情報を渡すか、コンテナが実行される環境に紐づくIAMロールを設定する必要があります。

特にCognitoのようなユーザー管理サービスへのAPI呼び出しは、適切な権限を持つ認証情報がなければ、ユーザー情報の取得や変更といった操作ができません。このため、Docker環境での認証情報の設定漏れや不備が、Cognito APIの失敗に直結しやすいのです。

Cognitoユーザー情報取得失敗問題の具体的な検証と解決策

ここからは、実際にNode.jsアプリケーションがDockerコンテナ内でCognitoユーザー情報を取得できない問題の再現方法から、その原因を特定し、解決に至るまでの具体的な手順を解説します。

ステップ1:問題の再現と初期調査

まず、Cognitoのユーザー情報を取得するシンプルなNode.jsコードを用意します。

get_user.js:

Javascript
const { CognitoIdentityServiceProviderClient, AdminGetUserCommand } = require("@aws-sdk/client-cognito-identity-service-provider"); const USER_POOL_ID = process.env.USER_POOL_ID; const USERNAME = process.env.USERNAME; const REGION = process.env.AWS_REGION || "ap-northeast-1"; // 明示的にリージョン指定 if (!USER_POOL_ID || !USERNAME) { console.error("Error: USER_POOL_ID and USERNAME environment variables must be set."); process.exit(1); } const client = new CognitoIdentityServiceProviderClient({ region: REGION }); async function getUserInfo() { const params = { UserPoolId: USER_POOL_ID, Username: USERNAME }; try { const command = new AdminGetUserCommand(params); const response = await client.send(command); console.log("User Info:", JSON.stringify(response, null, 2)); } catch (error) { console.error("Failed to get user info:", error); } } getUserInfo();

次に、このアプリケーションをDockerコンテナで実行するためのDockerfiledocker-compose.ymlを作成します。

Dockerfile:

Dockerfile
FROM node:lts-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD ["node", "get_user.js"]

docker-compose.yml:

Yaml
version: '3.8' services: app: build: . environment: USER_POOL_ID: your_user_pool_id # CognitoユーザープールIDに置き換えてください USERNAME: target_username # 取得したいユーザー名に置き換えてください AWS_REGION: ap-northeast-1 # お使いのリージョンに置き換えてください # ローカルのAWS認証情報を利用する場合 (開発・テスト用) # volumes: # - ~/.aws:/root/.aws:ro # コンテナ内のユーザーがrootの場合 # - ~/.aws:/home/node/.aws:ro # コンテナ内のユーザーがnodeの場合 (node:lts-alpineのデフォルト) # 環境変数で認証情報を直接渡す場合 (非推奨だがテスト用) # environment: # AWS_ACCESS_KEY_ID: your_access_key_id # AWS_SECRET_ACCESS_KEY: your_secret_access_key

ローカルでの実行 (成功例): まず、Dockerを使わずにローカル環境で実行し、期待通りに動作することを確認します。 ~/.aws/credentialsが正しく設定されており、Cognitoへのアクセス権限を持つユーザーが設定されていることを前提とします。

Bash
# 環境変数をセット export USER_POOL_ID="your_user_pool_id" export USERNAME="target_username" export AWS_REGION="ap-northeast-1" node get_user.js # 期待される出力: "User Info: {...}"

Dockerコンテナ内での実行 (失敗例): 次に、Dockerコンテナ内で実行してみます。docker-compose.ymlvolumesenvironmentの認証情報に関する行をコメントアウトした状態で実行します。

Bash
docker-compose build docker-compose up

多くの場合、以下のようなエラーメッセージが出力されるはずです。

app_1  | Failed to get user info: CredentialsProviderError: Could not load credentials from any providers
app_1  |     at /app/node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js:XXX:YYY
# あるいは
app_1  | Failed to get user info: AccessDeniedException: User: arn:aws:iam::xxxxxxxxxxxx:user/is_not_authorized_to_perform:cognito-idp:AdminGetUser_on_resource:arn:aws:cognito-idp:ap-northeast-1:xxxxxxxxxxxx:userpool/xxxxxxxxxxxx
# あるいは
app_1  | Failed to get user info: NetworkingError: Network Failure - A network connection problem occurred.

特に多いのがCredentialsProviderErrorです。これはSDKが認証情報をどこからも見つけられなかったことを意味します。

ステップ2:原因の特定 – 認証情報の欠落

上記のエラーは、Dockerコンテナ内でAWS SDKがCognitoサービスにアクセスするための認証情報を取得できていないことが原因です。 Dockerコンテナは、ホストマシンとは独立した実行環境を提供するため、ローカルの~/.aws/credentialsファイルや環境変数、IAMロールの情報はデフォルトではコンテナ内に引き継がれません。

コンテナ内に入って、現在の環境を確認してみましょう。 新しいターミナルを開き、以下のコマンドを実行します。

Bash
docker-compose exec app sh # Alpine Linuxベースなのでsh # もしくは docker-compose exec app bash (もしbashがインストールされていれば)

コンテナ内で以下のコマンドを実行してみます。

Bash
ls -la ~/.aws # 出力例: ls: /root/.aws: No such file or directory (credentialsファイルがない場合) env | grep AWS # 出力例: (何も表示されない、あるいはUSER_POOL_ID, USERNAME, AWS_REGIONのみ表示される)

これらのコマンドから、コンテナ内にAWS認証情報に関するファイルが存在しないこと、または必要な環境変数がセットされていないことが確認できます。SDKはこれらを探索して認証情報を取得するため、見つからなければ上記のCredentialsProviderErrorを返します。

ハマった点やエラー解決

筆者がこの問題に直面した際、いくつかの「ハマった点」がありました。

  1. 「ローカルで動くんだからDockerでも動くはず」という思い込み: ローカル開発環境の柔軟性(自動的に認証情報を取得する仕組み)に慣れていると、Dockerコンテナの厳密な環境分離を意識しにくく、原因究明に時間がかかりました。
  2. IAMロールの存在を過信: EC2インスタンスやECSタスクにIAMロールを設定している場合、開発環境のDockerコンテナでもそれが自動的に適用されると誤解しがちです。しかし、ローカルでdocker rundocker-compose upを実行するだけでは、ホストのIAMロールはコンテナには伝達されません。これはEC2インスタンスのメタデータサービスにアクセスできないためです。
  3. 環境変数をDockerfileに直接記述: セキュリティ上のリスクを理解せず、Dockerfile内でENV AWS_ACCESS_KEY_ID=xxxのように認証情報を直接書き込んでしまうケース。これはコードのリポジトリに機密情報が残るため、絶対に避けるべきです。
  4. リージョン指定の不足: aws-sdkのクライアント初期化時にregionを明示的に指定し忘れると、SDKがデフォルトのリージョンを特定できず、API呼び出しが失敗する場合があります。これは認証情報とは直接関係ありませんが、複合的なエラーの原因となることがあります。

解決策

Dockerコンテナ内でAWS SDKが認証情報を正しく取得できるようにするための解決策はいくつかあります。開発環境と本番環境で適切な方法を選択することが重要です。

解決策1:環境変数で認証情報を渡す(開発・テスト用)

最もシンプルで手軽な方法です。docker-compose.ymlenvironmentセクションや、docker runコマンドの-eオプションを使って、必要なAWS認証情報を環境変数としてコンテナに渡します。

docker-compose.ymlを修正:

Yaml
version: '3.8' services: app: build: . environment: USER_POOL_ID: your_user_pool_id USERNAME: target_username AWS_REGION: ap-northeast-1 # ★ここを追加またはコメント解除★ AWS_ACCESS_KEY_ID: your_access_key_id_for_dev AWS_SECRET_ACCESS_KEY: your_secret_access_key_for_dev # AWS_SESSION_TOKEN: your_session_token_if_temporary_credentials # 一時クレデンシャルの場合

注意: この方法は、認証情報がdocker-compose.yml.envファイルに平文で含まれるため、本番環境での利用は絶対に避けるべきです。開発・テスト目的でのみ使用し、Git管理から除外する(.gitignoreに追加するなど)ことを強く推奨します。

解決策2:共有認証情報ファイルをボリュームマウントする(開発・テスト用)

ローカルPCに設定済みの~/.aws/credentialsファイルをDockerコンテナ内にマウントする方法です。これにより、コンテナはホストと同じ認証情報ファイルを参照できるようになります。

docker-compose.ymlを修正:

Yaml
version: '3.8' services: app: build: . environment: USER_POOL_ID: your_user_pool_id USERNAME: target_username AWS_REGION: ap-northeast-1 # ★ここをコメント解除し、適切なパスに修正★ volumes: # コンテナ内のユーザーがrootの場合 - ~/.aws:/root/.aws:ro # コンテナ内のユーザーがnode:lts-alpineのデフォルトユーザー(node)の場合 # - ~/.aws:/home/node/.aws:ro # どちらか適切な方をコメント解除してください

roはリードオンリー(読み取り専用)を意味し、セキュリティを高めます。 この方法も、開発マシン上の認証情報がコンテナに共有されるため、注意が必要です。特にCI/CDパイプラインなどでは推奨されません。

解決策3:AWS IAM Roleを効果的に利用する(本番環境推奨)

本番環境、特にEC2、ECS、EKSなどのAWSサービス上でDockerコンテナを実行する場合、最もセキュアで推奨される方法はIAMロールを利用することです。

  • EC2インスタンスプロファイル: DockerコンテナがEC2インスタンス上で動作する場合、インスタンスに適切なIAMロールをアタッチすることで、SDKが自動的にそのロールの認証情報を取得できるようになります。
  • ECSタスクロール: Amazon ECSでコンテナを実行する場合、各タスクにIAMロールを割り当てることができます。これにより、タスク内のコンテナがそのロールの権限でAWSサービスにアクセスできるようになります。
  • EKS Pod IAM Roles for Service Accounts: Amazon EKSでは、KubernetesのService AccountにIAMロールを紐付けることで、特定のPodだけがそのロールの権限でAWSサービスにアクセスできるようになります。

これらの方法は、認証情報をコードや環境変数に直接記述する必要がなく、IAMの最小権限の原則に基づいた安全な認証を実現します。開発環境と本番環境で異なる認証戦略を持つことで、それぞれの環境の特性に合わせた運用が可能です。

最終確認

上記の解決策のいずれかを適用後、再度docker-compose upを実行し、Cognitoのユーザー情報が正しく取得できることを確認してください。

Bash
docker-compose up # 期待される出力: "User Info: {...}"

これで問題が解決されたはずです。

まとめ

本記事では、Docker環境でNode.jsのAWS SDKがCognitoユーザー情報を取得できないという具体的な問題について、その原因と解決策を詳細に解説しました。

  • 要点1: Dockerコンテナはホスト環境とは独立しており、AWS SDKはデフォルトではホストの認証情報を引き継がないため、認証情報が見つからないエラー(CredentialsProviderErrorなど)が発生します。
  • 要点2: 開発・テスト環境では、環境変数(AWS_ACCESS_KEY_IDなど)や、~/.aws/credentialsファイルをボリュームマウントすることで、認証情報をコンテナに渡すことができます。
  • 要点3: 本番環境では、IAMロール(EC2インスタンスプロファイル、ECSタスクロール、EKS Pod IAM Roleなど)を適切に利用することが、セキュリティと運用の観点から最も推奨される解決策です。

この記事を通して、Node.jsアプリケーションをDockerコンテナで運用する際のAWS認証情報の取り扱いに関する理解を深め、類似の認証関連エラーに遭遇した際のデバッグ能力を向上させることができたかと思います。

今後は、AWS CDKやTerraformといったInfrastructure as Code(IaC)ツールを用いて、AWSリソースとIAMロールをコードとして管理し、CI/CDパイプラインに組み込むことで、より堅牢で効率的な開発・デプロイプロセスを構築する方法についても記事にする予定です。

参考資料