Programming-[Infra]/Docker

도커 교과서(엘튼 스톤맨, 심효섭) - 3. 멀티 스테이지 빌드, 도커 네트워크

컴퓨터 탐험가 찰리 2023. 3. 5. 19:30
728x90
반응형

*빌드, 컴파일과 링크

빌드는 컴퓨터가 실행할 수 있는 .exe 파일을 생성하는 작업을 말한다. 어셈블리어로 작성된 .asm 파일 -> .obj 기계어 파일로 바꾸는 과정이 컴파일이다. 이 .obj 파일들이 서로 연결되어 실행될 수 있는 .exe 파일로 묶어주는 작업을 링크라고 한다. 다시 말해 빌드는 컴파일 + 링크 과정이다.

 

 

1. 애플리케이션 빌드 해보기: 자바 소스 코드

 

다음은 책에서 제공하는 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"]

 

 

- FROM diamol/maven AS builder 

기반이 되는 이미지를 레포지토리(제일 먼저 도커 허브 검색)에서 찾는다. diamol/maven이란 이름을 갖는 이미지를 찾고 사용한다. AS로 이 build stage를 builder로 호칭한다.

 

- WORKDIR, COPY, RUN mvn

WORKDIR은 이미지 안에 작업 디렉토리를 만드는 명령어이다. (로컬 디렉토리의 해당 위치에 만들지는 않는다. 아래 이미지를 보면 해당 디렉토리 위치에 /src는 없는 것을 확인할 수 있다.)

 

자바 프로젝트의 각종 라이브러리 디펜던시를 관리해주는 maven을 이용하는데, 그 maven을 빌드하고 설정하기 위한 내용이 적힌 부분이 pox.xml이다. 이것을 현재 디렉토리 위치(.)에 COPY 인스트럭션을 통해 복사하였다.

 

  • RUN은 이미지 빌드 프로세스 중에 명령을 실행하도록 도커에게 지시하는 도커 파일 명령이다. 명령 결과를 이미지 레이어에 저장한다.
  • mvn은 메이븐을 실행하는 명령이다.
  • -B는 Maven에게 "배치" 모드로 실행하도록 지시하는 Maven 명령줄 옵션이다. 즉, 실행되는 모든 목표에 대해 전체 출력을 표시하지 않는다. 입력을 요구하지 않거나 로그를 복잡하게 만드는 불필요한 출력을 원하지 않는 자동 빌드 프로세스에서 Maven을 실행하는 데 유용하다.
  • dependency:go-offline은 메이븐에게 프로젝트의 모든 종속성을 다운로드하여 로컬 저장소에 캐시하여 나중에 인터넷 연결이 없어도 사용할 수 있도록 하는 메이븐의 goal(실행 목표) 이다.

 

- COPY --from=builder / ....    .

멀티 스테이지를 적용한 부분이다. 최종적인 산출물은 맨 마지막 FROM 명령어를 통해 지정된 부분이지만, 기반이 되는 파일들은 맨 처음 FROM 명령어에서 정의한 diamol/maven 쪽에서 가져온다는 의미이다. 이렇게 함으로써 해당 이미지가 builder에서 파일을 복사해올 뿐 컴파일된 JAR 파일은 직접 소유하지 않고 있어도 되는 구조가 된다. 불필요한 이미지 레이어의 중복이 없도록 만드는 것이다.

 

 

이제 교재에서 제공해주는 파일 디렉토리로 가서 아래 명령어를 실행하면 이미지를 다운로드 받으면서 build한다. tag명을 image-of-the-day로 지정하였다.

docker image build -t image-of-the-day .

해당 이미지는 NASA의 오늘의 천문사진 서비스(https://apod.nasa.gov)에서 오늘 자 사진을 받아오는 REST API라고 한다.

 

 

2. 도커 네트워크 생성하기

 

앞서 생성한 REST API용 image는 학습할 프로젝트의 일부이다. 위 image는 java로 구동되었으나, node.js, Go를 이용한 이미지도 함께 빌드하여 3개 프로젝트가 서로 통신하면서 작동하도록 만든다!

 

네트워크 생성

컨테이너간 통신에 사용되는 도커 네트워크를 생성한다. nat이라는 이름으로 네트워크를 생성한다는 의미이다. 생성 후에는 sha256으로 해싱된 network 해시 코드가 보인다. 오류가 난다면 주로 network 이름이 이미 존재하기 때문일 것이라고 한다.

docker network create nat

 

컨테이너에 네트워크 연결

 

앞서 빌드한 이미지를 생성한 네트워크에 연결한다. 이미지의 80번 포트를 호스트 컴퓨터의 800번에 연결한다. 

docker container run --name iotd -d -p 800:80 --network nat image-of-the-day

-d는 이전에 배웠듯이 백그라운드로 실행하는 -detach의 약어이다.

 

 

이제 localhost:800/image에 접속하면 json으로 정보가 뜬다. (json-formatter 크롬 확장자 설치함)

 

 

3. 애플리케이션 추가 빌드: Node.js

 

node.js로 짜여진 이미지도 컨테이너화해서 띄워본다. 이 애플리케이션은 log를 남기는 역할을 하는 서버이다.

 

교재에서 제공하는 Dockerfile 내용은 다음과 같다.

 

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 패키지 관리자를 설치한다.

 

EXPOSE 80으로 80번 포트를 외부 호스트 컴퓨터에 노출시키고, node를 이용하여 node server.js 명령어를 실행한다.

 

Node.js는 자바스크립트 언어로 구현된다. 자바스크립트는 인터프리터형 언어라서 컴파일이 필요없다. 그래서 java처럼 JAR 파일을 컴파일하고 최종 애플리케이션 이미지에 복사할 필요가 없다. COPY --from 구문으로 패키지들이 있는 node_modules 전체를 복사한다.

 

  • --from=builder: 이 인자는 파일을 복사할 원본 이미지 이름 또는 ID를 지정한다. 위에서 지정한 builder가 된다.
  • /src/node_https/ /app/node_https/: 복사할 파일의 원본 및 대상 경로. 전체 node_modules 디렉토리를 빌더 이미지의 /src에서 현재 이미지의 /app으로 복사한다.

 

컴파일되어 소스코드 자체는 필요없는 자바와는 달리 Node.js 파일은 소스코드가 필요하다. 그래서 src/ .을 통해 소스 코드를 복사하였다. 이제 아래 명령어를 통해 이미지를 빌드한다.

 

docker image build -t access-log .

 

그리고 앞서와 마찬가지로 nat 네트워크에 연결한다.

 

docker container run --name accesslog -d -p 801:80 --network nat access-log

 

localhost:801/stats에 접속하면 로그 건수를 확인할 수 있다.

 

 

 

4. 애플리케이션 추가 빌드 : Go

 

go는 크로스 플랫폼을 지원하는 언어다. 윈도우, 리눅스, amd64, 모바일용 네이티브 바이너리를 위한 컴파일이 모두 지원된다. 도커 자체가 Go로 구현되었다고 한다.

 

교재에서 제공되는 Dockerfile의 내용은 다음과 같다.

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

 

해당 애플리케이션에서 앞서 만든 서버들을 바라보도록 환경 변수를 ENV 인스트럭션을 통해 설정하였다. 그리고 이전 애플리케이션들처럼 source가 되는 파일들을 COPY 하였다. 맨 마지막의 chmod 명령어는 'change mode'의 약어이며 특정 파일이나 디렉토리의 권한을 변경하는 Unix-based 명령어이다. +x 옵션은 실행할 권한을 준다는 것이다. 즉 chmod +x server는 server 디렉토리에 대해 실행 권한을 부여하는 것이다.(윈도우에서는 효과 x)

 

이미지를 빌드한다.

docker image build -t image-gallery .

 

이미지의 포트를 공개하고 nat 네트워크에 연결한다.

docker container run -d -p 802:80 --network nat image-gallery

 

localhost:802에 접속하면 다음과 같은 사진을 얻을 수 있다. 그리고 localhost:801/stats에서 logs 카운트도 올라간 것을 볼 수 있다.

 

 

 

이미지 크기 최적화

이번 go-lang을 이용한 이미지 크기를 알아본다. docker image ls를 실행하되 -f reference 구문을 추가하여 특정 키워드가 포함된 image만 조회해본다.

 

docker image ls -f reference=diamol/golang -f reference=image-gallery

 

교재에서 Go 빌드 도구를 포함하는 이미지의 크기는 800MB 이상이라고 한다(reference=diamol/golang으로 조회). 다만 나는 diamol/golang 관련 이미지가 없어서 그런지 조회되지는 않았다. 어쨌든, 이를 기반 이미지로 활용하여 image-gallery 이미지가 실행되어 단 26.2 MB만 차지하게 되었다. 해당 이미지를 빌드하는데 필요한 이미지 레이어들을 다른 기반 이미지에서 가져오기 때문에 애플리케이션의 이미지 크기를 최소화하고, 보안상 공격이 가능한 부분을 최소화할 수 있다는 장점이 있다고 한다.

 

 

 

5. 멀티 스테이지 Dockerfile

 

위에서 만들어본 애플리케이션 이미지들은 여러 개의 FROM 명령어가 포함된 멀티 스테이지 스크립트이다. 멀티 스테이지의 장점을 요약하면 다음과 같다.

 

  1. 표준화: 로컬 운영 체제와 컴퓨터 도구에 상관없이 표준화된 도커 이미지를 기반으로 컨테이너를 만든다. 기존 개발자나 신규 개발자 모두 똑같은 형태의 이미지를 만들 수 있다.
  2. 성능 향상: 멀티 스테이지의 각 단계가 이미지 레이어 캐시를 가짐으로써 캐시를 재사용하여 빌드에 필요한 시간 및 대역폭을 절약할 수 있다.
  3. 메모리 절약: 위 '이미지 크기 최적화' 에서 살펴본 것처럼 기반 이미지를 바탕으로 하기 때문에 공용으로 사용하는 이미지 레이어는 중복해서 사용할 필요가 없다. 이는 애플리케이션의 의존 모듈을 줄여서 외부 공격의 가능성을 최대한 차단하는 역할을 한다.

 

 

연습문제 풀기

 

1. Dockerfile 최적화

 

주어진 Dockerfile은 다음과 같다. 이 상태로 일단 이미지를 빌드하고 최적화해야한다.

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

 

이미지를 그대로 build 했더니 736MB가 나왔다. 이름은 practice로 지었다.

 

혹시나 해서 이름만 practice:2로 바꾸고 다시 빌드했더니 용량이 같다.

 

순서를 잘못 바꿔서 main.go 파일을 복사하지 않고 /web/server를 실행하면 에러가 난다.

 

FROM 절을 바꾸면 CACHED 되긴하지만 이런다고 image의 용량 자체가 줄어들진 않았다.

FROM practice:2

WORKDIR web

COPY main.go .
RUN go build -o /web/server
RUN chmod +x /web/server
CMD ["/web/server"]
EXPOSE 80

ENV USER=sixeyed

COPY index.html .

 

정답은 멀티스테이징을 활용하고 AS 구문과 --from 구문을 쓰는 것이였다.

 

FROM diamol/golang AS builder

COPY main.go .
RUN go build -o /server
RUN chmod +x /server

# app
FROM diamol/base

EXPOSE 80
CMD ["/web/server"]
ENV USER="sixeyed"

WORKDIR web
COPY --from=builder /server .
COPY index.html .

 

이미 정의된 diamol/golang 이미지를 builder로 지정하고 그 이미지로부터 main.go 파일 복사 및 /server 실행을 한다. 또한 dimol/base 이미지로부터는 /web/server 명령어를 실행하고 환경 변수를 설정하도록 한다.

 

다음으로 --from 명령어로 /server 디렉토리에 있는 파일을 복사해와서 이미지를 만든다. 실제로 해당 이미지에 필요한 내용은 index.html 뿐인 것이다.

 

 

728x90
반응형