Docker Meetup Tokyo #31 で Kubernetes 1.15 について話してきました
先日行われた Docker Meetup Tokyo #31 で、Kubernetes 1.15 の Scheduler 周りの新機能について発表してきました。
Kubernetes の Pod Preemption を利用すると、より重要な Pod にノードの計算リソースを割り当てる優先的に割り当てることができ、コストの最適化につながります。しかし優先度の低い Pod は実行中に強制的に終了されることとなり、長時間かかるバッチ処理が途中で中断されてしまうという弊害もあります。
本スライドでは、Kubernetes 1.15 から Alpha 機能として導入された NonPreemptingPriority と Scheduling Framework を利用して、中断されたくない Pod に対する Preemption を抑制する手法を提案します。
その他の変更点も含めて、1.15 のリリースノートの完全な解説については以下の記事を参照してください。
参考資料
Scheduler 一般
- Scheduling Special Interest Group
- SIG Scheduling Deep Dive (KubeCon EU 2019)
- kube-schedulerのソースコードを読みながらPodがNodeにBindされるまでを理解する (現状、最も詳しいソースコード解説)
- Configuring Multiple Schedulers (実験用にカスタム Scheduler を追加するとき)
Priority と Preemption
- Pod Priority and Preemption (Non-Preempting PriorityClasses)
- 猫でもわかる Pod Preemption
- Issue: Add non-preempting option to PriorityClasses
Scheduling Framework
- Scheduling Framework (あくまでもデザインプロポーザルの解説で、現状の実装とは一致しない)
- 猫でもわかる Scheduling Framework
- Issue: Scheduling Framework (Scheduling Framework 関連の親 Issue)
- Plugin 有効化のための設定項目
- 各 Plugin の Interface 定義
余談
思えば Scheduler についても色々なイベントで話したものです。今回のスライドを登録したことで Speaker Deck のスライド一覧 がついに 2 ページ目になりました。
そろそろ Scheduler 以外のコンポーネントについても手を広げていきたいなと思っていますが、それはまた別の話。
Kubernetes 1.15: SIG Scheduling の変更内容
はじめに
本記事では、Kubernetes 1.15 のリリースノート からスケジューリングに関する内容をまとめました。
なお、SIG Scheduling の変更内容については既に他の方から翻訳記事が出ていますが、本記事は後発ということもあり、すべての機能を実際に触ってみた上でサンプルコードを添えて解説していきます。
1.15 の新着情報 (1.15 What’s New)
今回、完全な変更ログは https://relnotes.k8s.io/ で、絞り込み可能なフォーマットで公開されています。確認とフィードバックお願いします!
特筆すべき機能アップデート (Additional Notable Feature Updates)
Scheduler のプラグインを作るための Scheduling Framework が新しく Alpha 版になりました。
Scheduler Framework は、Scheduler の各点に拡張できるポイントを設け、カスタム処理をプラグインとして差し込むための仕組みです。
Scheduler を拡張する仕組みとしては他に JSON Webhook を使用する Scheduler Extender もありますが、プラグインは本体と同時にコンパイルするため通信によるオーバヘッドを避けることができます。
今回の変更点を述べるためには全体像を説明する必要がありますが、ここに書くにはボリュームが多くなりそうなので別記事を立てる予定です。
(TODO: 別記事を書いたらリンク貼る)
既知の問題点 (Known Issues)
1.15 でオプション
--log-file
を指定すると問題が発生することが分かっています。一つのファイルにログが複数回出力される現象です。この問題の振る舞いや詳細、あるいは修正のための予備調査などは ここ に解説されています。
Scheduler もこの問題の影響を受けます。実際、kube-scheduler 起動時に --log-file=kube-scheduler.log
および --logtostderr=false
を指定した場合、以下のようなログファイルが出力されることを確認しました。*1
Log file created at: 2019/07/01 21:00:28 Running on machine: custom-scheduler-767dbc9c6-blhwk Binary: Built with gc go1.12.5 for linux/amd64 Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg Log file created at: 2019/07/01 21:00:29 Running on machine: custom-scheduler-767dbc9c6-blhwk Binary: Built with gc go1.12.5 for linux/amd64 Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg (snip)
通常の運用で Scheduler のログをファイルに書き出す必然性はまずないので実際には問題にはならないと思いますが、一応注意が必要です。
メトリクスの変更 (Metrics Changes)
追加されたメトリクス (Added metrics)
Scheduler について、それぞれのキュー内にある Pending 状態の Pod の個数を記録するメトリクスが追加されました。(#75501, @Huang-Wei)
追加されているメトリクスは以下の 3 種類です。
scheduler_pending_pods_num{queue="active"}
scheduler_pending_pods_num{queue="backoff"}
scheduler_pending_pods_num{queue="unschedulable"}
非推奨または変更されたメトリクス (Deprecated/changed metrics)
Scheduling Framework に Reserve、Pre-bind、Permit、Post-bind、Queue sort および Un-reserve の拡張点が実装されました。(#77567, @wgliang) (#77559, @ahg-g) (#77529, @draveness) (#77598, @danielqsj) (#77501, @JieJhih) (#77457, @danielqsj)
Scheduling Framework の進捗についてです。直接的にメトリクスに関連する話題ではないはずですが、なぜこの位置に置かれているのかよくわかりません。
ここに述べられている通り、v1.14 時点ですでに実装されていた Reserve と Pre-bind に加えて、さらに 4 つの拡張点が追加されました。これに伴い、設定項目として有効にする拡張点が選択できるようになるなど、前回まで場当たり的だった実装のリファクタリングも行われています。
ただし、Permit プラグインについては「戻り値として Wait を返すことで Bind 前の Pod を待機させる機能」は実装されているものの、その待機状態を解除する方法がまだ提供されていないようです。詳細は先にも挙げた詳細記事を参照してください。
(TODO: 別記事へのリンク)
特筆すべき機能 (Notable Features)
Preemption を行わない Pod の優先度 (NonPreemptingPriority) が作成できるようになりました。PriorityClass においてこれが指定された場合、対象の Pod は 優先度の低い Pod に対してキュー内では優先されますが、実行中の Pod を Preemption することはありません。(#74614, @denkensk)
実際に PriorityClass で指定する属性は preemptionPolicy
で、値としては PreemptLowerPriority
もしくは Never
が指定可能です。デフォルトは前者になります。Preemption する側 の Pod に指定する点に注意してください。
なお、Pod の Priority と Preemption の一般論については以下に解説があります。
この機能は Alpha 版なので、使用するには FeatureGates の有効化が必要です。kube-scheduler と apiserver の実行時引数として --feature-gates "NonPreemptingPolicy=true"
を与えておいてください。
実験してみましょう。以下、話を簡単にするためにシングルノードの Kubernetes クラスタを仮定します。
まず、以下のような 3 つの PriorityClass を用意します。low-priority
が Preemption される側の Pod 用、preempting-priority
と non-preempting-priority
が Preemption する側の Pod 用です。
priority.yaml
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority value: 100 description: "The priroity for preempted Pods." --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: preempting-priority value: 100000 description: "The priroity for preempting Pods." --- apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: non-preempting-priority value: 100000 preemptionPolicy: Never description: "The priroity for non-preempting Pods."
さらに、各 PriorityClass に対応した Pod を定義します。ここで resources.requests.memory: 5Gi
が指定されていますが、これは Node の利用可能メモリが 8Gi
程度だったためです。実験に使用している Node の性能を鑑みて「1 個なら置けるが 2 個は無理」という値にしてください。
low-priority-pod.yaml
apiVersion: v1 kind: Pod metadata: name: low-priority-pod spec: containers: - name: low-priority-pod image: k8s.gcr.io/pause:2.0 resources: requests: memory: 5Gi priorityClassName: low-priority
preempting-pod.yaml
apiVersion: v1 kind: Pod metadata: name: preempting-pod spec: containers: - name: preempting-pod image: k8s.gcr.io/pause:2.0 resources: requests: memory: 5Gi priorityClassName: preempting-priority
non-preempting-pod.yaml
apiVersion: v1 kind: Pod metadata: name: non-preempting-pod spec: containers: - name: non-preempting-pod image: k8s.gcr.io/pause:2.0 resources: requests: memory: 5Gi priorityClassName: non-preempting-priority
それでは実験です。Pod に先立って PriorityClass を作成しておきます。
$ kubectl create -f priority.yaml priorityclass.scheduling.k8s.io/low-priority created priorityclass.scheduling.k8s.io/preempting-priority created priorityclass.scheduling.k8s.io/non-preempting-priority created
まず、low-priority-pod
を作成後に preempting-pod
を作成します。
$ kubectl create -f low-priority-pod.yaml pod/low-priority-pod created $ kubectl get pods NAME READY STATUS RESTARTS AGE low-priority-pod 1/1 Running 0 14s $ kubectl create -f preempting-pod.yaml pod/preempting-pod created (wait for a while...) $ kubectl get pods NAME READY STATUS RESTARTS AGE preempting-pod 1/1 Running 0 70s
Node のメモリが不足したことにより、優先度が低い low-priority-pod
が Preemption されて終了したことが分かります。
次に、preempting-pod
を削除したうえで再度 low-priority-pod
を準備し、今度は non-preempting-pod
を作成してみます。
$ kubectl delete -f preempting-pod.yaml pod "preempting-pod" deleted $ kubectl create -f low-priority-pod.yaml pod/low-priority-pod created $ kubectl create -f non-preempting-pod.yaml pod/non-preempting-pod created (wait for a while...) $ kubectl get pods NAME READY STATUS RESTARTS AGE low-priority-pod 1/1 Running 0 48s non-preempting-pod 0/1 Pending 0 25s
今度は low-priority-pod
は Running 状態のまま、優先度の高い non-preempting-pod
が Pending になっています。これが preemptingPolicy: Never
の効果です。
その他の特筆すべき変更 (Other notable changes)
重複 Toleration の扱い
Best Effort の Pod に対して同じ key と effect を持った Toleration が複数指定されている場合、マージされて最後に指定された Toleration の値が有効になります。(#75985, @ravisantoshgudimetla)
気が付かないとわかりづらいですが、これは Admission Control で呼び出されるロジックのバグ修正です。
Admission Control のひとつ PodTolerationRestriction
は、Namespace のラベルとしてホワイトリストされた以外の Toleration が付いた Pod の作成を拒否します。
その過程で Toleration を走査するのですが、Pod の QoS が Best Effort 以外の場合には、以前から「メモリが枯渇気味の Node にも配置を避けない」という Toleration を付加する処理が行われており、ここでマージが行われていました。今回、Best Effort の場合でも同じマージ関数を通すための修正です。
なお Taint と Toleration の一般論については以下に解説があります。
実験してみましょう。今回もシングルノードクラスタを仮定します。また PodTolerationRestriction
はデフォルト無効なので、apiserver に --enable-admission-plugins=PodTolerationRestriction
を指定して有効にしておいてください。
まず、Node に Taint を付加します。この Node には mykey = myvalue
の Toleration を持つ Pod 以外は新たに配置されなくなります。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION kind-control-plane Ready master 64m v1.15.0 $ kubectl taint node kind-control-plane mykey=myvalue:NoSchedule node/kind-control-plane tainted
ここで、以下の Pod を考えます。
tolerant-pod.yaml
apiVersion: v1 kind: Pod metadata: name: tolerant-pod spec: containers: - name: tolerant-pod image: k8s.gcr.io/pause:2.0 tolerations: - key: mykey value: myvalue effect: NoSchedule - key: mykey value: dummy effect: NoSchedule
この Pod は mykey = myvalue
なる Toleration を持つため、一見すると配置可能なように見えます。試してみましょう。
$ kubectl create -f tolerant-pod.yaml pod/tolerant-pod created $ kubectl get pods NAME READY STATUS RESTARTS AGE tolerant-pod 0/1 Pending 0 38s
予想に反して、Pod は配置されず Pending のままになってしまいました。
では今度はふたつの Toleration の順序だけを入れ替えてみます。
tolerant-pod.yaml (edited)
apiVersion: v1 kind: Pod metadata: name: tolerant-pod spec: containers: - name: tolerant-pod image: k8s.gcr.io/pause:2.0 tolerations: - key: mykey value: dummy effect: NoSchedule - key: mykey value: myvalue effect: NoSchedule
同様に Pod を作成してみると、
$ kubectl delete -f tolerant-pod.yaml pod "tolerant-pod" deleted $ kubectl create -f tolerant-pod.yaml pod/tolerant-pod created $ kubectl get pods NAME READY STATUS RESTARTS AGE tolerant-pod 1/1 Running 0 18s
無事に Running になりました。
以上から、key
と effect
が同じで value
が異なる 2 種類の Toleration が同時に指定されているとき、後から指定されたものが有効になっていることが分かります。
PodAffinity のパフォーマンス改善
PodAffinity 指定時、Required および Preferred の両指定とも 2 倍のパフォーマンス向上を達成しました。(#76243, @Huang-Wei)
Pod の Affinity に基づいて各 Node をスコア付けする部分に手が入っています。
アルゴリズムを抜本的に変更したわけではなく、Node を走査する際に、今までループの外側で手動で取っていたロックを sync/atomic
パッケージ (GoDoc) に置き換えたようです。アトミックな加算 AddInt64 の使用に伴い、Node に対するスコアが int で保持されるようになっています。
なお Pod の Affinity については以下に解説があります。
競合状態の防止
高優先度の Pod に NominatedNodeName が登録されている際、その Node に対して低優先度の Pod をスケジュールしないようにすることで、競合状態が発生する問題を修正しました。(#77990. @Huang-Wei)
この変更ログの書き方はややミスリードで、今回、実際に入った変更は Pod のスケジューリングのロジックではありません。
NominatedNodeName は、Preemption 発生時にどの Node に対して Preemption を行ったかを記録する情報です。Preemption した側の Pod に付与されますが、必ずしも結果的にその Node に最終的に配置されることは保証していません。例えば、Preemption された低優先度 Pod の終了を待っているうちに他の Node に空きが出たり、逆に Preemption した Pod よりさらに高優先度の Pod が到着したりする状況も考えられます。
スケジューラは内部に Pod と NominatedNodeName の対応を保持しており、キュー内の Pod を操作すると同時にその対応を更新します。そして Premption 直後の特定のタイミングでは、Preemption した側の Pod の NominatedNodeName が空欄になった状態で更新関数が呼ばれるケースがあるようです。
今回の修正は、このような場合に NominatedNodeName が空欄で上書きされて消えてしまい、空いた Node に低優先度の Pod が横から入ることを防ぐためのものです。
まとめ
以上、Kubernetes v1.15.0 におけるリリースノートとその解説でした。押さえておくべき重要事項は次の 2 点です。
- Scheduling Framework に新しい拡張点が設定された
- Preemption は発生させない (ただしキュー内の順番としては優先される) PriorityClass の設定が可能になった
ところで、PriorityClass はもちろん Preemption 関連ですが、実は Scheduling Framework も Preemption と無関係ではありません。新しく実装された Queue sort プラグインを利用することで今まで難しかった Preemption の制御が可能になるのですが、それはまた別の話。
*1:なお 1.15.0 において --log-dir と --alsologtostderr が認識されていないように見えましたが、今回は深追いしていません。
Fun Fun Functional (1) で Haskell と Firebase を使ってライブコーディングしてきました
先日行われた Fun Fun Functional (1) で、Haskell と Firebase を使った Web アプリの作り方について発表してきました。
使用した要素技術は、GHCJS 上のフレームワーク Miso と、Fireabse SDK を呼び出すための DSL である JSaddle です。
GHCJS は Haskell のソースコードを JavaScript に変換するコンパイラで、GHC をフォークすることによって開発されています。
Miso は GHCJS 上で The Elm Architecture を実装するためのフレームワークです。Miso では事実上サーバサイドで書けない Elm と異なり、サーバサイドとの Model の共有や、初回アクセス時の HTML をサーバ側で構築するサーバサイドレンダリング (SSR) の仕組みが提供されています。
JSaddle は Haskell から JavaScript の関数を呼ぶための DSL です。Lens インタフェースに対応した JSM
モナドが提供されており、GHCJS でコンパイルした場合は単に IO
モナドに変換されます。
main :: IO () main = do -- console.log('Hello, JSaddle!'); jsg "console" ^. js1 "log" (val "Hello, JSaddle!")
これら要素技術の簡単な解説に加えて、LT の後半ではライブコーディングのパフォーマンスを披露しました。
作成したのは、簡単なコメントが残せるゲストブックを模した、以下のようなアプリです。バックエンドとして Firebase Realtime DB を利用しているため、複数のブラウザから開いた場合でも書き込みはリアルタイムで同期されます。
前掲のスライドは公開用にソースコードを補足してありますが、当日は文字通り、その場で段階的にコードを追記・変更することで機能が画面に反映されていく様子を紹介しました。
- まず動きのない HTML だけの View をレンダリングする
- ユーザからの入力に反応して Action を発行し、Update で Model に反映する
- Effect を利用してコメントを Realtime DB に保存する
- Subscription を利用して Realtime DB の変更を検知する
LT としてはあまり見ない形式ですが、Twitter 上で見る限り割と好評だったようです。こんな風に、実装の結果が目で見て分かりやすいのはフロントエンドの強みだなと改めて感じました。
(ちなみに @u_taka_23 ではなく @y_taka_23 です)最後は @u_taka_23 さんです!
— オプトテクノロジーズ (@OptTechnologies) May 27, 2019
なんとライブコーディングが始まりました...#FunFunFunctional pic.twitter.com/Yej8v4pG7a
チェシャ猫さんのライブコーディング素晴らしかった #miso #ghcjs #FunFunFunctional
— Yoshihiro503 (@yoshihiro503) May 27, 2019
ところで今回のライブコーディング、ハンズオン形式のチュートリアルとして仕立て直して公開しようかなとも思っているのですが、それはまた別の話。
Docker Meetup Tokyo #28 で Scheduler のカスタマイズについて話してきました
先日行われた Docker Meetup Tokyo #28で、Kubernetes Scheduler の挙動をカスタマイズする方法について発表してきました。
なお Scheduler のカスタマイズについては、つい最近 Kubernetes Meetup Tokyo #16 でも発表しています。ドキュメント類へのリンクも含めてまとめたものが以下の記事です。
両方のスライドを見比べて頂ければ分かる通り、内容としてはオーバラップしている部分がかなりあります。
ただし、前回はあくまでも Scheduling Framework の解説であったのに対し、今回は Scheduler のカスタマイズ全体を俯瞰する形で差別化を図っています。時間的にも前回 5 分に対して今回 9 分とやや余裕があったため、Amazon ECS の binpack 配置戦略やポリシの記述方法など、前回端折った具体的な設定についても少し触れてみました。
ちなみに、スライド中で挙げた 3 種類のカスタマイズ方法それぞれについて、ハンズオン形式で実装しながら学べるチュートリアルを作成するプランもあるのですが、それはまた別の話。
Kubernetes Meetup Tokyo #16 で Scheduling Framework について話してきました
先日行われた Kubernetes Meetup Tokyo #16 で、現在 Scheduling SIG で進められているプロジェクト Scheduling Framework について発表してきました。
Kubernetes では、Pod をどの Node に配置するかを決める手続きをスケジューリングと呼びます。
古典的な Kubernetes の用途、すなわち通常の long-running なサーバ群の管理においては、Pod のスケジューリングは比較的シンプルな問題でした。すなわち、Node の障害時でも可用性が保てるように Pod を複数の Node に散らし、一度立ち上がった Pod は基本的に動き続ける、というシナリオです。
しかし、Kubernetes は既にコンテナスケジューラのデファクトを獲得し、様々な性質を持ったアプリケーションがデプロイされる基盤となりました。この流れの中で、デフォルトの kube-scheduler ではカバーできないような複雑なスケジューリングの需要も生まれています。
この問題を解決するためのプロジェクトが Scheduling Framework です。本記事では、スライドに登場した用語や概念について、Scheduling SIG その他から提供されているドキュメントやサンプルを紹介します。
Scheduling SIG によるサブプロジェクト
kube-batch
kube-batch はバッチ処理向けのスケジューラで、かつて kube-arbitrator と呼ばれていたプロジェクトが改名しました。たった 1 行だけの Wiki が味わい深いです。
PodGroup
を指定することで複数の Pod を組にして扱う機能を備えているのが特徴で、この仕組みは Kubernetes を機械学習用基盤として使用する kube-flow プロジェクトにも導入されるようです。
Poseidon
Poseidon は、グラフ理論を利用して複雑かつ効率的なスケジューリングを目指す新機軸のプロジェクトです。
元になっているのは Firmament Scheduler と呼ばれる汎用のタスクスケジューリングの仕組みで、配置の制約を最小費用流問題に帰着させて最適化を行うようです。Kubernetes の Pod と Node に対して Firmament を実装したものが Poseidon という関係になっています。
正直なところ、この記事を書いている時点ではまだ論文を充分読み込んでいないので何とも言えませんが、内容がきちんと把握できたら改めてどこかのイベントで発表したいと思います。
Scheduler Extender
スケジューリング処理の一部分を JSON Webhook で外部サーバに委譲するための仕組みが Extender です。
Extender が設定できる処理は以下の 4 か所で、Scheduler 起動時の設定ファイルに外部サーバのエンドポイントなどを記述することで有効になります。
- Filter - 後段の順序付けフェイズに候補として残す Node を絞ることができます。リクエストはデフォルトのフィルタリングプロセスを通過した Pod、レスポンスはフィルタリング後の Pod です。
- Prioritize - 最良の Node を選択するためのスコアリング関数を追加できます。引数はフィルタリングで残った Pod、レスポンスは Node ごとのスコアです。
- Preempt - Pod の Preemption が発生する際に、犠牲となる Pod を消極的に選択することができます。リクエストは Node ごとに犠牲となる予定の Pod、レスポンスは実際に犠牲にする Pod です。
- Bind - Pod を Node に配置する前の準備などを行うことができます。ただしその場合、Pod と Node を結びつけている
Binding
リソースの作成まで含めて自分で行う必要があります。Pod の情報と Node 名がリクエストされます。
なお、上記のドキュメントは古いためか Preempt Extender の記載がありませんが、ソースコードを見ると実装されていて実際に動きます。
もし Extender を使用したい場合は、ドキュメントよりもむしろ、構造体の定義および @everpeace さんによる実装サンプルを参考にするとよいでしょう。
Scheduling Framework
Scheduling Framework のプロポーザルです。今回のスライドは基本的にこのプロポーザルに沿って説明しています。
上でも挙げたような特定用途のために作られたスケジューラは、基本的にスクラッチから開発されています。というのも kube-scheduler の実装は歴史的事情から抽象化が十分でなく、拡張性に限界があるためです。
そこで、Scheduling Framework では設計を刷新して新しい拡張点を定式化します。それぞれの拡張点では、Go のインタフェースとして実装されたプラグインを登録し処理を差し込めるようにするとともに、プラグイン同士が情報をやり取りできる仕組みが構築される予定です。
そう、あくまでも予定です。 プロポーザルには多数の拡張点が述べられていますが、現状で以下の二点のみ実装されています。
- Reserve - Node が確定した後、Pod ごとの goroutine が生成される前
- Pre-Bind - Pod ごとの goroutine に入った後、API Server に Binding が登録される前
この二点は、Pod を配置する Node に前もって何かを準備する処理を想定したもので、典型的な用途はネットワークストレージをボリュームとして確保するというものです。
従来、ボリュームの確保とバインドは assumeVolume
と bindVolume
という専用の関数で行われています。一方 GPU などボリューム以外のネットワークリソースについてはユーザが Bind Extender という形で各自実装する必要があります。
そこで、assumeVolume
に相当するものとして Reserve プラグインを、bindVolume
に相当するものとして Pre-Bind プラグインを実装し、リソース関連の処理を統合して抽象化することが Scheduling Framework の第一弾として意図されていました。が、今のところ拡張点が準備されただけで止まっており、assumeVolume
も bindVolume
もそのまま残っています。まあ、今後の展開を見守りましょう……。
まとめ
本記事では、Kubernetes スケジューラの再設計を目的としたプロジェクト Scheduling Framework と、その周辺トピックについて紹介しました。Scheduling SIG のマンパワー不足もあって、当初の予定ほど華々しい進展が見られないのは残念ですが、それはまた別の話。
Kubernetes 1.13: SIG Scheduling の変更内容
はじめに
本記事では、Kubernetes 1.13 の CHANGELOG からスケジューリングに関する内容をまとめました。
主な変更点
1.13 における SIG Scheduling の取り組みは主に安定性に焦点を当てており、いくつかの大きな機能の導入は次のバージョンまで延期することになりました。特記すべき変更として次に挙げる 2 点があります。
#69824: Taint based Eviction の有効化
TaintBasedEvictions
がベータに移行し、デフォルトで有効になりました。この機能が有効になっている場合、Node には自動的に条件 Taint が付加され、Pod は必要であれば Toleration を使用することができます。
Taint based eviction は、Node に問題が発生した際、その内容に応じて Node Controller が以下のような Taint を自動的に付加する仕組みです。
node.kubernetes.io/not-ready
node.kubernetes.io/unreachable
node.kubernetes.io/out-of-disk
node.kubernetes.io/memory-pressure
node.kubernetes.io/disk-pressure
node.kubernetes.io/network-unavailable
node.kubernetes.io/unschedulable
node.cloudprovider.kubernetes.io/uninitialized
今まで Pod のスケジューリングには「Not Ready な Node を避ける」といったロジックが入っていました。1.13 からこの TaintBasedEvictions
がデフォルトで有効になったことにより、障害時の Pod 退避は Taint による管理に統一されます。
Taint と Tolaration によるスケジューリングに統一されることで、Node 障害時の挙動をユーザがより柔軟にコントロールできるようになります。例えば Pod に tolerationSeconds
を指定することで「Node に問題 X が発生した際は n 秒以内に回復しなければ移動」といった挙動の調整が可能です。
tolerations: - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" tolerationSeconds: 6000
ちなみに、tolerationSeconds
が設定されていない場合、Admission Control により not-ready
と unreachable
に 300 秒の tolerationSeconds
が設定されます。要するに何も設定していない場合は Node の障害から最大 300 秒待って Pod が削除される、ということです。
#70298: critical-pod
アノテーションが非推奨に
Pod に対するクリティカルアノテーションが非推奨になりました。アノテーションの代わりに Pod の優先度を使用すべきです。
DNS や Metrics Server といった死なれるとクラスタ全体の動作に影響するような Pod のために、従来 scheduler.alpha.kubernetes.io/critical-pod
というアノテーションが用意されていましたが、今回から非推奨になりました。
代わりに、デフォルトで定義されている優先度クラス system-cluster-critical
と system-node-critical
を使用します。両者の定義は以下のようになっており、Node の移動が許容できるかどうかで用途が分かれています。
Name: system-cluster-critical Value: 2000000000 GlobalDefault: false Description: Used for system critical pods that must run in the cluster, but can be moved to another node if necessary. Annotations: <none> Events: <none> --- Name: system-node-critical Value: 2000001000 GlobalDefault: false Description: Used for system critical pods that must not be moved from their current node. Annotations: <none> Events: <none>
ただしこれらの優先度クラスは、1.11 以降 kube-system
Namespace 内でしか使えないことに注意が必要です。
- https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/
- https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/
また、この件とは直接関係しませんが、優先度による Preemption の動作原理については半年ほど前に書いた記事があるのでよければこちらもご笑覧ください。
#70040: 要対応
1.13 では kube-scheduler の設定ファイルの
apiVersion
がcomponentconfig/v1alpha1
ではなくkubescheduler.config.k8s.io/v1alpha1
になります。
API グループ componentconfig
は解体されつつあり、Scheduler 以外にも例えば kubeproxy.config.k8s.io
が 1.9 から導入されています。
しかし、そもそもこの設定ファイルの書式はドキュメントに記載されていないし、サンプルファイルのようなものも提供されていません。対応する構造体が以下に定義されているので、ファイルの記載項目を確認することは一応可能です。
SIG Scheduling リリースノート
メトリクス追加を除き、内部ロジックの修正のみです。
#59529
ボリュームをスケジューリングする操作にメトリクスが追加されました。
以下のメトリクスが登録されるようになっています。
binder_cache_requests_total
scheduling_duration_seconds
scheduling_stage_error_total
#65350
Toleration を含む大量の Pod を処理する際のメモリ使用量とパフォーマンスが向上しました。
#69758
ゾーン内の Node がすべて削除された際、スケジューラが無限ループに陥るバグを修正しました。
#71212
Pod のバインディングでエラーが発生した際、古いキャッシュが使用されないよう削除するようにしました。
#71063
スケジューラ内部のキャッシュが不整合になった際、panic になる挙動を修正しました。
#71085
kube-scheduler のリーダ選出がデッドロックに陥った際、unhealthy を報告するようになりました。
#70898
必要のない Pod まで preemption してしまう潜在的なバグを修正しました。
まとめ
ユーザ側にとってさほど大きな変更点はありませんが、これまで Pod の優先度を導入しないまま運用していた場合、critical-pod
アノテーションの件で影響を受ける可能性があります。まだ非推奨になっただけで廃止ではありませんが、早めに変更しておきましょう。
なお、このところ機能追加という意味では SIG Scheduling はやや低調で、実際マンパワーが足りていない印象がありますが、水面下では興味深い動きもいくつか見られます。
- スケジューラに拡張点を設けてカスタマイズ可能にする Scheduling Framework
- 複数の Pod を同時に All or Nothing でスケジューリングする coscheduling(旧称 Gang Scheduling)
あたりが目下のところ予定されている大きな機能追加ですが、それはまた別の話。
We Are JavaScripters! @26th で Elm と Firebase の連携について話してきました
先日行われた We Are JavaScripters! @19th で Elm と JavaScript ライブラリの連携について発表してきました。
Elm の初心者向けの解説としてよく Msg, Model, update からなるアーキテクチャが挙げられていますが、今回の発表ではもう一歩だけ進んで、Cmd と Sub を使って Elm から JavaScript のライブラリを呼ぶ方法について解説しました。
サーバとしての JS ライブラリ
他の AltJS では JavaScript を呼び出す際、ソースコードの内部に埋め込む形になるのが普通です。
例えば Haskell を JavaScript にコンパイルする GHCJS の場合、JSaddle という DSL を利用して次のように呼び出すことになります。
store :: Int -> IO () store n = runJSaddle () $ do ref <- jsg "firebase" ^. js0 "database" ^. js1 "ref" (val "/counter") ref ^. js1 "set" (val n) return ()
Firebase SDK を呼び出して Realtime DB に値をセットする部分です。JavaScript 側の関数を文字列で指定することで、直接 Haskell のソースコード内に IO
アクションとして JS の呼び出しが定義されていることがわかります。この場合、Haskell とは別に JavaScript 側を自分で実装する必要はありません。
一方、Elm で同様の呼び出しを実装する場合、「Port」「Command」「JavaScript 側での subscribe」という 3 つの部分に分割されます。
まず、JavaScript 側へのインタフェースとなる Port を次のように定義します。
port module RealtimeCounter.Port exposing (store) import Json.Encode as E port store : E.Value -> Cmd msg
JSON を受け取って、Cmd を発生させる関数 store
が定義されています。port
に指定された関数は型シグネチャだけが存在し、中身は記述しません。
次に、実際に update
関数の中でこの Cmd を発生させるために、Elm の Int
値を JSON エンコードしてこの port に流し込む関数を定義します。
import Json.Encode as E storeCount : Int -> Cmd msg storeCount = store << E.int
最後に、Elm 側から発生した Cmd を受け取るための JavaScript を実装します。
const { Elm } = require('./Main.elm'); const app = Elm.Main.init({ node: document.getElementById('app') }); app.ports.store.subscribe((count) => { firebase.database().ref('count').set(count); });
上に挙げたコールバックによる実装を見ても分かる通り、JavaScript 側はあたかも Web サーバのコントローラのように subscribe
で待ち受けており、Elm から Cmd によって JSON が渡されると実際に Realtime DB に値をセットします。
逆に、JavaScript 側から Elm 側に値を戻す必要がある場合には、JavaScript 側で send
関数を使用すると Elm 側からは Sub となって観測されます。具体的には下記のサンプルレポジトリを参照してください。
なお、実際に JS 連携する部分をコーディングする際には、boiyaa さんが書かれている詳しい記事も参考になると思います。
boiyaa さんの記事は Elm v1.18 時点で書かれているので起動時に Html.programWithFlags
を使用していますが、Elm v1.19 ではここに相当する関数は Browser.element
に変更になっています。
動作サンプル
実際に触って動かせるデモもデプロイしておきました。先に挙げたソースコードと合わせて、よかったら参考にしてみてください。
プレゼンの際にお見せしたデモは単にテキストを置いただけの画面でしたが、公開版はちょっと凝った CSS を付けてみました。カウンタ自体の Elm 側のロジックは非常にシンプルで行数も少ないので、GitHub からは Elm ではなく CSS のレポジトリ扱いされてしまっていますが、それはまた別の話。