天天看點

docker鏡像與容器操作

主要參考了docker從入門到實踐 ,遇到自己不會的又補充了一些内容

一、鏡像操作

1.擷取鏡像

  • Docker Registry 位址:<域名/IP>[:端口号]。預設位址是 Docker Hub(

    docker.io

    )。
  • 倉庫名:<使用者名>/<軟體名>。對于 Docker Hub,如果不給出使用者名,則預設為

    library

    ,也就是官方鏡像。

比如:

$ docker pull ubuntu:18.04
           

代表:

$ docker pull ubuntu:18.04 docker/library/ubuntu/:18.04
           

2.列出鏡像

鏡像 ID 則是鏡像的唯一辨別,一個鏡像可以對應多個 标簽。

鏡像體積

通過

docker system df

指令來便捷的檢視鏡像、容器、資料卷所占用的空間

$ docker system df
           

虛懸鏡像

虛懸鏡像是一個特殊的鏡像,這個鏡像既沒有倉庫名,也沒有标簽,均為

<none>

<none>               <none>              00285df0df87        5 days ago          342 MB
           

列出虛懸鏡像(dangling image)

$ docker image ls -f dangling=true
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              00285df0df87        5 days ago          342 MB
           

删除虛懸鏡像

docker image prune
           

列出所有鏡像

為了加速鏡像建構、重複利用資源,Docker 會利用 中間層鏡像。

$ docker image ls -a
           

列出部分鏡像

$ docker image ls ubuntu
$ docker image ls ubuntu:18.04
           

docker image ls

還支援強大的過濾器參數

--filter

,或者簡寫

-f

比如,我們希望看到在

mongo:3.2

之後建立的鏡像,可以用下面的指令:

$ docker image ls -f since=mongo:3.2
           

想檢視某個位置之前的鏡像也可以,隻需要把

since

換成

before

即可。

3.删除本地鏡像

其中,

<鏡像>

可以是

鏡像短 ID

鏡像長 ID

鏡像名

或者

鏡像摘要

比如:

$ docker image rm centos
Untagged: centos:latest
Untagged: [email protected]:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38
           

更精确的是使用

鏡像摘要

删除鏡像

$ docker image ls --digests

$ docker image rm [email protected]:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: [email protected]:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
           

Untagged 和 Deleted

鏡像的唯一辨別是其 ID 和摘要,而一個鏡像可以有多個标簽。

要求删除某個标簽的鏡像,而這個鏡像有多個标簽時,

Delete

行為就不會發生,隻是取消标簽

當該鏡像所有的标簽都被取消了,

Delete

行為會發生

4.定制鏡像

使用Dockerfile 建構鏡像。Dockerfile 是一個文本檔案,其内包含了一條條的 指令(Instruction),每一條指令建構一層,是以每一條指令的内容,就是描述該層應當如何建構。

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
           

FROM 指定基礎鏡像

FROM

就是指定 基礎鏡像,是以一個

Dockerfile

FROM

是必備的指令,并且必須是第一條指令。

RUN 執行指令

其格式有兩種:

  • shell 格式:

    RUN <指令>

    ,就像直接在指令行中輸入的指令一樣。剛才寫的 Dockerfile 中的

    RUN

    指令就是這種格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
           
  • exec 格式:

    RUN ["可執行檔案", "參數1", "參數2"]

    ,這更像是函數調用中的格式。

既然

RUN

就像 Shell 腳本一樣可以執行指令,那麼我們是否就可以像 Shell 腳本一樣把每個指令對應一個 RUN 呢?比如這樣:

FROM debian:stretch

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
           

Dockerfile 中每一個指令都會建立一層,而上面的這種寫法,建立了 7 層鏡像。這是完全沒有意義的,而且很多運作時不需要的東西,都被裝進了鏡像裡,比如編譯環境、更新的軟體包等等。結果就是産生非常臃腫、非常多層的鏡像,不僅僅增加了建構部署的時間,也很容易出錯。

正确的寫法應該是這樣:

FROM debian:stretch

RUN set -x; buildDeps='gcc libc6-dev make wget' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps
           
  • 之前所有的指令隻有一個目的,就是編譯、安裝 redis 可執行檔案。是以沒有必要建立很多層,僅僅使用一個

    RUN

    指令,并使用

    &&

    将各個所需指令串聯起來。要經常提醒自己,這并不是在寫 Shell 腳本,而是在定義每一層該如何建構。
  • Dockerfile 支援 Shell 類的行尾添加

    \

    的指令換行方式,以及行首

    #

    進行注釋的格式。
  • 這一組指令的最後添加了清理工作的指令,删除了為了編譯建構所需要的軟體,清理了所有下載下傳、展開的檔案,并且還清理了

    apt

    緩存檔案。這是很重要的一步,一定要確定每一層隻添加真正需要添加的東西,任何無關的東西都應該清理掉。

5.建構鏡像

其格式為:

例如:

$ docker build -t nginx:v3 .
           

注意其中的

.

,這個

.

是上下文路徑

-t

用法如下(通過

docker build --help

獲得)

-t, --tag list                Name and optionally a tag in the 'name:tag' format(
     --target string           Set the target build stage to build.
           
  • name:tag

    格式進行建構,其中tag是可選的
  • 設定目标建構階段去建構

建構上下文(Context)

了解

docker build

的工作原理:

​ Docker 在運作時分為 Docker 引擎(也就是服務端守護程序)和用戶端工具。

​ Docker 的引擎提供了一組 REST API,被稱為 Docker Remote API,而如

docker

指令這樣的用戶端工具,則是通過這組 API 與 Docker 引擎互動,進而完成各種功能。是以,雖然表面上我們好像是在本機執行各種

docker

功能,但實際上,一切都是使用的遠端調用形式在服務端(Docker 引擎)完成。也因為這種 C/S 設計,讓我們操作遠端伺服器的 Docker 引擎變得輕而易舉。

​ 建構鏡像時經常會需要将一些本地檔案複制進鏡像,比如通過

COPY

指令、

ADD

指令等。而

docker build

指令建構鏡像,其實并非在本地建構,而是在服務端,也就是 Docker 引擎中建構的。那麼在這種用戶端/服務端的架構中,如何才能讓服務端獲得本地檔案呢?

​ 這就引入了上下文的概念。當建構的時候,使用者會指定建構鏡像上下文的路徑,

docker build

指令得知這個路徑後,會将路徑下的所有内容打包,然後上傳給 Docker 引擎。這樣 Docker 引擎收到這個上下文包後,展開就會獲得建構鏡像所需的一切檔案。

如果在

Dockerfile

中這麼寫:

COPY ./package.json /app/
           

​ 這并不是要複制執行

docker build

指令所在的目錄下的

package.json

,也不是複制

Dockerfile

所在目錄下的

package.json

,而是複制 上下文(context) 目錄下的

package.json

​ 了解建構上下文對于鏡像建構是很重要的,避免犯一些不應該的錯誤。比如有些初學者在發現

COPY /opt/xxxx /app

不工作後,于是幹脆将

Dockerfile

放到了硬碟根目錄去建構,結果發現

docker build

執行後,在發送一個幾十 GB 的東西,極為緩慢而且很容易建構失敗。那是因為這種做法是在讓

docker build

打包整個硬碟,這顯然是使用錯誤。

​ 一般來說,應該會将

Dockerfile

置于一個空目錄下,或者項目根目錄下。如果該目錄下沒有所需檔案,那麼應該把所需檔案複制一份過來。如果目錄下有些東西确實不希望建構時傳給 Docker 引擎,那麼可以用

.gitignore

一樣的文法寫一個

.dockerignore

,該檔案是用于剔除不需要作為上下文傳遞給 Docker 引擎的。

​ 那麼為什麼會有人誤以為

.

是指定

Dockerfile

所在目錄呢?這是因為在預設情況下,如果不額外指定

Dockerfile

的話,會将上下文目錄下的名為

Dockerfile

的檔案作為 Dockerfile。

其它

docker build

的用法

直接用 Git repo 進行建構

docker build

還支援從 URL 建構,比如可以直接從 Git repo 中建構:

$ docker build -t hello-world https://github.com/docker-library/hello-world.git
# master:amd64/hello-world
           
用給定的 tar 壓縮包建構
$ docker build http://server/context.tar.gz
           

如果所給出的 URL 不是個 Git repo,而是個

tar

壓縮包,那麼 Docker 引擎會下載下傳這個包,并自動解壓縮,以其作為上下文,開始建構。

從标準輸入中讀取 Dockerfile 進行建構
docker build - < Dockerfile
           

or

cat Dockerfile | docker build -
           

如果标準輸入傳入的是文本檔案,則将其視為

Dockerfile

,并開始建構。這種形式由于直接從标準輸入中讀取 Dockerfile 的内容,它沒有上下文,是以不可以像其他方法那樣可以将本地檔案

COPY

進鏡像之類的事情。

從标準輸入中讀取上下文壓縮包進行建構
docker build - < context.tar.gz
           

如果發現标準輸入的檔案格式是

gzip

bzip2

以及

xz

的話,将會使其為上下文壓縮包,直接将其展開,将裡面視為上下文,并開始建構。

什麼是标準輸入?

執行一個shell指令行時通常會自動打開三個标準檔案,即标準輸入檔案(stdin),通常對應終端的鍵盤;标準輸出檔案(stdout)和标準錯誤輸出檔案(stderr),這兩個檔案都對應終端的螢幕。程序将從标準輸入檔案中得到輸入資料,将正常輸出資料輸出到标準輸出檔案,而将錯誤資訊送到标準錯誤檔案中。

輸入資料從終端輸入時,使用者費了半天勁輸入的資料隻能用一次。下次再想用這些資料時就得重新輸入。為此可以進行輸入重定向。輸入重定向是指把指令(或可執行程式)的标準輸入重定向到指定的檔案中。“輸入重定向運算符為

<

”,一般形式為:指令<檔案名。

可以用下面的指令把wc指令的輸入重定向為/etc/passwd檔案:

$ wc /etc/passwd

$ wc

的意思

wc将等待使用者告訴它統計什麼,這時shell就好象死了一樣,從鍵盤鍵入的所有文本都出現在螢幕上,但并沒有什麼結果,直至按下ctrl+d,wc才将指令結果寫在螢幕上。

二、容器操作

鏡像(

Image

)和容器(

Container

)的關系,就像是面向對象程式設計中的

執行個體

一樣,鏡像是靜态的定義,容器是鏡像運作時的實體。容器可以被建立、啟動、停止、删除、暫停等。

容器的實質是程序,但與直接在宿主執行的程序不同,容器程序運作于屬于自己的獨立的 命名空間。是以容器可以擁有自己的

root

檔案系統、自己的網絡配置、自己的程序空間,甚至自己的使用者 ID 空間。容器内的程序是運作在一個隔離的環境裡,使用起來,就好像是在一個獨立于宿主的系統下操作一樣。

1.啟動容器

啟動容器有兩種方式,一種是基于鏡像建立一個容器并啟動,另外一個是将在終止狀态(

exited

)的容器重新啟動。

因為 Docker 的容器實在太輕量級了,很多時候使用者都是随時删除和新建立容器。

建立并啟動

指令:

docker run

例如:

$ docker run ubuntu:18.04 /bin/echo 'Hello world'
Hello world
           

這跟在本地直接執行

/bin/echo 'hello world'

幾乎感覺不出任何差別。

下面的指令則啟動一個 bash 終端,允許使用者進行互動。

$ docker run -t -i ubuntu:18.04 /bin/bash
[email protected]:/#
           

其中,

-t

選項讓Docker配置設定一個僞終端(pseudo-tty)并綁定到容器的标準輸入上,

-i

則讓容器的标準輸入保持打開。

在互動模式下,使用者可以通過所建立的終端來輸入指令,例如

[email protected]:/# pwd
/
[email protected]:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
           
-t, --tty                            Allocate a pseudo-TTY(終端)
      --ulimit ulimit                  Ulimit options (default []) 
           
-i, --interactive                    Keep STDIN open even if not attached(标準輸入)
      --ip string                      IPv4 address (e.g., 172.30.100.104)
      --ip6 string                     IPv6 address (e.g., 2001:db8::33)
      --ipc string                     IPC mode to use
      --isolation string               Container isolation technology
      --kernel-memory bytes            Kernel memory limit
           

當利用

docker run

來建立容器時,Docker 在背景運作的标準操作包括:

  • 檢查本地是否存在指定的鏡像,不存在就從 registry 下載下傳
  • 利用鏡像建立并啟動一個容器
  • 配置設定一個檔案系統,并在隻讀的鏡像層外面挂載一層可讀寫層
  • 從宿主主機配置的網橋接口中橋接一個虛拟接口到容器中去
  • 從位址池配置一個 ip 位址給容器
  • 執行使用者指定的應用程式
  • 執行完畢後容器被終止

啟動已終止容器

docker container start

指令,直接将一個已經終止(

exited

)的容器啟動運作。

容器的核心為所執行的應用程式,所需要的資源都是應用程式運作所必需的。除此之外,并沒有其它的資源。可以在僞終端中利用

ps

top

來檢視程序資訊。

[email protected]:/# ps
  PID TTY          TIME CMD
    1 ?        00:00:00 bash
   11 ?        00:00:00 ps
           

可見,容器中僅運作了指定的 bash 應用。這種特點使得 Docker 對資源的使用率極高,是貨真價實的輕量級虛拟化。

檢視容器

docker ps
           

**備注:**docker ps 列出容器,預設列出隻在運作的容器;加-a可以顯示所有的容器,包括未運作的(例如異常退出(Exited)狀态的容器)。

2.守護态運作

更多的時候,需要讓 Docker 在背景運作而不是直接把執行指令的結果輸出在目前主控端下。此時,可以通過添加

-d

參數來實作。

在使用

-d

參數時,容器啟動後會進入背景。

-d, --detach                         Run container in background and print container ID
           

下面舉兩個例子來說明一下。

如果不使用

-d

參數運作容器。

$ docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
           

容器會把輸出的結果 (STDOUT) 列印到主控端上面

如果使用了

-d

參數運作容器。

$ docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
           

此時容器會在背景運作并不會把輸出的結果 (STDOUT) 列印到主控端上面(輸出結果可以用

docker logs

檢視)。

注: 容器是否會長久運作,是和

docker run

指定的指令有關,和

-d

參數無關。

使用

-d

參數啟動後會傳回一個唯一的 id,也可以通過

docker container ls

指令來檢視容器資訊。

$ docker container ls
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
77b2dc01fe0f  ubuntu:18.04  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        agitated_wright
           

要擷取容器的輸出資訊,可以通過

docker container logs

指令。

$ docker container logs [container ID or NAMES]
hello world
hello world
hello world
. . .
           

3.終止

兩種終止情況

  • 可以使用

    docker container stop

    來終止一個運作中的容器。
  • 此外,當 Docker 容器中指定的應用終結時,容器也自動終止。

對于上一章節中隻啟動了一個終端的容器,使用者通過

exit

指令或

Ctrl+d

來退出終端時,所建立的容器立刻終止。

終止狀态的容器可以用

docker container ls -a

指令看到。例如

$ docker container ls -a
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS                       PORTS     NAMES
14fc2feeaf75   ubuntu:18.04   "/bin/bash"              10 minutes ago   Exited (127) 8 seconds ago             vigorous_bartik
c7b40a45f89f   ubuntu:18.04   "/bin/echo 'Hello wo…"   16 minutes ago   Exited (0) 16 minutes ago              vigorous_beaver
235926bb3d95   nginx:alpine   "/docker-entrypoint.…"   5 days ago       Exited (0) 5 days ago                  web
f6e53ba8074b   ubuntu:18.04   "/bin/bash"              5 days ago       Exited (255) 4 days ago                serene_mccarthy
           

處于終止狀态的容器,可以通過

docker container start

指令來重新啟動。

此外,

docker container restart

指令會将一個運作态的容器終止,然後再重新啟動它。

4.進入容器

在使用

-d

參數時,容器啟動後會進入背景。

某些時候需要進入容器進行操作,包括使用

docker attach

指令或

docker exec

指令,推薦大家使用

docker exec

指令,原因會在下面說明。

attach

指令

下面示例如何使用

docker attach

指令。

$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
243c32535da7        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           nostalgic_hypatia

$ docker attach 243c
[email protected]:/#
           

注意: 如果從這個 stdin 中 exit,會導緻容器的停止。

exec

指令

docker exec

後邊可以跟多個參數,這裡主要說明

-i

-t

參數。

-i

:标準輸入

-t

:終端

$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
69d137adef7a        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           zealous_swirles

$ docker exec -i 69d1 bash
ls
bin
boot
dev
...

$ docker exec -it 69d1 bash
[email protected]:/#
           

如果從這個 stdin 中 exit,不會導緻容器的停止。這就是為什麼推薦大家使用

docker exec

的原因。

5.導出和導入

導出容器

如果要導出本地某個容器,可以使用

docker export

指令。

$ docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
7691a814370e        ubuntu:18.04        "/bin/bash"         36 hours ago        Exited (0) 21 hours ago                       test
$ docker export 7691a814370e > ubuntu.tar
           

這樣将導出容器快照到本地檔案。

導入容器快照

可以使用

docker import

從容器快照檔案中再導入為鏡像,例如

$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
test/ubuntu         v1.0                9d37a6082e97        About a minute ago   171.3 MB
           

or

$ docker import ubuntu.tar test/ubuntu:v1.0
REPOSITORY                                          TAG       IMAGE ID       CREATED         SIZE
test/ubuntu                                         v1.0      9e7643d858f0   7 seconds ago   63.3MB
           

此外,也可以通過指定 URL 或者某個目錄來導入,例如

$ docker import http://example.com/exampleimage.tgz example/imagerepo
           

注:使用者既可以使用

docker load

來導入鏡像存儲檔案到本地鏡像庫,也可以使用

docker import

來導入一個容器快照到本地鏡像庫。這兩者的差別在于容器快照檔案将丢棄所有的曆史記錄和中繼資料資訊(即僅儲存容器當時的快照狀态),而鏡像存儲檔案将儲存完整記錄,體積也要大。此外,從容器快照檔案導入時可以重新指定标簽等中繼資料資訊。

6.删除

删除容器

可以使用

docker container rm

來删除一個處于終止狀态的容器。例如

$ docker container rm trusting_newton
trusting_newton
           

如果要删除一個運作中的容器,可以添加

-f

參數。Docker 會發送

SIGKILL

信号給容器。

清理所有處于終止狀态的容器

docker container ls -a

指令可以檢視所有已經建立的包括終止狀态的容器,如果數量太多要一個個删除可能會很麻煩,用下面的指令可以清理掉所有處于終止狀态的容器。

$ docker container prune
           

參考資料:

docker從入門到實踐

datawhale docker教程

繼續閱讀