Gitlab CI에서 Docker 이미지 빌드 최적화

머리말





올해는 2022년이며, Docker에 더 이상 소개가 필요하지 않다고 확신합니다. 그리고 지속적 통합 및 지속적 제공 방식을 통해 고성능 민첩한 엔지니어링 팀을 유지하고 있다면 Gitlab도 생소한 이름이 아니어야 합니다. 이 블로그에서는 CI 분을 최대한 활용하여 Docker 이미지를 보다 효율적으로 빌드하고 다른 곳에서 더 잘 사용할 수 있도록 비용을 절약하는 몇 가지 작지만 효과적인 단계를 보여 드리겠습니다.

도커 및 유니온 파일 시스템





CI 파이프라인용 YAML을 작성하기 전에 잠시 시간을 내어 Docker의 핵심에 있는 Union File System을 살펴보겠습니다. 이미 이 개념에 정통한 경우 다음 섹션으로 건너뛰십시오.

Union File System is a file system available in Unix and Unix-like (Linux and BSD) Operating System which implements Union Mount for one or more other file systems. It allows developer to build a file system by overlaying layers of files and directories along with permissions. [1]



깃랩 CI





Gitlab CI는 Gitlab의 모든 계층에서 사용할 수 있는 CI 도구입니다. 공유 러너에서 실행될 수 있을 뿐만 아니라 자체 러너 인스턴스를 구성할 수 있습니다. How to setup and configure a runner은 이 블로그의 범위를 벗어나므로 독자에게 맡기겠습니다. 나머지 논의에서는 러너가 CI 작업용 Docker Executor를 실행하도록 구성되었다고 가정합니다.

도커파일



데모 목적을 위해 들어오는 모든 요청에 ​​대해 "Hello World"로 응답하는 Express.JS에서 실행되는 간단한 Node Environment 응용 프로그램을 제시하겠습니다. 구현은 다음과 같습니다.

// app.js

const express = require("express");
const app = express();

app.all("/", (req, res) => {
  res.send("Hello World");
});

app.listen(3000);


이제 이 애플리케이션을 컨테이너화하여 Docker 엔진 지원으로 여러 플랫폼에서 원활하게 실행할 수 있습니다. 그래서 우리는 다음과 같은 기본 Dockerfile을 제시합니다.

FROM node:16-alpine3.15
WORKDIR /
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production=true
COPY app.js ./app.js

EXPOSE 3000
CMD ["node", "app.js"]


도커 빌드



이제 셸에서 다음을 실행하여 이 Docker 이미지를 간단하게 빌드할 수 있습니다.

$ docker build -t hello-world .


이렇게 하면 hello-world:latest 태그 이름으로 로컬 개발 머신에 Docker 이미지가 생성됩니다.
app.js 에서 사소한 변경을 수행하는 경우 텍스트 뒤에 느낌표를 추가한다고 가정해 보겠습니다.

-  res.send("Hello World");
+  res.send("Hello World!");


그리고 이미지를 재구성하려고 하면 처음 몇 단계가 거의 즉시 완료되는 것을 볼 수 있습니다. 이는 Docker가 빌드 중에 Union File System의 이점을 활용하여 Caching Docker가 수행하기 때문입니다.

-> Step [3/7] COPY package.json yarn.lock ./
              - Using cache 24e08bf6


이는 의심할 여지 없이 빌드 프로세스의 속도를 높이면서 프로세스를 증분 및 최소로 유지합니다.

CI 환경



이제 CI 환경에서 위의 장점을 갖는 것은 언젠가 당장 불가능하지는 않지만 다소 복잡합니다. 이는 작업 사이에 있는 Executor의 정리 및 재사용 가능성 때문입니다. 이로 인해 우리는 이러한 머신을 본질적으로 상태 비저장 상태로 유지해야 합니다. 즉, 생성된 파일과 아티팩트가 작업 완료 후 머신에서 작동할 수 없습니다. 뿐만 아니라 빌드 프로세스가 회전하는 빌드 머신 세트를 활용하는 경우 동일한 머신에서 지속적으로 캐시를 제공하기가 어려울 것입니다.

기본 작업



먼저 .gitlab-ci.yml에서 빌드 프로세스의 기본 전략을 살펴보겠습니다.

stages:
  - build

build:
  stage: build
  image: docker:20.10.13-alpine3.15
  services:
    - docker:20.10.13-dind-alpine3.15
  script:
    - docker build -t hello-world .


Docker FS 레이어 캐싱



이제 캐싱 문제를 해결하기 위해 다음 단계를 추가하면 됩니다. 짜잔! 훨씬 더 빠른 빌드가 있습니다.

   services:
     -docker:20.10.13-dind-alpine3.15
+  before_script:
+    - mkdir -p .docker
+    - "docker load -q -i .docker/fs.tar 2>&1 || :"
+    - rm -rf .docker
   script:
-    - docker build -t hello-world .
+    - docker build --cache-from=hello-world -t hello-world .
+  after_script:
+    - mkdir -p .docker
+    - "docker save -o .docker/fs.tar hello-world 2>&1 || :"
+    - docker system prune -a
+  cache:
+    - key: $CI_COMMIT_REF_SLUG
+      paths:
+        - .docker
+      when: on_success


이제 다시 실행하는 동안 Gitlab은 먼저 압축을 푼 Tar 아카이브 파일에서 Docker 이미지를 캐시에서 로드하고 최신 이미지를 빌드하는 동안 사용합니다. 모든 성공적인 빌드는 차례로 새로운 아카이브를 생성하므로 다음 실행을 위해 캐시됩니다.

마지막 생각들



계속해서 Docker를 사용하는 Gitlab의 모든 단일 빌드 작업에 이것을 추가하는 것이 좋은 생각처럼 보일 수 있습니다. 그러나 처음부터 다시 빌드하는 것이 아카이브를 생성하고 업로드한 다음 추가로 다운로드하는 것보다 약간 더 빠르고 더 저렴한 대안이 될 수 있다는 점을 염두에 두십시오.


자세한 내용은 나를 팔로우하거나 Github

좋은 웹페이지 즐겨찾기