在 docker-in-docker 環境中使用 cache-from 提升編譯速度

提升 docker build 時間

在現代 CI/CD 的環境流程中,使用 Docker In Docker 來編譯容器已經相當流行了,像是 GitLab CIDrone 都是全走 Docker 環境,然而有很多人建議盡量不要在 CI 環境使用 Docker In Docker,原因在於 CI 環境無法使用 Host Image 資料,導致每次要上傳 Image 到 Docker Hub 時都需要重新下載所有的 Docker Layer,造成每次跑一次流程都會重複花費不少時間,而這個問題在 v1.13 時被解決,現在只要在編譯過程指定一個或者是多個 Image 列表,先把 Layer 下載到 Docker 內,接著對照 Dockerfile 內只要有被 Cache 到就不會重新再執行,講得有點模糊,底下直接拿實際例子來看看。

教學影片

歡迎訂閱我的 Youtube 頻道: http://bit.ly/youtube-boy

更多實戰影片可以參考我的 Udemy 教學系列

使用 –cache-from 加速編譯

在 Docker v1.13 版本中新增了 --cache-from 功能讓開發者可以在編譯 Dockerfile 時,同時指定先下載特定的 Docker Image,透過先下載好的 Docker Layer 在跟 Dockerfile 內文比較,如果有重複的就不會在被執行,這樣可以省下蠻多編譯時間,底下拿個簡單例子做說明,假設我們有底下的 Dockerfile

FROM alpine:3.9
LABEL maintainer="maintainers@gitea.io"

EXPOSE 22 3000

RUN apk --no-cache add \
    bash \
    ca-certificates \
    curl \
    gettext \
    git \
    linux-pam \
    openssh \
    s6 \
    sqlite \
    su-exec \
    tzdata

RUN addgroup \
    -S -g 1000 \
    git && \
  adduser \
    -S -H -D \
    -h /data/git \
    -s /bin/bash \
    -u 1000 \
    -G git \
    git && \
  echo "git:$(dd if=/dev/urandom bs=24 count=1 status=none | base64)" | chpasswd

ENV USER git
ENV GITEA_CUSTOM /data/gitea

VOLUME ["/data"]

ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/bin/s6-svscan", "/etc/s6"]

COPY docker /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
RUN ln -s /app/gitea/gitea /usr/local/bin/gitea

透過底下命令列可以編譯出 Image

$ docker build -t gitea/gitea .

而在命令列內可以看到花最多時間的是底下這個步驟

RUN apk --no-cache add \
    bash \
    ca-certificates \
    curl \
    gettext \
    git \
    linux-pam \
    openssh \
    s6 \
    sqlite \
    su-exec \
    tzdata

該如何透過 --cache-from 機制繞過此步驟加速 Docker 編譯時間,其實很簡單只要在網路上找到原本 image 就可以繞過此步驟,開發者總會知道原本的 Dockerfile 是用來編譯出哪一個 Image 名稱

$ docker build --cache-from=gitea/gitea -t gitea/gitea .

從上圖可以知道時間最久的步驟已經被 cache 下來了,所以 cache-from 會事先把 Image 下載下來,接著就可以使用該 Image 內的 cache layer 享受簡短 build time 的好處。

在 Gitlab CI 使用 cache-from

在 Gitlab CI 如何使用,其實很簡單,請參考此範例

image: docker:latest
services:
  - docker:dind
stages:
  - build
  - test
  - release
variables:
  CONTAINER_IMAGE: registry.anuary.com/$CI_PROJECT_PATH
  DOCKER_DRIVER: overlay2
build:
  stage: build
  script:
    - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.anuary.com
    - docker pull $CONTAINER_IMAGE:latest
    - docker build --cache-from $CONTAINER_IMAGE:latest --build-arg NPM_TOKEN=${NPM_TOKEN} -t $CONTAINER_IMAGE:$CI_BUILD_REF -t $CONTAINER_IMAGE:latest .
    - docker push $CONTAINER_IMAGE:$CI_BUILD_REF
    - docker push $CONTAINER_IMAGE:latest

這時候你會問時間到底差了多久,在 Node.js 內如果沒有使用 cache,每次 CI 時間至少會多不少時間,取決於開發者安裝多少套件,我會建議如果是使用 multiple stage build 請務必使用 cache-from

在 Drone 如何使用 –cache-from

在 Drone 1.0 架構內,可以架設多台 Agent 服務加速 CI/CD 流程,但是如果想要跨機器的 storage 非常困難,所以有了 cache-from 後,就可以確保多台 agent 享有 docker cache layer 機制。底下來看看 plugins/docker 該如何設定。

- name: publish
  pull: always
  image: plugins/docker:linux-amd64
  settings:
    auto_tag: true
    auto_tag_suffix: linux-amd64
    cache_from: appleboy/drone-telegram
    daemon_off: false
    dockerfile: docker/Dockerfile.linux.amd64
    password:
      from_secret: docker_password
    repo: appleboy/drone-telegram
    username:
      from_secret: docker_username
  when:
    event:
      exclude:
      - pull_request

這邊拿公司的一個環境當作範例,在還沒使用 cache 前編譯時間為 2 分 30 秒,後來使用 cache-from 則變成 30 秒

結論

使用 --cache-from 需要額外多花下載 Image 檔案的時間,所以開發者需要評估下載 Image 時間跟直接在 Dockerfile 內直接執行的時間差,如果差很多就務必使用 --cache-from。不管是不是應用在 Docker In Docker 內,假如您需要改別人 Dockerfile,請務必先下載對應的 Docker Image 在執行端,這樣可以省去不少 docker build 時間,尤其是在 Dockerfile 內使用到 apt-get instllnpm install 這類型的命令。