今年の頭から税理士ドットコム事業部に異動した @komtaki です。3 月末から 7 月まで育休を頂いていたのですが、無事復帰しました。
部署異動してすぐに、ジョブ追加の際にコンテナや CI/CD の最適化がされず開発体験を損なっていると感じました。そこで、異動直後の 2 月末に、フルスクラッチでコンテナと CI/CD を作り直しました。
約半年運用し GitLab CI でのデプロイ運用のデータが溜まり、定量的にデプロイを分析できるようになりました。
そこで税理士ドットコムのデプロイフローにどのような問題があったのか、CI/CD の設計の考え方と改善後の効果についてお話しします。
CI/CDとは
簡単におさらいすると、CI/CD とはソフトウェアの変更を常にテストし、自動で本番環境へ適用できるような状態にしておく開発手法です。CI/CD がうまく機能した場合、下記のような効果があります。
- 品質向上: 自動テストにより、バグを早期に発見しやすくなり、ソフトウェアの品質が向上します。
- 迅速なデリバリー: 自動デプロイにより、新機能や修正を素早くユーザーに提供できます。
- 作業の自動化: 繰り返しの作業を自動化することで、開発者はより価値のある作業に集中できます。
- 信頼性の向上: 自動化されたデプロイプロセスは、人間エラーを減少させ、システムの信頼性を高めます。
そして CI / CD は、CI と CD の 2 つのステージに分かれています。
CI: Continuous Integration
CI は、ソフトウェアの開発者がコードを共有するたびに、自動的にコードをビルドしてテストするプロセスです。主な目的は、チーム内でのコードの一貫性を保ち、早期に問題を発見しやすくすることです。
CD: Continuous Delivery / Continuous Deployment
CD は CI の延長線上にあり、アプリケーションを自動的に展開および提供するプロセスです。CD には 2 つのバリエーションがあります。
- Continuous Delivery(継続的デリバリー): このアプローチでは、自動化されたテストとデプロイメントプロセスを使用して、アプリケーションのリリース候補を生成します。リリースの手動承認が必要な場合もあります。
- Continuous Deployment(継続的デプロイメント): このアプローチでは、自動化されたテストが合格した場合、アプリケーションは自動的に本番環境にデプロイされます。人間の干渉は最小限に抑えられます。
2 つの大きな違いはデプロイが完全自動で行われるかという点です。
CI/CDを設計するポイント
これらの CI/CD の基本をふまえ、コンテナのメリットを生かす設計のポイントには、下記の 4 つがあります。
- コンテナイメージのタグは、commit short hash やセマンティックバージョンなどを使いる。タグの上書きはしない。
- テストされたコンテナをそのまま本番にリリースする。
- テストが通らなければ、デプロイされない。
- 切り戻しを含めたデプロイが 1click 短時間で完了できる。
昔 latest タグ運用はよく聞きましたが、トレーサビリティが低いので非推奨のプラクティスです。バグが出たときに、どのバージョンか特定するのが難しいです。
税理士ドットコムでどうなっていたか
歴史的経緯で CD を後から作り、適宜ジョブを継ぎ足した結果つぎはぎでした。
リリース用の CI/CD は AWS Code Pipeline、テスト用の CI は GitLab CI と用途で分かれていました。Code Pipeline は Code Commit で GitLab をミラーリングしてブランチ push で動きます。
アプリケーションは ECS Fargate でホストしているので、デプロイ先は ECS です。
2重に動くCIとbuildされる2種類のコンテナ
リリース用の AWS Code Pipeline と、テスト用の GitLab CI では別々にイメージを build しているので、コンテナイメージは共有されていません。
また GitLab CI でテストが失敗しても、AWS Code Pipeline は止まらず壊れたアプリケーションがそのままデプロイされました。
そもそも開発環境や CI でテストしているコンテナとリリースされているコンテナの Dockerfile が別でした。メインのアプリは PHP × Apache で動かしていましたが、DocumentRoot のディレクトリすら違いました。
切り戻しに15分以上かかる
latest タグ運用をしていたため、毎回コンテナを build しなおす必要がありました。そのため早くてもコンテナ build に 10 分、リリースに 5 分で、合計 15 分かかっていました。
バグが出たときの切り戻しは 1 分 1 秒でも早くしたいので、15 分は長すぎます。
定期実行バッチのデプロイが手作業
毎回、手作業で Step Functions と Cloud Watch Event を作成していました。最初は Cloud Formation で作成していたのですが、いつの間にか手作業で作ったバッチが増え、管理が崩壊。
そのため Step Functions のネーミングにぶれがあり、細かいログ出力の設定もバラバラ。すべての設定を変える場合、1 個ずつ手作業で変えていました。かなりの大仕事で、うっかり修正漏れをおこす可能性が高いです。
masterマージで自動デプロイ
いくつかのユースケースで現在のサービスの状況や開発のフローに合っていないと感じたため、自動デプロイしてほしくありませんでした。
例えば、開発環境の変更や、テストの修正でデプロイは不要ですが、CI でのテストは実行したいです。
また 2 つリリースしたい Merge Request (MR) がある場合、連続でマージすると連続で 2 回デプロイされてしまいました。そのため別のリリース用の MR を作って、1 つにまとめるなどの工夫をして運用していました。MR は GitHub の Pull Request と同じです。
さらに GitLab の負荷により CI の実行時間が 2-3 分変わり、ECS のデプロイが始まる時間を完全に制御できていませんでした。
作り直し
再設計した結果、CI/CD を GitLab に寄せました。AWS に寄せなかったのは、AWS のアカウントを持たないエンジニアでもテストの結果を見られるようにしたかったからです。
また CD には継続的デリバリーを選択し、完全自動デプロイではなく最後ワンクッションはさんで手動にしました。継続的デリバリーと継続的デプロイメントのどちらにするかは、サービスの特性や運用フローでも変わるはずです。
最終的な構成図が以下になります。
コンテナを作り直し
まず開発用のコンテナは、本番用のコンテナに寄せて統合しました。その上で、これまでベースイメージはubuntu
で PHP を自前で install していたのですが、php:8.1-apache-bookworm
に切り替えました。
PHP 公式イメージで PHP と Apache がインストール済み、debian は Ubuntu のベースになっており移行が簡単。さらにベースイメージは debian の slim になっており少しイメージを小さくできるためです。
https://hub.docker.com/_/phphub.docker.com
開発用のライブラリは最低限にし、xdebug だけは本番のコンテナに入れて環境変数で on/off を制御しています。
latestタグ運用廃止
latest タグを廃止して short commit ハッシュを使うようにしました。タグが上書きされないので、イメージのトレーサビリティが担保されます。
溜まった古いイメージは、ECR のライフサイクルポリシーで削除します。
定期実行バッチをCloud Formation化
段階的に Cloud Formation に取り込んで、既存のリソースを削除して作り直しました。Cloud Formation でテンプレートの設定を書いて、Cloud Formation のマクロでトランスフォームしてリソースを大量生成します。
マクロの実態は Lambda なので、好きにテンプレートを変更できます。
2023 年 7 月 26 日に、Cloud Formation で新しい組み込み関数のFn::ForEach
がリリースされましたが、現時点ではカンマ区切りのテキストの配列しか回せません。マクロを使わずに大量に似たようなリソースを作るのは難しいです。
成果
バグの混入が減った
開発、テスト、リリースのコンテナが全く同じになったことで、バグの原因特定が楽になりました。またテストが通らないとリリースができなくなったため、テストが通っている安心感があります。
反面、テストをしっかりメンテナンスしないとデプロイができなくなるので、フレーキーテストの混入を徹底的に防ぐ必要があると感じています。
切り戻しが1/3以下の5分に短縮
切り戻す先の commit の CI を click すれば、そのバージョンの ECS のタスク定義が作成されて、デプロイがすぐはじまります。コンテナがビルド済みなので、早いです。
masterマージとデプロイが増えた
バッチの設定を Cloud Formation にしたことで、設定が統一されました。さらにリソースの作成はこれまで SRE チームが管理していたのですが、プロダクトの開発チームで作業が完結するようになりました。
また自動デプロイを廃止し、デプロイは手動で GitLab の Job を動かします。その結果、テストコードの追加や開発環境の改善が気楽にできるようになりました。
GitLab API から取得した MR のデータを加工して、前年同期と並べてみます。
MR の作成からマージ(リリース)までの時間の中央値が 41%短縮、平均でも 26%短縮、作成された MR の数が 2 倍になっています。ただ前提として当時とは、開発メンバーの人数は同じですが、私も含めエンジニアメンバーが半分代わり、スクラム運営方法、開発している施策も違うので単純比較はできません。
それでも改善傾向にあるのは間違いないでしょう。
期間 | マージされたMRの数 | マージまでの時間の合計 | マージまでの時間の中央値 | マージまでの時間の平均 |
---|---|---|---|---|
2022 Q3 (7-9月) | 56 | 7003:53:14 | 72:23:18 | 125:04:10 |
2023 Q3 (7-9月) | 112 | 10393:28:30 | 47:29:30 | 92:47:56 |
なお Node.js のアップデートなどで 1000 時間(41 日)以上かかった MR が数個ありましたが、外れ値として除外しています。
DORA quick testのすすめ
あなたの会社の CI/CD は適切に設計されていますか。
Google が運用する DORA(DevOps Research and Assessment)という DevOps プラクティスの評価と向上を研究するコミュニティがあります。そこで下記の 4 つのテーマでクイックテストが提供されています。
- リードタイム(コードが commit されてからリリースまで)
- デプロイ頻度
- サービスで障害が発生し、切り戻す速度
- 切り戻し速度
ぜひトライしてパフォーマンスを確認してみましょう。
そしてもし CI/CD を再設計するなら、この記事が何かのヒントになると嬉しいです。
まとめ
今回は基本的な CI/CD の考え方を踏まえつつ、税理士ドットコムで再設計した内容をご紹介しました。
ただ、まだまだ改善の余地があります。改善のひとつとして、コンテナの脆弱性スキャンの追加を試してみたのですが、軽量化された slim イメージのスキャンでもノイズが多すぎ、今回は見送りました。また CI 上でのキャッシュ効率をあげれば、デプロイ時間をより短縮できるはずです。
税理士ドットコムでは、プロダクトの改善以外にもメールサーバーを AWS SES から SendGrid へ切り替えたり、mysql8 化など技術的改善を進めています。
メンバー募集中なので、共に改善していく仲間をお待ちしております。