-
사이드 프로젝트 백엔드 TB jenkins CI/CD 구축Project 2023. 3. 3. 00:57
시작하기 앞서...
이번에 새롭게 프로젝트를 진행하게 되었는데 처음 합류할 때 이번에는 반드시 직접 CI/CD를 Jenkins를 이용하여 구축하는 것이 목표였습니다. 프로젝트 직전까지 강의를 통해 Jenkins를 어느정도 익힌 후였고 실습을 통해 내것으로 좀 더 만들기 위해 프로젝트에 참여하여 CI/CD를 구축하게 되었습니다.
사실 아래 글이 절대 정답이 아니라 이번에 한 번 해보자는 마음으로 진행한 부분이 많아서 좀 더 좋은 방법이 많을텐데 아직은 모르는 부분이 많아서 그냥 이렇게 했구나 정도로만 생각해주시면 감사하겠습니다. 아직은 많이 부족한 상태입니다...
혹시 더 좋은 방법이 있다면 언제든지 댓글 달아주시면 감사하겠습니다. 피드백은 언제나 환영입니다 ㅎㅎ
그러면 지금부터 직접 경험한 TB 서버에서 CI/CD 구축 과정에 대해 소개해보도록 하겠습니다.
목차
- TB 서버 구성도
- Jenkins 설치
- 도커 설치 및 docker-compose.yml 파일 작성
- Jenkins Pipeline 작성
TB 서버 구성도
- 실제 배포할 서버가 아닌 테스트용 서버이고 jenkins를 이용한 CI/CD를 학습하는 것이 목적이었기 때문에 하나의 AWS EC2를 생성해서 jenkins와 DB, 각각의 application을 실행시켰습니다.
- 최종 배포할 때는 구조는 전부 변경 예정입니다.
- TB서버 구조는 아래 그림과 같습니다.
TB서버 프로젝트 구성도 위 그림에서는 현재 백엔드 api 서버만 그린 그림이기 때문에 프론트 app과 nginx는 따로 그리지 않았습니다. 이후에 추가할 예정입니다. 현재는 백엔드 ci/cd 구축에 집중해서 글을 작성하였습니다.
먼저 github에서 dev 브랜치로 mr요청이 완료되면 webhook을 보내서 Jenkins에서 파이프라인이 작동하도록 설정했습니다. 지금부터 위 구조를 설정하기 위해 진행했던 과정들을 남기도록 하겠습니다.
1. Jenkins 설치
Jenkins도 컨테이너로 띄워도 괜찮았지만 현재 프로젝트를 시작하기 전에 개인적으로 공부하던 ec2를 이어서 사용하였기 때문에 Jenkins는 직접 설치하여 사용하도록 했습니다.
이때 1가지 문제가 있었는데 AWT가 적절하게 설정되지 않았다는 에러로 인해 Jenkins가 실행되지 않았습니다.
AWT 설정 에러 아직 위 문제에 대한 정확한 원인은 파악하지 못했지만 아래 stackoverflow 글을 참고하여 설정을 바꿔주어 해결하였습니다.
Jenkins Deployment: AWT is not properly configured on this server. -Djava.awt.headless=true"?
Hi Stackoverflow friends I've recently configured Jenkins Server into Apache Tomcat 7.0.42 The procedure that I make was deploy the jenkins.war file into tomcat servlet container. Here I make ref...
stackoverflow.com
다음으로 Jenkins 포트 변경 과정입니다. Jenkins는 기본적으로 8080포트를 사용하는데 설치 당시에는 스프링 부트의 포트와 겹칠거라고 생각했기 때문에 다른 포트로 변경하였습니다. 포트 변경은 아래 블로그를 참고하였습니다.
젠킨스(Jenkins) - 포트(Port) 변경 두가지 방법
안녕하세요. 오늘은 젠킨스(Jenkins) - 포트(Port) 변경 두가지 방법에 대해 알아보도록 하겠습니다. 젠킨스의 기본 포트는 8080입니다. 하지만 대부분 8080은 다른 프로그램에서도 기본 포트로 사용하
jeeu147.tistory.com
GitHub와 Jenkins의 WebHook 연결의 경우 많은 레퍼런스들이 이미 존재하기 때문에 이번 글에는 따로 작성하지 않겠습니다.
참고로 처음에 webhook 테스트를 진행했을때 요청이 서버까지 전달되지 않아서 한참 찾아봤는데 내가 까먹고 ec2 보안그룹에서 인바운드 규칙에 내 방 ip만 허용했기 때문이었습니다... 다시는 실수하지 않겠지...
또 실수한 부분은 서버에 git을 설치하지 않아서 프로젝트 코드를 clone하지 못했는데 이부분도 인지하는데 생각보다 오래걸렸습니다... 역시 강의로 듣는것도 중요하지만 실제로 해보면서 내 것으로 만드는과정 또한 굉장히 중요한것 같습니다.
마지막으로 webhook 연결까지 성공 후 코드를 가져와 gradle 빌드를 진행할 때 에러가 발생했는데 이는 프리티어 서버를 사용하다보니 1GB의 메모리로는 빌드하지 못하는 문제가 발생했습니다. 메모리 부족 문제는 swap 메모리를 이용해서 일단 급하게 해결했는데 추후에 swap 메모리에 대해서는 자세히 공부해서 다시 글을 작성하도록 하겠습니다. okky의 내용과 aws 사이트에서 swap 공간을 통해 메모리 부족문제를 해결하는 레퍼런스가 있어서 참고하여 빌드까지 진행할 수 있었습니다.
OKKY - AWS EC2 프리티어 쓰시는분들 참고하세요!
일기장에 기록해둔건데 프리티어 쓰시는 분들에겐 좋은 정보일것 같아 남깁니다.제 일기장에서 긁어온거라 폼이 많이 깨지긴 했는데 감안하고 봐주세요! AWS 프리티어 EC2를 사용 중 겪은 문제
okky.kr
스왑 파일을 사용하여 Amazon EC2 인스턴스의 스왑 공간으로 메모리 할당
닫기 John 씨의 동영상을 통해 자세히 알아보기(3:37)
aws.amazon.com
처음에는 단순 Jenkins 설치와 github 연동 후 단순히 빌드까지만 테스트 할 생각이었기 때문에 금방 마무리 할 수 있다고 판단했는데 생각보다 다양한 이슈들이 있었고 특히 메모리 부족을 통해 서버의 리소스를 좀 더 효율적으로 쓸 필요가 있겠다고 많이 느꼈습니다.
개인적으로 CS 지식이 좀 부족하다고 생각은 하고 있었지만 바쁘다는 핑계로 그냥 넘겼는데 앞으로는 문제를 만날 때마다 잘 정리해야겠다는 생각을 했습니다...
2. 도커 설치 및 docker-compose 파일 작성
다음으로 서버에 도커를 설치하였는데 이 과정도 여러 레퍼런스가 있기 때문에 참고한 글들을 소개하는 것으로 넘어가겠습니다.
Amazon ECS에서 사용할 컨테이너 이미지 생성 - Amazon Elastic Container Service
Amazon ECS에서 사용할 컨테이너 이미지 생성 Amazon ECS는 작업 정의에 Docker 이미지를 사용하여 컨테이너를 시작합니다. Docker는 사용자가 컨테이너에서 분산 애플리케이션을 구축, 실행, 테스트 및
docs.aws.amazon.com
[AWS] EC2 Docker, Docker-compose 설치(Linux)
AWS에서는 docker와 docker-compose를 설치하는데 기존 리눅스 방법과는 다른 방법으로 설치를 진행해서 아래 방법을 찾아 공유하려고 합니다! 도커 설치 // 도커 설치 sudo amazon-linux-extras install docker //
narup.tistory.com
다음으로 TB서버에서 사용할 docker-compose.yml 파일 코드 부분입니다.
nginx 서비스는 생략한 상태로 3개의 서비스가 작성되어 있는것을 볼 수 있습니다. 여기서 backend 서비스의 depends_on 부분을 주석으로 바꾼것은 이후에 CD 작업을 진행할 때 아래 명령어를 사용할 예정인데 여기서 depends_on 속성 때문에 backend 서비스가 다시 빌드 되는 과정에서 mysql 서비스도 다시 생성되었기 때문에 주석처리 하였습니다.
docker-compose up --build -d backend
version: "3.7" services: mysql: image: mysql restart: always ports: - 3306:3306 environment: TZ: Asia/Seoul env_file: - ./db.env volumes: - /home/ec2-user/db/data:/var/lib/mysql - /home/ec2-user/db/conf.d:/etc/mysql/conf.d command: - --character-set-server=utf8 - --collation-server=utf8_general_ci networks: - wedding-network container_name: wedding-mysql backend: image: backend build: context: . dockerfile: ./Dockerfile ports: - 8081:8081 networks: - wedding-network container_name: wedding-backend # depends_on: # - mysql frontend: image: frontend build: context: . dockerfile: ./Dockerfile-prod ports: - 5173:5173 networks: - wedding-network container_name: wedding-fronend depends_on: - backend networks: wedding-network:
위 docker-compose 파일에서 맞이한 에러가 mysql 서비스의 env_file 을 불러오는 과정이었는데 이후 pipeline 스크립트를 보면 확인 가능하지만 db.env 파일이 docker-compose.yml 파일이 존재하는 위치에 같이 존재해야 에러 없이 빌드가 되었습니다.
처음에 permission denied 에러가 발생해서 설정 파일에 대한 권한 문제인것 같아 한동안 삽질을 하였는데 결국 파일의 위치 문제였습니다. 우연히 github 이슈와 도커 공식문서를 통해 확인하게 되었는데 다시한번 공식문서의 중요성을 느낄 수 있었습니다...
.env file is not read · Issue #4223 · docker/compose
I'm trying to use ENV variables declared in a .env file in my docker-compose file (I.e. I don't want to use the .env file in a container, just in the build process). I have the following: #...
github.com
Environment variables precedence
docs.docker.com
나중에 정식 배포를 하게 되면 아마 AWS의 RDS를 이용하게 될 것 같은데 그래도 이번기회에 직접 컨테이너 형태로 전부다 설치하면서 많이 배울 수 있었던것 같습니다.
3. Jenkins Pipeline 작성
Jenkins의 파이프라인을 작성하는 방법은 크게 Declarative Pipeline 과 Scripted Pipeline 방법이 있는데 Jenkins 공식문서에는 Declarative 방법으로 설명이 나와있었고 Scripted 방식이 좀 더 유연한 방식이라고 느껴졌는데 이번 프로젝트에서는 Declarative 방법으로도 충분히 Pipeline을 작성할 수 있다고 판단했기 때문에 해당 방식을 선택하였습니다.
pipeline { agent any stages { stage('Pull') { steps { checkout scmGit(branches: [[name: '*/dev']], extensions: [], userRemoteConfigs: [[credentialsId: 'wedding', url: 'https://github.com/Wedding-Registry/Back.git']]) } } stage('copy application.yml file to build directory and gradlw build') { steps { sh '''sudo cp /home/ec2-user/application-prod.yml ./src/main/resources; sudo cp /home/ec2-user/db/env/db.env .; ./gradlew build; ''' } } stage('Docker-compose up backend') { steps { sh 'docker-compose up --build -d backend' } } stage('Remove none image if exist') { steps { sh '''dangling_images=`sudo docker images -f "dangling=true" -q` echo $dangling_images if [[ ! -z $dangling_images ]] then docker rmi $(docker images -f "dangling=true" -q) echo "finish clean none images" else echo "No Dangling images found for cleanup" fi''' } } } }
지금부터 위 Pipeline 코드를 하나하나 살펴보도록 하겠습니다.
1. Pull
- git 명령어를 통해서도 코드를 가져올 수 있지만 개인적으로는 좀 더 다양한 기능을 가진다고 느낀 checkout 명령어를 선호해서 사용하였습니다.
- dev 브랜치에서 코드를 가져오도록 하였고
- 사실 credentialsId는 git 저장소가 public 저장소이기 때문에 필요는 없지만 만들어둔 credentialsId가 있었기 때문에 그냥 작성해 주었습니다.
2. 필요한 파일 복사 및 gradle 빌드
- Jenkins에서 변수로 사용할 중요 정보들을 따로 이미 서버에 작성해서 해당 파일들을 옮겨서 사용하는 방법을 선택했습니다.
- db.env 파일에는 mysql에 접속할 수 있는 유저와 패스워드, database가 작성되어 있습니다.
- 이를 Jenkins에서 관리하는 방법도 있었지만 이번에는 미리 파일로 작성해두었기 때문에 서버에 파일을 copy하여 Jenkins의 work 디렉토리로 옮겨서 이용하는 방법을 선택했습니다.
- 위에서 설명했듯이 db.env 파일은 docker-compose.yml 파일과 같은 위치에 존재해야하기 때문에 같이 copy해준 모습입니다.
3. docker-compose up
- backend 서비스만 빌드할 계획이었기 때문에 명령어 마지막에 backend 서비스를 같이 작성해주었습니다.
- --build 옵션을 통해 이미지를 새로 빌드하도록 설정했습니다.
- 만약 build 옵션이 없다면 기존 이미지를 그대로 다시 이용하여 컨테이너화 시켜주기 때문에 새로 작성한 코드가 반영되지 않습니다.
4. 기존 이미지 삭제
- 마지막으로 새로 이미지를 빌드했기 때문에 기존 이미지가 none으로 변경되고 새 이미지가 생성됩니다.
- 여러번 빌드를 하게되면 사용하지 않는 이미지가 계속 쌓였기 때문에 사용하지 않는 이미지를 삭제해주는 쉘 스크립트를 작성하였습니다.
- 스크립트 파일을 새로 작성해서 실행시킬까라는 고민을 조금 했는데 일단 스크립트 길이가 많거나 복잡한 로직은 아니었기 때문에 그냥 여러 줄로 pipeline에 작성하였습니다.
- 이때 if 조건에서 [] 를 한번 더 사용한 이유는 dangling_images가 여러개가 나올 때 binary operator expected 에러가 발생했는데 줄바꿈이나 공백이 존재하면 발생하는 에러인것 같습니다. 이때는 []를 한번 더 감싸주는 방법으로 해결 가능했기 때문에 위 스크립트처럼 코드를 작성해주었습니다.
- 만약 사용하지 않는 이미지가 없다면 그냥 넘어가고 사용하지 않는 이미지가 존재한다면 해당 이미지들을 전부 지워주도록 설정했습니다.
글을 마무리 하며
지금까지 과정을 거쳐서 백엔드 부분만 CI/CD 를 간단하게 구축하였는데 사실 글로 작성하다보니 짧아보였지만 정말 많은 에러들을 만났습니다...
그래도 생각보다 많은 것들을 새로 얻을 수 있던 경험이었고 이를 토대로 이후에 프론트 컨테이너와 nginx 컨테이너까지 구축하여 TB 서버에서 간단히 CI/CD 를 구현하도록 할 예정입니다... 4월 전까지 무조건 마무리 하고 다시 한번 총 정리하는 글을 올려보도록...
위 과정이 절대 정답이라고는 생각하지 않지만 그래도 한번 처음부터 끝까지 원하는 기능을 구현했다는 것만으로도 큰 의미라고 생각하고 좀 더 공부하게 된다면 더 좋은 방법으로 구현해보도록 하겠습니다.
실제 배포를 진행할 때는 각 서비스들을 서로 다른 서버에 띄울 예정인데 사실 docker-compose 보다는 쿠버네티스를 도입하면 좋겠지만 이 부분은 아직 학습이 되어있지 않기 때문에 우선은 할 수 있는 방법을 찾아서 해보도록 할 예정입니다. 이후에 쿠버네티스를 공부한다면 한번 도입 시도를 해보도록 하겠습니다.
긴 글 읽어주셔서 감사합니다
'Project' 카테고리의 다른 글
Github Actions CI 구성 (0) 2023.08.22 mono-repo / multi-module 프로젝트에서 spring 자동 설정 이용하기 (0) 2023.08.22 AWS S3 이미지 저장 및 삭제와 DB로직 트랜잭션 분리 (0) 2023.07.27