在 Docker 的設(shè)計(jì)中,容器內(nèi)的文件是臨時(shí)存放的爵川,并且隨著容器的刪除棋枕,容器內(nèi)部的數(shù)據(jù)也會(huì)一同被清空白修。不過,我們可以通過在 docker run 啟動(dòng)容器時(shí)重斑,使用 --volume/-v 參數(shù)來指定掛載卷兵睛,這樣就能夠?qū)⑷萜鲀?nèi)部的路徑掛載到主機(jī),后續(xù)在容器內(nèi)部存放數(shù)據(jù)時(shí)會(huì)就被同步到被掛載的主機(jī)路徑中。這樣做可以保證保證即便容器被刪除祖很,保存到主機(jī)路徑中的數(shù)據(jù)也仍然存在笛丙。
與 Docker 通過掛載卷的方式就可以解決持久化存儲(chǔ)問題不同,K8s 存儲(chǔ)要面臨的問題要復(fù)雜的多假颇。因?yàn)?K8s 通常會(huì)在多個(gè)主機(jī)部署節(jié)點(diǎn)胚鸯,如果 K8s 編排的 Docker 容器崩潰,K8s 可能會(huì)在其他節(jié)點(diǎn)上重新拉起容器笨鸡,這就導(dǎo)致原來節(jié)點(diǎn)主機(jī)上掛載的容器目錄無法使用姜钳。
當(dāng)然也是有辦法解決 K8s 容器存儲(chǔ)的諸多限制,比如可以對(duì)存儲(chǔ)資源做一層抽象形耗,通常大家將這層抽象稱為卷(Volume)哥桥。
K8s 支持的卷基本上可以分為三類:配置信息、臨時(shí)存儲(chǔ)激涤、持久存儲(chǔ)拟糕。
配置信息
無論何種類型的應(yīng)用,都會(huì)用到配置文件或啟動(dòng)參數(shù)倦踢。而 K8s 將配置信息進(jìn)行了抽象已卸,定義成了幾種資源,主要有以下三種:
ConfigMap
Secret
DownwardAPI
ConfigMap
ConfigMap 卷通常以一個(gè)或多個(gè) key: value 形式存在硼一,主要用來保存應(yīng)用的配置數(shù)據(jù)累澡。其中 value 可以是字面量或配置文件。
不過般贼,因?yàn)镃onfigMap 在設(shè)計(jì)上不是用來保存大量數(shù)據(jù)的愧哟,所以在 ConfigMap 中保存的數(shù)據(jù)不可超過 1 MiB(兆字節(jié))。
ConfigMap 有兩種創(chuàng)建方式:
通過命令行創(chuàng)建
通過 yaml 文件創(chuàng)建
通過命令行創(chuàng)建
在創(chuàng)建 Configmap 的時(shí)候可以通過 --from-literal 參數(shù)來指定 key: value哼蛆,以下示例中 foo=bar 即為字面量形式蕊梧,bar=bar.txt 為配置文件形式。
$ kubectl create configmap c1 --from-literal=foo=bar --from-literal=bar=bar.txt
bar.txt 內(nèi)容如下:
baz
通過 kubectl describe 命令查看新創(chuàng)建的名稱為 c1 的這個(gè) Configmap 資源內(nèi)容腮介。
$ kubectl describe configmap c1
Name: c1
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
bar:
----
baz
foo:
----
bar
Events: <none>
通過 yaml 文件創(chuàng)建
創(chuàng)建 configmap-demo.yaml 內(nèi)容如下:
kind: ConfigMap
apiVersion: v1
metadata:
name: c2
namespace: default
data:
foo: bar
bar: baz
通過 kubectl apply 命令應(yīng)用這個(gè)文件肥矢。
$ kubectl apply -f configmap-demo.yaml
$ kubectl get configmap c2
NAME DATA AGE
c2 2 11s
$ kubectl describe configmap c2
Name: c2
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
foo:
----
bar
bar:
----
baz
Events: <none>
得到的結(jié)果跟通過命令行方式創(chuàng)建的 Configmap 沒什么兩樣。
使用示例
完成 Configmap 創(chuàng)建后叠洗,來看下如何使用甘改。
創(chuàng)建好的Configmap 有兩種使用方法:
通過環(huán)境變量將 Configmap 注入到容器內(nèi)部
通過卷掛載的方式直接將 Configmap 以文件形式掛載到容器。
通過環(huán)境變量方式引用
創(chuàng)建 use-configmap-env-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: "use-configmap-env"
namespace: default
spec:
containers:
- name: use-configmap-env
image: "alpine"
# 一次引用單個(gè)值
env:
- name: FOO
valueFrom:
configMapKeyRef:
name: c2
key: foo
# 一次引用所有值
envFrom:
- prefix: CONFIG_ # 配置引用前綴
configMapRef:
name: c2
command: ["echo", "$(FOO)", "$(CONFIG_bar)"]
可以看到我們創(chuàng)建了一個(gè)名為 use-configmap-env 的 Pod灭抑,Pod 的容器將使用兩種方式引用 Configmap 的內(nèi)容十艾。
第一種是指定 spec.containers.env,它可以為容器引用一個(gè) Configmap 的 key: value 對(duì)腾节。其中valueFrom. configMapKeyRef 表明我們要引用 Configmap 忘嫉,Configmap 的名稱為 c2 荤牍,引用的 key 為 foo 。
第二種是指定 spec.containers.envFrom 庆冕,只需要通過 configMapRef.name 指定 Configmap 的名稱康吵,它就可以一次將 Configmap 中的所有 key: value 傳遞給容器。其中 prefix 可以給引用的 key 前面增加統(tǒng)一前綴访递。
Pod 的容器啟動(dòng)命令為 echo (CONFIG_bar) 晦嵌,可以分別打印通過 env 和 envFrom 兩種方式引用的 Configmap 的內(nèi)容。
# 創(chuàng)建 Pod
$ kubectl apply -f use-configmap-env-demo.yaml
# 通過查看 Pod 日志來觀察容器內(nèi)部引用 Configmap 結(jié)果
$ kubectl logs use-configmap-env
bar baz
結(jié)果表明力九,容器內(nèi)部可以通過環(huán)境變量的方式引用到 Configmap 的內(nèi)容。
通過卷掛載方式引用
創(chuàng)建 use-configmap-volume-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: "use-configmap-volume"
namespace: default
spec:
containers:
- name: use-configmap-volume
image: "alpine"
command: ["sleep", "3600"]
volumeMounts:
- name: configmap-volume
mountPath: /usr/share/tmp # 容器掛載目錄
volumes:
- name: configmap-volume
configMap:
name: c2
這里創(chuàng)建一個(gè)名為 use-configmap-volume 的 Pod邑闺。通過 spec.containers.volumeMounts 指定容器掛載跌前,name 指定掛載的卷名,mountPath 指定容器內(nèi)部掛載路徑(也就是將 Configmap 掛載到容器內(nèi)部的指定目錄下)陡舅。spec.volumes 聲明一個(gè)卷抵乓,而configMap.name 表明了這個(gè)卷要引用的 Configmap 名稱。
然后可通過如下命令創(chuàng)建 Pod 并驗(yàn)證容器內(nèi)部能否引用到 Configmap靶衍。
# 創(chuàng)建 Pod
$ kubectl apply -f use-configmap-volume-demo.yaml
# 進(jìn)入 Pod 容器內(nèi)部
$ kubectl exec -it use-configmap-volume -- sh
# 進(jìn)入容器掛載目錄
/ # cd /usr/share/tmp/
# 查看掛載目錄下的文件
/usr/share/tmp # ls
bar foo
# 查看文件內(nèi)容
/usr/share/tmp # cat foo
bar
/usr/share/tmp # cat bar
baz
創(chuàng)建完成后灾炭,通過 kubectl exec 命令可以進(jìn)入容器內(nèi)部。查看容器 /usr/share/tmp/ 目錄颅眶,可以看到兩個(gè)以 Configmap 中 key 為名稱的文本文件(foo 蜈出、bar), key 所對(duì)應(yīng)的 value 內(nèi)容即為文件內(nèi)容涛酗。
以上就是兩種將 Configmap 的內(nèi)容注入到容器內(nèi)部的方式铡原。容器內(nèi)部的應(yīng)用則可以分別通過讀取環(huán)境變量、文件內(nèi)容的方式使用配置信息商叹。
Secret
熟悉了 Configmap 的用法燕刻,接下來看下 Secret 的使用。Secret 卷用來給 Pod 傳遞敏感信息剖笙,例如密碼卵洗、SSH 密鑰等。因?yàn)殡m然Secret 與 ConfigMap 非常類似弥咪,但是它會(huì)對(duì)存儲(chǔ)的數(shù)據(jù)進(jìn)行 base64 編碼过蹂。
Secret 同樣有兩種創(chuàng)建方式:
通過命令行創(chuàng)建
通過 yaml 文件創(chuàng)建
通過命令行創(chuàng)建
Secret 除了通過 --from-literal 參數(shù)來指定 key: value,還有另一種方式聚至。即通過 --form-file 參數(shù)直接從文件加載配置榴啸,文件名即為 key,文件內(nèi)容作為 value晚岭。
# generic 參數(shù)對(duì)應(yīng) Opaque 類型鸥印,既用戶定義的任意數(shù)據(jù)
$ kubectl create secret generic s1 --from-file=foo.txt
foo.txt 內(nèi)容如下:
foo=bar
bar=baz
可以看到與 Configmap 不同勋功,創(chuàng)建 Secret 需要指明類型。上面的示例為命令通過指定 generic 參數(shù)來創(chuàng)建類型為 Opaque 的 Secret 库说,這也是 Secret 默認(rèn)類型狂鞋。需要注意的是除去默認(rèn)類型,Secret 還支持其他類型潜的,可以通過官方文檔查看骚揍。不過初期學(xué)習(xí)階段只使用默認(rèn)類型即可,通過默認(rèn)類型就能夠?qū)崿F(xiàn)其他幾種類型的功能
$ kubectl describe secret s1
Name: s1
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
foo.txt: 16 bytes
另外一點(diǎn)與 Configmap 不同的是啰挪,Secret 僅展示 value 的字節(jié)大小信不,而不直接展示數(shù)據(jù),這是為了保存密文亡呵,也是Secret 名稱的含義抽活。
通過 yaml 文件創(chuàng)建
創(chuàng)建 secret-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Secret
metadata:
name: s2
namespace: default
type: Opaque # 默認(rèn)類型
data:
user: cm9vdAo=
password: MTIzNDU2Cg==
通過 kubectl apply 命令應(yīng)用這個(gè)文件。
$ kubectl apply -f secret-demo.yaml
$ kubectl get secret s2
NAME TYPE DATA AGE
s2 Opaque 2 59s
$ kubectl describe secret s2
Name: s2
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 7 bytes
user: 5 bytes
同樣能夠正確創(chuàng)建出 Secret 資源锰什。但是可以看到通過 yaml 文件創(chuàng)建 Secret 時(shí)下硕,指定的 data 內(nèi)容必須經(jīng)過 base64 編碼,比如我們指定的 user 和 password 都是編碼后的結(jié)果汁胆。
data:
user: cm9vdAo=
password: MTIzNDU2Cg==
除此外也可以使用原始字符串方式梭姓,這兩種方式是等價(jià),示例如下:
data:
stringData:
user: root
password: "123456"
相對(duì)而言嫩码,我更推薦使用 base64 編碼的方式誉尖。
使用示例
同 Configmap 使用方式一樣,我們也可以通過環(huán)境變量或卷掛載的方式來使用 Secret 铸题。以卷掛載方式為例释牺。首先創(chuàng)建 use-secret-volume-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: "use-secret-volume-demo"
namespace: default
spec:
containers:
- name: use-secret-volume-demo
image: "alpine"
command: ["sleep", "3600"]
volumeMounts:
- name: secret-volume
mountPath: /usr/share/tmp # 容器掛載目錄
volumes:
- name: secret-volume
secret:
secretName: s2
即創(chuàng)建一個(gè)名為 use-secret-volume-demo 的 Pod,而 Pod 的容器通過卷掛載方式引用 Secret 的內(nèi)容回挽。
# 創(chuàng)建 Pod
$ kubectl apply -f use-secret-volume-demo.yaml
# 進(jìn)入 Pod 容器內(nèi)部
$ kubectl exec -it use-secret-volume-demo -- sh
# 進(jìn)入容器掛載目錄
/ # cd /usr/share/tmp/
# 查看掛載目錄下的文件
/usr/share/tmp # ls
password user
# 查看文件內(nèi)容
/usr/share/tmp # cat password
123456
/usr/share/tmp # cat user
root
可以發(fā)現(xiàn)被掛載到容器內(nèi)部以后没咙,Secret 的內(nèi)容將變成明文存儲(chǔ)。容器內(nèi)部應(yīng)用可以同使用 Configmap 一樣來使用 Secret 千劈。
作為可以存儲(chǔ)配置信息的 Configmap 和 Secret 祭刚, Configmap 通常存放普通配置, Secret 則存放敏感配置墙牌。
值得一提的是涡驮,使用環(huán)境變量方式引用 Configmap 或 Secret ,當(dāng) Configmap 或 Secret 內(nèi)容變更時(shí)喜滨,容器內(nèi)部引用的內(nèi)容不會(huì)自動(dòng)更新捉捅;使用卷掛載方式引用 Configmap 或 Secret ,當(dāng) Configmap 或 Secret 內(nèi)容變更時(shí)虽风,容器內(nèi)部引用的內(nèi)容會(huì)自動(dòng)更新棒口。如果容器內(nèi)部應(yīng)用支持配置文件熱加載寄月,那么通過卷掛載對(duì)的方式引用 Configmap 或 Secret 內(nèi)容將是推薦方式。
DownwardAPI
DownwardAPI 可以將 Pod 對(duì)象自身的信息注入到 Pod 所管理的容器內(nèi)部无牵。
使用示例
創(chuàng)建 downwardapi-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: downwardapi-volume-demo
labels:
app: downwardapi-volume-demo
annotations:
foo: bar
spec:
containers:
- name: downwardapi-volume-demo
image: alpine
command: ["sleep", "3600"]
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
volumes:
- name: podinfo
downwardAPI:
items:
# 指定引用的 labels
- path: "labels"
fieldRef:
fieldPath: metadata.labels
# 指定引用的 annotations
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
# 創(chuàng)建 Pod
$ kubectl apply -f downwardapi-demo.yaml
pod/downwardapi-volume-demo created
# 進(jìn)入 Pod 容器內(nèi)部
$ kubectl exec -it downwardapi-volume-demo -- sh
# 進(jìn)入容器掛載目錄
/ # cd /etc/podinfo/
# 查看掛載目錄下的文件
/etc/podinfo # ls
annotations labels
# 查看文件內(nèi)容
/etc/podinfo # cat annotations
foo="bar"
kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{\"foo\":\"bar\"},\"labels\":{\"app\":\"downwardapi-volume-demo\"},\"name\":\"downwardapi-volume-demo\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"command\":[\"sleep\",\"3600\"],\"image\":\"alpine\",\"name\":\"downwardapi-volume-demo\",\"volumeMounts\":[{\"mountPath\":\"/etc/podinfo\",\"name\":\"podinfo\"}]}],\"volumes\":[{\"downwardAPI\":{\"items\":[{\"fieldRef\":{\"fieldPath\":\"metadata.labels\"},\"path\":\"labels\"},{\"fieldRef\":{\"fieldPath\":\"metadata.annotations\"},\"path\":\"annotations\"}]},\"name\":\"podinfo\"}]}}\n"
kubernetes.io/config.seen="2022-03-12T13:06:50.766902000Z"
/etc/podinfo # cat labels
app="downwardapi-volume-demo"
不難發(fā)現(xiàn)漾肮,DownwardAPI 的使用方式同 Configmap 和 Secret 一樣,都可以通過卷掛載方式掛載到容器內(nèi)部以后茎毁,在容器掛載的目錄下生成對(duì)應(yīng)文件克懊,用來存儲(chǔ) key: value。不同的是 七蜘,因?yàn)镈ownwardAPI能引用的內(nèi)容已經(jīng)都在當(dāng)前 yaml 文件中定義好了谭溉,所以DownwardAPI 不需要預(yù)先定義,可以直接使用橡卤。
小結(jié)
ConfigMap 扮念、Secret 、DownwardAPI 這三種 Volume 存在的意義不是為了保存容器中的數(shù)據(jù)蒜魄,而是為了給容器傳遞預(yù)先定義好的數(shù)據(jù)扔亥。
臨時(shí)卷
接下來我們要關(guān)注的是臨時(shí)卷场躯,即臨時(shí)存儲(chǔ)谈为。K8s 支持的臨時(shí)存儲(chǔ)中最常用的就是如下兩種:
EmptyDir
HostPath
臨時(shí)存儲(chǔ)卷會(huì)遵從 Pod 的生命周期,與 Pod 一起創(chuàng)建和刪除踢关。
EmptyDir
先來看 emptyDir 如何使用伞鲫。EmptyDir 相當(dāng)于通過 --volume/-v 掛載時(shí)的隱式 Volume 形式使用 Docker。K8s 會(huì)在宿主機(jī)上創(chuàng)建一個(gè)臨時(shí)目錄签舞,被掛載到容器所聲明的 mountPath 目錄上秕脓,即不顯式的聲明在宿主機(jī)上的目錄。
使用示例
創(chuàng)建 emptydir-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: "emptydir-nginx-pod"
namespace: default
labels:
app: "emptydir-nginx-pod"
spec:
containers:
- name: html-generator
image: "alpine:latest"
command: ["sh", "-c"]
args:
- while true; do
date > /usr/share/index.html;
sleep 1;
done
volumeMounts:
- name: html
mountPath: /usr/share
- name: nginx
image: "nginx:latest"
ports:
- containerPort: 80
name: http
volumeMounts:
- name: html
# nginx 容器 index.html 文件所在目錄
mountPath: /usr/share/nginx/html
readOnly: true
volumes:
- name: html
emptyDir: {}
這里創(chuàng)建一個(gè)名為 emptydir-nginx-pod 的 Pod儒搭,它包含兩個(gè)容器 html-generator 和 nginx 吠架。html-generator 用來不停的生成 html 文件,nginx 則是用來展示 html-generator 生成的 index.html 文件的 Web 服務(wù)搂鲫。
具體流程為傍药,html-generator 不停的將當(dāng)前時(shí)間寫入到 /usr/share/index.html 下,并將 /usr/share 目錄掛載到名為 html 的卷中魂仍,而 nginx 容器則將 /usr/share/nginx/html 目錄掛載到名為 html 的卷中拐辽,這樣兩個(gè)容器通過同一個(gè)卷 html 掛載到了一起。
現(xiàn)在通過 kubectl apply 命令應(yīng)用這個(gè)文件:
# 創(chuàng)建 Pod
$ kubectl apply -f emptydir-demo.yaml
pod/emptydir-nginx-pod created
# 進(jìn)入 Pod 容器內(nèi)部
$ kubectl exec -it pod/emptydir-nginx-pod -- sh
# 查看系統(tǒng)時(shí)區(qū)
/ # curl 127.0.0.1
Sun Mar 13 08:40:01 UTC 2022
/ # curl 127.0.0.1
Sun Mar 13 08:40:04 UTC 2022
根據(jù) nginx 容器內(nèi)部 curl 127.0.0.1 命令輸出結(jié)果可以發(fā)現(xiàn)擦酌,nginx 容器 /usr/share/nginx/html/indedx.html 文件內(nèi)容即為 html-generator 容器 /usr/share/index.html 文件內(nèi)容俱诸。
能夠?qū)崿F(xiàn)此效果的原理是,當(dāng)我們聲明卷類型為 emptyDir: {} 后赊舶,K8s 會(huì)自動(dòng)在主機(jī)目錄上創(chuàng)建一個(gè)臨時(shí)目錄睁搭。然后將 html-generator 容器 /usr/share/ 目錄和 nginx 容器 /usr/share/nginx/html/ 同時(shí)掛載到這個(gè)臨時(shí)目錄上赶诊。這樣兩個(gè)容器的目錄就能夠?qū)崿F(xiàn)數(shù)據(jù)同步。
需要注意的是介袜,容器崩潰并不會(huì)導(dǎo)致 Pod 被從節(jié)點(diǎn)上移除甫何,因此容器崩潰期間 emptyDir 卷中的數(shù)據(jù)是安全的。另外遇伞,emptyDir.medium 除了可以設(shè)成 {}辙喂,還可以設(shè)成 Memory 表示內(nèi)存掛載。
HostPath
與 emptyDir 不同鸠珠,hostPath 卷能將主機(jī)節(jié)點(diǎn)文件系統(tǒng)上的文件或目錄掛載到指定的 Pod 中巍耗。并且當(dāng) Pod 刪除時(shí),與之綁定的 hostPath 并不會(huì)隨之刪除渐排。新創(chuàng)建的 Pod掛載到上一個(gè) Pod 使用過的 hostPath時(shí)炬太,原 hostPath 中的內(nèi)容仍然存在花吟。但這僅限于新的 Pod 和已經(jīng)刪除的 Pod 被調(diào)度到同一節(jié)點(diǎn)上奉瘤,所以嚴(yán)格來講 hostPath 仍然屬于臨時(shí)存儲(chǔ)。
hostPath 卷的典型應(yīng)用是將主機(jī)節(jié)點(diǎn)上的時(shí)區(qū)通過卷掛載的方式注入到容器內(nèi)部拂蝎, 進(jìn)而保證啟動(dòng)的容器和主機(jī)節(jié)點(diǎn)時(shí)間同步可缚。
使用示例
創(chuàng)建 hostpath-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: "hostpath-volume-pod"
namespace: default
labels:
app: "hostpath-volume-pod"
spec:
containers:
- name: hostpath-volume-container
image: "alpine:latest"
command: ["sleep", "3600"]
volumeMounts:
- name: localtime
mountPath: /etc/localtime
volumes:
- name: localtime
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
要實(shí)現(xiàn)時(shí)間同步霎迫,只需要將主機(jī)目錄 /usr/share/zoneinfo/Asia/Shanghai 通過卷掛載的方式掛載到容器內(nèi)部的 /etc/localtime 目錄即可。
可以使用 kubectl apply 命令應(yīng)用這個(gè)文件帘靡,然后進(jìn)入 Pod 容器內(nèi)部使用 date 命令查看容器當(dāng)前時(shí)間知给。
# 創(chuàng)建 Pod
$ kubectl apply -f hostpath-demo.yaml
pod/hostpath-volume-pod created
# 進(jìn)入 Pod 容器內(nèi)部
$ kubectl exec -it hostpath-volume-pod -- sh
# 執(zhí)行 date 命令輸出當(dāng)前時(shí)間
/ # date
Sun Mar 13 17:00:22 CST 2022 # 上海時(shí)區(qū)
看到輸出結(jié)果為 Sun Mar 13 17:00:22 CST 2022 ,其中 CST 代表了上海時(shí)區(qū)描姚,也就是主機(jī)節(jié)點(diǎn)的時(shí)區(qū)涩赢。如果不通過卷掛載的方式將主機(jī)時(shí)區(qū)掛載到容器內(nèi)部,則容器默認(rèn)時(shí)區(qū)為 UTC 時(shí)區(qū)轩勘。
小結(jié)
臨時(shí)卷內(nèi)容介紹了 K8s 的臨時(shí)存儲(chǔ)方案以及應(yīng)用筒扒,其中emptyDir 適用范圍較少,可以當(dāng)作臨時(shí)緩存或者耗時(shí)任務(wù)檢查點(diǎn)等绊寻。
需要注意的是花墩,絕大多數(shù) Pod 應(yīng)該忽略主機(jī)節(jié)點(diǎn),不應(yīng)該訪問節(jié)點(diǎn)上的文件系統(tǒng)榛斯。盡管有時(shí)候 DaemonSet 可能需要訪問主機(jī)節(jié)點(diǎn)的文件系統(tǒng)观游,而且hostPath 可以用來同步主機(jī)節(jié)點(diǎn)時(shí)區(qū)到容器,但其他情況下使用較少驮俗,特別hostPath 的最佳實(shí)踐就是盡量不使用 hostPath懂缕。
持久卷
臨時(shí)卷的生命周期與 Pod 相同,當(dāng) Pod 被刪除時(shí)王凑,K8s 會(huì)自動(dòng)刪除 Pod 掛載的臨時(shí)卷搪柑。而當(dāng) Pod 中的應(yīng)用需要將數(shù)據(jù)保存到磁盤聋丝,且即使 Pod 被調(diào)度到其他節(jié)點(diǎn)數(shù)據(jù)也應(yīng)該存在時(shí),我們就需要一個(gè)真正的持久化存儲(chǔ)了工碾。
K8s 支持的持久卷類型非常多弱睦,以下是 v1.24 版本支持的卷類型的一部分:
awsElasticBlockStore - AWS 彈性塊存儲(chǔ)(EBS)
azureDisk - Azure Disk
azureFile - Azure File
cephfs - CephFS volume
csi - 容器存儲(chǔ)接口 (CSI)
fc - Fibre Channel (FC) 存儲(chǔ)
gcePersistentDisk - GCE 持久化盤
glusterfs - Glusterfs 卷
iscsi - iSCSI (SCSI over IP) 存儲(chǔ)
local - 節(jié)點(diǎn)上掛載的本地存儲(chǔ)設(shè)備
nfs - 網(wǎng)絡(luò)文件系統(tǒng) (NFS) 存儲(chǔ)
portworxVolume - Portworx 卷
rbd - Rados 塊設(shè)備 (RBD) 卷
vsphereVolume - vSphere VMDK 卷
看到這么多持久卷類型不必恐慌,因?yàn)?K8s 為了讓開發(fā)者不必關(guān)心這背后的持久化存儲(chǔ)類型渊额,所以對(duì)持久卷有一套獨(dú)有的思想况木,即開發(fā)者無論使用哪種持久卷,其用法都是一致的旬迹。
K8s 持久卷設(shè)計(jì)架構(gòu)如下:
Node1 和 Node2 分別代表兩個(gè)工作節(jié)點(diǎn)火惊,當(dāng)我們?cè)诠ぷ鞴?jié)點(diǎn)創(chuàng)建 Pod 時(shí),可以通過 spec.containers.volumeMounts 來指定容器掛載目錄奔垦,通過 spec.volumes 來指定掛載卷屹耐。之前我們用掛載卷掛載了配置信息和臨時(shí)卷,而掛載持久卷也可以采用同樣的方式椿猎。每個(gè) volumes 則指向的是下方存儲(chǔ)集群中不同的存儲(chǔ)類型惶岭。
為了保證高可用,我們通常會(huì)搭建一個(gè)存儲(chǔ)集群犯眠。通常通過 Pod 來操作存儲(chǔ)按灶, 因?yàn)?Pod 都會(huì)部署在 Node 中,所以存儲(chǔ)集群最好跟 Node 集群搭建在同一內(nèi)網(wǎng)阔逼,這樣速度更快兆衅。而存儲(chǔ)集群內(nèi)部可以使用任何 K8s 支持的持久化存儲(chǔ)地沮,如上圖的 NFS 嗜浮、CephFS 、CephRBD 摩疑。
使用NFS
持久化掛載方式與臨時(shí)卷大同小異危融,我們同樣使用一個(gè) Nginx 服務(wù)來進(jìn)行測(cè)試。這次我們用 NFS 存儲(chǔ)來演示 K8s 對(duì)持久卷的支持(NFS 測(cè)試環(huán)境搭建過程可以參考文章結(jié)尾的附錄部分)雷袋,創(chuàng)建 nfs-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: Pod
metadata:
name: "nfs-nginx-pod"
namespace: default
labels:
app: "nfs-nginx-pod"
spec:
containers:
- name: nfs-nginx
image: "nginx:latest"
ports:
- containerPort: 80
name: http
volumeMounts:
- name: html-volume
mountPath: /usr/share/nginx/html/
volumes:
- name: html-volume
nfs:
server: 192.168.99.101 # 指定 nfs server 地址
path: /nfs/data/nginx # 目錄必須存在
將容器 index.html 所在目錄 /usr/share/nginx/html/ 掛載到 NFS 服務(wù)的 /nfs/data/nginx 目錄下吉殃,在 spec.volumes 配置項(xiàng)中指定 NFS 服務(wù)。其中server 指明了 NFS 服務(wù)器地址楷怒,path 指明了 NFS 服務(wù)器中掛載的路徑蛋勺,當(dāng)然這個(gè)路徑必須是已經(jīng)存在的路徑。然后通過 kubectl apply 命令應(yīng)用這個(gè)文件鸠删。
$ kubectl apply -f nfs-demo.yaml
接下來我們查看這個(gè) Pod 使用 NFS 存儲(chǔ)的結(jié)果:
在 NFS 節(jié)點(diǎn)中我們準(zhǔn)備一個(gè) index.html 文件抱完,其內(nèi)容為 hello nfs。
使用 curl 命令直接訪問 Pod 的 IP 地址刃泡,即可返回 Nginx 服務(wù)的 index.html 內(nèi)容巧娱,結(jié)果輸出為 hello nfs 碉怔,證明 NFS 持久卷掛載成功。
登入 Pod 容器禁添,通過 df -Th 命令查看容器目錄掛載信息撮胧。可以發(fā)現(xiàn)老翘,容器的 /usr/share/nginx/html/ 目錄被掛載到 NFS 服務(wù)的 /nfs/data/nginx 目錄芹啥。
現(xiàn)在,當(dāng)我們執(zhí)行 kubectl delete -f nfs-demo.yaml 刪除 Pod 后铺峭,NFS 服務(wù)器上數(shù)據(jù)盤中的數(shù)據(jù)依然存在叁征,這就是持久卷。
持久卷使用痛點(diǎn)
雖然通過使用持久卷逛薇,可以解決臨時(shí)卷數(shù)據(jù)易丟失的問題捺疼。但目前持久卷的使用方式還存在以下痛點(diǎn):
Pod 開發(fā)人員可能對(duì)存儲(chǔ)不夠了解,卻要對(duì)接多種存儲(chǔ)
安全問題永罚,有些存儲(chǔ)可能需要賬號(hào)密碼啤呼,這些信息不應(yīng)該暴露給 Pod
因此為了解決這些不足,K8s 又針對(duì)持久化存儲(chǔ)抽象出了三種資源 PV呢袱、PVC官扣、StorageClass。三種資源定義如下:
PV 描述的是持久化存儲(chǔ)數(shù)據(jù)卷
PVC 描述的是 Pod 想要使用的持久化存儲(chǔ)屬性羞福,既存儲(chǔ)卷申明
StorageClass 作用是根據(jù) PVC 的描述惕蹄,申請(qǐng)創(chuàng)建對(duì)應(yīng)的 PV
PV 和 PVC 的概念可以對(duì)應(yīng)編程語言中的面向?qū)ο笏枷耄琍VC 是接口治专,PV 是具體實(shí)現(xiàn)卖陵。
有了這三種資源類型后,Pod 就可以通過靜態(tài)供應(yīng)和動(dòng)態(tài)供應(yīng)這兩種方式來使用持久卷张峰。
靜態(tài)供應(yīng)
靜態(tài)供應(yīng)不涉及 StorageClass泪蔫,只涉及到 PVC 和 PV。其使用流程圖如下:
使用靜態(tài)供應(yīng)時(shí)喘批,Pod 不再直接綁定持久存儲(chǔ)撩荣,而是會(huì)綁定到 PVC 上,然后再由 PVC 跟 PV 進(jìn)行綁定饶深。這樣就實(shí)現(xiàn)了 Pod 中的容器可以使用由 PV 真正去申請(qǐng)的持久化存儲(chǔ)餐曹。
使用示例
創(chuàng)建 pv-demo.yaml 內(nèi)容如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-1g
labels:
type: nfs
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: nfs-storage
nfs:
server: 192.168.99.101
path: /nfs/data/nginx1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-100m
labels:
type: nfs
spec:
capacity:
storage: 100m
accessModes:
- ReadWriteOnce
storageClassName: nfs-storage
nfs:
server: 192.168.99.101
path: /nfs/data/nginx2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-500m
labels:
app: pvc-500m
spec:
storageClassName: nfs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500m
---
apiVersion: v1
kind: Pod
metadata:
name: "pv-nginx-pod"
namespace: default
labels:
app: "pv-nginx-pod"
spec:
containers:
- name: pv-nginx
image: "nginx:latest"
ports:
- containerPort: 80
name: http
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/
volumes:
- name: html
persistentVolumeClaim:
claimName: pvc-500m
其中 yaml 文件定義了如下內(nèi)容:
兩個(gè) PV:申請(qǐng)容量分別為 1Gi 、100m 敌厘,通過 spec.capacity.storage 指定台猴,并且他們通過 spec.nfs 指定了 NFS 存儲(chǔ)服務(wù)的地址和路徑。
一個(gè) PVC :申請(qǐng) 500m 大小的存儲(chǔ)。
一個(gè) Pod:spec.volumes 綁定名為 pvc-500m 的 PVC卿吐,而不是直接綁定 NFS 存儲(chǔ)服務(wù)旁舰。
通過 kubectl apply 命令應(yīng)用該文件:
$ kubectl apply -f pv-demo.yaml
以上完成創(chuàng)建,結(jié)果查看操作則如下:
首先通過 kubectl get pod 命令查看新創(chuàng)建的 Pod嗡官,并通過 curl 命令訪問 Pod 的 IP 地址箭窜,得到 hello nginx1 的響應(yīng)結(jié)果。
然后通過 kubectl get pvc 查看創(chuàng)建的 PVC:
STATUS 字段:標(biāo)識(shí) PVC 已經(jīng)處于綁定(Bound)狀態(tài)衍腥,也就是與 PV 進(jìn)行了綁定磺樱。
CAPACITY 字段:標(biāo)識(shí) PVC 綁定到了 1Gi 的 PV 上,盡管我們申請(qǐng)的 PVC 大小是 500m 婆咸,但由于我們創(chuàng)建的兩個(gè) PV 大小分別是 1Gi 和 100m 竹捉,K8s 會(huì)幫我們選擇滿足條件的最優(yōu)解。因?yàn)闆]有剛好等于 500m 大小的 PV 存在尚骄,而 100m 又不滿足块差,所以 PVC 會(huì)自動(dòng)與 1Gi 大小的 PV 進(jìn)行綁定。
通過 kubectl get pv 來查詢創(chuàng)建的 PV 資源倔丈,可以發(fā)現(xiàn) 1Gi 大小的 PV STATUS 字段為 Bound 狀態(tài)憨闰。CLAIM 的值,則標(biāo)識(shí)的是與之綁定的 PVC 的名字需五。
現(xiàn)在我們登錄 NFS 服務(wù)器鹉动,確認(rèn)NFS 存儲(chǔ)上不同持久卷(PV)掛載的目錄下文件內(nèi)容。
可以看到宏邮,/nfs/data/nginx1 目錄下的 index.html 內(nèi)容為 hello nginx1 泽示,即為上面通過 curl 命令訪問 Pod 服務(wù)的響應(yīng)結(jié)果。
到此持久卷完成使用蜜氨,我們總結(jié)下整個(gè)持久卷使用流程械筛。首先創(chuàng)建一個(gè) Pod, Pod 的 spec.volumes 中綁定 PVC记劝。這里的 PVC 只是一個(gè)存儲(chǔ)申明变姨,代表我們的 Pod 需要什么樣的持久化存儲(chǔ)族扰,它不需要標(biāo)明 NFS 服務(wù)地址厌丑,也不需要明確要和哪個(gè) PV 進(jìn)行綁定,只是創(chuàng)建出這個(gè) PVC 即可渔呵。接著我們創(chuàng)建兩個(gè) PV怒竿,PV 也沒有明確指出要與哪個(gè) PVC 進(jìn)行綁定,只需要指出它的大小和 NFS 存儲(chǔ)服務(wù)地址即可扩氢。此時(shí) K8s 會(huì)自動(dòng)幫我們進(jìn)行 PVC 和 PV 的綁定耕驰,這樣 Pod 就和 PV 產(chǎn)生了聯(lián)系,也就可以訪問持久化存儲(chǔ)了录豺。
其他
細(xì)心的你可能已經(jīng)發(fā)現(xiàn)朦肘,前文提到靜態(tài)供應(yīng)不涉及 StorageClass饭弓,但是在定義 PVC 和 PV 的 yaml 文件時(shí),還是都為其指定了 spec.storageClassName 值為 nfs-storage媒抠。因?yàn)檫@是一個(gè)便于管理的操作方式弟断,只有具有相同 StorageClass 的 PVC 和 PV 才可以進(jìn)行綁定,這個(gè)字段標(biāo)識(shí)了持久卷的訪問模式趴生。在 K8s 持久化中支持四種訪問模式:
RWO - ReadWriteOnce —— 卷可以被一個(gè)節(jié)點(diǎn)以讀寫方式掛載
ROX - ReadOnlyMany —— 卷可以被多個(gè)節(jié)點(diǎn)以只讀方式掛載
RWX - ReadWriteMany —— 卷可以被多個(gè)節(jié)點(diǎn)以讀寫方式掛載
RWOP - ReadWriteOncePod —— 卷可以被單個(gè) Pod 以讀寫方式掛載( K8s 1.22 以上版本)
只有具有相同讀寫模式的 PVC 和 PV 才可以進(jìn)行綁定阀趴。
現(xiàn)在我們來繼續(xù)實(shí)驗(yàn),通過命令 kubectl delete pod pv-nginx-pod 刪除 Pod苍匆,再次查看 PVC 和 PV 狀態(tài)刘急。
從上圖可以看到, Pod 刪除后 PVC 和 PV 還在浸踩,這說明 Pod 刪除并不影響 PVC 的存在叔汁。而當(dāng) PVC 刪除時(shí) PV 是否刪除,則可以通過設(shè)置回收策略來決定检碗。PV 回收策略(pv.spec.persistentVolumeReclaimPolicy)有三種:
Retain —— 手動(dòng)回收攻柠,也就是說刪除 PVC 后,PV 依然存在后裸,需要管理員手動(dòng)進(jìn)行刪除
Recycle —— 基本擦除 (相當(dāng)于 rm -rf /*)(新版已廢棄不建議使用瑰钮,建議使用動(dòng)態(tài)供應(yīng))
Delete —— 刪除 PV,即級(jí)聯(lián)刪除
現(xiàn)在通過命令 kubectl delete pvc pvc-500m 刪除 PVC微驶,查看 PV 狀態(tài)浪谴。
可以看到 PV 依然存在,其 STATUS 已經(jīng)變成 Released 因苹,此狀態(tài)下的 PV 無法再次綁定到 PVC苟耻,需要由管理員手動(dòng)刪除,這是由回收策略決定的扶檐。
注意:綁定了 Pod 的 PVC凶杖,如果 Pod 正在運(yùn)行中,PVC 無法刪除款筑。
靜態(tài)供應(yīng)的不足
我們一起體驗(yàn)了靜態(tài)供應(yīng)的流程智蝠,雖然比直接在 Pod 中綁定 NFS 服務(wù)更加清晰,但靜態(tài)供應(yīng)依然存在不足奈梳。
首先會(huì)造成資源浪費(fèi)杈湾,如上面示例中,PVC 申請(qǐng) 500m攘须,而沒有剛好等于 500m 的 PV 存在漆撞,這 K8s 會(huì)將 1Gi 的 PV 與之綁定
還有一個(gè)致命的問題,如果當(dāng)前沒有滿足條件的 PV 存在,則這 PVC 一直無法綁定到 PV 處于 Pending 狀態(tài)浮驳,Pod 也將無法啟動(dòng)悍汛,所以就需要管理員提前創(chuàng)建好大量 PV 來等待新創(chuàng)建的 PVC 與之綁定,或者管理員時(shí)刻監(jiān)控是否有滿足 PVC 的 PV 存在至会,如果不存在則馬上進(jìn)行創(chuàng)建员凝,這顯然是無法接受的
動(dòng)態(tài)供應(yīng)
因?yàn)殪o態(tài)供應(yīng)存在不足,K8s 推出一種更加方便的持久卷使用方式奋献,即動(dòng)態(tài)供應(yīng)健霹。動(dòng)態(tài)供的應(yīng)核心組件就是 StorageClass——存儲(chǔ)類。StorageClass 主要作用有兩個(gè):
一是資源分組瓶蚂,我們上面使用靜態(tài)供應(yīng)時(shí)指定 StorageClass 的目前就是對(duì)資源進(jìn)行分組糖埋,便于管理
二是 StorageClass 能夠幫我們根據(jù) PVC 請(qǐng)求的資源,自動(dòng)創(chuàng)建出新的 PV窃这,這個(gè)功能是 StorageClass 中 provisioner 存儲(chǔ)插件幫我們來做的瞳别。
其使用流程圖如下:
相較于靜態(tài)供應(yīng),動(dòng)態(tài)供應(yīng)在 PVC 和 PV 之間增加了存儲(chǔ)類杭攻。這次 PV 并不需要提前創(chuàng)建好祟敛,只要我們申請(qǐng)了 PVC 并且綁定了有 provisioner 功能的 StorageClass,StorageClass 會(huì)幫我們自動(dòng)創(chuàng)建 PV 并與 PVC 進(jìn)行綁定兆解。
我們可以根據(jù)提供的持久化存儲(chǔ)類型馆铁,分別創(chuàng)建對(duì)應(yīng)的 StorageClass,比如:
nfs-storage
cephfs-storage
rbd-storage
也可以設(shè)置一個(gè)默認(rèn) StorageClass锅睛, 通過在創(chuàng)建 StorageClass 資源時(shí)指定對(duì)應(yīng)的 annotations 實(shí)現(xiàn):
apiVersion: storage.K8s.io/v1
kind: StorageClass
metadata:
annotations:
storageclass.kubernetes.io/is-default-class: "true"
...
當(dāng)創(chuàng)建 PVC 時(shí)不指定 spec.storageClassName 埠巨,這個(gè) PVC 就會(huì)使用默認(rèn) StorageClass。
使用示例
仍然使用 NFS 來作為持久化存儲(chǔ)现拒。
首先需要有一個(gè)能夠支持自動(dòng)創(chuàng)建 PV 的 provisioner 辣垒,這可以在 GitHub 中找到一些開源的實(shí)現(xiàn)。示例使用 nfs-subdir-external-provisioner 這個(gè)存儲(chǔ)插件印蔬,具體安裝方法非常簡(jiǎn)單勋桶,只需要通過 kubectl apply 命令應(yīng)用它提供的幾個(gè) yaml 文件即可。完成存儲(chǔ)插件安裝后侥猬,可以創(chuàng)建如下 StorageClass:
apiVersion: storage.K8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
provisioner: K8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "true"
這個(gè) StorageClass 指定了 provisioner 為我們安裝好的 K8s-sigs.io/nfs-subdir-external-provisioner例驹。
Provisioner 本質(zhì)上也是一個(gè) Pod,可以通過 kubectl get pod 來查看陵究。指定了 provisioner 的 StorageClass 就有了自動(dòng)創(chuàng)建 PV 的能力眠饮,因?yàn)?Pod 能夠自動(dòng)創(chuàng)建 PV。
創(chuàng)建好 provisioner 和 StorageClass 就可以進(jìn)行動(dòng)態(tài)供應(yīng)的實(shí)驗(yàn)了铜邮。首先創(chuàng)建 nfs-provisioner-demo.yaml 內(nèi)容如下:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
storageClassName: nfs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Mi
---
apiVersion: v1
kind: Pod
metadata:
name: "test-nginx-pod"
namespace: default
labels:
app: "test-nginx-pod"
spec:
containers:
- name: test-nginx
image: "nginx:latest"
ports:
- containerPort: 80
name: http
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/
volumes:
- name: html
persistentVolumeClaim:
claimName: test-claim
這里我們只定義了一個(gè) PVC 和一個(gè) Pod,并沒有定義 PV。其中 PVC 的 spec.storageClassName 指定為上面創(chuàng)建好的 StorageClass nfs-storage 松蒜,然后只需要通過 kubectl apply 命令來創(chuàng)建出 PVC 和 Pod 即可:
$ kubectl apply -f nfs-provisioner-demo.yaml
persistentvolumeclaim/test-claim created
pod/test-nginx-pod created
現(xiàn)在查看 PV扔茅、PVC 和 Pod,可以看到 PV 已經(jīng)被自動(dòng)創(chuàng)建出來了秸苗,并且它們之間實(shí)現(xiàn)了綁定關(guān)系召娜。
然后登錄 NFS 服務(wù),給遠(yuǎn)程掛載的卷寫入 hello nfs 數(shù)據(jù)惊楼。
在 K8s 側(cè)玖瘸,就可以使用 curl 命令驗(yàn)證掛載的正確性了。
此時(shí)如果你通過 kubectl delete -f nfs-provisioner-demo.yaml 刪除 Pod 和 PVC檀咙,PV 也會(huì)跟著刪除雅倒,因?yàn)?PV 的刪除策略是 Delete 。不過刪除后NFS 卷中的數(shù)據(jù)還在弧可,只不過被歸檔成了以 archived 開頭的目錄蔑匣。這是 K8s-sigs.io/nfs-subdir-external-provisioner 這個(gè)存儲(chǔ)插件所實(shí)現(xiàn)的功能,這就是存儲(chǔ)插件的強(qiáng)大棕诵。
完成全部操作后裁良,我們可以發(fā)現(xiàn)通過定義指定了 provisioner 的 StorageClass,不僅實(shí)現(xiàn)了 PV 的自動(dòng)化創(chuàng)建校套,甚至實(shí)現(xiàn)了數(shù)據(jù)刪除時(shí)自動(dòng)歸檔的功能价脾,這就是 K8s 動(dòng)態(tài)供應(yīng)存儲(chǔ)設(shè)計(jì)的精妙。也可以說動(dòng)態(tài)供應(yīng)是持久化存儲(chǔ)最佳實(shí)踐笛匙。
附錄:NFS 實(shí)驗(yàn)環(huán)境搭建
NFS 全稱 Network File System彼棍,是一種分布式存儲(chǔ),它能夠通過局域網(wǎng)實(shí)現(xiàn)不同主機(jī)間目錄共享膳算。
以下為 NFS 的架構(gòu)圖:由一個(gè) Server 節(jié)點(diǎn)和兩個(gè) Client 節(jié)點(diǎn)組成座硕。
下面列出 NFS 在 Centos 系統(tǒng)中的搭建過程。
Server 節(jié)點(diǎn)
# 安裝 nfs 工具
yum install -y nfs-utils
# 創(chuàng)建 NFS 目錄
mkdir -p /nfs/data/
# 創(chuàng)建 exports 文件涕蜂,* 表示所有網(wǎng)絡(luò)上的 IP 都可以訪問
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports
# 啟動(dòng) rpc 遠(yuǎn)程綁定功能华匾、NFS 服務(wù)功能
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server
# 重載使配置生效
exportfs -r
# 檢查配置是否生效
exportfs
# 輸出結(jié)果如下所示
# /nfs/data
Client 節(jié)點(diǎn)
# 關(guān)閉防火墻
systemctl stop firewalld
systemctl disable firewalld
# 安裝 nfs 工具
yum install -y nfs-utils
# 掛載 nfs 服務(wù)器上的共享目錄到本機(jī)路徑 /root/nfsmount
mkdir /root/nfsmount
mount -t nfs 192.168.99.101:/nfs/data /root/nfsmount