考虑到Kubernetes提供的PHP+Redis留言板的Hello World例子对于绝大多数新手来说比较复杂,难以顺利上手和实践,在此将其替换成一个简单得多的Java Web应用的例子,可以让新手快速上手和实践。
该应用是一个运行在Tomcat里的Web App,结构比较简单,如图1.1所示,JSP页面通过JDBC直接访问MySQL数据库并展示数据。这里出于演示和简化的目的,只要程序正确连接数据库,就会自动完成对应的Table创建与初始化数据的准备工作。所以,当我们通过浏览器访问此应用时,就会显示一个表格页面,其中包含来自数据库的内容。
此应用需要启动两个容器:Web App容器和MySQL容器,并且Web App容器需要访问MySQL容器。如果仅使用Docker启动这两个容器,则需要通过Docker Network或者端口映射的方式实现容器间的网络互访。本例介绍在Kubernetes系统中是如何实现的。

图1.1 Java Web应用的结构
这里先安装Kubernetes和下载相关镜像,本书建议采用VirtualBox或者VMware Workstation在本机中虚拟一个64位的CentOS 7虚拟机作为学习环境。虚拟机采用NAT的网络模式以便连接外网,然后使用kubeadm快速安装一个Kubernetes集群(安装步骤详见2.2节的说明),之后就可以在这个Kubernetes集群中进行练习了。
首先,为MySQL服务创建一个Deployment定义文件mysql-deploy.yaml,下面给出了该文件的完整内容和说明:

以上YAML定义文件中的kind属性用来表明此资源对象的类型,比如这里的属性值表示这是一个Deployment;spec部分是Deployment的相关属性定义,比如spec.selector是Deployment的Pod选择器,符合条件的Pod实例受到该Deployment的管理,确保在当前集群中始终有且仅有replicas个Pod实例在运行(这里设置replicas=1,表示只能运行一个MySQL Pod实例)。当在集群中运行的Pod数量少于replicas时,Deployment控制器会根据在spec.template部分定义的Pod模板生成一个新的Pod实例,spec.template.metadata.labels指定了该Pod的标签,labels必须匹配之前的spec.selector。
创建好mysql-deploy.yaml文件后,为了将它发布到Kubernetes集群中,我们在Master上运行如下命令:

接下来,运行kubectl命令查看刚刚创建的Deployment:

查看Pod的创建情况时,可以运行下面的命令:

可以看到一个名称为mysql-85f4b4cdf4-k97wh的Pod实例,这是Kubernetes根据mysql这个Deployment的定义自动创建的Pod。由于Pod的调度和创建需要花费一定的时间,比如需要确定调度到哪个节点上,而且下载Pod所需的容器镜像也需要一段时间,所以一开始Pod的状态为Pending。在Pod成功创建启动完成后,其状态最终会更新为Running。
我们可以在Kubernetes节点的服务器上通过docker ps指令查看正在运行的容器,发现提供MySQL服务的Pod容器已创建且正常运行,并且MySQL Pod对应的容器多创建了一个Pause容器,该容器就是Pod的根容器。


最后,创建一个与之关联的Kubernetes Service—MySQL的定义文件(文件名为mysql-svc.yaml),完整的内容和说明如下:

其中,metadata.name是Service的服务名(ServiceName);spec.ports属性定义了Service的虚端口;spec.selector确定了哪些Pod副本(实例)对应本服务。类似地,我们通过kubectl create命令创建Service对象:

运行kubectl get命令,查看刚刚创建的Service对象:

可以发现,MySQL服务被分配了一个值为10.245.161.22的ClusterIP地址(在不同环境中分配的IP地址可能不同)。随后,在Kubernetes集群中新创建的其他Pod就可以通过Service的ClusterIP+端口号3306来连接和访问它了。
通常,ClusterIP地址是在Service创建后由Kubernetes系统自动分配的,其他Pod无法预先知道某个Service的ClusterIP地址,因此需要一个服务发现机制来找到这个服务。为此,Kubernetes最初巧妙地使用了Linux环境变量(Environment Variable)来解决这个问题。根据Service的唯一名称,容器可以从环境变量中获取Service对应的ClusterIP地址和端口号,从而发起TCP/IP连接请求。
前面定义和启动了MySQL服务,接下来采用同样的步骤完成Tomcat应用的启动。首先,创建对应的RC文件myweb-deploy.yaml,内容如下:

注意:在Tomcat容器内,应用将使用环境变量MYSQL_SERVICE_HOST的值连接MySQL服务,但这里为什么没有注册该环境变量呢?这是因为Kubernetes会自动将已存在的Service对象以环境变量的形式展现在新生成的Pod中。其更安全、可靠的方法是使用服务的名称mysql,这就要求集群内的DNS服务(kube-dns)正常运行。运行下面的命令,完成Deployment的创建和验证工作:


最后,创建对应的Service。以下是完整的YAML定义文件(myweb-svc.yaml):

“type:NodePort”和“nodePort:30001”表明此Service开启了NodePort格式的外网访问模式。比如,在Kubernetes集群外,客户端的浏览器可以通过30001端口访问myweb(对应8080的虚端口)。运行kubectl create命令进行创建:

运行kubectl get命令,查看已创建的Service:

至此,我们的第1个Kubernetes例子便搭建完成了,下一节将验证结果。
经过上面的流程,我们终于成功实现了Kubernetes上第1个例子的部署、搭建工作。现在一起来见证成果吧!在你的笔记本上打开浏览器,输入“http://虚拟机IP:30001/demo/”。
比如虚拟机IP为192.168.18.131(可以通过ip a命令进行查询),在浏览器里输入地址http://192.168.18.131:30001/demo/后,可以看到如图1.2所示的网页界面。

图1.2 通过浏览器访问Tomcat应用
如果无法打开这个网页界面,那么可能的原因包括:①因为防火墙的设置无法访问30001端口;②因为通过代理服务器上网,所以浏览器错把虚拟机的IP地址当作远程地址;等等。可以在虚拟机上直接运行curl 192.168.18.131:30001来验证能否访问此端口,如果还是不能访问,就肯定不是机器的问题了。
接下来尝试单击“Add…”按钮添加一条记录并提交,如图1.3所示,提交以后,数据就被写入MySQL数据库了。

图1.3 在留言板网页添加新的留言
至此,我们就完成了在Kubernetes上部署一个Web App和数据库的例子。可以看到,相对于传统的分布式应用部署方式,在Kubernetes之上仅通过一些很容易理解的配置文件和简单命令就能完成对整个集群的部署。
1.4节将对Kubernetes中的资源对象进行全面讲解,读者可以继续研究本节例子里的一些拓展内容,比如:研究Deployment、Service等配置文件的格式;熟悉kubectl的子命令;手工停止某个Service对应的容器进程,看看会发生什么;修改Deployment文件,改变副本数量并重新发布,观察结果。