CircleCI を使って Geppo のデプロイを劇的に改善しました | Geppoプロダクトブログ
最近、ラテベースにハマっている岩下(@jaxx2104)です。
以前 Geppo プロダクトブログで取り上げた「テスト環境増殖プロジェクト」によって、開発環境についてはインフラ基盤の刷新とCIの導入によって誰でも気軽にデプロイができるようになりました。ただ本番については未対応のままだったのと各ドメイン(サービス)が密結合だったので、昨日11月11日にリリースしたデプロイ基盤の改善について紹介したいと思います。
デプロイはどんなことが課題だったか
まずは案件や障害対応といった業務をこなしていきながら、情報収集のために各メンバーに対してヒアリングをしながらインフラ構成図を最新化しました。あとはグループワークの時間を設けてバリューストリームマッピングをみんなで書いて Geppo の機能を顧客に届けるために必要なプロセスと改善点を可視化するなどしました。
バリューストリームマッピングによってプロセスと改善点を可視化
そこで出たのは以下のような課題でした
・デプロイ作業は権限を持ったサーバーサイドエンジニアが手動で実施しており人依存
・バックエンドとフロントエンドそれぞれ単体でデプロイが出来ないので1回のリリースに複数エンハンスが乗っかり改修コストの増加
・サービスがスケールするたびに比例してデプロイ時間が増加しており60分超え
例えば...
上記のような状態で、エンジニアが会議も全く無く1日6時間がっつりコードを書ける状態だったとしても一日 2, 3回しか開発環境へのデプロイが出来ないという感じでした。
実際だとコードを書くこと以外にも時間を消費していてかなりストレスフルですし、エンハンス(機能開発)以外にバグ対応、本番障害などエンジニアの全ての業務における制約になります。
なので上記の課題を解決できるデプロイ基盤を作ることにしました。
デプロイ基盤を設計する
マイクロサービスアーキテクチャ
マイクロサービスアーキテクチャを参考にデプロイ基盤を設計しました。
・テスト、デプロイなどは積極的に自動化する。
・サービスを単独でデプロイできるようにする。
デプロイ基盤の設計
実際の構成はこんな感じにしました。ウェブ版とネイティブアプリ版は別サービスなので分離、ウェブ版についてもフロントエンドは User Interface でバックエンドは Data Access Layer と責務が明確に異なる部分があること、組織もバックエンドとフロントエンドでチームが分かれていること、エンハンスもそれぞれ単体でエンハンスや障害対応があることからデプロイを分離しました。デプロイ基盤もそれぞれのリポジトリに適応できるように汎用性を持たせています。
各サービスの担当者がデプロイ可能な基盤
苦労したこと
この作業をやっていくなかで一番大変だったのは、モノリシックな構成からビジネスやドメイン観点でそもそもリポジトリ分離をする必要があったことです。依存モジュールや共通で使っている関数やスタイルを疎結合にしながら複数リポジトリにデプロイ基盤を実装するという対応をしました。
またデプロイも複数ドメインに対して1つで実行していたこともあって、HTML, JS, CSSといった静的ファイルについてはビジネスやドメインの追加に継ぎ足しで対応してきたこともあってディレクトリ構成が非常に複雑化していました。
C の成果物が A,B のディレクトリに依存している図
今回リポジトリ分離によって疎結合になったので静的ファイルについては同階層のディレクトリで明確に分けれるようになりました。
改善前は全てのドメインが同じリリースに乗っかるような状態でデプロイ運用していましたが、改善後はドメインに対応するリポジトリのデプロイだけやれば良い状態になり、エンジニアがよりプロダクトに向き合うことが出来る状態になりました。
ブランチやリリースタグでデプロイする
デプロイやリリース手順書を最小にすることで誰でもデプロイ作業ができる状態にしておきたいので、普段からやっているブランチやリリースタグの操作によってトリガーする仕組みを設定します。
・release か feature ブランチにマージされたら開発環境へデプロイ
・staging ブランチにマージされたらステージング環境へデプロイ
・リリースタグを作成したら本番環境へデプロイ
上記のような条件だと以下のような記述になります。
workflows:
version: 2
branch_build:
jobs:
- build
- deploy_dev:
requires:
- build
filters:
branches:
only: /^(release|feature).*?$/
- deploy_stg:
requires:
- build
filters:
branches:
only: /^staging.*?$/
tag_build:
jobs:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
- deploy_prd:
requires:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
https://circleci.com/docs/ja/2.0/configuration-reference/
https://circleci.com/docs/ja/2.0/configuration-reference/#tags
context 機能を使って環境変数を使い分ける
本番と開発環境のアカウントを別運用にしているので、管理のしやすさという観点で同じ環境変数名でジョブごとで認証情報を使い分けるということをしたくなります。
その場合 context 機能を使って同じ環境変数名のままジョブによって使い分けるということが出来ます。またプロジェクト間での利用も可能できるので便利です。
・aws-development のジョブは開発の認証情報
・aws-production のジョブは本番の認証情報
上記のような context だと以下のような記述になります。
workflows:
version: 2
branch_build:
jobs:
- build
- deploy_dev:
context: aws-development
requires:
- build
filters:
branches:
only: /^(release|feature).*?$/
- deploy_stg:
context: aws-development
requires:
- build
filters:
branches:
only: /^staging.*?$/
tag_build:
jobs:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
- deploy_prd:
context: aws-production
requires:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
Manual Approval 機能を使って並列にデプロイする
フロントエンドとバックエンドの単体デプロイが可能になりましたが、並列でデプロイをした際に反映タイミングを揃える仕組みが必要になってきます。
これについてはアプリケーションやインフラ層で対処する案なども考えましたが、CircleCI の Manual Approval 機能を使って並列デプロイをする場合は手動で承認することで帳尻を合わせるということにしました。
個人的にリリースタグの作成ミスはありそうだなと思っているので、そういうオペミス防止の観点でも承認デプロイという仕組みは良いなと思います。
workflows:
version: 2
branch_build:
jobs:
- build
- deploy_dev:
context: aws-development
requires:
- build
filters:
branches:
only: /^(release|feature).*?$/
- hold_stg:
type: approval
requires:
- build
filters:
branches:
only: /^staging.*?$/
- deploy_stg:
context: aws-development
requires:
- hold_stg
filters:
branches:
only: /^staging.*?$/
tag_build:
jobs:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
- hold_prd:
type: approval
requires:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
- deploy_prd:
context: aws-production
requires:
- build
filters:
tags:
only: /^v\d+\.\d+\.\d+$/
branches:
ignore: /.*/
これまでだとメンテナンス時間を1時間ほど必要としていましたが、この改善によってリリースによってはメンテナンス時間が最短10分で済むようになりました。
改善による結果
これらのリポジトリやデプロイの分離をしたのと、それぞれのジョブについても並列化をしたこと依存モジュールのキャッシュ最適化をしたことで単体/並列デプロイが可能になりデプロイ時間も劇的に改善しました 🎉
バックエンドとフロントエンドのWorkflow
改善前と改善後のデプロイ時間は以下のような感じになりました。
数値でも劇的に改善 🎉
これからについて
デプロイを改善したことでプロダクトに対する仮説に対して、より多くの検証を機能開発として実施出来るようになりました。また機能開発だけではなく品質の考え方については平均修復時間(MTTR)を重視し、障害対応をより早くできればいいなと思っていますし、改善についても機能開発と依存なくリリースが出来るようにしていくことで技術的負債を小さく返却していけるようなプロダクトを目指していきたいと思います。
リリースサイクルはエンジニアにとってガチャを引ける回数なので、これまでより多くの仮説検証を回せるようにしてプロダクトの価値を引き出したいと思っています。
リリースサイクルを月次から日次に
体制面について
体制面では、半期ごとキックオフを開催し今回の改善内容と今後目指すプロダクトについて話をさせてもらい開発だけでなく経営、セールス、マーケ、CSチームとの認識の共有が出来ました。直近で起こった取り組みでいうとリリースサイクルが見直されたことで会議体が定例のような1レーンではなく、プロジェクト単位で複数レーンで実施されるようになりました。
技術面について
デプロイ時間に関しては改善しましたが、以下のような部分についてはまだまだ改善の余地があると思います。
・Blue-Green Deployment で承認デプロイもメンテナンス時間も無くす
・Feature Toggle でブランチ運用でのコンフリクトやマージ待ち無くす
・Canary Release でクライアントハレーションを少なく機能届ける
技術的にも面白い取り組みだと思うのでコツコツと今回構築したデプロイ基盤を改善しながら、プロダクトブログで紹介できれば良いなと思います 💪
—
そんな Geppo を一緒に改善したいエンジニア絶賛募集中ですので、気になる方は以下の募集要項か Twitter などで声かけてもらえると嬉しいです!
この記事が気に入ったらサポートをしてみませんか?