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

Kubernetesのソースコードリーディング入門

 Kubernetesのソースコードリーディング入門

「APC勉強会#34 Kubernetesのソースコードリーディング入門」の発表資料です。

ShinMatsuzaki

August 23, 2019
Tweet

Other Decks in Programming

Transcript

  1. コードが読めると.... • 仕様に強くなれる • トラブルシュート力が向上 • 試験設計に強くなれる • Deepな技術解説記事が書けるかも? •

    Operator*1 の開発者になれるかも? *1 CustomControllerを使って運用を自動化しよう という手法、開発されるCustomControllerのこと https://coreos.com/blog/introducing-operators.html
  2. kubernetes / apimachinery • APIオブジェクトのSchemaを定義するレポジトリ • k8s v1.8より、APIオブジェクトは kubernetes/apimachinery にて定義されるデータ構造とコード

    の自動生成機能(code generation)を使って、実装される仕様に変わった。[1] kubernetes / api • Kubernetes / apimachinery にて定義のSchemaを元に実際のAPIオブジェクトを定義 kubernetes / client-go • kube-apisererと通信を行うclient生成用の各種関数(i.e. ) + 構造体(i.e. struct Clientset) • イベント通知エンジンである informer framework を構成する関数, キュー, キャッシュ機構etc [1] Kubernetes Deep Dive: Code Generation for CustomResources https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/ 主要なレポジトリ(2)
  3. Specification = DesiredStateを定義 CurrentState. Reconciliation Loopの評価に使用される Kind(str型)とAIPVersion(str型)をメンバに持つ構造体 Interface. kind情報生成用の関数 &

    APIオブジェクトの複製用の関数の実装を強制する https://github.com/kubernetes/api/blob/0772a1bdf941dcf775fef01dd13d667ea3031902/core/v1/types.go#L3488 お勧め(3): 構造体を読む Pod metav1.TypeMeta metav1.ObjectMeta PodSpec PodStatus
  4. Piyo Hoge M ( int型 ) Hoge N ( int型

    ) 参考: go言語の構造体の埋め込みパターン • 構造体メンバに別の構造体を追加できる ※ 構造体へのポインタ型としても埋め込みが可能 • 以下の場合、Piyo.Hoge.N でもアクセスできるし、埋め込まれた構造名を省略し、 Piyo.N でもアクセスできる ↑構造体名 ↑構造体メンバ
  5. struct Command Use string Long string 参考:cobra.Command.Execute() -> cobra.Command.Run() Run

    func(cmd *Command, args []string) Run (c *Command) Execute() error Run (c *Command) ExecuteC() (cmd *Command, err error) Run (c *Command) execute(a []string) (err error) (1)call ポインタ レシーバ (2)call (3)call
  6. 実際にkube-schedulerのコードを読んでみる https://github.com/kubernetes/kubernetes/blob/2d3c76f9091b6bec110a5e63777c332469e0cba2/cmd/kube-scheduler/app/server.go#L109 *completedConfig struct CompletedConfig //public *Config struct completedConfig //private

    // CustomControllerが必要とする諸々の // コンテキスト情報 - ComponentConfig - LoopbackClientConfig - InsecureServing - InsecureMetricsServing - Authentication - Authorization - SecureServing - Client - InformerFactory - PodInformer - EventClient - Recorder - Broadcaster - LeaderElection struct Config 埋込 埋込
  7. Scheduler構造体を初期化 sched.config.Argorithm.Scheduleすると、実際には何が実行されるのか? config *Config struct Scheduler struct Config struct genericScheduler

    埋込 埋込 Algorithm: algo https://github.com/kubernetes/kubernetes/blob/2d3c76f9091b6bec110a5e63777c332469e0cba2/cmd/kube-scheduler/app/server.go#L159 func (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (result ScheduleResult, err error) ポインタ レシーバ Scheduler構造体 生成時に、2種の 構造体が埋め込 まれている
  8. findNodesThatFit() : perdicate判定 Predicate Rule #1 Predicate Rule #2 Predicate

    Rule #3 ✓ ✓ ✓ • findNodesThatFit() 及び podFitsOnNode() により 各ノードに対しpredicateルールに基づいたチェックが実行される。 • Nodeが全てのpredicateに適合する場合のみ、 ノードが filtered に追加され、次のprioritizeステージに進む filtered failedPredicateMap 全てのルールに適合? Yes No
  9. PrioritizeNodes : prioritize判定 • predicates判定を全てクリアしたノードは、次にprioritizeルールにより点数評価をされる ルール名 条件の合否 重み スコア ルール#1

    0 (不合格) 1 0 ルール#2 1 (合格) 2 2 ルール#3 1 (合格) 1 1 ルール#4 0 (不合格) 0 0 ルール#5 1 (合格) 3 3 合計点 6 ← のようなイメージで ノード毎の総合得点が採点される
  10. /pkg /cmd 以上の処理の流れをまとめると.. main() *エントリーポイント ↓ cobra.Command.Execute() ↓ cobra.Command.Run() ↓

    runCommand() ↓ Run ↓ func (sched *Scheduler) Run() ↓ (wait.Until) ↓ func (sched *Scheduler) scheduleOne() ※メインルーチン ↓ func (sched *Scheduler) schedule() ↓ func (g *genericScheduler) Schedule() ※実際のPredicate/Prioritize処理
  11. 構造体関係の情報をまとめると.. struct Scheduler struct Config Algorithm: algo struct genericScheduler func

    (g *genericScheduler) Schedule(pod *v1.Pod, nodeLister algorithm.NodeLister) (result ScheduleResult, err error) ポインタ レシーバ func (sched *Scheduler) Run() func (sched *Scheduler) scheduleOne() func (sched *Scheduler) schedule() ポインタ レシーバ call call call ・実際のスケジューリングを行うメソッド
  12. よく出てくるパターン struct Config Hoge M ( int型 ) struct Hoge

    N ( int型 ) ① Daemonを生成するために必要な変数や関数を Config として構造体にまとめる ② それをDaemon生成用の関数(例:newDaemon())に引数として渡し、 Daemonを表すインスタンス(構造体)を初期化/生成 struct Config Hoge M ( int型 ) Hoge N ( int型 ) struct Daemon Config ③ Daemonを表すインスタンス(構造体)に付属のRun()メソッドを実行し、Linux上でProcessを起動 例: Daemon.Run()
  13. 関数を読む際 関数を読むときは、caller側とcallee側両方確認して、頭の中でマージする 呼び出し側(caller) 呼ばれている側(callee) sched に何が代入されるのかは、 callee側にて、戻り値を確認しな いとわからない 引数に渡される実際の値は、 呼び出し元を見ないとわからない

    (左) https://github.com/kubernetes/kubernetes/blob/2d3c76f9091b6bec110a5e63777c332469e0cba2/cmd/kube-scheduler/app/server.go#L159 (右) https://github.com/kubernetes/kubernetes/blob/2d3c76f9091b6bec110a5e63777c332469e0cba2/pkg/scheduler/scheduler.go#L121
  14. 複数の構造をイメージしながら読む 処理の流れ 構造体とメソッド main() ↓ cobra.Command.Execute() ↓ cobra.Command.Run() ↓ runCommand()

    ↓ Run ↓ func (sched *Scheduler) Run() ↓ (wait.Until) ↓ func (sched *Scheduler) scheduleOne() ↓ func (sched *Scheduler) schedule() ↓ func (g *genericScheduler) Schedule() struct Scheduler Algorithm: algo func (sched *Scheduler) Run() func (sched *Scheduler) scheduleOne() func (sched *Scheduler) schedule() ポインタ レシーバ call call struct Config
  15. • Clienset構造体は各APIオブジェクトに対応の構造体をメンバに持つ。 構造体に付属のメソッドを実行することで、RESTClientをメンバに持つ構造体が得られる (1) “apiVersion: v1” 用の構造体 (2) “kind: Nodes”

    用のメソッド Clientset構造体 (3) メソッドを実行すると、restClientをメンバ に持つnodes構造体が生成される [1] [1] c.restClientの中身はRESTClient構造体。 NewForConfigによりClientset生成時に、 RESTClientFor()-> NewRESTClient() により生成される https://github.com/kubernetes/client-go/blob/a240a565b073d748b78933afa3ea43fc922367b3/rest/client.go#L119-L127
  16. $HOME 配下の kubecofngを 元にconfigを生成 1. ~/.kube/config を元に Config構造体 を生成 NewForConfig()

    でclientset構造体 を生成 CoreV1().Nodes(). List()でAPIオブジェ クトを取得 -> GetName() メソッドで名前を 取得 参考: BuildConfigFromFlags() https://github.com/kubernetes/client-go/blob/a240a565b073d748b78933afa3ea43fc922367b3/tools/clientcmd/client_config.go#L539 ★
  17. $HOME 配下の kubecofngを 元にconfigを生成 2. Config構造体を元にClientset構造体を作る NewForConfig() でclientset構造体 を生成 CoreV1().Nodes().

    List()でAPIオブジェ クトを取得 -> GetName() メソッドで名前を 取得 参考: NewForConfig() https://github.com/kubernetes/client-go/blob/a240a565b073d748b78933afa3ea43fc922367b3/kubernetes/clientset.go#L346 ★
  18. $HOME 配下の kubecofngを 元にconfigを生成 3. Clientのメソッド経由でAPI情報を取得 NewForConfig() でclientsetを 生成 CoreV1().Nodes().

    List()でAPIオブジェ クトを取得 -> GetName() メソッドで名前を 取得 CoreV1上のKind: Nodeオブジェクトを全て取得 取得したAPIオブジェクトに具備の メソッドを実行し名前を取得
  19. informer framework • k8sの各controllerは、APIオブジェクトに対する変更を watch と呼ばれる機能で監視し、 イベントが検知されると、イベントハンドラに従い処理を行う • イベント情報を流通させるパイプラインは、informer framework

    と呼ばれており、 client-goライブラリにより実装されている 画像の出展 https://github.com/kubernetes/sample-controller/blob/master/docs/controller-client-go.md APIオブジェクト 変更イベントの データフロー celint-goにて、 実装が提供されている 部分 個別の実装が必要な 部分 ※workqueue除く
  20. - string - Deltas - string - Deltas - string

    - Deltas - string - Deltas enqueue enqueue type Deltas []Delta Type Object DeltaFIFO.items Delta FIFO Queueの上は何が流れている? https://github.com/kubernetes/client-go/blob/master/tools/cache/delta_fifo.go Delta.Typeは、 DeltaType型(=string) 型であり、addition, deletion, etc が入る • k8sのAPIオブジェクトは、Delta FIFO と呼ばれるQueueを通じて流通される(※注1) • Delta = APIオブジェクトの変更イベントを指す • キュー内部にDeltas というスライスが用意されており、1つのキューで複数のイベント情報を流通できる Type Object Type Object 注1:実際は、struct DeltaFIFO のメンバのitemsというmapに格納される interface{} 型、 実際のAPIオブジェク トの情報が格納される
  21. ⑥Informer側に、任意のCallBack 関数をイベントハンドラして登録す ると、イベントがDeltaFifoQueue からdequeueされる度に発火をさせ ることができる。 変更 イベント ※イベントハンドラのイメージ ⑦これを利用して、APIオブジェク トからkeyを抽出し、「APIの変更

    イベント」としてControllerが持つ 別のQueue(WorkQueue)への enqueueを行う。 ↑ APIオブジェクトから、 <namespace>/<object name> 形式のkeyを生成する ↑ workqueueにpush <namespace> / <object name> 形式
  22. Workqueue example(pod event notifier) 以下に少し改造したコードを載せています https://gist.github.com/shinmatsuzaki/4b2b10fefb1f9fa18e18be109ee32784 • client-goのレポジトリにて提供されているリファレンス実装 https://github.com/kubernetes/client-go/blob/master/examples/workqueue/main.go •

    CRDは使わず、単にpodの何らかのevent(Sync/Add/Update)が発生したことのみを通知 • sig-api-machinery にて策定のcontrollerのデザインのサンプル実装でもある https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/controllers.md • 1 File, 約200行のみで動く! ⇒ 学習に最適
  23. main() の処理の流れ(2) • indexerは、CacheStoreとkey funcを持つ cache構造体 として生成される https://github.com/kubernetes/client-go/blob/a240a565b073d748b78933afa3ea43fc922367b3///tools/cache/store.go#L239-L244 • informerは、reflectorをメンバに持つ、controller構造体として生成される。

    この際に、DeltaFIFOも併せて生成される https://github.com/kubernetes/client-go/blob/a240a565b073d748b78933afa3ea43fc922367b3///tools/cache/controller.go#L75-L126 https://github.com/kubernetes/client-go/blob/a240a565b073d748b78933afa3ea43fc922367b3///tools/cache/controller.go#L345 informer indexer DeltaFIFO
  24. Workqueue example(pod event notifier) 以下に少し改造したコードを載せています https://gist.github.com/shinmatsuzaki/4b2b10fefb1f9fa18e18be109ee32784 • client-goのレポジトリにて提供されているリファレンス実装 https://github.com/kubernetes/client-go/blob/master/examples/workqueue/main.go •

    CRDは使わず、単にpodのevent(Sync/Add/Update)のみを通知 • sig-api-machinery にて策定のcontrollerのデザインのサンプル実装でもある https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/controllers.md • 1 File, 約200行のみで動く! ⇒ 学習に最適
  25. 参考:k8sの標準のControllerのArchitecture 出展:https://www.slideshare.net/harryzhang735/kubernetes-beyond-a-black-box-part-1 P28 Reflector ThreadSafeStore EventHandler 各Controllerが 持つ独自の WorkQueue shared

    infromerは、 各APIリソースの変更を検知するた めのkube-controller-managerによ り起動される共用Infomer, 複数のControllerで別個にinformer を持つことが無駄のだめ、共用I/F として提供されている
  26. kube-schedulerも同様の仕組みを採用している pop() event handler informer Delta FIFO Scheduling Queue *

    1 スケジューリング処理 • informerでpodをwatch ⇒ add eventが発生の場合のみ、Pod Queueにenqueueし、schedulingを実行 pop() Shedulerのメインルーチン =無限ループ *1 pod priorityが有効な場合は、 PriorityQueue
  27. 参考書籍 • Operator Patternを主題にした本 • Chapter 3 Basics of clien-go

    にて、後半で解説の Client-go, Clientset, Informer等の解説がされています。