Apex+Terraform으로 서버리스 아키텍처 전체 코드화

제 팀은 AWS의 인프라 구성 관리에 Terraform을 활용합니다.
다만, 최근에는 성능이 그다지 요구되지 않는 배치 처리나 API등은 서버리스 아키텍쳐를 채용하는 것이 많아져 왔습니다.
하지만 람다 함수 코드 자체의 배포를 Terraform에 맡기는 것이 힘들기 때문에,
Lambda의 배포는 개발 팀이 사용했던 Serverless Framework로 나뉩니다.
이것에는 큰 과제가 있었고, Terraformで作ったS3のイベントをトリガーにするLambdaファンクションを作りたい , 같은, 나중에 Lambda를 추가하는 요건에 대응하지 못하고 고민하고 있었습니다.
그럴 때 Apex는 Terraform을 Wrap 할 수 있다고 하늘에서 목소리가 내려 왔기 때문에 시도해보기로했습니다.

검증에 이용한 환경


  • MacOS X(10.11.6)
  • Apex(1.0.0-rc2)
  • Terraform (v0.11.11)
    ※ 당 엔트리는 Apex 나 Terraform 도입에 대해서는 언급하지 않습니다

  • 만드는 환경




    Lambda에서 사용하는 IAM Role과 API Gateway는 Terraform에 관리를 맡기고 Lambda 자체는 Apex에 관리합니다.

    파일 구성


    ./project.json # プロジェクト全体の定義(たぶんfunction.jsonで設定を上書きできる気がする)
    ./functions/hello/function.json # 関数の定義
    ./functions/hello/hello.py # Lambdaで実行するコード(別にPythonじゃなくてもOK)
    ./infrastructure/main.tf # Terraformで管理するAWSリソースの定義
    

    . /p 로지ぇct. j 그런



    어쩌면 프로젝트 전체의 공통 설정을 기재하는 느낌이라고 생각합니다.
    죄송합니다, 제대로 조사하지 않았습니다. . .
    {
      "name": "apex-sample",
      "description": "apex test 20190405",
      "memory": 128,
      "timeout": 5,
      "role": "arn:aws:iam::{各自のAWS Account ID}:role/apex_sample",
      "environment": {}
    }
    

    . / 훙 c 치온 s / 헵 / 훙 c 치온. j 그런



    사용할 runtime과 함수 이름을 정의합니다.runtimehandler 만약 제대로 하면 최소한은 괜찮다고 생각합니다.
    {
        "description": "hello",
        "runtime": "python3.6",
        "handler": "hello.lambda_handler",
        "environment":
        {
            "ENV": "dev"
        }
    }
    

    . / 훙 c 치온 s / 헵 / 헉. py


    hello world 를 응답 본문으로 반환하기만 하면 됩니다.
    최저한 API Gateway의 사양에 준거한 응답을 돌려주면 괜찮습니다.
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    def lambda_handler(event, context):
        json = {"statusCode": 200, "body": "hello world"}
        return json
    

    . / 인 f 들 st 하는 c 얽힌 / 마인. tf



    완전히 Terraform 코드입니다.
    귀찮은 느낌에 1 파일로 정리 버렸습니다만, module화하거나 하는 편이 관리는 하기 쉽다고 생각합니다.variable "apex_function_hello" {}에서 apex에서 변수를 받는 것이 포인트입니다.
    정의한 리소스는 IAM Role과 API Gateway뿐입니다.
    provider "aws" {
      region = "ap-northeast-1"
    }
    
    terraform {
      backend "s3" {
        bucket = "apex-test-bucket"
        key    = "apex/test20190404/terraform.tfstate"
        region = "ap-northeast-1"
      }
    }
    variable "apex_function_hello" {}
    
    data "aws_region" "current" {}
    data "aws_iam_policy_document" "lambda" {
      version = "2012-10-17"
      statement {
        actions = [
          "logs:*"
        ]
        resources = [
          "*"
        ]
        effect = "Allow"
      }
    }
    
    resource "aws_iam_policy" "policy" {
      name = "apex_sample"
      path = "/"
      policy = "${data.aws_iam_policy_document.lambda.json}"
    }
    
    resource "aws_iam_role" "lambda" {
      name = "apex_sample"
      description = ""
      assume_role_policy = <<EOF
    {
      "Version":"2012-10-17",
      "Statement":
      [
        {
          "Effect":"Allow",
          "Principal":
          {
            "Service":"lambda.amazonaws.com"
          },
          "Action":"sts:AssumeRole"
        }
      ]
    }
    EOF
      force_detach_policies = false
      path = "/"
      max_session_duration = 3600
    }
    
    resource "aws_iam_role_policy_attachment" "lambda" {
      role       = "${aws_iam_role.lambda.name}"
      policy_arn = "${aws_iam_policy.policy.arn}"
    }
    
    resource "aws_api_gateway_rest_api" "rest_api" {
        name = "apex_sample"
        description = "Created by AWS Lambda"
        endpoint_configuration {
            types = ["REGIONAL"]
        }
        minimum_compression_size = -1
        api_key_source = "HEADER"
    }
    
    resource "aws_api_gateway_resource" "proxy" {
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      parent_id   = "${aws_api_gateway_rest_api.rest_api.root_resource_id}"
      path_part   = "{proxy+}"
    }
    
    resource "aws_api_gateway_method" "proxy" {
      rest_api_id   = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id   = "${aws_api_gateway_resource.proxy.id}"
      http_method   = "ANY"
      authorization = "NONE"
    }
    
    resource "aws_api_gateway_integration" "lambda" {
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      resource_id = "${aws_api_gateway_method.proxy.resource_id}"
      http_method = "${aws_api_gateway_method.proxy.http_method}"
    
      integration_http_method = "POST"
      type                    = "AWS_PROXY"
      uri                     = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${var.apex_function_hello}/invocations"
    }
    
    resource "aws_api_gateway_deployment" "apigw" {
      depends_on = [
        "aws_api_gateway_integration.lambda",
      ]
    
      rest_api_id = "${aws_api_gateway_rest_api.rest_api.id}"
      stage_name  = "default"
    }
    
    resource "aws_lambda_permission" "apigw" {
      statement_id  = "AllowAPIGatewayInvoke"
      action        = "lambda:InvokeFunction"
      function_name = "${var.apex_function_hello}"
      principal     = "apigateway.amazonaws.com"
      source_arn = "${aws_api_gateway_deployment.apigw.execution_arn}/*/*"
    }
    
    output "base_url" {
      value = "${aws_api_gateway_deployment.apigw.invoke_url}"
    }
    

    배포



    환경 변수 설정


    $ export AWS_ACCESS_KEY_ID=hogehoge
    $ export AWS_SECRET_ACCESS_KEY=piyopiyo
    $ export AWS_DEFAULT_REGION=ap-northeast-1
    $ export AWS_REGION=ap-northeast-1
    

    Terraform 백엔드 초기화


    $ apex infra init
    

    apex infra 커멘드가 terraform 커멘드를 wrap 해 주는 것 같습니다.

    IAM Role 배포



    API Gateway를 배포하려면 Lambda가 필요하며 Lambda를 배포하려면 IAM Role이 필요합니다.
    그래서 먼저 IAM Role만 배포합니다.
    $ apex infra apply -target aws_iam_policy.policy -target aws_iam_role.lambda -target aws_iam_role_policy_attachment.lambda
    

    즉시 var.apex_function_hello 값을 입력하라는 메시지가 표시되지만 IAM Role에서는 특별히 사용하지 않으므로 적절하게 입력하여 Enter합니다.

    Lambda 배포


    $ apex deploy
       • creating function         env= function=hello
       • created alias current     env= function=hello version=1
       • function created          env= function=hello name=apex-sample_hello version=1
    

    나머지 AWS 리소스 배포


    $ apex infra apply
    

    여기에서는 Lambda 함수가 배포되었으므로 var.apex_function_hello의 값을 듣지 못합니다.

    동작 확인



    이제 모든 리소스를 배포할 수 있었기 때문에 동작을 확인해 보겠습니다.
    $ curl https://budfnr6xp6.execute-api.ap-northeast-1.amazonaws.com/default/apex-sample_hello
    hello world
    

    제대로 응답이 돌아왔습니다.
    이것이라면 Terraform의 코드를 만들어 내면 VPC에 의존하는 리소스 등과의 공존도 가능하게 될 것 같네요.

    후 정리 (환경 파기)


    $ apex infra destroy
    $ apex delete
    

    각각 프롬프트에서는 yes 라고 대답하면 OK.

    참고로 한 사이트


  • Apex 공식 사이트
  • Terraform 공식 사이트(Serverless Applications with AWS Lambda and API Gateway)
  • 좋은 웹페이지 즐겨찾기