kubernetes關於pod掛載卷的知識
首先要知道卷是pod資源的屬性,pv,pvc是單獨的資源。pod 資源的volumes屬性有多種type,其中就包含有掛載pvc的類型。這也幫我理清了之間的關系。
pv一般是系統管理員做的
pvc 是一般k8s用戶聲明的,大概意思就是說我需要一個 什么權限的,多少存儲空間的卷,然后k8s api收到這個請求就去找pv資源對象,如果兩者相匹配,那么pv就和pvc綁定了。
從這里我們也想到了,pv如果是手動創建的話,那就麻煩大了。幾個,幾十個還好說,上萬個,管理員該如何創建這么多。所以才有了動態創建pv的需求。這就引出另外一個資源 storageClass ,這個資源聲明后端掛載什么樣的存儲服務,比如nfs,chef等,有了這個一般用戶在定義pvc的時候,在提出存儲空間和讀寫權限的同時聲明用那個storageClass了,
如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata: mysql-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: managed-nfs-storage (注意這里)
resources:
requests:
storage: 5Gi
以上便是pvc使用storageClass資源對象創建pv,pvc的綁定了
api收到對storageClass聲明后,就會調用這個storageClass的生產者進行生產。
我們就會想到,在創建storageClass資源對象的時候肯定會指定生產者。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: example-nfs
provisioner: example.com/nfs 這里指定生產者,其中example.com/nfs只是一個標簽,你在deploymend里定義什么這里就寫什么。
mountOptions:
- vers=4.1
那么問題又來了。在k8s中生產者組件不支持nfs,所以才會安裝第三方組件。第三方組件的安裝就需要創建相關的rbac的角色和賬戶。第三方組件是用deploymend的資源托管相關組件pod的。
那么通過deploy部署的pod怎么就是provisioner了?這個我不清楚,后面學習后在總結吧。
回到正題上。
volumes 的6種
1. emptyDir
2. gitRepo
3. hostpath
4.PersistentVolumeClaim
5.configMap,secret
6. 各種雲平台的存儲磁盤卷如google的gce,aws的ebs,azure的azureDisk
其實4只是一個概括,nfs,chef 這些網絡存儲通通可以單獨來使用。但我覺得實際使用中還是講這些網絡存儲轉化成pv,pvc
從簡單開始學習
emptyDir 兩種應用場景: 1. 同一個pod中,各個容器間共享文件。 2. 當程序對大數據處理時內存空間不夠時臨時寫入文件(當然也可以使用宿主主機的內存)
例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPaht: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {} (為{}表示使用節點服務器的文件系統)
- name: html-2
emptyDir:
medium: Memory (使用節點服務器的內存)
以上就是emptyDir的用法。gitRepo其實是依賴於emptyDir的,你可以把它理解成,聲明了一個emptyDir然后在把gitrepo下載填充到emptyDir
例子:
apiVersion: v1
kind: Pod
metadata:
name: gitrepo-volume-pod
spec:
containers:
- image: nginx: alpine
name: web-nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
repository: https://github.com/luksa/kubia-website-example.git
revision: master
directory: . (這個.很重要,表示在當前emptyDir目錄下不然就會創建一個kubia-website-example目錄)
以上就是gitRepo的用法,其中有個問題就是pod中的gitrepo並不會及時更新。如果想要及時更新需要用到sidecar ,加入到pod中,在Docker Hub搜索 “git ryc”獲取相關鏡像。
還有一個情況不得不使用sidecar container 。gitrepo 不支持連接私有庫,也就是不能是ssh密鑰連接,也不可以有用戶名和密碼。這時候就需要使用支持驗證連接的sidecar container來實現了。具體怎么使用,用到的時候在研究吧.
至此gitRepo卷類型算是簡單介紹了,下面學習hostpath
大多數時候pod應該忽略他們的主機節點,因此他們也不需要訪問節點文件系統上的文件。但是某些系統級別的pod(通常由DaemonSet管理)確實需要讀取節點的文件或使用節點文件系統來訪問節設備,這時候就需要用到hostPath
hostPath 算是第一個持久化存儲卷了,但是這個很少用到,因為這個卷只能在某一單一節點上,pod重啟后很可能在另外一個節點上。當然可以使用NodeSelector但這樣看起來也不高明。所以建議使用網絡存儲。hostPath過
接下來是網絡存儲和雲平台提供的存儲磁盤卷。這兩種在用到的時候找相關的屬性進行配置即可。也沒什么要注意的,實際應用場景用到最多的持久存儲是pv,pvc,storageClass
configmap
kubectl create configmap fortune-config --from-literal=sleep-interval=25
一般configmap包含多個映射條目所以創建時可以多次使用--from-literal參數
kubectl create conigmap fortune-config --from-literal=foo=bar --from-literal=bar=baz --from-literal=one=two
kubectl get configmap fortune-config -o yaml
configmap同樣可以存儲粗力度的配置數據,比如完整的配置文件。kubectl create configmap 命令支持從硬盤上讀取文件,並將文件內容單獨存儲為ConfigMap中的條目:
kubectl create configmap my-config --from-file=config-file.conf
運行此命令,kubectl 會在當前目錄下找config-file.conf文件,並將文件內容存儲在configmap中以config-file.conf 為鍵名的條目下。當然也可以手動指定鍵名
kubectl create configmap my-config --from-file=customkey=config-file.conf
kubectl create configmap my-config --from-file=/path/to/dir
這時候,kubectl會為文件中每個文件單獨創建條目,僅限文件名可作為合法ConfigMap鍵名的文件。
當然也可以將上面的參數混合使用
configmap設置完成了,如何將映射的值傳遞給pod的容器?三種方法
一,設置環境變量,例子如下:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
很好奇當pod 容器中引用不存在的configmap會發生什么?
pod會嘗試運行所有容器,只有這個容器啟動失敗,其他正常(假如有多個容器),當你事后創建處這個configmap,無需重啟pod,容器就會成功。當然也可以引用為可選的,設置configMapKeyRef.optional: true即可,這樣即使ConfigMap不存在,容器也能正常啟動。
如果configmap中條目很多,用env屬性列出麻煩且容易出錯。那么有沒有辦法一次導入呢。用envFrom, 例如configmap中有三個條目FOO、BAR和FOO-BAR
spec:
containers:
- image: some-image
envFrom:
- prefix: CONFIG_ 所有環境變量均包含前綴CONFIG_ ,不設置就將引用configmap中的鍵名
configMapRef:
name: my-config-map
如此,容器中多出兩個變量 CONFIG_FOO 、CONFIG_BAR
為什么是兩個,因為另外一個FOO-BAR包含破折號,不是一個合法的環境變量名稱。被忽略了,所以我們應該注意創建configmap時 不要使用-
上面我們學習了如何將configmap中的條目以變量的形式傳入到容器中
那么如何將configmap中的條目作為容器運行的參數args呢?例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune-env-from-configmap
spec:
containers:
- image: luksa/fortune:env
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
args: ["$(INTERVAL)"]
環境變量和參數都適合於變量值較短的場景。configmap是可以包含完整配置文件內容的,當你想要將其暴露給容器時,可以使用上一章中提到的卷的類型。configmap卷會將ConfigMap中的每個條目均暴露成一個文件。運行在容器的進程通過讀取文件內容獲得對應的條目值。
configmap-files為一個目錄
kubectl create configmap fortune-config --from-file=configmap-files
例子:
apiVersion: v1
kind: Pod
metadata:
name: fortune-configmap-volume
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
...
- name: config
mountPaht: /etc/nginx/config.d
readOnly: true
....
volumes:
...
- name: config
configMap:
name: fortune-config
...
一種特殊情況,當你只想暴露configmap-files目錄下的某一個配置文件,該如何做:
volumes:
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: gzip.conf
這樣配置后,掛載fortune-config 卷后,就只有my-nginx-config.conf 並且掛載后的名稱為gzip.conf
另一種特殊情況,我們通過上述掛載configmap卷后會發現,被掛載的目錄之前的文件都被隱藏掉了。那么如果你需求不想隱藏之前的文件,該如何做:
spec:
containers:
- image: some/image
volumeMounts:
- name: myvolume
mountPath: /etc/someconfig.conf 掛載到指定的某一個文件,而不是某個文件夾
subPath: myconfig.conf 掛載指定的條目,而不是完整的卷
為configMap卷中的文件設置權限
volumes:
- name: config
configmap:
name: fortune-config
defaultMode: "6600"
更新應用配置且不重啟應用程序
使用環境變量或命令行參數作為配置源的弊端在於無法在進程運行時更新配置。將ConfigMap暴露為卷可以達到配置熱更新的效果,無需重新創建pod或重啟容器。
ConfigMap被更新后,卷中引用它的文件也會相應更新,進程發現文件被改變后進行重載。kubernetes同樣支持文件更新后手動通知容器。但要注意的是,更新configmap之后對應文件的更新會相當耗時。
我們使用kubectl edit configmap fortune-config 來更改某一個值。
然后執行
kubectl exec fortune-configmap-volume -c web-server -- cat /etc/nginx/config.d/my-nginx-config.conf
查看文件是不是被修改了,如果沒有稍等一會再查看。在確認更改后,我們來手動通知容器進行重載。
kubectl exec fortune-configmap-volume -c web-server -- nginx -s reload
你可能會疑惑 Kubernetes更新完configmap卷中的所有文件之前(所有的文件被更新而不是一部分),應用是否會監聽到文件編號並主動進行重載。答案是不會,會在所有的文件更新后,一次性更新到pod容器中。kubernetes通過富豪鏈接做到這一點的。
kubectl exec -ti fortune-configmap-volume -c web-server -- ls -lA /etc/nginx/config.d
..4989_09_04_15_06.028485
..data -> ..4989_09_04_15_06.028485
my-nginx-config.conf -> ..data/my-nginx-config.conf
sleep-interval -> ..data/sleep-interval
可以看到,被掛載configMap卷中的文件是..data文件夾中文件的符號鏈接,而..data又是連接到 ..4989的符號鏈接,每當ConfigMap被更新后,Kubernetes 會創建一個這樣的文件夾,卸任所有文件並重新將符號..data鏈接至新文件夾,通過這種方式可以一次性修改所有文件。
如果掛載configmap中的某一個文件,而不是文件夾,configmap更新之后對應的文件不會被更新。如果現在你需要掛載單個文件且在修改Configmap后想自動更新,可以將該卷掛載到其他文件夾,然后做一個軟鏈接。
至於如何實現應用重載配置 ,需要應用自己實現了。
configmap都是以名文存儲的,有些信息比較敏感,就需要用秘文存儲了。
這就需要使用kubernetes 的secret資源了。
首先有一個情況我們需要了解
使用kubectl describe pod pod-id 查看任何一個pod你都會發現默認有掛載一個secret卷。
Volumes: default-token-ps7ff: Type: Secret (a volume populated by a Secret) SecretName: default-token-ps7ff Optional: false 這個卷里的內容我們可以使用kubectl describe secrets查看 # kubectl describe secrets default-token-ps7ff Name: default-token-ps7ff Namespace: default Labels: <none> Annotations: kubernetes.io/service-account.name: default kubernetes.io/service-account.uid: 6efa7f7c-6a61-11e9-bfdb-0a382f97318e Type: kubernetes.io/service-account-token Data ==== ca.crt: 1359 bytes namespace: 7 bytes token: eyJhbGciOiJSUzI1NiIsImtp...
可以看出這個Secret包含是哪個條目ca.crt 、namespace與token
包含了從pod內部安全訪問Kubernetes API服務器所需的全部信息。盡管我們希望做到應用對kubernetes無感知,但是直連Kubernetes沒有其他方法,你只能使用secret卷提供的文件來訪問kubernetes API。
使用kubectl describe pod命令顯示secret卷北掛載的位置:
mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-ps7ff (ro)
注意: default-token Secret默認會被掛載至每個容器。可以通過設置pod定義中的automountServiceAccountToken字段為false,或者設置pod使用的服務賬戶中的相同字段為false來關閉這種默認行為。
查看容器中掛載了哪些條目
# kubectl exec nginx-dnm9n -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token
創建Secret
openssl genrsa -out https.key 2048
openssl req -new -x509 -key https.key -out https.cert -days 3650 -subj /CN=www.kubia-example.com
# kubectl create secret generic fortune-https --from-file=https.key --from-file=https.cert --from-file=foo
secret/fortune-https created
也可以使用 --from-file=fortune-https 囊括這個文件夾中的文件
# kubectl get secret fortune-https -o yaml
# kubectl describe secret fortune-https
對比configmap與secret
secret與configMap有比較大的區別,這也是為何kubernetes開發者們在支持了Secret一段時間之后會選擇創建ConfigMap。創建的Secret的YAML格式定義顯示
# kubectl get secret fortune-https -o yaml
apiVersion: v1
data:
foo: YmFyCg==
https.cert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURFekNDQWZ1Z0F3SUJBZ0lKQU96Y00rNzI3RWJHTUEwR0NTcUdTSWIzRFFFQkN3VUFNQ0F4SGpBY0JnTlYKQkF
https.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBeFYvUVJiazJiRm8zRmdZdWpaTWxPVGg3MUxpY3AyUS9pL2pib2E1SExlUlpSTDBi
kind: Secret
. . .
將其與CoinfigMap的Yaml格式定義做對比
apiVersion: v1
data:
bar: baz
foo: bar
one: two
kind: ConfigMap
注意到Secret條目的內容會被以Base64格式編譯,而ConfigMap直接以純文本展示。這種區別導致在處理YAML和JSON格式的Secret時會稍許有些麻煩,需要在設置和讀取相關條目時對內容進行編解碼。
這個具體使用中是這樣的,
比如你現在想把一個配置文件加入到Secret中,那么你首先將配置文件中的內容通過BASE64進行編碼后才能作為條目。
當然你會問難道kubernetes不提供base64編碼?提供,只能對字符串,不能接受文件。如下:
kund: Secret
apiVersion: v1
stringData:
foo: plain text
data:
https.cert: lksjkaldjldasjladgjsjl...
https.key: lsiodjsdlfjahcdo...
創建后使用kubectl get secret -o yaml會看到stringData字段中的所有條目會被Base64編碼后展示在data字段下。所以stringData字段是只寫不可讀的。
如何在pod中使用Secret
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: luksa/fortune:env
name: html-generator
env:
- name: INTERVAL
valueFrom:
configMapKeyRef:
name: fortune-config
key: sleep-interval
volumeMounts:
- name: html
mountPaht: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
- name: config
mountPath: /etc/nginx/conf.d
readOnly: true
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: html
emptyDir: {}
- name: config
configmap:
name: fortune-config
items:
- key: my-nginx-config.conf
path: https.conf
- name: certs
secret:
secretname: fortune-https
簡單點的實例:
apiVersion: v1
kind: Pod
metadata:
name: fortune-https
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: certs
mountPath: /etc/nginx/certs/
readOnly: true
ports:
- containerPort: 80
- containerPort: 443
volumes:
- name: certs
secret:
secretName: fortune-https
當然也可以將secret條目暴露為環境變量。但不建議這么做,應用通常會在錯誤報告時轉儲環境變量,或者是啟動時打印在應用日志中,無意中就暴露Secret信息。另外,子進程會繼承父進程的所有環境變量,如果是通過第三方二進制程序啟動應用,你並不知道它使用敏感數據做了什么。所以不建議用環境變量,建議使用secret卷的形式掛載到pod.
env:
- name: FOO_SECRET
valueFrom:
secretKeyRef:
name: fortune-https
key: foo
學會了secret,接下來就有一個比較常用的secret實際應用,dockerhub
kubectl create secret docker-registry mydockerhubsecret --docker-username=myusername --docker-password=mypassword --docker-email=
my.email@provider.com
使用:
apiVersion: v1
kind: Pod
metadata:
name: private-pod
spec:
imagePullSecrets:
- name: mydockerhubsecret
containers:
- image: username/private:tag
name: main
假設系統通常運行大量Pod,你可能會好奇是否需要為每個Pod都添加相同的鏡像拉取Secret.並不是,可以通過添加Secret至ServiceAccount 使所有pod都能自動添加上鏡像拉取的Secret.