echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【AWS CDK】LambdaをDockerイメージでデプロイする方法(前編)

今回のお題

タイトルの通りですが、AWSにおいてLambda関数をDockerイメージとしてデプロイする方法です。

なお今回のお題は前編・後編に分かれており、今回は前編になります。

  • 前編:AWS CDKの定義(今回はここ)
  • 後編:Dockerfileの定義&ハマりどころ(これは次回)

どんな時にDockerイメージでデプロイするの?

自分もこの辺はあまり詳しくないのですが、下記のようなケースでしょうか?(正直自信がない)

Dockerに詳しい・Dockerの知見が豊富
そりゃそうか...というか、Dockerに詳しいなら、得意な方法で作成した方がやりやすい&早いからというのはあるかも

フロントエンドなどをDockerで作成する必要がある
ECSを使うなど、フロントエンドなどをDockerで作成する必要がある場合、それならばLambdaも合わせてDockerで...とした方が、環境として統一しやすいのかも

インフラ側とアプリ側の管理の分離
Dockerを使わない場合、基本的にLambdaのソースはインフラ側ソース(AWS CDKなど)と一緒に管理する必要がありますが、諸々の理由*1で「インフラ側とアプリ側は分けたい」というのはよく出てくる話です。

Dockerならば、アプリ側はECRにイメージをpushするだけであり、デプロイそのものはインフラ側でAWS CodePipelineなどで定義できるので、「インフラ側とアプリ側の管理の分離」の解決手段の1つになると思います。(他にも方法はあると思いますが)

今回使用するコード

今回使用するコード(Dockerfile, Lambda関数)は、下記AWS公式ページを参考にしたものを使用します。(Dockerfileについては後編で詳しく触れます)
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/nodejs-image.html#nodejs-image-instructions

なおDockerfileでは上位フォルダは参照できないので、注意しましょう。(自分もこれを知らなくて少しハマった)

# Dockerfile
FROM public.ecr.aws/lambda/nodejs:18

# /var/task/は、Lambda環境変数LAMBDA_TASK_ROOTの値
WORKDIR /var/task/
# Copy function code
COPY index.js .
  
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "index.handler" ]
// Lambda関数。なおデプロイ時は事前にtscコマンドなどで
// jsにトランスパイルしてください。
import { Context, APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
  
// ハンドラ関数
export const handler = async(event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
    const res = createResponse(200, `this is ${context.functionName} message`);
    return res;
};
  
// レスポンスオブジェクト(APIGatewayProxyResult)を作成する関数
const createResponse = (statusCode: number, message: string = ""): APIGatewayProxyResult => {
    return {
      statusCode: statusCode,
      headers: {
        "content-type": "application/json"
      },
      body: JSON.stringify({
        status: statusCode === 200 ? 'success' : 'fail',
        message,
      }),
    } as APIGatewayProxyResult;
  }   

AWS CDKの定義

で、本題の「AWS CDKでLambdaをDockerイメージでデプロイする方法」ですが、非常にシンプルで、下記DockerImageFunctionクラスの定義をスタックに追加するだけです。

参考:https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda.DockerImageFunction.html

const lambdaDockerFunction = new lambda.DockerImageFunction(this, 'SampleLambdaDockerFunction', {
  // プロパティは他にもたくさんあるが、必須項目はcodeのみ
  code: lambda.DockerImageCode.fromImageAsset(path.resolve(__dirname, '../lambda'),

  // functionName(=関数名)も任意項目。(分かりやすくするために追加しているだけ)
  functionName: 'SampleLambdaDockerFunction',
});
  
// 必須ではないが、動作確認用に関数URLを設定する
// authTypeはセキュリティに関する部分なので、ご注意を  
lambdaDockerFunction.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.NONE,
});

codeにはDockerImageCodeインスタンスを指定しますが、実質以下のstatic関数のいずれかを指定する形になります。

  • DockerImageCode.fromImageAsset(directory, props?):「Dockerfile」ファイルがあるフォルダのパスを指定する
  • DockerImageCode.fromEcr(repository, props?):Lambdaイメージが格納されているECRリポジトリを指定する

なお、どちらもpropsの説明は省略するので、気になる人は下記公式サイトを見てください

fromImageAsset()を使う方法

fromImageAssetを使う場合、引数directoryに「Dockerfile」ファイルがあるフォルダのパスを指定するだけでOKです。(先程のソースを参照)

この状態でcdk deploy すると、Lambda関数の作成だけでなく、イメージビルド、タグ付け、イメージpushなどが全て行われます。

なおpushされたイメージは、cdk bootstrap 時に自動作成された cdk-hnb659fds-container-assets-<アカウント番号>-<リージョン名> というECRリポジトリに格納されています。(下の画像の通り、pushするとdocker buildした際のハッシュがイメージタグとして付けられるようです)

またLambda関数もちゃんとデプロイできています。(関数名&関数URLは別で追加しています。)

関数URLをクリックすると、ちゃんと正常動作することが確認できます。

fromEcr()を使う方法

fromEcrを使う場合、引数repositoryにEcrリポジトリ(正確にはIRepositoryインターフェース)を指定すればOKです。(下記ソースを参照)
ソースコードなどのパスを指定する(=リポジトリに含める)必要がないので、インフラとアプリの分離などを行いたい際に便利です。

// 注意:このソースをそのままcdk deployすると、Lambda関数の作成でエラーになります。(理由は後述)
// ECRリポジトリの定義
const ecrRepo = new ecr.Repository(this, 'SampleLambdaDockerEcrRepository', {
  repositoryName: 'resigtory-for-lambda-docker-sample',
});
    
// fromEcr()以外はfromImageAssetと同じなので、詳細は省略
const lambdaDockerEcrFunction = new lambda.DockerImageFunction(this, 'SampleLambdaDockerEcrFunction', {
  code: lambda.DockerImageCode.fromEcr(ecrRepo),
  functionName: 'SampleLambdaDockerEcrFunction',
});
    
lambdaDockerEcrFunction.addFunctionUrl({
  authType: lambda.FunctionUrlAuthType.NONE,
});

ただし注意点があり「該当のリポジトリにLambdaのイメージがpush指定したされていないと、cdk deploy時にLambda関数の作成がエラーになる」という点があります。

そのため、実際に適用する際はそこら辺の整合性をうまく取る必要があります。(この辺も後編で記載しようと思います) *2

Dockerでデプロイした場合の制約

マネジメントコンソールでソースコードを表示・編集できない

先程の画像でも表示されている通り、Dockerでデプロイした場合、マネジメントコンソールでソースコードを表示・編集できません。
「一時的にちょっとだけソースを変えたい」という場合でも、イメージビルド・push作業を実施する必要があります。

Lambda Layerが使えない

Dockerでデプロイした場合、Lambda Layerは使えません。(有効なのはコンテナ内部のソースのみ、ということだと思います)
なので必要なモジュールなどは、Dockerfileで RUN npm install を実行するなどして、あらかじめイメージに含めておく必要があります。

まとめ

以上が、LambdaをDockerイメージでデプロイする際のAWS CDKの定義です。

ちょっとややこしい面もありますが、便利な面もあるので、必要に応じて使い分けていけるといいですね。

次回は後編として、Dockerfileの書き方とか、発生する問題に対する解決策などを記載しようと思います。

それでは、今回はこの辺で。

*1:インフラとアプリのライフサイクルが異なる、ソースの開発は外部に依頼する...など

*2:なので上記ソースは、いきなりcdk deployしても正常にデプロイできません。ただし今回は説明の簡素化のためにあえて一緒に書きました