반응형
- 요런 구조로 무중단 배포를 위한 CI/CD 파이프라인을 구축하였다.
과정을 간단하게 살펴보자
- 우분투 서버에 도커, 도커 컴포즈 설치
- 우분투 서버에 젠킨스 설치
- 우분투 서버 SSH 키 등록
- SSH 키를 생성하여 공개키는 authorized_keys 파일에 등록하고, 비밀키는 젠킨스 자격증명에 추가해야 한다.
- 깃허브 웹훅 설정
- 도커파일, 도커 컴포즈 파일, 파이프라인 스크립트 작성
ssh 사용자명@서버_아이피
- 참고로 위 명령어를 통해 비밀번호 입력 없이 ssh에 접속이 된다면, 정상적으로 키가 등록된 것이다.
나를 힘들게 한 이슈들
1. 오라클 클라우드 프리티어 인스턴스의 부족한 메모리 용량
- 젠킨스를 빌드하는데 계속해서 서버가 멈춰서 OCI 콘솔에 접속하여 서버를 재시작하는 과정을 계속 거쳤어야 했다.
- 처음에는 파이프라인 스크립트가 잘못 됐나 싶어서 계속 고쳐보는데 서버가 멈추는 게 이상했다.
- 알고 보니 우분투 서버의 메모리가 대략 1GB 정도인데, 오라클 DB와 젠킨스 모두 가동되고 있어서 젠킨스에서 작업을 할 경우 메모리가 오버되어 서버가 멈추는 것이었다.
- 이제 도커 컨테이너 환경에 오라클을 포함시킬 것이라 호스트 서버에는 오라클이 필요 없어서 삭제하였다.
- 사용 메모리가 많이 줄었지만 그래도 부족해서 챗 지피티에게 물어보니 스왑이라는 것이 있더라.
# 1GB 스왑 파일 생성
sudo fallocate -l 1G /swapfile
# 권한 설정
sudo chmod 600 /swapfile
# 스왑 파일 초기화
sudo mkswap /swapfile
# 스왑 활성화
sudo swapon /swapfile
- 위 코드를 통해 스왑을 활성화하면 물리적 메모리가 부족할 때 하드 디스크의 일부를 가상 메모리로 사용할 수 있다.
- 위 이미지는 메모리를 확인하는 것이다.
- 스왑을 활성화하면 메모리를 확인할 때 스왑 영역이 생긴다.
혹시나 또 서버가 멈출까 봐 빌드할 때마다 1초 간격으로 계속 붙어서 모니터링했다.
application.yml 주입
- 스프링 환경변수 파일이다.
- 해당 파일은 보안 문제 때문에 깃에 올릴 수도 없다.
- 그런데 도커파일과 도커컴포즈 파일도 깃에 올려야 돼서 거기에다 환경변수를 설정할 수도 없는 노릇이다.
- 본인은 application.yml 파일을 우분투 서버에 넣어 버리고 깃허브 프로젝트 클론한 경로에 복붙하는 코드를 젠킨스 파이프라인 스크립트에 추가하였다.
데이터베이스 연결
- 오라클 11g XE만 그런 건지는 모르겠는데 dialect를 명시하지 않으면 연결이 안 된다.
- 그런데 막상 설정하면 스프링에선 필요 없다고 로그가 남는다. 🤐
- 스프링 3 이후로는 driver랑 dialect를 명시할 필요가 없는 것은 맞는데, 환경에 따라 스프링이 해당 정보들을 자동으로 인식하지 못하는 경우가 있는 것 같다.
- 이런 경우에는 application.yml 파일에 명시를 해서 스프링이 인식할 수 있도록 해주어야 한다는 것을 알게 되었다.
- 그런데 본인은 고집부리고 도커파일이나 네트워크 등 다른 부분에서 원인을 찾다가 시간 꽤나 썼다.
docker network inspect 도커컴포즈_네트워크명
- 어찌어찌 톰캣 타임존 설정까지 처리하니 스프링 애플리케이션에서 오라클 컨테이너 서비스 명을 인식을 못했다.
- 도커 컨테이너들이 서로 통신하려면 같은 네트워크에 있어야 한다.
- 이 부분은 알고 있어서 그렇게 설정을 했는데도 상기 명령어로 상태를 확인해 보면 스프링 애플리케이션이 분명 네트워크에 붙는데 컨테이너가 계속 내려갔다.
- 내려가는 원인을 로그로 확인하고 싶은데, Unable to access jarfile 에러 메시지만 계속 출력되었다.
- 이건 볼륨 설정이 잘못되어서 그렇다. 볼륨 설정은 호스트 경로:컨테이너 속 경로로 설정을 한다.
- 내 경우는 호스트에 있는 스프링 애플리케이션 디렉터리와 컨테이너를 볼륨 설정해서 컨테이너가 계속 내려갔다.
- 스프링 애플리케이션 프로젝트 경로는 빌드되어야 하는 디렉터리인데, 컨테이너 속 디렉터리로 덮어씌워진 것이다.
- 그런데 본인은 볼륨 문제를 인지 못하고 주야장천 네트워크 연결 문제만 파고 또 파고들었다.
- 지금도 어이가 없는 것이 볼륨 문제 때문에 로그를 확인할 수 없으니까 본인은 다음 명령어로 스프링 애플리케이션 로그를 확인했다.
docker run -it 종료된_도커컴포즈_스프링_서비스명
- 이 명령어에는 치명적인 문제가 있다.
- 위에서 언급했듯 도커 컨테이너들이 서로 통신하려면 같은 네트워크 상에 있어야 한다.
- 그런데 본인은 도커 컴포즈에 네트워크 설정을 해놓고 위 명령어에 네트워크를 명시하지 않은 것이다.
docker run -it --network 데이터베이스가_속한_네트워크명 종료된_도커컴포즈_스프링_서비스명
- 제대로 디버깅하려면 위 명령어를 입력해야 한다.
- 오라클과 같은 네트워크 상에서 스프링 애플리케이션을 실행하지 않으니 당연히 데이터베이스는 붙지 않았고, 스프링 애플리케이션에서는 데이터베이스 서비스명을 인식하지 못한다는 에러 로그만 뽑아낼 뿐이었다.
- 포트 충돌, 방화벽, 스프링 컨테이너 실행 전 오라클 초기화 여부 등 확인 안 해본 게 없는데 문제가 발견이 안돼서 미치는 줄 알았다.
- 네트워크 옵션을 포함하여 실행하니 스프링 애플리케이션이 정상적으로 작동함을 알 수 있었고, 이는 application.yml 파일과 네트워크 및 데이터베이스 연결에는 문제가 없음을 의미했다.
- 그럼에도 스프링 애플리케이션 컨테이너가 계속 내려가는 것은 도커 컴포즈 파일이나 도커파일에 문제가 있다는 것이었고, 해당 파일들을 검토하며 볼륨에 문제가 있음을 알게 되어 문제를 해결했다.
- 결론적으로 로그를 추적하자니 원인은 여러 가지가 섞여있고, 이런 부분이 추적하기 어려운 점이 있어서 너무 힘들었다.
그래서 뭘 한 거죠?
젠킨스 파이프라인 스크립트
pipeline {
agent any
stages {
stage('Checkout') {
steps {
git branch: '브랜치명', url: '깃허브_프로젝트_주소'
}
}
stage('Prepare') {
steps {
sshagent(['젠킨스_자격증명_키']) {
sh 'mkdir -p ${WORKSPACE}/프로젝트/src/main/resources'
sh 'scp -o StrictHostKeyChecking=no 사용자명@서버도메인:/home/ubuntu/application.yml ${WORKSPACE}/프로젝트/src/main/resources/application.yml'
}
}
}
stage('Build and Deploy with Docker Compose') {
steps {
sh 'docker-compose -f ${WORKSPACE}/docker-compose.yml up --build -d'
}
}
}
post {
failure {
echo 'Deployment failed.'
}
}
}
- 위 스크립트는 다음 과정을 따른다.
- 깃허브 웹훅을 통해 푸시를 감지해서 소스 코드를 ${WORKSPACE}로 가져온다.
- 서버에서 직접 소스 코드의 resources 경로로 주입해 준다. 깃허브에는 application.yml 파일이 있으면 안 된다.
- 도커 컴포즈 파일을 빌드한다. 이 시점에 네트워크가 생성되고, 그 안에서 도커 컨테이너들이 생성되고 실행된다.
도커 컴포즈 파일
version: '3.8'
services:
oracle-db:
container_name: oracle-db
image: gvenzl/oracle-xe:11-slim
ports:
- "1521:1521"
environment:
ORACLE_PASSWORD: 패스워드
volumes:
- oracle-data:/opt/oracle/oradata
networks:
- app-network
spring-app:
container_name: spring-app
image: spring-app
build: ./spring-app
ports:
- "18080:18080"
volumes:
- ./logs:/usr/local/app/logs
depends_on:
- oracle-db
restart: on-failure
networks:
- app-network
react-app:
container_name: react-app
build: ./react-app
image: react-app
ports:
- "3000:80"
volumes:
- ./react-app:/app
networks:
- app-network
networks:
app-network:
name: app-network
driver: bridge
volumes:
oracle-data:
driver: local
개인적으로 도커컴포즈나 도커파일은 서비스마다 구조가 다를 것이기 때문에 누군가의 파일을 보고 공부하는 것보다는 챗 지피티 붙잡고 따라 하고 이해하는 게 빠르다고 생각한다. 근데 이것 때문에 챗 지피티랑 많이 싸웠다. 😂
따라 하되 결국 본인이 어느 시점에서는 문제점을 이해해야 한다.
리액트 애플리케이션 도커파일
FROM node:18-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
- 리액트 도커파일에서는 아직까지는 별 문제가 없었다.
- 이제 리액트로 화면 개발해야 하는데, 그때 뭔가 문제점이 터져 나오지 않을까 싶다.
- nginx는 기본적으로 80 포트에서 제공되므로 EXPOSE 80 설정을 유지해야 한다고 한다.
- 이에 도커컴포즈 파일의 리액트 포트 설정도 3000:80으로 수정하였다.
스프링 애플리케이션 도커파일
# 빌드 이미지
FROM gradle:8.8-jdk17 as build
WORKDIR /app
# JAR 파일 생성
COPY . .
RUN gradle clean build --no-daemon
# Base image
FROM openjdk:17-jdk-slim
# 작업 디렉터리 설정
WORKDIR /usr/local/app
# JAR 파일 복사
COPY --from=build /app/build/libs/프로젝트_jar_파일명 /usr/local/app/프로젝트_jar_파일명.jar
# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "/usr/local/app/프로젝트_jar_파일명.jar"]
# 기본 포트 설정
EXPOSE 18080
- 자바 파일 빌드할 때는 데몬 사용 안 하는 게 좋다고 한다.
- 컨테이너는 생명 주기가 짧다.
- 생명주기가 짧다는 것은 컨테이너는 사용하다 보면 망가지는 게 당연한 자원이고, 그때마다 컨테이너 자원을 해제하고 재생성하는 것이다.
- 그런데 데몬이 설정되어 있다면 해제되지 않는 자원이 생길 수가 있다고 한다.
application.yml 파일
spring:
datasource:
url: jdbc:oracle:thin:@oracle-db:1521:xe?oracle.jdbc.timezoneAsRegion=false
username: 사용자명
password: 비밀번호
driver-class-name: oracle.jdbc.OracleDriver
jpa:
database-platform: org.hibernate.dialect.OracleDialect
hibernate:
ddl-auto: update
logging:
level:
org.hibernate.SQL: debug
org.hibernate.orm.jdbc.bind: trace
kr.co.프로젝트명: debug
file:
name: /usr/local/app/logs/app.log
jwt:
secret-key: 문자열
server:
port: 18080
- 배포할 때마다 항상 이것 때문에 골치가 아팠다.
- 모든 파일들 작성을 모두 마치면 깃허브에 소스 코드를 푸시하거나, 젠킨스 콘솔에서 [지금 빌드] 버튼을 누르면 된다.
- 진짜로 파이프라인 실행이 성공하면 위 이미지처럼 도커 컨테이너가 모두 올라와 있어야 한다.
- 포스트맨 요청도 정상적으로 들어간 것을 확인할 수 있다.
마치며
젠킨스 파이프라인 스크립트를 101번이나 삽질해서 성공했다.
위 이미지에서 성공이 여러 개 찍혀있어서 혼동할 수 있지만 젠킨스 파이프라인 빌드가 성공하더라도 도커 컨테이너가 모두 정상 실행되지 않는다면 그건 성공이라고 볼 수 없다.
사실 중간에 OCI 서버 비밀키 잃어버려서 인스턴스를 재생성했는데, 그것까지 하면 150번 넘게 빌드한 것 같다.
혹시나 이렇게 삽질하실 분들은 테스트 코드는 제외하고 빌드할 것을 추천드린다. 테스트 코드 빌드가 엄청 오래 걸린다. 실제 운영 코드가 잘 빌드되면 그때 테스트 코드를 붙이시면 되지 않을까 싶다.
몇 달 전에 도커 처음 써보면서 에러 로그보다는 실제 원인을 파악해야 한다는 교훈을 얻었는데, 이번에 그 생각이 더 격하게 와닿았다..
그래도 이제 어떤 프로젝트를 하든 단순 깃허브 푸시를 통해 무중단 배포가 가능하다.
힘들었지만 문제를 해결하는 경험과 앞으로 써먹을 수 있는 편의성이 남았다.
날씨가 갑자기 추워져서 독감도 걸리고, 겨울이라 그런가 괜히 몸도 무거워서 특히 힘들었다. 이제 배포는 맛볼 거 다 봤고 앞으로는 화면 개발하고, 테스트하면서 서버 개발도 더 이어갈 것이다. 그리고 2월부터는 리트코드좀 풀어야겠다.
요즘 몸도 마음도 너무 힘들어서 좀 쉬어 가야 할 것 같은데 내일이 벌써 월요일이네.. 😥
이미지 출처
Level Design: Out of Bounds
Level Design: Out of Bounds It suddenly hit me the other day what it is that I really enjoy about going outside. I am of course talking about Doom. As you explore the dingey, maze-like military complex of Doom's first episode, you're occasionally blessed w
danjb.com
'개발' 카테고리의 다른 글
이미저를 통해 라즈베리파이5에 우분투를 설치해보자 (2) | 2025.03.14 |
---|---|
라즈베리파이5 언박싱 및 케이스 조립 (1) | 2025.03.14 |
우분투 서버에 프로젝트를 배포해보자 (4) | 2025.01.04 |
아직도 젠킨스를 안 써본 사람이 있다고? #설치 #빌드 (4) | 2025.01.01 |
오라클 클라우드에서 무료로 제공하는 인스턴스를 사용해보자 (3) | 2024.12.22 |