你真的搞清楚k8s的subpath了吗?
我们经常会使用subpath,当我们直接挂载一个存储或者configmap到一个目录后,目录下面原有的内容将会被覆盖。比如,我们只想替换nginx容器里面的 /etc/default.conf 这个文件,但如果通过configmap 直接挂载到 /etc 目录是不行的,会直接覆盖整个目录,导致原有的文件被覆盖了。此时,就需要借助subpath了。
通过subpath我们可以只挂载一个文件到/etc 目录下,从而避免全目录覆盖,但这里也要一个小瑕疵,就是这个文件后续的变化不会更新到容器里面了,这点需要注意。
回到主题,那么这个subpath是如何实现的? 我们先看这样一个例子。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
volumeMounts:
- mountPath: /mnt/aaa/bbb
name: volume-test
subPath: aaa/bbb
- mountPath: /mnt/ccc/ddd
name: volume-test
subPath: ccc/eee #故意写成eee
volumes:
- name: volume-test
hostPath:
path: /srv
当我们登录到容器里面后,可以看到 /mnt/aaa/bbb 和 /mnt/ccc/ddd 这两个目录,在宿主机上可以看到 /srv/aaa/bbb 和 /srv/aaa/eee目录。这个subpath 其实是我们存储的subpath,k8s 会在存储的目录下找寻这个文件或者目录,如果不存在,则会创建。我们看一下k8s代码实现。
# 存储在宿主机上路径
volumePath := hostPath
# 拼接 subpath 路径
hostPath = filepath.Join(volumePath, subPath)
# 如果路不存在则先创建这个subpath路径
if subPathExists, err := hu.PathExists(hostPath); err != nil {
klog.Errorf("Could not determine if subPath %s exists; will not attempt to change its permissions", hostPath)
} else if !subPathExists {
perm, err := hu.GetMode(volumePath)
if err != nil {
return nil, cleanupAction, err
}
if err := subpather.SafeMakeDir(subPath, volumePath, perm); err != nil {
// Don't pass detailed error back to the user because it could give information about host filesystem
klog.Errorf("failed to create subPath directory for volumeMount %q of container %q: %v", mount.Name, container.Name, err)
return nil, cleanupAction, fmt.Errorf("failed to create subPath directory for volumeMount %q of container %q", mount.Name, container.Name)
}
}
如果存储里面已经包含了subpath路径,比如 NAS存储子目录已经存在,或者configmap 某个key 已经存在,此时就不会创建这个文件或者目录,反之,如果不存在,则会首先创建这个目录。
后续只是将这个subpath路径 bind mount 到容器里面的路径,和其他存储挂载没啥差别。