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していきましょう!