动机:当前的我们的微服务之间是通过NFS共享附件等文件的,要逐步向S3演进。一段时间内同时存在S3读写和本地读写。因此搭建MinIO存储文件,提供S3访问,同时挂载到POD内部提供本地访问。
此为过渡方案,风险点:
- 性能损耗:csi-s3模拟块存储会有额外开销,性能远不如原生块存储,随机读写性能差
- 稳定性风险:MinIO需手动保障集群高可用(如多副本、纠删码),csi-s3插件的生产级案例较少,故障排查难度高于原生块存储CSI(如Ceph CSI)。
- 运维复杂:在kubernetes中同时维护MinIO集群和csi-s3插件
集群中部署MinIO
在Kubernetes集群中拿4个节点专门部署MinIO,每个节点提供一块本地磁盘,每块磁盘大小一致500G。
由于节点数量 4 小于默认的 EC:4+2 所需的最小节点数 6,MinIO 会自动降级为 单副本 + 校验块 策略,具体为 EC:2+2(2 数据块 + 2 校验块),以适配 4 节点集群的容错能力(最多允许 2 节点故障而不丢失数据)。
因此总容量为1000G。
使用LocalVolume做存储
创建4个LocalVolume,为节点上跑的MinIO实例提供持久存储。
四个LocalVolume只有LocalVolume名称和所在的节点不同。 LocalVolume名称只要区别于其他PV的名字就可以了,MinIO的StatefulSet通过PVC绑定PV,绑定时只匹配存储类名和PV容量。
yaml
# pv-1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: node1-local-volume # LocalVolume名称
spec:
capacity:
storage: 500Gi # PV容量
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain # 默认值为 Retain
storageClassName: minio-local-storage # 存储类名,这是自定义的
local:
path: /data/minio # 本地磁盘挂载路径
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- 192.168.64.9 # 所在的节点
PersistentVolume的persistentVolumeReclaimPolicy有2种取值:
Retain
- 当 PVC 被删除后,PV 会从
Bound(绑定)状态变为Released(已释放),但不会自动删除,也不会清除 PV 中的数据 - 适合需要手动管理数据的场景(如数据需要备份、迁移或二次使用)
- Released 状态的 PV 无法直接被新的 PVC 绑定,需手动操作(如删除 PV 重新创建,或清理数据后修改 PV 状态为
Available)
Delete
- 当 PVC 被删除后,PV 会自动被 Kubernetes 删除,同时关联的底层存储资源(如云厂商的云盘、分布式存储的卷)也会被删除,数据会被清除
- 适合临时存储、测试环境,或希望自动清理资源的场景(避免存储资源泄漏)
- 依赖存储插件支持(如 AWS EBS、GCE PD、Ceph RBD 等通常支持)
存储类 StorageClass
yaml
# minio-storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: minio-local-storage # 存储类名和上面的对应
provisioner: kubernetes.io/no-provisioner # Local PV 无动态供应器
volumeBindingMode: WaitForFirstConsumer # 等待 Pod 调度后再绑定 PV
StorageClass 的 reclaimPolicy 用于为动态创建的PV设定默认回收策略,默认值为 Delete。LocalVolume 不支持动态 provisioning,必须由管理员手动创建 PV 并指定 local 类型, persistentVolumeReclaimPolicy 由用户指定(若未指定则默认 Retain),与 StorageClass 无关
MinIO集群,四节点
yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: minio
name: minio-svc
spec:
ports:
- name: http
port: 9000
protocol: TCP
targetPort: 9000
- name: console
port: 8000
protocol: TCP
targetPort: 8000
selector:
app: minio
type: ClusterIP
---
apiVersion: v1
kind: Secret
metadata:
name: minio-secret
stringData:
password: pwd4minio@qx # 管理员密码
username: admin # 管理员账户
type: kubernetes.io/basic-auth
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: minio
spec:
podManagementPolicy: Parallel # 适用于 Pod 之间无严格依赖关系,可以快速完成创建或删除
replicas: 4
selector:
matchLabels:
app: minio
serviceName: minio-svc
template:
metadata:
labels:
app: minio
spec:
affinity:
nodeAffinity: # 节点亲和调度,打了hcm-minio=minio标签的节点上才有磁盘(local pv)
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: hcm-minio
operator: In
values:
- "minio"
podAntiAffinity: # pod反亲和调度,避免两个pod调度到一个节点上争夺同一磁盘(local pv)
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- minio
topologyKey: kubernetes.io/hostname
containers:
- args:
- server
- http://minio-{0...3}.minio-svc.default.svc.cluster.local/data
- --console-address
- :8000 # Web 控制台
- --address
- :9000 # S3 API 端口
env:
- name: MINIO_ROOT_USER # 通过环境变量指定账户名密码
valueFrom:
secretKeyRef:
key: username
name: minio-secret
- name: MINIO_ROOT_PASSWORD
valueFrom:
secretKeyRef:
key: password
name: minio-secret
image: m.daocloud.io/minio/minio:RELEASE.2024-09-22T00-33-43Z
imagePullPolicy: IfNotPresent
name: minio
ports:
- containerPort: 9000
name: http
protocol: TCP
- containerPort: 8000
name: console
protocol: TCP
resources:
limits:
cpu: 12
memory: 32Gi
requests:
cpu: 2
memory: 4Gi
volumeMounts:
- mountPath: /data
name: minio
volumeClaimTemplates:
- metadata:
name: minio
spec:
storageClassName: minio-local-storage # 存储类
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Gi # 用于和PV中的容量匹配。
PV和PVC中的容量仅用于声明、绑定。MinIO将bucket挂载挂载到POD中模拟块存储时,真正限制容量的是bucket。
部署CSI-S3
公开的 CSI-S3 插件并不多,github上受欢迎的两个的项目也不足1000 star,这里用了 github.com/yandex-clou... 。为什么?因为这个项目的中的镜像(它的examples中)有现成的。
先部署csi-s3
部署 /deploy/kubernetes 下的三个文件 csi-s3.yaml、driver.yaml和provisioner.yaml
例子
例子在 /deploy/kubernetes/examples目录下,下面稍作了改动,是自己的环境中用到的
secret & storageclass
yaml
apiVersion: v1
kind: Secret
metadata:
namespace: kube-system
name: csi-s3-secret
stringData:
accessKeyID: admin
secretAccessKey: pwd4minio@qx
endpoint: http://minio-svc.default.svc.cluster.local:9000
region: ""
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: csi-s3
provisioner: ru.yandex.s3.csi
parameters:
mounter: geesefs
# you can set mount options here, for example limit memory cache size (recommended)
# options: "--memory-limit 1000 --dir-mode 0777 --file-mode 0666"
# to use an existing bucket, specify it here:
#bucket: some-existing-bucket
csi.storage.k8s.io/provisioner-secret-name: csi-s3-secret
csi.storage.k8s.io/provisioner-secret-namespace: kube-system
csi.storage.k8s.io/controller-publish-secret-name: csi-s3-secret
csi.storage.k8s.io/controller-publish-secret-namespace: kube-system
csi.storage.k8s.io/node-stage-secret-name: csi-s3-secret
csi.storage.k8s.io/node-stage-secret-namespace: kube-system
csi.storage.k8s.io/node-publish-secret-name: csi-s3-secret
csi.storage.k8s.io/node-publish-secret-namespace: kube-system
动态提供PV(bucket)
yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-s3-pvc
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
storageClassName: csi-s3
---
apiVersion: v1
kind: Pod
metadata:
name: csi-s3-test-nginx
namespace: default
spec:
containers:
- name: csi-s3-test-nginx
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html/s3
name: webroot
volumes:
- name: webroot
persistentVolumeClaim:
claimName: csi-s3-pvc
readOnly: false