一条大路(捷径)通罗马:一种利用docker进行微服务架构的可行方案
当我们要做一件的常常发现有诸多的方法和工具可以实现,也就是"条条大路通罗马", 寻找最优工具和方法的过程也会花费很多精力和时间,有一种“明明知道条条大路通罗马,却不知道接下来怎么走的感觉” 。在微服务架构方案中也何尝不是如此! 本文隐去了漫漫长途的探索和横向对比,直接给出其中一种可行方案,也是通往罗马的捷径。
本文默认读者朋友至少研究过微服务(可以没有真实使用过),了解微服务架构的基本概念,和单体的不同,以及需要address的issue。 如果没有,可以尝试先读:
Microservice Architecture (Examples and Diagram)
Introduction to microservices architectures
7 Things to Consider While Moving to a Microservices Architecture
4 Challenges You Need to Address with Microservices Adoption
Microservices and containers: 6 things to know at start time
Setup Microservices Architecture in Python with ZeroMQ & Docker
预览捷径有多快
在everyday work中,需要部署一个或者几十个容器应用到云端,无论是首次部署还是版本升级,无论是部署到1台服务器还是2000多台机器的集群,一般只需要执行下面的1-5行command:
# 首次使用hub.docker.com需要登陆,也可以使用阿里云提供的免费registry
docker login -u=wz -p="<密码隐藏了>" hub.docker.com
# 最新的master分支build一个新的docker镜像
docker build -t "wz/ladyluck_web:0.1.1" ./
# 推送到docker hub 或者阿里云提供的免费registry
docker push wz/lady_luck_web:0.1.1
# 使用stack-test.yml文件部署到测试用的docker集群
docker --host tcp://<隐藏了的测试环境ip>:<隐藏了的端口> \
--tlsverify --tlscacert "./_cert/ca.pem" --tlscert "./_cert/cert.pem" \
--tlskey "./_cert/key.pem" --with-registry-auth \
stack deploy -c stack-test.yml ladyluck_web
# 测试通过之后使用stack.yml文件部署到生产环境
docker --host tcp://<隐藏了的生产环境ip>:<隐藏了的端口> \
--tlsverify --tlscacert "./_cert/ca.pem" --tlscert "./_cert/cert.pem" \
--tlskey "./_cert/key.pem" --with-registry-auth \
stack deploy -c stack.yml ladyluck_web
而且不用登陆服务器,不用提前备份当前本版应用,不用担心部署失败造成服务中断。
为什么这么容易? 因为我们有:
- docker swarm: 管理服务器集群
- traefik: 负责url despatch,如果微服务应用需要通过http对外服务
- Seneca: 负责微服务间通讯,如果你的微服务应用是使用node.js开发的
- Oracle Graal: 如果你的微服务不是使用node.js开发但仍想使用Seneca
如果你还有一下小疑问,例如 “文件如何同步?”,“ sticky cookie/session能保证吗?” 之类的。没关系,继续往下读,连代码中的注释也要认真看哦,会找到答案的!
使用docker swarm管理服务器集群
为什么选择swarm而不是 Kubernetes / Mesos?
But it, with all the ideas described here, is what I would recommend for teams of less than 200 developers, or clusters of less than 1000 machines.
对于成员小于200人的开发团队,或者需要管理的机器少于1000台,我还是推荐swarm。
This includes small / medium size organizations (like when you are not Google or Amazon), startups, one-man projects, and "hobby" projects.
-- by https://dockerswarm.rocks/
而我的直观感觉也是如此,Kubernetes / Mesos的文档多而沉长,不太容易找到想找的内容,特别是Kubernetes面对的使用场景很多,文档超级多。 而swarm是为docker 容器而生,概念少,文档少,学习起来也快。
如果你使用的阿里云的容器服务,那么docker swarm已经默认开启了,你可以在控制台找到连接这个机器的host地址和证书文件。
如果你使用的云服务商没有提供swarm集群服务,可以自行开启。方法是:
step 1: 确保机器已经安装了docker,然后ssh到你要使用swarm集群的机器上执行 docker run --rm swarm create
,并邀请更多机器加入集群,详情请参考 https://hub.docker.com/_/swarm
step 2(optional): 参考 https://github.com/docker/swarm/blob/master/discovery/README.md 开启这个集群的TLS链接。 这个步骤是可选的,但是为了保证本文后面的步骤也和阿里云提供的容器服务保持一致,最好还是去配置一下。
使用traefik做url despatch
traefik是做这么一件事的: 把来自不同的域名和path的请求转发给正确的后端微服务处理。
在docker swarm集群中使用traefik很简单。
step 1 : 准备一个stack.yml 文件:
version: '3.6'
services:
traefik:
image: traefik:1.4
# **如何保障sticky-sessions? 后端服务器健康检查? 最大连接数限制? 请查看:**
# https://docs.traefik.io/basics/#sticky-sessions
command: --web --logLevel=DEBUG --docker --docker.swarmmode --docker.watch --docker.domain=litup.me \
--defaultEntryPoints='http' \
--entryPoints='Name:http Address::80 compress:true'
# --entryPoints='Name:https Address::443 TLS'
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# - /dev/null:/traefik.toml
labels:
- "traefik.enable=false"
networks:
- public
deploy:
replicas: 2
placement:
constraints: [node.role == manager]
restart_policy:
condition: on-failure
networks:
public:
driver: overlay
ipam:
driver: default
config:
- subnet: 10.1.0.0/24
step 2: 利用上面使用docker swarm管理服务器集群
获得的swarm集群管理链接和证书文件部署,只需要在本地机器(需要已经安装并启动了docker)执行以下command
docker --host tcp://<隐藏了的生产环境ip>:<隐藏了的端口> \
--tlsverify --tlscacert "./_cert/ca.pem" --tlscert "./_cert/cert.pem" \
--tlskey "./_cert/key.pem" --with-registry-auth \
stack deploy -c stack.yml traefik
step 3: 在其它微服务的stack.yml 文件中通过配置label就可以使用traefik。 你现在正在读的这篇博文就是使用traefik 分发请求的,它的stack.yml文件大概是这样子的(留意label的配置):
version: '3.6'
services:
ghost:
image: ghost:2.20-alpine
networks:
- traefik_public
volumes:
# 如何实现多台服务器间的文件同步的答案在这里:
# 如果ghost_content是一个阿里云oss volume, 那么/var/lib/ghost/content的文件就会写到oss了
# 而应用程序本身不用做任何改动,只管往 /var/lib/ghost/content 读写文件
- ghost_content:/var/lib/ghost/content
environment:
url: "http://blog.litup.me"
deploy:
replicas: 2
restart_policy:
condition: on-failure
update_config:
# parallelism: 2
# delay: 10s
order: start-first
failure_action: rollback
resources:
limits:
cpus: "0.6"
memory: 668M
labels:
# https://docs.traefik.io/configuration/backends/docker/
- "traefik.docker.network=traefik_public"
# 权重,千万别小看它,可以搞蓝绿发布,灰度发布的
- "traefik.weight=100"
- "traefik.port=2368"
# 了解modifiers、Matchers的概念: https://docs.traefik.io/basics/#modifiers
- "traefik.frontend.rule=Host:blog.litup.me" #;PathPrefixStrip:/blog"
volumes:
ghost_content:
networks:
traefik_public:
external: true
然后使用上面step2中同样的 docker --host*** stack deploy ***
部署(记得改一下stack应用的名称) 就可以通过上面services.ghost.deploy.labels
配置的blog.litup.me 访问这个ghost blog了。
版本升级/共存
对于api类的server端应用,有时候版本升级带来breaking change,这时iOS/安卓App可能没那么快完成新版的覆盖了,这时同时保留旧版和新版的应用就很有必要了。
stack.yml
version: '3.6'
services:
api-v2:
image: <可以是公司名的repository-name>/user-api:v2.5.9
###----省略很多行------------
deploy:
labels:
# 这样通过 http://api.mydomain.com/v2/user/*** 还是可以访问到v2版的user服务
- "traefik.frontend.rule=Host:api.mydomain.com";PathPrefixStrip:/v2/user"
api-v3:
# 更强大的是,这个v3.0.1的user-api可以使用和上面v2.5.9不同的框架版本、语言版本、甚至不同的语言开发,只要它能被打包成docker镜像
# 这对于需要把老式的JAVA应用无缝迁移到现代化的Java/node.js/python应用时非常有效
image: <可以是公司名的repository-name>/user-api:v3.0.1
###----省略很多行------------
deploy:
labels:
# 通过 http://api.mydomain.com/v3/user/*** 访问到v3版的user服务
- "traefik.frontend.rule=Host:api.mydomain.com";PathPrefixStrip:/v3/user"
api-v3-my:
# 你以为上面就演示完吗? 更强大的是:整个user-api可能是老式的JAVA应用,但是大部分功能(例如注册、登陆等)运行稳定,我们没必要去动它
# 而现在的业务需求是在个人中心新增功能,因为一些原因,比如之前负责这块的团队不在了,或者原应用确实也太老旧,或者足够庞大复杂了,不想在上面继续新增业务功能了
# 这时你可以在一个新的微服务,选择适合的语言和框架开发这些功能,只要它能被打包成docker镜像,都可以和旧版的应用共存
image: <可以是公司名的repository-name>/user-api-my:v3.0.1
###----省略很多行------------
deploy:
labels:
# 通过 http://api.mydomain.com/v3/user/my/*** 访问到v3版的个人中心服务
- "traefik.frontend.rule=Host:api.mydomain.com";PathPrefixStrip:/v3/user/my"
# 你甚至还可以为用户中心的一个细分功能开辟一个微服务,例如资产管理“http://api.mydomain.com/v3/user/my/balance”
# 这在某一个小功能需要紧急bug fix,而整个/user或者user/my又没有到更新周期的情况特别有用,
# 你可以先部署这1小块,等/user或者user/my新版出来的时候再把这个小小的微服务合并回去,因为它们是共享codebase的,除了正常的git merge之外没有额外的工作量
# 这里就不继续举例了。
看,一个docker + 一个traefik就给我们无限的想象。
Seneca负责微服务间的通讯
如果你的各微服务间不需要或者很少需要互相通讯,上面已经足够了。但是服务器间需要密集的通讯,Seneca 可以让你省去亲自和mq / rpc /gRpc /甚至tcp 打交道。
Seneca 是一个能让您快速构建基于消息的微服务系统的工具集,你不需要知道各种服务本身被部署在何处,不需要知道具体有多少服务存在,也不需要知道他们具体做什么,任何你业务逻辑之外的服务(如数据库、缓存或者第三方集成等)都被隐藏在微服务之后。
Seneca :NodeJS 微服务框架入门指南 这篇文章已经写得足够好了,我就不重新介绍一篇了。
微服务程序不是使用node.js开发的
可以尝试通过GraalVM使用Seneca。<- 我还没有做试验
GraalVM 是一个高性能的通用虚拟机,可以运行使用 JavaScript,Python 3,Ruby,R,基于 JVM 的语言以及基于 LLVM 的语言开发的应用。 GraalVM 消除了编程语言之间的隔离性,并且通过共享运行时增强了他们的互操作性。它可以独立运行,也可以运行在 OpenJDK,Node.js,Oracle,MySQL 等环境中
还有很多要考虑的问题呢?
刚才说的 “不用提前备份当前本版应用,不用担心部署失败造成服务中断”, 为什么这么牛B呢? 请看:
也许你会问,微服务架构本身还有很多问题要解决啊,例如 “文件如何同步?”,“ sticky cookie/session能保证吗?”, “后端服务器健康检查?” ,“最大连接数限制?” 之类的。 回头再看一下就能找到答案了,代码中的注释也要认真看哦。
是的还有更多
小地方的tips未来会在这里补充,但不包括, 比如 “数据库的分片/读写分离” 之类的,因为本文的主题是快速实现微服务架构,不是 大*** ,高****。
附录1:
docker swarm 的alternative:
http://highscalability.com/blog/2019/4/8/from-bare-metal-to-kubernetes.html
http://highscalability.com/blog/2019/4/8/from-bare-metal-to-kubernetes.html
Kubernetes: The Surprisingly Affordable Platform for Personal Projects
Kubernetes for personal projects? No thanks!
JAVA应用容器化工具: https://jaxenter.com/jib-java-containerization-146647.html
附录2:
关于“只需要一条大路通罗马”
漫画家蔡志忠有一个演讲,题目叫做《努力是没有用的》。读完这份演讲稿,我觉得他说的有道理。
有些人非常勤奋,别人休息和娱乐的时候,都在工作学习。但是努力了一辈子,人生也没有显著的提升,就像报道里经常说的:"某某在平凡的岗位上,勤勤恳恳工作了一辈子"。
另一方面,很多成功者似乎也没有特别努力,就取得了许多成就,过上了好日子。蔡志忠以自己为例,他从小就喜欢画画,然后一直画,不知不觉就成了大漫画家,名利双收,从没有觉得过得很辛苦。
老师或父母老是说,努力就会走到巅峰----才怪。如果这样,不是所有人都走上巅峰了吗?没有人开始不努力,为什么后来不努力,因为努力没有效果。"
人生不是走斜坡,你持续走就可以走到巅峰;人生像走阶梯,每一阶有每一阶的难点,学物理有物理的难点,学漫画有漫画的难点,你没有克服难点,再怎么努力都是原地跳。所以当你克服难点,你跳上去就不会下来了。
蔡志忠的核心观点就是黑体的那句话,成功的人生是台阶式向上,而不是一条水平线。努力只是说明你拼命在走,跟你能不能向上走,关系不大。那些努力却没有结果的人,根本原因就在于,他一直走在平面上,没有走到更高的台阶。
也就是说,垂直方向的努力更有意义,水平方向的努力意义不大。你把同一件事情勤奋地做上十遍,还是只会做这一件事;你做完这件事后,再去挑战更难的事情,就有机会学会做两件事。
初学者经常问我,前端开发应该学习哪一个框架?我的回答就是,你觉得哪一个框架比较容易,就用那个。因为它们都是解决同样的问题,你只要知道怎么解决就可以了,没必要深究哪一个解决得更好。对你更重要的是,要去解决更多的问题,而不是如何最好地解决一个问题。
只有通过解决更多的问题,人生才能摆脱水平运动,进入上升运动。当然,这里还有一个天赋和兴趣的问题,如果找到属于你的领域,不用特别努力就能上台阶;如果找不对领域,再努力也只能做水平运动。