docker學習筆記(3)- 映象
簡介
在docker學習筆記(1)- 架構概述一節中可以看到映象是docker三大元件之一,可以將Docker映象類比為虛擬機器的模版。
- 映象由多個層組成,每層疊加之後從外部看就像一個獨立的物件,映象的內部包括作業系統、應用程式、應用執行時所必須的依賴包等。
- 使用映象時從倉庫中拉取映象到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
幾種不會刪除映象的情況:
- Untagged:一個映象可能有多個標籤標籤指向,可以看到下面兩個映象的IMAGE ID是一樣的,因此只刪除一個並沒有真正delete映象,而是刪除了標籤,所有標籤都Untagged後才會真正刪除映象,Deleted
- 從上層向基礎層方向依次查詢,如果有其他映象依賴當前映象也無法真正Deleted
- 如果有容器以此映象為基礎啟動,不管容器是否執行,該映象都不可刪除
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/