ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 호스트 빌드에서 도커의 멀티 스테이지 빌드까지
    인프라/도커 2024. 1. 3. 01:22

     

     

    도커에 익숙해지기 위한 몇 가지 실습들을 진행해보았습니다.

    아래 순서로 글을 진행해보겠습니다.

     

    1. 로컬에서 스프링 프로젝트 빌드해서 실행하기

    2. 로컬에서 빌드한 파일을 컨테이너에서 실행시키기

    3. 컨테이너에서 빌드 후 실행시키기

    4. 멀티스테이지 빌드 후 실행시키기

     

     


     

    1.  로컬에서 빌드해서 실행하기

    개발환경에서 가장 기초적으로 사용하는 세팅입니다.

    localhost에서 서버를 띄우는 세팅입니다.

     

     

    스프링 cli를 이용해서 프로젝트를 생성하고 8080에 접속했을 때 'Hello World'가 보이도록 index.html을 추가합니다.

    $ spring init --dependencies=web myserver

     

     

    빌드명령어를 실행해서 jar파일을 생성합니다.

    $ ./gradlew build

     

     

    빌드된 파일을 실행시켜서 웹서버를 띄웁니다.

    $ java -jar ./build/libs/myserver-0.0.1-SNAPSHOT.jar


    IDE에서 bootRun으로 실행시키는 과정을 수동으로 진행해보았습니다.

     

     

     

     

     

    2.  로컬에서 빌드한 파일을 컨테이너에서 실행시키기

    서버를 띄운다는 것은 빌드된 파일을 멈추지 않는 환경에서 실행시킨다고 생각할 수 있습니다.

    도커를 사용해서 컨테이너 환경에서 웹서버를 띄워보겠습니다.

     

     

    이미지 생성을 위해서 Dockerfile을 작성합니다.

    # 빌드된 파일이 실행되어야할 환경을 컨테이너에 설치
    FROM eclipse-temurin:17
    
    # 호스트에서 빌드된 파일을 컨테이너로 복사
    COPY ./build/libs/myserver-0.0.1-SNAPSHOT.jar ./app.jar
    
    # 컨테이너 실행시에 app.jar를 실행
    ENTRYPOINT ["java","-jar","/app.jar"]

     

     

    도커 이미지를 생성합니다.

    # -t 옵션은 이미지에 이름 부여
    $ docker build -t myserver .

     

     

     

    만들어진 이미지를 사용해서 컨테이너를 실행시킵니다.

    # -p 옵션 [호스트포트]:[컨테이너포트] 맵핑
    $ docker run -it -p 8090:8080 myserver

     

    호스트의 8090에 요청시 "Hello world" 확인 가능

     

     

     

     

     

    3.  컨테이너에서 빌드 후 실행시키기

    2번처럼 도커를 사용한다면 도커의 큰 장점을 포기하는 것과 같습니다.

    자바가 JVM을 통해서 OS에 관계 없이 실행할 수 있느 것이 큰 장점인 것 처럼, 도커의 컨테이너 기술도 호스트 환경에 관계없이 동일한 환경을 제공하는 것에 큰 이점이 있습니다. 도커 컨테이너 안에서 작동하는것은 호스트 환경과 무관하게 컨테이너가 실행만 되면 동일한 동작을 보장하기 때문입니다.

     

     

    빌드에 필요한 요소들을 컨테이너로 복사하는 Dockerfile을 생성합니다.

    # 빌드된 파일이 실행되어야할 환경을 컨테이너에 설치
    FROM eclipse-temurin:17
    
    # 컨테이너의 작업 경로 설정
    WORKDIR ./workspace/app
    
    # 호스트에서 빌드된 파일을 컨테이너로 복사
    COPY ./gradlew /workspace/app/
    COPY ./settings.gradle /workspace/app/
    COPY ./build.gradle /workspace/app/
    COPY ./gradle /workspace/app/gradle/
    COPY ./src /workspace/app/src/
    
    # 빌드 실행
    RUN /workspace/app/gradlew build

     

     

    위 Dockerfile을 사용해서 도커 이미지를 빌드하고 스프링 서버가 잘 빌드되었는지 확인합니다.

    $ docker build -t myapp .
    
    $ docker run -it myapp bash

     

     

     

    컨테이너의 bash를 사용해서 / 경로로 이동하면 'WORKDIR' 로 설정한 workspace를 찾아볼 수 있습니다.

     

    build 폴더를 찾아가면 빌드된 파일을 확인할 수 있습니다.

    도커 이미지 생성 중에 문제가 있다면 이처럼 bash로 접속해서 한 단계씩 확인해 볼 수 있습니다.

     

     

    그러면 도커 이미지 실행시 바로 서버가 실행될 수 있도록 Dockerfile을 수정해줍니다.

    # 빌드된 파일이 실행되어야할 환경을 컨테이너에 설치
    FROM eclipse-temurin:17
    
    # 컨테이너의 작업 경로 설정
    WORKDIR ./workspace/app
    
    # 호스트에서 빌드된 파일을 컨테이너로 복사
    COPY ./gradlew /workspace/app/
    COPY ./settings.gradle /workspace/app/
    COPY ./build.gradle /workspace/app/
    COPY ./gradle /workspace/app/gradle/
    COPY ./src /workspace/app/src/
    
    # 빌드 실행
    RUN /workspace/app/gradlew build
    
    # 빌드 파일 위치 이동
    RUN mv /workspace/app/build/libs/myserver-0.0.1-SNAPSHOT.jar /workspace/app/app.jar
    
    # 컨테이너 실행시에 app.jar를 실행
    ENTRYPOINT ["java","-jar","app.jar"]

     

     

    이제 도커에서 서버를 실행시킵니다.

    호스트의 8090 포트로 접속하면 도커 컨테이너에 떠있는 서버를 호출할 수 있습니다.

    $ docker run -it -p 8090:8080 myapp

     

    호스트의 8090에 요청시 "Hello world" 확인 가능

     

     

     

     

     

    4.  멀티스테이지 빌드 후 실행시키기

    빌드된 자바 파일을 실행하기 위해서는 jre가 필요하다. jdk는 jre + @ 이기 때문에 빌드 이후 시점에서는 @들은 불필요한 파일이 된다.

    따라서, 빌드된 파일을 실행하기 위해 꼭 필요한 요소들로 이미지를 구성하면 더 적은 리소스를 사용하는 이점을 가질 수 있다.

     

    멀티 스테이지 빌드는 빌드 과정을 여러 단계로 나눠서 단계별로 관리가 가능하고, 최종 결과물에서는 꼭 필요한 파일만 사용해서 이미지 사이즈를 줄이는 이득을 취할 수 있다.

     

    https://docs.docker.com/build/building/multi-stage/#differences-between-legacy-builder-and-buildkit

     

     

    도커 파일을 변경해서 멀티 스테이지 빌드를 가능하게 만듭니다.

    각 스테이지는 'FROM' 키워드를 기준으로 구분됩니다.

    # 첫번째 스테이지 시작
    FROM eclipse-temurin:17-jdk as builder
    
    # 컨테이너의 작업 경로 설정
    WORKDIR /workspace/app
    
    # 호스트에서 빌드된 파일을 컨테이너로 복사
    COPY ./gradlew /workspace/app/
    COPY ./settings.gradle /workspace/app/
    COPY ./build.gradle /workspace/app/
    COPY ./gradle /workspace/app/gradle/
    COPY ./src /workspace/app/src/
    
    # 빌드 실행
    RUN /workspace/app/gradlew build
    RUN mv /workspace/app/build/libs/myserver-0.0.1-SNAPSHOT.jar /workspace/app/app.jar
    
    
    
    # 두번째 스테이지 시작
    FROM eclipse-temurin:17-jre as runner
    
    # 각 스테이지는 별도의 환경이기 때문에 다시 선언
    WORKDIR /workspace/app
    
    # 이전 스테이지에서 jar파일 복사
    COPY --from=builder /workspace/app/app.jar /workspace/app/app.jar
    
    # 컨테이너 실행시에 app.jar를 실행
    ENTRYPOINT ["java","-jar","app.jar"]

     

     

     

    도커 이미지를 생성해보면 단일 스테이지 빌드 때와 비교해서 용량이 많이 줄어든 것을 볼 수 있습니다.

    단일 스테이지 빌드에는 673 mb 였던 이미지가 멀티 스테이지 빌드에서는 282mb 줄어들었습니다.

    # 이미지 빌드
    $ docker build -t myapp .
    
    # 이미지의 메타정보 확인
    $ docker inspect myapp

     

    단일 스테이지 빌드 ( 673mb )
    멀티 스테이지 빌드 ( 282 mb )

     

     

    이전 과정들과 마찬가지로 컨테이너 실행을 통해서 서버를 띄울 수 있습니다.

    $ docker run -it -p 8090:8080 myapp

     

     


     

    24.03.20 추가

    이런 식으로도 작성이 가능합니다.

    # Build stage
    FROM eclipse-temurin:17-jdk as build
    WORKDIR /workspace/app
    
    COPY gradlew .
    COPY gradle gradle
    COPY build.gradle .
    COPY settings.gradle .
    COPY src src
    
    RUN ./gradlew build -x test
    
    # Run stage
    FROM eclipse-temurin:17-jre
    VOLUME /tmp
    ARG JAR_FILE=/workspace/app/build/libs/*.jar
    COPY --from=build ${JAR_FILE} app.jar
    ENTRYPOINT ["java", "-jar","/app.jar"]

     

     

     


    [출처]

    https://docs.docker.com/build/building/multi-stage/#differences-between-legacy-builder-and-buildkit

     

Designed by Tistory.