로컬에서 운영으로 넘어갈 때 달라지는 것
지금까지 만든 게시판 API는 내 컴퓨터에서 실행하고, 테스트하고, H2 데이터베이스로 확인했다. 하지만 실제 서비스는 조금 다르다. 서버 주소가 다르고, 데이터베이스 계정도 다르고, 로그를 보는 방식도 다르다.
이 차이를 코드마다 if로 나누면 금방 복잡해진다. 그래서 Spring Boot는 설정을 코드와 분리한다. 설정은 애플리케이션이 실행될 때 입는 옷과 같다. 같은 코드라도 개발용 옷을 입으면 개발 환경으로, 운영용 옷을 입으면 운영 환경으로 실행된다.
이번 장의 목표는 네 가지다.
개발, 테스트, 운영 설정을 나누기
Gradle로 실행 가능한 JAR 파일 만들기
비밀번호 같은 민감한 값을 환경 변수로 관리하기
로그와 헬스 체크로 실행 상태 확인하기
AI 생성 이미지
프로파일로 환경별 설정 나누기
프로파일(Profile)은 실행 환경의 이름이다. 예를 들어 dev, test, prod처럼 나눌 수 있다.
dev: 개발 환경test: 테스트 환경prod: 운영 환경
Spring Boot에서는 application-{프로파일}.yml 파일을 만들면 된다.
src/main/resources
├─ application.yml
├─ application-dev.yml
├─ application-test.yml
└─ application-prod.yml먼저 공통 설정은 application.yml에 둔다.
spring:
application:
name: board-api
server:
port: 8080개발 환경 설정은 application-dev.yml에 둔다.
spring:
datasource:
url: jdbc:h2:file:./data/board-dev
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
logging:
level:
com.example.board: DEBUG테스트 환경은 매번 깨끗하게 시작하는 편이 좋다. 그래서 메모리 데이터베이스를 자주 사용한다.
spring:
datasource:
url: jdbc:h2:mem:board-test
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
logging:
level:
root: WARN운영 환경은 실제 데이터베이스를 사용한다고 가정하자. 여기서는 중요한 값들을 직접 쓰지 않고 환경 변수에서 읽는다.
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
logging:
level:
root: INFO
com.example.board: INFO${DB_PASSWORD}는 "DB_PASSWORD라는 이름의 환경 변수를 찾아서 넣어라"라는 뜻이다. 냉장고 문에 붙은 메모처럼, 코드 안에 직접 쓰지 않고 실행하는 곳에서 값을 제공한다.
팁: 운영 환경에서
ddl-auto: update를 쉽게 쓰면 테이블 구조가 의도치 않게 바뀔 수 있다. 초보 단계에서는 개발 환경에만update를 두고, 운영에서는validate로 "엔티티와 테이블 구조가 맞는지 확인만" 하도록 두는 편이 안전하다. 단,validate는 테이블을 새로 만들어 주지 않는다. 운영 데이터베이스의 테이블은 미리 만들어 두어야 한다.
어떤 프로파일로 실행할지 정하기
프로파일을 만들었다면 실행할 때 어떤 프로파일을 사용할지 알려 줘야 한다.
IntelliJ에서 실행할 때는 실행 설정의 Program arguments에 다음 값을 넣을 수 있다.
--spring.profiles.active=dev터미널에서는 이렇게 실행한다.
./gradlew bootRun --args='--spring.profiles.active=dev'Windows PowerShell이라면 다음처럼 실행할 수 있다.
.gradlew.bat bootRun --args="--spring.profiles.active=dev"서버에서 실행할 때는 환경 변수로 지정하는 방식도 많이 사용한다.
export SPRING_PROFILES_ACTIVE=prod
java -jar build/libs/board-api-0.0.1-SNAPSHOT.jar프로파일은 "어떤 설정 파일을 추가로 읽을지" 결정한다. prod로 실행하면 기본 application.yml을 읽고, 이어서 application-prod.yml을 함께 읽는다. 같은 설정이 있으면 프로파일 파일의 값이 우선 적용된다.
실행 가능한 JAR 파일 만들기
개발할 때는 IntelliJ의 실행 버튼이나 ./gradlew bootRun을 자주 사용한다. 하지만 서버에 올릴 때는 보통 실행 가능한 하나의 파일로 만든다. 그 파일이 JAR이다.
JAR는 자바 애플리케이션과 필요한 파일을 묶은 압축 상자라고 생각하면 된다. Spring Boot는 내장 톰캣까지 포함한 실행 가능한 JAR를 만들 수 있다. 그래서 서버에 별도로 톰캣을 설치하지 않아도 다음 명령어로 실행할 수 있다.
먼저 테스트를 실행한다.
./gradlew clean test테스트가 통과하면 JAR를 만든다.
./gradlew clean bootJarWindows에서는 다음 명령어를 사용한다.
.gradlew.bat clean bootJar성공하면 보통 다음 위치에 파일이 생긴다.
build/libs/board-api-0.0.1-SNAPSHOT.jar파일 이름은 프로젝트 이름과 버전에 따라 달라질 수 있다. 만약 위 이름이 보이지 않는다면 build/libs 폴더 안에 어떤 .jar 파일이 생겼는지 확인하자.
이제 JAR 파일을 직접 실행한다.
java -jar build/libs/board-api-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev브라우저나 HTTP Client로 기존 API를 호출해 보자.
GET http://localhost:8080/posts정상 응답이 오면 이제 프로젝트는 IntelliJ 없이도 실행 가능한 상태가 된 것이다. 이것이 배포 준비의 첫 단계다.
민감한 설정은 환경 변수로 숨기기
데이터베이스 비밀번호, 외부 API 키, 토큰 같은 값은 Git에 올리면 안 된다. 한 번 공개 저장소에 올라간 비밀번호는 이미 노출된 것으로 봐야 한다.
나쁜 예는 다음과 같다.
spring:
datasource:
password: my-real-db-password좋은 예는 환경 변수를 사용하는 것이다.
spring:
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}Linux나 macOS에서는 다음처럼 값을 지정한다.
export SPRING_PROFILES_ACTIVE=prod
export DB_URL='jdbc:postgresql://localhost:5432/board'
export DB_USERNAME='board_user'
export DB_PASSWORD='strong-password'
java -jar build/libs/board-api-0.0.1-SNAPSHOT.jarWindows PowerShell에서는 다음처럼 지정한다.
$env:SPRING_PROFILES_ACTIVE="prod"
$env:DB_URL="jdbc:postgresql://localhost:5432/board"
$env:DB_USERNAME="board_user"
$env:DB_PASSWORD="strong-password"
java -jar buildlibsboard-api-0.0.1-SNAPSHOT.jar위 예시는 PostgreSQL을 사용한다고 가정한 값이다. 지금까지 책의 실습을 H2로 진행했다면, 실제 PostgreSQL 서버에 연결하려면 build.gradle에 PostgreSQL 드라이버도 필요하다.
dependencies {
runtimeOnly 'org.postgresql:postgresql'
}MySQL을 사용한다면 URL과 드라이버가 달라진다. 중요한 점은 "운영 데이터베이스 종류에 맞는 드라이버가 프로젝트에 들어 있어야 한다"는 것이다.
환경 변수를 사용하면 같은 JAR 파일을 개발 서버, 테스트 서버, 운영 서버에서 다르게 실행할 수 있다. 파일은 같고, 실행할 때 주입되는 값만 달라진다.
흔한 오류:
Could not resolve placeholder 'DB_PASSWORD'가 보이면 Spring Boot가DB_PASSWORD값을 찾지 못했다는 뜻이다. 환경 변수 이름이 설정 파일과 정확히 같은지 확인하자. 터미널을 새로 열었다면 이전에 등록한 환경 변수가 사라졌을 수도 있다.
로그 레벨 정리하기
로그(Log)는 애플리케이션이 실행 중 남기는 기록이다. 초보자에게는 콘솔에 찍히는 긴 글처럼 보이지만, 운영에서는 문제를 찾는 가장 중요한 단서다.
로그 레벨은 기록의 자세함을 정한다.
ERROR: 실행이 실패한 심각한 문제WARN: 당장 실패는 아니지만 주의해야 할 문제INFO: 정상 실행 흐름을 알리는 기본 정보DEBUG: 개발 중 자세히 보고 싶은 정보TRACE: 매우 상세한 추적 정보
개발 환경에서는 자세한 로그가 도움이 된다.
logging:
level:
root: INFO
com.example.board: DEBUG
org.hibernate.SQL: DEBUG운영 환경에서는 너무 자세한 로그가 오히려 방해가 된다. 비밀번호나 개인정보가 실수로 찍힐 위험도 있다.
logging:
level:
root: INFO
com.example.board: INFO
org.hibernate.SQL: WARN로그는 많이 찍는 것이 목표가 아니다. 장애가 났을 때 원인을 찾을 만큼만 남기는 것이 목표다. 특히 요청 본문, 비밀번호, 토큰, 주민등록번호 같은 민감한 값이 로그에 남지 않도록 조심해야 한다.
Actuator로 상태 확인하기
서버가 실행 중인지 확인할 때 게시글 목록 API를 호출할 수도 있다. 하지만 운영에서는 상태 확인 전용 주소를 따로 두는 편이 좋다. Spring Boot는 이를 위해 Actuator를 제공한다. Actuator는 운영 중인 애플리케이션을 모니터링하고 관리할 수 있는 기능을 제공하며, HTTP 엔드포인트로 상태를 확인할 수 있다.1
먼저 build.gradle에 의존성을 추가한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
}의존성을 추가했다면 애플리케이션을 다시 빌드하거나 다시 실행해야 한다. 그리고 다음 주소로 요청한다.
GET http://localhost:8080/actuator/health정상이라면 다음과 비슷한 응답을 볼 수 있다.
{
"status": "UP"
}UP은 애플리케이션이 정상 상태라는 뜻이다. 운영 환경에서는 로드 밸런서나 모니터링 도구가 이 주소를 주기적으로 호출해 서버가 살아 있는지 확인한다. Spring Boot의 health 엔드포인트는 애플리케이션 상태 확인에 사용되며, 운영 모니터링과 알림에도 자주 쓰인다.2
운영 설정에는 노출할 Actuator 엔드포인트를 최소한으로 제한하자.
management:
endpoints:
web:
exposure:
include: "health,info"
endpoint:
health:
show-details: neverhealth는 상태 확인용이고, info는 애플리케이션 정보를 보여 줄 때 사용한다. 반대로 너무 많은 엔드포인트를 외부에 공개하면 내부 정보가 노출될 수 있다. Actuator 엔드포인트는 유용하지만 민감한 정보를 포함할 수 있으므로 공개 범위를 신중하게 정해야 한다.1
info에 간단한 정보를 넣고 싶다면 다음처럼 작성할 수 있다.
management:
info:
env:
enabled: true
info:
app:
name: board-api
version: 0.0.1확인은 다음 주소로 한다.
GET http://localhost:8080/actuator/info운영 서버가 외부 인터넷에 직접 노출된다면 Actuator 주소를 아무나 볼 수 없게 막아야 한다. 이 책에서는 Spring Security 설정까지 깊게 다루지는 않지만, 최소한 공개 범위를 health와 info 정도로 줄이고, 자세한 health 정보는 숨기는 습관을 들이자.
배포 전 체크리스트
서버에 올리기 전에는 다음을 확인하자.
./gradlew clean test가 성공하는가?./gradlew clean bootJar로 JAR 파일이 만들어지는가?java -jar ... --spring.profiles.active=prod로 실행되는가?운영 설정에 실제 비밀번호가 직접 적혀 있지 않은가?
필요한 환경 변수가 서버에 등록되어 있는가?
운영 데이터베이스에 필요한 테이블이 준비되어 있는가?
실제 데이터베이스에 맞는 JDBC 드라이버가 추가되어 있는가?
/actuator/health가UP을 응답하는가?운영 로그 레벨이 너무 자세하지 않은가?
이번 장까지 오면 게시판 API는 더 이상 "내 컴퓨터에서만 돌아가는 코드"가 아니다. 환경별 설정을 분리했고, 실행 가능한 JAR를 만들었고, 민감한 값은 환경 변수로 뺐고, 실행 상태를 확인하는 주소도 준비했다.
다음 장에서는 지금까지 배운 컨트롤러, 서비스, JPA, 검증, 예외 처리, 테스트, 설정 관리 흐름을 하나로 묶어 게시판 API를 완성한다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.