AWS Global AcceleratorをKubernetesのリソースから管理する

AWS上に作ったKubernetesでサービスを外部に公開する方法はいくつか存在する.簡単にやるならServiceをtype: LoadBalancerで定義すればNetworkLoadBalancerが作れるし,aws-load-balancer-controllerを使えばApplicationLoadBalancerも作れる.ただ,このLBの前段にGlobalAcceleratorを作りたくなった場合はどうしたらいいだろうか. NLBにしろALBにしろ,Kubernetes内のServiceやIngressの定義に応じて動的に作成された場合,作成された後にGlobalAcceleratorのEndpointGroupに登録する必要があるので,毎回手動作業が発生してしまう. というわけで,これを解決するOSSを作った.

github.com

使い方

このコントローラはGlobalAcceleratorとRoute53のレコードを管理する.Kubernetes上のService/Ingressに特定のアノテーションを付与すると,それに応じてリソースを作成する.

  • aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed: "yes" を追加したService/Ingressに対しては,それに対応するGlobalAcceleratorを作る.ただし,LoadBalancerが紐付いていないService/Ingressに対してはなにもできない
  • さらに,aws-global-accelerator-controller.h3poteto.dev/route53-hostname: your.hostnameを追加したService/Ingressに対しては,↑で作られたGlobalAcceleratorをyour.hostnameとしてRoute53に登録する

Service type: LoadBalancerの場合

type: LoadBalancerのServiceを作り,これにアノテーションを追加する.

apiVersion: v1
kind: Service
metadata:
  annotations:
    aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed: "yes"
    aws-global-accelerator-controller.h3poteto.dev/route53-hostname: "foo.h3poteto-test.dev"
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
  name: h3poteto-test
  namespace: default
spec:
  externalTrafficPolicy: Local
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    port: 443
    protocol: TCP
    targetPort: 443
  selector:
    app: h3poteto
  sessionAffinity: None
  type: LoadBalancer

これをapplyすると,

  1. NLBが作成される.これはServiceリソースのstatusで確認できる

     status:
       loadBalancer:
         ingress:
         - hostname: your-lb-name.elb.ap-northeast-1.amazonaws.com
    
  2. GlobalAcceleratorが作られ,そのEndpointGroupに上記のNLBが登録される
  3. foo.h3poteto-test.devというレコードが foo.h3poteto-test.devもしくはh3potet-test.devのHosted Zone内に作られ,Aliasレコードで上記GlobalAcceleratorが登録される.

AWS LoadBalancer Controllerの場合

AWS LoadBalancer Controllerには,すでにGlobalAcceleratorサポートの予定があるようだが,このコントローラはその件とは特に関係がない.

github.com

ALBの場合は,Ingressを作る.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: h3poteto-test
  namespace: default
  annotations:
    aws-global-accelerator-controller.h3poteto.dev/global-accelerator-managed: "yes"
    aws-global-accelerator-controller.h3poteto.dev/route53-hostname: "foo.h3poteto-test.dev,bar.h3poteto-test.dev"
    alb.ingress.kubernetes.io/scheme: internet-facing
spec:
  ingressClassName: alb
  rules:
  -  http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: h3poteto-test
            port:
              number: 80

ちなみにroute53-hostnameアノテーションには,区切りで複数のドメイン名を指定できる.

ingress-nginxの場合

ingress-nginxの場合,ingress-nginx controllerが type: LoadBalancerのServiceを作成し,そのNLBを通したリクエストがnginxを通して各Serviceに流れる.Ingressはこのnginxの設定のために作成する.

まず,ingress-nginx controllerが使うServiceにaws-global-accelerator-controller.h3poteto.dev/global-accelerator-managedアノテーションを付与する必要がある.helmを使う場合

$ helm install ingress-nginx ingress-nginx/ingress-nginx \
  –set controller.service.annotations.”aws-global-accelerator-controller\.h3poteto.dev/global-accelerator-managed”=”yes”

とすることでアノテーションを追加できる.

次に各Ingressリソースを以下のように定義する.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: h3poteto-test
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    aws-global-accelerator-controller.h3poteto.dev/route53-hostname: "foo.h3poteto-test.dev,bar.h3poteto-test.dev"
spec:
  rules:
  -  http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: h3poteto-test
            port:
              number: 80

中身について

このコントローラはServiceとIngressのリソースを監視していて,アノテーションが付与された場合に動作するようになっている.ただし,status.LoadBalancerの値が埋まるまでは何もできないので,それを待つ.Route53の方についても,GlobalAcceleratorが作成されるまでは何もできないので,それを待つ. そのため,たとえアノテーションがついていたとしても,LoadBalancerがstatusに埋まらない場合(type: NodePortとかね),このコントローラは何もしない.

作成されたGlobalAcceleratorの検出方法

もちろん,ServiceやIngressリソースにstatus.LoadBalancerのフィールドはあるがstatus.GlobalAcceleratorのようなフィールドは存在しない.たま,このコントローラは独自のカスタムリソースを使わない. となると一度作成したGlobalAcceleratorをどこかで記憶しておかなきゃいけないのだが,これはリソースのタグで行っている.作ったGlobalAcceleratorに,元となったService名等を書き込んでいる.

Route53の場合は,external-dnsと同じ方式で,同名のTXTレコードに書き込んでいる.TXTなのでリクエストしたら見えるので,あまり良い仕組みだとは思わないが…….せめてレコードにタグが付与できれば良かったのだが,どうもできないらしいので.

今後の予定

一応いくつかissueは作ってある.

  • 既存のGlobalAcceleratorへのバインド

    これはAWS LoadBalancer ControllerのTargetGroup Bindingみたいなもので,このコントローラでGlobalAcceleratorを作らずに,存在するGlobalAcceleratorのEndpointGroupに指定のService/IngressのLBを追加するだけのモード.

  • Endpoint weightのサポート

    上記ができるようになると,一つのGlobalAcceleratorに複数のLBを付与できるようになる.そうすると各Endpointごとのweightを指定したくなってくる.


あとがき

このOSS,もともと副業で関わってるoViceのために書いていた.oViceでは内部でingress-nginxを使っており,これにGlobalAcceleratorをつけるのが結構大変だったからだ. しかし,先日oViceはALB + TargetGroup Bindingを使うという決定をした.一部ingress-nginxは残るものの,基本的にはALBを使う.これは,GlobalAccelerator + NLBだとclientIPの保持ができないからだ.

docs.aws.amazon.com

ちなみにALBならできる.

アプリケーションの機能的に,clientIPがどうしても必要なためこれを変更することとなった.

TargetGroup Bindingを使うのであれば,ALBはTerraformで定義すれば良いし,それより前段のGlobalAcceleratorももちろんTerraformで定義したほうがよい.そのため,このコントローラを使う必要がなくなってしまった.

というわけでこのOSSは私の趣味OSSになりました.oViceでは使わないけどサポートは続けていく予定です.