📚

serverless frameworkでコンテナLambdaを使ってみる

2023/05/02に公開

記事の内容

serverless frameworkでコンテナLambdaをAWSへデプロイする手順をまとめてみました。
Lambdaは特別なことしなければ、zipでアップロードするのが基本です。

ですが、osに依存するパッケージが必要な処理の場合、Lambdaのデプロイは大変になります。
Lambdaレイヤーを使うこともできますが、1レイヤー250MBの制限があります。
大きなパッケージだと250MBは簡単に超えてしまいます。

そこで、コンテナLambdaにすることで、パッケージのos依存と容量の問題をいい感じに解決できます。
serverless frameworkがコンテナLambdaのデプロイをサポートしているので試してみました。

関係する技術・ツール

  • serverless framework 3系
  • typescript
  • docker

作業の流れ

  1. serverless.ts作成
  2. dockerfile作成
  3. デプロイ

ディレクトリ構成

想定するプロジェクトディレクトリは下記のような構成です。

├ docker
|   └ nodejs
|       └ prod
|	   └ dockerfile
├ src
|  └ functions
|        └ s3trigger
|             └ index.ts
├ package.json
├ tsconfig.json
└ serverless.ts

1. serverless.ts作成

こちらを参考にserverless.tsを作成します。tsなのでtypescriptで作成します。

serverless.ts
import type { AWS } from "@serverless/typescript";

const serverlessConfiguration: AWS = {
  service: "function-in-docker",
  useDotenv: true,
  frameworkVersion: "3",
  provider: {
    name: "aws",
    runtime: "nodejs16.x",
    region: "ap-northeast-1",
    stage: "${opt:stage, 'dev'}",
    ecr: {
      images: {
        s3triger: {
          file: "./docker/nodejs/prod/dockerfile",
          path: "./",
        },
      },
    },
    environment: {
      AWS_NODEJS_CONNECTION_REUSE_ENABLED: "1",
      NODE_OPTIONS: "--enable-source-maps --stack-trace-limit=1000",
      BUCKET_NAME: "BUCKET_NAME",
    },
  },
  functions: {
    create_thumbnail: {
      image: {
        name: "s3triger",
      },
      architecture: "x86_64",
    },
  },
};

module.exports = serverlessConfiguration;

docker buildに関する設定はprovider.ecr内で定義します。
上記の場合、

  • file: 相対パスでdocker/nodejs/prod/dockerfileをビルド
  • path: 相対パスで./をビルドコンテクストに指定します。

結果、docker/nodejs/prod/dockerfileを元に、プロジェクトのルート(serverless.tsがあるディレクトリ)をビルド場所にしています。

2. dockerfile作成

docker/nodejs/prodにdockerfileを作成します。

dockerfile
FROM --platform=linux/x86_64 public.ecr.aws/lambda/nodejs:16 as builder
WORKDIR /usr/app
COPY package.json tsconfig.json src/functions/s3trigger/index.ts ./
RUN npm install -g yarn && \
    yarn install


FROM --platform=linux/x86_64 public.ecr.aws/lambda/nodejs:16

RUN yum update -y && \
    yum install -y \
      amazon-linux-extras \
      ghostscript-9.25-5.amzn2.0.2 \
      python \
      python3 \
      build-essential \
      libjpeg-dev \
      libpng-dev \
      libcurl4-openssl-dev \
      mupdf-tools \
      libfreetype6-dev \
      qpdf && \
    amazon-linux-extras enable libreoffice && \
    yum install -y \
      libreoffice-core \
      libreoffice-calc \
      libreoffice-opensymbol-fonts \
      libreoffice-ure \
      libreoffice-writer \
      libreoffice-pyuno \
      libreoffice-impress \
      libreoffice-langpack-ja \
      ipa-gothic-fonts \
      ipa-mincho-fonts \
      ipa-pgothic-fonts \
      ipa-pmincho-fonts

WORKDIR ${LAMBDA_TASK_ROOT}

COPY --from=builder /usr/app/dist ./
COPY --from=builder /usr/app/node_modules ./node_modules/
CMD ["index.handler"]

マルチステージビルドを活用しています。簡単に解説すると、
1つ目のFROMではtypescriptで作成したLambda関数をコンパイルしています。
この環境はコンパイルが目的で、実際にデプロイするコンテナではありません。
ですので、最終的には破棄される環境です。

2つ目のFROMから実際にデプロイされるコンテナです。
amazonlinux上にyumで様々なパッケージをインストールしています。これらが今回osに依存するパッケージ群です。
またCOPY --from=builder /usr/app/dist .のようにファイルをコピーしています。
この--from=builderは1つ目の環境を指しています。つまり、1つ目の環境でコンパイルしたソースをコピーしていることになります。
このように、マルチステージビルドにより、ビルドした環境から必要最低限のソースを厳選してコンテナイメージを作成できます。

3. デプロイ

ここまでできたら、デプロイしてみます。事前にdockerを起動しておきます。

プロジェクトのルートで

sls deploy

を実行します。

すると、Lambdaのパッケージングとともに、docker buildも実行されます。
これによりビルドしたイメージがECRにpushされます。
リポジトリ名はserverless-<service>-<stage>のようになります。

また、こちらに記載ありますが、過去のイメージは自動で削除されないようです。
関数をzipでデプロイする際はs3に配置されることになりますが、コンテナLambaにしたことによってLambda関数はdockerイメージに含まれることになります。したがってdockerイメージを残しておかないとLambdaのバージョン管理ができなくなってしまうわけですね。

デプロイ方法は変更できない。

ちなみに、Lambdaのデプロイ方法は変更することができません。したがってzip↔︎dockerの双方向どちらからも変更できません。さらに、cfnで作成した場合も同様です。たとえcfnで作成した該当のLambda関数を手動で削除してもスタック履歴的に変更できないようです。
変更しようとすると下記のようなエラーとなりました。

Resource handler returned message: "Invalid request provided: Updating PackageType is not supported"

https://github.com/aws/aws-sam-cli/issues/2629

この場合cfnスタックを削除するか、関数名を変えるなどの対応が必要になるので注意する必要があります。

以上になります。
これで無事、serverless frameworkでコンテナLambdaをデプロイすることができました。

NCDCエンジニアブログ

Discussion