概要
Kubernetesのスケジューラは、未スケジュールのPodを監視し、適切なノードを見つけてバインディング情報をAPIサーバーに書き込むコンポーネントです。この記事では、Kubernetes 1.13バージョンのスケジューラが実際のスケジューリング処理を開始する前に行われる初期化処理について、ソースコードレベルで分析します。
Cobraフレームワークとmain関数
Cobraとは
Cobraは、強力で現代的なCLIアプリケーションを作成するためのGoライブラリです。Kubernetes、Docker、Hugoなど、多くの有名なGoプロジェクトで採用されています。Cobraは以下の特徴を持っています:
- サブコマンドの簡単な作成
- フラグの位置に依存しないサポート
- 自動生成されるヘルプとマニュアル
- 高度なカスタマイズが可能
Cobraの使用方法
まず、Cobraをインストールしてプロジェクトを作成してみましょう:
# Cobraのインストール
go get -u github.com/spf13/cobra/cobra
# 新しいプロジェクトの作成
cobra init k8s-scheduler-example
生成されたプロジェクト構造は以下のようになります:
k8s-scheduler-example/
├── cmd/
│ └── root.go
├── LICENSE
└── main.go
main.goの内容は非常にシンプルです:
package main
import "k8s-scheduler-example/cmd"
func main() {
cmd.Execute()
}
次に、サブコマンドを追加してみましょう:
cobra add version
これにより、cmdディレクトリにversion.goが作成されます。このファイルでは、rootCmdにversionCmdが追加されます:
func init() {
rootCmd.AddCommand(versionCmd)
}
さらに、多階層のサブコマンドも作成可能です:
cobra add create -a 'server'
これにより、serverコマンドの下にcreateサブコマンドが追加され、kubectl get podsのような構造が実現できます。
Kubernetesスケジューラのmain関数
Kubernetesスケジューラのmain関数は、cmd/kube-scheduler/scheduler.goにあります。その内容は以下のようになっています:
func main() {
command := app.NewSchedulerCommand()
if err := command.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
ここで呼び出されているNewSchedulerCommand()関数を見てみましょう。この関数は*cobra.Command型のオブジェクトを返します:
// NewSchedulerCommandはデフォルトパラメータで*cobra.Commandオブジェクトを作成します
func NewSchedulerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "kube-scheduler",
Long: `Kubernetesスケジューラは、ポリシーが豊富でトポロジーを認識し、
ワークロードに特化した機能であり、可用性、パフォーマンス、キャパシティに
大きな影響を与えます。スケジューラは個々および集合的なリソース要件、
サービス品質要件、ハードウェア/ソフトウェア/ポリシーの制約、
アフィニティとアンチアフィニティの仕様、データの局所性、
ワークロード間の干渉、締め切りなどを考慮する必要があります。`,
Run: func(cmd *cobra.Command, args []string) {
if err := runCommand(cmd, args, opts); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
},
}
return cmd
}
スケジューラの実行時に呼び出されるrunCommand関数は以下のようになっています:
// runCommandはスケジューラを実行します
func runCommand(cmd *cobra.Command, args []string, opts *options.Options) error {
config, err := opts.Config()
stopCh := make(chan struct{})
// 完了した設定を取得
completedConfig := config.Complete()
return Run(completedConfig, stopCh)
}
最後に、実際のスケジューラの実行を担当するRun関数を見てみましょう:
// Runは指定された設定に基づいてスケジューラを実行します
// エラーが発生した場合またはstopChが閉じられた場合にのみ戻ります
func Run(completedConfig schedulerserverconfig.CompletedConfig, stopCh <-chan struct{}) error {
// スケジューラを作成
sched, err := scheduler.New(completedConfig.Client,
completedConfig.InformerFactory.Core().V1().Nodes(),
stopCh,
scheduler.WithName(completedConfig.ComponentConfig.SchedulerName))
// 再利用可能なrun関数を準備
run := func(ctx context.Context) {
sched.Run()
<-ctx.Done()
}
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
go func() {
select {
case <-stopCh:
cancel()
case <-ctx.Done():
}
}()
// リーダー選択が無効なため、完了するまでインラインで実行
run(ctx)
return fmt.Errorf("リーダー選択なしで終了しました")
}
このRun関数内で呼び出されているsched.Run()が、実際のスケジューリング処理を開始するエントリーポイントです。このメソッドはpkg/scheduler/scheduler.goに定義されています。
最後に、スケジューラの主要な構造体であるSchedulerを見てみましょう:
// Schedulerは新しくスケジュールされていないPodを監視します
// Podが適合するノードを見つけようとし、バインディングをAPIサーバーに書き戻します
type Scheduler struct {
config *factory.Config
}
このScheduler構造体が持つConfigオブジェクトは、スケジューリング処理に必要な様々なコンポーネントを保持しています:
// Configはスケジューラの設定された入力データの実装です
type Config struct {
// SchedulerCacheへの変更はNodeListerとAlgorithmによって
// 観察されることが期待されます
SchedulerCache schedulerinternalcache.Cache
// EcacheはPodのバインドに成功した後、影響を受ける
// キャッシュ項目を楽観的に無効化するために使用されます
Ecache *equivalence.Cache
NodeLister algorithm.NodeLister
Algorithm algorithm.ScheduleAlgorithm
GetBinder func(pod *v1.Pod) Binder
// PodConditionUpdaterはスケジューリングエラーの場合にのみ使用されます
// スケジューリングに成功した場合、PodScheduled条件は/bindハンドラで
// APIサーバーで更新されるため、バインディングとPodConditionの設定はアトミックです
PodConditionUpdater PodConditionUpdater
// PodPreemptorはPodを退去させ、Podアノテーションを更新するために使用されます
PodPreemptor PodPreemptor
// NextPodは次のPodが利用可能になるまでブロックする関数であるべきです
// チャネルを使用しないのは、Podのスケジューリングに時間がかかる可能性があり、
// Podがチャネルにある間に古くなってしまうのを避けたいからです
NextPod func() *v1.Pod
// SchedulingQueueはスケジュールされるPodを保持します
SchedulingQueue internalqueue.SchedulingQueue
}
これらのコンポーネントが連携して、Kubernetesクラスタ内でのPodのスケジューリングを実現しています。次回は、このスケジューラの実際の動作プロセスについて詳しく分析していきます。