SREとバックエンドを統合してバックエンドに転向しました
はじめに
タイトルの通り、SRE歴だと6年 + 5社目ですが、株式会社チカクのSREからバックエンドに転向して2ヶ月経ちました。
この2ヶ月、フェーズが変わったプロダクトに追従出来ていなかったチームの開発プロセスを刷新することにメンバーと注力していました。
それについてお話しします。
SREを無くした
「SREはバックエンドが分かってないと信頼性を担保することは出来ない」
「バックエンドはインフラが分かってないとインフラを意識した設計、冪等性の担保、柔軟でベストな設計が出来ない」
と私は考えており、少人数で複雑なアーキテクチャでなければSREとバックエンドを分離する必要はないと思ってます。
またSREとバックエンド双方に課題があったことから、
ようやくこのタイミングでSREとバックエンドを統合することを、会社的にも良いと判断しそのような体制にすることができました。
SREの課題
弊社SREは私1人で運用していますが、ドキュメント化やdrawioで図にされており、
make apply ....
と叩けば誰でもterraformが打てる環境だったりPull Request welcome状態なのですが、
課題として役割的にも知見的にもやりたがる人がいないことでした。
結果、私の知見がドキュメントベースで蓄積するだけで他メンバーのaws, terraformの経験が成熟せず、awsコンポーネントを用いた設計するときがSPOFになる状態でした。
バックエンドの課題
社内事情が色々あって仕方ないタイミングだったと思いますが、「言語とフレームワークは選んだのであとはよろしく」と急に依頼がくるというカオスな状態でした。
「運用するための必要な修正が難しい場合はSREに依頼がくる」ことが多かったり、またDB migrate方法もSREが確立しなければなりませんでした。
なので選ばれた言語/フレームワークを理解する必要があり、これは結構しんどかったので最初の設計段階から関与する必要がありました。
cron(定期実行batch)に関しても「冪等性を担保してないので多重実行禁止だけど、数分単位で動く」というケースもあります。
おそらく「冪等性はインフラレイヤで担保してくれるだろう」という考えからだとは思いますが、ロック機構を作ってまでインフラレイヤで担保するかというところから議論したい気持ちではあります。
個人的には第一にバックエンド側で担保。どうしても無理ならインフラレイヤで担保するのが良いですね。
数分単位で動くのであればデーモン化 + job queueシステム。そんなに実行しないのであればcronにするのがベストだと思ってます。
他にも色々課題が山積みで、あとの項目でも一部紹介します。
SREとバックエンド統合してどうなった?
アーキテクチャ設計段階でSREとバックエンド双方の知識を保有してレビューをするので上記課題も解決できました。
設計時は、全バックエンドメンバーでシーケンス図(mermaid記法)をリアルタイムに書きながら議論し合える環境にしたのも良かったと思います。
課題としてはterraformのコードの質ですね。
terraformはresource名や設定が破壊的変更になったりするので、そこのネームスペースや依存関係をいかに考えて実装しないとサービス断が発生します。
そこは経験かと思います。
バックエンドに転向して何したの?
1. 開発体制を変えた
まず少人数なので性善説ベースで。READMEや自明なものはどんどんセルフマージしてOKにしました。
もし何かあったらその人の責任で、責任分散の意図でレビュー依頼をしましょうというルールです。
Pull Requestに関しては、今までは何も書いてないPRが多かったのですが、絶対書くべき項目として「概要と動作確認」を用意しました。
issueベースでPRがあればベストです。
レビューコメントは「feature/imo/yagni/nits/must」使いつつ、must以外はapprove。
これらに関しては、mergeスピードが速くし開発スピードをあげたい、PRにログ(挙動)を残したい、宗教的論争をなくしたいのが目的です。
実装があってればリファクタコメントはimoにして、気になる人が直せば良いです。
私はこんな感じでレビューコメントをしてたりします。
2. service design docを書いた
のような、サービスを立ち上げる際に必要なチェックリストです。
「自分たちは自明なことでも、PdMやその他エンジニアがこのシステム大事なことをみれば分かりやすく書く。深ぼりたいときはコードを読む」を意識してます。
現在のリストはこちら。ここから成熟していけたらと思ってます。
- サービス - Sequence Diagram - aws architecture diagram - バックグラウンド - ここに存在するサービスがなぜ必要なのか - 技術 - 言語 - ルーティング - ORM - AWS - Monitoring - 目標と非目標 - やること、やらないこと - インターフェイス - HTTPならURIをまとめたもの(swagger)のリンクを貼る - 依存関係 - SLO - データベース - 使ってるDBと権限 - バックアップと復旧 - RTO(目標復旧時間) - RPO(目標復旧時点) - PI/PII - セキュリティに関する考慮事項 - リファレンス - このリポジトリを利用する上で参考になるurl
3. Rails, DjangoからGoに移行した
弊社ではRails, Djangoのフレームワークを利用してますが、まずこのような課題がありました。
自由にカスタマイズすることに弱い
- 「ログをこうしてほしい」と伝えても自由にカスタマイズするのが難しいようでした。
- Djangoのloggerに関してはSREが実装しました。
OSSにPRを出すのに弱い
- 実装に必要だけど対応してないOSSがあった場合、アップデート待ちでした。
- 全然更新されず、SREがPRを出してたりしました。
これらの原因は技術力ではなく、言語/フレームワークと技術力のミスマッチだと思ってます。
「ある程度フレームワークを使わない実装をGoで書き、適材適所でpackageを用いた方が自由にカスタマイズ出来て良い」と私は判断して会社に通しており、新しいサービスではGoを採用しました。
今後はRails, DjangoサービスもGoに移行する予定です。
4. Goを導入するにあたって
まずtodo-apiのboilerplateを作成しました。
SOLID、凝集度、結合度を意識したクリーンアーキテクチャの実装で、CRUDのAPIとそれぞれのテストを書いて、メンバーに解説しました。
新サービスではそれをベースにAPIをメンバーに書いてもらい、batch系は自分が書いております。
それもいずれメンバーに引継ぎますが、シーケンス図、コメントを読みながらテスト環境で実行すればもういけると思います。
またGoのplaygroundリポジトリを作成しました。
- html/templateをどうやって使うか
- 並行処理
- 並行処理中のgraceful shutdown
- spreadsheetの操作
などさまざまなサンプルを用意しました。
またメソッド名に「自明でもある程度コメントを書く」ということをしてます。
これはRailsで全くコメントを書いてない運用をした結果、初見者が参入しづらい状態になってたのと、
メンバーに「私が同じことしてもいいか?」と聞いたところ「それは厳しい」と言われたのもあります。
(コメントじゃなくてcommit logやPRに書けばいいだけなんですが、まぁcommit log見ない人もいるし、社内リポジトリならこのほうが分かりやすいですよね)
またGoを導入したことで「なければ作ればいい」という発言がよく出るようになったのは一番の成果だと思います。
5. ドキュメントを減らしてmakeに寄せた
開発環境を用意するためにREADMEを見て、このコマンドを打って...
といったオペレーション系ドキュメントはMakefileに寄せた方が冪等性が担保されます。
make help
と打つと、開発やオペレーションに必要なコマンドが出てくるようになってます。
こちらはapiの例。
$ make help dist create .tar.gz linux & darwin to /bin clean このMakefileで利用したファイルを docker image 以外クリアにする clean_all このMakefileで利用したファイルをクリアにする build build build/cross create to build for linux & darwin to bin/ run go run run/binary run binary go/get 特定のパッケージをgo getする go/mod_tidy 不要なgo packageを削除する go/test go test go/create_fixtures fixutresをDBに作る go/test_with_create_fixtures fixutresをDBに作りつつtestする go/test_package 特定のパッケージのみのテストを行う docker/build docker build docker_compose/up compose起動 docker_compose/down compose停止 docker_compose/down_f composeではないけど、dockerで強制停止する. conflict対策 docker_compose/down_all compose停止 + 全てを初期化する docker_compose/rebuild appだけbuildし直す migrate/up migration. docker compose up後に実行できる migrate/down migrationのrollback. docker compose up後に実行できる migrate/create migrationファイル作成. migrations/ にup/downが作成される # deploy系はdeploy.mkで管理してるこっち $ make -f deploy.mk deploy/all docker build/push -> db migrate/seed -> fargate deploy login ECRにlogin build docker build. linux/amd64用にbuildする push docker push to ECR。ENVタグとrevisionがついたタグ両方をpushする deploy 既にあるイメージでfargate deployだけする(dockerbuild/pushはしない) image_check ecrにイメージがあるかチェックする。無駄なbuildを無くすため。 db/migrate db/migrateする
6. メールシステムを新規作成した
前述した通り、RailsのAction Mailerを使ってるとGoでメールを送るシステムがありませんでした。
本当はsendgridを使って運用面もしっかり管理できるような体制にしたかったのですが、期間がなかったので、
SQS -> Lambda -> SESのシステムを作り、GoでSQSにメール用のqueueを送る go-mailer
という社内パッケージを作成しました。
// こんなメソッドを用意して使ってます func (m *Mail) SendMail() error { mailer := &gomailer.Mailer{ AwsProfile: m.AwsProfile, AwsRegion: m.AwsRegion, Mail: gomailer.Mail{ To: m.ToMailAddress, From: m.FromMailAddress, Bcc: m.BccMailAddress, Template: gomailer_template.TemplateTest, BodyReplace: map[string]string{ "FullName": m.FullName, }, }, } // SQSにqueueを積む if err := mailer.Send(); err != nil { return err } return nil }
パッケージにメールのテンプレートを保管してるので、Goで送るメールは全てそこに集約され管理しやすくもなるといった意図もあります。
また移行するときSESをus-east-1からap-northeast-1に作り直しました。
7. 新サービス用のバッチを書いた
意識したのは冪等性。
一連の流れがあったとして、コンテナに停止シグナルが送られ際に、最後まで実行してgraceful shutdownしてほしいところはどこか。
何回実行しても同じ結果になるか。メンバーとそこを中心に議論とレビューとサンプルを用意して実装しました。
あとはなるべくローカル開発環境で実行させたく、そこの環境構築も力を入れてたのですが、
開発終わり間際でロジックが変わったりして、そこは今微妙になってますが、改善していきます。
(例えばAWS S3の代替は https://github.com/minio/minio を使ったりしてます)
経営的なところを手伝った
あまり話せないのですが、戦略面で2週間ほど色々と手伝ってました。
正直とてもしんどかったですが、私は言語化が得意だったり、場をまとめてポジティブに進めていくのがうまいんだなと再認識できたので、やってよかったです。
次はどうするか
運用面。ドキュメントやテストコードが足りないのでそこは補填していく気持ちです。
またR&D的にflutterのplaygroundを社内で書き始めてます。
そこも色々と課題があってflutterを一度チョイスしてみてます。
あとは色々勉強する必要があるので、今後も引き続き勉強していきたいと思ってます。