基础设施与软件架构

从经典三层架构到容器云

从单体架构到微服务架构

服务管理

服务编排与部署

随着业务的增加,从单体架构到微服务架构似乎是一种必然的趋势。容器带来了新的服务发布方式——以容器镜像作为服务从开发到运维的交付产物——但是并不能解决服务拆分带来的庞大服务数量问题。此外,对于一个大型系统来说,无论是服务之间存在的依赖关系,还是服务与外部系统的依赖关系都是非常复杂的,依靠人工来记录和配置这些依赖显然费力不讨好,而且容易出错。因此,我们的容器云平台迫切需要对容器进行编排与部署的工具。

在展开进一步讨论前,首先明确“编排”和“部署”的定义:

编排(orchestration):一个系统由多个服务组成,编排的作用就是明确这些服务之间的耦合关系,以及被部署对象对环境的依赖,制定部署流程中各个动作的执行顺序。编排的作用是将多个服务组合成一个完整的,可以运行的系统。

部署(deployment):部署是将系统在实际环境中运行起来。在这一过程中,将根据编排指定的服务耦合关系以及部署动作的执行顺序,启动指定的服务,包括获取加载配置、初始化环境、启动服务等。部署完成后,系统得到正常运行。

明确定义以后,是不是可以尝试自己来设计一个编排部署工具呢?

先想一想编排这个阶段。看起来是要定义一份配置文件,在这份配置文件中,由运维人员负责编写系统需要使用的服务以及服务之间的耦合关系和执行流程,编写完成后,交付给部署阶段来使用。在引入容器以后,配置文件中服务项的各个字段基本可以参考Docker容器的启动参数来设计,比如容器使用的镜像、容器进程的启动命令、环境变量、暴露的端口等;而服务之间的依赖耦合关系其实可以用DAG(有向无环图)来定义,服务项可以加一个字段来表示某个服务依赖于另一个服务。

假设我们已经得到了这么一份“编排配置文件“,那么应该如何使用它来部署呢?

手动是不可能手动的,这辈子都不可能手动的。既然要自动化部署,那么首先肯定要解析这份配置文件,所谓解析,就是将这份配置文件翻译成Docker容器的启动命令,这个实现起来不难。现在我们得到了系统所有服务的容器启动命令,那么问题来了,如何确定这些启动命令的执行先后顺序呢?这个也不难,前面说的DAG已经解决了这个问题,剩下的就是将DAG排序成可执行序列。接下来,只需要向Docker daemon发送这些启动命令就可以完成部署的过程,如果部署过程中发生了错误,那就中断部署过程,根据策略杀死已经部署的服务或者干脆忽略。

到这里,我们已经设计出了一套自动化容器编排部署工具(单机版)。只要写一份配置文件,执行一条命令,系统就启动起来了,再也不用一个一个敲命令启服务了~

听上去很不错,我们的自动化容器编排部署工具(单机版)简单又好用。但是——遗憾的在于上述过程只能适用于单机系统,因为Docker daemon显然是单机的。如果是集群化部署,应该如何设计编排和部署方案呢?因为集群化部署涉及到多宿主机的调度问题,这是下一节的内容,这里先按下不表。

聪明的你是不是已经想到了,既然Docker daemon是单机的,那么有没有可能设计一套API管理一组宿主机的Docker daemon呢?

讲了那么多自己的构想,最后还是回到业界,来看看成熟产品都是怎么做编排部署的吧?

Compose编排小神器

Docker compose素有容器编排小神器的美名,主要原因就在于它够简单。只需要编写一份配置文件,然后执行一条docker-compose up -d <your-config>.yaml就完成了编排部署的过程。

以下配置文件是一个依赖MySQL的web服务,可以看到服务的最小粒度是service(其实也就是容器),其中挂载卷、环境变量、端口映射、重启策略都有涉及到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: "3"

services:
webapp:
image: examples/web
ports:
- 80:80
volumes:
- "/data"
depends_on:
- mysql

mysql:
image: mysql:5.6
restart: always
environment:
- MYSQL_ROOT_PASSWORD=<password>
- MYSQL_ROOT_HOST=%
ports:
- 3306:3306

但是讲道理,我们20分钟设计出来的自动化容器编排部署工具(单机版)不也具备这些功能吗?那compose还有哪些额外功能呢?

  • 首先是自动build镜像功能,如果service指定build字段,那么会根据build字段下的配置信息去寻找Dockerfile来进行镜像构建;
  • 其次,Compose是支持集群部署的,但是得依赖于我们后面会提到的Swarm。

最后,考虑一种情况,在系统中有一个容器包含volume,这个volume还可能被其他容器引用。如果容器更新时只是简单地删除旧容器创建新容器,那么新建的这个容器将会丢失旧容器的存储卷内容;而先创建新容器再删除旧容器又可能会发生端口冲突。Compose的处理策略是引入一个中间容器引用旧容器的volume,然后删除旧容器,再新建容器,引用这个中间容器的volume,最后删除中间容器。

K8s以Pod为单位的编排

Fleet基于systemd的编排

服务的调度

scheduler

服务高可用

健康检查、故障恢复

kubelet

rc的重启

服务迁移

冷迁移

服务缩扩容

hpa, rc

服务发现与注册

service, endpoints

服务负载均衡

ingress, service

服务运行监控(监控、日志)

prometheus, fluentd

资源管理

计算

request, limits, resourceQuota

网络

二层网络 flannel

存储

pv, pvc, stroageclass

资源隔离

namespace