docker已成為現在流行的一個開源容器引擎,我們在工作中也經常要與其打交道。這篇文章將主要從以下四個大方面闡述作者對docker的理解,希望能幫助大家由淺入深的或者是查漏補缺的更好的使用docker,歡迎大家在評論區留言交流。
docker裡的一些基本概念
docker常用指令詳解
Dockerfile最佳實踐
docker面試題
Docker裡的一些基本概念
鏡像和容器的關係
由圖中可以看出,紅色部分的只讀層為鏡像,而容器就是多層可讀層+一層讀寫層。
image鏡像
鏡像是由一堆可讀層組成,除了最下邊一層,每層都有一個指向下層的指針。image由Dockerfile構建而來,每層都對應一個dockerfile指令,例如下邊的Dockerfile,每個命令會創建一層鏡像。
FROM ubuntu:18.04COPY . /appRUN make /appCMD python /app/app.pycontainer容器
容器=鏡像+讀寫層。可以通過docker run [image]從一個鏡像運行一個容器。
Dockerfile
指定鏡像的生成規則,可以通過docker build -f [Dockerfile]生成一個鏡像。
容器是怎麼來的
Docker指令
根據上一部分我們知道了一個容器是怎麼來的,下邊列出的是作者在平時工作中經常會使用到的一些指令以及詳解。
Dockerfile
假如我們有如下Dockerfile,關於Dockerfile的詳解會在下一部分。
# syntax=docker/dockerfile:1FROM node:12-alpinecopy hello.js ./hello.js #hello.js內容為console.log('hello world');CMD ["node", "hello.js"]build
我們可以通過build指令從Dockerfile生成一個image。docker build -t hello:v1 .-t 表示我想要生成image的name和一個可選的tag,格式為name:tag。. 注意:這裡的 .並不是表示Dockerfile的位置,而是此次構建的context,後邊會詳解。那麼大家可能注意到我們並沒有指定Dockerfile的路徑,因為build默認會從當前目錄尋找一個叫 Dockerfile的文件。如果你的Dockerfile不在當前路徑呢,我們可以使用-f [path]來指定其路徑。其他常用的和image相關的指令還有:
docker images:查看當前宿主機上所有的image
docker tag hello:v1 hello:v2:把image的tag從v1變成v2
docker rmi [image-id]:刪除一個指定的image
run
docker run hello:v1我們可以把容器運行起來了。當然docker run有很多參數,我經常用的有:
a. -d: 以detached mode運行b. -p: 後加3000:3000即表示把容器內的3000埠發布到宿主機的3000端 口上
c. -v: /src:/src,把宿主機的src目錄綁定到容器內
d. -it: 以交互模式運行container
其他常用的和container相關的指令還有:
a. docker ps -a:查看所有宿主機上的container以及其狀態等信息
b. docker stop [container-id]:停止一個容器
c. docker rm [container-id]:刪除一個容器,-f表示強制。
Dockerfile常用指令詳解下圖列出的是筆者記錄的需要常用的一些指令及其特點。
其中包括的要點有:
ADD和COPY的區別,分別在什麼時候使用:
所有的文件複製都使用copy命令
add <src>的src可以是一個遠程url,如果是可解壓文件如 tar.gz, 會自動解壓
數據的兩種綁定方式,使用 docker inspect [container-id] 可以找到Mounts欄位查看
目錄綁定,type=bind
數據卷綁定,type=volume
dockerfile裡設置的VOLUME欄位的作用:
注意:dockerfile裡的volume只能指定容器內的目錄,不能指定主機的目錄,可以在docker run的時候通過-v 進行覆蓋,如果不覆蓋,會默認在/var/lib/docker/volumes/xxx創建匿名volume。所以dockerfile裡的VOLUME欄位的作用是為了防止用戶忘記在運行容器時忘記將動態文件掛在為卷。
CMD和ENTRYPOINT的區別:
a. CMD可以作為ENTRYPOINT的參數,docker把CMD命令拼接到ENTRYPOINT之後
b. CMD可以在run時候被參數覆蓋,而ENTRYPOINT不可以
c. ENTRYPOINT可以執行一個腳本,這樣CMD的參數會被腳本接收
d. 一般最佳實踐是將ENTRYPOINT設置為容器的main command
e. 多個CMD只有最後一個生效,同理ENTRYPOINT也一樣CMD和ENTRYPOINT的兩種語法:
a. EXEC寫法:CMD ["executable","param1","param2"]
b. EXEC寫法:CMD ["executable","param1","param2"]
c. 永遠使用EXEC寫法ENV和RUN export的區別:
a. 通過ENV設置的會持續存在,包括multiStage的dockerfile
b. RUN export只會在當前image層有效,如下例子:
build這個image,最後一步的輸出結果為:
我們可以看到,FOO 是輸出來了,因為ENV可以跨多層生效,BAR 沒有輸出,而BAZ輸出了,是因為RUN export只存在於當前層。ARG命令:
a. ARG指定的參數不能在容器運行時讀取
b. ARG指定的參數可以在docker build時候通過—build-arg覆蓋
c. 對於multiStage的dockerfile,需要在FROM之後使用如下指令重新指定 一下,這是因為ARG只在一當前它所指定的stage中生效ARG DARSHAN_VER=3.1.6
FROM fedora:29 as buildARG DARSHAN_VERRUN curl -O "<ftp://ftp.mcs.anl.gov/pub/darshan/releases/darshan-${DARSHAN_VER}.tar.gz>" \\ && tar ..
FROM fedora:29ARG DARSHAN_VERCOPY --from=build "/usr/local/darshan-${DARSHAN_VER}" "/usr/local/darshan-${DARSHAN_VER}"..WORKDIR
WORKDIR aWORKDIR bWORKDIR cRUN pwd #a/b/cmultiStage多階段構建
docker從17.5開始支持多階段構建,是為了解決:
鏡像層次多,體積過大,部署時間長
原始碼可能洩露
通過多個FROM指定多個構建階段,前邊階段可能是多個builder,把最終需要delivery的binary或其他file 複製到最終鏡像。
FROM golang:alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as pr
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]可以通過如下指令build某個stage的image:
docker build --target builder -t alexellis2/href-counter:latest .Dockerfile最佳實踐keep small
使用合適的base image,例如openJDK代替Ubuntu + jdk;常用的linux鏡像一般有Ubuntu,CentOS,Alpine,一般更推薦使用Alpine,其大小分別如下:
如果你要運行的包包含所有的依賴,那麼可以使用空的image:scratch
...
# 運行:使用scratch作為基礎鏡像FROM scratch as prod# 在build階段複製可執行的go二進位文件appCOPY --from=builder /go/release/app /# 啟動服務CMD ["/app"]c. 多階段構建,使用maven build java app,然後tomcat deploy
高效利用緩存,儘量把變化最小的放在前邊。例如把需要下載的依賴和業務代碼分開:
a. 對於前端項目,可以ADD package.json ./RUN npm iADD src ./RUN npm run buildb. 對於golang項目,可以
ADD go.mod go.sum ./RUN go mod downloadADD server ./RUN go build app.go利用.dockerignore來減小build context,可以提高docker load context的速度,例如:
指令串聯,一方面可以減少image分層,縮小體積;另一方面避免達到最大層數(127)。例如多個RUN合併為一個:
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執行yum install -y 的時候可以一次install多個工具,例如:
yum install -y gcc gcc-c++ make執行apt-get install -y的時候增加選項— no-install-recommends,去除非必須的依賴
使用RUN echo $(ls -al) 代替 RUN ls
在實際使用的過程中發現ls不會輸出內容,加上echo後可以了。不知道大家有沒有遇到這種情況。優先使用copy over add。
Docker面試題
Docker和虛擬機的區別
傳統虛擬機是虛擬化出來一套硬體,在其上運行作業系統,而docker是直接運行在宿主的內核,沒有自己的內核,也沒有硬體虛擬化,更加輕便。ADD和COPY的區別,見上邊。
docker-compose vs k8s vs docker swarm
docker-compose:單機node集群編排
k8s:多主機集群
swarm:多主機集群
還有其他一些,基本上文都覆蓋到了,也歡迎大家補充。
總結本文主要介紹了筆者在工程中的一些常見docker用法,可能需要有一些docker基礎之後食用更佳,歡迎評論區留言討論。