참고
Dockerfile 최적화
NOTE
도커 파일의 최적화 ⇒ 경량의 컨테이너 서비스 제공!
•
이미지에서 최적의 환경을 제공하기 위한 요소
◦
빌드시간
◦
이미지 크기
◦
재사용성
◦
보안
◦
유지보수성 등
•
경량의 컨테이너 서비스란?
◦
도커에서 제공하는 베이스 이미지들의 지향점에 필요한 최소한의 파일만 보유한다.
◦
이미지에 불필요한 파일을 포함하지 않기 위해 .dockerignore을 사용한다.
최적화 방안
NOTE
1.
컨테이너 이미지에 불필요한 바이너리 모두 제거
•
불필요한 패키지 설치를 피하고, 설치된 패키지 파일은 autoremove, clean 등의 명령으로 제거한다.
•
ex) apt install -y --no-install-recommands <패키지명>
2.
Docker에서 제공하는 최소 이미지인 alpine linux, scratch를 사용한다.
•
scratch 이미지에 Go와 같은 언어를 사용하여 정적 링크 바이너리를 만들어서 Dockerfile에 참조하면 경량화가 가능하다.
•
Alpine Linux를 사용하면 최종 이미지 크기와 복잡성을 줄이는데 도움이 된다!
3.
Multi-stage-build를 사용하여 최종 이미지 크기를 최소화한다.
2개의 베이스 이미지(FROM)를 사용한다.
•
Dokcerfile의 From이 2개이상이면 분리된 작업 공간(stage 제공)
•
1번째 stage에서 생성된 실행파일 등을 2번째(배포 이미지)에 제공
4.
레이어 최소화 (명령어 그룹화 개념)
RUN 명령어를 한번에 실행시켜 레이어를 줄인다.
•
레이어는 최대 127개까지만 가질 수 있다.
•
새로운 레이어 생성 명령어 ⇒ FROM, RUN, COPY, ADD, ENV, LABEL
•
다른 명령어는 build 과정에서 이미지 생성하고 사라진다.
도커파일 최적화 핵심
NOTE
•
하나의 어플리케이션, 하나의 컨테이너
◦
하나의 컨테이너에 2개 이상의 어플리케이션을 설정하면 결합성이 높고, 확장성이 낮다.
◦
하나의 컨테이너에 하나의 애플리케이션 동작을 보장해야함
◦
이러한 설계는 MSA 지향적 설계를 고려해야 장애가 발생해도 애플리케이션 자체가 유지될 수 있게된다.
◦
여러 컨테이너로 나눠야 재사용성이 좋다.
▪
3-Tier ⇒ Front, Back, Database 컨테이너 분리
▪
많은 수의 프로세스가 컨테이너 내에서 실행된다면 그만큼 CPU, Memory 소비가 커진다.
•
캐쉬를 사용하자
도커는 이미지를 만들때 레이어 개념을 사용해 데이터를 저장하고, 저장된 레이어가 있다면 재사용한다.
◦
이미지를 빌드하면 각 명령어 단위로 캐싱이 이루어진다.
◦
동일한 명령의 실행은 캐싱을 통해 재사용되어 빌드가 빨라진다.
◦
캐싱 효과를 높이기 위해 명령어의 위치를 명확히 해야한다.
▪
일정하게 유지될 명령은 Dockerfile의 위쪽에 배치하고,
▪
변경될 수 있는 명령은 아래쪽에 배치하면 기존 캐시를 부정할 가능성이 줄어든다.
•
보안 강화
◦
Dockerfile의 사용자를 지정하지 않으면 root를 사용한다 (별로 권장되지 않음)
▪
USER 명령어 사용해서 root 못쓰게 막을 수 있다.
◦
커널 정보를 공유하기에 잠재적 보안 문제를 가진다, Docker Host에 대한 권한이 쉽게 상승하게됨
◦
이미지 서명
▪
도커 매니페스트의 서명을 확인하여 신뢰할만한가 확인하며 위조되지 않았나 검증
◦
익숙하지 않은 이미지를 사용하는 경우 Dive를 사용해 이미지 검사
▪
SETUID 및 SETGID 비트가 있는 모든 바이너리를 찾아서 제거
▪
이러한 바이너리는 권한을 escalation하는데 사용됨
다양한 Dockerfile build
이미지 빌드과정
NOTE
# IMAGE_NAME:TAG -> -t 생성할 이미지명:버전
# DOCKERFILE_NAME -> Dockerfile이 아닌 다른 파일명인 경우 -f
# DOCERFILE_LOCATION -> 현재 경로면 "." | 다른 경로 명시
docker build -t IMAGE_NAME:TAG [-f DOCKERFILE_NAME] DOCKERFILE_LOCATION
Bash
복사
EXPOSE, CMD는 실제로 레이어를 생성하지 않음(걍 이미지용이다.)
docker run을 실행하면 이제 수정이 가능해진다.
Dockerfile 경량화 (패키지 최소화, alpine 사용)
NOTE
1. 기본 Dockerfile
FROM ubuntu:14.04
MAINTAINER "kevin-lee <hylee@dshub.cloud>"
LABEL "purpose"="webserver practice"
RUN apt update && apt install apache2 -y
WORKDIR /var/www/html
COPY index.html .
EXPOSE 80
CMD apachectl -D FOREGROUND
Docker
복사
원본 그대로 사용
28.3초
2. clean, autoremove를 통한 패키지 최소화
FROM ubuntu:14.04
MAINTAINER "kevin-lee <hylee@dshub.cloud>"
LABEL "purpose"="webserver practice"
RUN apt update && apt install apache2 -y && \
apt clean autoclean && \
apt autoremove -y && \
rm -rfv /tmp/* /var/lib/apt/lists/* /var/tmp/*
WORKDIR /var/www/html
COPY index.html .
EXPOSE 80
CMD apachectl -D FOREGROUND
Docker
복사
clean, autoremove를 통한 불필요한 파일 제거
24초
3. alpine 이미지 사용
FROM alpine
MAINTAINER "kevin-lee <hylee@dshub.cloud>"
LABEL "purpose"="webserver practice"
RUN apk update && apk add apache2
WORKDIR /var/www/html
COPY index.html .
EXPOSE 80
CMD ["/usr/sbin/httpd","-D","FOREGROUND"]
Docker
복사
알파인 사용
4초
각 버전별 용량차이 (알파인이 압도적임)
스크래치를 통한 최적화
NOTE
scratch ⇒ 아무것도 없는 빈 이미지!
# 일반 도커파일
vi dockerfile-ubuntu
FROM ubuntu:20.04
COPY go-to-lightweight-image /
CMD ["/go-to-lightweight-image"]
---
# 스크래치 파일
vi dockerfile-scratch
FROM scratch
COPY go-to-lightweight-image /
CMD ["/go-to-lightweight-image"]
---
docker build -t lightweight:1.0 -f dockerfile-ubuntu --no-cache .
docker build -t lightweight:2.0 -f dockerfile-scratch --no-cache .
Bash
복사
일반 빌드 2.3초
스크래치 빌드 0.4초
용량도 압도적으로 좋음
•
sratch 내부 실행을 위해 다음이 필요하다.
◦
정적 바이너리 실행파일을 위해 gcc 활용
◦
busybox 컨테이너 실행 도구를 cp
◦
실행 파일이 있는 rootfs.tar.xz를 cp
멀티스테이지 빌드
NOTE
컨테이너 이미지를 만들면서 빌드 등에는 필요하지만 최종 컨테이너 이미지에 필요없는 환경을 제거할 수 있도록 단계를 나누어서 이미지를 만드는 방법!
컨테이너 실행 시에는 빌드에 사용한 파일 및 디렉토리를 모두 삭제하고 실행한다!
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 /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
Docker
복사
대표적으로 node-Nginx 멀티 스테이지 빌드가 자주 사용된다.
# 일반 빌드
vi dockerfile-go1
FROM golang:1.15-alpine3.12
WORKDIR /app/
COPY gostart.go /app/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/gostart
ENTRYPOINT [ "/app/gostart" ]
---
# 멀티스테이지 빌드
vi dockerfile-go2
FROM golang:1.15-alpine3.12 AS gobuilder-stage
MAINTAINER kevin,lee <hylee@dshub.cloud>
LABEL "purpose"="Application Deployment using Multi-stage builds.
"
WORKDIR /app/
COPY gostart.go /app/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/gostart
FROM scratch
COPY --from=gobuilder-stage /app/gostart /app/gostart
CMD ["/app/gostart"]
---
docker build -t goapp:1.0 -f dockerfile-go1 --no-cache .
docker build -t goapp:2.0 -f dockerfile-go2 --no-cache .
docker run --name goapp1 -p 9091:9090 -d -h goapp1 goapp:1.0
docker run --name goapp2 -p 9092:9090 -d -h goapp2 goapp:2.0
Bash
복사
51초 - 일반 빌드
7.6초 - 멀티 스테이지 빌드
용량 차이가 제일 뚜렷함