こんにちは! QAの諸星です。
何かと耳にすることが多いCI/CD、他社ではどう導入したのか気になりませんか? 今回は弊社のとあるプロダクトでの事例をご紹介します!
なぜCI/CDの導入に至ったのか?
以前、そのプロダクトにはデプロイもテストもすべて手動で属人的な偏りがありました。
そのため、作業効率向上やリスク低減などの側面から、CI/CDの実践を目指す動きが必要だということは開発メンバーの共通認識でした。
課題は山積みだった
- テストの実施環境が整備されていないために、テスト実施のコストが高かった
- サーバーの環境変数の情報整理が進んでおらず、テスト環境を容易に増やすことができなかった
- デプロイするためにサーバーに直接入って手動でコマンドを叩いていた
- リリース前にコンフリクト解消した場合に、その後のテスト再実施が限定的になっていた
現在振り返ってみて
当初は「CI/CD」の「C」にも及ばない状況でしたが、開発メンバーが同じ方向を目指して自律的に動いた結果、今は「CI/C」くらいには到達できていると感じます。特に重要な成果は「いつ・どの環境でテストが行われるか迷わない」「リリース作業自体に不安がない」という2点です。開発者の不安や迷いの原因を解消したことで、継続的インテグレーション・継続的デプロイという理想に近付くことができたと考えています。
CI/CDがどのように導入されていったのか?
- テスト環境の整備
- GitHub Actionsでデプロイを自動化
- リリース集約の自動化
- Huskyを使ってcommit前にLinterや単体テストを自動実行
1. テスト環境の整備
最初に取り組んだ課題はテスト環境の整備でした。なぜならリリースする成果物の品質担保が最優先だからです。
前述のとおり、当時はサーバーの環境変数の情報整理が進んでいませんでしたが、SREチームが数か月かけて環境ごとの設定をコードベースで管理下に置くことに成功しました。その後、開発環境・ステージング環境・本番環境の3つに整理されていきました。振り返ってみると、環境整備がもっとも大変だったように感じます。
2. GitHub Actionsでデプロイを自動化
以前は検証環境や本番環境への適用が手作業だったため、手間がかかる上に危険を伴う作業でした。そこで、テスト環境の整備の次にデプロイ自動化に着手しました。
ツールはGitHub Actionsを使用し、コードはYAMLで記述しました。実際に書き上がったコードは21行で済んだ上に、GUI上の操作だけでデプロイが完結するようになりました。
それまでの問題点
- もともとは踏み台サーバーの中に開発者個人がログインしていた
- サーバー上で手動でAnsibleを実行するコマンドを叩くことで、本番環境へのデプロイを行っていた
GitHub Actionsによるデプロイ自動化の成果
- 省力化:デプロイ作業がボタン1つでデプロイ可能になった
- 作業の標準化:誰がデプロイしても同じ結果が得られるようになった
- リスク低減:開発者はサーバーの中にログインする必要がなくなり、作業ミスのリスクが減った
3. リリース集約の自動化
個別にリリース?
以前は各自のタイミングでリリース作業を行っていました。そのため、問題が発生した場合に対応できるメンバーが少なかったこともありました。また、リリース直後に本番に近い環境でしか見つからないバグを見つけてしまうなどの悔しい問題も発生していました。
みんなでリリースする
そこで「本番に近いデータ・スペックを持つステージング環境で最終確認をすること」と「リリースを複数人でミスなく行う」の2つを目標にリリース作業全体を見直しました。その結果、1日1回集約をして複数人でリリース作業を行うことにしました。集約とリリース作業はSlack botやGitHub Actionsを活用して自動化しました。
新しいリリース集約フロー
- 毎日「リリース集約するリス」というSlack botが出現する
- 開発者は修正ブランチをdevelopブランチ(デフォルトブランチ)にマージして、リリース集約リスに報告する
- 翌日、developにマージした内容がステージング環境に反映される
- 開発者はステージング環境で最終確認を実施する
- リリース日当日、developにマージしたメンバーがGoogle Meetに集合して、デプロイワークフローを実行する
リリース作業が劇的に省力化
新しい集約フローの導入によって、リリース回数が減ったことで作業にかかる時間は削減されました。また、それまで1〜2名で行っていたリリース作業を4〜5名で行うようになったことで、リリース作業者の心理的負担も軽減されたそうです。また、デプロイ作業をGitHub Actionsが実行してくれている待ち時間に、雑談によるコミュニケーションが促進されるという思わぬ側面もありました。
一方で新しい課題も
リリースを集約することはメリットばかりではありませんでした。一度のリリースで複数の修正がリリースされることで、revertやhotfixの複雑さは上がってしまいました。
長い目で見たときにはリリースを集約することが最善ではないと考えています。CI/CDの理想からいえば、集約などせずに自動的にリリースされるべきです。しかし、理想に近づくための過程において一時的に相反する方針をとることも完全に悪ではないと考えています。
4. 単体テストを自動実行
次はテストの自動実行についてです。この時点で既にテスト環境やブランチ戦略が整備されていたため、手を動かせば達成できるタスクになっていました。
commitごとにローカル環境でJestとPHPUnitを実行するために使ったツールは、Huskyとlint-stagedです。Huskyはどんなツールかというと、Gitの特定のアクションが行われたことを検知してアクションを実行してくれる、番犬のような存在です。もう一方のlint-stagedは、git add
したファイルだけを対象に指定のコマンドを実行できるツールです。しかも、lint-stagedにはファイル名のパターン(e.g. *.php
)ごとに異なるコマンドを設定可能なので、「*.jsをaddした場合だけJestを実行する」といったことが可能です。
この2つを組み合わせることで、「commitの度に、addしたファイルを対象にJestやPHPUnitを実行する」といったことが実現可能になりました。
それまでの問題点
- 有志が少しずつコードベースの単体テストを書いていたが、浸透していなかった
- 失敗するテストが放置されていた
- 単体テストもLinterも実行するタイミングが無かった
テスト自動化によって得られた成果
- 単体テストが常にAll Greenの状態で常にリリースできている
- テストを作る・メンテナンスする意識につながった
- TDDを実践しやすい土壌ができた
工夫したこと
ちなみに、commitごとに開発環境で実行するテストはaddしたファイルだけを対象にしていますが、pushした後にGitHub Actionsを使ってランナー上で実行するテストはリポジトリ内の全ファイルを対象としています。このように実行環境ごとにテスト対象を見直すことで、開発中にcommitに時間がかかりすぎることを防いでいます。
導入にあたって一番骨が折れたのは、開発環境(Mac)とランナー(Linux)の環境差分でした。Macでは大文字・小文字を区別しませんが、Linuxでは厳密に区別します。そのため、大文字・小文字に誤りがあるとMacでは成功するテストがLinuxだと失敗してしまうのです。この問題に気づいてからテストコードをすべて修正するまで1週間ほどかかってしまいました。
まとめ
以前はデプロイもテストもすべて手動で属人的な偏りがありましたが、自動化に伴って「作業の標準化」「省力化」など様々な成果が得られました。この成果は決してひとりの頑張りが生んだわけではなく、開発メンバーが協力し合ったからこそ生まれたものだと私は感じています。実際、このCI/CDの導入・推進は誰からも指示されたものではなくて、各人が必要だと思う行動を積み重ねていった結果です。私はそんな開発メンバーと一緒に働けて幸せだなと思っています。
今回取り上げたGitHub Actionsの実装に関する記事も機会があればそのうち書きたいと思います。執筆の励みになりますので、ご興味がある方は☆のクリックもよろしくお願いします!
株式会社コドモンでは一緒に働く仲間を募集中です。
自律的な働きが大きな流れを生み出し、一緒に波に乗っていく。そんな職場環境に興味がある方はぜひお気軽にエントリーください。