前回でPodも起動できたことだし、ここらでややこしくも重要なPodの配置管理についてやってしまいます。
細かい説明は他サイトにお任せリンクにして、ここでは例えばどういう意図で配置を考えるのか、Terraformで管理するとどういうコードになるのか、というのを確認できればと思います。
Pod配置を管理する意図と手段
意図
Pod配置とは何のことかというと、PodをどのNode もしくは、どのNodeの集合体に起動するかを、任意で決めるということです。配置することを、スケジュールとも呼んでいるようですね。なぜ、あるPodが載るNodeを選ぶことがあるかというと、まずNode自体には色んな条件があります。インスタンスタイプ、AZ、オンデマンド/スポット、Autoscalingする/しない、などなど。そしてPodにも色んな特性があります。複数Podのうち一部が落ちても大丈夫だったり、管理系のできるだけ継続稼働してほしいタイプだったり。
EKS Nodeは、EC2に特定のタグさえついていればNodeとして参加できるので、色んな条件のNodeが混在していてもクラスタとしては稼働するのですが、Podをポンと立ち上げた時に、何もしなければどのNodeに起動されるかはその時次第になってしまいます。Podの特性に対して、Node条件がそぐわない状況は当然作り出したくないわけで、色んなタイプのPodが出現してきて、でも色んなタイプのNodeも扱いたい、そんなワガママクラスタを実現したい時に任意で配置操作をしたくなるのです。
今回の場合、というかKubernetesを管理していると、色んな管理系のPodが出現してきます。メトリクス管理するやつとかですね。そういうPodが、Spot Node に所属して突然ダウンしたり、Autoscaling の Decrease によって削除されても、Podとしては新たに自動起動するわけです。が、一時的にいなくなる可能性がそれなりにあり、ダウンタイムも数十秒単位であるとしたら、そんな環境を許容していいわけがありません。
私の場合、大きく2つに role という形で分けることにしました。1つが master で、常駐し続けてほしい管理系Pod用。もう1つが scaler で、Autoscaling による自動増減が発生して大丈夫なPod用です。
これが言うだけなら簡単なのですが、結構実現するのに苦労したのは、EKSならではという部分もあったように思います。
手段
色々あるので詳しくは下記リンクをみていただきたいのですが、- KubernetesのNode Affinity, Inter-Pod Affinityについて – Qiita
- KubernetesのTaintsとTolerationsについて – Qiita
- Kubernetes道場 17日目 – Label / NodeSelector / Annotationについて – Toku's Blog
- Kubernetes道場 18日目 – Affinity / Anti-Affinity / Taint / Tolerationについて – Toku's Blog
- Kubernetes勉強会第5回 〜node affinity, pod affinity、Init containers、lifecycle hooks〜
私が今のところ使っているのは、NodeSelector, NodeAffinity, Taints/Tolerations の3つです。それらについて、Terraformコードの抜粋とともに補足説明していきます。
NodeSelector
これは非常にシンプルで、特定のラベルのKeyValueが付与されているNodeに、Podを起動するという指定になります。つまり普通の手順でやれば、先にNodeにラベルをつけておいて、Deployment でそれを指定するということになります。対象となるNodeがなければPodが起動せず、その旨のエラーログが kubectl describe deployment などで確認できます。Nodeへの任意のラベルの付与は、AutoscalingGroup作成とUserDataのスクリプトにて、–kubelet-extra-args –node-labels=${role_label_key}=$ROLE_VALUE をつけるのを紹介しました。変数名を具体的な値にすると、–node-labels=kubernetes.io/role=master とかになります。
この、時々出てくる kubernetes.io/role のような形式のキーは、普通にクラスタを使う分にはあまり登場しないのですが、今回のようなチョット特殊なことをやろうとすると出てきます。このキー名、完全に任意でつけても良い場合もあるのですが、公式?として利用しているキー名の場合があり、role の場合はまさにそれになります。私の場合、開発中に任意でこのキー名をつけて動かしていたあとに、ドキュメントをみて同名を扱っているのをみて、さすがワイやでって一人心の中でドヤってました。
そのNodeのラベルに対して、Deployment側ではこんな感じで指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
locals { eks_role_label_key = "kubernetes.io/role" } resource "kubernetes_deployment" "main" { ... spec { ... template { ... spec { node_selector = { "${local.eks_role_label_key}" = "scaler" } ... } |
NodeAffinity
こちらはNodeSelectorの、より柔軟なラベル指定という感じです。複数のラベルを扱ったり、値に対して条件を指定することができます。あとで DaemonSet を扱う時に出てきますが、コード抜粋としてはこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
resource "kubernetes_daemonset" "node_manager" { ... spec { ... template { ... spec { ... affinity { node_affinity { required_during_scheduling_ignored_during_execution { node_selector_term { match_expressions { key = "beta.kubernetes.io/os" operator = "In" values = ["linux"] } match_expressions { key = "beta.kubernetes.io/arch" operator = "In" values = ["amd64"] } } } } } |
これ、何をしているかというと、EKS Nodeとして起動したNodeには、EKSがこの2つのラベルを自動的につけるので、それに対応するようにマッチさせています。
この辺は結構踏み込んでやっと存在に気づいたりするやつです。初めてNodeの describe を眺めてみて、お、なんや、つけてもいないラベルがぎょーさんついとるやないか。って記憶の片隅に置いておくと、あとで突然役に立ったりするでしょう。
Taints/Tolerations
これは非常に面白い仕組みで、Node側につけたTaintsを、Pod側のDeployment等がTolerationsによって許容する、という意味。最初はとってもチンプンカンプンです。まずNode側としては、Taintsを付与することで、いったん全てのPodスケジュールを拒否するような状態になります。Taint = 汚染、汚れ なので、『アタイは汚れちまったのさ、こないでおくれっ』というイメージです。一部のNodeに付与した場合、Podはそこ以外に起動するし、全てのNodeに付与した場合はどこにもPodを起動できなくなります。
それに対し、Pod側として Tolerations を付与することでNodeに起動できるようになります。Toleration = 寛容、容認 なので、『そんなオメェでも、オレァかまわねぇよっ』と歌舞いていくスタイルです。
例えば、こういう Taints を Node につけたとすると
1 2 3 4 5 6 7 |
$ kubectl describe nodes -o yaml # の一部 ... taints: - effect: NoSchedule key: node-role.kubernetes.io/master - effect: NoSchedule key: CriticalAddonsOnly |
EKSが勝手に起動する deployment : coredns は、このような指定なので、上記Nodeへ強制的に配置されることになります。
1 2 3 4 5 6 7 |
$ kubectl get deployment coredns --namespace kube-system -o yaml ... tolerations: - effect: NoSchedule key: node-role.kubernetes.io/master - key: CriticalAddonsOnly operator: Exists |
全Nodeに Taints を付与し、全Podに Tolerations をつけることで、Podの起動先を完全に管理することができます。これは、Taints の拒否が強力なために最初は煩わしく感じるかもしれませんが、自分が取りたい構成によっては、その強力さが頼もしく感じるでしょう。
Taints は Node の UserData でつけているので、Tolerations は Terraform でつけましょう。注意点として、kubernetes_deployment 等で toleration を使えるようになったのが今夏あたりからなので、エラーが出るなら terraform init -upgrade で更新してから実行しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
resource "kubernetes_deployment" "main" { ... spec { ... template { ... spec { ... toleration { effect = "NoSchedule" key = "node-role.kubernetes.io/master" } toleration { operator = "Exists" key = "CriticalAddonsOnly" } } |
こうすると、ストライクゾーン全開放のイケメンができあがります。本当にどこでもよければこれでいいし、全許容しつつ NodeSelector で指定するのもシンプルでよいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
resource "kubernetes_deployment" "main" { ... spec { ... template { ... spec { ... toleration { operator = "Exists" } } |
利用用途
なぜ急にこんな小難しい仕組みを紹介したかというと、すでに書いた Deployment でも実は使っていますし、この先に出てくる helm パッケージによるPodにも適用するからです。いちいち削ってコピペすると、わかりづらくなっていくので。本当はこのような複雑気味な仕組みをいくつも併用したくなかったのですが、これにはワケがあります。
大きな理由としては1つで、既に書いた通り、管理系とスケール系で分けたかったということ。これは AutoscalingGroup を2つに分けることで実現しています。
それに対し、1つ1つリソースを構築していく中で、Podの管理タイプが複数あることがわかってきました。
この中で特にやっかいなのが EKS が起動する coredns です。こいつはとにかく起動できそうなNodeに 2つ のPodをパッと起動しようとします。名前の通りDNS機能を所持しているので、リスキーなNodeに置くわけにはいかず、安全なNodeに案内してあげる必要があります。
しかし、Deoloyment そのものを EKS に支配されているので、NodeSelector や Tolerations を指定できるものではなく、ではどうするかというと 既につけておいてくれてある Tolerations を利用するしかない、ということになります。
その理由を主軸に、自身が任意で起動する Pod や、helm による Pod も master/scaler の役割で配置しようとすると、これらの配置機能を多用することになる、というわけです。
途中は coredns に悩まされた期間もありましたが、終わってみればこれらの機能は、中規模以上のクラスタにおいて、おそらく不可欠な存在であることがわかったので、結果オーライという感じです。
私の場合、スポットインスタンスとAutoscalingの利用を前提として設計するので、どうしても複雑気味な仕組みを取り入れることになってしまいます。必ずしも必要な仕組みというわけではないと思いますが、確実なコントロール下に置けるというのは運用において強みなので、改善や拡張など先を見据えるならば、最初から入れてしまうほうが結果的に楽になりそうな気がします。
そーいえば、coredns は2Pod起動するのに AZ も同一になっちゃう問題とか、一部で囁かれていましたが、そういうところがどーなっていくかを見守るのもKubernetesの楽しみ方の一つだとかかんとか。