第4课 在k8s集群运行一个极简的web APP

k8s4.png

摘要

本文是《Kubernetes权威指南:从Docker到Kubernetes实践全接触》的实践篇,把该书的第一个案例在环境上完整跑起来,补充一些比较的信息,便于学习者参考。

实践内容

本示例是一个运行在Tomcat里的Web App,如图1.1所示,JSP页面通过JDBC直接访问MySQL数据库并展示数据。出于演示和简化的目的,只要程序正确连接到了数据库,就会自动完成对应的Table的创建与初始化数据的准备工作。所以,当我们通过浏览器访问此应用时,就会显示一个表格的页面,数据则来自数据库。

JAVA WEB APP
2.1.2.1.1 MySQL容器

(1)下载docker镜像

# 本案例有一个大坑,kubeguide/mysql-master:latest对应的是mysql8.0的版本,跟java无法匹配。
# 需要采用5.7的版本。
#docker pull kubeguide/mysql-master:latest

$ docker pull mysql:5.7

(2)创建编辑RC资源文件

mysql-rc.yaml:

apiVersion: v1
kind: ReplicationController
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        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,陷入恶性循环中。

(3)发布到kubernetes集群中:

$ kubectl create -f mysql-rc.yaml

(4)查看资源创建情况

用kubectl命令查看刚刚创建的RC

$ kubectl get rc
NAME    DESIRED   CURRENT   READY   AGE
mysql   1         1         0       9s

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

$ kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE    IP           NODE        NOMINATED NODE   READINESS GATES
mysql-dcr5z   1/1     Running   0          114s   10.244.2.2   k8s-node1              

可以看到其在k8s-node1节点运行。在k8s-node1节点,我们通过docker ps指令查看正在运行的容器:

$ docker ps |grep mysql
167e968bd621        mysql                  "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes                            k8s_mysql_mysql-dcr5z_default_55c116e6-aa2c-402e-87dd-2fcbc4877010_0
51ae0f275c3c        k8s.gcr.io/pause:3.2   "/pause"                 3 minutes ago       Up 3 minutes                            k8s_POD_mysql-dcr5z_default_55c116e6-aa2c-402e-87dd-2fcbc4877010_0

(4)创建ubernetes Service

创建一个与之关联的Kubernetes Service——MySQL的定义文件,完整的内容和解释如下:

mysql-svc.yaml :

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
    - port: 3306
  selector:
    app: mysql

(5)运行kubectl命令,创建Service:

$ kubectl create -f mysql-svc.yaml

(6)查看创建的Service情况

运行kubectl命令查看刚刚创建的Service:

$  kubectl get service
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1                443/TCP    22h
mysql        ClusterIP   10.107.219.146           3306/TCP   14s

可以发现,MySQL服务被分配了一个值为10.107.219.146的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连接请求。

(7)其他Kubectl命令测试

我们可以执行下述命令查看在集群中有多少个Node:

$  kubectl get nodes
NAME         STATUS   ROLES                  AGE   VERSION
k8s-master   Ready    control-plane,master   22h   v1.20.5
k8s-node1    Ready    worker                 14h   v1.20.5
k8s-node2    Ready    worker                 22h   v1.20

通过kubectl describe node 查看某个Node的详细信息:

$  kubectl describe node k8s-master

可以用kubectl describepod xxxx来查看它的描述信息,以定位问题的成因:

$ kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
mysql-dcr5z   1/1     Running   0          7m3s

$ kubectl describe pod mysql-dcr5z

Name:         mysql-dcr5z
Namespace:    default
Priority:     0
Node:         k8s-node1/192.168.0.3
Start Time:   Tue, 17 Aug 2021 11:39:20 +0800
Labels:       app=mysql
Annotations:  
Status:       Running
...

可以通过执行kubectl scale命令来一键完成:

$  kubectl scale rc mysql --replicas=3

$ kubectl get pods -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP           NODE        NOMINATED NODE   READINESS GATES
mysql-4gnp5   1/1     Running   0          60s   10.244.2.4   k8s-node1              
mysql-7pv7f   1/1     Running   0          60s   10.244.2.3   k8s-node1              
mysql-dcr5z   1/1     Running   0          10m   10.244.2.2   k8s-node1              

可知,这3个pods都被k8s自动分配到k8s-node1节点了。

需要注意的是,删除RC并不会影响通过该RC已创建好的Pod。为了删除所有Pod,可以设置replicas的值为0,然后更新该RC。另外,kubectl提供了stop和delete命令来一次性删除RC和RC控制的全部Pod。

2.1.2.2.2 Tomcat应用

(1)下载镜像:

$ docker search kubeguide
NAME                   DESCRIPTION             STARS           OFFICIAL       AUTOMATED
kubeguide/redis-master             redis-master with "Hello World!"                33     
kubeguide/tomcat-app               Tomcat image for Chapter 1                      30     kubeguide/guestbook-php-frontend   Guestbook PHP website                           25     
kubeguide/guestbook-redis-slave    Guestbook redis slave                           23     
kubeguide/mysql-master             mysql master                                    6  [OK]
kubeguide/hadoop                                                                   5     
kubeguide/centos7-ansible          ansible with sshpass tool installed and sshk…   4     

$ docker pull kubeguide/tomcat-app:v2
 

(2)创建对应的RC文件myweb-rc.yaml:

apiVersion: v1
kind: ReplicationController
metadata:
  name: myweb
spec:
  replicas: 3
  selector:
    app: myweb
  template:
    metadata:
      labels:
        app: myweb
    spec:
      containers:
      - name: myweb
        image: kubeguide/tomcat-app:v1
        ports:
        - containerPort: 8080
        env:
        - name: MYSQL_SERVICE_HOST
          value: 'mysql'
        - name: MYSQL_SERVICE_PORT
          value: '3306'

注意:在Tomcat容器内,应用将使用环境变量MYSQL_SERVICE_HOST的值连接MySQL服务。更安全可靠的用法是使用服务的名称mysql。

(3)发布到集群中:

$ kubectl create -f myweb-rc.yaml

(4)创建对应的Service(myweb-svc.yaml):

apiVersion: v1
kind: Service
metadata:
  name: myweb
spec:
  type: NodePort
  ports:
    - port: 8080
      nodePort: 31330
  selector:
    app: myweb

type=NodePort和nodePort=31330的两个属性表明此Service开启了NodePort方式的外网访问模式。在Kubernetes集群之外,比如在本机的浏览器里,可以通过31330这个端口访问myweb(对应到8080的虚端口上)。

采用netstat -nltp|grep 31330 命令来确认在service运行前,端口是否已被占用。

(5)运行kubectl create命令进行创建:

$ kubectl create -f myweb-svc.yaml

注意:记得防火墙打开31330端口。

(6)运行kubectl命令,查看创建的Service:

$ kubectl get services -o wide
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE   SELECTOR
kubernetes   ClusterIP   10.96.0.1                443/TCP          23h   
mysql        ClusterIP   10.107.219.146           3306/TCP         12m   app=mysql
myweb        NodePort    10.109.12.156            8080:31330/TCP   36s   app=myweb

查看pods情况:

$ kubectl get pods -o wide

NAME          READY   STATUS    RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES
mysql-4gnp5   1/1     Running   0          8m5s    10.244.2.4   k8s-node1              
mysql-7pv7f   1/1     Running   0          8m5s    10.244.2.3   k8s-node1              
mysql-dcr5z   1/1     Running   0          17m     10.244.2.2   k8s-node1              
myweb-b2kfl   1/1     Running   0          3m46s   10.244.1.4   k8s-node2              
myweb-nblv5   1/1     Running   0          3m46s   10.244.1.3   k8s-node2              
myweb-rxws6   1/1     Running   0          3m46s   10.244.1.2   k8s-node2              

可知,有3个mysql分布在k8s-node1上,3个myweb分布在k8s-node2上。

2.1.2.2.3 测试

请确认对应的网络安全组已打开端口31330. 例如在windows下采用 psping 114.67.107.240:31330 确认端口已打开。

在浏览器输入http://虚拟机IP(外网):31330/demo/。比如虚拟机IP为114.67.107.240 (可以通过#ip a命令进行查询),在浏览器里输入地址http://114.67.107.240:31330/demo/后,可以看到如图1.2所示的网页界面。

问题描述:

先测试下服务是否联通。

# 测试获取web服务是否已启动。
curl http://192.168.0.3:31330/demo/



...

Error:com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.

此时表示无法跟MySql服务器链接。查看pods的打印信息:

p# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
mysql-ppzgm   1/1     Running   0          48m
myweb-87j84   1/1     Running   0          28m
myweb-drqg9   1/1     Running   0          28m
myweb-dtfdt   1/1     Running   0          28m
myweb-gcfh5   1/1     Running   0          28m
myweb-kzcxc   1/1     Running   0          28m
root@k8s-master:/data/k8s/JavaWebApp# kubectl logs myweb-87j84
30-Jul-2021 06:33:35.868 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version:        Apache Tomcat/8.0.35
30-Jul-2021 06:33:35.869 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          May 11 2016 21:57:08 UTC
...
30-Jul-2021 06:33:45.065 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 746 ms
Connecting to database...
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    
    ... 39 more
Goodbye!

说明:

从github issue:https://github.com/kubeguide/K8sDefinitiveGuide-V4-Sourcecode/issues/7 找到原因:

1.3.3 myweb-rc.yaml 镜像用的mysql-connector-java-5.1.37.jar,不兼容1.3.2 mysql-rc.yaml默认镜像 mysql 8,导致连接失败,需要将1.3.2 mysql-rc.yaml 指定mysql5.7版本。

# 需要采用5.7的版本。
docker pull mysql:5.7

# 删除已运行的RC
kubectl delete -f mysql-rc.yaml

# 删除已运行的RC
kubectl delete -f myweb-svc.yaml

参考

(1)本书示例中的Docker镜像下载地址为:
https://hub.docker.com/u/kubeguide/

(2)https://github.com/kubeguide/K8sDefinitiveGuide-V4-Sourcecode

你可能感兴趣的:(第4课 在k8s集群运行一个极简的web APP)