はじめに
AWS ECS/EC2でデプロイして問題ないコードはAWS Lambdaで問題が発生と、その解決方法について共有します。
プロジェクトの背景
RailsアプリはAWS ECS上で安定して動作していましたが、一部機能をサーバーレス化するためにAWS Lambdaを導入しました。
Lambdaは既存のRailsプロジェクト内に実装し、コードの共有や管理を簡単にしました。
プロジェクト構造
rails_project/
├── app/
├── config/
├── db/
└── lambda/
├── FunctionA/
├── app.rb # Lambda入口ファイル
└── Dockerfile # Lambdaビルド用Dockerfile
├── FunctionB/
└── ...
問題の概要
同じコードベースにもかかわらず、ECSでは正常に動作していた機能が、Lambdaではうまく動かない問題に直面しました。
エラーメッセージ:
"errorMessage": "The keyfile 'config/gcp/credentials.json' is not a valid file.",
"errorType": "Init<RuntimeError>",
"stackTrace": [
"/usr/local/bundle/gems/googleauth-1.13.1/lib/googleauth/credentials.rb:559:in `verify_keyfile_exists!'",
"/usr/local/bundle/gems/googleauth-1.13.1/lib/googleauth/credentials.rb:614:in `update_from_filepath'",
"/usr/local/bundle/gems/googleauth-1.13.1/lib/googleauth/credentials.rb:402:in `initialize'",
"/usr/local/bundle/gems/google-cloud-tasks-v2-1.2.0/lib/google/cloud/tasks/v2/cloud_tasks/client.rb:205:in `new'",
"/usr/local/bundle/gems/google-cloud-tasks-v2-1.2.0/lib/google/cloud/tasks/v2/cloud_tasks/client.rb:205:in `initialize'",
"/usr/local/bundle/gems/google-cloud-tasks-3.0.0/lib/google/cloud/tasks.rb:85:in `new'",
"/usr/local/bundle/gems/google-cloud-tasks-3.0.0/lib/google/cloud/tasks.rb:85:in `cloud_tasks'",
"/app/config/initializers/google_cloud_tasks.rb:8:in `<top (required)>'",
]
原因
この問題の根本的な原因は、ECS環境とLambda環境でのワーキングディレクトリの違いにあります。
設定ファイルの状態
設定ファイル (config/settings/staging.yml
) では、Google Cloud の認証ファイルのパスを相対パスで指定していました:
gcp:
workload_identity:
credentials: 'config/gcp/credentials.json'
ECSでは問題ない理由
ECS環境ではアプリケーションのルートが /app にあり、Rails起動時に RAILS_ROOT も /app に設定されるため、config/gcp/credentials.json のような相対パスも /app/config/gcp/credentials.json として正しく解決されます。
Lambdaで問題が発生する詳細な仕組み
Lambda環境では、Dockerfileで以下のように設定していました:
# /app/lambda/RailsRunnerFunction/Dockerfile.lambda
FROM amazonlinux:2
...
# アプリケーションファイルをコンテナにコピー
COPY --chown=user ../../ /app
# 作業ディレクトリをLambdaハンドラーのディレクトリに設定
WORKDIR /app/lambda/MyFunction
ENTRYPOINT ["aws_lambda_ric"]
CMD ["app.LambdaFunction::Handler.process"]
問題の詳細な流れは以下の通りです:
1. Lambda関数の起動プロセス
まず、Lambdaが起動すると指定されたハンドラーが実行されます:
CMD ["app.LambdaFunction::Handler.process"]
2. app.rbでのRails環境の読み込み
Lambdaハンドラーのあるファイル (app.rb
) では、以下のようにRails環境を読み込んでいます:
# /app/lambda/MyFunction/app.rb
# Railsの環境を利用するため
require_relative '../../config/environment'
module LambdaFunction
class Handler
def self.process(event:, context:)
# Lambda処理
end
end
end
注目すべきは require_relative '../../config/environment'
という部分です。これは相対パスで、/app/config/environment.rb
を読み込むことになります。
3. Rails初期化時の問題
そして、Rails環境が読み込まれると、config/initializers
ディレクトリの全ファイルが実行されます:
# /app/config/environment.rb
Rails.application.initialize! # ここで全初期化処理が走る
しかし、重要なポイントとして、この時点でもカレントディレクトリは
/app/lambda/MyFunction
のままなのです。
4. 初期化コードでの認証ファイル読み込み
Google Cloud Tasks の初期化コードでは:
# /app/config/initializers/google_cloud_tasks.rb
credentials_path = Settings[:gcp][:workload_identity][:credentials]
この credentials_path
は相対パスなので、カレントディレクトリ (/app/lambda/MyFunction
) から解決されてしまいます:
/app/lambda/MyFunction/config/gcp/credentials.json
しかし、実際のファイルは異なる場所にあります:
/app/config/gcp/credentials.json
5. エラーの発生
googleauth
ライブラリ内部で、ファイルの存在確認が行われ、見つからないとエラーが発生します:
# googleauth gem内部コード
def verify_keyfile_exists! keyfile
exists = ::File.file? keyfile
raise "The keyfile '#{keyfile}' is not a valid file." unless exists
end
このため、「The keyfile 'config/gcp/credentials.json' is not a valid file.」というエラーが発生するのです。
このエラーメッセージから「ファイルが無効」と思いがちですが、実際にはファイルが見つからないことを意味しています。
解決策
この問題には主に2つの解決策があります:
解決策1: 絶対パスを使用する
設定ファイルで絶対パスを使用することで、どの環境でも一貫した動作を保証できます:
gcp:
workload_identity:
credentials: '/app/config/gcp/credentials.json'
解決策2: パスを動的に解決する
Rails環境内でパスを適切に解決するコードを追加します:
# config/initializers/google_cloud_api.rb
if Rails.env.staging? || Rails.env.production?
begin
# 相対パスを絶対パスに変換
credentials_path = Settings[:gcp][:workload_identity][:credentials]
credentials_path = File.join(Rails.root, credentials_path) if credentials_path && !credentials_path.start_with?('/')
...
rescue => e
Rails.logger.error "Failed to initialize API client: #{e.message}"
API_CLIENT = nil
end
end
学び
-
設定ファイルでは絶対パスを優先する:特に複数の環境で実行される可能性があるアプリケーションでは、絶対パスを使用することでパス解決の問題を避けられます。
-
Railsアプリケーションを Lambda で実行する際の考慮点:Lambda環境はウェブサーバーとは異なる動作をするため、パス解決、初期化方法、環境変数の取り扱いなどに注意が必要です。