这里写目录标题
- 1、Kubernetes是什么
- 2、为什么要用Kubernetes
- 3、从一个简单的例子开始
- 3.1、环境准备
- 3.2、启动MySQL服务
- 3.3、启动Tomcat应用
- 3.4、通过浏览器访问网页
1、Kubernetes是什么
首先,它是一个全新的基于容器技术的分布式架构领先方案。这个方案虽然还很新,但它是谷歌十几年以来大规模应用容器技术的经验积累和升华的重要成果。确切地说,Kubernetes是谷歌严格保密十几年的秘密武器—Borg的一个开源版本。Borg是谷歌的一个久负盛名的内部使用的大规模集群管理系统,它基于容器技术,目的是实现资源管理的自动化,以及跨多个数据中心的资源利用率的最大化。十几年以来,谷歌一直通过Borg系统管理着数量庞大的应用程序集群。由于谷歌员工都签署了保密协议,即便离职也不能泄露Borg的内部设计,所以外界一直无法了解关于它的更多信息。直到2015年4月,传闻许久的Borg论文伴随Kubernetes的高调宣传被谷歌首次公开,大家才得以了解它的更多内幕。正是由于站在Borg这个前辈的肩膀上,汲取了Borg过去十年间的经验与教训,所以Kubernetes一经开源就一鸣惊人,并迅速称霸容器领域。
其次,如果我们的系统设计遵循了Kubernetes的设计思想,那么传统系统架构中那些和业务没有多大关系的底层代码或功能模块,都可以立刻从我们的视线中消失,我们不必再费心于负载均衡器的选型和部署实施问题,不必再考虑引入或自己开发一个复杂的服务治理框架,不必再头疼于服务监控和故障处理模块的开发。总之,使用Kubernetes提供的解决方案,我们不仅节省了不少于30%的开发成本,还可以将精力更加集中于业务本身,而且由于Kubernetes提供了强大的自动化机制,所以系统后期的运维难度和运维成本大幅度降低。
然后,Kubernetes是一个开放的开发平台。与J2EE不同,它不局限于任何一种语言,没有限定任何编程接口,所以不论是用Java、Go、C++还是用Python编写的服务,都可以被映射为Kubernetes的Service(服务),并通过标准的TCP通信协议进行交互。此外,Kubernetes平台对现有的编程语言、编程框架、中间件没有任何侵入性,因此现有的系统也很容易改造升级并迁移到Kubernetes平台上。
最后,Kubernetes是一个完备的分布式系统支撑平台。Kubernetes具有完备的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建的智能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制,以及多粒度的资源配额管理能力。同时,Kubernetes提供了完善的管理工具,这些工具涵盖了包括开发、部署测试、运维监控在内的各个环节。因此,Kubernetes是一个全新的基于容器技术的分布式架构解决方案,并且是一个一站式的完备的分布式系统开发和支撑平台。
在正式开始本章的Hello World之旅之前,我们首先要学习Kubernetes的一些基本知识,这样才能理解Kubernetes提供的解决方案。
在Kubernetes中,Service是分布式集群架构的核心,一个Service对象拥有如下关键特征。
- 拥有唯一指定的名称(比如mysql-server)。
- 拥有一个虚拟IP(Cluster IP、Service IP或VIP)和端口号。
- 能够提供某种远程服务能力。
- 被映射到提供这种服务能力的一组容器应用上。
Service的服务进程目前都基于Socket通信方式对外提供服务,比如Redis、Memcache、MySQL、Web Server,或者是实现了某个具体业务的特定TCP Server进程。虽然一个Service通常由多个相关的服务进程提供服务,每个服务进程都有一个独立的Endpoint(IP+Port)访问点,但Kubernetes能够让我们通过Service(虚拟Cluster IP+Service Port)连接到指定的Service。有了Kubernetes内建的透明负载均衡和故障恢复机制,不管后端有多少服务进程,也不管某个服务进程是否由于发生故障而被重新部署到其他机器,都不会影响对服务的正常调用。更重要的是,这个Service本身一旦创建就不再变化,这意味着我们再也不用为Kubernetes集群中服务的IP地址变来变去的问题而头疼了。
容器提供了强大的隔离功能,所以有必要把为Service提供服务的这组进程放入容器中进行隔离。为此,Kubernetes设计了Pod对象,将每个服务进程都包装到相应的Pod中,使其成为在Pod中运行的一个容器(Container)。为了建立Service和Pod间的关联关系,Kubernetes首先给每个Pod都贴上一个标签(Label),给运行MySQL的Pod贴上name=mysql标签,给运行PHP的Pod贴上name=php标签,然后给相应的Service定义标签选择器(Label Selector),比如MySQL Service的标签选择器的选择条件为name=mysql,意为该Service要作用于所有包含name=mysql Label的Pod。这样一来,就巧妙解决了Service与Pod的关联问题。
这里先简单介绍Pod的概念。首先,Pod运行在一个被称为节点(Node)的环境中,这个节点既可以是物理机,也可以是私有云或者公有云中的一个虚拟机,通常在一个节点上运行几百个Pod;其次,在每个Pod中都运行着一个特殊的被称为Pause的容器,其他容器则为业务容器,这些业务容器共享Pause容器的网络栈和Volume挂载卷,因此它们之间的通信和数据交换更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放入同一个Pod中;最后,需要注意的是,并不是每个Pod和它里面运行的容器都能被映射到一个Service上,只有提供服务(无论是对内还是对外)的那组Pod才会被映射为一个服务。
在集群管理方面,Kubernetes将集群中的机器划分为一个Master和一些Node。在Master上运行着集群管理相关的一组进程kube-apiserver 、 kube-controller-manager和kube-scheduler,这些进程实现了整个集群的资源管理、Pod调度、弹性伸缩、安全控制、系统监控和纠错等管理功能,并且都是自动完成的。Node作为集群中的工作节点,运行真正的应用程序,在Node上Kubernetes管理的最小运行单元是Pod。在Node上运行着Kubernetes的kubelet、kube-proxy服务进程,这些服务进程负责Pod的创建、启动、监控、重启、销毁,以及实现软件模式的负载均衡器。
最后,看看传统的IT系统中服务扩容和服务升级这两个难题,以及Kubernetes所提供的全新解决思路。服务的扩容涉及资源分配(选择哪个节点进行扩容)、实例部署和启动等环节,在一个复杂的业务系统中,这两个难题基本上靠人工一步步操作才得以解决,费时费力又难以保证实施质量。
在Kubernetes集群中,只需为需要扩容的Service关联的Pod创建一个RC(Replication Controller),服务扩容以至服务升级等令人头疼的问题都迎刃而解。在一个RC定义文件中包括以下3个关键信息。
- 目标Pod的定义。
- 目标Pod需要运行的副本数量(Replicas)。
- 要监控的目标Pod的标签。
在创建好RC(系统将自动创建好Pod)后,Kubernetes会通过在RC中定义的Label筛选出对应的Pod实例并实时监控其状态和数量,如果实例数量少于定义的副本数量,则会根据在RC中定义的Pod模板创建一个新的Pod,然后将此Pod调度到合适的Node上启动运行,直到Pod实例的数量达到预定目标。这个过程完全是自动化的,无须人工干预。有了RC,服务扩容就变成一个纯粹的简单数字游戏了,只需修改RC中的副本数量即可。后续的服务升级也将通过修改RC来自动完成。
以将在第2章中介绍的PHP+Redis留言板应用为例,只要为PHP留言板程序(frontend)创建一个有3个副本的RC+Service,为Redis读写分离集群创建两个RC:写节点(redis-master)创建一个单副本的RC+Service,读节点(redis-slaver)创建一个有两个副本的RC+Service,就可以快速完成整个集群的搭建过程,是不是很简单?
2、为什么要用Kubernetes
使用Kubernetes的理由很多,最重要的理由是,IT行业从来都是由新技术驱动的。
当前,Docker这门容器化技术已经被很多公司采用,从单机走向集群已成为必然,云计算的蓬勃发展正在加速这一进程。Kubernetes作为当前被业界广泛认可和看好的基于Docker的大规模容器化分布式系统解决方案,得到了以谷歌为首的IT巨头们的大力宣传和持续推进。
2015年,谷歌联合20多家公司一起建立了CNCF(CloudNative Computing Foundation,云原生计算基金会)开源组织来推广Kubernetes,并由此开创了云原生应用(Cloud Native Application)的新时代。作为CNCF“钦定”的官方云原生平台,Kubernetes正在颠覆应用程序 的开发方式。
如今,数百家厂商和技术社区共同构建了非常强大的云原生生态,市面上几乎所有提供云基础设施的公司都以原生形式将Kubernetes作为底层平台,可以预见,会有大量的新系统选择Kubernetes,不论这些新系统是运行在企业的本地服务器上,还是被托管到公有云上。
使用Kubernetes会收获哪些好处呢?
首先,可以“轻装上阵”地开发复杂系统。以前需要很多人(其中不乏技术达人)一起分工协作才能设计、实现和运维的分布式系统,在采用Kubernetes解决方案之后,只需一个精悍的小团队就能轻松应对。在这个团队里,只需一名架构师负责系统中服务组件的架构设计,几名开发工程师负责业务代码的开发,一名系统兼运维工程师负责Kubernetes的部署和运维,因为Kubernetes已经帮我们做了很多。
其次,可以全面拥抱微服务架构。微服务架构的核心是将一个巨大的单体应用分解为很多小的互相连接的微服务,一个微服务可能由多个实例副本支撑,副本的数量可以随着系统的负荷变化进行调整。微服务架构使得每个服务都可以独立开发、升级和扩展,因此系统具备很高的稳定性和快速迭代能力,开发者也可以自由选择开发技术。谷歌、亚马逊、eBay、Netflix等大型互联网公司都采用了微服务架构,谷歌更是将微服务架构的基础设施直接打包到Kubernetes解决方案中,让我们可以直接应用微服务架构解决复杂业务系统的架构问题。
再次,可以随时随地将系统整体“搬迁”到公有云上。Kubernetes最初的设计目标就是让用户的应用运行在谷歌自家的公有云GCE中,华为云(CCE)、阿里云(ACK)和腾讯云(TKE)先后宣布支持Kubernetes集群,未来会有更多的公有云及私有云支持Kubernetes。同时,在Kubernetes的架构方案中完全屏蔽了底层网络的细节,基于Service的虚拟IP地址(Cluster IP)的设计思路让架构与底层的硬件拓扑无关,我们无须改变运行期的配置文件,就能将系统从现有的物理机环境无缝迁移到公有云上。
然后,Kubernetes内在的服务弹性扩容机制可以让我们轻松应对突发流量。在服务高峰期,我们可以选择在公有云中快速扩容某些Service的实例副本以提升系统的吞吐量,这样不仅节省了公司的硬件投入,还大大改善了用户体验。中国铁路总公司的12306购票系统,在客流高峰期(如节假日)就租用了阿里云进行分流。
最后,Kubernetes系统架构超强的横向扩容能力可以让我们的竞争力大大提升。对于互联网公司来说,用户规模等价于资产,因此横向扩容能力是衡量互联网业务系统竞争力的关键指标。我们利用Kubernetes提供的工具,不用修改代码,就能将一个Kubernetes集群从只包含几个Node的小集群平滑扩展到拥有上百个Node的大集群,甚至可以在线完成集群扩容。只要微服务架构设计得合理,能够在多个云环境中进行弹性伸缩,系统就能够承受大量用户并发访问带来的巨大压力。
3、从一个简单的例子开始
考虑到Kubernetes提供的PHP+Redis留言板的HelloWorld例子对于绝大多数刚接触Kubernetes的人来说比较复杂,难以顺利上手和实践,所以在此将其替换成一个简单得多的Java Web应用例子,可以让新手快速上手和实践。
此Java Web应用的结构比较简单,是一个运行在Tomcat里的Web App,如图所示,JSP页面通过JDBC直接访问MySQL数据库并展示数据。出于演示和简化的目的,只要程序正确连接到了数据库,就会自动完成对应的Table的创建与初始化数据的准备工作。所以,当我们通过浏览器访问此应用时,就会显示一个表格的页面,数据则来自数据库。
此应用需要启动两个容器:Web App容器和MySQL容器,并且Web App容器需要访问MySQL容器。在Docker时代,假设我们在一个宿主机上启动了这两个容器,就需要把MySQL容器的IP地址通过环境变量注入Web App容器里;同时,需要将Web App容器的8080端口映射到宿主机的8080端口,以便在外部访问。在本章的这个例子里,我们介绍在Kubernetes时代是如何达到这个目标的。
3.1、环境准备
首先,安装Kubernetes和下载相关镜像,本书建议采用VirtualBox或者VMware Workstation在本机虚拟一个64位的CentOS 7虚拟机作为学习环境。虚拟机采用NAT的网络模式以便连接外网,然后使用kubeadm快速安装一个Kubernetes集群。之后就可以在这个Kubernetes集群中进行练习了。
注:本书示例中的Docker镜像下载地址为https://hub.docker.com/u/kubeguide/。
3.2、启动MySQL服务
首先,为MySQL服务创建一个RC定义文件mysql-rc.yaml,下面给出了该文件的完整内容和解释:
apiVersion:v1kind:ReplicationController# 副本控制器RCmetadata:name:mysql# RC的名称,全局唯一spec:replicas:1# Pod副本的期待数量selector:app:mysql# 符合目标的Pod拥有此标签template:# 根据此模板创建Pod的副本(实例)metadata:labels:app:mysql# Pod副本拥有的标签,对应RC的Selectorspec:containers:# Pod内容器的定义部分-name:mysql# 容器的名称image:mysql# 容器对应的Docker Imageports:-containerPort:3306# 容器应用监听的端口号env:# 注入容器内的环境变量-name:MYSQL_ROOT_PASSWORDvalue:"123456"以上YAML定义文件中的kind属性用来表明此资源对象的类型,比如这里的值为ReplicationController,表示这是一个RC;在spec一节中是RC的相关属性定义,比如spec.selector是RC的Pod标签选择器,即监控和管理拥有这些标签的Pod实例,确保在当前集群中始终有且仅有replicas个Pod实例在运行,这里设置replicas=1,表示只能运行一个MySQL Pod实例。当在集群中运行的Pod数量少于replicas时,RC会根据在spec.template一节中定义的Pod模板来生成一个新的Pod实例,spec.template.metadata. labels指定了该Pod的标签,需要特别注意的是:这里的labels必须匹配之前的spec.selector,否则此RC每创建一个无法匹配Label的Pod,就会不停地尝试创建新的Pod,陷入恶性循环中。
在创建好mysql-rc.yaml文件后,为了将它发布到Kubernetes集群中,我们在Master上执行命令:
# kubectl create -f mysql-rc.yamlreplicationcontroller"mysql"created接下来,用kubectl命令查看刚刚创建的RC:
# kubectl get rcNAME DESIRED CURRENT AGE mysql111m查看Pod的创建情况时,可以运行下面的命令:
# kubectl get podsNAME READY STATUS RESTARTS AGE mysql-c95jc1/1 Running02m我们看到一个名为mysql-xxxxx的Pod实例,这是Kubernetes根据mysql这个RC的定义自动创建的Pod。由于Pod的调度和创建需要花费一定的时间,比如需要一定的时间来确定调度到哪个节点上,以及下载Pod里的容器镜像需要一段时间,所以我们一开始看到Pod的状态显示为Pending。在Pod成功创建完成以后,状态最终会被更新为Running。
我们通过docker ps指令查看正在运行的容器,发现提供MySQL服务的Pod容器已经创建并正常运行了,此外会发现MySQL Pod对应的容器还多创建了一个来自谷歌的pause容器,这就是Pod的“根容器”,详见1.4.3节的说明。
# docker ps | grep mysql72ca992535b4 mysql"docker-entrypoint.sh"12minutes ago Up12minutes k8s_mysql.86dc506e_mysql-c95jc_default_511d6705-5051-11e6-a9d8-000c29ed42c1_9f89 d0b4 76c1790aad27 gcr.io/google_containers/pause-amd64:3.0"/pause"12minutes ago Up12minutes k8s_POD.16b20365_mysql-c95jc_default_511d6705-5051-11e6-a9d8-000c29ed42c1_28520a ba最后,创建一个与之关联的Kubernetes Service—MySQL的定义文件(文件名为mysql-svc.yaml),完整的内容和解释如下:
apiVersion:v1kind:Service# 表明是Kubernetes Servicemetadata:name:mysql# Service的全局唯一名称spec:ports:-port:3306# Service提供服务的端口号selector:# Service对应的Pod拥有这里定义的标签app:mysql其中,metadata.name是Service的服务名(ServiceName);port属性则定义了Service的虚端口;spec.selector确定了哪些Pod副本(实例)对应本服务。类似地,我们通过kubectl create命令创建Service对象。
运行kubectl命令,创建Service:
# kubectl create -f mysql-svc.yamlservice"mysql"created运行kubectl命令查看刚刚创建的Service:
# kubectl get svcNAME CLUSTER-IP EXTERNAL-IP PORT(S)AGE mysql169.169.253.143<none>3306/TCP 48s可以发现,MySQL服务被分配了一个值为169.169.253.143的Cluster IP地址。随后,Kubernetes集群中其他新创建的Pod就可以通过Service的ClusterIP+端口号3306来连接和访问它了。
通常,Cluster IP是在Service创建后由Kubernetes系统自动分配的,其他Pod无法预先知道某个Service的Cluster IP地址,因此需要一个服务发现机制来找到这个服务。为此,最初时,Kubernetes巧妙地使用了Linux环境变量(Environment Variable)来解决这个问题,后面会详细说明其机制。现在只需知道,根据Service的唯一 名称,容器可以从环境变量中获取Service对应的ClusterIP地址和端口,从而发起TCP/IP连接请求。
3.3、启动Tomcat应用
上面定义和启动了MySQL服务,接下来采用同样的步骤完成Tomcat应用的启动过程。首先,创建对应的RC文件myweb-rc.yaml,内容如下:
apiVersion:v1kind:ReplicationControllermetadata:name:mywebspec:replicas:2selector:app:mywebtemplate:metadata:labels:app:mywebspec:containers:-name:mywebimage:kubeguide/tomcat-app:v1ports:-containerPort:8080注意:在Tomcat容器内,应用将使用环境变量MYSQL_SERVICE_HOST的值连接MySQL服务。更安全可靠的用法是使用服务的名称mysql,详见本章Service的概念和第4章的说明。运行下面的命令,完成RC的创建和验证工作:
#kubectl create -f myweb-rc.yamlreplicationcontroller"myweb"created# kubectl get podsNAME READY STATUS RESTARTS AGE mysql-c95jc1/1 Running02h myweb-g9pmm1/1 Running03s最后,创建对应的Service。以下是完整的YAML定义文件(myweb-svc.yaml):
apiVersion:v1kind:Servicemetadata:name:mywebspec:type:NodePortports:-port:8080nodePort:30001selector:app:mywebtype=NodePort和nodePort=30001的两个属性表明此Service开启了NodePort方式的外网访问模式。在Kubernetes集群之外,比如在本机的浏览器里,可以通过30001这个端口访问myweb(对应到8080的虚端口上)。
运行kubectl create命令进行创建:
# kubectl create -f myweb-svc.yamlYou have exposed yourserviceon an external port on all nodesinyour cluster. If you want to expose thisserviceto the external internet, you may need tosetup firewall rulesfortheserviceport(s)(tcp:30001)to serve traffic. See http://releases.k8s.io/release-1.3/docs/user-guide/services-firewalls.mdformoredetails.service"myweb"created我们看到上面有提示信息,意思是需要把30001这个端口在防火墙上打开,以便外部的访问能穿过防火墙。
运行kubectl命令,查看创建的Service:
# kubectl get servicesNAME CLUSTER-IP EXTERNAL-IP PORT(S)AGE mysql169.169.253.143<none>3306/TCP 2m myweb169.169.149.215<nodes>8080/TCP 1m kubernetes169.169.0.1<none>443/TCP 10m至此,我们的第1个Kubernetes例子便搭建完成了,我们将在下一节中验证结果。
3.4、通过浏览器访问网页
经过上面的几个步骤,我们终于成功实现了Kubernetes上第1个例子的部署搭建工作。现在一起来见证成果吧!在你的笔记本上打开浏览器,输入http://虚拟机IP:30001/demo/。
比如虚拟机IP为192.168.18.131(可以通过#ip a命令进行查询),在浏览器里输入地址http://192.168.18.131:30001/demo/后,可以看到如图所示的网页界面。
如果看不到这个网页界面,那么可能有几个原因,比如因为防火墙的问题无法访问30001端口,或者因为是通过代理上网的,浏览器错把虚拟机的IP地址当作远程地址了。可以在虚拟机上直接运行curl 192.168.18.131:30001来验证此端口能否被访问,如果还是不能访问,就肯定不是机器的问题了。
接下来,可以尝试单击“Add…”按钮添加一条记录并提交,如图所示,在提交以后,数据就被写入MySQL数据库中了。
至此,我们终于完成了Kubernetes上的Tomcat例子,这个例子并不是很复杂。我们也看到,相对于传统的分布式应用的部署方式,在Kubernetes之上我们仅仅通过一些很容易理解的配置文件和相关的简单命令就完成了对整个集群的部署,这让我们惊诧于Kubernetes的创新和强大。
下一节,我们将对Kubernetes中的基本概念和术语进行全面学习,在这之前,读者可以继续研究这个例子里的一些拓展内容,如下所述。
- 研究RC、Service等配置文件的格式。
- 熟悉kubectl的子命令。
- 手工停止某个Service对应的容器进程,然后观察有什么现象发生。
- 修改RC文件,改变副本数量,重新发布,观察结果。