For the complete documentation index, see llms.txt. This page is also available as Markdown.

プロダクト開発のための Kubernetes 入門

ウォンテッドリーのサービスを構成するマイクロサービスは、そのほとんどが Kubernetes クラスタ上で動いています。新しいコードのデプロイや、開発用クラスタで rails c のような一時的なコマンドを実行するといった日常の作業は、インフラチームの手を借りずにすべてのエンジニアが自分で行えるようになっています。

この章は、プロダクト開発を進めるうえで知っておきたい Kubernetes の最低限の概念 と、ウォンテッドリーでそれをどう使っているか をまとめた入門です。インフラの専門知識は前提としません。普段はアプリケーションコードを書いているバックエンド・フロントエンドのエンジニアが、開発用クラスタを触れるようになることを目指しています。

このドキュメントで扱わないもの:

  • kube コマンドの詳細な使い方

    • kube を参照してください。

  • Rolling Update や Canary などデプロイ戦略の詳細

  • ウォンテッドリーが利用しているクラウドサービスやネットワーク構成

なぜ Kubernetes を使うのか

ウォンテッドリーのプロダクトは多数のマイクロサービスで構成されており、それぞれが独立してデプロイされ、お互いに通信しています。このような構成では、以下のような課題が常に発生します。

  • アプリケーションが落ちたときに作業が必要

    • 回復可能であれば自動で再起動してほしい

  • アクセスに対して計算リソースが追いつかない

    • 自動で台数を増やしてほしい

  • 新しいバージョンのアプリケーションをリリースした時にダウンタイムが発生する

    • ユーザーに影響なくバージョンを入れ替えたい

  • 他のアプリケーションと通信する際にいちいち接続先を指定しなければならない

    • お互いの台数や IP の変化を意識せず通信したい

Kubernetes はこれらを 宣言的 に解決するための基盤です。例えば「このアプリを 3 台で動かしてほしい」と宣言しておけば、Kubernetes が常にその状態を保つよう動き続けます。台数が足りなくなれば自動で増やし、壊れれば自動で立て直す。こうした性質を セルフヒーリング と呼びます。

コンテナと Kubernetes の関係

コンテナと Kubernetes は役割が異なります。コンテナはアプリケーションとライブラリを一つにまとめた実行環境であり、Kubernetes はそのコンテナを宣言した通りの状態に保ち続けるためのインフラです。

コンテナとは何か

コンテナとは、アプリケーションのコードと、その実行に必要なもの一式(言語ランタイム、ライブラリ、設定ファイルなど)を一つのパッケージにまとめ、ホスト OS から隔離された状態で動かす仕組みです。ここでいうランタイムとは、Ruby や Node.js のように、書かれたコードを実際に解釈・実行するためのソフトウェアのことです。Rails アプリを動かすには Ruby ランタイムが、Node.js アプリを動かすには Node.js ランタイムが必要になります。

こうしたランタイムやライブラリのバージョンは、ホストごとに微妙に違うことが珍しくありません。コンテナはこれらをすべてパッケージの中に同梱してしまうため、「自分の手元では動いたのに本番では動かない」という環境差分の問題が起きにくくなります。

このパッケージの設計図にあたるのがイメージです。イメージは「どの OS をベースに、どのライブラリを入れ、どのコードを置き、どのコマンドで起動するか」を記述したテンプレートで、ファイルとして配布・共有できます。イメージを実体として起動したものがコンテナです。クラスを定義してインスタンスを作るのと同じ関係で、1 つのイメージから何個でもコンテナを起動できます。

コンテナとイメージを扱うためのソフトウェアをコンテナランタイムと呼び、もっとも広く使われているのが Docker です。ウォンテッドリーでは、各マイクロサービスのリポジトリに Dockerfile(イメージの作り方を書いたファイル)が置かれており、CI がそれを元に Docker イメージをビルドします。

コンテナはアプリケーションを「どこでも同じように動く単位」にする

イメージという形でアプリと依存をまとめてしまうと、開発者のローカル・QA・本番のいずれでも同じイメージをそのまま動かせます。ウォンテッドリーのマイクロサービスは Rails / Go / Node.js などランタイムが異なりますが、CI で Docker イメージとしてビルドされた後は、どのクラスタでも同一の成果物が動きます。

ただし、コンテナそのものは「アプリを 1 回起動する単位」を定義しているだけで、何台動かすか・落ちたらどうするか・他のサービスとどう通信するかには関与しません。手元で docker run してプロセスが落ちれば、それは止まったままになります。本番サービスとして安定して動かし続けるには、コンテナの外側で誰かが面倒を見る仕組みが必要になります。

Kubernetes は「あるべき状態」を宣言として受け取り、現実を一致させ続ける

その仕組みが Kubernetes です。Kubernetes は、手続き(どう動かすか)ではなく状態(どうあってほしいか)を宣言として受け取るモデルを採用しています。例えば「このイメージから作ったコンテナを 3 台動かしてほしい」と宣言しておけば、Kubernetes は現在の状態を監視し続け、台数が 3 台になっていなければ自動で過不足を調整します。これが前章で触れた宣言的な解決であり、セルフヒーリングの正体です。

この分業によって、アプリケーション開発者が見るレイヤ(Rails / Go / Node.js のコード)と、Kubernetes が見るレイヤ(どのサーバーで何台動かすか、落ちたらどう立て直すか)はきれいに分離されます。エンジニアは成果物としてのコンテナイメージを差し出すことに集中でき、配置や再起動の責任は Kubernetes に委ねられます。

Kubernetes の基礎知識

なぜ Pod や Deployment が必要なのか

前章で見たように、コンテナは「アプリを 1 回起動する単位」を定義しているだけで、何台動かすか・落ちたらどうするか・他のサービスとどう通信するかには関与しません。本番でサービスを動かし続けるには、その外側に次のような役割を担う仕組みが要ります。

  • コンテナを実際にどこかのサーバー上で起動し、必要なリソースを割り当てる役 → Pod

  • 同じ種類の Pod を何台動かすかを管理し、壊れたら立て直す・新しいバージョンに入れ替える役 → Deployment

  • 使い捨ての Pod 群に対して、安定した名前で通信できるようにする役 → Service

  • クラスタの外(ユーザーのブラウザなど)から内部の Service へリクエストを通す役 → Ingress

  • 多数のサービスのリソースを混ざらないように分けて管理する役 → Namespace

Kubernetes の基本概念は、いずれも前章の課題(セルフヒーリング・台数の自動調整 etc.)に対する具体的な答えになっています。

開発するうえで気をつけること

これらの概念の上でアプリケーションを書くときに、特に意識しておきたいことが 3 つあります。詳細は各節と後段で扱います。

  • Pod は使い捨てである前提でコードを書く: Pod はいつでも壊れたり別のサーバーに移されたりします。ローカルディスクに長期保存したい状態を置いたり、「自分が同じ Pod で動き続ける」ことを期待した実装をしてはいけません。

  • 新旧バージョンが一時的に同居することを意識する: Deployment による入れ替え(Rolling Update)では、古い Pod と新しい Pod が短時間ですが同時に動きます。API・DB スキーマ・キャッシュ形式などは、両方のバージョンが同居しても壊れないように後方互換を保って変更します。

  • 接続先は Service 名で指定する: Pod の IP は再起動のたびに変わるため、別のマイクロサービスへ通信するときは Pod の IP ではなく Service 名を使います。台数を増減させても呼び出し側のコードを変える必要がなくなります。

  • マイクロサービスごとに 1 つの Namespace を割り当てられている: kube コマンドは現在のディレクトリのリポジトリから対応する Namespace を自動的に判定します。

Kubernetes のリソースの紹介

Kubernetes には非常に多くの概念がありますが、プロダクト開発を始めるうえで最低限おさえたいのは次の 5 つです。

Pod — アプリケーションの最小単位

Pod は Kubernetes 上で動くアプリケーションの最小構成単位で、1 つ以上のコンテナをまとめたものです。多くの場合「1 つのアプリケーションサーバー = 1 つの Pod」と考えて差し支えありません。Pod は複数の Node(Pod を動かす計算リソース。物理マシンや VM のこと)上に分散して配置されます。

Pod は使い捨てを前提にした存在で、壊れたり再起動したりすると別の Pod に置き換わります。したがって、アプリケーションは「自分が同じ Pod で動き続ける」ことを期待してはいけません。

Deployment — Pod の世代管理とセルフヒーリング

Deployment は Pod の管理役です。「この Pod を何台動かしてほしい」「今動いているバージョンを新しいものに入れ替えてほしい」といった指示を受け、実際の状態がそれに一致するように Pod を作ったり消したりし続けます。

たとえば Deployment に「3 台動かす」と宣言してあれば、Pod が 1 台壊れても Kubernetes が自動で新しい Pod を立ち上げて 3 台構成を保ちます。新しいバージョンをデプロイしたいときも、Deployment が古い Pod を少しずつ新しい Pod に置き換えます(これを Rolling Update と呼びます。詳細は リリース・デプロイ戦略 を参照)。

関連する概念として、定期的に Pod を起動する CronJob や、一度だけ実行される Job もあります。バッチ処理や DB マイグレーションなどで使われます。

Service — Pod への通信口

Pod は使い捨てで IP も変わり続けるため、別のアプリケーションが Pod に直接アクセスするのは現実的ではありません。Service はその間に立って、「この名前で通信すれば、裏にいる Pod のどれかに届く」というエンドポイントを提供します。

Service は Pod に対するロードバランサーとしても働きます。同じ Deployment が管理する複数の Pod に対して、Service 経由で通信することで自動的に分散されます。

Ingress — 外部からの HTTP 入口

Service だけではクラスタの外からはアクセスできません。Ingress は HTTP レベル(L7)のルーティングを担当し、「このホスト/パスへのリクエストはこの Service に流す」というルールを定義します。ユーザーからのリクエストは、クラスタ外部のロードバランサー → Ingress → Service → Pod という経路で届きます。

Namespace — リソースのグルーピング

Namespace は Kubernetes 上のリソースを分類するための名前空間です。同じ名前の Pod や Service も、異なる Namespace に置けば共存できます。操作権限の単位にもなっており、「この Namespace の中だけ触れる」といった制御もできます。

ウォンテッドリーでは マイクロサービスごとに 1 つの Namespace を割り当てる というルールで運用しています。これにより、リポジトリと Namespace が自然に対応づき、どのリソースがどのサービスのものか一目で分かります。

Object の階層イメージ

ここまでに登場した Object の関係性を図にすると次のとおりです。

ウォンテッドリーにおける Kubernetes の使い方

ここからは、ウォンテッドリーで普段どのように Kubernetes を使って開発しているかを説明します。

3 つのクラスタ(Production / QA / Sandbox)

ウォンテッドリーでは用途の異なる 3 つのクラスタを運用しています。「QA環境」や「Sandbox クラスタ」、「本番環境」と呼ばれることもあります。

  • Production: ユーザーからのアクセスを受ける本番環境です。

  • QA: 本番デプロイ前の動作確認用クラスタです。原則として本番と同じバージョンのコードが動いており、QA テストチームや社内のビジネスサイドが動作確認に使います。

  • Sandbox: 開発用クラスタです。

後述する kube コマンドは、第一引数で対象クラスタを明示します(例: kube sandbox get pod)。これは、うっかり本番に対して操作してしまう事故を防ぐためです。

マイクロサービスと Namespace

前述のとおり、マイクロサービスのリポジトリごとに Namespace が 1 つ割り当てられます。kube コマンドは 現在のディレクトリのリポジトリから対応する Namespace を自動的に判定する ため、基本的には「操作したいマイクロサービスのリポジトリに cd してから kube を叩く」という使い方になります。

環境変数は dotenv Secret で管理する

アプリケーションの設定値はコードに埋め込まず環境変数で渡すのが基本です。ウォンテッドリーでは各 Namespace に dotenv という名前の Secret を作り、そこに設定値を入れて Pod の環境変数に展開しています。

値の読み書きは kube 経由で行います。直接 kubectl で Secret を編集することは通常ありません。

自動デプロイの仕組み(CI/CD と Argo CD)

ウォンテッドリーのアプリケーションは、コードを master ブランチ(または main ブランチ)にマージすると自動的にデプロイされます。流れは大きく 2 系統あります。

  • アプリケーションコード: CI が Docker イメージをビルドしてテストを実行し、成功すれば Kubernetes 上の Pod を新しいイメージに入れ替えます。

  • Kubernetes マニフェスト: マニフェストのリポジトリに変更がマージされると、Argo CD が GitOps の仕組みでクラスタに反映します。

入れ替えは Rolling Update で行われるため、古いバージョンと新しいバージョンが一時的に同居する時間が存在します。Pull Request を設計するときは「バックエンドとフロントエンドの変更を分ける」「DB スキーマの変更とそれに依存するコードの変更を分ける」といった後方互換性の意識が重要です。

Kubefork — 自分専用の開発環境

開発中のコードを試すたびに Sandbox に直接デプロイすると、他の開発者の作業を壊してしまうことがあります。これを避けるために、ウォンテッドリーには Kubefork という仕組みがあります。

Kubefork は、既存の開発用クラスタを擬似的にコピーして「自分専用の仮想クラスタ」を作る機能です。特定のリクエストだけを自分の fork に流すことで、他の人に影響を与えずに自分の変更を確認できます。Pull Request を作ると PR ごとに自動で Kubefork 環境が用意されるので、レビュワーに動作を見てもらうときにも便利です。

具体的な使い方は Kubefork を参照してください。

よく使う kube コマンド

ウォンテッドリーのクラスタを操作する入口は、社内 CLI の kube です。ここでは概念と紐付けた代表例だけを載せます。コマンドの詳細や拡張機能は kube にまとまっています。

やりたいこと
コマンド例

動いている Pod の一覧を見る

kube <env> get po

Pod のログをリアルタイムで見る

kube <env> tail

デプロイ済みの Pod 上でコマンドを実行する(例: Rails console)

kube <env> sh -d rails c

自分のブランチのコードを Pod 上で実行する(例: DB マイグレーション)

kube <env> sh -c rails db:migrate

自分のブランチを Sandbox にデプロイする

kube sandbox deploy -c

環境変数を追加/更新する

kube <env> dotenv set FOO=bar

自分専用の仮想クラスタを作る

kube <env> fork

<env>prod / qa / sandbox のいずれかです。kube はカレントディレクトリから対象 Namespace を判断するので、操作したいマイクロサービスのリポジトリに移動してから実行 してください。

話を聞きに行きたい

もっと知りたい

最終更新