Stanby Tech Blog

求人検索エンジン「スタンバイ」を運営するスタンバイの開発組織やエンジニアリングについて発信するブログです。

EKSのCoreDNSを安定させるための取り組み

EKSのCoreDNSを安定させるための取り組み

こんにちは。StanbyのProductPlatformグループでSREをやっている小林です。

今回はEKS環境のCoreDNSを安定稼働させるために取り組んだことを紹介します。

何が起きたのか

まず前提として、当社サービスの多くはECS/Fargateで運用しており、現在EKS/FargateへFrontから段階的に移行しています。 移行についての経緯や方法については、CloudNative Days Tokyo2021での登壇資料をご覧ください。

本題に戻します。ある日、EKSで稼働しているシステムでエラーが多発しました。5xxのHTTPステータスコードが大量発生し、Web閲覧障害が断続的に発生していました。

FrontからAPIの通信で5xxが発生しているようで、Pod内で稼働しているコンテナを調査すると、名前解決で失敗していました。

EKSが内部の名前解決やサービスディスカバリに使っているCoreDNS Podの数を倍に増やすとエラーが解決したので原因はCoreDNSと判断し、原因調査と再発防止への取り組みが必要になりました。

CoreDNSの負荷状況を見る

障害が起きたときはCoreDNSの監視をしていなかったので、検証環境に負荷をかけて事象を再現してCoreDNSの状態を監視することを試みました。

弊社は監視にDatadogを使っております。なのでCoreDNSをDatadogで監視するために、CoreDNSのPodに対してサイドカーコンテナとしてDatadogコンテナを追加し、DatadogコンテナからDatadogにメトリクスを送信しようとしました。

しかしEKS/Fargateのせいなのか我々の設定が悪いのか、Datadogにメトリクスを送信できませんでした。

AWSやDatadogのサポートともやり取りしたのですが解決に時間がかかりそうで、また一方でコンテナ基盤がFaragateではなくEC2だとメトリクスが取得できることを確認できました。 なので、CoreDNSが動いているkube-systemというnamespaceだけをEC2で動かす、Hybrid EKS環境を構築しました。

仕組みとしては、特定のnamespaceを使うFargate profileを作成し、namespaceの指定がない場合はdefaultのmanaged node groupにdeployしています。

これでメトリクスが取得でき、

  • Webへのリクエストに応じてCoreDNSへのリクストも増加すること
  • 負荷が一定状になると、CoreDNSのPodが再起動を起こすこと

が確認できました。

CoreDNS安定化させるためにやったこと

[導入に至らず]ノードにDNSキャッシュを持たせる

検証はしたけど導入には至らなかった対策として、NodeLocal DNSキャッシュを紹介します。

これは、クラスターノード上でDNSキャッシュエージェントをDaemonSetで稼働させることで、クラスターのDNSパフォーマンスを向上させる機能です。

ただアプリケーション環境のノードはFargateなので、DaemonSetを動かすことができず、断念しました。

ndots設定を変更

名前解決のために使うresolve.confにはndotsという、名前解決するときにローカルドメインをどのくらい優先させるかを設定できるパラメータがあります。

デフォルトは5で、EKSの場合だと以下のようになっています。

nameserver 10.100.0.10
search default.svc.cluster.local svc.cluster.local cluster.local ec2.internal
options ndots:5

ndots:5だとドットが5以下の場合はFQDNと見做さず、以下の順で名前解決を試みます。 この状態でexample.comへの名前解決を試みた場合の挙動は以下の順序で、ローカルドメインを自分で補完して名前解決を試みます。

  1. example.com.default.svc.cluster.local.
  2. example.com.svc.cluster.local.
  3. example.com.cluster.local.
  4. example.com.ec2.internal.
  5. example.com.

これだと名前解決にコストがかかりすぎるのでndotsを1に設定し、corednsへの負荷を軽減させました。

詳細はAWSの公式記事を参照してください。

CoreDNSリリース時の切断軽減

CoreDNSへの設定変更を反映する際、ローリングデプロイしてるにも関わらずリリース時に接続が不安定になり、アプリケーションPodが全てALB TargetGroupから切り離される事象が発生しました。 CoreDNS Podが処理中のリクエストがあるにも関わらずPodがterminateされているようで、終了を待機させる必要がありました。

CoreDNSコンテナにはsleepコマンドがないので、lameduckオプションを入れて終了を待機させました。

実際のcorefileは以下のようになります。これは終了を20秒待機させる設定です。

.:53 {
    errors
        health {
            lameduck 20s
            }
}

EKS1.21環境のCoreDNSのpod配置戦略を検討する

Hybrid EKSになり、CoreDNSがEC2ノード上で動くので、EC2ノードで障害が起きた時のこと考慮する必要があります。

特にEC2ノードが突然停止した場合にサービスへの影響を最小限に抑える必要があります。 また、ノードを増減させた場合もPodが偏らないようにする工夫が必要になります。

topologySpreadConstraintsを使ってPodの配置を調整し、deschedulerを使ってノードが増減した時にPodを再配置させるようにしました。

topologySpreadConstraints とは

topologySpreadConstraintsは、Podをregion、zone、node、およびユーザが定義したドメインでPodを分散させ、高い可用性と効率的なリソース利用を実現できる仕組みです。

Podをデプロイするときの動きとなり、ノードを追加しても再配置は行われません、

実際にCoreDNS Podに設定した値を元に解説します。

        "topologySpreadConstraints": [
            "maxSkew": 5,
            "topologyKey": "kubernetes.io/hostname",
            "whenUnsatisfiable": "DoNotSchedule"
          }
        ]
  • topologyKeyは分散させる単位です。kubernetes.io/hostnameだとノード単位でPodを分散させます。
  • maxSkewはtopologykey同士を比較したときに、配置pod数の最大と最小の差分の許容範囲です。この設定だと、ノード間のPod数差分を5まで許容するということです。
  • whenUnsatisfiableは、maxSkewを満たせるようなノードがない場合の挙動を制御します。DoNotScheduleはデフォルトの設定で、maxSkewを満たすようなPodのスケジュールを行いません。(実際の挙動を見ると、均等に配置してるようでした)

これでPodのデプロイ時にノード間でPodが均等に配置されるようになりました。

descheduler とは

Deschedulerは、ノードの状態を見て移動可能なPodを見つけ出しそれらを退避させます。現在の実装ではdeschedulerは、退去させられたPodの交換をスケジュールせず、退去させらたPodの配置はデフォルトのスケジューラに依存します。

実際にCoreDNS Podに設定した値を元に解説します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    evictSystemCriticalPods: true
    evictLocalStoragePods: true
    maxNoOfPodsToEvictPerNode: 1
    nodeSelector: node-type=default
    strategies:
      "RemovePodsViolatingTopologySpreadConstraint":
         enabled: true
         params:
           includeSoftConstraints: false 
           namespaces:
             include:
             - "kube-system"

CoreDNSはkube-systemにあるので、evictSystemCriticalPods: trueが必要でした。

strategies: RemovePodsViolatingTopologySpreadConstraintを設定することで、先ほど設定したtopologySpreadConstraintsが退去させるポリシーになります。

あとはDeploymentのdescheduling-intervalに設定した間隔で、topologySpreadConstraintsに違反したPodが退去させられます。

公式にあるサンプルは以下のようになっています。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: descheduler
  namespace: kube-system
  labels:
    app: descheduler
spec:
  replicas: 1
  selector:
    matchLabels:
      app: descheduler
  template:
    metadata:
      labels:
        app: descheduler
    spec:
      priorityClassName: system-cluster-critical
      serviceAccountName: descheduler-sa
      containers:
        - name: descheduler
          image: k8s.gcr.io/descheduler/descheduler:v0.23.1
          imagePullPolicy: IfNotPresent
          command:
            - "/bin/descheduler"
          args:
            - "--policy-config-file"
            - "/policy-dir/policy.yaml"
            - "--descheduling-interval"
            - "5m"
            - "--v"
            - "4"

これでKubernetesのノードを運用する上で問題になりがちな、Podが偏る問題にもある程度対応できました。

Datadogで監視する

CoreDNSのメトリクスが取れるようになったので、Datadogで監視を始めました。

DatadogのBlog記事を参考に、以下のメトリクスを監視しています。 カッコ内はDatadogで取得できるCoreDNSのメトリクスです。

  • corednsのresponseの数(coredns.forward_response_rcode_count)
  • corednsのエラーコード(coredns.response_code_count{rcode:serverfail})
  • corednsのリクエストタイム(coredns.request_duration.seconds.sum / coredns.request_duration.seconds.count)
  • corednsのforwardリクエストタイム(coredns.forward_request_duration.seconds.sum / coredns.forward_request_duration.seconds.count)

ダッシュボードも作成し、負荷の変化を追えるようにしました。

まとめ

CoreDNSの負荷低減を行ったおかげでエラー数が激減し安定稼働するようになりました。Hybrid EKSはチーム内での議論や負荷試験を行い本番導入し、現在は本番環境で安定稼働しております。 現状に満足することなく他にも様々な取り組みを行なっているので、またの機会に公開し、皆様の日々の運用へのヒントになったら嬉しいです。

最後までお読みいただき有難う御座いました。

スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com