FaaSのIacに関して「Zappa」で立ち向かってみる
この記事はRecruit Engineers Advent Calendar 2019 の19日目の記事です。
こんにちは @kazu0716 です
サーバサイド開発、インフラ/ミドルの構築・運用、データ分析/基盤構築・運用、クラウドセキュリティに関して考えることをしています
最近は、オンプレ環境からのPublicクラウドへの移行をもっぱらやってます
今回も、ノリでアドベントカレンダーなんて参加しました
昨年同様ギリギリになって書き出してます。
そして、前回のアドベントカレンダーから、何も書いてないのが今年最大の反省点ですね・・・
ネタはいっぱいあるので、来年こそは!!
Issues of FaaS in our worksplace
lambdaでpythonでなくとも、javascriptやrubyでさくっと何かの処理を自動化したりしたいなーってことよくありますよね
個人で何かやろうってときもそうだし、職場でも無数のlambdaが動いていたりします
lambda や cloud function は FaaS(Function as a Service) に分類されるそうですが
さくっと作ってそのまま放置とかされると、結構厄介かなというのがあり、きちんと管理したいなと思うことがよくありました
最近だと、lamdbaのランタイムのEOL等で、既存の動いているlambdaを見たときに「なんやねんこれ!どこにソースあんねん!」ってなりましたww
- こういうのとか

- こういうのとか

私はPythonが好きで、何やるにもだいたいpythonでやるので、pythonでlambda作るとき/管理するのに便利なツールないかなと探していました
What's Zappa
Zappaとはpythonをlambdaにdeplyする便利ツールです
API-GatewayやCloudWatchも一緒にdeployしてくれたりと便利なのですが、個人的には lambdaのIac(Infrastructure as a code) ができるのが良いなと思ってます
Iac(Infrastructure as a code)の詳細に関しては、リンク先のwikiを読んでいただくのが良いかと思いますが、私がやりたきことは
- lambdaやAPI-Gateway、CludWatchの設定情報をgithubでソースコード管理したい(AWS key等のCredentialな情報は除く)
- 設定変更をし、deployやupdateをコマンド1行とかで手軽にしたい
- s3にソースをuplodaして、AWSコンパネからボタンをぽちぽちしたくない。。。
を満たしてくれる、この便利ツールを早速使ってみようと思います
How to use Zappa
ZappaでFlaskとAPI-Gatewayとかってのは、既に記事があるので今回は、CloudWatchでCron的にPython Scriptを実行してみようと思います
What's you make
監視のSciprtでcronで定期的にクローリングして、その結果をDatadogに飛ばすものを作ろうと思います
FaceBookやGooglePlayが落ちてないか、もしくは担当サービスのStaticページが落ちてないかの監視をするために作りました
Deploy lambda by using Zappa
とっても簡単です。Zappaサイコー!!
- AWS Commandをインストールし、アクセスキー等の認証情報を設定します
$ aws configure AWS Access Key ID [****************4HN5]: AWS Secret Access Key [****************c78Q]: Default region name [ap-northeast-1]: Default output format [None]:
- zappaコマンドでs3経由でlambdaにdeployする(参考)
# 初回時 $ zappa deploy dev # 既に起動している場合はupdateする $ zappa update dev
- cloudwatchのログも確認できる
$ zappa tail dev
Settings Zappa
Deployは簡単なのですが、Zappaではlambdaと何かの組み合わせでを細かい設定含め、1コマンドdeplyすることができます
詳細はREADMEを読んでいただくのが良いと思うのですが、今回はこんな感じの設定をしました
$ cat zappa_settings.json
{
"dev": {
"lambda_description": "外部監視用スクリプト",
"app_function": "app.lambda_handler",
"aws_region": "ap-northeast-1",
"profile_name": "default",
"project_name": "external-monitoring",
"runtime": "python3.7",
"s3_bucket": "$hoge$",
"events": [
{
"function": "app.lambda_handler",
"expression": "rate(5 minutes)"
}
],
"apigateway_enabled": false,
"keep_warm": false,
"log_level": "INFO",
"memory_size": 512,
"timeout_seconds": 300,
"manage_roles": true
}
}
ポイントはeventsの設定で特定関数が5分ごとに実行する設定をしている ということになるかと思います
今回はrole等は設定してないですが、この場合問題なく動くのですが、deplyするプロジェクト名を使って自動的に作成してしまうので
zappaでdeployするたびにroleが量産される ということが起こるので、少し注意が必要かと思います
# Advanced settingsから抜粋 "role_name": "MyLambdaRole", // Name of Zappa execution role. Default <project_name>-<env>-ZappaExecutionRole. To use a different, pre-existing policy, you must also set manage_roles to false. "role_arn": "arn:aws:iam::12345:role/app-ZappaLambdaExecutionRole", // ARN of Zappa execution role. Default to None. To use a different, pre-existing policy, you must also set manage_roles to false. This overrides role_name. Use with temporary credentials via GetFederationToken.
Python Script
特に、面白いところはないと思いますwww
可読性を最大にするため、極力謎記法は使ってないです
Credentialな情報は環境変数に設定しているので、その設定は初回のみAWSのコンパネでぽちぽちしています
アクセス先のURL等の情報は config.ini を作って外出ししています
$ cat app.py
# -*- coding: utf-8 -*-
import json
import logging
import os
from configparser import ConfigParser
from threading import Thread
import requests
from bs4 import BeautifulSoup
from datadog import api, initialize
config = ConfigParser()
config.read(os.path.join(os.path.dirname(__file__), './config.ini'), 'UTF-8')
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# NOTE: Datadog SDKの初期設定
options = {'api_key': os.environ['APIKEY'],
'app_key': os.environ['APPKEY']}
initialize(**options)
def create_dd_event(title, text, tags):
if api.Event.create(title=title, text=text, tags=tags, alert_type="error")['status'] != "ok":
raise Exception("Datadogへのイベント送信が失敗しました。")
else:
logger.info("datadogにイベント通知しました。")
def monitor_enmusubi():
"""
サイトの外部監視
"""
try:
for url in config["Enmusubi"]:
code = requests.get(config["Enmusubi"][url]).status_code
if code != 200:
create_dd_event(
"サイト({0}, ステータスコード: {1})".format(url, code),
config["Enmusubi"][url],
["env:external", "service:enm", "target:enmusubi"]
)
except Exception as e:
raise e
def monitor_facebook():
"""
Facebookの障害監視
"""
try:
if requests.get(config['Facebook']['Url']).json()['current']['health'] != 1:
create_dd_event(
"Facebook",
config['Facebook']['DashboardUrl'],
["env:external", "service:enm", "target:facebook"]
)
except Exception as e:
raise e
def monitor_app_store():
"""
AppleStoreの障害検知
"""
# TODO: 動的ページなのでSeleniumが必要になるので後回し
pass
def monitor_datadog():
"""
Datadogサービスの障害検知
"""
try:
html = BeautifulSoup(requests.get(config['Datadog']['Url']).text)
if "error" in ["error" for status in html.find_all("span", class_="component-status") if "Operational" not in status.text]:
create_dd_event(
"Datadog",
config['Datadog']['Url'],
["env:external", "service:enm", "target:datadog"]
)
except Exception as e:
raise e
def lambda_handler(event, context):
"""
CloudWatchに定期実行される関数
Parameters
----------
event: dict, required
API Gateway Lambda Proxy Input Format
# api-gateway-simple-proxy-for-lambda-input-format
Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
context: object, required
Lambda Context runtime methods and attributes
Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html
"""
try:
results = []
# NOTE: 監視用関数をマルチスレッドで実行
for task in [monitor_datadog, monitor_facebook, monitor_app_store, monitor_enmusubi]:
t = Thread(target=task)
t.start()
results.append(t)
# NOTE: 全てのスレッドが完了したことを確認
for result in results:
result.join()
except Exception as e:
logging.error("エラーが発生しました。" + str(e.args))
終わりに
lambdaとかcloud functionは便利ですが、雑に作らずこういうツール使って、githubにコード残してくれると離任の際に引き継ぎも楽なので、小さなところもIacしていきましょう!