使用 Kong 实现微服务架构的 API 网关模式的方法是什么?
这篇文章是为了Kong – API Gateway Pattern速习会@Wantedly而创建的资料。
当服务变得更大时,我想要做的事情也增加了。
-
- より高速な実装に置き換えたい
-
- APIを複数のサービスに分けて開発したい
マイクロサービス化
为什么选择微服务?
-
- James Lewis/Martin Fowlerの”Microservices”日本語訳
- State of the Art in Microservices by Adrian Cockcroft – Qiita
尽管有很多理由被提出来,但是…
-
- 人が扱える大きさの限界
-
- 「明確な」境界が必要
名前空間やスコープなど、プログラミング言語でも使用している
一段上の概念だと思うと良い
大きいメソッドが管理できないのと同様に、大きいサービスも管理できない
組織体系に影響を与える
100人のチームで開発するのが嫌ならやった方が良い
数人のチーム * 20個とかでもコンフリクト無く進められる
在这里要做的事情
如何同时使用多个API
-
- 連携するか
-
- 管理するか
存在するAPIを見失わないか
使っていいAPIだけ使えるか
変更するか
特に内部の実装言語の変更するか
学习并尝试使用可以解决这个问题的软件,并且稍微熟悉一下Docker Compose等相关知识。
API 网关模式
“外表单块式,实现微服务”
-
- 一箇所見に行けば全てのAPIを見つけられる
-
- 細かい権限管理も可能
-
- 各APIで何回も実装しないといけない部分を省略できる
Authentication
Rate Limiting
现实例子
如何实现
让我们使用Kong。
从官方网站获取的信息。左侧代表传统方案,右侧代表使用Kong的情况。
其他的方式 (Tā de
据我所知,实际上没有其他更好的选择。
可以的方法:
-
- Nginxで自前実装
KongはOAuthまで実装しているのでそれを自前でやるのは大変そう
AWS API Gateway
下におけるものがLambda等なので、マイクロを通り越してナノサービスな気がして実際使いにくそう
需要注意的事情。
-
- Gatewayが死んだら全てのAPIが終わり
絶対に(99.99…%)死なないようにする
早急な復旧手順を作っておく
孔的情况
-
- データストアに高スケーラビリティで有名なCassandraを使用
逆にちょっと「うっ」って思う部分でもあるけど、Dockerですぐ立ち上がる
別のデータストアも使えるようになる計画はある模様
CassandraをS3にバックアップとるものは多数存在
https://github.com/Netflix/Priam/wiki/Backups
https://github.com/tbarbugli/cassandra_snapshotter
Kong自体は死んでもcassandraが生きていれば元通りになる
Kongはクラスタを組める作りになっているのでスケーラビリティもある
尝试使用Kong
安装
可以使用Docker进行操作。
可以选择以下两种方式中的任意一种。
- 只需按照https://getkong.org/install/docker/的方法进行操作。使用Docker Compose进行部署。
1. 按照文件的指示去做。
$ docker pull cassandra:2.2.5
$ docker pull mashape/kong
$ docker run -p 9042:9042 -d --name cassandra cassandra:2.2.5
等一下,等到 Cassandra 准备好以后再进行下一步,否则会失败。
$ docker run -d --name kong \
--link cassandra:cassandra \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
-p 7946:7946 \
-p 7946:7946/udp \
--security-opt seccomp:unconfined \
mashape/kong
请确认是否在运动。
$ curl http://$(docker-machine ip default):8001
用Docker Compose进行启动
创建一个工作目录,并将docker-compose.yml文件写成以下方式。
version: '2'
services:
kong:
image: mashape/kong
depends_on:
- cassandra
ports:
- 8000:8000
- 8443:8443
- 8001:8001
- 7946:7946
- 7946:7946/udp
security_opt:
- seccomp:unconfined
cassandra:
image: cassandra:2.2.5
ports:
- 9042:9042
接下来
docker-compose up
只需要做就好。
由于以下关于“等待”方面的悲伤现实,我认为第一次启动kong会失败。如果发生这种情况,
-
- 2回docker-compose up
以下のように別々で、少し間を空けてコマンドを叩く
$ docker-compose up cassandra
$ docker-compose up kong
最好做(wo4 zuo4)
可悲的是,所有的depends_on写法都不会等待我完全站起来。
Compose总是按照依赖顺序启动容器……然而,Compose不会等待容器“准备就绪”。
-
- 荒れるIssue (244 comments)
https://github.com/docker/compose/issues/374
使用现成的东西
我不仅准备了用Kong实现的样例JSON API,还包括使用Go和Rails实现的版本。
git clone git@github.com:awakia/modern-architecture-2016.git
docker-compose up cassandra
请稍等一下。
docker-compose up --no-deps kong
如果有时间的话,
docker-compose build
请先做好。
已经站立的集装箱的固定方法
docker rm -f <container-name>
您可以停止并删除正在运行的容器。
docker rm -f $(docker ps -a -q)
所以,您可以删除所有的容器。
顺便提一句,
docker rm $(docker ps -a --filter 'status=exited' -q)
所以,您可以清除所有已经完成的容器。
API注册
相关官方文件:
-
- チュートリアル: https://getkong.org/docs/0.7.x/getting-started/adding-your-api/
リファレンス: https://getkong.org/docs/0.7.x/admin-api/
プロキシの仕組み解説: https://getkong.org/docs/0.7.x/proxy/
首先了解一下会很好
8001番がAPIのAdmin用 (admin_api_listen)
8000番がHTTP経由のAPIのユーザー用 (proxy_listen)
8443番がHTTPS経由のAPIのユーザー用 (proxy_listen_ssl)
所以管理方面的请求基本上会采用以下的形式。
curl -X POST http://$(docker-machine ip default):8001/apis/ --data 'hoge=fuga'
准备API
刚才的
git clone git@github.com:awakia/modern-architecture-2016.git
使用
docker-compose up go-api
我们来做吧。
由于中文味道在`go-api/server.go`和用于启动它的Dockerfile上,所以我们需要稍微查看一下。
添加API
$ curl -i -X POST \
% --url http://$(docker-machine ip default):8001/apis/ \
% --data 'name=go-api' \
% --data 'upstream_url=http://go-api:5000' \
% --data 'request_path=/go'
HTTP/1.1 201 Created
Date: Wed, 30 Mar 2016 19:40:37 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/go-api:5000","request_path":"\/go","id":"93148ea0-0776-4f18-a426-605418158c17","created_at":1459366837000,"name":"go-api"}
复制用
curl -i -X POST \
--url http://$(docker-machine ip default):8001/apis/ \
--data 'name=go-api' \
--data 'upstream_url=http://go-api:5000' \
--data 'request_path=/go'
确认已注册的API。
在命令行上进行确认。
$ curl http://$(docker-machine ip default):8001/apis | jq
请在浏览器上确认。
$ open http://$(docker-machine ip default):8001/apis
-
- ブラウザ上で整形するなら以下がオススメ
JSONView – Chrome Extention
请确认是否实际运行了。
open http://$(docker-machine ip default):8000/go
修改已添加的API
$ curl -i -X PATCH \
% --url http://$(docker-machine ip default):8001/apis/go-api \
% --data 'strip_request_path=true'
HTTP/1.1 200 OK
Date: Wed, 30 Mar 2016 19:55:45 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/go-api:5000","request_path":"\/go","id":"93148ea0-0776-4f18-a426-605418158c17","name":"go-api","strip_request_path":true,"created_at":1459366837000}
复制用途
curl -i -X PATCH \
--url http://$(docker-machine ip default):8001/apis/go-api \
--data 'strip_request_path=true'
确认有什么变化
open http://$(docker-machine ip default):8000/go
/go => /
正在以上游API发送!
使用插件
正如 https://getkong.org/plugins/ 所示,有各种各样的插件可用。
目前,可以分为以下六种情况。
-
- Authentication
-
- Security
-
- Traffic Control
-
- Analytics & Monitoring
-
- Transformations
- Logging
另外,您也可以自己创建插件。
使用OAuth插件
我认为这是在这些插件中最难使用的一个。
如果官方教程对于身份验证的示例过于复杂,那么请参考它们中使用的更简单的身份验证示例。
-
- https://getkong.org/docs/0.7.x/getting-started/enabling-plugins/
- https://getkong.org/docs/0.7.x/getting-started/adding-consumers/
添加OAuth插件
$ curl -X POST http://$(docker-machine ip default):8001/apis/go-api/plugins \
% --data "name=oauth2" \
% --data "config.enable_client_credentials=true"
{"api_id":"93148ea0-0776-4f18-a426-605418158c17","id":"56971111-d189-4377-811f-deee02f1374d","created_at":1459371154000,"enabled":true,"name":"oauth2","config":{"mandatory_scope":false,"token_expiration":7200,"enable_implicit_grant":false,"hide_credentials":false,"provision_key":"21eaa43d19934b60a7198ab463040af0","accept_http_if_already_terminated":false,"enable_authorization_code":true,"enable_client_credentials":true,"enable_password_grant":false}}
复印用途
curl -X POST http://$(docker-machine ip default):8001/apis/go-api/plugins \
--data "name=oauth2" \
--data "config.enable_client_credentials=true"
- config.enable_client_credentials
将该选项设置为true。
这是启用了OAuth的四种授权流程之一,即最简单的“客户端凭证授权”流程。
确认页面
open http://$(docker-machine ip default):8000/go
如果缺少提供访问令牌,那么会返回错误信息:无效的请求,这表示功能正常。
OAuth有四种流程。
在OAuth中,获取Access Token的认证方式可以分为四种。
虽然有各种不同的流程,但最终只要获取到Access Token并且与API请求一起发送,确保能正确返回结果就可以了。
阅读 OAuth 2.0 规范所需的四个授权流程。
在Kong的默认设置中,只有”Authorization Code Grant”流程在顶级Web后端服务器上的身份验证中启用。
然而,如果使用内部公开的API,则必须创建UI并且相当麻烦。因此,在这里我们将使用“客户端凭证授权”流程。
通过客户端凭证授权获取访问令牌
以下的流程如下。 de .)
-
- 在构建使用API的服务(或者使用API的人)时,首先创建表示消费者的Consumer。
例如,当Wantedly使用Facebook API时,Wantedly本身或者开发Wantedly的开发者可以成为Consumer。
Consumer创建应用程序。
例如,wantedly.com或者Sync可以作为应用程序。
获取访问令牌。
1. 创建消费者
如果要创建名为example_consumer的消费者,则按照以下方式进行。
$ curl -X POST http://$(docker-machine ip default):8001/consumers/ --data username=example_consumer
{"username":"example_consumer","created_at":1459404233000,"id":"b5b221ec-12b7-45be-9830-3a84a97fbb6e"}
2. 注册应用程序
如果一个名为example_consumer的消费者尝试在网站https://example.com上创建一个名为”Example App”的应用程序,情况将如下所示。
$ curl -X POST http://$(docker-machine ip default):8001/consumers/example_consumer/oauth2 --data name=Example%20Application --data redirect_uri=https://example.com/oauth2_callback
{"consumer_id":"b5b221ec-12b7-45be-9830-3a84a97fbb6e","client_id":"da5da65dca1044a2aac7d86a694b9536","id":"fdac9287-0c4b-4ffd-89a0-8ad711b6f758","name":"Example App","created_at":1459404289000,"redirect_uri":"https:\/\/example.com\/oauth2_callback","client_secret":"8df2d9f629ee4d049292614e1ee0524f"}
在注册申请时必须填写的必要信息为以下两项。
-
- name
アプリケーションの名前 (例: Wantedly)
redirect_uri
リダイレクト先のURI (例: https://www.wantedly.com/oauth_callback )
通过这样做,您可以获得以下物品。
-
- client_id
- client_secret
顺便提一下,你也可以通过参数直接指定这两个值。
获取访问令牌
根据刚刚获取的client_id和client_secret进行请求。
在请求时,除了这两个值之外,还必须添加表示授权方式的grant_type=client_credentials。
$ curl -k -X POST https://192.168.99.100:8443/oauth2/token \
% --data grant_type=client_credentials \
% --data client_id=da5da65dca1044a2aac7d86a694b9536 \
% --data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"token_type":"bearer","access_token":"4f739dc516a74360bcd00d47fc517955","expires_in":7200}
复制用
curl -k -X POST https://192.168.99.100:8443/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
我可以看到成功获取了访问令牌。
如果使用这个作为基础进行访问,
我可以看到你仍然可以像以前一样访问它。
获取访问令牌的关键点
其实,这第三个阶段有一些容易出问题的地方。
-
- 必须指定要使用的API的RequestHost
- 必须通过HTTPS进行POST访问
实际上,在上述请求中,我已经提供了先解决这两个问题的结果。
接下来,我将详细解释这两个步骤。
3.1. 指定 API 请求的主机
如果没有任何考虑就提出请求,
$ curl -X POST http://$(docker-machine ip default):8000/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"request_path":"\/oauth2\/token","message":"API not found with these values","request_host":["192.168.99.100"]}
在这种情况下,该API在请求的主机上不存在,会被报告为错误。
由于没有指定之前创建的go-api的请求主机,所以出现了这个问题。
3.1.1. 添加请求主机的方法1
如果将request_host设置为docker-machine ip default的值192.168.99.100,则可以解决此问题。
$ curl -X PATCH --url http://$(docker-machine ip default):8001/apis/go-api --data request_host=$(docker-machine ip default)
{"upstream_url":"http:\/\/go-api:5000","request_path":"\/go","id":"93148ea0-0776-4f18-a426-605418158c17","name":"go-api","strip_request_path":true,"created_at":1459366837000,"request_host":"192.168.99.100"}
3.1.2. 添加请求主机的方法2
不妨隨意將帶有OAuth的API註冊到Request Host為192.168.99.100。
$ curl -i -X POST \
--url http://$(docker-machine ip default):8001/apis/ \
--data 'name=for_host' \
--data 'upstream_url=http://go-api:5000' \
--data request_host=$(docker-machine ip default)
HTTP/1.1 201 Created
Date: Thu, 31 Mar 2016 09:58:50 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/go-api:5000","id":"800b9b40-4a23-43db-b9ab-e1d214f6c7c3","name":"for_host","created_at":1459418330000,"request_host":"192.168.99.100"}
$ curl -X PUT http://$(docker-machine ip default):8001/apis/for_host/plugins \
--data "name=oauth2" \
--data "config.enable_client_credentials=true" \
--data "config.token_expiration=0"
{"api_id":"800b9b40-4a23-43db-b9ab-e1d214f6c7c3","id":"67d4d8d2-f1fb-4f25-9a0b-ab106cfd4191","created_at":1459418404000,"enabled":true,"name":"oauth2","config":{"mandatory_scope":false,"token_expiration":0,"enable_implicit_grant":false,"hide_credentials":false,"provision_key":"cda2051a3f6b43d1aa079ca6b7d078a5","accept_http_if_already_terminated":false,"enable_authorization_code":true,"enable_client_credentials":true,"enable_password_grant":false}}
虽然感觉不舒服,但实际上这是我推荐的选项。
实际上,除了其他API之外,RequestHost必须唯一指定。因此,无法实现使用多个request_path的实现。
确认
在此之上,让我们再次输入与刚刚相同的指令试试看。
curl -X POST http://$(docker-machine ip default):8000/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"error_description":"You must use HTTPS","error":"access_denied"}
被告知要使用HTTPS访问,你已经向前迈进了一步。
3.2. 通过HTTPS进行访问
只需要一个选项,原句的中文释义为:“由于HTTPS的端口是8443,因此只需访问该端口即可。”
只是普通地发出请求时,会出现SSL证书警告,导致curl:(60)SSL证书问题:无效证书链的错误。
如果实际使用时,您可以使用SSL插件并添加证书,但由于本次只是在本地进行实验,所以可以忽略此警告。
在使用curl命令时,您可以使用”-k”选项来忽略安全警告。
$ curl -k -X POST https://$(docker-machine ip default):8443/oauth2/token \
--data grant_type=client_credentials \
--data client_id=da5da65dca1044a2aac7d86a694b9536 \
--data client_secret=8df2d9f629ee4d049292614e1ee0524f
{"token_type":"bearer","access_token":"8d32e180038647e388d8eee748b82908","expires_in":7200}
现在已经获得了新的 Access Token。
删除OAuth插件
由于现在有OAuth,如果不输入访问令牌就无法访问,因此让我们将其删除。
在插件中有一个ID,让我们获取它。
$ curl http://$(docker-machine ip default):8001/apis/go-api/plugins | jq '.data[0].id'
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 476 0 476 0 0 34472 0 --:--:-- --:--:-- --:--:-- 36615
"ce0d7078-31ea-4475-ad92-7fd567a4a106"
复制这个,并发出DELETE查询。
curl -X DELETE http://192.168.99.100:8001/plugins/ce0d7078-31ea-4475-ad92-7fd567a4a106
额外的内容: 我想要做这样的事情
-
- Expireされると困るので、Expireしないようにしたい。
追加したAPIのConfigurationで、config.token_expiration=0と設定すれば出来ます。
コンシューマごとに使えるAPIの権限管理をしたい
ACL Pluginを使えば出来ます。
具体的には、ConsumerにGroupを付与し、各APIはそのGroupをホワイトリストに入れるかブラックリストに入れるかのどちらかを選べます。
OAuthで、scopeを使いたい
config.scopesの[configuration]で指定できます。
Authorizeの時はscopeパラメータに渡します。
API側では、X-Authenticated-Scopeに認証済みのものがカンマ区切りで渡されるのでそれを使います。
请提供网页链接
-
- 公式ドキュメント: https://getkong.org/plugins/oauth2-authentication/#client-credentials
ソースコード: https://github.com/Mashape/kong/blob/master/kong/plugins/oauth2/access.lua
質問しているIssue: https://github.com/Mashape/kong/issues/1081
我们尝试启动另一个实施方案吧。
让我们尝试启动Rails服务器。
docker-compose up rails-api
确认Rails API已经运行
$ curl -i -X POST \
% --url http://$(docker-machine ip default):8001/apis/ \
% --data 'name=rails' \
% --data 'upstream_url=http://rails-api:3000' \
% --data 'request_path=/rails' \
% --data 'strip_request_path=true'
HTTP/1.1 201 Created
Date: Thu, 31 Mar 2016 09:47:12 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Access-Control-Allow-Origin: *
Server: kong/0.7.0
{"upstream_url":"http:\/\/rails-api:3000","request_path":"\/rails","id":"74c79c94-d723-4434-aaae-27af812c0eed","created_at":1459417632000,"strip_request_path":true,"name":"rails"}
复印用
curl -i -X POST \
--url http://$(docker-machine ip default):8001/apis/ \
--data 'name=rails-api' \
--data 'upstream_url=http://rails-api:3000' \
--data 'request_path=/rails' \
--data 'strip_request_path=true'
请确认你是否站着。
open http://$(docker-machine ip default):8000/rails
尽管仅有这么少的内容,我们已经确认无论是用Rails还是Go,都可以在同一个主机上启动两种实现。
在每种语言中,您可以选择进行相同的实现并进行迁移,或者将其构建为一个实现不同角色的结构,取决于您自己。
总结
最初存在的问题
同时使用多个API
-
- 連携するか
-
- 管理するか
存在するAPIを見失わないか
使っていいAPIだけ使えるか
変更するか
特に内部の実装言語の変更するか
如何将多个API连接在一起。
-
- それぞれのマイクロサービスでAPIを作っていき、request_pathにより分岐させる
Rate Limitingにより、内部APIでも他に高負荷をかけすぎる実装を排除
如何管理多个API
-
- 存在するAPIを見失わないか
open http://$(docker-machine ip default):8001/apis
もちろんこれを元にUIを作っても良い
使っていいAPIだけ使えるか
ACL Pluginを使って権限管理
内部からの呼び出しも、OAuthのアクセストークンを使う
要如何更改API内部的实现语言?
通过request_path来将各个API的实现分别处理,并逐步进行迁移。换句话说,我们将逐步将其迁移到新的实现中。
-
- /api/user、/api/company、/api/job最初的UpstreamURL是相同的。
-
- 只有/api/user需要进行不同的实现,并改变UpstreamURL。
- 继续这样进行,直到完成迁移。
故障排除
由于网络问题,无法拉取图像。
请确保你的设备真的已经连接到网络。
错误信息
Network timed out while trying to connect to https://index.docker.io/v1/repositories/usename/reponame/images. You may want to check your internet connection or if you are behind a proxy.
处理方法 fǎ)
这似乎是虚拟机的问题。重新启动一下就能解决。
docker-machine restart default
访问应该正常运行的服务URL却没有连接上。
应对方法
首先,如果使用docker-machine,应该使用docker-machine的IP地址,而不是localhost或者127.0.0.1。
docker-machine ip default
需要连接到出现的IP地址(通常为192.168.99.100),所以要确认是否正确。
如果连接到正确的IP和正确的端口,暂且、
docker ps
让我们查看一下输出结果,确保它正常运行。
在某些情况下,可能会遇到死机或者发现端口未正确暴露等问题。
如果已经死了,本来就不应该显示出来。
docker ps -a
找到已退出的名字
docker logs <container-name>
因此,知道他为什么去世是很好的。
如果正在移动
docker logs <container-name>
查看日志,确认是否没有输出错误。
docker exec -it <container-name> bash
有时需要根据图像的不同,将bash变为bin/sh,并进入其中。
-
- fileに吐かれているログを見てみる
-
- 実際に起動コマンドやコンソールコマンドを叩いてみる
- ネットワークの状況(ifconfig, ip addrなど)を確認してみる
采取各种方法来寻找原因。
在rails-api容器中,数据库尚未被创建,也未进行迁移。
处理方法
要在docker上运行命令,可以使用docker run或docker-compose run。
在本例中,应该使用以下命令来运行。
docker-compose run rails-api rake db:create db:migrate
无法连接到数据库是因为某种原因,因此在这种情况下,
-
- きちんとDBコンテナが立っているか
docker psで確認し、docker-compose up postgresで起動
ネットワークはpostgresというホスト名で引きに行くことが出来るか
docker exec -it bashで中にはいり、ping postgresが通るか調べる
做这个会很好。
空的容器没有立起来。
应对措施
当Cassandra容器启动并处于未准备状态时,如果尝试启动Kong,则会导致其崩溃。
只需稍等片刻后再次尝试启动(如docker-compose up kong),应该可以顺利运行。