通过上篇文章,我们已经成功在Kubernetes集群上部署了一个可以运行的应用程序,并且通过创建一个Service将我们的应用程序暴露给外部的客户前来访问,如果你使用的是类如阿里巴巴ACK这样的公有云Kubernetes容器服务,那么你就可以在全世界任何有网络连接的地方访问你的应用了。接下来,我们首先对我们的SpringCloud引用程序进行代码修改,让应用程序可以返回运行宿主机的IP地址和Hostname,然后更新集群中应用程序的版本,最后我们来扩容应用的实例,让大家亲身体会一下Kubernetes给我们带来的运维便利。
注意:需要完全理解本章的内容必须有一个可供读者练习所学内容的Kubernetes环境,如果你还没有这么一个环境,请参考笔者前边的文章,本篇文章后续的所有内容都假设Docker和Minikube环境启动并正常运行。
容器化部署对于大部分企业来说,最吸引人的部分是灵活的横向扩展,我们在前边文章部署的k8ssample应用其实只有一个实例,我们可以通过运行kubectl get pods就可以看到只有一个POD(应用程序的实例)在运行,但是对着企业的业务不断发展,以及营销活动的大促的影响,一个实例所提供的业务处理能力慢慢就跟不上了, 我们需要更多的处理机器来应对增加的业务访问量。而通过增加实例来扩展系统的方式,我们叫scalling out,而在Kubernetes上,扩容应用可以说是小菜一碟。
【部署新版本的应用程序和扩容】
新版本的SpringCloud应用程序为v1.1,会返回应用运行机器的IP地址和hostname,笔者已经将这个版本的镜像上传到Dockerhub,读者可以有自己的应用来学习本篇的内容,也可以自己使用笔者构建的版本,在自己的minikube集群上运行如下的命令:
➜ ~ kubectl create deployment yunpan-dp-new --image=qigaopan/k8ssample:v1.1
deployment.apps/yunpan-dp-new created
然后可以通过kubectl get pods来验证应用是否已经成功部署,如下是笔者在自己的机器上运行kubectl get pods的输出:
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
yunpan-dp-94f5bbccd-v8npq 1/1 Running 2 2d8h
yunpan-dp-new-7948898cb-p2hxd 1/1 Running 0 25s
可以看到应用已经在健康运行了。准备工作已经完成,接下来,我们来把应用扩容。为了将应用的运行实例从1个增加到3个,我们需要运行命令:kubectl scale deployment yunpan-dp-v1.1 --replicas=3。通过这个命令,我们以声明式的方式,告诉Kubernetes集群,我需要3个应用的实例,请帮我扩容。这里大家需要注意的是,我们并没有告诉K8S集群如何做这件事情,比如增加2个新的实例,或者把之前的一个删除,然后新建三个实例,我们只是告诉我要的目标是什么,我不关心具体的过程。而这正是Kubernetes提供的核心特性”声明式“部署和运维的精髓,通过告诉Kubernetes集群我们需要的应用实例的目标个数,让Kubernetes决定如何达成这个目标状态。
具体来说,Kubernetes收到这个更新后,会将replicas=3这个信息保存到ETCD持久化数据库中,然后控制器观察到这个更新,调用处理逻辑来让应用程序达到这个目标状态。不难理解,控制器会基于应用程序的当前状态,对比应用的期望状态,然后基于目标状态和当前状态,确定要执行的操作。
虽然说使用kubectl scale deployment这样的操作,看起来很命令,但是背后的逻辑其实就是修改这个deployment的对象属性,我们来看看scale命令执行后的效果:
➜ ~ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
yunpan-dp-new 3/3 3 3 162m
通过返回的实例状态信息,我们可以看到通过scale命令扩容的实例已经ready,虽然从命令的输出很难看出这些应用实例是否被部署到单独的POD,但是笔者前边的文章多次强调过,POD是Kubernetes调度的基本单位,因此不难猜测这些应用的实例都属于单独的POD,我们可以通过kubectl get pods来验证一下:
➜ ~ kubectl get pods
NAME READY STATUS RESTARTS AGE
yunpan-dp-new-7948898cb-p2hxd 1/1 Running 0 5h45m
yunpan-dp-new-7948898cb-z47pj 1/1 Running 0 3h4m
yunpan-dp-new-7948898cb-z74pr 1/1 Running 0 3h4m
从命令的输出可以看到,对于我们部署的deployment来说,有三个pod实例在运行,并且这些应用都已经成功启动,我们从Ready列的状态”Running“就能知道,所有的POD都在运行状态。有时候我们希望知道具体哪个POD运行在哪台工作节点,特备是我们想直接通过这台机器的IP地址和端口号来调试服务的时候,kubectl在罗列POD的时候,提供了-o wide选项,通过增加这个选项,我们就能获取到更多pod的信息,如下图所示:
➜ ~ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
yunpan-dp-new-7948898cb-p2hxd 1/1 Running 0 5h49m 172.17.0.12 minikube <none> <none>
yunpan-dp-new-7948898cb-z47pj 1/1 Running 0 3h8m 172.17.0.13 minikube <none> <none>
yunpan-dp-new-7948898cb-z74pr 1/1 Running 0 3h8m 172.17.0.14 minikube <none> <none>
由于笔者本地的环境是单节点集群,因此你可以看到所有的POD实例其实都运行在这台叫minikube的虚拟机节点上。如果你是在类如阿里云ACK这样的托管环境或者自己本地搭建了多节点环境,你就能看到我们scale的POD实例通常都会被调度到不同的工作节点上,并且调度器会尽最大的努力把POD平均调度到各个工作节点上,关于调度器的工作原理,请关注笔者的后续文章。
注意:在过去几年的实际项目中,笔者被人问的最多的问题就是,我们是否应该关心POD具体运行在哪个node节点上。这是个很好的问题,因为这个问题并不是简单的要或者不要,而是需要你彻底的理解Kubernetes平台提供的虚拟化能力和机制。笔者在前边文章介绍过,在负责某个头部企业多次大促工作的时候,环境检查工作中有一项是关于POD的亲和度检查,具体就是检查调度器是否将某个应用的数十个POD调度到一台机器上,这样做的目的是为了避免在高并发访问流量的场景下,不至于出现故障。实际上笔者从来没有见过扩容系统后,调度结果不满足预期的场景,这样的检查充其量就是万无一失而已。
那么我们是否需要关系POD运行在哪个Node上呢?答案其实很显然,我们不应该关心,这是我们引入Kubernetes这样的容器编排和自动化部署运维平台的原因,由于所有的应用实例都来自于相同的镜像和配置信息,并且大部分情况下集群中的工作节点都运行着相同的操作系统以及内核版本,因此这些应用实例本质上是identical的,无论运行到哪个工作节点,提供的服务都是完全相同。
我们在前边的文章中介绍过内核不同可能会造成某些应用运行问题,但是如果我们需要特殊版本的内核,应该在YAML文件中做特殊的配置说明,比如说我们可以通过给工作节点制定相关的标签,这样在部署的时候,声明POD只能调度到持有对应标签的工作节点上。
由于每个应用的POD实例都有独立的IP地址和端口号,因此我们不用担心多个应用实例是运行在一台工作节点,还有运行在多个工作节点上,因为对于Kubernetes来说,每个实例都是一个处理节点,可以通过对应的IP地址进行访问。正是有这种抽象,我们scale应用程序才会如此的容易,特别是当我们的应用被部署到生产环境之后,我们可以根据实际的业务波动,来增加或者减少实例的数量,而完成这个工作,只需要简单的执行一条scale命令,省去了传统运维中需要的手动部署应用,配置,以及将多个实例组成集群。
注意:应用能够被灵活的扩展,并不是只需要Kubernetes提供对应的能力就行,应用本身必须具备无状态的特性,而无状态是云原生架构模式的核心,关于云原生和云计算,大家只需要透彻理解一句话:云计算关心的是应用程序在哪里运行的问题,而云原生关系的是如何运行应用程序来使用云计算提供的红利,包括但不限于:扩展性,稳定性,可见性等。
好了,我们将应用程序的运行实例成功扩容到3个,并且运行在POD中的应用会返回POD的IP地址和hostname,接下来,我们从客户端来访问一下我们的应用,看看Service是否如预期般将访问流量均衡的负载到三个实例上。首先我们创建一个新的Servcie来负载我们新创建的deployment:
➜ ~ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
yunpan-dp-new LoadBalancer 10.103.2.74 <pending> 8085:30684/TCP 49s
接着我们需要通过--url来获取到Service的IP地址和端口号,如果读者对如何获取不熟悉,请阅读笔者关于POD的第一篇文章。在笔者的本地环境中,对外暴露的IP地址和端口号是:http://127.0.0.1:61263, 因此我们可以连续访问三次资源:curl http://127.0.0.1:61263/v1/yunpan/k8s/hello,结果如下:
➜ curl http://127.0.0.1:61263/v1/yunpan/k8s/hello
被访问机器的Host:yunpan-dp-new-6db45f8bdd-fmlvm ,IP地址是:172.17.0.15%
➜ curl http://127.0.0.1:61263/v1/yunpan/k8s/hello
被访问机器的Host:yunpan-dp-new-6db45f8bdd-tbbcd ,IP地址是:172.17.0.12%
➜ curl http://127.0.0.1:61263/v1/yunpan/k8s/hello
被访问机器的Host:yunpan-dp-new-6db45f8bdd-8dkrr ,IP地址是:172.17.0.13%
通过上边的输出可以看到,三次访问命中了三个POD(无论是IP地址,还是hostname都体现出了这个结论)。请求会被均衡的负载到每个应用的实例上,这就是Service在Kubernetes集群中扮演的角色,将请求流浪负载到一个应用的多个实例上,Service的这种能力其实也是LoadBalancer的能力,具体的工作原理如下图所示:
如上图所示,负载均衡扮演的就是将我们的请求流量分发给三个应用的实例,正是因为有这样的机制,我们无论是增加还是减少POD的数量,对客户端永远都是无感的,也降低了扩容需要的时间。
到这里为止,我们成功的通过Deployment和Service对象,以及隐含的POD对象将我们的第一个SpringCloud应用程序成功部署到Kubernetes集群,但是你是不是也很好奇,我的第一个微服务应用,到现在为止部署架构长啥样啊,咱们来稍微总结一下,看看这个简单微服务应用的部署架构和逻辑架构长啥样。
如下图所示是我们部署到Kubenetes集群应用的部署架构图,整个应用由三个运行的应用实例组成,这些应用的POD实例被部署到集群中的工作节点上(注意在单节点集群中,管理节点和工作节点都运行在叫minikube的这个虚拟机上)。
而逻辑架构其实和具体部署的结构没有关系,逻辑架构其实就是我们所说的系统架构图,系统架构图描述的是多个系统模块逻辑关系,通过组成系统来对外提供预期的服务;而部署架构图和底层的基础设施有很大的关系,比如我们部署在一个VPC还是多个VPC,服务具体通过HTTP协议还是RPC协议来交互等,大家在设计系统的时候,一定要有三个视图,逻辑,物理和集成。
好了,今天的内容就这么多了,希望大家能够喜欢,笔者接下来会继续介绍POD对象,敬请关注!