Kongってなに?
マルチクラウド環境でのマイクロサービスやAPIを1つのエントリポイントで管理運用することができる、APIゲートウェイプラットフォームです。
Amazon API GatewayやGoogleのCloud Endpoints、API Gatewayとかの類ですね。
ここでいうKongは主にKong Gatewayのことを指していますが、他にもKong Meshとか色々あります。
内部にはNginx/Cassandra/PostgreSQLとかが使われている。
Enterprise版とOSS版があるのですが、ここではOSS版を利用していきます。
準備
OSS版を各種環境でインストールする方法があります。
macOSの場合
$ brew tap kong/kong
$ brew install kong
$ kong version
2.7.0
$ mkdir kong
$ kong config init
いったんDB-less modeにします。
$ export KONG_DATABASE=off
$ kong start
Kong started
$ kong stop
Kong stopped
ulimitで警告がでるようならulimit -n 4096などで変更しておきます。
ex) dockerの場合
$ git clone https://github.com/Kong/docker-kong
$ cod docker-kong/compose
$ docker-compose up
構成
APIのアプリケーションを3つ用意してKong Gatewayを利用します。
internet
└── kong-gateway (:8000, :8001)
├── node-web (:3002)
├── rails-web (:3000)
└── rust-web (:3001)
RailsでAPI作成
$ bundle exec rails new rails-web -T -d sqlite3 --api
$ bundle exec rails g scaffold User name:string email:string
$ vim db/seeds.rb
3.times { User.create name: Faker::Name.name, email: Faker::Internet.email }
$ bundle exec rails db:migrate db:seed
レスポンスを確認
$ bundle exec rails foreman start
$ curl -s http://localhost:3000/users | jq
[
{
"id": 1,
"name": "Augustine Leannon",
"email": "lang.heller@hudson.co",
"created_at": "2021-12-25T07:13:32.878Z",
"updated_at": "2021-12-25T07:13:32.878Z"
},
{
"id": 2,
"name": "Octavio Monahan",
"email": "thaddeus@bauch.org",
"created_at": "2021-12-25T07:13:32.883Z",
"updated_at": "2021-12-25T07:13:32.883Z"
},
{
"id": 3,
"name": "Elsie Thiel",
"email": "philip.barton@mayer.net",
"created_at": "2021-12-25T07:13:32.887Z",
"updated_at": "2021-12-25T07:13:32.887Z"
}
]
RustでAPI作成
$ cargo new rust-web
[dependencies]
actix-web = "3.3.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Post {
title: String,
content: String,
}
#[get("/posts")]
async fn index_post() -> impl Responder {
HttpResponse::Ok().json(Post {
title: String::from("post title"),
content: String::from("post description content"),
})
}
#[post("/posts")]
async fn create_post(post: web::Json<Post>) -> impl Responder {
println!("{:?}", post);
HttpResponse::Ok().body("ok")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(index_post).service(create_post))
.bind("127.0.0.1:3001")?
.run()
.await
}
起動してレスポンスを確認する
$ cargo run
$ curl -s http://localhost:3001/posts | jq
{
"title": "post title",
"content": "post description content"
}
NodeでAPI作成
$ mkdir node-web
$ cd node-web
$ yarn init
$ yarn add express
const express = require('express')
const app = express()
app.get('/messages', (req, res) => {
const content = [
{ subject: 'Unforgiven', body: `Here's looking at you, kid.` },
{ subject: 'The Princess Bride', body: 'I love the smell of napalm in the morning.' },
{ subject: 'The Third Man', body: 'You talking to me?' },
]
res.json(content)
})
app.listen(3002, () => console.log('Listening on port 3002'))
レスポンスを確認
$ node app.js
$ curl -s http://localhost:3002/messages | jq
[
{
"subject": "Unforgiven",
"body": "Here's looking at you, kid."
},
{
"subject": "The Princess Bride",
"body": "I love the smell of napalm in the morning."
},
{
"subject": "The Third Man",
"body": "You talking to me?"
}
]
kongの設定
先程kong initしたときにできたkong.ymlを編集します。
services:
- name: rails-web
url: http://localhost:3000
routes:
- name: user-routes
paths:
- /api/rails
- name: rust-web
url: http://localhost:3001
routes:
- name: post-routes
paths:
- /api/rust
- name: node-web
url: http://localhost:3002
routes:
- name: message-routes
paths:
- /api/node
kong.ymlと同じ場所にkong.confを作成します。
database = off
declarative_config = ./kong.yml
kongを起動します。
$ kong start -c kong.conf
Kong started
それぞれ設定したURLにkong経由でアクセスしてみます。
$ curl -s http://localhost:8000/api/rails/users | jq
[
{
"id": 1,
"name": "Augustine Leannon",
"email": "lang.heller@hudson.co",
"created_at": "2021-12-25T07:13:32.878Z",
"updated_at": "2021-12-25T07:13:32.878Z"
},
{
"id": 2,
"name": "Octavio Monahan",
"email": "thaddeus@bauch.org",
"created_at": "2021-12-25T07:13:32.883Z",
"updated_at": "2021-12-25T07:13:32.883Z"
},
{
"id": 3,
"name": "Elsie Thiel",
"email": "philip.barton@mayer.net",
"created_at": "2021-12-25T07:13:32.887Z",
"updated_at": "2021-12-25T07:13:32.887Z"
}
]
$ curl -s http://localhost:8000/api/rust/posts | jq
{
"title": "post title",
"content": "post description content"
}
$ curl -s http://localhost:8000/api/node/messages | jq
[
{
"subject": "Unforgiven",
"body": "Here's looking at you, kid."
},
{
"subject": "The Princess Bride",
"body": "I love the smell of napalm in the morning."
},
{
"subject": "The Third Man",
"body": "You talking to me?"
}
]
それぞれKong Gatewayを通してAPIが取得できました。
プラグインを設定してみる
色々ありますので今回は以下のプラグインを設定してみます。中にはEnterpriseでないと利用できないものもあるのでOSSでの利用ができそうなものをピックアップしました。
- Authentication
- JWT
- Traffic Control
- Proxy Cache
- Rate Limitting
キャッシュの設定
kong.yml
plugins:
- name: proxy-cache
service: node-web
config:
response_code:
- 200
request_method:
- GET
- HEAD
content_type:
- text/plain
- application/json
cache_ttl: 300
strategy: memory
$ curl -i http://localhost:8000/api/node/messages
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 204
Connection: keep-alive
X-Cache-Key: 7c55750e1a2f5e73df7a0d9bf9892880
X-Cache-Status: Bypass
X-Powered-By: Express
ETag: W/"cc-gw3x3v8iu3ri0ITrNa7RwN1YRnI"
Date: Sat, 25 Dec 2021 09:00:22 GMT
X-Kong-Upstream-Latency: 1
X-Kong-Proxy-Latency: 2
Via: kong/2.7.0
[{"subject":"Unforgiven","body":"Here's looking at you, kid."},{"subject":"The Princess Bride","body":"I love the smell of napalm in the morning."},{"subject":"The Third Man","body":"You talking to me?"}]
X-Cache-* のあたりが追加されています。
レートリミットの設定
plugins:
- name: rate-limiting
service: rust-web
config:
second: 5 # 秒間5回
minute: 10 # 分間10回
hour: 10000 # 1時間10,000回
policy: local
fault_tolerant: true
hide_client_headers: false
redis_ssl: false
redis_ssl_verify: false
初回アクセス時
$ curl -i http://localhost:8000/api/rust/posts
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 59
Connection: keep-alive
RateLimit-Limit: 5
RateLimit-Remaining: 4
RateLimit-Reset: 1
X-RateLimit-Limit-Second: 5
X-RateLimit-Limit-Minute: 10
X-RateLimit-Limit-Hour: 10000
X-RateLimit-Remaining-Hour: 9999
X-RateLimit-Remaining-Second: 4
X-RateLimit-Remaining-Minute: 9
date: Sat, 25 Dec 2021 09:07:08 GMT
X-Kong-Upstream-Latency: 1
X-Kong-Proxy-Latency: 2
Via: kong/2.7.0
{"title":"post title","content":"post description content"}
制限がかかった状態
curl -i http://localhost:8000/api/rust/posts
HTTP/1.1 429 Too Many Requests
Date: Sat, 25 Dec 2021 09:07:36 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
RateLimit-Limit: 10
RateLimit-Remaining: 0
RateLimit-Reset: 24
Retry-After: 24
X-RateLimit-Limit-Second: 5
X-RateLimit-Limit-Minute: 10
X-RateLimit-Limit-Hour: 10000
X-RateLimit-Remaining-Hour: 9990
X-RateLimit-Remaining-Second: 5
X-RateLimit-Remaining-Minute: 0
Content-Length: 41
X-Kong-Response-Latency: 1
Server: kong/2.7.0
{
"message":"API rate limit exceeded"
}
JWTの設定
plugins:
- name: jwt
service: rails-web
config:
secret_is_base64: false
run_on_preflight: true
consumers:
- username: hime-chan
custom_id: abcd123
jwt_secrets:
- consumer: hime-chan
algorithm: HS256
key: key1234
secret: hogehoge1234
keyやsecretは$ head /dev/urandom | hexdump | sha256sum
とかで生成しておくとよいです。
DBを利用しているならAPIからユーザを作成できます(今回はDB-lessなので必要ないです)
$ curl -d "username=hime-chan&custom_id=abcd123" http://localhost:8001/consumers
設定した情報の確認
$ curl -s http://localhost:8001/consumers/hime-chan/jwt | jq
{
"data": [
{
"key": "key1234",
"created_at": 1640426398,
"rsa_public_key": null,
"algorithm": "HS256",
"secret": "hogehoge1234",
"tags": null,
"consumer": {
"id": "b84d0924-e7b6-5c17-bfb3-c4eacce71cca"
},
"id": "d6f2fe3c-d64a-5577-beac-d35e04b23d8b"
}
],
"next": null
}
ヘッダにJWTがない状態でアクセス
$ curl -i http://localhost:8000/api/rails/users
HTTP/1.1 401 Unauthorized
Date: Sat, 25 Dec 2021 09:29:56 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Content-Length: 26
X-Kong-Response-Latency: 1
Server: kong/2.7.0
{"message":"Unauthorized"}
適当なJWTでアクセス
curl -i http://localhost:8000/api/rails/users -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
HTTP/1.1 401 Unauthorized
Date: Sat, 25 Dec 2021 10:03:48 GMT
Content-Type: application/json; charset=utf-8
Connection: keep-alive
Content-Length: 42
X-Kong-Response-Latency: 0
Server: kong/2.7.0
{"message":"No mandatory 'iss' in claims"}
https://jwt.io でTokenを作成します。
payloadに
{
"iss": "key1234",
"name": "Hanako",
"user_id": 100
}
VERIFY SIGNATUREの部分に設定したsecret hogehoge1234
を設定して生成します。
作成したTokenをつけてAPI呼び出し
$ curl -i http://localhost:8000/api/rails/users -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrZXkxMjM0IiwibmFtZSI6IkhhbmFrbyIsInVzZXJfaWQiOjEwMH0.roghKCqrODUgni7MM8RM6XU-9lpozDysctKqWkhgOXE'
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Vary: Accept
ETag: W/"f3fb8adbc998b02efefb6493f1f588d7"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: e6fce311-e8d4-4d56-b14f-bc167be07e1d
X-Runtime: 0.019405
Server-Timing: start_processing.action_controller;dur=0.11962890625, sql.active_record;dur=0.701904296875, instantiation.active_record;dur=0.044921875, process_action.action_controller;dur=1.932861328125
X-Kong-Upstream-Latency: 25
X-Kong-Proxy-Latency: 2
Via: kong/2.7.0
[{"id":1,"name":"Augustine Leannon","email":"lang.heller@hudson.co","created_at":"2021-12-25T07:13:32.878Z","updated_at":"2021-12-25T07:13:32.878Z"},{"id":2,"name":"Octavio Monahan","email":"thaddeus@bauch.org","created_at":"2021-12-25T07:13:32.883Z","updated_at":"2021-12-25T07:13:32.883Z"},{"id":3,"name":"Elsie Thiel","email":"philip.barton@mayer.net","created_at":"2021-12-25T07:13:32.887Z","updated_at":"2021-12-25T07:13:32.887Z"}]
他に気になったプラグインは
- Key Authentication
- OAuth 2.0 Authentication
- Session
- ACL
- IP Restriction
- Datadog
- Prometheus
- GraphQL
- gRPC
- Logging関連
。。。いっぱいあるなぁ
所感
とにかく色々な会社で開発されたシステムがそれぞれ提供する独自のAPIを一元管理できるようになるのは大きなところだと思います。
認証一つとってもそれぞれのAPIで実装するのではなくKongで吸収できるという効率化もはかれそう。モニタリングやロギングも管理できますね。
プラグインも多数あり、これはOSSというコミュニティの特色なのかも。
kongのプラグインはLuaやGoで書けるので、利用するサービスにあったプラグインがなければ自前で作ることも可能ですね。
ただ、大手プロバイダによるマネージドサービスではないので、そういった部分ではProduction環境でのコストはかかりそう。
digitaloceanに構築したり、Dockerのコンテナとして実行する、Kubernetesで運用する、など色々と工夫が必要そうです。
今後はKong Meshなんかも試してみたいです。