1. 程式人生 > >Docker快速入門——Docker鏡像制作

Docker快速入門——Docker鏡像制作

hub shadow powers libc roo str this href redis

Docker快速入門——Docker鏡像制作

一、Dockerfile腳本

1、Dockerfile腳本簡介

Dockerfile是一個文本文件,其內包含了一條條的指令(Instruction),每一條指令構建一層,因此每一條指令的內容就是描述該層應當如何構建。
Dockerfile文件示例如下:

##  Dockerfile文件格式
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# 1、第一行必須指定 基礎鏡像信息
FROM centos

# 2、維護者信息
MAINTAINER docker_user [email protected]

# 3、鏡像操作指令
RUN yum install -y nginx

# 4、容器啟動執行指令
CMD /usr/sbin/nginx

Dockerfile分為四部分:基礎鏡像信息、維護者信息、鏡像操作指令、容器啟動執行指令。第一部分必須指明基礎鏡像名稱;第二部分通常說明維護者信息;第三部分是鏡像操作指令,例如RUN指令,每執行一條RUN 指令,鏡像添加新的一層,並提交;第四部分是CMD指令,指明運行容器時的操作命令。
Dockerfile官方文檔:
https://docs.docker.com/engine/reference/builder/
Dockerfile最佳實踐文檔:
https://docs.docker.com/engine/userguide/eng-
image/dockerfile_best-practices/
Docker官方鏡像Dockerfile:

https://github.com/docker-library/docs

2、FROM指令

FROM用於指定基礎鏡像,因此Dockerfile中FROM是必備指令,並且必須是第一條指令。
在Docker Store有非常多的高質量的官方鏡像,有直接可用的服務類鏡像,如nginx、redis、mongo、mysql 、httpd、ph、tomcat 等;有方便開發、構建、運行各種語言應用的鏡像,如node、openjdk、 python、ruby、golang等;有基礎的操作系統鏡像,如ubuntu、debian、centos、fedora、alpine等。
Docker存在一個特殊的scratch鏡像,scratch鏡像是虛擬的概念,並不實際存在,表示一個空白的鏡像。

如果以scratch 為基礎鏡像,不以任何鏡像為基礎,後續所寫的指令將作為鏡像第一層開始存在。不以任何系統為基礎,直接將可執行文件復制進鏡像如swarm、coreos/etcd。Linux下靜態編譯的程序並不需要有操作系統提供運行時支持,所需的一切庫都已經在可執行文件裏,因此直接FROM scratch會讓鏡像體積更加小巧。
FROM語法格式為:

FROM <image>
FROM <image>:<tag>
FROM <image>:<digest>

FROM限制如下:
A、FROM必須是Dockerfile中第一條非註釋命令
B、在一個Dockerfile文件中創建多個鏡像時,FROM可以多次出現。只需在每個新命令FROM前,記錄提交上次的鏡像ID。
C、tag或digest是可選的,如果不使用這兩個值時,會使用latest版本的基礎鏡像。

3、RUN指令

在鏡像的構建過程中執行特定的命令,並生成一個中間鏡像。格式:

#shell格式
RUN <command>
#exec格式
RUN ["executable", "param1", "param2"]

RUN命令將在當前image中執行任意合法命令並提交執行結果。命令執行提交後,就會自動執行Dockerfile中的下一個指令。
RUN指令創建的中間鏡像會被緩存,並會在下次構建中使用。如果不想使用緩存鏡像,可以在構建時指定--no-cache參數,如:docker build --no-cache。

4、COPY指令

COPY指令語法格式:

COPY <源路徑>...   <目標路徑>  
COPY ["<源路徑1>",...  "<目標路徑>"]   

COPY 指令將從構建上下文目錄中<源路徑>的文件/目錄復制到新的一層的鏡像內的<目標路徑>位置。
<源路徑>可以是多個,甚至可以是通配符,其通配符規則要滿足Go 的filepath.Match規則,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

目標路徑可以是容器內的絕對路徑,也可以是相對於工作目錄的相對路徑(工作目錄可以用WORKDIR指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行創建缺失目錄。
使用COPY指令,源文件的各種元數據都會保留,比如讀、寫、執行權限、文件變更時間等。

5、ADD指令

ADD指令在COPY基礎上增加了一些功能,比如<源路徑>可以是一個URL,Docker引擎會試圖去下載URL鏈接的文件放到<目標路徑>。
在構建鏡像時,復制上下文中的文件到鏡像內,格式:

ADD <源路徑>... <目標路徑>
ADD ["<源路徑>",... "<目標路徑>"]

如果Docker發現文件內容被改變,則後續指令都不會再使用緩存。

6、CMD指令

CMD用於指定在容器啟動時所要執行的命令。CMD有三種格式:

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

省略可執行文件的exec格式,使CMD中的參數當做ENTRYPOINT的默認參數,此時ENTRYPOINT應該是exec格式。
如果CMD是/bin/bash,使用docker run -it ubuntu啟動容器時,會直接執行進入bash。docker run -it ubuntu cat /etc/os-release會在啟動容器時輸出系統版本信息。
在CMD指令格式上,推薦使用exec格式,exec格式在解析時會被解析為JSON數組,因此必須使用雙引號,而不要使用單引號。
如果使用shell格式,CMD命令會被包裝為sh -c的參數的形式進行執行。比如:
CMD echo $HOME會將其變更為:
CMD ["sh", "-c","echo $HOME"]

7、ENTRYPOINT指令

ENTRYPOINT指令用於給容器配置一個可執行程序。每次使用鏡像創建容器時,通過ENTRYPOINT指定的程序都會被設置為默認程序。ENTRYPOINT有兩種形式:

ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

通過docker run執行的命令不會覆蓋ENTRYPOINT,而docker run命令中指定的任何參數,都會被當做參數再次傳遞給ENTRYPOINT。Dockerfile中只允許有一個ENTRYPOINT命令,多指定時會覆蓋前面的設置,而只執行最後的ENTRYPOINT指令。
docker run運行容器時指定的參數都會被傳遞給ENTRYPOINT,且會覆蓋 CMD命令指定的參數。執行docker run image -d時,-d指定的參數將被傳遞給入口點。也可以通過docker run --entrypoint重寫 ENTRYPOINT 入口點。
ENTRYPOINT ["/usr/bin/nginx"]

8、ENV指令

ENV指令用於設置環境變量,後續的指令可以直接使用。

ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

ENV示例如下:

ENV VERSION=1.0 DEBUG=on     NAME="Happy Feet"

9、ARG指令

ARG指令用於指定傳遞給構建運行時的變量。

ARG <name>[=<default value>]

通過ARG指定兩個變量:

ARG site
ARG build_user=scorpio

上述指令指定site和build_user兩個變量,其中build_user指定了默認值。使用docker build構建鏡像時,可以通過

--build-arg <varname>=<value>

選項參數來指定或重設置相應變量的值。
docker build --build-arg site=www.baidu.com -t baidu/test .
build_user變量使用默認值scorpio。

10、VOLUME指令

VOLUME指令用於創建掛載點,即向基於所構建鏡像創始的容器添加卷:
VOLUME ["/data"]
一個卷可以存在於一個或多個容器的指定目錄,該目錄可以繞過聯合文件系統,並具有以下功能:
A、卷可以容器間共享和重用
B、容器並不一定要和其它容器共享卷
C、修改卷後會立即生效
D、對卷的修改不會對鏡像產生影響
E、卷會一直存在,直到沒有任何容器在使用它
VOLUME可以將源代碼、數據或其它內容添加到鏡像中,而不提交到鏡像中,並使多個容器間共享數據。

11、EXPOSE指令

EXPOSE指令為構建的鏡像設置監聽端口,使容器在運行時監聽。格式如下:

EXPOSE <port> [<port>...]

EXPOSE指令並不會讓容器監聽host的端口,如果需要容器監聽Host端口,需要在docker run時使用?-p、-P?參數來發布容器端口到host的某個端口上。

12、WORKDIR指令

WORKDIR指令用於在容器內設置一個工作目錄。
WORKDIR /path/to/workdir
通過WORKDIR設置工作目錄後,Dockerfile中的後續命令RUN、CMD、ENTRYPOINT、ADD、COPY 等命令都會在工作目錄下執行。

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

pwd最終將會在?/a/b/c?目錄中執行。使用docker run運行容器時,可以通過-w參數覆蓋構建時所設置的工作目錄。

13、USER指令

USER指令用於指定運行鏡像所使用的用戶。
USER daemon
使用USER指定用戶時,可以使用用戶名、UID 或 GID,或是兩者的組合。

USER user
USER user:group
USER uid
USER uid:gid
USER user:gid
USER uid:group

使用USER指定用戶後,Dockerfile中的後續命令RUN、CMD、ENTRYPOINT 都將使用該用戶。鏡像構建完成後,通過docker run 運行容器時,可以通過-u參數來覆蓋所指定的用戶。

14、HEALTHCHECK指令

HEALTHCHECK [OPTIONS] CMD command  
通過運行一個容器內部的命令來檢測容器是否健康
HEALTHCHECK NONE
關閉任何來自基礎image的健康檢測
options
--interval=DURATION?(default:?30s)
--timeout=DURATION?(default:?30s)
--retries=N?(default:?3)

15、ONBUILD指令

ONBUILD指令用於設置鏡像觸發器。
ONBUILD [INSTRUCTION]
當所構建的鏡像被用作其它鏡像的基礎鏡像,鏡像中的觸發器將會被觸發。當鏡像被使用時,可能需要做一些處理:

[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

16、LABEL

LABEL指令用於為鏡像添加元數據,元數以鍵值對的形式指定。

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

使用LABEL指定元數據時,一條LABEL指定可以指定一或多條元數據,指定多條元數據時不同元數據之間通過空格分隔。推薦將所有的元數據通過一條LABEL指令指定,以免生成過多的中間鏡像。
LABEL version="1.0" description="hello world" by="scorpio"
LABEL指定的元數據可以通過docker inspect查看。

17、STOPSIGNAL

STOPSIGNAL指令用於設置停止容器所要發送的系統調用信號。
STOPSIGNAL signal
所使用的信號必須是內核系統調用表中的合法的值,如:SIGKILL。

18、SHELL

SHELL指令用於設置執行命令(shell)所使用的的默認shell類型。
SHELL ["executable", "parameters"]
SHELL在Windows環境下比較有用,Windows下通常會有cmd和 powershell兩種shell,可以通過SHELL來指定所使用的shell類型。

二、Dockerfile構建鏡像

1、Dockerfile構建簡介

docker build命令會根據Dockerfile文件及上下文構建新Docker鏡像。構建上下文是指Dockerfile所在的本地路徑或一個URL(Git倉庫地址)。構建上下文環境會被遞歸處理,所以構建所指定的路徑還包括子目錄,而URL還包括其中指定的子模塊。
構建會在Docker後臺守護進程(daemon)中執行,而不是CLI中。構建前,構建進程會將全部內容(遞歸)發送到守護進程。通常,應該將一個空目錄作為構建上下文環境,並將Dockerfile文件放在該目錄下。
在構建上下文中使用的Dockerfile文件,是一個構建指令文件。為了提高構建性能,可以通過.dockerignore文件排除上下文目錄下不需要的文件和目錄。
在Docker構建鏡像的第一步,docker CLI會先在上下文目錄中尋找.dockerignore文件,根據.dockerignore?文件排除上下文目錄中的部分文件和目錄,然後把剩下的文件和目錄傳遞給Docker服務。
Dockerfile文件一般位於構建上下文的根目錄下,也可以通過-f指定Dockerfile文件的位置:
docker build -f /path/to/a/Dockerfile .
構建時,還可以通過-t參數指定構建成鏡像的倉庫、標簽。如果存在多個倉庫下,或使用多個鏡像標簽,就可以使用多個-t參數:
docker build -t nginx/v3:1.0.2 -t nginx/v3:latest .
在Docker守護進程執行Dockerfile中的指令前,首先會對Dockerfile進行語法檢查,有語法錯誤時會返回錯誤提示信息。
Dockerfile文件:

FROM ubuntu:14.04  
ADD run.sh /  
VOLUME /data  
CMD ["./run.sh"]  

Dockerfile文件構建的容器如下:
技術分享圖片

2、同構的鏡像構建

同構的鏡像構建是指鏡像構建環境與運行環境兼容。
同構鏡像構建一般要求編譯環境與鏡像所使用的base image是兼容的,比如在Ubuntu 14.04上編譯應用,並將應用打入基於ubuntu系列base image的鏡像。因為應用的編譯環境與其部署運行的環境是兼容的,在Ubuntu 14.04下編譯出來的應用,可以基本無縫地在基於ubuntu:14.04及後續版本base image鏡像中運行;但在不完全兼容的base image中,比如CentOS中就可能會運行失敗。

package main 

import ( 
        "net/http" 
        "log" 
        "fmt" 
) 

func home(w http.ResponseWriter, req *http.Request) { 
        w.Write([]byte("Welcome to this website!\n")) 
} 

func main() { 
        http.HandleFunc("/", home) 
        fmt.Println("Webserver start") 
        fmt.Println("  -> listen on port:1111") 
        err := http.ListenAndServe(":1111", nil) 
        if err != nil { 
                log.Fatal("ListenAndServe:", err) 
        } 
} 

編譯:
go?build?-o?httpserver?httpserver.go?
Dockerfile文件:

From ubuntu:14.04 

COPY ./httpserver /root/httpserver 
RUN chmod +x /root/httpserver 

WORKDIR /root 
ENTRYPOINT ["/root/httpserver"] 

構建httpserver服務鏡像:
docker?build?-t?httpserver:latest?.?
啟動httpserver服務容器:
docker?run?httpserver?
基於ubuntu基礎鏡像構建出的應用鏡像太過臃腫,因此有必要基於golang:latest構建自己專用的golang-builder image,Dockerfile.build可以用於build golang-builder image:

FROM golang:latest 
WORKDIR /go/src 
COPY httpserver.go . 
RUN go build -o httpserver ./httpserver.go 

構建golang-builder鏡像:
docker?build?-t?golang-builder:latest?-f?Dockerfile.build?.?
從golang-builder創建一個容器appsource
docker create --name appsource golang-builder:latest
從appsource容器中將httpserver拷貝到主機當前目錄
docker cp appsource:/go/src/httpserver ./
刪除appsource容器
docker rm -f appsource
刪除golang-builder鏡像
docker rmi golang-builder:latest
從當前目錄構建出httpserver鏡像
docker build -t httpserver:latest .
httpserver鏡像的大小依舊停留在200MB。要想減小httpserver鏡像的大小,必須使用更小的base image,即alpine 。 Alpine image的大小不到4M,再加上應用的size,最終應用鏡像的大小估計可以縮減到20M以下。
Dockerfile.alpine?文件:

From alpine:latest 

COPY ./httpserver /root/httpserver 
RUN chmod +x /root/httpserver 

WORKDIR /root 
ENTRYPOINT ["/root/httpserver"] 

構建alpine版應用鏡像:
docker build -t httpserver-alpine:latest -f Dockerfile.alpine .
啟動httpserver-alpine容器會失敗,因為alpine image並非ubuntu環境的同構image。

3、異構的鏡像構建

異構鏡像構建是指構建環境與運行環境不兼容。
Go將runtime中的C代碼都用Go重寫,對libc的依賴已經降到最低,但提供了兩個版本的實現:C實現和Go實現。默認情況下,即在CGO_ENABLED=1情況下,程序和預編譯的標準庫都采用C的實現。因此采用不同libc實現的debian系和alpine系自然存在不兼容的情況。考慮異構鏡像創建首先對Go程序進行靜態構建,然後將靜態構建後的Go應用放入alpine image中。
Dockerfile.build文件如下:

FROM golang:alpine 

WORKDIR /go/src 
COPY httpserver.go . 

RUN go build -o httpserver ./httpserver.go 

構建builder鏡像:

docker?build?-t?myrepo/golang-static-builder:latest?-f?Dockerfile.build?.?
docker create --name appsource golang-static-builder:latest 
docker cp appsource:/go/src/httpserver ./ 
docker rm -f appsource 
docker rmi golang-static-builder:latest 
docker build -t httpserver-alpine:latest -f Dockerfile.alpine . 

運行httpserver服務容器:
docker?run?httpserver-alpine:latest?
alpine版golang builder鏡像Dockerfile:

FROM golang:alpine 

WORKDIR /go/src 
COPY httpserver.go . 

RUN go build -o httpserver ./httpserver.go 

三、Docker多階段構建

1、Dockerfile多階段構建

2017年5月發布的?Docker 17.05.0-ce?中,Docker官方提供了簡便的多階段構建(multi-stage build)方案。
對於多階段構建,可以在Dockerfile中使用多個FROM語句。每個FROM指令可以使用不同的基礎鏡像,作為一個構建階段,多條?FROM?就是多階段構建,雖然最後生成的鏡像只能是最後一個階段的結果,但是,能夠將前置階段中的文件拷貝到後邊的階段中。
多階段構建最大的使用場景是將編譯環境和運行環境分離。

# 編譯階段
FROM golang:1.10.3

COPY server.go /build/

WORKDIR /build

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags ‘-w -s‘ -o server

# 運行階段
 FROM scratch

# 從編譯階段的中拷貝編譯結果到當前鏡像中
COPY --from=0 /build/server /

ENTRYPOINT ["/server"]

Dockerfile的COPY指令--from=0參數,從前邊的階段中拷貝文件到當前階段中,多個FROM語句時,0代表第一個階段。除了使用數字,還可以給階段命名,比如:

# 編譯階段
FROM golang:1.10.3 as builder

COPY server.go /build/

WORKDIR /build

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags ‘-w -s‘ -o server

# 運行階段
 FROM scratch

# 從編譯階段的中拷貝編譯結果到當前鏡像中
COPY --from=builder /build/server /

ENTRYPOINT ["/server"]

COPY –from指令從單獨的image中復制,使用本地image名稱,本地或Docker鏡像倉庫中可用的標記或標記ID。?

2、停在特定構建階段

構建映像時,不一定需要構建整個Dockerfile文件的每個階段,可以指定目標構建階段。使用Dockerfile構建,在builder階段停止:
docker build --target builder -t builder:latest .
停在特定構建階段非常適合的場景如下:
A、調試特定的構建階段
B、在debug階段,啟用所有調試或工具,而在production階段盡量精簡
C、在testing階段,應用程序將填充測試數據,但在production階段則使用生產數據

3、Dockerfile多項目構建

利用多階段構建可以多個項目的二進制文件構建在一個鏡像中發布。

from debian as build-essential
arg APT_MIRROR
workdir /src

from build-essential as A
copy srcA .
run make

from build-essential as B
copy srcB .
run make

from alpine
copy --from=A binA .
copy --from=B binB .
cmd ...

四、C++鏡像制作

1、C++應用開發

HelloDocker.cpp文件如下:

#include <iostream>

int main()
{
    std::cout << "Hello, Docker!" << std::endl;
    return 1;
}

2、查找C++鏡像

docker search gcc
技術分享圖片
包含多種版本的gcc,包括嵌入式版本的gcc-arm-embedded-docker

3、下載C++鏡像

docker pull gcc
技術分享圖片

4、gcc鏡像查看

docker images

5、使用GCC鏡像制作鏡像

Dockerfile文件編寫:

FROM gcc:latest
RUN  mkdir -p /home/user/docker/src/HelloDocker
COPY HelloDocker.cpp /home/user/docker/src/HelloDocker
WORKDIR /home/user/docker/src/HelloDocker
RUN  g++ HelloDocker.cpp -o HelloDocker
CMD ["./HelloDocker"]

使用Dockerfile文件創建鏡像:
docker build -t hellodocker:v1 .
技術分享圖片
鏡像查看:
docker images
啟動鏡像:
docker run -d hellodocker:v1
容器運行情況查看
docker ps

6、使用可執行程序制作鏡像

Dockerfile文件編寫:

FROM gcc:latest
RUN  mkdir -p /home/user/docker/HelloDocker
COPY HelloDocker /home/user/docker/HelloDocker
WORKDIR /home/user/docker/HelloDocker
#RUN  g++ HelloDocker.cpp -o HelloDocker
CMD ["./HelloDocker"]

構建鏡像:
docker build -t hellodocker:v1 .
啟動容器:
docker run -d hellodocker:v1

Docker快速入門——Docker鏡像制作