Kubernetes完全ガイド読んだ感想・まとめ
青山さんのKubernetes完全ガイド読みました
インプレスさんより出版されているKubernetes完全ガイドを読みました。今まで行き当たりばったりでKubernetesを学んできた自分にとって、ものすごく学びが多かったですし、体系だった知識に整理できた一冊でした。図がたくさんあって理解もスムーズだったように思えます。
Kubernetesにこれから触る人や私みたいにその場しのぎで学んできた方は絶対に読むべき一冊だと思います。
※ 私はAmazon ECSとGKEを使い始めて半年くらいです
今回Kubernetes入門を見て改めて整理出来た項目を中心に挙げていきたいと思います。
- 青山さんのKubernetes完全ガイド読みました
kubectl
Contextの切り替え
kubectlで操作する際に、操作対象のクラスターや認証情報が必要になります。実態はデフォルトだと、$HOME/.kube/config
に記載されるらしいです。EKSとGKEを使っている場合に、複数の異なる環境にアクセスする場合にはContextの登録・切り替えが必要になります。
# クラスタ名の登録 kubectl config --kubeconfig=config-demo set-cluster development --server=https://1.2.3.4 --certificate-authority=fake-ca-file # 認証情報の登録 kubectl config --kubeconfig=config-demo set-credentials developer --client-certificate=fake-cert-file --client-key=fake-key-seefile # コンテキスト名 / クラスタ名 / namespace / 認証情報 kubectl config --kubeconfig=config-demo set-context dev-frontend --cluster=development --namespace=frontend --user=developer kubectl config --kubeconfig=config-demo set-context dev-storage --cluster=development --namespace=storage --user=developer kubectl config --kubeconfig=config-demo set-context exp-scratch --cluster=scratch --namespace=default --user=experimenter # 設定情報を見る kubectl config --kubeconfig=config-demo view # 切り替えコマンド kubectl config --kubeconfig=config-demo use-context dev-frontend
Configure Access to Multiple Clusters - Kubernetes
kubectlで適用される差分
今まで意識してこなかったと同時に、なぜ意識できなかったのか後悔の多いところです。
さらに言えば、kubectl applyのマニフェストのマージ方法というところになります。
削除項目の差分適用は、新しくapplyされるマニフェストと、以前applyされたマニフェストとの比較で行われますが、追加・更新の項目の差分適用は以前applyされたマニフェストではなく、そこからKubernetesが変更を加えたものとの比較になります。つまり、kubectl get deploy xxxxx -o yaml
した結果との比較です。
applyした結果、podのテンプレートと変更があれば、ローリングアップデートが起こりますね。以前適応したマニフェストにrollingUpdateについて書いてない状態から、rollingUpdateでmaxSurge25%、minUnAvailable25%ととしても、デフォルト値としてKubernetes側がセットした値とかわらないため、rollingUpdateは起こりません。
また、削除項目の差分適用が以前applyされたマニフェストとの比較ということで、単純にcreateされたマニフェストから、applyで更新すると削除できない項目が出てきてしまいます。createコマンドを使用するときはkubectl create --save-config
を使用すること。
マニフェストファイルの設計
結合度の高いリソースである場合、例えばDeploymentとService、HPA、PDBは同じマニフェストファイルに書いても良いかもしれない。ConfigMapやSecretなどの疎結合のリソースに関してはマニフェストファイルを分けるべき。1つのファイルに書く場合には---
で区切る。上から順に適用されていくため、書く順番は考慮されるべきである。
kubectl apply -f ./ -R
で再帰的にマニフェストを適応させることが出来るらしいので、ディレクトリ構造は極力わかりやすい形に持っていくことも可能。
その他
- Stern(https://github.com/wercker/stern)導入で複数のpodをログを同時に見ることが可能
- 環境変数
KUBE_EDITOR
で使用するエディタを指定できる。 kubectl copy
コマンドkubectl port-forward deployment/xxxxxx 8080:3306
でkubectlを実行したインスタンス(もしくはローカルマシン)のポート8080にアクセスすると対象Podの3306番のpodに接続される。- デバッグは
-v=6
や-v=8
を使う。
Workloadsリソース
Podのデザインパターン
- サイドカー:メインコンテナに機能を追加する。個人的にはkinesisにアクセスログを流すflunetdコンテナとかがこれに当たると思いました。
- アンバサダー:外部システムとのやり取りの代理を行う。HAProxyコンテナのイメージです。AWSでいうならRDSの死活監視を行ってトラフィックを流すようなもの。
- アダプタ:外部からのアクセスのインターフェース。JavaアプリケーションとかだったらPrometheus Tomcat Exporterのようなイメージでしょうか。
Rolling Updateの処理
マニフェストをapply -> .spec.template以下の構造体のハッシュ値を計算 -> 同じハッシュ値のReplicaSetが既存していなければ作成 -> ローリングアップデート戦略にもとづいて、現在のReplicaSetのPod数を減らし新しいReplicaSetのPod数を増やしていく。ロールバックは、戻すリビジョンを指定しそれに対応するReplicaSetのPod数を増やし、現在のReplicaSetのPod数を減らしていく。kubectl rollout pause deployment sample
でローリングアップデートを止めることも可能。
Daemon Setのアップデート戦略
Ondelete
とRolling Update
がある。Ondelete
はDaemosetのtemplateが変更されてもPodの更新は行われない。更新する場合には手動でPodをdeleteしオートヒーリングでPodを新たに作成する際に新しい定義となる。apiVersionがapps/v1
はデフォルトがRolling Update
ですが、それより前のバージョンでのデフォルト値はOndelete
なので注意が必要。
参考:
Default apps/v1beta2 StatefulSet and DaemonSet strategy to RollingUpdate · Issue #49604 · kubernetes/kubernetes · GitHub
Stateful Setの特徴
業務では使用したこと無いワークロードであるところのStateful Setですが他のワークロードとの違いが顕著であるように見受けられます。以下に列挙していきます。
- 作成されるPod名のSuffixは数字のインデックスが付与されたのものになる。
- scaleすると、今あるpodのIndexに+1された名前のPodが作成される。デフォルトでは、Ready状態になってから次のPodを作成し始める。なので増えるときは1つずつ。ただ、podManagementPolicyをPararellにするとdeploymentと同じように複数同時に起動する。
- Rolling UpdateはpodManagementPolicyにかかわらず1つずつ行われる。partitionを設定することで一部だけを更新することが可能
- Podの再起動時にも同じPersistentDiskを使用する
その他
- Podテンプレートのコンテナ実行時のコマンド指定
kubernetes | Dockerfile |
---|---|
command | ENTRYPOINT |
args | CMD |
- Pod全コンテナの/etc/hostsを書き換える機能があり、.spec.hostAliasに指定する
kubectl apply
したときに--record
オプション必須つけることでアノテーションにchange-causeが記録される
Discovery&LBリソース
Service
kind | type | description |
---|---|---|
ClusterIP | ClusterIP | クラスタ内のみから疎通性があるInternal Networkに作り出される仮想IP。spec.ports.portがClusterIPで待ち受けるIPアドレス。spec.ports.targetPortが転送先コンテナのPort番号。手動でIPを固定する場合には、spec.ClusterIPを指定する。 |
ExternalIP | ClusterIP | typeはClusterIP。特定のKubernetesノードで受信したトラフィックをコンテナに転送する。spec.ExternalIPsで受信するノードのIPを列挙する |
NodePort | NordPort | ExternalIPと似ている。ただしこれは、全ノードからトラフィックを受け付けるイメージ。NodePortで利用できるポート範囲は30000~32767 |
LoadBalancer | LoadBalancer | Production環境でクラスタ外からトラフィックを受けるときに使用される。Kubernetesクラスタ外に疎通性のある仮想IPを払い出す。上の外部疎通ServiceはノードがSPOFになるが、これは外部のロードバランサを利用するためにノードの障害に強い。GCPとAWSでは、spec.loadBalancerSourceRangesに接続を許可する送信元IPアドレス範囲を指定できる。 |
Headless Service | ClusterIP | DNSラウンドロビンを使ったエンドポイントの提供。type:ClusterIPかつspec.ClusterIPがNoneのもの。負荷分散に向かない。 |
ExternalName Service | ExternalName | クラスタ内からService名で名前解決するとCNAMEが返ってくる。外部サービスと疎結合にするために使用する。 |
None-Selector Service | * | 自分で指定したメンバに負荷分散することが出来る。EndPointとServiceを同じ名前で作ることで紐付けられる。typeは自由だがメリットを考えるとClusterIP一択。Selectorには何も指定しない |
Ingress
Ingressリソースは一旦作成すると、その後あんまりにも触る機会が無く忘れてしまいがちです。
L7のロードバランシングを行うリソース。L4のServiceと区別するため別のリソースとして扱われている。大別してクラスタ外のロードバランサを利用するものとクラスタ内のIngress用Podをデプロイするものがある。Ingressは事前に作成されたServiceをバックエンドとして転送を行う仕組みになっています。なのでNodePortのリソースをまず用意します。その後に、IngressリソースのserviceNameの項目で結びつけます。L7なので当然パスベースルーティングもマニフェストで指定することが出来ます。https通信を行う場合には別途でSecretを作成する必要性がある。
その他
- 正式なFQDNを指定せずに、Service名だけでも解決できるのは、
/resolve/conf
にsearch [namespace].svc.cluster.local
という文言があるため。
Config & Storageリソース
Secret
1つのSecretの中に複数のKey-Value値。 大体は、マニフェストから作成することが考えられる。base64でエンコードした値をマニフェストに埋め込みますが、その状態で管理してもセキュリティなど無いに等しいため、kubesec(https://github.com/shyiko/kubesec)と暗号鍵のマネージドサービスを使って暗号化した状態でリポジトリに保存しておくのが良きかと思う。
apiVersion: v1 kind: Secret metadata: name: mysecret type: Opaque data: username: YWRtaW4= password: MWYyZDFlMmU2N2Rm
利用方法も複数ある。環境変数として渡す手段とVolumeとしてマウントする手段があります。Volumeとしてマウントした場合には動的なSecretの変更を行うことができます。また、それぞれ特定のKeyを渡す手段(spec.containers[].env[].valueFrom.secretKeyRef
)とすべてのKeyを渡す手段(spec.containers[].envFrom.secretRef
)がある。マニフェストに一覧性を持たせたい場合には前者、マニフェストを冗長にしたくない場合には後者を用いると良さげ。
kind | type | description |
---|---|---|
Generic | Opaque | マニフェストから作成するケースが多そう。DBなどの資格情報はこれで管理する。 |
TLS | kubernetes.io/tls | kubectl create secret tls custom-tls-cert --key /path/to/tls.key --cert /path/to/tls.crt で作成。keyが秘密鍵でcertが証明書ファイル。 |
Dockerレジストリ | kubernetes.io/dockerconfigjson | Registoryサーバと認証情報を引数で指定して作成する。podテンプレートのspec.containers[].imagePullSecrets で名前を指定する。 |
Service Account | kubernetes.io/service-account-token | 自動で作成されるもの。利用者が意識することはほとんどない |
ConfigMap
ConfigMapも殆どの場合でマニフェストから作成されるケースが多そう。Secretと違いBase64にエンコードしない点に注意。数値はダブルクォートで囲む。渡し方はSecretとほぼ変わらず、spec.containers[].env[].valueFrom.secretKeyRef
-> spec.containers[].env[].valueFrom.configMapKeyRef
/ spec.containers[].envFrom.secretRef
-> spec.containers[].envFrom.configMapRef
とフィールドの名前が変わったくらい。
Persistent Volume Claim
ややこしい名前のリソースたちの違い
- Volume
- Persistent Volume
- 実はConfig & StorageリソースではなくClusterリソース
- 予め作成しておく必要性がある
- Dynamic Provisioningを使用しない場合にはラベルを付与すること
- アクセスモードが複数あるが、大体はReadWriteOnceかReadOnlyManyが許可されていて、ReadWriteManyは許可されていない。
- Reclaim PolicyでPersistentVolumeClaimが削除された際のPersistent Volumeの挙動を決められる。
- PersistentVolumeClaim
その他
- SecretとConfigMap以外からPodテンプレートで環境変数を渡すには
- 静的設定
env: - name: TZ value: Asia/Tokyo
- podの情報を参照 。
env[].valueFrom.fieldRef.fieldPath
で指定する。 - コンテナの情報を参照。
env[].valueFrom.resourceFieldRef.containerName
でコンテナの名前、env[].valueFrom.resourceFieldRef.resouce
で取得するフィールドを指定する。 - SubPathマウントで特定のディレクトリをルートとしてマウントすることができる。mountPathがコンテナ側・subPathがボリューム側のディレクトリパスの指定となる。
リソース管理とオートスケーリング
requestsとlimit
requests/limitともに各コンテナに定義する。
この値が適正かどうか判断する際に役に立つのが、Nodeごとにリソース状況。これはkubectl describe node hogehoge
で見ることが可能。
Requestsを大きくしすぎず、RequestsとLimitsの間に顕著な差を作らないことで適正なスケールアウトができるようになる。
- requests
- 使用するリソースの下限を指定するもの。
- この値をもとにNodeにPodがスケジューリングされる。
- Limits
- 使用するリソースの上限を指定するもの。
- この値を超えないようにPodの使用するリソース量がコントロールされる。
- メモリの場合はLimitsを超えるとOOMでコンテナプロセスが殺される。
HPA(水平スケーリング)
30秒に1回の頻度でオートスケーリングすべきかのチェックが走る。必要なレプリカ数の計算は以下で計算される。
マニフェストの中でDeploymentを指定する。
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
スケールアウトの式 (最大で3分に1回)
avg(currentMetricValue) / desiredMetricValue > 1.1
スケールインの式(最大で5分に1回)
avg(currentMetricValue) / desiredMetricValue < 0.9
その他
- LimitRangeでリソースの最小・最大値・デフォルト値を設定可能。新規でPodを作成するときに適用される。
- OOM KillerによってPodを停止される場合には、QoS Class「Best Effort」「Burstable」「Guaranteed」の順で停止される。RequestsとLimitsが指定されていてなおかつ値が同じ場合には「Guaranteed」となり削除されにくくなる。逆にどちらも指定していない場合には、停止される対象になりやすい。
- ResouceQuotaによってNameSpaceごとのリソース量・数に制限をかけることができる。
コンテナのライフサイクル
ヘルスチェック
Amazon ECSでもELBを使用したヘルスチェックがありますが、それよりもだいぶ柔軟です。ヘルスチェック方式は3種類あり、コマンドベースのチェック(exec)とHTTPベースのチェック(httpGet)、TCPベースのチェック(tcpSocket)がある。HTTPベースのチェックではホスト名やHeaderを設定できたりもする。
Amazon ECSにおける「healthCheckGracePeriodSeconds(ヘルスチェックまでの猶予期間)」にあたるのがinitialDelaySecondsとなります。
- Liveness Probe
- 失敗時にはPodを再起動する。メモリリークなどでPodの再起動なしには状態が回復しずらい場合に使用。
- 成功と判断するまでのチェック回数であるSuccessThresholdは必ず1
- Readiness Probe
コンテナライフサイクル
restartPolicyはPodに定義するものでPodが停止した場合にどのような挙動を取るか指定したものです。基本的には、終了コードにかかわらず起動するAlwaysなのではと思うのですが、Jobなどは終了コード0以外で再起動するOnFailureになるのではと思います。Neverは再起動を行わないものです。
Init Containers
初期化処理を行うためのコンテナ。InitContainersの処理が終わらないと、spec.containers
で指定したコンテナが起動しない。GCSからファイルをGETしてくる必要性があるときなどに使用している。この機能がAmazon ECSにも欲しい…。
postStart/preStop
コンテナの起動・終了時に行われる処理。postStartは、コンテナのENTRYPOINTとほぼ同じタイミングで実行されるため、どうしてもコンテナ内部で処理をしなくてはいけない場合以外はinitContainersに処理をしてもらったほうが良さそう。
preStopの処理の流れは、この本を読む前にこの記事(Kubernetes: 詳解 Pods の終了 - Qiita)で知っていましたが、改めて別の図も交えてだとよりわかりやすいなと思う。spec.terminationGracePeriodSeconds
をpreStop処理にかかる時間を考慮した時間にしないと、デフォルトの30秒がたった時点でSIGKILLコマンド、preStop処理の途中だった場合はSIGTERMコマンドが送られ、その2秒後にSIGKILLが送られる。nginxやapacheなどのWEBサーバでGraceful Shatdownを行う場合に、コマンドだと非同期での実行となる場合があるのでsleepコマンドを入れておくことが望ましい。
その他
- 親リソース(ReplicaSet)が削除された場合には子リソースのPodを削除するという処理がはいる。この親子関係は、マニフェストの
metadata.ownerReference
から読み取ることが可能。ex) Deployment削除 -> ReplicaSet削除 -> pod削除 - podを即座に削除したい場合
kubectl delete pod hogehoge --grace-period 0 --force
高度なスケジューリング
Node Affinity
Podが特定のノード上でしか実行できないような条件付ができる。
- requiredDuringSchedulingIgnoredDuringExecution
- 必須条件のポリシー
- この条件を満たさないとスケジューリングされない
- 検証環境でプリエンプティブインスタンスに起動されると困るものは、priemptive:falseのものを指定したりとか
- preferredDuringSchedulingIgnoredDuringExecution
- 優先条件のポリシー
- この条件を満たさない場合でもスケジューリングされる
- 優先度の重み(weight)を設定することができる
Node AntiAffinity
これは、matchExpressionで指定できるオペレータでNotがついているものを使えば実現できるもの。
Inter-Pod Affinity
基本的にはNode Affinityと同じだが、topologyKeyというのを指定する必要がある。topologyKeyはスケジューリング対象の範囲を示す。これはホストやゾーンなどを指定でき、matchExpressionのじょうけんを満たすPodのホストもしくはゾーンに配置するという意味になる。
TaintsとTolerations
Podが条件を提示してNode側が許可する形のスケジューリング。node affinityと違う点は他にもあり、考慮されるのがスケジューリングのときだけではないという点がある。
まとめ
本当にめっちゃめちゃ良い本だったので買いましょう。