映象構建最佳實踐

映象分層

使用 docker image history 命令,您可以看到用於建立映象中每個層的命令。

  1. 使用 docker image history 命令檢視您建立的 getting-started 映象中的層。

    $ docker image history getting-started
    

    您應該會得到類似以下內容的輸出。

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "src/index.j…    0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   

    每一行都代表映象中的一個層。這裡的顯示將基礎層放在底部,最新的層放在頂部。透過這個,您還可以快速檢視每個層的大小,有助於診斷大映象。

  2. 您會注意到其中幾行被截斷了。如果您新增 --no-trunc 標誌,您將獲得完整的輸出。

    $ docker image history --no-trunc getting-started
    

層快取

現在您已經瞭解了分層的實際作用,為了幫助減少容器映象的構建時間,有一個重要的經驗需要學習。一旦某個層發生變化,所有下游的層也必須重新建立。

看一下您為入門應用建立的以下 Dockerfile。

# syntax=docker/dockerfile:1
FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

回到映象歷史輸出,您會看到 Dockerfile 中的每個命令都成為映象中的一個新層。您可能還記得,當您對映象進行更改時,yarn 依賴項必須重新安裝。每次構建時都傳輸相同的依賴項是沒有意義的。

要解決這個問題,您需要重新組織 Dockerfile,以支援依賴項的快取。對於基於 Node 的應用程式,這些依賴項在 package.json 檔案中定義。您可以先只複製該檔案,安裝依賴項,然後再複製其他所有內容。這樣,只有在 package.json 發生變化時,您才需要重新建立 yarn 依賴項。

  1. 更新 Dockerfile,先複製 package.json,安裝依賴項,然後再複製其他所有內容。

    # syntax=docker/dockerfile:1
    FROM node:lts-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "src/index.js"]
  2. 使用 docker build 構建一個新映象。

    $ docker build -t getting-started .
    

    您應該會看到類似以下的輸出。

    [+] Building 16.1s (10/10) FINISHED
    => [internal] load build definition from Dockerfile
    => => transferring dockerfile: 175B
    => [internal] load .dockerignore
    => => transferring context: 2B
    => [internal] load metadata for docker.io/library/node:lts-alpine
    => [internal] load build context
    => => transferring context: 53.37MB
    => [1/5] FROM docker.io/library/node:lts-alpine
    => CACHED [2/5] WORKDIR /app
    => [3/5] COPY package.json yarn.lock ./
    => [4/5] RUN yarn install --production
    => [5/5] COPY . .
    => exporting to image
    => => exporting layers
    => => writing image     sha256:d6f819013566c54c50124ed94d5e66c452325327217f4f04399b45f94e37d25
    => => naming to docker.io/library/getting-started
  3. 現在,對 src/static/index.html 檔案進行更改。例如,將 <title> 更改為“The Awesome Todo App”。

  4. 現在再次使用 docker build -t getting-started . 構建 Docker 映象。這一次,您的輸出應該會有些不同。

    [+] Building 1.2s (10/10) FINISHED
    => [internal] load build definition from Dockerfile
    => => transferring dockerfile: 37B
    => [internal] load .dockerignore
    => => transferring context: 2B
    => [internal] load metadata for docker.io/library/node:lts-alpine
    => [internal] load build context
    => => transferring context: 450.43kB
    => [1/5] FROM docker.io/library/node:lts-alpine
    => CACHED [2/5] WORKDIR /app
    => CACHED [3/5] COPY package.json yarn.lock ./
    => CACHED [4/5] RUN yarn install --production
    => [5/5] COPY . .
    => exporting to image
    => => exporting layers
    => => writing image     sha256:91790c87bcb096a83c2bd4eb512bc8b134c757cda0bdee4038187f98148e2eda
    => => naming to docker.io/library/getting-started

    首先,您應該注意到構建速度快了很多。而且,您會看到有幾個步驟正在使用先前快取的層。推送和拉取此映象及其更新也會快得多。

多階段構建

多階段構建是一個非常強大的工具,可以幫助使用多個階段來建立一個映象。它們有幾個優點:

  • 將構建時依賴項與執行時依賴項分開
  • 僅傳輸應用程式執行所需的內容,從而減少整體映象大小

Maven/Tomcat 示例

在構建基於 Java 的應用程式時,您需要一個 JDK 來將原始碼編譯成 Java 位元組碼。但是,在生產環境中不需要這個 JDK。此外,您可能正在使用像 Maven 或 Gradle 這樣的工具來幫助構建應用程式。這些在最終映象中也是不需要的。多階段構建可以提供幫助。

# syntax=docker/dockerfile:1
FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

在此示例中,您使用一個階段(稱為 build)來使用 Maven 執行實際的 Java 構建。在第二個階段(從 FROM tomcat 開始),您從 build 階段複製檔案。最終的映象只是建立的最後一個階段,可以使用 --target 標誌覆蓋。

React 示例

在構建 React 應用程式時,您需要一個 Node 環境來將 JS 程式碼(通常是 JSX)、SASS 樣式表等編譯成靜態的 HTML、JS 和 CSS。如果您不進行伺服器端渲染,您的生產構建甚至不需要 Node 環境。您可以將靜態資源放在一個靜態的 nginx 容器中。

# syntax=docker/dockerfile:1
FROM node:lts AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

在之前的 Dockerfile 示例中,它使用 node:lts 映象來執行構建(最大化層快取),然後將輸出複製到 nginx 容器中。

摘要

在本節中,您學習了一些映象構建的最佳實踐,包括層快取和多階段構建。

相關資訊

後續步驟

在下一節中,您將瞭解可用於繼續學習容器的其他資源。

接下來做什麼