「Kubernetes 完全ガイド」を読んだ
Sep 17, 2019 00:00 · 1066 words · 6 minute read
Kubernetes 完全ガイド で Kubernetes に入門した。
1章 Docker の復習と「Hello, Kubernetes」
Docker コンテナを作成する際の注意点
- 1コンテナ1プロセス
- 水平スケールやコンテナの再利用をシンプルにするため。
- Immutable Infrastructure なイメージ
- コンテナ起動後に外部ライブラリをインストールしたり、外部リソースを取得するな。
- 軽量な Docker イメージ
- Alpine Linux を使う。
- yum や apt でインストールしたときのキャッシュファイルなどは削除する。
- root 以外の実行ユーザー
- プロセスを起動するユーザーの権限は最小化しておく。
- Dockerfile 内で権限を制限したユーザーを作成し、
USER命令で指定する。
Docker イメージのビルド
「golang:1.10.1-alpine3.7」は「alpine3.7」をベースイメージにして作成イメージサイズは「alpine3.7」が約4.2 MB に対して「golang:1.10.1-alpine3.7」は約376 MB である。これは「golang:1.10.1-alpine3.7」が Go のビルドツールを含んでいるため。実行コンテナはコンパイルされた実行ファイルさえあれば良いので、Go のビルドツールは不要である。このとき「マルチステージビルド」が役に立つ。
# コンパイル
FROM golang:1.10.1-alpine3.7 as builder
COPY ./main.go ./
RUN go build -o /go-app ./main.go
# 実行コンテナ
FROM alpine:3.7
EXPOSE 8080
COPY --from=builder /go-app .
ENTRYPOINT ["./go-app"]これによって「alpine3.7」に実行ファイルがはいっただけのイメージが作成できる。このイメージは約10.8 MB で、大きく軽量化できた。alpine ではなく、scratch をベースにすれば更に小さくできる。
2章 なぜ Kubernetes が必要なのか?
Docker を始めとするコンテナランタイムだけでは、複数台のコンテナホストを協調して動作させたり管理できないため、オーケストレーションツールが必要。
Kubernetes の主な特徴機能
- Infrastructure as Code
- リソース管理やデプロイの管理を宣言的なコードで書ける
- スケーリング
- コンテナの数を増減させる
- スケジューリング
- コンテナのデプロイ先 Node の決定
- セルフヒーリング
- プロセスが死んだら、もしくはヘルスチェックに失敗したら、アプリケーションを自動復旧する
- サービスディスカバリ・ロードバランシング
- ロードバランシング機能(Service)によって、コンテナ群に対してルーティングを行ったり、スケール時に自動追加・削除を行う
- データ管理
- etcd という分散キーバリューストアを利用しており、コンテナ間の共通設定ファイルや認証情報などを保存・管理できる
3章 Kubernetes 環境の選択肢
ローカル
- 本書の minikube はバージョンが v0.28.2 なので、2019年8月時点の最新 v1.3.1 では削除されたコマンド(get-k8s-versions など)が載っているが、無視して問題ない。
- Docker for Mac も Kubernetes へのデプロイが可能らしい。 https://docs.docker.com/docker-for-mac/kubernetes/
Kubernetes 構築ツール
Docker コンテナに付与される IP アドレスはホストの外から疎通できない Internal IP なので、ノードをまたいだコンテナ間通信ができない。 Funnel はノード間をつなぐネットワークに仮想的なトンネル(オーバーレイネットワーク)を構成することで、クラスタ内の Pod 同士の通信(Pod ネットワーク)を実現している。 Funnel 以外にも Pod ネットワークを実現する手段は複数ある。
Rancher サーバを通して、Kubernetes クラスタの構築や管理を行う。
マネージド Kubernetes サービス
GKE, AKS, EKS
4章 API リソースと kubectl
Kubernetes クラスタは Master と Node からなっている。Master は API Endpoint やスケジューリング、スケーリングの管理を行う。Node はいわゆる Docker Host でコンテナを実行する環境。
クラスタを操作するには kubectl とマニフェストファイルを用いて、「リソース」を登録する。kubectl はマニフェストファイルに基づいて Master の API を叩く。API は一般的な RESTful API であり、kubectl を使わなくてもよい。
Workloads リソース
コンテナを起動させるためのリソース
- Pod
- ReplicationController
- ReplicaSet
- Deployment
- DaemonSet
- StatefulSet
- Job
- CronJob
Discovery & LB リソース
サービスディスカバリやエンドポイントを提供するリソース。
- Service
- Ingress
Config & Storage リソース
設定や永続ボリュームを提供するリソース
- Secret
- ConfigMap
- PersistentVolumeClaim
Cluster リソース
セキュリティの設定などクラスタの振る舞いを定義する定義するリソース
Metadata リソース
他のリソースを制御するリソース
Namespace
Namespace ごとに権限を分け、さらに Network Policy を組み合わせることでクラスタの分離を行う。
認証情報と Context
kubectl は(デフォルトでは) ~/.kube/config に書かれた情報を利用して接続を行う。このファイルには接続先クラスタやユーザー、Context に関する情報が書かれている。
Context は cluster と user の組み合わせであり、kubectl は Context を切り替えることで複数クラスタを使い分ける。
作成・取得・更新・削除
# 作成
kubectl create -f {ファイル}
# 取得
kubectl gets {リソースタイプ}
# 更新
kubectl apply -f {ファイル}
# 削除
kubectl delete -f {ファイル}apply はリソースに変更があれば変更処理を行い、変更がなければ何もしない。またリソースがなければ作成を行う。作成時は create よりも apply を使うことが推奨される。なぜなら差分検出は、
前回 apply または create --save-config した内容は考慮されるが、 create した内容は考慮されず、適切に差分が検出されない可能性が発生するから。
ひとつのファイルの中に複数のリソースを記述することができる。--- で分割する。リソースの適用は上から順番に行われる。複数ファイルのリソースをすべて実行したいときは -f オプションでディレクトリを指定することで、そのディレクトリ下のリソースファイルをすべて実行する。ファイル名順に実行される。順序を指定するためにプレフィックスが必要になるかもしれない。一括してシステム全体のアップデートを行えるようにするために、すべてのマイクロサービスのマニフェストを一つのディレクトリにまとめると良い。
アノテーション・ラベル
- アノテーション
metadata.annotationsに設定- システムが利用するメタデータで、ツールやマネージド・サービス固有の機能を設定する場合や、ベータ機能の ON/OFF に使われる。
- ラベル
metadata.labelsに設定- リソースを分別するためのもの。 kubectl get コマンドは
-lオプションにより、ラベルでフィルターできるし、 Service リソースや ReplicaSet リソースはラベルを条件に設定できる。 - プロジェクト、アプリケーションの種類、バージョン、環境あたりは定番。
リソース削除 --prune オプション
ヒューマンエラーを避けるためにも、実運用時は kubectl を手動で実行するよりも、バージョン管理システムでコードを管理し、 CI/CD パイプラインで自動的に実行させるほうがよい。 apply --prune コマンドで、マニフェストから削除されたリソースを削除できる。ドキュメントを読む限りは2019年8月時点でも --prune オプションはアルファ。
その他コマンド
stern
kubectl では Pod 単位のログ出力しか見ることができない。コンテナが消されるかもしれないし、自分のみたいログがどの Pod に出力されたかわからないときもある。stern を使うことで複数 Pod のログをまとめて見ることができる。
Workloads リソース
コンテナを起動させるためのリソース
- Pod
- ReplicationController
- ReplicaSet
- Deployment
- DaemonSet
- StatefulSet
- Job
- CronJob
Pod が最小リソースで、それを管理する上位リソースがある。例:Pod ← ReplicaSet ← Deployment
Pod
Pod は1つ以上のコンテナから構成され、Pod 内のコンテナはネットワーク的に隔離されていない。Pod に対して IP Address が割り当てられる。Pod 内のコンテナは互いに localhost 宛で通信できる。
1つの Pod に複数のコンテナを入れるデザインパターンが存在する。Pod 内はデータ領域を共有できる。
- サイドカーパターン
- メインコンテナに機能を追加
- 例:ログを外部サービスへ出力
- アンバサダーパターン
- 外部システムのやり取りを代理
- 例:データベースのプロキシ
- アダプタパターン
- 外部からのアクセスのインターフェース
- 例:メトリクスを監視ソフトが求めるフォーマットへ変換
ReplicaSet
Pod のレプリカを作成し、指定した数の Pod を維持し続けるリソース。
ReplicaSet は selector.matchLabels で指定したラベルの Pod 数を維持する。また spec.template.metadata.labels で Pod のラベルをつけるため、基本的にはこの2つは一致する。
Deployment
ReplicaSet を管理するリソース。ローリングアップデートの際は、新しい ReplicaSet を作成し、 新しい ReplicaSet のレプリカ数を徐々に増やしつつ、古いReplicaSet のレプリカ数を徐々に減らす。Deployment 経由の起動は様々なメリットがあるので、たとえ 1Pod だけをデプロイしたい場合も Deployment で起動することが推奨される。
spec.template 構造体のハッシュ値をもとに ReplicaSet を作成するため、レプリカ数の変更は反映されないし、ロールバック時は レプリカ数が0になっている過去の ReplicaSet を利用する。
ただし、kubectl rollout によるロールバックよりも CI/CD から古いマニュフェストを kubectl apply するほうがよい運用。
kubectl rollout pause kubectl rollout resume でロールアウトの停止、再開ができる。
spec.strategy.type でアップデート戦略を変えられる。デフォルトは徐々に Pod を置き換えていく RollingUpdate だが、全削除してから全作成する Recreate 戦略も設定可能。ダウンタイムが発生するが、切り替えが速く、余剰なリソースを使わないメリットがある。
DaemonSet
ノードに Pod を1つずつ配置するリソース。Fluentd や Datadog など必ず各 Node 上で動作させたいプロセスのために利用されることが多い。
StetefulSet
spec.volumeClaimTemplates を指定でき、各 Pod に対して persistent volume を設定できる。 Pod の名称のサフィックスが連番であり、スケール時には最も大きい番号の Pod が削除/作成される。これは 0 番目の Pod をマスター、それ以外をスレーブにする冗長化に向いている。
Job
N 並列で実行し、指定した回数コンテナの実行を保証するリソース。バッチ処理など。終了回数を指定しない場合、特定のタスクを実行し続けるワークキューとして働く。
CronJob
Job を管理するリソース。cron で定期的に実行したいタスク用。spec.concurrencyPolicy で、同時実行(いわゆる突き抜け)の可否を設定できる。
6章 Discovery & LB リソース
- Service リソース: L4 ロードバランシング
- Ingress リソース: L7 ロードバランシング
Pod 内のコンテナはすべて同じ IP アドレスが割り当てられるので、同一 Pod 内のコンテナには localhost 宛に通信し、別の Pod のコンテナにはその Pod の IP アドレス宛に通信する。
ノードごとに異なるネットワークセグメントを構成し、ノード間のトラフィックは VXLAN や L2 Routing などの技術を利用して転送する。
Service のエンドポイントには、外部のロードバランサが払い出す仮想 IP アドレス(Virtual IP Address)やクラスタ内で利用可能な仮想 IP アドレス(Cluster IP)など様々な種類がある。
サービスディスカバリ
- 環境変数を利用したサービスディスカバリ
- DNS A レコードを利用したサービスディスカバリ
- サービス名を使って名前解決するのが Kubernetes のポータビリティを保つためにも推奨される方法
- DNS SRV レコードを利用したサービスディスカバリ
- ポート名とプロトコルから名前解決することも可能。
クラスタ内 DNS は *.cluster.local しか登録されていないので、それ以外のレコードは外部 DNS へ問い合わせる。また dnsPolicy で明示的に外部 DNS を利用することもできる。
ExternalIP
spec.type を "ClusterIP" にし、 spec.externalIPs にノードの IP(ExternalIP)を spec.ports[].port にノードの IP と ClusterIP で受け取る Port 番号を指定する。クラスタ外からも指定したノードへの疎通が可能かつ、Pod へのリクエスト分散も行われる。
NodePort Service
ExternalIP と異なり、全ノードの IP アドレスでクラスタ外からも疎通が可能。指定できるポート範囲は 30000 ~ 32767 となっている。
NodePort によるロードバランシングはノードをまたいだ Pod へも分散する。spec.externalTrafficPolicy を使うことで、同じノード上の Pod だけに通信を制限できたりする。そのノードに Pod が存在するとは限らないので、ノードをまたいだロードバランシングのほうがよい。
LoadBalancer Service
プロダクション環境で、クラスタ外からトラフィックを受ける場合は LoadBalancer Service を使うとよい。クラスタ外のロードバランサに仮想 IP を払い出す。NodePort や ExternalIP はノードに割り当てられた IP アドレスを利用するためそのノードが単一障害点になるが、LoadBalancer Service は外部のロードバランサを使うので、ノード障害に強い。
GKE の場合、アドレスのタイプは「リージョン」である必要がある。「グローバル」は使えない。
NetworkPolicy によるアクセス制御も可能だが、できればロードバランサで制御したが方がレイテンシが良かったり、スケーラビリティを保ちやすい。
Headless Service
個々の Pod の IP アドレスが直接返ってくる Service。spec.type を "ClusterIP" にし、spec.clusterIP が "None" を指定する。StatefulSet の場合は Pod 名で名前解決ができる。例えば "{pod名}.{service名}.{namespace}.svc.cluster.local" で対応する Pod の IP アドレスが返ってくる。StatefulSet 以外の場合は、"{service名}.{namespace}.svc.cluster.local" で対応する Pod の IP のひとつが返ってくる。どの Pod の IP が返ってくるかはラウンドロビン
で決まる。
ExternalName Service
Service 名の名前解決に対して、外部ドメイン宛の CNAME を返す。外部サービスと内部サービスの間の切り替えをする際、アプリケーションの変更をせずに切り替えできるようにするためのもの。
None-Selector Service
Service 名の名前解決に対して、自分で指定したメンバへのロードバランシングを作成する。spec.type は "ClusterIP"。
Ingress
L7 ロードバランシングを提供する。kind を "Ingress" に指定する。外部アクセスをクラスタ内のサービスに分散する。Ingress リソースを作成するだけでは不十分で、Ingress Controller が必要。
GKE Ingress Controller はクラスタ外のロードバランサを利用し、Nginx Ingress Controller はクラスタ内に Ingress 用の Pod をデプロイする。
7章 Config & Storage リソース
Secret & ConfigMap
設定ファイル、パスワードなどを挿入したり、永続化ボリュームを提供するためのリソース。
環境変数を渡すには Pod テンプレートに env または envFrom を指定する。環境変数の情報源として
- 静的設定
- Pod 情報
valueFrom.fieldRef - コンテナ情報
valueFrom.resourceFieldRef - Secret リソース
valueFrom.secretKeyRef - ConfigMap リソース
valueFrom.configMapKeyRef
Kubernetes のマニュフェストの command や args で環境変数を利用するときは $(ENV_NAME) のように $() で指定する。
Secret リソースのマニュフェストは暗号化されていないので、Git リポジトリにそのまま含めることができない。解決策の例として kubesec があり、これはクラウドサービスの KMS を使って暗号化し、Git リポジトリに含めることができる。ファイル全体ではなく、キーバリューのうちバリューのみを暗号化するので Git の差分の可読性に優れるらしい。
Secret を作成するには kubectl create secret コマンドを使う。
PersistentVolumeClaim
- Volume
- あらかじめ用意されたボリュームをボリュームを、マニュフェストで指定して利用する。Kubernetes はボリュームの作成を行わない。Pod の定義に直接書き込む。
- PersistentVolume
- 独立したリソース。外部の永続ボリュームを提供するシステムと連携して、ボリュームの作成や削除が可能。クラスタに紐づく。
- ネットワーク越しにディスクをアタッチする。
- PersistentVolumeClaim
- 作成された PersistentVolume を Pod から利用するための登録。
- DynamicProvisioning を使うと、PersistentVolumeClaim が利用されたタイミングで PersistentVolume を動的に作成することが可能。
- ラベルセレクタや容量でマッチした PersistentVolume を割り当てる。
Volume プラグイン
- emptyDir
- Pod のディスク領域。Pod が terminate されると削除される。
- hostPath
- Node 上の領域をコンテナにマッピングする。セキュリティの観点から、信頼できないコンテナでマウントするのは控える。
- downwardAPI
- Pod 情報をファイルとして配置するためのプラグイン。
- projected
- Secret/ConfigMap/downwardAPI/serviceAccountToken のボリュームマウントを1つのディレクトリにまとめるプラグイン。
9章 リソース管理とオートスケーリング
CPU や GPU、メモリ等のリソースを制限できる。Pod 定義内に記載する。
requests で下限を、limits で上限を指定できる。下限分を確保できなければスケジューリングされない。limits のほうはオーバーコミットすることもある。
GKE などクラスタ自体のオートスケーリングが可能な環境もある。下限分を確保できず pending の Pod ができると Cluster Autoscaler が発動する。実際の使用量とは関係ない。基本方針は、requests と limits の差を大きくしないことと、requests を小さくしすぎないこと。実際はパフォーマンステストを行いながら徐々に上げていくことになる。メモリは OOM が発生しない程度を確保すること。逆に requests と limits の差を小さくすると集約率は低くなるというデメリットもある。
Pod/Container/PersistentVolumeClaim は LimitRange で リソース制約のデフォルト値や制限範囲を定めることもできる。
requests と limits の指定の有無によって QoS クラスが決まる。QoS クラスは優先度が決められており、OOMKiller で Pod が削除されるときには、優先度の低いものから削除される。
HorizontalPodAutoscaler
Deployment/ReplicaSet/ReplicationController のレプリカ数を CPU 負荷に応じてオートスケールするためのリソース。Pod に resource requests が必要。
30秒に1回のペースでチェックを行い、次の式から必要なレプリカ数を計算する。
必要なレプリカ数 = ceil( sum(PodのCPUの使用率) / targetAverageUtilization )
スケールアウトの条件: avg(PodのCPUの使用率) / targetAverageUtilization > 1.1 スケールインの条件: avg(PodのCPUの使用率) / targetAverageUtilization < 0.9
CPU 以外のリソースでオートスケールを行う場合は、prometheus などメトリクスサーバとの連携が必要。
10章 ヘルスチェックとコンテナのライフサイクル
- LivenessProbe
- Pod が正常に動作しているかの確認。動作していない場合は Pod を再起動する。
- メモリリークによる処理機能低下など再起動なしに回復が難しい場合に使う。
- ReadinessProbe
- Pod がサービスインできるかの確認。準備できていなければトラフィックをその Pod に流さない。
- DB 接続やキャッシュのロード、時間のかかる起動プロセスの完了を待つのに使う。
ヘルスチェックはコンテナごとに行い、ひとつでも失敗したらその Pod は失敗したとみなす。
チェック方式として
- exec: コマンド実行して終了コードが0かどうか
- httpGet: HTTP リクエストをしてステータスが200~399かどうか
- tcpSocket: TCP セッションを確立できるかどうか
Init Controllers を使うことで Pod 内でコンテナを起動する前に別のコンテナを起動させる機能。外部からのファイル取得、設定ファイルの生成や、起動前のチェックなどセットアップなどに利用する。
ライフサイクル
- postStart
- Entrypoint の実行と順序は保証されていない。ほぼ同時に起動する。
- preStop
- Pod 削除リクエストが Kubernetes API サーバに届くと「preStop 処理 + SIGTERM 処理」と「Service からの除外」が非同期に実行される。terminationGracePeriodSeconds 内に preStop 処理 + SIGTERM 処理が終わらない場合は、強制的に SIGKILL シグナルがコンテナに送られる。
11章 メンテナンスとノードの停止
スケジューリング対象からの除外と復帰は cordon/uncordon コマンド使って行う。除外されても実行中の Pod は起動したまま。Pod を排出するには drain コマンドを使う。
12章 高度で柔軟なスケジューリング
特定のノードだけで Pod をスケジューリングすることができる。
ノードにはデフォルトでラベルがついているし、手動でつけることもできる。例えば本番/開発環境、ディスクの種類や CPU の種類などをラベルにできる。
nodeSelector を使うことでラベルによる絞り込みができる。spec.affinity.nodeAffinity を使えば更に細かい設定も可能。ラベルの存在を見たり、不等式でフィルターしたり、優先度を指定できる。
Taints Tolerations を使えば、条件に合致しない Pod をノードから追い出すことが可能。例えば GPU や FPGA 専用のノードには、特定の Pod をスケジューリングしたくないときに使える。
13章 セキュリティ
ServiceAccount は Namespace に紐づくリソースで、Pod 起動時には必ずひとつ割り当てる必要がある。認証・認可も ServiceAccount を用いる。ServiceAccount 作成時に自動的に secret が作成される。secret はトークンと証明書から構成され、このトークンは Kubernetes API への認証情報として利用できる。Pod に割り当てた ServiceAccount の権限が、その Pod の権限となる。ServiceAccount を指定した Pod にはトークン・証明書が Volume として埋め込まれている。Pod 上のアプリケーションはこのトークンや証明書を使うことが可能。
RBAC, Role Based Access Control。Role とユーザ(UserAccount と ServiceAccount)を RoleBinding で紐付けることで権限を管理する。
NetworkPolicy を使って、Pod 間の通信におけるセキュリティの設定ができる。
AdmissionControl という認証・認可の次のフェーズを追加することが可能。
14章 マニフェストの汎用化を行うオープンソースソフトウェア
システムの規模が大きくなると、 Kubernetes のマニフェストの数が増えたり複雑化してくる。共通処理を汎用化させるためのソフトウェアがある。
- Helm
- https://helm.sh/
- Kubernetes のパッケージマネージャ。
- テンプレートからマニフェストを作成できる。
- ksonnet
- 2019年9月時点では、終了している?
- kustomize
15章 モニタリング
メトリクス監視サービス・ツールの一例
- Datadog
- 有料。ホスト・コンテナ単位で課金が発生する。
- DaemonSet で Datadog Agent を各ノードで起動することで CPU 使用率などのメトリクスを収集する。
- Prometheus
- 無料。ただしメトリクス保存サーバのリソースが必要。
16章 コンテナログの集約
kubectl logs コマンドで標準出力・標準エラー出力されたログを見ることができる。
Fluentd でログを収集するには DaemonSet で各ノードに Fluentd Pod をひとつずつ起動する。起動された Fluentd はそのノード上のコンテナの標準出力・標準エラー出力を転送する。GKE の場合はデフォルトで Stackdriver Logging に転送する Fluentd が起動する。保存するログのフィルタリングは Fluentd でも可能だが、コンテナの再作成などが必要なことから、Stackdriver Logging 側で行うほうが楽。
17章 Kubernetes環境でのCI/CD
Spinnaker は CD を実現するソフトウェアで、カナリアリリース、Blue/Green デプロイメント、ロールバックなどのリリース管理が可能。
Skaffold はアプリケーションのソースコードの変更を検知すると、Docker イメージのビルド、レジストリへの push、Kubernetes クラスタへのデプロイを一元的に行う。
18章 マイクロサービスアーキテクチャとサービスメッシュ
マイクロサービスの問題のひとつに、システム全体のモニタリングが難しい点がある。Observability と呼ばれている。モニタリングが不十分だと原因やボトルネックの特定に時間がかかる。これを解決するのがサービスメッシュという考え方。
サービスメッシュは Pod 間通信にプロキシをはさみ、トラフィックのモニタリング(エラーレート、レイテンシ、コネクション数、リクエスト数など)を行う。サービスメッシュを実現するソフトウェアの例として Istio, Linkerd が挙げられる。またトラフィックをコントリールして、カナリアリリースを行うことも可能。また転送先のマイクロサービスに問題がある場合に、すぐに転送元のマイクロサービスにエラーを返し、タイムアウト待ちの連鎖でシステム全体が壊れることを防ぐ、Circuit Breaker という機能もある。Fault Injection を使ってシステムのテストを行うことも可能。再送制御もできる。Istio は Pod の中に Envoy を内包させ、すべてのトラフィックが Envoy 経由で 行われる。Envoy の設定は Pilot コンポーネントが行い、 Envoy が収集したメトリクスは Mixer コンポーネントが集約する。
19章 Kubernetesのアーキテクチャを知る
Kubenetes クラスタの構成要素
- etcd
- 分散 KVS 。冗長性のためにクラスタが組まれ、分散合意アルゴリズムには Raft が使われている。
- kube-apiserver
- kubectl のリクエスト送信先。
- kube-scheduler
- kube-controller-manager
- kubelet (+ container runtime)
- docker-shim などコンテナランタイムと連携し、ノード上で Pod を起動する処理を行う。
- kube-proxy (+ Network Plugin)
- ClusterIP や NodePort 宛のトラフィックが正常に Pod に転送する。転送方法に userspace, iptables, ipvs 3のモードがある。iptables が速いし、安定して実績もあるが、スケールするとパフォーマンスが下がる。ipvs は性能も高いし、ロードバランスのアルゴリズムも選択肢がある。
- kube-dns (CoreDNS)