惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
Simon Willison's Weblog
Simon Willison's Weblog
S
Schneier on Security
B
Blog
NISL@THU
NISL@THU
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
AWS News Blog
AWS News Blog
W
WeLiveSecurity
PCI Perspectives
PCI Perspectives
博客园 - 三生石上(FineUI控件)
Stack Overflow Blog
Stack Overflow Blog
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
P
Palo Alto Networks Blog
I
Intezer
美团技术团队
博客园 - Franky
大猫的无限游戏
大猫的无限游戏
H
Heimdal Security Blog
T
Troy Hunt's Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Forbes - Security
Forbes - Security
T
The Exploit Database - CXSecurity.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Check Point Blog
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
U
Unit 42
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
Cisco Blogs
S
SegmentFault 最新的问题
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
AI
AI
WordPress大学
WordPress大学
Help Net Security
Help Net Security
Security Archives - TechRepublic
Security Archives - TechRepublic
Microsoft Azure Blog
Microsoft Azure Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
小众软件
小众软件
Cyberwarzone
Cyberwarzone
Scott Helme
Scott Helme
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
G
GRAHAM CLULEY
阮一峰的网络日志
阮一峰的网络日志
Last Week in AI
Last Week in AI
H
Hacker News: Front Page

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 kubernetes service如何通过iptables转发 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络 理解Linux TunTap设备 快速了解iptables kafka中listener和advertised.listeners的作用 django rest_framework 分页 django后端服务、logstash和flink接入VictoriaMetrics指标监控 python中import原理 docker容器单机网络 手动实现docker容器bridge网络模型 mysql之MVCC原理 mysql之日志 使用java开发logstash的filter插件 使用python实现单例模式的三种方式 redis之缓存 redis之分片集群 redis之哨兵机制 redis之主从库同步 redis之持久化 redis之五种基本数据类型 go中如何处理error pod中将代码与运行环境分离 ddt源码分析 python装饰器的使用方法 读书笔记:如何阅读一本书 使用ddt实现unittest的参数化测试 使用kubeadm安装k8s 优化gin表单的错误提示信息 gin中validator模块的源码分析 go简单使用grpc python简单使用grpc k8s之PV、PVC和StorageClass k8s之DaemonSet k8s之Job和CronJob k8s之ConfigMap和Secret k8s之Service k8s之Pod k8s之Deployment 容器的本质 docker容器 python迭代器与生成器 python元编程 python垃圾回收机制 python上下文管理器 django rest_framework使用jwt django rest_framework异常处理 django rest_framework 自定义文档 django压缩文件下载 django rest_framework使用pytest单元测试 django restframework choice 自定义输出数据 django Filtering 使用 django viewset 和 Router 配合使用时报的错 django model的序列化 django中使用AbStractUser django.core.exceptions.ImproperlyConfigured Application labels aren't unique, duplicates users django 中 media配置 django 外键引用自身和on_delete参数 django 警告 while time zone support is active Flask使用flask_socketio实现websocket flask结合mongo tornado 文件上传 tornado 使用jwt完成用户异步认证 tornado 用户密码 bcrypt加密 tornado 结合wtforms使用表单操作 tornado finish和write区别 tornado 使用peewee-async 完成异步orm数据库操作 pyspark streaming简介 和 消费 kafka示例 使用hue创建ozzie的pyspark action workflow count的性能优化 django rest_framework Authentication django celery 结合使用 网站
k8s之StatefulSet
zhengwenfeng · 2022-08-31 · via 郑文峰的博客

# 什么是StatefulSet?

是用来创建有状态应用,可以通过过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。

什么是有状态应用?

首先是需要有数据的持久化,即使Pod被重启后,也能恢复,与重启前保持一致。然后是应用创建的所有pod有依赖关系,顺序的创建、需要运行在指定的宿主机上,并且都有对应的网络标志。

应用场景?

分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。

# 使用StatefulSet

# 创建StatefulSet

创建yaml文件定义StatefulSet对象如下,与Deployment比较,多了一个serviceName字段,这个是用来指定StatefulSet所管理的pod是用域名访问是通过该service所设定的。


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
        name: redis
        ports:
        - containerPort: 6379

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

创建StatefulSet对象

[root@k8s-worker1 zwf]# kubectl apply -f statefulset.yaml  -n zwf
statefulset.apps/redis-sts created

[root@k8s-worker1 zwf]# kubectl get sts -n zwf
NAME        READY   AGE
redis-sts   2/2     54s

1
2
3
4
5
6

查看创建的Pod会发现,命名不再是随机创建的名字,而是有了顺序号,从0开始,而k8s也会按照这个顺序一次创建。

[root@k8s-worker1 zwf]# kubectl get pods -n zwf
NAME          READY   STATUS    RESTARTS   AGE
redis-sts-0   1/1     Running   0          61s
redis-sts-1   1/1     Running   0          54s

1
2
3
4

输出pod中的hostname发现与pod的名称也保持一致,也就是应用可以自行决定依赖关系,比如该例子中可以使用0号pod作为主实例,而1号pod作为从实例。

[root@k8s-worker1 zwf]# kubectl exec -it redis-sts-0 -n zwf -- hostname
redis-sts-0

1
2

# Service配置

定义匹配上面的创建StatefulSet对象所有管理的Service,也就是标签筛选需要和pod的标签保持一致,并且这里的metadata.name也要与StatefulSet中的serviceName一样。


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

spec:
  selector:
    app: redis-sts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14

创建Service对象,我们可以看到已经将StatefulSet所创建的pod加入到端点列表了,也就是可以稳定的通过Service来访问到Pod

[root@k8s-worker1 zwf]# kubectl apply -f service_statefulset.yaml -n zwf
kservice/redis-svc created

[root@k8s-worker1 zwf]# kubectl get svc -n zwf
NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
redis-svc              ClusterIP   10.0.0.246   <none>        6379/TCP       5s

[root@k8s-worker1 zwf]# kubectl describe svc redis-svc -n zwf
Name:              redis-svc
Namespace:         zwf
Labels:            <none>
Annotations:       <none>
Selector:          app=redis-sts
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.0.0.246
IPs:               10.0.0.246
Port:              <unset>  6379/TCP
TargetPort:        6379/TCP
Endpoints:         10.222.126.51:6379,10.222.194.84:6379
Session Affinity:  None
Events:            <none>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

但是我们的Pod不是一般的的应用,是有状态的应用,需要有稳定的网络标识,所以会为每一个Pod也创建一个域名,格式是:<podName>.<serviceName>.<namesapce>.svc.cluster.local

我们进入pod中验证一下,通过ping redis-sts-1.redis-svc.zwf.svc.cluster.local发现是可以ping通的,虽然Pod的IP会变化,但是通过固定的域名就能访问到指定Pod了。

[root@k8s-worker1 zwf]# kubectl exec -it redis-sts-0 -n zwf -- ping redis-sts-1.redis-svc.zwf.svc.cluster.local
PING redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51) 56(84) bytes of data.
64 bytes from redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51): icmp_seq=1 ttl=62 time=0.964 ms
64 bytes from redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51): icmp_seq=2 ttl=62 time=0.932 ms

1
2
3
4

既然我们的pod有了稳定的网络标识,Service也就不需要再分配ClusterIP了,这个时候,只需要添加字段clusterIP: None,这样就不会再分配IP了,这样的Service称为Headless Service


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

spec:
  clusterIP: None
  selector:
    app: redis-sts

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

创建Headless Service,可以看到CLUSTER-IP为None

[root@k8s-worker1 zwf]# kubectl get svc -n zwf
NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
demoapp-nodeport-svc   NodePort    10.0.0.144   <none>        80:31999/TCP   21h
demoapp-svc            ClusterIP   10.0.0.74    <none>        80/TCP         22h
redis-svc              ClusterIP   None         <none>        6379/TCP       4s

1
2
3
4
5

Service和StatefulSet配置图如下:

# 持久化配置

接下来是给StatefulSet对象添加持久化配置。

定义StatefulSet描述如下:


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

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

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

参数:

  • volumeClaimTemplates,用来将PVC的定义嵌入到StatefulSet中的字段,是创建PVC的模板,可以让每一个Pod都能自动创建PVC
  • voulumeMounts,是用来选择上面的PVC挂载在容器的/data目录中

创建StatefulSet对象,可以看到pod已经创建起来了。

[root@k8s-worker1 zwf]# kubectl apply -f statefulset_pvc.yaml -n zwf
statefulset.apps/redis-pv-sts created

[root@k8s-worker1 zwf]# kubectl get sts -n zwf
NAME           READY   AGE
redis-pv-sts   2/2     25

[root@k8s-worker1 zwf]# kubectl get pods -n zwf
NAME             READY   STATUS    RESTARTS   AGE
redis-pv-sts-0   1/1     Running   0          63s
redis-pv-sts-1   1/1     Running   0          54s

1
2
3
4
5
6
7
8
9
10
11

我们查看pvc,会发现创建了2个对象 ,以PVC名称-pod名称命名。

[root@k8s-worker1 zwf]# kubectl get pvc -n zwf
NAME                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
redis-100m-pvc-redis-pv-sts-0   Bound    pvc-2712daa4-36b4-4a63-ac2f-7d3b31e2a887   100Mi      RWX            nfs-client     109s
redis-100m-pvc-redis-pv-sts-1   Bound    pvc-a3781354-e182-42f7-b6f6-983999603653   100Mi      RWX            nfs-client     100s

1
2
3
4

进入到pod中,使用redis-cli运行redis客户端,添加一些数据

[root@k8s-worker1 zwf]# kubectl exec -it redis-pv-sts-0 -n zwf /bin/bash
[ root@redis-pv-sts-0:/data ]$ redis-cli  
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> quit

1
2
3
4
5
6
7
8
9
10

将pod删除,然后再重新进入Pod中,查询之前创建的redis的key,还是能够查询到。

[root@k8s-worker1 zwf]# kubectl exec -it redis-pv-sts-0  -n zwf -- /bin/bash
[ root@redis-pv-sts-0:/data ]$ redis-cli  
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> get age
"18"

1
2
3
4
5
6
7
8
9

配置的关系图如下: