AWS Lambdaでheadless-chromeを動かしてRedashのグラフをキャプチャしてSlackに投稿する


この記事はニフティグループ Advent Calendar 2018の5日目の記事です。
4日目は@motaHackさんのMongoDBとmetabaseでjsonファイルを見やすくしたい 後編でした。

はじめに

 弊社でもデータ可視化のためにRedashを使っていますが、Redash本家で提供されているslack botが大人の事情で使えない(使いづらい)ため、AWS Lambdaでheadless-chromeを動かしてRedashのグラフを画面キャプチャしSlack APIで投げ込むことにしました。(Redash用にサーバーあるならそこでbot動かせばいいという話もありますが...)

下準備

Slack APIのTokenを準備する

先人の知恵が沢山あるので説明は割愛しますが、今回はファイルをアップロードしますのでスコープはfiles.uploadが利用できるものにする必要があります。

参考1:Slack API 推奨Tokenについて
参考2:OAuth Permission scopes

AWS周りの設定をする

RedashサーバーがLambdaと同じsubnet上にあるとかアクセスしやすい所にあるのであれば特にやることはありません。大人の事情でRedashへのアクセスがグローバルIPアドレスからになる場合は、IPアドレスでのアクセス制限がかけられるようにElastic IP、VPCにNATゲートウェイとインタネットゲートウェイの設定をした方がよいと思います。

参考:VPC Lambda関数にインターネットアクセス権限を付与するにはどうすればよいですか?

Lambdaに組み込むパッケージなどを展開する環境を準備する

Linuxな環境であれば何でも大丈夫ですが、エディタに拘りがなければAWS Cloud9が楽だとおもいます。(周りで使っている人を見かけませんが)
この記事では、細かいdeployのやり方などは割愛します。

導入

headless-chromeを用意する

 Lambdaにアップロードできるファイルサイズには制限があるのでできるだけコンパクトにしておく必要がありますが、自力でやるのは大変なので先人が作成してgithubで公開しているもの(serverless-chrome)を使用します。pythonの場合は動かないバージョンがあったりするため注意が必要です(参考)

ダウンロードできたら、binディレクトリを作って展開しておきます。

$ wget https://github.com/adieuadieu/serverless-chrome/releases/download/v1.0.0-37/stable-headless-chromium-amazonlinux-2017-03.zip
$ mkdir bin
$ unzip stable-headless-chromium-amazonlinux-2017-03.zip -d bin/
$ rm stable-headless-chromium-amazonlinux-2017-03.zip

その他にchromedriver、Seleniumも必要なので同じディレクトリに展開しておきます。
chromedriverはchromeのバージョンにあったものを使う必要がありますので、serverless-chromeのv1.0.0-37ならchromium 64.0.3282.167 (stable channel) 相当ということでchromedriver 2.37をダウンロードします。

$ wget https://chromedriver.storage.googleapis.com/2.37/chromedriver_linux64.zip
$ unzip chromedriver_linux64.zip -d bin/
$ rm chromedriver_linux64.zip

Selenium(python)はpipでインストールします。(-tでインストール先ディレクトリの指定を忘れないように)

$ pip install selenium -t .

必須ではありませんが、日本語フォントを入れないと日本語が豆腐化しますのでIPAフォントなどを入れておくとよいと思います。
.fontsディレクトリを作成してその下にフォントファイルを置いておくと反映されます。

$ wget https://oscdl.ipa.go.jp/IPAexfont/IPAexfont00301.zip
$ unzip IPAexfont00301.zip
$ mkdir .fonts
$ cp IPAexfont00301/*.ttf .fonts/
$ rm -f -r IPAexfont00301*

Lambda Functionを書く

pythonでは以下のようになります。

lambda_handler.py
from selenium import webdriver
import time
import os

os.environ['HOME'] = '/var/task'

SLACK_CHANNEL = 'xxxxxxxxxxxx'
SLACK_TOKEN = 'xoxb-000000000000000000000-xxxxxxxxxxxxxxxxxxxxx'
# グラフのURLは埋め込み用iframeを表示するリンクから確認する
REDASH_URL = "https://redash.xxxx/query/xxx/visualization/xxxx?api_key=xxxxx"

def lambda_handler(event, context):
    options = webdriver.ChromeOptions()
    options.binary_location = "./bin/headless-chromium"
    options.add_argument("--headless")
    options.add_argument("--disable-gpu")
    options.add_argument("--window-size=1920,1080")
    options.add_argument("--single-process")
    options.add_argument("--ignore-certificate-errors")
    options.add_argument("--homedir=/tmp")

    driver = webdriver.Chrome("./bin/chromedriver",chrome_options=options)
    driver.get(REDASH_URL)
    time.sleep(5) # 読み込みが遅いページはsleepで時間を稼ぐ
    driver.save_screenshot('/tmp/screenshot.png')

    driver.close()

    # slackに投稿
    files = {'file' : open('/tmp/screenshot.png','rb')}
    params = {
        'token' : SLACK_TOKEN,
        'channels': SLACK_CHANNEL,
        'filename' : 'redashGraph',
        'initial_comment': 'グラフのキャプチャですよ',
        'title' : 'Redashのグラフ'
    }

    requests.post(url='https://slack.com/api/files.upload', params=params, files=files)

注意点

  • Lambdaの上限メモリが少ないと処理が終わるまでに時間がかかったりします(デフォルトの128MBだとつらめ)
  • プログラム中で環境変数HOMEに/var/taskを設定しているところがありますが、この設定をしないと日本語フォントが反映されません
  • Lambda実行時にheadless-chromeがファイルを作ったりしますので、自由に書き込めるディレクトリ/tmpをhomedirとして指定しておく必要があります
  • sleepを入れているところがありますが、コメントの通りsleepしないとJavaScriptなどで非同期に読み込んでいたり、読み込み自体が遅いページではキャプチャされない(真っ白になる)ことがあります(処理時間はできるだけ短くしたいところですが...)

上の例のようにRedashグラフのURLとかslackのtokenなどを埋め込むとメンテが辛いので、Lambdaの環境変数にするなり設定を別の場所に持つなりした方がいいと思いますが、何かの参考になれば幸いです。

次は@hicka04さんの「社内でSwift勉強会を開いた話」です。