
和這種蛋糕一樣,Docker的容器和鏡像也是使用類似的分層文件系統構建而成的。
這樣做的好處就是可以節省硬盤空間,也利於復用等等。因為Docker基於鏡像創建容器的時候,其鏡像是共享的;而且鏡像里面的層如果已存在,也無需再下載。
下面拉取一個mongodb的鏡像,拉取的過程中可以看到:

圖中紅框范圍內的就是mongo鏡像的不同分層,也就是鏡像中的分層文件系統。
然而這些鏡像層是只讀的:

這樣的限制多少看起來有點嚴格,如果你想使用該鏡像讀寫數據庫怎么辦?或者記錄Log到文件,或者在容器運行的時候替換一些源代碼該怎么辦?
幸運的時候使用該鏡像的容器會有可用於讀寫的"薄薄"一層:

從圖中也可以看出容器和鏡像的不同之處。
你可以在容器層進行寫入,但是如果容器被刪除了,那么可讀寫的這一層也會被刪除。
這樣就不太友好了,而這時我們可以使用Volume(卷)。
下面就是這個問題,如何把源碼裝進容器里?
1.可以在制做鏡像的時候把源碼直接寫入鏡像。(這個先不考慮)
2.把源碼裝進容器的可讀寫層。(這個是我要介紹的)
Volume是什么?
- Volume(卷)是容器中一個特別種類的目錄,通常叫做數據volume,顧名思義,里面可以放置各種類型的數據,例如代碼、日志文件、數據文件等等。
- Volume可以在容器間被共享和復用。可以讓多個容器對同一個volume進行讀寫,也可以讓一個容器讀寫多個volume。
- 對鏡像的更新並不會影響volume。
- Volume是被持久化的,即使容器刪除了,它仍然還在。
可以這樣去理解Volume,如果有一個容器,那么我們可以在這個容器里面定義一個Volume:

那么想要寫到哪里去呢?
可以讓Docker自己搞定,或者你也可以自定義。
讓Docker決定寫入的位置
先介紹第一種情況,當你寫入到volume的時候,比如在Docker容器里的代碼對/var/www做了一個寫入的操作,那該目錄其實就是你docker host里面的一個裝載的文件夾(mounted folder)的別名。Docker host也就是容器的宿主,如果你使用的是Linux系統或Windows 2016及以上版本的系統,那么該宿主就是操作系統。容器也就是運行在該系統上。

那么在這個例子里,我們寫入的這個volume,它可以不是容器的可讀寫層,它實際上可以寫入docker host的裝載的文件夾,也就是操作系統的文件夾。即使你把容器刪除了,docker host里的文件夾仍在健在。
通常我們使用如下命令來運行容器:
docker run -p 8000:80 microsoft/dotnet-samples:aspnetapp
而我們可以使用-v參數來指定volume:
docker run -p 8000:80 -v /var/www microsoft/dotnet-samples:aspnetapp
這樣的話,/var/www只是容器Volume的別名,實際被寫入的區域在Docker Host里,docker會自動的創建這個區域。
可以使用docker inspect 容器名這個命令來查看相關的路徑。
執行該命令后的結果中會顯示如下部分Mounts:

其中Destination是volume在容器里的地址(別名),而Source則是Volume在宿主中的地址。
以上這部分介紹的就是讓Docker來創建寫入的目錄。
自定義寫入的位置
下面講一下如何自定義這個目錄的地址。

這樣就對我們開發寫代碼比較友好了,我的代碼存放於Windows/Mac系統中,然后我們讓Volume讀寫我們代碼所在的區域。
那么應該使用哪個Docker命令呢?
docker run -p 8000:80 -v ${PWD}:/var/www microsoft/dotnet-samples:aspnetapp
使用-v在容器里創建一個volume,它在容器的地址是/var/www,但是當你對它進行讀寫操作時,它實際上找的是宿主的地址,在這里也就是當前的工作目錄(curent working directory)。
如果你這時再執行docker inspect命令,其結果大概如下:

把ASP.NET Core的源碼連接到Volume
首先使用dotnet cli或者VS建立一個ASP.NET Core項目:

然后使用dotnet run測試一下網站是否能正常運行:


接下來看看這個ASP.NET Core網站如何與Volume聯系在一起。
首先下載aspnetcore-build鏡像:docker pull microsoft/dotnet:2.1-sdk
下載完鏡像之后,就需要創建容器和Volume了,不過在此之前先打開命令行,進入ASP.NET Core項目源碼的目錄:

然后執行下面的命令(Windows 10 Powershell):
docker run -it -p 8080:5001 -v ${PWD}:/app --workdir "/app" microsoft/dotnet /bin/bash
這句話里-it參數表示進入交互模式
-p 8080:5001 表示把容器里的5001端口映射給宿主的8080端口。
-v 表示創建volume
${PWD}是指宿主當前的目錄。
${PWD}:/app就是把容器里的/app文件夾連接到了宿主系統里的當前文件夾,而容器里的/app目錄就是應用程序將要運行的位置。
--workdir "/app"表示容器里當前的工作目錄是/app。
然后使用microsoft/dotnet這個鏡像。
最后使用/bin/bash返回一個終端,以便讓我與容器里進行交互。
執行命令后,Docker可能會有提示需要共享一個目錄,點擊確認即可。
然后我就會進入Container了:

進入容器之后,我就可以執行dotnet restore, dotnet build等等命令了:

當然了,可以執行dotnet run:

然而這時候,我訪問本機(宿主)的localhost:8080,確無法顯式頁面。
首先為了簡便,先把HTTPS重定向相關的內容去掉。
然后要讓應用監聽任意地址的5001端口:

然后再次運行dotnet run。
隨后在宿主系統的瀏覽器打開http://localhost:8080即可打這個ASP.NET Core的web應用了:

