Appearance
数据存储
容器会频繁创建销毁,容器在销毁同时,数据也会被清除。那么为了保存数据,k8s 提出了 volume,也就是共享目录。
volume 在 pod 上定义,被一个 pod 中的多个容器挂载到具体文件目录下,k8s 通过 volume 实现同 pod 的不同容器数据共享以及持久化存储。
volume 的生命周期和 pod 中的容器相关,容器终止、重启时,volume 数据不会丢失。
volume 有多种类型:
- 简单存储:EmptyDir、HostPath、NFS
- 高级存储:PV、PVC
- 配置存储:ConfigMap、Secret
简单存储
EmptyDir
最简单的 Volume 类型,就是主机上的一个空目录。pod 在分配到 node 的同时就会创建一次,k8s 会自动分配宿主机上的目录。pod 销毁时,EmptyDir 中的数据也会被销毁。
一般作用于临时目录、多容器共享目录。

yml
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.14-alpine
ports:
- containerPort: 80
volumeMounts:
# 挂载目录为 /var/log/nginx
- mountPath: /var/log/nginx
name: logs-volume
- name: busybox
image: busybox:1.30
command: ['/bin/bash', '-c', 'tail -f /logs/access.log']
# 挂载目录为 /logs
volumeMounts:
- mountPath: /logs
name: logs-volume
volumes:
- name: logs-volume
emptyDir: {}pod 中有两个容器,容器之间通过 volume 进行文件共享。其中 nginx 容器将自身的 /var/log/nginx 目录挂载到了 volume 下,busybox 容器将 /log 目录挂载到了 volume 下。
nginx 写 log 文件,通过 volume,busybox 可以读取 nginx 中的 log 文件并且将其输出。
HostPath
EmptyDir 中的数据不会被持久化,pod 结束就会销毁。如果想要持久化,最简单的方式是 HostPath。
hostPath 是将 pod 中的 volume 实际挂载到 node 上,这样就可以保证数据持久化。

yml
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.14-alpine
ports:
- containerPort: 80
volumeMounts:
# 挂载目录为 /var/log/nginx
- mountPath: /var/log/nginx
name: logs-volume
- name: busybox
image: busybox:1.30
command: ['/bin/bash', '-c', 'tail -f /logs/access.log']
# 挂载目录为 /logs
volumeMounts:
- mountPath: /logs
name: logs-volume
volumes:
- name: logs-volume
# 唯一的区别是从 EmptyDir 改为了 HostPath
hostPath:
path: /tmp/logs
# 此目录存在则使用,不存在则创建后使用
type: DirectoryOrCreatetype 类型:
- DirectoryOrCreate:此目录存在则使用,不存在则创建后使用
- Directory:目录必须存在
- FileOrCreate:文件存在则使用,不存在则创建后使用
- File:文件必须存在
NFS
如果说 HostPath 是 EmptyDir 的升级版,那么 NFS 是 HostPath 的升级版。它解决了 Node 节点故障导致的 Path 问题。
使用 NFS 方式,需要 NFS 服务器,Pod 中的存储会直接上传到 NFS 中,这样即使 Pod 故障,NFS 没问题,数据就可以访问。
首先需要安装 nfs:
随便找一台节点做 nfs 服务器,这里选用的是 node1:
yum -y install nfs-utils准备共享目录:
sudo mkdir /data/nfs -pv共享目录以读写权限暴露给
192.168.10.*:vim /etc/exports/etc/exports文件是 nfs 读取的一个默认文件text/data/nfs 192.168.10.0/24(rw,no_root_squash)在其他节点上都要安装 nfs,目的不是为了变为服务器,而是为了使用 nfs 设备
在其他节点上关闭 nfs 服务,我们其他节点上只需要一个驱动包即可。
使用 nfs
ymlapiVersion: v1 kind: Pod metadata: name: nfs namespace: dev spec: containers: - name: nginx image: nginx:1.14-alpine ports: - containerPort: 80 volumeMounts: # 挂载目录为 /var/log/nginx - mountPath: /var/log/nginx name: logs-volume - name: busybox image: busybox:1.30 command: ['/bin/sh', '-c', 'tail -f /logs/access.log'] # 挂载目录为 /logs volumeMounts: - mountPath: /logs name: logs-volume volumes: - name: logs-volume # 使用 nfs nfs: path: /data/nfs server: 192.168.10.101
高阶存储
高阶存储是 pv、pvc。
简单来说,pv 是由管理员维护的,将所有存储配置抽象为相同的资源对用户提供。用户只需要知道 pv 的规则即可,不在乎究竟是哪个文件系统。
pvc 是用户维护的,是对 pv 的申请。
pv
pv 就是对各种存储设备的抽象,每个存储都不同,则有一定的差异性。
yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
# 存储类型,和底层对应,不同的存储方式对应的配置不同,要看对应的官方文档
nfs:
path: /data/nfs/pv/pv1/
server: 192.168.10.101
# 存储能力,对外提供的资源共多少,当前仅支持存储空间的设置
capacity:
storage: 2G
# 访问模式,ReadOnlyMany(只读)、ReadWriteMany(可读写)、ReadWriteOnce(可读写,但只能被一个 pvc 独占)
# 具体存储系统支持的访问模式不同,nfs 三种全都支持
accessModes: [ReadWriteMany]
# 回收策略,pv 不再被使用后,数据的处理方式。Retain(保留)、Delete(删除)、Recycle(已经废弃,效果等于 rm -rf /data/*)
persistentVolumeReclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
nfs:
path: /data/nfs/pv/pv2/
server: 192.168.10.101
capacity:
storage: 1G
accessModes: [ReadWriteMany]
persistentVolumeReclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv3
spec:
nfs:
path: /data/nfs/pv/pv3/
server: 192.168.10.101
capacity:
storage: 2G
accessModes: [ReadWriteMany]
persistentVolumeReclaimPolicy: Delete一个 pv 的生命周期:
- available: 可用
- bound: 已被 pvc 绑定
- released: pvc 已被删除,但是资源还未被集群重新声明
- failed: 回收失败
注意一点,pv 没有名称空间的概念,我们在编写配置文件和查看的时候都不需要带名称空间
pvc
yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
namespace: dev
spec:
# 可以根据访问模式来挑选适合的 pv,还有一些选项,和上面的 pvc 配置是一样的
accessModes: [ReadWriteMany]
resources:
requests:
storage: 1Gpvc 有 namespace,这代表它是分环境的。

在 pods 中使用 pvc
yml
apiVersion: v1
kind: Pod
metadata:
namespace: dev
name: pod
spec:
containers:
- name: busybox
image: busybox:1.30
command: ['/bin/sh', '-c', 'touch /tmp/hello.txt; while true; do /bin/echo $(date +%T) >> /tmp/hello.txt; sleep 3; done;']
volumeMounts:
- mountPath: /tmp/
name: volume
volumes:
- name: volume
persistentVolumeClaim:
# 这个名字是 pvc 的名字,这里的 pvc 名字就是 pvc
claimName: pvc
readOnly: false配置存储
ConfigMap
ConfigMap 是一种比较特殊的存储卷,它的主要作用是用来存储配置信息的。
创建 configmap.yaml,内容如下:
yml
apiVersion: v1
kind: ConfigMap
metadata:
name: configmap
namespace: dev
data:
info: |
username:admin
password:123456# 创建configmap
[root@k8s-master01 ~]# kubectl create -f configmap.yaml
configmap/configmap created
# 查看configmap详情
[root@k8s-master01 ~]# kubectl describe cm configmap -n dev
Name: configmap
Namespace: dev
Labels: <none>
Annotations: <none>
Data
====
info:
----
username:admin
password:123456
Events: <none>接下来创建一个 pod-configmap.yaml ,将上面创建的 configmap 挂载进去
yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-configmap
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
volumeMounts: # 将configmap挂载到目录
- name: config
mountPath: /configmap/config
volumes: # 引用configmap
- name: config
configMap:
name: configmap# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-configmap.yaml
pod/pod-configmap created
# 查看pod
[root@k8s-master01 ~]# kubectl get pod pod-configmap -n dev
NAME READY STATUS RESTARTS AGE
pod-configmap 1/1 Running 0 6s
#进入容器
[root@k8s-master01 ~]# kubectl exec -it pod-configmap -n dev /bin/sh
# cd /configmap/config/
# ls
info
# more info
username:admin
password:123456
# 可以看到映射已经成功,每个configmap都映射成了一个目录
# key--->文件 value---->文件中的内容
# 此时如果更新configmap的内容, 容器中的值也会动态更新Secret
在 kubernetes 中,还存在一种和 ConfigMap 非常类似的对象,称为 Secret 对象
它主要用于存储敏感信息,例如密码、秘钥、证书等等
[root@k8s-master01 ~]# echo -n 'admin' | base64 #准备username
YWRtaW4=
[root@k8s-master01 ~]# echo -n '123456' | base64 #准备password
MTIzNDU2secret.yaml
yaml
apiVersion: v1
kind: Secret
metadata:
name: secret
namespace: dev
type: Opaque
data:
username: YWRtaW4=
password: MTIzNDU2# 创建secret
[root@k8s-master01 ~]# kubectl create -f secret.yaml
secret/secret created
# 查看secret详情
[root@k8s-master01 ~]# kubectl describe secret secret -n dev
Name: secret
Namespace: dev
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 6 bytes
username: 5 bytes创建 pod-secret.yaml,将上面创建的secret挂载进去
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
namespace: dev
spec:
containers:
- name: nginx
image: nginx:1.17.1
volumeMounts: # 将secret挂载到目录
- name: config
mountPath: /secret/config
volumes:
- name: config
secret:
secretName: secret# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-secret.yaml
pod/pod-secret created
# 查看pod
[root@k8s-master01 ~]# kubectl get pod pod-secret -n dev
NAME READY STATUS RESTARTS AGE
pod-secret 1/1 Running 0 2m28s
# 进入容器,查看secret信息,发现已经自动解码了
[root@k8s-master01 ~]# kubectl exec -it pod-secret /bin/sh -n dev
/ # ls /secret/config/
password username
/ # more /secret/config/username
admin
/ # more /secret/config/password
123456安全认证
访问控制概述
Kubernetes 作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务
所谓的安全性其实就是保证对 Kubernetes 的各种客户端进行认证和鉴权操作
客户端
在Kubernetes集群中,客户端通常有两类:
- User Account:一般是独立于 kubernetes 之外的其他服务管理的用户账号。
- Service Account:kubernetes管理的账号,用于为 Pod 中的服务进程在访问 Kubernetes 时提供身份标识。

认证、授权与准入控制
ApiServer是访问及管理资源对象的唯一入口。任何一个请求访问ApiServer,都要经过下面三个流程:
- Authentication(认证):身份鉴别,只有正确的账号才能够通过认证
- Authorization(授权): 判断用户是否有权限对访问的资源执行特定的动作
- Admission Control(准入控制):用于补充授权机制以实现更加精细的访问控制功能。

认证管理
Kubernetes集群安全的最关键点在于如何识别并认证客户端身份,它提供了3种客户端身份认证方式:
HTTP Base认证:通过用户名+密码的方式认证
这种认证方式是把“用户名:密码”用BASE64算法进行编码后的字符串放在HTTP请求中的Header Authorization域里发送给服务端。服务端收到后进行解码,获取用户名及密码,然后进行用户身份认证的过程。
HTTP Token认证:通过一个Token来识别合法用户
这种认证方式是用一个很长的难以被模仿的字符串--Token来表明客户身份的一种方式。每个Token对应一个用户名,当客户端发起API调用请求时,需要在HTTP Header里放入Token,API Server接到Token后会跟服务器中保存的token进行比对,然后进行用户身份认证的过程。
HTTPS 证书认证:基于CA根证书签名的双向数字证书认证方式
这种认证方式是安全性最高的一种方式

授权管理
授权发生在认证成功之后,通过认证就可以知道请求用户是谁, 然后Kubernetes会根据事先定义的授权策略来决定用户是否有权限访问,这个过程就称为授权。
每个发送到ApiServer的请求都带上了用户和资源的信息:比如发送请求的用户、请求的路径、请求的动作等,授权就是根据这些信息和授权策略进行比较,如果符合策略,则认为授权通过,否则会返回错误。
API Server目前支持以下几种授权策略:
- AlwaysDeny:表示拒绝所有请求,一般用于测试
- AlwaysAllow:允许接收所有请求,相当于集群不需要授权流程(Kubernetes默认的策略)
- ABAC:基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
- Webhook:通过调用外部REST服务对用户进行授权
- Node:是一种专用模式,用于对kubelet发出的请求进行访问控制
- RBAC:基于角色的访问控制(kubeadm安装方式下的默认选项)
- RBAC(Role-Based Access Control) 基于角色的访问控制,主要是在描述一件事情:给哪些对象授予了哪些权限
其中涉及到了下面几个概念:
- 对象:User、Groups、ServiceAccount
- 角色:代表着一组定义在资源上的可操作动作(权限)的集合
- 绑定:将定义好的角色跟用户绑定在一起

RBAC引入了4个顶级资源对象:
- Role、ClusterRole:角色,用于指定一组权限
- RoleBinding、ClusterRoleBinding:角色绑定,用于将角色(权限)赋予给对象
Role、ClusterRole
一个角色就是一组权限的集合,这里的权限都是许可形式的(白名单)
yaml
# Role只能对命名空间内的资源进行授权,需要指定nameapce
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
namespace: dev
name: authorization-role
rules:
- apiGroups: [""] # 支持的API组列表,"" 空字符串,表示核心API群
resources: ["pods"] # 支持的资源对象列表
verbs: ["get", "watch", "list"] # 允许的对资源对象的操作方法列表yaml
# ClusterRole可以对集群范围内资源、跨namespaces的范围资源、非资源类型进行授权
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: authorization-clusterrole
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]需要详细说明的是,rules中的参数:
apiGroups: 支持的API组列表
"","apps", "autoscaling", "batch"resources:支持的资源对象列表
"services", "endpoints", "pods","secrets","configmaps","crontabs","deployments","jobs", "nodes","rolebindings","clusterroles","daemonsets","replicasets","statefulsets", "horizontalpodautoscalers","replicationcontrollers","cronjobs"verbs:对资源对象的操作方法列表
"get", "list", "watch", "create", "update", "patch", "delete", "exec"
RoleBinding、ClusterRoleBinding
角色绑定用来把一个角色绑定到一个目标对象上,绑定目标可以是 User、Group 或者 ServiceAccount
yaml
# RoleBinding可以将同一namespace中的subject绑定到某个Role下,则此subject即具有该Role定义的权限
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: authorization-role-binding
namespace: dev
subjects:
- kind: User
name: heima
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: authorization-role
apiGroup: rbac.authorization.k8s.ioyaml
# ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: authorization-clusterrole-binding
subjects:
- kind: User
name: heima
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: authorization-clusterrole
apiGroup: rbac.authorization.k8s.ioRoleBinding 引用 ClusterRole 进行授权
RoleBinding 可以引用 ClusterRole,对属于同一命名空间内 ClusterRole 定义的资源主体进行授权
一种很常用的做法就是,集群管理员为集群范围预定义好一组角色(ClusterRole),然后在多个命名空间中重复使用这些 ClusterRole
这样可以大幅提高授权管理工作效率,也使得各个命名空间下的基础性授权规则与使用体验保持一致
yaml
# 虽然authorization-clusterrole是一个集群角色,但是因为使用了RoleBinding
# 所以heima只能读取dev命名空间中的资源
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: authorization-role-binding-ns
namespace: dev
subjects:
- kind: User
name: heima
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: authorization-clusterrole
apiGroup: rbac.authorization.k8s.io准入控制
通过了前面的认证和授权之后,还需要经过准入控制处理通过之后,apiserver才会处理这个请求。
准入控制是一个可配置的控制器列表,可以通过在Api-Server上通过命令行设置选择执行哪些准入控制器:
--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,
DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds只有当所有的准入控制器都检查通过之后,apiserver才执行该请求,否则返回拒绝。
当前可配置的Admission Control准入控制如下:
- AlwaysAdmit:允许所有请求
- AlwaysDeny:禁止所有请求,一般用于测试
- AlwaysPullImages:在启动容器之前总去下载镜像
- DenyExecOnPrivileged:它会拦截所有想在 Privileged Container上执行命令的请求
- ImagePolicyWebhook:这个插件将允许后端的一个 Webhook 程序来完成 admission controller 的功能。
- Service Account:实现 ServiceAccount 实现了自动化
- SecurityContextDeny:这个插件将使用 SecurityContext 的 Pod 中的定义全部失效
- ResourceQuota:用于资源配额管理目的,观察所有请求,确保在 namespace 上的配额不会超标
- LimitRanger:用于资源限制管理,作用于 namespace 上,确保对 Pod 进行资源限制
- InitialResources:为未设置资源请求与限制的 Pod,根据其镜像的历史资源的使用情况进行设置
- NamespaceLifecycle:如果尝试在一个不存在的 namespace 中创建资源对象,则该创建请求将被拒绝。当删除一个 namespace 时,系统将会删除该 namespace 中所有对象
- DefaultStorageClass:为了实现共享存储的动态供应,为未指定 StorageClass 或 PV 的 PVC 尝试匹配默认的 StorageClass,尽可能减少用户在申请 PVC 时需了解的后端存储细节
- DefaultTolerationSeconds:这个插件为那些没有设置 forgiveness tolerations并具有notready:NoExecute和unreachable:NoExecute 两种 taints 的 Pod 设置默认的“容忍”时间,为 5min
- PodSecurityPolicy:这个插件用于在创建或修改Pod时决定是否根据Pod的security context和可用的PodSecurityPolicy对Pod的安全策略进行控制
DashBoard
# 下载yaml
[root@k8s-master01 ~]# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0/aio/deploy/recommended.yaml
# 修改kubernetes-dashboard的Service类型
kind: Service
apiVersion: v1
metadata:
labels:
k8s-app: kubernetes-dashboard
name: kubernetes-dashboard
namespace: kubernetes-dashboard
spec:
type: NodePort # 新增
ports:
- port: 443
targetPort: 8443
nodePort: 30009 # 新增
selector:
k8s-app: kubernetes-dashboard
# 部署
[root@k8s-master01 ~]# kubectl create -f recommended.yaml
# 查看namespace下的kubernetes-dashboard下的资源
[root@k8s-master01 ~]# kubectl get pod,svc -n kubernetes-dashboard
NAME READY STATUS RESTARTS AGE
pod/dashboard-metrics-scraper-c79c65bb7-zwfvw 1/1 Running 0 111s
pod/kubernetes-dashboard-56484d4c5-z95z5 1/1 Running 0 111s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/dashboard-metrics-scraper ClusterIP 10.96.89.218 <none> 8000/TCP 111s
service/kubernetes-dashboard NodePort 10.104.178.171 <none> 443:30009/TCP 111s
# 创建账号
[root@k8s-master01-1 ~]# kubectl create serviceaccount dashboard-admin -n kubernetes-dashboard
# 授权
[root@k8s-master01-1 ~]# kubectl create clusterrolebinding dashboard-admin-rb --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:dashboard-admin
# 获取账号token
[root@k8s-master01 ~]# kubectl get secrets -n kubernetes-dashboard | grep dashboard-admin
dashboard-admin-token-xbqhh kubernetes.io/service-account-token 3 2m35s
[root@k8s-master01 ~]# kubectl describe secrets dashboard-admin-token-xbqhh -n kubernetes-dashboard
Name: dashboard-admin-token-xbqhh
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: dashboard-admin
kubernetes.io/service-account.uid: 95d84d80-be7a-4d10-a2e0-68f90222d039
Type: kubernetes.io/service-account-token
Data
====
namespace: 20 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImJrYkF4bW5XcDhWcmNGUGJtek5NODFuSXl1aWptMmU2M3o4LTY5a2FKS2cifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkYXNoYm9hcmQtYWRtaW4tdG9rZW4teGJxaGgiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGFzaGJvYXJkLWFkbWluIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiOTVkODRkODAtYmU3YS00ZDEwLWEyZTAtNjhmOTAyMjJkMDM5Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmVybmV0ZXMtZGFzaGJvYXJkOmRhc2hib2FyZC1hZG1pbiJ9.NAl7e8ZfWWdDoPxkqzJzTB46sK9E8iuJYnUI9vnBaY3Jts7T1g1msjsBnbxzQSYgAG--cV0WYxjndzJY_UWCwaGPrQrt_GunxmOK9AUnzURqm55GR2RXIZtjsWVP2EBatsDgHRmuUbQvTFOvdJB4x3nXcYLN2opAaMqg3rnU2rr-A8zCrIuX_eca12wIp_QiuP3SF-tzpdLpsyRfegTJZl6YnSGyaVkC9id-cxZRb307qdCfXPfCHR_2rt5FVfxARgg_C0e3eFHaaYQO7CitxsnIoIXpOFNAR8aUrmopJyODQIPqBWUehb7FhlU1DCduHnIIXVC_UICZ-MKYewBDLw
ca.crt: 1025 bytes通过浏览器访问 Dashboard 的 UI,在登录页面上输入上面的token

