대학교 강의에서 클라우드 개념을 사용한 자유 주제에 대한 팀 프로젝트를 진행하며, 팀 프로젝트의 주제대로 클론코딩하여 수정한 스프링 프로젝트를 Docker-compose를 사용하여 배포하게 되었다.
본 게시글에서는 Docker-compose를 사용하여 스프링을 배포하는 것에만 집중하며, Docker를 왜 사용하는지 Docker를 어떻게 설치하는지에 대한 정보는 제외한다.
다음 프로젝트에서 준비물로 필요한 사항은 다음과 같다.
1. 스프링 프로젝트가 올라가 있는 깃허브
2.적절한 용량의 EC2 인스턴스 생성
3.스프링 빌드를 위한 스프링 내부 설정 파일
4.스프링 빌드 파일을 사용하기 위한 Docker 스크립트 파일
5.Docker-compose 사용을 위한 스크립트 파일
6.aws 인스턴스의 보안 규칙
본 게시글에서는 위의 프로젝트에 대한 사전에 필요한 준비사항을 한 단계씩 설명하면서 배포를 진행할 것이다.
완성 프로젝트 구조는 다음과 같다.
1. 스프링 프로젝트가 올라가 있는 깃허브
작성한 스프링 프로젝트를 깃허브에 푸쉬하였으면 1차적인 준비는 완료되었다.
2.적절한 용량의 EC2 인스턴스 생성
인스턴스 생성에 관한 자세한 게시글은 다른 글을 참조하길 바란다. 보안 인바운드 규칙은 docker-compose.yml 파일을 작성하며 설명한다.
생성한 인스턴스에서 git 명령어를 통해 프로젝트를 받아온 화면이다.
3.스프링 빌드를 위한 스프링 내부 설정 파일
<build.gradle>
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.7'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'webcloud'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// db
implementation 'mysql:mysql-connector-java'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// validation
implementation 'org.springframework.boot:spring-boot-starter-validation'
// JWT
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
// QueryDSL
implementation 'com.querydsl:querydsl-core'
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
bootJar{
archivesBaseName = 'bestrestaurant'
archiveFileName = 'bestrestaurant.jar'
archiveVersion = "0.0.0"
}
tasks.named("jar") {
enabled = false
}
sourceSets{
main{
java{
srcDirs += [generated]
}
}
}
아래의 코드 조각은 Gradle 빌드 스크립트의 일부분이다.
bootJar{
archivesBaseName = 'bestrestaurant'
archiveFileName = 'bestrestaurant.jar'
archiveVersion = "0.0.0"
}
tasks.named("jar") {
enabled = false
}
이 스크립트는 Spring Boot 기반의 Java 프로젝트를 빌드하는 데 사용되며, 코드를 하나씩 살펴보면 다음과 같다
1. bootJar 태스크는 Spring Boot 애플리케이션을 실행 가능한 JAR 파일로 패키징하는 작업을 수행한다.
- `archivesBaseName = 'bestrestaurant'`: 생성된 JAR 파일의 기본 이름을 `'bestrestaurant'`로 설정한다.
- `archiveFileName = 'bestrestaurant.jar'`: 생성된 JAR 파일의 이름을 `'bestrestaurant.jar'`로 설정한다.
- `archiveVersion = "0.0.0"`: 생성된 JAR 파일의 버전을 `'0.0.0'`으로 설정한다.
즉, Gradle에서 빌드하여 생성하는 jar 파일명을 변경하는 작업이다. 여기서 변경된 bestrestaurant.jar은 추후 Dockerfile에서 재활용될 것이다.
2. `tasks.named("jar") {...}`: `jar`
- `enabled = false`: `jar`
스프링 부트 Gradle은 2.5부터 빌드 시 jar 파일을 두개 생성한다. 별도의 설정을 하지 않았을 때 "프로젝트 이름.jar"과 "프로젝트 이름-plain.jar"이 생성될 것이다. 해당 태스크는 plain-jar을 생성하지 않도록 하는 작업이다.
자세한 설명은 다음 블로그를 참조하면 좋을 것 같다.
SpringBoot Executable jar & Plain jar (velog.io)
<application.properties> (위치는 /src/main/resources)
spring.profiles.default=dev
# DB config
spring.jpa.show_sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
# REST API Response UTF-8 setting
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=true
# Storage config
spring.servlet.multipart.location=C:/Users/user/Desktop/BestRestaurent
# Redis config
spring.cache.type=redis
spring.redis.host=redis
spring.redis.port=6379
#spring.redis.timeout=15000
# URL config
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
# MySQL config
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://mysql:3306/bestrestaurant?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=1234
아래의 코드 조각은 application.properties의 일부분이다.
# Redis config
spring.cache.type=redis
spring.redis.host=redis
spring.redis.port=6379
#spring.redis.timeout=15000
# MySQL config
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql 컨테이너 이름://mysql:3306/bestrestaurant?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=1234
spring.redis.host = redis에서 docker-compose에서 사용할 redis는 Redis의 컨테이너 이름이다.
mysql:3306에서 mysql은 docker-compose에서 사용할 MySQL 컨테이너의 이름이다.
4.스프링 빌드 파일을 사용하기 위한 Docker 스크립트 파일
ec2 인스턴스에서 받아온 스프링 프로젝트를 Docker로 배포하기 위해서는 프로젝트의 맨 위 루트에 Dockerfile을 작성해야 한다.
스프링 배포파일을 사용하는 Dockerfile, 스프링 / redis / mysql / jenkins 등을 컨테이너로 한번에 띄어 사용하기 위한 docker-compose.yml등을 작성해놓은 모습이다.
*locustfile.py와 locust.py는 정량적 성능을 측정하기 위하여 작성한 스크립트 파일이며 본 게시글에서는 설명하지 않는다.
스프링 프로젝트를 다음 명령어를 통해 빌드 시
chmod +x ./gradlew
./gradlew build -x test
"프로젝트이름.jar"이라는 스프링 어플리케이션 실행 파일은 build/libs/ 경로에 생성되기 때문에 Dockerfile은 다음과 같이 작성한다. (여기서 프로젝트 이름은 3.스프링 배포를 위한 스크립트 파일(build.gradle, application.properites)의 build.gradle 설정에 의해 본인이 변경 가능하다.)
<Dockerfile>
FROM openjdk:11-jdk
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} bestrestaurant.jar
ENTRYPOINT ["java", "-jar", "/bestrestaurant.jar"]
5.Docker-compose 사용을 위한 스크립트 파일
1~4의 환경설정을 끝마쳤다면 docker-compose를 사용하여 spring, redis, mysql을 컨테이너로 한번에 띄울 준비가 완료되었다.
*아! 참고로 본 게시글의 스프링 배포 작업은 로컬에서 스프링, redis, mysql 연동이 잘 됨을 확인한 뒤 수행하는 과정이다.
<docker-compose.yml>
version: "3"
services:
mysql:
image: mysql
container_name: mysql
environment:
MYSQL_DATABASE: bestrestaurant
MYSQL_ROOT_HOST: root
MYSQL_ROOT_PASSWORD: 1234
#MYSQL_USER: root
#MYSQL_PASSWORD : root
ports:
- "3306:3306"
redis:
container_name: redis
image: redis
ports:
- "6379:6379"
bestrestaurant:
build:
context: .
dockerfile: Dockerfile
restart: always
container_name: bestrestaurant
ports:
#- "8081:8081"
#- "8080:8080"
# - "8081:8080"
- "9000:8080"
depends_on:
- mysql
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/bestrestaurant?useSSL=false&serverTimezone=UTC&useLegetimeCode=false&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: 1234
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
ports:
- "8080:8080"
volumes:
- jenkins_home:/var/jenkins_home
network_mode: host
volumes:
jenkins_home:
Docker Compose는 여러 개의 독립적인 서비스를 정의하고 관리하기 위한 도구이며 services(서비스 섹션)는 컨테이너화된 서비스를 정의한다. 위의 Dockerfile에서 services는 각각 1.mysql, 2.redis, 3.bestrestaurant, 4.jenkins를 컨테이너로 실행한다. 이해를 위하여 각각의 코드의 설명을 다음에 작성하였다.
1.mysql
mysql:
image: mysql
container_name: mysql
environment:
MYSQL_DATABASE: bestrestaurant
MYSQL_ROOT_HOST: root
MYSQL_ROOT_PASSWORD: 1234
#MYSQL_USER: root
#MYSQL_PASSWORD : root
ports:
- "3306:3306"
MySQL 데이터베이스를 실행하기 위해 mysql 이미지(docker image)를 사용하고 있다. 컨테이너 이름은 mysql로 설정되어 있으며, 환경 변수를 통해 데이터베이스 이름, 루트 호스트, 루트 패스워드 등을 설정하고 있다. 또한 포트 매핑을 통해 호스트와 컨테이너의 포트를 연결하고 있다. 위의 코드는 application.properties의 다음 코드 구문과 이어진다.
spring.datasource.url=jdbc:mysql://mysql:3306/bestrestaurant?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
2.redis
redis:
container_name: redis
image: redis
ports:
- "6379:6379"
Redis 서비스를 정의하고 있고 redis 이미지를 사용하며, 컨테이너 이름과 포트 매핑을 설정하고 있다.(부수적인 설명은 필요 없다고 생각한다.)
3.bestrestaurant
bestrestaurant:
build:
context: .
dockerfile: Dockerfile
restart: always
container_name: bestrestaurant
ports:
#- "8081:8081"
#- "8080:8080"
# - "8081:8080"
- "9000:8080"
depends_on:
- mysql
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/bestrestaurant?useSSL=false&serverTimezone=UTC&useLegetimeCode=false&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: 1234
다음은 각 요소의 설명이다.
- `build`: `bestrestaurant` 서비스의 빌드에 관련된 설정이다.
-`context: .`은 현재 디렉토리를 빌드 컨텍스트로 사용함을 의미하며,
-`dockerfile: Dockerfile`은 Dockerfile을 사용하여 이미지를 빌드함을 의미한다.(이미 4에서 Dokcerfile은 작성했다.)
- `restart: always`: 컨테이너가 항상 재시작되도록 설정되어 있다. 예외 또는 중단이 발생해도 자동으로 재시작된다.(스프링이 mysql이나 redis와 연동이 되지 않을 경우 등)
- `container_name: bestrestaurant`: 컨테이너의 이름을 `bestrestaurant`로 지정한다.(이는 docker-compose를 실행한 뒤 배포된 컨테이너의 이름에서 확인이 가능하다.)
- `ports: - "9000:8080"`: 호스트의 9000번 포트를 컨테이너의 8080번 포트로 매핑한다.
따라서 호스트에서 `http://"AWS 인스턴스의 Public IP":9000`으로 애플리케이션에 액세스할 수 있다.
- `depends_on: - mysql - redis`: `bestrestaurant` 서비스가 실행되기 전에 `mysql`과 `redis` 서비스가 선행되어야 함을 나타낸다. 즉, `bestrestaurant` 서비스는 `mysql`과 `redis`가 실행 중인 상태여야 한다.
*나의 스프링 프로젝트는 스프링 어플리케이션을 실행시키면 쿼리가 실행되며 연동된 MySQL의 DB에 테이블들이 구축되므로 MySQL이 먼저 실행되어있지 않을 경우 오류가 발생하기 때문이다.
- `environment`: 애플리케이션에 필요한 환경 변수를 설정한다. 여기서는 데이터베이스 연결에 사용되는 환경 변수를 설정하고 있다.(Spring을 DB와 연동하는 역할이다.)
`SPRING_DATASOURCE_URL`은 MySQL 데이터베이스의 URL을 지정하며, `SPRING_DATASOURCE_USERNAME`과 `SPRING_DATASOURCE_PASSWORD`는 데이터베이스에 연결할 때 사용되는 사용자 이름과 비밀번호를 지정한다.
4.jenkins
jenkins:
image: jenkins/jenkins:lts
container_name: jenkins
ports:
- "8080:8080"
volumes:
- jenkins_home:/var/jenkins_home
network_mode: host
volumes:
jenkins_home:
본 게시글은 docker-compose를 사용한 스프링 배포에 집중할 것이기 때문에 Jenkins는 다른 게시글에 정리한다.
(이 부분은 삭제해도 괜찮을 것이다.)
6.aws 인스턴스의 보안 규칙
"5.Docker-compose 사용을 위한 스크립트 파일(docker-compose.yml)"에서
mysql, redis, bestrestaurant는 각각 다음처럼 포트포워딩이 되어있음을 확인할 수 있다.
ports:
- "3306:3306"
...
redis
ports:
- "6379:6379"
...
bestrestaurant
ports:
#- "8081:8081"
#- "8080:8080"
# - "8081:8080"
- "9000:8080"
이는 외부에서 인스턴스의 public IP:9000을 URL로 입력하면 스프링 어플리케이션으로 접속할 수 있다는 소리이다.
인바운드(들어오는) 트래픽에 대한 허용 또는 거부 규칙을 정의하여 네트워크 보안을 강화하는 aws 인스턴스의 특징으로 인해 다음의 포트포워딩은 aws의 인바운드 규칙을 설정하지 않으면 외부에서 해당 주소에 접근이 불가능하다.
따라서 다음과 같이 인스턴스의 보안 인바운드 규칙을 설정하면 컨테이너를 실행 후 외부에서 접근이 가능해지게 된다.
배포 확인
1~6의 과정을 통해 docker-compose로 spring에 mysql, redis가 연동되어 돌아가는 프로젝트가 컨테이너로 띄워짐을 다음 명령어들을 통해 확인하자
먼저 배포작업이다. docker-compose 파일의 bestrestaurant 서비스는 Dockerfile을 실행하는데 Dockerfile은 스프링 프로젝트가 배포되어 만들어진 실행파일인 jar을 찾아서 실행시키므로 먼저 빌드 명령어를 통해 스프링 프로젝트를 빌드한다.
빌드 권한부여
chmod +x ./gradlew
스프링 build
./gradlew build -x test
다음은 docker-compose 실행 명령어이다.
컨테이너 생성
docker-compose up --build -d
1~6의 준비과정은 이 명령어를 위해 작성됨을 파악할 수 있다.
마지막으로 컨테이너가 띄워져있는지 확인하는 명령어이다.
docker ps -a
bestrestaurant는 포트포워딩을 통해 외부에서 "인스턴스의 public IP:9000"으로 접속이 가능하며 이를 사용하여 REST API연동 테스트를 수행할 수 있다. 필자는 연동 테스트를 Postman을 통해 수행하였으며 다음은 request가 정확히 스프링 프로젝트에 전달되어 요청이 성공한 화면이다.
이 글에서는 jenkins에 대한 설명을 생략하였다. 왜냐하면 jenkins와 git 연동, jenkins 환경 설정등의 내용이 길기 때문에 따로 게시글을 작성하는 것이 좋을 것 같았기 때문이다. jenkins에 대한 내용은 Docker-compose를 이용한 Spring(+Redis +MySQL), Jenkins 배포(2)에서 따로 정리하였다.
'개발 > Cloud' 카테고리의 다른 글
Docker-compose를 이용한 Spring(+Redis +MySQL), Jenkins 배포(2) (0) | 2023.06.27 |
---|