(前半)Kubernetesでのよくある失敗10選

2020/06/04 07:37

Marek Bartik   
Kubernetes、AWS、DevOpsをはじめ、インフラストラクチャーへの深い知識と情熱を捧げるソフトウェアエンジニア。NoOps/NoCodeのマニアでもある。
この記事は、著者の許可を得て配信しています。
https://blog.pipetail.io/posts/2020-05-04-most-common-mistakes-k8s/

次の記事
(後半)Kubernetesでのよくある失敗10選

私たちは長年のkubernetesを使ってきた経験の中で、かなりの数のクラスタを見る機会がありました(GCP、AWS、Azure上で管理されたクラスタと管理されていないクラスタの両方です)。そして間違いが何度も繰り返されているのを目にします。これは決して恥ずかしいことではありません。みんな同じように間違いを繰り返しているのですから。

そこで、今回は私たちがよく目にする失敗や間違いを紹介し、それらを修正する方法について少し話してみたいと思います。

リソース - リクエストとリミット

これは間違いなくこのリストの中で最も注目されるべきものであり、一番最初にお話しすべきトピックでしょう。

CPUリクエストは通常、設定されていないか、または非常に低く設定されているかのどちらかです(各ノードに多くのポッドを搭載できるように)。そしてノードはこのようにオーバーコミットされています。需要が高い時には、ノードのCPUがフルに利用され、ワークロードは「要求されたもの」しか取得せず、CPUがスロットルされ、アプリケーションのレイテンシの増加やタイムアウトなどを引き起こします。

BestEffort (これは絶対にしないでください):

    resources: {}

かなり低いCPU (これは絶対にしないでください):

    resources:
      requests:
        cpu: "1m"

一方で、CPUのリミットを設定すると、ノードの CPU が十分に利用されていない場合でもポッドを不必要にスロットルすることができ、これもまたレイテンシの増加を引き起こす可能性があります。linuxカーネルのCPU CFSクォータと、設定されたCPUリミットとCFSクォータをオフにしてCPUスロットルを行うことについては、公開討論されています。CPUリミットは解決できないほどの問題を引き起こす可能性があります。その件に関しての詳細は以下のリンクを参照してください。

メモリのオーバーコミットは、より多くの問題を引き起こす可能性があります。CPUの限界に達するとスロットルが発生し、メモリの限界に達するとポッドがkillされます。OOMkillを見たことがありますか?そう、それが私たちが話しているものです。問題が起こる可能性のある頻度を最小限にしたいですよね?メモリをオーバーコミットしないようにして、下の例のように、メモリ要求を制限値に等しく設定して、Guaranteed(優先度が一番高い) QoS(Quality of Service)を使用してください。このトピックについては、Henning Jacobs氏(Zalando)のプレゼンテーションをご覧ください。

Burstable(最もOOMkillされやすい)

    resources:
      requests:
        memory: "128Mi"
        cpu: "500m"
      limits:
        memory: "256Mi"
        cpu: 2

Guaranteed:

    resources:
      requests:
        memory: "128Mi"
        cpu: 2
      limits:
        memory: "128Mi"
        cpu: 2

では、リソースを設定する際に役に立つのは何でしょうか?

metrics-server を使って、現在のポッド (とその中のコンテナ) の CPU とメモリの使用量を見ることができます。おそらく、皆さんはすでに実行しているはずです。これらを実行するだけです。

kubectl top pods
kubectl top pods --containers
kubectl top nodes

しかし、これらは現在の使用状況を示しています。数字についての大まかな傾向を見るには良いのですが、最終的にはこれらの使用量のメトリクスを時間内に見たいと思うでしょう(例えば、ピーク時のCPU使用量は?昨日の朝はどうだったか、などの質問に答えるためには使用量が必要ですよね)。そのためには、PrometheusやDataDog、またその他多くのツールを使うといいでしょう。それらのツールを使って、メトリクス・サーバからメトリクスをインジェストして保存し、それをクエリーしてグラフ化することができます。

VerticalPodAutoscalerはこの作業を自動化するのに便利です。 CPU/メモリの使用量を時間内に見て、それに基づいて何度も新しいリクエストとリミットを設定します。

コンピュートを有効に活用するのは簡単なことではありません。それはテトリスで遊んでいるようなものです。平均利用率が低い(10%程度)のにコンピュートに多くのお金を払っていることに気がついたら、AWS FargateやVirtual Kubeletをベースにした製品をチェックしてみてはいかがでしょうか。そういった製品は、サーバーレス/使用量に応じた課金モデルになっているため、経費を節約できるかもしれません。

liveness and readiness probes

デフォルトでは、liveness and readiness probesは指定されていません。そして時々、それがそのままになっていることがあります。

しかし、回復不可能なエラーが発生したときに、どのようにしてサービスを再起動する他の方法はあるのでしょうか?ロードバランサーはどのようにして特定のポッドがトラフィックの処理を開始できることを認知するのでしょうか? あるいはどのようにより多くのトラフィックを扱えるようになるのでしょうか?

次のこの2つにおける違いを知らない人はたくさんいます。

・Liveness probeは、probeが失敗した場合、ポッドを再起動する

・Readiness probeは、kubernetes サービスからの失敗したポッドを切断し(これは kubectl get endpoints で確認できます)、probeが再び成功するまでトラフィックが送信されなくなる

両方ともポッドの全ライフサイクル中に実行されます。これは重要です。

ポッドの準備が完了してトラフィックのサービスを開始できるようになったことを伝えるために、開始時にだけreadiness probeが実行されると考える人がたくさんいますが、それは使用例の一つに過ぎません。

もう一つは、ポッドの寿命がある間に、ポッドがあまりにも多くのトラフィックを処理する(または費用のかかるコンピュートをする)ために、ポッドが熱くなりすぎた場合に、やるべき仕事を増やさずに冷却させてから、readiness probeが成功して、再び多くのトラフィックを送り始めるかどうかを判断するためです。この場合、(readiness probeに失敗したときに) liveness probeにも失敗するのは非常に逆効果になります。なぜ、healthyで多くの仕事をこなしているポッドを再起動するのでしょうか?

どちらのprobeも設定しない方が、間違った設定をするよりも良い場合もあります。前述したように、liveness probeとreadiness probeが等しくなると、大きな問題が発生します。liveness probesは危険なので、まず、readiness probeだけを設定することから始めた方が良いでしょう。

共有されているディペンデンシーのいずれかがダウンしている場合は、probesのいずれも失敗しないでください。それはすべてのポッドのカスケード障害を引き起こすことになり、自分で自分の首を絞めているようになってしまいます。

すべての http サービスのためのロードバランサー

クラスタ内には、外部にエクスポーズしようと思っている http サービスがもっとあるかもしれません。

type: LoadBalancerとしてkubernetesサービスをエクスポーズする場合、LoadBalancerとして公開した場合、そのコントローラ(ベンダー固有)は外部のLoadBalancer(必ずしもL7ロードバランサーとは限らず、L4 lbの可能性が高い)をプロビジョニングしてリコンサイルします。そしてそれらのリソースの多くを生成しようとするのであれば、その費用は高額(外部のスタティックな ipv4 address、コンピューティング、一秒あたりの料金…)になります。

この場合、外部のロードバランサーを共有することはより合理的かもしれません。それから、type: NodePortとしてサービスをエクスポーズします。あるいは、nginx-ingress-controller (または traefik) のようなものをデプロイして、外部のロードバランサにエクスポーズされている単一の NodePort エンドポイントとし、kubernetes のイングレスリソースに基づいてクラスタ内のトラフィックをルーティングすることもできます。

互いに通信する他のクラスタ内(マイクロ)サービスは、ClusterIPサービスやデフォルトで何の手も加える必要もないDNSサービスディスカバリを介して通信することができます。それらのパブリックDNS/IPを使用しないと、レイテンシーやクラウドコストに影響を与える可能性があるので注意が必要です。

non-kubernetes-aware クラスタの自動スケーリング

クラスタにノードを追加したり、クラスタからノードを削除したりするときには、ノードのCPU使用率のようなシンプルなメトリックは考慮すべきではありません。ポッドをスケジューリングする際には、ポッドとノードのアフィニティ、 taints とtolerations、リソースリクエスト、QoSなど、多くのスケジューリング制約に基づいて決定します。これらの制約を理解していない外部の自動スケーラーを使うのは面倒になるかもしれません。

新しいポッドがスケジュールされているけれども、使用可能なCPUはすべてリクエストされており、ポッドは保留状態になっていると想像してください。外部の自動スケーラーは、現在使用されている平均的なCPU(要求されていない)を見て、スケールアウトをしません(別のノードを追加しないという意味)。またポッドはスケジュールされることはありません。

スケーリングイン(クラスタからノードを削除する)は常に困難です。ステートフルなポッド(永続ボリュームが取り付けられている)があり、永続ボリュームは通常特定のアベイラビリティゾーンに属するリソースであり、リージョン内ではレプリケートされていないため、カスタム自動スケーラーはこのポッドが接続されているノードを削除し、スケジューラーはこのポッドを別のノードにスケジュールすることができないため、永続ディスクが接続されている唯一のアベイラビリティゾーンによって非常に制限されます。ポッドは再び保留状態になっています。

コミュニティでは、自分のクラスタで実走っていて、ほとんどの主要なパブリッククラウドベンダーのAPIと統合されているcluster-autoscalerを広く使用しており、これらのすべての制約を把握しており、前述のケースにおいて、スケールアウトします。また、我々が設定した制約に影響を与えることなく、スマートにスケールアウトできるかどうかを判断し、コンピュートにかかる経費を節約します。

IAM/RBACに頼らない

マシンやアプリケーションに永久的な秘密を持つIAMユーザーを使わないようにしましょう。むしろ、ロールやサービスアカウントを使って一時的な秘密を生成するようにしてください。

私たちはこういう状態をよく目にします 。アプリケーションのコンフィギュレーションでアクセスと秘密キーをハードコーディングしているような状態です。手元にCloud IAMがあるときには秘密をローテーションさせないようにするのです。適切な場合は、ユーザーの代わりにIAMロールとサービスアカウントを使用します。

kube2iamをとばして、Štěpán Vraný氏のこのブログ記事で説明しているように直接サービスアカウントのIAMロールにいっています。

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-role
  name: my-serviceaccount
  namespace: default

そんなに難しくなかったでしょ?

また、不要な場合にはサービスアカウントやインスタンスプロファイルに管理者権限やクラスタ管理者権限を与えてはいけません。これは、特にk8のRBACでは少し難しいですが、それでも努力する価値はあります。

appstore
googleplay
会員登録
URLからPICKする

会員登録して、もっと便利に利用しよう

  • 1.

    記事をストックできる
    気になる記事をPickして、いつでも読み返すことができます。
  • 2.

    新着ニュースをカスタマイズできます
    好きなニュースフィードをフォローすると、新着ニュースが受け取れます。