@amsy810's Blog

~Kubernetes / Cloud Native / Docker / etc~

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 とは?

f:id:masaya_aoyama:20191204151318p:plain

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-kubelethttps://github.com/virtual-kubelet/virtual-kubelet) のようにも聞こえますね。virtual-kubelet は、Kubernetes の Node として1台の仮想的なノードを追加しておきます。その仮想ノードの後ろには、膨大なコンピューティングリソースを持ったコンテナ実行基盤(プール)のようなものが待ち構えており、その仮想ノードにスケジューリングされた Pod はそのリモートノード上で Pod が実行されるようになります。代表的な実装例としては、Microsoft Azure の ACI などでしょうか。

f:id:masaya_aoyama:20191204131908p:plain
virtual-kubeletの概要
参考:https://raw.githubusercontent.com/virtual-kubelet/virtual-kubelet/release-1.2/website/static/img/diagram.svg

virtual-kubelet のプロバイダーの中に AWS Fargate Provider もあるのを把握していたため、これが EKS on Fargate だと思っていましたが、どうやら違うようですリポジトリhttps://github.com/virtual-kubelet/aws-fargate)をみても、半年〜1年程度更新がないため、virtual-kubelet 経由のものよりかは、EKS on Fargate を使っていくべきでしょう。

EKS on Fargate の検証

検証環境は下記のとおりです。

クラスタの構築時に --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」コマンドから設定可能です。

f:id:masaya_aoyama:20191204135043p:plain
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 追記)

ちゃんと 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単位でノードがいるので、通常のノードでぴったりにしようとした際に発生するオートスケーラのスケールインの影響みたいなものを受けない