An Introduction to Kong – 孔子简介

最后修改: 2018年 1月 25日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

Kong is an open-source API gateway and microservice management layer.

Kong是一个开源的API网关和微服务管理层。

Based on Nginx and the lua-nginx-module (specifically OpenResty), Kong’s pluggable architecture makes it flexible and powerful.

基于Nginx和lua-nginx-module(特别是OpenResty),Kong的可插拔架构使其灵活且功能强大。

2. Key Concepts

2.关键概念

Before we dive into code samples, let’s take a look at the key concepts in Kong:

在我们深入研究代码样本之前,让我们看看Kong的关键概念。

  • API Object – wraps properties of any HTTP(s) endpoint that accomplishes a specific task or delivers some service. Configurations include HTTP methods, endpoint URIs, upstream URL which points to our API servers and will be used for proxying requests, maximum retires, rate limits, timeouts, etc.
  • Consumer Object – wraps properties of anyone using our API endpoints. It will be used for tracking, access control and more
  • Upstream Object – describes how incoming requests will be proxied or load balanced, represented by a virtual hostname
  • Target Object – represents the services are implemented and served, identified by a hostname (or an IP address) and a port. Note that targets of every upstream can only be added or disabled. A history of target changes is maintained by the upstream
  • Plugin Object – pluggable features to enrich functionalities of our application during the request and response lifecycle. For example, API authentication and rate limiting features can be added by enabling relevant plugins. Kong provides very powerful plugins in its plugins gallery
  • Admin API – RESTful API endpoints used to manage Kong configurations, endpoints, consumers, plugins, and so on

The picture below depicts how Kong differs from a legacy architecture, which could help us understand why it introduced these concepts:

下图描述了Kong与传统架构的不同之处,这可以帮助我们理解为什么它引入了这些概念。

Client - Kong
(source: https://getkong.org/)

客户端-金刚
(来源:https://getkong.org/)

3. Setup

3.设置

The official documentation provides detailed instructions for various environments.

官方文档为各种环境提供了详细的说明

4. API Management

4.API管理

After setting up Kong locally, let’s take a bite of Kong’s powerful features by proxying our simple stock query endpoint:

在本地设置好Kong后,让我们通过代理我们的简单股票查询端点来尝尝Kong的强大功能。

@RestController
@RequestMapping("/stock")
public class QueryController {

    @GetMapping("/{code}")
    public String getStockPrice(@PathVariable String code){
        return "BTC".equalsIgnoreCase(code) ? "10000" : "0";
    }
}

4.1. Adding an API

4.1.添加一个API

Next, let’s add our query API into Kong.

接下来,让我们把我们的查询API添加到Kong中。

The admin APIs is accessible via http://localhost:8001, so all our API management operations will be done with this base URI:

管理API可以通过http://localhost:8001访问,所以我们所有的API管理操作都将通过这个基础URI完成。

APIObject stockAPI = new APIObject(
  "stock-api", "stock.api", "http://localhost:8080", "/");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis", apiEntity, String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Here, we added an API with the following configuration:

在这里,我们添加了一个API,配置如下。

{
    "name": "stock-api",
    "hosts": "stock.api",
    "upstream_url": "http://localhost:8080",
    "uris": "/"
}
  • “name” is an identifier for the API, used when manipulating its behaviour
  • “hosts” will be used to route incoming requests to given “upstream_url” by matching the “Host” header
  • Relative paths will be matched to the configured “uris”

In case we want to deprecate an API or the configuration is wrong, we can simply remove it:

如果我们想废除一个API或者配置是错误的,我们可以简单地删除它。

restTemplate.delete("http://localhost:8001/apis/stock-api");

After APIs are added, they will be available for consumption through http://localhost:8000:

在添加API后,它们将通过http://localhost:8000提供给消费者。

String apiListResp = restTemplate.getForObject(
  "http://localhost:8001/apis/", String.class);
 
assertTrue(apiListResp.contains("stock-api"));

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "stock.api");
RequestEntity<String> requestEntity = new RequestEntity<>(
  headers, HttpMethod.GET, new URI("http://localhost:8000/stock/btc"));
ResponseEntity<String> stockPriceResp 
  = restTemplate.exchange(requestEntity, String.class);

assertEquals("10000", stockPriceResp.getBody());

In the code sample above, we try to query stock price via the API we just added to Kong.

在上面的代码示例中,我们试图通过我们刚刚添加到Kong的API来查询股票价格。

By requesting http://localhost:8000/stock/btc, we get the same service as querying directly from http://localhost:8080/stock/btc.

通过请求http://localhost:8000/stock/btc,我们得到与直接从http://localhost:8080/stock/btc查询相同的服务。

4.2. Adding an API Consumer

4.2.添加一个API消费者

Let’s now talk about security – more specifically authentication for the users accessing our API.

现在让我们来谈谈安全问题–更确切地说,是访问我们的API的用户的认证。

Let’s add a consumer to our stock query API so that we can enable the authentication feature later.

让我们给我们的股票查询API添加一个消费者,这样我们以后就可以启用认证功能。

To add a consumer for an API is just as simple as adding an API. The consumer’s name (or id) is the only required field of all consumer’s properties:

为一个API添加一个消费者就像添加一个API一样简单。消费者的名字(或ID)是所有消费者属性中唯一需要的字段。

ConsumerObject consumer = new ConsumerObject("eugenp");
HttpEntity<ConsumerObject> addConsumerEntity = new HttpEntity<>(consumer);
ResponseEntity<String> addConsumerResp = restTemplate.postForEntity(
  "http://localhost:8001/consumers/", addConsumerEntity, String.class);
 
assertEquals(HttpStatus.CREATED, addConsumerResp.getStatusCode());

Here we added “eugenp” as a new consumer:

在这里,我们添加了 “eugenp “作为一个新的消费者。

{
    "username": "eugenp"
}

4.3. Enabling Authentication

4.3.启用认证功能

Here comes the most powerful feature of Kong, plugins.

这里涉及到Kong的最强大的功能,即插件。

Now we’re going to apply an auth plugin to our proxied stock query API:

现在我们要在我们的代理股票查询API上应用一个auth插件。

PluginObject authPlugin = new PluginObject("key-auth");
ResponseEntity<String> enableAuthResp = restTemplate.postForEntity(
  "http://localhost:8001/apis/stock-api/plugins", 
  new HttpEntity<>(authPlugin), 
  String.class);
assertEquals(HttpStatus.CREATED, enableAuthResp.getStatusCode());

If we try to query a stock’s price through the proxy URI, the request will be rejected:

如果我们试图通过代理URI查询一只股票的价格,该请求将被拒绝。

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "stock.api");
RequestEntity<String> requestEntity = new RequestEntity<>(
  headers, HttpMethod.GET, new URI("http://localhost:8000/stock/btc"));
ResponseEntity<String> stockPriceResp = restTemplate
  .exchange(requestEntity, String.class);
 
assertEquals(HttpStatus.UNAUTHORIZED, stockPriceResp.getStatusCode());

Remember that Eugen is one of our API consumers, so we should allow him to use this API by adding an authentication key:

记住,Eugen是我们的API消费者之一,所以我们应该通过添加一个认证密钥来允许他使用这个API。

String consumerKey = "eugenp.pass";
KeyAuthObject keyAuth = new KeyAuthObject(consumerKey);
ResponseEntity<String> keyAuthResp = restTemplate.postForEntity(
  "http://localhost:8001/consumers/eugenp/key-auth", 
  new HttpEntity<>(keyAuth), 
  String.class); 
assertTrue(HttpStatus.CREATED == keyAuthResp.getStatusCode());

Then Eugen can use this API as before:

然后Eugen可以像以前一样使用这个API。

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "stock.api");
headers.set("apikey", consumerKey);
RequestEntity<String> requestEntity = new RequestEntity<>(
  headers, 
  HttpMethod.GET, 
  new URI("http://localhost:8000/stock/btc"));
ResponseEntity<String> stockPriceResp = restTemplate
  .exchange(requestEntity, String.class);
 
assertEquals("10000", stockPriceResp.getBody());

5. Advanced Features

5.先进的功能

Aside from basic API proxy and management, Kong also supports API load-balancing, clustering, health checking, and monitoring, etc.

除了基本的API代理和管理外,Kong还支持API负载平衡、集群、健康检查和监控等。

In this section, we’re going to take a look at how to load balance requests with Kong, and how to secure admin APIs.

在这一节中,我们要看一下如何用Kong来平衡请求,以及如何保证管理用API的安全。

5.1. Load Balancing

5.1.负载平衡

Kong provides two strategies of load balancing requests to backend services: a dynamic ring-balancer, and a straightforward DNS-based method. For the sake of simplicity, we’ll be using the ring-balancer.

Kong提供了两种对后端服务进行负载均衡的策略:一种是动态环形平衡器,另一种是基于DNS的直接方法。为了简单起见,我们将使用环形平衡器

As we mentioned earlier, upstreams are used for load-balancing, and each upstream can have multiple targets.

正如我们前面提到的,上游用于负载平衡,每个上游可以有多个目标。

Kong supports both weighted-round-robin and hash-based balancing algorithms. By default, the weighted-round-robin scheme is used – where requests are delivered to each target according to their weight.

Kong同时支持加权轮回和基于哈希的平衡算法。默认情况下,使用加权轮回方案–即根据请求的权重将其传递给每个目标。

First, let’s prepare the upstream:

首先,让我们准备一下上游。

UpstreamObject upstream = new UpstreamObject("stock.api.service");
ResponseEntity<String> addUpstreamResp = restTemplate.postForEntity(
  "http://localhost:8001/upstreams", 
  new HttpEntity<>(upstream), 
  String.class);
 
assertEquals(HttpStatus.CREATED, addUpstreamResp.getStatusCode());

Then, add two targets for the upstream, a test version with weight=10, and a release version with weight=40:

然后,为上游添加两个目标,一个是weight=10的测试版本,一个是weight=40的发布版本。

TargetObject testTarget = new TargetObject("localhost:8080", 10);
ResponseEntity<String> addTargetResp = restTemplate.postForEntity(
  "http://localhost:8001/upstreams/stock.api.service/targets",
  new HttpEntity<>(testTarget), 
  String.class);
 
assertEquals(HttpStatus.CREATED, ddTargetResp.getStatusCode());

TargetObject releaseTarget = new TargetObject("localhost:9090",40);
addTargetResp = restTemplate.postForEntity(
  "http://localhost:8001/upstreams/stock.api.service/targets",
  new HttpEntity<>(releaseTarget), 
  String.class);
 
assertEquals(HttpStatus.CREATED, addTargetResp.getStatusCode());

With the configuration above, we can assume that 1/5 of the requests will go to test version and 4/5 will go to release version:

通过上述配置,我们可以假设1/5的请求将进入测试版本,4/5将进入发布版本。

APIObject stockAPI = new APIObject(
  "balanced-stock-api", 
  "balanced.stock.api", 
  "http://stock.api.service", 
  "/");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis", apiEntity, String.class);
 
assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "balanced.stock.api");
for(int i = 0; i < 1000; i++) {
    RequestEntity<String> requestEntity = new RequestEntity<>(
      headers, HttpMethod.GET, new URI("http://localhost:8000/stock/btc"));
    ResponseEntity<String> stockPriceResp
     = restTemplate.exchange(requestEntity, String.class);
 
    assertEquals("10000", stockPriceResp.getBody());
}
 
int releaseCount = restTemplate.getForObject(
  "http://localhost:9090/stock/reqcount", Integer.class);
int testCount = restTemplate.getForObject(
  "http://localhost:8080/stock/reqcount", Integer.class);

assertTrue(Math.round(releaseCount * 1.0 / testCount) == 4);

Note that weighted-round-robin scheme balances requests to backend services approximately to the weight ratio, so only an approximation of the ratio can be verified, reflected in the last line of above code.

请注意,加权循环方案是按照权重比例来平衡对后端服务的请求,所以只能验证比例的近似值,反映在上述代码的最后一行。

5.2. Securing the Admin API

5.2.确保管理API的安全

By default, Kong only accepts admin requests from the local interface, which is a good enough restriction in most cases. But if we want to manage it via other network interfaces, we can change the admin_listen value in kong.conf, and configure firewall rules.

默认情况下,Kong只接受来自本地接口的管理请求,这在大多数情况下是一个足够好的限制。但如果我们想通过其他网络接口来管理它,我们可以在kong.conf中改变admin_listen值,并配置防火墙规则。

Or, we can make Kong serve as a proxy for the Admin API itself. Say we want to manage APIs with path “/admin-api”, we can add an API like this:

或者,我们可以让Kong作为Admin API本身的一个代理。假设我们想管理路径为”/admin-api “的API,我们可以像这样添加一个API。

APIObject stockAPI = new APIObject(
  "admin-api", 
  "admin.api", 
  "http://localhost:8001", 
  "/admin-api");
HttpEntity<APIObject> apiEntity = new HttpEntity<>(stockAPI);
ResponseEntity<String> addAPIResp = restTemplate.postForEntity(
  "http://localhost:8001/apis", 
  apiEntity, 
  String.class);
 
assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Now we can use the proxied admin API to manage APIs:

现在我们可以使用代理的管理员API来管理API。

HttpHeaders headers = new HttpHeaders();
headers.set("Host", "admin.api");
APIObject baeldungAPI = new APIObject(
  "baeldung-api", 
  "baeldung.com", 
  "http://ww.baeldung.com", 
  "/");
RequestEntity<APIObject> requestEntity = new RequestEntity<>(
  baeldungAPI, 
  headers, 
  HttpMethod.POST, 
  new URI("http://localhost:8000/admin-api/apis"));
ResponseEntity<String> addAPIResp = restTemplate
  .exchange(requestEntity, String.class);

assertEquals(HttpStatus.CREATED, addAPIResp.getStatusCode());

Surely, we want the proxied API secured. This can be easily achieved by enabling authentication plugin for the proxied admin API.

当然,我们希望代理的API是安全的。这可以通过为代理的管理员API启用认证插件轻松实现。

6. Summary

6.总结

In this article, we introduced Kong – a platform for microservice API gateway and focused on its core functionality – managing APIs and routing requests to upstream servers, as well as on some more advanced features such as load balancing.

在这篇文章中,我们介绍了Kong–一个微服务API网关的平台,并重点介绍了它的核心功能–管理API和路由请求到上游服务器,以及一些更高级的功能,如负载均衡。

Yet, there’re many more solid features for us to explore, and we can develop our own plugins if we need to – you can continue exploring the official documentation here.

然而,还有许多坚实的功能需要我们去探索,如果需要的话,我们可以开发自己的插件–你可以继续探索这里的官方文档

As always, the full implementation can be found over on Github.

一如既往,完整的实现可以在Github上找到