この記事は、CYBOZU SUMMER BLOG FES '25の記事です。
クラウド基盤本部 PDX(Platform Developer Experience)チームの川満です。
この記事は「【連載】サイボウズのKubernetesプラットフォームを支えるOSS」の第2回目として、サイボウズのKubernetesプラットフォームにおける踏み台サーバーの提供について紹介します。
blog.cybozu.io
Teleport
サイボウズではKubernetesクラスタへのアクセス管理を行うコンポーネントとしてTeleportを採用しています。
github.com
Teleportには条件付きで無料利用できるTeleport Community Editionと有料のTeleport Enterprise Editionがあります。 サイボウズではTeleport Enterprise Editionを利用して、Kubernetesクラスタ上にTeleportをデプロイしています。
Teleportには多くの機能がありますが、サイボウズではTeleport Zero Trust Accessを活用しています。
すごく簡単に言ってしまえば、Zero Trust Accessは高機能な踏み台サーバーを提供してくれる機能です。サーバーやデータベース、KubernetesクラスタなどをTeleportのリソースとして登録することで、Teleportを経由して、それらに安全にリモートアクセスできるようになります。 また、誰がどのリソースにアクセスしたかを記録するAuditログやリソース上でどのような操作が行われているか記録するセッションレコーディングといった監査のための機能も充実しています。
サイボウズでは主に以下の用途で、このZero Trust Access機能を利用しています。
- Kubernetesクラスタへのアクセス管理
- Kubernetesクラスタへの踏み台サーバーの提供
1のアクセス管理については以前の記事で概要を説明しているので、よければご覧ください。
blog.cybozu.io
今回の記事では2の踏み台サーバーの提供とそれに伴って発生した課題をどのように解決したか紹介します。
Kubernetesクラスタへの踏み台サーバーの提供
サイボウズのKubernetesプラットフォームでは利用者向けに踏み台サーバーを提供しています。 この踏み台サーバーはKubernetesのPodとしてデプロイしており、利用者はTeleportを介してログインできます。
踏み台サーバーにログインすると、Kubernetesクラスタ内のリソースに直接アクセスできるようになります。 また、以下のような特徴もあります。
- 頻繁に使用するツールがインストール済み
- 使用したいツールのインストールをリクエストできる仕組みを整備しています
- 隔離された環境で安全に利用できる
- 利用チームごとに踏み台サーバーを用意しており、それぞれのサーバーでKubernetesのRBACを利用してアクセス権限を管理しています
- ネットワーク通信を一部制限しています
- 前述の仕組みでリクエストが承認されてインストールされたツールのみ使用できます
- SSHの鍵管理が不要
- 踏み台サーバーへはSSHで接続しますが、Teleportへのシングルサインオンでユーザー認証を行うので、ユーザーごとの鍵管理は不要です
- ブラウザだけで完結する
- Teleportを経由してブラウザ上で踏み台サーバーにログインできます1
このような踏み台サーバーを利用者には日々のオペレーションで便利に使ってもらっています。 一方で、以下のような場合に踏み台サーバーのPodが削除されると、ユーザーのログインセッションが中断するという課題もありました。
- メンテナンスに伴うKubernetesノードの再起動によってPodが削除・再作成される
- 踏み台サーバーPodで使用しているコンテナイメージの更新によってPodが削除・再作成される
特に2のコンテナイメージ更新は、Teleportのバージョン更新やツール更新のため、頻繁に実施していました。 利用者への影響を避けるように更新を実施していましたが、かなり手間のかかる作業でした。
このような課題を解決するため、login-protectorというKubernetesコントローラーを開発し、導入しました。
github.com
ログインセッションが中断するタイミング
login-protectorの詳しい紹介の前に、我々が直面した課題について詳細を説明します。 踏み台サーバーのPodが消失し、ログインセッションが中断するのは、以下のような中断(disruptions)が発生した場合です。
- Involuntary disruptions: ハードウェア故障やカーネルパニックなど回避できないもの
- Voluntary disruptions: Involuntary disruptionsに該当しないもの(アプリケーションオーナーやクラスタ管理者が行う操作によるものを含む)
Involuntary disruptionsはイメージしやすいと思いますが、Voluntary disruptionsには具体的にどのようなものがあるでしょうか。
Voluntary disruptionsの例
Voluntary disruptionsの例として以下のようなものがあります。
- メンテナンスのためのKubernetesノードのDrain
- DeploymentやStatefulSetのPodTemplate(
.spec.template
)の更新 - ユーザー操作によるPodの削除(操作ミスによるものも含む)
我々が踏み台サーバーで直面していた課題がまさに上記の a, b にあたるものでした。 Involuntary disruptionsを回避することはできませんが、上記のVoluntary disruptionsの場合、次のような方法で回避できます。
- DrainによるPod削除の回避: PodDisruptionBudget(PDB)を利用したPodの保護
- PodTemplate更新によるPod削除の回避: updateStrategy
OnDelete
の利用 - ユーザー操作によるPod削除の回避: Validating Webhookの利用
login-protectorは1, 2の方法を利用してログインセッションを保護することで我々が直面していた課題を解決します。
DrainによるPod削除の回避: PodDisruptionBudget(PDB)を利用したPodの保護
サーバーの修理やアップグレードなどメンテナンスのためにKubernetesノードを再起動する際、事前にkubectl node drain
を実行します。
DrainによってPodのEviction(退避)が行われるため、稼働するサービスへの影響を抑えながらメンテナンスを実施できます。
このPodのEvictionによって、Podの削除・再作成が発生しますが、こちらを回避するためにPodDisruptionBudget(PDB)を利用できます。
PDBはvoluntary disruptionが発生した際に同時に削除されるPodの数をmaxUnavailable
やminAvailable
といったパラメータを使って制限します。
これによってvoluntary disruptionが発生した際も、すべてのPodが削除されサービス断が発生するといった事態を避けることができます。
同時に削除されるPodの数を0(maxUnavailable: 0
)に制限するPDBを作成してkubectl node drain
してみると、
❯ kubectl get pod -owide -ntest NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES test-0 1/1 Running 0 9s 10.244.2.2 kind-worker2 <none> <none> ❯ kubectl get pdb -ntest NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE test N/A 0 0 28s ❯ kubectl drain kind-worker2 --ignore-daemonsets node/kind-worker2 cordoned Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-szj7t, kube-system/kube-proxy-7zsfn evicting pod test/test-0 error when evicting pods/"test-0" -n "test" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget. evicting pod test/test-0 error when evicting pods/"test-0" -n "test" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget. evicting pod test/test-0 [...]
PodのEvictionが失敗するためDrainが完了しないことがわかります。
login-protectorを使用する場合、ユーザーのログイン状況をチェックするサイドカーコンテナを踏み台サーバーのPodに追加しておきます。
※ ログイン状況は踏み台サーバー内のプロセスにTTYが割り当てられているかをlocal-session-trackerというプログラムがチェックすることで確認しています。
login-protectorは定期的にサイドカーコンテナにアクセスしてユーザーのログイン状況を確認します。
もしユーザーがログインしている場合はmaxUnavailable: 0
のPDBを作成します。これによってEvictionによるPodの削除ができなくなるため踏み台サーバーをDrainから保護できます。
ただしこの状態ではノードのDrainが完了しません。このためユーザーがログアウトしたタイミングでPDBを削除します。
このようにPDBを利用してPod削除を回避することで、ユーザーがログインしている間にDrainが実施されてもユーザーのログインセッションを保護できます。
PodTemplate更新によるPod削除の回避: updateStrategy OnDelete
の利用
次にPodTemplate更新によるPod削除を回避する方法について解説します。
DeploymentやStatefulsetでコンテナイメージを変更したい場合やアプリケーションのパラメータを変更したい場合、マニフェストのPodTemplate(.spec.template
)を変更します。変更によってローリングアップグレードが開始され自動的にPodの削除・再作成が行われ変更が適用されます。
このPodTemplateの更新によるPodの削除・再作成はPDBで回避できません。 DeploymentやStatefulSetでのローリングアップグレードはPDBの制限を受けないからです。
そこで、login-protectorではStatefulSetのupdateStrategyであるOnDelete
の仕組みを利用しています。
updateStrategyとしてOnDelete
を設定すると、PodTemplateを更新しても、Podは自動で再作成されません。ではどのタイミングで再作成されるかというとPodを手動で削除した時です。
踏み台サーバーのPodを管理するStatefulSetではupdateStrategyとしてOnDelete
を設定しておきます。
こうすることでコンテナイメージ更新などでPodTemplateが更新されても自動でPodの削除・再作成が行われることはありません。
ただしこのままではPodが古い状態のままです。そこでlogin-protectorは対象のStatefulSetのPodTemplateが更新されているかをチェックして、更新されている場合はPodの削除を試みます。
このチェックでは、StatefulSetの.status.updateRevision
と、Podに付与された.metadata.labels.controller-revision-hash
を比較しています。
これらの値が一致していなければPodTemplateが更新されていると判断します。
実際にupdateStrategyとしてOnDelete
を設定して、コンテナイメージ更新を行うと以下のような挙動になります。
# `OnDelete`でStatefulSetを作成 ❯ kubectl -n test get sts test -oyaml | yq .spec.updateStrategy.type OnDelete # ubuntu 22.04のイメージを使用 ❯ kubectl -n test get pod test-0 -oyaml | yq '.spec.containers[] | select(.name="ubuntu") | .image' ghcr.io/cybozu/ubuntu:22.04 # '.status.updateRevision'と'.metadata.labels.controller-revision-hash'は一致 ❯ kubectl -n test get sts test -oyaml | yq '.status.updateRevision' test-568478c75b ❯ kubectl -n test get pod test-0 -oyaml | yq '.metadata.labels.controller-revision-hash' test-568478c75b # ubuntu 24.04イメージに更新 ❯ kubectl -n test set image sts/test ubuntu=ghcr.io/cybozu/ubuntu:24.04 statefulset.apps/test image updated # '.status.updateRevision'と'.metadata.labels.controller-revision-hash'が一致していないのでPodTemplateが更新されていると判断できる ❯ kubectl -n test get sts test -oyaml | yq '.status.updateRevision' test-7f768b7b4b ❯ kubectl -n test get pod test-0 -oyaml | yq '.metadata.labels.controller-revision-hash' test-568478c75b # Podは再作成されていない ❯ kubectl -n test get pod test-0 -oyaml | yq '.spec.containers[] | select(.name="ubuntu") | .image' ghcr.io/cybozu/ubuntu:22.04 # Podを削除する ❯ kubectl -n test delete pod test-0 pod "test-0" deleted # '.status.updateRevision'と'.metadata.labels.controller-revision-hash'が一致し、イメージも更新されている ❯ kubectl -n test get pod test-0 -oyaml | yq '.metadata.labels.controller-revision-hash' test-7f768b7b4b ❯ kubectl -n test get pod test-0 -oyaml | yq '.spec.containers[] | select(.name="ubuntu") | .image' ghcr.io/cybozu/ubuntu:24.04
もしPodTemplateが更新されている場合は、login-protectorが対象Podに対してEviction APIを実行します。Eviction APIによってPodが削除されると、Podの再作成時に更新後のPodTemplateが適用されます。
なお、Eviction APIはPDBを考慮し、PDBの条件を満たせない場合はエラーが返されます。PodTemplateが更新されたタイミングでユーザーがログインしていると前述の仕組みによりPDBが作成されているため、Eviction APIはエラーになります。このため、ユーザーがログインしている間はPodTemplateの更新は保留されることになります。
この仕組みによって、ユーザーがログイン中にコンテナイメージ更新作業を行っても、実際の適用はログアウトするまで保留されます。 こうしてコンテナイメージ更新からもユーザーのログインセッションを保護できます。
login-protector導入後の状況
login-protectorを導入したことでノードのメンテナンスやコンテナイメージ更新からユーザーのログインセッションを保護できました。 Kubernetesの標準的な機能を利用して実装しているため、今のところ定期的な依存関係の更新のみで運用できています。
一方で、ユーザーログイン中はノードの再起動やイメージ更新ができないため、必要なメンテナンスが滞ってしまうのではないかという懸念もありました。 この点については、PDBの作成時刻をもとに長時間のログインを検出して、必要に応じてユーザーにログアウトを促す運用としています。 また、意図せずログインしたままになっている状態を避けるため、長時間操作がない場合は自動的に踏み台サーバーからログアウトするように設定しています。
まとめ
この記事では「【連載】サイボウズのKubernetesプラットフォームを支えるOSS」の第2回目として、Teleportによる踏み台サーバーの提供について紹介しました。 ノードのメンテナンスやPodのイメージ更新で踏み台サーバーへの接続が切断してしまうという課題がありました。これをKubernetesの標準的な機能を使って実装したlogin-protectorを導入することで解決しました。
次回の第3回目ではAccurateによるNamespace管理のセルフサービス化について紹介する予定です。次回の記事もぜひご覧ください。