文章目錄
回顧
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讀取權限.