Amazon EKSでIngress Controllerに何を採用するか検討した

大島 雅人
70

まとめの最後に追記があります

こんにちは、スタディサプリ ENGLISH SREグループの大島です。

現在、Amazon Elastic Container Service for Kubernetes(以下EKS)を本番運用するにあたって各要素について検討を行なっています。今回は、その一貫でAmazon EKSでIngress Controllerに何を採用するかについて検討した内容をご紹介します。

内容に少し自信がない部分もあるので、色々とツッコミをいただけると嬉しいです。

LoadBalancer ServiceType

そもそもサービスを外部に公開するには、Kubernetesのresourceの一つであるServicetype: LoadBalancerを指定すれば、AWSではClassic Load Balancer(以下CLB)が起動して外部に公開できます。以下のようなYAMLで設定できます。

apiVersion: v1
kind: Service
type: LoadBalancer
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

外部に公開するサービスが一つだけで済むユースケースでは何の問題もないのですが、複数サービスを外部公開したいケースでは下記のような懸念点があります

  • 一つのserviceごとにCLBが一つずつ立ち上がるのでリソースを効率的に使えない
  • CLBではなくApplication Load Balancer(以下ALB)を使いたいが使えない
  • CLBは複数のtargetを紐づける機能がないためホストベースでルーティング等ができない

これらの懸念を解消するため、KubernetesにはIngressというresourceが用意されています。

Ingress

Ingressは外部からのHTTP、HTTPSのリクエストを負荷分散しつつルーティングしてくれるKubernetesのresourceです。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: test-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /testpath
        backend:
          serviceName: test
          servicePort: 80

このresourceは抽象的なものであり、これをどのように実現するかはIngress Controllerに移譲されています。Google Kubernetes Engine(以下GKE)であれば最初からGLBCがIngress Controllerとして組み込まれていますが、EKSの場合は自分で選択して導入する必要があります。

Ingress Controller検討するにあたり、なるべく安定したコンポーネントを利用したいので、AWSでサポート開始のアナウンスされたALB Ingress ControllerとKubernetes公式レポにもあるNginx Ingress Controllerに絞って検討しました。

また、安定したコンポーネントも利用しつつ、設定も柔軟にできる組み合わせを見つけたかったので、下記の5パターンを検討しました。

  1. Nginx Ingress ControllerでLoadBalancer(CLB)を利用する
  2. Nginx Ingress ControllerでLoadBalancer(NLB)を利用する
  3. TerraformでALBをたてたあとNodePortでNginx Ingress Controllerを利用する
  4. ALB Ingress Controllerを利用する
  5. ALB Ingress ControllerとNginx Ingress Controllerを組み合わせる

1. Nginx Ingress ControllerでLoadBalancer(CLB)を利用する

Nginx Ingress Controllerを利用するというパターンで、公式ドキュメントにもある正攻法な方法です。Nginx Ingress ControllerをKubernetesのServiceで公開するだけなので簡単に導入できます。CLBのL4ロードバランサー、L7ロードバランサーどちらか好きな方で設定することができます。

構成はこのようになります。

pros

  • 公式ドキュメントにもある導入方法
  • Nginx Ingress Controllerの豊富なオプションを利用できる
  • CLB側でSecurity Groupの設定もできる

cons

  • CLBで公開されるのでL7を利用するとHTTP/2が利用できない
  • CLBではなくALBやNLBなど新しいサービスを利用したい

Nginx Ingress Controller公式でも紹介されている方法なので特にデメリットというほどのものもありませんが、AWS公式ドキュメントにも下記の記載があるように、なるべくAWSのロードバランサーのサービスは新しいものを使っていきたいと思っています。

Virtual Private Cloud (VPC) を使用する場合、レイヤー 7 には Application Load Balancer を、レイヤー 4 には Network Load Balancer 使用することを推奨します。

2. Nginx Ingress ControllerでLoadBalancer(NLB)を利用する

Nginx Ingress Controllerを利用するパターンでCLBではなく、Network LoadBalancerで公開するパターンです。

構成はこのようになります。

pros

  • 公式ドキュメントにもある導入方法
  • Nginx Ingress Controllerの豊富なオプションを利用できる
  • CLBではなく高性能なNLBを利用できる

cons

  • NLBサポート自体は現時点でalphaの機能
  • NLBのTLS Terminationの設定が入るのはkubernetes 1.15になりそう
  • NLBには直接security groupを設定できない

Kubernetes 1.9からLoadBalancerでNLBも利用可能になりました。ただし、まだ alpha版であるのと、NLBのTLS Terminationのサポートも最近マージされたところなので、実際に入ってくるのはKubernetes 1.15あたりになりそうです。EKSのバージョンは今の所1.12なのでまだまだ先になりそうです。

3. TerraformでALBをたてたあとNodePortでNginx Ingress Controllerを利用する

この構成は、1と2で実現出来なかったNginx Ingress ControllerをALBで公開したいという目的で検討しました。

pros

  • ALBを利用できる
  • ALB、AWS Certificate Manager(以下ACM)を自分でTerraformで管理できるのでkubernetesから切り離せる

cons

  • Nginx Ingress ControllerをService type: NodePortで運用しないといけない
  • Nginx Ingress Controller起動時のオプションでNodePortを固定する必要があるのでNode数 < Ingress Controller数にできない
  • NodePortを利用する場合アクセス元IPの取得の制限などがある

この構成の場合、ALBやACMをTerraformでKubernetesの外側で管理できるため疎結合にできるというメリットがあります1)AWSのリソースであるALB等をKubernetesのresourceとして立ててしまうとKubernetesのクラスターと密結合になってしまいます。このため便利な反面クラスターの交換等を試したいときに分離が難しくなるという側面もあります。。一方で、動的にリソースが決定されるKubernetesと疎結合にするとALBとNginx Ingress Controllerの紐付けを何らかの形で行う必要があり、Nginx Ingress Controllerのportを特定するためにNodePortサービスを使うしかありません。ただ、Nginx Ingress Controllerの公開方法としてNodePortの使用については公式ページでも注意書きされているなど懸念が残ります。

4. ALB Ingress Controllerを利用する

1、2、3ではNginx Ingress Controllerを検討してきましたが、ここからはALB Ingress Controllerを利用する場合についてです。

もともとはCoreOSのOrganizationで開発が行われていましたが、Amazon EKS、AWS ALB イングレスコントローラーによる ALB サポートを開始とアナウンスがあり採用することに問題はなさそうです。

ALB Ingress Controllerを使った場合の構成はこのようになります。

公式ドキュメントの構成図の方が分かりやすいですが、一応理解のために自分でも図に起こしてみました。公式ドキュメントの図はこちらです。

公式ドキュメントの図にmode instancemode IPがありますが、これはannotationに書くことで選択できます。EKSの場合はamazon-vpc-cni-k8sがデフォルトで利用されているのでIPモードでtargetを紐付ける方がbetterです。target groupの実際の設定を見るとイメージしやすいのではないでしょうか。

インスタンスモードの場合
IPモードの場合

pros

  • ALBを利用できる
  • ALBのlistener ruleを使ったredirectやfix responseを返す機能も利用できる
  • ALBにつけたsecurity groupで対応できる

cons

  • サービスのヘルスチェックを個別に設定できない
  • ALBにない機能はもちろん使えない

殆どのユースケースでALB Ingress Controllerで困るということはまずないと思います。しかし、ALBのtarget groupのヘルスチェックパスをIngressのruleごとに変えることができないという問題があります。例えば公開したいサービスが複数ある場合に、全てのアプリケーションで同一のパスでヘルスチェックを実装しないといけないことになります。それ自体大変なことではないですが、なるべくアプリケーション開発側へのルールは少なくして疎結合にしておきたいところです。

また、公開したいサービスごとにIngressを作成してしまえば、ヘルスチェックのパスの問題は解決できますが、それだと公開したいサービスごとにALBがたつためリソースの効率が悪いという懸念があります。

5. ALB Ingress ControllerとNginx Ingress Controllerを組み合わせる

今までのパターンのいいとこ取りができるのがこの構成です。安定したALBを利用しつつ、柔軟な設定が可能なNginx Ingress Controllerを利用できます。

リクエストの流れはこのようになります。

  1. ClientからのリクエストをALB Ingress Controllerで作成されたALBで受けとる
  2. ALBからNginx Ingress ControllerのServiceへ流す
  3. Nginx Ingress Controllerの各PodはIngressのruleに従いリクエストを各サービスへルーティングする

構成図はこちら。

pros

  • ALBを利用できる
  • ALBのlistener ruleを使ったredirectやfix responseを返す機能も利用できる
  • ALBにつけたsecurity groupで対応できる
  • Nginx Ingress Controllerの豊富なオプションが利用できる
  • health checkのパスはサービスごとに自由に設定できる

cons

  • Ingress Controllerを2つ運用する必要がある

最初のリクエストを受けるところでALBを利用できるので、HTTP/2を使えたりSecurity Group等、AWSのマネージドサービスのメリットを享受できます。また、Kubernetesならではの動的な設定にもNginx Ingress Controllerで対応できます。

デメリットは、Ingress Controllerを2つデプロイしないといけないことです。構成が少し複雑にはなってしまいますが、それ以上にメリットが大きいので悪くない構成だと思います。この構成にすることで、ALBのアクセスログよりもリアルタイムにNginx Ingress Controllerのアクセスログを取得することもできるので、アクセスに応じた処理の柔軟度も上がりそうです。

まとめ

様々なパターンを紹介してきましたが、どれにするべきという結論を出すのはユースケース次第ということになってしまうでしょう。我々のユースケースでは、5番目のALBとNginxを組み合わせるのが良さそうという結論に至りました。

本記事では、負荷テストやパフォーマンスの観点が含まれていないので、今後検証していく予定です。

追記: 2019/06/04

公開したいサービスごとにIngressを作成するパターンだとALBがIngressごとが作成されてしまって効率が悪いと書きましたが、現在ALB Ingress Controller側でIssueが上がっており将来的には対応されそうです。

詳細は上記のIssueを読んでもらうとして、alb.ingress.kubernetes.io/group.nameという新しいannotationを設定すれば、例えば、ヘルスチェックのパスが異なる公開したいサービスがあった場合に、Ingressのresourceを複数作ったとしても、ALBは一つで済むようになります。

イメージが湧きにくいと思いますので、具体的には以下のようなIngressのyamlを書くだけで実現できるということになると思われます。

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress1
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: /ping1
    alb.ingress.kubernetes.io/group.name: api-server
spec:
  rules:
  - http:
      paths:
      - path: /test1
        backend:
          serviceName: test1
          servicePort: 80
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress1
  annotations:
    alb.ingress.kubernetes.io/healthcheck-path: /ping2
    alb.ingress.kubernetes.io/group.name: api-server
spec:
  rules:
  - http:
      paths:
      - path: /test2
        backend:
          serviceName: test2
          servicePort: 80

脚注   [ + ]

1. AWSのリソースであるALB等をKubernetesのresourceとして立ててしまうとKubernetesのクラスターと密結合になってしまいます。このため便利な反面クラスターの交換等を試したいときに分離が難しくなるという側面もあります。