はじめに
OSSのKongは世界で最も人気のあるAPI Gatewayで、認証認可や流量制御などプラグインを有効化するだけで用意することができます。
プラグインはこちらのPlugin Hubにリストアップされており、必要なものを選んで導入することができますが、必要な機能がない場合、Luaという言語でカスタムプラグインを作成することも可能です。
本記事ではカスタムプラグイン内部でAPIアクセスを行い、その結果を踏まえてUpstreamへリクエストを行うサンプルプラグインを作成します。
記事のサンプルコードはこちらになります。
また、OSSのAPI Gateway「Kong」の日本コミュニティ Kong Community, Japan として活動も行っています。ご興味のある方はコミュニティサイトをご覧ください。
kong.ymlの作成
今回はDBレスモード(yamlファイルでの設定管理)でのKong利用とします。
まず、適当なkong.ymlを作成します
_format_version: "1.1"
services:
- name: example_service
url: http://httpbin.org/post
routes:
- name: example_route
methods:
- POST
paths:
- /httpbin/post
ここでは/httpbin/post
にきたリクエストをhttp://httpbin.org/post
に流す設定をしています。
カスタムプラグインの作成
ここでは例として、kongからhttp://httpbin.org/post
にリクエストを送る前に、規制状態を確認するAPIにリクエストし、その結果を踏まえてレスポンスを返すプラグインを作成します。
KongのプラグインはLua
という言語で作成可能で、主な構成ファイルはhandler.lua
とschema.lua
になります。
-
handler.lua
ライフサイクル(request, response, logなど)の各フェーズで実行するプラグインの処理を記載する。
-
schema.lua
ユーザーが動作を変更するための変数を定義する。
handler.luaの作成
plugins/restriction-check
というディレクトリを作成し、handler.lua
というファイルを作成します。以下が全文です。
local http = require "resty.http"
local cjson = require "cjson"
local RestrictionCheck = {
VERSION = "1.0.0",
PRIORITY = 99999,
}
function RestrictionCheck:access(conf)
local httpc, err = http.new()
httpc:set_timeout(5000)
local response, error = httpc:request_uri(conf.target_url, {
method = "GET"
})
if error then
return kong.response.exit(
500,
{
message = "Restriction Check API is not working."
}
)
end
local table_response_body = cjson.decode(response.body)
if table_response_body[conf.decide_key] then
return kong.response.exit(
503,
{
message = "This API is under maintenance."
}
)
end
end
return RestrictionCheck
ライブラリは以下を使用します
-
lua-resty-http
- httpクライアントとして使用します。
- Dockerfileでビルドする際にインストールします。
-
cjson
- jsonのエンコード/デコードで使用します。
- kongのイメージに元から入っているのでインストール不要です。
まずは以下のようにバージョンとPriorityを定義します
local RestrictionCheck = {
VERSION = "1.0.0",
PRIORITY = 99999,
}
Priorityはプラグインが実行される順番に寄与し、値が大きいほどフェーズ内(Access、Body_filterなど)において先に実行されます。
Plugin Ordering Reference
ここでは大きめな値99999
を設定しておきます。
次に、処理を記載します。
function RestrictionCheck:access(conf)
...
end
kongの処理のライフサイクルはこちらのようになっており、リクエストがkongに来た時の処理はaccess
フェーズとなるため、その時の処理を記述します。
また引数として、kong.ymlファイルで指定するconfigの設定値(後述するschema.luaで定義します)を受け取ることができます。
そして、以下のようにAPIアクセスする処理を記載します
local httpc, err = http.new()
httpc:set_timeout(5000)
local response, error = httpc:request_uri(conf.target_url, {
method = "GET"
})
if error then
return kong.response.exit(
500,
{
message = "Restriction Check API is not working."
}
)
end
local table_response_body = cjson.decode(response.body)
if table_response_body[conf.decide_key] then
return kong.response.exit(
503,
{
message = "This API is under maintenance."
}
)
end
lua-resty-http
では、httpc:request(params)
でHTTPリクエストできます。レスポンスボディはresponse.body
に文字列で入っているので、cjsonでluaのテーブルにデコードして使っています。
詳しくはgithubを確認してください。
また、APIのurlはtarget_url
、規制状態を表す項目はdecide_key
として、kong.ymlの設定値から取得することにします。後述のschema.luaで定義します。
加えて、kong.response.exit
でkongでレスポンスを返すことができます(公式ドキュメント)
ここでは、規制状態取得APIへのリクエストでエラーが起きた時、および規制状態がtrue
だったときにそれぞれエラーレスポンスを返す実装としています。
schema.luaの作成
plugins/restriction-check
にschema.lua
というファイルを作成します。
handler.lua
で用いるtarget_url
、decide_key
を定義します。
local typedefs = require "kong.db.schema.typedefs"
return {
name = "restriction-check",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ target_url = {
type = "string",
required = true
}},
{ decide_key = {
type = "string",
required = true
}}
}
},
},
}
}
ここでは共にString型で必須(required=true
)として定義します。
記述方法の詳細は公式ドキュメントをご覧ください(describing-your-configuration-schema)
Dockerfileの作成
lua-resty-httpのインストール
kongのベースイメージ(ここではkong:3.0.0-ubuntu
を使っています)に
今回使用するライブラリlua-resty-http
をインストールします。
luarocks
というluaのパッケージ管理ライブラリを使います。
FROM kong:3.0.0-ubuntu
USER root
RUN luarocks install lua-resty-http
...
作成したプラグインの追加
今回編集したプラグインを使えるようにDockerfileに記述を追加します
作成したrestriction-checkディレクトリを/usr/local/share/lua/5.1/kong/plugins/にコピーします。
また、以下の環境変数を設定します
Env Variable | Value | Description |
---|---|---|
KONG_UNTRUSTED_LUA | on | カスタムLuaコードを使えるようにする設定 |
KONG_PLUGINS | bundled,restriction-check | bundled(imageに含まれているプラグイン)と今回作成したrestriction-check を指定する |
KONG_DATABASE | off | db-lessモード(kong.ymlで設定値を管理) |
KONG_DECLARATIVE_CONFIG | /kong/declarative/kong.yml | kong.ymlのファイルを指定 |
...
COPY kong.yml /kong/declarative/kong.yml
ENV KONG_DATABASE off
ENV KONG_DECLARATIVE_CONFIG=/kong/declarative/kong.yml
ENV KONG_PROXY_ACCESS_LOG /dev/stdout
ENV KONG_ADMIN_ACCESS_LOG /dev/stdout
ENV KONG_PROXY_ERROR_LOG /dev/stderr
ENV KONG_ADMIN_ERROR_LOG /dev/stderr
ENV KONG_UNTRUSTED_LUA on
ENV KONG_PLUGINS bundled,restriction-check
COPY plugins/restriction-check /usr/local/share/lua/5.1/kong/plugins/restriction-check
プラグインの使用
kongおよびwiremockの設定
まずはkongとwiremockを起動するためのdocker-compose.yml
を作成します。
version: "3.9"
services:
kong:
build: .
container_name: kong
command: >
/bin/bash -c "
kong start"
environment:
- KONG_LOG_LEVEL=debug
restart: always
ports:
- 8000:8000
wiremock:
image: rodolpheche/wiremock
container_name: mock
ports:
- "8080:8080"
volumes:
- ./mock.json:/home/wiremock/mappings/mock.json
restart: always
規制状態を取得するAPIをここではwiremockで作成します。APIのパスは/restriction
としています。
mock.json
を作成し、そこにモックレスポンスを記載します。
{
"request": {
"url": "/restriction",
"method": "GET"
},
"response": {
"status": 200,
"body": "{\"restriction\": true}",
"headers": {
"Content-Type": "application/json"
}
}
}
詳細はwiremock dockerを参照してください。
routeへのプラグインの追加
kong.ymlの/httpbin/post
にrestriction-checkを有効化します。
schema.yml
で設定したfieldを指定することができます。
以下のように設定することでhttp://httpbin.org/post
にリクエストが行く前に、http://host.docker.internal:8080/restriction
にアクセスし、レスポンスボディのrestriction
の値がtrueの場合は503を返す動きとなります。
- name: example_service
url: http://httpbin.org/post
routes:
- name: example_route
methods:
- POST
paths:
- /httpbin/post
plugins:
- name: restriction-check
config:
target_url: http://host.docker.internal:8080/restriction
decide_key: restriction
コンテナの起動
これでコンテナを起動します
docker-compose up -d --build
動作確認
localhost:8000/httpbin/postにリクエストしてみます
今回upstreamに指定しているhttpbin.org/postはリクエストした内容をレスポンスとして返してくれます
curl -X POST -H "Content-Type: application/json" -d '{"test": "test"}' http://localhost:8000/httpbin/post
>>>
HTTP/1.1 503 Service Temporarily Unavailable
...
{"message":"This API is under maintenance."}
503が返ってくることを確認できます。
続いて、規制状態を外してみます。以下のようにmock.json
を書き換えて、コンテナを起動し直します。
{
...
"response": {
"status": 200,
"body": "{\"restriction\": false}",
...
}
}
コンテナを起動し直し、再度リクエストを送ります。
docker compose down
docker compose up -d --build
curl -X POST -H "Content-Type: application/json" -d '{"test": "test"}' http://localhost:8000/httpbin/post
>>>
HTTP/1.1 200 OK
...
{
"args": {},
"data": "{\"test\": \"test\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "16",
"Content-Type": "application/json",
"Host": "httpbin.org",
"X-Forwarded-Host": "localhost",
"X-Forwarded-Path": "/httpbin/post",
"X-Forwarded-Prefix": "/httpbin/post"
},
"json": {
"test": "test"
},
"url": "http://localhost/post"
}
200で返ってきていることを確認できます。
このようにプラグインからAPIアクセスすることが可能です。