容器化技术(No.5) -- Kubernetes 进阶(三)Pod 数据持久化, 有状态应用部署及权限控制

回顾

容器化技术(No.4) – Kubernetes 进阶(二)

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中容器需要访问宿主机文件

ValueBehavior
Empty string (default) is for backward compatibility, which means that no checks will be performed before mounting the hostPath volume.
DirectoryOrCreateIf 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.
DirectoryA directory must exist at the given path
FileOrCreateIf 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.
FileA file must exist at the given path
SocketA UNIX socket must exist at the given path
CharDeviceA character device must exist at the given path
BlockDeviceA 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 启动顺序和唯一性

    1. 稳定, 唯一的网络标识符, 持久存储
    2. 有序, 优雅的部署和扩展、删除和终止
    3. 有序, 滚动更新

应用场景: 数据库, 分布式应用

稳定的网络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 DomainService (ns/name)StatefulSet (ns/name)StatefulSet DomainPod DNSPod Hostname
cluster.localdefault/nginxdefault/webnginx.default.svc.cluster.localweb-{0…N-1}.nginx.default.svc.cluster.localweb-{0…N-1}
cluster.localfoo/nginxfoo/webnginx.foo.svc.cluster.localweb-{0…N-1}.nginx.foo.svc.cluster.localweb-{0…N-1}
kube.localfoo/nginxfoo/webnginx.foo.svc.kube.localweb-{0…N-1}.nginx.foo.svc.kube.localweb-{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资源要经过以下三关才可以:

  1. Authentication (鉴权)
  2. Authorization (授权)
  3. 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读取权限.

总结

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值