k8s 挂载卷介绍(四)

时间:2022-03-29 08:48:26
kubernetes关于pod挂载卷的知识
首先要知道卷是pod资源的属性,pv,pvc是单独的资源。pod 资源的volumes属性有多种type,其中就包含有挂载pvc的类型。这也帮我理清了之间的关系。
pv一般是系统管理员做的
pvc 是一般k8s用户声明的,大概意思就是说我需要一个 什么权限的,多少存储空间的卷,然后k8s api收到这个请求就去找pv资源对象,如果两者相匹配,那么pv就和pvc绑定了。
从这里我们也想到了,pv如果是手动创建的话,那就麻烦大了。几个,几十个还好说,上万个,管理员该如何创建这么多。所以才有了动态创建pv的需求。这就引出另外一个资源 storageClass ,这个资源声明后端挂载什么样的存储服务,比如nfs,chef等,有了这个一般用户在定义pvc的时候,在提出存储空间和读写权限的同时声明用那个storageClass了,
如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: mysql-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: managed-nfs-storage (注意这里)
resources:
requests:
storage: 5Gi
以上便是pvc使用storageClass资源对象创建pv,pvc的绑定了
api收到对storageClass声明后,就会调用这个storageClass的生产者进行生产。
我们就会想到,在创建storageClass资源对象的时候肯定会指定生产者。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: example-nfs
provisioner: example.com/nfs 这里指定生产者,其中example.com/nfs只是一个标签,你在deploymend里定义什么这里就写什么。
mountOptions:
- vers=4.1
那么问题又来了。在k8s中生产者组件不支持nfs,所以才会安装第三方组件。第三方组件的安装就需要创建相关的rbac的角色和账户。第三方组件是用deploymend的资源托管相关组件pod的。
那么通过deploy部署的pod怎么就是provisioner了?这个我不清楚,后面学习后在总结吧。
回到正题上。
volumes 的6种
1. emptyDir
2. gitRepo
3. hostpath
4.PersistentVolumeClaim
5.configMap,secret
6. 各种云平台的存储磁盘卷如google的gce,aws的ebs,azure的azureDisk
其实4只是一个概括,nfs,chef 这些网络存储通通可以单独来使用。但我觉得实际使用中还是讲这些网络存储转化成pv,pvc
从简单开始学习
emptyDir 两种应用场景: 1. 同一个pod中,各个容器间共享文件。 2. 当程序对大数据处理时内存空间不够时临时写入文件(当然也可以使用宿主主机的内存)
例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPaht: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {} (为{}表示使用节点服务器的文件系统)
- name: html-2
emptyDir:
medium: Memory (使用节点服务器的内存)
以上就是emptyDir的用法。gitRepo其实是依赖于emptyDir的,你可以把它理解成,声明了一个emptyDir然后在把gitrepo下载填充到emptyDir
例子:
apiVersion: v1
kind: Pod
metadata:
name: gitrepo-volume-pod
spec:
containers:
- image: nginx: alpine
name: web-nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
repository: https://github.com/luksa/kubia-website-example.git
revision: master
directory: . (这个.很重要,表示在当前emptyDir目录下不然就会创建一个kubia-website-example目录)
以上就是gitRepo的用法,其中有个问题就是pod中的gitrepo并不会及时更新。如果想要及时更新需要用到sidecar ,加入到pod中,在Docker Hub搜索 “git ryc”获取相关镜像。
还有一个情况不得不使用sidecar container 。gitrepo 不支持连接私有库,也就是不能是ssh密钥连接,也不可以有用户名和密码。这时候就需要使用支持验证连接的sidecar container来实现了。具体怎么使用,用到的时候在研究吧.
至此gitRepo卷类型算是简单介绍了,下面学习hostpath
大多数时候pod应该忽略他们的主机节点,因此他们也不需要访问节点文件系统上的文件。但是某些系统级别的pod(通常由DaemonSet管理)确实需要读取节点的文件或使用节点文件系统来访问节设备,这时候就需要用到hostPath
hostPath 算是第一个持久化存储卷了,但是这个很少用到,因为这个卷只能在某一单一节点上,pod重启后很可能在另外一个节点上。当然可以使用NodeSelector但这样看起来也不高明。所以建议使用网络存储。hostPath过
接下来是网络存储和云平台提供的存储磁盘卷。这两种在用到的时候找相关的属性进行配置即可。也没什么要注意的,实际应用场景用到最多的持久存储是pv,pvc,storageClass
configmap
kubectl create configmap fortune-config --from-literal=sleep-interval=25
一般configmap包含多个映射条目所以创建时可以多次使用--from-literal参数
kubectl create conigmap fortune-config --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
kubectl get configmap fortune-config -o yaml
configmap同样可以存储粗力度的配置数据,比如完整的配置文件。kubectl create configmap 命令支持从硬盘上读取文件,并将文件内容单独存储为ConfigMap中的条目:
kubectl create configmap my-config --from-file=config-file.conf
运行此命令,kubectl 会在当前目录下找config-file.conf文件,并将文件内容存储在configmap中以config-file.conf 为键名的条目下。当然也可以手动指定键名
kubectl create configmap my-config --from-file=customkey=config-file.conf
kubectl create configmap my-config --from-file=/path/to/dir
这时候,kubectl会为文件中每个文件单独创建条目,仅限文件名可作为合法ConfigMap键名的文件。
当然也可以将上面的参数混合使用
configmap设置完成了,如何将映射的值传递给pod的容器?三种方法
一,设置环境变量,例子如下:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
很好奇当pod 容器中引用不存在的configmap会发生什么?
pod会尝试运行所有容器,只有这个容器启动失败,其他正常(假如有多个容器),当你事后创建处这个configmap,无需重启pod,容器就会成功。当然也可以引用为可选的,设置configMapKeyRef.optional: true即可,这样即使ConfigMap不存在,容器也能正常启动。
如果configmap中条目很多,用env属性列出麻烦且容易出错。那么有没有办法一次导入呢。用envFrom, 例如configmap中有三个条目FOO、BAR和FOO-BAR
spec:
containers:
- image: some-image
envFrom:
- prefix: CONFIG_ 所有环境变量均包含前缀CONFIG_ ,不设置就将引用configmap中的键名
configMapRef:
name: my-config-map
如此,容器中多出两个变量 CONFIG_FOO 、CONFIG_BAR
为什么是两个,因为另外一个FOO-BAR包含破折号,不是一个合法的环境变量名称。被忽略了,所以我们应该注意创建configmap时 不要使用-
上面我们学习了如何将configmap中的条目以变量的形式传入到容器中
那么如何将configmap中的条目作为容器运行的参数args呢?例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
args: ["$(INTERVAL)"]
环境变量和参数都适合于变量值较短的场景。configmap是可以包含完整配置文件内容的,当你想要将其暴露给容器时,可以使用上一章中提到的卷的类型。configmap卷会将ConfigMap中的每个条目均暴露成一个文件。运行在容器的进程通过读取文件内容获得对应的条目值。
configmap-files为一个目录
kubectl create configmap fortune-config --from-file=configmap-files
例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune-configmap-volume
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
...
- name: config
mountPaht: /etc/nginx/config.d
readOnly: true
....
volumes:
...
- name: config
configMap:
name: fortune-config
...
一种特殊情况,当你只想暴露configmap-files目录下的某一个配置文件,该如何做:
volumes:
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: gzip.conf
这样配置后,挂载fortune-config 卷后,就只有my-nginx-config.conf 并且挂载后的名称为gzip.conf
另一种特殊情况,我们通过上述挂载configmap卷后会发现,被挂载的目录之前的文件都被隐藏掉了。那么如果你需求不想隐藏之前的文件,该如何做:
spec:
containers:
- image: some/image
volumeMounts:
- name: myvolume
mountPath: /etc/someconfig.conf 挂载到指定的某一个文件,而不是某个文件夹
subPath: myconfig.conf 挂载指定的条目,而不是完整的卷
为configMap卷中的文件设置权限
volumes:
- name: config
configmap:
name: fortune-config
defaultMode: "6600"
更新应用配置且不重启应用程序
使用环境变量或命令行参数作为配置源的弊端在于无法在进程运行时更新配置。将ConfigMap暴露为卷可以达到配置热更新的效果,无需重新创建pod或重启容器。
ConfigMap被更新后,卷中引用它的文件也会相应更新,进程发现文件被改变后进行重载。kubernetes同样支持文件更新后手动通知容器。但要注意的是,更新configmap之后对应文件的更新会相当耗时。
我们使用kubectl edit configmap fortune-config 来更改某一个值。
然后执行
kubectl exec fortune-configmap-volume -c web-server -- cat /etc/nginx/config.d/my-nginx-config.conf
查看文件是不是被修改了,如果没有稍等一会再查看。在确认更改后,我们来手动通知容器进行重载。
kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
你可能会疑惑 Kubernetes更新完configmap卷中的所有文件之前(所有的文件被更新而不是一部分),应用是否会监听到文件编号并主动进行重载。答案是不会,会在所有的文件更新后,一次性更新到pod容器中。kubernetes通过富豪链接做到这一点的。
kubectl exec -ti fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/config.d
..4989_09_04_15_06.028485
..data -> ..4989_09_04_15_06.028485
my-nginx-config.conf -> ..data/my-nginx-config.conf
sleep-interval -> ..data/sleep-interval
可以看到,被挂载configMap卷中的文件是..data文件夹中文件的符号链接,而..data又是连接到 ..4989的符号链接,每当ConfigMap被更新后,Kubernetes 会创建一个这样的文件夹,卸任所有文件并重新将符号..data链接至新文件夹,通过这种方式可以一次性修改所有文件。
如果挂载configmap中的某一个文件,而不是文件夹,configmap更新之后对应的文件不会被更新。如果现在你需要挂载单个文件且在修改Configmap后想自动更新,可以将该卷挂载到其他文件夹,然后做一个软链接。
至于如何实现应用重载配置 ,需要应用自己实现了。
configmap都是以名文存储的,有些信息比较敏感,就需要用秘文存储了。
这就需要使用kubernetes 的secret资源了。
首先有一个情况我们需要了解
使用kubectl describe pod pod-id 查看任何一个pod你都会发现默认有挂载一个secret卷。
Volumes:
default-token-ps7ff:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-ps7ff
Optional: false
这个卷里的内容我们可以使用kubectl describe secrets查看
# kubectl describe secrets default-token-ps7ff
Name: default-token-ps7ff
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: default
kubernetes.io/service-account.uid: 6efa7f7c-6a61-11e9-bfdb-0a382f97318e Type: kubernetes.io/service-account-token Data
====
ca.crt: 1359 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtp...
可以看出这个Secret包含是哪个条目ca.crt 、namespace与token
包含了从pod内部安全访问Kubernetes API服务器所需的全部信息。尽管我们希望做到应用对kubernetes无感知,但是直连Kubernetes没有其他方法,你只能使用secret卷提供的文件来访问kubernetes API。
使用kubectl describe pod命令显示secret卷北挂载的位置:
mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-ps7ff (ro)
注意: default-token Secret默认会被挂载至每个容器。可以通过设置pod定义中的automountServiceAccountToken字段为false,或者设置pod使用的服务账户中的相同字段为false来关闭这种默认行为。
查看容器中挂载了哪些条目
# kubectl exec nginx-dnm9n -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token
创建Secret
openssl genrsa -out https.key 2048
openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.kubia-example.com
# kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
secret/fortune-https created
也可以使用 --from-file=fortune-https 囊括这个文件夹中的文件
# kubectl get secret fortune-https -o yaml
# kubectl describe secret fortune-https
对比configmap与secret
secret与configMap有比较大的区别,这也是为何kubernetes开发者们在支持了Secret一段时间之后会选择创建ConfigMap。创建的Secret的YAML格式定义显示
# kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
foo: YmFyCg==
https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lKQU96Y00rNzI3RWJHTUEwR0NTcUdTSWIzRFFFQkN3VUFNQ0F4SGpBY0JnTlYKQkF
https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBeFYvUVJiazJiRm8zRmdZdWpaTWxPVGg3MUxpY3AyUS9pL2pib2E1SExlUlpSTDBi
kind: Secret
. . .
将其与CoinfigMap的Yaml格式定义做对比
apiVersion: v1
data:
bar: baz
foo: bar
one: two
kind: ConfigMap
注意到Secret条目的内容会被以Base64格式编译,而ConfigMap直接以纯文本展示。这种区别导致在处理YAML和JSON格式的Secret时会稍许有些麻烦,需要在设置和读取相关条目时对内容进行编解码。
这个具体使用中是这样的,
比如你现在想把一个配置文件加入到Secret中,那么你首先将配置文件中的内容通过BASE64进行编码后才能作为条目。
当然你会问难道kubernetes不提供base64编码?提供,只能对字符串,不能接受文件。如下:
kund: Secret
apiVersion: v1
stringData:
foo: plain text
data:
https.cert: lksjkaldjldasjladgjsjl...
https.key: lsiodjsdlfjahcdo...
创建后使用kubectl get secret -o yaml会看到stringData字段中的所有条目会被Base64编码后展示在data字段下。所以stringData字段是只写不可读的。
如何在pod中使用Secret
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: luksa/fortune:env
name: html-generator
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
volumeMounts:
- name: html
mountPaht: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: html
emptyDir: {}
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: https.conf
- name: certs
secret:
secretname: fortune-https
简单点的实例:

apiVersion:  v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: certs
secret:
secretName: fortune-https
当然也可以将secret条目暴露为环境变量。但不建议这么做,应用通常会在错误报告时转储环境变量,或者是启动时打印在应用日志中,无意中就暴露Secret信息。另外,子进程会继承父进程的所有环境变量,如果是通过第三方二进制程序启动应用,你并不知道它使用敏感数据做了什么。所以不建议用环境变量,建议使用secret卷的形式挂载到pod.
env:
- name: FOO_SECRET
valueFrom:
secretKeyRef:
name: fortune-https
key: foo
学会了secret,接下来就有一个比较常用的secret实际应用,dockerhub
kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=my.email@provider.com
使用:
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
- name: mydockerhubsecret
containers:
- image: username/private:tag
name: main
假设系统通常运行大量Pod,你可能会好奇是否需要为每个Pod都添加相同的镜像拉取Secret.并不是,可以通过添加Secret至ServiceAccount 使所有pod都能自动添加上镜像拉取的Secret.