TT Lab
はじめる
学ぶ 学習パス コース

CNPA — クラウドネイティブプラットフォームエンジニアリングアソシエイト

基準線はどこに打ち込めば守られるのか

TT Labで続きを見る

一言でいうと

プラットフォームの最小ベースライン(baseline)は、Wikiの文書ではなく、APIサーバーが拒否できる形で存在して初めて守られます。そして、ベースラインを引き上げるときは、まず警告で知らせ、影響を調べ、守れない側には期限付きの例外を与えます。

なぜ必要なのか

プラットフォームチームが最初に作る成果物は、たいてい文書です。「すべてのPodはrootで動かしません」「イメージタグは固定します」「メトリクスポートを開けておいてください」のようなルールをまとめて、社内Wikiに載せます。そして半年後にクラスターを見渡すと、そのルールを守っているワークロードは半分もありません。

理由は単純です。文書は何も拒否しません。ルールに違反したデプロイも成功し、成功したデプロイは誰も元に戻しません。一方、APIサーバーが拒否すれば、その場でわかり、直さなければデプロイできません。ベースラインの実体は、文書ではなく拒否できる能力です。

かといって、最初からすべてを塞ぐと、別の問題が起きます。昨日までできていたデプロイが、今日突然塞がれると、プラットフォームは障害の原因として記憶されます。そのため、ベースラインには、レベルと手順が一緒に必要です。

どう動くのか

Pod Security Admissionの3つのレベルと3つのモード

Kubernetesに組み込まれているPod Security Admissionは、ネームスペースのラベルだけで動作します。レベルはprivileged、baseline、restrictedの3つで、各レベルごとにモードを別々に設定できます。

モード 役割 使う場面
enforce 違反したPodを作成できないようにします いま守れるレベル
audit 監査ログにだけ記録します あとで統計を見るとき
warn 作成する人に警告を表示します 次に引き上げるレベル

ここにコツが1つあります。enforceはいま守れるレベルに、warnは次に引き上げるレベルに設定します。そうすれば、開発者はデプロイのたびに「次の段階ではこれが塞がれます」を事前に読めるので、引き上げの日に驚きません。

ラベルにはレベルだけでなく、バージョンも一緒に書きます(enforce-version: v1.30)。レベルの定義はKubernetesのバージョンごとに少しずつ広がるため、バージョンを書かないと、クラスターをアップグレードした瞬間に、ポリシーの内容が黙って変わります。その日に誰もポリシーに触っていないのにデプロイが塞がれると、原因を探すのに1日かかります。

引き上げる前に調べる方法

レベルを引き上げてよいかは、推測せずにサーバーに尋ねます。ネームスペースのラベルを変更するリクエストをサーバーdry-runで送ると、APIサーバーがそのネームスペースで現在動いているPodを新しいレベルで評価して、警告として返します。どのPodが何の理由で引っかかるのかが、そのまま出ます。何も作らずに、答えだけを受け取る方法です。

準拠(conformance)はポータビリティそのものです

CNCFが言う準拠とは、「認証のスタンプ」ではなく、上位バージョンのAPIだけを使えば、どのクラスターでも同じように動作するという約束です。そのため、廃止されたAPIを残しておくと、準拠が崩れます。extensions/v1beta1で書かれたマニフェストは、最新のクラスターでは、文法エラーではなくマッピング失敗として拒否されます。そのグループ自体が提供されないためです。このとき出てくる言葉が「no matches for kind」で、この文を読めれば、原因が1分でわかります。

移行するときにフィールドが増える場合もあります。Ingressをnetworking.k8s.io/v1に移行すると、パスごとにpathTypeを必ず書く必要があります。以前はコントローラーごとに解釈が違っていた部分を、APIが明示させるようにしたものです。

オブザーバビリティは契約です

プラットフォームが「メトリクスを自動で収集します」と言うには、何を根拠に収集するのかが、オブジェクトとして存在している必要があります。Prometheus OperatorのServiceMonitorが、その役割を担います。ServiceMonitorは、Serviceのラベルで対象を選び、Serviceのポート名でスクレイプ対象を選びます。どちらか一方がずれただけでも、オブジェクトは問題なく作成され、メトリクスだけが黙って空になります。そのため、この関係は作成したあとに、「本当に何を選んだのか」を問い合わせて確認する必要があります。

例外は期限とともに与えます

ベースラインを今は守れないチームは、必ず出てきます。ここでの選択肢は3つです。ベースラインを下げるか、そのチームを塞ぐか、期限付きの例外を与えるかです。前の2つは、それぞれプラットフォームを無意味にするか、プラットフォームを敵にしてしまいます。例外には、なぜ例外なのか、いつまでなのかをオブジェクトに書いておきます。書いていない例外は永久の適用除外になり、永久の適用除外が積み重なると、ベースラインは再び文書に戻ります。

現場での姿

このコースが動いているホームラボでも、同じことがありました。ラボ用Podは、特権なし(capabilityをすべて外した状態)で起動しますが、その前提を文書にしか書いていなかったら、利便性のために1つずつ開けられていたでしょう。今はPodのスペック自体がそれを強制しているので、ラボが成立しなければ、権限を開ける代わりにラボの設計を変えます。ベースラインがオブジェクトにあれば、議論は設計へと移っていきます。

オブザーバビリティの契約の面でも、同じ種類のトラブルがありました。メトリクスの収集対象が、ラベルの1文字違いのせいで紐づかなかったのに、オブジェクトは正常でダッシュボードだけが空だったため、しばらく誰も気づきませんでした。「作成された」ことと「実際に何かを選んでいる」ことは別の命題であり、この区別は、クラスターに問い合わせて初めて確認できます。

次のラボですること

ネームスペース1つにベースラインを設定し、それが実際に拒否するかを確認します。廃止されたAPIがなぜ拒否されるのかを自分の目で見て、上位バージョンのAPIに移行したあと、スクレイプの契約をServiceMonitorで結び、そのセレクターが本当にそのServiceを選んでいるかを問い合わせてみます。最後に、ベースラインを1段階引き上げる影響をサーバーに尋ね、守れないネームスペースには、期限付きの例外を残します。