チェシャ猫の消滅定理

数学にプログラミング、素敵なもの何もかも。

Docker Meetup Tokyo #31 で Kubernetes 1.15 について話してきました

先日行われた Docker Meetup Tokyo #31 で、Kubernetes 1.15 の Scheduler 周りの新機能について発表してきました。

Kubernetes の Pod Preemption を利用すると、より重要な Pod にノードの計算リソースを割り当てる優先的に割り当てることができ、コストの最適化につながります。しかし優先度の低い Pod は実行中に強制的に終了されることとなり、長時間かかるバッチ処理が途中で中断されてしまうという弊害もあります。

本スライドでは、Kubernetes 1.15 から Alpha 機能として導入された NonPreemptingPriorityScheduling Framework を利用して、中断されたくない Pod に対する Preemption を抑制する手法を提案します。

その他の変更点も含めて、1.15 のリリースノートの完全な解説については以下の記事を参照してください。

ccvanishing.hateblo.jp

参考資料

Scheduler 一般

Priority と Preemption

Scheduling Framework

余談

思えば 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-prioritynon-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 になりました。

以上から、keyeffect が同じで 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 です。

GHCJSHaskellソースコードJavaScript に変換するコンパイラで、GHC をフォークすることによって開発されています。

github.com

Miso は GHCJS 上で The Elm Architecture を実装するためのフレームワークです。Miso では事実上サーバサイドで書けない Elm と異なり、サーバサイドとの Model の共有や、初回アクセス時の HTML をサーバ側で構築するサーバサイドレンダリング (SSR) の仕組みが提供されています。

github.com

ccvanishing.hateblo.jp

JSaddleHaskell から JavaScript の関数を呼ぶための DSL です。Lens インタフェースに対応した JSM モナドが提供されており、GHCJS でコンパイルした場合は単に IO モナドに変換されます。

main :: IO ()
main = do
    -- console.log('Hello, JSaddle!');
    jsg "console" ^. js1 "log" (val "Hello, JSaddle!")

github.com

これら要素技術の簡単な解説に加えて、LT の後半ではライブコーディングのパフォーマンスを披露しました。

作成したのは、簡単なコメントが残せるゲストブックを模した、以下のようなアプリです。バックエンドとして Firebase Realtime DB を利用しているため、複数のブラウザから開いた場合でも書き込みはリアルタイムで同期されます。

f:id:y_taka_23:20190529193951p:plain

前掲のスライドは公開用にソースコードを補足してありますが、当日は文字通り、その場で段階的にコードを追記・変更することで機能が画面に反映されていく様子を紹介しました。

  1. まず動きのない HTML だけの View をレンダリングする
  2. ユーザからの入力に反応して Action を発行し、Update で Model に反映する
  3. Effect を利用してコメントを Realtime DB に保存する
  4. Subscription を利用して Realtime DB の変更を検知する

LT としてはあまり見ない形式ですが、Twitter 上で見る限り割と好評だったようです。こんな風に、実装の結果が目で見て分かりやすいのはフロントエンドの強みだなと改めて感じました。

(ちなみに @u_taka_23 ではなく @y_taka_23 です)

ところで今回のライブコーディング、ハンズオン形式のチュートリアルとして仕立て直して公開しようかなとも思っているのですが、それはまた別の話。

Docker Meetup Tokyo #28 で Scheduler のカスタマイズについて話してきました

先日行われた Docker Meetup Tokyo #28で、Kubernetes Scheduler の挙動をカスタマイズする方法について発表してきました。

なお Scheduler のカスタマイズについては、つい最近 Kubernetes Meetup Tokyo #16 でも発表しています。ドキュメント類へのリンクも含めてまとめたものが以下の記事です。

ccvanishing.hateblo.jp

両方のスライドを見比べて頂ければ分かる通り、内容としてはオーバラップしている部分がかなりあります。

ただし、前回はあくまでも 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

github.com

kube-batch はバッチ処理向けのスケジューラで、かつて kube-arbitrator と呼ばれていたプロジェクトが改名しました。たった 1 行だけの Wiki が味わい深いです。

PodGroup を指定することで複数の Pod を組にして扱う機能を備えているのが特徴で、この仕組みは Kubernetes機械学習用基盤として使用する kube-flow プロジェクトにも導入されるようです。

Poseidon

github.com

Poseidon は、グラフ理論を利用して複雑かつ効率的なスケジューリングを目指す新機軸のプロジェクトです。

元になっているのは Firmament Scheduler と呼ばれる汎用のタスクスケジューリングの仕組みで、配置の制約を最小費用流問題に帰着させて最適化を行うようです。Kubernetes の Pod と Node に対して Firmament を実装したものが Poseidon という関係になっています。

正直なところ、この記事を書いている時点ではまだ論文を充分読み込んでいないので何とも言えませんが、内容がきちんと把握できたら改めてどこかのイベントで発表したいと思います。

Scheduler Extender

github.com

スケジューリング処理の一部分を 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

github.com

Scheduling Framework のプロポーザルです。今回のスライドは基本的にこのプロポーザルに沿って説明しています。

上でも挙げたような特定用途のために作られたスケジューラは、基本的にスクラッチから開発されています。というのも kube-scheduler の実装は歴史的事情から抽象化が十分でなく、拡張性に限界があるためです。

そこで、Scheduling Framework では設計を刷新して新しい拡張点を定式化します。それぞれの拡張点では、Go のインタフェースとして実装されたプラグインを登録し処理を差し込めるようにするとともに、プラグイン同士が情報をやり取りできる仕組みが構築される予定です。

そう、あくまでも予定です。 プロポーザルには多数の拡張点が述べられていますが、現状で以下の二点のみ実装されています。

  • Reserve - Node が確定した後、Pod ごとの goroutine が生成される前
  • Pre-Bind - Pod ごとの goroutine に入った後、API Server に Binding が登録される前

この二点は、Pod を配置する Node に前もって何かを準備する処理を想定したもので、典型的な用途はネットワークストレージをボリュームとして確保するというものです。

従来、ボリュームの確保とバインドは assumeVolumebindVolume という専用の関数で行われています。一方 GPU などボリューム以外のネットワークリソースについてはユーザが Bind Extender という形で各自実装する必要があります。

そこで、assumeVolume に相当するものとして Reserve プラグインを、bindVolume に相当するものとして Pre-Bind プラグインを実装し、リソース関連の処理を統合して抽象化することが Scheduling Framework の第一弾として意図されていました。が、今のところ拡張点が準備されただけで止まっており、assumeVolumebindVolume もそのまま残っています。まあ、今後の展開を見守りましょう……。

まとめ

本記事では、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-readyunreachable に 300 秒の tolerationSeconds が設定されます。要するに何も設定していない場合は Node の障害から最大 300 秒待って Pod が削除される、ということです。

#70298: critical-pod アノテーションが非推奨に

Pod に対するクリティカルアノテーションが非推奨になりました。アノテーションの代わりに Pod の優先度を使用すべきです。

DNS や Metrics Server といった死なれるとクラスタ全体の動作に影響するような Pod のために、従来 scheduler.alpha.kubernetes.io/critical-pod というアノテーションが用意されていましたが、今回から非推奨になりました。

代わりに、デフォルトで定義されている優先度クラス system-cluster-criticalsystem-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 内でしか使えないことに注意が必要です。

また、この件とは直接関係しませんが、優先度による Preemption の動作原理については半年ほど前に書いた記事があるのでよければこちらもご笑覧ください。

ccvanishing.hateblo.jp

#70040: 要対応

1.13 では kube-scheduler の設定ファイルの apiVersioncomponentconfig/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 を呼び出す際、ソースコードの内部に埋め込む形になるのが普通です。

例えば HaskellJavaScriptコンパイルする 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 となって観測されます。具体的には下記のサンプルレポジトリを参照してください。

github.com

なお、実際に JS 連携する部分をコーディングする際には、boiyaa さんが書かれている詳しい記事も参考になると思います。

boiyaa さんの記事は Elm v1.18 時点で書かれているので起動時に Html.programWithFlags を使用していますが、Elm v1.19 ではここに相当する関数は Browser.element に変更になっています。

動作サンプル

実際に触って動かせるデモもデプロイしておきました。先に挙げたソースコードと合わせて、よかったら参考にしてみてください。

f:id:y_taka_23:20181124221326p:plain デモページ

プレゼンの際にお見せしたデモは単にテキストを置いただけの画面でしたが、公開版はちょっと凝った CSS を付けてみました。カウンタ自体の Elm 側のロジックは非常にシンプルで行数も少ないので、GitHub からは Elm ではなく CSS のレポジトリ扱いされてしまっていますが、それはまた別の話。