1.3 从一个简单的例子开始
考虑到Kubernetes提供的PHP+Redis留言板的Hello World例子对于绝大多数刚接触Kubernetes的人来说比较复杂,难以顺利上手和实践,所以在此将其替换成一个简单得多的Java Web应用例子,可以让新手快速上手和实践。
此Java Web应用的结构比较简单,是一个运行在Tomcat里的Web App,如图1.1所示,JSP页面通过JDBC直接访问MySQL数据库并展示数据。出于演示和简化的目的,只要程序正确连接到了数据库,就会自动完成对应的Table的创建与初始化数据的准备工作。所以,当我们通过浏览器访问此应用时,就会显示一个表格的页面,数据则来自数据库。
图1.1 Java Web应用的结构
此应用需要启动两个容器:Web App容器和MySQL容器,并且Web App容器需要访问MySQL容器。在Docker时代,假设我们在一个宿主机上启动了这两个容器,就需要把MySQL容器的IP地址通过环境变量注入Web App容器里;同时,需要将Web App容器的8080端口映射到宿主机的8080端口,以便在外部访问。在本章的这个例子里,我们介绍在Kubernetes时代是如何达到这个目标的。
1.3.1 环境准备
首先,安装Kubernetes和下载相关镜像,本书建议采用VirtualBox或者VMware Workstation在本机虚拟一个64位的CentOS 7虚拟机作为学习环境。虚拟机采用NAT的网络模式以便连接外网,然后使用kubeadm快速安装一个Kubernetes集群(安装步骤详见2.2节的说明)。之后就可以在这个Kubernetes集群中进行练习了。
注:本书示例中的Docker镜像下载地址为https://hub.docker.com/u/kubeguide/。
1.3.2 启动MySQL服务
首先,为MySQL服务创建一个RC定义文件mysql-rc.yaml,下面给出了该文件的完整内容和解释:
apiVersion: v1 kind: ReplicationController # 副本控制器RC metadata: name: mysql # RC的名称,全局唯一 spec: replicas: 1 # Pod副本的期待数量 selector: app: mysql # 符合目标的Pod拥有此标签 template: # 根据此模板创建Pod的副本(实例) metadata: labels: app: mysql # Pod副本拥有的标签,对应RC的Selector spec: containers: # Pod内容器的定义部分 - name: mysql # 容器的名称 image: mysql # 容器对应的Docker Image ports: - containerPort: 3306 # 容器应用监听的端口号 env: # 注入容器内的环境变量 - name: MYSQL_ROOT_PASSWORD value: "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.yaml replicationcontroller "mysql" created
接下来,用kubectl命令查看刚刚创建的RC:
# kubectl get rc NAME DESIRED CURRENT AGE mysql 1 1 1m
查看Pod的创建情况时,可以运行下面的命令:
# kubectl get pods NAME READY STATUS RESTARTS AGE mysql-c95jc 1/1 Running 0 2m
我们看到一个名为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 mysql 72ca992535b4 mysql "docker-entrypoint.sh" 12 minutes ago Up 12 minutes k8s_mysql.86dc506e_mysql-c95jc_default_511d6705-5051-11e6-a9d8-000c29ed42c1_9f89 d0b4 76c1790aad27 gcr.io/google_containers/pause-amd64:3.0 "/pause" 12 minutes ago Up 12 minutes k8s_POD.16b20365_mysql-c95jc_default_511d6705-5051-11e6-a9d8-000c29ed42c1_28520a ba
最后,创建一个与之关联的Kubernetes Service——MySQL的定义文件(文件名为mysql-svc.yaml),完整的内容和解释如下:
apiVersion: v1 kind: Service # 表明是Kubernetes Service metadata: 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.yaml service "mysql" created
运行kubectl命令查看刚刚创建的Service:
# kubectl get svc NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE mysql 169.169.253.143 <none> 3306/TCP 48s
可以发现,MySQL服务被分配了一个值为169.169.253.143的Cluster IP地址。随后,Kubernetes集群中其他新创建的Pod就可以通过Service的Cluster IP+端口号3306来连接和访问它了。
通常,Cluster IP是在Service创建后由Kubernetes系统自动分配的,其他Pod无法预先知道某个Service的Cluster IP地址,因此需要一个服务发现机制来找到这个服务。为此,最初时,Kubernetes巧妙地使用了Linux环境变量(Environment Variable)来解决这个问题,后面会详细说明其机制。现在只需知道,根据Service的唯一名称,容器可以从环境变量中获取Service对应的Cluster IP地址和端口,从而发起TCP/IP连接请求。
1.3.3 启动Tomcat应用
上面定义和启动了MySQL服务,接下来采用同样的步骤完成Tomcat应用的启动过程。首先,创建对应的RC文件myweb-rc.yaml,内容如下:
apiVersion: v1 kind: ReplicationController metadata: name: myweb spec: replicas: 2 selector: app: myweb template: metadata: labels: app: myweb spec: containers: - name: myweb image: kubeguide/tomcat-app:v1 ports: - containerPort: 8080
注意:在Tomcat容器内,应用将使用环境变量MYSQL_SERVICE_HOST的值连接MySQL服务。更安全可靠的用法是使用服务的名称mysql,详见本章Service的概念和第4章的说明。运行下面的命令,完成RC的创建和验证工作:
#kubectl create -f myweb-rc.yaml replicationcontroller "myweb" created # kubectl get pods NAME READY STATUS RESTARTS AGE mysql-c95jc 1/1 Running 0 2h myweb-g9pmm 1/1 Running 0 3s
最后,创建对应的Service。以下是完整的YAML定义文件(myweb-svc.yaml):
apiVersion: v1 kind: Service metadata: name: myweb spec: type: NodePort ports: - port: 8080 nodePort: 30001 selector: app: myweb
type=NodePort和nodePort=30001的两个属性表明此Service开启了NodePort方式的外网访问模式。在Kubernetes集群之外,比如在本机的浏览器里,可以通过30001这个端口访问myweb(对应到8080的虚端口上)。
运行kubectl create命令进行创建:
# kubectl create -f myweb-svc.yaml You have exposed your service on an external port on all nodes in your cluster. If you want to expose this service to the external internet, you may need to set up firewall rules for the service port(s) (tcp:30001) to serve traffic. See http://releases.k8s.io/release-1.3/docs/user-guide/services-firewalls.md for more details. service "myweb" created
我们看到上面有提示信息,意思是需要把30001这个端口在防火墙上打开,以便外部的访问能穿过防火墙。
运行kubectl命令,查看创建的Service:
# kubectl get services NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE mysql 169.169.253.143 <none> 3306/TCP 2m myweb 169.169.149.215 <nodes> 8080/TCP 1m kubernetes 169.169.0.1 <none> 443/TCP 10m
至此,我们的第1个Kubernetes例子便搭建完成了,我们将在下一节中验证结果。
1.3.4 通过浏览器访问网页
经过上面的几个步骤,我们终于成功实现了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上的Tomcat例子,这个例子并不是很复杂。我们也看到,相对于传统的分布式应用的部署方式,在Kubernetes之上我们仅仅通过一些很容易理解的配置文件和相关的简单命令就完成了对整个集群的部署,这让我们惊诧于Kubernetes的创新和强大。
下一节,我们将对Kubernetes中的基本概念和术语进行全面学习,在这之前,读者可以继续研究这个例子里的一些拓展内容,如下所述。
◎ 研究RC、Service等配置文件的格式。
◎ 熟悉kubectl的子命令。
◎ 手工停止某个Service对应的容器进程,然后观察有什么现象发生。
◎ 修改RC文件,改变副本数量,重新发布,观察结果。