前面介绍了Deployment以及如何部署无状态应用。
- Kubectl 部署无状态应用
- Deployment Controller详解(上)
- Deployment Controller详解(下)
本文将继续介绍如何在k8s上部署有状态应用。
有状态和无状态服务的区别
- 无状态:
- 上面所说的deployment 认为所有的pod都是一样的
- 不用考虑顺序的要求
- 不用考虑在哪个node节点上运行
- 可以随意扩容和缩容
- 有状态
- 实例之间有差别,每个实例都有自己的独特性,元数据不同,例如etcd,zookeeper
实例之间不对等的关系,以及依靠外部存储的应用
- 实例之间有差别,每个实例都有自己的独特性,元数据不同,例如etcd,zookeeper
StatefulSet
Deployments和ReplicaSets是为无状态服务而设计,StatefulSet则是为了解决有状态服务的问题。StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序,其应用场景包括:
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
- 有序收缩,有序删除(即从N-1到0)
好像不太懂?简单来说,部署的无状态应用,每一次的更新部署、都会更新IP以及存储等等。对于业务来说,后端频繁更换ip和存储是一件非常不友好的事情,但有状态服务部署后每次更新都会拥有稳定的存储以及网络标志。
本文目标
- 如何创建 StatefulSet
- StatefulSet 如何管理其 Pod
- 如何删除 StatefulSet
- 如何扩展 StatefulSet
- 如何更新 StatefulSet 的 Pod
1. 创建 StatefulSet
首先创建一个nginx service以及两个web StatefulSet
vim statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
statefulset.yaml
接下来我们打开两个终端,其中一个用于部署,另一个观察部署情况。
第一个终端运行kubectl get pods --watch -l app=nginx
观察 StatefulSet Pod 的创建。
第二个终端执行kubectl apply -f statefulset.yaml
创建Service 和 StatefulSet。
有以下输出:
service/nginx created
statefulset.apps/web created
上面的命令创建两个 Pod,每个 Pod 运行一个 nginx Web 服务器。
插曲
当执行完 kubectl apply -f statefulset.yaml
后,发现另一个终端出现以下输出:
排查思路:
- 查看pods
2. 查看pod详细信息
执行kubectl describe pod web-1
发现Events中确实创建并且run起来了,但是pod重启失败。
造成Back-off restarting failed container nginx in pod 的原因主要是因为容器内PID为1的进程退出导致(通常用户在构建镜像执行CMD时,启动的程序,均是PID为1)。一般遇到此问题,使用者需自行排查原因,可从如下几个方向入手:
- 镜像封装是否有问题,如是否有PID为1的常驻进程
- 举例1:在容器dockerfile中,最后的CMD执行的是nginx start,执行后,nginx服务一直在前台打印日志,服务常驻,进程持续存在,则不会有问题
- 举例2:在容器dockerfile中,最后的CMD执行的是nohup nginx start &,执行后,该命令就结束了,对应的进程也就没了,容器back-off重启
- 常驻进程是否异常退出
- 服务启动后,会尝试连接一个第三方服务,重试10次连接失败,则程序终止退出,本地环境和这个三方环境通,换了个k8s之后,这个服务不通了,则会出现服务退出,进程结束,容器back-off
- 镜像使用方式不对
- 例如镜像封装的时候,并没有指定启动参数,需要使用者在使用该镜像的时候,自己添加启动命令及参数(command,args),如果不添加,容器内可能没有正确的执行命令,运行即退出
解决方式
容器没有任务的时候会处于非running状态,要保持running状态大家推荐的方法是在容器起来之后去循环访问一个系统自带的文件/dev/null,默认情况下里面是空的,tail -f就是实时打印日志的命令。
在yaml 文件中加入
command: ["/bin/bash", "-ce", "tail -f /dev/null"]
此时,执行kubectl delete statefulset web
。删掉旧服务,并重新执行kubectl apply -f statefulset.yaml
部署。发现:
kubectl get pods
-----
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 6s
web-1 1/1 Running 0 5s
回到原文。
经过小插曲的修改后,执行kubectl get statefulset web
kubectl get statefulset web
------
NAME READY AGE
web 2/2 13m
发现两个pod都已经ready了。
有序 Pod 创建过程
对于具有n个副本的StatefulSet ,当部署 Pod 时,它们是按顺序创建的,从{0…n-1}排序。从第一个终端中命令的输出将如下例所示:
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
未完,待续…