c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programmingに分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡

音樂はSoundCloud等バラバラの場所に公開中です。申し訳ないがlinkをたどるなどして探してください。

考察は現在は主に此のblogで公表中です。

programmingは、ひろくみせるものはGitHubで、個人的なものはBitBucketで開発中です。

c4se

Terraformのmoduleを書く

AWSの設定をTerraformで行ってゐる。マネージメントコンソールからからやると簡單な設定であってもAWS CLIやTerraformからやらうとすると大變なものに成る場合も在る。コンソールでは一つに纏められてゐるものが個々別のオブジェクトとして其々操作しなければならないものや、裏で自動で作られるものも明示して作らねばならないものが在り、設定が膨れる。大變だ。一回だけなら膨れても何でも問題に成らないのだが、似たオブジェクトを沢山作らねばならぬと成れば、大變だ。否、似たものであれば纏められる筈である。Terraformにはモジュール機能が在る。

以前API GatewayとLambdaを連携させてTerraformから設定た (API GatewayとLambdaの組み合はせでリリースするバージョンを制御する)。

此所で作ったものは、

有り體に此れ丈がAPIを作る度に必要に成る。死にたく成る。抽象化して呉れ。

書いてゐると判るが、以下の纏まりは重複する。Lambdaはaws_lambda_function毎に、

  • aws_lambda_function
  • aws_lambda_alias stagingとprod
  • aws_lambda_permission stagingとprod

が。API Gatewayではaws_api_gateway_method毎に、

が繰り返す。此れ等を其々モジュールに纏めてみる。

出來上がったものが以下に在るのでコードを読めば解ると云ふ話も在る。→ https://github.com/ne-sachirou/c4se-infra/tree/31bcdcaf8bc11c210163087c01b4087115d9eccd/terraform/modules

Terraformモジュールの構成

Terraformのモジュールは、Terraformのコードを分離する機能だ。モジュールを呼び出す時變數を與へる事が出來る。即ち、變數を切り替へる丈で重複させられるコードは、同じモジュールで扱へる。引數を與へTerraformのコードを吐く函數の樣なものだ。

terraform-community-modulesに例が在る。

モジュールのファイルは、入力・本体・出力に分けて以下の樣にされる事が多い。

  • variables.tf モジュールで使ふ變數は全て此所で定義する
  • main.tf
  • outputs.tf モジュールの外から見える値は全てoutput記述で書かねばならない。其れを全て此所に書く

例として以下の樣に使へる "a" と云ふモジュールを書く。

module "a_of_sample" {
  source = "./a"
  var1 = "sample"
}

resource b {
  attr = "${module.a_of_sample.out1}"
}

./a ディレクトリに三っつのファイルを作る。

# a/variables.tf
variable "var1" {}
# a/main.tf
resource some_resource {
  attr = "${var.var1}"
}
# a/outputs.tf
output "out1" {
  value = "${var.var1}"
}

ファイルの分け方は通常のTerraformと同じく任意なので、もっと細かく分けてもよい。variables.tfとoutputs.tfは此う置いておくのが他人から見易いであらう。

モジュールを使ったTerraformを実行する時には、planを作る前に terraform get を行なふ。

terraform get
terraform plan --out=terraform.tfplan
terraform apply terraform.tfplan

GitHub上等に在るモジュールを使ってゐれば .terraform/ にcloneされ、ローカルにモジュールを作ってあれば .terraform/ 下にシンボリックリンクが張られる。.terraform/ は.gitignoreする。

モジュールに限らずTerraformでは条件分岐は未だ出來ない。ループは少しは出來る。以下で實例を下[もと]に詳細を見てゆく。

API Gateway向けLambdaのモジュール

  • aws_lambda_function
  • aws_lambda_alias stagingとprod
  • aws_lambda_permission stagingとprod

を纏めてみる。繰り返すコードをただ抽出するだけで出來る。

引數は、

variable "description" {}

variable "filename" {}

variable "function_name" {}

variable "handler" {}

variable "memory_size" {}

variable "prod_function_version" {
  default = "$LATEST"
}

variable "role" {}

variable "runtime" {}

variable "timeout" {}

でよいであらう。出力は、他からLambdaを呼び出すのにfunction_nameが要るから、

output "function_name" {
  value = "${var.function_name}"
}

と成らう。するとメインは此う成る。

resource "aws_lambda_function" "function" {
  description = "${var.description}"
  filename = "${var.filename}"
  function_name = "${var.function_name}"
  handler = "${var.handler}"
  memory_size = "${var.memory_size}"
  role = "${var.role}"
  runtime = "${var.runtime}"
  source_code_hash = "${base64sha256(file(var.filename))}"
  timeout = "${var.timeout}"
}

resource "aws_lambda_alias" "staging" {
  depends_on = ["aws_lambda_function.function"]
  description = "staging"
  function_name = "${var.function_name}"
  function_version = "$LATEST"
  name = "staging"
}

resource "aws_lambda_alias" "prod" {
  depends_on = ["aws_lambda_function.function"]
  description = "production"
  function_name = "${var.function_name}"
  function_version = "${var.prod_function_version}"
  name = "prod"
}

resource "aws_lambda_permission" "staging_apigateway" {
  action = "lambda:InvokeFunction"
  depends_on = ["aws_lambda_function.function"]
  function_name = "${var.function_name}"
  principal = "apigateway.amazonaws.com"
  qualifier = "staging"
  statement_id = "${var.function_name}_staging_apigateway"
}

resource "aws_lambda_permission" "prod_apigateway" {
  action = "lambda:InvokeFunction"
  depends_on = ["aws_lambda_function.function"]
  function_name = "${var.function_name}"
  principal = "apigateway.amazonaws.com"
  qualifier = "prod"
  statement_id = "${var.function_name}_prod_apigateway"
}

此れ等を ./modules/aws_lambda_function_for_apigateway に保存しやう。呼び出しは此う成る。殆どaws_lambda_functionの内容だけで済む。

module "aws_lambda_function_for_apigateway_sample" {
  source = "./modules/aws_lambda_function_for_apigateway"
  description = "Sample."
  filename = "./sample.zip"
  function_name = "sample"
  handler = "handler.handler"
  memory_size = 128
  prod_function_version = "1"
  role = "${aws_iam_role.sample.arn}"
  runtime = "python2.7"
  timeout = 3
}

Lambdaコードの ./sample.zip やLambda実行權限の aws_iam_role.sample.arn は今迄通り用意する必要が在る。本番デプロイしてpublish versionした度に prod_function_version を上げてゆく。

Lambda向けAPI Gatewayのメソッドのモジュール

さて、先のLambdaを呼び出すAPI Gatewayを作ってみる。

は今迄通り作っておく。aws_api_gateway_resource.sample_sample/sample を指すとする。

をモジュールにしやう。

モジュールの前に、aws_api_gateway_method_responseとaws_api_gateway_integration_responseをエラーのステータスコードの個數分作るのはループするのが有効である。ループは、洞のresourceにも在るcount属性に依って行なへる。例へば、

resource a "a_with_count" {
  count = 2
  attr = "${count.index}"
}

は、

resource a "a_with_count_0" {
  attr = 0
}

resource a "a_with_count_1" {
  attr = 1
}

と、resource名を除き等しい。又モジュールに配列は渡せないから、文字列分割函數split函數を使って、

variable "status_codes" {
  default = "400,500"
}

resource a "a_with_status_codes" {
  count = "${length(split(",", var.status_codes))}"
  status_code = "${element(split(",", var.status_codes), count.index)}"
}

とループ出來る。何とかして呉れ。

ループも出來る樣に成ったのでモジュール化する。マッピングも全て與へなければならないので引數はいささか多い。成るべく省略出來るやうdefaultを定義しておく。

variable "authorization" {
  default = "NONE"
}

variable "aws_region" {}

variable "aws_account_id" {}

variable "error_status_codes" {}

variable "function_name" {}

variable "http_method" {}

variable "integration_request_parameters_in_json" {
  default = "{}"
}

variable "integration_request_templates" {
  default = <<EOF
{ "stage": "$stageVariables.stage" }
EOF
}

variable "integration_response_parameters_in_json" {
  default = "{}"
}

variable "integration_response_templates" {
  default = "$input.json('$')"
}

variable "method_request_parameters_in_json" {
  default = "{}"
}

variable "method_response_parameters_in_json" {
  default = "{}"
}

variable "resource_id" {}

variable "rest_api_id" {}

此れでもう大体見える。出力 (output) は不要だらう。メインは大体長い。長いからモジュール化したので仕方が無い。

resource "aws_api_gateway_method" "method" {
  authorization = "${var.authorization}"
  http_method = "${var.http_method}"
  request_parameters_in_json = "${var.method_request_parameters_in_json}"
  resource_id = "${var.resource_id}"
  rest_api_id = "${var.rest_api_id}"
}

resource "aws_api_gateway_integration" "integration" {
  depends_on = ["aws_api_gateway_method.method"]
  http_method = "${var.http_method}"
  integration_http_method = "POST"
  request_parameters_in_json = "${var.integration_request_parameters_in_json}"
  request_templates = {
    "application/json" = "${var.integration_request_templates}"
  }
  resource_id = "${var.resource_id}"
  rest_api_id = "${var.rest_api_id}"
  type = "AWS"
  uri = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${var.aws_region}:${var.aws_account_id}:function:${var.function_name}:$${stageVariables.stage}/invocations"
}

resource "aws_api_gateway_method_response" "method_response_200" {
  depends_on = ["aws_api_gateway_method.method"]
  http_method = "${var.http_method}"
  resource_id = "${var.resource_id}"
  response_parameters_in_json = "${var.method_response_parameters_in_json}"
  rest_api_id = "${var.rest_api_id}"
  status_code = "200"
}

resource "aws_api_gateway_integration_response" "integration_response_200" {
  depends_on = [
    "aws_api_gateway_integration.integration",
    "aws_api_gateway_method_response.method_response_200"
  ]
  http_method = "${var.http_method}"
  resource_id = "${var.resource_id}"
  response_parameters_in_json = "${var.integration_response_parameters_in_json}"
  response_templates = {
    "application/json" = "${var.integration_response_templates}"
  }
  rest_api_id = "${var.rest_api_id}"
  status_code = "200"
}

resource "aws_api_gateway_method_response" "method_response_error" {
  count = "${length(split(",", var.error_status_codes))}"
  depends_on = ["aws_api_gateway_method.method"]
  http_method = "${var.http_method}"
  resource_id = "${var.resource_id}"
  rest_api_id = "${var.rest_api_id}"
  status_code = "${element(split(",", var.error_status_codes), count.index)}"
}

resource "aws_api_gateway_integration_response" "integration_response_error" {
  count = "${length(split(",", var.error_status_codes))}"
  depends_on = ["aws_api_gateway_integration.integration"]
  http_method = "${var.http_method}"
  resource_id = "${var.resource_id}"
  response_templates = {
    "application/json" = <<EOF
{ "error": "$input.path('$.errorMessage').substring(5)" }
EOF
  }
  rest_api_id = "${var.rest_api_id}"
  selection_pattern = "${element(split(",", var.error_status_codes), count.index)}: .+"
  status_code = "${element(split(",", var.error_status_codes), count.index)}"
}

aws_api_gateway_method_response.method_response_erroraws_api_gateway_integration_response.integration_response_error がcountを使ってゐる。エラーメッセージは、「500: Some error.」等 \d{3}: が先頭に附くのを前提した。Terraformはモジュールに配列や連想配列を渡せないので此うしないとモジュールに出來ない。

此れ等を ./modules/aws_api_gateway_method_for_lambda_function に保存する。呼び出し例として GET /sample を作ってみる。

module "aws_api_gateway_method_research_random_id_get" {
  source = "./modules/aws_api_gateway_method_for_lambda_function"
  aws_region = "${var.aws_region}"
  aws_account_id = "${var.aws_account_id}"
  error_status_codes = "400,500"
  function_name = "${module.aws_lambda_function_for_apigateway_sample.function_name}"
  http_method = "GET"
  resource_id = "${aws_api_gateway_resource.sample_sample.id}"
  rest_api_id = "${aws_api_gateway_rest_api.smaple.id}"
}

マッピングがdefaultで定義したもので濟めば此の樣に短い。

やりましたね!

餘談

LambdaとAPI GatewayServerless Framworkに載せ替へたので上記モジュールは不要と化しましたがわたしは元氣です。

Switch API Gateway from Terraform to Serverless Framwork.