使用 save / export 分享 image

不久前,曾聽到一個神奇的需求:希望在無網路的環境下使用 Docker。這種需求我還是第一次聽到。

第二階段的最後一天,來說明如何在無網路的環境下分享 Docker image 與啟動 container。

安裝 Docker

第一步就是麻煩事了,我會使用 Vagrant 包 Docker box 再到機器上匯入,接著啟動 VM 後,如果搞砸了,只要 VM 砍掉重練即可。

Vagrant 有安裝檔案,另外再配上 VirtualBox 安裝檔,即可完成虛擬機環境的準備。

再來是 Vagrant Box 準備,參考安裝 Docker 環境提到的 Vagrant 環境建置。使用 vagrant up 建好 VM 後,再使用 vagrant package 指令打包成 .box 檔:

$ vagrant package
==> default: Attempting graceful shutdown of VM...
==> default: Clearing any previously set forwarded ports...
==> default: Exporting VM...
==> default: Compressing package to: /Users/miles/GitHub/MilesChou/docker/package.box

這個動作跟 docker commit 非常像。

到目前為止會有三份檔案如下:

  1. Vagrant 安裝檔
  2. VirtualBox 安裝檔
  3. Docker VM 檔(package.box)

接著放入隨身碟後,就可以移架到主機上安裝 Vagrant 與 VirtualBox 了。

匯入 box 檔

Docker VM 檔是 .box 格式,匯入 Vagrant 使用 vagrant box add 指令:

$ vagrant box add --name docker ./package.box
==> box: Box file was not detected as metadata. Adding it directly...
==> box: Adding box 'docker' (v0) for provider:
box: Unpacking necessary files from: file:///Users/miles/GitHub/MilesChou/docker/package.box
==> box: Successfully added box 'docker' (v0) for 'virtualbox'!

完成匯入後,因 box 名稱不同,因此要換一個名字。依上面的範例,要使用 docker 這個 box 名稱:

Vagrant.configure("2") do |config|
config.vm.box = "docker"

# config.vm.network "forwarded_port", guest: 80, host: 8080
# config.vm.network "private_network", ip: "192.168.33.10"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
end

這裡要注意的是,forwarded_port 與之前提到的 Port forwarding 概念完全相同,只是 container 換成了 VM。以下是設定範例參考:

Image Container port Docker Run Vagrantfile Host port
httpd 80 8080:80 guest: 8080, host: 8080 8080
mysql 3306 3306:3306 guest: 3306, host: 3306 3306
registry 5000 443:5000 guest: 443, host: 1443 1443

當調整完設定後,只要下 vagrant reload 即可重載設定。

再來就是 Docker image 檔該如何產生與匯入了,Docker 提供兩種方法可以產生與匯入 tar 檔,以下簡單做說明。

docker savedocker load

昨天有說明如何把 Docker image push 到 registry 上。類似的原理,image 的內容是可以被打包起來的。這裡準備了一個簡單的 Dockerfile 來實驗:

FROM alpine

RUN apk add --no-cache vim
# 使用上面的 Dockerfile build image
docker build -t vim .

Build image 後,使用 docker save 可以將 image 內容輸出成 tar 檔:

# 確認 image 的 SHA256 為 bcdbe56cd759
docker images vim

# 將 image 保存成 tar
docker save vim > vim.tar

# 移除 image
docker rmi vim

# 確認 image 不在
docker images vim

# 把剛剛保存的 image 再載入 repository
docker load < vim.tar

# 確認 image 的 SHA256
docker images vim

這裡可以看到 SHA256 完全一致,這代表 image 內容有被完整保存下來的,而且大小是很接近的:

$ ls -lh vim.tar
-rw-r--r-- 1 miles staff 32M 10 5 01:34 vim.tar

$ docker images vim
REPOSITORY TAG IMAGE ID CREATED SIZE
vim latest bcdbe56cd759 17 hours ago 32.5MB

Docker 有提供 docker history 指令可以查看 image layer 的資訊:

$ docker history vim
IMAGE CREATED CREATED BY SIZE COMMENT
bcdbe56cd759 21 hours ago /bin/sh -c apk add --no-cache vim 26.9MB
<missing> 4 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 4 months ago /bin/sh -c #(nop) ADD file:c92c248239f8c7b9b… 5.57MB

最上面很明顯是 Vim 的,而下面兩行 missing 即為 Alpine 的 layer,可以用同樣的指令查 Alpine 會更清楚:

$ docker history alpine
IMAGE CREATED CREATED BY SIZE COMMENT
a24bb4013296 4 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 4 months ago /bin/sh -c #(nop) ADD file:c92c248239f8c7b9b… 5.57MB

這裡再做另一個實驗,把 Vim image 與 Alpine image 都移除,再匯入 vim.tar

# 移除 image
docker rmi vim alpine

# 把剛剛保存的 image 再載入 repository
docker load < vim.tar

# 確認 image 的 SHA256
docker images vim

這代表匯出的 vim.tar 會完整地包含所有 FROM 的 image 資訊。

為什麼要特別確認這件事,因為下一個指令會不一樣。

docker exportdocker import

一樣是輸出 tar 檔,但 docker export 的目標是 container,它能將 container 輸出成 tar 檔:

# 執行一個 container,注意這裡沒有 --rm
docker run -it --name vim alpine

# 做點檔案系統的改變再離開
apk add --no-cache vim && exit

# 把 container 的檔案系統匯出 tar
docker export vim > vim-export.tar

# 從 tar 導入檔案系統
docker import - vim < vim-export.tar

# 再多做一次看看
docker import - vim < vim-export.tar

最後的 docker import 可以發現,執行兩次的 digest 是不一樣的。這代表 docker export 產出的 tar 檔,其實是沒有包含 FROM image 資訊的。

docker history 資訊如下:

$ docker history vim
IMAGE CREATED CREATED BY SIZE COMMENT
407b5b2c246a 4 hours ago 32.5MB Imported from -

只有一層 layer,Alpine 的檔案系統已經被包含在這裡面了。

save v.s. export

docker savedocker export 的目的都一樣是把 Docker 的系統保存成檔案,但結果是不大一樣的。以下做個簡單的比較:

docker save docker export
將 image 打包 將 container 打包
完整保留 image layer 資訊 只會有一層 layer
可以保存多個 image 在一個 tar 檔裡 只能保存一個 container 在一個 tar 檔裡

使用上,docker save 用途很明確是分享 image。而 docker export 比較像是想把目前運作中的 container 狀態保存下來,拿到別台機器上做別的用途,比方說 debug 等。

上面的範例是有把 container 停止才下 docker export 指令,實際上也可以用在執行中的 container。

指令補充說明

docker save

把 image 使用 tar 打包輸出。預設會使用標準輸出(STDOUT),用法:

docker save [OPTIONS] IMAGE [IMAGE...]

因為是使用標準輸出,所以會使用導出(>)的方法輸出檔案,也可以使用下面這個參數來取代導出:

  • -o|--output 不使用標準輸出,改使用輸出檔案,後面接檔案名稱即可

docker load

把打包的 tar 檔載入到 Docker repository 裡。預設會使用標準輸入(STDIN),用法:

docker image load [OPTIONS]

類似 save,只是它是使用導入(<)來讀取檔案內容,一樣可以使用參數來取代導入:

  • -i|--input 不使用標準輸入,改成直接指定檔案

docker export

把 container 的檔案系統使用 tar 打包輸出。預設會使用標準輸出(STDOUT),用法:

docker export [OPTIONS] CONTAINER

docker save 類似,也有使用導出(>)的方法輸出檔案,也有參數可以取代導出:

  • -o|–output 不使用標準輸出,改使用輸出檔案,後面接檔案名稱即可

docker import

把打包的 tar 檔的檔案系統導入到 Docker repository 裡。預設會使用標準輸入(STDIN),用法:

docker image import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

docker load 類似,使用導入(<)來讀取檔案內容,但它沒有選項可以取代導入,而是改成使用參數的方法。下面是使用導入與不使用的對照範例:

docker image import myimage < my-export.tar
docker image import my-export.tar myimage

今日自我回顧

基本操作 container 的方法,與 build image 的方法在這二十天裡已說明完了,這樣已可應付八成開發上遇到的問題。

明天會開始說明更詳細的部分,包括各元件的細節,或是維運相關的功能等。

參考資料