Kubernetes実践ガイドを読んでみる
Workloads API: コンテナの実行に関するリソース
- Pod
- ReplicationController
- ReplicaSet
- Deployment
- DaemonSet
- StatefulSet
- Job
- CronJob
Pod が最小単位、上位リソースが存在。
ツリー構造の図 (p.121) がわかりやすい
Deployment と Pod の間に ReplicaSet というものが挟まるのか。なるほど。
Job / CronJob あたりはまあそうだよな、という感じ。DaemonSet / StatefulSet / ReplicationController あたりはまだよくわかってない。
Pod
- 1つ以上のコンテナから構成、同じPodに含まれるコンテナ同士はネットワーク的に隔離されない。
- ECSにおけるタスク的なサイズ感なのかなと理解。
- IPが振られる単位、内部は localhost でやりとりできるのも同じ
- サイドカーの概念とかはこの複数コンテナを含めることで実現されている感じか
- Proxy
- LocalCache
- SSL Termination
- etc...
- nginx x redis のような構成は非推奨。まあそうですよね
design pattern
- サイドカー / アンバサダー / アダプタ。複数あるの知らなかったし全部サイドカーに内包されてると思ってた。ちゃうんか
- サイドカー
- メインコンテナ+補助機能
- データや設定にまつわるパターン
- ConfigUpdater
- Git Syncer
- S3 Tansfer
- etc...
- アンバサダー
- 中継。プロキシ
- メインからはlocalhostアクセスすれば良い。疎結合
- アダプタ
- 差分を吸収。ORMとかで考えるやつか
- Prometheus の Exporter とか
Pod DNS の設定 (dnsPolicy) のデフォルトが Default (KubernetesNodeの/etc/resolv.confを引き継ぐ) ではないの地味に罠だなあ。名前どうにかならんかったのかw
実際は ClusterFirst。
hostNetwork を true にしていると、Default に近い動作になると(実際にDefault設定になるのかはよくわかってない)
クラスタ内DNSを参照させるには dnsPolicy: ClusterFirstWithHostNet を明示的に指定
ReplicaSet / ReplicationController
- Pod のレプリカを作成、指定した数のPodを維持し続ける
- ReplicationController の後継が ReplicaSet → 基本 ReplicaSet を使う
- ReplicaSet では指定のラベルのついたPodの数が規定数あるかを確認して増減を行う
- Googleっぽいリソース管理の仕方(ラベルベース)だなあという印象
- spec.selector と spec.template.metadata.labels が一致しなかったら無限にPod作られるのでは、、、?と思ったらエラーで落ちるのね。ちゃんと本に書いてあってさすがだった
- レプリカの制御条件は equality-based / set-based。名前でわかりやすい
- 詳細は12章とのこと
Deployment
- 複数の ReplicaSet を管理、ローリングアップデート・ロールバックを実現
- 1つの Pod しかなくても Deployment を利用することが推奨
- Pod のみ: 障害発生時に復旧されない (ReplicaSet の機能)
- ReplicaSet のみ: ローリングアップデートができない
- apiVersion ...?
- Pod は v1 って指定してる
- Deployment は apps/v1
- 元々(APIGROUP)/(APIVERSION) という指定らしい
- Pod は Core Group 所属だから APIGROUPの指定がないため v1 のみ
- spec.template のハッシュ値を使って ReplicaSet を管理
DaemonSet
- ReplicaSet の特殊な形...?
- 各ノードに Pod を一つずつ配置するリソース
- Replicas の指定なし
- Pod を配置しない Node の設定は Anti-Affinity とか使えば可能
- Node を増やしても DaemoSet は勝手に増える
- EKS におけるログルーティングで定期的に名前は見てたけどこれのことか...! CLAgent のインストールは確か DaemonSet で行っていたはず。確かに Node ごとに一つ立ち上がればいいからぴったり
- 本にも書いてあったw
- Fluentd や Datadog などで使用
- RollingUpdate できるけれど、Podが一つしか立ち上がらないという性質上、maxSurgeは指定できない
2ヶ月経ったけど再開
StatefulSet
- ReplicaSet の特殊系。DBなどステートフルなワークロードに対応
- suffix に数字のインデックスが付与
- Pod 名が変わらない
- データ永続化の仕組み (Persistent Volume を使うと再起動時に同じディスクを用いる) を持つ
- 同一indexを持つ Pod は同じ Volume をマウントすることで再作成されてもデータを引き継げる
- Pod は並行して作成されない、一つずつ作成
- podManagementPolicy を Parrallel にすると
- 並行作成される作成される
- 削除は連番大きい方からしてくれるっぽい
StatefulSet のアップデート戦略
- onDelete
- RolingUpdate
- 必ず一個ずつアップデートがかかる
- 余分な Pod が作られるやり方はできない
- partition を設定すると、設定した値以降の Pod が最大インデックスのものから更新される
Job
- 一度限りの処理をするためのリソース
- Pod の停止が正常終了と期待される
- Batch 的用途
- N並列で実行しながら、指定した回数のコンテナの実行を保証する
- uuid を自動生成するため label / selector を明示的に付与するのは非推奨
- completions / parallelism / backoffLimit
- ttlSecondsAfterFinished を設定するとJobが終了後一定秒数経過で削除される (v1.18 alpha)
- Multi Workqueue
- Job を使うときに completions を指定しない
- すると1つのタスクが完了するまで並列して処理を実行し続ける (1つ完了したらジョブが完了する)
- 使い方
- キューを用意し、ポーリングして処理をし続けるようなワークロードで用いる
- キューの中身がなくなったら正常終了するようにプログラムを作っておけば、キューにタスクが残っているうちは処理を続け、なくなったら終了するような処理が作れる
- バッチ処理とかで使えるイメージ
- Parallelism を 1 に指定すると、処理が正常終了するまで一個ずつタスクを立ち上げ続けるような処理も可能
CronJob
- Job の上位概念と考えるとわかりやすい (ReplicaSet と Deployment の関係的な)
- スケジュールされた時間に Job を作成する
- --from をつけてCronJob から Job を生成することができたりする
- suspend 可能
- concurrencyPolicy
- 古いジョブと同時に実行される可能性があるためポリシーを設定可能
- Options
- Allow: 制御しない
- Forbid: 前のJobがあると次のJobは実行しない
- Replace: 前のJobをキャンセルしてJobを開始する
- startingDeadlineSeconds
- Job開始時間に Master がダウンしていたなど開始時刻が遅れた場合の許容秒数
- デフォルトではどれだけ遅れても Job を作成する
- successfulJobsHistroyLimit / failedJobsHistoryLimit
- Job をいくつ保持しておくかを指定
- 残しておくことでステータスと共にログを確認できる
- とはいえログは外部に吐いて集約しておく方がいいよ
- デフォルトはいずれも 3
Service API
- リソースの公開を司る
- エンドポイントを用意して外部向けに公開したりサービスディスカバリに利用されたりする
- Service
- L4
- Options
- ClusterIP
- ExternalIP
- NodePort
- LoadBalancer
- Headless
- ExternalName
- None-Selector
- Ingress
Kubernetes のネットワーク
- Pod は Service を利用しなくても相互通信が可能
- 同一 Pod: localhost
- 異なるPod: Pod のローカルIPに流す
- Kubernetes クラスタの内部ネットワークは CNI プラグインによるが、基本的には Node ごとに異なるネットワークセグメントを構成、ノード間のトラフィックは VXLAN / L2 Routing などの技術で転送、互いに通信できるように構成する
- Service を使うメリット
- Pod 宛トラフィックをトラフィックをロードバランシングできる
- サービスディスカバリおよびクラスタ内 DNS の実現
Service ClusterIP
- クラスター内部からIPで参照するときに用いる Service Type
- 特定ラベルを持つ Pod に対する通信をロードバランシング
- Tips
- Deployment 定義内で port に名前をつけておくことで、targetPort を名前で指定することができる
- Pod ごとに異なる Port を使う必要がある場合でも名前で指定することで汎用的に Service を使うことが可能
Kubernetes におけるサービスディスカバリ
- 環境変数を用いたサービスディスカバリ
- サービス名の - を _ に変換した上で大文字に変換された環境変数が設定される
- docker --links で設定されるのと同じイメージ
- enableServiceLinks を false にすると環境変数の登録が無効化される (default: true)
- クラスタ内 DNS を用いたサービスディスカバリ
- DNS A レコード
- [Service].[Namespace].svc.cluster.local -> ServiceIP
- [Service] / [Service].[Namespace] だけでも名前解決可能
- ちなみに逆引きも可能
- DNS SRV レコード
- あるドメインで提供されているサービスについて、プロトコル・ホスト名・ポートなど任意の情報を登録し、ドメインから引けるようにするレコード。SRV = SERVICE
- SRV レコードを解釈できるソフトウェアを用いる場合はこちらを用いることでポート番号を含めたエンドポイントまでDNS解決することが可能
- [_ServiceのPort名].[_Protocol].[Service名].[Namespace名].svc.cluster.local
- Ex. _http-port._tcp.sample-clusterip.default.svc.cluster.local
- DNS A レコード
クラスタ内 DNS / クラスタ外 DNS
- dnsPolicy を用いて明示的に設定をしない限り、Podはクラスタ内DNSを利用して名前解決を行う
- クラスタ内 (.cluster.local) 以外のレコードは外部 DNS へ再帰問い合わせを行う
- 大規模クラスタの場合、各ノードのローカル上に DNS キャッシュサーバーを用意する仕組みがある
ClusterIP Service
- 最も基本的な Service (type: ClusterIP)
- Internal な IP を発行、Cluster 内で利用する
- 各ノードで実行している kube-proxy が Pod 向けの転送を行っている
- Cluster 外からトラフィックを受ける必要がない場面で使用。デフォルトで Kubernetes API はこの ClusterIP を発行する kubernetes Service として立ち上がる
- spec.clusterIP を指定することで静的に IP を指定することも可能 (ただし Immutable)
ExternalIP Service
- Node の IP:Port で受信したトラフィックをコンテナに転送することで外部疎通性を確保する
- 特別な事情がない限りは後述の NodePort Service が推奨
- type: ClusterIP で externalIPs を指定した場合をこう呼んでいる
- externalIPs には Node の IP を指定
- 全ての Node の IP を指定する必要はなし
- IP を確認したい場合はノードの情報から確認可能
- ただし OS 上から認識できる形で割り振られている IP しか利用できない
- Cluster 内部通信には ClusterIP を利用するためこちらも付与されている
NodePort Service
- 全ての Kubernetes Node の IPアドレス:Port で受信したトラフィックをコンテナに転送する
- ExternalIP で全ノードからトラフィックを受け付ける設定をするのに近い
- 厳密には 0.0.0.0:Port を使用し全ての IP で Bind する形になる
- Docker swarm mode で Service を Export した時と一緒、らしい
- ClusterIP が設定、ExternalIP は未設定の状態になる
- 複数 NodePort Service で同一 Port は指定できない (当たり前)
LoadBalancerService
- プロダクション環境でクラスタ外からトラフィックを受ける場合に一番実用的で使い勝手がより Service
- Kubernetes クラスタ外のロードバランサーに外部疎通性のある仮想 IP を払い出す
- インフラがロードバランサーに対応している必要あり
- GCP / AWS / Azure / OpenStack など主要なクラウドプロバイダは提供している
- Node ではなく外部リソースを利用
- Node が SPoFにならない
- Docker Desktop では localhost でロードバランサーサービスを構築する
- Port がかぶるロードバランサーサービスは作成できない
- loadBalancerIP を指定することで静的な IP を払い出すことも可能
- GCP では指定ができない (外部 IP を払い出した上でそのIPを利用する必要あり)
- loadBalancerSourceRanges を設定すると GKE や EKS では自動的にクラウドベンダの提供するファイアウォール機能を使ってアクセス元を絞ってくれる。デフォルトは 0.0.0.0/0
- AWS の場合 SG が自動で更新されるらしい
- https://aws.amazon.com/jp/premiumsupport/knowledge-center/eks-cidr-ip-address-loadbalancer/
Service のその他機能
セッションアフィニティ (ClusterIP / NodePort / LoadBalancer)
- 一定期間 (sessionAffinityConfig.clientIP.timeoutSeconds) のリクエストに関しては同一 Pod におくる設定
- NodePort でも設定可能だがどの Node にリクエストが転送されるかによって同一 Pod ではないパターンが発生しうる
- LoadBalancer でも同じ
- クライアント IP をもとに同一 Node へ転送される構成になっている場合のみ有効
Node 間通信の排除、送信もとIPの保持 (NodePort / LoadBalancer)
- LoadBalancer によって Node 単位でのロードバランシングが行われるが、Node 到着後にさらに別 Node を含めた Pod へのロードバランシングが行われる、不要な二重バランシングが発生する
- NodePort も同じ
- 均一にリクエストが分散しやすいが不要なオーバーヘッドが発生する上送信元IPが消失する
- externalTrafficPolicy を設定することで対応可能
- Cluster (Default)
- Local: Node 到達後は Node をまたぐバランシングは行わない
- LoadBalancer の場合は有効化するのはあり
- healthChackNodePort を設定、各Nodeに必要なPodが存在するかを確認してからバランシングさせることが可能
- NodePort の場合到達 Node にPodが存在しないと捌けなくなるため極力利用しない方が吉
トポロジを意識した Service 転送
- Topoogy-aware Service Routing
- リージョンやAZなどのトポロジを考慮し、レイテンシを抑えた通信を可能にする
- externalTrafficPolicy と一緒には使えない
- どのトポロジ範囲に優先的にトラフィックを送るか優先度を決定する
- 例えば 同一ノード → 同一ゾーン → その他Pod のように優先度を指定できる
- 実際には topologyKeys に配列順でトポロジ範囲を指定する
- kubernetes.io/hostname を指定すると同一ホスト名 = 同一ノードの Pod を優先
- * を指定すると全Pod
- kubernetes v1.18 では以下のみ指定可能
- kubernetes.io/hostname
- topology.kubernetes.io/zone
- topology.kubernetes.io/region
Headless Service
- 対象となる個々の Pod の IP アドレスが返ってくる Service
- ロードバランシングはされない
- DNS Round Robin を使ったエンドポイントを提供、クラスタ内DNSから転送先の Pod の IP が返ってくる
- 通信自体は直接 Pod に対して行う形
- DNS キャッシュに注意する必要
- Serviceのspec.type: ClusterIP
- Serviceのspec.clusterIP: None
- ロードバランシングはされない
ExternalName Service
- Service Name の名前解決に外部のドメイン宛の CNAME を返す
- 別の名前を設定したい場合、クラスタ内のエンドポイントを切り替えやすくしたい場合などに使用
- 外部APIのエンドポイントをプログラムに直接埋め込むと疎結合で無くなってしまう
- ExternalName Service を利用することで変更箇所を最小限に止めることが可能
- 外部サービスと内部サービスの切り替えも簡単
- FQDNが一致すれば内部と外部の切り替えも簡単にできちゃう
- 外部APIのエンドポイントをプログラムに直接埋め込むと疎結合で無くなってしまう
- 別の名前を設定したい場合、クラスタ内のエンドポイントを切り替えやすくしたい場合などに使用
None-Selector Service
- 名前解決を行うと自分で指定したメンバに対してロードバランシングする
- Selector 以外の方法でリソースを指定し、ロードバランシングする仕組みと思えばOK
- 外部サービスに対してこれを利用した場合、IPを使ってロードバランシングを行うようなイメージになる(?)
- Service / Endpoints を個別に作成
- ServiceはClusterIP に指定、Selector は指定しない(当然)
- Service の下に Endpoints がいるイメージ
- None-Selector Service が返す IP を利用すると Endpoints へアクセス、登録されているサービスにバランシングされる
- 本の例では ipv4 での指定しか書いていないが、ここにfqdnを書き込むことができる
- 外部サービスの FQDN を書き込めば、IPでバランシングするExternalName Service みたいな動きになる
Ingress
- L7 ロードバランシング。タイプも Service ではなく Ingress。
- 実体は Controller やプラットフォームの実装による
- GKE Ingress Controller:クラスタ外のロードバランサを使用@GCP
- Nginx Ingress Controller:クラスタ内にIngress用のPodをデプロイ
- AWSの場合
- ALB Ingress Controller が存在
- 最近は LoadBalancer Controller に名前が変わった (Service/LoadBalancer にも対応したから?)
- v1.18 でも Beta 実装
- 仕様が固まりきっていないから。Controller やプラットフォームの実装が安定しているかを主に見るべき
- v1.19 で GA済
- LoadBalancerとIngressの違いは L7 or L4
- Ingress の場合 L7 のためパスベースやホストベースのルーティング、SSL終端などが可能
- https://sff8.hatenablog.com/entry/2018/10/27/234757
Config & Storage API
-
種類
- Secret
- ConfigMap
- PersistentVolumeClaim
-
Kubernetes における環境変数
- Pod テンプレートに env or envFrom を指定
- 静的設定
- Pod の情報
- コンテナの情報
- Secret リソースの機密情報
- ConfigMap リソースの設定値
- Pod テンプレートに env or envFrom を指定
-
静的設定
- spec.containers[].env に直接値を指定
- name: A \ value: B で書く
- TZ の設定などで利用
- spec.containers[].env に直接値を指定
-
Pod の情報
- fieldRef を使うことで Pod の各種値を取得できる
- valueFrom: fieldRef: fieldPath: spec.nodeName など
-
コンテナの情報
- resourceFieldRef を利用して参照
-
Secret リソースの機密情報
- 機密情報などは Secret を利用して設定
-
ConfigMap リソースの設定値
- 単純な Key-Value や設定ファイルを保管
- Pod に直接情報を埋め込む代わりに利用
- 複数の Pod で重複がある場合に一括変更できるなど管理のメリットを得られる
-
Kubernetes における環境変数利用
- command や args で直接環境変数を利用することはできない
- マニュフェストに定義したものは $() で三勝かのう
- OS 定義のものなどを利用したい場合は shell スクリプトにした上でスクリプト内で利用する必要あり
- command や args で直接環境変数を利用することはできない
Secret
- 機密情報を保管するためのリソース
- いくつかのタイプに分かれる
- Opaque
- kubernetes.io/tls
- kubernetes.io/basic-auth
- kubernetes.io/dockerconfigjson
- kubernetes.io/ssh-auth
- kubernetes.io/service-account-token
- bootstrap.kubernetes.io/token
- ひとつの Secret 内に複数の Key-Value が保存される
- 格納可能なサイズは 1 MB
- secretKeyRef を使って特定の KV を環境変数に渡せる
- secretRef である Secret の全ての KV を環境変数に渡せる
- Volumeとしてマウントすることも
- この場合 Key をファイル名とするファイルが生成される
- こちらを用いると動的な変更が可能
- Pod の再作成も起きないため瞬断もなし
ConfigMap
- 設定情報などの Key-Value で保持できるデータを保存
- 設定ファイル自体の保管も可能
- EKS では IAM ユーザーとクラスタのユーザーを紐付ける設定を ConfigMapで保持する
- 基本利用は Opaque の Secret と同じ
- Secret は以下のように機密情報に特化しセキュアに扱えるよう対策が施されている
- Master の etcd に保管
- メモリ内の tmpfs に保管される
- kubectl から見えづらい
- base64 でエンコードされている
- ConfigMap に .sh ファイルを配置することも可能
- mode でパーミッションを指定しマウント可能
- これにより .sh ファイルを ConfigMap から取り込み、実行することもできる
- Secret は以下のように機密情報に特化しセキュアに扱えるよう対策が施されている
PersistentVolumeClaim
- 永続化領域を利用するためのリソース
- Volume / PersistentVolume / PersistentVolumeClaim
- Volume
- あらかじめ用意された利用可能なボリュームを利用可能にするもの
- Pod定義内に直接書き込む形で接続を行う
- Kubernetes 上から新規でボリュームを作成したり既存のボリュームを消したりできない
- PersistentVolume
- 外部の永続ボリュームを提供するシステムと連携
- ネットワーク越しにディスクをアタッチするタイプが基本
- Container Storage Interface (CSI) が定義されており、プロバイダとの結合度合いを下げ柔軟性を上げる
- アクセスモードが定義できる (RWO / ROX / RWX)
- ReClaim Policy
- Retain
- PV は消えるがデータは消えない
- PVの再利用は不可。新しくPVを作り直し Claim から利用する必要あり
- Delete
- データもPVも消える
- DynamicProvisioning の時のデフォルト設定
- Recycle (非推奨)
- Retain
- PersistentVolumeClaim
- PVリソースの中から指定したスペックをアサインしてもらうための要求
- PV > PVClaim なら割り当てがされてしまうことに注意
- PV を定義してクラスタにボリュームを登録、Podから利用するためにPVClaimを定義する
- Dynamic Provisioning 機能が提供されることも
- Claim に応じて PV が作成される機能
- 通常 Claim リソースを作成すると同時に Volume が作られるため利用していないClaimがあると容量を無駄に使ってしまう可能性がある。volumeBindingMode を WaitForFirstConsumer にすることでPodにアタッチするタイミングまでPV作成を遅らせることができる
- PVリソースの中から指定したスペックをアサインしてもらうための要求
- Volume
ClusterAPIs / MetadataAPIs
ClusterAPIs
- セキュリティ周りの設定、クォータ設定などクラスタの挙動を制御
- Node
- Namespace
- PV
- ResourceQuota
- ServiceAccount
- Role
- ClusterRole
- RoleBinding
- ClusterRoleBinding
- NetworkPolicy
MetadataAPIs
- クラスタ上にコンテナを起動させるのに利用するリソース
- LimitRange
- HorizontalPodAutoscaler
- PodDisruptionBudget
- ClusterResourceDefinition
リソース管理とオートスケーリング
リソース制限
requests / limits
- resources.requests
- Pod起動時に必要なリソース量を定義
- 足りないと起動されない
- resources.limits
- Podが利用可能なリソース使用量の上限を指定
- 実際のNodeのリソース量を超えて指定できちゃう
- このためあまり高い値を設定すると CPU 負荷上がりまくったり OOM 発生したり大変なことになる可能性あり
- 単体しても可能だが、
- requests のみ
- ホストの負荷上限一杯一杯まで頑張って使う
- OOMとか起きやすくなる
- limits のみ
- limits = requests になる
- オーバーコミットはできない
- GPUなどのリソースについても Requests / Limits 制限がかけられる
- requests のみ
システム用リソース
- kube-reserved / system-reserved の 2 種類のリソースが用意されている
- これらの分リソースが確保されているため Kubernetes システム自体が止まることを防いでくれる
- Allocatable なリソース領域は、リソース全体からこれらのリソース使用量を引いた値になる
Eviction Manager
- Allocatable / kuber-reserved / system-reserved で使用しているリソースの合計が Eviction Threshold を超えていないか定期的に確認
- 超過してたら Pod を Evict する
- 設定値は Kubernetes 環境やノードのインスタンスタイプなどによって異なる
複数コンテナ利用時
- InitContainer のリソースの最大値 (Init は順次実行され、全てのコンテナの前に実行される) or 複数コンテナのリソース使用量の合計値 (通常コンテナは並列実行される) のどちらかをリソース値として使用
Cluster Autoscaler とリソース不足
- Node を自動的に追加する機能
- クラウドベンダーなどは多くが提供
- Pending 状態の Pod ができた状態で初めて Cluster Autoscaler は実行される
- = すでにリソースが足りない状態になってからスケールが実行されるということ
- = Requests と Limits の指定を適切に行うことが大切
- Ex. Requests がデカすぎる
- 実際のプロセスが利用しているリソース消費量が小さくても Pod が追加できず Requests が満たせず Node 追加が起きる可能性
- Ex. Limits と Requests の乖離が大きい
- 実際の負荷が高いのに Requests が低いせいでPodが追加できてしまい Node が増えず高負荷状態が続く
LimitRange によるリソース制限
- Metadata API の LimitRanage
- [TODO]