こんにちは。運用プロジェクトでサーバーサイドエンジニアをしている森と申します。
私の所属するプロジェクトでは定期的にMySQLの特定のテーブルのデータをBigQueryに転送しているのですが、MySQLのテーブルの構造の変更を転送を管理しているSREチームに共有し忘れ、転送が失敗してデータがロストするということがしばしば発生しました。
そこで、それを防ぐためにGitHubのプルリクエストに転送対象テーブルのスキーマ変更が含まれていたら、SREチームにslackで通知を飛ばすようにしました。
本記事では、その手法について書きます。ソースコードは解説用に一部内容を変更している箇所があります。
GitHub Actionsとは?
GitHubの操作をフックとし、継続的インテグレーション (CI) と継続的デプロイメント (CD) 機能を実現できます。
例として、プルリクエストの作成時やマージ時にテストを走らせるなど、Jenkinsのようなことができます。
また、GitHub Marketplaceに有志があげている汎用的なアクションを利用することもできます。
値段も月の限度を超えなければ無料で使えるので、GitHubを使っていてCIをしたければ一考の価値ありです。
実行したい処理の流れ
処理の流れは以下のようになります。
- プルリクエストのマージ時に、マージするブランチとマージ先のブランチの差分からテーブルの変更を検知する
- SRE管理の別レポジトリにある転送対象テーブルの一覧を取得する
- 1と2の結果を比較して、対象テーブルが変更されていたらslackに通知を送る
1. プルリクエストのマージ時に、マージするブランチとマージ先のブランチの差分からテーブルの変更を検知する
GitHub Actionsはアクション実行時の様々な情報を持っているため、マージするブランチとマージ先のブランチのコミットハッシュを取得できます。その間のdiffをとることでテーブルの変更を検知します。私のプロジェクトではCodeIgniterのマイグレーションクラスに以下のようにSQLを記述するようにしているので、 ALTER TABLE `TABLE_NAME`
が含まれているかどうかで判定します。
$sqlList[] = <<< 'QUERY_EOD'
ALTER TABLE `user` MODIFY `platform_user_id` VARCHAR(16) DEFAULT NULL COMMENT 'hoge';
QUERY_EOD;
2. SRE管理の別レポジトリにある転送対象テーブルの一覧を取得する
対象のレポジトリはプライベートレポジトリになっており、簡単には取得できません。
アクセストークンを使えばGitHubのAPIを使って取得できます。
3. 1と2の結果を比較して、対象テーブルが変更されていたらslackに通知を送る
シェルスクリプトで比較を行、通知にはslackのIncoming Webhook APIを使います。
yaml作成
GitHub Actionsの処理(ワークフローといいます)はyaml形式で記述します。
リポジトリの .github/workflows
ディレクトリにファイルを作成するだけで、自動でGitHubが認識してくれます。
ここでは report_analysis_table_change.yml
とします。
ワークフロー名設定
まず、ワークフロー名を記述します。このワークフロー名はGitHubのレポジトリページのActionsで参照されるので、処理の内容が分かる名前にしましょう。
name: Report Analisys Table Change
発火条件の設定
続いて、どのようなときにこのワークフローが発火するかを記述します。
今回はプルリクエストをマージしたときに発火するようにします。
また、全てのプルリクに対して発火する必要はないので、master
と、release
から始まるブランチで、かつ差分にMySQLのスキーマ変更ファイルが含まれている場合のみに絞ります。
on:
push:
branches:
- master
- 'release/**'
paths: codeigniter/application/migrations/*.php
処理内容の設定
次にジョブの設定をします。ワークフローの実行内容を記述します。
jobs:
report_analisys_table_change:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: 0
- name: report analisys table change
env:
MIGRATION_DIR: codeigniter/application/migrations
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
SLACK_WEBHOOK_URL_TO_SRE: ${{ secrets.SLACK_WEBHOOK_URL_TO_SRE }}
run: |
BEFORE_SHA=$(jq -r '.before' $GITHUB_EVENT_PATH)
AFTER_SHA=$(jq -r '.after' $GITHUB_EVENT_PATH)
REF_URL=$(jq -r '.compare' $GITHUB_EVENT_PATH)
REF_BRANCH=$(jq -r '.ref' $GITHUB_EVENT_PATH)
curl -L -o repo.zip -H "Authorization: token ${REPO_ACCESS_TOKEN}" \
https://api.github.com/repos/ORGANIZATION_NAME/SRE_REPO_NAME/zipball/master
unzip repo.zip -d SRE_REPO_NAME
git diff ${BEFORE_SHA} \
-- ${MIGRATION_DIR} \
| grep 'ALTER TABLE' \
| while read statement ; do \
echo $statement
grep table: SRE_REPO_NAME/TARGET_TABLE_NAME_LIST.yaml \
| sed -e "s/ *//g" -e "s/table:\([a-z_]*\)\s*/\1/" \
| while read table_name ; do \
if [[ "$statement" =~ "\`$table_name\`" ]]; then
report="<@SLACK_USER_ID> ${REF_BRANCH}で${table_name}テーブルに変更がかかりました\n\n内容: $(echo ${statement} | tr \' \")\n差分: ${REF_URL}"
echo $report
echo "{'attachments':[{'fallback':'${report}','color':'#ffd700','fields':[{'title':'table変更通知','value':'${report}'}]}]}" \
| curl -k --verbose -X POST -H 'Content-Type:application/json;' \
--data @- ${SLACK_WEBHOOK_URL_TO_SRE}
fi
done
done
以上がジョブの設定になります。大事なところをピックアップして解説していきます。
runs-on: ubuntu-latest
処理を実行するマシンを設定します。Linux、macOS、Windowsを選択できます。
また、自分で用意したマシンを使うこともできます。
特にOS依存がなければLinuxを使うのがおすすめです(MacOSは少し値段があがります)。
- uses: actions/checkout@master
with:
fetch-depth: 0
uses
でパブリックリポジトリにあるアクションの指定ができます。
このアクションではプルリクのレポジトリをマシンにcheckoutします。
デフォルトでは最新のコミット1つしか取得しませんが、今回は過去のコミットとの比較をしたいので、fetch-depth
を0に設定して全てのコミットを持ってきます。
env:
MIGRATION_DIR: codeigniter/application/migrations
REPO_ACCESS_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
SLACK_WEBHOOK_URL_TO_SRE: ${{ secrets.SLACK_WEBHOOK_URL_TO_SRE }}
処理内で参照したい環境変数を設定します。
アクセストークンやSSH鍵などはベタ書きにするとセキュリティ上よろしくないので、Secretsを使いましょう。
暗号化されたシークレットの作成と保存
ここからはいよいよ処理内容に入っていきます。
BEFORE_SHA=$(jq -r '.before' $GITHUB_EVENT_PATH)
AFTER_SHA=$(jq -r '.after' $GITHUB_EVENT_PATH)
REF_URL=$(jq -r '.compare' $GITHUB_EVENT_PATH)
REF_BRANCH=$(jq -r '.ref' $GITHUB_EVENT_PATH)
$GITHUB_EVENT_PATH
にはこのアクションを実行する際のいろいろな情報がjson形式で格納されています。
今回はマージ前とあとの差分を出すためのSHAと、slackに転送するためにコミット差分を表すGitHubのurlと、ブランチ名を変数に入れておきます。
curl -L -o repo.zip -H "Authorization: token ${REPO_ACCESS_TOKEN}" \
https://api.github.com/repos/ORGANIZATION_NAME/SRE_REPO_NAME/zipball/master
unzip repo.zip -d SRE_REPO_NAME
弊社はBigQueryへの転送テーブルのリストを違うレポジトリに管理しているので、これをGitHub Apiを使って取得します。
public repogitoryでない場合はアクセストークンが必要になります。
git diff ${BEFORE_SHA} \
-- ${MIGRATION_DIR} \
| grep 'ALTER TABLE' \
| while read statement ; do \
echo $statement
grep table: SRE_REPO_NAME/TARGET_TABLE_NAME_LIST.yaml \
| sed -e "s/ *//g" -e "s/table:\([a-z_]*\)\s*/\1/" \
| while read table_name ; do \
if [[ "$statement" =~ "\`$table_name\`" ]]; then
(slackに転送する処理)
fi
done
done
上記のコードではマージ前とマージ後の差分を比較し、ALTER TABLE
の行が含まれていたらそのテーブル名がSREレポジトリのTARGET_TABLE_NAME_LIST.yaml
ファイルにそのテーブル名が含まれているか調べています。
report="<@SLACK_USER_ID> ${REF_BRANCH}で${table_name}テーブルに変更がかかりました\n\n内容: $(echo ${statement} | tr \' \")\n差分: ${REF_URL}"
echo $report
echo "{'attachments':[{'fallback':'${report}','color':'#ffd700','fields':[{'title':'table変更通知','value':'${report}'}]}]}" \
| curl -k --verbose -X POST -H 'Content-Type:application/json;' \
--data @- ${SLACK_WEBHOOK_URL_TO_SRE}
先ほどのコードの「slackに転送する処理」の部分が上記のコードです。slackのwebhook APIを使ってブランチ、変更されたテーブル名などの情報をSREのチャンネルに転送しています。
実行結果
プルリクエストをマージするとこんな感じでslackに飛んできます!
苦労したこと
ローカル環境でテストができない
2020年5月の段階ではアクション設定ファイルをローカルやサンドボックス環境などで実験することができないので、テストのブランチを作って何回もアクションをhookしてテストするしかありません。
間違ってmasterにpushするなど、事故が起きないように注意する必要があります。
ドキュメントが少ない、探しづらい
新しめの機能ということもあり、「これをやりたい」と思って調べてもなかなか見つけにくかったです。
自分は公式のドキュメントをみてもよくわからないところがあったので、ググった情報と組み合わせて実装を進めました。
さて、今回はGitHub Actionsを使ってプルリクエストのマージ時に差分をマージ解析し、slackに通知を飛ばす設定を作りましたが、いかがでしたか?
GitHubを使っていればすぐに利用でき、プルリクエストやマージ時に処理を走らせるのはかなり便利なので、ぜひ使ってみてください!