大家都知道, Docker 这些年让 IT 界产生了深刻的变革, 从开发到测试到运维,处处都有它的身影。 它同时也和微服务架构相互促进,并肩前行。
在最新版的 Docker(CE 17.03) 里,随着 swarm mode
的成熟,
在较简单的场景里已经可以不再需要专门的基础设施管理
,
服务编排
,服务发现
,健康检查
,负载均衡
等等。
但是API gateway
还是需要一个的。或许再加上一个日志收集
,
你的微服务架构就五脏俱全了。
我们知道Nginx Plus
是可以很好的胜任 API gateway 的工作的,
但它是商业软件。Nginx
我们不说认证啊限流啊统计啊之类的功能,
单就请求转发这一点最基本的就出了问题。
我们知道 Docker 是用 DNS 的方式,均衡同一名称的服务请求到不同的 node , 但是 Nginx 为了速度,在反向代理的时候会有一个不可取消的 DNS Cache , 这样我们 Docker 在根据容器的扩展或收缩动态的更新 DNS ,可 Nginx 却不为所动, 坚持把请求往固定的 IP 上发,不说均衡,这个 IP 甚至可能已经失效了呢。
有一个配置文件上的小 Hack 可以实现 Nginx 每次去查询 DNS ,我本来准备写一篇文章来着,
现在看来不用了,我们找到了更优雅的API gateway
, Caddy 。
我上篇文章也写了一个它的简介。
接下来的所有代码,都在这个demo中, 你可以 clone 下来玩,也能在此基础上做自己的实验。
我们先用 golang 写一个最简单的 HTTP API ,你可以用你会的任何语言写出来,
它为GET
请求返回 Hello World 加自己的 hostname .
package main
import (
"io"
"log"
"net/http"
"os"
)
// HelloServer the web server
func HelloServer(w http.ResponseWriter, req *http.Request) {
hostname, _ := os.Hostname()
log.Println(hostname)
io.WriteString(w, "Hello, world! I am "+hostname+" :)\n")
}
func main() {
http.HandleFunc("/", HelloServer)
log.Fatal( http.ListenAndServe(":12345", nil))
}
我们需要把上面的应用做成一个 docker 镜像,暴露端口12345
。
接着才有可能使用Docker Swarm
启动成集群。
本来做镜像特别简单,但我为了让大家直接拉镜像测试时快一点,用了两步构建,
先编译出应用,然后添加到比较小的alpine
镜像中。大家可以不必在意这些细节。
我们还是先来看看最终的docker-compose.yml
编排文件吧。
version: '3'
services:
app:
image: muninn/caddy-microservice:app
deploy:
replicas: 3
gateway:
image: muninn/caddy-microservice:gateway
ports:
- 2015:2015
depends_on:
- app
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
这是最新版本的docker-compose
文件,不再由docker-compose
命令启动,而是要用docker stack deploy
命令。
总之现在这个版本在编排方面还没有完全整合好,有点晕,不过能用。现在我们看到编排中有两个镜像:
为了让 caddy 当作 gateway ,我们主要来看一下Caddyfile
:
:2015 {
proxy / app:12345
}
好吧,它太简单了。它监听本机的 2015 端口,将所有的请求都转发到 app:12345 。 这个 app ,其实是一个域名,在 docker swarm 的网络中,它会被解析到这个名字服务随机的一个实例。
将来如果有很多 app ,将不同的请求前缀转发到不同的 app 就好啦。 所以记得写规范的时候让一个 app 的 endpoint 前缀尽量用一样的。
然后 caddy 也需要被容器化,感兴趣的可以看看 Dockerfile.gateway .
理解了上面的内容,就可以开始运行服务端了。直接用我上传到云端的镜像就可以。本文用到的三个镜像下载时总计 26M 左右,不大。
clone 我背景章节提到的库进入项目目录,或者仅仅复制上文提到的 compose 文件存成docker-compose.yml
,然后执行如下命令。
docker-compose pull
docker stack deploy -c docker-compose.yml caddy
啊,对了,第二个 stack 命令需要你已经将 docker 切到了 swarm 模式,如果没有会自动出来提示,根据提示切换即可。 如果成功了,我们检查下状态:
docker stack ps caddy
如果没问题,我们能看到已经启动了 3 个 app 和一个 gateway 。然后我们来测试这个 gateway 是否能将请求分配到三个后端。
我们是可以通过访问http://{your-host-ip}:2015
来测试服务是不是通的,用浏览器或者 curl 。
然后你会发现,怎么刷新内容都不变啊,并没有像想象中的那样会访问到随机的后端。
不要着急,这个现象并非因为 caddy 像 nginx 那样缓存了 dns 导致均衡失败,而是另一个原因。 caddy 为了反向代理的速度,会和后端保持一个连接池。当只有一个客户端的时候,用到总是那第一个连接呢。 为了证明这一点,我们需要并发的访问我们的服务,再看看是否符合我们的预期。
同样的,测试我也为大家准备了镜像,可以直接通过 docker 使用。
docker run --rm -it muninn/caddy-microservice:client
感兴趣的人可以看 client 文件夹里的代码,它同时发起了 30 个请求,并且打印出了 3 个后端被命中的次数。
另外我还做了一个 shell 版本,只需要sh test.sh
就可以,不过只能看输出拉,没有自动检查结果。
好了,现在我们可以知道, caddy 可以很好的胜任微服务架构中的 API Gateway 了。
什么?你说没看出来这是个 API Gateway 啊。我们前边只是解决了容器项目中 API Gateway 和 DNS 式服务发现配合的一个难题, 接下来就简单了啊,我们写 n 个 app ,每个 app 是一个微服务,在 gateway 中把不同的 url 路由到不同的 app 就好了啊。
caddy
还可以轻松的顺便把认证中心做了,微服务建议用 jwt 做认证,将权限携带在 token 中, caddy 稍微配置下就可以。
我后续也会给出教程和 demo 。 auth2.0 我认为并不适合微服务架构,但依然是有个复杂的架构方案的,这个主题改天再说。
caddy
还可以做API 状态监控
,缓存
,限流
等 API gateway 的职责,不过这些就要你进行一些开发了。
你还有什么更多的想法吗?欢迎留言。
ps. V2EX 建议发链接的,不过我博客没开留言,全文贴在这吧。之前还有一篇介绍 caddy 的,没有啥技术含量,就不贴啦。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.