Docker入門之創建鏡像初步

目錄

目錄 1

1. 前言 1

2. 基本概念 2

2.1. 倉庫 2

2.2. 鏡像ID和容器ID 2

3. 最簡鏡像 2

3.1. 目錄結構 2

3.2. hello.go 2

3.3. Dockerfile 3

3.4. CMD和ENTRYPOINT 3

3.5. RUN和CMD 4

3.6. 生成鏡像 4

3.7. 啓動容器 5

4. 鏡像進階 5

4.1. 下載基礎鏡像 6

4.2. 準備本地程序源碼 6

4.3. 編寫Dockerfile 6

4.4. 生成鏡像 7

4.5. 啓動容器 7

5. 常見問題 7

5.1. stat /bin/sh: no such file or directory 7

5.2. COPY failed: ... stat no such file or directory 7

5.3. exec user process caused "no such file or directory" 8

附:安裝GO 8

 

  1. 前言

本文介紹在CentOS7上從構建一個最簡單無依賴的鏡像開始,逐步揭示Docker鏡像的構建和Dockerfile的應用。

什麼是鏡像?可理解鏡像(image)爲一個可執行程序文件,而容器(container)則是進程(運行態),Kubernetes(即k8s)中的概念POD則相當於進程組。

謹記:容器運行在Linux內核之上,不包含位於內核之上的glibc等庫,以及ls等命令。如果容器中的程序依賴glibc等庫或者依賴ls等命令,則容器自身應當包含這些設施。另外,容器中的程序等必須和內核兼容,否則將會遇到“FATAL: kernel too old”錯誤,該錯誤和庫文件ld-linux.so有關。

  1. 基本概念
    1. 倉庫

Docker倉庫(Repository)是存儲Docker鏡像的地方。

    1. 鏡像ID和容器ID

鏡像(image)是靜態的,容器(container)是運行中的鏡像。如果說鏡像是程序文件,則容器是進程。把鏡像ID看作文件名,則容器ID可視爲進程ID,因此每次啓動的容器ID是不相同的。

同一鏡像可以啓動多個容器,容器間的ID不會相同:

# docker ps

CONTAINER ID IMAGE     COMMAND     CREATED       STATUS       PORTS  NAMES

7518f632b6d0 centos  "/bin/bash" 4 seconds ago Up 2 seconds        focused_turing

d97bd379589c centos  "/bin/bash" 6 minutes ago Up 6 minutes        friendly_nightingale

  1. 最簡鏡像

從最簡鏡像開始,有助於快速瞭解Dockerfile和Docker鏡像的構建。

    1. 目錄結構

# tree /root/docker/hello

/root/docker/hello

|-- Dockerfile

|-- hello

`-- hello.go

 

0 directories, 3 files

    1. hello.go

GO編譯出來的可執行程序不依賴libc、libdl、linux-vdso和libonion等庫,可以構建最簡單的Dockerfile和最小的鏡像。hello.go源代碼如下:

# cat hello.go

package main

import "fmt"

func main() {

  fmt.Println("Hello, world!\n");

}

 

編譯hello.go,生成可執行程序hello:

# go build -o hello hello.go

# ls

hello  hello.go

    1. Dockerfile

編寫一個最簡單(不基於任何已有鏡像)的Dockerfile,僅將本地的hello程序打包到鏡像中,並在啓動容器時運行hello。內容如下:

# cat Dockerfile

FROM scratch

COPY hello /

CMD ["/hello"]

 

Dockerfile格式解釋:

關鍵詞

說明

#

表示註釋

FROM

用於指定基礎鏡像,scratch表示不基於任何基礎鏡像。

COPY

表示複製本地文件到容器的指定目錄,注意本地文件目錄是相對Dockerfile文件所在的目錄,而不是系統的根目錄。如果是遠端的文件,則需使用ADD命令。

CMD

用於指定啓動容器時默認執行的命令,一個Dockerfile只有最後一條CMD有效,其它的CMD會被忽略,CMD有三種書寫格式。

    1. CMD和ENTRYPOINT

如果在Dockerfile中沒有指定ENTRYPOINT,執行命令“docker run”也沒有指定“--entrypoint”,則執行CMD指定的命令。另外,可通過命令行參數“--entrypoint”覆蓋ENTRYPOINT。

Dockerfile中的CMD有三種書寫格式:

 

書寫格式

說明

格式1

CMD ["executable","param1","param2"]

EXEC執行方式

格式2

CMD ["","param2"]

指定了ENTRYPOINT時,作爲ENTRYPOINT的參數,請注意ENTRYPOINT也分EXECShell兩種書寫格式。

格式3

CMD command param1 param2

Shell執行方式,這要求鏡像中有可執行程序“/bin/sh”,執行時實際是:

/bin/sh -c "command param1 param2",

如果鏡像中無“/bin/sh”,則在啓動容器時報錯“stat /bin/sh: no such file or directory”。

 

  1. 什麼是EXEC執行方式?

# /bin/whoami

root

 

  1. 什麼是Shell執行方式?

# sh -c "/bin/whoami"

root

 

如果CMD和ENTRYPOINT組合使用,則兩者均需JSON數組格式。

    1. RUN和CMD

Dockerfile中的每一條RUN命令均會產生一個新的鏡像,因此應當儘可能減少RUN命令數,如使用“&&”將多條寫成一條。

RUN mkdir /data/test && chown test /data/test

 

RUN和CMD完全不同,RUN是生成鏡像時執行,而CMD是啓動容器時執行。RUN和鏡像相關,CMD和容器相關。

    1. 生成鏡像

執行命令“docker build”生成鏡像(也叫構建鏡像,一個鏡像由鏡像ID唯一標識),執行命令“docker images”查看鏡像列表,生成鏡像有點類似於編譯。

# docker images

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

 

# 參數“--tag”用於指定鏡像名(或叫鏡像標籤),

# 如果不指定“--tag”,則鏡像名爲匿名(<none>)。

# 如果文件Dockerfile沒有發生變化,

# 則重複執行build不會生成新的鏡像。

# docker build --tag hello . # 或docker build --tag hello -f Dockerfile .

Sending build context to Docker daemon  2.013MB

Step 1/3 : FROM scratch

 --->

Step 2/3 : COPY hello /

 ---> be473a78a240

Step 3/3 : CMD /hello

 ---> Running in e6584dd16fe2

Removing intermediate container e6584dd16fe2

 ---> 92672788bc94

Successfully built 92672788bc94 <-- 這是鏡像ID

Successfully tagged hello:latest

 

# docker images # “IMAGE ID”爲鏡像ID,這裏值爲92672788bc94

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

hello               latest              92672788bc94        2 seconds ago       2.01MB

    1. 啓動容器

最簡單的啓動容器方法:

# docker run hello

Hello, world!

 

 

也可如下方式啓動容器:

docker run -it hello

docker run -i -t hello

也可帶上“--rm”參數(容器停止後自動刪除):

docker run -it --rm hello

 

這裏的參數“-i”和參數“-t”,分別表示:

參數

作用

-i

i是interactive的縮寫,作用是讓容器的標準輸入保持打開,以進入命令交互界面模式

-t

t是tty的縮寫,作用是讓docker分配一個僞終端並綁定到容器的標準輸入上

-d

d是deamon的縮寫,作用是讓容器以後臺守護方式運行

-p

p是port的縮寫,作用是指定端口映射

-P

P是port的縮寫,作用是隨機分配端口

--name

爲容器指定一個新的名字

--rm

容器退出時自動刪除,如果不指定,則需要通過命令“docker rm”來刪除

  1. 鏡像進階

這一節的鏡像不從零開始,而是基於已有鏡像生成新的鏡像。

從scratch創建一個實用的鏡像不易,也是不必要的,除了學習目的。容器雖然運行在本地的Linux內核之上,但依賴的庫(運行時環境)卻需要容器本身包含,比如核心的libc和libdl等庫。這也是在創建最簡鏡像時採用GO程序的原因,避免了這些依賴,然而實際中很難避免這些依賴,因此最好的辦法是基於其它鏡像構建自己的鏡像。

alpine是Docker官方提交的只有5MB多大小的Linux鏡像,包管理工具爲apk,可以用來做學習研究用。alpine不帶glibc庫,它帶的是musl libc(一個輕量級的C標準庫)。如果有glibc需求,可用基於alpine的alpine-glibc鏡像,這個也有Docker官方提供的。

另外,還有一個第三方的tinycore鏡像,只有7MB多大小,包含了libc等更爲豐富基礎設施。如果可以訪問docker.io,則可直接執行命令“docker pull tinycore”將tinycore鏡像拉取到本地,否則通過Docker的鏡像導出(先在一臺可以訪問docker.io機器上pull鏡像,然後導出成tar文件)和導入功能間接拉取到。

不同的基礎鏡像除了所帶的庫等不同外,鏡像大小也是考慮的重要因素之一,原則上越小越好,本節內容官方的Centos鏡像。

    1. 下載基礎鏡像

這裏選擇官方的centos作爲基礎鏡像,執行拉取鏡像命令:

# docker pull docker.io/centos

 

如想找其它的centos鏡像,可執行命令“docker search centos”搜索。如果本地不能訪問docker.io,則可在一臺可訪問docker.io機器先拉取下來,然後使用Docker的導出(save)導入(load)載入進來。

檢查centos鏡像是否可用:

# docker images | grep centos

centos              latest              0f3e07c0138f        2 months ago        220MB

 

檢查鏡像centos版本:

# docker run -it --rm centos cat /etc/centos-release

CentOS Linux release 8.0.1905 (Core)

    1. 準備本地程序源碼

以C程序爲例,源代碼如下:

# cat echo1.c

#include <stdio.h>

int main(int argc, char* argv[]) {

  if (argc == 1) printf("=> ECHO1: docker\n");

  else printf("=> ECHO1: %s\n", argv[1]);

}

 

編譯生成可執行程序:

# gcc -g -o echo1 echo1.c

    1. 編寫Dockerfile

# cat Dockerfile.echo1

FROM centos

COPY echo1 /

CMD ["/echo1"]

    1. 生成鏡像

# docker build --tag echo1 -f Dockerfile.echo1 .

    1. 啓動容器

默認不帶參數方式運行(因爲Dockerfile.echo1中沒有ENTRYPOINT,所以執行的是CMD部分命令):

# docker run -it --rm echo1

=> Hello: docker

 

帶參數方式執行(實爲“--entrypoint”方式):

# docker run -it --rm echo1 /echo1 centos

=> ECHO1: centos

 

上述等同於:

# docker run -it --rm --entrypoint='/echo1' echo1

=> ECHO1: docker

 

“--entrypoint”帶參數方式如下(參數在最後,並不是“--entrypoint”值的一部分):

# docker run -it --rm --entrypoint='/echo1' echo1 world

=> ECHO1: world

  1. 常見問題
    1. stat /bin/sh: no such file or directory

啓動窗口時報如下錯誤,可能是Dockerfile中的CMD格式錯誤:

docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown.

ERRO[0000] error waiting for container: context canceled

 

原因是CMD書寫爲Shell格式,但鏡像中沒有/bin/sh這個文件。

    1. COPY failed: ... stat no such file or directory

在創建鏡像時報如下錯誤,是因爲COPY命令的源文件或目錄不是相對Dockerfile所在目錄的路徑,比如使用了本地路徑。

COPY failed: stat /data/docker/tmp/docker-builder891858880/bin/sh: no such file or directory

 

比如下列COPY即會報這個錯誤:

COPY /bin/sh /bin/

 

解決辦法是先將/bin/sh複製到Dockerfile文件所在目錄,然後再創建鏡像。

    1. exec user process caused "no such file or directory"

運行容器時報如下錯誤:

standard_init_linux.go:211: exec user process caused "no such file or directory"

 

這個錯誤有多種原因,比如:

  1. Dockerfile非UNIX格式(換符符);
  2. 容器中的可執行程序依賴的庫不存在,比如沒有libc庫;
  3. CMD格式錯誤。

附:安裝GO

安裝GO步驟:

  1. 下載安裝包

從GO的官網(https://golang.org/dl/)上下載,選擇Linux安裝包(本文下載的爲go1.13.5.linux-amd64.tar.gz)。

  1. 上傳安裝包

將安裝包(比如go1.13.5.linux-amd64.tar.gz)上傳到/usr/local目錄。如果Linux能夠訪問網絡,也可直接在/usr/local上下載,比如:

# cd /usr/local

# wget https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz

 

  1. 安裝和設置

在/usr/local目錄下解壓即完成安裝,實際上也可能解壓到其它目錄。

# cd /usr/local

# tar xzf go1.13.5.linux-amd64.tar.gz

 

設置環境變量,以方便執行(go.sh可無可執行權限):

# cat /etc/profile.d/go.sh

export PATH=/usr/local/go/bin:$PATH

 

如果不想重新登錄而直接生效,可手工直接執行一次go.sh:

# source /etc/profile.d/go.sh

 

 

發佈了519 篇原創文章 · 獲贊 73 · 訪問量 446萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章