일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Render Queue
- react
- Custom Hook
- Headless 컴포넌트
- next.js
- prettier
- Microtask Queue
- prettier-plugin-tailwindcss
- Event Loop
- AJIT
- type assertion
- 타입 단언
- Pull Request
- webpack
- Dockerfile
- JavaScript
- 주니어개발자
- CS
- 좋은 PR
- Sparkplug
- TypeScript
- linux 배포판
- Compound Component
- task queue
- 명시적 타입 변환
- 프로세스
- 프로젝트 구조
- 암묵적 타입 변환
- docker
- VirtualDom
- Today
- Total
너굴 개발 일지
[리뷰] 도커 교과서 - 멀티 스테이지 빌드 본문
도커 교과서의 4장(애플리케이션 소스코드에서 도커 이미지까지)을 읽으면서 공부한 것을 정리한 글입니다.
Dockerfile을 통한 멀티 스테이지 빌드
만약 팀단위로 작업을 하는데 도커가 없다면 코드를 빌드하기 위한 별도의 서버인 빌드 서버가 필요합니다.
빌드 서버 사용시, 팀원이 파일 누락 후 푸쉬할 경우 빌드 서버에서 빌드가 실패된 것을 팀원들도 알 수 있고 건전한 프로젝트를 유지할 수 있지만 빌드 서버 유지 비용이 추가되며 유지 보수를 위한 큰 오버헤드가 발생할 수 있습니다. 예를 들어 로컬 컴퓨터, 빌드 서버와의 사용 도구 버전이 다를 경우 빌드가 실패됩니다.
하지만 도커를 이용해 빌드 툴체인을 패키징하여 공유한다면 이런 단점을 극복할 수 있습니다.
먼저 개발에 필요한 모든 도구를 배포하는 이미지를 생성하고 애플리케이션 패키징시 해당 이미지를 사용해 소스 코드 컴파일을 진행할 수 있습니다.
멀티 스테이지 빌드 예시
FROM diamol/base AS build-stage
RUN echo 'Building...' > /build.txt
FROM diamol/base AS test-stage
COPY --from=build-stage /build.txt /build.txt
RUN echo 'Testing...' >> /build.txt
FROM diamol/base
COPY --from=test-stage /build.txt /build.txt
CMD cat /build.txt
위 예시 Dockerfile은 build-stage에서 파일 생성 후, test-stage 단계에서 해당 파일을 복사해 파일을 생성한다음 마지막 빌드 단계에서 해당 파일을 복사해 실행합니다.
빌드 단계에는 다음과 같은 특징이 있습니다.
- 각 빌드 단계는
FROM
인스트럭션으로 시작하며AS
파라미터로 빌드 단계인 이름 명시 가능 (마지막 단계는 이름 없음) - 빌드 단계가 여러개여도 최종 산출물은 마지막 단계의 내용물을 담은 도커 이미지 생성
- 각 빌드 단계는 격리되어 있으며, 마지막 빌드 단계의 산출물은 이전 단계에서 명시적으로 복사한 것만 포함 가능
- 어디 단계에서라도 명령 실패시, 전체 빌드 실패
인스트럭션 설명
RUN
: 빌드 중에 컨테이너 안에서 명령을 실행한 다음, 결과를 이미지 레이어에 저장하며 FROM에서 지정한 이미지에서 실행할 수 있는 명령어여야 함 (예제에서는 파일 생성을 목적으로 사용)
위 예제에서는 단순히 텍스트 파일을 생성하고 복사하는 과정이었지만 멀티 스테이지 빌드를 다음과 같이 적용할 수 있습니다.
- build-stage : 빌드 도구가 설치된 기반 이미지 사용, 로컬에서 소스 코드 복사 후 빌드 실행
- test-stage : 단위 테스트 프레임워크가 설치된 기반 이미지 사용, 앞서 빌드한 바이너리 복사 후 단위 테스트 실행
- 마지막 단계 : 애플리케이션 실행 런타임 기반 이미지 사용, build 단계에서 빌드하고 test 단계에서 테스트를 마친 바이너리를 이미지에 복사해 사용
이처럼 Dockerfile을 이용해 멀티 스테이지 빌드를 적용하면 빌드 도구를 도커 이미지를 통해 중앙 집중적으로 관리할 수 있고 처음에 언급했던 팀 공통설정에서 벗어날 위험성을 차단합니다.
멀티 스테이지 빌드 예시 : 자바 소스 코드
FROM diamol/maven AS builder
WORKDIR /usr/src/iotd
COPY pom.xml .
RUN mvn -B dependency:go-offline
COPY . .
RUN mvn package
# app
FROM diamol/openjdk
WORKDIR /app
COPY --from=builder /usr/src/iotd/target/iotd-service-0.1.0.jar .
EXPOSE 80
ENTRYPOINT ["java", "-jar", "/app/iotd-service-0.1.0.jar"]
위 Dockerfile 코드는 자바 빌드 도구인 메이븐을 이용해 애플리케이션 빌드 후 자바 런타임인 OpenJDK를 이용해 애플리케이션을 실행할 수 있는 이미지를 생성합니다.
인스트럭션 설명
ENTRYPOINT
:CMD
명령어와 비슷하며 컨테이너 생성 후 최초로 실행시 수행되는 명령어 지정
위 작업을 통해 알 수 있는 점은 다음과 같습니다.
- 도커만 있다면 빌드 도구, 런타임 설치가 없어도 애플리케이션을 어디서든 실행할 수 있음
- 최종적으로 생성된 이미지에는 빌드 도구가 포함되지 않기에 최적화된 크기의 이미지 생성 가능 (생성된 이미지에서 mvn 명령어 사용할 수 없음)
와
CMD
의 차이
ENTRYPOINT
두 인스트럭션의 가장 큰 차이점은 컨테이너 시작시 실행 명령에 대한 default 지정 여부입니다.
만약를 사용해 컨테이너 수행 명령을 정의한 경우, 해당 컨테이너가 수행될 때 반드시
ENTRYPOINT
에서 지정한 명령이 수행되도록 지정합니다.
ENTRYPOINT
하지만,를 사용하면, 컨테이너 실행시 인자값을 준 경우 Dockerfile에 지정된
CMD
값 대신 지정한 인자값으로 변경해 실행합니다.
CMD
따라서 컨테이너가 수행될 때 변경되지 않을 명령은 CMD보다는로 정의하는 것이 좋습니다.
ENTRYPOINT
또한명령어는 도커 컨테이너에서 실행될 때, 커맨드 라인에 명시적으로 인자값을 지정하지 않은 경우에 기본 명령어 역할을 하는 인자 설정시 사용하는 것이 좋습니다.
CMD
ENTRYPOINT, CMD 함께 사용한 예시
ROM centos:7
ENTRYPOINT ["echo", "Hello,"]
CMD ["Darwin"]
docker build -t hello:together .
docker run hello:together
Hello, Darwin # 매개변수 없이 컨테이너를 실행하면 CMD에 지정한 인자 사용
docker run hello:together world
Hello, world # 매개변수 넘겨주면 CMD 기본값은 오버라이딩되어 무시됨
멀티 스테이지 빌드 예시 : Node.js 소스 코드
FROM diamol/node AS builder
WORKDIR /src
COPY src/package.json .
RUN npm install
# app
FROM diamol/node
EXPOSE 80
CMD ["node", "server.js"]
WORKDIR /app
COPY --from=builder /src/node_modules/ /app/node_modules/
COPY src/ .
이전 자바 소스 코드에서는 첫번째 빌드 단계에서 컴파일된 애플리케이션을 담은 파일인 JAR 파일을 생성하며, 소스코드는 이미지에 포함되지 않았습니다.
하지만 Node.js는 인터프리어 언어인 자바스크립트로 구현되기에 컨테이너화된 Node.js 애플리케이션 실행시, Node.js 런타임과 소스코드가 이미지에 포함되어야 합니다.
물론 컴파일 과정이 없지만 멀티 스테이지 빌드를 통해 의존성 모듈 최적화를 진행할 수 있습니다.
스크립트 진행은 다음과 같습니다.
- builder 단계에서 애플리케이션 의존성 모듈이 정의된 package.json 파일 복사 후 의존성 모듈 내려 받음 (컴파일 X)
- 최종 단계에서는 작업 디렉터리를 만들고 미리 내려 받은 의존성 모듈, 소스 코드 복사 후 Node 서버를 실행하는 명령어를 실행하는 이미지 생성
위 과정에서 내려받은 의존 모듈은 이미지 레이어에 캐시되므로 만약 소스 코드만 변경해 재빌드시, 이전보다 빠르게 진행됩니다.
React에서 multi-stage 빌드 적용 전/후 비교 예시
FROM ubuntu:18.04
RUN apt-get update -y \
&& apt-get install curl -y
# install nodejs
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - \
&& apt-get install -y nodejs
# build
COPY my-app /usr/src/app
WORKDIR /usr/src/app
RUN npm install && npm rum build
# install nginx
RUN apt-get install -y nginx
RUN mv ./build/* /var/www/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
위는 멀티 스테이지 빌드를 적용하지 않은 Dockerfile로 하나의 스테이지에 모든 작업 내용이 실행됩니다. 따라서 최종 명령어를 실행하기 위한 불필요한 것들도 설치됩니다.
FROM node:16-alpine as builder
WORKDIR /app
COPY ./my-app ./
RUN npm install && npm run build
FROM nginx:1.21.0-alpine as production
COPY --from=builder /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
위는 멀티 스테이지 빌드를 적용한 Dockerfile로 마지막 빌드 단계에서는 React 빌드 결과물만을 이용해 실행하기에 이미지 크기가 줄어들게 됩니다.
Dockerfile을 통한 멀티 스테이지 빌드 정리
장점
- 표준화
- 사용중인 운영체제 종류, 도구 설치 여부와 관계 없이 모든 빌드 과정은 컨테이너 내부에서 진행
- 컨테이너는 모든 도구를 정확한 버전으로 가지기에 도구간의 버전차이로 인한 빌드 실패 문제 예방
- 성능 향상
- 멀티 스테이지의 각 빌드 단계는 독립적인 캐시를 가짐
- 물론 캐시된 이미지 레이어가 없을 경우, 남은 인스트럭션이 모두 실행되지만 범위가 해당 빌드 단계 안으로 국한됨
- 캐시 재사용을 통한 빌드 단계 시간 절약 가능
- 사용되는 디스크 용량 줄임
- 빌드 과정에서 필요한 파일들만 복사해 다음 빌드 단계에서 사용함으로써 최종 산출물은 이미지 크기를 줄일 수 있음
'독서' 카테고리의 다른 글
[리뷰] 도커 교과서 - 이미지 공유, 도커 볼륨 (0) | 2023.09.03 |
---|---|
[리뷰] 도커 교과서 - 컨테이너, 도커 이미지 (0) | 2023.08.03 |
[리뷰] 코어 자바스크립트 4장 - 콜백 함수 (0) | 2023.06.05 |
[리뷰] 코어 자바스크립트 3장 - this (0) | 2023.06.04 |
[리뷰] 코어 자바스크립트 2장 - 실행 컨텍스트 (0) | 2023.06.03 |