K8s中的数据存储实现机制、机密信息的配置与存储
K8s中的数据存储实现机制一、k8s中可用的数据卷我们都知道容器和Pod是存在周期是短暂的。也就是说它们的生命周期可能很短它们会被频繁地销毁和创建。在容器销毁时保存在容器内部文件系统中的数据都会被清除。为了持久化保存在容器的数据我们可以使用Kubernetes VolumeVolume的生命周期独立于容器Pod中的容器可能被销毁和重建但Volume会被保留。其实Kubernetes Volume是一个目录这一点与Docker Volume类似当Volume被mount 到 PodPod 中的所有容器都可以访问这个 Volume。Kubernetes Volume 也支持多种类型的数据存储包括emptyDir、hostPath、GCE Persistent Disk、NFS、Ceph等。Volume提供了对各种存储的抽象容器在使用Volume读写数据的时候不需要关心数据到底是存放在本地节点的文件系统中还是存储在云硬盘上。对它来说所有类型的 Volume都只是一个目录。二、emptyDir类型存储emptyDir是最基础的Volume类型需要注意emptyDir Volume对于容器来说是持久的对于Pod则不是。当Pod从节点删除时Volume的内容也会被删除。但如果只是容器被销毁而Pod还在则Volume不受影响。也就是说emptyDir Volume的生命周期与Pod一致。[rootmaster data]# more pods.yml apiVersion: v1 kind: Pod metadata: name: share-pods spec: containers: - name: pods1 image: busybox volumeMounts: - name: share-vm mountPath: /pods1 args: - /bin/sh - -c - echo hello k8s pods /pods1/hello ; sleep 3000 - name: pods2 image: busybox volumeMounts: - name: share-vm mountPath: /pods2 args: - /bin/sh - -c - cat /pods2/hello ; sleep 3000 volumes: - name: share-vm emptyDir: {}这里yml文件模拟了一个pod中有两个容器的场景Pod有两个容器分别是pods1和pods2它们共享一个Volume。Pods1在Volume中写数据pods2则是从Volume读取数据。执行如下命令创建Pod[rootmaster data]# kubectl apply -f pods.yml [rootmaster data]# kubectl logs share-pods pods1注意查看pod运行日志的方法一个pod里面运行多个容器的时候查看容器日志需要指定pod名称及容器名。最后再通过docker inspect查看容器的详细配置信息可以发现两个容器都mount了同一个目录[rootnode2 share-vm]# kubectl get pod -o wide [rootnode2 share-vm]# docker ps [rootnode2 share-vm]# docker inspect b2324aa1bc18 [rootnode2 share-vm]# docker inspect daff68dfdb49这里/var/lib/kubelet/pods/111722b9-df23-468d-893f-f420ce7dc82a/volumes/kubernetes.io~empty-dir/share-vm就是emptyDir在Host上的真正路径。emptyDir是Host上创建的临时目录其优点是能够方便地为Pod中的容器提供共享存储不需要额外的配置。但它不具备持久性如果 Pod 不存在了emptyDir 也就没有了。根据这个特性emptyDir特别适合Pod中的容器需要临时共享存储空间的场景。三、hostpath存储类型hostPath Volume 的作用是将 Docker Host文件系统中已经存在的目录mount到Pod的容器。比如kube-apiserver和kube-controller-manager就是这样的应用执行如下命令[rootmaster data]# kubectl edit --namespacekube-system pod kube-apiserver-master查看kube-apiserver Pod 的配置可以发现它挂载了三个hostPath volume分别是k8s、certs和pki分别对应node目录/etc/kubernetes、/etc/ssl/certs和/etc/pki。如果Pod被销毁了hostPath对应的目录还会被保留从这点看hostPath的持久性比emptyDir强。但是如果node节点崩溃hostPath也就没法访问了。hostPath volume在实际中使用不多因为它们增加了Pod与节点的耦合性限制了Pod的使用。下面举个例子把某个node节点上系统的/tmp目录挂载到容器中编写一个yml文件内容如下[rootmaster data]# cat hostpath.yml apiVersion: v1 kind: Pod metadata: name: myhostpath spec: containers: - name: nginx image: nginx volumeMounts: - name: hostpath mountPath: /usr/share/nginx/html volumes: - name: hostpath hostPath: path: /tmp type: Directory这个pod是启动nginx容器然后把主机的/tmp目录挂载到nginx容器的网站根目录下这里共享的是/tmp目录如果是挂载其他的目录的话要确保每个节点上都有这个目录接着执行如下操作[rootmaster data]# kubectl apply -f hostpath.yml [rootmaster data]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES myhostpath 1/1 Running 0 7m48s 10.244.1.95 node1 none none找到此pod所在的节点这里是node1接着在node1上执行如下操作[rootnode1 data]# echo test hostpath /tmp/index.html [rootmaster data]# curl 10.244.1.95 test hostpath四、使用NFS作为数据存储NFS也可以作为k8s的数据存储但先需要部署一台NFS服务需要启动这个服务,然后还需要在每个node节点安装nfs-utils去支持nfs文件类型否则会报错在每个node节点执行如下操作[rootnode1 data]# yum install nfs-utils -y接着编写一个yml文件内容如下apiVersion: v1 kind: Pod metadata: name: nfs-pod spec: containers: - name: nginx image: nginx volumeMounts: - name: nfsdata mountPath: /usr/share/nginx/html volumes: - name: nfsdata nfs: path: /nfs server: 172.16.213.230然后执行这个yml文件[rootmaster data]# kubectl apply -f nfs-pod.yml最后执行如下操作测试数据存储[rootmaster data]# kubectl get pods -o wide [rootnfsserver ~]# echo nfs test /nfs/index.html [rootmaster data]# curl 10.244.1.96五、使用NFS PV存储数据PV是外部存储系统中的一块存储空间由管理员创建和维护。与Volume一样PV具有持久性生命周期独立于 Pod。PVC是对PV的申请。PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时用户可以创建一个 PVC指明存储资源的容量大小和访问模式比如只读等信息Kubernetes会查找并提供满足条件的 PV。有了PVC用户只需要告诉Kubernetes需要什么样的存储资源而不必关心真正的空间从哪里分配如何访问等底层细节信息。这些底层信息交给管理员来处理只有管理员才应该关心创建 PersistentVolume的细节信息。Kubernetes支持多种类型的PV比如 AWS EBS、Ceph、NFS等。1、部署一个NFS服务同样在使用NFS共享之前需要先搭建一台NFS服务器并设置一个共享目录例如/nfs此目录要修改下目录属主否则可能没有写入权限执行如下命令[rootnfsserver data]# chown -R nobody:nobody /nfs最后还要在K8s集群每个节点安装nfs-utils否则识别不到nfs文件系统。2、创建与管理PV先创建一个PV配置文件nfs-pv1.yml文件内容如下[rootmaster data]# more nfs-pv1.yml apiVersion: v1 kind: PersistentVolume metadata: name: mypv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle storageClassName: nfs1 nfs: path: /nfs/pv1 server: 172.16.213.230含义介绍如下 capacity 指定PV的容量为1G。 accessModes指定访问模式为ReadWriteOnce其他支持的访问模式有 ReadWriteOnce 表示PV能以read-write模式mount到单个节点。 ReadOnlyMany 表示PV能以read-only模式mount到多个节点。 ReadWriteMany 表示PV能以read-write模式mount到多个节点。 persistentVolumeReclaimPolicy 指定PV的回收策略为Recycle此外pv还支持的策略有 Retain表示需要管理员手工回收。 Recycle表示清除PV中的数据相当于自动执行rm -rf /thevolume/*指令。 Delete 表示删除Storage Provider上的对应存储资源例如AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume等。 storageClassName 指定PV的class为nfs1。相当于为PV设置了一个分类PVC可以通过引用这个分类名申请相应的PV资源。 path指定PV在NFS服务器上对应的目录。3、创建与使用pvc首先创建一个名为mypvc1的PVC配置文件nfs-pvc1.yml内容如下[rootmaster data]# more nfs-pvc1.yml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mypvc1 spec: accessModes: - ReadWriteMany storageClassName: nfs1 resources: requests: storage: 1Gi volumeName: mypv1然后创建pv并查看状态执行如下命令[rootmaster data]#kubectl apply -f nfs-pvc1.yml [rootmaster data]#kubectl get pvc从 kubectl get pvc 和 kubectl get pv 的输出可以看到 mypvc1 已经 Bound 到 mypv1申请成功。接下来就可以在 Pod 中使用存储了这里创建一个Deployment配置文件deploy-pod2-nginx.yml内容如下[rootmaster data]# more deploy-pod2-nginx.yml apiVersion: apps/v1 kind: Deployment metadata: name: nginxserver-deployment spec: replicas: 3 selector: matchLabels: app: nginxserver template: metadata: labels: app: nginxserver spec: containers: - name: nginx-pod image: nginx volumeMounts: - name: mynfsdata mountPath: /usr/share/nginx/html volumes: - name: mynfsdata persistentVolumeClaim: claimName: mypvc1注意这个Deployment中pod与使用普通 Volume 的格式类似在 volumes 中通过 persistentVolumeClaim指定使用 mypvc1 申请的 Volume。要实现对刚刚创建pod的访问还需要创建一个servicenfs-service-nginx.yml 内容如下[rootmaster data]# more nfs-service-nginx.yml apiVersion: v1 kind: Service metadata: name: service-nginx spec: type: NodePort selector: app: nginxserver ports: - protocol: TCP nodePort: 31000 port: 80 targetPort: 80最后看看如何在pod中使用pvc执行如下命令[rootmaster data]# kubectl apply -f deploy-pod2-nginx.yml [rootmaster data]# kubectl apply -f nfs-service-nginx.yml [rootmaster data]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 none 443/TCP 18d service-nginx NodePort 10.106.0.151 none 80:31000/TCP 55m最后可以通过浏览器访问http://master:31000/访问创建的pod服务也可以通过curl ClusterIP:80访问pod服务。4、如何回收pv/pvc当 PV 不再需要时可通过删除 PVC 回收。执行如下命令删除不需要的pvc[rootmaster data]# kubectl delete pvc mypvc1 [rootmaster data]# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES recycler-for-mypv1 0/1 ContainerCreating 0 3s none node1 none none [rootmaster data]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE mypv1 1Gi RWX Recycle Available nfs1 3h58m当 PVC mypvc1 被删除后Kubernetes启动了一个新 Pod recycler-for-mypv1这个 Pod 的作用就是清除 PV mypv1 的数据。此时 mypv1 的状态为 Released表示已经解除了与 mypvc1 的 Bound正在清除数据不过此时还不可用。当数据清除完毕mypv1 的状态重新变为 Available此时则可以被新的 PVC 申请。这里因为PV的回收策略设置的是Recycle所以数据会被清除但这可能不是我们想要的结果。如果希望保留数据可以将策略设置为Retain。演示设置为Retain的效果[rootmaster data]# kubectl delete pvc mypvc2 [rootmaster data]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE mypv2 2Gi RWX Retain Released default/mypvc2 nfs2 76m使用retain虽然删除pod后的数据得到了保留但其 PV 状态会一直处于 Released不能被其它PVC申请。为了重新使用存储资源可以删除并重新创建PV。删除操作只是删除了PV对象存储空间中的数据并不会被删除。执行如下操作过程[rootmaster data]# kubectl delete pv mypv2 [rootmaster data]# kubectl apply -f nfs-pv2.yml [rootmaster data]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE mypv2 2Gi RWX Retain Available nfs2 6s从输出可知新建的mypv2状态为Available已经可以被PVC申请并使用了。k8s应用中机密信息的配置与存储一、k8s资源Secret在实际的应用中可能需要存储一些机密信息例如密码、token或者秘钥等这类数据虽然可以存放在Pod或者镜像中但安全性太差为了减少机密信息暴露的风险Kubernetes提供了Secret这个解决方案。Secret是用来保存敏感数据的k8s资源它通过密文的方式存储数据用户可以创建自己的secret系统也会有自己的secret。Pod使用Secret的方式是以Volume的形式mount到Pod容器可通过文件的方式使用Secret中的敏感数据。二、创建Secret的方式创建Secret常用的方式有两种第一种通过 –from-literal实现例如[rootmaster secret]# kubectl create secret generic mysecret 1--from-literalusernameadmin --from-literalpassword123456每个–from-literal 对应一个信息条目。第二种通过 YAML 配置文件例如apiVersion: v1 kind: Secret metadata: name: mysecret data: username: YWRtaW4K password: MTIzNDU2NzgK注意文件中的敏感数据必须是通过base64编码后的结果执行下面命令进行编码[rootmaster secret]# echo admin | base64 YWRtaW4K [rootmaster secret]# echo 12345678 | base64 MTIzNDU2NzgK可见上面的username是adminpassword是12345678.要查看secret可执行如下命令[rootmaster secret]# kubectl get secret mysecret [rootmaster secret]# kubectl describe secret mysecret如果还想查看具体的Value可以用如下命令[rootmaster secret]# kubectl edit secret mysecret查到对应的敏感信息后可通过base64将Value反编码执行如下操作[rootmaster secret]# echo YWRtaW4K | base64 --decode admin [rootmaster secret]# echo MTIzNDU2NzgK | base64 --decode 12345678三、在pod中使用secret通过volume方式使用Secret是最常见的做法此外volume方式支持动态更新也就是当Secret更新后容器中的数据也会自动更新。看下面这个例子apiVersion: v1 kind: Pod metadata: name: pod-test spec: containers: - name: app-pod image: busybox args: - /bin/sh - -c - sleep 10; touch /tmp/check; sleep 30000 volumeMounts: - name: secrets mountPath: /etc/secret readOnly: true volumes: - name: secrets secret: secretName: mysecret这个例子中定义了volume为secrets来源为mysecret。然后将secrets mount到容器路径/etc/secret下并指定读写权限为readOnly。下面的操作是创建Pod并在容器中读取Secret命令如下[rootmaster secret]# kubectl apply -f secret-pod.yml [rootmaster secret]# kubectl exec -it pod-test sh / # ls /etc/secret password username / # cat /etc/foo/username admin/ # / # cat /etc/foo/password 12345678/ #可以看到K8s会在指定的路径/etc/secret下为每条敏感数据创建一个文件文件名就是数据条目的Key这里是/etc/secret/username和/etc/secret/passwordValue则以明文存放在文件中。最后我们修改password更新为xyz123base64编码为eHl6MTIzCg看看新的password会不会自动同步到容器执行如下命令[rootmaster secret]# kubectl apply -f secret.yml过一段时间之后执行如下命令检查[rootmaster secret]# kubectl exec -it pod-test sh / # ls /etc/secret password username / # cat /etc/secret/password xyz123/ #四、ConfigMap的使用与配置Secret可以为Pod提供密码、Token、私钥等敏感数据而对于一些非敏感数据比如应用的配置信息则可以使用ConfigMap。ConfigMap的创建和使用方式与Secret非常类似主要的不同是ConfigMap数据以明文的形式存放。ConfigMap常见的创建方式有两种分别是1、通过–from-literal实现例如[rootmaster secret]# kubectl create configmap myconfigmap1 --from-literalconfig1abc --from-literalconfig2def每个 --from-literal 对应一个信息条目。2、通过 YAML 配置文件实现看下面这个yml文件apiVersion: v1 kind: ConfigMap metadata: name: configmap-test data: config1: ABC123 config2: XXYYY然后创建这个configmap,执行如下命令:[rootmaster secret]# kubectl apply -f configmap.yml [rootmaster secret]# kubectl describe configmaps configmap-test可以发现,文件中的数据都是直接以明文存储的。与Secret一样Pod也可以通过Volume的方式使用Secret。看下面这个例子:apiVersion: v1 kind: Pod metadata: name: configmap-test spec: containers: - name: app-pod image: busybox args: - /bin/sh - -c - sleep 10; touch /tmp/check; sleep 30000 volumeMounts: - name: myconfigmap mountPath: /etc/config readOnly: true volumes: - name: myconfigmap configMap: name: configmap-test大多数情况下配置信息都以文件形式提供所以在创建ConfigMap时可以采用--from-file或YAML方式读取ConfigMap时通常采用Volume方式。