FirehoseのHTTP配信機能でMackerelにメトリックを投稿する

先日、Amazon Kinesis Data Firehose が任意の HTTP エンドポイントに対しての配信機能をサポートしました。

aws.amazon.com

従来の S3 / Redshift / Elasticsearch Service などのマネージドなリソースに対してデータを配信する機能に加えて、自分で作った HTTP(s) のエンドポイントに対しても Firehose からデータを投げ込んでくれる機能です。

Amazon Kinesis Data Firehose now supports data delivery to Datadog

既に Datadog や New Relic へもマネージドでデータを配信できていて、それらに送りたいデータはとりあえず Firehose に流しておけば、実際の送信やバッファリングやリトライは Firehose が面倒を見てくれるという嬉しい状態ですね。

で、ここで思ったのは「Mackerelにも対応したい」

…ということで作ってみました。

github.com

Firehose に Mackerel のサービスメトリックを流して、この HTTP endpoint を設定すると Mackerel API に送信してくれるものです。

fujiwara/ridge を使うことで、ひとつのバイナリで単体での Go の http server としても動きますし、Lambda (API Gateway / ALB) としても動くようになっています。

動かしてみる

普通にビルドしてどこかのサーバーで動かしてもよいのですが、Firehose の配信先は https が必須で多少面倒かもしれません。ここではとりあえず Amazon API Gateway で動かすことにします。Go が必要です。

$ GOARCH=amd64 GOOS=linux make
$ ROLE_ARN=arn:aws:iam::123456789012:role/lambda make deploy

Linux (Lambda) 用のバイナリをビルドして、適当な IAM Role (権限は AWSLambdaBasicExecution のみで十分です) を指定して fujiwara/lambroll でデプロイできる Makefile がサンプルとして入っています。function の定義は以下で、Role 以外に必要な設定は特にありません。

{
  "Description": "A PoC of Firehose HTTP endpoint for Mackerel",
  "FunctionName": "firehose-http-endpoint-for-mackerel",
  "Handler": "firehose-http-endpoint-for-mackerel",
  "MemorySize": 128,
  "Role": "{{ must_env `ROLE_ARN` }}",
  "Runtime": "go1.x",
  "Timeout": 30,
  "TracingConfig": {
    "Mode": "PassThrough"
  }
}

デプロイした Lambda を使う API Gateway の HTTP Integration を設定します。Route は $default をこの関数に向けるだけです。

f:id:sfujiwara:20200803104848p:plain
API Gateway HTTP integration

Firehose の Destination 設定は以下の通りにしてください。

  • HTTP endpoint URL: 作成した API Gateway の URL。path は /service (https://xxxx/service
  • Content encoding: Disabled
  • Access key: Mackerel の API Key
  • Parameters:
    • service: メトリックを投稿する Mackerel のサービス名
  • Buffer interval: 何秒でも動作はしますがラグが少ない方がよいので 60

Firehose に投稿したいサービスメトリックを送ります。

$ aws firehose put-record --delivery-stream-name mackerel-endpoint \
    --record "Data=Base64にしたサービスメトリックの値"

値の形式は JSON {"name":"metric.name","time":1596382129,"value":27759} とテキスト形式(mackrel plugin が出力するタブ区切りのもの) metric.name 27759 1596382129 に対応しています。

あとは Firehose が60秒ごとにまとめて API Gateway を叩いて来るタイミングで、Mackerel API へ送信されるはずです。

うれしいこと

直接 Mackerel API を叩いたり mkr コマンドなどで送信するのと比べて、Firehose を介することで嬉しいことはなにかというと…

  • Mackerel のメンテナンス時、送信失敗時のリトライ、バッファリングを Firehose に任せられる
    • 最大 7200 秒までリトライ可能
    • 失敗したデータは S3 にバックアップも可能
  • Mackerel API key を送信元に配る必要がない
    • Firehose 側に設定されていれば OK
    • 送信元には Firehose に対してのアクセス権が与えられていればよい
  • mkr / mackerel-agent などのツールがなくても aws-cliSDK のみで送信できる

ぐらいでしょうか。mackerel-agent を EC2 で動かしていれば再送やバッファリングは自動で行われますが、直接 API を叩いて値を送信する場合は失敗時のケアを自分でやらないとメトリックが欠落してしまいます。特に Lambda などから送信する場合は状態を維持するのが面倒なので、欠落の心配が減るのがいいですね。

ご要望

Firehose http endpoint を使って Mackerel へメトリックを送るコードを書いてみましたが、これはあくまで PoC ということで、Mackerel の公式で http endpoint がサポートされてほしいなと思います。そうすると Firehose の設定で公式 endpoint に向けるだけで済みますし、API を直接アクセスする以外のメトリック送信手段として AWS のユーザーには嬉しいんじゃないでしょうか。ご検討いただければ幸いです。

複数の Terraform state を結合する tfstate-merge を書いて20個以上の state を大統一した

あるところに、ひとつの Web サービスの本番環境が 20 個以上の Terraform state で管理されているリポジトリがありました。

おもに AWS 上で動いている、大変年代物かつ巨大なモノリスのサービスです。最初は何もコード管理されていない状態から徐々に Terraform を導入したのですが、その際に「一度に全部管理はできないから、徐々に Terraform 化していこう」という方針で管理を始めたところ…

  • 新機能を追加するのでそのリソースを別 state で管理
  • 改修があるのでそれらのリソースを別 state で管理

を繰り返すうちに、中に数個程度しかリソースがない state がどんどん増殖し、いつの間にやら20個以上になってしまったという状態です。ちなみにリソース定義は合計で 500 程度ありました。

さすがにここまで分割されてしまうと相互参照はできないし、VPC / Subnet / Security Group などの共通で使っているリソースはいちいち data resource として定義するのも面倒だし、何度も同じことを書かないといけないし、と利点がなさ過ぎたため、統合を考えました。

tfstate-merge

github.com

最近 tfstate-lookup というコマンド/ライブラリを書いて、JSON 形式の tfstate (version 4) にはちょっと仲良くなっていたので、こんな感じで結合したらいけるんじゃないかなと雑に書いてみたのが tfstate-merge コマンドです。

これは 2つの state ファイルを引数に取り、結合するコマンドです。Ruby 製(コアモジュール縛り)なので、手元に ruby があれば実行できると思います。

$ tfstate-merge aaa.tfstate bbb.tfstate

とすると、bbb.tfstate の中の resource をすべて aaa.tfstate にマージした物を標準出力に出力します。

何も考えずに結合してしまうと、同一の名前で実体は別のリソースがあった場合に壊してしまうため、簡単なチェック機能を持っています。

  • 通常の resource (state では mode=managed) については、同じ名前があったらエラー
  • data resource (mode=data) については、同じ名前の場合は attributes が同一でなければエラー
  • terraform version が揃っていなければエラー (念のため)
  • state version 4 (terraform v0.12) のみ対応

エラーがなければ、最後に aaa.tfstate の serial 属性を ++ して出力します。

tfstate-merge による統合作業

実際にどのような作業を行うかの例を説明します。

tfstate-merge は remote state には対応していないため、一旦結合したい state file をローカルに持ってきた上で作業します。

作業は結合先の .tf が存在している場所で行います。

$ tfstate-merge -i.bak terraform.tfstate xxx.tfstate
resource data.aws_vpc.main is duplicated

2つの tfstate を結合します。オプションで -i.bak とすると、作業前の terraform.tfstate を terraform.tfstate.bak として保存し、結合後の内容を terraform.tfstate に上書きします。

data resource が重複している場合は警告が標準エラー出力に出力されるので、その後の調整の参考にしてください。運が悪いことに managed resource name が重複している場合は、一度 terraform state mv でリソース名を変更してから再挑戦するのがよいでしょう。

xxx.tfstate で管理している .tf をコピーしてきて、その中の terraform, provider の定義、重複している data resource を削除します。

そして terraform plan を行うと、No changes. Infrastructure is up-to-date. になるはずです。(ならなければ頑張って調整しましょう…)

最後に、追加した .tf をリポジトリにコミットし、tfstate を remote へ書き戻したら作業完了です。

まとめ

  • 複数の Terraform state file を結合する tfstate-merge を書きました
  • 過度に分割しすぎてしまった state をまとめたい場合にご利用ください

ecspresso と lambroll で tfstate からの値参照に対応した

Amazon ECS デプロイツール ecspressoAWS Lambad デプロイツール Lambroll で、設定ファイル中に tfstate (Terraform state file)の値を検索して使えるようにしました。

これまで面倒だったところ

ecspresso ではサービスとタスク定義、lambroll では関数定義を JSON で用意します。 その JSON の中では {{ env "FOO" }} のような記法で環境変数を展開してから読み込む機能があります。

デプロイや環境ごとに可変になる部分を、デプロイ時に環境変数によって外から与えることで定義ファイルをいちいち更新しなくていいという作りになっています。

しかし、たとえば ecspresso のサービス定義には以下のように、subnet, security group, LB target group などの ID が必要になります。

{
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "subnets": [
        "subnet-03476ecc408d3e5e9",
        "subnet-027dd89f0c35a6323"
      ],
      "securityGroups": [
        "sg-0feded3d26f6360e1"
      ]
    }
  },
  "loadBalancers": [
    {
      "containerName": "app",
      "containerPort": 80,
      "targetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:0123456789012:targetgroup/app/d8b8346d68b86bf5"
    }
  ]
}

これらのリソースの管理は ecspresso / lambroll の管轄外のため、ファイルにべた書きすると謎の IDになってしまいます。

別途なんらかの方法で環境変数に設定してもいいのですが、個人的にはこれらのリソースは Terraform で管理しているので、Terraform のリソース名を参照できると便利なのでは!と思い立ち、実装してみました。

Terraform は、リソース定義に使用する .tf ファイルと実体のリソースを結びつける中間状態を terraform.tfstate という JSON で管理しています。State - Terraform by HashiCorp

このファイルを検索すれば、リソース名から実体のIDを検索できます。

tfstate 参照でどのよう変わるか

ecspresso

ecspresso (v0.14.0〜) で設定ファイルに plugins で tfstate の読み込み先を追加します。

terraform.tfstate はローカルファイルのほか、S3 の remote state にも対応しています (.terraform/terraform.tfstate に保存されているもの)

plugins:
- name: tfstate
  config:
    path: terraform.tfstate

これで、サービス定義を以下のように記述できます。

{
  "launchType": "FARGATE",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "subnets": [
        "{{ tfstate `aws_subnet.az-a.id` }}",
        "{{ tfstate `aws_subnet.az-b.id` }}"
      ],
      "securityGroups": [
        "{{ tfstate `aws_security_group.default.id` }}"
      ]
    }
  },
  "loadBalancers": [
    {
      "containerName": "app",
      "containerPort": 80,
      "targetGroupArn": "{{ tfstate `aws_lb_target_group.app.arn` }}"
    }
  ]
}

対応する terraform の .tf ファイルはたとえば以下のように記述されていて、これを terraform apply すると実体のリソースが作成/更新され、tfstate には実体のリソースが持っているIDなどの属性が保存されています。

resource "aws_subnet" "az-a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.0.1/24"
  availability_zone = "ap-northeast-1a"

  tags = {
    Name = "az-a"
  }
}

リポジトリで人間が管理するファイルには謎の ID が出現しなくなりました。

lambroll

lambroll (v0.5.0〜) では、実行時に --tfstate オプションで tfstate のファイルのパスを指定します。

{
  "FunctionName": "hello",
  "Handler": "index.handler",
  "MemorySize": 128,
  "Role": "{{ tfstate `aws_iam_role.lambda.arn` }}",
  "Runtime": "nodejs12.x",
  "Timeout": 3,
  "TracingConfig": {
    "Mode": "PassThrough"
  }
}
$ lambroll --tfstate terraform.tfstate deploy --log-level debug --dry-run
2020/04/02 11:37:41 [info] lambroll v0.5.1
2020/04/02 11:37:41 [info] starting deploy function hello2
2020/04/02 11:37:41 [info] creating zip archive from .
2020/04/02 11:37:41 [debug] -rw-rw-r--        145 2019-10-28T09:43:31Z index.js
2020/04/02 11:37:41 [info] zip archive wrote 278 bytes
2020/04/02 11:37:41 [info] updating function configuration **DRY RUN**
2020/04/02 11:37:41 [debug]
{
  FunctionName: "hello2",
  Handler: "index.handler",
  MemorySize: 128,
  Role: "arn:aws:iam::0123456789012:role/hello_lambda_role",
  Runtime: "nodejs12.x",
  Timeout: 3,
  TracingConfig: {
    Mode: "PassThrough"
  }
}

使っている技術

tfstate を読むために、tfstate-lookup というパッケージ/コマンドを作りました。

github.com

コマンドとして使うと tfstate からリソース名 (+属性名) で値を抽出できますし、Go のパッケージとしても利用できます。

$ tfstate-lookup -s .terraform/terraform.tfstate aws_vpc.main.id
vpc-1a2b3c4d

$ tfstate-lookup aws_vpc.main
{
  "arn": "arn:aws:ec2:ap-northeast-1:123456789012:vpc/vpc-1a2b3c4d",
  "assign_generated_ipv6_cidr_block": false,
  "cidr_block": "10.0.0.0/16",
  "default_network_acl_id": "acl-001234567890abcde",
  "default_route_table_id": "rtb-001234567890abcde",
  "default_security_group_id": "sg-01234567890abcdef",
  "dhcp_options_id": "dopt-64569903",
  "enable_classiclink": false,
  "enable_classiclink_dns_support": false,
  "enable_dns_hostnames": true,
  "enable_dns_support": true,
  "id": "vpc-1a2b3c4d",
  "instance_tenancy": "default",
  "ipv6_association_id": "",
  "ipv6_cidr_block": "",
  "main_route_table_id": "rtb-001234567890abcde",
  "owner_id": "123456789012",
  "tags": {
    "Name": "main"
  }
}

実は terraform 単体でも output を定義すれば terraform output ... で表示はできるのですが、検索したいリソースについていちいち output を定義する必要があってだいぶ面倒だったので、自前で tfstate を parse するコードになっています。

内部では go-jq を使って、クエリを組み立てて json を検索するだけ、というシンプルな作りです。go-jq 便利! github.com

まとめ

ecspresso v0.14、lambroll v0.5 で、設定ファイルから tfstate の値を参照できるようになりました。

Terraform で AWS のリソースを管理しつつ、ECS や Lambda の管理には ecspresso / lambroll を利用している、という環境では便利かと思います。どうぞご利用ください。

SRE NEXT 2020 基調講演で「Webサービスを1日10回デプロイするための取り組み」を話しました

SRE NEXT 2020 という、国内初の SRE に特化したカンファレンスで基調講演をご依頼いただいて、「Webサービスを1日10回デプロイするための取り組み」を話してきました。

sre-next.dev

speakerdeck.com

これまでいろいろ発表はしてきましたが、基調講演というのは初めてで、さて何を話したほうがいいのか悩みました。とはいえ自分は基本的に技術でなにかを解決する話しかできないので、かなり現場寄りの話です。いつもどおり。

時間の関係でいろいろ省略した部分があるのですが、懇親会で聞かれた CircleCI に移行して増えたコストの話については LobiのCIをJenkinsからCircleCIに移行したはなし - KAYAC engineers' blog にあるとおり

数万円程度だったCI関連費用は、およそ2倍の十数万円になりました。

ぐらいなので、Jenkins サーバのお守りに掛かる諸々のコストがなくなったことや、体験の向上を考えると見合った投資だろうと思っています。


一日参加して、どれもいい発表だったのですが、印象に残った発表の感想を軽く書いておきます。

  • 基調講演 分散アプリケーションの信頼性観測技術に関する研究
    • まさに NEXT を見据えた研究で、オープニングに相応しい内容でした
  • パフォーマンスを最大化するための SRE のオンボーディング事例
    • どんなに人を集めても力を発揮できる受け入れ体制がないと何もならないので、組織的にそこを改善していくのは重要ですね
  • ZOZO MLOps のチームリーディングとSRE(Engineering)
    • まずひとつリリースまで持っていく、次にそれがまぐれでないことを証明するために複数で成功させる、というチームとしてのミッションが明確で、こういうリーダーの下で働けたら楽しいだろうな、と思わされました
    • 最後は技術で殴る必要がある (物事を解決できないで理想だけ語っても意味がない) というのもまさにその通り

懇親会でもいろいろな人と話ができて楽しかったですね。でもみんなもうちょっと野菜食べたほうがいいと思います。(この写真は懇親会終了時)

最高に楽しいカンファレンスでした。運営の皆さん、参加者の皆さん、ありがとうございました!

lambroll と bash layer で気軽に Lambda shell script を実行する

先日えいやと書いた AWS Lambda のデプロイツール lambroll ですが、これと公開済みの bash layer を使うとかなり気軽に(雑な) shell script を Lambda で実行できて体験がよかったので書いておきます。

AWS Lambda のミニマルなデプロイツール lambroll を書いた - 酒日記 はてな支店


今回はとある理由で ECS のサービス内のタスクを定期的に入れ換えたかったので、aws ecs update-service を一発実行する、という要件。やりたいことはただ aws-cli を一発叩くだけでできるのに、Lambda 化するには Node やら Go やらで SDK 使ってコードを書いて…というのがめんどくさいこと、よくあると思います。

まず適当にディレクトリを掘って、そこで lambroll init で初期化します。

$ mkdir ecs-task-replacer
$ cd ecs-task-replacer
$ lambroll init --function-name ecs-task-replacer
2019/11/13 22:16:21 [info] lambroll v0.2.1
2019/11/13 22:16:23 [info] function ecs-task-replacer is not found
2019/11/13 22:16:23 [info] creating .lambdaignore
2019/11/13 22:16:23 [info] creating function.json
2019/11/13 22:16:23 [info] completed

関数が存在しないので、次のようなスケルトンの function.json が生成されます。

{
  "FunctionName": "ecs-task-replacer",
  "Handler": "index.handler",
  "MemorySize": 128,
  "Role": "arn:aws:iam::123456789012:role/YOUR_LAMBDA_ROLE_NAME",
  "Runtime": "nodejs10.x",
  "Timeout": 3
}

これを bash layer GitHub - gkrizek/bash-lambda-layer: Run Bash scripts in AWS Lambda via Layers を使うように書き換えます。 Layers, MemorySize, Runtime, Role, Timeout を適当に指定。この例のように aws-cli を使う場合、Memory が少ない(CPUも比例して少ない) と aws コマンドを起動するのに大変時間がかかるので、1コアをフルに使える 1.5GB を指定するのがよいです。

{
  "FunctionName": "ecs-task-replacer",
  "Handler": "index.handler",
  "Layers": [
    "arn:aws:lambda:ap-northeast-1:744348701589:layer:bash:8"
  ],
  "MemorySize": 1536,
  "Role": "arn:aws:iam::123456789012:role/lambda-function",
  "Runtime": "provided",
  "Timeout": 30
}

関数本体は index.sh というファイルに、event を jq で処理して aws-cli を叩くだけのを書きます。今回は event で ECS cluster と service を指定できるように {"cluster": "CLUSTER_NAME", "service": "SERVICE_NAME"} という構造にしてみました。(決め打ちでいいならハードコードしちゃってもいいですね)

function handler {
    set -e

    EVENT=$1
    SERVICE=$(echo "$EVENT" | jq -r .service)
    CLUSTER=$(echo "$EVENT" | jq -r .cluster)

    aws ecs update-service \
        --cluster "$CLUSTER" \
        --service "$SERVICE" \
        --force-new-deployment

    echo "{\"success\": true}" >&2
}

lambroll deploy でデプロイします。

$ lambroll deploy
2019/11/14 08:40:52 [info] lambroll v0.2.1
2019/11/14 08:40:52 [info] starting deploy function ecs-task-replacer
2019/11/14 08:40:54 [info] creating zip archive from .
2019/11/14 08:40:54 [info] zip archive wrote 342 bytes
2019/11/14 08:40:54 [info] updating function configuration
2019/11/14 08:40:54 [info] updating function code
2019/11/14 08:41:00 [info] deployed version 2
2019/11/14 08:41:00 [info] updating alias set current to version 2
2019/11/14 08:41:01 [info] alias updated
2019/11/14 08:41:01 [info] completed

手元から実行するには lambroll invoke を使って、標準入力に event を与えます。--log-tail を付けると実行した Lambda のログ(の最後の4KB)が出力されます。

$ echo '{"cluster":"default", "service":"example"}' | lambroll invoke --log-tail
2019/11/14 08:46:35 [info] lambroll v0.2.1
{"success": true}
2019/11/14 08:46:57 [info] StatusCode:200 ExecutionVersion:$LATEST
s reached a steady state."
            },
            {
                "id": "8304a25b-8329-4099-903f-677013206fe8",
                "createdAt": 1572800779.798,
                "message": "(service example) has reached a steady state."
            },
...(略)
        "schedulingStrategy": "REPLICA",
        "enableECSManagedTags": false,
        "propagateTags": "NONE"
    }
}
END RequestId: c821f023-0af2-48e5-ae21-c6f381e9ec65
REPORT RequestId: c821f023-0af2-48e5-ae21-c6f381e9ec65  Duration: 20484.06 ms   Billed Duration: 20600 ms   Memory Size: 128 MB Max Memory Used: 103 MB Init Duration: 44.74 ms
2019/11/14 08:46:57 [info] completed

おしまい。

定期実行するには CloudWatch Events でポチポチとスケジュールを定義してやればよいですね。

最初に IAM role を用意するところと、init で生成された function.json を書き換えるところが少しだけ手間ですが、そのあとは index.sh を書き換えて deploy して invoke するのに1コマンドずつ叩くだけです。

個人的には雑に cron で shell script を定期実行したいだけなのに、コード書いて Lambda 用意してって億劫だなあ、みたいなストレスがだいぶ減る気がします。どうぞお試しください。

AWS Lambda のミニマルなデプロイツール lambroll を書いた

3行で

  • シンプル/ミニマルな Lambda のデプロイツール lambroll を書いてるよ
  • Lambda API 以外は極力触らないやつです
  • 既存 function の移行も簡単です

開発の経緯

AWS Lambda を管理、デプロイするのに数年来 Apex を使っていましたが、最近更新がないと思っていたら案の定というか、残念ながら No longer maintained となってしまいました。

で、代替を探したのですが…

SAM も Serverless もどちらも結局 CloudFormation がお出ましになるし、自分としては関連リソースは別に Terraform で管理しているし、悩ましいなあ、という状況でした。

とはいえ aws-cli だけでやるのもちょっと面倒なことがあり、仕方ないので先週金曜にえいやっと書き始めたのが lambroll です。

github.com

"lamb roll"で検索すると美味しそうですね。

f:id:sfujiwara:20191101010814p:plain

lambroll は何をして、何をしないのか

README に書いてありますが、こんなかんじです。

やること

  • zip archive の作成
  • Lambda 関数の設定とコード、alias、タグの作成、更新

やらないこと

  • 周辺リソースの作成と管理
    • IAM Role とかトリガとか API Gateway とか…
  • Lambda 実行環境用のバイナリ(ネイティブ拡張)のビルド

Lambda の API で叩ける範囲以外は基本しません (S3 へファイル上げる以外)。Runtime や Layer もなにも関知しないので、zip に入れるものは自分でいい感じにビルドしてください。個人的には最近 Go / bash layer しか使わないのでビルドに困ってないというのもあります。

Quick Start

インストールは mac なら brew install fujiwara/tap/lambroll でできます。Linux はリリースバイナリを適当に落としてください。

既存の Lambda 関数を lambroll init で持ってきて管理できます。--download を付けると $LATEST で動いている zip もダウンロードしてきます。

$ mkdir hello
$ cd hello
$ lambroll init --function-name hello --download
2019/10/26 01:19:23 [info] function hello found
2019/10/26 01:19:23 [info] downloading function.zip
2019/10/26 01:19:23 [info] creating function.json
2019/10/26 01:19:23 [info] completed

$ unzip -l function.zip
Archive:  function.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      408  10-26-2019 00:30   index.js
---------                     -------
      408                     1 file

$ unzip function.zip
Archive:  function.zip
 extracting: index.js

$ rm function.zip

あとは lambroll deploy すると、

  1. カレントディレクトリを zip にまとめる
    • .lambdaignore ファイルで除外指定できます
  2. 関数の設定、コード、タグを更新
  3. alias current を設定

が行われてデプロイされます。簡単ですね!

rollback のための alias 設定は Apex のと互換仕様のつもりです。

command

現在 init, list, deploy, rollback, delete, invoke, archive があります。

function.json

Lambda 関数の設定は function.json に書かれているので、設定を変えたいときはそこをいじってください。構造は CreateFunction API + Tags です。

kayac/go-config による環境変数の展開記法が使えるので、可変にしたい部分に {{ must_env }} {{ env }} 記法を入れておくと、lambroll 実行時の環境変数を埋め込むことができます。stg と prod を別関数にする、などに使ってください。

{
  "Description": "hello function for {{ must_env `ENV` }}",
  "Environment": {
    "Variables": {
      "BAR": "baz",
      "FOO": "{{ env `FOO` `default for FOO` }}"
    }
  },
  "FunctionName": "{{ must_env `ENV` }}-hello",
  "Handler": "index.js",
  "MemorySize": 128,
  "Role": "arn:aws:iam::123456789012:role/hello_lambda_function",
  "Runtime": "nodejs10.x",
  "Tags": {
    "Env": "{{ must_env `ENV` }}"
  },
  "Timeout": 5
}

実際どうなの

一昨日から社内で dog fooding していて、いままで Apex や SAM でデプロイされていたものをどんどこ移行していますが、なにしろものが単純なので普通に動いてます (動かない理由がない)。

Apex の後継とはおこがましいので言いませんが、こういうシンプル (not easy) なやつをお好みなかたはどうぞご利用ください。

ecspresso で設定ファイル生成と CodeDeploy による Blue/Green デプロイに対応した

ecspresso は筆者が開発している Amazon ECS 用のデプロイツールです。

github.com

最近 AWS DevDay Tokyo 2019 の「第1回 AWS Fargate かんたんデプロイ選手権」でも紹介していただいて、気をよくして最近あれこれ機能追加をしていたのでそのご紹介です。

speakerdeck.com

分かりやすい導入記事をクラスメソッドさんの blog で書いていただきましたのでこちらもどうぞ。

dev.classmethod.jp

設定ファイル生成機能

ecspresso init で、既存の ECS サービスの情報を引っこ抜いて定義ファイルを生成し、そのまま ecspresso deploy できるようになりました。(v0.11)

README の Quick Start にもあるように、region, cluster, service, config を指定して (適切な credential がある状態で) 実行すると、以下の3ファイルをカレントディレクトリに生成します。

  • ecspresso の設定ファイル
  • ECS サービスの定義ファイル
  • サービスに設定されているタスク定義ファイル

特に動作しているサービスに影響はしないので、稼働中のサービスに対して実行しても問題ありません。

$ ecspresso init --region ap-northeast-1 --cluster default --service myservice --config config.yaml
2019/10/12 01:31:48 myservice/default save service definition to ecs-service-def.json
2019/10/12 01:31:48 myservice/default save task definition to ecs-task-def.json
2019/10/12 01:31:48 myservice/default save config to config.yaml

その後、そのまま deploy を行うと、新しいタスク定義を登録してサービスを更新し、デプロイが行われます。簡単!

$ ecspresso deploy --config config.yaml

実運用するには、タスク定義ファイルに {{ must_env "IMAGE_TAG" }} のような記法でデプロイごとに更新されるであろう部分に環境変数を展開するように記述しておき、実行時の環境変数設定でタスクを更新するのがお勧めです。

他のツールや、AWSコンソールからポチポチで作ってしまった ECS サービスやタスク定義を、簡単にコードによる管理に落とし込むことができます。もし ecspresso の使用をやめるとしても特に何も起こらないので、気軽にお試しいただけます。

CodeDeploy による Blue/Green デプロイに対応

これまで ECS の機能によるローリングデプロイのみに対応していましたが、Blue/Green デプロイを行いたいという要望があったので、AWS CodeDeploy によるデプロイにも対応しました。(v0.12)

{
  "deploymentController": {
    "type": "CODE_DEPLOY"
  },

サービス定義で deploymentController.type = "CODE_DEPLOY" となっている場合、ecspresso deploy を実行すると CodeDeploy に新しい Deployment を作成します。その後の進行は CodeDeploy 側で行われます。

$ ecspresso deploy --config config.yaml --rollback-events DEPLOYMENT_FAILURE
2019/10/15 22:47:07 myService/default Starting deploy
Service: myService
Cluster: default
TaskDefinition: myService:5
TaskSets:
   PRIMARY myService:5 desired:1 pending:0 running:1
Events:
2019/10/15 22:47:08 myService/default Creating a new task definition by ecs-task-def.json
2019/10/15 22:47:08 myService/default Registering a new task definition...
2019/10/15 22:47:08 myService/default Task definition is registered myService:6
2019/10/15 22:47:08 myService/default desired count: 1
2019/10/15 22:47:09 myService/default Deployment d-XXXXXXXXX is created on CodeDeploy
2019/10/15 22:47:09 myService/default https://ap-northeast-1.console.aws.amazon.com/codesuite/codedeploy/deployments/d-XXXXXXXXX?region=ap-northeast-1

ログはこんな感じになり、最後に CodeDeploy の AWS Console の URL が出力されています。端末で実行していて、かつ open コマンドが存在する場合には、open $url を実行することでブラウザの画面が開きます。

ecspresso create でサービスを新規作成する場合には、CodeDeploy のリソース (Application, DeploymentGroup) は特に作成はしないので、なんらかの方法で (コンソールポチでも aws cli でも Terraform でも CloudFormation でもお好みで) 各自作成してください。

ecspresso deploy 時にはサービスに対応する CodeDeploy Application / DeploymentGroup を自動的に見つけて、それに対して新規デプロイを発行します。


ということで、ecspresso の最近の新機能の紹介でした。ECS のデプロイ、タスク定義管理に悩んでいる方がいらっしゃいましたら、どうぞご利用ください。