Kong GatewayをHelmで構築する際、デフォルトでKongは自己署名証明書を作成して起動する。
ただ、この証明書の管理をcert-managerが生成する証明書に置き換えることも出来る。
そうすることで、証明書の管理がManifestベースで可能となり、cert-managerによる自動更新も働くため管理コストが抑えられる。
これの検証に手間取ったので、記録がてら手順を残す。
なお、検証を行うにあたり以下は用意済みのものとする。
- helmコマンド
- Kubernetesクラスタの構築
-
type: LoadBalancer
を使うためのLBの用意
ここではKubernetesはvSphere with Tanzu(Kubernetes:v1.25.7)を利用した。
また、以降でKICという言葉が出てくるが、これはKong Ingress Controllerの略である。
事前準備:cert-managerのインストールとIssuerの準備
Helmでサクッとインストールする。
helm repo add jetstack https://charts.jetstack.io --force-update
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
証明書を発行するためのIssuer
を作成する。
今回は自己署名証明書で進める。
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
EOF
Kong Gatewayの構築
今回、ドメインを用意するのが面倒だったので、ワイルドカードDNSサービスのnip.ioを使う。
また、AdminAPIとKong Managerのホスト名は以下とする。
- kong-admin.<KICのExternalIPをハイフンで繋いだもの>.nip.io
- kong-manager.<KICのExternalIPをハイフンで繋いだもの>.nip.io
検証環境ではKICのExternalIPが事前に分からなかったため、以下の流れで構築する。
- Helmの
values.yaml
を使ってKong GatewayとKICを立て、IPを取得 - KICのIPを
values.yaml
に反映 - 更新した
values.yaml
を使ってIngressを再デプロイ
最初にHelmのvalues.yaml
を作成する。
作成にあたり、公式のサンプル集を参考にした。
cat <<EOF > kong-values.yaml
env:
database: "postgres"
admin:
enabled: true
annotations:
konghq.com/protocol: "https"
ingress:
enabled: true
ingressClassName: kong
tls: kong-kong-admin-cert
hostname: kong-admin.10-214-154-167.nip.io
postgresql:
enabled: true
certificates:
enabled: true
clusterIssuer: "selfsigned-issuer"
admin:
commonName: "10-214-154-167.nip.io"
dnsNames:
- "*.10-214-154-167.nip.io"
manager:
annotations:
konghq.com/protocol: "https"
ingress:
enabled: true
ingressClassName: kong
tls: kong-kong-admin-cert
hostname: kong-manager.10-214-154-167.nip.io
EOF
設定で試行錯誤したのでいくつか補足説明をする。
annotations:
konghq.com/protocol: "https"
上記のannotationはService
に設定され、これがないとKICがプロトコルをhttpとして扱ってしまい、"400 The plain HTTP request was sent to HTTPS port
が表示されて上手くいかないので設定する必要がある。
tls: kong-kong-admin-cert
上記はIngress
をTLS化する際に証明書を渡すパラメータであり、cert-managerのkind: Certificate
によって自動生成されるSecret
名を指定する。
これは命名ルールがドキュメント等で明文化されていない気がするが、Helm Chart内で<Helmのリリース名>-<コンポーネント名>-cert
として定義されている。
Helmのリリース名をkong
以外にする場合は変更する必要があるので注意。
hostname: kong-admin.10-214-154-167.nip.io
これは前述のように後からkong-admin.<KICのExternalIPをハイフンで繋いだもの>.nip.io
に置き換えるため、一旦仮で入れている。置換し易い文字列なら何でもOK。
clusterIssuer: "selfsigned-issuer"
これは先ほど作成したcert-managerのClusterIssuer
を設定している。
ここで定義したものは他のコンポーネント全てに利用されるが、コンポーネント毎に設定することも可能。
commonName: "10-214-154-167.nip.io"
dnsNames:
- "*.10-214-154-167.nip.io"
証明書の内容を指定する。ここも後で置換する。
なお、KongのHelmの設定でデフォルトでKICもデプロイされるようになっているため、このvalues.yaml
を適用するとKICもセットでデプロイされる。
(1Pod内にKong GatewayとKICそれぞれのイメージが含まれる)
作成したvalues.yaml
を使ってKong GatewayとKICをデプロイする。
helm upgrade -i -f kong-values.yaml kong kong/kong -n kong --create-namespace
デプロイすると、以下のようにIngress
が作成される。
$ kubectl get ing -n kong
NAME CLASS HOSTS ADDRESS PORTS AGE
kong-kong-admin kong kong-admin.10-214-154-167.nip.io 10.214.154.169 80, 443 72s
kong-kong-manager kong kong-manager.10-214-154-167.nip.io 10.214.154.169 80, 443 72s
ワイルドカードDNSサービスを利用すると、kong-admin.10-214-154-167.nip.io
にアクセスすると10.214.154.167
にアクセスできる。
しかし、KICがProxyするアドレス(10.214.154.169
)と異なるため、このホスト名ではアクセス出来ない。
そのため、values.yaml
を書き換えて再デプロイする。
KIC_IP=$(kubectl get svc kong-kong-proxy -n kong -o jsonpath={.status.loadBalancer.ingress[0].ip} | sed "s/\./-/g")
sed -i "s/10-214-154-167/$KIC_IP/g" kong-values.yaml
helm upgrade -i -f kong-values.yaml kong kong/kong -n kong
問題なければ以下のように是正される。
$ kubectl get ing -n kong
NAME CLASS HOSTS ADDRESS PORTS AGE
kong-kong-admin kong kong-admin.10-214-154-169.nip.io 10.214.154.169 80, 443 8m42s
kong-kong-manager kong kong-manager.10-214-154-169.nip.io 10.214.154.169 80, 443 8m42s
これでKong ManagerとAdminAPIにアクセスできるようになる。
ただし、デプロイ直後はKong Managerにアクセスしても各種情報が適切に表示されず、Gateway ServiceなどにアクセスしてもGateway Services could not be retrieved
といったエラーが表示される。
これの原因が分からず、デバッグレベルをあげてもよく分からなかったのだが、解決方法としてはAdminAPIにアクセスすると何故か治る。
※追記:
KongManagerからAdminAPIにアクセスしているが、CORSのせいでエラーになっている。
なので、一度AdminAPIにアクセスすると治る。
ドメインを同じにしてしまえば問題は起きないので、気になる人はとりあえずドメインを合わせるとよい。
以下のような感じでAdminAPI側にアクセスする。
この後Kong Managerにアクセスすると適切に表示されるようになる。
証明書もcert-managerにより発行されたものになっている。
動作確認
構築したものが正常に動作するか念の為確認する。
以前「Kong AI Gatewayをdeck CLIで構築する」というのを書いたが、これをこのKubernetes環境で動かしてみる。
最初にenv.sh
という環境変数を詰めたファイルを作成する。
CohereのAPIトークンに加え、deck
の参照先Kong Gatewayを環境変数DECK_KONG_ADDR
を使って設定する。
また、自己署名証明書なのでDECK_TLS_SKIP_VERIFY
で証明書のチェックをスキップする。
cat <<EOF > env.sh
export COHERE_API_KEY=qIzGBRKxxxxxxx
export DECK_KONG_ADDR=https://kong-admin.10-214-154-169.nip.io
export DECK_TLS_SKIP_VERIFY=true
EOF
. env.sh
次に上述の記事で実行した内容をスクリプト化して実行する。
なお、今回はせっかくなのでGateway ServiceをHTTPS化した。
cat <<'EoF' > ./run.sh
#!/bin/bash
set -x
. env.sh
deck gateway dump -o kong-base.yaml --yes
export SERVICE_NAME=my-service
export SERVICE_ADDR=${SERVICE_NAME}.10-214-154-169.nip.io
cat <<EOF > ./service.yaml
services:
- name: $SERVICE_NAME
host: $SERVICE_ADDR
port: 443
protocol: https
EOF
deck gateway sync kong-base.yaml service.yaml
SERVICE_UUID=$(http ${DECK_KONG_ADDR}/services --verify no | jq '.data[] | select(.name == "'"$SERVICE_NAME"'")'.id)
echo $SERVICE_UUID
cat <<EOF > ./cohere-route.yaml
routes:
- name: cohere-api_cohere-chat
service:
id: $SERVICE_UUID
paths:
- "~/cohere-chat\$"
methods:
- POST
EOF
deck gateway sync kong-base.yaml service.yaml cohere-route.yaml
ROUTE_UUID=$(http ${DECK_KONG_ADDR}/routes --verify no | jq '.data[] | select(.name == "cohere-api_cohere-chat")'.id)
echo $ROUTE_UUID
cat <<EOF > ./cohere-ai-proxy.yaml
plugins:
- name: ai-proxy
route: $ROUTE_UUID
config:
route_type: "llm/v1/chat"
auth:
header_name: "Authorization"
header_value: "Bearer $COHERE_API_KEY"
model:
provider: "cohere"
name: "command"
options:
max_tokens: 512
temperature: 1.0
EOF
deck gateway sync kong-base.yaml service.yaml cohere-route.yaml cohere-ai-proxy.yaml
EoF
chmod +x ./run.sh
./run.sh
これでCohereへKong経由でAPIが投げられるようになっているので、試してみる。
curl -k -s --http1.1 -X POST https://my-service.10-214-154-169.nip.io/cohere-chat \
-H "Content-Type: application/json" --data-raw '{
"messages": [{
"role": "user",
"content": "Where is the capital of Japan?"
}] }'
ちょっと内容が変な気もするが、それっぽい答えが返ってきた。
{"id":"20ddf039-4f47-4f5e-a53f-b68f2488deaf","model":"command","choices":[{"message":{"content":"The capital of Japan is called Tokyo, it is the most populous metropolitan area in the world. \n\nTokyo is a very diverse city consisting of 23 city wards, multiple interwoven municipalities, and many neighborhoods. While blending traditional and modern cultures, it is known for its unique ambiance and ubiquitous skyscrapers, giving way to its popular name \"Tokyo Sky desper\" (Old Tokyotrapolis). \n\nTokyo will host the Summer Olympics of 2020, however, due to the COVID-19 pandemic, it will be postponed until summer of 2021. \n\nLet me know if you would like to know more about Tokyo or Japan!","role":"assistant"},"finish_reason":"stop","index":0}],"usage":{"completion_tokens":126,"prompt_tokens":69,"total_tokens":195},"object":"chat.completion"}
ちなみに--http1.1
をつけない場合、An unexpected error occurred
が返ってくる。
Proxyのログを見るとbuffered proxying cannot currently be enabled with http/2, please use http/1.x instead
と出ており、http/2だと上手く動かないようなので注意。
2024/03/22 09:16:29 [error] 1323#0: *3454 [kong] init.lua:405 [ai-proxy] /usr/local/share/lua/5.1/kong/plugins/ai-proxy/handler.lua:94: buffered proxying cannot currently be enabled with http/2, please use http/1.x instead, client: 192.168.2.1, server: kong, request: "POST /cohere-chat HTTP/2.0", host: "my-service.10-214-154-169.nip.io", request_id: "de29186609a64b43d452be6a074b451c"