임대일
5.4 컨테이너 애플리케이션 구성: SpringBoot 백엔드 컨테이너 구성 본문
1. 백엔드 구성하기
이번 시간에는 리피 백엔드 컨테이너의 이미지를 생성한다. leafy 백엔드 애플리케이션은 자바로 개발되어 있다. 자바로 개발된 소스코드는 jar 혹은 war라는 파일로 프로그램을 빌드할 수 있다. jar 혹은 war 파일을 실행시키려면 OS에 자바 런타임이 설치되어 있어야 한다. 그리고 소스코드를 애플리케이션으로 빌드하려면, Maven이나 Gradle이라는 빌드 프로그램이 필요하다. leafy에서는 Gradle을 활용한다.
이 이미지를 구성하는 순서를 정리해보면, 첫번째로 먼저 OS 위에 자바 런타임과 Gradle을 설치한다. 그리고 소스코드를 다운받은 후에, 이 소스코드를 gradle clean build라는 명령을 사용해서 애플리케이션으로 빌드한다. 빌드가 완료되면 jar 파일이 생성된다. 애플리케이션을 실행할 때 이렇게 자바 명령과 jar 파일의 경로를 지정해서 애플리케이션을 실행할 수 있다.
백엔드 애플리케이션에서는 소스 코드 빌드 과정이 포함되어 있기 때문에 이전 파트에서 배웠던 멀티 스테이징 빌드 기술을 활용한다. 애플리케이션 빌드에 사용되는 이미지는 Gradle 이미지를 사용한다. 이 Gradle 이미지에서 빌드를 실행해서 만들어진 jar 파일을 이 자바 애플리케이션 실행 기능만 가지고 있는 OpenJDK 이미지로 복사할 것이다. 그리고 OpenJDK 이미지에서는 이 컨테이너를 실행할 때 복사한 jar 파일을 애플리케이션으로 실행할 것이다.
2. 백엔드 이미지 구성하기
이제 VScode를 열자. 백엔드 폴더를 열고, 소스 코드의 기능을 간단하게 요약해서 정리하겠다. 스프링 부트 자체는 저희 강의의 내용에서 벗어나기 때문에 공통적으로 적용되는 백엔드 애플리케이션의 기능과 코드의 일부분만 설명한다. 크게 복잡하지 않은 애플리케이션이기 때문에 개발 경험 있으신 분들은 코드를 한번 살펴보시는 것도 좋을 것이다. 먼저 src/java/com/devwiki/lefy
폴더를 열어준다. 해당 폴더 안에 있는 소스 코드들이 실제로 애플리케이션을 구성하는 소스이다.
이 중에서 컨트롤러 폴더 안에 있는 소스는 이 애플리케이션이 어떤 경로의 요청을 받아서 어떤 응답을 제공할 것인지를 정의하고 있다. 데이터를 가져오고 가공하는 부분은 서비스라는 폴더의 내용이 있다. 그리고 내부에서 데이터를 주고받기 위해 사용하는 DTO가 있다. 데이터베이스와 정보를 주고받기 위한 모델, 레파지토리 폴더가 있다. 그리고 도커 파일은 소스 코드의 최상단에 위치하고 있다.
이 소스 코드들이 의존하는 라이브러리 정보는 build.gradle에 정리되어 있다. 이렇게 Dependencies 쪽에 이 소스 코드가 애플리케이션으로 빌드 되기 위해 필요한 라이브러리 정보들이 정리되어 있는 것이다.
컨트롤러 폴더의 plant에 plantController.java 를 예시로 한번 열어보면 이렇게 애플리케이션이 받아들이는 경로가 표시되어 있다. 그래서 애플리케이션으로 API 요청을 보낼 때 해당 URI로 요청을 전달하면 이 요청에 대한 처리는 plantController.java 에 정의된 로직이 처리하게 된다. 예를 들어 애플리케이션에 /api/v1/plants
라는 요청을 GET 메서드로 보내게 되면 이렇게 getAllPlants
라는 메서드가 요청을 처리하게 된다. 이 getAllPlants
메서드에서는 PlantsService
라는 인스턴스를 통해서 모든 식물의 정보를 불러와서 HTTP 응답을 생성해서 클라이언트에게 제공한다.
그러면 실제로 모든 식물의 정보를 불러오는 로직을 확인해 보면 service/plant/PlantsService.java
라는 클래스가 있다. 이 클래스에서 getAllPlants
로 정의되어 있는 부분이 아까 컨트롤러에서 호출하는 부분이다.
이 getAllPlants
에서 수행하는 역할은 데이터베이스와 연결되어 있는 플랜츠 레포지토리를 통해서 실제로 DB 서버의 정보를 모두 불러온 다음에 이 DB 서버에서 불러온 정보를 가공해서 컨트롤러로 전달해 주는 것이다. 이 정보를 전달하는 과정에서 정보를 가공하고 정렬하는 역할도 이 서비스에서 비즈니스 로직을 통해서 처리된다.
이런 식으로 애플리케이션에 구성된 API의 목록을 간단히 정리했다. 먼저 식물과 관련된 API는 모든 식물을 조회하는 API와 식물 아이디로 단건 조회, 식물 추가, 수정, 삭제하는 API가 있다. 사용자 관련 API는 모든 사용자를 조회하거나 사용자 아이디로 한 명의 사용자를 조회하고 사용자를 추가, 수정, 삭제하고 사용자의 로그인을 처리하는 API가 있다. 사용자 식물과 식물 다이어리에도 비슷한 기능을 제공하는 API가 정의되어 있다. 특히 식물 일기 쪽에서는 메인 화면에 표시하기 위해서 최근 5건의 일기를 조회하는 부분이 추가되어 있다. 애플리케이션의 소스코드 구조와 애플리케이션이 제공하는 API를 확인해 보았다.
마지막으로 애플리케이션의 구성 정보를 확인하는 /resources/application.properties
파일을 확인해 보자. 이 파일을 열어보면 이렇게 키와 값 형태로 데이터들이 정의되어 있는 것을 확인하실 수 있다.
이 파일에서는 애플리케이션이 데이터베이스에 접근하기 위한 URL과 DB명, 사용자명, 비밀번호 정보를 저장해 두어야 한다. 이 정보를 소스코드 내에 값을 저장할 수도 있지만 애플리케이션이 실행되는 시점에서 환경 변수를 통해서 값을 설정할 수도 있다. 소스코드에 계정 정보를 저장할 경우에는 보안의 위험이 있다. 그래서 보통은 애플리케이션을 실행하는 시점에 OS의 시스템 환경 변수를 읽어와서 실행하도록 값을 설정한다. 이렇게 달러 표시와 괄호를 열어서 변수 처리를 할 수 있다. 저희가 이전 시간에 ENV 컬러 앱으로 실습했던 것처럼 OS의 특정 환경 변수 값을 지정함으로써 애플리케이션이 실행되는 시점에 값을 제공해 주는 것이다. SpringBoot에서는 이렇게 달러 표시와 가로로 둘러싸게 되면 이 시스템의 DB_URL
이라고 정의되어 있는 환경 변수 값을 불러올 수 있다. 만약 시스템 환경 변수의 값이 비어 있으면 기본 값으로 콜론 오른쪽에 정의된 값을 사용한다.
이전 시간에 데이터베이스 이미지를 빌드할 때 도커 파일의 계정명과 패스워드를 지정해 두었다. 그때 지정한 계정 정보와 소스 코드에 있는 기본 값은 동일하기 때문에 별도로 수정할 부분은 없다. 하지만 DB_URL
같은 경우 로컬 호스트가 아닌 실제 데이터베이스 서버의 URL로 수정해야 한다.
--env DB_URL={변경할DB의URL}
그리고 이 데이터는 이따가 컨테이너를 실행할 때 환경 변수를 주입해서 URL을 수정한다.
지금까지는 소스 코드의 구조와 내용에 대해서 설명했다. Dockerfile을 작성하기 전에 PostgreSQL에서도 하셨던 것처럼 컨테이너를 직접 실행해서 컨테이너 내부에서 직접 환경을 구성한다.
# leafy-backned 폴더로 이동(esaydocker에서 시작할 경우 cd leafy/leafy-backned)
cd ../leafy-backend/
먼저 작업 폴더를 leafy-backend로 이동한다. 그리고 첫 번째 컨테이너에서 Gradle 컨테이너를 실행해보자.
docker run -it --name gradle gradle:7.6.1-jdk17 bash
docker run –it –name 컨테이너명은 gradle로 지정하시고요. 이미지명은 gradle에 7.6.1-jdk17로 지정한다. 그리고 실행 명령어는 한 칸 띄시고 bash로 지정한다. 컨테이너를 실행하면 이미지를 다운받으면서 컨테이너가 실행되면서 그레이들의 쉘로 들어오신 것을 확인할 수 있다. 그러면 이제 WORKDIR 지시어를 사용했을 때 나타나는 효과인 폴더를 만들고 이동해 보도록 하겠다.
# app 폴더 생성 및 이동(WORKDIR /app과 같은 효과)
mkdir /app && cd /app
먼저 mkdir /app
으로 폴더를 생성한다. 그리고 &
를 두 번 입력하고, 한 칸 띈 다음에 cd /app
폴더로 이동한다. 이렇게 현재 경로가 /home/gradle
에서 /app
으로 이동된 것을 확인할 수 있다. WORKDIR 지시어에서 /app
으로 지정하면 정확히 이 명령을 사용한 것과 같은 기능을 수행한다.
docker ps
ls
# gradle 컨테이너의 /app 경로로 . 경로 복사(꼭 leafy-backend 경로에서 실행해야 합니다.)
docker cp . gradle:app
그럼 두 번째 셀로 가셔서 이제 docker ps 명령을 사용해서 그레이들이 제대로 실행되어 있는지 확인한다. ls 명령을 사용해서 소스 코드가 잘 있는지 확인하고 이 소스 코드들을 docker cp 명령을 사용해서 복사하도록 하자. docker cp에 점(.
)으로 현재 폴더의 모든 파일들을 지정한다. gradle 컨테이너의 app 폴더로 복사한다. 엔터를 눌러주면 정상적으로 카피가 된다.
ls
# gradle 빌드 실행
gradle clean build --no-daemon
방금 만들어진 앱 폴더에 ls 명령을 사용해 보시면 이렇게 호스트 머신의 leafy-backend 디렉터리에 있던 모든 파일들이 복사된 것을 확인할 수 있다. 여기서 애플리케이션을 빌드하면 빌드라는 디렉터리가 만들어질 것이다. 지금은 빌드를 하기 전이기 때문에 빌드 디렉터리가 없는 것을 확인할 수 있다. 그럼 이제 애플리케이션을 빌드해 보자. gradle clean build --no-daemon
을 입력한다. 엔터를 눌러서 애플리케이션을 빌드한다.
ls
# gradle 빌드를 실행한 결과 확인
ls build/libs
정상적으로 빌드가 될 것이다. 다시 ls 명령을 사용하면 이렇게 빌드 디렉터리가 생성된 것을 확인하실 수 있다. 그럼 이제 ls 명령에 build/libs
폴더를 검색해 보면 이렇게 jar 파일이 만들어진 것을 확인할 수 있다. 그럼 이제 이 jar 파일을 한번 실행시켜 보자.
# gradle 컨테이너 내부에서 빌드로 만들어진 jar 파일 실행
java -jar /app/build/libs/Leafy-0.0.1-SNAPSHOT.jar
java -jar /app/build/libs/Leafy-0.0.1-SNAPSHOT.jar
파일을 지정한다. 정상적으로 애플리케이션이 실행되는 것을 확인할 수 있다. 다만 에러가 나면서 종료되는데, 이 종료된 로그를 보시면 데이터베이스 연결이 정상적으로 이루어지지 않았기 때문에 애플리케이션이 종료된다.아까 본 것처럼 소스 코드에는 DB의 URL이 로컬 호스트로 정의되어 있다. 이 컨테이너는 로컬 호스트가 아닌 우리가 실행시킨 PostgreSQL의 컨테이너로 접속해야 한다. 백엔드 애플리케이션을 실행할 때 이 데이터베이스의 URL을 환경 변수를 지정해서 덮어쓰기할 수 있다. 우리가 ENV 노드 컬러 앱에서 컬러 값을 덮어쓰기 했던 것처럼 말이다.
docker rm -f gradle
그럼 오른쪽 셀에서 docker rm 명령을 사용해서 gradle 컨테이너는 종료하도록 한다. 지금까지 Dockerfile을 작성하기 전에 이미지가 내부에서 만들어지는 과정을 직접 실습을 통해서 확인해 보았다. 이 하나하나 손수 작업했던 과정들을 떠올려 보면서 도커 파일을 작성해 보자.
# 빌드 이미지로 OpenJDK 11 & Gradle을 지정
FROM gradle:7.6.1-jdk11 AS build
# 소스코드를 복사할 작업 디렉토리를 생성
WORKDIR /app
# 호스트 머신의 소스코드를 작업 디렉토리로 복사
COPY . /app
# Gradle 빌드를 실행하여 JAR 파일 생성
RUN gradle clean build --no-deamon
# 런타임 이미지로 OopenJDK 11 JRE-slim 지젖ㅇ
FROM openjdk:11-jre-slim
# 애플리케이션을 실행할 작업 디렉토리를 생성
WORKDIR /app
# 빌드 이미지에서 생성된 JAR 파일을 런타임 이미지로 복사
COPY --from=buld /app/build/libs/*.jar /app/leafy.jar
EXPOSE 8080
ENTRYPOINT ["java"]
CMD ["-jar", "leafy.jar"]
일단 빌드에 사용되는 첫 번째 이미지는 그레이들의 7.6.1 버전을 사용할 것이다. 그리고 AS를 사용해서 빌드 스테이징이라는 것을 명시한다. WORKDIR로 작업 폴더를 /app으로 지정해 주고 그리고 COPY 지시어로 소스 코드를 모두 /app 폴더로 이동한다. 그리고 빌드 명령인 gradle clean build 명령을 실행하면 jar 파일이 만들어질 것이다. 그리고 다음 실행 이미지는 OpenJDK의 11 버전을 사용했다. 이 이미지는 OS 위에 자바 애플리케이션을 실행하기 위한 JDK 런타임만 설치되어 있는 상태이다. 이 OpenJDK 이미지에도 마찬가지로 /app 폴더를 생성한다. 그리고 COPY 명령을 사용해서 이전 gradle 이미지에서 빌드한 jar 파일을 이 OpenJDK 내부의 leafy.jar 파일로 복사했다. 그리고 EXPOSE로 8080 포트를 사용한다는 것을 명시했다. ENTRYPOINT에는 "java"를 지정하고, CMD에는 "-jar", leafy.jar로 파일명을 지정했다. Dockerfile을 이 파일 내용과 동일하게 작성했다면, 이제 이미지로 빌드해 보자.
# 도커파일을 사용해 leafy-backend 이미지 빌드
docker build -t 레지스트리계정명/leafy-backend:1.0.0 .
docker build -t 다음 레지스트리계정명에 leafy-backend로 이미지 이름을 지정한다. 버전은 1.0.0, 그리고 점(.
)을 추가해서 이미지를 빌드한다.
빌드가 완료된다. 환경별로 차이는 있을 수 있는데 보통 3분 정도 걸린다.
# 이미지 push
docker push easydocker123/leafy-backend:1.0.0
그럼 빌드한 이미지를 각 사용자의 레지스트리로 푸시해 보자. docker push 계정명을 입력하고,leafy-backend:1.0.0을 입력해서 push해 준다. push가 정상적으로 완료되었으면 컨테이너를 실행해 보자.
# 도커파일을 사용해 leafy-backend 이미지 빌드
docker run -d -p 8080:8080 -e DB_URL=leafy-postgres --name leafy --network leafy-network 레지스트리계정명/leafy-backend:1.0.0
docker run -d -p 포트는 8080으로 지정해 준다. 그리고 이제 -e
명령을 사용해서 DB_URL
을 leafy-postgres
로 수정한다. 그리고 컨테이너 이름은 leafy
로 지정한다. --network
에 leafy-network
를 지정하고 사용자의 레지스트리계정명에 leafy-backend:1.0.0
버전을 지정한다.
# 실행중인 컨테이너 확인
docker ps
컨테이너가 잘 실행된다. docker ps 명령으로 leafy-postgres와 leafy-backed가 실행된 것을 확인할 수 있다.
# leafy 컨테이너의 로그 확인
docker logs leafy
이제 docker logs에 leafy 컨테이너명을 지정해서 컨테이너의 로그를 확인한다. 아까 본 에러 없이 애플리케이션이 잘 실행되고 있는 것을 확인할 수 있다. 그럼 이 컨테이너로 API 요청을 보냈을 때 데이터가 잘 들어오는지 확인하자.
# leafy 컨테이너의 로그 확인
curl http://localhost:8080/api/v1/users
curl 명령어가 없다면 건너뛰어도 된다. 강의 화면만 참고하면 된다.
curl에 http://localhost
에 8080 포트에 /api/v1/users
를 확인해 보면 이렇게 백엔드 애플리케이션을 통해서 응답을 json 형태로 받아온 것을 확인할 수 있다. 이번 시간에는 leafy 백엔드 애플리케이션을 이미지로 빌드하고 컨테이너로 실행해 봤다. 다음 시간에는 프론트엔드를 빌드해 보도록 하자.
'도커 > 개발자를 위한 쉬운 도커' 카테고리의 다른 글
5.5 컨테이너 애플리케이션 구성: vue.js 프론트엔드 컨테이너 (2) | 2024.07.23 |
---|---|
5.3 컨테이너 애플리케이션 구성: PostgreSQL 컨테이너 구성 (2) | 2024.07.23 |
5.2컨테이너 애플리케이션 구성: Leafy 애플리케이션 구성 (4) | 2024.07.23 |
5.1 컨테이너 애플리케이션 구성: 클라우드 네이티브 애플리케이션 구성 (1) | 2024.07.23 |
4.6 도커(Docker) 이미지 빌드: 멀티 스테이지 빌드(Multi-Stage-Build) (0) | 2024.06.29 |