Looker + hubot によるデータ業務の自動化
はじめに
🔺YAMAP 分析チームの松本です。 Looker Advent Calendar 2020 兼 YAMAP エンジニア Advent Calendar 2020 3日目の記事です。 よろしくお願いします。
YAMAP では登山地図の情報をより充実・正確にするために、ユーザーデータを活用した 伊能忠敬プロジェクト に取り組んでいます。 具体的な一例としては、ユーザーさんの軌跡を分析して新規ルートを抽出し、登山地図に反映しています。
このルート抽出処理は Python で実装しており、従来は分析者が手元のマシンで実行していました。 また、地図上のどこに対してルート抽出を試みるかは、データ基盤のデータを Looker で可視化して決めています。
今夏にはじめたルート抽出の取り組みも軌道に乗り、最近では自動化・効率化の機運が高まっていました。
どう自動化するか
Python ルート抽出処理については、もともと運用していた分析システム(Python / GKE)に組み込みました。 あとは Looker のデータから抽出処理を kick する所を自動化すれば OK ですが、ここでは 2 つの方法を検討しました。
- Looker Action Hub でカスタムアクションを開発する
- Looker から Slack へ抽出候補 csv を投稿させる。hubot で csv を読み取り、抽出処理を kick する
今回は以下の理由から、2 の方法を採用しました。
- 自動化の仕組みから GKE クラスタに対して kubectl コマンドを実行するため ※1
- ルート抽出処理の kick は k8s job をデプロイする形で行いたく、そのために kubectl コマンドで実行したかった
- これは 1 の方法では実現できなさそうに思われた
- 一連のオペレーションは人間が ChatOps として実行できるようにもしておきたかったため
- YAMAP ではユーザーさんからルート追加要望を承る場合もあり、その場合はオペレーターがルート抽出を kick できると便利
ということで、以下のような構想になります。
自動化した結果
- hubot の名前は "yak" にしました。ヤクはヒマラヤ地方の高地に生息する牛さんです
- Looker から csv をスケジュール配信します。コメントに hubot のコマンドを書いておきます
- yak がファイルをパースして、kubectl コマンドでルート抽出処理を kick します
- 分析システム(yama)が処理完了を教えてくれます
- この後、地図エディタで成果物を人間がチェックして、登山者の皆様にお届けしています
やったね 🎉
まとめと展望
Looker + hubot で、YAMAP 伊能忠敬プロジェクトのオペレーションの一部を自動化しました。 Looker には Action Hub という便利な仕組みも用意されていますが、今回のように事情がある場合は hubot などチャットボットを利用することが出来ます。
今回は僕が所属する分析チームのオペレーションを自動化しましたが、今後は hubot や Action Hub も活用して社内のデータ業務を自動化し、データ活用を加速したいと思います。
それにしても YAMAP では様々なデータ活用が広がっており、それを柔軟に支えてくれる Looker は本当に素晴らしいツールだと思います! (hubot アドベントカレンダーみたいになってしまいスミマセンでした 😅)
実装(ご参考まで)
何もコードがないのは寂しいので、hubot の yama extract-route コマンドの正常系の実装だけ載せておきます。
'use strict'; const util = require('util'); const childProcess = require('child_process'); const exec = util.promisify(childProcess.exec); const fs = require('fs'); const csv = require('csv'); const request = require('request'); module.exports = (robot) => { robot.respond(/yama extract-route/i, async (res) => { const fileInfo = res.message.rawMessage.files[0], tmpFilePath = `tmp/${fileInfo.timestamp}_${fileInfo.name}`; let data = []; const parser = csv.parse((err, row) => { data = row; }); request .get(fileInfo.url_private_download) .auth(null, null, true, process.env.HUBOT_SLACK_TOKEN) .pipe(fs.createWriteStream(tmpFilePath)) .on('finish', () => { fs.createReadStream(tmpFilePath) .pipe(parser) .on('end', async () => { // process head 10 rows. for (let row of data.slice(1, 4)) { const result = await exec(`LANDMARK=${row[1]} k8s/scripts/deploy/run_route_extraction`); res.send(result.stdout); } }); }); }); }
補足
今回採用しなかった Looker Action Hub を改めて調べると、GCP の CloudStorage に csv を PUT するアクションが用意されていました。
CloudStorage の PUT イベントに対して CloudFunction をトリガすることで、※1 はクリアできる可能性があることに、この記事を書きながら気づきました...