SEMENTO 프로젝트에선 Jenkins 기반의 CI/CD 로직을 구현했었다. 그런데 SSAFY 수료 이후, 우수작품 전시에 이 프로젝트를 올리고자 서버를 이전해달라는 요청이 왔다. 그래서 우리팀은 새로운 서버에 Jenkins 및 관련 스크립트, 그리고 Docker와 NginX설정 등을 다시 만들어야 했었다.
이 과정에서 반복작업에 불편함을 느꼈던 나는, 서버 이전을 고려한 아키텍처를 설계할 수는 없을까? 라는 고민을 하게 되었다. 결과적으로, 클라우드 기반 CI/CD 파이프라인 구축으로 서버 이관시의 비효율을 해결하고, 자동화 할 수 있었다.
기존 시스템의 비효율
본격적인 설명에 들어가기 전에, 기존 시스템 아키텍처에 대해 설명해보고자 한다.
docker cp /home/.../config.properties jenkins-1:/.../config.properties
server {
listen 80;
server_name //도메인명
환경변수파일을 Ec2에서 Jenkins 컨테이너로 이동시키는 스크립트와, NginX설정파일 내에 도메인명이 적혀있는 등 매번 같은 작업을 수행해줘야하는 비효율을 찾을 수 있었다. 정리하자면 다음과 같다.
- 설정 파일의 하드코딩으로 인해 배포 도메인 변경시 관련 코드를 전부 찾아 업데이트 필요.
- nginx.conf, docker-compose.yml 등 설정파일을 서버에 직접 작성했었기 때문에 이관시에도 수동으로 옮기는 작업이 필요.
- 환경변수파일은 보안상 커밋에 포함되지 않으므로 변경시마다 매번 수정해줘야 하는데, 빌드는 자동화 되었음에도 이 파일만 직접 서버에 추가하는것이 일관적이지 않다고 느껴짐. 또한, 이관시 한글자라도 빠트리면 에러가 날 수 있다는것이 비효율적임.
- Jenkins와같은 On-premises 시스템의 경우 파이프라인을 이관하려면 따로 백업을 수행하거나, 처음부터 설정을 다시해야함.
클라우드 기반 자동화 도구
결국 서버의 Jenkins는 배포서버 내부에서 돌아가고 있어, 서버 이관시에 함께 옮겨줘야 하는것이 문제였다. 그렇다면 배포용 서버를 따로 두면 해결할 수 있으리라 생각했고, 떠오른 방법은 두가지가 있었다. 서버를 하나 더 둔뒤 CI/CD용으로 사용하는것과 애초에 CI/CD 자체가 클라우드 기반으로 동작하도록 제공하는 툴(GithubActions 등)을 사용 하는것이었다.
나는 이중에서 후자 방식을 선택했다. 비용적으로도 부담이 없고, 기존에도 Github를 통해 협업하던 중이었으므로 좋은 대안인것 같았다.
해결방안(1) 기존시스템 비효율 개선 : 새로운 자동화 도구 도입
문제를 개선하기 위해 다음 두가지를 고려했다.
- 환경변수를 따로 관리할 수 있을것(매번 환경변수를 수동으로 옮기지 않기 위함)
- 쉘 스크립트 안에서 .env와 설정파일을 동적으로 생성하여 사용할 수 있을것.
위와같은 부분을 해결하기 위해서는, GithubActions의 Secrets를 활용하면 된다. Secrets를 통해 민감한 환경변수를 소스코드와 분리하여 저장할 수 있으며, 워크 플로우 내에서 쉘 스크립트 실행이 가능하므로 .env, 설정파일 등을 동적으로 설정 할 수 있기 때문!
해결방안(2) 분리된 CI/CD 환경의 통합 : Docker Hub를 활용한 배포 프로세스 최적화
GithubActions의 도입과정에서, Jenkins와는 다른 특징때문에 고민하게 된 부분이 있었다.
Jenkins의 경우 배포서버 내부에서 실행되므로 바로 Deploy할 수 있었으나 GithubActions의 경우 배포서버가 아닌 외부서버이므로, 배포서버에 접근하여 배포할 파이프라인이 필요했다.
이를 위해 총 세가지 방식을 구상하였으며, 장/단점을 고려한 결과 DockerHub를 사용하는 세번째 방법을 선택하게 되었다.
핵심 구현 로직(1) template 파일을 만들어 환경변수를 동적으로 생성
GithubActions 실행 컨텍스트 안에선 Secrets에 접근이 가능하다는 성질을 이용해 환경변수를 동적으로 생성한다.
이를 위해 .env, nginx.conf, application.properties의 템플릿을 지정했다.
server {
listen 443 ssl;
http2 on;
server_name ${DOMAIN_NAME};
ssl_certificate ${SSL_CERTIFICATE};
ssl_certificate_key ${SSL_CERTIFICATE_KEY};
#nginx.config 셋팅
- name: Create nginx.conf from template
run: |
envsubst '${DOMAIN_NAME} ${SSL_CERTIFICATE} ${SSL_CERTIFICATE_KEY}' < ./configuration/nginx.conf.template > ./configuration/nginx.conf
env:
DOMAIN_NAME: ${{ secrets.DOMAIN_NAME }}
SSL_CERTIFICATE: ${{ secrets.SSL_CERTIFICATE }}
SSL_CERTIFICATE_KEY: ${{ secrets.SSL_CERTIFICATE_KEY }}
이처럼 배포 스크립트에서 envsubst를 통해 변수를 값으로 대체하여 nginx.conf라는 이름으로 저장하도록 구현하였습니다.(NginX 파일은 환경변수를 읽을수 없기 때문)
핵심 구현 로직(2) 도커허브를 중추로 한 백엔드 배포
백엔드를 빌드하여 도커허브의 private 레포지토리에 푸쉬하며, 해당 이미지를 통해 docker-compose가 컨테이너를 셋팅하도록 한다.
#도커허브 로그인
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
#도커이미지 빌드&푸쉬
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
push : true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/bbegok:latest
services:
app:
image: ${DOCKERHUB_USERNAME}/bbegok:latest
platform: linux/arm64
ports:
- "8080:8080"
depends_on:
- db
environment:
SPRING_DATASOURCE_URL: ${MYSQL_URL}
SPRING_DATASOURCE_USERNAME: ${MYSQL_USER}
SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD}
결과
- 모든 설정값과 필수 파일들을 GitHub Actions으로 관리함으로써, 서버 이전 시 Secrets에 저장된 HOST와 DOMAIN 정보만 업데이트하면 간편하게 시스템을 이전할 수 있게 되었다.
- 파일의 수동 이관 과정이 사라져 일관적인 CI/CD가 가능해졌다.
- DockerHub의 private 레포지토리를 중간 저장소로 활용해 GitHub Actions와 배포 서버 간 Docker Image 전송 체계를 구축함으로써, 기존 Jenkins에서 직접 빌드하던 방식에 비해 배포 서버의 네트워크 부하를 크게 절감했다.
처음 목표했던 ‘서버 이관시 효율화’ 라는 목표를 달성하는 과정에서 다양한 인프라 구조가 있음을 깨달았다. 배포 프로세스 최적화를 위해 스스로 고안한 방법만 해도 세가지인데, 실무에선 훨씬 많은 고려할 점이 있으므로 굉장히 다양한 의견이 나올것이다. 그중에 서버가 1개인 상황에서 문제점을 개선할 효율적 프로세스를 고민해본것은 이후 대규모 시스템을 이해하는데 큰 자산이 되어줄 것이다.
또한 초반 아키텍처 설계의 중요성을 깨달았다. 기존 프로젝트에선 기획과 인프라설계가 동시에 진행되는 경우가 많았고, 기획이 끝나기도 전에 기본 인프라를 구축 한 뒤 이후 덧붙이는 형식으로 진행했었다. 그러나 초반부터 기획을 잘 해놓는다면, 환경변수와 같은 공통적으로 사용하는 데이터를 초반에 정의하고, 다양한 컨테이너에서 공유함으로써 보다 유지보수가 편리한 시스템이 될 것이다. 따라서 개발 초반 아키텍처 설계의 중요성을 체감하고, 백엔드 개발자로서 성장하는데 인프라적 지식의 중요성에 대해 고찰하는 계기가 되었다! 😀
'프로젝트 > 빼곡' 카테고리의 다른 글
Spring 명시적 Null값으로 부분 업데이트(PATCH) 구현하기 (3) | 2025.01.02 |
---|---|
여러기기에서 로그인, 다중 로그인 Spring에서 구현하기 (4) | 2024.12.20 |
다른 도메인 환경에서 쿠키 셋팅하기(Feat.samesite Cookie) (4) | 2024.12.12 |
Redis로 최근검색어 구현하기 (1) | 2024.11.25 |
라즈베리파이를 이용한 홈서버 구축 (1) | 2024.11.21 |