Kong Gatewayには展開方法として、Traditional、Hybrid、DB-lessの3種類の方法がある。
DB-lessモードについては他の展開方法と異なりDBを利用せず、各ノードごとに予めKong Gatewayのエンティティ(ServiceやRouteなど)を定義したファイルを用意して構築する形になる。
これのメリットとしては以下あたりがある。
- DBのライセンス、構築やメンテナンスが不要
- Kong Gatewayの状態は基本的にはYAMLが全てとなる(稼働中の設定変更はあまり考慮する必要がない)
一方で以下のようなデメリットもある。
- Admin APIやKong Managerによる変更が基本的には出来ない
- Admin APIを使うdeckも一部利用できない
- 構成変更をする時、各ノードごとに設定変更を行っていく必要がある(中央で管理してくれるControl Planeのようなものがないため)
- DBを前提とする機能(Rate Limiting Pluginのカウンタ保存先など)が利用できない
Kubernetes環境だとetcdで吸収できるところもあるが、オンプレ環境などでは特にスケール観点で制約が重くのしかかるケースがあり、適用されるケースは限定的ではある。
とはいえ、DBが不要というメリットは大きいので特性を理解した上で使うのは有りだと思う。
このような特性を持つDB-lessモードのKong Gatewayに対して、APIOpsのパイプラインを組むとどんな感じになるかを少し考えてみる。
APIOps
APIOpsではOpen API Spec(OAS)を入力として、その内容を自動的にKong Gatewayの設定に変換して設定変更を行ったり、Dev Portalのようなものにドキュメントを自動更新するような一連のフレームワークである。
DBが利用可能な場合の一連のフローはざっくり以下のような感じになる。
それぞれに対応するコマンドは以下となる。
プロセス | コマンド |
---|---|
OASのLinting | inso lint spec |
OASをdeck形式に変換 | deck file openapi2kong |
Pluginの設定を追加 | deck file add-plugins |
deckのYAMLを検証 | deck gateway validate |
既存環境との差分を出力 | deck gateway diff |
既存環境のバックアップ | deck gateway dump |
deckのYAMLを適用 | deck gateway sync |
動作確認 | ./tests/e2e.sh |
Portalにアップロード | curl -X PATCH ... |
テストの部分やPortalの部分はプロジェクトごとに異なるので割愛するが、基本的にはdeckを中心に一連のパイプラインが構成されていることが分かる。
ここでDB-lessだと問題になるのが、Admin APIのGET以外のメソッドを利用している部分が1つ、あとはDB-lessモードで使えるYAMLはDeckのYAMLと完全な互換性がない点がある。
そのため、それぞれ代替案を考える必要がある。
DB-lessのYAML
DB-lessモードではKong Gatewayの設定をYAMLで定義するが、deckのYAMLと完全な互換性がない。
具体的には以下のような点がある。
-
_info
(default値の設定などで利用)を設定するとエラーとなる -
_workspace
はdefaultのみ利用可能で、その他のworkspaceを指定しても無視される
_info
は非常に便利なため使いたい人も多いと思う。
そういう人のためにdeck file render
というコマンドが用意されている。
これは本来完全なdeck形式同士のYAMLをマージするためのコマンドであるが、_info
を展開してくれるという副次的な効果もある。
これを使ってDB-lessモードでのYAMLを生成することが出来る。
なお、validateの機能も備えているので、検証も併せて行うことが出来る。
Admin APIの代替
DB-lessモードではAdmin APIのGET以外のメソッドを利用できないが、例外が一部存在する。
-
/config
エンドポイントへのPOST - UpstreamエンティティのTargetのhealthy/unhealthyへのPUT
前者の仕様は実は動的にKong Gatewayの設定を変更可能であり、ここにYAMLをPOSTすることで設定変更が可能である。
ただし、メモリ上のみ更新されるため、Kong Gatewayの再起動で設定が元に戻ってしまう。
実際に利用する際は一時的な変更でない場合はファイルの置換も併せて実施したい。
これまでの説明を踏まえると、以下のように変更することでDB-lessモードでもAPIOpsのパイプラインを構築できる。
コマンドは以下のように置き換わる。
プロセス | コマンド |
---|---|
OASのLinting | inso lint spec |
OASをdeck形式に変換 | deck file openapi2kong |
Pluginの設定を追加 | deck file add-plugins |
deckのYAMLをDB-less用に変換 | deck file render |
既存環境との差分を出力 | deck gateway diff |
既存環境のバックアップ | deck gateway dump |
DB-lessのYAMLを置換 | scp |
DB-lessのYAMLを適用 | curl -X POST :8001/config |
動作確認 | ./tests/e2e.sh |
Portalにアップロード | curl -X PATCH ... |
各ノードへのscpはノード数が増えると面倒なので、その場合はpull型の仕組みを導入したり他の自動化ツールと組み合わせたり、NFSの利用を検討するなどもしてみてよいと思う。
実機検証
実際に上記フローで上手くいくかをGitLab環境で検証した。
検証した際のコードを以下に置いており、コードを読めばAPIOpsの実装自体は大体分かると思うので、個々のステップの詳細は割愛する。
https://github.com/imurata/gitlab-dbless-apiops
ジョブのフローは以下のように組んでいる。
.gitlab-ci.yml
もDB-less部分に絞って少し解説する。
DB-less用にファイルを変換している箇所は以下となる。
stage: convert-dbless
script:
- |
set -x
echo "Convert deck.yaml to db-less.yaml"
deck file render deck.yaml -o db-less.yaml
artifacts:
paths:
- db-less.yaml
前のステージでdeck.yaml
が生成されているため、それをDB-less用に変換している。
またアーティファクトとして残すようにし、後から確認できるようにもしている。
deploy
ステージでは最初にscpするための前処理を行っている。
before_script:
- |
set -euox pipefail
apk add --no-cache openssh-client bash coreutils curl ca-certificates
mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"
SSH_KEY="$CI_PROJECT_DIR/ssh-key"
echo "$SSH_PRIVATE_KEY" | base64 -d > "$SSH_KEY"
chmod 600 "$SSH_KEY"
: > "$HOME/.ssh/known_hosts"
for h in $DEPLOY_HOSTS; do
h_no_user="${h#*@}" # "host:/path"
host="${h_no_user%%:*}" # "host"
ssh-keyscan -p "${SSH_PORT:-22}" -H "$host" >> "$HOME/.ssh/known_hosts" 2>/dev/null || true
done
chmod 644 "$HOME/.ssh/known_hosts"
SSH_PRIVATE_KEY
はGitLabのCI/CDの変数で登録しておき、Base64エンコードしたものをSSHの鍵として利用する。
またknown_hosts
ファイルには、初回ログイン時に聞かれるメッセージを省略するためにデプロイ先のホストの情報を追加しておく必要がある。
script
部分が実装になるが、ここでは各ホストごとにscpでファイルを配置した後、/config
エンドポイントにPOSTしてプロセスを再起動せずに設定値を有効化している。
script:
- |
set -x
echo "Deploy"
for h in $DEPLOY_HOSTS; do
echo ">>> distributing to $h"
scp -i $SSH_KEY ./db-less.yaml "$h"
h_no_user="${h#*@}" # "host:/path"
host="${h_no_user%%:*}" # "host"
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST http://${host}:8001/config \
-H "Content-Type: application/yaml" \
--data-binary @./db-less.yaml)
BODY=$(echo "$RESPONSE" | head -n -1)
CODE=$(echo "$RESPONSE" | tail -n1)
echo "Response body:"
echo "$BODY"
echo "Response code: $CODE"
if [[ "$CODE" =~ ^2 ]]; then
return 0
else
return 1
fi
done
また、curlの戻り値は基本ゼロなので、HTTPステータスコードを取得して2xx系以外だとエラーにするようにしている。
実際にOASを更新して動作させると、以下のようにジョブが成功し、Kong Gatewayの設定も更新されていることが確認できた。
Deploy
+ echo '>>> distributing to ubuntu@44.212.196.195:/home/ubuntu/dbless/kong-dbless.yaml'
>>> distributing to ubuntu@44.212.196.195:/home/ubuntu/dbless/kong-dbless.yaml
+ scp -i /builds/imurata/db-less-apiops/ssh-key ./db-less.yaml ubuntu@44.212.196.195:/home/ubuntu/dbless/kong-dbless.yaml
+ h_no_user=44.212.196.195:/home/ubuntu/dbless/kong-dbless.yaml
+ host=44.212.196.195
+ curl -s -w '\n%{http_code}' -X POST http://44.212.196.195:8001/config -H 'Content-Type: application/yaml' --data-binary @./db-less.yaml
+ RESPONSE='{"services":{"0c51a34b-17a7-54b6-968a-4a8d05539b87":{"port":80,"tls_verify":null,"enabled":true,"tls_verify_depth":null,"ca_certificates":null,"client_certificate":null,"tags":["db-less"],"updated_at":1757552461,"connect_timeout":60000,"protocol":"http","name":"httpbin","id":"0c51a34b-17a7-54b6-968a-4a8d05539b87","created_at":1757552461,"path":"/","write_t
:(省略)
+ echo 'Response code: 201'
Response code: 201
+ '[[' 201 '=~' ^2 ]]
+ return 0
Cleaning up project directory and file based variables
00:00
Job succeeded
おまけ
DB-lessモードの注意点がDB-less mode limitationsに記載されている。
ここでは以下5つの項目について制限として挙げられている。
- Memory cache requirements
- No central database coordination
- Read-only Admin API
- Kong Manager compatibility
- Plugin compatibility
このうち、mem_cache_size
についての制限は古い仕様の頃の制限であり、ドキュメントの記載ミスの類であるので実際は気にしなくて問題ない。
あとUpstreamのTargetのhealthy/unhealthyはPOST
で変更出来るようなことが書いているが、これも誤りでありPUT
が正しい。
DB-lessモードについては利用者が少ないせいか、このようにドキュメントのミスとかがちらほらあるのでドキュメントを参照するのも大事だが、同時に実機検証などもしっかり行った方が良さそうだ。