クラウドベースのソフトウェア開発プロジェクトの多くでは、アプリケーションを効率よく、安全に、かつ生産的にクラウド環境へデプロイするためのCI/CDプロセスが整備・運用されています。
CI/CDをめぐる議論はクラウド側・リモート側に集中しがちで、その直前の工程——開発者のノートPC上で行われるローカル開発やテスト——が話題に上ることはあまりありません。
本記事では、ローカル開発でよく使われる手法の多くが理想とはほど遠い理由を示したうえで、生産性を高める代替アプローチとしてクラウドネイティブな手法のデモをご紹介します。🌤
Photo by Christina @ wocintechchat.com on Unsplash
👨🏼💻 ローカル開発
万能なベストプラクティスは存在せず、組織やプロジェクトの要件によって異なりますが、典型的なCI/CDフローはおおむね次のような形になります。

ここでは、ステップ#1の直前に位置する「ローカル開発」に注目してみましょう。
多くの場合、ローカル開発は開発者のマシン上で行われます。具体的には、計画、設計、コーディング、テストの作成・実行、ときにはPOC、さらには手動での動作確認まで含まれます。
新しい機能や修正が他の機能やロジックにリグレッションを引き起こさず正しく動くかを検証するため、クラウド環境を模したローカル環境の構築に多大な労力を割いているプロジェクトも少なくありません。
こうしたセットアップは主に、システムテスト、統合テスト、E2Eテストといった自動テストの実行に使われます。もちろん、手動テストだってやろうと思えば可能です 😏。
さらに、ローカル環境はコードのデバッグにもよく使われます。私が「デバッグ駆動開発」と呼んでいるスタイルを愛用する開発者もいますが、用途はそれだけにとどまりません。手強いバグの調査や、複雑なフローを把握したいときにもデバッグは大いに役立ちます。
ローカル開発でよく使われる手法
Kubernetesにデプロイされるマイクロサービス群を扱うプロジェクトに取り組んでいると仮定しましょう。業界でよく見かける手法をいくつか挙げてみます。
- Docker-compose 方式:既存のK8s YAMLマニフェストの複製のような形で、自分のサービスとサードパーティ依存をローカルで起動するためのdocker-composeマニフェストを作成・保守する方法。
- ローカルK8s 方式: minikube/k3s/kindなどでローカルにKubernetesクラスターを立ち上げ、各種スクリプトと組み合わせてサービスをローカルにデプロイしてテストする方法。
- テストスイート方式: Makefileやbashスクリプトでサービスごとの統合テストスイートを起動し、サードパーティ依存をDockerコンテナとしてローカルで立ち上げる方法(dockertestなどが代表例)。
いずれもアプリケーションをローカルで実行・テストするための有力な選択肢です。
従来のローカル開発手法の弱点
とはいえ、上記の手法にはいくつかの弱点があります。
サードパーティ側でローカル実行がサポートされていない
クラウドベースのサードパーティ依存の中には、ローカル実行手段を提供していないものがあります。「モック化/スタブ化」によってAPIをエミュレートできる場合もありますが、保守の手間が大きく、現実を忠実に再現できるわけではありません。言ってしまえば「ごまかし」です。たとえばDBクエリにバグがあった場合、本物のDBインスタンスと「対話」しないテストやデバッグで、それをどうやって突き止めるのでしょうか?
リソース不足の問題
アプリケーションが多くのリソースを必要とする場合、ノートPCでは力不足になることがあります。たとえばメモリリークを再現するために負荷テストを実行する、あるいはレプリカが20個同時に動いて初めて再現するバグを追っている、といったケースです。気づけばノートPCがフリーズしている——なんてこともしばしば 😅。これでは生産性が大きく削がれます。
デバッグ中のシャットダウン
数日に一度しか発生しないような厄介なバグをテスト・デバッグしたい場合、ローカルマシンがシャットダウンやスリープ、休止状態に入ってしまい、最初からやり直しになることがあります。
シングル・ソース・オブ・トゥルースの欠如
Docker-compose 方式では、環境を表す定義を複数管理することになり、「シングル・ソース・オブ・トゥルース」の原則に反してしまいます。
統合テスト ≠ 実際の統合
テストスイート方式における統合テストは、(ローカル実行であっても)多くのメリットがある優れた手法です。とはいえ、実際のクラウド環境上で変更を統合することと同じではありません。他サービスへのネットワーク接続性の問題、設定の不備、起動時クラッシュなど、検出できない統合・デプロイ起因のバグや障害が残ります。
ここで挙げた弱点はいずれも生産性を下げ、開発体験を損ないます。しかも多くの場合、現実を反映していません(おなじみの「俺のマシンでは動くんだけど!」パターンですね)。これでは得られる価値も限定的です。
では、生産性も使い勝手も高めながらローカル開発フェーズに臨める、もっとモダンな方法はないのでしょうか?あります。
☁️ **_クラウドネイティブな開発者環境_**
ここからは、リモートのクラウドネイティブ環境について見ていきましょう。
ローカル環境をリモート環境へと置き換えることで、上述の問題は解消されます。さらに、開発体験はモダンで信頼性の高い、クラウドネイティブなものへと進化します。
クラウドネイティブな開発者環境はどう機能するのか?
各開発者に個別のクラウド環境を割り当てます。これは開発・本番環境の(完全ではないものの、十分に近い)レプリカです。
クラウドネイティブな個人環境のアプローチを採用すれば、チームの各開発者は自分の環境を自由に使えるようになります。新機能やバグ修正のテスト、バグの再現、自己学習のための実験——いずれも本番に極めて近い環境で実施できます。✌️
さらに、これらの環境を活かすツール群を整えれば、価値はいっそう高まります。たとえば手動・自動テストの実行、本番トラフィックを個人環境に「ミラーリング」する、といった応用も可能です。
もうひとつ思い浮かぶのは、これらの環境上でライブのリモートデバッグを行うことです。完全に隔離された状態で、他の環境に干渉することなくデバッグできます。この用途のために用意された既存のツール(次章で実例を紹介します)を活用するとよいでしょう。
生産性・保守性・信頼性で得られるメリット
クラウドネイティブな開発者環境に切り替えるべき理由は数多くありますが、ここでは私が特に推したい8点を共有します。
具体的には:
- 既存のK8s YAMLマニフェストをそのまま再利用でき、デプロイ設定を二重に保守する必要がなくなる(DRY原則を維持 🌵)。
- クラウド環境へのデプロイが、開発・本番へのデプロイと同じ手順で行えるうえに、より強い隔離が得られる。
- ローカルハードウェアへの依存から解放される。クラウドインフラなら、リソースを大量に消費するテストでスケールアップし、週末はスケールダウンするといった運用も容易。
- ノートPCのシャットダウンや休止モードを心配せず、長時間のテストを環境内で実行できる。
- モックではなく実際のクラウドサービスAPIを利用するため、統合上の問題を素早く発見できる(例:PubSub、Cloud SQL、Datastoreなど多数のクラウドサービスとの通信)。
- 本番で恩恵を受けているのと同様に、モニタリング、アラート、プロファイリング、トレーシング、ログ集約といったクラウドの組み込みインフラを活用できる。
- 毎回手の込んだセットアップスクリプトを走らせる必要がなく、時間を節約できる。クラウドは高速で、必要なときに環境がすぐ整う。
- 初心者であれば、他の環境に影響を与えずにこれらの環境でKubernetesを実践的に学べる。
そして開発者だけでなく、DevOps/SREエンジニアも、共有環境を変更する前に自分の環境でインフラ設定の変更を試せるという形で、個人環境の恩恵を受けられます。
🙋♂️ ちょっと質問が!
クラウド料金が爆発しないか心配です
真っ先に思いつくのは、開発者ごとに完全に独立したクラウドインスタンスのレプリカをプロビジョニングし、プロジェクト単位で隔離して、各プロジェクトにインフラのレプリカを持たせる構成です。
これは一般的にはよい実践で、特にセキュリティの観点では強力な隔離が得られます。しかし、会社やチームの規模が大きくなるにつれ、このアプローチのコストが問題になりがちです。
多くの場合、開発環境は本番環境から隔離されているため、セキュリティとコストのトレードオフを取る余地があります。
補足:セキュリティとコストのトレードオフは組織や要件によって異なります。影響をしっかり把握したうえで判断してください。GKE Security guideが参考になります。
では、どうトレードオフするのか? クラウドサービスのマルチテナント機能を活用し、環境間でリソースを共有することでコストを抑えられます。
これは「ソフトマルチテナンシー」と呼ばれるもので、「ソフト」とは、同じ「信頼された」組織内の複数ユーザー間でリソースを共有することでリスクを限定する、という意味です。
たとえば、あるアプリケーションを開発するチームの全開発者でK8sクラスターを共有し、開発者ごとに自分のK8s namespace内でアプリケーションスタック全体を複製する、といった構成です。
もうひとつの例は、全開発者が単一のDBインスタンスを共有し、開発者ごとに専用のDBユーザー・スキーマ・テーブルを持つ構成です。
次章でデモするのは、まさにこのアプローチです。
セットアップや保守は大変ではありませんか? 🤕🥸
そんなことはありません!Infrastructure as CodeとGitOpsの原則に基づき、自動化と再現性を高めることが可能です(そうすべきです)。
すべての設定がコードで記述されていれば、個人環境の作成・破棄も数クリックで完了します。プロセスが自動化されている分、人為的なミスの余地も減ります。
なぜ開発環境のスコープに含めないのか? 🤔
従来の開発環境は隔離されておらず、すべての開発者で共有されています。あなたがコードをマージするのと同時に同僚も自分のコードをマージするため、本質的には「るつぼ」のような状態です。チームが大きくなるにつれ、共有環境でのデバッグや実験は現実的ではなくなります。
加えて、開発環境のステージはタイミングとしてやや遅すぎます。すでにPRを作成し、コードレビューを通し、PRをマージした段階で、自分の変更がサービスを即座にクラッシュさせると気づく——これはブロッカー扱いとなり、リバートやロールバック、ホットフィックスを要します。あなたにとっても他の開発者にとっても、時間の浪費です。
個人環境か、エフェメラル環境か? 👀
シンプルに言えば、選ぶのはあなた次第です。
基本的に、個人環境は特定の人に長期的な目的で割り当てる環境です。一方、エフェメラル環境は特定の変更のために作成され、その変更がマージされたあとに破棄される環境です。
同僚の
がエフェメラル環境について素晴らしい記事を書いているので、ぜひそちらもご覧ください。
もちろん両方を採用することもできますが、ニーズによっては過剰になる可能性があります。
Part 2では、Terraform、ArgoCD、Telepresenceを用いたアーキテクチャ例で、こうしたセットアップを具体的に紹介します。Part 2へ進む!