docker學習筆記(3)- 鏡像
簡介
在docker學習筆記(1)- 架構概述一節中可以看到鏡像是docker三大組件之一,可以將Docker鏡像類比為虛擬機的模版。
- 鏡像由多個層組成,每層疊加之後從外部看就像一個獨立的對象,鏡像的內部包括作業系統、應用程式、應用運行時所必須的依賴包等。
- 使用鏡像時從倉庫中拉取鏡像到Docker主機,然後使用該鏡像可以啟動一個或多個容器,也可以將容器構建為鏡像。
具體的概念與實現在後續實現Docker的基礎技術中記錄,先整理鏡像的用法
相關命令
鏡像加速
中國訪問Docker hub有速度緩慢甚至會無法的情況,換用中國雲廠商提供的加速服務,可以添加多個源,在/etc/docker/daemon.json文件中添加如下json內容,沒有daemon.json可以自己新建
{
"registry-mirror": [
"//hub-mirror.c.163.com/",
"//reg-mirror.qiniu.com"
]
}
# 重啟docker
systemctl daemon-reload
systemctl restart docker
# 查看是否生效,在使用docker pull時會快很多
docker info
>
Registry: //index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
127.0.0.0/8
Registry Mirrors:
//hub-mirror.c.163.com/
//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", "//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 "//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 //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="//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="//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》楊保華,戴王劍,曹亞侖 著
//docs.docker.com/