Oisix ra daichi Creator's Blog(オイシックス・ラ・大地クリエイターズブログ)

オイシックス・ラ・大地株式会社のエンジニア・デザイナーが執筆している公式ブログです。

ALB ControllerのIngressGroupでALBを集約してコスト削減しました

SREの林です。本記事では最近知ったAmazon Elastic Kubernetes Service (以後はEKS)で利用する AWS Load Balancer(以後ALB) Controller の IngressGroup 機能でALBを集約してコスト削減を行なった事例を紹介します。

kubernetes-sigs.github.io

最初にまとめ

ALB ControllerのIngressGroupを利用すると、EKSクラスタ内で共通の一つのALBに対して複数のIngressを管理することができます。これによって「各Ingress毎にALBを作成した場合のコスト」を削減できます。*1

IngressGroupを有効にするのはとても簡単です。既存の ingress.yaml への変更点は以下の3点でした。

  • alb.ingress.kubernetes.io/group.name の追記
  • alb.ingress.kubernetes.io/group.order の追記
  • ingresses.spec.rules.host の追記

これによって以下のようにALBを一つにまとめることができました。

Before

Before

After

After

抱えていた課題

さて、私たちは複数のKubernetes(以後はK8s)クラスタをEKSで管理しています。そのクラスタの一つに”SREチーム検証用&社内向けのミッションクリティカルではないアプリケーション”専用のEKSクラスタがあります。

そのEKSクラスタのマニフェストの構成は以下の通りです。各アプリケーション毎にディレクトリを作成し、その中にYAMLファイルを配置しています。

.
├── awx
├── guacamole
│   ├── base
│   ├── dev
│   ├── imacrea
│   └── init
├── netbox_ldap
│   ├── base
│   ├── dev
│   ├── init
│   └── prod
├── omnidb
├── other_api_services
│   ├── alb-alb-controller
│   └── metrics_server
├── pgadmin
├── portal
├── redmine
│   ├── 100_namespace
│   ├── 200_external_dns
│   └── 300_manifests
├── shlink
├── trac
└── volumes

各アプリケーションディレクトリの中のファイル構成は基本的に以下の通りです。どれもEKSを運用する中で基本的なリソースですので一つ一つをここでは説明しませんが、今回焦点となる ingress.yaml ファイルがHTTPSで利用する各アプリケーションのディレクトリの中に作成されています。

trac/
├── README.md
├── deployment.yaml
├── external-dns.yaml
├── ingress.yaml
├── namespace.yaml
├── pvc.yaml
├── sc.yaml
├── service.yaml
└── serviceaccount.yaml

図にするとこんなイメージです。*2

Before

ポイントは以下の最後にある”1つのアプリケーションに対して1つのALBが作成される”点です。

  1. 1つのアプリケーションは1つのIngressを持つ
  2. 1つのIngressは1つのALBを作成する
  3. 結果として1つのアプリケーションに対して1つのALBが作成される

ALB一つのお値段っておいくら?

上述した構成でもアプリケーションは問題なく利用できていました。しかしチームで定期的に行っているクラウドコスト費用削減運動の中で、私はALBの乱立に心を痛め続けていました。

さて、ここで参考として”東京リージョンのALB1台の月額費用”を確認してみましょう。*3

Elastic Load Balancing 料金表

上記のAWSのドキュメントによると以下とあります。

  • Application Load Balancer 時間(または 1 時間未満)あたり、0.0243USD
  • LCU 時間(または 1 時間未満)あたり、0.008USD
    • 新しい接続: 1 秒あたりの新たに確立された接続の数。通常、接続ごとに多くのリクエストが送信されます。
    • アクティブ接続: 1 分あたりのアクティブな接続の数。
    • 処理タイプ: ロードバランサーによって処理された HTTP(S) リクエストと応答のバイト数 (GB 単位)。
    • ルール評価: ロードバランサーにより処理されたルールの数とリクエストレートの積。最初に処理される 10 個のルールは無料 (ルール評価 = リクエスト率 * (処理されたルールの数 - 無料分の 10 個のルール))。

今回対象としているのは”SREチーム検証用&社内向けのミッションクリティカルではないアプリケーション”群ですので、LCUに関しては誤差程度のコストとし、ALB時間についてのみ着目します。

0.0243USD * 24h * 30d = 17.496USD * 約127円(レート) = 約2,222円/月

となりました。LCUの変動分もざっくり加算するとALBにつき多く見積もって 3,000円/月 程度のコストはかかると見込んで良さそうです。

コンテナ化してEKSへ集約したのにコストメリットが出ない問題

今回対象のEKSクラスタは”SREチーム検証用&社内向けのミッションクリティカルではないアプリケーション”用と説明しました。この中の"ミッションクリティカルではないアプリケーション"は以下の特性を持ちます。

  • そのアプリケーションの停止はお客様へ影響する業務に直接関わらない
  • ほとんど使用しないが昔の情報があるのでアプリケーションを廃止することはできない
  • 以前は国産クラウドのVMで月額1,500円程度で動いていた

問題となるのは最後の特性です。VM環境では 1,500円/月で動いていたものが、コンテナに移行してEKSに集約してもALBを作ると 3,000円/月 となってしまいます。私たちはこれまでVMで動いていたアプリケーションをコンテナにマイグレーションをし集約していく取り組みを行なっています。それがコスト改善につながる期待もあるからです。

コンテナの特性についてGoogle Cloudのドキュメント what are containers? より以下の文を引用します。

Efficient operations

Containers are lightweight and allow you to use just the computing resources you need. This lets you run your applications efficiently.

引用文の通り、私たちの”ミッションクリティカルではない”アプリケーションについてもVMからコンテナへ移行したことでアプリケーション自体のリソースは lightweight になりました。 使用するリソースが少なくなればそれにかかる費用も安くなるのがクラウドですが、"ALBが1つ作られるだけで移行前よりもコストが増額する状況"になっていたのです。

これは喜ばしい状況ではありませんでした。

IngressGroupで解決

本来であれば解決策は難しいものではありません。私たちが管理するEC2で構成されるアプリケーションの多くは一つのALBを利用して複数のアプリケーションへルーティングを行なっており、それをIngressでも行えれば良いのです。

ALBの持つルーティング機能は非常に強力です。ALBドキュメントのRule condition typesが詳しいですが、以下の要素をルーティングの条件として利用できます。

  • host-header
  • http-header
  • http-request-method
  • path-pattern
  • query-string
  • source-ip

私たちのケースではアプリケーションそれぞれに異なるホスト(URL)を付与していますので、1つのALBで host-headerを条件に各アプリケーションへのルーティングができれば問題は解決します。しかしそれをEKSに対してマニフェストファイルで表現する方法を知りませんでした...



それを可能としたのが冒頭で紹介したALB ControllerのIngressGroup 機能です。以下に概要を引用します。

IngressGroup feature enables you to group multiple Ingress resources together. The controller will automatically merge Ingress rules for all Ingresses within IngressGroup and support them with a single ALB. In addition, most annotations defined on an Ingress only apply to the paths defined by that Ingress.

support them with a single ALB !!これぞ正しく私が求めていた機能でした。

IngressGroupを有効にするためのingress.yamlの変更点

IngressGroupを有効にするのはとても簡単です。変更点は以下の3点でした。

  • alb.ingress.kubernetes.io/group.name の追記
  • alb.ingress.kubernetes.io/group.order の追記
  • ingresses.spec.rules.host の追記

参考としてFiles changedも載せておきます。*4

Screen Shot 2022-05-29 at 19.54.49.png (447.7 kB)

実際の ingress.yaml についても紹介しておきます。今回の変更点にはコメントを付与しています。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig":
      { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'
    alb.ingress.kubernetes.io/auth-idp-cognito: '{"UserPoolArn":"arn:aws:cognito-idp:ap-northeast-1:123456789012:userpool/ap-northeast-1_hoge","UserPoolClientId":"hoge","UserPoolDomain":"hoge"}'
    alb.ingress.kubernetes.io/auth-on-unauthenticated-request: authenticate
    alb.ingress.kubernetes.io/auth-scope: openid
    alb.ingress.kubernetes.io/auth-session-cookie: AWSELBAuthSessionCookie
    alb.ingress.kubernetes.io/auth-session-timeout: "604800"
    alb.ingress.kubernetes.io/auth-type: cognito
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:123456789012:certificate/hoge-piyo-fuga
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/success-codes: "200"
    alb.ingress.kubernetes.io/group.name: sre.k8s-middleware-common # Cluster共通のALBを使う。コスト削減
    alb.ingress.kubernetes.io/group.order: '110' # 1-1000 の範囲
    external-dns.alpha.kubernetes.io/hostname: trac.example.com
    kubernetes.io/ingress.class: alb
  creationTimestamp: null
  labels:
    app: trac
  name: ingress-trac
  namespace: trac
spec:
  rules:
  - host: trac.example.com # Cluster共通のALBを使う場合は必須
    http:
      paths:
      - backend:
          service:
            name: ssl-redirect
            port:
              name: use-annotation
        path: /*
        pathType: ImplementationSpecific
      - backend:
          service:
            name: trac-service
            port:
              number: 80
        path: /*
        pathType: ImplementationSpecific
status:
  loadBalancer: {}

そしてこれによって以下のように一つのALBで複数のアプリケーションに対応することができました!スッキリです!!

After

最後に

以上がALB Controllerの IngressGroup 機能の紹介でした。

”塵も積もればなんとやら”です。EKSでALB Controllerをお使いの皆様でIngressとALBが一対一となっている場合、ミッションクリティカルなアプリケーションでなければコスト重視でIngressGroupを利用するのも良いかもしれません。

参考情報

group.orderが重複するとエラー

group.order に複数のIngressで同じ値をセットした場合どうなるかを検証したところ、エラーとなりましたのでログを記載しておきます。クラスタ用リポジトリのREADMEに一覧を記載して管理するなどした方が良さそうですね。

  • ingress-awx-prod という名前のIngress
  • group.namesre.k8s-middleware-common と他と同じ名前を指定
  • group.order は '100' を指定して既に利用済みだった
aws-load-balancer-controller-7657f8b975-xbpkj controller {"level":"info","ts":1653900236.4752069,"logger":"controllers.ingress","msg":"successfully built model","model":"{\"id\":\"awx/ingress-awx-prod\",\"resources\":{}}"}
aws-load-balancer-controller-7657f8b975-xbpkj controller {"level":"error","ts":1653900236.4759805,"logger":"controller-runtime.manager.controller.ingress","msg":"Reconciler error","name":"sre.k8s-middleware-common","namespace":"","error":"conflict Ingress group order: 100"}
aws-load-balancer-controller-7657f8b975-xbpkj controller {"level":"error","ts":1653900236.481908,"logger":"controller-runtime.manager.controller.ingress","msg":"Reconciler error","name":"sre.k8s-middleware-common","namespace":"","error":"conflict Ingress group order: 100"}
... 以下同じエラーの繰り返し

Command to list group.order in a k8s cluster

(2022-06-14追記)
上述した通り、 group.order が重複するとエラーになることもあり、当初はリポジトリのREADME.mdで表形式で手動管理していましたが当然のように記載漏れがあったため、group.order重複エラーに辛酸を舐めさせられたSREメンバたちがコマンドを組み立ててくれました。それがこちらです!


クラスタ内のgroup.orderを簡単に確認するコマンド

$ kubectl get ingress -A -o=custom-columns="Namespace:metadata.namespace,IngressName:metadata.name,IngressGroupName:metadata.annotations.alb\.ingress\.kubernetes\.io/group\.name,GroupOrder:metadata.annotations.alb\.ingress\.kubernetes\.io/group\.order" --sort-by=".metadata.annotations.alb\.ingress\.kubernetes\.io/group\.order"

結果としては以下のようになり、大変見やすいです。 --sort-by=".metadata.annotations.alb\.ingress\.kubernetes\.io/group\.order" によって見やすさに拍車がかかっていると感じています。

$  kubectl get ingress -A -o=custom-columns="Namespace:metadata.namespace,IngressName:metadata.name,IngressGroupName:metadata.annotations.alb\.ingress\.kubernetes\.io/group\.name,GroupOrder:metadata.annotations.alb\.ingress\.kubernetes\.io/group\.order" --sort-by=".metadata.annotations.alb\.ingress\.kubernetes\.io/group\.order"
Namespace           IngressName          IngressGroupName            GroupOrder
kotslack            ingress-kotslack     <none>                      <none>
guacamole-imacrea   guacamole-imacrea    <none>                      <none>
guacamole-dev       guacamole-dev        <none>                      <none>
envoy-proxy         ingress-proxy        <none>                      <none>
trac                ingress-trac         sre.k8s-middleware-common   100
portal              ingress-portal       sre.k8s-middleware-common   110
netbox              ingress-netbox       sre.k8s-middleware-common   115
netbox-dev          dev-ingress-netbox   sre.k8s-middleware-common   116
shlink              ingress-shlink       sre.k8s-middleware-common   120
shlink              ingress-shlink-web   sre.k8s-middleware-common   121
awx                 ingress-awx-prod     sre.k8s-middleware-common   200
redmine-test        ingress-redmine      sre.k8s-middleware-common   300
pgadmin             ingress-pgadmin      sre.k8s-middleware-common   310
omnidb              ingress-omnidb       sre.k8s-middleware-common   320

*1:当たり前と言われればその通りですが盲点になりがち

*2:DeploymentやServiceなどは図が複雑になりすぎたため省略しています

*3:2022-05-29時点

*4:一部マスクあり

Oisix ra daichi Creator's Blogはオイシックス・ラ・大地株式会社のエンジニア・デザイナーが執筆している公式ブログです。

オイシックス・ラ・大地株式会社では一緒に働く仲間を募集しています