k3s 環境の Ingress で外部認証(カスタム認証)を行なってみます。
k3s ではデフォルトで Traefik が用いられており、Traefik の forwardAuth
を設定する事で外部認証を行う事ができます。
ここでは、「Lima で k3s 環境を構築し BuildKit でアプリをビルドして実行」で構築した k3s 環境(Lima で実行)をそのまま利用する事にします。
1. 認証処理
単純な認証処理を Deno で実装してみました。
Authorization
ヘッダーの値が abc123
だった場合にのみ、認証成功(HTTP ステータスコード 200)となるようにしています。
import { serve } from 'https://deno.land/std@0.182.0/http/server.ts'
const port = parseInt(Deno.env.get('PORT') ?? '8100')
const handler = (req: Request) => {
req.headers.forEach((v, k) => console.log(`header ${k}:${v}`))
console.log('-----')
const v = req.headers.get('authorization') ?? ''
if (v === 'abc123') {
// 認証成功
return new Response(undefined, { status: 200 })
}
return new Response(undefined, { status: 403 })
}
serve(handler, { port })
この認証処理をホスト OS(macOS)側で実行しておきます。
% deno run --allow-all auth/auth.ts
Listening on http://localhost:8100/
2. Ingress 設定
ホスト OS で実行している認証処理を Kubernetes 環境から呼び出せるように ExternalName
の Service を作成します。
kind: Service
apiVersion: v1
metadata:
name: auth-svc
spec:
type: ExternalName
externalName: host.lima.internal
$ sudo kubectl apply -f auth-svc.yaml
Traefik v2 では forwardAuth
等の機能は Middleware となっており、kind: Middleware
リソースで設定できます。
下記では forwardAuth の他に stripPrefix
(URL のパスから prefix に合致する先頭を取り除く)も定義して、Ingress へ適用しています。
Middleware は traefik.ingress.kubernetes.io/router.middlewares
の値として文字列で指定します。
個々の Middleware は <namespace>-<name>@kubernetescrd
で指定でき、",
" で区切る事で複数指定できるようです。
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: sampleauth
spec:
forwardAuth:
address: http://auth-svc.default.svc.cluster.local:8100
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: sample-prefix
spec:
stripPrefix:
prefixes:
- /sample/
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sample-ingress
annotations:
traefik.ingress.kubernetes.io/router.middlewares:
default-sampleauth@kubernetescrd, default-sample-prefix@kubernetescrd
spec:
rules:
- http:
paths:
- path: /sample/
pathType: Prefix
backend:
service:
name: sampleapp
port:
number: 8080
これを適用して Ingress を作成します。
$ sudo kubectl apply -f sample-ingress.yaml
ここで、forwardAuth の address の値を http://auth-svc:8100
のようにすると、次のように名前解決に失敗するので注意が必要でした。(k3s の Traefik は kube-system 名前空間で実行しているためだと思われる)
level=debug msg="Error calling http://auth-svc:8100. Cause: Get \"http://auth-svc:8100\": dial tcp: lookup auth-svc on 10.43.0.10:53: no such host" middlewareType=ForwardedAuthType middlewareName=default-sampleauth@kubernetescrd
このエラーを確認するには、Traefik のログレベルを変更する必要があり、次のような HelmChartConfig
を適用します。
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
additionalArguments:
- "--log.level=DEBUG"
また、Traefik の HelmChartConfig で設定できる項目等は下記で確認できます。
3. 動作確認
ホスト OS からアクセスして動作確認してみます。
まずは、認証に失敗するケースです。HTTP ステータスコードが 403 となりました。
% curl http://localhost/sample/test1 -v
* Trying 127.0.0.1:80...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /sample/test1 HTTP/1.1
> Host: localhost
> User-Agent: curl/7.87.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 403 Forbidden
< Content-Length: 0
・・・
次に成功するケースです。
stripPrefix も機能しており、特に問題無さそうです。
% curl http://localhost/sample/test2 -H "Authorization: abc123"
ok:/test2
認証処理側の出力結果は次のようになりました。
% deno run --allow-all auth/auth.ts
Listening on http://localhost:8100/
header accept:*/*
header accept-encoding:gzip
header host:auth-svc.default.svc.cluster.local:8100
header user-agent:curl/7.87.0
header x-forwarded-for:10.42.1.39
header x-forwarded-host:localhost
header x-forwarded-method:GET
header x-forwarded-port:80
header x-forwarded-proto:http
header x-forwarded-server:traefik-6b5c9ddb87-x528k
header x-forwarded-uri:/sample/test1
header x-real-ip:10.42.1.39
-----
header accept:*/*
header accept-encoding:gzip
header authorization:abc123
header host:auth-svc.default.svc.cluster.local:8100
header user-agent:curl/7.87.0
header x-forwarded-for:10.42.1.39
header x-forwarded-host:localhost
header x-forwarded-method:GET
header x-forwarded-port:80
header x-forwarded-proto:http
header x-forwarded-server:traefik-6b5c9ddb87-x528k
header x-forwarded-uri:/sample/test2
header x-real-ip:10.42.1.39
-----
x-forwarded-for 等に設定されている 10.42.1.39
の Pod の内容は以下の通りです。
$ sudo kubectl -n kube-system describe pod/svclb-traefik-3c0b1c72-7pngl
Name: svclb-traefik-3c0b1c72-7pngl
Namespace: kube-system
Priority: 0
Service Account: svclb
Node: lima-limak3s/192.168.5.15
Start Time: Sat, 29 Apr 2023 21:53:17 +0000
Labels: app=svclb-traefik-3c0b1c72
controller-revision-hash=7884fc7946
pod-template-generation=1
svccontroller.k3s.cattle.io/svcname=traefik
svccontroller.k3s.cattle.io/svcnamespace=kube-system
Annotations: <none>
Status: Running
IP: 10.42.1.39
IPs:
IP: 10.42.1.39
Controlled By: DaemonSet/svclb-traefik-3c0b1c72
...省略
レスポンスが遅くなる原因 (CoreDNS メトリクスを確認)
ここで、初回アクセスと時間を置いてからアクセスした場合のレスポンスが異様に遅いのが気になりました。
0.01秒ほどの処理が、この場合は以下のように 1秒程度かかります。
% time curl http://localhost/sample/test6 -H "Authorization: abc123"
...省略 0.00s user 0.01s system 0% cpu 1.022 total
調べてみたところ CoreDNS のメトリクスで気になる箇所を見つけました。
...省略
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.00025"} 0
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.0005"} 2
...省略
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.256"} 6
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.512"} 6
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="1.024"} 11
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="2.048"} 11
...省略
coredns_forward_request_duration_seconds_sum{rcode="NOERROR",to="192.168.5.3:53"} 5.018901210999999
coredns_forward_request_duration_seconds_count{rcode="NOERROR",to="192.168.5.3:53"} 11
...省略
上記の状態から http://localhost/sample/xxx
へ 1回アクセスし、レスポンスに 1秒かかった後のメトリクスが以下です。
...省略
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.00025"} 0
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.0005"} 3
...省略
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.256"} 7
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="0.512"} 7
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="1.024"} 13
coredns_forward_request_duration_seconds_bucket{rcode="NOERROR",to="192.168.5.3:53",le="2.048"} 13
...省略
coredns_forward_request_duration_seconds_sum{rcode="NOERROR",to="192.168.5.3:53"} 6.020857919999999
coredns_forward_request_duration_seconds_count{rcode="NOERROR",to="192.168.5.3:53"} 13
...省略
全体的に 2カウント増えており、le="0.0005"
と le="1.024"
でそれぞれ 1カウントずつ増えています。(累計になっており分かり難いかもしれませんが)
le="1.024"
のカウントが増えている事から、CoreDNS からの forward つまりは host.lima.internal
の名前解決に 1秒程度かかってしまっているのが遅い原因だと考えられます。
更に、レスポンスが速い時(0.01秒程度)は上記 forward のカウントが増えなかったので、これが原因とみて間違いなさそうでした。
ちなみに、CoreDNS のメトリクスは port-forward してホスト OS 側から取得しました。
$ sudo kubectl port-forward -n kube-system service/kube-dns 9153:9153
Forwarding from 127.0.0.1:9153 -> 9153
Forwarding from [::1]:9153 -> 9153
% curl http://127.0.0.1:9153/metrics