Kubernetes的概念与功能
架构师普遍有这样的愿景:在系统中有ServiceA、ServiceB、ServiceC这3种服务,其中ServiceA需要部署3个实例,ServiceB与ServiceC各自需要部署5个实例,希望有一个平台(或工具)自动完成上述13个实例的分布式部署,并且持续监控它们。当发现某个服务器宕机或者某个服务实例发生故障时,平台能够自我修复,从而确保在任何时间点正在运行的服务实例的数量都符合预期。这样一来,团队只需关注服务开发本身,无须再为基础设施和运维监控的事情头疼了。
在 Kubernetes出现之前,没有一个平台公开声称实现了上面的愿景。Kubernetes是业界第一个将服务这个概念真正提升到第一位的平台。在Kubernetes的世界里,所有概念与组件都是围绕Service运转的。正是这种突破性的设计,使Kubernetes真正解决了多年来困扰我们的分布式系统里的众多难题,让团队有更多的时间去关注与业务需求和业务相关的代码本身,从而在很大程度上提高整个软件团队的工作效率与投入产出比。
Kubernetes里的Service其实就是微服务架构中微服务的概念,它有以下明显特点。
- 每个Service都分配了一个固定不变的虚拟IP地址——Cluster IP。
- 每个Service都以TCP/UDP方式在一个或多个端口 (Service Port)上提供服务。
- 客户端访问一个 Service时,就好像访问一个远程的TCP/UDP服务,只要与Cluster IP建立连接即可,目标端口就是某个Service Port。
Service既然有了IP地址,就可以顺理成章地采用DNS域名的方式来避免IP地址的变动了。Kubernetes 的 DNS组件自动为每个Service都建立了一个域名与IP的映射表,其中的域名就是Service的Name,IP就是对应的Cluster IP,并且在Kubernetes的每个Pod(类似于Docker'容器)里都设置了DNS Server为 Kubernetes 的 DNS Server,这样一来,微服务架构中的服务发现这个基本问题得以巧妙解决,不但不用复杂的服务发现API供客户端调用,还使所有以TCP/IP方式通信的分布式系统都能方便地迁移到Kubernetes平台上,仅从这个设计来看,Kubernetes就远胜过其他产品。
我们知道,在每个微服务的背后都有多个进程实例来提供服务,在Kubernetes平台上,这些进程实例被封装在Pod中,Pod基本上等同于Docker容器,稍有不同的是,Pod其实是一组密切捆绑在一起并且“同生共死”的 Docker 容器,这组容器共享同一个网络栈与文件系统,相互之间没有隔离,可以直接在进程间通信。最典型的例子是Kubenetes Sky DNS Pod,在这个Pod里有4个Docker '容器。
那么,Kubernetes里的 Service 与 Pod 是如何对应的呢?我们怎么知道哪些Pod 为某个Service提供具体的服务?下图给出了答案——“贴标签”。
每个Pod都可以贴一个或多个不同的标签(Label),而每个Service都有一个“标签选择器”(Label Selector),标签选择器确定了要选择拥有哪些标签的对象。下面这段YAML格式的内容定义了一个被称为ku8-Redis-master的Service,它的标签选择器的内容为“app: ku8-redis-master",表明拥有“app= ku8-redis-master”这个标签的Pod都是为它服务的:
apiversion: v1
kind: Service
metadata:
name: ku8-redis-masterspec:
ports:
- port: 6379selector:
app: ku8-redis-master
下面是 ku8-redis-master这个 Pod 的定义,它的 labels属性的内容刚好匹配Service 的标签选择器的内容:
apiversion: v1kind: Pod
metadata:
name: ku8-redis-masterlabels:
app: ku8-redis-master
spec:
containers:
name: serverimage: redisports:
-containerPort:6379
restartPolicy: Never
如果我们需要一个Service在任意时刻都有N个Pod实例来提供服务,并且在其中1个Pod实例发生故障后,及时发现并且自动产生一个新的Pod实例以弥补空缺,那么我们要怎么做呢?答案就是采用 Deployment/RC,它的作用是告诉Kubernetes,拥有某个特定标签的 Pod需要在Kubernetes集群中创建几个副本实例。Deployment/RC的定义包括如下两部分内容。
●目标Pod的副本数量(replicas)。
●目标Pod的创建模板(Template)。
下面这个例子定义了一个RC,目标是确保在集群中任意时刻都有两个 Pod,其标签为“ app:ku8-redis-slave”,对应的容器镜像为redis slave,这两个 Pod 与ku8-redis-master构成了Redis主从集群(一主二从):
apiversion :v1
kind: ReplicationControllermetadata:
name: ku8-redis-slavespec:
replicas: 2template:
metadata:
labels:
app: ku8-redis-slavespec:
containers:
name: server
image: devopsbq/redis-slave
env:
name: MASTER ADDR
value: ku8-redis-masterports:
-containerPort:6379
至此,上述YAML文件创建了一个一主二从的Redis集群,其中Redis Master被定义为一个微服务,可以被其他Pod或 Service访问,如下图所示。
注意上图在 ku8-reids-slave的容器中有MASTER_ADDR的环境变量,这是Redis Master 的地址,这里填写的是“ku8-redis-master”,它是Redis Master Service 的名称,之前说过:Service的名称就是它的DNS域名,所以Redis Slave容器可以通过这个DNS与Redis Master Service进行通信,以实现Redis 主从同步功能。
Kubernetes 的核心概念就是Service、Pod 及 RC/Deployment。围绕着这三个核心概念,Kubernetes实现了有史以来最强大的基于容器技术的微服务架构平台。比如,在上述Redis集群中,如果我们希望组成一主三从的集群,则只要将控制Redis Slave的 ReplicationController中的replicas改为3,或者用kubectrl scale命令行功能实现扩容即可。命令如下,我们发现,服务的水平扩容变得如此方便:
kubectl scale --replicas=3 rc/ku8-redis-slave
不仅如此,Kubernetes还实现了水平自动扩容的高级特性——HPA ( Horizontal PodAutoscaling ),其原理是基于Pod 的性能度量参数(CPU utilization和 custom metrics)对RC/Deployment管理的Pod进行自动伸缩。举个例子,假如我们认为上述Redis Slave集群对应的Pod也对外提供查询服务,服务期间Pod的 CPU利用率会不断变化,在这些Pod 的CPU平均利用率超过80%后,就会自动扩容,直到CPU利用率下降到80%以下或者最多达到5个副本位置,而在请求的压力减小后,Pod的副本数减少为1个,用下面的HPA命令即可实现这一目标:
kubectl autoscale rc ku8-redis-slave --min=1 --max=5 --cpu-percent=80
除了很方便地实现微服务的水平扩容功能,Kubernetes还提供了使用简单、功能强大的微服务滚动升级功能(rolling update),只要一个简单的命令即可快速完成任务。举个例子,假如我们要将上述Redis Slave服务的镜像版本从devopsbq/redis-slave升级为leader/redis-slave,则只要执行下面这条命令即可:
kubectl rolling-update ku8-redis-slave --image=leader/redis-slave
滚动升级的原理如下图所示,Kubernetes在执行滚动升级的过程中,会创建一个新的RC,这个新的RC使用了新的Pod镜像,然后Kubernetes每隔一段时间就将旧RC的replicas数减少一个,导致旧版本的Pod副本数减少一个,然后将新RC的replicas数增加一个,于是多出一个新版本的Pod副本,在升级的过程中 Pod副本数基本保持不变,直到最后所有的副本都变成新的版本,升级才结束。