📕 목차
1. Build Server
2. Practice : Java
3. Practice : Node.js
4. Practice : Go
5. Docker Multi-stage build 장점
6. 연습 문제
1. Build Server
📌 As-is
- 프로그래밍 언어는 프로젝트 빌드를 위한 다양한 도구가 필요하다.
- 소프트웨어 프로젝트 빌드를 위해서 팀원 모두가 같은 도구를 사용해야 한다.
- 도구 버전을 맞추는 데 시간 소요
- 작업용 PC에서 하나를 업데이트 해버리면 빌드 서버와 버전이 달라지는 것만으로도 실패 가능
- Managed build service를 사용하더라도 회피가 어려우며, 도구 선택 폭이 좁아짐.
✒️ Background
• Docker의 Image를 작게 만들어 빌드, 배포 시간을 단축 필요성 제기
• 각각의 Instruction들은 Dockerfile 하나의 Layer로 추가되므로 최적화 필요
• Multi Stage build 방식 이전
∘ builder-pattern : 하나의 Dockerfile이 아닌 두 가지의 Dockerfile 유지
∘ Dockerfile.build : 여러 명령어 실행을 분리하지 않고 하나의 Layer에서 처리 가능 (빌드를 위한 Image)
∘ Dockerfile : 애플리케이션 실행을 위한 Dockerfile
∘ build.sh : 각각의 스크립트를 따로 작성
📌 To-be. Multi-stage build
- Multi-stage build
- Container Image를 만들면서 최종 Container에는 필요 없는 환경(빌드, 테스트 등)을 제거
- 단계를 나눈다. (아래는 예시)
- 개발에 필요한 모든 도구를 배포하는 Dockerfile을 Image로 작성
- Application Packaging을 위한 Dockerfile에서 (1)의 Image를 사용해 소스 코드 컴파일
- 실행을 위한 Dockerfile에서 (2)의 Image에서 컴파일된 파일을 가져와 실행
- 최종 Container 실행 시에는 Build에 사용한 의존 파일들이 모두 삭제되고 없다.
- 여러 개의 FROM 명령문을 사용 (각 Build 마다)
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
- AS 파라미터 : 해당 빌드 단계에 build-stage 이름 부여
- Run Instruction
- 빌드 중에 Container 안에서 명령을 실행 → 결과를 Image Layer에 저장
- RUN Instruction에서 실행할 수 있는 명령은 FROM Instruction에서 지정한 Image에서 실행할 수 있어야 함
- --from=build-stage : 이전 Build 단계의 File System 지정
- 위의 Docekrfile script는 Multi-stage build를 적용
- build 단계
- test 단계
- 실행 단계 → 최종 산출물
- 각 Build 단계는 서로 격리됨
- Build 단계별 기반 Image가 다를 수 있음 → 사용 가능 도구 달라짐
- 마지막 Build 단계 산출물은 이전 Build 단계에서 명시적으로 복사해온 것만 포함 가능
- 어느 한 단계에서라도 명령이 실패하면 전체 Build 실패
✅ Image build
docker image build -t mulit-stage .
build-stage, test-stage, stage-2(최종) 단계가 모두 다름을 알 수 있다.
📌 Java Application에 workflow 적용 시
- 단계 예시
- build 도구가 설치된 기반 Image : 로컬 환경에서 소스 코드를 복사하여 build 명령 실행
- Unit Test Framework가 설치된 기반 Image : Build한 바이너리를 복사하여 단위 테스트 수행
- Runtime이 설치된 기반 Image : 테스트가 통과한 바이너리들을 복사하여 애플리케이션 실행
- 애플리케이션 이식성 확보 → 클라우드 상에 배포 가능
- Docker만 있다면 Container를 통해 어떤 환경에서도 애플리케이션 빌드 및 실행 가능
- 대부분의 주요 Application Framework는 Docker Hub를 통해 Build Tool이 내장된 공식 Image들을 제공
2. Practice : Java
📌 maven으로 Spring 빌드하는 Dockerfile script
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"]
- Maven : Build 절차와 dependency module 입수 방법 정의 도구
- OpenJDK : 자유롭게 재배포 가능한 Java Runtime이자 Develop Tool
✨ Dockerfile script 분석
- Build 단계
- 기반 이미지 : diamol/maven (maven과 OpenJDK 포함)
- WORKDIR : '/usr/src/iotd'로 Container 내부 경로 이동
- COPY pom.xml . : 로컬 PC의 pom.xml을 WORKDIR로 복사
- maven 실행 → 단계 분리
- 추가된 dependency module이 있다면 Image cache 재활용, 없으면 해당 단계 실행
- COPY . . : 소스 코드 전체를 WORKDIR로 복사
- mvn package : compile을 통해 JAR 파일 생성
- 실행 단계
- 기반 이미지 : diamol/openjdk (Java 11 Runtime만 포함)
- WORKDIR : 'app/'로 Container 내부 경로 이동
- COPY : 이전 Build 결과에서 JAR 파일만 복사 (모든 dependency module과 compile된 애플리케이션 포함)
- EXPOSE 80 : 애플리케이션을 80번 port로 공개
- ENTRYPOINT : CMD와 같은 기능. Docker가 해당 Image로 Container를 만들면 실행하는 부분
1️⃣ Image build
docker image build -t image-of-the-day .
- NASA의 오늘의 천문 사진 서비스에서 사진을 가져오는 간단한 REST API
2️⃣ Docker Network
docker network create nat
- Docker의 Container는 격리된 환경에서 동작하므로 기본적으로 다른 Container와 통신 불가능
- 여러 개의 Container를 하나의 Docker Network에 연결시키면 통신 가능
- Docker Deamon이 실행되면서 기본으로 생성되는 Network 존재
- bridge : 하나의 Host PC 내에서 여러 Container들이 소통할 수 있도록 함 (기본값)
- host : Container를 Host PC와 동일한 Network에서 동작시키기 위함
- none : 여러 Host에 분산되어 돌아가는 Container 간 Networking을 위함
- 명령어 종류
- `docker network ls` : 현재 생성되어 있는 Docker Network List 조회
- `docker network inspect` : Network 상세 정보 확인
- (연결 방법1) `docker network connect <network name> <container name>`
- (연결 방법2) `docker container run --network <network name> <container name>`
- (연결 해제) `docker network disconnect <network name> <container name>`
3️⃣ 80 port의 nat 네트워크에 컨테이너를 접속
docker container run --name iotd -d -p 800:80 --network nat image-of-the-day
💡 Docker만 설치되어 있다면 해당 애플리케이션을 어디서든 실행할 수 있다.
• 애플리케이션 이미지에 빌드 도구는 포함되지 않는다.
• 애플리케이션 이미지에는 Dockerfile 빌드 단계 중 최종 산출물만이 포함된다.
• 이전 단계에서 포함 시키고 싶은 것은 최종 단계에서 명시적으로 해당 컨텐츠를 복사해와야 한다.
3. Practice : Node.js
📌 Java와 Javascript 차이
- JS는 인터프리터 언어이므로 컴파일 절차 없음
- Application 실행을 위해 Node.js Runtime과 소스 코드가 최종 Image에 필요
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/ .
- package.json 내용을 기반으로 npm install 하여 node_modules 로딩
- 이전 빌드 단계의 node_modules 복사 & 로컬 환경의 src/ 복사
1️⃣ Image build
docker image build -t access-log .
- 내려받은 의존 모듈은 Docker Image Layer Cache에 저장되므로, 코드만 수정했다면 Build 속도는 더 빨라진다.
2️⃣ Run Container
docker container run --name access-log -d -p 801:80 --network nat access-log
- pip를 사용하는 Python, gems를 사용하는 Ruby에도 같은 방법을 적용할 수 있다.
4. Practice : Go
📌 Java와 Go의 차이
- Go는 Native binary로 compile되는 현대적인 cross-platform language
- 어떤 환경에서든 동작하는 binary를 compile 가능
- Java, Node.js, Python 처럼 별도의 Runtime 불필요
- Cloud Native Language로서도 인기 높다
- Docker가 Go로 구현
FROM diamol/golang AS builder
COPY main.go .
RUN go build -o /server
# app
FROM diamol/base
ENV IMAGE_API_URL="http://iotd/image" \
ACCESS_API_URL="http://accesslog/access-log"
CMD ["/web/server"]
WORKDIR /web
COPY index.html .
COPY --from=builder /server .
RUN chmod +x server
- Go 언어 도구 구성이 들어간 base Image 외, 별도의 의존 모듈 받는 단계 없다
- FROM diamol/base : 최소한의 OS Image가 들어간 base Image 사용
1️⃣ Image build
docker image build -t image-gallery .
- Go 컴파일러는 로그 양이 비교적 적다.
- build에 실패한 경우에만 로그를 출력
2️⃣ build에 사용된 Go 빌드 도구 이미지와 빌드된 Go 애플리케이션 이미지 크기 비교
docker image ls -f reference=diamol/golang -f reference=image-gallery
- 논리적 용량이므로 실제 Image 크기는 아니다.
- 그럼에도 최종 애플리케이션 Image에 모든 것이 들어가지는 않는다.
- Image 크기를 최소한으로 줄여, 외부로부터 공격 가능한 부분을 줄일 수 있다.
💡 대부분의 Docker command는 출력 내용 필터링 기능을 제공한다. (-f reference = "")
3️⃣ Run Container
docker container run -d -p 802:80 --network nat image-gallery
✒️ Docker image build
(참고: jenkins build pipeline은 연속적인 작업들을 jenkins에서 하나의 파이프라인(작업)으로 묶어 관리할 수 있게 만드는 플러그인이다.)
책에는 "docker image build 명령 자체가 파이프라인 정의 역할을 한다"고 나오는데 어떤 의미일까?
Dockerfile을 사용하여 Docker Image를 build하는 과정 자체가 Build 파이프라인을 정의하는 것이다.
Build 파이프라인은 SW develop process에서 여러 단계를 거쳐 컴파일·테스트·배포 과정이 자동화된 프로세스다.
이 때, Docker를 사용하여 Application을 Container에 넣고 Build 하는 과정은 독립된 단계로 구성되며, Dockerfile을 사용하여 Build 단계를 정의한다.
즉, Docker Image를 build하는 과정이 Build 파이프라인의 일부로 간주될 수 있음을 의미한다.
Docker Image를 build하는 동안 Dockerfile에 작성된 단계는 순차적으로 실행되며, 이를 통해 Application build process를 정의하고 제어할 수 있다.
5. Docker Multi-stage build 장점
1️⃣ 표준화
- 어떤 운영체제이고, 로컬 환경에 어떤 도구를 설치했는지 상관 없이 동일한 작업을 수행할 수 있다.
- 모든 Build process가 Docker Container의 내부에서 이뤄진다.
- 모든 Container는 모든 Tools를 정확한 버전으로 갖추고 있다.
2️⃣ 성능 향상
- Multi-stage build 각 단계는 자신만의 Cache를 갖는다.
- Docker는 Build 중에 각 Instruction에 해당하는 Layer cache를 찾아 재사용한다.
- Cache 재사용으로 90% 이상의 Build process에서 시간이 절약된다.
3️⃣ 최적화
- 최종 산출물 Image에 불필요한 도구는 모두 제외할 수 있다.
- 대표적인 예로 curl이 있다.
- command line tool(& library), URL을 이용해 데이터 전송을 가능하게 한다.
- Internet protocol transfer과 관련된 모든 것들이 curl이 담당하는 부분이다.
- Image 크기가 줄면 Application 시작 시간을 단축시키고, Dependency module을 줄여 보안 취약점을 최소화할 수 있다.
6. 연습 문제
- ch04/lab의 Dockerfile로 Image를 build하고, Dockerfile을 최적화한 다음 새로운 Image를 생성
- 최적화 Image의 크기를 리눅스 환경(15MB), 윈도 환경(260MB)으로 줄여라
- 현재 Dockerfile 스크립트의 HTML 파일을 수정하면 일곱 단계 build를 재수행 한다
- HTML 파일을 수정하더라도 재수행하는 build 단계를 한 단계로 줄여라.
1️⃣ 현재 리눅스 환경 기준 800MB
2️⃣ Dockerfile script 분석
FROM diamol/golang
WORKDIR web
COPY index.html .
COPY main.go .
RUN go build -o /web/server
RUN chmod +x /web/server
CMD ["/web/server"]
ENV USER=sixeyed
EXPOSE 80
- Multi-stage build 방식이 적용되지 않음
- diamol/golang에서 GO 언어 도구를 가져와 build하는 단계를 분리할 수 있음
- index.html 순서를 아래로 내리면 파일이 수정되어도 재수행하는 build 수를 줄일 수 있음.
3️⃣ Dockerfile 최적화
FROM diamol/golang AS builder
COPY main.go .
RUN go build -o /web/server
RUN chmod +x /web/server
# app
FROM diamol/base
EXPOSE 80
CMD ["/web/server"]
ENV USER=sixeyed
WORKDIR /web
COPY --from=builder /web/server /server
COPY index.html .