Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kubernetes と Toil の削減に立ち向かう 〜 Controller 開発時に考えていること 〜 3-shake SRE Tech Talk #1 / srett1-k8s-amsy810

Kubernetes と Toil の削減に立ち向かう 〜 Controller 開発時に考えていること 〜 3-shake SRE Tech Talk #1 / srett1-k8s-amsy810

SRE のベストプラクティスの 1 つとして、Toilを削減し、安定的に運用していくというものがあります。
Kubernetes では Controller による機能拡張を行うことで運用をモデル化し、こうした Toil を削減することができます。
本セッションでは、実際に SSL 証明書の期限をチェックする例を題材(仮)に、実際に Controller を開発する流れについて紹介します。

https://3-shake.connpass.com/event/208764/

https://youtu.be/J3q5iDsUVHQ

Masaya Aoyama (@amsy810)

April 27, 2021
Tweet

More Decks by Masaya Aoyama (@amsy810)

Other Decks in Programming

Transcript

  1. あるべき理想の状態(Desired State)≒ Declarative へと収束する 何か問題が発⽣した場合でも、Controller により セルフヒーリングされる ※ 厳密には Controller

    も API を⽤いて変更します。 reconcile() { … } 登録 (via API Request) Watch クラスタの状態 コンテナの作成・削除 Controller 登録された時に、ただ起動させるだけではない Kubernetes と Reconciliation Loop
  2. Controller 内では Reconciliation loop(調整ループ)と呼ばれる あるべき状態へと収束させるループ処理 が実⾏されている Kubernetes の内部には様々な Controller と呼ばれるプログラムが動作している

    Observe Diff Act Observe: 現状を確認 Diff: 理想と現実の差分を計算 Act: 差分に対する処理を実施 reconcile() { … } Controller Kubernetes と Reconciliation Loop
  3. ReplicaSet Controller の責務は 「指定されたレプリカ数で Pod を維持し続けること」 Observe Diff Act Observe:

    現状を確認 Diff: 理想と現実の差分を計算 Act: 差分に対する処理を実施 クラスタの状態 理想の状態 reconcile() { … } Controller 例: ReplicaSet Controller の例
  4. 例えば、コンテナ(Pod)を 3 つ起動させる ReplicaSet リソースの場合 Observe Diff Act Observe: 現状を確認

    Diff: 理想と現実の差分を計算 Act: 差分に対する処理を実施 クラスタの状態 理想の状態 reconcile() { … } Controller 例: ReplicaSet Controller の例
  5. たとえば 2 つしかコンテナ(Pod)が起動していない場合… Observe: 理想=3、現状=2 Observe Diff Act Observe: 現状を確認

    Diff: 理想と現実の差分を計算 Act: 差分に対する処理を実施 クラスタの状態 理想の状態 reconcile() { … } Controller 例: ReplicaSet Controller の例
  6. たとえば 2 つしかコンテナ(Pod)が起動していない場合… Diff: 1 つコンテナ(Pod)が⾜りない Observe Diff Act Observe:

    現状を確認 Diff: 理想と現実の差分を計算 Act: 差分に対する処理を実施 クラスタの状態 理想の状態 reconcile() { … } Controller 例: ReplicaSet Controller の例
  7. たとえば 2 つしかコンテナ(Pod)が起動していない場合… Act: 1つ nginx:1.16 のコンテナ(Pod)を作成する Observe Diff Act

    Observe: 現状を確認 Diff: 理想と現実の差分を計算 Act: 差分に対する処理を実施 クラスタの状態 理想の状態 reconcile() { … } Controller 例: ReplicaSet Controller の例
  8. Reconciliation Loop のまとめ ⼈が考えること(運⽤ロジック)をプログラムに落とし込んで⾃動化する = 運⽤を Kubernetes に任せることができる 多くの状態は Kubernetes

    に保存されている ため、API 経由で確認可能 外部システムを観測するものも状態を Kubernetes に保存 運⽤者 ReplicaSet の監視(watch) Pod の制御 (create / delete / patch) via API ReplicaSet の設定を見ておき、 指定されたレプリカ数で Podを維持する
  9. Reconciliation Loop のまとめ ⼈が考えること(運⽤ロジック)をプログラムに落とし込んで⾃動化する = 運⽤を Kubernetes に任せることができる 多くの状態は Kubernetes

    に保存されている ため、API 経由で確認可能 外部システムを観測するものも状態を Kubernetes に保存 reconcile() { … } Controller ReplicaSet の監視(watch) Pod の制御 (create / delete / patch) via API 運⽤のコード化 ReplicaSet の設定を見ておき、 指定されたレプリカ数で Podを維持する
  10. Kubernetes の構成要素 Kubernetes では⾮常に沢⼭の Controller が動いている • ReplicaSet Controller •

    Deployment Controller • Ingress Controller • Node Controller • Scheduler • etc. これらの Controller が⾮同期に動作することで ⼀つの分散システムとして成り⽴っている reconcile() { … } Controller reconcile() { … } Controller reconcile() { … } Controller reconcile() { … } Controller reconcile() { … } Controller 実際の状態 理想の状態 watch
  11. Kubernetes Controller による Toil の削減 Toil を Controller 化(⾃動化)するかは「頻度」 「複雑さ(知⾒の豊富さ)」などを元に決定する

    • 頻度が少ない場合︓ Controller のメンテナンスコストのほうが⾼くなってしまう場合も • 複雑すぎる場合︓ 知⾒が乏しいことによるプログラム化の困難さ 実装コストが⾼すぎる場合も Controller 化するということ = 運⽤を⼀定のところまでモデル化(抽象化)し、汎⽤的に実装していく = 実装上、考えることが多い(考えないで書くだけならスクリプトで⼗分) shell script などよりも Controller のほうが優れている点 • Kubernetes の Resource をもとにした管理系(状態の取得、保存、連携) => 運⽤ロジック外の実装の削減
  12. 運⽤のモデル化 Record When How どんな時にその運⽤業務が 必要となるのか︖ • 障害が起きたとき • アーキテクチャが

    変化したとき • 通知が来たとき いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • コンテナの起動 • データベースの バックアップ取得 • 証明書の期限の確認 Reconcile 関数で何をするか 処理の結果を どこに記録するか • Message Queue に記録 • Slack 等に記録 • モニタリング基盤への記録 Reconcile 関数の処理結果を どう記録するか
  13. How

  14. 運⽤のモデル化 – 証明書の有効期限の確認 Record When How どんな時にその運⽤業務が 必要となるのか︖ • ︖︖︖

    • ︖︖︖ • ︖︖︖ いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数で何をするか 処理の結果を どこに記録するか • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数の処理結果を どう記録するか
  15. 運⽤オペレーションのプログラム化 運⽤オペレーションの挙動 = Reconcile() 関数の処理内容(+CustomResource による設定値) 考慮すべき点 様々なタイミングで複数回実⾏されるため、冪等性が担保されていること 複雑な処理の場合は物事の関⼼事を分離し、個別のオペレーションに分割すること (OSS

    の実装などを参考にすると良い) ※ 今回は紹介しないがその他の有効な⼿段 • client-go と CronJob を利⽤する • フレームワークを利⽤して Build-in Resource を扱う • 追加の Annotations を付与して設定値として利⽤する • etc Observe Diff Act
  16. ReplicaSet の Reconcile() 関数の例(擬似) RS=$(kubectl -n NAMESPACE get rs NAME

    -o json) FPods=$(kubectl get pods -l SELECTOR | wc -l) DIFF=$(( $FPods – RS.SPEC.REPLICAS )) if $FPods -lt 0; then kubectl -n NAMESPACE create pod –f RS.SPEC.TEMPLATE else if $FPods –gt 0; then kubectl -n NAMESPACE delete pods TARGET_POD fi rs, err := rsc.rsLister.ReplicaSets(namespace).Get(name) allPods, err := rsc.podLister.Pods(rs.Namespace).List(labels.Everything()) filteredPods := controller.FilterActivePods(allPods) filteredPods, err = rsc.claimPods(rs, selector, filteredPods) diff := len(filteredPods) - int(*(rs.Spec.Replicas)) if diff < 0 { rsc.podControl.CreatePodsWithControllerRef( rs.Namespace, &rs.Spec.Template, ... ) } else if diff > 0 { rsc.podControl.DeletePod(rs.Namespace, targetPod.Name, rs) } https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/controller/replicaset/replica_set.go より改変して抜粋 Observe Diff Act
  17. 【具体例】Reconcile 関数で何をするか 1. 証明書の取得 … Secret の⼀覧取得 2. 証明書の失効期限の確認(openssl x509

    ...、etc) … crypto/x509 package を利⽤して Decode 3. 証明書の有効期限と現在時刻から残期限を計算 … Decode 結果を元に計算 4. 残期限に対する処理 … 期限が近い場合は通知 1. 残期限が短い場合は通知する 2. 残期限が⻑い場合は期限が近づいたら再度実施 ※ 今回は ReplicaSet と Pod のように親⼦関係にあるリソースは存在しませんが、適宜リソースを管理するものも多くあります。 ※ 将来的には ACME などに置き換えられていくという話は置いておいてください。ACME 向けには Cert-manager という OSS があります。
  18. 【具体例】Reconcile 関数で何をするか 1. 証明書の取得 … 対象となる Secret の⼀覧取得 2. 証明書の失効期限の確認(openssl

    x509 ...、etc) … crypto/x509 package を利⽤して Decode 3. 証明書の有効期限と現在時刻から残期限を計算 … Decode 結果を元に計算 4. 残期限に対する処理 … 期限まで⼀定の時間の場合は通知 1. 残期限が短い場合は通知する 2. 残期限が⻑い場合は期限が近づいたら再度実施 ※ 今回は ReplicaSet と Pod のように親⼦関係にあるリソースは存在しませんが、適宜リソースを管理するものも多くあります。 ※ 将来的には ACME などに置き換えられていくという話は置いておいてください。ACME 向けには Cert-manager という OSS があります。
  19. 【具体例】Reconcile 関数で何をするか 1. 証明書の取得 …… チェック対象とする証明書を指定したい 2. 証明書の失効期限の確認(openssl x509 ...、etc)

    3. 証明書の有効期限と現在時刻から残期限を計算 4. 残期限に対する処理 …… 残期限の閾値を指定したい 1. 残期限が短い場合は通知する 2. 残期限が⻑い場合は期限が近づいたら再度実施 汎⽤性を意識して幾つかのパラメータを定義する CustomResource ちょうど運⽤者が処理する際の、個別の指⽰に当たるもの kind: CertCheck metadata: name: certcheck-sample spec: selector: matchLabels: certcheck-group: sample threshold: 100
  20. 運⽤のモデル化 – 証明書の有効期限の確認 Record When How どんな時にその運⽤業務が 必要となるのか︖ • ︖︖︖

    • ︖︖︖ • ︖︖︖ いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • 証明書の取得 • 失効期限の確認 • 残期限の計算と 記録 および 通知 Reconcile 関数で何をするか 処理の結果を どこに記録するか • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数の処理結果を どう記録するか
  21. 運⽤のモデル化 – 証明書の有効期限の確認 Record When How どんな時にその運⽤業務が 必要となるのか︖ • ︖︖︖

    • ︖︖︖ • ︖︖︖ いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数で何をするか 処理の結果を どこに記録するか • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数の処理結果を どう記録するか
  22. When: いつ Reconcile 関数を呼び出すか • Controller の起動時 • SyncPeriod の実⾏時(default:

    10 hours) • リソースの変更時(状態の変更時) • その他様々な Event Source から情報を受けたとき • 再実⾏時(明⽰的な Requeue / Error 発⽣時) • Reconciliation が適切に動作するために • 状況の変化をもとに適切なタイミング(影響がある可能性があるタイミング)に 冪等性を保って Reconcile が⾏われること
  23. リソース変更の Watch func (r *MyDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return

    ctrl.NewControllerManagedBy(mgr). For(&myappsv1.MyDeployment{}). Owns(&appsv1.ReplicaSet{}). Complete(r) } • Reconciler の初期化時に Reconcile() 関数を呼び出す条件を指定することが可能 • https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/builder For: Controller の Reconcile 対象のリソースを指定 Owns: リソースが作成する⼦リソースを指定 (⼦リソースの状態変化時にも Reconcile される) 事前に OwnerReference を付与しておく必要がある WithEventFilter: Reconcile 対象の Event(CREATE / DELETE / etc)を制御
  24. リソース変更の Watch func (r *MyDeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return

    ctrl.NewControllerManagedBy(mgr). For(&myappsv1.MyDeployment{}). Owns(&appsv1.ReplicaSet{}). Complete(r) } MyDeployment A ReplicaSet A ReplicaSet B ownerReference ownerReference For() Owns() … Reconcile 関数を発⽕させるリソースを指定 For(): MyDeployment A が変更された場合 => MyDeployment A が発⽕ Owns(): ReplicaSet B が変更された場合 => MyDeployment B が発⽕ MyDeployment B
  25. Source と EventHandler Reconcile() 関数を呼び出す条件をさらに詳細にすることが可能(Watch() 関数を利⽤) Ref: https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/source Ref: https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/handler

    Kind︓ 特定の kind のリソースの変更イベント Channel︓ Go channel への通知イベント(外部イベントなど) Informer︓ informer 経由でのイベント EnqueueRequestForObject︓ 受け取ったリソースのイベントを発⽕ EnqueueRequestForOwner︓ 受け取ったリソースの owner のイベントを発⽕ EnqueueRequestsFromMapFunc︓ 受け取ったリソースのイベントを元に マッピングする関数にかけてイベントを発⽕ source handler source handler WorkQueue に追加し、Reconcile の実⾏を待つ
  26. Secret の変更を元にした CertCheck リソースの発⽕ source handler WorkQueue に追加し、 Reconcile の実⾏を待つ

    kind: CertCheck metadata: name: certcheck-sample spec: selector: matchLabels: app: myapp kind: Secret metadata: name: mycert labels: app: myapp spec: {} kind: CertCheck metadata: name: certcheck-B spec: selector: matchLabels: app: myapp Secret リソースの 変更イベントの検知 (source.Kind) 該当の Secret リソースに関係のある CertCheck にマッピング (handler.EnqueueRequestFromMapFunc)
  27. ⼀定時間後の Reconcile の再実⾏ ⼀定時間後に再度 Reconcile() 関数を実⾏する場合は、返り値に設定を⾏う • e.g. アラート発報の期限まで Requeue

    • ※ 実際には sync period の 10h で reconcile が実⾏されるため、⻑期間の Requeue は効果なし 時間がかかる処理などの場合は、Requeue して Reconcile() 関数内での待機や複雑化を避ける
  28. 【具体例】When: いつ Reconcile 関数を呼び出すか 1. アラート発砲までの期限のしきい値が変更された場合 • CertCheck リソースの変更を検知したい︓For() で検知可能

    2. 証明書が更新された場合 • Secret は CertCheck の⼦リソースではないため Own() では不可 • Watches() を利⽤して変更された Secret を元に Reconcile する CertCheck に mapping する • source.Kind{} + handler.EnqueueRequestsFromMapFunc{} 3. アラート発報の期限になった場合 • アラート発報の期限まで Requeue
  29. 運⽤のモデル化 – 証明書の有効期限の確認 Record When How どんな時にその運⽤業務が 必要となるのか︖ • 閾値が変更されたとき

    (CertCheck resource) • 証明書が変更されたとき (Secret resource) • 失効期限が近づいたとき いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数で何をするか 処理の結果を どこに記録するか • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数の処理結果を どう記録するか
  30. 運⽤のモデル化 – 証明書の有効期限の確認 Record When How どんな時にその運⽤業務が 必要となるのか︖ • ︖︖︖

    • ︖︖︖ • ︖︖︖ いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数で何をするか 処理の結果を どこに記録するか • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数の処理結果を どう記録するか
  31. Record: 処理結果の何をどう記録するか (⼀般的に何を記録するべきかに関しては今回は割愛。) 1. 別リソースの変更 • e.g. ReplicaSet などでは Pod

    の作成・削除⾃体が処理結果となる 2. Reconcile() から直接通知 3. Custom Resource の Status フィールドに記録 4. Build-in Resource / Custom Resource の Annotations に記録 5. Event リソースの通知 6. Custom Metrics として出⼒
  32. 2. Reconcile() 関数から直接通知 Custom Resource に notification ⽤のフィールドを⽤意し、その情報を⽤いて直接通知を⾏う 実際には Token

    は Secret として持ち、Custom Resource から Object Reference などで参照する https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/apis/core/types.go#L4606-L4649 通知や待ち時間が多い場合は⾮同期化なども検討 func (r *CertCheckReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { ... api := slack.New(ins.Spec.Notification.Slack.Token) if remaining < 0 { _, _, err := api.PostMessage( ins.Spec.Notification.Slack.Channel, slack.MsgOptionText("Cert is expired", false) ) } ... } kind: CertCheck metadata: name: certcheck-sample spec: threshold: 100 selector: {} notification: slack: channel: my-chan token: XXXXXXXX
  33. 3. Custom Resource の status フィールドに記録 ⼀般的なリソースは、apiVersion / kind /

    metadata / spec / status の 5 つのフィールドから構成 status フィールドを利⽤して、そのリソースに関連する状態を管理することができる kind: CertCheck metadata: name: certcheck-sample spec: {} status: targetCertsCount: 3 certificates: - name: mycert notBefore: 2030-01-01… notAfter: 2030-01-01… active: true - ... CertCheck が対象としている 証明書の数 CertCheck が対象としている 証明書の状況 他にも前回チェックしたタイミングなど 様々な情報を保持可能
  34. 3. Custom Resource の status フィールドに記録 • .status はサブリソースを明⽰的に定義する •

    .spec と同じように構造を定義 • 「// +kubebuilder:subresource:status」を付与 kind: CertCheck metadata: name: certcheck-sample spec: {} status: targetCertsCount: 3 certificates: - name: mycert notBefore: 2030-01-01… notAfter: 2030-01-01… active: true - ...
  35. 3. Custom Resource の status フィールドに記録 • 更新時は r.Status().Update(ctx, obj)

    関数を使って更新する • .spec 以下の更新は無視される .status 専⽤の関数 kind: CertCheck metadata: name: certcheck-sample spec: {} status: targetCertsCount: 3 certificates: - name: mycert notBefore: 2030-01-01… notAfter: 2030-01-01… active: true - ...
  36. 4. Build-in Resource / Custom Resource の Annotation に記録 各リソースの

    Annotations に情報を記録する その Controller が管理する Custom Resource の関連リソースに対しての情報の付与も容易 kind: CertCheck metadata: name: certcheck-sample annotations: certcheck.amsy.dev/certsCount: 3 spec: {} status: {} kind: Secret metadata: name: mycert annotations: certcheck.amsy.dev/active: true certcheck.amsy.dev/expiredAt: 2030-01-01… spec: {} kind: Secret metadata: {} spec: {} kind: Secret metadata: {} spec: {}
  37. 5. Kubernetes の Event リソース Event リソースを発⾏して記録 • Event リソースは

    1 つの Object に紐付け Secret or CertCheck リソース • Event リソースはデフォルトで 1 時間で消失する (kube-apiserver の --event-ttl で延⻑可) • Event Exporter などと組み合わせて通知可能 • 「kubectl get / describe」コマンドで確認可能 func (r *CertCheckReconciler) Reconcile(…) (…) { ... if remaining < 0 { r.Recorder.Eventf( &sec, v1.EventTypeWarning, "Expired", ”Cert %s is expired ", sec.Name ) } ... } ※ 事前に mgr から Recoder を取得しておく
  38. 6. Custom Metrics として記録 Counter、Guage、Summary、Histogram などの metrics type をサポート デバイス名などでメトリクスを分類する⽤途には

    XXVec の metrics type も利⽤可能 Ref: https://book.kubebuilder.io/reference/metrics.html 事前にグローバル変数化と登録 Reconcile 関数内で値の書き換え
  39. 【具体例】Record: 処理結果の何をどう記録するか チェック対象の SSL 証明書の⼀覧は︖ • Custom Resource の status

    フィールドに⼀覧を表⽰ • Secret リソースの Annotations に記録 有効期限と残期限などの状態は︖ • Custom Resource の status フィールドに⼀覧を表⽰ • Secret リソースの Annotations に記録 • Prometheus Metrics として公開 猶予期限が超過した証明書の状態は︖ • Event リソースで Warning を出⼒ • metrics として出⼒(残⽇数、超過した証明書の数、etc) • Reconcile() 関数内で特定の Slack などに通知
  40. 運⽤のモデル化 – 証明書の有効期限の確認 Record When How どんな時にその運⽤業務が 必要となるのか︖ • ︖︖︖

    • ︖︖︖ • ︖︖︖ いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ • ︖︖︖ • ︖︖︖ • ︖︖︖ Reconcile 関数で何をするか 処理の結果を どこに記録するか • CR の status field に チェック対象の⼀覧を表⽰ • Secret の Annotation に 期限が分かりやすいように 記録 • Event resource で通知 Reconcile 関数の処理結果を どう記録するか
  41. Conclusion 今回は説明がしやすいように、証明書の期限を確認する Controller を例にパターンを紹介しました。 実務では Toil の種別を元に Controller 化するかを決め、適切なモデル化を⾏って実装してください。 なお、今回の例は、外形監視でわかる証明書の有効期限を受けて処理をする形でも代替可能ですが、

    今回の例では Kubernetes 上で有効期限の情報があることで、さらなる拡張や連携が可能にもなります Record When How どんな時にその運⽤業務が 必要となるのか︖ いつ Reconcile 関数 を 呼び出すか どのような処理が 必要となるのか︖ Reconcile 関数で何をするか 処理の結果を どこに記録するか Reconcile 関数の処理結果を どう記録するか