LambdaのエラーログをPython3でSlackに飛ばしたい
概要
Lambdaに吐いたログを監視して、エラーを検知したらSlackに通知したい。
ここでいうと"[ERROR]~"みたいな。
ググると、先人たちがたくさんやってるけど、自分の学習用振り返りとして。
TL;DR
- SNSを作成
- CloudWatchログ メトリクスフィルター作成
- CloudWatchアラームを設定
- Slack通知Lambdaを作る
手順
SNS 作成
Lambdaの実行するためにSNSを作成する。
名前だけであとは特に何もせず作成。
CloudWatchログ メトリクスフィルター作成
この"[ERROR].*"が出たら、Slack通知がしたい。
メトリクスフィルターを作成する。
[ERROR]をマッチさせるよう「"[ERROR]"」にする。(ダブルクォートしないと完全一致じゃなくなる。)
[ERROR]が出ているログストリームで「パターンをテスト」をして、マッチしたらOK。
「フィルター名」はログストリーム内のメトリクスフィルターの名前なので、適当でいい(と思う)。
「メトリクス名前空間」と「メトリクス名」は、CloudWatchメトリクスで監視するときの名前になるので、サービス名とメトリクス名とかにした方がいいかもしれない。
「メトリクス値」は検知したときにCloudWatchメトリクスに記録される値なので、検知数にしておく。
CloudWatchアラームを設定
出来たら、メトリクスフィルターリンクをクリックして、CloudWatchメトリクスを確認しに行く。
メトリクスがちゃんと取れていたら、OK。
ベルマークを押して、CloudWatchアラームを作成する。
閾値や監視間隔はおのおので。
予め作っておいたSNSトピックを選択して、作成。
アラーム名は「alert-${Lambdaファンクション名}」にする(いろいろあってこうしておきたい)
監視する側はこれでOK。
Slack通知Lambdaを作る
Python3でロールは適当なロールを選択
ソース
ほぼ、写経。Lambdaファンクション名が欲しかったのとSlackメッセージのフォーマットを整えたかったので、そこだけ追記。
CloudWatch上のエラーログをslackに通知する - Qiita
import boto3 import calendar import datetime import json import logging import os import re import slackweb from base64 import b64decode from urllib.request import Request, urlopen from urllib.error import URLError, HTTPError logger = logging.getLogger() logger.setLevel(logging.INFO) # The base-64 encoded, encrypted key (CiphertextBlob) stored in the kmsEncryptedHookUrl environment variable ENCRYPTED_HOOK_URL = os.environ['kmsEncryptedHookUrl'] # The Slack channel to send a message to stored in the slackChannel environment variable SLACK_CHANNEL = os.environ['slackChannel'] HOOK_URL = "https://" + boto3.client('kms').decrypt(CiphertextBlob=b64decode(ENCRYPTED_HOOK_URL))['Plaintext'].decode('utf-8') #抽出するログデータの最大件数 OUTPUT_LIMIT=5 #何分前までを抽出対象期間とするか TIME_FROM_MIN=5 client = boto3.client('logs') def lambda_handler(event, context): message = json.loads(event['Records'][0]['Sns']['Message']) metricfilters = client.describe_metric_filters( metricName = message['Trigger']['MetricName'] , metricNamespace = message['Trigger']['Namespace'] ) function_name = metricfilters['metricFilters'][0]['logGroupName'].replace('/aws/lambda/', '') timeto = datetime.datetime.strptime(message['StateChangeTime'][:19] ,'%Y-%m-%dT%H:%M:%S') + datetime.timedelta(minutes=1) u_to = calendar.timegm(timeto.utctimetuple()) * 1000 #開始時刻は終了時刻のTIME_FROM_MIN分前 timefrom = timeto - datetime.timedelta(minutes=TIME_FROM_MIN) u_from = calendar.timegm(timefrom.utctimetuple()) * 1000 response = client.filter_log_events( logGroupName = metricfilters['metricFilters'][0]['logGroupName'] , filterPattern = metricfilters['metricFilters'][0]['filterPattern'], startTime = u_from, endTime = u_to, limit = OUTPUT_LIMIT ) logger.info("response : " + str(response)) for event in response['events']: postText = ''' {logStreamName} {message} '''.format( logStreamName=str(event['logStreamName']), message=str(event['message'])).strip() url = "https://ap-northeast-1.console.aws.amazon.com/lambda/home?region=ap-northeast-1#/functions/" + function_name attachments = [] attachment = { "color": "#ff3333", "title": f"Error Log find at {function_name}", "title_link": url, "fields": [ { "title": "Reason", "value": postText } ] } attachments.append(attachment) slack = slackweb.Slack(url=HOOK_URL) response = slack.notify(channel=SLACK_CHANNEL, attachments=attachments)
Lambdaの環境変数
SlackのフックURLとチャンネルを登録する
フックURLは暗号化する。
しない場合はLambdaソースでdecryptしてるところを消して直接変数投入でいいと思う。
SNSのサブスクリプションにLambdaを登録する
赤枠部分に↑で作ったLambdaファンクションを入れてあげる。
再度、Lambdaを見に行って、トリガーにSNSが設定されていればOK。
テスト
適当に作ったhelloファンクションにログを仕込む。
import boto3 import json import logging import os def lambda_handler(event, context): logger = logging.getLogger() logger.setLevel(logging.INFO) logger.error("error 1") logger.error("error 2") logger.error("error 3") return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda!') }
helloのファンクションのログが出力されたことを確認。
Slackに通知が飛べば完成。
文言はもっといい感じにできると思う。
Creating rich message layouts | Slack
VPC内にEIP固定のTransfer for SFTPが立てたい話
日本語版と英語版のマニュアルが違う(ニュアンスとかじゃなくて内容が)
おそらく日本語版の方が古いとおもう。
基本は英語版を読んだ方がいいと思う。
読んでも分からなかったので、手を動かして理解したけど
分からなかった日本語版、VPCエンドポイントは今選べない(多分、ドキュメントが古い)
手順
EIPを取得
Transfer for SFTP構築
VPC hosted→Internet Facingを選択、前述で取得したEIPをアタッチする
カスタムドメインはおのおの設定する
日本語版のドキュメントだと、VPCエンドポイントを作成しろって書かれているけど、Transfer構築時に自動で作られる。
ので、Transfer構築ボタンをポチったら、VPCエンドポイント画面で待っていればOK
SecurityGroupはVPCのデフォルトSecurityGroupが割り当てられるので、変更する。
構築完了後、digを引いて自分の設定したEIPが返ってくればOK
結局何が分かりにくかったのか
日本語版の下記の手順が不要だった。
おそらく日本語版ドキュメントリリース後にアップデートされて、自動でVPCエンドポイントが作られるようになったんだと思う。
つまり、ドキュメントは英語版読もうね。ってことだと思う。
Nginx: Cookieから特定のデータだけログ出力したい
ログ量かなり増えそう
xxx.xxx.36.106 - - [21/Oct/2019:16:30:58 +0900] "GET / HTTP/1.1" 200 14828 "https://hoge-hogeo.com/search/hoge/fuga" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36" cookie[_ga=GA1.2.xxxxxxxxx.xxxxxxxxxx; _fbp=fb.1.xxxxxxxxx.xxxxxxxxx; _gcl_au=1.1.xxxxxxxxx.xxxxxxxxx; _gcl_aw=GCL.xxxxxxxxx.EAxxxxxxxxxKm8_3xxxxxxxxxEAAxxxxxxxxxE; _gac_UA-xxxxxxxxx-32=1.1xxxxxxxxx.xxxxxxxxx; _ga=GA1.3xxxxxxxxx; USER_TYPE=0; login=0&xxxxxxxxx; _session_id=e86d040431736e4ffac39d9e8cc9858e; _gid=GA1.2.xxxxxxxxx.xxxxxxxxx; __juicer_sesid_9i3nsdfP_=1xxxxxxxxx-xxxxxxxxx5; __juicer_session_referrer_9i3nsdfP_=xxxxxxxxx-83c4-xxxxxxxxx___https%253A%252F%252Fhoge-hogeo.com%252Fsearch%253Fhoge%253Dfuga%2526order_type%253D0; _dc_gtm_UA-xxxxxxxxx=1; _td_global=xxxxxxxxx-xxxxxxxxx; __juicer_sesid_9i3nsdfP_=xxxxxxxxx-xxxxxxxxx-xxxx-xxxxxxxxx; ___o2u_o2id=xxxxxxxxx-xxxxxxxxx; _gid=GA1.3.xxxxxxxxx.xxxxxxxxx; _gat_UA-xxxxxxxxx=1; __juicer_session_referrer_9i3nsdfP_=xxxxxxxxx-xxxxxxxxx-xxxxxxxxx___https%253A%252F%252Fhoge-hogeo.com%252F; _td=xxxxxxxxx-axxxxxxxxx]
配列っぽいから、特定のデータだけ取り出せないかな。
session_idだけ出してみる。
log_format frima '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$cookie_session_id"';
でない
xxx.xxx.36.106 - - [23/Oct/2019:15:19:38 +0900] "GET /images/hoge/fuga/icon_hoge.svg HTTP/1.1" 200 1279 "https://hoge-hogeo.com/search/hoge/fuga "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36" "-"
もしかして_(アンスコ)二つ???
log_format frima '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$cookie__session_id"';
でた
xxx.xxx.36.106 - - [23/Oct/2019:15:20:56 +0900] "GET /images/hoge/fuga/icon_hoge.svg HTTP/1.1" 200 1279 "https://hoge-hogeo.com/search/hoge/fuga "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36" "cd15992f3578db3a5217d78198028ae4"
これでNginxのログとRailsのログを突合できそう。
Zappa触ってる その2
apigateway_enabledが効かない
Zappa/cli.py at master · Miserlou/Zappa · GitHub
use_apigatewayをapigateway_enabledで上書きするんじゃなくて、普通にソースを置換してほしかった。
ちょうどここ上手く動かないよ。。。(Pythonレベル低くてデバッグに時間かかってる) 0.50で直ったっぽい
API Keyの設定がびみょい
API Gateway構築時、API Keyのenabledは出来るが、使用量プランにAPIを紐づけるのはやってくれない。
使用量プランに紐づけてくれないから、
API Key認証がされずforbiddenになる。
API Keyはzappa deploy時に作らなければいけないものではない(予め作っておけばいいし)けど、
使用量プランの設定は設定してくれないと、API Keyが有効化出来ない。
ソースは↓のお借りした。
Zappa触ってる
穏やかじゃなくて草。
使ってみてるけど、まだまだ開発中なのかなという印象。
- API Gatewayの使用可否がうまく動かない
- apigateway_enabledとuse_apigateway変数が世代交代中でわちゃわちゃしてそう
- S3にパッケージアップロードしてるけど、デプロイ終わったら消してる
- デカいパッケージだとLambda上で見れないんだし、世代管理にした方が嬉しい気がする
- S3トリガーがうまく動かない
- zappa deploy時にS3トリガーを設定出来ない。(lambda funcとトリガーを同時に作れない)
- ので、初期deploy時はzappa deployが完了してからzappa updateを打つ必要がある
普通に慣れてる人はTerraform使った方が早そう。
自分はLambdaのアプリケーションデプロイはやったことないけど。
Transfer:ことはじめ
概要
S3からデータを見たいんだって、別会社の人が。
SFTPじゃなくてもいい気がするんだけど、いつのまにかやることが決まってたから作る。
打ち合わせ呼んでクレメンス、、、立ててるだけで2万超えるやで。
はぁ。。。
手順
Transfer構築
CloudWatch Logs用のロールつくる
Transferが吐くログをCloudWatch Logsに記録させるために、先にロールをつくる
信頼関係はTransfer(TransferがCloudWatch Logsにログを送るから)
Transfer たてる
Custom hostname書くとR53にCNAMEを勝手につくってくれる。便利。
ちなみにhoge-hogeo.comは僕の持ち物ではないので、名前解決はできない。(ブログ用ダミー)
SFTPユーザ使う場合は左(多分)
Logging roleは↑で作ったロールを入れる
完成
stopしてもお金は払ってや
検証で立てたらちゃんとdeleteしないと死ぬやつや
ユーザつくる
ポリシーつくる
権限はよしなに。
これはメンテナンス用ユーザのポリシーだからcreate/delete bucket以外の権限を付けといた。
ロールつくる
作ったポリシーをアタッチする。
信頼関係はTransfer(TransferがS3にモノを置いたりするから)
Transferコンソールからユーザを作成する
Home directoryはS3バケット名、Optionalを入れなければs3://バケット名/がログインしたときのホームディレクトリになる
test.txtを配置しておく
S3のバケットポリシーは空。あとでちゃんとバケットポリシー埋めないと。。。
動作確認
見れた。
$ sftp -i .ssh/key.pem test-sftp@sftp.hoge-hogeo.com Connected to test-sftp@sftp.hoge-hogeo.com. sftp> ls test.txt sftp> ls -l -rwxr--r-- 1 - - 17 Oct 10 05:49 test.txt
SFTPのコマンド分からん。
CloudWatch Logs
ログインしたら、CONNECTEDのログが出た。
一応S3が直接見れないことを確認。
$ aws s3 ls s3://staging-xxxxxxxx An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
bundle installで'ld: library not found for -lssl'が出てつらい話
ローカルにアプリケーションcloneしてきてrubyのデプロイツールを検証したい。
bundle installが通らない
$ bundle install --path vendor/bundle Fetching mysql2 0.5.2 Installing mysql2 0.5.2 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. 〜中略〜 ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [mysql2.bundle] Error 1 make failed, exit code 2 Gem files will remain installed in /Users/hogehogeo/Documents/hogeo-service/vendor/bundle/ruby/2.6.0/gems/mysql2-0.5.2 for inspection. Results logged to /Users/hogehogeo/Documents/hogeo-service/vendor/bundle/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/mysql2-0.5.2/gem_make.out An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'` succeeds before bundling.
gem installしてみる
gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/
"ld: library not found for -lssl"で落ちる
$ gem install mysql2 Building native extensions. This could take a while... ERROR: Error installing mysql2: ERROR: Failed to build gem native extension. 〜中略〜 current directory: /Users/hogehogeo/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/mysql2-0.5.2/ext/mysql2 make "DESTDIR=" compiling client.c compiling infile.c compiling mysql2_ext.c compiling result.c compiling statement.c linking shared-object mysql2/mysql2.bundle ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) make: *** [mysql2.bundle] Error 1 make failed, exit code 2 Gem files will remain installed in /Users/hogehogeo/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/mysql2-0.5.2 for inspection. Results logged to /Users/hogehogeo/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/extensions/x86_64-darwin-18/2.6.0-static/mysql2-0.5.2/gem_make.out
参考:【Rails】MySQL2がbundle installできない時の対応方法 - Qiita
openssl自体は入ってるらしいので、upgradeしてみる
Error: openssl 1.0.2r is already installed To upgrade to 1.0.2t, run `brew upgrade openssl` $ brew upgrade openssl ==> Upgrading 1 outdated package: openssl 1.0.2r -> 1.0.2t ==> Upgrading openssl ==> Downloading https://homebrew.bintray.com/bottles/openssl-1.0.2t.mojave.bottle.tar.gz
中身見てみる
$ brew info openssl openssl: stable 1.0.2t (bottled) [keg-only] SSL/TLS cryptography library https://openssl.org/ /usr/local/Cellar/openssl/1.0.2t (1,795 files, 12.0MB) Poured from bottle on 2019-09-25 at 12:18:20 From: https://github.com/Homebrew/homebrew-core/blob/master/Formula/openssl.rb ==> Caveats A CA file has been bootstrapped using certificates from the SystemRoots keychain. To add additional certificates (e.g. the certificates added in the System keychain), place .pem files in /usr/local/etc/openssl/certs and run /usr/local/opt/openssl/bin/c_rehash openssl is keg-only, which means it was not symlinked into /usr/local, because Apple has deprecated use of OpenSSL in favor of its own TLS and crypto libraries. If you need to have openssl first in your PATH run: echo 'export PATH="/usr/local/opt/openssl/bin:$PATH"' >> ~/.bash_profile For compilers to find openssl you may need to set: export LDFLAGS="-L/usr/local/opt/openssl/lib" export CPPFLAGS="-I/usr/local/opt/openssl/include" For pkg-config to find openssl you may need to set: export PKG_CONFIG_PATH="/usr/local/opt/openssl/lib/pkgconfig" ==> Analytics install: 268,610 (30 days), 1,333,944 (90 days), 6,296,651 (365 days) install_on_request: 51,876 (30 days), 187,062 (90 days), 858,928 (365 days) build_error: 0 (30 days)
上記を参考にしながらパスを指定
$ bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl/include" $ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"
bundle install再実行
$ bundle install --path vendor/bundle 〜中略〜 Using mini_magick 4.9.5 Using mustermann 1.0.3 Fetching mysql2 0.5.2 Installing mysql2 0.5.2 with native extensions
通った。
もうやりたくない。