飞道的博客

k8s之部署有状态应用

425人阅读  评论(0)

写在前面

本文一起看下k8s对于有状态应用部署提供的解决方案。

1:有状态应用和无状态应用

如果是一个应用每次重启时依赖环境都能和第一次启动时的完全一致,则就可以称这类应用是无状态应用用,反之,就是有状态应用,如下:

支付系统(无状态应用):
    重启时只要其依赖的环境正常,则就可以正常启动对外提供服务,如其可能依赖MySQL,只要MySQL还是正常运行的,该服务就正常
MySQL(有状态应用):
    运行期间,用户创建了user表,并插入了一些数据,如果重启时,user表被删除或者是表中数据被清除则MySQL将不能正常对外提供服务。

其实,状态就是数据了,这样看来,我们只需要结合Deployment和PersistentVolume,就可以解决有状态应用的部署问题了,即只要解决了应用的数据持久化问题就行了,但k8s的眼光则显得更加的高瞻远瞩,它认为应用的状态不仅仅是数据持久化,还应该包括启动顺序(服务之间存在依赖关系),网络标识(外部通过唯一标识访问POD)等,如果将这些内容也考虑到状态的范畴的话,不管是deployment还是DaemonSet都会显得力不从心,基于此,k8s对deployment进行升级提供了StatefulSet API对象,从其名称我们也能够看出一二,定义如下:

dongyunqi@mongodaddy:~$ kubectl api-resources | egrep -w 'StatefulSet|KIND'
NAME                              SHORTNAMES   APIVERSION                             NAMESPACED   KIND
statefulsets                      sts          apps/v1                                true         StatefulSet

2:StatefulSet

sts的头信息如下:

apiVersion: apps/v1
kind: StatefulSet 
metadata:
  name: my-sts

定义如下yaml:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-sts

spec:
  serviceName: redis-svc
  replicas: 2
  selector:
    matchLabels:
      app: redis-sts

  template:
    metadata:
      labels:
        app: redis-sts
    spec:
      containers:
      - image: redis:5-alpine
        name: redis
        ports:
        - containerPort: 6379

 

可以看到其和deployment是十分相似的,除了文件头的KIND不同外,在spec中还多了一个serviceName,其它的如replicas,selector等和Deployment是一样的,这也很好理解,因为都是用来维护一组POD嘛。接下来我们apply查看:

dongyunqi@mongodaddy:~/k8s$ kubectl apply -f redis-sts.yml 
statefulset.apps/redis-sts created
dongyunqi@mongodaddy:~/k8s$ kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
redis-sts-0   1/1     Running   0          22s
redis-sts-1   1/1     Running   0          3s

注意NAME列,名称是sts的name-序号,这里序号越小则说明创建的越早从AGE列也可以看出来,这就解决了有状态应用中的启动顺序问题,比如可以让redis-sts-0作为redis的主节点,redis-sts-1作为从节点,但POD之间如何知道彼此的关系呢?也比较简单,因为NAME使用的其实就是容器的hostname,如下:

dongyunqi@mongodaddy:~/k8s$ kubectl get pod -l app=redis-sts
NAME          READY   STATUS    RESTARTS   AGE
redis-sts-0   1/1     Running   0          15m
redis-sts-1   1/1     Running   0          15m
dongyunqi@mongodaddy:~/k8s$ kubectl exec -it redis-sts-0 -- hostname
redis-sts-0
dongyunqi@mongodaddy:~/k8s$ kubectl exec -it redis-sts-1 -- hostname
redis-sts-1

怎么让POD有固定的网络标识,即k8s环境中的域名呢?这就需要用到service 的内容,利用其为POD生成固定的网络标识,定义yaml如下:

apiVersion: v1
kind: Service
metadata:
  name: redis-svc

spec:
  selector:
    app: redis-sts

  ports:
  - port: 6379
    protocol: TCP
    targetPort: 6379

注意这里的name: redis-svc要和sts中的serviceName对应,这样service就知道POD是sts的pod,就会为其生成固定的网络标识了,应用后describe查看:

dongyunqi@mongodaddy:~/k8s$ kubectl get service
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP    11d
redis-svc    ClusterIP   10.102.97.104   <none>        6379/TCP   8s
dongyunqi@mongodaddy:~/k8s$ kubectl describe service redis-svc
Name:              redis-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=redis-sts
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.102.97.104
IPs:               10.102.97.104
Port:              <unset>  6379/TCP
TargetPort:        6379/TCP
Endpoints:         10.10.4.5:6379,10.10.4.6:6379
Session Affinity:  None
Events:            <none>

 

和普通的service类似,也找到了后端的两个POD10.10.4.5:6379,10.10.4.6:6379,但POD的域名就不再是IP 地址. 名字空间,而是Pod 名. 服务名. 名字空间.svc.cluster.local,比如pod 0的域名就是redis-sts-0.redis-svc.default.svc.cluster.local,也可以简写为Pod 名.服务名,如下测试:

dongyunqi@mongodaddy:~/k8s$ kubectl exec redis-sts-0 -it -- ping redis-sts-0.redis-svc
PING redis-sts-0.redis-svc (10.10.4.5): 56 data bytes
64 bytes from 10.10.4.5: seq=0 ttl=64 time=0.141 ms
64 bytes from 10.10.4.5: seq=1 ttl=64 time=0.043 ms
64 bytes from 10.10.4.5: seq=2 ttl=64 time=0.043 ms
64 bytes from 10.10.4.5: seq=3 ttl=64 time=0.050 ms
^C
--- redis-sts-0.redis-svc ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.043/0.069/0.141 ms
dongyunqi@mongodaddy:~/k8s$ kubectl exec redis-sts-0 -it -- ping redis-sts-0.redis-svc.default.svc.cluster.local
PING redis-sts-0.redis-svc.default.svc.cluster.local (10.10.4.5): 56 data bytes
64 bytes from 10.10.4.5: seq=0 ttl=64 time=0.029 ms
64 bytes from 10.10.4.5: seq=1 ttl=64 time=0.126 ms
^C
--- redis-sts-0.redis-svc.default.svc.cluster.local ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.029/0.077/0.126 ms

 

这里我们仅仅是使用Service为POD生成固定的网络标识,但是并不需要其负载均衡的功能,因此不需要额外为service生成IP地址,以减少不必须的开销,此时需要配置clusterIP: None

然后,我们来看下service和sts之间的依赖关系,如下图:

接下来还有一个很重要的有状态应用的问题需要解决,那就是数据持久化,也一起来看下,同样也是使用persistent volume,本文以NFS 为例来说明。sts为了强调和持久化存储的一对一绑定关系,增加了volumeClaimTemplates属性来定义内嵌的PVC,yaml如下:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-pv-sts

spec:
  serviceName: redis-pv-svc

  volumeClaimTemplates:
  - metadata:
      name: redis-100m-pvc
    spec:
      storageClassName: nfs-client
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 100Mi

  replicas: 2
  selector:
    matchLabels:
      app: redis-pv-sts

  template:
    metadata:
      labels:
        app: redis-pv-sts
    spec:
      containers:
      - image: redis:5-alpine
        name: redis
        ports:
        - containerPort: 6379

        volumeMounts:
        - name: redis-100m-pvc
          mountPath: /data

 

注意这里mountPath: /data就是redis的rdb,aof 数据文件存储目录。apply后查看如下:

dongyunqi@mongodaddy:~/k8s$ kubectl get sts
NAME           READY   AGE
redis-pv-sts   2/2     13s
redis-sts      2/2     73m
dongyunqi@mongodaddy:~/k8s$ kubectl get pod -l app=redis-pv-sts
NAME             READY   STATUS    RESTARTS   AGE
redis-pv-sts-0   1/1     Running   0          45s
redis-pv-sts-1   1/1     Running   0          39s

在nfs server目录会自动创建持久化目录来存储redis的数据,如下:

dongyunqi@mongodaddy:/tmp/nfs$ ll | grep '100'
drwxrwxrwx  2       999 root       4096  1月 28 12:56 default-redis-100m-pvc-redis-pv-sts-0-pvc-de19a2a3-4b4e-400a-be87-95fd09ebb0e9/
drwxrwxrwx  2       999 root       4096  1月 28 12:56 default-redis-100m-pvc-redis-pv-sts-1-pvc-98dd5b76-904e-42b7-83ab-3a797796b57f/

PVC如下:

dongyunqi@mongodaddy:/tmp/nfs$ kubectl get pvc | egrep '100|CAPACITY'
NAME                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
redis-100m-pvc-redis-pv-sts-0   Bound    pvc-de19a2a3-4b4e-400a-be87-95fd09ebb0e9   100Mi      RWX            nfs-client     17m
redis-100m-pvc-redis-pv-sts-1   Bound    pvc-98dd5b76-904e-42b7-83ab-3a797796b57f   100Mi      RWX            nfs-client     17m

接下来测试下数据持久化功能,首先,模拟数据写入:

dongyunqi@mongodaddy:/tmp/nfs$ kubectl exec -it redis-pv-sts-0 -- redis-cli
127.0.0.1:6379> set name jack
OK
127.0.0.1:6379> get name
"jack"
127.0.0.1:6379> 

然后模拟pod 0意外退出,如下:

dongyunqi@mongodaddy:/tmp/nfs$ kubectl get pod
NAME             READY   STATUS    RESTARTS   AGE
...
redis-pv-sts-0   1/1     Running   0          20m
redis-pv-sts-1   1/1     Running   0          20m
dongyunqi@mongodaddy:/tmp/nfs$ kubectl delete pod redis-pv-sts-0
pod "redis-pv-sts-0" deleted
dongyunqi@mongodaddy:/tmp/nfs$ kubectl get pod | grep 'redis-pv-sts'
redis-pv-sts-0   1/1     Running   0          34s
redis-pv-sts-1   1/1     Running   0          21m

可以看到pod 0很快就重新创建出来了,再进入看下数据是否还在:

dongyunqi@mongodaddy:/tmp/nfs$ kubectl exec -it redis-pv-sts-0 -- redis-cli
127.0.0.1:6379> get name
"jack"

成功!

写在后面

小结

本文一起看了如何通过k8s定义的StatefulSet对象来解决有状态应用的部署问题,并重点分析了数据持久化,启动顺序,依赖关系,网络标识问题。希望本文能够帮助到你。

参考文章列表

k8s之挂载NFS到POD中

k8s之Service


转载:https://blog.csdn.net/wang0907/article/details/128768913
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场