出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

lambda関数のコンテナイメージサポートをterraformで構築する

AWS Lambda関数をコンテナイメージでデプロイする環境をterraformで構築したい。 特にterraformでは対象のコンテナイメージを管理から外したい場合における 初期構築方法について検証する。

検証コードはこちら。

github.com

lambda関数によるイメージ指定

Lambda関数を package_type = Image で作成する場合、 コンテナイメージを指定する必要があり、ECRだけでは作成できない。 イメージをpushせずに構築しようとすると、以下のようなエラーになる。

resource "aws_lambda_function" "sample" {
  function_name = "sample-container"
  role          = aws_iam_role.lambda.arn

  package_type = "Image"
  image_uri    = "${aws_ecr_repository.sample_prepared.repository_url}:latest"
}
aws_lambda_function.sample_prepared: Creating...
╷
│ Error: error creating Lambda Function (1): InvalidParameterValueException: Source image xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/sample-prepared:latest does not exist. Provide a valid source image.
│ {
│   RespMetadata: {
│     StatusCode: 400,
│     RequestID: "4c6a36d1-1ed8-4c2a-959a-50f8b066d21d"
│   },
│   Message_: "Source image xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/sample-prepared:latest does not exist. Provide a valid source image.",
│   Type: "User"
│ }
│
│   with aws_lambda_function.sample_prepared,
│   on external_registry.tf line 3, in resource "aws_lambda_function" "sample_prepared":
│    3: resource "aws_lambda_function" "sample_prepared" {
│

このため、有効なコンテナイメージを準備しておきLambda関数を構築可能とする必要がある。 このコンテナイメージにはAWSが提供してくれるイメージなどは利用できず、 対象のAWSアカウントに自分で準備する必要がある。

docs.aws.amazon.com

Lambda 関数の作成は、Amazon ECR のコンテナレジストリと同じアカウントから実行する必要があることにご注意ください

以上から、対策方針としては以下の3つがある。

  • ダミーECRリポジトリおよびコンテナイメージを準備しておく
  • local-exec でECRレジストリ作成と同時にイメージを作成する
  • docker providerでイメージまで作成する

方針1. 外部のダミーECRレジストリおよびコンテナイメージを準備しておく

一番シンプルなのは事前に専用のダミーコンテナイメージを準備しておく方法。 ダミーイメージをデプロイ時に合わせて構築することは難しく、 lambdaの仕様上別AWSアカウントのイメージ等を利用することはできない。 このため自分で事前にイメージを構築しておく。

resource "aws_lambda_function" "sample_prepared" {
  function_name = "sample-container-prepared"
  role          = aws_iam_role.lambda.arn

  package_type = "Image"
  # 初期構築時はダミーイメージを指定しておく
  image_uri = "${data.aws_ecr_repository.prepared.repository_url}:latest"

  lifecycle {
    ignore_changes = [image_uri]
  }
}

# 実際にlambda関数で実行するコンテナイメージを格納するレジストリ
resource "aws_ecr_repository" "sample_prepared" {
  name                 = "sample-prepared"
  image_tag_mutability = "MUTABLE"
}

# 以下は事前に手動で構築しておいたECRレジストリおよびコンテナイメージ
data "aws_ecr_repository" "prepared" {
  name = "prepared"
}

手作業でECRレジストリを作成し、適当なイメージをlatestタグでプッシュしておく。 初回構築時のみLambda関数がこのコンテナイメージをロードするが、 以降はlifecycleで指定している通り無視されるので、 別のイメージタグを指定しようが異なるリポジトリのイメージを指定しようが terraform上では差分は発生しない。

この方法はシンプルで手軽に対応可能な一方で、 手動でECRリポジトリの作成とイメージのpushが必要であること、 初回構築のためだけに別のECRリポジトリへの依存を明記する必要があるといった気持ち悪さがある。 実際に利用するECRリポジトリが対象lambda関数から指定されないことは誤解を生じやすいのでできれば避けたい。

方針2. local-exec でECRレジストリ作成と同時にイメージを作成する

初回構築のためだけに別ECRリポジトリを準備・利用するのは気持ち悪いので 初回構築時にECRリポトリにダミーイメージを格納する。 こういった操作にはlocal-execが便利なのでこれで実現する。

resource "aws_lambda_function" "sample_localexec" {
  function_name = "sample-container-localexec"
  role          = aws_iam_role.lambda.arn

  package_type = "Image"
  image_uri    = "${aws_ecr_repository.sample_localexec.repository_url}:latest"

  depends_on = [null_resource.generate_dummy_image]
}

# 実際にlambda関数で実行するコンテナイメージを格納するレジストリ
resource "aws_ecr_repository" "sample_localexec" {
  name                 = "sample-localexec"
  image_tag_mutability = "MUTABLE"
}

# ダミーコンテナイメージの保存
resource "null_resource" "generate_dummy_image" {
  provisioner "local-exec" {
    command = "aws ecr get-login-password | docker login --username AWS --password-stdin ${data.aws_caller_identity.current.account_id}.dkr.ecr.ap-northeast-1.amazonaws.com"
  }

  # ダミーイメージとしてalpineを利用する
  provisioner "local-exec" {
    command = "docker pull alpine:latest"
  }

  provisioner "local-exec" {
    command = "docker tag alpine:latest ${aws_ecr_repository.sample_localexec.repository_url}"
  }

  provisioner "local-exec" {
    command = "docker push ${aws_ecr_repository.sample_localexec.repository_url}"
  }
}

この方法を利用することで lambda関数のイメージ指定先として実際に利用するECRリポジトリを指定できる。 事前の操作も不要であり構築も容易である。

課題として、動作環境にawscliおよびdocker cliが必要なことが挙げられる。 通常の開発環境であれば存在を仮定しても問題なさそうだが、これが問題になるのがTerraform Cloud環境である。 Terraform Cloud への追加ソフトウェアインストールについてはドキュメントにまとめられているが、 基本的に非推奨でありさまざまな制約も存在する。 やはりAWS CLIが欲しいという意見は挙がっているようだが、 Terraform Cloud環境でaws cli や docker cli を利用する方針は避けた方がよさそう。

※別途確認したところ、Terarform Cloud環境にaws cliは存在したがdocker cliは存在しなかった。

方針3. docker providerでイメージまで作成する

local-execを利用すればLambda関数の構築に必要なダミーdockerイメージを生成・格納できるが Terraformの実行環境に追加ソフトウェアの依存が生まれてしまう。 これを解決する方法として、terraformでdockerイメージを制御する terraform-provider-docker を利用する。 docker providerを利用することで、追加でのawscliやdocker cliなしにdockerイメージのpushが可能になる。

resource "aws_lambda_function" "sample_dockerprovider" {
  function_name = "sample-container-dockerprovider"
  role          = aws_iam_role.lambda.arn

  package_type = "Image"
  image_uri = "${aws_ecr_repository.sample_dockerprovider.repository_url}:latest"
}

resource "aws_ecr_repository" "sample_dockerprovider" {
  name                 = "sample-dockerprovider"
  image_tag_mutability = "MUTABLE"
}

# Dockerレジストリにダミーイメージを格納しておく
resource "docker_registry_image" "sample_dockerprovider" {
  name = "${aws_ecr_repository.sample_dockerprovider.repository_url}:latest"

  build {
    context = "dummy"
  }
}

# docker-providerでECRを利用するための認証設定
data "aws_ecr_authorization_token" "token" {
}

provider "docker" {
  registry_auth {
    address  = "${data.aws_caller_identity.current.account_id}.dkr.ecr.ap-northeast-1.amazonaws.com"
    username = data.aws_ecr_authorization_token.token.user_name
    password = data.aws_ecr_authorization_token.token.password
  }
}

ここで一番実現したいことは、DockerHub上のalpineイメージを対象のECRリポジトリにコピーすること。 しかし、docker providerではこのような機能は議論されているが実装さていない。 このため、dummyディレクトリにあるダミー用のDockerfileにてコンテナイメージビルドした上で、そのイメージをECRリポジトリにpushすることで実現する。

FROM alpine

この方法であれば追加でaws cliや docker cliは不要でdocker providerを追加すれば解決できる。 ただし、hashicorp管理ではない別のproviderへ依存することになるのでこれを許容できるかが鍵となる。

まとめ

  • lambda関数でコンテナイメージサポートを利用する場合、初期構築時からコンテナイメージが必要となる
  • Terraformで上記lambda関数を構築する場合は構築時用のコンテナイメージをどのように準備するかが課題になる。
  • 別ECRリポジトリを参照する方法、local-execで構築する方法、docker providerで構築する方法が考えられる。
  • どの方針も一長一短なのでどれを選択するかは状況次第

参考