Lambdaをどう管理する?
Terraformでインフラコードを管理しているとLambdaの管理は割と悩ましい問題になる。 Terraformで管理したいのはインフラコードであってアプリケーションコードではないのだが、 TerraformでLambdaのresourceを定義するためにはアプリケーションコードが必要になってしまうからだ。
当初考えていた手段は以下の通り
- terraform repositoryにlambda zip fileを置く
- lambda zip fileをs3で管理する
- terraform repositoryでapplication codeも管理してしまう
- terraformを諦めてSAM CLIを利用する
- etc ...
できるだけterraformでの管理を行いたかったので、当初 02. を検討していたが、2020/08時点でterraformにある既知の不具合のせいで s3上のzip fileに変更がない場合でも必ずapplyの対象になってしまうので都合が悪い。
色々考えた結果、上述の手段を一旦忘れることにして、lambda_functionだけは AWS CLI
で管理して
その他のresourceをterraformで管理するということに落ち着いた。
ただ、できる限り自動化したかったので、aws cli自体はcodebuild上で行うようにしている。
以下、備忘録としてコードを残しておく (ところどころ省略しています)。cloudwatch logsにlogを出力するだけのlambdaを想定した。
1. lambda用のcloudwatch logs, iamを用意する
resource "aws_cloudwatch_log_group" "this" { # 名称は /aws/lambda/function_nameとなること name = "/aws/lambda/${local.lambda_function_name}" retention_in_days = 7 } # lambda execution data "aws_iam_policy_document" "lambda_execution_role_policy" { statement { effect = "Allow" actions = [ "logs:PutLogEvents", "logs:CreateLogStream", "logs:DescribeLogStreams", ] resources = [ "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:${aws_cloudwatch_log_group.this.name}", "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:${aws_cloudwatch_log_group.this.name}:log-stream:*", ] } } # 実践Terraformで紹介されているmoduleをそのまま利用している module "lambda_execution_role" { source = "./modules/iam-role" identifier = "lambda.amazonaws.com" name = "${local.account_name}-lambda-function" policy = data.aws_iam_policy_document.lambda_execution_role_policy.json }
2. aws cli create function
# console上で実行。Makefileにbuild && zipするようなコードを書いている $ make zip-service # 1.で作成したroleを使ってlambda functionを作成する $ aws lambda create-function --function-name load --runtime go1.x --zip-file fileb://load.zip --handler load --role arn:aws:iam::999999999999:role/my-lambda-function
3. Lambda permission, alias, notification 等を定義
# s3 eventで発火するようにしている resource "aws_s3_bucket_notification" "s3_notification" { bucket = module.s3_bucket_for_csv.this_s3_bucket_id lambda_function { lambda_function_arn = aws_lambda_alias.lambda_alias.arn events = ["s3:ObjectCreated:*"] filter_prefix = "CSV_FILES/" filter_suffix = ".csv" } depends_on = [aws_lambda_permission.lambda_load_permission] } resource "aws_lambda_permission" "lambda_load_permission" { statement_id = "AllowExecutionFromS3Bucket" action = "lambda:InvokeFunction" function_name = local.lambda_function_name principal = "s3.amazonaws.com" qualifier = aws_lambda_alias.lambda_alias.name source_arn = module.s3_bucket_for_csv.this_s3_bucket_arn } resource "aws_lambda_alias" "lambda_alias" { name = "load-lambda-alias" description = "alias for load lambda" function_name = local.lambda_function_name function_version = "$LATEST" # routing configでversionごとに重み付けをすることも可能 /** routing_config { additional_version_weights = { "1" = 0 } } */ }
4. buildspec.yaml
version: 0.2 phases: install: runtime-versions: golang: 1.14 build: commands: - echo "`date` build start" - echo "change directory | $APPLICATION_DIRECTORY" - cd $APPLICATION_DIRECTORY - make zip-service post_build: commands: - echo "`date` build finish" - echo "publish Lambda" - aws lambda update-function-code --function-name load --zip-file fileb://load.zip --publish
5. codeBuild, codePipeline
# iam等、一部省略 resource "aws_codebuild_project" "application" { name = "${local.account_name}-codebuild-application" service_role = module.codebuild_role.iam_role_arn artifacts { type = "CODEPIPELINE" } source { type = "CODEPIPELINE" buildspec = local.build_spec_template_path } cache { type = "NO_CACHE" } logs_config { cloudwatch_logs { status = "ENABLED" group_name = aws_cloudwatch_log_group.codebuild.name } s3_logs { status = "DISABLED" } } environment { # https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0" type = "LINUX_CONTAINER" compute_type = "BUILD_GENERAL1_SMALL" # 特権を付与する privileged_mode = true # buildspec.yamlに渡す環境変数 environment_variable { name = "APPLICATION_DIRECTORY" value = "./lambda-function" } } tags = local.tags } resource "aws_codepipeline" "this" { name = local.codepipeline_name role_arn = module.codepipeline_role.iam_role_arn artifact_store { location = module.s3_bucket_for_codepipeline_artifact.this_s3_bucket_id type = "S3" } stage { name = "Source" action { name = "Source" category = "Source" owner = "ThirdParty" provider = "GitHub" version = "1" run_order = 1 output_artifacts = ["Source"] configuration = { Owner = "foo" Repo = "bar" Branch = "master" OAuthToken = "dummy" PollForSourceChanges = false } } } stage { name = "Build" action { name = "BuildApplication" category = "Build" owner = "AWS" provider = "CodeBuild" version = "1" run_order = 1 input_artifacts = ["Source"] output_artifacts = ["BuildApplication"] configuration = { ProjectName = aws_codebuild_project.application.name } } } lifecycle { ignore_changes = [ # GitHubの接続設定はCodePipeline Web GUIで上書きする stage[0].action[0].configuration, ] } }
カナリアリリースを行いたい場合、別途code deployを記述すれば可能だと思われるが、現時点で業務で利用する予定がないので取り敢えずここまで。