CI/CD 파이프라인 구축기

2025. 5. 7. 21:24·Project/PickLab

새로운 팀 프로젝트를 시작하게 되며 개발을 시작하기 전 기획 단계에서 CI/CD 파이프라인을 먼저 구축하기로 결정을 하였다.  이전 개인프로젝트와 팀 프로젝트를 진행하며 단순한 CI/CD 파이프라인을 구축해본 경험이 있기도 하고, 개발 시작 전에도 무언가 하고 싶다는 생각이 들어 CI/CD 파이프라인 구축을 담당해보겠다고 하였다.

 

그렇게 담당해서 workflow 파일을 만들며 지금까지 내가 해왔던 CI/CD 파이프라인은 너무 단순하게 구축했었다는 것을 알 수 있었다..

 

이전에 어떻게 작성을 했었는데?

정확히는, 내가 이전에 작성한 워크플로우의 경우는 CI는 없고 CD만 존재하는, 즉 배포만 자동화시키는 스크립트였다.

대략적인 흐름만 분석하면

Github Actions에서 Repository를 내려받음
-> Docker build를 통한 프로젝트 Docker Image 생성
-> Docker hub에 이미지 push
-> SSH 연결을 통해 VM 인스턴스 접속(AWS, GCP 등)
-> VM 인스턴스 내부에서 docker image pull 및 프로젝트실행

내가 당시에 왜 CI도 같이 포함되어 있는 파일이라고 생각을 했는지 돌이켜보면 Docker Image를 gradle의 bootBuildImage 명령어를 통해서 만들 때, 테스트에 실패하게 되면 에러가 발생하며 중간에 워크플로우가 멈추게 되었다. 따라서 CI와 CD를 모두 만족시키는 워크플로우 파일이라고 생각했다.

하지만 엄밀히 따져보면 이 과정은 Continuous Integration 보다는 Continuous Deploy에만 집중되어 있다는 것을 알게 되었다.

당시, 이 리뷰를 받았을 때 따로 연락드려 위와 같은 이유로 CI도 포함되어 있는 것 같다고 말씀드렸다.
하지만, 현재 워크플로우를 적용한다면 Pull Request 등록 시 코드의 테스트를 실행할 수 없고 안정성이 확보되지 않은 코드가 main 브랜치에 합쳐지고 난 후 배포되는 과정에서 오류를 발견한다면 이는 제대로 된 CI가 아니라고 생각한다는 관점을 말씀해주셨다.
이걸 듣고 머리를 한 대 맞은듯이 다시 정확한 개념부터 검색을 해보게 되었다.
PR 등록 → 테스트 실행(CI Workflow) → 테스트 성공 시 병합 → 병합 이후 배포(CD Workflow) 의 과정이 옳은 과정이라는 생각을 하게 되었다. 즉, 테스트 실패 시 병합 자체를 막아 안정성을 확보할 수 있어야 한다.

따라서, CI 워크플로우 파일과 CD 워크플로우 파일을 별도로 구분하여 작성하게 되었다.

 

CI 워크플로우의 경우 간단했다. Github Actions에 git repository를 내려받고, gradle test를 통해 테스트를 돌리는 과정만 실행하면 됐다.

그렇게 작성을 해가던 도중.. env 파일 설정은 어떻게 해주어야하지..? 하는 의문이 들게 되었다.

CD 과정에서는 서버 내부에 ssh 접속을 해서 WAS를 실행하기 때문에 서버 내부에서 .env 파일을 관리하는 방식을 택하게 되었는데 CI의 경우에는 Github Actions 내부에서 모든걸 진행해야 하니 서버 내부의 .env 파일을 활용할 수 없었다.

환경변수 적용

그러던 도중, 같이 작업하는 개발자분께서 리뷰로 다음과 같은 답변을 주셨다.

리뷰 그대로 현재 서버 내부에서 .env파일을 관리한다면 장점보다는 단점이 더 많다고 생각되었다.

  1. .env 파일 내용이 변경될때마다 ssh 접속을 통해 서버 내부로 돌아가서 직접 수정을 해주어야 함(외부에서 급하게 수정해야한다면??)
  2. 로드밸런서 등을 적용하게 되어 서버가 단일 서버가 아닌 여러 개가 된다면?(.env파일 변경사항을 모든 서버에 다 들어가서 해줘야함. 2~3개일때는 수동으로 하더라도 10개 이상이 된다면??)
  3.  오토 스케일링(사용해볼 일이 있을 지 모르겠지만..) 같은 기술을 활용할 때 환경변수를 자동으로 적용할 수 없게 될 것 같다. 즉, 확장성을 고려하지 못한 방법이다.

따라서, 리뷰 답변에서 말씀해주신대로 별도의 환경변수를 관리하는 도구를 도입하거나 별도의 민감정도 repository를 설정하는 것이 더 타당하다고 생각되었다.

조금 조사를 해 보았을 때 도플러(Doppler)와 같은 도구가 존재하는 것 같지만 그 정도로 좋은 기능까지 필요없고 또한 비용을 나오게 하고 싶지 않아서 Private Repository를 이용하기로 했다.

별도의 Private Repository를 설정하고, Repo에 접근할 수 있는 Token을 생성하여 repository secret에 적용하였다.

또한, 이 방법은 장점은 환경변수 뿐만 아니라 서버 설정에 필요한 모든 파일(ex. 배포 스크립트 등)을 한 Repository에서 보다 용이하게 관리할 수 있다는 점이었다.

 

이 과정을 적용하며 흐름이 조금 변경되었다.

기존 CD에서 서버 내부에 있는 .env 파일을 그대로 사용하다보니 고려하지 못했지만 외부에서 .env파일을 가져오다 보니 권한 등의 부분을 모두 파악해서 VM 인스턴스 내부로 복사를 해오고, 복사해온 파일 또한 ssh로 접속한 계정에서 read할 수 있는 권한을 주는 등 생각보다 여러 가지 설정이 필요하다는 것을 알게 되었다.

- name: Clone private repository
    uses: actions/checkout@v4
    with:
      repository: ${{ secrets.PRIVATE_REPO }}
      token: ${{ secrets.PRIVATE_REPO_TOKEN }}
      path: .configs

  - name: Copy env file and deploy script
    run: |
      cp .configs/${{ secrets.PRIVATE_ENV_FILE }} .env
      cp .configs/${{ secrets.PRIVATE_DEPLOY_FILE }} script.sh
  - name: Google Auth
    id: "auth"
    uses: "google-github-actions/auth@v1"
    with:
      credentials_json: ${{ secrets.GCE_SA_KEY }}

  - name: set up Cloud SDK
    uses: "google-github-actions/setup-gcloud@v1"
    with:
      project_id: ${{ secrets.GCE_PROJECT_ID }}

  - name: Copy .env and deploy script to GCP VM
    run: |
      gcloud compute scp .env ${{ secrets.GCE_USERNAME }}@${{ secrets.GCE_INSTANCE_NAME }}:${{ secrets.ENV_FILE }} \
        --zone=${{ secrets.GCE_INSTANCE_ZONE }} --quiet
      gcloud compute scp script.sh ${{ secrets.GCE_USERNAME }}@${{ secrets.GCE_INSTANCE_NAME }}:${{ secrets.DEPLOY_FILE }} \
        --zone=${{ secrets.GCE_INSTANCE_ZONE }} --quiet

대략 위와 같은 과정을 통해서 GCP VM 인스턴스 내부로 환경변수 파일과 배포 스크립트를 가져와 실행하게 되었다.

CI에서 또한 .env파일을 가져와 Github Actions 세션 내부에서 환경변수를 설정해 테스트 환경에서도 환경변수를 적용할 수 있게 하였다.

 

끝난 줄 알았는데..

그렇게 끝난 것이라고 생각하며 뿌듯해하고 있었는데 

와 같은 2개의 리뷰를 받으며 다시 고민에 빠지기 시작했다.

  1. 서브모듈이라는 키워드는 처음 들어보는 키워드기 때문에 어떤 것인지 알아보고 적용 가능하면 적용해보자는 생각을 했다.
  2. 추후 비즈니스 로직이 추가되면 실제 DB구조에서 데이터를 가져다 써야 정확한 테스트가 되지 않을까? 생각했는데, 이렇게 되면 운영 DB에 불필요한 부하가 일어날 수 있고 외부에 의존하지 않는 테스트 구조가 더 좋다는 의견을 주셨다. 사실 이 부분은 내가 테스트 코드를 정말 제대로 작성해본 경험이 없어서 놓쳤던 것 같다.

Github 서브모듈 적용

GitHub 서브모듈은 여러 프로젝트에서 동일한 설정 파일이나 모듈을 공유해야 할 때 유용하다. 공통 코드를 중복 없이 관리하고, 버전 추적까지 가능하기 때문에 팀 단위 개발에서 유지보수 효율성을 크게 높여준다.

현재 우리 상황에서는 Private Repository를 통해 별도의 파일들을 관리하고 있다. 만약, 이 파일들의 변경이 이루어져야 한다면 별도의 저장소를 clone하거나 직접 원격 저장소를 웹으로 접속해 내용을 변경해야 하는 번거로움이 있다. 따라서, 서브 모듈을 적용하여 이를 쉽게 유지보수 할 수 있도록 설정했다.

Github 서브 모듈의 경우 사진과 같이 main repository에 파일이 적용된다기보다는 포인터의 개념처럼 서브모듈 repository의 commit 상태를 추적하는 방식이다.

따라서 메인 저장소에서 작업이 이루어져도 이는 서브 모듈로 적용된 저장소에 영향을 주지 않고, 서브 모듈로 적용된 저장소에서 변화가 일어나면 이를 메인 저장소에 적용시키기 위해 최신화를 시켜주어야 한다(서브 모듈의 최신 커밋을 바라볼 수 있도록)

 

테스트용 DB 적용(Github Actions 내부에서)

이번에 조사하며 새롭게 알게 된 내용인데, Github Actions 내부에서 docker-compose 작성 방식과 같이 services: 단락을 통해 별도의 컨테이너를 띄울 수 있다.

이런식으로 설정을 해두고 Spring 테스트 환경에서 jdbc:mysql://localhost:3306/testdb 로 url을 지정해주면 local에서 docker을 사용하듯이 Github Actions 내부에서도 컨테이너를 사용할 수 있다.

따라서, 별도로 인프라를 구축하지 않고 외부 의존을 띄지 않는 테스트 환경을 쉽게 구성할 수 있었다.

 

이전 프로젝트들에서 CI/CD 파이프라인을 구축하며 고려해보지 못했던 부분들을 많이 생각해볼 수 있게 된 좋은 경험이었다.

계속 부족한 점이 없는지 파악하고 필요하다면 보완해나가야겠다..

 

PR 시 서브모듈에 존재하는 환경변수를 제대로 주입받지 못하는 문제 해결(2025/05/21 추가)


프로젝트 작업을 진행하며 서브모듈을 사용한 CI workflow 파일에서 큰 오류(?)가 있는 것을 발견할 수 있었다.

 

소셜 로그인 작업을 위해 main 브랜치에서 분기한 social_login 브랜치에서 작업을 진행하며, 환경변수가 추가적으로 필요해 서브모듈에 값을 업데이트하고 메인 프로젝트에서는 최신 커밋을 바라볼 수 있도록 작업하였다.

 

그 후 PR을 작성하여 올렸을 때 실패한 모습을 확인할 수 있었다.

 

원인을 파악해보니 다음과 같았다.

  • Main 브랜치 -> 서브모듈의 A커밋을 바라보고 있음(최신화되기 이전)
  • SocialLogin 브랜치 -> 환경변수를 업데이트 한 최신 커밋을 바라보고 있음

하지만 현재 내 workflow 파일에서는 main 브랜치를 가져와서 그 안의 서브모듈 커밋을 이용하고 있기 때문에, 최신화된 환경변수를 적용하지 못해 contextLoads() 테스트에서 오류가 발생하는 상황이었다.

 

따라서, PR 에서는 현재 PR이 작성된 브랜치를 가져와서 이에 맞는 서브모듈 커밋을 사용할 수 있도록 workflow를 변경해주었다.

기존의 구조에서, 어떠한 이벤트인지 파악하고 이에 따라 적절한 브랜치를 checkout 할 수 있도록 설정했다.

 

모두 설정하고 나서 생각해보니 코드의 변경이 없는 단순 문서 작업(지금처럼 workflow 파일만 수정하는 작업 등) 시에는 테스트 코드를 돌리는 것이 시간적으로 비효율적일 것 같다.(테스트 코드가 많아지면 많아질수록 불필요해진다고 생각한다.)

 

따라서, 팀원들과 상의 후 브랜치명에서 'feat/', 'fix/', 'docs/' 등으로 구분하고 'docs/' 등으로 시작하는 브랜치는 CI 흐름을 타지 않도록 설정하는 것도 좋은 방법인 것 같다.

'Project > PickLab' 카테고리의 다른 글

예외 상황에서 트래킹하기 편하도록 응답 핸들러를 작성해보자  (1) 2025.06.18
'Project/PickLab' 카테고리의 다른 글
  • 예외 상황에서 트래킹하기 편하도록 응답 핸들러를 작성해보자
dev_Mins
dev_Mins
  • dev_Mins
    천천히 빠르게!
    dev_Mins
  • 전체
    오늘
    어제
    • 분류 전체보기 (39)
      • 42Seoul (2)
      • Back-End (19)
        • Spring (8)
      • Project (13)
        • PickLab (2)
      • 끄적끄적 (3)
      • Algorithm (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    무한스크롤 페이징 성능
    Spring Boot
    페이징 성능
    db이중화
    Spring Data JPA
    Spring Security
    spring stomp
    JWT
    스프링
    excpetionhandler
    무한스크롤 성능
    AWS
    42서울
    spring 트래픽
    42seoul
    스프링 jwt
    Spring
    스프링 실시간 채팅
    무한스크롤 페이징
    로드밸런서
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dev_Mins
CI/CD 파이프라인 구축기
상단으로

티스토리툴바