概要

  • Terraformハンズオンの第二弾です。今回は、AWS環境にサーバーレスの基本構成となる API Gateway + Lambda + DynamoDB のリソースを作成します。Terraform を実行後に、クライアントからREST API を送信して応答を得ることがゴールとなります。

対象読者

  • Terraform および AWS初学者向けの記事です。Terraform, AWS 学習の近道は、どちらも小規模のシステムを構築することになります。今回は、Terraform を使用して、各リソースを順番に作成することでTerraform の理解を深めます。

深く理解するために

  • Terraform ハンズオンの理解を深めるため、Terraform を使用して構築する前に、AWS コンソールを使って一通り構築をすることをお勧めします。コンソールで作成したリソースとコードが頭の中で繋がり、理解が深まると思います。
  • 最初はコードの意味が分からなくとも、コードを使用して構築ができることを経験しましょう。次はコードの理解を努め、サーバーレスの構成を変えたり、Lambdaのコードを変更するなど、カスタマイズを試してみましょう。

Terraform のModule とは

  • 今回は、Terraform のモジュールを利用します。Terraformは、メインとなるルートモジュールから他のモジュールを呼び出すことができます。モジュール化によって、子モジュールのリソースを簡潔に記述できます。モジュールは複数回呼び出すこともできるため、リソース構成をパッケージ化して再利用できます。モジュールの呼び出しに必要な値を与えることで、環境に合わせた構築ができます。

ハンズオンの準備

開発環境の準備

  • 開発環境にTerraform および AWS CLI をインストールします。
  • AWS コンソールを利用して、API Gateway, Lambda, DynamoDB およびIAM のリソースが作成可能な IAM のアクセスキーを準備します。
  • 開発環境にアクセスキーを登録します。アクセスキーの管理は、aws configureコマンドで登録する方法、シェル環境変数に設定する方法、Terraform の変数に設定する方法などいくつかのパターンあります。
  • 以下、aws configureコマンドで登録する方法です。
$ aws configure
AWS Access Key ID [None]: ********************
AWS Secret Access Key [None]: ****************************************
Default region name [None]: ap-northeast-1
Default output format [None]: json
  • 以下、シェル環境変数に設定する方法です。
export AWS_ACCESS_KEY_ID=AAAAAAAAAAAAAAAAAAAA
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export AWS_DEFAULT_REGION=ap-northeast-1
  • その他 Terraform利用時のアクセスキーの設定方法は、以下の記事を参考にして下さい。
概要 AWS 環境のクレデンシャルを設定したにも関わらず、terraform plan実行時に、Error: No valid credential となりました。 以下、エラーメッセージとなります。 Error: No valid credential sources found for AWS Provider.Please see https://terraform.io/doc...

TF コードの準備

  • 始めに、envsディレクトリmodulesディレクトリを作成します。次に、envsディレクトリ配下に、基本となるTF コード(例: main.tf, variables.tf, outputs.tf, provider.tf, backend.tf )を作成します。main.tfがコードを記述するエントリポイントになります。今回は、variables.tf, outputs.tf は使用しませんが、この2つはmain.tf に対する入力値と出力値になります。
  • 次に、provider.tfの記述例です。provider.tf には、最低限1つのprovider を定義し、region を指定します。
provider "aws" {
  region                  = "us-east-1"
}
  • 次は、backend.tfの記述例です。tfstateファイルはデフォルトでローカルに保存されますが、チームで共有する場合は、tfstateファイルをS3 に保存します。事前に、S3 バケットを作成し、パブリックアクセスをブロックに設定します。また、tfstateファイルを管理するバケットのバージョニング設定を有効にすることも良いと思います。
  • terraform apply を実行後に、自動的にS3 バケットにtfstateファイルが作成されます。
# tfstate をローカル管理する場合
terraform {
  backend "local" {
    path = "./terraform.tfstate"
  }
}

# tfstate をS3管理する場合
#terraform {
#  backend "s3" {
#    bucket  = "bucket-name"
#    region  = "us-east-1"
#    key     = "dir/terraform.tfstate"
#    encrypt = false
# S3のAWSアカウントがデプロイ先と異なる場合に指定
#    access_key = ""
#    secret_key = ""
#  }
#}

ハンズオン1:DynamoDB作成

  • ハンズオン1 は、DynamoDB にて、社員一覧のテーブルを作成します。

  • modulesディレクトリ配下に、dynamodb/dynamodb.tf を作成します。
HandsOn_AWS_Serverless
├── envs
│   ├── backend.tf
│   ├── main.tf
│   └── provider.tf
└── modules
    └── dynamodb
        └── dynamodb.tf
  • dynamodb.tf を編集します。テーブル内のアイテムは、適宜修正ください。
  • output は、モジュール間でデータを受け渡しするためのリソースになります。
variable "prefix" {
  type = string
}

resource "aws_dynamodb_table" "employee_list" {
  name         = "${var.prefix}_employee_list"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "EmployeeId"
  attribute {
    name = "EmployeeId"
    type = "S"
  }
}

resource "aws_dynamodb_table_item" "employee_list_item" {
  table_name = aws_dynamodb_table.employee_list.name
  hash_key   = "EmployeeId"
  item = jsonencode({
    EmployeeId = {
      S = "a00000110"
    },
    FirstName = {
      S = "Taro"
    },
    LastName = {
      S = "Momo"
    },
    Office = {
      S = "Nagoya"
    }
  })
}

output "employee_list_table" {
  value = aws_dynamodb_table.employee_list
}
  • main.tf を修正します。source には先ほど作成したdynamodb のモジュールを指定します。prefix は任意の文字列を指定してください。
module "dynamodb" {
  source = "../modules/dynamodb"
  prefix = "sample1"
}
  • terraform コマンドを実行します。main.tf のディレクトリで、以下のコマンドを実行します。terraform plan にエラーがなければ、terraform apply を実行します。
    • terraform init
    • terraform plan
    • terraform apply
  • 念のため、terraform init を実行する前に、下記のAWS CLI コマンドを実行して、デプロイ先となるAWSアカウントが正しいことを確認することをお勧めします。
    • aws sts get-caller-identity
  • DynamoDB のリソースをデプロイします。
$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.dynamodb.aws_dynamodb_table.employee_list will be created
  + resource "aws_dynamodb_table" "employee_list" {
      + arn              = (known after apply)
      + billing_mode     = "PAY_PER_REQUEST"
      + hash_key         = "EmployeeId"
      + id               = (known after apply)
      + name             = "sample1_employee_list"
      + read_capacity    = (known after apply)
      + stream_arn       = (known after apply)
      + stream_label     = (known after apply)
      + stream_view_type = (known after apply)
      + tags_all         = (known after apply)
      + write_capacity   = (known after apply)

      + attribute {
          + name = "EmployeeId"
          + type = "S"
        }

      + point_in_time_recovery {
          + enabled = (known after apply)
        }

      + server_side_encryption {
          + enabled     = (known after apply)
          + kms_key_arn = (known after apply)
        }

      + ttl {
          + attribute_name = (known after apply)
          + enabled        = (known after apply)
        }
    }

  # module.dynamodb.aws_dynamodb_table_item.employee_list_item will be created
  + resource "aws_dynamodb_table_item" "employee_list_item" {
      + hash_key   = "EmployeeId"
      + id         = (known after apply)
      + item       = jsonencode(
            {
              + EmployeeId = {
                  + S = "a00000110"
                }
              + FirstName  = {
                  + S = "Taro"
                }
              + LastName   = {
                  + S = "Momo"
                }
              + Office     = {
                  + S = "Nagoya"
                }
            }
        )
      + table_name = "sample1_employee_list"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

module.dynamodb.aws_dynamodb_table.employee_list: Creating...
module.dynamodb.aws_dynamodb_table.employee_list: Still creating... [10s elapsed]
module.dynamodb.aws_dynamodb_table.employee_list: Creation complete after 15s [id=sample1_employee_list]
module.dynamodb.aws_dynamodb_table_item.employee_list_item: Creating...
module.dynamodb.aws_dynamodb_table_item.employee_list_item: Creation complete after 0s [id=sample1_employee_list|EmployeeId||a00000110|]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: ./terraform.tfstate
  • コンソールから作成されたリソースを確認します。

ハンズオン2:IAM ロール作成

  • ハンズオン2 は、Lambda 用のIAM ロールを作成します。

  • modulesディレクトリ配下に、iam/iam.tf を作成します。
HandsOn_AWS_Serverless
├── envs
│   ├── backend.tf
│   ├── main.tf
│   ├── provider.tf
│   └── terraform.tfstate
└── modules
    ├── dynamodb
    │   └── dynamodb.tf
    └── iam
        └── iam.tf
  • iam.tf を編集します。
variable "prefix" {
  type = string
}

variable "employee_list_table-arn" {
  type = string
}

resource "aws_iam_role" "tr_lambda_role" {
  name = "${var.prefix}_tr_lambda_role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      },
    ]
  })
}

resource "aws_iam_role_policy_attachment" "tr_lambda_role_policy_attach" {
  role       = aws_iam_role.tr_lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_role_policy" "tr_lambda_role_policy_policy" {
  name = "${var.prefix}_tr_lambda_policy"
  role = aws_iam_role.tr_lambda_role.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "dynamodb:GetItem"
        ]
        Resource = [
          var.employee_list_table-arn
        ]
      }
    ]
  })
}

output "tr_lambda_role-arn" {
  value = aws_iam_role.tr_lambda_role.arn
}
  • main.tf を修正します。source には先ほど作成したiam のモジュールを指定します。prefix は任意の文字列を指定してください。
  • employee_list_table-arn に、先ほど dynamodb.tf に記述したoutput を指定します。
module "iam" {
  source = "../modules/iam"
  prefix = "sample1"
  employee_list_table-arn = module.dynamodb.employee_list_table.arn
}
  • terraform コマンドを実行します。main.tf のディレクトリで、以下のコマンドを実行します。terraform plan にエラーがなければ、terraform apply を実行します。
    • terraform init
    • terraform plan
    • terraform apply
  • IAM のリソースのリソースをデプロイします。
$ terraform apply
module.dynamodb.aws_dynamodb_table.employee_list: Refreshing state... [id=sample1_employee_list]
module.dynamodb.aws_dynamodb_table_item.employee_list_item: Refreshing state... [id=sample1_employee_list|EmployeeId||a00000110|]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.iam.aws_iam_role.tr_lambda_role will be created
  + resource "aws_iam_role" "tr_lambda_role" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "lambda.amazonaws.com"
                        }
                      + Sid       = ""
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "sample1_tr_lambda_role"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)

      + inline_policy {
          + name   = (known after apply)
          + policy = (known after apply)
        }
    }

  # module.iam.aws_iam_role_policy.tr_lambda_role_policy_policy will be created
  + resource "aws_iam_role_policy" "tr_lambda_role_policy_policy" {
      + id     = (known after apply)
      + name   = "sample1_tr_lambda_policy"
      + policy = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "dynamodb:GetItem",
                        ]
                      + Effect   = "Allow"
                      + Resource = [
                          + "arn:aws:dynamodb:us-east-1:111111111111:table/sample1_employee_list",
                        ]
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role   = (known after apply)
    }

  # module.iam.aws_iam_role_policy_attachment.tr_lambda_role_policy_attach will be created
  + resource "aws_iam_role_policy_attachment" "tr_lambda_role_policy_attach" {
      + id         = (known after apply)
      + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      + role       = "sample1_tr_lambda_role"
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

module.iam.aws_iam_role.tr_lambda_role: Creating...
module.iam.aws_iam_role.tr_lambda_role: Creation complete after 2s [id=sample1_tr_lambda_role]
module.iam.aws_iam_role_policy_attachment.tr_lambda_role_policy_attach: Creating...
module.iam.aws_iam_role_policy.tr_lambda_role_policy_policy: Creating...
module.iam.aws_iam_role_policy_attachment.tr_lambda_role_policy_attach: Creation complete after 1s [id=sample1_tr_lambda_role-20220414154047163300000001]
module.iam.aws_iam_role_policy.tr_lambda_role_policy_policy: Creation complete after 1s [id=sample1_tr_lambda_role:sample1_tr_lambda_policy]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: ./terraform.tfstate
  • コンソールから作成されたリソースを確認します。

ハンズオン3:Lambda 作成

  • ハンズオン3 は、Lambda を作成します。

  • modulesディレクトリ配下に、lambda/src ディレクトリ、lambda/src/tr_lambda.py、lambda/lambda/lambda.tf を作成します。
$ tree HandsOn_AWS_Serverless
HandsOn_AWS_Serverless
├── envs
│   ├── backend.tf
│   ├── main.tf
│   ├── provider.tf
│   ├── terraform.tfstate
│   └── terraform.tfstate.backup
└── modules
    ├── dynamodb
    │   └── dynamodb.tf
    ├── iam
    │   └── iam.tf
    └── lambda
        ├── lambda.tf
        └── src
            └── tr_lambda.py
  • tr_lambda.py を編集します。
import os
import json
import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.getenv('TABLE_NAME'))

def handler(event, context):
    employee_info = table.get_item(Key={'EmployeeId': 'a00000110'})['Item']
    employee_id = employee_info['EmployeeId']
    employee_firstname = employee_info['FirstName']
    employee_lastname = employee_info['LastName']
    employee_office = employee_info['Office']
    return {
        'statusCode': 200,
        'body': json.dumps({
            'Id': employee_id,
            'Firstname': employee_firstname,
            'Lastname': employee_lastname,
            'Office': employee_office,
        })
    }
  • lambda.tf を編集します。
variable "prefix" {
  type = string
}

variable "employee_list_table-name" {
  type = string
}

variable "tr_lambda_role-arn" {
  type = string
}

data "archive_file" "tr_lambda" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/upload/lambda.zip"
}

resource "aws_lambda_function" "tr_lambda" {
  filename         = data.archive_file.tr_lambda.output_path
  function_name    = "${var.prefix}_tr_lambda"
  role             = var.tr_lambda_role-arn
  handler          = "tr_lambda.handler"
  source_code_hash = data.archive_file.tr_lambda.output_base64sha256
  runtime          = "python3.8"
  timeout          = 29
  environment {
    variables = {
      TABLE_NAME = var.employee_list_table-name
    }
  }
}

output "tr_lambda-invoke-arn" {
  value = aws_lambda_function.tr_lambda.invoke_arn
}
  • main.tf を修正します。source には先ほど作成したlambda のモジュールを指定します。prefix は任意の文字列を指定してください。
  • employee_list_table_name に、先ほど dynamodb.tf に記述したoutput を指定します。
  • tr_lambda_role-arn には、先ほど iam.tf に記述したoutput を指定します。
module "lambda" {
  source                = "../modules/lambda"
  prefix                = "sample1"
  employee_list_table-name  = module.dynamodb.employee_list_table.name
  tr_lambda_role-arn        = module.iam.tr_lambda_role-arn
}
  • terraform コマンドを実行します。main.tf のディレクトリで、以下のコマンドを実行します。terraform plan にエラーがなければ、terraform apply を実行します。
    • terraform init
    • terraform plan
    • terraform apply
  • Lambda のリソースのリソースをデプロイします。
$ terraform apply
module.lambda.data.archive_file.tr_lambda: Refreshing state...
module.iam.aws_iam_role.tr_lambda_role: Refreshing state... [id=sample1_tr_lambda_role]
module.dynamodb.aws_dynamodb_table.employee_list: Refreshing state... [id=sample1_employee_list]
module.dynamodb.aws_dynamodb_table_item.employee_list_item: Refreshing state... [id=sample1_employee_list|EmployeeId||a00000110|]
module.iam.aws_iam_role_policy.tr_lambda_role_policy_policy: Refreshing state... [id=sample1_tr_lambda_role:sample1_tr_lambda_policy]
module.iam.aws_iam_role_policy_attachment.tr_lambda_role_policy_attach: Refreshing state... [id=sample1_tr_lambda_role-20220414154047163300000001]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.lambda.aws_lambda_function.tr_lambda will be created
  + resource "aws_lambda_function" "tr_lambda" {
      + architectures                  = (known after apply)
      + arn                            = (known after apply)
      + filename                       = "../modules/lambda/upload/lambda.zip"
      + function_name                  = "sample1_tr_lambda"
      + handler                        = "tr_lambda.handler"
      + id                             = (known after apply)
      + invoke_arn                     = (known after apply)
      + last_modified                  = (known after apply)
      + memory_size                    = 128
      + package_type                   = "Zip"
      + publish                        = false
      + qualified_arn                  = (known after apply)
      + reserved_concurrent_executions = -1
      + role                           = "arn:aws:iam::111111111111:role/sample1_tr_lambda_role"
      + runtime                        = "python3.8"
      + signing_job_arn                = (known after apply)
      + signing_profile_version_arn    = (known after apply)
      + source_code_hash               = "VWlQRjcX2al8ylHGGDu1dB2AUs17ONMmbne3piGvLYg="
      + source_code_size               = (known after apply)
      + tags_all                       = (known after apply)
      + timeout                        = 29
      + version                        = (known after apply)

      + environment {
          + variables = {
              + "TABLE_NAME" = "sample1_employee_list"
            }
        }

      + ephemeral_storage {
          + size = (known after apply)
        }

      + tracing_config {
          + mode = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

module.lambda.aws_lambda_function.tr_lambda: Creating...
module.lambda.aws_lambda_function.tr_lambda: Creation complete after 7s [id=sample1_tr_lambda]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: ./terraform.tfstate
  • コンソールから作成されたリソースを確認します。tr_lambda.py のコードも展開されていることが分かります。

  • Lambda のテストを行います。[Test] を押します。テスト用にイベント名を入力し、[保存]を押します。次に、再度[Test] を押します。
  • テストは成功し、Status はSucceeded となりました。レスポンスも期待通り、返りました。

ハンズオン4:API Gateway 作成

  • ハンズオン4 は、API Gateway を作成します。

  • modulesディレクトリ配下に、api-gateway ディレクトリ、api-gateway/api-gateway.tf を作成します。
HandsOn_AWS_Serverless
├── envs
│   ├── backend.tf
│   ├── main.tf
│   ├── provider.tf
│   ├── terraform.tfstate
│   └── terraform.tfstate.backup
└── modules
    ├── api-gateway
    │   └── api-gateway.tf
    ├── dynamodb
    │   └── dynamodb.tf
    ├── iam
    │   └── iam.tf
    └── lambda
        ├── lambda.tf
        ├── src
        │   └── tr_lambda.py
        └── upload
            └── lambda.zip
  • api-gateway.tf を編集します。
variable "prefix" {
  type = string
}

variable "tr_lambda-invoke-arn" {
  type = string
}

resource "aws_api_gateway_rest_api" "tr_api" {
  name = "${var.prefix}_tr_api"
}

resource "aws_api_gateway_method" "tr_api_get" {
  authorization = "NONE"
  http_method   = "GET"
  resource_id   = aws_api_gateway_rest_api.tr_api.root_resource_id
  rest_api_id   = aws_api_gateway_rest_api.tr_api.id
}

resource "aws_api_gateway_integration" "tr_api_get" {
  http_method             = aws_api_gateway_method.tr_api_get.http_method
  resource_id             = aws_api_gateway_rest_api.tr_api.root_resource_id
  rest_api_id             = aws_api_gateway_rest_api.tr_api.id
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = var.tr_lambda-invoke-arn
}

resource "aws_api_gateway_deployment" "tr_api" {
  depends_on = [
    aws_api_gateway_integration.tr_api_get
  ]
  rest_api_id = aws_api_gateway_rest_api.tr_api.id
  stage_name  = "test"
  triggers = {
    redeployment = filebase64("${path.module}/api-gateway.tf")
  }
}

output "tr_api-execution-arn" {
  value = aws_api_gateway_rest_api.tr_api.execution_arn
}
  • lambda.tf にAPI Gateway に関する記述を追記します。(追記とコメントしている2箇所になります)
variable "prefix" {
  type = string
}

variable "employee_list_table-name" {
  type = string
}

variable "tr_lambda_role-arn" {
  type = string
}

variable "tr_api-execution-arn" {               # 追記
  type = string                                 # 追記
}                                               # 追記

data "archive_file" "tr_lambda" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/upload/lambda.zip"
}

resource "aws_lambda_function" "tr_lambda" {
  filename         = data.archive_file.tr_lambda.output_path
  function_name    = "${var.prefix}_tr_lambda"
  role             = var.tr_lambda_role-arn
  handler          = "tr_lambda.handler"
  source_code_hash = data.archive_file.tr_lambda.output_base64sha256
  runtime          = "python3.8"
  timeout          = 29
  environment {
    variables = {
      TABLE_NAME = var.employee_list_table-name
    }
  }
}

resource "aws_lambda_permission" "tr_lambda_permit" {               # 追記
  statement_id  = "AllowAPIGatewayGetTrApi"                         # 追記
  action        = "lambda:InvokeFunction"                           # 追記 
  function_name = aws_lambda_function.tr_lambda.arn                 # 追記
  principal     = "apigateway.amazonaws.com"                        # 追記
  source_arn    = "${var.tr_api-execution-arn}/test/GET/"           # 追記
}                                                                   # 追記

output "tr_lambda-invoke-arn" {
  value = aws_lambda_function.tr_lambda.invoke_arn
}
  • main.tf を修正します。
  • lambda 側のtr_api-execution-arn には、先ほど api_gateway.tf に記述したoutput を指定します。
  • api_gateway 側のtr_lambda-invoke-arn には、先ほど lambda.tf に記述したoutput を指定します。
module "lambda" {
  source                = "../modules/lambda"
  prefix                = "sample1"
  employee_list_table-name  = module.dynamodb.employee_list_table.name
  tr_lambda_role-arn        = module.iam.tr_lambda_role-arn
  tr_api-execution-arn = module.api_gateway.tr_api-execution-arn            # 追記
}

module "api_gateway" {
  source                 = "../modules/api-gateway"
  prefix                = "sample1"
  tr_lambda-invoke-arn = module.lambda.tr_lambda-invoke-arn
}
  • terraform コマンドを実行します。main.tf のディレクトリで、以下のコマンドを実行します。terraform plan にエラーがなければ、terraform apply を実行します。
    • terraform init
    • terraform plan
    • terraform apply
  • API Gateway のリソースのリソースをデプロイします。
$ terraform apply
module.lambda.data.archive_file.tr_lambda: Refreshing state... [id=68d1ce2bad53ef6bcfa33c7157b6a910411e399e]
module.iam.aws_iam_role.tr_lambda_role: Refreshing state... [id=sample1_tr_lambda_role]
module.dynamodb.aws_dynamodb_table.employee_list: Refreshing state... [id=sample1_employee_list]
module.dynamodb.aws_dynamodb_table_item.employee_list_item: Refreshing state... [id=sample1_employee_list|EmployeeId||a00000110|]
module.iam.aws_iam_role_policy.tr_lambda_role_policy_policy: Refreshing state... [id=sample1_tr_lambda_role:sample1_tr_lambda_policy]
module.iam.aws_iam_role_policy_attachment.tr_lambda_role_policy_attach: Refreshing state... [id=sample1_tr_lambda_role-20220414154047163300000001]
module.lambda.aws_lambda_function.tr_lambda: Refreshing state... [id=sample1_tr_lambda]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.api_gateway.aws_api_gateway_deployment.tr_api will be created
  + resource "aws_api_gateway_deployment" "tr_api" {
      + created_date  = (known after apply)
      + execution_arn = (known after apply)
      + id            = (known after apply)
      + invoke_url    = (known after apply)
      + rest_api_id   = (known after apply)
      + stage_name    = "test"
      + triggers      = {
          + "redeployment" = "dmFyaWFibGUgInByZWZpeCIgewogIHR5cGUgPSBzdHJpbmcKfQoKdmFyaWFibGUgInRyX2xhbWJkYS1pbnZva2UtYXJuIiB7CiAgdHlwZSA9IHN0cmluZwp9CgpyZXNvdXJjZSAiYXdzX2FwaV9nYXRld2F5X3Jlc3RfYXBpIiAidHJfYXBpIiB7CiAgbmFtZSA9ICIke3Zhci5wcmVmaXh9X3RyX2FwaSIKfQoKcmVzb3VyY2UgImF3c19hcGlfZ2F0ZXdheV9tZXRob2QiICJ0cl9hcGlfZ2V0IiB7CiAgYXV0aG9yaXphdGlvbiA9ICJOT05FIgogIGh0dHBfbWV0aG9kICAgPSAiR0VUIgogIHJlc291cmNlX2lkICAgPSBhd3NfYXBpX2dhdGV3YXlfcmVzdF9hcGkudHJfYXBpLnJvb3RfcmVzb3VyY2VfaWQKICByZXN0X2FwaV9pZCAgID0gYXdzX2FwaV9nYXRld2F5X3Jlc3RfYXBpLnRyX2FwaS5pZAp9CgpyZXNvdXJjZSAiYXdzX2FwaV9nYXRld2F5X2ludGVncmF0aW9uIiAidHJfYXBpX2dldCIgewogIGh0dHBfbWV0aG9kICAgICAgICAgICAgID0gYXdzX2FwaV9nYXRld2F5X21ldGhvZC50cl9hcGlfZ2V0Lmh0dHBfbWV0aG9kCiAgcmVzb3VyY2VfaWQgICAgICAgICAgICAgPSBhd3NfYXBpX2dhdGV3YXlfcmVzdF9hcGkudHJfYXBpLnJvb3RfcmVzb3VyY2VfaWQKICByZXN0X2FwaV9pZCAgICAgICAgICAgICA9IGF3c19hcGlfZ2F0ZXdheV9yZXN0X2FwaS50cl9hcGkuaWQKICBpbnRlZ3JhdGlvbl9odHRwX21ldGhvZCA9ICJQT1NUIgogIHR5cGUgICAgICAgICAgICAgICAgICAgID0gIkFXU19QUk9YWSIKICB1cmkgICAgICAgICAgICAgICAgICAgICA9IHZhci50cl9sYW1iZGEtaW52b2tlLWFybgp9CgpyZXNvdXJjZSAiYXdzX2FwaV9nYXRld2F5X2RlcGxveW1lbnQiICJ0cl9hcGkiIHsKICBkZXBlbmRzX29uID0gWwogICAgYXdzX2FwaV9nYXRld2F5X2ludGVncmF0aW9uLnRyX2FwaV9nZXQKICBdCiAgcmVzdF9hcGlfaWQgPSBhd3NfYXBpX2dhdGV3YXlfcmVzdF9hcGkudHJfYXBpLmlkCiAgc3RhZ2VfbmFtZSAgPSAidGVzdCIKICB0cmlnZ2VycyA9IHsKICAgIHJlZGVwbG95bWVudCA9IGZpbGViYXNlNjQoIiR7cGF0aC5tb2R1bGV9L2FwaS1nYXRld2F5LnRmIikKICB9Cn0KCm91dHB1dCAidHJfYXBpLWV4ZWN1dGlvbi1hcm4iIHsKICB2YWx1ZSA9IGF3c19hcGlfZ2F0ZXdheV9yZXN0X2FwaS50cl9hcGkuZXhlY3V0aW9uX2Fybgp9"
        }
    }

  # module.api_gateway.aws_api_gateway_integration.tr_api_get will be created
  + resource "aws_api_gateway_integration" "tr_api_get" {
      + cache_namespace         = (known after apply)
      + connection_type         = "INTERNET"
      + http_method             = "GET"
      + id                      = (known after apply)
      + integration_http_method = "POST"
      + passthrough_behavior    = (known after apply)
      + resource_id             = (known after apply)
      + rest_api_id             = (known after apply)
      + timeout_milliseconds    = 29000
      + type                    = "AWS_PROXY"
      + uri                     = "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:111111111111:function:sample1_tr_lambda/invocations"
    }

  # module.api_gateway.aws_api_gateway_method.tr_api_get will be created
  + resource "aws_api_gateway_method" "tr_api_get" {
      + api_key_required = false
      + authorization    = "NONE"
      + http_method      = "GET"
      + id               = (known after apply)
      + resource_id      = (known after apply)
      + rest_api_id      = (known after apply)
    }

  # module.api_gateway.aws_api_gateway_rest_api.tr_api will be created
  + resource "aws_api_gateway_rest_api" "tr_api" {
      + api_key_source               = (known after apply)
      + arn                          = (known after apply)
      + binary_media_types           = (known after apply)
      + created_date                 = (known after apply)
      + description                  = (known after apply)
      + disable_execute_api_endpoint = (known after apply)
      + execution_arn                = (known after apply)
      + id                           = (known after apply)
      + minimum_compression_size     = -1
      + name                         = "sample1_tr_api"
      + policy                       = (known after apply)
      + root_resource_id             = (known after apply)
      + tags_all                     = (known after apply)

      + endpoint_configuration {
          + types            = (known after apply)
          + vpc_endpoint_ids = (known after apply)
        }
    }

  # module.lambda.aws_lambda_permission.tr_lambda_permit will be created
  + resource "aws_lambda_permission" "tr_lambda_permit" {
      + action        = "lambda:InvokeFunction"
      + function_name = "arn:aws:lambda:us-east-1:111111111111:function:sample1_tr_lambda"
      + id            = (known after apply)
      + principal     = "apigateway.amazonaws.com"
      + source_arn    = (known after apply)
      + statement_id  = "AllowAPIGatewayGetTrApi"
    }

Plan: 5 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

module.api_gateway.aws_api_gateway_rest_api.tr_api: Creating...
module.api_gateway.aws_api_gateway_rest_api.tr_api: Creation complete after 1s [id=ii1wux2em1]
module.api_gateway.aws_api_gateway_method.tr_api_get: Creating...
module.lambda.aws_lambda_permission.tr_lambda_permit: Creating...
module.api_gateway.aws_api_gateway_method.tr_api_get: Creation complete after 1s [id=agm-ii1wux2em1-xo03v7lk60-GET]
module.api_gateway.aws_api_gateway_integration.tr_api_get: Creating...
module.api_gateway.aws_api_gateway_integration.tr_api_get: Creation complete after 0s [id=agi-ii1wux2em1-xo03v7lk60-GET]
module.api_gateway.aws_api_gateway_deployment.tr_api: Creating...
module.lambda.aws_lambda_permission.tr_lambda_permit: Creation complete after 1s [id=AllowAPIGatewayGetTrApi]
module.api_gateway.aws_api_gateway_deployment.tr_api: Creation complete after 1s [id=q6drc4]

Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: ./terraform.tfstate

ハンズオン5:結合テスト

  • クライアントから API Gateway にリクエストを投げ、レスポンスが期待通り返ることを確認します。

  • ブラウザもしくは curl コマンドを送り、レスポンスを確認します。

$ curl -vv https://ii1wux2em1.execute-api.us-east-1.amazonaws.com/test
*   Trying 143.204.73.9...
* TCP_NODELAY set
* Connected to ii1wux2em1.execute-api.us-east-1.amazonaws.com (143.204.73.9) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Unknown (8):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS Unknown, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Client hello (1):
* TLSv1.3 (OUT), TLS Unknown, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.execute-api.us-east-1.amazonaws.com
*  start date: Apr 12 00:00:00 2022 GMT
*  expire date: May 11 23:59:59 2023 GMT
*  subjectAltName: host "ii1wux2em1.execute-api.us-east-1.amazonaws.com" matched cert's "*.execute-api.us-east-1.amazonaws.com"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
* Using Stream ID: 1 (easy handle 0x55b666780600)
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
> GET /test HTTP/2
> Host: ii1wux2em1.execute-api.us-east-1.amazonaws.com
> User-Agent: curl/7.58.0
> Accept: */*
>
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* TLSv1.3 (OUT), TLS Unknown, Unknown (23):
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
< HTTP/2 200
< content-type: application/json
< content-length: 80
< date: Fri, 15 Apr 2022 14:15:19 GMT
< x-amzn-requestid: 77c200eb-cc72-40ad-911d-9f213b8cd879
< x-amz-apigw-id: QoCeoGg_oAMF1kQ=
< x-amzn-trace-id: Root=1-62597df7-73259f1424f36bce45bbb9f9;Sampled=0
< x-cache: Miss from cloudfront
< via: 1.1 3fa2b0ecfcbadde1c11e5ba46e1b6308.cloudfront.net (CloudFront)
< x-amz-cf-pop: NRT12-C2
< x-amz-cf-id: BlFKWtuDmYC_3h5mG8QRul6BKpwD5nsAgWrH_GN3HE0JXrgMeqaKrw==
<
* TLSv1.3 (IN), TLS Unknown, Unknown (23):
* Connection #0 to host ii1wux2em1.execute-api.us-east-1.amazonaws.com left intact
{"Id": "a00000110", "Firstname": "Taro", "Lastname": "Momo", "Office": "Nagoya"}

元記事はこちら

https://oji-cloud.net/2022/01/21/post-7028/
著者:新川貴章


アイレットなら、AWS で稼働するサーバーを対象とした監視・運用・保守における煩わしい作業をすべて一括して対応し、経験豊富なプロフェッショナルが最適なシステム環境を実現いたします。AWS プレミアコンサルティングパートナーであるアイレットに、ぜひお任せください。

AWS 運用・保守サービスページ:
https://cloudpack.jp/service/aws/maintenance.html

その他のサービスについてのお問合せ、お見積り依頼は下記フォームよりお気軽にご相談ください。
https://cloudpack.jp/contact/form/