0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS LambdaとECSでの相対パス処理の違いによる問題解決

Posted at

はじめに

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 ライブラリ内部で、ファイルの存在確認が行われ、見つからないとエラーが発生します:

/usr/local/bundle/gems/googleauth-1.13.1/lib/googleauth/credentials.rb
# 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

学び

  1. 設定ファイルでは絶対パスを優先する:特に複数の環境で実行される可能性があるアプリケーションでは、絶対パスを使用することでパス解決の問題を避けられます。

  2. Railsアプリケーションを Lambda で実行する際の考慮点:Lambda環境はウェブサーバーとは異なる動作をするため、パス解決、初期化方法、環境変数の取り扱いなどに注意が必要です。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?