🌐

Kubernetes で Traefik proxy を使う

2024/07/15に公開

Traefik proxy はモダンで多機能な OSS の proxy サーバーです。nginx のように別サーバーへの proxy や負荷分散、ユーザー認証を行うことができ、k8s で使用する場合は ingress controller としても使用できます。
traefik を docker で使用する記事は割と多いですが、kubernetes 上で使用する記事はあまりなさそうだったので試してみます。

インストール

k8s クラスタ上に traefik をデプロイして svc や ingress リソースを管理するためには ClusterRole や ServiceAccount の設定 の作成が必要になります。ただこのあたりのリソースは helm chart でまとめてインストールできるのでこちらを使うのが楽です。
https://github.com/traefik/traefik-helm-chart

リポジトリを追加してインストール

helm repo add traefik https://traefik.github.io/charts
helm install -n traefik traefik traefik/traefik --create-namespace

これで単一の traefik pod が起動します

$ k get -n traefik pod
NAME                       READY   STATUS    RESTARTS   AGE
traefik-857b5bbcd6-lglrl   1/1     Running   0          75s

svc は LoadBalancer として作成されます。

$ k get svc
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
traefik   LoadBalancer   10.103.153.81   <pending>     80:31810/TCP,443:30906/TCP   27m

kubectl describe pod で見て分かるとおりイメージには dockerhub 上の通常の traefik が使用されています。 kubernetes 用に特別にカスタマイズされているわけではなく、traefik の機能の一つである provider のうち kubernetes ingress providerkubernetes CRD provider を利用して kubernetes 関連のリソースを処理するようになっています。helm でデプロイされる pod では引数の --providers.kubernetesingress--providers.kubernetescrd でそれぞれ有効化しています。

Containers:
  traefik:
    Container ID:  containerd://2a3f5e5af4659db69d3e8909c938c1e832fc552bf2d005673be2c2f85e859043
    Image:         docker.io/traefik:v3.0.4
    Image ID:      docker.io/library/traefik@sha256:a208c74fd80a566d4ea376053bff73d31616d7af3f1465a7747b8b89ee34d97e
    Ports:         9100/TCP, 9000/TCP, 8000/TCP, 8443/TCP
    Host Ports:    0/TCP, 0/TCP, 0/TCP, 0/TCP
    Args:
      --global.checknewversion
      --global.sendanonymoususage
      --entryPoints.metrics.address=:9100/tcp
      --entryPoints.traefik.address=:9000/tcp
      --entryPoints.web.address=:8000/tcp
      --entryPoints.websecure.address=:8443/tcp
      --api.dashboard=true
      --ping=true
      --metrics.prometheus=true
      --metrics.prometheus.entrypoint=metrics
      --providers.kubernetescrd
      --providers.kubernetesingress
      --entryPoints.websecure.http.tls=true
      --log.level=INFO

現時点では traefik のバージョンとして v3 系が使用されます。メジャーバージョンが異なる場合は動作が変わったり対応していない機能があるので注意。

また、ログレベルを DEBUG に設定すると pod ログでより詳細な情報が出力されるようになるため、traefik 側でどのような処理が行われているかを調べながら検証したい場合には DEBUG に設定するのがおすすめです。こちらは helm chart の values より変更できます。

https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L305-L312

Kubernetes 関連の機能を使ってみる

上記でインストールされた traefik では docker で使うときと同様に普通の proxy や LoadBalancer (LB) として使用できますが、ドキュメントでは kubernetes のリソースを管理する機能として以下の 3 つが記載されています。

  • Kubernetes Ingress
  • Kubernetes CRD
  • Kubernetes Gateway API

これらの機能をそれぞれ試してみます。

Kubernetes Ingress

https://doc.traefik.io/traefik/providers/kubernetes-ingress/

Kubernetes Ingress はいわゆる k8s の ingress controller の機能となっており、ingress リソースのプロパティを元に traefik の構成を動的に変更することで対象サービスへの proxy や LB を実現します。helm インストール時に traefik の ingressClass traefik が自動で作成されるため、ingress リソースにこれを割り当てることで traefik により管理されます。

ingress の動作確認のため、nginx 用の pod と svc を作成します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: nginx

上記の svc にルーティングするための ingress を作成。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
spec:
  rules:
  - host: nginx.test
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              name: http

ingress を作成すると ingressClass に traefik が割り当てられます (インストール時にデフォルト class に設定されるため)。

$ k get ingress
NAME    CLASS     HOSTS   ADDRESS   PORTS   AGE
nginx   traefik   *                 80      95s

traefik LB svc にアクセスすると、traefik を経由して nginx pod にルーティングされます。

$ curl -H "host: nginx.test" 10.109.84.42
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

svc type が LoadBalancer なので、クラスタ外部からは traefik pod が稼働している node の IP address と nodePort でアクセスできます。

$ curl -H "host: nginx.test" http://192.168.3.124:31839
...
<title>Welcome to nginx!</title>

このあたりは nginx ingress controller などの他 ingress controller と比較して目新しい要素はなく普通に使用することができます。

TLS 通信

traefik では他の多くの ingress controller と同様に、ingress や svc に専用の annotations を追加することでいろいろな設定を行うことができます。例えばクライアントから traefik pod への通信を TLS にするには ingress の annotations に traefik.ingress.kubernetes.io/router.tls: "true" を追加します。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
  annotations:
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  rules:
  - host: ingress.centre.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              name: http

この場合、サーバ証明書には Traefik 側で自動で生成される証明書が使用されます。curl 等で詳細を確認すると issuer: CN=TRAEFIK DEFAULT CERT となっていることが確認できます。

$ curl https://ingress.centre.com -vk
*   Trying 10.109.84.42:443...
* Connected to ingress.centre.com (10.109.84.42) port 443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=TRAEFIK DEFAULT CERT
*  start date: Jul 10 14:35:23 2024 GMT
*  expire date: Jul 10 14:35:23 2025 GMT
*  issuer: CN=TRAEFIK DEFAULT CERT
*  SSL certificate verify result: self-signed certificate (18), continuing anyway
...

独自の証明証を使用する場合は secret に格納した後、ingress の spec.tls.secretName で指定します。この場合 annotations の指定は不要です。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
spec:
  tls:
    - secretName: ingress-secret
  rules:
  - host: ingress.centre.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx
            port:
              name: http

リクエストを実行すると issuer が変更され、自己署名証明書の issuer となっていることが確認できます。なお、リクエストと traefik 側のドメインが一致しない場合には fallback して traefik デフォルトの証明証が使用されるようになっています。

$ curl https://ingress.centre.com -vk
*   Trying 10.109.84.42:443...
* Connected to ingress.centre.com (10.109.84.42) port 443 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN: server accepted h2
* Server certificate:
*  subject: C=JP; ST=japan; L=tokyo; O=centre; OU=centre; CN=ingress.centre.com
*  start date: Oct  9 11:02:43 2023 GMT
*  expire date: Sep 15 11:02:43 2123 GMT
*  issuer: C=JP; ST=japan; O=myroot; OU=myroot; CN=rootca.local
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
...

Traefik - pod 間を TLS にする

上記の方法では TLS は traefik で終端されるため、traefik とバックエンド (nginx) の通信は HTTP で行われます。traefik - バックエンド間の通信も TLS で行うには以下の 3 つの方法があります。

https://doc.traefik.io/traefik/routing/providers/kubernetes-ingress/#communication-between-traefik-and-pods

There are 3 ways to configure Traefik to use HTTPS to communicate with pods:

If the service port defined in the ingress spec is 443 (note that you can still use targetPort to use a different port on your pod).
If the service port defined in the ingress spec has a name that starts with https (such as https-api, https-web or just https).
If the service spec includes the annotation traefik.ingress.kubernetes.io/service.serversscheme: https.

こちらについても動作を見てみます。
まず nginx pod を HTTPS に対応するため、サーバ証明書の secret を作成。

kubectl create secret generic nginx-secret \
    --from-file=tls.crt=nginx.centre.com.crt  \
    --from-file=tls.key=nginx.centre.com.key

Port 443 で HTTPS を待ち受けるように nginx の config ファイルを準備。

nginx.conf
server {
    listen 443 ssl;
    server_name nginx.centre.com;

    ssl_certificate /etc/nginx/ssl/tls.crt;
    ssl_certificate_key /etc/nginx/ssl/tls.key;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

HTTP 通信は HTTPS にリダイレクトするように設定。

default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    return 301 https://$host$request_uri;
}

ファイルを configmap として作成。

$ kubectl create configmap nginx-config --from-file=nginx.conf=./nginx.conf
$ kubectl create configmap default-config --from-file=default.conf=./default.conf

configmap と証明書の secret をコンテナにマウントするように pod を設定。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-tls
  template:
    metadata:
      labels:
        app: nginx-tls
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/nginx-tls.conf
          subPath: nginx.conf
        - name: default-config
          mountPath: /etc/nginx/conf.d/default.conf
          subPath: default.conf
        - name: tls-secret
          mountPath: /etc/nginx/ssl
          readOnly: true
      volumes:
      - name: nginx-config
        configMap:
          name: nginx-config
      - name: default-config
        configMap:
          name: default-config
      - name: tls-secret
        secret:
          secretName: nginx-secret

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx-tls
  ports:
  - protocol: TCP
    port: 443
    targetPort: 443

これで HTTPS のみでリクエストを受け付ける nginx pod が起動します。pod の IP address に HTTPS アクセスすると接続が確認できます。

$ curl https://10.96.68.112 -k
...
<title>Welcome to nginx!</title>

次にクライアント → traefik → nginx の経路で接続しようとすると traefik → nginx は http 通信で行われるので The plain HTTP request was sent to HTTPS port となります。

$ curl https://ingress.centre.com
<html>
<head><title>400 The plain HTTP request was sent to HTTPS port</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>The plain HTTP request was sent to HTTPS port</center>
<hr><center>nginx/1.27.0</center>
</body>
</html>

traefik を HTTPS に対応するために、 svc と ingress の target name をそれぞれ https に合わせます。

---
apiVersion: v1
kind: Service
  ports:
  - name: https            # Set to https
    protocol: TCP
    port: 4444
    targetPort: 443

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-tls
  annotations:
    traefik.ingress.kubernetes.io/router.tls: "true"
spec:
  rules:
  - host: ingress.centre.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port:
              name: https            # Set to https

この場合は port 名に https が含まれるという以下の条件に一致するので、traefik が自動で検出して traefik → nginx を HTTPS で通信します。

If the service port defined in the ingress spec has a name that starts with https (such as https-api, https-web or just https).

ただ今回は nginx pod 側で自己証明書を使用しているため検証に失敗し Internal Server Error となります。

$ curl https://ingress.centre.com
Internal Server Error%

traefik pod のログを見ると自己証明書が信頼できないため接続に失敗していることがわかります。

2024-07-11T10:06:08Z DBG github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:196 > Service selected by WRR: 038e85d3daa88262
2024-07-11T10:06:08Z DBG github.com/traefik/traefik/v3/pkg/server/service/proxy.go:100 > 500 Internal Server Error error="tls: failed to verify certificate: x509: cannot validate certificate for 10.244.1.25 because it doesn't contain any IP SANs"

この場合は traefik pod に対象の rootCA 証明書を配置するか、検証をスキップするよう insecureSkipVerify: true に設定する必要があります。
ここでは簡単のため insecureSkipVerify を追加する方法で対応します。k edit deployments.apps traefik で deployment を編集し、traefik コンテナの引数に serversTransport.insecureSkipVerify=true を追加します。

deployment
   spec:
      automountServiceAccountToken: true
      containers:
      - args:
        - --global.checknewversion
        - --global.sendanonymoususage
        - --entryPoints.metrics.address=:9100/tcp
        - --entryPoints.traefik.address=:9000/tcp
        - --entryPoints.web.address=:8000/tcp
        - --entryPoints.websecure.address=:8443/tcp
        - --api.dashboard=true
        - --ping=true
        - --metrics.prometheus=true
        - --metrics.prometheus.entrypoint=metrics
        - --providers.kubernetescrd
        - --providers.kubernetesingress
        - --entryPoints.websecure.http.tls=true
        - --log.level=DEBUG
+        - --serversTransport.insecureSkipVerify=true

kubectl rollout restart deployment traefik で pod を更新すると通信が成功するようになります。

$ curl https://ingress.centre.com -I
HTTP/2 200
accept-ranges: bytes
content-type: text/html
date: Thu, 11 Jul 2024 10:07:43 GMT
etag: "6655da96-267"
last-modified: Tue, 28 May 2024 13:22:30 GMT
server: nginx/1.27.0
content-length: 615

nginx pod のアクセスログを見ると traefik pod の IP アドレス 10.244.1.29 からの通信が成功していることが確認できます。

$ k get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
nginx-deployment-58dc469c55-t4lz9   1/1     Running   0          13m   10.244.1.25   k8s-w1   <none>           <none>
traefik-b47cfbc96-9l9c5             1/1     Running   0          31s   10.244.1.29   k8s-w1   <none>           <none>

# nginx のログ
10.244.1.29 - - [11/Jul/2024:10:07:43 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.88.1" "10.244.0.0"

よって traefik → バックエンド 間も HTTPS 通信できることが確認できました。

ingress provider の機能は ingress の annotations に基づいて自動的に traefik の設定を更新するため、他の ingress controller を既に使い慣れている場合は特に問題なく使えます。設定可能な annotations などはドキュメントの以下を参照。

https://doc.traefik.io/traefik/routing/providers/kubernetes-ingress/

kubernetes CRD

https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/

kubernetes CRD は CRD を用いて traefik における routers や middleware, 各種 options などに相当するリソースを作成する機能となっています。ingress controller では ingress や svc の annotation などに基づいて自動で設定する機能となっていましたが、kubernetes CRD では CRD を使って直接 traefik 側のリソースを作成することができます。

ドキュメントによると、ingress 側で多数の annotations をつけてリソースを管理することなく traefik の機能を活用したいという需要から、CRD を使って直接 traefik のオブジェクトを作成できるようにしたという背景があったとのこと。

However, as the community expressed the need to benefit from Traefik features without resorting to (lots of) annotations, the Traefik engineering team developed a Custom Resource Definition (CRD) for an IngressRoute type, defined below, in order to provide a better way to configure access to a Kubernetes cluster.

例えば、ingress controller 検証時のように nginx pod に HTTPS で通信するように設定するには以下の IngressRoute カスタムリソースを作成します。

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: nginx
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`ingress.centre.com`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: nginx-service
      port: 4444

この IngressRoute を作成してリクエストを送信すると、先程と同様に nginx pod に HTTPS で接続できます。

$ curl https://ingress.centre.com -k

...
<title>Welcome to nginx!</title>

このときも先程と同様に クライアント → traefik pod → nginx pod という通信経路となっており、各 pod 間の通信は HTTPS で行われます。


kubernetes CRD では CRD を使うことで traefik の各種リソースを作成できるため、用途に応じて ingress controller よりも詳細なルーティング設定や traefik 独自の機能を利用することができます。traefik の機能はドキュメントに豊富に記載されていますが、以下ではよく使いそうな機能や面白そうなものをいくつか選んで検証してみます。

負荷分散

IngressRoute の svc で複数のサービスを指定するとラウンドロビンでリクエストが負荷分散されるようになります。試しに 2 つの flask svc に負荷分散するように services を指定します。

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: flask-lb
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`flask.test`) && PathPrefix(`/`)
    kind: Rule
    services:
      - name: flask1
        port: 5000
      - name: flask2
        port: 5000

flask1, flask2 svc はさらにそれぞれ 3 つの pod にルーティングされるように設定します。

$ k get pod
NAME                                READY   STATUS    RESTARTS   AGE
flask1-5d778f5dd5-47xg2             1/1     Running   0          3m47s
flask1-5d778f5dd5-4nsxc             1/1     Running   0          3m47s
flask1-5d778f5dd5-784ll             1/1     Running   0          3m47s
flask2-7d79bb7c54-788jc             1/1     Running   0          3m47s
flask2-7d79bb7c54-9x6lb             1/1     Running   0          3m47s
flask2-7d79bb7c54-lhhp8             1/1     Running   0          3m47s

Flask pod ではレスポンスで POD_ID を返すように設定し、リクエスト時にどの pod にルーティングされたか分かるようにします。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: flask1
spec:
  selector:
    matchLabels:
      app: flask
      task: task1
  replicas: 3
  template:
    metadata:
      labels:
        app: flask
        task: task1
    spec:
      containers:
      - name: flask
        image: registry.centre.com:5000/flask-k8s-test
        ports:
        - containerPort: 5000
        env:
          - name: POD_ID
            valueFrom:
              fieldRef:
                fieldPath: metadata.name

通信経路のイメージとしては以下のようになります。


通信経路のイメージ図

この構成でクライアントからリクエスト 10000 回送信した結果、flask1, flask2 に対してちょうど 5000 リクエストずつ分散されていることが確認できました。また、各 pod に対するリクエスト数を集計すると以下のようになっていました。

  • flask1-5d778f5dd5-47xg2 : 1666
  • flask1-5d778f5dd5-4nsxc : 1667
  • flask1-5d778f5dd5-784ll : 1667
  • flask2-7d79bb7c54-788jc : 1666
  • flask2-7d79bb7c54-9x6lb : 1668
  • flask2-7d79bb7c54-lhhp8 : 1667

これにより負荷分散の機能が正常に機能し、リクエストが各 pod に均等に分散されていることがわかります。この検証では flask1, 2 はいずれも同一サービスなので負荷分散するメリットはあまりないですが、特に複雑な設定を行わず、単に services に分散させたい svc を複数指定することで負荷分散できることがわかります。

加重ラウンドロビン

負荷分散の際に加重ラウンドロビン (Weighted Round Robin: WRR) により重み付けした上でラウンドロビンを実行することも可能です。
https://doc.traefik.io/traefik/routing/providers/kubernetes-crd/#weighted-round-robin

traefik の Services オブジェクトに対応する TraefikService リソースを作成し、宛先の svc と weight をそれぞれ指定します。

apiVersion: traefik.io/v1alpha1
kind: TraefikService
metadata:
  name: wrr1
spec:
  weighted:
    services:
      - name: flask1
        port: 5000
        weight: 3
      - name: flask2
        port: 5000
        weight: 2

IngressRoute では services に上記の TraefikService を指定します。

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: flask-lb
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`flask.test`) && PathPrefix(`/`)
    kind: Rule
    services:
      - name: wrr1
        kind: TraefikService

ラウンドロビンの検証と同様にリクエストを 10000 回送ってみると、ちょうど flask1: 6000, flask2: 4000 となっていました。確かに TraefikService で指定した通り 3:2 の割合でリクエストが分散されています。

TCP ルーティング

IngressRouteTCP リソースを作成すると tcp のルーティング設定を行うことができます。
ここでは検証として 2 つの postgres svc を用意し、クライアントからの接続が IngressRouteTCP によって分散されることを見てみます。

IngressRouteTCP リソースを作成。weight の設定は WRR と同様に 3:2 に設定します。

apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
  name: postgres
spec:
  entryPoints:
    - web
  routes:
  - match: "HostSNI(`psql.test`)"
    priority: 1
    services:
    - name: postgres-1
      port: 5432
      weight: 3
    - name: postgres-2
      port: 5432
      weight: 2
  tls: {}

pod と svc

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres-1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: postgres1
  template:
    metadata:
      labels:
        app: postgres1
    spec:
      containers:
      - name: postgres
        image: postgres
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_DB
          value: postgres
        - name: POSTGRES_USER
          value: postgres
        - name: POSTGRES_PASSWORD
          value: postgres
---
apiVersion: v1
kind: Service
metadata:
  name: postgres-1
spec:
  ports:
  - port: 5432
    targetPort: 5432
  selector:
    app: postgres1
  type: ClusterIP

pod は postgres1, 2 で各 3 つずつ起動。

postgres-1-6787577bd7-drh9f         1/1     Running   0          9m40s   10.244.1.50   k8s-w1   <none>           <none>
postgres-1-6787577bd7-kqt4j         1/1     Running   0          9m38s   10.244.1.51   k8s-w1   <none>           <none>
postgres-1-6787577bd7-m2w6k         1/1     Running   0          9m43s   10.244.1.49   k8s-w1   <none>           <none>
postgres-2-65c7c6788f-6plvf         1/1     Running   0          15m     10.244.1.43   k8s-w1   <none>           <none>
postgres-2-65c7c6788f-9cn9x         1/1     Running   0          15m     10.244.1.44   k8s-w1   <none>           <none>
postgres-2-65c7c6788f-zsxxs         1/1     Running   0          15m     10.244.1.45   k8s-w1   <none>           <none>

postgres への接続には以下の python スクリプトを使用します。postgres に接続後、inet_server_addr() で pod の IP アドレスが取得できるのでどの pod にルーティングされたか判別できます。

check.py
import psycopg2
import csv

def connect(count, writer):
    conn = psycopg2.connect(
      dbname="postgres",
      user="postgres",
      password="postgres",
      host="psql.test",
      port=80
  )
    cur = conn.cursor()
    cur.execute("SELECT inet_server_addr();")
    server_ip = cur.fetchone()[0]
    data = [count, server_ip]
    print(data)
    writer.writerow(data)

    cur.close()
    conn.close()

with open("out.csv", "w") as f:
    writer = csv.writer(f)
    for i in range(10000):
        connect(i, writer)

上記を実行して結果を集計したところ、weight で割り当てたとおり postgres-1 svc に 6000 回、postgres-2 svc に 4000 回の通信が分散されていることが確認できました。よって TCP 通信も適切に分散されていることがわかります。

GRPC ルーティング

https://doc.traefik.io/traefik/user-guides/grpc/

traefik では GRPC 通信のルーティングも設定できます。ドキュメントでは grpc-go helloworld を使った例が記載されているので、こちらを参考に動作検証してみます。

ローカルでの動作検証

まず quickstart の手順に沿ってローカルで grcp サーバーを起動して正常に動作することを確認します。

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
$ git clone -b v1.65.0 --depth 1 https://github.com/grpc/grpc-go
$ cd grpc-go/examples/helloworld

後のエラー回避のため server 側の go コードに以下を追加。

greeter_server/main.go

import (
+	"google.golang.org/grpc/reflection"
}

	s := grpc.NewServer()
+	reflection.Register(s)
	pb.RegisterGreeterServer(s, &server{})

サーバーを起動

$ go run greeter_server/main.go
2024/07/13 18:06:52 server listening at [::]:50051

client 用の go ファイルも用意されていますが、ここでは grpcurl を使って確認します。

$ grpcurl -plaintext  0.0.0.0:50051 list
grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection
helloworld.Greeter

$ grpcurl -plaintext -d '{"name": "test"}'  0.0.0.0:50051 helloworld.Greeter/SayHello
{
  "message": "Hello test"
}

正常に返信が来ることが確認できました。次に k8s 内で pod で実行するため、grpc server をコンテナイメージ化します。

Dockerfile
FROM golang:1.23rc1-bookworm

RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28 && \
    go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
RUN  git clone -b v1.65.0 --depth 1 https://github.com/grpc/grpc-go
WORKDIR grpc-go/examples/helloworld

COPY greeter_server/main.go greeter_server/main.go
RUN go build -o /server greeter_server/main.go

ENTRYPOINT [ "/server" ]

k8s での実行

まず上記で作成したイメージを使用する pod, svc を作成。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grpc-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: grpc
  template:
    metadata:
      labels:
        app: grpc
    spec:
      containers:
      - name: grpc
        image: registry.centre.com:5000/grpc-test-server
        ports:
        - containerPort: 50051
---
apiVersion: v1
kind: Service
metadata:
  name: grpc
spec:
  ports:
  - name: grpc
    port: 50051
    targetPort: 50051
  selector:
    app: grpc
  type: ClusterIP

ドキュメント では http の routes と server で grpc server への宛先を指定しているので、これに対応する IngressRoute で routes[].services に上記の grpc svc を指定すればルーティングされるようになります。ただ実際に試すとそのままではルーティングされず、scheme: h2c を設定する必要がありました(参考)。ドキュメントの以下の記述に対応しているのかもしれません。

We don't need specific configuration to use gRPC in Traefik, we just need to use h2c protocol, or use HTTPS communications to have HTTP2 with the backend.

最終的な IngressRoute の中身は以下。

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: grpc
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`grpc.test`) && PathPrefix(`/`)
    kind: Rule
    services:
    - name: grpc
      port: 50051
      scheme: h2c

これで k8s クラスタ外部からの通信も対象の grpc server pod にルーティングされるようになります。traefik の LoadBalancer svc に grpcurl でリクエストを送ると実際に通信できていることが確認できます。

$ cat /etc/hosts
192.168.3.124 grpc.test

$ grpcurl -insecure grpc.test:31830 list
grpc.reflection.v1.ServerReflection
grpc.reflection.v1alpha.ServerReflection
helloworld.Greeter

$ grpcurl -insecure -d '{"name": "test"}'  grpc.test:31830 helloworld.Greeter/SayHello
{
  "message": "Hello test"
}

k8s クラスタ外へのルーティング

上記の例ではルーティング先はいずれも k8s 内のサービスとなっていますが、k8s の externalName を利用することで k8s 外にあるアプリケーションなどにルーティングすることも出来ます。
ただ実際試してみると externalName services not allowed のエラーが出力されました。こちら を参考に traefik の args に以下の 4 つを追加することで機能しました。

Containers:
  traefik:
    Args:
      ...
      --providers.kubernetesingress.allowemptyservices=true
      --providers.kubernetesingress.allowexternalnameservices=true
      --providers.kubernetescrd.allowemptyservices=true
      --providers.kubernetescrd.allowexternalnameservices=true

後でわかりましたが helm chart で allowExternalNameServices などを true に設定することで上記が引数に追加されるようです。
https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L222-L231


こちらの動作も実際に試してみます。今回は k8s クラスタ外部の IP アドレス 192.168.3.181 のホスト上で起動する nginx にドメイン名 external.test で通信できるように設定します。


通信経路

IngressRoute と service を以下のように作成。IngressRoute の spec は今までと同じですが、service を spec.type: ExternalName として作成します。

---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: external
spec:
  entryPoints:
    - web
  routes:
  - match: Host(`external.test`)
    kind: Rule
    services:
    - name: external-svc
      port: 80

---
apiVersion: v1
kind: Service
metadata:
  name: external-svc
spec:
  externalName: external.test
  type: ExternalName
  ports:
    - port: 80

externalName で指定したドメイン名 external.test を名前解決できるように coredns を編集したり外部 DNS を参照するように設定する必要があります。ここでは簡単のため coredns configmap に直接ドメインと ip address を記載します。

coredns
$ k -n kube-system edit configmaps coredns

data:
  Corefile: |
    .:53 {
      errors
        health {
          lameduck 5s
        }
+        hosts {
+          192.168.3.181 external.test
+          fallthrough
+        }
...

これで traefik を経由して対象の nginx に通信できるようになったので、クラスタ内から external.test に向けてリクエストを送信するとレスポンスが返ってきます。クラスタ外部からは今までと同様に traefik LB svc にアクセスすれば通信できます。

$ curl -H "Host: external.test" http://10.109.84.42:80 -I
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 615
Content-Type: text/html
Date: Thu, 11 Jul 2024 17:22:17 GMT
Etag: "6655da96-267"
Last-Modified: Tue, 28 May 2024 13:22:30 GMT
Server: nginx/1.27.0

traefik pod のログから、external svc への route が serverName: f33c40c9f1fdd360 として作成されているのがわかり、上記の curl リクエストでこの server が選択されていることが確認できます。

2024-07-11T17:19:33Z DBG github.com/traefik/traefik/v3/pkg/server/service/service.go:301 > Creating server entryPointName=web routerName=traefik-external-4004d7032e8ea90ff1d0@kubernetescrd serverName=f33c40c9f1fdd360 serviceName=traefik-external-4004d7032e8ea90ff1d0@kubernetescrd target=http://external.test:80
2024-07-11T17:22:40Z DBG github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:196 > Service selected by WRR: f33c40c9f1fdd360

nginx 側のアクセスログでは、traefik pod が稼働する node の IP アドレス 192.168.3.124 からリクエストが送信されていることが確認できます。

192.168.3.124 - - [11/Jul/2024:17:22:40 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.88.1" "10.244.0.0"

k8s 側での externalName svc の作成やドメイン名の名前解決の対応は必要ですが、この機能を利用すると traefik を docker で使うときのように通常の proxy server として利用できます。traefik バックエンドのサービスにアクセスするクライアントは traefik が k8s 上で動いていることを意識せず通常の proxy や LoadBalancer のように使用でき、traefik 管理側の観点では k8s の可用性などのメリットを享受できるためいろいろな使い方ができそうです。

OIDC 認証

traefik proxy の有償版 traefik enterprise では OIDC 認証 の機能があるのでデフォルトで外部の OIDC provider と連携できますが、OSS 版の traefik proxy ではこの機能がありません。しかし、Middleware の ForwardAuth を使うと外部の認証サービス側で認証を行い、その結果に基づいてルーティング先へのアクセスを許可・拒否できます。認証自体はは外部認証サービス側で実行するため、Basic 認証や OIDC 認証などが使用できます。

例えば以前に記事にした 認証・認可サーバの OSS Authelia の ドキュメント ではまさに traefik を使って OIDC 認証を行う例が記載されています。もちろん他の oidc provider でも同様の構成は実現できますが、ここでは一例として authelia を使った OIDC 認証が実行できることを試してみます。

ここではバックエンドの nginx にルーティングする前に authelia での OIDC 認証 (シングルサインオン) を実行し、ユーザー認証に成功したら実際に nginx pod にルーティングする構成にします。


通信経路

まず authelia への転送は traefik の middleware で行うので、 Middleware カスタムリソースのマニフェストを作成します。

middleware.yml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: authelia
spec:
  forwardAuth:
    address: "http://auth.oidc.test:9091/api/authz/forward-auth"
    trustForwardHeader: true
    authResponseHeaders:
      - 'Remote-User'
      - 'Remote-Groups'
      - 'Remote-Email'
      - 'Remote-Name'
    tls:
      insecureSkipVerify: true

middleware を適用するには IngressRouteroutes[].middleware に上記の metadata.name を指定します。

apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: nginx-oidc
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`nginx.oidc.test`) && PathPrefix(`/`)
    kind: Rule
    middlewares:
      - name: authelia
    services:
    - name: nginx
      port: http

authelia は k8s クラスタ外の別ホスト上で docker で作成します(k8s 内で立てても ok )。Authelia の設定ファイル configuration.yml で必要な設定は以下。

  • server.tls
    • authelia を HTTPS に対応させるための証明書のパス。
  • endpoints.authz.forward-auth
    • implementation: ForwardAuth を指定。
  • session.cookies
    • authelia_url: authelia の認証を行うエンドポイントとなるドメイン名。好きな値で良いが、ここでは auth.oidc.test とする。
    • domain: authelia_url で指定したドメイン名のサブドメイン名に設定する。
  • access_control.rules
    • domain: OIDC 認証の対象とするドメイン名。
    • policy: one_factor に指定。

その他の設定は ドキュメント以前書いた記事 を参照

configuration.yml
theme: "dark"
server:
  address: 'tcp://:9091'
  tls:
    key: /certs/authelia.centre.com.key
    certificate: /certs/authelia.centre.com.crt
  endpoints:
    authz:
      forward-auth:
        implementation: 'ForwardAuth'
log:
  level: 'debug'
identity_validation:
  reset_password:
    jwt_secret: 'a_very_important_secret'

authentication_backend:
  file:
    path: '/config/users_database.yml'
  refresh_interval: 1m
access_control:
  default_policy: 'deny'
  rules:
    - domain: 'nginx.oidc.test'
      policy: 'one_factor'
    - domain: 'auth.oidc.test'
      policy: 'one_factor'
session:
  secret: 'insecure_session_secret'
  cookies:
    - name: 'authelia_session'
      domain: 'oidc.test'
      authelia_url: 'https://auth.oidc.test:9091'
      expiration: '1 hour'  # 1 hour
      inactivity: '5 minutes'  # 5 minutes
regulation:
  max_retries: 3
  find_time: '2 minutes'
  ban_time: '5 minutes'
storage:
  encryption_key: 'you_must_generate_a_random_string_of_more_than_twenty_chars_and_configure_this'
  local:
    path: '/config/db.sqlite3'
notifier:
  filesystem:
    filename: '/config/notification.txt'

authelia を起動するための docker-compose.yml

docker-compose.yml
---
services:
  authelia:
    image: authelia/authelia
    container_name: authelia
    volumes:
      - ./authelia:/config
      - ./certs:/certs
    ports:
      - 9091:9091
    environment:
      - TZ=Asia/Tokyo

ディレクトリ構成

.
├── authelia
│   ├── configuration.yml
│   ├── notification.txt
│   └── users_database.yml
├── certs
│   ├── authelia.centre.com.crt
│   └── authelia.centre.com.key
└── docker-compose.yml

上記を実行して authelia の docker コンテナを起動。

k8s クラスタから外部の authelia に名前解決できるよう coredns の configmap に追加。

coredns
apiVersion: v1
data:
  Corefile: |
     .:53 {
          ....
          hosts {
            192.168.3.181 external.test
+            192.168.3.18 auth.oidc.test
            fallthrough
          }

最後に k8s クラスタ外部のクライアントから各ドメイン名が解決できるよう /etc/hosts などに追加。

/etc/hosts
192.168.3.124 nginx.oidc.test    # traefik pod の node の ip address
192.168.3.18 auth.oidc.test      # authelia コンテナのホストの ip address

これで準備が完了しました。動作確認はブラウザを使ったほうが分かりやすいので chrome で確認します。

まず k8s 外部クライアントから k8s クラスタ上の traefik LoadBalancer svc にドメイン名 nginx.oidc.test でアクセスすると証明書の警告が表示されます(traefik の証明書が信頼されていないため)。そのまま進むと authelia のドメイン auth.oidc.test にリダイレクトされ、認証画面が表示されます。

authelia 側で設定した username/password を入力すると認証に成功し、nginx pod にルーティングされます。

2 回目以降は SSO が効くので authelia 側のセッションが切れるまではユーザー認証無しで nginx にアクセスできます。準備はやや大変ですが使う分には簡単に OIDC 認証できました。

このように ForwardAuth middleware を使用することで外部の OIDC プロバイダーを使用した OIDC 認証が実現できます。バックエンドのアプリケーションやサービス自体が OIDC 認証に対応していればそちらを使ってもいいですが、対応していないサービスを使う場合や自作アプリケーションで OIDC 処理の実装や管理が面倒な場合、この機能を使うことで処理を外部の OIDC プロバイダーに任せることができます。

Kubernetes Gateway API

概要

https://doc.traefik.io/traefik/routing/providers/kubernetes-gateway/

Gateway API は k8s の ingress や LoadBalancer の機能を元にロール志向や拡張性などを強化した次世代の機能です。詳細は Gateway API を参照。
Gateway API はそれ単体でリソースを管理するわけではなく、ingress リソースと ingress controller の関係のように Gateway Controller と呼ばれるコンポーネントを用いることで動作します。対応している Gateway Controller の一覧は Implementations にあり、Traefik Proxy も GA となっています。一方で traefik proxy のドキュメントでは実験的となっており、まだ全ての機能が実装されているわけでないようです。

This provider is proposed as an experimental feature and partially supports Gateway API v1.0.0 specification.

traefik における Kubernetes Gateway API provider では kubernetes CRD provider と同様に Gateway API のリソースに基づいて traefik の構成を更新するようになっています。現時点では以下のリソースが実装されている状況になっています。

  • GatewayClass: defines a set of Gateways that share a common configuration and behaviour.
  • Gateway: describes how traffic can be translated to Services within the cluster.
  • HTTPRoute: defines HTTP rules for mapping requests from a Gateway to Kubernetes Services.
  • TCPRoute: defines TCP rules for mapping requests from a Gateway to Kubernetes Services.
  • TLSRoute: defines TLS rules for mapping requests from a Gateway to Kubernetes Services.

一方で GRPCRoute などはまだ未実装のようです。

kubernetes CRD で検証したように HTTP や TCP のルーティング設定、TLS の設定などは Traefik proxy 自体の機能で管理できるため、traefik をメインに使う場合はわざわざ Gateway API を使ってリソースを管理するメリットはあまりなさそうです。一方で ingressClass のように gateway を扱う gateway controller がクラスタ内に複数存在するような場合や、 gateway を分割して用途に合わせて運用していくようなケースでは Gateway API provider を使っていくメリットがあるとも言えます。

使ってみる

Gateway API の実装は各 gateway controller に依存することから、内容を見ただけではどのように動作するかわかりづらい部分も結構あります。なので実際に使って動作を見てみます。
Gateway API 機能を有効化するためには traefik の実行時引数に --experimental.kubernetesgateway--providers.kubernetesgateway=true を指定する必要があります。helm values ではそれぞれ以下に対応しています。
experimental.kubernetesgateway は experimental.kubernetesGateway.enabled: true に対応。

https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L122-L130

providers.kubernetesgateway は providers.kubernetesGateway.enabled: true に対応。

https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L265-L274

helm install して構築すると、traefik.io/gateway-controller という名前の gateway Controller によって管理される gatewayClass: traefik が作成されます。

$ k describe gatewayclasses.gateway.networking.k8s.io traefik

Spec:
  Controller Name:  traefik.io/gateway-controller
Status:
  Conditions:
    Last Transition Time:  2024-07-14T14:38:34Z
    Message:               Handled by Traefik controller
    Observed Generation:   1
    Reason:                Handled
    Status:                True
    Type:                  Accepted
Events:                    <none>

これは ingress で言うところの ingressClass と ingress controller を用意した段階に対応するので、これから実際の gateway API リソースを作っていきます。リソースの設定例が Example に記載されているので、これを参考に Gateway, HTTPRoute を作成します。

---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: my-gateway
spec:
  gatewayClassName: traefik
  listeners:
    - name: websecure
      protocol: HTTPS
      port: 8443
      tls:
        certificateRefs:
          - kind: Secret
            name: nginx-secret

---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: http-app
spec:
  parentRefs:
    - name: my-gateway
  hostnames:
    - nginx.gateway.centre.com
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: nginx
          port: 80
          weight: 1

これらのリソースを作成すると traefik の gateway controller が検知して traefik の構成をセットアップしてくれます。traefik pod の debug ログを見ると以下のようなルーティング設定が追加されています。

2024-07-13T15:09:02Z DBG github.com/traefik/traefik/v3/pkg/server/configurationwatcher.go:227 > Configuration received config={"http":{"routers":{"traefik-http-app-my-gateway-websecure-82dd8725fafb6ba9097d":{"entryPoints":["websecure"],"rule":"Host(nginx.gateway.centre.com) \u0026\u0026 PathPrefix(/)","ruleSyntax":"v3","service":"traefik-http-app-my-gateway-websecure-82dd8725fafb6ba9097d-wrr","tls":{}}},"services":{"traefik-http-app-my-gateway-websecure-82dd8725fafb6ba9097d-wrr":{"weighted":{"services":[{"name":"traefik-nginx-80","weight":1}]}},"traefik-nginx-80":{"loadBalancer":{"passHostHeader":true,"responseForwarding":{"flushInterval":"100ms"},"servers":[{"url":"http://10.244.1.102:80"},{"url":"http://10.244.1.103:80"}]}}}},"tcp":{},"tls":{},"udp":{}} providerName=kubernetesgatewa

整形すると以下のような traefik リソースが追加されていることがわかります。

http:
  routers:
    traefik-http-app-my-gateway-websecure-82dd8725fafb6ba9097d:
      entryPoints:
        - websecure
      rule: Host(`nginx.gateway.centre.com`) && PathPrefix(`/`)
      ruleSyntax: v3
      service: traefik-http-app-my-gateway-websecure-82dd8725fafb6ba9097d-wrr
      tls: {}
  services:
    traefik-http-app-my-gateway-websecure-82dd8725fafb6ba9097d-wrr:
      weighted:
        services:
          - name: traefik-nginx-80
            weight: 1
    traefik-nginx-80:
      loadBalancer:
        passHostHeader: true
        responseForwarding:
          flushInterval: 100ms
        servers:
          - url: http://10.244.1.102:80
          - url: http://10.244.1.103:80
tcp: {}
tls: {}
udp: {}

HTTPRoute で定義した hostname: nginx.gateway.centre.com で traefik LB svc に curl すると nginx pod に到達します。

$ curl -k -H "Host: nginx.gateway.centre.com" https://10.98.162.149:443
...
<title>Welcome to nginx!</title>

このように Gateway API CRD でも kubernetes CRD を使ったときと同様に対象の svc にルーティングするように設定できます。

作成した Gateway API の CRD と traefik のリソースを対応付けると以下のようになります。上記の例では HTTPRoute にバックエンドの nginx svc のみ指定していますが、用途によって middleware や serviceTransport (TraefikService) を関連付けることもできます。詳細は HTTPROute を参照。

上記の例では gateway リソースで gatewayClassName: traefik を指定しているので CRD を元に traefik のリソースが自動で構成されましたが、他の gatewayClass を指定すればそちらの gateway controller を元にリソースが管理されます。ingressClass や storageClass のように複数の gatewayClass が存在する際に指定した class に基づいて実装や管理を分けられる点が Gateway API を使うメリットの一つとなっています。

その他

その他細かいもののメモ

Observability

Log

ログは通常の traefik のログの他に アクセスログ も有効化できます。デフォルトでは無効化されているので helm values の logs.access.enabled: true で有効化します。その他 formta や記録するログのフィルタリング、buffesize なども設定できます。

https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L316-L343

Metrics

metrics は traefik pod の port 9100 でデフォルトで公開されており、traefik の entrypoint として metrics という名前が設定されています。

Containers:
  traefik:
    Ports:         9100/TCP, 9000/TCP, 8000/TCP, 8443/TCP
    Args:
      --entryPoints.metrics.address=:9100/tcp
      --metrics.prometheus=true
      --metrics.prometheus.entrypoint=metrics

上記の port の /metrics にアクセスすると prometheus 形式のメトリクスが取得できます。

$ curl  http://10.244.1.55:9100/metrics
# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.5735e-05
go_gc_duration_seconds{quantile="0.25"} 0.000143858

svc はデフォルトで作成されていないので、svc 経由で取得する場合は例えば以下のようなサービスを作成する必要があります。

apiVersion: v1
kind: Service
metadata:
  name: traefik-metrics
spec:
  selector:
    app.kubernetes.io/instance: traefik-traefik
    app.kubernetes.io/name: traefik
  ports:
  - name: metrics
    protocol: TCP
    port: 9100
    targetPort: 9100
  type: ClusterIP
$ k get svc
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
traefik-metrics   ClusterIP      10.109.51.238   <none>        9100/TCP                     12s

$ curl  10.109.51.238:9100/metrics

# HELP go_gc_duration_seconds A summary of the pause duration of garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 3.5735e-05

デフォルトは prometheus 形式ですが引数の値を変更することで以下の形式にも変更可能。

  • Datadog
  • InfluxDB2
  • Prometheus
  • StatsD

あとは Opentelemetry 形式にもできるようです。詳細は以下を参照。

https://doc.traefik.io/traefik/observability/metrics/overview/

Trace

opentelemetry 形式の trace に対応しています。

helm でインストールしたものではデフォルトで有効になっていないので、helm values のこのあたりを設定して有効化できます。

https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L488-L530

ちなみに github 上では http エンドポイントが http://localhost:4318/v1/metrics と書かれていますが、ドキュメントでは http://localhost:4318/v1/traces になっています。traces のエンドポイントはだいたい traces なのでおそらくドキュメントのほうが正しいです。

Dashboard

https://doc.traefik.io/traefik/operations/dashboard/

helm だとダッシュボードは有効化されていますが entrypoint が設定されていないので、helm values の
ingressRoute.dashboard.enabled を true に設定します。

https://github.com/traefik/traefik-helm-chart/blob/51fc5647f06c757c671f1a283d531799b5fd4316/traefik/values.yaml#L153-L157

設定後に再デプロイすると http://[pod_ip]:9000/dashboard/ でダッシュボードにアクセスできます。ダッシュボードでは router や service の状態, 有効化されている provider の情報などの構成情報を視覚的に確認できます。

おわりに

Kubernetes で traefik を使う際は kubernetes CRD で traefik リソースを管理できるので、docker で設定ファイルに proxy 設定を記載するのとほぼ変わらない使用感で運用することができます。また、 k8s の ingress リソースを管理する ingress controller や Gateway API を管理する Gateway controller としても使用できます。traefik 自体の豊富な機能もそのまま使えるので、OSS の proxy サーバーでどれを使うか迷っている際は有力な候補となりそうです。

Links

Discussion