容器是短暫的
記住容器是臨時的、短暫的。每當容器完成執行指令時,它就完全“關閉”了。
=> docker run node ls
bin
boot
dev
etc
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
它使用鏡像node啟動一個新容器并在容器内執行指令(列出檔案和目錄)。
該指令給出一個輸出并完成。指令完成後,容器将關閉并且其所有資料都将丢失。
執行 Javascript 程式
假設我們有一個包含非常基本的 Javascript 程式的檔案:
helloWorld.js
greeting = (message) => {
console.log(message)
}
greeting("Hello, world")
我們如何使用容器運作這個程式?可以嘗試做類似的事情:
docker run node node helloWorld.js
得到錯誤:
Error: Cannot find module '/app/helloWorld.js'
...
那是因為容器是隔離的,不能共享同一個主機檔案系統。
綁定挂載:在主機和容器之間同步資料
我們必須将檔案helloWorld.js與容器同步。Docker 提供了一種挂載卷的方法,這意味着:
從Host 挂載一個目錄或檔案到 Container,這樣在 Host 中所做的每一個更改都會被鏡像到 Container,反之亦然。
您更改主機中的檔案/目錄,容器将看到更改。您更改容器中的檔案/目錄,主機将看到更改。
讓我們與容器同步:
docker run
-v $(pwd)/docker-101/helloWorld.js:/app/helloWorld.js
node
node /app/helloWorld.js
Hello, world
解釋:
- pwd是指目前目錄的完整路徑名
- -v {hostPath}:{containerPath}将檔案/目錄挂載到容器
- node是鏡像
- node /app/helloWorld.js使用容器内定義的path執行指令
一個真實的項目
假設我們有一個這樣的項目目錄:
/docker-101
/src
/components
components.js
index.js
從docker-101目錄内部,執行node
docker run
-v $(pwd):/app
node
node /app/index.js
目前目錄中的所有檔案都将挂載到容器中的/app。
如果我們想進入容器并從那裡操作檔案怎麼辦?
docker run
-it
-v $(pwd):/app
node
bash
.這裡:
- -it訓示 Docker 保持容器終端(bash/shell)打開
- bash在容器内打開一個新的 bash/shell
執行這個docker run指令,保持容器終端打開,在容器内部執行更多指令并建立新檔案:
root@8dcbfa6d777c:/# cd /app
root@8dcbfa6d777c:/app# ls
index.js src
root@8dcbfa6d777c:/app# touch new-file.js
root@8dcbfa6d777c:/app# ls
index.js new-file.js src
root@8dcbfa6d777c:/app# exit
exit
現在,退出容器後,在主機執行ls,我們會發現,相應的新建立的檔案都同步在host上了:
index.js new-file.js src
這種類型的卷稱為Path Volume。
使用mount參數
另一種挂載卷的方法是使用選項--mount,它指定挂載類型、源和目标。
docker run
--mount type=bind,source=$(pwd),target=/app
node
node /app/helloWorld.js
mount更明确,但在大多數情況下使用-v已經足夠了。
命名卷與匿名卷
綁定加載是由程式員自己維護的,而資料卷是由 Docker 引擎維護的存儲方式,使用 docker volume create 指令建立,可以利用卷驅動支援多種存儲方案。其預設的驅動為 local,也就是本地卷驅動。本地驅動支援命名卷和匿名卷。
docker volume create my-volume
docker volume inspect my-volume
[
{
"CreatedAt": "2022-02-05T00:47:59Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-volume/_data",
"Name": "my-volume",
"Options": {},
"Scope": "local"
}
]
Mountpoint訓示的路徑是主機中的确切路徑。是以,每個使用此卷的容器都會将其資料直接同步到此挂載點。
而匿名卷就是沒名字的卷,一般是 docker run -v /data 這種不指定卷名的時候所産生,或者 Dockerfile 裡面的定義直接使用的。
如果容器啟動時使用了 --rm 選項,容器停止時,容器被自動删除,匿名卷也會自動被删除。
反之,如果沒有--rm選項,即使使用命名 docker container rm my_container 删除了容器,匿名卷不會被自動删除。每次建立新的容器,都會重新建立新的匿名卷。 重新開機容器則使用已有附加的匿名卷。
要删除匿名卷,使用指令 docker volume rm 或者 prune
是以,一般而言,匿名卷隻存放無關緊要的臨時資料,随着容器消亡,這些資料将失去存在的意義。
在 Dockerfile 中定義的挂載,是指匿名資料卷。無法在Dockerfile 内建立命名卷。
最佳實踐
不應該在容器存儲層内進行資料寫入操作,所有寫入應該使用卷。如果定制鏡像的時候,就可以确定某些目錄會發生頻繁大量的讀寫操作,那麼為了避免在運作時由于使用者疏忽而忘記指定卷,導緻容器發生存儲層寫入的問題,就可以在 Dockerfile 中使用 VOLUME 來指定某些目錄為匿名卷。這樣即使使用者忘記了指定卷,也不會産生不良的後果。
比如,Dockerfile 中說 VOLUME /data,那麼如果直接 docker run,其 /data 就會被挂載為匿名卷,向 /data 寫入的操作不會寫入到容器存儲層,而是寫入到了匿名卷中。但是如果運作時 docker run -v mydata:/data,這就覆寫了 /data 的挂載設定,要求将 /data 挂載到名為 mydata 的命名卷中。是以說 Dockerfile 中的 VOLUME 實際上是一層保險,確定鏡像運作可以更好的遵循最佳實踐,不向容器存儲層内進行寫入操作。