玩過docker容器的人,對數據掛載肯定不陌生,volume :幾種叫法,數據卷,資料卷
通過 -v 把宿主機目錄綁定到容器中的目錄
數據掛載的好處,僅僅是我認為的。不一定正確
1:數據能持久化保存,因為容器一旦刪除,啥都沒有了。但掛載的目錄是不會刪除的
2:修改和查看方便,比如要修改和查看數據,直接看本地,不用進去容器看,比較容器里面很多命令是沒有安裝的
這種方式是:-v 的方式稱之為:bind mount,要區別於volume
volume方式是在Dockerfile 指定,比如:
VOLUME /data
VOLUME ["/data1","/data2"]
bind mount
容器以宿主機文件夾為准
volume
宿主有數據時,以宿主機為准
宿主無數據,從容器復制過來,再以宿主機為准
這里講的是bind mount方式,通過查看容器的inspect 就可以發現
首先來 看一個我們常用的netcore 打包成鏡像的Dockerfile
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base ENV TIMEZONE Asina/Shanghai WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build WORKDIR /src COPY . . RUN dotnet restore RUN dotnet build FROM build AS publish RUN dotnet publish "mytest.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "mytest.dll"]
首先build成images
docker build -t test .
如果不掛載的方式,運行容器
docker run -d -p 8080:80 --restart=always --name my test
這樣netcore文件都在容器的/app中,每次查看,都必須 docker exec ... 進入容器
那么已掛載的方式呢,相比很快就會想到了-v,立馬嘗試
docker run -d -p 8080:80 --restart=always -v /root/data:/app --name my test
-v /root/data:/app 意思是把容器內的app目錄,掛載到宿主機的/root/data中,也就是2個目錄bind
首先明確下,我這里是沒有提前創建/root/data 這個目錄的。不過掛載后,docker會給我們創建
但run之后,這個容器是起不來的,不過我這里加了restart,所以一直重啟,
為什么這個容器run不起來?我們來分析下
只要理解了volume的意思,
bind 一定要注意,主機目錄為空的話,會清空容器的目錄,
也就是說,我們沒掛載的時候,容器內app是有東西的,當我們把本地一個空 目錄(/root/data)
掛載到容器內的/app下,就會清空容器內文件夾的內容,
就是說:你掛載了宿主機目錄,我就以宿主機目錄為主,容器的內有數據,我都給清空,所以導致容器無法啟動
因為你Dockefile 中的入口是:ENTRYPOINT ["dotnet", "mytest.dll"]
都沒有這個mytest.dll,自然無法啟動
也許有人會問,壓根就沒必要這樣掛載啊,我只掛載需要的,比如日志文件,配置文件,
那好,繼續干
docker run -d -p 8080:80 --restart=always -v /root/logs:/app/logs -v /root/appsettings.json:/app/appsettings.json --name my test
logs 一開始肯定是為空,項目跑起來才生成,沒問題
appsettings.json配置文件,手動拷貝即可
但我在開發的時候就有一個疑問,項目里面不僅僅這2個文件,還會有upload上傳文件
template 模板文件,nlog 配置文件我覺得寫多個-v不合適,
我就琢磨着。不能在容器啟動的時候,把內部的數據拷貝到宿主機器嗎。
容器數據拷貝到宿主機命令:
docker cp my:/app /root/myapp
宿主機拷貝到容器,反過來即可
docker cp /root/myapp my:/app
所以我想到了2種方法,估計不是最優的
1:數據掛載后,手動拷貝文件到本地目錄,但我們是通過dockerfile 運行的
難道我又手動 dotnet publish ....一次,繁瑣
2:容器運行后,把容器數據拷貝到本地,不行嗎?是行的
分析:
既然數據在容器路徑app中,運行的時候,會清空,那為什么不事先保存在其他目錄中了
那修改下上面的Dockerfile
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base ENV TIMEZONE Asina/Shanghai WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build WORKDIR /src COPY . . RUN dotnet restore RUN dotnet build FROM build AS publish RUN dotnet publish "mytest.csproj" -c Release -o /app/publish FROM base AS final # 創建一個文件夾,存放文件,同時是被復制到宿主機的 RUN mkdir -p /data # 這是程序的最終工作目錄,也就是宿主機掛載的目錄 WORKDIR /app COPY --from=publish /app/publish /data ENTRYPOINT ["dotnet", "mytest.dll"]
加了一個data存放文件,程序啟動的時候,把data拷貝到宿主機目錄,綁定到宿主機app中
自己寫一個shell文件,init.sh,我是這樣寫的
#運行容器,設置了restart,會一直重啟,等待下面的拷貝完成,就會啟動成功 docker run -d -p 8802:80 --restart=always -v /root/data:/app --name my test #把容器內部的數據拷貝到本地掛載目錄 docker cp my:/data /root #刪除容器中的data文件,非必須的操作 docker exec my rm -rf /data
然后執行 sh init.sh,OK,訪問成功!
有沒有感覺這樣搞很麻煩。。確實,我自己看了都覺得麻煩,先是run,然后又cp。最后又exec
那為什么不把這些命令統一起來呢。仔細分析,上面的Dockerfile的入口是執行一個dll
ENTRYPOINT ["dotnet", "mytest.dll"]
當啟動主程序之前還需要執行大量的前置操作時, 可以將 ENTRYPOINT 的入口指令設置為一個腳本 ,我這里添加一個 start.sh
所以我們改進下:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base ENV TIMEZONE Asina/Shanghai WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build WORKDIR /src COPY . . RUN dotnet restore RUN dotnet build FROM build AS publish RUN dotnet publish "mytest.csproj" -c Release -o /app/publish FROM base AS final RUN mkdir -p /data WORKDIR /app COPY --from=publish /app/publish /data COPY start.sh /data RUN chmod +x /data/start.sh ENTRYPOINT ["/data/start.sh"]
我們依然是把publish 的文件放到data中。然后ENTRYPOINT 統一執行一個start.sh
start.sh腳本為:
#!/bin/bash mv -f /data/* /app dotnet mytest.dll
最后build和run就會按照預期的效果
docker build -t myimages . docker run -d -p 8804:80 -v /root/mynew:/app --name my myimages
然后在測試一次,依然成功,洗漱睡覺!!