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

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

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

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

Rustをスクリプト言語としてコンパイルせずに呼び出す

コンパイルはする。

crystal run や go run や rdmd や runghc のやうに、事前ビルドのコマンドを走らせる事無くインタープリタのやうに實行する機能が Rust には無いらしいので自作した。

#!ruby -rdigest/sha2
o="/tmp/#{Digest::SHA512.file __FILE__}-#{File.basename __FILE__}";IO.popen(["rustc","-","-o",o],"r+"){|io|io.write DATA.read}unless File.exist? o;system o,*ARGV
__END__
// Rustコード

Rust です(〃l _ l)

このやうにして、

#!ruby -rdigest/sha2
o="/tmp/#{Digest::SHA512.file __FILE__}-#{File.basename __FILE__}";IO.popen(["rustc","-","-o",o],"r+"){|io|io.write DATA.read}unless File.exist? o;system o,*ARGV
__END__
use std::env;

fn main() {
  let args: Vec<String> = env::args().collect();
  println!("Hello World! {}", args[1]);
}

./sample.rs momonga

として使へる。

標準ライブラリが充實してゐる事を願ふ。

追記 20161028

毎回 Rust コードにコピペするのは面倒なので矢張り外部スクリプトにした。此れ

#!/usr/bin/env ruby -rdigest/sha2 -rtmpdir
o = File.join Dir.tmpdir, "#{Digest::SHA512.file ARGV[0]}-#{File.basename ARGV[0]}"
IO.popen(['rustc', '-', '-o', o], 'r+') { |io| io.write IO.read(ARGV[0], encoding: Encoding::UTF_8) } unless File.exist? o
system o, *ARGV[1..-1]

を rust-run 等の名で PATH の通ったディレクトリに保存し、

#!/usr/bin/env rust-run
fn main() {
  println!("Hello World!");
}

とすれば使へる。

Serverless Framework (1.0) でAWS LambdaとAWS API Gatewayを設定する、單純なサンプルを書いた

Serverless Framework (1.0) で AWS Lambda と AWS API Gateway を設定する、單純なサンプルを書いた。

github.com

説明は全部 GitHub の方に書いてある。

f:id:Kureduki_Maari:20161019175426p:plain

サーバーレスアーキテクチャの好い所は、インフラの管理とアプリケーションの處理とを分離出來る事だ。インフラはアプリケーションの内容を考へずに抽象的な構成を管理する。アプリケーションはインフラの事を考へずに抽象的に處理を行なふ。アプリケーションとしては抽象化せざるを得ない。インフラがアプリケーションの内容やプロセスの狀態を決して考慮して呉れないからだ。

Terraform が餘りにも大變なので Serverless Framework は癒やしである。Serverless Framework に就いては以下に雜なスライドを上げた事が在った。

Serverless Framework (1.0.0-beta2) が在ります - c4se 記:さっちゃんですよ ☆

Gitの要らないlocal & remoteブランチを撰んで消すツールを作った

git cleanup-branch

で消せる。

github.com

feature ブランチ等、Git の merge 濟みのブランチが local にも remote にも溜まってゆく。注意してゐれば溜まらないのだが、注意を怠ると溜ってゆく。git branch -a --merged で一覧し、消したいブランチを撰び、git branch -d BRANCHgit push REMOTE :BRANCH で消してゆく事は出來る。毎回此れを遣る譯だ。嫌だ。

ツールに任せる事にしやう。消してはいけないブランチも在るから、撰ぶ手間は減らせないが、一氣に撰んで後は見ておく丈に出來る。

デモ

GitHub 上の release からバイナリを下載[download]する。或いは GitHub から HEAD のソースコードを clone して、make する。HEAD のバージョンは NCurses を使ってゐるので、Crystal と libncurses が要る。git-cleanup-branch と云ふバイナリが手に入るから、此れを PATH の通ったディレクトリに置く。Git は git-x と云ふバイナリが在れば此れを git x とサブコマンド風に呼べる。ので git-cleanup-branchgit cleanup-branch としても實行できる。Git リポジトリに移動して此れを實行すると、目的を達せられるだらう。

Crystal で開發したり、對話的な CUI を作ったり、對話的な CUI を Cucumber でテストしたりと、面白かった。

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.