一般来说, 挂载存储都是把一个 PV 挂载到一个路径, 挂载后此路径下原来的文件就会不见, 只能看到挂载后的文件, 和在 Linux 下挂载磁盘是一样的. 那么现在有个需求:
需求
此服务是 java 程序, 数据库使用的是内嵌的 h2 database, 下图中的两个文件就是数据库的文件. 这两个文件是存在于根目录下的, 假设此程序数据库文件是代码写死的 (真实情况是: 数据库路径是可以更改的), 现在要部署到 kubernetes 中, 并对数据库做持久化.
使用 subpath
根据之前挂载 configmap 到单个文件的经验, 我们应该使用 subpath 来挂载, 先创建好 pvc, yml 如下
1 2 3 4 5 6 7 8 9 10 11
| apiVersion: v1 kind: PersistentVolumeClaim metadata: name: metabase-pvc spec: storageClassName: managed-nfs-storage accessModes: - ReadWriteOnce resources: requests: storage: 10Gi
|
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
| spec: replicas: 1 serviceName: "metabase" selector: matchLabels: app: metabase template: metadata: labels: app: metabase spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/iuxt/metabase:test name: metabase volumeMounts: - name: metabase-pv mountPath: /metabase.db.mv.db subPath: metabase.db.mv.db - name: metabase-pv mountPath: /metabase.db.trace.db subPath: metabase.db.trace.db volumes: - name: metabase-pv persistentVolumeClaim: claimName: metabase-pvc
|
但是这样挂载后, 容器内的 metabase.db.mv.db 和 metabase.db.trace.db 是目录, 并不是文件, 程序也自然无法启动.
subpath 原理
下面是绑定 subPath 的源码部分,我们可以看到下面的 t.Model()&os.ModeDir
部分,如果 subPath
是一个文件夹的话就会去创建这个文件夹,如果是文件的话就进行单独挂载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func doBindSubPath(mounter Interface, subpath Subpath, kubeletPid int) (hostPath string, err error) { ... t, err := os.Lstat(subpath.Path) if err != nil { return "", fmt.Errorf("lstat %s failed: %s", subpath.Path, err) } if t.Mode() & os.ModeDir > 0 { if err = os.Mkdir(bindPathTarget, 0750); err != nil && !os.IsExist(err) { return "", fmt.Errorf("error creating directory %s: %s", bindPathTarget, err) } } else { if err = ioutil.WriteFile(bindPathTarget, []byte{}, 0640); err != nil { return "", fmt.Errorf("error creating file %s: %s", bindPathTarget, err) } } ... }
|
那么我们可不可以通过手动创建好文件来实现需求, 答案是可以的. 你可以自己测试下, 但是在存储中手动创建文件也太不优雅了..
使用 init container 来自动创建空文件
init container 非常适合来做这件事, 那么完整的 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
| apiVersion: apps/v1 kind: StatefulSet metadata: labels: name: metabase-StatefulSet name: metabase spec: replicas: 1 serviceName: "metabase" selector: matchLabels: app: metabase template: metadata: labels: app: metabase spec: initContainers: - name: init image: busybox:1.28 command: ['sh', '-c', "touch /metabase/metabase.db.mv.db /metabase/metabase.db.trace.db"] volumeMounts: - name: metabase-pv mountPath: /metabase volumes: - name: metabase-pv persistentVolumeClaim: claimName: metabase-pvc containers: - image: registry.cn-hangzhou.aliyuncs.com/iuxt/metabase:test name: metabase ports: - containerPort: 3000 protocol: TCP
volumeMounts: - name: metabase-pv mountPath: /metabase.db.mv.db subPath: metabase.db.mv.db - name: metabase-pv mountPath: /metabase.db.trace.db subPath: metabase.db.trace.db - name: metabase-pv mountPath: /metabase/plugins subPath: plugins volumes: - name: metabase-pv persistentVolumeClaim: claimName: metabase-pvc --- kind: Service apiVersion: v1 metadata: labels: app: metabase name: metabase spec: ports: - port: 3000 targetPort: 3000 selector: app: metabase --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: metabase annotations: nginx.ingress.kubernetes.io/ssl-redirect: "true" spec: ingressClassName: intranet tls: - hosts: - metabase.i.com secretName: i-com rules: - host: metabase.i.com http: paths: - path: / pathType: ImplementationSpecific backend: service: name: metabase port: number: 3000
|
这样完美解决了问题, 并且不会影响到目录下的其他文件.