Traefik から Envoy Gateway に乗り換えた

以前書いたように、おうち k8s クラスタではクラスタ外からのアクセスのために Traefik を導入していた。

dayflower.hatenablog.com

ところが、 (最初はうまくいっていたものの) あるときから Helm でインストールした MySQLIngressRouteTCP で外部からアクセスできなくなってしまった。

(telnet で接続してみたところ通常 server から接続直後に応答があるべきところ client からなんらかのパケットを送らないとセッションが開始しない)

いろいろ調べてみたがどうにもうまくいかず、せっかくなので別のソフトウェアに乗り換えることにした。

Istio にしようかとも思ったが、サービスメッシュのような複雑なことはするつもりはなくコンパクトなものがいいなと思い、 同じ Envoy ベースの Envoy Gateway にすることにした。

Traefik Proxy Envoy Gateway
ベース Traefik Proxy Envoy
Ingress Controller ×
Gateway API
独自 CRD
TLS 証明書管理 ×

星取表でみるかぎり完全に Traefik に負けてしまうのだが、裏を返すと限定した機能にフォーカスしているといえる。

Ingress に対応していないのがちょっとつらいところだが、 Gateway API のほうが実現できる機能も多いため、 Envoy Gateway でいくことにする。 Envoy に興味もあったし。

インストール

Helm でインストールした。

Traefik とちがい、 values はデフォルトからいじっていない。

これは、 Traefik にくらべて TLS 証明書管理機能がないこと、また、対象とするポート等の設定は Resource として定義することから、本体自体のカスタマイズは特に必要なかったことなどが理由と思う。

Argo CD での Helm インストール

CRD が大きすぎて、デフォルトだと Argo CD で管理できない (Too long: must have at most 262144 bytes のように怒られる)。

適当に調べて Replace mode で配布するようにしてみたが、それでもうまくいかなかったので、結局手で helm install することにした。

いま改めて調べると Replace ではなく Server Side Apply を利用するほうがよいらしい (ref. Fixing Argo CD "Too long must have at most 262144 bytes" error)。

時間ができたらふたたび Argo CD による配布に挑戦してみようと思う。

(2025-03-14 追記: spec.syncPolicy.syncOptionsServerSideApply=true を付与することで、無事 Argo CD で管理できるようになった)

セットアップ

cert-manager による TLS 証明書管理

Traefik はいい感じにTLS の証明書を管理してくれる (ACME 対応の認証局であれば自動発行も可能) が、 Envoy Gateway にはそのような機能はない。

なので、定番である cert-manager を利用することにする。

自分は k8s 基盤として MicroK8s を利用しているので cert-manager addon を enable してインストールした。

(MicroK8s での説明 だと、あたかも Ingress 環境があることが必須のように読めてしまうが、 ACME DNS01 Challenge をするぶんには Ingress は必要ではない)

Let's Encrypt で *.wildcard.example.com の証明書を発行する resource は以下のようになる。

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  namespace: default
  name: wildcard.example.com
spec:
  issuerRef:
    kind: ClusterIssuer
    name: luadns-issuer
  secretName: wildcard.example.com-tls
  commonName: wildcard.example.com
  dnsNames:
    - wildcard.example.com
    - "*.wildcard.example.com"

metadata.namespec.secretName はなんでもかまわない。

spec.issueRef はこのあいだの記事で定義した ClusterIssuer を参照している。

dayflower.hatenablog.com

spec.secretName で指定した Secret に証明書が格納される。 これを後述する Gateway から参照することになる。

GatewayClass の定義

Gateway をハンドリングする controller を指定する GatewayClass を定義する。

Ingress における IngressClass と同じようなものと思えば問題ない。

---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller

Envoy Gateway (Controller) の設定をカスタマイズするためには、以下のような記述になる。 (オフィシャルの example が参考になる)

---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
  name: envoy-gateway
spec:
  controllerName: gateway.envoyproxy.io/gatewayclass-controller
  parametersRef:
    group: gateway.envoyproxy.io
    kind: EnvoyProxy
    namespace: envoy-gateway-config
    name: config
---
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
  namespace: envoy-gateway-config
  name: config
spec:
  ...

spec に設定可能なパラメータは オフィシャルドキュメントの EnvoyProxySpec を参照のこと。

とはいえ、通常の場合わざわざカスタマイズする必要もなく、 GatewayClass だけ定義すればよいと思う。

Gateway の定義

Traefik の場合、どの port を開けるかといった設定はすべて起動時オプションで指定する必要があった (Helm でインストールする場合は values.yaml で指定)。

Gateway API ではそういった port 設定もすべて CRD になっている。 具体的には Gateway resource を利用する。

---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  namespace: default
  name: envoy-gateway
spec:
  gatewayClassName: envoy-gateway
  addresses:
    - type: IPAddress
      value: 192.168.0.100
  listeners:
    - name: https
      protocol: HTTPS
      port: 443
      tls:
        mode: Terminate
        certificateRefs:
          - kind: Secret
            namespace: default
            name: wildcard.example.com-tls
      allowedRoutes:
        kinds:
          - kind: HTTPRoute
        namespaces:
          from: All
  • spec.gatewayClassName に上記で設定した GatewayClass の name を指定する
    • Ingress や PersistentVolume に似ている
  • spec.listeners[].tls.certificateRefs に上記で設定した cert-manager による Certificate resource により生成される Secret resource を指定している
  • spec.listeners[].allowedRoutes.kinds に、当該 Gateway に利用可能な route resource を指定している (後述)
  • spec.addresses は、External LoadBalancer (metallb 等) が適切にセットアップされていれば、とくに設定する必要はない
    • 筆者の環境では (MicroK8s に) metallb は導入しておらず、 single node の物理 I/F にアサインされたアドレスをそのまま利用しているので、 IP アドレスを明示的に指定している

ルーティングの設定

詳細な説明は不要かと思うが、実際の Service に対応する HTTPRoute resource の定義はたとえば以下のようになる。 (ちなみに HTTPS であっても HTTPRoute を利用する。HTTPSRoute はない)

---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: httproute
spec:
  parentRefs:
    - name: envoy-gateway
      namespace: default
      sectionName: https
  hostnames:
    - 'nantoka.wildcard.example.com'
  rules:
    - backendRefs:
        - name: service-name
          port: 80
      matches:
        - path:
            type: PathPrefix
            value: /
  • spec.parentRefs に上記で設定した Gateway resource を指定する
    • spec.parentRefs[].sectionNameGateway resource の listener で設定した name を指定する
  • spec.rules[].backendRefsトラフィックをルーティングする Service を指定する

感想

以下に Traefik から乗り換えてよかったところ、残念だったところをあげる。

よかったところ

  • 標準にのっとっている安心感 (まだ core ではないが)
    • 実際には Traefik も標準 Ingress resource / Gateway API resource を提供しているが
  • 設定まわりがきちんと CRD として定義されており、管理がしやすい

残念なところ

  • 設定が CRD なので、裏をかえすと、利用したい機能にたいして、定義するべきリソースが増える
  • Ingress がない
    • これはそもそも Envoy Gateway のポリシーだからあたりまえではある。だが、
    • 他の Helm chart 等で Ingress 設定を指定できるケースがあり、その場合でも別途 HTTPRoute resource を自力で定義しないといけないのがめんどくさい
  • Traefik とちがい Web の status dashboard がない
    • じゃあ実際に使うのかというと使わないのだが、あればあったで登録されている route を web から一覧でみれて便利