文章目录
回顾
Kubernetes 对象(三)
Pod 数据持久化
参考文档: https://kubernetes.io/docs/concepts/storage/volumes/
- Kubernetes 中的 Volume 提供了在容器中挂载外部存储的能力
- Pod 需要设置卷来源 (spec.volume) 和挂载点 (spec.containers.volumeMounts) 两个信息后才可以使用相应的Volume
常用数据卷类型
- 本地卷:
hostPath
,emptyDir
- 网络卷:
nfs
,ceph
(cephfs, rbd),glusterfs
- 公有云:
aws
,azure
- Kubernetes 资源:
downwardAPI
,configMap
,secret
emptyDir
创建一个空卷, 挂载到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/count;sleep 1;done"]
volumeMounts:
- name: data
mountPath: /data
- name: read
image: centos
command: ["bash","-c","tail -f /data/count"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
hostPath
挂载Node文件系统上文件或者目录到Pod中的容器.
应用场景: Pod中容器需要访问宿主机文件
Value | Behavior |
---|---|
Empty string (default) is for backward compatibility, which means that no checks will be performed before mounting the hostPath volume. | |
DirectoryOrCreate | If nothing exists at the given path, an empty directory will be created there as needed with permission set to 0755, having the same group and ownership with Kubelet. |
Directory | A directory must exist at the given path |
FileOrCreate | If nothing exists at the given path, an empty file will be created there as needed with permission set to 0644, having the same group and ownership with Kubelet. |
File | A file must exist at the given path |
Socket | A UNIX socket must exist at the given path |
CharDevice | A character device must exist at the given path |
BlockDevice | A block device must exist at the given path |
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- sleep 30000
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
hostPath:
path: /tmp
type: Directory
验证: 进入 Pod 中的 /data 目录内容与当前运行 Pod 的节点内容一样.
注意
**注意: **应注意, 该
FileOrCreate
模式不会创建文件的父目录. 如果挂载文件的父目录不存在, 则pod无法启动. 为确保此模式有效, 您可以尝试分别挂载目录和文件, 如下所示.apiVersion: v1 kind: Pod metadata: name: test-webserver spec: containers: - name: test-webserver image: k8s.gcr.io/test-webserver:latest volumeMounts: - mountPath: /var/local/aaa name: mydir - mountPath: /var/local/aaa/1.txt name: myfile volumes: - name: mydir hostPath: # Ensure the file directory is created. path: /var/local/aaa type: DirectoryOrCreate - name: myfile hostPath: path: /var/local/aaa/1.txt type: FileOrCreate
网络存储
以 NFS 为例
搭建 NFS:
先准备一台 NFS 服务器作为测试, 并在每个 Node 上安装 nfs-utils 包, 用于 mount 挂载时用.
# Server
$ yum install nfs-utils -y
$ vi /etc/exports
# /nfs/path *(rw,no_root_squash)
$ mkdir -p /nfs/path
$ systemctl start nfs
$ systemctl enable nfs
# Node
$ yum install nfs-utils -y
$ mount -t nfs 192.168.0.14:/nfs/path /mnt/
配置 YAML
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes:
- name: data
nfs:
server: 192.168.0.14
path: /data/nfs
PV & PVC
PersistentVolume (PV) : 对存储资源创建和使用的抽象, 使得存储作为集群中的资源管理
PV供给分为:
-
静态
-
动态
PersistentVolumeClaim (PVC) : 让用户不需要关心具体的Volume实现细节
PV 静态供给
静态供给是指提前创建好很多个PV, 以供使用.
示例: 先准备三个PV, 分别是5G, 10G, 20G, 修改下面对应值分别创建.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001 # 修改PV名称
spec:
capacity:
storage: 30Gi # 修改大小
accessModes:
- ReadWriteMany
nfs:
path: /nfs/path/pv001 # 修改目录名
server: 192.168.0.14
创建一个Pod使用PV:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
创建并查看PV与PVC状态:
$ kubectl apply -f pod-pv.yaml
$ kubectl get pv,pvc
会发现该PVC会与5G PV进行绑定成功.
然后进入到容器中/usr/share/nginx/html (PV挂载目录) 目录下创建一个文件测试:
$ kubectl exec -it my-pod bash
$ cd /usr/share/nginx/html
$ echo "hello" index.html
再切换到NFS服务器, 会发现也有刚在容器创建的文件, 说明工作正常.
$ cd /nfs/path/pv001
$ ls
index.html
参考文档: kubernetes.io/docs/concepts/storage/persistent-volumes/
注意
- 容量并不是必须对应 (pv!=pvc) , 根据就近选择合适 pv.
- 扩容: Kubernetes 层面支持扩容操作, 但是具体实现依赖存储介质, 需具体后端存储的支持
- 限制: pv 容量应用于匹配, 可以创建超过 pv 数, 可以限制, 但要后端存储支持
PV 动态供给
Dynamic Provisioning 机制工作的核心在于 StorageClass 的 API 对象.
StorageClass 声明存储插件, 用于自动创建 PV.
Kubernetes 支持动态供给的存储插件:
参考文档:kubernetes.io/docs/concepts/storage/storage-classes
PV 动态供给实践 (NFS)
工作流程
由于K8S不支持NFS动态供给, 还需要先安装上图中的nfs-client-provisioner插件:
$ cd nfs-client
$ vi deployment.yaml # 修改里面NFS地址和共享目录为你的
$ kubectl apply -f .
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-df88f57df-bv8h7 1/1 Running 0 49m
测试:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
这次会自动创建 5GPV 并与 PVC 绑定.
$ kubectl get pv,pvc
测试方法同上, 进入到容器中 /usr/share/nginx/html (PV挂载目录) 目录下创建一个文件测试.
再切换到NFS服务器, 会发现下面目录, 该目录是自动创建的PV挂载点. 进入到目录会发现刚在容器创建的文件.
$ ls /opt/nfs/
default-my-pvc-pvc-51cce4ed-f62d-437d-8c72-160027cba5ba
有状态应用部署
StatefulSet 控制器概述
StatefulSet:
-
部署有状态应用
-
解决 Pod 独立生命周期, 保持 Pod 启动顺序和唯一性
- 稳定, 唯一的网络标识符, 持久存储
- 有序, 优雅的部署和扩展、删除和终止
- 有序, 滚动更新
应用场景: 数据库, 分布式应用
稳定的网络ID
说起 StatefulSet 稳定的网络标识符, 不得不从 Headless 说起了.
标准Service:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 9376
Headless Service:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
clusterIP: None
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 9376
标准 Service 与Headless Service 区别是 clusterIP: None
, 这表示创建 Service 不要为我 (Headless Service) 分配 Cluster IP, 因为不需要.
为什么标准 Service 需要?
这就是无状态和有状态的控制器设计理念了, 无状态的应用 Pod 是完全对等的, 提供相同的服务, 可以在飘移在任意节点. 而像一些分布式应用程序, 例如 zookeeper 集群、etcd 集群、mysql 主从, 每个实例都会维护着一种状态, 每个实例都各自的数据, 并且每个实例之间必须有固定的访问地址 (组建集群) , 这就是有状态应用. 所以有状态应用是不能像无状态应用那样, 创建一个标准 Service, 然后访问 ClusterIP 负载均衡到一组 Pod 上. 这也是为什么 Headless Service 不需要 ClusterIP 的原因, 它要的是能为每个 Pod 固定一个"身份".
举例说明:
apiVersion: v1
kind: Service
metadata:
name: headless-svc
spec:
clusterIP: None
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "headless-svc"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
相比之前讲的 yaml, 这次多了一个 serviceName: "headless-svc"
字段, 这就告诉 StatefulSet 控制器要使用 headless-svc
这个 headless service 来保证 Pod 的身份. 多副本部署时是有序的.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod 1/1 Running 0 7h50m
web-0 1/1 Running 0 6h55m
web-1 1/1 Running 0 6h55m
web-2 1/1 Running 0 6h55m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/headless-svc ClusterIP None <none> 80/TCP 7h15m
service/kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 8d
临时创建一个Pod, 测试DNS解析:
$ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx.default.svc.cluster.local
Server: 10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
Name: nginx.default.svc.cluster.local
Address 1: 172.17.26.3 web-1.headless-svc.default.svc.cluster.local
Address 2: 172.17.26.4 web-2.headless-svc.default.svc.cluster.local
Address 3: 172.17.83.3 web-0.headless-svc.default.svc.cluster.local
结果得出该 Headless Service 代理的所有 Pod 的 IP 地址和 Pod 的DNS 记录.
通过访问 web-0.nginx 的 Pod 的 DNS 名称时, 可以解析到对应 Pod 的 IP 地址, 其他Pod 的DNS名称也是如此, 这个DNS名称就是固定身份, 在生命周期不会再变化:
/ # nslookup web-0.headless-svc.default.svc.cluster.local
Server: 10.0.0.2
Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local
Name: web-0.headless-svc.default.svc.cluster.local
Address 1: 172.17.83.3 web-0.headless-svc.default.svc.cluster.local
注意
ClusterIP 记录格式:
..svc.cluster.local
ClusterIP=None 记录格式:
. ..svc.cluster.local
示例: web-0.headless-svc.default.svc.cluster.local
进入容器查看它们的主机名:
$ kubectl exec web-0 hostname
web-0
$ kubectl exec web-1 hostname
web-1
$ kubectl exec web-2 hostname
web-2
可以看到, 每个Pod都从StatefulSet的名称和Pod的序号中获取主机名的.
不过, 相信你也已经注意到了, 尽管 web-headless-svc 这条记录本身不会变, 但它解析到的 Pod 的 IP 地址, 并不是固定的. 这就意味着, 对于"有状态应用"实例的访问, 你必须使用 DNS 记录或者 hostname 的方式, 而绝不应该直接访问这些 Pod 的 IP 地址.
以下是Cluster Domain, Service name, StatefulSet名称以及它们如何影响StatefulSet的Pod的DNS名称的一些选择示例.
Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
---|---|---|---|---|---|
cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0…N-1}.nginx.default.svc.cluster.local | web-{0…N-1} |
cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0…N-1}.nginx.foo.svc.cluster.local | web-{0…N-1} |
kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0…N-1}.nginx.foo.svc.kube.local | web-{0…N-1} |
稳定的存储
StatefulSet 的存储卷使用 VolumeClaimTemplate 创建, 称为卷申请模板, 当 StatefulSet 使用VolumeClaimTemplate 创建一个 PersistentVolume 时, 同样也会为每个 Pod 分配并创建一个编号的 PVC.
示例:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "headless-svc"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "managed-nfs-storage"
resources:
requests:
storage: 1Gi
$ kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pv001 5Gi RWX Retain Released default/my-pvc 8h
persistentvolume/pv002 10Gi RWX Retain Available 8h
persistentvolume/pv003 30Gi RWX Retain Available 8h
persistentvolume/pvc-2c5070ff-bcd1-4703-a8dd-ac9b601bf59d 1Gi RWO Delete Bound default/data-web-0 managed-nfs-storage 6h58m
persistentvolume/pvc-46fd1715-181a-4041-9e93-fa73d99a1b48 1Gi RWO Delete Bound default/data-web-2 managed-nfs-storage 6h58m
persistentvolume/pvc-c82ae40f-07c5-45d7-a62b-b129a6a011ae 1Gi RWO Delete Bound default/data-web-1 managed-nfs-storage 6h58m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-web-0 Bound pvc-2c5070ff-bcd1-4703-a8dd-ac9b601bf59d 1Gi RWO managed-nfs-storage 6h58m
persistentvolumeclaim/data-web-1 Bound pvc-c82ae40f-07c5-45d7-a62b-b129a6a011ae 1Gi RWO managed-nfs-storage 6h58m
persistentvolumeclaim/data-web-2 Bound pvc-46fd1715-181a-4041-9e93-fa73d99a1b48 1Gi RWO managed-nfs-storage 6h58m
结果得知, StatefulSet 为每个 Pod 分配专属的 PVC 及编号. 每个 PVC 绑定对应的 PV, 从而保证每一个 Pod 都拥有一个独立的 Volume.
在这种情况下, 删除 Pods 或 StatefulSet 时, 它所对应的 PVC 和 PV 不会被删除. 所以, 当这个 Pod 被重新创建出现之后, Kubernetes 会为它找到同样编号的 PVC, 挂载这个 PVC 对应的 Volume, 从而获取到以前保存在 Volume 里的数据.
小结
StatefulSet与Deployment区别: 有身份的!
身份三要素:
-
域名
-
主机名
-
存储 (PVC)
Kubernetes 鉴权框架与用户权限分配
参考文档: kubernetes.io/docs/reference/access-authn-authz/controlling-access
Kubernetes 的安全框架
-
访问 Kubernetes 集群的资源需要过三关: 认证、鉴权、准入控制
-
普通用户若要安全访问集群 API Server, 往往需要
证书
、Token
或者用户名+密码
;Pod访问, 需要ServiceAccount
-
K8S安全控制框架主要由下面3个阶段进行控制, 每一个阶段都支持插件方式, 通过API Server配置来启用插件.
访问API资源要经过以下三关才可以:
- Authentication (鉴权)
- Authorization (授权)
- Admission Control (准入控制)
传输安全, 认证, 授权, 准入控制
传输安全:
- 基于 HTTPS 通信
鉴权: 三种客户端身份认证
- HTTPS 证书认证: 基于CA证书签名的数字证书认证
- HTTP Token认证: 通过一个Token来识别用户
- HTTP Base认证: 用户名+密码的方式认证
授权:
RBAC (Role-Based Access Control, 基于角色的访问控制) : 负责完成授权 (Authorization) 工作.
根据API请求属性, 决定允许还是拒绝.
准入控制:
Adminssion Control实际上是一个准入控制器插件列表, 发送到API Server的请求都需要经过这个列表中的每个准入控制器插件的检查, 检查不通过, 则拒绝请求.
使用RBAC授权
RBAC (Role-Based Access Control, 基于角色的访问控制) , 允许通过Kubernetes API动态配置策略.
角色
- Role: 授权特定命名空间的访问权限
- ClusterRole: 授权所有命名空间的访问权限
角色绑定
- RoleBinding: 将角色绑定到主体 (即subject)
- ClusterRoleBinding: 将集群角色绑定到主体
主体 (subject)
- User: 用户
- Group: 用户组
- ServiceAccount: 服务账号
示例: 为 irvin 用户授权default命名空间Pod读取权限
1、用K8S CA签发客户端证书
$ cat > ca-config.json <<EOF
{
"signing": {
"default": {
"expiry": "87600h"
},
"profiles": {
"kubernetes": {
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
],
"expiry": "87600h"
}
}
}
}
EOF
$ cat > irvin-csr.json <<EOF
{
"CN": "irvin", # 用户名
"hosts": [],
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "CN",
"ST": "BeiJing",
"L": "BeiJing",
"O": "k8s",
"OU": "System"
}
]
}
EOF
$ cfssl gencert -ca=/etc/kubernetes/pki/ca.crt -ca-key=/etc/kubernetes/pki/ca.key -config=ca-config.json -profile=kubernetes irvin-csr.json | cfssljson -bare irvin
2、生成kubeconfig授权文件
# 生成kubeconfig授权文件:
$ kubectl config set-cluster kubernetes \
--certificate-authority=/etc/kubernetes/pki/ca.crt \
--embed-certs=true \
--server=https://192.168.0.10:6443 \
--kubeconfig=irvin.kubeconfig
# 设置客户端认证
$ kubectl config set-credentials irvin \
--client-key=irvin-key.pem \
--client-certificate=irvin.pem \
--embed-certs=true \
--kubeconfig=irvin.kubeconfig
# 设置默认上下文
$ kubectl config set-context kubernetes \
--cluster=kubernetes \
--user=irvin \
--kubeconfig=irvin.kubeconfig
# 设置当前使用配置
$ kubectl config use-context kubernetes --kubeconfig=irvin.kubeconfig
3、创建RBAC权限策略
创建角色 (权限集合) :
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
将 irvin 用户绑定到角色:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
- kind: User
name: irvin
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
测试:
$ kubectl --kubeconfig=irvin.kubeconfig get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7h25m
web-1 1/1 Running 0 7h25m
web-2 1/1 Running 0 7h25m
$ kubectl --kubeconfig=irvin.kubeconfig get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "irvin" cannot list resource "pods" in API group "" in the namespace "kube-system"
irvin用户只有访问default命名空间Pod读取权限.