首页 > 代码库 > Kubernetes有状态应用管理——PetSet
Kubernetes有状态应用管理——PetSet
1、介绍
在Kubernetes中,大多数的Pod管理都是基于无状态、一次性的理念。例如Replication Controller,它只是简单的保证可提供服务的Pod数量。如果一个Pod被认定为不健康的,Kubernetes就会以对待牲畜的态度对待这个Pod——删掉、重建。相比于牲畜应用,PetSet(宠物应用),是由一组有状态的Pod组成,每个Pod有自己特殊且不可改变的ID,且每个Pod中都有自己独一无二、不能删除的数据。
众所周知,相比于无状态应用的管理,有状态应用的管理是非常困难的。有状态的应用需要固定的ID、有自己内部可不见的通信逻辑、特别容器出现剧烈波动等。传统意义上,对有状态应用的管理一般思路都是:固定机器、静态IP、持久化存储等。Kubernetes利用PetSet这个资源,弱化有状态Pet与具体物理设施之间的关联。一个PetSet能够保证在任意时刻,都有固定数量的Pet在运行,且每个Pet都有自己唯一的身份。一个“有身份”的Pet指的是该Pet中的Pod包含如下特性:
1) 有固定的主机名,且DNS可寻址
2) 一个有序的index
3) 静态存储
4) 应用举例:
5) 数据库应用,如Mysql、PostgreSQL,需要一个固定的ID(用于数据同步)以及外挂一块NFS Volume(持久化存储)。
6) 集群软件,如zookeeper、Etcd,需要固定的成员关系。
2、使用限制
1) 1.4新加功能,1.3及之前版本不可用;
2) DNS,要求使用1.4或1.4之后的DNS插件,1.4之前的插件只能解析Service对应的IP,无法解析Pod(HostName)对应的域名;
3) 日常运维,对于PetSet,唯一能够更改的就是replicas;
4) 需要持久化数据卷(PV),且数据卷要在创建PetSet之前创建;
5) 删除或缩容PetSet不会删除对应的持久化数据卷,这么做是处于数据安全性的考虑;
6) 只能通过手动的方式升级PetSet。
3、PetSet示例
以下示例演示了创建两个PV,创建两个PetSet的过程。在PetSet创建完成之后,验证了其hostName及直接Pod访问。
3.1 创建PV
创建PV的时候,我两个PV选择了同一个NFS server,创建也是成功的。Kubernetes在创建PV的时候并不会校验NFS server是否存在,是否能够连接成功,也不会校验storage设置的大小是否真实。所以说,PV的创建需要管理员在一开始就设置好,依赖人为的校验。
[root@k8s-master pv]# cat nfs-pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv0001 spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle nfs: path: "/data/disk1" server: 192.168.20.47 readOnly: false [root@k8s-master pv]# cat nfs-pv2.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv0002 spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle nfs: path: "/data/disk1" server: 192.168.20.47 readOnly: false [root@k8s-master pv]# kubectl create -f nfs-pv.yaml persistentvolume "pv0001" created [root@k8s-master pv]# kubectl create -f nfs-pv2.yaml persistentvolume "pv0002" created [root@k8s-master pv]# kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE pv0001 5Gi RWX Recycle Available 8s pv0002 5Gi RWX Recycle Available 5s
3.2 创建PetSet
创建PetSet的时候需要先创建一个“headless”的Service,即service显示的将ClusterIP设置为none。而用户可以通过直接访问PetSet中的Pod的IP(通过Pod的HostName解析得到),来访问后端的服务的。Kubernetes在1.4之后的dns插件之上才支持这种类型的DNS解析
[root@k8s-master pv]# cd ../petset/ [root@k8s-master petset]# cat test-petset.yaml # A headless service to create DNS records apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web # *.nginx.default.svc.cluster.local clusterIP: None selector: app: nginx --- apiVersion: apps/v1alpha1 kind: PetSet metadata: name: web spec: serviceName: "nginx" replicas: 2 template: metadata: labels: app: nginx annotations: pod.alpha.kubernetes.io/initialized: "true" spec: terminationGracePeriodSeconds: 0 containers: - name: nginx image: gcr.io/google_containers/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi [root@k8s-master petset]# kubectl create -f test-petset.yaml service "nginx" created petset "web" created [root@k8s-master petset]# kubectl get petset NAME DESIRED CURRENT AGE web 2 2 11s [root@k8s-master petset]# kubectl get pod NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 16s web-1 1/1 Running 0 12s [root@k8s-master petset]# kubectl get pv NAME CAPACITY ACCESSMODES RECLAIMPOLICY STATUS CLAIM REASON AGE pv0001 5Gi RWX Recycle Bound default/www-web-0 1m pv0002 5Gi RWX Recycle Bound default/www-web-1 1m [root@k8s-master petset]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESSMODES AGE www-web-0 Bound pv0001 5Gi RWX 22s www-web-1 Bound pv0002 5Gi RWX 22s [root@k8s-master petset]# kubectl exec -ti web-0 /bin/bash root@web-0:/# cd /usr/share/nginx/html root@web-0:/usr/share/nginx/html# ls root@web-0:/usr/share/nginx/html# touch 1.out root@web-0:/usr/share/nginx/html# exit exit [root@k8s-master petset]# ssh 192.168.20.47 #登录到NFS主机 root@192.168.20.47‘s password: Last login: Tue Mar 28 11:54:58 2017 from 10.0.251.145 [root@localhost ~]# echo "123456">> /data/disk1/1.out [root@localhost ~]# exit 登出 Connection to 192.168.20.47 closed. [root@k8s-master petset]# kubectl exec -ti web-0 /bin/bash root@web-0:/# cat /usr/share/nginx/html/1.out 123456 root@web-0:/# exit exit [root@k8s-master petset]# kubectl exec -ti web-1 /bin/bash root@web-1:/# cat /usr/share/nginx/html/1.out 123456 root@web-1:/# exit exit [root@k8s-master petset]#
3.3 验证域名解析
检查PetSet中的Pod的hostName
[root@k8s-master ~]# for i in 0 1; do kubectl exec web-$i -- sh -c ‘hostname‘; done web-0 web-1
检查通过hostName解析IP
[root@k8s-master dns14]# kubectl get svc nginx NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx None <none> 80/TCP 5h [root@k8s-master dns14]# kubectl describe svc nginx Name: nginx Namespace: default Labels: app=nginx Selector: app=nginx Type: ClusterIP IP: None Port: web 80/TCP Endpoints: 10.0.28.3:80,10.0.82.6:80 Session Affinity: None No events. [root@k8s-master ~]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE web-0 1/1 Running 0 42m 10.0.28.3 k8s-node-1 web-1 1/1 Running 0 42m 10.0.82.6 k8s-node-4 [root@k8s-master ~]# kubectl exec -i -t frontend-service-1988680557-xuysd /bin/bash #进入集群中的一个pod [root@frontend-service-1988680557-xuysd /]# nslookup web-0.nginx Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-0.nginx.default.svc.cluster.local Address: 10.0.28.3 [root@frontend-service-1988680557-xuysd /]# nslookup web-1.nginx Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-1.nginx.default.svc.cluster.local Address: 10.0.82.6 <680557-xuysd /]# nslookup web-1.nginx.default.svc.cluster.local #通常情况下,直接解析web-1.nginx即可得到对应的IP,但再一些容器内发现只有解析全部的名称“web-1.nginx.default.svc.cluster.local”才能得到IP,这个地方需要研究下区别在哪。 Server: 10.254.10.2 Address: 10.254.10.2#53 Non-authoritative answer: Name: web-1.nginx.default.svc.cluster.local Address: 10.0.82.6
3.4 验证Pet重建
#为web-0添加一个index页面,内容为它自己的HostName,注意该目录我们将其外挂到了PV之上,是一个NFS路径 [root@k8s-master dns14]# kubectl exec web-0 -- sh -c ‘echo $(hostname) > /usr/share/nginx/html/index.html‘ #在其它Pod上获取web-0的欢迎页 [root@k8s-master dns14]# kubectl exec -it web-1 -- curl web-0.nginx web-0 [root@k8s-master dns14]# kubectl exec -it web-0 -- curl web-0.nginx web-0 #查询web-0的存活时间 [root@k8s-master dns14]# kubectl get pod | grep web web-0 1/1 Running 0 5h web-1 1/1 Running 0 5h [root@k8s-master dns14]# kubectl delete pod web-0 #删除web-0 pod "web-0" deleted [root@k8s-master dns14]# kubectl get pod | grep web #可以看到,web-0被删除后几乎立即又被重建了 web-0 1/1 Running 0 3s web-1 1/1 Running 0 5h [root@k8s-master dns14]# kubectl get pod | grep web web-0 1/1 Running 0 5s web-1 1/1 Running 0 5h #查看web-0中的欢迎页是否还在 [root@k8s-master dns14]# kubectl exec -it web-0 -- curl web-0.nginx web-0 [root@k8s-master dns14]# kubectl exec -it web-1 -- curl web-0.nginx web-0
4、运维
4.1 Pet互相发现
通常,Pets需要互相知道对方的存在,在之前的示例中,我们演示了操作员“告诉”一个Pet,它有多少个同伴,但这显然是不够的。一种方法是,在Pod内部调用Kubectl的api来获取该Pet对应的PetSet中的其他成员,但并不推荐这么做。这样做的话,就会使得你的Pod可以反过来操控外部的组件。另一种方法是通过DNS解析,利用工具nslookup工具可以将nginx(上文定义的headless service)中的所有endPoint都查找出来。具体见下:
# apt-get update && apt-get install -y dnsutils ... # nslookup -type=srv nginx Server: 10.254.10.2 Address: 10.254.10.2#53 nginx.default.svc.cluster.local service = 10 50 0 web-1.nginx.default.svc.cluster.local. nginx.default.svc.cluster.local service = 10 50 0 web-0.nginx.default.svc.cluster.local. # nslookup web-1.nginx.default.svc.cluster.local Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-1.nginx.default.svc.cluster.local Address: 10.0.82.6 # nslookup web-0.nginx.default.svc.cluster.local Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-0.nginx.default.svc.cluster.local Address: 10.0.28.3
4.2 更新及扩缩容
如之前使用限制(3)中讲的,对于PetSet,Kubernetes能帮我们自动做的仅有“replicas”,即副本数量。对于扩充副本数量来说,Kubernetes每次会按照顺序,一个个的创建Pod,且在前一个没有running或ready之前,不会创建下一个;对于缩减副本数量来说,Kubernetes每次会按照顺序,一个个的删除Pod。且在前一个没有真正的shutdown之前,不会删除下一个。
需要注意的是,缩容时,虽然删除了一些Pod,但Pod对于的持久化存储PVC—PV是不会被删除的。例如,我们一开始创建了3个Pet,pod-0、pod-1、pod-2,挂载了pvc-0——pv-0、pvc-1——pv-1、pvc-2——pv-2,在缩容到2个副本的时候,最后一个pod-2会被删除,但pvc-2——pv-2则不会被删除,里面的数据还是安全的。PV的最终删除就像它一开始创建一样,是由管理员统一管理的。
4.3 镜像更新
有时需要更新镜像到新的版本,那该如何操作呢?虽然Kubernetes没有给我们提供一些自动更新整个Pet集群的功能,但通过它提供的edit和set image功能也基本上够我们用的了。更新Pet中的镜像功能示例如下:
[root@k8s-master ~]# kubectl get po web-0 --template ‘{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}‘ gcr.io/google_containers/nginx-slim:0.8 [root@k8s-master ~]# kubectl exec -i -t frontend-service-1988680557-xuysd /bin/bash #进入集群中的一个pod [root@frontend-service-1988680557-xuysd /]# nslookup web-0.nginx Server: 10.254.10.2 Address: 10.254.10.2#53 Name: web-0.nginx.default.svc.cluster.local Address: 10.0.28.3 [root@k8s-master ~]# kubectl edit petset/web #执行之后就像打开一个vi界面,修改对应的镜像名称或版本,保存退出 petset "web" edited #也可以用set image的方式进行更改 [root@k8s-master ~]# kubectl set image petset/web nginx=gcr.io/google_containers/nginx-slim:0.7 petset "web" image updated [root@k8s-master ~]# kubectl delete po web-0 #手动删除第一个Pod,PetSet会自动给我们再起一个 pod "web-0" deleted [root@k8s-master ~]# kubectl get po web-0 -o wide #查看Pod的IP已经更改了 NAME READY STATUS RESTARTS AGE IP NODE web-0 1/1 Running 0 34s 10.0.62.4 k8s-node-2 [root@k8s-master ~]# kubectl get po web-0 --template ‘{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}‘ gcr.io/google_containers/nginx-slim:0.7 #查看镜像版本信息,版本也已经更改了
之后可以一个个的将PetSet中的Pod删除,PetSet会自动按照新版本的镜像帮我们启动起来。
Kubernetes有状态应用管理——PetSet