개발환경
- spring boot 3.2.0
- gradle 8.5
- ec2
- Docker
- Github Actions
프로젝트의 개발서버를 구축하면서 CI/CD 과정을 진행하는 도중 문제가 발생했습니다. 사소한 실수였고, 다시 반복하지 않고자 기록을 남깁니다. 우선 개발 서버의 배포 과정은 다음과 같습니다.
- ec2에 docker 환경 구성
- ec2에서 MySQL 컨테이너 실행
- 로컬에서 spring boot 프로젝트 clean build > docker image로 만들어 docker hub에 push
- ec2에서 docker hub에 올린 이미지를 컨테이너로 만들고 실행 (docker run -d -p 8080:8080 [환경변수들] [hub계정명]/[이미지명])
이렇게 수동으로 웹 애플리케이션 서버의 이미지를 만들었을 때, 서버가 정상적으로 동작하는 것을 확인했습니다. 그 후 소스코드 수정 시마다 해당 과정을 반복해야 되는 수고를 덜기 위해 github actions를 이용한 CI/CD 구축을 진행했습니다. 비용과 개발 난이도 측면에서 가장 효율적인 Github Actions를 선택했습니다.
Github Actions CI 진행 과정
1. GitHub Repository -> Actions -> Java With Gradle Configure -> Configure 클릭
2. gradle.yml 파일 수정 (내부는 위 3~4번 과정과 동일합니다.) - 서브모듈 적용(아래 서술)
name: Java CI with Gradle
on:
push:
branches: [ "dev_BE" ] # dev_BE 브랜치에 push or pull 이벤트 발생 시 동작합니다.
pull_request:
branches: [ "dev_BE" ]
permissions: # 소스 코드에 액세스할 수 있는 권한 정의
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3 # actions/checkout@v3를 사용하여 코드를 가져오고
with:
token: ${{ secrets.ACTION_TOKEN }}
submodules: true # 서브모듈도 함께 가져오도록 설정
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Gradle Grant 권한 부여
run: chmod +x gradlew
# Spring Boot 어플리케이션 Build
- name: Setup Gradle
uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
- name: Build with Gradle Wrapper
run: ./gradlew clean build
- name: docker image build
run: docker build -t [사용자명]/[이미지명] .
# DockerHub Login
- name: docker login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Docker Hub push
- name: docker Hub push
run: docker push [사용자명]/[이미지명]
3. GitHub Actions Secret Key를 생성합니다. repository의 Settings - Security - Secrets and Variable - Actions 클릭
4. New repository secret 클릭 후 이름, 내용 작성 및 저장 > 저장 후 수정 시 해당 내용은 다시 안 보입니다!
5. 설정이 완료되면 gradle.yml에서 사용이 가능해집니다.
6. 이후 gradle.yml에서 설정한 이벤트를 발생시켜 Actions Flow를 실행합니다.
이렇게 CI 진행 과정과 성공여부를 Github Actions에서 확인할 수 있습니다.
7. 그 후 jar 빌드 및 해당 내용을 도커 이미지로 빌드 후 docker hub에 push 된 것을 확인합니다.
8. 최신상태로 이미지가 생성되었다면, ec2에서 해당 이미지를 컨테이너화 합니다.
9. docker 컨테이너 정상 동작 및 EC2 퍼블릭 IP : 8080 접속을 확인합니다.
전체적인 과정은 위와 같습니다. 그리고 중요한 점!! docker hub에 push 하기 전, 반드시 application.yml 내용을 반영할 수 있도록 설정해야 합니다.
서브모듈 적용
일반적으로 applicaiton.yml에는 민감한 내용이 담기고, 이런 내용을 은닉화 하기 위해 .gitignore에 application.yml을 추적하지 않도록 설정한 뒤 작업을 진행합니다. 하지만 지금처럼 github remote 레포지토리에 있는 내용을 통해 CI작업을 진행하는 경우에 applicaiton.yml은 어떻게 관리해야 할까요? 구글링 한 결과 Jasypt를 통한 설정파일 암호화 방법, Github Secret에 application.yml의 내용을 저장하는 방법, 그리고 서브모듈을 사용하는 방법을 찾을 수 있었습니다. 저는 세 방법 중 서브모듈을 적용해 모든 설정을 마무리했습니다.(아래 블로그 글을 참고했습니다.)
- 서브모듈 레포지토리명: submodule-config
- 슈퍼프로젝트에 저장된 application.yml의 경로: src/main/resources/submodule-config/application.yml
🚨 이때, 서브모듈로 설정한 private repository의 application.yml 내용은 하드 코딩 해주셔야 합니다.
🚨 그리고 서브모듈의 application.yml 파일이 올라가는 경로를 .gitignore에 반드시!!! 추가해주셔야 합니다.
https://resilient-923.tistory.com/422
[Spring/TIL] Github Actions 배포환경에서 Github Submodule로 application.yml 관리하기.
현재 프로젝트의 핵심 기능 구현이 어느 정도 마무리가 되면서 이제 배포환경을 고려해야 했습니다. 가장 흔하게 사용하는 Jenkins 말고 이번에는 Github Actions를 사용해 보기로 결정했는데요. 여러
resilient-923.tistory.com
문제상황
하지만 문제가 발생했습니다. 기존에 수동으로 spring boot를 컨테이너화 한 후 실행했을 때는 모든 기능이 문제없이 동작했는데, 뜬금없이 아래와 같이 종속성문제, 그리고 placeholder 문제가 발생했습니다...🤦♂️
예상되는 원인은 다음과 같았습니다.
- 종속성 주입 실패
- application.yml 설정 누락
- gradle.yml 설정 오류
- placehodler 문제
- 소스코드 내부의 문제
- application.yml 설정 누락
- Dockerfile 설정 오류
- Github Actions secret key 오류
- 서브모듈 경로 문제
문제 원인
수동으로 컨테이너화 해서 실행했을 때는 잘 동작했기 때문에 정말 난감했는데요, 예상 가능한 문제들을 하나하나 소거해 나가면서 겨우 원인을 찾을 수 있었습니다. 결론적으로 원인은 서브모듈에 저장된 application.yml의 내용을 읽지 못해서 발생했습니다. 그럼 왜 읽지 못한 것일까요?
placeholder 문제가 발생한 지점은 jwt의 시크릿키 정보를 @Value 어노테이션을 통해 불러오는 지점이었습니다. @Value 어노테이션은 표현식 기반으로 값을 주입해 주는 어노테이션인데요, 저는 아래와 같이 프로퍼티 치환자 방식으로 application.yml 저장된 시크릿 키를 주입했습니다.
하지만 서브모듈을 슈퍼 프로젝트( 상위 저장소 - 현재 프로젝트)와 연결했음에도 "${jwt.key}"는 여전히 기존 로컬에서 사용하던 application.yml을 가리키고 있었습니다. 그리고 해당 yml을 주석 처리하면 아무것도 가리키는 게 없다고 출력됐습니다.
문제의 정확한 원인은 공식문서에서 찾을 수 있었습니다. @Value 어노테이션이 application.yml을 인식하지 못하는 것은 스프링 부트 정책에 대한 이해도 부족으로 인한 잘못된 경로 설정 때문이었습니다.
제가 지정한 서브모듈의 이름과 경로는 해당 조건들을 하나도 충족하지 못했습니다. 그래서 spring boot의 정책상 application.yml를 읽을 수 없었기 때문에 해당 오류들이 발생했던 것이었습니다.
해결
저는 우선 슈퍼프로젝트에 서브모듈과 관련된 디렉터리들을 삭제하고, 깃헙에 생성한 서브모듈 레포지토리를 삭제한 뒤, 서브모듈 생성 과정을 다시 반복했습니다. 그렇게 다시 생성된 서브모듈명과 경로는 다음과 같습니다.
- 서브모듈명: config
- 슈퍼 프로젝트에 위치한 서브모듈의 application.yml 위치: src/main/resources/config/application.yml
classpath를 /config 변경하고 문제를 해결할 수 있었습니다.
참고
[spring] yml파일 인식 못할 경우
본 글은 정확하지 않을 수 있습니다. 참고용으로만 봐주시면 감사하겠습니다. 현재 폴더 구조는 대략 이렇다. backend-security폴더는 서브모듈이며 배포환경에 맞는 환경파일들을 모아놓았다. # appl
jjuunn.tistory.com
Core Features
In the absence of an Executor bean in the context, Spring Boot auto-configures an AsyncTaskExecutor. When virtual threads are enabled (using Java 21+ and spring.threads.virtual.enabled set to true) this will be a SimpleAsyncTaskExecutor that uses virtual t
docs.spring.io
21. Externalized Configuration
21. Externalized Configuration Spring Boot likes you to externalize your configuration so you can work with the same application code in different environments. You can use properties files, YAML files, environment variables and command-line arguments to
docs.spring.io
https://www.devkuma.com/docs/git/git-submodule/
Git 서브모듈(submodule) 추가 및 삭제 방법
프로젝트는 하다 보면 외부 모듈이 필요한 경우가 있다. 그럴 때 사용하는 서브 모듈 사용법에 대해서 알아 보겠다.
www.devkuma.com
'Spring' 카테고리의 다른 글
Redis를 이용한 QueryDSL 페이징 조회 성능 향상 (0) | 2024.05.26 |
---|---|
[트러블슈팅] @ColumnDefault() 사용 시 DB에 null로 저장되는 이유와 해결방법 (0) | 2024.02.08 |