在Kubernetes平台上,编排的基本单位是POD,而不是容器。我们将一组有亲密关系的容器部署在一个叫POD的对象中,由于这些关系紧密的容器最终都运行在同一台宿主机上,并且共享了特定的命名空间,因此这些在同一个POD中的容器实例,可以通过localhost这样的回路设备高效通信。
运行在同一个pod中的容器这种模式就如同豌豆荚(a pea pod),豌豆荚中的每一颗豌豆就是一个容器实例,但是Kubernetes中POD的概念和豌豆荚略有区别,豌豆荚中的豌豆是粒粒分明啊,相互之前没有太明显的关系,而部署到一个POD的应用,必须是业务上,或者说架构设计上具有某种亲密关系,我们才将他们放到一个POD中,由于Kubernetes的调度的基本单位是POD,因此我们在考虑将多个容器实例放到一个POD的时候,一定要考虑清楚,非常慎重。如果没有充分的理由,这些不同的容器实例应该分属于不同的POD。
笔者在介绍容器基础的时候,给大家介绍过两个容器可以共享某些特定的命名空间,比如通过共享网络命名空间,容器进程就可以使用相同的网络接口(eth0和lo网络接口),而使用相同网络接口的结果是,这两个容器就有相同的IP地址和端口号空间。通过共享UTS命名空间,两个容器进程看到的就是相同的系统hostname参数,而这些影响决定了我们在同一个POD中启动多个容器,每个容器进程会看到的运行环境,容器看到的是POD的命名空间。为了让大家对工作节点,POD和容器之间的关系有个之间的了解, 笔者准备了如下所示的关系图:
如上图所示,POD才是我们传统的数据中心虚拟机概念在Kubernetes平台的体现,每个POD可以看成是一个应用程序的实例,而我们调度的,或者说希望Kubernetes提供可扩展性支持的,本质上就是应用程序。在传统的虚拟化方案中,理想情况是我们创建多个虚拟机实例,每个虚拟机实例中运行我们的一个应用程序,应用程序可以随着负载的变化,增加或者减少虚拟机实例的数量。虚拟机中运行的是应用程序进程以及和应用程序关系亲密的进程。这样的部署关系搬到Kubernetes中,就是Pod和进程这种1对多的关系,这也是很多人在“迁云”或者容器化部署自己应用的时候,很容易出现理解的误区,无法准确理解传统数据中心部署架构和Kubernetes部署平台的映射关系,应用还是那个应用,但是你需要基于不同的概念来体现你的部署目标。
在Kubernetes平台上,我们的应用程序可以只由一个进程组成,比如笔者在上篇文章给大家介绍的k8ssample镜像,里边就只有一个通过tomcat部署的spring cloud服务;也可以是由一个住进程和多个辅助进程组成,每个进程都运行在自己的容器中,而POD会被基于当前集群的资源情况,调度到不同的工作节点上。
每个POD都有自己独立的IP地址,hostname,PID进程序列空间,网络接口(网络设备)以及其他的系统资源。从这一点希望大家能体会到,凡是和系统资源分配和调度相关的属性都属于POD,而和运行哪个进项来启动容器进程的都是和容器的配置相关,这个大的原则希望大家能够记住,因为我们后续介绍对象结构的时候,光POD对象的属性,都可以让你晕头转向,更别提加上容器对象的属性。
另外笔者必须提一点,从POD和容器对象的角度来讲,Kubernetes从一开始就只是把容器以及容器的运行时,具体的实现(比如说Docker)设计成整个平台的一个模块而已,因此容器对象的配置,或者说容器对象其实只是POD对象的一个字段而已,这种设计真的非常优秀,非常的深思熟虑,非常的具备扩展性。这种思想,其实是笔者非常推崇的, 我们很多号称架构师的同学,你仔细看,做的都是管理的工作,而像这种会影响系统长期演进和扩展的设计,或者他根本就不懂,或者他根本就没有架构的能力,最后也不知道系统是如何设计出来的,笔者认为架构师要从业务,技术框架选型,架构设计,非功能性需求应对,运维等各个方面都比较平衡,然后认真对待每个实际的项目,从项目中实践自己所声称的架构原则。凡是讲不清楚自己设计的出发点,取舍,和优劣的架构师,都应该深刻反思一下自己的不负责任的行为。
扯得有点远了,我们回归正题,有了POD的这个概念后,我们重新组织一下笔者在前边文章中的那句话:运行在同一个POD中的容器,会觉得自己是运行在这台宿主机上的唯一的进程,它看不到运行在其他POD中的容器进程,即便是这台宿主机上运行着数十个不同的POD。
说到这里,你可能会好奇,运行在Kunbernetes集群中的POD到底长什么样?这是个很好的问题,而kubectl客户端工具为我们提供了对应的工具来探索细节。需要注意的是,容器(container)在Kubernetes平台上并不是顶层的对象,我们是无法通过kubectl来罗列容器对象的,取而代之的是,Kubernetes提供了pods这个顶层对象,我们可以通过如下的命令来罗列正在运行的POD:
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
yunpan-dp-94f5bbccd-v8npq 1/1 Running 0 32h
上边清单上展示的这个叫yunpan-dp-xx的pod,就是我们在上篇文章上部署的应用,大家可以通过这个Pod的状态看出,POD正在健康运行。或者说我们部署的应用程序,已经运行起来了。有时候我们部署完应用后,马上通过get pods获取的结果可能显示POD正在pending,这主要是因为我们给Kubernetes集群部署新的应用的过程,其实是个最终一致性的过程,比如运行kubectl -f后,返回的信息只是告诉我们对象已经创建,而没有任何应用是否启动的信息。
当API Server将对象保存到ECTD之后,调度器需要基于集群现在的资源情况,给应用寻找合适的工作节点,以及分配工作节点后,如果应用运行需要的镜像还不在宿主机的缓存中,还需要从对应的镜像仓库下载指定版本的镜像文件,这些都需要时间;只有镜像被下载下来之后,宿主机上的kubelet才会驱动容器运行时来启动应用,并将应用的状态反馈给管理节点,这样我们才能从这个get pods的命令中看到对应的状态。
虽然说这个最终一致性的设计方案,可以极大的提升整个集群的吞吐量,但是复杂度陡然激增啊,对于开发和运维人员,我们就需要借助其他的手段来监控容器启动和运行时遇到的错误,比如我们启动应用使用的镜像版本不存在,或者容器仓库的地址写错了,而这些错误信息都会展示在get pods返回的STATUS列。另外如果是因为镜像拉取导致应用启动失败,笔者建议大家登陆到宿主机上,通过docker pull来手动的调试一下,Kubernetes集群中网络问题占据了运维和开发的大部分比例。
但是如果造成应用启动不是由于镜像无法拉取,为了排查具体哪里出错,我们就需要使用其他的办法来获取具体的出错信息,kubectl提供了kubectl describe pod这个命令(如果你还有印象,我们用这个命令也获取过工作节点的更多详细信息)来获取pod更多详细信息。如果pod在启动或者运行的过程出现问题,那么出错的具体原因就可以从这个命令的输出信息中获取到。具体来说,我们可以从输出的信息中,找到Events节点,对于部署到集群中的应用,Events部分详细记录了整个启动过程以及出现的异常。在笔者的mac机器上,输出如下图所示:
从上图可以看到容器的整个启动的过程,因为笔者本地有对应的镜像,因此你是看不到拉取镜像的日志输出,但是大家需要注意的是,这是一种常用的运维技巧,特别是当部署的应用反复重启都失败的情况,我们需要通过describe命令来具体看看发生了啥。为了帮助大家更好的理解,笔者结合上一篇文章中部署yunpan-pd这个deployment的过程,通过下图来详细介绍一下背后的机制:
具体来说,当我们执行kubectl create命令,会通过API Server提供的Restful API在ETCD中创建对应的deployment对象,接着通过这个对象中关于pod模板部分的信息,controller会基于API Server的API创建pod对象,并持久化到etcd数据库中。
调度器(一种特殊的controller)注意到有个新的pod需要创建,会结合集群现在的资源选装,决定这个pod可以运行在哪个工作节点,并且把pod的对应字段做相应的更新,当更新完成后,运行在对应工作节点的kubelet注意到有个pod分配给了自己,听过API Server提供的API后去POD的详细信息,然后执行拉取镜像(如果有必要),驱动宿主机上的容器运行时比如Docker来基于拉取的镜像创建容器实例,并启动容器中的应用,监控容器实例的运行情况,并将信息发送给controller,并更新到API Server的对象上。
注意:调度这个词在Kubernetes中特质“给新创建的Pod确定运行工作节点这个事情”,虽然说是最终一致性,但是POD会在保存到etcd后马上就开始调度和执行,就如同我们在传统的计算机操作系统上做任务管理,当我们启动某个进程后,操作系统会马上给这个进程分配内存和CPU时间,整个程序就开始运行了。Kubernetes平台的调度器用来决定pod应该在哪个工作节点上运行,而和操作系统的调度器功能不同的是,如果一个pod被调度到某个工作节点,你们只要这台工作节点健康,这个pod会一直运行在这个工作节点上,即便是这个pod由于自身原因运行失败被重新启动。
到这里为止,我们的应用终于在集群上运行起来了,但是你会发现我们遇到了一个问题,我们如何访问到这个应用,虽然说Pod有独立的IP地址,但是这个IP地址一看都是内部地址,我们能从外部访问到吗?如果访问不到,我们该如何访问应用提供的接口功能呢?请继续阅读。
【 将应用提供的接口暴露给外部客户端访问】
笔者前边多次强调,在Kubernetes平台上,所有事物都是通过对象来表示,提供pod能力访问接入功能,也是由叫做Service的对象来承载。Kubernetes提供了不同类型的Service对象,我们需要根据自己的实例业务需求来决定使用哪一种。具体来说,有些应用只需要将自己提供API接口暴露给部署在相同Kuberntes集群的应用使用,比如库存服务暴露给订单服务来提供库存扣减,库存占用和库存恢复等功能;而有些服务需要暴露给外部的应用,比如用户中心的登陆接口,就需要通过暴露给互联网山的端应用来访问,比如H5和移动端。
Kubernetes提供了LoadBalancer类型的Service,来创建可以对外提供服务的负载均衡器类型的Service,这样我们就可以通过这个负载均衡器的IP地址来访问部署在集群中POD上的服务能力。我们可以通过如下的kubectl命令来创建一个service对象:
➜ ~ kubectl expose deployment yunpan-dp --type=LoadBalancer --port 8085
service/yunpan-dp exposed
笔者前边文章介绍过,通过create deployment命令创建了一个叫yunpan-dp的deployment对象,那么我们就可以使用expose deployment命令来给这个deployment对象创建对应的Service对象,并且类型是LoadBalancer,上边的这个命令告诉Kubernetes集群三个事情:
1,我们想通过这个新创建的Service对象来暴露所有属于yunpan-dp这个deployment对象的pod。
2,我们希望创建一个LoadBalancer类型的Service,这样外部的应用就可以通过这个LB来访问deployment中pod提供的服务能力。
3,应用程序的访问端口是8085,因此我们就必须通过这个端口来访问。
注意:由于我们并没有给Service提供具体的名字,因此这个Service的名字将会和deployment的名字一样。
Service对象和其他笔者已经介绍过的Kubernetes对象一样,可以通过kubectl get命令来罗列出来,如下图所示,是在笔者的mac机器上运行kubectl get services的输出结果:
上图中显示了很多的服务实例,大家只需要关注最后一个高亮的,这就是我们刚才的命令创建的LoadBalancer类型的Service对象。反应快的同学应该注意到上图有个问题,为啥服务的状态是<pending>这么个奇怪的名字,请继续阅读。
Kubernetes虽然说提供了LoadBalancer类型的Service对象,但是Kubernetes本身并没有提供LB(这句话有绕,如果不明白,可以仔细考虑一下LB的本质是什么),如果我们使用的是像阿里云ACK这样的托管Kubernetes服务,那么K8S集群已经和云平台进行了集成,因此创建这个类型的服务就可以直接调用阿里云的LB服务来创建负载均衡器,并且通过这个负载均衡器来接受外部的请求流量。
这种通过云平台的能力来创建负载均衡的方法,是很多在公共云上部署应用的通用模式,为了让大家对这个过程能够理解的更加透彻,笔者准备了如下的部署过程图:
如上图所示,通过驱动云平台来创建接受外部流量的LB,通常创建外部的LB需要一些时间,因此如果我们使用的是云平台上的托管Kubernetes环境,创建Service的命令执行完成后,过一段时间我们再次运行kubectl get services就可以看到EXTERNAL-IP这一列有了可用的公共IP地址了。如下所示:
➜ ~ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
yunpan-dp LoadBalancer 10.131.24.17 35.246.179.43 8085:30187/TCP 6m9s
这样我们就可以通过这个35.246.179.43的IP地址,来访问我们的服务了。很不幸的是,如果你使用的是minikube集群,不会有任何的负载均衡器被创建,我们需要通过其他的方式来访问服务。
【如何访问部署在Minikube集群中的POD服务?】
如笔者前边的介绍,并不是所有类型的集群都提供LoadBalancer类型的Service,而Minikube就属于其中之一。虽然我们可以通过kubectl expose成功创建出Service对象,但是因为没有与之关联的LB,因此你会看到EXTERNAL-IP部分一直都是<pending>,如下是笔者机器上Minikube环境的输出:
➜ ~ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
yunpan-dp LoadBalancer 10.111.36.149 <pending> 8085:30187/TCP 6m9s
幸运的是Kubernetes提供了其他的方式来访问部署到容器中的服务接口,我们今天先介绍一种简单的,笔者会在后续的文章中详细介绍所有的访问方式。
具体来说,我们可以通过运行命令 minikube service yunpan-dp --url来返回访问Service的IP地址和端口号,如下是在笔者mac机器上的Minikube集群中返回的结果:
如上图所示,我们可以通过http://127.0.0.1:52381 来访问部署到集群中的SpringCloud服务,在笔者的机器上访问服务,获取到期待的结果:
你可以会好奇这个IP地址和端口号来自于哪里,其实啊,这个IP地址不出所料的就是Minikube运行的虚拟机的IP地址,我们可以通过minikube ip来确认这一点。而对于端口号52381来说,由于Minikube虚拟机是我们的单机集群的唯一机器,因此也是工作节点,而端口号52381也称作node port,工作节点端口号,这就是笔者上班说的,从负载均衡接受流量的Service监听端口。
最后,如果你知道自己的工作节点的IP地址,我们可以直接通过工作节点的IP和端口号来访问具体的服务,比如在上边的例子中,如果我们有多个工作节点,其中一个工作节点的IP地址是:10.111.36.111,那么我们就可以通过http://10.111.36.111:52381 来访问服务暴露的API接口能力。下图是这种访问方式的图示,为了帮助大家更好的理解:
好了, 今天的文章就这么多了,下一篇文章我们继续深入理解POD这个对象。