Kubernetes スケジューラの実装原則

Kubernetes スケジューラの実装原則

Kube-scheduler は、Kubernetes のコア コンポーネントの 1 つです。主にクラスター リソース全体のスケジューリング機能を担当します。特定のスケジューリング アルゴリズムと戦略に従って、最適な作業ノードにポッドをスケジュールし、クラスター リソースをより合理的かつ完全に活用します。これは、Kubernetes を使用することを選択した非常に重要な理由でもあります。新しい技術が企業のコスト削減や効率性の向上に役立たなければ、それを推進するのは難しいと思います。

スケジュール作成プロセス

デフォルトでは、kube-scheduler によって提供されるデフォルトのスケジューラは、ほとんどの要件を満たすことができます。これまでに示した例では、基本的にデフォルトの戦略を使用しており、これにより、実行に十分なリソースを持つノードにポッドを割り当てることができるようになります。ただし、実際のオンライン プロジェクトでは、Kubernetes よりも独自のアプリケーションをよく理解している場合があります。たとえば、ポッドが特定のノード上でのみ実行できるようにしたり、これらのノードが特定の種類のアプリケーションを実行するためにのみ使用できるようにしたりすることが考えられます。これには、スケジューラが制御可能であることが必要です。

kube-scheduler の主な機能は、特定のスケジューリング アルゴリズムとスケジューリング ポリシーに従って、適切なノードにポッドをスケジュールすることです。独立したバイナリプログラムです。起動後、API サーバーをリッスンし続け、空の PodSpec.NodeName を持つ Pod を取得し、各 Pod のバインディングを作成します。

kube-scheduler の構造

このプロセスは単純に思えますが、実際の運用環境では、考慮すべき問題が数多くあります。

  • すべてのノードのスケジュールの公平性をどのように確保するのでしょうか?すべてのノードリソース構成が必ずしも同じであるとは限らないことを知っておく必要があります。
  • 各ノードにリソースが割り当てられていることを確認するにはどうすればよいですか?
  • クラスター リソースを効率的に使用するにはどうすればよいでしょうか?
  • クラスター リソースを最大限に活用するにはどうすればよいですか?
  • Pod スケジューリングのパフォーマンスと効率を確保するにはどうすればよいですか?
  • ユーザーは実際のニーズに応じて独自のスケジュール戦略をカスタマイズできますか?

Kubernetes スケジューラは、実際の環境におけるさまざまな複雑な状況を考慮して、プラグインの形で実装されており、ユーザーによるカスタマイズや二次開発が容易になります。スケジューラをカスタマイズし、プラグインの形で Kubernetes と統合できます。

kubernetes スケジューラのソース コードは、kubernetes/pkg/scheduler にあります。 Scheduler によって作成され実行されるコア プログラムは、pkg/scheduler/scheduler.go にあります。 kube-scheduler のエントリ プログラムを表示する場合、対応するコードは cmd/kube-scheduler/scheduler.go にあります。

スケジュールは主に以下の部分に分かれています。

  • 1 つ目は、条件を満たさないノードを除外する事前選択プロセスです。このプロセスは述語(フィルタリング)と呼ばれます
  • 次に最適化プロセスが行われます。これは、通過したノードを優先順位(スコアリング)に従って並べ替えるものです。
  • 最後に、優先度が最も高いノードが選択されます。途中のステップでエラーが発生した場合は、エラーが直接返されます。

述語ステージでは、まずすべてのノードを走査し、条件を満たさないノードを除外します。これは必須のルールです。このステージで出力された要件を満たすすべてのノードが記録され、第 2 ステージの入力として使用されます。すべてのノードが条件を満たさない場合、いずれかのノードが条件を満たすまでポッドは保留状態のままになります。この期間中、スケジューラは再試行を続けます。

したがって、アプリケーションをデプロイするときに、Pod が常に Pending 状態になっている場合は、スケジュール条件を満たすノードが存在しないことを意味します。この時点で、ノード リソースが使用可能かどうかを確認できます。

優先順位の段階では、ノードを再度スクリーニングします。複数のノードが条件を満たす場合、システムはノードを優先順位(優先度)に従って並べ替え、最終的に最も優先度の高いノードを選択して Pod アプリケーションをデプロイします。

以下はスケジュール プロセスの簡単な図です。

kube-scheduler フィルター

より詳細なプロセスは次のとおりです。

  • まず、クライアントはAPIサーバーのREST APIまたはkubectlツールを通じてPodリソースを作成します。
  • ユーザリクエストを受信すると、APIサーバは関連データをetcdデータベースに保存します。
  • スケジューラは API サーバーを監視して、スケジュール (バインド) されていない Pod のリストを表示し、それらを反復処理して各 Pod にノードを割り当てようとします。この割り当てプロセスは、上で説明した 2 つの段階です。
  • 事前選択段階 (述語)、ノードのフィルタリング、スケジューラは一連のルールを使用して、要件を満たさないノードをフィルタリングします。たとえば、ポッドがリソース要求を設定すると、ポッドに必要なリソースよりも利用可能なリソースが少ないホストは明らかに除外されます。
  • 優先度: ノードの優先度をスコア付けします。前のステージで除外されたノード リストのスコアリング。スケジューラは、デプロイメントによって制御される複数の Pod レプリカを可能な限り異なるホストに分散したり、負荷が最も低いホストを使用したりといった、いくつかの全体的な最適化戦略を考慮します。
  • 上記の段階でフィルタリングした後、スコアが最も高いノードが Pod にバインドするために選択され、その結果が etcd に保存されます。最後に選択されたノードに対応する kubelet は、Pod を作成するための関連操作を実行します (もちろん、監視 APIServer によっても検出されます)。

現在、スケジューラはプラグインの形式でスケジューリング フレームワークを実装しています。デフォルトの組み込みスケジュール プラグインは次のコードに示されています。

 // pkg/scheduler/framework/plugins/registry.go

// NewInTreeRegistry はすべての内部プラグインを含むレジストリを構築します。
// 外部プラグインは、WithFrameworkOutOfTreeRegistry オプションを介して追加のプラグインを登録できます。
func NewInTreeRegistry ()ランタイムレジストリ{
fts : = plfeature です特徴{
EnableDynamicResourceAllocation :機能デフォルトのフィーチャゲート有効(機能. DynamicResourceAllocation )、
EnableReadWriteOncePod :機能デフォルトのフィーチャゲート有効機能.ReadWriteOncePod)
EnableVolumeCapacityPriority :機能デフォルトのフィーチャゲート有効(機能. VolumeCapacityPriority )、
EnableMinDomainsInPodTopologySpread :機能デフォルトのフィーチャゲート有効( features.MinDomainsInPodTopologySpread )
EnableNodeInclusionPolicyInPodTopologySpread :機能デフォルトフィーチャゲート有効( features.NodeInclusionPolicyInPodTopologySpread )、
EnableMatchLabelKeysInPodTopologySpread :機能デフォルトのフィーチャゲート有効(機能. MatchLabelKeysInPodTopologySpread )、
EnablePodSchedulingReadiness :機能デフォルトのフィーチャゲート有効( features.PodSchedulingReadiness )、
}

レジストリ: =ランタイムレジストリ{
動的リソース名前:ランタイムFactoryAdapter ( ftsdynamicresources新規)、
セレクタスプレッド名前: selectorspread新しい
イメージローカリティ名前: imagelocality新しい
汚染の許容名前: tainttoleration新しい
ノード名名前:ノード名新しい
ノードポート名前: nodeports新しい
ノードアフィニティ名前: nodeaffinity新しい
ポッドトポロジスプレッド名前:ランタイムFactoryAdapter ( ftspodtopologyspread新規)、
ノードはスケジュールできません名前: nodeunschedulable新しい
ノードリソース名前:ランタイムFactoryAdapter ( ftsnoderesources . NewFit )、
ノードリソースBalancedAllocationName :ランタイムFactoryAdapter ( ftsnoderesources . NewBalancedAllocation )、
ボリュームバインディング名前:ランタイムFactoryAdapter ( ftsvolumebinding新規)、
音量制限名前:ランタイムFactoryAdapter ( ftsvolumerestrictions新規)、
ボリュームゾーン名前: volumezone新しい
ノードボリューム制限CSIName :ランタイムFactoryAdapter ( ftsnodevolumelimitsNewCSI )、
ノードボリューム制限EBSName :ランタイムFactoryAdapter ( ftsnodevolumelimitsNewEBS )、
ノードボリューム制限GCEPDName :ランタイムFactoryAdapter ( ftsnodevolumelimitsNewGCEPD )、
ノードボリューム制限AzureDiskName :ランタイムFactoryAdapter ( ftsnodevolumelimitsNewAzureDisk )、
ノードボリューム制限CinderName :ランタイムFactoryAdapter ( ftsnodevolumelimitsNewCinder )、
インターポダフィニティ名前: interpodaffinity新しい
キューソート名前: queuesort新しい
デフォルトバインダー名前: defaultbinder新しい
デフォルトのプリエンプション名前:ランタイムFactoryAdapter ( ftsdefaultpreemption新規)、
スケジューリングゲート名前:ランタイムFactoryAdapter ( ftsschedulinggates新規)、
}

返品登録
}

上記から、スケジューラの一連のアルゴリズムは、スケジューリングのさまざまな段階でさまざまなプラグインによって完了することがわかります。まず、スケジュールのフレームワークを理解しましょう。

スケジューリングフレームワーク

スケジューリング フレームワークは、一連の拡張ポイントを定義します。ユーザーは、拡張ポイントによって定義されたインターフェースを実装して、独自のスケジューリング ロジック (拡張機能と呼びます) を定義し、その拡張機能を拡張ポイントに登録できます。スケジューリング フレームワークは、スケジューリング ワークフローの実行時に対応する拡張ポイントに遭遇すると、ユーザーが登録した拡張機能を呼び出します。スケジューリング フレームワークは、特定の目的のために拡張ポイントを予約します。拡張ポイントの一部の拡張機能はスケジューラの意思決定方法を変更できる一方、拡張ポイントの一部の拡張機能は通知を送信するだけです。

Pod がスケジュールされるたびに、スケジュール プロセスとバインディング プロセスという 2 つのプロセスに従って実行されることがわかります。

スケジューリング プロセスは Pod に適したノードを選択し、バインディング プロセスはスケジューリング プロセスの決定をクラスターに適用します (つまり、選択されたノードで Pod を実行します)。スケジューリング プロセスとバインディング プロセスの組み合わせは、スケジューリング コンテキストと呼ばれます。スケジューリング プロセスは​同步​実行され (ある時点でスケジュールされる Pod は 1 つだけ)、バインディング プロセスは非同期的に実行できる (同じ時点で複数の Pod を同時にバインドできる) ことに注意してください。

次のような状況が発生すると、スケジュールおよびバインディング プロセスは途中で終了します。

  • スケジューラは、現在このポッドに代替ノードがないと考えています
  • 内部エラー

この時点で、Pod はスケジュール キューに戻され、次の再試行を待機します。

拡張ポイント

次の図は、スケジューリング フレームワーク内のスケジューリング コンテキストと拡張ポイントを示しています。拡張機能では複数の拡張ポイントを登録できるため、より複雑なステートフル タスクを実行できます。

スケジュールフレームワークの拡張

PreEnqueue この拡張機能は、Pod が内部アクティブ キューに追加される前に呼び出され、Pod がスケジュールの準備完了としてマークされます。すべての PreEnqueue プラグインが Success を返す場合にのみ、Pod はアクティブ キューに追加されます。それ以外の場合は、内部のスケジュール不可能な Pod リストに追加され、スケジュール不可能にはなりません。 (Pod の API に .spec.schedulingGates フィールドを追加して、Pod がスケジュールの準備ができているかどうかをマークできます。Pod がスケジュールの準備ができたら、サービス プロバイダーはこのフィールドを変更してスケジューラに通知できます。)

  1. QueueSort 拡張機能は、スケジュールする Pod のキューをソートして、最初にスケジュールする Pod を決定するために使用されます。 QueueSort 拡張機能では、基本的に、2 つの Pod のうちどちらがスケジュールの優先度が高いかを比較するメソッド Less(*QueuedPodInfo, *QueuedPodInfo) を実装するだけで済みます。同時に有効にできる QueueSort プラグインは 1 つだけです。
  2. プレフィルター拡張機能は、ポッド情報を前処理したり、クラスターまたはポッドが満たす必要のある前提条件を確認したりするために使用されます。プレフィルターがエラーを返す場合、スケジュール プロセスは終了します。
  3. フィルター拡張機能は、Pod を実行できないノードを除外するために使用されます。スケジューラは各ノードに対してフィルター拡張を順番に実行します。いずれかのフィルターがノードを選択不可としてマークした場合、残りのフィルター拡張は実行されません。スケジューラは複数のノードで同時にフィルター拡張を実行できます。
  4. ポストフィルターは通知タイプの拡張ポイントです。拡張機能を呼び出すためのパラメーターは、フィルター フェーズ後にオプション ノードとしてフィルターされるノードのリストです。この情報は拡張機能で使用され、内部状態を更新したり、ログやメトリック情報を生成したりできます。
  5. スコアリング拡張機能は、すべてのオプション ノードにスコアを付けるために使用されます。スケジューラは各ノードに対してスコアリング拡張機能を呼び出し、スコアリング結果は範囲内の整数になります。正規化スコアリング フェーズでは、スケジューラは特定のノードに対する各スコアリング拡張機能のスコアリング結果と拡張機能の重みを結合して、最終的なスコアリング結果として算出します。
  6. Normalize スコアリング拡張機能は、スケジューラがノードの最終的なソートを実行する前に、各ノードのスコアリング結果を変更します。この拡張ポイントに登録された拡張機能が呼び出されると、同じプラグイン内のスコアリング拡張機能のスコアリング結果がパラメータとして取得されます。スケジューリング フレームワークがスケジュールを実行するたびに、すべてのプラグインで正規化スコアリング拡張機能が呼び出されます。
  7. Reserve は、ステートフル プラグインがノード上の Pod 用に予約されたリソースを取得するために使用できる通知拡張ポイントです。このイベントは、スケジューラがポッドをノードにバインドする前に発生します。目的は、Pod がノードにバインドされるのを待機している間にスケジューラが新しい Pod をノードにスケジュールするときに、実際に使用されるリソースが使用可能なリソースを超える状況を回避することです (Pod のノードへのバインドは非同期で行われるため)。これはスケジュール設定プロセスの最後のステップです。 Pod が予約状態になった後、バインディングが失敗した場合は Unreserve 拡張機能がトリガーされ、バインディングが成功した場合は Post-bind 拡張機能によってバインディング プロセスが終了します。
  8. Permit 拡張機能は、Pod とノードのバインドを防止または遅延するために使用されます。許可の延長では、次の 3 つのいずれかを実行できます。
  • 承認: すべての許可拡張機能がポッドとノードのバインドを承認すると、スケジューラはバインド プロセスを続行します。
  • 拒否: 許可拡張機能によってポッドのノードへのバインドが拒否された場合、ポッドはスケジュールのためにキューに戻され、予約解除拡張機能がトリガーされます。
  • wait: 許可拡張機能が wait を返す場合、Pod は別の拡張機能によって承認されるまで許可フェーズのままになります。タイムアウト イベントが発生すると、待機状態が拒否に変わり、Pod はスケジュールされるキューに戻され、Unreserve 拡張機能がトリガーされます。
  1. 事前バインド拡張機能は、Pod バインディングの前に何らかのロジックを実行するために使用されます。たとえば、事前バインド拡張機能を使用すると、ネットワークベースのデータ ボリュームをノードにマウントして、ポッドで使用できるようになります。いずれかの事前バインド拡張機能がエラーを返す場合、Pod はスケジュールされるキューに戻され、Unreserve 拡張機能がトリガーされます。
  2. Bind 拡張機能は、Pod をノードにバインドするために使用されます。
  • バインド拡張機能は、すべての事前バインド拡張機能が正常に実行された場合にのみ実行されます。
  • ディスパッチ フレームワークは、登録されている順序でバインド拡張機能を 1 つずつ呼び出します。
  • 特定のバインド拡張機能では、Pod を処理するか処理しないかを選択できます。
  • バインド拡張機能がポッドとノードのバインドを処理する場合、残りのバインド拡張機能は無視されます。
  1. Post-bind は通知拡張機能です。
  • ポッドがノードに正常にバインドされた後、バインド後の拡張機能が受動的に呼び出されます。
  • バインド後拡張機能は、バインド プロセスの最後のステップであり、リソースのクリーンアップ アクションを実行するために使用できます。
  1. Unreserve は通知拡張機能です。リソースが Pod 用に予約されていて、バインディング プロセス中に Pod が拒否された場合、予約解除拡張機能が呼び出されます。予約解除スケーリングにより、ポッド用に予約されていたノード上のコンピューティング リソースが解放されます。プラグインでは、予約拡張子と予約解除拡張子がペアで表示される必要があります。

独自のプラグインを実装する場合は、プラグインをスケジューリング フレームワークに登録し、構成を完了する必要があります。さらに、拡張ポイント インターフェイスを実装する必要があります。対応する拡張ポイント インターフェースは、以下に示すように、ソース コード pkg/scheduler/framework/interface.go ファイルにあります。

 // プラグインは、すべてのスケジューリング フレームワーク プラグインの親タイプです。
プラグインインターフェース{
名前()文字列
}

// PreEnqueuePlugin は、「PreEnqueue」プラグインによって実装される必要があるインターフェースです。
// これらのプラグインは、Pod を activeQ に追加する前に呼び出されます。
// 注意: preEnqueueプラグインは軽量かつ効率的であることが期待されるため、
// 外部エンドポイントへのアクセスなどのコストのかかる呼び出しが含まれます。そうでなければ他の
// イベント ハンドラーでの Pod のエンキュー。
PreEnqueuePluginインターフェース{
プラグイン
// PreEnqueue は、activeQ に Pod を追加する前に呼び出されます。
PreEnqueue ( ctx context . Context , p * v1 . Pod ) *ステータス
}

// LessFuncはポッド情報をソートする関数です
タイプLessFunc func ( podInfo1 , podInfo2 * QueuedPodInfo ) bool

// QueueSortPlugin は、「QueueSort」プラグインによって実装される必要があるインターフェースです。
// これらのプラグインは、スケジュール キュー内のポッドをソートするために使用されます。キューソートは1回のみ
// プラグインは一度に 1 つだけ有効にできます。
QueueSortPluginインターフェース{
プラグイン
// Less は、スケジュール キュー内のポッドをソートするために使用されます。
Less ( * QueuedPodInfo* QueuedPodInfo )ブール値
}

// EnqueueExtensionsは、プラグインが効率的に実装できるオプションのインターフェースです。
// スケジュール不可能なポッドを内部スケジュールキューに移動します。プラグイン
// ポッドのスケジューリングに失敗するプラグイン (例: フィルター プラグイン) は、このインターフェイスを実装することが期待されます。
EnqueueExtensionsインターフェース{
// EventsToRegisterはPodを引き起こす可能性のある一連のイベントを返します
// このプラグインのスケジュールによって失敗しました。
// イベントは内部スケジューリングキューをインスタンス化するときに登録されます。
// イベント ハンドラーを動的に構築するために活用されます。
// 注意: 返されるリストは静的である必要があります (構成パラメータに依存しない)。
// そうしないと、未定義の動作が発生します。
イベント登録() []クラスターイベント
}

// PreFilterExtensionsは、プラグインに含まれるインターフェースで、
// 事前に計算された値に増分更新を行うためのコールバック
// 州。
PreFilterExtensionsインターフェース{
// AddPodは、影響を評価しようとしているときにフレームワークによって呼び出されます
// podToSchedule をスケジュールするときにノードに podToAdd を追加します。
AddPod ( ctx context . Contextstate * CycleStatepodToSchedule * v1 . PodpodInfoToAdd * PodInfonodeInfo * NodeInfo ) *ステータス
// RemovePodは、影響を評価しようとしているときにフレームワークによって呼び出されます
// podToSchedule をスケジュールするときにノードから podToRemove を削除します。
RemovePod ( ctx context . Contextstate * CycleStatepodToSchedule * v1 . PodpodInfoToRemove * PodInfonodeInfo * NodeInfo ) *ステータス
}

// PreFilterPlugin は、「PreFilter」プラグインによって実装される必要があるインターフェースです。
// これらのプラグインは、スケジュール サイクルの開始時に呼び出されます。
タイプPreFilterPluginインターフェース{
プラグイン
// PreFilter はスケジュール サイクルの開始時に呼び出されます。すべてのプレフィルター
// プラグインは成功を返す必要があります。そうでない場合、ポッドは拒否されます。プレフィルターはオプションで
// 下流でどのノードを評価するかを制御する PreFilterResult を返します。これは便利です
// O(1) 時間で処理するノードのサブセットを決定できる場合。
PreFilter ( ctx context . Contextstate * CycleStatep * v1 . Pod ) ( * PreFilterResult* Status )
// PreFilterExtensions は、プラグインが実装している場合は PreFilterExtensions インターフェースを返します。
// そうでない場合は nil になります。プレフィルタープラグインは段階的に拡張機能を提供することができます
// 前処理された情報を変更します。フレームワークは拡張機能が
// AddPod/RemovePodはPreFilterの後にのみ呼び出され、クローンされた場合でも呼び出されます。
// CycleStateであり、これらの関数を複数回呼び出す場合があります。
// 特定のノードで再度フィルタリングします。
プレフィルター拡張機能()プレフィルター拡張機能
}

// FilterPlugin はフィルター プラグインのインターフェースです。これらのプラグインは、
// ポッドを実行できないホストを除外するためのフィルター拡張ポイント。
// この概念は、元のスケジューラでは「述語」と呼ばれていました。
// これらのプラグインは、Status.code で「Success」、「Unschedulable」、または「Error」を返す必要があります。
// ただし、スケジューラは他の有効なコードも受け入れます。
// 「成功」以外の場合は、指定されたホストが除外されます。
// ポッドを実行しています。
タイプFilterPluginインターフェース{
プラグイン
// フィルターはスケジューリング フレームワークによって呼び出されます。
// すべてのFilterPluginsは「Success」を返す必要があります。
// 指定されたノードはポッドに適合します。フィルターが「成功」を返さない場合、
// 「Unschedulable」、「UnschedulableAndUnresolvable」、または「Error」を返します。
// 評価対象のノードについては、フィルタプラグインは渡された
// この特定のノードの情報のnodeInfo参照(例:ポッド)
// ノード上で実行されていると見なされる)を検索するのではなく、
// NodeInfoSnapshot は同じになることが保証されないためです。
// たとえば、プリエンプション中に、元のコピーを渡すことがあります
// 評価するためにいくつかのポッドが削除されたnodeInfoオブジェクト
// ターゲット ポッドをスケジュールするためにそれらをプリエンプトする可能性があります。
フィルター( ctx context . Contextstate * CycleStatepod * v1 . PodnodeInfo * NodeInfo ) *ステータス
}

// PostFilterPlugin は「PostFilter」プラグインのインターフェースです。これらのプラグインは
// ポッド後はスケジュールできません。
PostFilterPluginインターフェース{
プラグイン
// PostFilter はスケジューリング フレームワークによって呼び出されます。
// PostFilter プラグインは、次のいずれかのステータスを返す必要があります。
// - スケジュール不可: プラグインは正常に実行されますが、ポッドをスケジュール可能にすることはできません。
// - 成功: プラグインは正常に実行され、ポッドをスケジュール可能にすることができます。
// - エラー: 内部エラーのためプラグインが中止されました。
//
// 情報プラグインは他のプラグインより先に構成する必要があり、常に Unschedulable ステータスを返します。
// オプションで、成功ステータスとともに nil 以外の PostFilterResult が返される場合があります。例えば、
// プリエンプションプラグインはnominatedNodeNameを返すことを選択する可能性があり、フレームワークはそれを再利用して更新することができます。
// プリエンプター ポッドの .spec.status.nominatedNodeName フィールド。
PostFilter ( ctx context.Contextstate * CycleStatepod * v1.Pod filteredNodeStatusMap NodeToStatusMap ) ( * PostFilterResult * Status )
}

// PreScorePlugin は「PreScore」プラグインのインターフェースです。 PreScoreは
// 情報拡張ポイント。プラグインはノードのリストとともに呼び出されます
// フィルタリングフェーズを通過しました。プラグインはこのデータを使用して内部を更新する場合があります
// 状態を取得したり、ログ/メトリックを生成したりします。
PreScorePluginインターフェース{
プラグイン
// PreScoreはノードのリストの後にスケジューリングフレームワークによって呼び出されます
// フィルタリングフェーズを通過しました。すべてのプレスコアプラグインは成功を返すか、
// ポッドは拒否されます
PreScore ( ctx context . Contextstate * CycleStatepod * v1 . Podnodes [] * v1 . Node ) *ステータス
}

// ScoreExtensions はスコア拡張機能のインターフェースです。
ScoreExtensionsインターフェース{
// NormalizeScore は、同じプラグインの「Score」によって生成されたすべてのノード スコアに対して呼び出されます。
// 方法。 NormalizeScoreの実行が成功すると、スコアリストが更新され、
// 成功ステータス。
NormalizeScore ( ctx context . Contextstate * CycleStatep * v1 . Podscores NodeScoreList ) *ステータス
}

// ScorePluginは、スコアプラグインがランキング付けするために実装する必要があるインターフェースです。
// フィルタリングフェーズを通過したノード。
タイプScorePluginインターフェース{
プラグイン
// スコアはフィルタリングされた各ノードで呼び出されます。成功と整数を返す必要があります
// ノードのランクを示します。すべてのスコアリングプラグインは成功を返すか、
// ポッドは拒否されます。
スコア( ctxコンテキスト.コンテキスト状態* CycleStatep * v1 . PodnodeName文字列) ( int64*ステータス)

// ScoreExtensions は、ScoreExtensions インターフェースを実装している場合はそのインターフェースを返し、実装していない場合は nil を返します。
スコア拡張()スコア拡張
}

// ReservePlugin は、Reserve と Unreserve を備えたプラグインのインターフェースです
// メソッド。これらはプラグインの状態を更新するためのものです。このコンセプト
// 元のスケジューラでは「assume」と呼ばれていました。これらのプラグインは
// Status.code で Success または Error のみを返します。しかし、スケジューラは受け入れる
// 他の有効なコードも同様です。成功以外のものは
// ポッドの拒否。
タイプReservePluginインターフェース{
プラグイン
// スケジューラキャッシュが空になったときに、スケジューリングフレームワークによってReserveが呼び出されます。
// 更新されました。このメソッドが失敗したステータスを返す場合、スケジューラは
// 有効なすべての ReservePlugins の Unreserve メソッド。
予約( ctxコンテキスト.コンテキスト状態* CycleStatep * v1 . PodnodeName文字列) *ステータス
// 予約済みのポッドが削除されたときに、Unreserve がスケジューリングフレームワークによって呼び出されます。
// 拒否されました、後続のプラグインの予約中にエラーが発生しました、または
// 後のフェーズで。 Unreserveメソッドの実装はべき等である必要があります
// 対応する予約が存在しない場合でも、スケジューラによって呼び出される場合があります。
// 同じプラグインのメソッドが呼び出されませんでした。
予約解除( ctx context . Contextstate * CycleStatep * v1 . PodnodeName文字列)
}

// PreBindPlugin は、「PreBind」プラグインによって実装される必要があるインターフェースです。
// これらのプラグインは、ポッドがスケジュールされる前に呼び出されます。
PreBindPluginインターフェース{
プラグイン
// PreBind はポッドをバインドする前に呼び出されます。すべてのprebindプラグインは
// 成功しない場合はポッドは拒否され、バインディングのために送信されません。
PreBind ( ctx context . Contextstate * CycleStatep * v1 . PodnodeName文字列) *ステータス
}

// PostBindPlugin は、「PostBind」プラグインによって実装される必要があるインターフェースです。
// これらのプラグインは、ポッドがノードに正常にバインドされた後に呼び出されます。
PostBindPluginインターフェース{
プラグイン
// ポッドが正常にバインドされた後に PostBind が呼び出されます。これらのプラグインは
// 情報提供。この拡張ポイントの一般的な用途は、清掃です。
// 上。ポッドがスケジュールされた後にプラグインの状態をクリーンアップする必要がある場合、
// バインドされている場合、PostBind は登録する必要がある拡張ポイントです。
PostBind ( ctxコンテキスト.コンテキスト状態* CycleStatep * v1 . Podノード名文字)
}

// PermitPlugin は、「Permit」プラグインによって実装される必要があるインターフェースです。
// これらのプラグインは、ポッドがノードにバインドされる前に呼び出されます。
PermitPluginインターフェース{
プラグイン
// Permit はポッドをバインドする前 (およびプラグインを事前バインドする前) に呼び出されます。許可する
// プラグインは、Pod のバインドを防止または遅延するために使用されます。許可プラグイン
// 成功を返すか、タイムアウト期間を待ってください。そうしないと、ポッドは拒否されます。
// 待機タイムアウトまたはポッドが拒否された場合にもポッドは拒否されます。
// 待っている。プラグインが「wait」を返す場合、フレームワークは待機するだけであることに注意してください。
// 他のプラグインがポッドを拒否しない限り、残りのプラグインを実行した後。
許可( ctx context . Contextstate * CycleStatep * v1 . PodnodeName string ) ( * Statustime . Duration )
}

// BindPlugin は、「Bind」プラグインによって実装される必要があるインターフェースです。バインド
// プラグインはポッドをノードにバインドするために使用されます。
タイプBindPluginインターフェース{
プラグイン
// すべての事前バインド プラグインが完了するまで、バインド プラグインは呼び出されません。それぞれ
// バインド プラグインは設定された順序で呼び出されます。バインドプラグインは、
// 指定された Pod を処理するかどうかを指定します。バインドプラグインがPodを処理することを選択した場合、
// 残りのバインド プラグインはスキップされます。バインドプラグインがポッドを処理しない場合、
// ステータス コードで Skip を返す必要があります。バインドプラグインがエラーを返した場合、
// ポッドは拒否され、バインドされません。
Bind ( ctx context . Contextstate * CycleStatep * v1 . PodnodeName string ) *ステータス
}

スケジューリング フレームワーク プラグインを有効または無効にするには、クラスターをインストールして構成するときに KubeSchedulerConfiguration リソース オブジェクトを使用します。次の構成例では、reserve および preBind 拡張ポイントを実装するプラグインを有効にし、別のプラグインを無効にし、プラグイン foo の構成情報を提供します。

 APIバージョン: kubescheduler.config.k8s.io/v1
種類: KubeSchedulerConfiguration

---
プラグイン:
予約する:
有効:
- 名前: foo
- 名前: バー
無効:
- 名前: バズ
事前バインド:
有効:
- 名前: foo
無効:
- 名前: バズ

プラグイン設定:
- 名前: foo
引数: >
fooプラグインで解析できるコンテンツ

拡張機能が呼び出される順序は次のとおりです。

  • 特定の拡張ポイントに対応する拡張機能が設定されていない場合、スケジューリングフレームワークはデフォルトのプラグインの拡張機能を使用します。
  • 拡張機能が拡張ポイントに対して構成されアクティブ化されている場合、ディスパッチ フレームワークは最初に既定のプラグインの拡張機能を呼び出し、次に構成内の拡張機能を呼び出します。
  • デフォルトのプラグイン拡張機能は常に最初に呼び出され、その後、KubeSchedulerConfigurationで拡張機能が有効になっている順序で、拡張ポイントの拡張機能が1つずつ呼び出されます。
  • まずデフォルトのプラグイン拡張機能を無効にし、次に有効リストの特定の位置でデフォルトのプラグイン拡張機能を有効にすることができます。これにより、デフォルトのプラグイン拡張機能が呼び出される順序を変更できます。

デフォルトのプラグイン foo が予約拡張ポイントを実装していると仮定します。プラグイン bar を追加し、それを foo の前に呼び出す場合は、まず foo を無効にし、次に bar と foo の順序で有効にする必要があります。構成例は次のようになります。

 APIバージョン: kubescheduler.config.k8s.io/v1
種類: KubeSchedulerConfiguration

---
プロフィール:
- プラグイン:
予約する:
有効:
- 名前: バー
- 名前: foo
無効:
- 名前: foo

ソースコードディレクトリ pkg/scheduler/framework/plugins/examples にはいくつかのサンプルプラグインがあり、それらの実装を参照することができます。

実際、スケジューリング フレームワークのプラグインを実装するのは難しくありません。対応する拡張ポイントを実装し、プラグインをスケジューラに登録するだけです。以下は、初期化中にデフォルトのスケジューラによって登録されるプラグインです。

 // pkg/scheduler/algorithmprovider/registry.go
func NewRegistry ()レジストリ{
レジストリを返す{
// ファクトリーマップ:
// 新しいプラグインはここに登録されます。
// 例:
// {
// stateful_plugin.Name: stateful.NewStatefulMultipointExample、
// fooplugin.Name: fooplugin.New,
// }
}
}

ただし、一部のプラグインはデフォルトで登録されていないため、スケジューラにプラグイン コードを認識させたい場合は、自分でスケジューラを実装する必要があります。もちろん、このスケジューラを自分たちだけで完全に実装する必要はありません。デフォルトのスケジューラを呼び出して、上記の NewRegistry() 関数でプラグインを登録するだけです。 kube-scheduler ソース コード ファイル kubernetes/cmd/kube-scheduler/app/server.go には、NewSchedulerCommand エントリ関数があります。この関数のパラメーターは Option 型のリストであり、このオプションはプラグイン構成の定義になります。

 // オプションはframework.Registryを設定します。
タイプオプションfunc (フレームワーク.レジストリ)エラー

// NewSchedulerCommand は、デフォルトのパラメータと registryOptions を持つ *cobra.Command オブジェクトを作成します。
func NewSchedulerCommand ( registryOptions ... Option ) * cobra指示{
......
}

したがって、この関数を関数エントリとして直接呼び出し、独自のプラグインをパラメーターとして渡すことができます。このファイルの下には、Option インスタンスを作成するための WithPlugin という関数もあります。

 func WithPlugin ( name 文字ファクトリーランタイム。PluginFactory )オプション{
return func (レジストリランタイム.レジストリ) error {
レジストリを返しますレジスタ名前工場
}
}

最終的に、エントリ関数は次のようになります。

パッケージメイン

輸入
「k8s.io/コンポーネントベース/cli」
「k8s.io/kubernetes/cmd/kube-scheduler/app」
「数学/ランド」
「オス」
// スキーム パッケージが初期化されていることを確認します。
_ "シンプルスケジューラ/pkg/スケジューラ/apis/config/スキーマ"
「シンプルスケジューラ/pkg/スケジューラ/フレームワーク/プラグイン」
"時間"


関数main (){
ランドシード(時間. Now () . UTC () . UnixNano ())
コマンド: = app新しいスケジューラコマンド(
アプリWithPlugin (プラグイン. Name ,プラグイン. New ))
コード: = cli実行コマンド
OS終了コード
}

その中で、app.withplugin(sample.name、sample.new)は、次に実装するプラグインです。 withplugin関数のパラメーターから、ここのサンプルがフレームワークの値である必要があることもわかります。PluginFactoryタイプ、およびPluginFactoryの定義は関数です。

タイプPluginFactory = func(configuration runtime.object、f framework.handle)(framework.plugin、error)

したがって、sample.newは実際には上記の関数です。この関数では、プラグインからいくつかのデータを取得し、論理処理を実行できます。プラグインの実装は次のとおりです。ここでは、データを取得してログを印刷するだけです。実際のニーズがある場合は、取得したデータに従って処理できます。ここでは、プレフィルター、フィルター、およびプレバインドの3つの拡張ポイントのみを実装します。他の人も同じように拡張できます。

パッケージプラグイン

輸入
"コンテクスト"
「fmt」
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"K8s.io/klog/v2"
「K8s.io/kubernetes/pkg/scheduler/framework」
「simple-scheduler/pkg/scheduler/apis/config」
「simple-scheduler/pkg/scheduler/apis/config/validation」


const name = "sample-plugin"

タイプサンプルstruct {
args * configsampleargs
フレームワークを処理しますハンドル
}

funcs * samplename () string {
返品
}

func s * sample prefilterctxcontext。context。cyclestate * framework。cyclestatepod * v1。pod * framework。prefilterresult * framework。Status {
クログV3 )。 infof"prefilter pod:%v" pod。Name
nilnilを返します
}

funcs * sampleフィルターctxコンテキストコンテキストサイクレステート*フレームワークサイクレステートポッド* v1。podnodeinfo *フレームワーク。NodeInfo) *フレームワーク状態{
クログV3 )。 infof"フィルターポッド:%v、ノード:%v" pod。NAMEnodeInfo。Node)。名前
フレームワークを返しますNewStatusフレームワーク成功""
}

funcs * samplepre -bindctxコンテキストコンテキスト状態*フレームワークサイクレステート pod * v1。podnodename string*フレームワーク状態{
nodeinfoの場合err= sハンドルsnapshotsharedLister ()。 nodeinfos ()。 getnodename );エラー!=ゼロ{
フレームワークを返しますnewStatusFramework。Error fmt。Sprintf "pre -bind get node:%s info serper:%s" nodenameerr。errer ()))
}それ以外{
クログV3 )。 infof"プリバインドノード情報:%+v"nodeinfo。node
フレームワークを返しますNewStatusフレームワーク成功""
}
}

func newFPARGSランタイムオブジェクトFHフレームワークハンドル)(フレームワークプラグインエラー){
argsok= fpargs * config。sampleargs
もしわかりました{
nilfmtを返しますerrorf"type%tのargsを取得し、 *sampleargsが欲しい"fpargs
}
err=検証の場合validatesmeplatepluginargs* args );エラー!=ゼロ{
nilerrを返します
}
return sample {
argsargs
ハンドルFH
}、 nil
}

完全なコードは、リポジトリhttps://github.com/cnych/sample-scheduler-frameworkで取得できます。

プラグインを発送するためのパラメーターも次のとおりです。

 // +k8s:deepcopy-gen:interfaces = k8s.io/apimachinery/pkg/runtime.object

sampleargs structをタイプ{
metav1タイプメタ
FavoriteColor String `json" favory_color、omitempty " `
FavoriteNumber int `json" favorial_number、omitempty " `
感謝の文字列`json" antys_to、omitempty " `
}

framework.decodeinto関数は古いバージョンで提供され、渡したパラメーターを直接変換しますが、新しいバージョンはruntime.Objectオブジェクトである必要があるため、対応するディープコピーメソッドを実装する必要があります。したがって、アノテーション +K8S:deepcopy-gen:interfaces = k8s.io/apimachinery/pkg/runtime.objectを構造に追加し、Kubernetesソースコードで提供されるハック/update-gen.shスクリプトを介して対応するディープコピー方法を自動的に生成できます。

file register.goで同意するsampleargsをaddwondtypes関数に呼び出して追加する必要があります。また、Main.goファイルでは、ここに定義されているスキーマをインポートしました。これは、PKG/APIで導入されたすべての構成初期化スキーム/構成を使用することに注意してください。

実装が完了した後、コンパイルしてミラーにパッケージ化できます。次に、展開コントローラーを使用して、通常のアプリケーションとして展開できます。クラスター内のいくつかのリソースオブジェクトを取得する必要があるため、もちろんRBAC許可を適用してから、-configパラメーターを介してスケジューラを構成する必要があります。また、Kubescheduleconfigurationリソースオブジェクト構成も使用します。プラグインを介して実装したプラグインを有効または無効にするか、PluginconFigを介していくつかのパラメーター値をプラグインに渡すことができます。

 サンプル-スケジューラヤム
種類クラスターロール
ApiversionRBAC許可K8SIO / V1
メタデータ:
名前サンプル-スケジューラ-クラスターロール
ルール
- apigroups
- 「」
リソース
-エンドポイント
-イベント
動詞
-作成する
-得る
-アップデート
- apigroups
- 「」
リソース
-ノード
動詞
-得る
-リスト
-時計
- apigroups
- 「」
リソース
-ポッド
動詞
-消去
-得る
-リスト
-時計
-アップデート
- apigroups
- 「」
リソース
-バインディング
-ポッド/バインディング
動詞
-作成する
- apigroups
- 「」
リソース
-ポッド/ステータス
動詞
- パッチ
-アップデート
- apigroups
- 「」
リソース
-ReplicationControllers
-サービス
動詞
-得る
-リスト
-時計
- apigroups
-アプリ
-拡張機能
リソース
-レプリカセット
動詞
-得る
-リスト
-時計
- apigroups
-アプリ
リソース
-ステートフルセット
動詞
-得る
-リスト
-時計
- apigroups
-ポリシー
リソース
-PoddisurptionBudgets
動詞
-得る
-リスト
-時計
- apigroups
- 「」
リソース
-PersistentVolumeclaims
-PersistentVolumes
動詞
-得る
-リスト
-時計
- apigroups
- 「」
リソース
-構成マップ
動詞
-得る
-リスト
-時計
- apigroups
- 「Storage.k8s.io」
リソース
-StorageClasses
-CSINODES
動詞
-得る
-リスト
-時計
- apigroups
- 「coordination.k8s.io」
リソース
-リース
動詞
-作成する
-得る
-リスト
-アップデート
- apigroups
- 「events.k8s.io」
リソース
-イベント
動詞
-作成する
- パッチ
-アップデート
---
APIバージョン: v1
種類ServiceAcCount
メタデータ:
名前サンプル-スケジューラ-SA
名前空間 Kube-システム
---
種類クラスターロールバインディング
ApiversionRBAC許可K8SIO / V1
メタデータ:
名前サンプル-スケジューラ-クラスターロールバインディング
名前空間 Kube-システム
Roleref
ApigroupRBAC許可k8s .io
種類クラスターロール
名前サンプル-スケジューラ-クラスターロール
科目
-種類ServiceAcCount
名前サンプル-スケジューラ-SA
名前空間 Kube-システム
---
APIバージョン: v1
種類: ConfigMap
メタデータ:
名前スケジューラ- config
名前空間 Kube-システム
データ
スケジューラ- configyaml : |
ApiversionKubeschedulerconfigK8SIO / V1
種類Kubescheduleconfiguration
LeaderElection
leaderelecttrue
リースされた復元15
RenewDeadline10 s
ResourceLockEndPointSleases
ResourceNameサンプル-スケジューラ
ResourceNamesPace Kube-システム
retryperiod2
プロフィール:
-スケジュール名サンプル-スケジューラ
プラグイン:
Prefilter
有効:
-名前「Sample-Plugin」
フィルター
有効:
-名前「Sample-Plugin」
プラグイン設定:
-名前サンプル-プラグイン
args#runtime 物体
FavoriteColor"#326ce5"
FavoriteNumber7
ありがとう「Kubernetes」
---
ApiversionApps / V1
種類展開
メタデータ:
名前サンプル-スケジューラ
名前空間 Kube-システム
ラベル
コンポーネントサンプル-スケジューラ
仕様:
セレクター:
マッチラベル
コンポーネントサンプル-スケジューラ
テンプレート
メタデータ:
ラベル
コンポーネントサンプル-スケジューラ
仕様:
serviceaccountname sample -scheduler -sa
PriorityClassNameシステム-クラスター-クリティカル
ボリューム
-名前スケジューラ- config
configMap
名前スケジューラ- config
コンテナ
-名前スケジューラ
画像CNYCH /サンプル-スケジューラV0 .26 .4
ImagePullpolicyifnotpresent
指示
-サンプル-スケジューラ
--cnotallow = / etc / kubernetes / scheduler -config.yaml
--v = 3
ボリュームマウント
-名前スケジューラ- config
MountPath/ etc / Kubernetes

上記のリソースオブジェクトを直接展開するだけで、Sample-Schedulerというスケジューラを展開します。次に、このスケジューラを使用するためにアプリケーションを展開できます。

 テスト-スケジューラヤム
ApiversionApps / V1
種類展開
メタデータ:
名前テスト-スケジューラ
仕様:
セレクター:
マッチラベル
アプリテスト-スケジューラ
テンプレート
メタデータ:
ラベル
アプリテスト-スケジューラ
仕様:
スケジュール名サンプル-スケジューラデフォルトのデフォルトを指定しないで、使用するスケジューラを指定する-スケジューラ
コンテナ
-画像nginx1.7 .9
ImagePullpolicyifnotpresent
名前nginx
ポート
-containerport 80

ここで、スケジュール名フィールドを手動で指定し、上記のスケジューラー名サンプルスケジューラーに設定したことに注意してください。

このリソースオブジェクトを直接作成し、作成が完了したら、カスタムスケジューラのログ情報を表示します。

 pods -n kube -system -l compnotallow =サンプル-スケジューラ取得するkubectl
名前準備完了ステータス再起動年齢
サンプル-スケジューラ-896658CD7 -K7VCL 1/1ランニング0 57
➜kubectl logs -fサンプル-スケジューラ-896658CD7 -K7VCL -N Kube -System
I0114 091418.878613 1 EventHandlersGO 173 ]予定外のポッドデフォルト/テストためイベント追加-6486F D49FC -ZJHCX
I0114 091418.878670 1スケジューラGO 464 ] PODスケジュール試してみるデフォルト/テスト-スケジューラ-6486F D49FC -ZJHCX
I0114 091418.878706 1サンプルGo77 ] 「Prefilter Podを開始」 pod = "test-scheduler-6486fd49fc-zjhcx"
I0114 091418.878802 1サンプルGo93 ] "Start Filter pod" pod = "test-scheduler-6486fd49fc-zjhcx" node = "node2" prefilterstate =& { resource :{ millicpu0メモリ0 ephemeralStorage0 aoverpodnumber:0 scalarresours0 scalResours0 []}}}
I0114 091418.878835 1サンプルGo93 ] "Start Filter pod" pod = "test-scheduler-6486fd49fc-zjhcx" node = "node1" prefilterstate =& { resource :{ millicpu0 ephemeralStorage0 approadPodNumber0 scalRRESOURS0 scalResours0 []}}}
I0114 091418.879043 1 default_binderGO 51 ]デフォルト/テストバインドしよとする-SCHEDULER -6486F D49FC -ZJHCXからNode1
I0114 091418.886360 1スケジューラGO609 ] "POD" pod = "default/test-scheduler-6486fd49fc-zjhcx" node = "node1" evaluatedNodes = 3 feablenodes = 2
I0114 091418.887426 1 EventHandlersGO205 ]予定外のポッドデフォルト/テストためのイベント削除-スケジューラ-6486F D49FC -ZJHCX
I0114 091418.887475 1 EventHandlersGO 225 ]スケジュールされたポッドデフォルト/テストイベント追加-スケジューラ-6486F D49FC -ZJHCX

ポッドを作成した後、対応するログがカスタマイズされたスケジューラに表示され、対応するログが定義した拡張ポイントに表示されることがわかります。私たちの例が成功していることを証明してください。 PODのスケジュール名を確認して確認することもできます。

 ➜kubectlポッドを取得します
名前準備完了ステータス再起動年齢
テスト-スケジューラ-6486F D49FC -ZJHCX 1/1ランニング0 35
➜Kubectlポッドテスト取得-Scheduler -6486F D49FC -ZJHCX -O Yaml
......
RestArtPolicy常に
スケジュール名サンプル-スケジューラ
SecurityContext :{}
ServiceAcCountデフォルト
......

Kubernetes v1.17から始めて、スケジューラフレームワークに組み込まれた事前に選択された優先関数がプラグインであるため、スケジューラを拡張するために、このスケジューリングのフレームワークを習得して理解する必要があります。

<<:  5つの異なるタイプのエッジソリューション

>>:  ローコード + サーバーレスは、次世代デジタル企業の開発に新たなパラダイムを切り開きます

推薦する

Mituoテンプレート:家電業界におすすめのウェブサイトテンプレート

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますMituo...

Baidu SEOを行う際には、Googleにも十分注意してください。

2010年上半期にGoogleが中国から撤退したため、中国本土のネットユーザーは香港にサーバーがある...

アリババは、次世代のインターネットカーを定義する5つの大きな進化を遂げたAliOS 2.0システムをリリースしました。

想像してみてください。車のドアを開けると、車があなたの身元を認識し、シート、バックミラー、エアコンの...

ウェブサイトを最適化するには、大規模なウェブサイトの例に従う必要があります。

ウェブサイトの外部プロモーションは、すべてのウェブサイトが行う必要があることです。Baidu の新し...

ウェブサイトの高速化に役立つ口コミマーケティングを構築する方法

新製品のウェブサイトが立ち上げられるたびに、ウェブマスターとして口コミマーケティング手法を確立するこ...

ウェブサイトがブロックされたときに知っておくべき8つの要素

1. ウェブサイトが K-ed されるとはどういう意味ですか?ウェブサイトが正常に登録されていた場合...

メガレイヤー: シンガポールの高防御\香港の高防御、専用サーバー月額522元から、CN2ネットワーク、E3-1230/16gメモリ/480gSSD/30M帯域幅

メガレイヤーは、新たな事業である高防御サーバーを立ち上げ、香港高防御サーバー、シンガポール高防御サー...

物理マシンと仮想マシンでは、K8s 環境でコンテナを実行するのにどちらが適していますか?

[[319956]] K8S 環境でコンテナを物理マシン上で直接実行するか、分離された仮想マシン上で...

中小企業向けインターネットマーケティングソリューション

電子商取引の発展に伴い、中国のビジネス分野は徐々に「有形」から「無形」へと移行しています。インターネ...

JVMはオブジェクトが死んだと判断し、GCリサイクルを検証します。

[[377367]]この記事はWeChatの公開アカウント「bugstack」から転載したもので、著...

ガールフレンドにKubernetesを理解してもらうために12枚の絵を描きました

私は最近 Kubernetes を使い始め、その内部をより深く理解したいと考えていました。これらにつ...

貴州省のビッグデータとクラウドコンピューティングの学部卒業生第1期生が卒業、就職の心配なし

[[235895]] 「入社2日目から業務に慣れ、開発業務に取り組み始めました。」貴州大学省実証ソフ...

#Dry Goods# digitalocean が 100 ドルをプレゼント、複数のコンピューター ルームが利用可能

digitalocean が最後にプロモーションを行ったのはいつだったか思い出せませんが、少なくとも...

Pinduoduo の根底にあるビジネスロジック!

Pinduoduoは、C2B グループ購入に重点を置いたサードパーティのソーシャル e コマース プ...

サーバーレス コンピューティングにより開発が容易になり、運用コストが削減される理由

エンタープライズ開発者は通常、ビジネス上の問題を解決するためにコードの作成に多くの時間を費やします。...