Lambdaで動くコンテナイメージ(Ruby)を作成し、RDS(PostgreSQL)と接続してみた

はじめに・動機

先日、RDS(PostgreSQL)に接続するLambda関数をRubyで実装しようとしたとき、pg gemの動作がうまく動きませんでした。このため、今回はLambda関数をコンテナイメージで動かすことをやってみます。

コンテナイメージを作る

AWSのドキュメントにLambda関数で動くコンテナイメージの作り方が記載されていたのでそれに従います。

docs.aws.amazon.com

ベースイメージとして、public.ecr.aws/lambda/ruby:3.2を使うのがポイントかと思います。pg gemを使いたいので、関連ライブラリをインストールする必要があります。実際に実装したDockerfileは以下のようなものになりました。

FROM public.ecr.aws/lambda/ruby:3.2

RUN yum install -y amazon-linux-extras && \
    amazon-linux-extras enable postgresql14 && \
    yum group install "Development Tools" -y

RUN yum install -y postgresql-devel

# Copy Gemfile and Gemfile.lock
COPY Gemfile Gemfile.lock ${LAMBDA_TASK_ROOT}/

# Install the specified gems
RUN bundle config set --local path 'vendor/bundle' && \
    bundle install

# Copy function code
COPY lambda_function.rb ${LAMBDA_TASK_ROOT}/

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "lambda_function.LambdaFunction::Handler.process" ]

必然的にコンテナイメージも大きくなります。マルチステージビルド化で多少小さくなるかもしれませんが、まずは動作確認のためにこのまま進めます。

コンテナイメージを作っておくと、ローカルで動作確認も取れます。docker runで動かして、別のターミナルからcurl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'を叩けばOK。今回は、以下のようなdocker-compose.ymlを作成してdocker compose upで起動して、

version: '3'
services:
  app:
    build: .
    image: my_lambda_app:0.0.1
    ports:
      - "9000:8080"
    environment:
      PG_HOSTNAME: db
      PG_USERNAME: "postgres"
      PG_PASSWORD: "postgres"

  db:
    image: postgres:15
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "postgres"

先ほどのcurl "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'を叩けば、動作確認も簡単にできました。

CodeBuildでコンテナイメージを作成し、ECRにpushする

次に、Lambda上で動かすことを考えます。CodeBuildでコンテナイメージを作成し、ECRにpushすることを実装します。

AWSのドキュメントにサンプルがあるのでそれをお手本に。

docs.aws.amazon.com

CodeBuildの作成時に、「特権付与」にチェックを入れるのを忘れずに。

また、ポリシーの追加も必要です。こんな感じで。

以下のbuildspec.ymlを作成してビルドを実行します。

version: 0.2

phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNTID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  build:
    commands:
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNTID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - docker push $AWS_ACCOUNTID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG

成功すると、作成していたECRにイメージがpushされています。

コンテナイメージをLambdaで動かす

あとはLambdaで動かします。Lambda作成時に「コンテナイメージ」を選択して、画面の指示通りに指定してあげればOK。

今回は、SELECT * FROM pg_stat_activityの結果を出力させるものを動かしました。RDSの接続設定をすれば、無事動くことが確認できました。

考察

今回の検証で、一番時間がかかったのはDockerfileの作成でした。ベースイメージのpublic.ecr.aws/lambda/ruby:3.2Amazon Linux 2ですのでgemのビルドに必要な前提ライブラリであるDevelopment Toolspostgresql-develを特定するのが一番時間がかかりました。特定さえできれば、あとはコピペで良いかなと考えています。

今回はRDS for PostgreSQLに接続することが目的でしたので、postgresql-develが前提ライブラリでしたが、RDS for MySQLなどはそれぞれ別の前提ライブラリが必要となることは注意点となります。

コンテナイメージを作ることでローカルでの動作確認が取れるというのは思わぬメリットでした。AWS上での開発も高速とはいえ、ローカルで動作させる速度には敵わないのでこれは大きなメリットかなと思います。