考虑如何在集群中创建容器,也就是容器如何调度的问题;容器创建后如何运作才能对外提供服务,也就是服务如何编排的问题。
容器调度
容器调度的问题,说的是现在集群里有一批可用的物理机或者虚拟机,当服务需要发布的时候,该选择哪些机器部署容器的问题。
比如集群里只有 10 台机器,并且已经有 5 台机器运行着其他容器,剩余 5 台机器空闲着,如果此时有一个服务要发布,但只需要 3 台机器就行了,这个时候可以靠运维人为的从 5 台空闲的机器中选取 3 台机器,然后把服务的 Docker 镜像下载下来,再启动 Docker 容器服务就算完成发布。但如果集群机器的规模扩大到几十台或者上百台时,要发布的服务也有几十个或者上百个的时候,由于每个服务对容器的要求,以及每台机器上正在运行的容器情况变得很复杂,就不太可能靠人肉运维了。
容器调度要解决的问题如下:
1)主机过滤
主机过滤是为了解决容器创建时什么样的机器可以使用的问题,主要包含两种过滤。
- 存活过滤。也就是说必须选择存活的节点,因为主机也有可能下线或者是故障状态。
- 硬件过滤。打个比方,现在你面对的集群有 Web 集群、RPC 集群、缓存集群以及大数据集群等,不同的集群硬件配置差异很大,比如 Web 集群往往用作计算节点,它的 CPU 一般配置比较高;而大数据集群往往用作数据存储,它的磁盘一般配置比较高。这样的话如果要创建计算任务的容器,显然就需要选择 Web 集群,而不是大数据集群。
2)策略调度
调度策略主要是为了解决容器创建时选择哪些主机最合适的问题,一般都是通过给主机打分来实现的。比如 Swarm 就包含了两种类似的策略:spread 和 binpack,它们都会根据每台主机的可用 CPU、内存以及正在运行的容器的数量来给每台主机打分。spread 策略会选择一个资源使用最少的节点,以使容器尽可能的分布在不同的主机上运行。它的好处是可以使每台主机的负载都比较平均,而且如果有一台主机有故障,受影响的容器也最少。而 binpack 策略恰恰相反,它会选择一个资源使用最多的节点,好让容器尽可能的运行在少数机器上,节省资源的同时也避免了主机使用资源的碎片化。
具体选择哪种调度策略,还是要看实际的业务场景,通常的场景有:
- 各主机的配置基本相同,并且使用也比较简单,一台主机上只创建一个容器。这样的话,每次创建容器的时候,直接从还没有创建过容器的主机当中随机选择一台就可以了。
- 在某些在线、离线业务混布的场景下,为了达到主机资源使用率最高的目标,需要综合考量容器中跑的任务的特点,比如在线业务主要使用 CPU 资源,而离线业务主要使用磁盘和 I/O 资源,这两种业务的容器大部分情况下适合混跑在一起。
- 还有一种业务场景,主机上的资源都是充足的,每个容器只要划定了所用的资源限制,理论上跑在一起是没有问题的,但是某些时候会出现对每个资源的抢占,比如都是 CPU 密集型或者 I/O 密集型的业务就不适合容器混布在一台主机上。
服务编排
1)服务依赖
大部分情况下,微服务之间是相互独立的,在进行容器调度的时候不需要考虑彼此。但有时候也会存在一些场景,比如服务 A 调度的前提必须是先有服务 B,这样的话就要求在进行容器调度的时候,还需要考虑服务之间的依赖关系。
Docker Compose 这种通过 yaml 文件来进行服务编排的方式是比较普遍的算法,通过类似 yaml 文件的方式定义了服务扩容的模板,模板除了定义了服务创建容器时的镜像配置、服务池配置以及主机资源配置以外,还定义了关联依赖服务的配置。比如微博的 Feed 服务依赖了 user 服务和 card 服务,假如 user 服务扩容的模板 ID 为 1703271839530000,card 服务扩容的模板 ID 为 1707061802000000,那么 Feed 服务的扩容模板里就会像下面这样配置,它代表了每扩容 10 台 Feed 服务的容器,就需要扩容 4 台 user 服务的容器以及 3 台 card 服务的容器。
{"Sid":1703271839530000,"Ratio":0.4} {"Sid":1707061802000000,"Ratio":0.3}
2)服务发现
容器调度完成以后,容器就可以启动了,但此时容器还不能对外提供服务,服务消费者并不知道这个新的节点,所以必须具备服务发现机制,使得新的容器节点能够加入到线上服务中去。
比较常用的服务发现机制包括两种,一种是基于 Nginx 的服务发现,一种是基于注册中心的服务发现。
- 基于 Nginx 的服务发现
这种主要是针对提供 HTTP 服务的,当有新的容器节点时,修改 Nginx 的节点列表配置,然后利用 Nginx 的 reload 机制,会重新读取配置从而把新的节点加载进来。比如基于 Consul-Template 和 Consul,把 Consul 作为 DB 存储容器的节点列表,Consul-Template 部署在 Nginx 上,Consul-Template 定期去请求 Consul,如果 Consul 中存储的节点列表发生变化,就会更新 Nginx 的本地配置文件,然后 Nginx 就会重新加载配置。
- 基于注册中心的服务发现
这种主要是针对提供 RPC 服务的,当有新的容器节点时,需要调用注册中心提供的服务注册接口。在使用这种方式时,如果服务部署在多个 IDC,就要求容器节点分 IDC 进行注册,以便实现同 IDC 内就近访问。
3)自动扩缩容
容器完成调度后,仅仅做到有容器不可用时故障自愈还不够,有时候还需要根据实际服务的运行状况,做到自动扩缩容。
一个很常见的场景就是,大部分互联网业务的访问呈现出访问时间的规律性。白天和晚上的使用人数要远远大于凌晨的使用人数;而白天和晚上的使用人数也不是平均分布的,午高峰 12 点半和晚高峰 10 点半是使用人数最多的时刻。这个时候就需要根据实际使用需求,在午高峰和晚高峰的时刻,增加容器的数量,确保服务的稳定性;在凌晨以后减少容器的数量,减少服务使用的资源成本。
常见的自动扩缩容的做法是根据容器的 CPU 负载情况来设置一个扩缩容的容器数量或者比例,比如可以设定容器的 CPU 使用率不超过 50%,一旦超过这个使用率就扩容一倍的机器。
要考虑到 Kubernetes 本身的复杂性以及概念理解的门槛,对于大部分中小业务团队来说,在生产环境上使用 Kubernetes 都会显得大材小用,并且还需要部署并运维 Kubernetes 周边的一些基础设施,比如 etcd 等。