docker學習筆記(3)- 映象

Hui_Tong發表於2022-03-16

簡介

docker學習筆記(1)- 架構概述一節中可以看到映象是docker三大元件之一,可以將Docker映象類比為虛擬機器的模版。

  1. 映象由多個層組成,每層疊加之後從外部看就像一個獨立的物件,映象的內部包括作業系統、應用程式、應用執行時所必須的依賴包等。
  2. 使用映象時從倉庫中拉取映象到Docker主機,然後使用該映象可以啟動一個或多個容器,也可以將容器構建為映象。

具體的概念與實現在後續實現Docker的基礎技術中記錄,先整理映象的用法

相關命令

映象加速

國內訪問Docker hub有速度緩慢甚至會無法的情況,換用國內雲廠商提供的加速服務,可以新增多個源,在/etc/docker/daemon.json檔案中新增如下json內容,沒有daemon.json可以自己新建

{
 "registry-mirror": [
         "https://hub-mirror.c.163.com/",
         "https://reg-mirror.qiniu.com"
  ]
}
# 重啟docker
systemctl daemon-reload
systemctl restart docker
# 檢視是否生效,在使用docker pull時會快很多
docker info
>
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Registry Mirrors:
  https://hub-mirror.c.163.com/
  https://reg-mirror.qiniu.com/

搜尋映象

docker search centos --filter=stars=20
>
NAME                           DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
centos                         The official build of CentOS.                   7066      [OK]
centos/systemd                 systemd enabled base container.                 105                  [OK]
centos/mysql-57-centos7        MySQL 5.7 SQL database server                   92
centos/postgresql-96-centos7   PostgreSQL is an advanced Object-Relational …   45
centos/httpd-24-centos7        Platform for running Apache httpd 2.4 or bui…   43
centos/python-35-centos7       Platform for building and running Python 3.5…   39
centos/php-56-centos7          Platform for building and running PHP 5.6 ap…   34
centos/mysql-56-centos7        MySQL 5.6 SQL database server                   22
  • NAME:映象名字

  • DESCRIPTION:映象描述資訊,預設會被截斷,可使用--no-trunc取消截斷

  • STARS:收藏數,--filter=starts=20,搜尋收藏數大於20的映象

  • OFFICIAL:由docker官方維護支援的映象,最好使用官方映象作為基礎映象

  • AUTOMATED:該映象由docker hub的自動構建流程建立的

拉取映象

# 拉取映象 docker pull <映象名稱>
# 不提供倉庫名預設為docker.io,tag預設為最新的tag,使用者名稱預設為官方映象
docker pull ubuntu
>
Using default tag: latest
latest: Pulling from library/ubuntu
7c3b88808835: Pull complete
Digest: sha256:8ae9bafbb64f63a50caab98fd3a5e37b3eb837a3e0780b78e5218e63193961f9
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest # 最後一行顯示完整的映象名稱
# 指定倉庫名和tag
docker image pull docker.io/library/ubuntu:16.04
>
16.04: Pulling from library/ubuntu
58690f9b18fc: Pull complete
b51569e7c507: Pull complete
da8ef40b9eca: Pull complete
fb15d46c38dc: Pull complete
Digest: sha256:0f71fa8d4d2d4292c3c617fda2b36f6dabe5c8b6e34c3dc5b0d17d4e704bd39c
Status: Downloaded newer image for ubuntu:16.04
docker.io/library/ubuntu:16.04
  • 映象名稱的格式為:Docker倉庫地址/使用者名稱/軟體名:tag
  • 上面提到映象是分層儲存的,可以看到pull時也是一層一層進行,給出每層ID的前12位,拉取完成後給出給出一個sha256的摘要,用來確保下載一致性

推送映象

使用docker push推送映象到倉庫,也可以推送到私有倉庫,在docker學習筆記(2)- 倉庫一節中有記錄

列出映象

# 列出本地映象
docker image ls
>
REPOSITORY                 TAG       IMAGE ID       CREATED        SIZE
registry                   2         8948869ebfee   5 days ago     24.2MB
ubuntu                     latest    2b4cba85892a   10 days ago    72.8MB
portainer/portainer-ce     latest    ed396c816a75   4 weeks ago    280MB
joxit/docker-registry-ui   latest    c4f5113ae220   4 months ago   24.8MB
centos                     7         eeb6ee3f44bd   5 months ago   204MB
centos                     latest    5d0da3dc9764   5 months ago   231MB
ubuntu                     16.04     b6f507652425   6 months ago   135MB
radial/busyboxplus         latest    fffcfdfce622   7 years ago    12.9MB
# 列出所有映象,包括中間層映象
docker image ls -a
# 檢視映象、容器、儲存卷等實際消耗空間
docker system df
>
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          8         2         984.2MB   680.5MB (69%)
Containers      2         2         0B        0B
Local Volumes   5         0         184.3MB   184.3MB (100%)
Build Cache     0         0         0B        0B
# 列出映象sha256摘要
docker image ls --digests
REPOSITORY                                       TAG       DIGEST                                                                    IMAGE ID       CREATED        SIZE
busybox                                          latest    sha256:caa382c432891547782ce7140fb3b7304613d3b0438834dce1cad68896ab110a   2fb6fc2d97e1   2 days ago     1.24MB
www.codemachine.in/busybox                       latest    sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38   2fb6fc2d97e1   2 days ago     1.24MB
  • 一個映象可以對應多個標籤,IMAGE ID是映象的唯一標識

  • Docker Hub中顯示的映象體積是網路傳輸即壓縮後的體積,而下載到本地後會解壓縮,所以本地看到的映象SIZE更大

  • 通過image ls 列出的映象體積並不是本地實際消耗的空間,映象是多層儲存結構並且可以繼承複用,因此不同的映象可能會使用相同的基礎映象,Union FS使得相同的層只需儲存一份

format展示

# 僅僅顯示image ID
docker image ls -q
# 刪除所有列出的映象
docker image rm $(docker image ls -q)

使用go模版語法

# 列出映象ID和倉庫名
docker image ls --format "{{.ID}}: {{.Repository}}"
>
2fb6fc2d97e1: busybox
2fb6fc2d97e1: www.codemachine.in/busybox
5d0da3dc9764: 172.17.73.129:6000/centos
5d0da3dc9764: centos
5d0da3dc9764: www.codemachine.in/centos
5d0da3dc9764: www.codemachine.in/centos
32b8411b497a: dockersamples/atseasampleshopapp_reverse_proxy
8dbf7c60cf88: dockersamples/visualizer
# 自定義列顯示
docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
>
IMAGE ID       REPOSITORY                                       TAG
2fb6fc2d97e1   busybox                                          latest
2fb6fc2d97e1   www.codemachine.in/busybox                       latest
5d0da3dc9764   172.17.73.129:6000/centos                        latest
5d0da3dc9764   centos                                           latest
5d0da3dc9764   www.codemachine.in/centos                        galen
5d0da3dc9764   www.codemachine.in/centos                        latest
32b8411b497a   dockersamples/atseasampleshopapp_reverse_proxy   <none>
8dbf7c60cf88   dockersamples/visualizer                         <none>

dangling映象

這類映象沒有標籤和倉庫名,在pull或者build了新版本映象後,新舊映象同名,舊的映象名稱與tag被取消,產生了dangling映象

# 檢視dangling映象
docker image ls -f dangling=true
>
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
<none>              <none>              00285df0df87        5 days ago          342 MB
# -f後還可以跟since,before,label等引數過濾

刪除映象

可以使用映象ID、映象名、sha256摘要來刪除映象

# OPTION: -f 強制刪除
docker image rm [OPTION] <NAME>
# 刪除未使用的映象(清理多餘映象)
docker image prune
[docker@docker1 ~]$ docker images
REPOSITORY                                       TAG       IMAGE ID       CREATED        SIZE
busybox                                          latest    2fb6fc2d97e1   2 days ago     1.24MB
www.codemachine.in/busybox                       latest    2fb6fc2d97e1   2 days ago     1.24MB
[docker@docker1 ~]$ docker image rm busybox
Untagged: busybox:latest
Untagged: busybox@sha256:caa382c432891547782ce7140fb3b7304613d3b0438834dce1cad68896ab110a
[docker@docker1 ~]$ docker images
REPOSITORY                                       TAG       IMAGE ID       CREATED        SIZE
www.codemachine.in/busybox                       latest    2fb6fc2d97e1   2 days ago     1.24MB
[docker@docker1 ~]$ docker image rm 2fb6fc2d97e1
Untagged: www.codemachine.in/busybox:latest
Untagged: www.codemachine.in/busybox@sha256:14d4f50961544fdb669075c442509f194bdc4c0e344bde06e35dbd55af842a38
Deleted: sha256:2fb6fc2d97e10c79983aa10e013824cc7fc8bae50630e32159821197dda95fe3
Deleted: sha256:797ac4999b67d8c38a596919efa5b7b6a4a8fd5814cb8564efa482c5d8403e6d

幾種不會刪除映象的情況:

  1. Untagged:一個映象可能有多個標籤標籤指向,可以看到下面兩個映象的IMAGE ID是一樣的,因此只刪除一個並沒有真正delete映象,而是刪除了標籤,所有標籤都Untagged後才會真正刪除映象,Deleted
  2. 從上層向基礎層方向依次查詢,如果有其他映象依賴當前映象也無法真正Deleted
  3. 如果有容器以此映象為基礎啟動,不管容器是否執行,該映象都不可刪除

Dockerfile

構建映象實際上是在每一層新增配置、檔案等。將每一層修改、安裝、構建、操作等命令寫入dockerfile指令碼中,這樣在構建映象時使用了什麼命令,做了什麼操作,同時可以配合多階段構建來精簡映象體積和降低部署複雜度。

docker build用法

# 指定映象名,使用當前目錄下的Dockerfile
docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .
# 指定Dockerfile路徑
docker build -f /path/to/a/Dockerfile .
# 從標準輸入中讀取Dockerfile進行構建
docker build - < Dockerfile
cat Dockerfile | docker build -
# 讀取壓縮包構建
docker build - < context.tar.gz
  • 構建上下文(Context):docker採用的是C/S架構,在執行時docker engine提供了一組REST API,在使用客戶端時其實是通過API與docker engine互動,那麼就算我們是在本機執行docker命令,諸如ADD、COPY這類這令時,實際上還是使用遠端呼叫的方式在服務端完成(docker engine),docker build -t <NAME> .的意思是將當前目錄作為構建映象上下文的路徑,然後將該路徑的所有內容打包上傳到docker engine,之後docker engine用收到的檔案構建映象。

  • 一般將dockerfile置於專案根目錄,如果該目錄下有些東西不希望在構建時傳給docker engine,可以新增到.dockerignore檔案中。

指令

書寫Dockerfile的常用指令,詳細參考見Dockerfile reference

FROM

指定基礎映象

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
# --platform:提供映象使用平臺,linux/amd64, linux/arm64, or windows/amd64
# AS <name>: 指定此構建階段的別名,供後面的FROM和COPY引用
  • 特殊映象scratch是一個空白的映象,執行的指令會在映象第一層開始寫,靜態編譯適用

RUN

執行命令列命令

# shell格式
RUN <command>
RUN /bin/bash -c "echo hello"
# exec格式
RUN ["executable", "param1", "param2"]
RUN ["/bin/bash", "-c", "echo hello"]
  • 每一行RUN執行就會使映象新增一層,過多使用RUN使得映象臃腫很容易就達到Union FS限制的最大層數,利用 && 和換行 的方式執行多條命令在這一層將所有的事情做完是個不錯的選擇
  • 通常用於安裝軟體包

COPY

從Context目錄中的檔案複製到映象新一層的目錄下

COPY [--chown=<user>:<group>] <源路徑>... <目標路徑>
or
COPY [--chown=<user>:<group>] ["<源路徑1>",... "<目標路徑>"]
# 源路徑可以是多個或滿足Go語言filepath.Match規則的萬用字元
COPY package.json /usr/src/app/
COPY hom* /mydir/
COPY hom?.txt /mydir/
  • <目標路徑> 可以是容器內的絕對路徑,也可以是相對於工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)
  • 使用 COPY 指令,原始檔的各種後設資料都會保留

CMD

Docker 不是虛擬機器,容器就是程式。既然是程式,那麼在啟動容器的時候,需要指定所執行的程式及引數。CMD 指令就是用於指定預設的容器主程式的啟動命令的。

CMD echo $HOME
or
CMD [ "sh", "-c", "echo $HOME" ]
  • 可被替換:比如centos預設CMD /bin/bash,那麼使用docker run -it centos就會進入/bin/bash下,如果使用docker run -it centos cat /etc/redhat-release就會變成輸出release號後停止

ENTRYPOINT

與CMD功能相似,只不過CMD容器執行時若新增了引數(如上所說),那麼預設CMD的引數就會被替換掉,而ENTRYPOINT會將新增的引數跟在原有引數的後邊,這樣就可以像使用命令一樣使用容器

ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]

也可以做啟動容器前的準備工作,以下為redis建立使用者,然後為ENTRYPOINT指定指令碼,該指令碼判斷CMD引數是否是啟動redis-server,如果是使用redis使用者啟動,如果是其他操作則繼續使用root,這樣既保證了服務執行的安全性,又不妨礙使用root使用者做一些除錯和資訊獲取等操作

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD [ "redis-server" ]

# docker-entrypoint.sh
#!/bin/sh
...
# allow the container to be started with `--user`
if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
	find . \! -user redis -exec chown redis '{}' +
	exec gosu redis "$0" "$@"
fi

exec "$@"

ENV

設定環境變數,在後面的指令中引用

ENV <key>=<value> ...

VOLUME

在映象中建立掛載點,但是無法指定建立在主機的對應目錄,可以通過docker inspect <CONTAINER NAME>檢視Source掛載目錄是哪個

VOLUME ["<路徑1>", "<路徑2>"...]
or
VOLUME <路徑>

EXPOSE

宣告容器執行時打算用什麼埠,並不會自動在宿主機和容器進行埠對映。可以使用docker run -p <主機埠:容器埠>進行埠對映,也可以使用docker run -P隨機對映EXPOSE的埠

EXPOSE <port> [<port>/<protocol>...]
EXPOSE 80/tcp

WORKDIR

指定當前工作目錄,後面各層(RUN,CMD,ENTRYPOINT,COPY,ADD)的當前目錄就被改為WORKDIR目錄,如果該目錄不存在則會自動建立

WORKDIR /path/to/workdir
# 示例,pwd的路徑為/a/b/c
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

USER

指定使用者身份,影響後面各層操作的使用者,使用者必須事先建立好

USER <使用者名稱>[:<使用者組>]

如果是執行SHELL時候要改變身份,不要使用 su 或者 sudo,這些都需要比較麻煩的配置,而且在 TTY 缺失的環境下經常出錯。建議使用 ``gosu`

# 建立 redis 使用者,並使用 gosu 換另一個使用者執行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下載 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 設定 CMD,並以另外的使用者執行
CMD [ "exec", "gosu", "redis", "redis-server" ]

LABEL

為映象新增後設資料

LABEL <key>=<value> <key>=<value> <key>=<value> ...

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

SHELL

用來指定RUN ENTRYPOINT CMD 指令的 shell,Linux 中預設為 ["/bin/sh", "-c"]

SHELL ["executable", "parameters"]

ONBUILD

一般作為基礎映象時使用,該指令在構建當前映象時不會執行,當其他映象以此為基礎映象時才會執行

ONBUILD <其它指令>

使用git倉庫構建

docker build -t hello-world git://github.com/docker-library/hello-world.git\#master:amd64/hello-world
>
Sending build context to Docker daemon  22.02kB
Step 1/3 : FROM scratch
 --->
Step 2/3 : COPY hello /
 ---> e0499e772bd9
Step 3/3 : CMD ["/hello"]
 ---> Running in 1eeb706f26e2
Removing intermediate container 1eeb706f26e2
 ---> 6abb50a2e5cc
Successfully built 6abb50a2e5cc
Successfully tagged hello-world:latest
# 檢視映象
docker images
REPOSITORY               TAG       IMAGE ID       CREATED          SIZE
hello-world              latest    6abb50a2e5cc   47 seconds ago   13.3kB

指定要構建的git倉庫地址,切換到master分支,進入amd64/hello-world目錄開始構建

使用tar壓縮包構建

docker engine下載該tar包並自動解壓,以解壓後的資料夾作為上下文開始構建

docker build http://server/context.tar.gz

Dockerfile多階段構建

Docker v17.05開始支援多階段構建 (multistage builds),解決了以下問題:

  • 如果使用一個Dockerfile,映象體積過大使得部署時間過長(比如編譯依賴元件繁多,但實際執行中並不需要),而且容易洩露原始碼
  • 如果使用多個Dockerfile(比如編譯和執行分開進行),中間需要指令碼整合不同構建階段內容是個比較複雜的工作,容易出現問題

下面對比單個Dockerfile構建和多階段構建一個go helloworld程式的區別。

# app.go
package main

import "fmt"

func main(){
    fmt.Printf("Hello World!");
}

使用單個檔案

Dockerfile

FROM golang:alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk --no-cache add git ca-certificates

WORKDIR /go/src/github.com/go/helloworld/

COPY app.go .

RUN go mod init

RUN GOPROXY="https://goproxy.io" GO111MODULE=on go get -d -v github.com/go-sql-driver/mysql \
  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
  && cp /go/src/github.com/go/helloworld/app /root

WORKDIR /root/

CMD ["./app"]

build

docker build -t go/helloworld:1 .
# 執行容器
docker container run go/helloworld:1
>
Hello World!%

多階段構建

Dockerfile

# 將此階段命名為builder
FROM golang:alpine as builder
# 解決下載go慢的問題
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk --no-cache add git

WORKDIR /go/src/github.com/go/helloworld/

RUN GOPROXY="https://goproxy.io" GO111MODULE=on go get -d -v github.com/go-sql-driver/mysql

COPY app.go .
# 處理go.mod缺失問題
RUN go mod init
# 編譯app.go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# 製作應用映象,此階段命名為prod
FROM alpine:latest as prod

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=0 /go/src/github.com/go/helloworld/app .

CMD ["./app"]

build

docker build -t go/helloworld:2 .
# 執行容器
docker container run go/helloworld:2
>
Hello World!%

對比兩個映象的大小,可以看到通過多階段構建的方法,摒棄編譯所需環境依賴,最後的應用映象要精簡很多很多

docker image ls |grep go/hello
>
go/helloworld                    2         38f137a75add   6 minutes ago    7.86MB
go/helloworld                    1         e7606d3c0921   17 minutes ago   353MB

構建到某一階段

依據上面的Dockerfile,如果我們只想構建到Build階段的映象時,可以用--targe引數指定此階段別名來實現

docker build --target builder -t username/imagename:tag .

結束

本篇主要彙總go映象相關操作指令等,映象原理等架構技術會在後面深入學習docker底層實現時分析

學習自:
《Docker技術入門與實戰(第3版)》Nigel,Poulton(奈吉爾·波爾頓) 著,李瑞豐,劉康 譯
《深入淺出Docker》楊保華,戴王劍,曹亞侖 著
https://docs.docker.com/

相關文章