使用 Docker 時,最經常使用的命令無非是 docker container
和 docker image
相關的子命令,固然最初沒有管理類命令(或者說分組)的時候,最常使用的命令也無非是 docker run
docker commit
docker build
和 docker images
這些。linux
今天來聊一下和 Docker 中核心概念 image
相關的重要命令, docker build
或者說 docker image build
爲了簡便起見,下文的命令所有使用 docker build
。git
先簡單介紹下 Docker Image, 一般狀況下咱們將其稱之爲鏡像,鏡像是由多個層組成的文件,這些層用於在容器內執行代碼(命令)等。每一個鏡像基本上都是根據應用程序完整的可執行版本進行構建的,而且須要注意的是,它會依賴於主機的系統內核。當用戶在運行鏡像時,這將會建立一個或者多個容器實例。算法
Dockerd 是 Docker 的服務端,默認狀況下提供 Unix Domain Socket 鏈接,固然也能夠監聽某個端口,用於對外提供服務。 因此有時候,咱們也可使用服務器上的 Docker daemon 來提供服務,以加快構建速度及解決一些網絡問題之類的。docker
好的,基礎概念瞭解了, 那咱們開始進入正題。json
咱們知道構建鏡像的方法有多種,本文中咱們只介紹使用 Dockerfile 經過 docker build
的方式構建鏡像。後端
爲了簡便,咱們以一個簡單的 Dockerfile 開始。構建一個容器內使用的 kubectl 工具 (固然選擇它的緣由在於 kubectl 足夠大,並不考慮可用性,這個稍後解釋)性能優化
FROM scratch LABEL maintainer='Jintao Zhang <moelove.info>' ADD kubectl /kubectl ENTRYPOINT [ "/kubectl" ]
Dockerfile 足夠簡單,只是將 kubectl 的二進制文件拷貝進去,並將 Entrypoint 設置爲 kubectl 。服務器
我我的通常爲了不環境的污染,大多數的事情都在容器內完成。包括 dockerd 我也啓在容器內。其中的原理再也不介紹,能夠參考我以前的文章或分享。使用起來很簡單:網絡
docker run --privileged -d -P docker:stable-dind
注意這裏使用了 -P
因此本地會隨機映射一個端口,固然你也能夠直接指定映射到容器內的 2375 端口。app
(Tao) ➜ build git:(master) docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b56f6483614d docker:stable-dind "dockerd-entrypoint.…" 9 hours ago Up 9 hours 0.0.0.0:32769->2375/tcp trusting_babbage
咱們直接使用啓動在容器內的 dockerd 進行構建,經過上面的 docker ps
命令能夠看到是映射到了本地的 32769 端口。因此咱們使用如下命令進行構建:
(Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 images REPOSITORY TAG IMAGE ID CREATED SIZE (Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 build -t local/kubectl . Sending build context to Docker daemon 55.09MB Step 1/4 : FROM scratch ---> Step 2/4 : LABEL maintainer='Jintao Zhang <moelove.info>' ---> Running in ebcf44071bf0 Removing intermediate container ebcf44071bf0 ---> eb4ea1725ff2 Step 3/4 : ADD kubectl /kubectl ---> 1aad06c4dbb4 Step 4/4 : ENTRYPOINT [ "/kubectl" ] ---> Running in 2fc78fe974e3 Removing intermediate container 2fc78fe974e3 ---> 457802d4bf3e Successfully built 457802d4bf3e Successfully tagged local/kubectl:latest (Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 images REPOSITORY TAG IMAGE ID CREATED SIZE local/kubectl latest 457802d4bf3e 3 seconds ago 55.1MB
看日誌及結果,能夠看到咱們已經成功的構建了咱們所需的鏡像。說了這麼多,其實咱們今天的內容纔剛剛開始。
在本文一開始,我已經提過 Dockerd 是 Docker 的後端服務,經過上面的
docker -H 0.0.0.0:32769 images
這條命令能夠看到咱們經過 -H
指定了本地 32679 端口的 dockerd 服務,這實際上是個 HTTP 服務,咱們來驗證下。
(Tao) ➜ kubectl git:(master) curl -i 0.0.0.0:32769/_ping HTTP/1.1 200 OK Api-Version: 1.37 Docker-Experimental: false Ostype: linux Server: Docker/18.03.1-ce (linux) Date: Tue, 04 Sep 2018 17:20:51 GMT Content-Length: 2 Content-Type: text/plain; charset=utf-8 OK%
能夠看到幾條關鍵的信息 Api-Version: 1.37 這個代表了當前使用的 API 版本,本文的內容也是以 1.37 爲例進行介紹,這是當前的穩定版本。咱們也能夠經過 docker version
進行查看。
(Tao) ➜ kubectl git:(master) docker -H 0.0.0.0:32769 version Client: Version: 18.06.0-ce API version: 1.37 (downgraded from 1.38) Go version: go1.10.3 Git commit: 0ffa825 Built: Wed Jul 18 19:11:45 2018 OS/Arch: linux/amd64 Experimental: false Server: Engine: Version: 18.03.1-ce API version: 1.37 (minimum version 1.12) Go version: go1.9.5 Git commit: 9ee9f40 Built: Thu Apr 26 07:23:03 2018 OS/Arch: linux/amd64 Experimental: false
能夠看到我本地在用的 docker cli 版本較高,當鏈接到低版本的 dockerd 時,API 版本降級至與 dockerd 版本保持一致。
固然,你可能會問,若是是 dockerd 版本高會如何呢?其實我平常中的開發環境就是這樣,大多數 API 都沒什麼影響, 不過這並非今天的重點。
root@bdcdac73ee20:/# docker version Client: Version: 17.06.0-ce API version: 1.30 Go version: go1.8.3 Git commit: 02c1d87 Built: Fri Jun 23 21:15:15 2017 OS/Arch: linux/amd64 Server: Version: dev API version: 1.39 (minimum version 1.12) Go version: go1.10.3 Git commit: e8cc5a0b3 Built: Tue Sep 4 10:00:36 2018 OS/Arch: linux/amd64 Experimental: false
回到咱們上面的構建過程當中。咱們能夠看到日誌內容的第一行:
... Sending build context to Docker daemon 55.09MB
從這條日誌,咱們能夠獲得兩個信息:
第一條結論,咱們在上一小節已經討論過了,咱們來重點看下第二條結論。
(Tao) ➜ kubectl git:(master) ls -al 總用量 53808 drwxrwxr-x. 2 tao tao 4096 9月 5 01:00 . drwxrwxr-x. 3 tao tao 4096 9月 5 00:57 .. -rw-rw-r--. 1 tao tao 109 9月 5 01:00 Dockerfile -rwxrwxr-x. 1 tao tao 55084063 9月 5 00:53 kubectl (Tao) ➜ kubectl git:(master) du -sh . 53M . (Tao) ➜ kubectl git:(master) du -sh kubectl Dockerfile 53M kubectl 4.0K Dockerfile
按照咱們 Dockerfile 的內容,咱們須要將 kubectl 的二進制包放入鏡像內,因此 build context 雖然比二進制文件多出來 2M 左右的大小你也不會很意外。
但我這裏作了另外一個例子,很少贅述,代碼能夠在個人 [GitHub]() 中找到。這裏貼出來結果:
(Tao) ➜ text git:(master) ls -al 總用量 16 drwxrwxr-x. 2 tao tao 4096 9月 5 01:45 . drwxrwxr-x. 4 tao tao 4096 9月 5 01:44 .. -rw-rw-r--. 1 tao tao 77 9月 5 01:45 Dockerfile -rw-rw-r--. 1 tao tao 61 9月 5 01:45 file (Tao) ➜ text git:(master) du -b Dockerfile file 77 Dockerfile 61 file (Tao) ➜ text git:(master) docker -H 0.0.0.0:32769 build --no-cache=true -t local/file . Sending build context to Docker daemon 3.072kB ...
相信你看到這個結果已經明白我想表達的意思,咱們繼續探索下這個過程。
前面咱們已經說過,這就是個普通的 HTTP 請求,因此咱們固然能夠直接抓包來看看到底發生了什麼?
很簡單,經過 dockerd 的地址,使用 POST
方法,訪問 /build
接口, 固然實際狀況是會增長前綴,即我在上面提到的版本號,在目前的環境中使用的是 /v1.37/build
這個接口。
而這個請求攜帶了一些頗有用的參數,和頭信息。這裏我來簡單說下:
build 請求的頭部,主要有如下兩個
Content-Type
默認值爲 application/x-tar
,代表本身是一個歸檔。X-Registry-Config
這個頭部信息中包含着 registry 的地址及認證信息,而且以 base64 進行編碼。對 docker 熟悉的朋友或者看過我以前文章的朋友應該知道, Docker cli 在 login 成功後,會將認證信息保存至本地,密碼作 base64 保存。而 build 的時候則會將此信息再次 base64 進行編碼。經過這裏也能夠看出來,在使用遠端 Dockerd 的時候, 應該儘可能配置 TLS 以防止中間人攻擊,形成密碼泄漏等狀況。請求參數中,列幾個比較有意義的:
t
這其實就是咱們 docker build -t
時候指定的參數,而且,咱們能夠同時指定多個 -t
同時構建多個不一樣名稱的鏡像。memory
cpusetcpus
這些主要用於資源限制buildargs
若是想要了解這個參數,能夠回憶下 Dockerfile 中的 ARG
指令的用法固然,咱們想要探索的過程其實重點就在於請求頭部了, 整個請求的輸入流,必須是一個 tar
壓縮包,而且支持 identity
(不壓縮), gzip
, bzip2
, xz
等壓縮算法。
咱們來看下基本的實現:
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) { query, err := cli.imageBuildOptionsToQuery(options) if err != nil { return types.ImageBuildResponse{}, err } headers := http.Header(make(map[string][]string)) buf, err := json.Marshal(options.AuthConfigs) if err != nil { return types.ImageBuildResponse{}, err } headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) headers.Set("Content-Type", "application/x-tar") serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) if err != nil { return types.ImageBuildResponse{}, err } osType := getDockerOS(serverResp.header.Get("Server")) return types.ImageBuildResponse{ Body: serverResp.body, OSType: osType, }, nil }
這篇主要內容集中在 docker build 的過程及其原理上,爲何首先要寫這篇,主要是由於鏡像和咱們息息相關, 而且也是咱們使用的第一步。而不少狀況下,推動業務容器化也都要面臨着性能優化及其餘規範之類的。
其實關於 build 的細節還有不少,若是有空,我就再更新下一篇。
能夠經過下面二維碼訂閱個人文章公衆號【MoeLove】