EKS on Fargate:virtual-kubelet の違い + Network/LB 周りの調査
EKS on Fargate
こんにちは。 サイバーエージェントの青山(@amsy810)です。
この記事は Kubernetes3 Advent Calendar の 4日目の記事です(EKS #2 にもクロスポストしています)。 re:Invent で EKS 関連の何かしらの発表がされることを見越して Advent Calendar を埋めたので、書くネタが見つかってホッとしています。
KubeCon 会期中に 「Managed Worker Node for EKS」 がリリースされ歓喜の声が上がりましたが、今回は re:Invent で 「EKS on Fargate」 がリリースされ歓喜の声が上がっているようです。 今回は EKS on Fargate のアーキテクチャを見ていきたいと思います。virtual-kubelet と近いと思ってますが果たして。 (EKS on Fargate の詳細な記事はプロの誰かが書くと思うので、私は主に virtual-kubelet との対比と、大好きなネットワーク周りの軸でかければと思います。)
EKS on Fargate とは?
EKS on Fargate は 12/3 PST にリリースされた、AWS Fargate をバックエンドとして、Kubernetes Pod を起動することができる機能です。Fargate がいることにより、EC2インスタンスを管理する必要がなくなります。よしなにやっておいてほしい人からすると嬉しいのではないでしょうか。 https://aws.amazon.com/jp/blogs/aws/amazon-eks-on-aws-fargate-now-generally-available/
ご存知の方もいるかと思いますが、ここまで聞くと類似プロダクトとして、OSS で開発が進められている virtual-kubelet(https://github.com/virtual-kubelet/virtual-kubelet) のようにも聞こえますね。virtual-kubelet は、Kubernetes の Node として1台の仮想的なノードを追加しておきます。その仮想ノードの後ろには、膨大なコンピューティングリソースを持ったコンテナ実行基盤(プール)のようなものが待ち構えており、その仮想ノードにスケジューリングされた Pod はそのリモートノード上で Pod が実行されるようになります。代表的な実装例としては、Microsoft Azure の ACI などでしょうか。
virtual-kubelet のプロバイダーの中に AWS Fargate Provider もあるのを把握していたため、これが EKS on Fargate だと思っていましたが、どうやら違うようです。 リポジトリ(https://github.com/virtual-kubelet/aws-fargate)をみても、半年〜1年程度更新がないため、virtual-kubelet 経由のものよりかは、EKS on Fargate を使っていくべきでしょう。
EKS on Fargate の検証
検証環境は下記のとおりです。
- eksctl: 0.11.0-rc.0
- Kubernetes: v1.14.8-eks
クラスタの構築時に --fargate オプションを利用することで構築可能です。リリース時点から ap-northeast-1 でもデプロイ可能でした。
$ eksctl create cluster --name amsy810-cluster --region ap-northeast-1 --fargate
しばらくすると、EKS クラスタの構築が完了します。初期段階では coredns だけ起動している状態なため、2 Pod だけが立ち上がっている状態です。
# (一部アウトプットを省略) $ kubectl get pods -A -o wide NAME READY STATUS IP NODE coredns-6d75bbbf58-5jn27 1/1 Running 192.168.129.12 fargate-ip-192-168-129-12.ap-northeast-1.compute.internal coredns-6d75bbbf58-96nbt 1/1 Running 192.168.166.59 fargate-ip-192-168-166-59.ap-northeast-1.compute.internal
ノードの状態を見てみると 下記の通り 2 台だけ起動している状態です。
# (一部アウトプットを省略) $ kubectl get node -o wide NAME STATUS ROLES VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME fargate-ip-192-168-129-12.ap-northeast-1.compute.internal Ready <none> v1.14.8-eks 192-168-129-12 <none> Amazon Linux 2 4.14.152-127.182.amzn2.x86_64 containerd://1.3.0 fargate-ip-192-168-166-59.ap-northeast-1.compute.internal Ready <none> v1.14.8-eks 192-168-166-59 <none> Amazon Linux 2 4.14.152-127.182.amzn2.x86_64 containerd://1.3.0
EKS on Fargate では、virtual-kubelet とは異なり Pod 1つにつき 1 Node が展開される ような形になっています。virtual-kubelet の場合には、「kubectl get node」した際に、1 つの大きな 仮想 Node だけが存在しており、その仮想 Node 上で複数の Pod が起動するような形になるため、異なるポイントです。また、Fargate Node の IP と Pod の IP は同じになっています。他にも、各 Node の Pod Capacity が 1 になっていたり、「eks.amazonaws.com/compute-type=fargate:NoSchedule」 taints も付与されていました。
なお、当然ですが下記のような 2 Container in 1 Pod な Pod も、1 つの Fargate Node にデプロイされます。
cat << _EOF_ | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: sample-2pod spec: containers: - name: nginx-container image: nginx:1.12 - name: redis-container image: redis:3.2 _EOF_
# (一部アウトプットを省略) $ kubectl get pods sample-2pod -o wide NAME READY STATUS IP NODE sample-2pod 2/2 Running 192.168.151.223 fargate-ip-192-168-151-223.ap-northeast-1.compute.internal
Fargate で起動する Pod の選択
EKS on Fargate では、通常ノードではなく Fargate で起動する Pod を設定しておきます。例えば、eksctl で作られるクラスタのデフォルト では default namespace と kube-system namespace にスケジューリングされる Pod は Fargate 側で実行されるようになっています。そのため、事前に条件を設定していない Namespace や Pod Selector にマッチしていない Pod の場合には、Fargate 側で実行されない点だけ注意が必要です。現状だとすべての Namespace という設定はできなさそうですが、いずれ対応されるんじゃないかなと思います(12/4 18:00 追記:既に Tori さんから Issue 化されていると教えていただきました!流石!)。設定は EKS の管理画面または「aws eks create-fargate-profile」コマンドから設定可能です。
ちなみに少し細かい話です。最初は MutatingWebhook 等で scheduling Policy(nodeAffinity、taints 等)を書き換えてるのかと思っていましたが、そもそも default scheduler を利用していないようです。Pod 登録時に該当するものは spec.schedulerName だけ Mutating して後は独自スケジューラにまかせているんだと思います。
$ kubectl get pods sample-2pod -o jsonpath="{.spec.schedulerName}" fargate-scheduler
EKS on Fargate の起動時間
EKS on Fargate での Pod の起動時間は大体 45-50 sec 程度でした(数回のみの施行)。ノードを増やしていくことと比較すると、かなり早くコンテナを起動できるといえます。この点は virtual-kubelet の利点の一つである、クラスタ外の warmpool で起動させる部分と同じような形です。
initContainer と emptyDir の使用
ちょっと発展的に initContainer や emptyDir を使用してみましたが、こちらも問題なく動作するようです。基本的に後述する注意点以外は普通の Kubernetes だなぁという使用感なのかと思います。
cat << _EOF_ | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: sample-initcontainer spec: initContainers: - name: output-1 image: centos:6 command: ['sh', '-c', 'sleep 20; echo 1st > /usr/share/nginx/html/index.html'] volumeMounts: - name: html-volume mountPath: /usr/share/nginx/html/ - name: output-2 image: centos:6 command: ['sh', '-c', 'sleep 10; echo 2nd >> /usr/share/nginx/html/index.html'] volumeMounts: - name: html-volume mountPath: /usr/share/nginx/html/ containers: - name: nginx-container image: nginx:1.12 volumeMounts: - name: html-volume mountPath: /usr/share/nginx/html/ volumes: - name: html-volume emptyDir: {} _EOF_
kubectl exec による任意のコマンド実行と kubectl port-forward
Fargate ではコンテナ内のデバッグがしづらいみたいな話をよく耳にします。kubectl exec も出来ないのだろうかと不安になりましたが、kubectl exec は通常通りできるようです。これはもはやほぼ Kubernetes ですね。
$ kubectl exec -it sample-initcontainer -- cat /usr/share/nginx/html/index.html 1st 2nd
同様に kubectl port-forward や logs なんかも動きます。kubelet が Fargate 側にいそうな霊圧を感じますね。
$ kubectl port-forward sample-2pod 30080:80 $ kubectl logs sample-2pod -c nginx-container
現状の注意点
現状では、ここ にかかれている通り、下記の制約があるようです。まだファーストリリースなので、下記の要件で合わない方は AWS の早い機能追加に期待というところですね。
- ポッドごとに最大 4 vCPU 30 Gb Memory の制約
- 永続ボリュームまたはファイルシステムを必要とするステートフルワークロードは非サポート
- DaemonSet、Privileged Pod、HostNetwork、HostPort などは利用不可
- 使用可能なロードバランサーは、Application Load Balancer のみ
ALB Ingress
EKS on Fargate で払い出される Pod の IP Address 帯は Subnet のものになっているため、従来どおり ALB から疎通可能なのでしょう(Container-native Load Balancing となる type: ip 前提)。一方で "type: LoadBalancer" Service も仕組み的には kube-proxy(の iptables 等) で DNAT されているだけなので、遠くない将来にいずれ実現されるんじゃないかなと思います。
または、通常 Node が一台以上あれば、 LB からそのノードに転送された後、kube-proxy が Pod 宛に DNAT して Fargate 側に転送されて上手くいくのでは…?という気もしているので、実験してみましょう(偏るという話は置いておいて)。
更に NodeGroup を追加してハイブリット構成にしてみる(type: LB も試してみる)
というわけで、まず NodeGroup を追加します。
$ eksctl create nodegroup --cluster amsy810-cluster
するとノードの一覧はこんな感じになります。
$ kubectl delete pod --all $ kubectl get node NAME STATUS ROLES AGE VERSION fargate-ip-192-168-129-12.ap-northeast-1.compute.internal Ready <none> 3h35m v1.14.8-eks fargate-ip-192-168-166-59.ap-northeast-1.compute.internal Ready <none> 3h33m v1.14.8-eks ip-192-168-6-3.ap-northeast-1.compute.internal Ready <none> 5m29s v1.14.7-eks-1861c5 ip-192-168-72-240.ap-northeast-1.compute.internal Ready <none> 5m29s v1.14.7-eks-1861c5
試しに "type: LoadBalancer" Service と Deployment をデプロイしてみましょう。
$ kubectl apply -f https://raw.githubusercontent.com/MasayaAoyama/kubernetes-perfect-guide/master/samples/chapter06/sample-deployment.yaml $ kubectl apply -f https://raw.githubusercontent.com/MasayaAoyama/kubernetes-perfect-guide/master/samples/chapter06/sample-lb.yaml $ kubectl get pods -o wide NAME READY STATUS IP NODE sample-deployment-6cd85bd5f-4hwtt 1/1 Running 192.168.100.80 fargate-ip-192-168-100-80.ap sample-deployment-6cd85bd5f-pkn6b 1/1 Running 192.168.153.79 fargate-ip-192-168-153-79.ap sample-deployment-6cd85bd5f-xktr6 1/1 Running 192.168.130.33 fargate-ip-192-168-130-33.ap $ kubectl describe svc sample-lb | grep Endpoints Endpoints: 192.168.100.80:80,192.168.130.33:80,192.168.153.79:80
結論として、やはり読みどおり "type: LoadBalancer" も Worker Node があれば動くには動くようです。内部の実装上は、LB から Worker Node に転送された後、kube-proxy が DNAT で 各 Pod 宛に通信を転送するだけなので、Fargate Node まで VPC 経由で疎通する仕組み上、疎通しているのだと思います。Fargate がどこにあるか次第ですが、Hop数自体は Kubernetes の Node またぎと同等なので、そこまでレイテンシは乗らないんじゃないかなと思っています。この仕組だと、Fargate + 通常 WorkerNode 両方に振り分けることもできそうですね。この方法がサポート対象な方法なのかは分かりません。
$ kubectl get svc sample-lb NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE sample-lb LoadBalancer 10.100.69.184 xxx-yyy.ap-northeast-1.elb.amazonaws.com 8080:30082/TCP 5m15s $ curl -I http://xxx-yyy.ap-northeast-1.elb.amazonaws.com:8080 HTTP/1.1 200 OK Server: nginx/1.12.2
なお、同様に ClusterIP も Pod 宛に転送されるだけなので、通常通り動きます。上手く出来ていますね。
まとめ
EKS on Fargate では、virtual-kubelet とは異なる形で実装されました。virtual-kubelet を実装しようとするとベンダー毎の違いがかなり大きいはずなので、なかなか皆で足並みを揃えて進めるのは難しかったのではないかなと思います。また、virtual-kubelet の kubelet バージョン(Kubernetes API バージョン)の追従という心配もなくなり、AWS におまかせできるという大きなメリットもあります。
EKS on Fargate を少し触った所感では、Warmpool された Kubernetes Node が後ろに待機していてくれるイメージに近いので、より PaaS っぽく Kubernetes を使いやすくなるんじゃないかと思います(周辺エコシステムや CI/CD 等の整備は必要ですが)。
また、EKS は拡張性が高い反面、手放しで利用するには少しハードルがありましたが、最近は様々な機能開発で簡単に使う手段も増えてきて導入ハードルが下がってきたなと印象です。AWS Container Roadmap(https://github.com/aws/containers-roadmap)にも Managed 系の Issue がたくさん上がっているので、拡張性を重視した使い方はもちろんですが、利便性の高い使い方も今後も増えていくのではないでしょうか。
(いろんな Kubernetes のアーキテクチャが出てきて楽しいぞ)
Fargate profile なくても動くのでは…?と言われたので試しました(12/4 16:50 追記)
ということは、Fargate Profile 作らなくても直接 schedulerName: fargate-scheduler を指定すれば Pod が立ち上がるのでは…?
— チェシャ猫 (@y_taka_23) 2019年12月4日
ちゃんと Scheduler の方で Profile も見ているようですね。なんか悪いこと(抜け道探し)してる気分になってきました。
$ kubectl create ns newone $ cat << _EOF_ | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: sample-pod-manual-fargate namespace: newone spec: schedulerName: fargate-scheduler containers: - name: nginx-container image: nginx:1.12 _EOF_ $ kubectl describe pods sample-pod-manual-fargate -n newone Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling <unknown> fargate-scheduler Misconfigured Fargate Profile: pod does not have profile label eks.amazonaws.com/fargate-profile $ cat << _EOF_ | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: sample-pod-manual-fargate namespace: newone labels: eks.amazonaws.com/fargate-profile: fp-default spec: schedulerName: fargate-scheduler containers: - name: nginx-container image: nginx:1.12 _EOF_ $ kubectl describe pods sample-pod-manual-fargate -n newone Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling <unknown> fargate-scheduler Misconfigured Fargate Profile: pod's namespace does not match the ones listed in fargate profile fp-default
何が嬉しいのか考えてみた(追記:随時更新します)
- ノードの種別管理とかは不要になり、ピッタリなサイズでサービスを提供できる(4core? 8core? とか)
- ノイジーネイバーの影響も受けにくい(Fargate 側では集約しているので、そこの影響はあるかも)
- Pod単位でノードがいるので、通常のノードでぴったりにしようとした際に発生するオートスケーラのスケールインの影響みたいなものを受けない