k8s入门教程十三:statefulset控制器

statefulset简介

statefulset也是一种POD控制器,那为什么要放在PV/PVC之后再简介呢?这是因为statefulset是必须也有持久化数据,每个POD所对应的PV都是不一样的。相对于Deployment所创建的POD是无状态的,那statefulset是属于有状态的,即可以保留POD的状态信息。其特点有:

  • 1、稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 2、稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
  • 3、有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
  • 4、有序收缩,有序删除(即从N-1到0)
  • 5、有序的滚动更新

k8s入门教程九:Service之服务发现 的最后有提出了一个疑问,那就是headless service这东西有什么用?这是因为statefulset的特性所决定的:在statefulset中是要求有序的,每一个pod的名称必须是固定的。当节点挂了,重建之后的标识符是不变的,每一个节点的节点名称是不能改变的。pod名称是作为pod识别的唯一标识符,必须保证其标识符的稳定并且唯一。为了实现标识符的稳定,这时候就需要一个headless service 解析直达到pod,还需要给pod配置一个唯一的名称。

Pod名称、PVC和PV关系图如下:

NFS实例演示

NFS前期准备

在一台机器上面安装NFS

1
2
3
4
5
6
7
8
9
10
11
12
yum install -y nfs-common nfs-utils rpcbind
mkdir /data/nfs{0..3}
chmod 755 /data/nfs{0..3}
chown nfsnobody /data/nfs{0..3}
cat >/etc/exports <<EOF
/data/nfs0 *(rw,no_root_squash,no_all_squash,sync)
/data/nfs1 *(rw,no_root_squash,no_all_squash,sync)
/data/nfs2 *(rw,no_root_squash,no_all_squash,sync)
/data/nfs3 *(rw,no_root_squash,no_all_squash,sync)
EOF
systemctl start rpcbind && systemctl enable rpcbind
systemctl start nfs && systemctl enable nfs
  • /data/nfs0:共享的数据目录
  • *:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
  • rw:读写的权限
  • sync:表示文件同时写入硬盘和内存
  • no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份

在另一台机器可以使用showmount测试下。

1
2
3
4
5
6
[root@master ~]# showmount -e 192.168.1.61
Export list for 192.168.1.61:
/data/nfs3 *
/data/nfs2 *
/data/nfs1 *
/data/nfs0 *

再实际挂载测试下:

1
2
3
4
mkdir /nfs{0..3}
mount -t nfs 192.168.1.61:/data/nfs0 /nfs0
date >/nfs0/index.html
umount /nfs0/

没有出现异常的话,说明NFS工作是正常的了。

或者使用docker来实现NFS:

1
docker run -d --net=host --privileged --name nfs-server katacoda/contained-nfs-server:centos7 /exports/data-0001 /exports/data-0002

这时以/etc/exports这个配置,就相当于在192.168.1.61创建了一个共享目录,分别为:/data/nfs0 /data/nfs1 /data/nfs2 /data/nfs3,这几个以下需要使用到。

statefulset实例

创建一个NFS共享的PV,以下配置申明了一个名为pv-volume0的5G空间,挂载的目录为 192.168.1.61:/data/nfs0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-volume0
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: slow
mountOptions:
- hard
- nfsvers=4.1
nfs:
path: /data/nfs0
server: 192.168.1.61

再创建一个StatefulSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 创建一个名为nginx的headless svc,端口80为内部使用,注意跟nodePort的区别
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
# 副本为3的StatefulSet,serviceName名为nginx
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
# volumeClainTemplate作用:当在使用statefulset创建pod时,会自动生成一个PVC,从而请求绑定一个PV,从而有自己专用的存储卷。
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "slow"
resources:
requests:
storage: 1Gi

由于StatefulSet是3个副本,但是看到只创建了web-0。在创建web-1时出错了。这是由于没有可用的PV了。所以一个POD是绑定一个PVC的,每个POD所对应的存储空间是不一样的。

1
2
3
4
[root@master nfs]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 3m44s 10.32.0.5 master <none> <none>
web-1 0/1 Pending 0 42s <none> <none> <none> <none>

按上面的创建PV的模板修改一下,再创建2个PV。之后就可以看到正常运行了。

1
2
3
4
5
6
7
8
9
10
11
[root@master nfs]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 4m2s 10.32.0.5 master <none> <none>
web-1 1/1 Running 0 3m56s 10.44.0.5 node1 <none> <none>
web-2 1/1 Running 0 60s 10.32.0.7 master <none> <none>
[root@master nfs]# curl 10.32.0.5
/data/nfs0 ---> web-0
[root@master nfs]# curl 10.44.0.5
/data/nfs1 ---> web-1
[root@master nfs]# curl 10.32.0.7
/data/nfs2 ---> web-2

查看一下PV以及PVC

1
2
3
4
5
6
7
8
9
10
[root@master nfs]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-volume0 5Gi RWO Recycle Bound default/www-web-0 slow 26m
pv-volume1 8Gi RWO Retain Bound default/www-web-1 slow 8m10s
pv-volume2 10Gi RWO Retain Bound default/www-web-2 slow 7m25s
[root@master nfs]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pv-volume0 5Gi RWO slow 11m
www-web-1 Bound pv-volume1 8Gi RWO slow 10m
www-web-2 Bound pv-volume2 10Gi RWO slow 8m3s

删除pvc时,回收策略为Recycle,会自动删除文件,而Retain,会保存文件。

statefulset总结

  • 匹配 Pod name ( 网络标识 ) 的模式为:$(statefulset名称)-$(序号),从零开始,比如上面的示例:web-0,web-1, web-2
  • StatefulSet 为每个 Pod 副本创建了一个 DNS 域名,这个域名的格式为: $(podname).(headless server name),也就意味着服务间是通过Pod域名来通信而非 Pod IP,因为当Pod所在Node发生故障时, Pod 会 被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化
  • StatefulSet 使用 Headless 服务来控制 Pod 的域名,这个域名的 FQDN 为:$(service name).$(namespace).svc.cluster.local
  • 根据 volumeClaimTemplates,为每个 Pod 创建一个 pvc,pvc 的命名规则匹配模式: (volumeClaimTemplates.name)-(pod_name),比如上面的 volumeMounts.name=www, Pod name=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2
  • 删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv

Statefulset的启停顺序:

  • 有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1),并且,在下一个 Pod运行之前所有之前的Pod必须都是Running和Ready状态。
  • 有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0
  • 有序扩展:当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。

mysql实例

以下是一个Mysql的实例化的StatefulSet的ymal,原链接为: https://github.com/oracle/kubernetes-website/blob/master/docs/tasks/run-application/mysql-statefulset.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
apiVersion: apps/v1beta2
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
# Generate mysql server-id from pod ordinal index.
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Add an offset to avoid reserved server-id=0 value.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# Copy appropriate conf.d files from config-map to emptyDir.
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/master.cnf /mnt/conf.d/
else
cp /mnt/config-map/slave.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map

- name: clone-mysql
image: gcr.io/google-samples/xtrabackup:1.0
command:
- bash
- "-c"
- |
set -ex
# Skip the clone if data already exists.
[[ -d /var/lib/mysql/mysql ]] && exit 0
# Skip the clone on master (ordinal index 0).
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# Clone data from previous peer.
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# Prepare the backup.
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d

containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 500m
memory: 1Gi
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
# Check we can execute queries over TCP (skip-networking is off).
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1

- name: xtrabackup
image: gcr.io/google-samples/xtrabackup:1.0
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# 从备份信息文件里读取MASTER_LOG_FILEM和MASTER_LOG_POS这两个字段的值,用来拼装集群初始化SQL
if [[ -f xtrabackup_slave_info ]]; then
# 如果xtrabackup_slave_info文件存在,说明这个备份数据来自于另一个Slave节点。这种情况下,
#XtraBackup工具在备份的时候,就已经在这个文件里自动生成了"CHANGE MASTER TO" SQL语句。
# 所以,我们只需要把这个文件重命名为change_master_to.sql.in,后面直接使用即可
mv xtrabackup_slave_info change_master_to.sql.in
# 所以,也就用不着xtrabackup_binlog_info了
rm -f xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# 如果只存在xtrabackup_binlog_inf文件,那说明备份来自于Master节点,我们就需要解析这个备份信息文件,读取所需的两个字段的值
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm xtrabackup_binlog_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
#如果只存在xtrabackup_binlog_inf文件,那说明备份来自于Master节点,我们就需要解析这个备份信息文件,读取所需的两个字段的值
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
# 使用change_master_to.sql.orig的内容,也是就是前面拼装的SQL,组成一个完整的初始化和启动Slave的SQL语句
mv change_master_to.sql.in change_master_to.sql.orig
mysql -h 127.0.0.1 <<EOF
$(<change_master_to.sql.orig),
MASTER_HOST='mysql-0.mysql',
MASTER_USER='root',
MASTER_PASSWORD='',
MASTER_CONNECT_RETRY=10;
START SLAVE;
EOF
fi
# 使用ncat监听3307端口。它的作用是,在收到传输请求的时候,直接执行"xtrabackup --backup"命令,备份MySQL的数据并发送给请求者
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi

参考链接

Kubernetes学习之路(十七)之statefulset控制器

  • 本文作者: wumingx
  • 本文链接: https://www.wumingx.com/k8s/kubernetes-statefulset.html
  • 本文主题: k8s入门教程十三:statefulset控制器
  • 版权声明: 本博客所有文章除特别声明外,转载请注明出处!如有侵权,请联系我删除。
0%