AWS上に作ったKubernetesでサービスを外部に公開する方法はいくつか存在する.簡単にやるならServiceをtype: LoadBalancer
で定義すればNetworkLoadBalancerが作れるし,aws-load-balancer-controllerを使えばApplicationLoadBalancerも作れる.ただ,このLBの前段にGlobalAcceleratorを作りたくなった場合はどうしたらいいだろうか.
NLBにしろALBにしろ,Kubernetes内のServiceやIngressの定義に応じて動的に作成された場合,作成された後にGlobalAcceleratorのEndpointGroupに登録する必要があるので,毎回手動作業が発生してしまう.
というわけで,これを解決するOSSを作った.
使い方
このコントローラは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すると,
NLBが作成される.これはServiceリソースのstatusで確認できる
status: loadBalancer: ingress: - hostname: your-lb-name.elb.ap-northeast-1.amazonaws.com
- GlobalAcceleratorが作られ,そのEndpointGroupに上記のNLBが登録される
foo.h3poteto-test.dev
というレコードがfoo.h3poteto-test.dev
もしくはh3potet-test.dev
のHosted Zone内に作られ,Aliasレコードで上記GlobalAcceleratorが登録される.
AWS LoadBalancer Controllerの場合
AWS LoadBalancer Controllerには,すでにGlobalAcceleratorサポートの予定があるようだが,このコントローラはその件とは特に関係がない.
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の保持ができないからだ.
ちなみにALBならできる.
アプリケーションの機能的に,clientIPがどうしても必要なためこれを変更することとなった.
TargetGroup Bindingを使うのであれば,ALBはTerraformで定義すれば良いし,それより前段のGlobalAcceleratorももちろんTerraformで定義したほうがよい.そのため,このコントローラを使う必要がなくなってしまった.