Dockerfile 是一個文字檔案,我們可以通過組合一條條的指令 (Instruction),來構建滿足我們需求的 Docker 映象
文件
Best practices for writing Dockerfiles
簡單上手
使用 Dockerfile 構建SpringBoot 工程的映象
- 新建 SpringBoot 專案,預設的埠是 8080 ,新建 Controller 和 Mapping
@RestController
public class HelloController {
@GetMapping("hello")
public String hello() {
return "hello world!";
}
}
啟動專案,訪問 http://localhost:8080/hello 測試
- 打 jar 包
注意,需要在 pom 中新增 spring-boot-maven-plugin 外掛,否則執行 jar 包時會提示:沒有主清單屬性
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
#打包
mvn package
target 目錄下就可以找到 .jar 檔案,我這裡的檔名為:demo-0.0.1-SNAPSHOT.jar
在 Linux 新建 ~/springboot
資料夾,並將 jar 包上傳到這個資料夾下
- 新建 Dockerfile
在這個檔案下新建 Dockerfile 檔案
# 基於 openjdk:8-jre 這個基礎映象進行構建
FROM openjdk:8-jre
# 這裡的 demo-0.0.1- SNAPSHOT.jar 要對應上傳的 jar 包名稱
# 將 本地 jar包 複製到容器內
COPY demo-0.0.1-SNAPSHOT.jar app.jar
# 開放 8080 埠
EXPOSE 8080
# 執行命令、引數
ENTRYPOINT ["java","-jar"]
CMD ["app.jar"]
儲存檔案,退出編輯器
- 編譯 Docker 映象
# build 是構建 Docker 映象的命令
# -t 指定映象的 tag
# 名稱:demo 版本:v1.0
# 最後的 . 表示 build context 目錄為當前目錄,目的是為了找到 所需的 jar 包
docker build -t demo:v1.0 .
- 啟動容器
# 前臺啟動剛構建的 SpringBoot 容器
# -p 對映容器8080埠 到宿主機的 8080 上
docker run -p 8080:8080 demo:v1.0
- 測試
訪問 Linux 的8080 埠,注意替換為自己的 Linux 的地址,並開放 8080 埠
http://192.168.43.161:8080/hello
build context
Dockerfile 預設會使用它自己所在的目錄作為 context,通過 docker 執行構建命令後,Docker daemon 會拷貝 context 目錄下的所有檔案
,所以 context 目錄不要放置專案無關的檔案,或者可以使用 .dockerignore
定義忽略檔案,也可以指定 context 路徑
# build 命令通過 Dockerfile 構建映象
# 指定 ~/dockerfile 為 build context
docker build ~/dockerfile
# 不需要新增檔案到 context 可以使用 -
docker build -
可以通過 stdin 的方式,避免生產 Dockerfile 檔案,直接 build 映象
docker build -t myimage:latest -<<EOF
FROM busybox
RUN echo "hello world"
EOF
除了可以指定 context外,還可以通過-f 指定 Dockerfile 所在的路徑
docker build -f dockerfiles/Dockerfile .
最佳實踐
非常推薦官方的 Dockerfile最佳實踐:Best practices for writing Dockerfiles
- 每個容器單一職責,有利於橫向擴充和複用
- 舊版強調減少層數以提高效能,現在只有 RUN, COPY, ADD 這幾個命令會建立層,其他命令只會建立中間層。並且只有使用到資源最終會被拷貝到最終映象
- 多個引數按字母順序排列,並使用空格和
\
進行分割,提高可讀性 --no-cache
不使用快取,預設 build 過程中如果檢查到有可重用的映象層則使用。從基礎映象開始,每一條命令逐一檢查,如果命令不一樣則快取失效。使用ADD
和COPY
則會校驗使用到的檔案校驗和
是否相同,除了這兩個命令,其他則不會通過檔案變化來決定是否匹配快取,而是僅通過命令本身是否一致來判斷是否匹配快取,比如:RUN apt-get -y update
會改變容器內的檔案,但是也只使用這個命令匹配快取,而不會通過檔案的變動。一旦快取失效,後續都會產生新的映象層
Dockerfile 指令 (instructions)
FROM
Dockerfile 的第一個命令一般都是 FROM,通過這個指定該映象的 Base Image,推薦基礎映象:alpine,因為它完整且輕量,如果不需要 Base Image 可以用 FROM scratch
,代表該映象基於一個空映象進行構建
RUN
由於上面提到的快取匹配原則,RUN apt-get update
命令可能會導致直接使用了原來快取的映象層,而沒有執行該命令獲取最新的軟體列表,可以使用 RUN apt-get update && apt-get install -y
來使快取失效
可以使用 \
分割,提高可讀性:
RUN apt-get update && apt-get install -y \
curl
CMD
指定容器啟動時執行的命令,通常預設採用的格式:CMD ["executable", "param1", "param2"…]
,如:
CMD ["perl", "-de0"]
這樣使用 docker run -it
命令進入容器時,就會預設進入 shell 介面
EXPOSE
指定容器需要監聽的埠
ENV
可以使用 ENV 更新 PATH 環境變數,例如
ENV PATH=/usr/local/nginx/bin:$PATH
注意!每一個 ENV
指令都會建立一個新的中間層 (intermediate layer),如果使用 ENV 設定了變數,在未來的層 unset 了變數,那麼它在 unset 之前依然是可用的。為了防止這種情況,我們應該用 RUN 進行環境變數的 設定和取消
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
ADD or COPY
兩個命令功能相似,優先使用COPY,它的作用只是將本地檔案拷貝到容器內,而 ADD 則有其他特性,比如:自動將本地 tar 檔案提取到映象中、遠端URL
如果多個步驟需要使用不同的檔案,應該單獨 COPY,而不是一次性 COPY,這樣部分檔案變化不會導致所有的快取都失效
避免使用 ADD 通過 URL 獲取包,可以使用 curl
或者 wget
,這樣可以在提取後刪除檔案,避免映象多一層,還可以通過管道,就不需要再手動刪除中間檔案
RUN mkdir -p /usr/src/things \
&& curl -SL https://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
ENTRYPOINT
使用 ENTRYPOINT 設定主命令,還可以用 CMD 設定預設的可選引數
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
執行編譯映象,指定名稱為:s3cmd,執行容器
docker run s3cmd
預設會執行 s3cmd
並帶上 --help
引數,即:顯示該命令的幫助
執行下面命令:
docker run s3cmd ls s3://mybucket
ls s3://mybucket
會覆蓋預設可選引數 --help
如果需要覆蓋 ENTRYPOINT,需要使用 --entrypoint
引數
VOLUME
暴露映象中可變和使用者可修改的資料,比如:儲存檔案、配置檔案,比如:
VOLUME /data
設定的目錄會在容器執行時自動掛載為匿名卷,如果沒有設定,就會寫入容器儲存層
USER
如果不需要使用 sudo
,可以通過 USER 切換到非 root 使用者,例如:
RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres
WORKDIR
WORKDIR 指令可以來指定工作目錄,不存在會自動建立
Dockerfile 不同於 Shell,下面的命令其實是不同的層,第一條的 cd
不會影響第二條命令,最終執行結束會導致在 /app 下找不到 world.txt 檔案
RUN cd /app
RUN echo "hello" > world.txt
應該使用:
WORKDIR /app
RUN echo "hello" > world.txt