hoge-hogeoのひきこもごも

インフラエンジニアだけど形を持ったインフラを触ったことがない人の徒然

LambdaのエラーログをPython3でSlackに飛ばしたい

概要

Lambdaに吐いたログを監視して、エラーを検知したらSlackに通知したい。

ここでいうと"[ERROR]~"みたいな。

f:id:hoge-hogeo:20200604120037p:plain

ググると、先人たちがたくさんやってるけど、自分の学習用振り返りとして。

qiita.com

TL;DR

  • SNSを作成
  • CloudWatchログ メトリクスフィルター作成
  • CloudWatchアラームを設定
  • Slack通知Lambdaを作る

手順

SNS 作成

Lambdaの実行するためにSNSを作成する。

名前だけであとは特に何もせず作成。 f:id:hoge-hogeo:20200602172646p:plain

CloudWatchログ メトリクスフィルター作成

この"[ERROR].*"が出たら、Slack通知がしたい。

f:id:hoge-hogeo:20200602165744p:plain

メトリクスフィルターを作成する。

f:id:hoge-hogeo:20200602170120p:plain

[ERROR]をマッチさせるよう「"[ERROR]"」にする。(ダブルクォートしないと完全一致じゃなくなる。)

[ERROR]が出ているログストリームで「パターンをテスト」をして、マッチしたらOK。

f:id:hoge-hogeo:20200602170545p:plain

「フィルター名」はログストリーム内のメトリクスフィルターの名前なので、適当でいい(と思う)。

「メトリクス名前空間」と「メトリクス名」は、CloudWatchメトリクスで監視するときの名前になるので、サービス名とメトリクス名とかにした方がいいかもしれない。

「メトリクス値」は検知したときにCloudWatchメトリクスに記録される値なので、検知数にしておく。

f:id:hoge-hogeo:20200602170827p:plain

CloudWatchアラームを設定

出来たら、メトリクスフィルターリンクをクリックして、CloudWatchメトリクスを確認しに行く。

f:id:hoge-hogeo:20200604122110p:plain

メトリクスがちゃんと取れていたら、OK。

ベルマークを押して、CloudWatchアラームを作成する。 f:id:hoge-hogeo:20200602172203p:plain

閾値や監視間隔はおのおので。

f:id:hoge-hogeo:20200602172331p:plain

予め作っておいたSNSトピックを選択して、作成。

f:id:hoge-hogeo:20200602172917p:plain

アラーム名は「alert-${Lambdaファンクション名}」にする(いろいろあってこうしておきたい) f:id:hoge-hogeo:20200602173121p:plain

監視する側はこれでOK。 f:id:hoge-hogeo:20200602173310p:plain

Slack通知Lambdaを作る

Python3でロールは適当なロールを選択 f:id:hoge-hogeo:20200602173556p:plain

ソース

ほぼ、写経。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してるところを消して直接変数投入でいいと思う。 f:id:hoge-hogeo:20200602174837p:plain

SNSサブスクリプションにLambdaを登録する

赤枠部分に↑で作ったLambdaファンクションを入れてあげる。 f:id:hoge-hogeo:20200602175259p:plain

再度、Lambdaを見に行って、トリガーにSNSが設定されていればOK。 f:id:hoge-hogeo:20200602175650p:plain

テスト

適当に作った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のファンクションのログが出力されたことを確認。 f:id:hoge-hogeo:20200605122235p:plain

Slackに通知が飛べば完成。

文言はもっといい感じにできると思う。

Creating rich message layouts | Slack

f:id:hoge-hogeo:20200605122332p:plain

VPC内にEIP固定のTransfer for SFTPが立てたい話

f:id:hoge-hogeo:20200501123228p:plain

日本語版と英語版のマニュアルが違う(ニュアンスとかじゃなくて内容が)

おそらく日本語版の方が古いとおもう。

基本は英語版を読んだ方がいいと思う。

読んでも分からなかったので、手を動かして理解したけど

docs.aws.amazon.com

分からなかった日本語版、VPCエンドポイントは今選べない(多分、ドキュメントが古い)

docs.aws.amazon.com

手順

EIPを取得

f:id:hoge-hogeo:20200501115816p:plain

Transfer for SFTP構築

VPC hosted→Internet Facingを選択、前述で取得したEIPをアタッチする

カスタムドメインはおのおの設定する

f:id:hoge-hogeo:20200501115827p:plain

日本語版のドキュメントだと、VPCエンドポイントを作成しろって書かれているけど、Transfer構築時に自動で作られる。

ので、Transfer構築ボタンをポチったら、VPCエンドポイント画面で待っていればOK

SecurityGroupはVPCのデフォルトSecurityGroupが割り当てられるので、変更する。

f:id:hoge-hogeo:20200501115838p:plain

構築完了後、digを引いて自分の設定したEIPが返ってくればOK f:id:hoge-hogeo:20200501115847p:plain

結局何が分かりにくかったのか

日本語版の下記の手順が不要だった。

おそらく日本語版ドキュメントリリース後にアップデートされて、自動でVPCエンドポイントが作られるようになったんだと思う。

f:id:hoge-hogeo:20200501123442p:plain

つまり、ドキュメントは英語版読もうね。ってことだと思う。

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

f:id:hoge-hogeo:20200219165901p:plain

use_apigatewayをapigateway_enabledで上書きするんじゃなくて、普通にソースを置換してほしかった。

ちょうどここ上手く動かないよ。。。(Pythonレベル低くてデバッグに時間かかってる) 0.50で直ったっぽい

API Keyの設定がびみょい

API Gateway構築時、API Keyのenabledは出来るが、使用量プランにAPIを紐づけるのはやってくれない。

鍵必須でAPI Gatewayは作ってくれるけど、 f:id:hoge-hogeo:20200220151032p:plain

使用量プランに紐づけてくれないから、 f:id:hoge-hogeo:20200220151313p:plain

API Key認証がされずforbiddenになる。 f:id:hoge-hogeo:20200220151826p:plain

API Keyはzappa deploy時に作らなければいけないものではない(予め作っておけばいいし)けど、

使用量プランの設定は設定してくれないと、API Keyが有効化出来ない。

ソースは↓のお借りした。

dev.classmethod.jp

Zappa触ってる

f:id:hoge-hogeo:20200219151014p:plain

穏やかじゃなくて草。

使ってみてるけど、まだまだ開発中なのかなという印象。

  • 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に記録させるために、先にロールをつくる f:id:hoge-hogeo:20191011150336p:plain

 

信頼関係はTransfer(TransferがCloudWatch Logsにログを送るから) f:id:hoge-hogeo:20191011150457p:plain

Transfer たてる

Custom hostname書くとR53にCNAMEを勝手につくってくれる。便利。

ちなみにhoge-hogeo.comは僕の持ち物ではないので、名前解決はできない。(ブログ用ダミー)

SFTPユーザ使う場合は左(多分)

Logging roleは↑で作ったロールを入れる f:id:hoge-hogeo:20191011151854p:plain

完成

f:id:hoge-hogeo:20191011152304p:plain

stopしてもお金は払ってや

検証で立てたらちゃんとdeleteしないと死ぬやつや f:id:hoge-hogeo:20191011154030p:plain

ユーザつくる

ポリシーつくる

権限はよしなに。

これはメンテナンス用ユーザのポリシーだからcreate/delete bucket以外の権限を付けといた。 f:id:hoge-hogeo:20191010152408p:plain

ロールつくる

作ったポリシーをアタッチする。 f:id:hoge-hogeo:20191010152955p:plain

信頼関係はTransfer(TransferがS3にモノを置いたりするから) f:id:hoge-hogeo:20191011145708p:plain

Transferコンソールからユーザを作成する

Home directoryはS3バケット名、Optionalを入れなければs3://バケット名/がログインしたときのホームディレクトリになる f:id:hoge-hogeo:20191010153101p:plain

test.txtを配置しておく f:id:hoge-hogeo:20191010150141p:plain

S3のバケットポリシーは空。あとでちゃんとバケットポリシー埋めないと。。。 f:id:hoge-hogeo:20191010151453p:plain

動作確認

見れた。

$ 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のログが出た。 f:id:hoge-hogeo:20191011153104p:plain

一応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

通った。

もうやりたくない。