目录
K8S中定义变量的方式
1、自定义值
2、从Pod属性中获取值
3、从ConfigMap、Secret获取值
ConfigMap
ConfigMap是一个K8S存储资源,用于存储应用程序配置文件
Pod使用configmap数据有两种方式:
- 变量注入
- 数据卷挂载
# 先定义一个ConfigMap
[root@k8s-master configMap]# vim configmap-env.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap-demo
data:
# 键值
abc: "123"
cde: "456"
# 这个是多行数据,一般这个更常用,这个可以直接将配置文件粘贴进去,知道行首位置对应上即可,一般的配置文件也是key:value形式的,这个"|",是表示多行数据的意思,没有这个"|",表示是键值
redis-config: |
port: 6379
host: 192.168.113.149
# 写入到K8S中
[root@k8s-master configMap]# kubectl apply -f configmap-env.yaml
configmap/configmap-demo created
# 查看信息可以看到其中的数据
[root@k8s-master configMap]# kubectl get configmap
NAME DATA AGE
configmap-demo 3 5s
kube-root-ca.crt 1 10d
[root@k8s-master configMap]# kubectl describe configmap configmap-demo
Name: configmap-demo
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
abc:
----
123
cde:
----
456
redis-config:
----
port: 6379
host: 192.168.113.149
Events: <none>
# 定义一个Pod去获取值
[root@k8s-master configMap]# vim configmap-pod.yaml
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: web
image: nginx
# 以下是固定格式,是从ConfigMap中获取的值,这些值是刚刚定义过的
env:
- name: ABCD
valueFrom:
configMapKeyRef:
name: configmap-demo
key: abc
- name: CDEF
valueFrom:
configMapKeyRef:
name: configmap-demo
key: cde
# 这个是数据卷挂载
volumeMounts:
# 通过name去关联volumes
- name: config
# 挂载的路径
mountPath: "/config"
# 这里设置的是只读,可以取消也可以加上
readOnly: true
# 定义数据卷的来源,他这里是从ConfigMap获取
volumes:
# 定义一个name,在volumeMounts中会使用
- name: config
configMap:
name: configmap-demo
items:
# 这个key刚刚也在创建的ConfigMap中有定义
- key: "redis-config"
# 挂载到容器的文件名
path: "redis.properties"
写好yaml后执行并查看变量情况
# 创建pod
[root@k8s-master configMap]# kubectl apply -f configmap-pod.yaml
[root@k8s-master configMap]# kubectl get pods
NAME READY STATUS RESTARTS AGE
configmap-demo-pod 1/1 Running 0 12s
# 查看pod中的内容
[root@k8s-master configMap]# kubectl exec -it configmap-demo-pod bash
# 这个是刚刚挂载的配置文件
root@configmap-demo-pod:/# cat config/redis.properties
port: 6379
host: 192.168.113.149
root@configmap-demo-pod:/# echo $ABCD
123
root@configmap-demo-pod:/# echo $CDEF
456
# 以上这些变量都是刚刚在配置ConfigMap中定义好的
Secret
Secret和ConfigMap类似,区别在于Secret主要存储敏感数据
可以存储以下三种数据类型:
- docker-registry(kubernetes.io/dockerconfigjson):存储镜像仓库认证信息
- generic(Opaque):存储密码、密钥等(yaml里数据需要经过base64编码)
- tls(kubernetes.io/tls):存储TLS证书
创建Secret
分为散布
第一步:将用户密码进行编码
echo -n 'admin' |base64
echo -n '1f2d1e2e67df' | base64
第二步、将编码后值放到Secret
apiVersion: v1
kind: Secret
metadata:
name: db-user-pass
# 指定使用的那种类型,也就是之前说的内三种类型(docker-registry(kubernetes.io/dockerconfigjson)、generic(Opaque)、tls(kubernetes.io/tls)),type写括号内的
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
# 查看创建好的secret
[root@k8s-master secret]# kubectl apply -f secret.yaml
[root@k8s-master secret]# kubectl get secret
db-user-pass Opaque 2 6s
secret创建好后,我们来创建Pod来看看
# 创建一与Secret相关联的Pod
apiVersion: v1
kind: Pod
metadata:
name: secret-demo-pod
spec:
containers:
- name: demo
image: nginx
env:
- name: USER
valueFrom:
secretKeyRef:
name: db-user-pass
key: username
- name: PASS
valueFrom:
secretKeyRef:
name: db-user-pass
key: password
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
- name: config
secret:
secretName: db-user-pass
items:
- key: username
path: username.txt
- key: password
path: password.txt
# 可以进入Pod验证一下
应用程序如何动态更新配置
当我修改了一些变量后,即使变量会同步到Pod中,但是服务在不重启的情况下也是不会应用的
应用程序动态更新配置方案
- 当ConfigMap发生变更时,应用程序自动感知动态加载(需要程序自身支持)
- 触发滚动更新,即重启服务
- 给程序预留一个接口,用于通知动态加载配置(Sidecar),例如nginx -s reload
当然这上边可能需要开发去支持下,但是运维也可以通过inotify去动态感知配置文件的变化,如果监控到配置文件有更新,那么可以通过reload类似的接口去下载
数据卷
数据卷概述
为什么需要数据卷
容器中的文件在磁盘上是临时存放的,这给容器中运行比较重要的应用程序带来一些问题
- 问题1:当容器升级或崩溃时,kubelet会重建容器,容器内文件会丢失
- 问题2:一个pod中运行多个容器需要共享文件
Kubernetes卷(Volume)这一抽象概念能够解决着两个问题
常用的数据卷:
- 节点本地(hostPath,emptyDir)
- 网络(NFS、Ceph、GlusterFS)
- 公有云(AWS EBS)
- K8S资源(configmap,secret)
数据卷更多支持功能
emptyDir
emptyDir卷: 是一个临时存储卷(Pod所在节点),与Pod生命周期绑定一起,如果Pod删除了卷也会被删除
应用场景: Pod容器之间数据共享
# 案例
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
# 这个是写容器(业务容器),产生一些数据
- name: write
image: centos
# 模拟运行中生成的数据
command: ["bash","-c","for i in {1..100};do echo $i >> /data/access.log;sleep 1;done"]
# 数据挂载点
volumeMounts:
- name: data
mountPath: /data
# 读容器(辅助容器)
- name: read
image: centos
# 模拟从业务容器读取数据
command: ["bash","-c","tail -f /data/access.log"]
# 数据挂载点
volumeMounts:
- name: data
mountPath: /data
# 数据共享点(Pod所在节点)
volumes:
- name: data
emptyDir: {}
# apply后进入到Pod中查看
# 无论是进入read还是进入write,都可以看到data目录下有一个access.log的文件
[root@k8s-master emptyDir]# kubectl exec -it my-pod -c read -- bash
# emptyDir是本地挂载的,所以可以去找到对应的节点,在对应节点找到指定的data目录中的数据
# 根据Pod名称去寻找,"k8s_write_my-pod_default_d06fc492-c2bc-4e1b-97e7-61f74796296a_3"中后面的"d06fc492-c2bc-4e1b-97e7-61f74796296a"就是它的Id
[root@k8s-node1 pods]# docker ps | grep my-pod
9880225f17ac centos "bash -c 'for i in {…" About a minute ago Up About a minute k8s_write_my-pod_default_d06fc492-c2bc-4e1b-97e7-61f74796296a_3
3c4c26ae2205 centos "bash -c 'tail -f /d…" 7 minutes ago Up 7 minutes k8s_read_my-pod_default_d06fc492-c2bc-4e1b-97e7-61f74796296a_0
# 进入到kubelet的存储目录下
[root@k8s-node1 pods]# cd /var/lib/kubelet/pods/
# 找到对应ID
[root@k8s-node1 data]# pwd
/var/lib/kubelet/pods/d06fc492-c2bc-4e1b-97e7-61f74796296a/volumes/kubernetes.io~empty-dir/data
# 可以看到数据都在这下面,将Pod删除后该目录就会消失
[root@k8s-node1 data]# tailf access.log
hostPath
hostPath: 挂载Node(Pod所在节点)文件系统上文件或者目录到Pod中的容器
应用场景: Pod中容器需要访问宿主机文件
案例
[root@k8s-master volume]# vim hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod3
spec:
containers:
- name: test
image: busybox
args:
- /bin/sh
- -c
- sleep 36000
volumeMounts:
- name: data
mountPath: /data
- name: tmp
mountPath: /aaa
# 将宿主机的/tmp目录挂载到容器的/data目录
volumes:
- name: data
hostPath:
path: /tmp
# 指定类型,具体的可以在官网查看详情
type: Directory
# 可以定义其他类型的,也可以定义多个相同类型的
- name: tmp
emptyDir: {}
# 验证,该Pod被分配到了Node1节点上
[root@k8s-master volume]# kubectl apply -f hostpath.yaml
[root@k8s-master volume]# kubectl exec -it my-pod3 -- sh
/ # ls data/
/ # mkdir data/aa
# 创建一个也可以在node1节点上发现创建的文件
[root@k8s-node1 tmp]# ls
aa
以上两种方式可以解决"问题2",但是无法解决"问题1",为了解决"问题1"可以使用以下方式解决
数据卷:NFS
NFS数据卷: 提供对NFS挂载支持,可以自动将NFS共享路径挂载到Pod中
NFS: 是一个主流的文件共享服务器
NFS部署
每个节点都需要部署
yum -y install nfs-utils
# 服务端配置以下内容
vim /etc/exports
# 加入以下内容
/data/kubernetes *(rw,no_root_squash)
mkdir -p /data/kubernetes
systemctl start nfs
systemctl enable nfs
示例
# 创建yaml
[root@k8s-master nfs]# kubectl create deployment nginx --image=nginx --dry-run=client -o yaml > deployment.yaml
# 修改为以下内容
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: wwwroot
mountPath: /usr/share/nginx/html
volumes:
- name: wwwroot
nfs:
server: 192.168.113.148
path: /data/kubernetes
# 创建service
[root@k8s-master nfs]# kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort
# 查看pod
[root@k8s-master nfs]# kubectl get pods |grep nginx
nginx-5c4886479-4lbv5 1/1 Running 0 6m54s
nginx-5c4886479-bsctn 1/1 Running 0 6m54s
nginx-5c4886479-sn2ls 1/1 Running 0 6m54s
# 查看svc和ep
[root@k8s-master nfs]# kubectl get svc,ep |grep nginx
service/nginx NodePort 10.103.201.7 <none> 80:31040/TCP 6m40s
endpoints/nginx 10.244.169.144:80,10.244.36.88:80,10.244.36.89:80 6m40s
# 验证NFS挂载存储
# 在挂载目录下加入html页面
[root@k8s-master nfs]# cd /data/kubernetes/
[root@k8s-master kubernetes]# echo "aaa" > index.html
访问
当删除pod或者加入新的pod也会存在这个目录,但是正常生产环境中是不会直接使用这个的,下面通过PV、PVC来实现这个功能
持久卷概述
- PersistentVolume(PV):持久数据卷,对存储资源的抽象,使得存储作为集群中的资源管理
- PersistentVolumeClaim(PVC):持久数据卷申请,用户定义使用的存储容量,使得用户不需要关心后端存储实现
Pod申请PVC作为卷来使用,Kubernetes通过PVC查找绑定的PV,并挂载到Pod中提供程序使用
PV与PVC使用流程
使用pv和pvc最终其实也是使用到nfs之类的共享存储,不过引入了pv和pvc的概念来保障安全性、专业性等,可以通过一下示例可以简单的了解创建流程和实现原理,一个PV只能对应一个PVC
# 创建Pod及PVC
[root@k8s-master pv]# vim deployment-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-pvc
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: pvc
template:
metadata:
labels:
app: pvc
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: wwwroot
mountPath: /usr/share/nginx/html
volumes:
- name: wwwroot # pvc
persistentVolumeClaim:
claimName: my-pvc
---
# pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: web-pvc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: pvc
type: NodePort
[root@k8s-master pv]# vim pv.yaml
# 创建PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/kubernetes
server: 192.168.113.148
PV生命周期
ACCESS MODES(访问模式)
AccessModes是用来对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载(类似于块儿存储)
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载(文件存储)
RECLAIM POLICY(回收策略):
目前PV支持的策略有三种:
- Retain(保留):保留数据,需要管理员手工清理数据
- Recycle(回收):清除PV中的数据。效果相当于执行rm -rf
- Delete(删除):与PV相连的后端存储同时删除
修改回收策略:persistentVolumeReclaimPolicy:Retain
STATUS(状态):
一个PV的生命周期中,可能会处于4中不同的阶段:
- Available(可用):表示可用状态,还未被任何PVC绑定
- Bound(已绑定):表示PV已经被PVC绑定
- Released(已释放):PVC被删除,但是资源还未被集群重新声明
- Failed(失败):表示该PV的自动回收失败
PV和PVC匹配条件
根据存储容量和访问模式匹配,匹配最接近的PV容量(向大的匹配),并且PV和PVC从资源来讲,不具备限制能力,具体还得根据后端存储而定,像NFS是不具备存储限制能力的,即使通过PVC去申请下存储资源,那么也不会根据申请的资源去限制他的使用
PV的动态供给
以上所讲述的PV和PVC都是静态供给,需要运维人员提前手动创建好
而这种方式繁琐复杂,PV多了也不方便运维,所以就有了PV的动态供给(StorageClass)
这里还是以NFS为共享存储来实现(可以在该链接中查看支持动态供给的存储插件,其中打√的就是支持,不打√的就需要去社区获取插件来支持了,NFS就是如此)
K8S默认不支持NFS动态供给,需要单独部署社区开发的插件
项目地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
创建一个动态供给
[root@k8s-master nfs-external-provisioner]# vim deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
# 国内的一个镜像站
image: lizhenliang/nfs-subdir-external-provisioner:v4.0.1
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
# 配置StorageClass的一个标识
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.113.148
- name: NFS_PATH
value: /data/kubernetes
volumes:
- name: nfs-client-root
nfs:
server: 192.168.113.148
path: /data/kubernetes
# 存储类,其实就是定义了一个标记,
[root@k8s-master nfs-external-provisioner]# vim class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
# 对应后端供给插件的一个标识,和deployment中env.value的内个标识一致的,不懂得原理不要去改动他
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
# 在这个动态供给中,它创建的pv默认规则为delete,删除pvc后pv也会删除,存储的数据也会删除,但是可以将下方参数"false"改为"true",他会在删除pv后将原来的目录做一个归档备份,并且class.yaml不支持动态更新,更新的话需要使用delete -f删掉后apply -f重新构建,不会影响已有数据的
archiveOnDelete: "false"
# 创建k8s的认证
[root@k8s-master nfs-external-provisioner]# vim rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
# 构建yaml
[root@k8s-master nfs-external-provisioner]# kubectl apply -f .
# 查看创建的存储类,默认的回收策略是Delete
[root@k8s-master nfs-external-provisioner]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 2m56s
创建一个pvc
# 创建一个测试Pod
[root@k8s-master pv]# vim deployment-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-pvc
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: pvc
template:
metadata:
labels:
app: pvc
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: wwwroot
mountPath: /usr/share/nginx/html
volumes:
- name: wwwroot # pvc
persistentVolumeClaim:
claimName: my-pvc
---
# pvc
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
# 加入指定存储类的名字,改参数标识指定存储类,使用动态供给
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: web-pvc
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: pvc
type: NodePort
[root@k8s-master pv]# kubectl apply -f deployment-pvc.yaml
[root@k8s-master pv]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound pvc-06220b0a-fc6b-493b-a031-7057d8022a05 10Gi RWX managed-nfs-storage 6s
[root@k8s-master pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-06220b0a-fc6b-493b-a031-7057d8022a05 10Gi RWX Delete Bound default/my-pvc managed-nfs-storage 8s
这里理一下思路吧,先通过nfs的插件创建一个Pod用来管理nfs的动态供给,由之前的三种yaml实现(class.yaml、deployment.yaml、rbac.yaml),其中class定义了一个标签和名称,标签在deployment中也有定义,而名称是在用户需要配置pvc时配置storageClassName项,和单纯的PV使用NFS存储而言,他为每个应用分配独立的存储,之间是不会冲突的,但是同一组Pod的存储还是共享的,不是独立的