Kubernetesのmanifestを検証しよう

要約

  • k8sは、manifestをサービスが動くように設定するのは簡単だが、適切な設定は分かりづらい
  • その結果、いまいちな設定をしてしまいがち
  • kube-scoreはmanifestの問題と対応方法を教えてくれる
  • k8s初心者にこそ、ツールによるmanifestの自動検証はおすすめ

想定読者

Kubernetes(以下、k8s)でとりあえずサービスを動かせるが、雰囲気で触っている人向けです。

k8sの設定は雰囲気で決められがち

こんにちは。ANDPADでバックエンドの開発をしているzigeninです。 ANDPADでは、バックエンドのサービスをk8sで動かしています。

k8sを使っていて感じる一番の問題は、manifestで設定できることが多すぎて、良い設定が分かりづらいことです。

そのせいか、k8sに習熟していないと以下のような設定をしがちです*1

apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - resources:
            limits: {}
            requests: {}

コンテナに対してCPUリソースやメモリのrequests(最低値)やlimits(最大値)の指定していない点が問題です*2。たとえば、CPU使用量の最低値を指定しないと、NodeのCPU使用量が100%に達したときにPodにCPU時間を割り当ててもらえないことがあります。平常時はリソースの指定をしなくてもPodは動いてしまうので、不慣れな頃は「ヨシ!」としがちだと思います。

このような問題箇所を無くすためにはどうしたら良いのでしょうか?

k8sの設定の問題箇所を無くすには?

まず考えられるのは、バックエンド開発者全員がk8sのことをしっかり学んで設定を見直すことでしょう。 これは大事なことですが、3つ問題があります。

  1. バックエンドの開発者には、k8s以外にも考えないといけないことが沢山ある
  2. k8sを本などで学習しても、適切かつ最小限の設定は良く分からない
  3. レビューにおいて、レビュワーがk8sに習熟していても問題点を見落とす可能性がある

ではどうしたら良いでしょうか?

静的解析でエラーを出したりポリシー違反を検出するのと同じように、開発サイクルの中でmanifestの検証を行うようにします。また、それをリリースやPRマージの基準にします。ここでは、それらが自動化されていることがとても重要です。

k8sのmanifestを検証するツール

k8sのmanifestを検証するツールは色々あります*3

Tool Star Built-in
rule
Readability Add
rule
Note
kube-score 1.9 k x k8sのAPIバージョンの指定ができない。
kube-linter 2 k x チェック結果に深刻度が表示されず、1つの指摘が1行で出力されるのが見づらい。カスタマイズ性は高くない。
polaris 2.7 k クラスタの検証可。Dashboardなどもあり動的なk8sの検証もできそう。
kubeaudit 1.3 k x セキュリティ重視で、可用性方面の指摘は少ない印象。クラスタの検証可。
checkov 4.6 K TerraformやAWS Cloud Formationの検証もできる。指摘の深刻度は表示されない。クラスタの検証可。
config-lint 164 k x ? ? terraform, k8s, yaml, json, csvの内容を静的に検証するツール。k8sのmanifest検証のさせ方が良く分からなかったのであまり調べていない。
cooper 255 x ? カスタムルールはJavascriptとして記述するので、拡張性は高そう。
conftest 2.3 k x ? k8s特化ではなく構造化されたデータを検証するツール。カスタムルールはRogoで記述するので拡張性は高そう。
kubeval 2.9 k x x 数字を指定すべきところに文字列を指定するとエラーにするタイプの検証。
kubeconform 631 x カスタマイズできるkubevalといった印象。

今回、ツールに求めている性質は以下です。

  • 特別な設定なしにmanifestがk8sのベストプラクティスに沿っているかを検証してくれる*4
  • 実績がある
  • 指摘の深刻度、対応方法が分かりやすい
  • 頭を使う余地があまりない
  • セキュリティ以外の問題も検出してくれる

最終的に、kube-scoreを採用しました。

他のツールも遜色はありませんので、目的に合致しているなら他のツールを使うのも良いと思います*5。 大事なのは、ツールにmanifestを検証させることで、k8sを雰囲気で触っているレベルから脱することです。

kube-scoreでmanifestを検証する

kube-scoreをCIから実行する例を示します。 CIツールはCircle CIで、k8s周辺のツールとしてHelmを使っている前提です。

# 最新のkube-scoreをInstallする
# kube-scoreの入ったDocker Imageは存在しますが、CIツールが提供しているDocker Imageを使った方が実行時間は短いです
KUBE_SCORE_VERSION=$(curl -s "https://api.github.com/repos/zegl/kube-score/releases/latest" | jq -r '.tag_name')
sudo curl -fL "https://github.com/zegl/kube-score/releases/download/${KUBE_SCORE_VERSION}/kube-score_${KUBE_SCORE_VERSION:1}_linux_amd64" -o "/usr/local/bin/kube-score"
sudo chmod +x "/usr/local/bin/kube-score"

# kube-scoreを実行
# プロダクトのmanifestの標準出力させて、kube-scoreに出力内容を検証させています
helm template <your-app-name> <your-chart-name> <your-options> | kube-score score -

検証結果の例

apps/v1/Deployment xxxxxxxxxxxxx                                             💥
    [CRITICAL] Container Image Tag
        · xxxxxxxxxxxxxxx -> Image with latest tag
            Using a fixed tag is recommended to avoid accidental upgrades
    [CRITICAL] Container Security Context ReadOnlyRootFilesystem
        · xxxxxxxxxxxxxxx -> The pod has a container with a writable root filesystem
            Set securityContext.readOnlyRootFilesystem to true
autoscaling/v2beta1/HorizontalPodAutoscaler xxxxxxxxxxxxx                    ✅
policy/v1beta1/PodDisruptionBudget xxxxxxxxxxxxx                             ✅
v1/Service xxxxxxxxxxxxx                                                     ✅

kube-scoreはこのように人間にとって見やすい形式で問題を教えてくれます。 また、問題の深刻度(CRITICAL or WARNING)も教えてくれるのが分かりやすくて良いです。

気づきが得られたkube-scoreの指摘

kube-scoreが指摘してくれた問題には、割と良く知られている問題と、指摘されて初めて気づいた問題の両方がありました。後者の問題をいくつかピックアップします。

なお、kube-scoreの全ての検証内容が知りたい方は、こちらを参照願います

PodDisruptionBudgetが存在しません

PodDisruptionBudgetは、ノードからPodを排出するときに、Podを停止することができる最大数を設定するリソースです。 この指定がないと、ノードからPodを排出した時に、特定Deployment管理下のレプリカが全て停止してしまう可能性があります*6

k8sの公式DocからPodDisruptionBudgetの記述例を引用します。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: zk-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: zookeeper

レプリカ数がそれほど多くないのであれば、この設定で良いかと思います。

そうでない場合は、maxUnavailableの値を1より大きくしたり、割合で指定したりしてください。 なお、minAvailableという設定項目もあります。

resource.requests.ephemeral-storage/resource.limits.ephemeral-storageの指定がありません

最初、この問題が指摘される意味が分かりませんでした。 自分が扱っているプロダクトではephemeral-storageに書き込みをしないですし、そういうプロダクトも多いと思います。 それなのに、なぜephemeral-storageの使用量の指定がチェックされるのか?

Podが出力するログもephemeral-storageの使用量に含まれるというのが答えです*7。 ephemeral-storageにファイルの読み書きをしなくても、全てのPodはephemeral-storageを使用しているということです。

最終的には、自分のプロダクトのephemeral-storageの使用量の平均や最大値をメトリクス収集サービスで確認した上で、requestsとlimitsを設定しました。

なお、kube-scoreと異なり、kube-linterはこれを問題として指摘しないです。 アプリケーションコードからephemeral-storageに読み書きしないPodなら、ephemeral-storageのrequestsとlimitsは指定しなくても問題ないのかもしれません。 実際、私のプロダクトのephemeral-storageの使用量も思っていたよりは多かったですが、limitがないことで問題が起きるような使用量ではなかったですし、稼働時間とともにephemeral-storageの使用量が単調増加していく傾向はありませんでした。

NetworkPolicyを設定してください

NetworkPolicyを設定しないと、Podの通信相手に制限はありません。 なのでセキュリティ的に問題があるということです。

NetworkPolicyを設定すると、IPアドレス、namespaceのラベル、PodのラベルなどでPodの通信相手を制限することができます。

現在、自分が開発しているプロダクトについては、NetworkPolicyの設定は見送りました。 Podから見て内向きの通信はk8s外の要素でANDPAD内部に限定されている、内部に悪意のあるプログラムが紛れ込んでも他にも防備はある、ANDPADの外部への通信は必要、といった理由です。

k8sで動いているPodはインターネットへの通信が不要なものも多いでしょうから、その場合は外向きの通信を制限すると良いと思います*8

inter-pod anti-affinity を設定してください

全Podが同じNode上で実行されていると、そのNodeがダウンするとプロダクトがダウンしてしまいます。 それを防ぐために、inter-pod anti-affinityを設定してなるべくPodが同じNodeにデプロイされないようにした方が良いようです。

私は、いくつかの理由で対応しないことにしました。

  • k8s公式曰く、Nodeが数百台規模あるようなクラスタでinter-pod anti-affinityを使うとスケジューリングが遅くなる*9

    Pod間アフィニティとアンチアフィニティは、大規模なクラスター上で使用する際にスケジューリングを非常に遅くする恐れのある多くの処理を要します。 そのため、数百台以上のNodeから成るクラスターでは使用することを推奨されません。

  • kube-scoreでは、この問題は警告レベルだった
  • レプリカ数とノード数がそれなりにあれば、全レプリカが同じNodeにデプロイされる可能性は低い

inter-pod anti-affinityを設定するとしたら、絶対に同じNodeにPodをデプロイしてはいけないのか(requiredDuringSchedulingIgnoredDuringExecution)、状況によっては同じNodeにPodをデプロイしても良いのか(preferredDuringSchedulingIgnoredDuringExecution)を選択する必要があります。全レプリカが同じNodeにデプロイされる問題が起きる可能性は残りますが、後者の設定が良いのかと思っています*10

まとめ

以上、k8sのmanifestの検証方法、kube-score、指摘される問題を紹介しました。 k8sの触り始めだと、kube-scoreのようなツールを入れるのは面倒に感じると思います。 ただ、触り始めの良くわからない頃だからこそ、ツールによるmanifestの検証には価値があります。 私もkube-scoreの指摘を見て、多くの知見を得ることができました。

k8sを雰囲気で触っている段階の方は、ぜひk8sのmanifestの自動検証をしてみてください 😎

最後に

アンドパッドでは一緒に働く仲間を大募集しています。
ご興味を持たれた方はカジュアル面談や情報交換のご連絡をお待ちしております。 engineer.andpad.co.jp

*1:設定は、説明に必要な部分だけの抜粋です。

*2:リソース指定に関しては、LimitRangeでリソース割り当てのデフォルト値を設定することも対処の一つと思います。ただ、必要なリソースはプロダクトの性質によって異なります。デフォルト値に頼らず、プロダクトの性質を考えた上で値を設定した方が良いと思います。

*3:ここに挙げたツールはcheckov以外は全てGo製なので、 CIから使いやすいです。

*4:k8sを雰囲気で触っている人が使うことを想定しているからです。

*5:polarisはバランスが良かったです。kube-scoreに満足できなくなったらpolarisに切り替えるかもしれません。

*6:全レプリカが排出対象のノードに動いている状態で、そのノードからPodを排出するケースです。

*7:https://stackoverflow.com/questions/68078426/kubernetes-ephemeral-storage-limit-and-container-logs

*8:NetworkPolicyは、デフォルトで使えるとは限りません。たとえば、ANDPADではEKSを使っていますが、EKSの場合は、別途NetworkPolicyを扱うプラグインをインストールする必要があります。

*9:ANDPADは、k8sのクラスタを細分化せず一つにまとめたい雰囲気を感じています。ですので、現状のANDPADのk8sクラスタは大規模ではありませんが、気づかない内に大規模クラスタになっている可能性があります。

*10:レプリカ数 > ノード数ならpreferredDuringSchedulingIgnoredDuringExecutionにしないといけません。