CloudWatch Events + Lambda + CircleCI + Apex で Lambda をサーバレスに実行 & デプロイする

先週,プロダクトで開発合宿を企画して実施してきた.今回のテーマは「開発効率/運用効率の改善」だったので,僕はインフラチームとして先輩と一緒に開発に取り組んだ.お題は「AWS コスト削減」にした.開発合宿で開発したツールで学んだことを簡単にまとめておこうと思う.

Time-based approach

AWS Well-Architected Framework の「コスト最適化」に「タイムベースアプローチ」と呼ばれるベストプラクティスがある.そんな難しいものではなく,単純に開発系インスタンスを夜間や週末に止めることでコストを最適化できるし,クラウドネイティブな設計にもできるというものだ.意外と開発系インスタンスが週末も起動されているチームも多いのではないだろうか?

Time-based approach Examples of a time-based approach include following the sun, turning off Development and Test instances over the weekend, following quarterly or annual schedules (e.g., Black Friday).

今回は「AWS コスト削減」という課題に対して「タイムベースアプローチをサーバレスアーキテクチャで実現する」という手法で解決することをゴールに決めた(実際にはもう少し他の施策も含めてコスト削減をした).

アーキテクチャ図

今回は以下のようなアーキテクチャで実現した.ポイントは大きく3点ある.

  • スケジューリング実行を CloudWatch Events で行った
    • Jenkins を使いたくなかった
  • インスタンスの起動停止を Lambda で行った
    • 専用のインスタンスを立てたくなかった
  • Lambda Function のデプロイを CircleCI + Apex で行った
    • Jenkins を使いたくなかった

f:id:kakku22:20170217220344p:plain

CloudWatch Events

CloudWatch Events を使うと,cron 形式のスケジューリング設定をして Lambda を起動することができる.注意点は2点で「cron 式が AWS 独自なこと」と「UTC 設定なこと」と言える.

例えば「日本時間で “火水木金土” の 00:00」と設定する場合,CloudWatch Events の cron 式は以下のようになる.

0 15 ? * MON-FRI *

  • UTC 表記なので 15:00 になる
  • 曜日指定だと1日戻るため “月火水木金” を指定する必要がある
  • 日(3番目)と月(4番目)の両方に * を指定することができず,片方は ? にする必要がある
  • 6番目に「製造年」を設定する必要がある(必要ないから * にした)

特に ? の部分が意味不明で,設定にハマった.ドキュメントを読んだらちゃんと書いてあったけど,なんなんだろう.「製造年」もよくわからなかった.

cron 式の Day-of-month フィールドおよび Day-of-week フィールドに同じ 値を指定することはできません。 一方のフィールドに値を指定すると、もう一方のフィールドで ? (疑問符) を使用する必要があります。

docs.aws.amazon.com

ちなみに CloudWatch Events から Lambda を実行するときに

  • { "action": "start" }
  • { "action": "stop" }

というイベントを発火させて,Lambda 側でハンドリングするように実装した.

Lambda

あまり難しいことはしてなく,以下の記事などを参考にして,Node で API を叩いてインスタンスを起動したり,停止したりした.

唯一面倒だったのは Zabbix の考慮で,インスタンスを落とすとアラートが鳴ってしまうため,Zabbix のメンテナンスを API 経由で ON/OFF できるようにする必要があった.ここは先輩に書いてもらった.

dev.classmethod.jp

あとは Lambda Function に割り当てる IAM ロールで,以下のように必要最低限に抑えた.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:Describe*",
                "ec2:StartInstances",
                "ec2:StopInstances",
                "logs:*"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

Apex

今回 Lambda のデプロイもサーバレスにしたかったため,Apex を使った.Apex は Lambda のデプロイに特化していることもあって非常にシンプルで,Serverless のように複雑ではないところが良かった.また Lambda 以外の AWS リソースを操作したい場合は,Terraform を使うということで,無理に手広くサポートしていないところも逆に好印象だった.

github.com

ドキュメントも非常に充実していて,特に困ることは無かった.主に以下のコマンドを使った.

  • apex deploy
  • apex invoke
  • apex logs
  • apex metrics

apex.run

ドキュメントにも載っているが,コマンド補完もすることができるので,便利だった.また Apex に必要最低限な IAM ポリシーもドキュメントに書いてあるため,すぐに使うことができた.

_apex()  {
  COMPREPLY=()
  local cur="${COMP_WORDS[COMP_CWORD]}"
  local opts="$(apex autocomplete -- ${COMP_WORDS[@]:1})"
  COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
  return 0
}

complete -F _apex apex

Apex で1点だけ残念だったのは,CloudWatch Events の設定は Terraform で行う必要があることだった.方針として正しいとは思うけど,CloudWatch Events だけでも良いから Apex で登録できると良いのになとは思った.今回は CloudWatch Events のためだけに Terraform を使う気にはならなかったため,手動で設定して Lambda と紐付けた.

github.com

CircleCI

雑に書いた.ポイントは master にマージしたときに apex deploy をするところ.これで CircleCI から Lambda のデプロイができるようになった.CircleCI にクレデンシャルを登録することを忘れずに!

machine:
  timezone: Asia/Tokyo

dependencies:
  post:
    - curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sudo sh
    - apex version

deployment:
  master:
    branch: master
    commands:
      - apex deploy

まとめ

  • AWS Well-Architected Framework を見るとクラウドネイティブなベストプラクティスが学べる
  • CloudWatch Events + Lambda + CircleCI + Apex で Lambda をサーバレスに実行 & デプロイできるようにした
  • 開発合宿は最高に燃える!

関連記事

kakakakakku.hatenablog.com