容器化技術(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中容器需要訪問宿主機文件

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 啓動順序和唯一性

    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 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資源要經過以下三關纔可以:

  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讀取權限.

總結

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章