一条大路(捷径)通罗马:一种利用docker进行微服务架构的可行方案

当我们要做一件的常常发现有诸多的方法和工具可以实现,也就是"条条大路通罗马", 寻找最优工具和方法的过程也会花费很多精力和时间,有一种“明明知道条条大路通罗马,却不知道接下来怎么走的感觉” 。在微服务架构方案中也何尝不是如此! 本文隐去了漫漫长途的探索和横向对比,直接给出其中一种可行方案,也是通往罗马的捷径。

本文默认读者朋友至少研究过微服务(可以没有真实使用过),了解微服务架构的基本概念,和单体的不同,以及需要address的issue。 如果没有,可以尝试先读:

Microservice Architecture (Examples and Diagram)

Introduction to microservices architectures

monolith-vs-microservices

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

When not to use microservices

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的请求转发给正确的后端微服务处理。

traefik-architecture-1-

在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 等环境中

全栈虚拟机GraalVM初体验

还有很多要考虑的问题呢?

刚才说的 “不用提前备份当前本版应用,不用担心部署失败造成服务中断”, 为什么这么牛B呢? 请看:

stack-

也许你会问,微服务架构本身还有很多问题要解决啊,例如 “文件如何同步?”,“ 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:
关于“只需要一条大路通罗马”

漫画家蔡志忠有一个演讲,题目叫做《努力是没有用的》。读完这份演讲稿,我觉得他说的有道理。
有些人非常勤奋,别人休息和娱乐的时候,都在工作学习。但是努力了一辈子,人生也没有显著的提升,就像报道里经常说的:"某某在平凡的岗位上,勤勤恳恳工作了一辈子"。
另一方面,很多成功者似乎也没有特别努力,就取得了许多成就,过上了好日子。蔡志忠以自己为例,他从小就喜欢画画,然后一直画,不知不觉就成了大漫画家,名利双收,从没有觉得过得很辛苦。
老师或父母老是说,努力就会走到巅峰----才怪。如果这样,不是所有人都走上巅峰了吗?没有人开始不努力,为什么后来不努力,因为努力没有效果。"
人生不是走斜坡,你持续走就可以走到巅峰;人生像走阶梯,每一阶有每一阶的难点,学物理有物理的难点,学漫画有漫画的难点,你没有克服难点,再怎么努力都是原地跳。所以当你克服难点,你跳上去就不会下来了。
蔡志忠的核心观点就是黑体的那句话,成功的人生是台阶式向上,而不是一条水平线。努力只是说明你拼命在走,跟你能不能向上走,关系不大。那些努力却没有结果的人,根本原因就在于,他一直走在平面上,没有走到更高的台阶。
也就是说,垂直方向的努力更有意义,水平方向的努力意义不大。你把同一件事情勤奋地做上十遍,还是只会做这一件事;你做完这件事后,再去挑战更难的事情,就有机会学会做两件事。
初学者经常问我,前端开发应该学习哪一个框架?我的回答就是,你觉得哪一个框架比较容易,就用那个。因为它们都是解决同样的问题,你只要知道怎么解决就可以了,没必要深究哪一个解决得更好。对你更重要的是,要去解决更多的问题,而不是如何最好地解决一个问题。
只有通过解决更多的问题,人生才能摆脱水平运动,进入上升运动。当然,这里还有一个天赋和兴趣的问题,如果找到属于你的领域,不用特别努力就能上台阶;如果找不对领域,再努力也只能做水平运动。