在API经济和微服务的背景下,如何对服务的API进行管理是大家都很感兴趣的话题。本文通过利用阿里云的容器服务和API网关,构建一个完整的基于Docker的具有API管理功能的服务。
API管理
假定我们需要这么一个经典的后端服务,访问如下API接口的时候返回Hello World:
$ curl http://apisvc.hostxx/api
<p>Hello World</p>
这个服务推出后广受欢迎,但是烦恼总是伴随幸福不期而至:
- 对API进行计费怎么做?
- 外界访问API的流量太高了,如何进行流量控制?
- 外界访问API的并发连接太多了,能不能把这许多连接合成一个长连接访问服务?
- 如何对API进行保护,让只有授权的应用才能访问API?
- ... ...
这实际上涉及到了API管理的内容,并且很多和业务逻辑无关。是否可以利用云上的PaaS服务来完成呢?
答案是,可以。利用阿里云的API网关就可以完成上面所有任务。当然,API网关的功能不止这些,如果想了解更详细的信息,请移步官网API网关。
定义后端服务
为了简单起见,我们的应用利用openresty实现,运行在阿里云容器服务上。
Openresty应用
Openresty是基于NGINX和LuaJIT的Web应用框架。实现我们的这个经典服务只需要nginx.conf配置文件做如下声明即可:
http {
server {
listen 8080;
location /api {
default_type text/html;
content_by_lua '
ngx.say("<p>hello, world</p>")
';
}
}
}
在本地启动openresty后访问:
$ /usr/local/openresty/bin/openresty -p . -c conf/nginx.conf
$ curl http://localhost:8080/api
<p>hello, world</p>
后端服务就成了。
构建Docker镜像
构建Docker镜像的Dockerfile内容如下:
FROM openresty/openresty:latest
RUN mkdir -p /work/conf work/logs
COPY nginx.conf /work/conf
WORKDIR /work
ENTRYPOINT ["/usr/local/openresty/bin/openresty", "-g", "daemon off;","-p","/work/","-c","/work/conf/nginx.conf"]
容器启动时为其增加了daemon off
设置,这让nginx进程一直在前台运行,保证容器不退出。
部署到阿里云容器服务
部署到阿里云容器服务需要定义docker-compose.yml,这个文件和标准的docker compose部署模版文件完全兼容,只不过增加了一些阿里云特有的label。比如:
aliyun.lb.port_8080: http://${slbname}:8080
这个label的意思是把openresty应用对外通过负载均衡8080端口暴露出来。
version: '2'
services:
hello:
image: registry.cn-hangzhou.aliyuncs.com/jingshanlb/openresty-helloworld
restart: always
ports:
- 8080:8080
labels:
aliyun.lb.port_8080: http://${slbname}:8080
aliyun.scale: "2"
我们的测试集群是运行在经典网络中,开通一个内网阿里云负载均衡,并添加http 8080到8080的监听器。上面的部署文件讲hello服务通过这个负载均衡对外提供服务,访问端点为负载均衡的阿里云内网地址,如下图:
这个地址会用来配置API网关中对后端的访问,请记好。
在API网关中定义对后端的访问
创建API
我们首先要在API网关中定义API,并绑定和后端的映射关系。进入API网关控制台,首先创建一个API分组:
在这个分组中创建API:
对后端的访问地址为如下形式:
http://<负载均衡IP地址>:8080/api
API对外路径定义为/api
,如下图所示:
把API发布成功后可以进入调试API
进行测试:
庆祝一下,我们的服务对外提供API了。
在应用程序能够访问我们的API之前,我们还需要在API网关控制台上生成应用访问所需要的token。
在控制台创建一个新的应用:
授权该应用可以访问我们的经典服务:
进入AppKey页面,记录下AppKey
和AppSecret
进入分组管理页面,记录下API对外的二级域名:
这三个信息是应用程序访问API所需的端点和认证信息,我们马上要把它们填入到程序中。
访问API的应用示例
下面我们可以构建一个访问API的应用示例。访问SDK下载页面,可以看到很多语言的示例。
本文我们使用Java语言创建应用,生成的最终示例在github上:https://github.com/binblee/apigateway-docker-demo/tree/master/client,大家有兴趣可以参考。
在DemoClient的主程序中声明3个变量表示前面拿到的三个信息:AppKey,AppSecret和分组二级域名(API_HOST)。
public class DemoClient {
//APP KEY
private String APP_KEY;
// APP密钥
private String APP_SECRET;
//API域名
private String API_HOST;
...
把三个参数代入到HTTP请求中:
//请求URL
String url = "/api";
Map<String, String> headers = new HashMap<String, String>();
headers.put(HttpHeader.HTTP_HEADER_ACCEPT, ContentType.CONTENT_TYPE_TEXT);
Request request = new Request(Method.GET, HttpSchema.HTTP + API_HOST + url, APP_KEY, APP_SECRET, Constants.DEFAULT_TIMEOUT);
request.setHeaders(headers);
request.setSignHeaderPrefixList(CUSTOM_HEADERS_TO_SIGN_PREFIX);
//调用服务端
HttpResponse response = Client.execute(request);
为了不把ApiKey和ApiSecret等敏感信息硬编码到程序里,我们采用在环境变量中指定的方式:
Map<String, String> env = System.getenv();
APP_KEY = env.get("APP_KEY");
if(APP_KEY == null || APP_KEY.length() == 0){
System.err.println("Please specify APP_KEY environment variable.");
return false;
}
构建并运行 Demo Client
为了方便示例程序的构建,我们使用Gradle。
dependencies {
compile('org.springframework.boot:spring-boot-starter')
compile('org.apache.httpcomponents:httpclient:4.2.1')
compile('org.apache.httpcomponents:httpcore:4.2.1')
compile('commons-lang:commons-lang:2.6')
compile('org.eclipse.jetty:jetty-util:9.3.7.v20160115')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
运行如下命令可以构建一个包含所有依赖的Uber Jar:
./gradle build -x test
这个命令的意思是构建Jar包,但不执行测试。我们的示例Client的测试代码都需要填写ApiKey和ApiSecret等信息,没有的话测试会失败。为了不影响构建Jar包,我们编译的时候忽略测试。
构建成功后,执行如下命令运行Demo Client:
export APP_KEY=<placeholder>
export APP_SECRET=<placeholder>
export API_HOST=<placeholder>
java -jar build/libs/apidemoclient-0.0.1-SNAPSHOT.jar
把前面得到的信息分别填入,可以看到如下执行结果:
...
17:23:47.329 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Releasing connection org.apache.http.impl.conn.ManagedClientConnectionImpl@2b05039f
17:23:47.329 [main] DEBUG org.apache.http.impl.conn.BasicClientConnectionManager - Connection can be kept alive indefinitely
200
Tengine
Mon, 12 Sep 2016 09:23:41 GMT
text/html;charset=UTF-8
chunked
keep-alive
Accept-Encoding
Accept-Encoding
*
POST, GET, OPTIONS
X-Requested-With, X-Sequence, _aop_secret, _aop_signature
172800
717E9CD3-320A-49F0-8F4E-82C3C6CA913E
CONTAINERID=; Expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/
<p>hello, world</p>
BINGO,访问应用成功调用了云上的API。
本文的示例代码在此:https://github.com/binblee/apigateway-docker-demo,供大家参考。
小节
本文演示了如何利用阿里云的API网关管理后台Docker应用API,和读者探讨了如何利用API网关和CaaS的能力为服务提供API管理功能。