アプリケーションはどこで動くのか?サーバ、コンテナ、サーバーレス、エッジ

アプリケーションを開発し、ローカルで動作確認ができた。次に、他の人が使える場所に配置する必要がある。「このアプリケーションはどこで動くのか?」という単純な問いが、ソフトウェアの構築、テスト、出荷のすべてを左右する。

答えは一つとは限らない。アプリケーションはオフィスのクローゼットにある物理サーバで動くかもしれない。クラウド上の仮想マシンで動くかもしれない。Kubernetesで管理されるコンテナとしてパッケージ化されるかもしれない。呼び出されたときだけ存在するサーバーレス関数として動くかもしれない。あるいは、ユーザの近く、IoTデバイスやネットワークノード上のエッジで動く必要があるかもしれない。

これらのターゲットごとに、CI/CDパイプラインの設計は変わる。ツールも重要だが、より深い問いは「パイプラインが何を処理する必要があるか」だ。それぞれのターゲットを順に見ていこう。

サーバへのデプロイ:物理または仮想

サーバに直接デプロイする場合、パイプラインはフルスタックを扱う必要がある。コードを出荷するだけではない。特定のOS、特定のミドルウェア、特定のバージョンのライブラリ、特定の設定ファイルを必要とするアプリケーションを出荷する。

ビルドプロセスは通常、バイナリ、パッケージ、またはファイルのセットを生成する。パイプラインはそれらのファイルをサーバに転送し、インストールし、アプリケーションを再起動する。ロールバックは、ファイルの置き換え、または同じマシン上の以前のバージョンへの復帰を意味する。

サーバデプロイのパイプラインは長くなりがちだ。サーバのプロビジョニング、依存関係のインストール、環境の設定、すべてが正しく動作するかの確認といったステップが必要になる。複数のサーバを管理する場合、それら全体の更新を調整する必要もある。

利点は制御性だ。マシン上で何を動かすかを正確に決定できる。欠点は、すべてのサーバがスノーフレーク(個別管理が必要な特殊な環境)になりがちなことだ。環境間のわずかな違い(ライブラリのバージョンが微妙に異なる、手動で編集された設定ファイルなど)が、再現が難しい問題を引き起こす可能性がある。

コンテナへのデプロイ

コンテナは状況を一変させる。アプリケーションとそのすべての依存関係はイメージにパッケージ化される。そのイメージは一度ビルドされ、どこにでもデプロイされる。コンテナ内部の環境は、開発、テスト、本番で一貫している。

パイプラインの焦点が変わる。サーバ設定の管理ではなく、イメージのビルド、レジストリへの保存、Kubernetesのようなオーケストレーションプラットフォームへのデプロイに集中する。ロールバックはより簡単になる。以前のイメージバージョンを指し示すだけでよい。

しかし、新たな課題も現れる。イメージのセキュリティを確保する必要がある。イメージのバージョンとタグを管理する必要がある。トラフィックを中断させずに実行中のコンテナを更新する必要がある。また、データベースのようなステートフルなコンポーネントはコンテナモデルにうまく適合しないため、その扱いも考慮する必要がある。

コンテナは一貫性と移植性を提供する。しかし、コンテナランタイム、オーケストレーション、ネットワーキングの理解が必要になる。チームは、サーバ上だけでなく、コンテナ内部で発生する問題のデバッグ方法を学ぶ必要がある。

サーバーレスへのデプロイ

サーバーレスは抽象化をさらに進める。サーバについて考える必要はまったくない。関数を書き、プラットフォームにアップロードすれば、プラットフォームが実行、スケーリング、可用性を処理する。

パイプラインはある意味でシンプルになる。関数コードをパッケージ化してデプロイするだけだ。プロビジョニングするサーバも、設定するOSも、管理するコンテナもない。

しかし、課題は別の領域に移る。関数のバージョンをどう管理するか?環境変数やシークレットをどう設定するか?実行環境が完全に制御下にない場合、関数をどうテストするか?コールドスタート(関数が最近呼び出されていないために応答に時間がかかる現象)にどう対処するか?

サーバーレスは、イベント駆動型のワークロード、トラフィックが変動するAPI、断続的に実行されるタスクに適している。運用オーバーヘッドを削減するが、ランタイム環境に対する制御は制限される。

エッジへのデプロイ

エッジデプロイは、別の種類の複雑さをもたらす。アプリケーションは多くの場所で、しばしば限られたリソースで動作する必要がある。IoTデバイス、ルータ、CDNノード、小売店のPOSシステムなどを考えてみてほしい。

パイプラインは、数千から数百万のデバイスへのアップデート配布を処理しなければならない。アップデートをプッシュするときに、オフラインのデバイスもあるかもしれない。ネットワーク接続が不安定なデバイスもあるかもしれない。簡単に交換できないハードウェアで動作するデバイスもあるかもしれない。

エッジでのロールバックは難しい。スイッチを一つ切り替えて、すべてのデバイスを一度に元に戻すことはできない。段階的なロールアウト、アップデートを見逃したデバイスの処理、アップデート後に失敗したデバイスの復旧のための戦略が必要になる。

エッジデプロイはソフトウェアの問題だけではない。ロジスティクスの問題でもある。遠隔地のデバイスに正しいバージョンを確実に届けるにはどうするか?常に接続されているとは限らないデバイスをどう監視するか?ストレージやメモリが不足したデバイスにどう対処するか?

ターゲットは永続的ではない

重要なのは、デプロイターゲットは永続的な決定ではないということだ。同じアプリケーションが時間の経過とともにターゲット間を移動することもある。物理サーバから始め、仮想マシンに移行し、その後コンテナに移行し、さらにアプリケーションの一部をサーバーレス関数に分割するかもしれない。

移動のたびにパイプラインは変化する。ビルドプロセスが変わる。デプロイ戦略が変わる。ロールバックの仕組みが変わる。監視と可観測性の要件が変わる。

鍵となるのは、各ターゲットがパイプラインに何を要求するかを理解することだ。単にどのツールを使うかではない。その影響を理解していれば、最新のトレンドではなく、実際のニーズに合ったパイプラインを設計できる。

デプロイターゲット選択のための実践的チェックリスト

ターゲットを決める前に、以下の質問を検討しよう。

次のフローチャートは、これらの質問への回答がどのようにデプロイターゲットにつながるかを可視化するのに役立つ。

flowchart TD A[開始: 要件は何か?] --> B{環境を完全に制御する必要があるか?} B -- はい --> C[サーバ: 物理またはVM] B -- いいえ --> D{デプロイ間で一貫した環境が必要か?} D -- はい --> E[コンテナ: Docker, Kubernetes] D -- いいえ --> F{イベント駆動または変動するトラフィックか?} F -- はい --> G[サーバーレス: 関数] F -- いいえ --> H{多くの分散ロケーションがあるか?} H -- はい --> I[エッジ: IoT, CDN] H -- いいえ --> J[要件を再評価] C --> K[考慮点: プロビジョニング、構成管理、ロールバックの複雑さ] E --> L[考慮点: イメージセキュリティ、オーケストレーション、ステートフルサービス] G --> M[考慮点: コールドスタート、限られたランタイム制御、テスト] I --> N[考慮点: オフラインアップデート、段階的ロールアウト、デバイス管理]
  • チームはどの分野に最も経験があるか?サーバに詳しいチームは、Kubernetesよりもサーバデプロイの方が苦労が少ない。
  • ランタイム環境をどの程度制御する必要があるか?制御が増えるほど、パイプラインの複雑さも増す。
  • ロールバックをどのように行うか?ターゲットによって、ロールバックが簡単なもの(コンテナ)もあれば、困難なもの(エッジデバイス)もある。
  • デプロイをどのようにテストするか?サーバーレスやエッジ環境は、ローカルで再現するのが難しい。
  • トラフィックパターンはどうか?安定したトラフィックにはコンテナやサーバが適している。急激なトラフィック変動にはサーバーレスが適している。
  • 管理するインスタンス数はいくつか?数台のサーバは管理可能だが、数千台のエッジデバイスには別のアプローチが必要になる。

最も重要なこと

デプロイターゲットはパイプラインの形状を決定する。ビルドが何を生成するか、テストがどのように実行されるか、アップデートがどのようにユーザに届くか、障害からどのように回復するかを決める。

選択は、アプリケーションのニーズ、チームの能力、運用の現実に基づいて行うべきだ。コンテナが人気だからとか、サーバーレスが未来だからという理由ではない。適切なターゲットとは、確実に運用でき、安全に更新でき、問題が発生したときに効果的にデバッグできるターゲットのことだ。

パイプラインはその選択を反映すべきであり、それに逆らうべきではない。