Server&load/CI CD

개인 프로젝트에서 쓰이는 CI/CD 구축 방법 실습하기

몽자비루 2025. 12. 28. 22:07

이번에는 개인 프로젝트에서 쓰이는 CI/CD 프로세스를 직접 구축해보려고 한다.

 

1. spring boot 환경 구축하기

먼저 해당 링크에서 아래와 같이 설정한 뒤에 다운로드한 스트링 부트 파일을 사용할 예정이고,

해당 폴더를 VS Code 에서 열어 자동으로 build 를 실행하므로 끝날 때까지 기다려준다.

이후에 src/main/java/com/example/{file_name} 에 "AppController" 파일을 추가하고 아래 내용을 입력한다.

package com.example.rusharp_server;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController {
    // Add your endpoint mappings and methods here
    @GetMapping("/")
    public String home() {
        return "Welcome to Rusharp Server!";
    }
}

 

이후에 RusharpServerApplication.java 을 실행시키면 localhost:8080 에서 "Welcome to Rusharp Server!"가 나온다.

이렇게 Springboot 가 실행되는 것을 확인한 뒤에 이제 Github repository에 올리고 CI/CD를 진행해보려고 한다.

 

2. EC2에 spring boot 서버 배포하고 접속하기

먼저 Github 에 아래와 같이 push를 진행하여 넣고 AWS 를 사용하여 EC2 에 서버를 배포한다.

 

그 다음으로 AWS 에서 EC2의 인스턴스를 시작하고, 아래와 같이 설정했다.

Machine Image
instance 유형 및 key pair
네트워크 설정에 HTTP, HTTPS 추가 및 편집으로 8080 port추가

 

해당 인스턴스에 연결하고 t2.micro의 memory 는 1GB 인데 이를 높이기 위해서 t2.micro 램 스왑을 설정해야 한다.

만약 해당 과정이 번거롭다면 인스턴스 유형을 t3a.small 이상으로 진행하면 된다.

#1) Swap 파일 생성 (2GB)
#dd 명령어를 사용하여 128MB 블록을 16개 생성하여 총 2GB를 만든다. (약간의 시간이 소요될 수 있습니다.)

sudo dd if=/dev/zero of=/swapfile bs=128M count=16

#2) 권한 설정
#보안을 위해 root 계정만 읽고 쓸 수 있도록 권한을 수정힌다.

sudo chmod 600 /swapfile

#3) Swap 영역 설정
#생성한 파일을 Swap 공간으로 포맷팅한다.

sudo mkswap /swapfile

#4) Swap 활성화
#Swap 메모리를 즉시 활성화한다.

sudo swapon /swapfile

#5) 설정 확인
#메모리가 정상적으로 늘어났는지 확인합니다. Swap 항목에 약 2.0G가 잡혀있으면 성공.

free -h

#6) 재부팅 후에도 유지하기 (fstab 등록)
#위의 swapon 명령어는 리부팅 하면 초기화된다. 서버가 재시작되어도 자동으로 Swap이 잡히도록 설정 파일(/etc/fstab)을 수정한다..
#아래 명령어를 복사해서 실행하면 파일 맨 끝에 설정이 자동으로 추가된다.

echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab

#7) [옵션] JVM 옵션 설정하기
#Swap을 설정했다고 해서 무한정 메모리를 쓸 수 있는 것은 아니다. Swap(디스크)은 RAM보다 속도가 훨씬 느리기 때문에, 애플리케이션이 Swap 영역을 너무 적극적으로 사용하면 서버가 매우 느려진다.
#따라서 Spring Boot 실행 시 최대 힙 메모리(-Xmx)를 물리 메모리의 70~80% 수준으로 제한하는 것이 좋다다.

#-Xmx768m: 힙 메모리를 최대 768MB로 제한하여, 남은 물리 메모리와 Swap 영역을 OS 및 기타 프로세스가 사용할 수 있도록 여유를 줍니다.

java -Xmx768m -jar your-application.jar

그 다음으로 스프링 부트 프로젝트를 EC2 서벗에서 배포하고 실행시키기 위한 환경을 설정한다.

아리 코드를 순서대로 입력하고 나면 java 17버전으로 설치되는 것을 확인할 수 있다.

sudo apt update
sudo apt install openjdk-17-jdk -y
java -version

 

이후 git clone 을 사용해서 EC2에 spring boot 폴더를 불러온 뒤 ./gradlew clean build 로 실행한다.

 

이후 /build/libs 하위에 rusharp-server-0.0.1 SNAPSHOT.jar 파일을 nohup java -jar [filename] & 으로 실행시킨다.

 

다음이 lsof -i:8080 으로 8080 port에서 실행되는 프로세스가 java 인지 확인하고

publiciPs:8080 으로 접속하면  "Welcome to Rusharp Server!" 텍스트가 나오는 것을 확인할 수 있다.

3. 코드 업데이트 후 배포하기

그렇다면 만약 코드에 업데이트 사항이 있는 경우, 어떻게 재배포를 하게 될까?

 

먼저 local  환경에서 코드를 수정한 뒤에 push를 통해 github 환경에 코드를 등록한다.

 

그다음으로 EC2로 들어가서 git pull을 진행한 뒤에 기존에 있던 서버 (8080 포트)를 종료시킨다.

 

다음으로 새로 받은 코드를 기준으로 ./gradlew clean build 를 통해 빌드를 진행한 뒤에

SNAPSHOT.jar 파일을 실행시키고 8080포트에서 실행중인 프로세스를 확인하면 java인 것을 볼 수 있다.

 

만약 매번 수정사항이 생길 때마다 빌드 후 배포하면 위와같은 번거로운 작업을 실행해야 하는데,

github actions 의 CI/CD 프로세스를 사용하면 위와 같은 모늗 일련의 과정을 한번에 처리할 수 있다.

 

그 전에 현재는 pull할 때마다 매번 닉네임과 비밀번호를 입력해야 하는데 자동 로그인이 되도록 하기 위해서

git config --global credential.helper store 를 입력하고 git pull 을 진행했을 때,

닉네임과 token 비밀번호를 한번 입력하면 그 다음부터는 닉네임과 비밀번호를 요청하지 않는다.

 

다만 이렇게 되면 EC2에 접근할 수 있는 모든 사용자가 개인 token비밀번호를 볼 수 있는 보안적 한계가 생긴다.

 

3. CI/CD 프로세스로 배포/빌드하기

그렇다면 위 과정을 CI/CD 프로세스로 배포하기 위해서는 먼저 .github/workflows 에서 deploy.yml파일을 생성한다.

 

앞서 Github Actions 는 일종의 로직을 실행시킬 수 있는 하나의 컴퓨터라고 정의했었는데,

EC2도 spring boot 를 실행할 수 있는 일종의 컴퓨터로서, Github actions 에서 ssh 로 원격접속 후 실행되어야 한다.

 

ssh 로 원격접속하는 방법은 여러개가 있는데, appleboy/ssh-action 을 사용했고 형식은 링크를 참고했다.

name : Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  My-Deploy-Job:
    runs-on: ubuntu-latest

    steps:
      - name: SSH 로 EC2에 접속
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.EC2_HOST }} # EC2의 주소
          username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
          key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
          script_stop: true # 아래 script 중 실패하는 명령이 하나라도 있으면 실패로 처리
          script: |
            cd /home/ubuntu/rusharp-server/
            git pull origin main
            ./gradlew clean build
            sudo fuser -k -n tcp 8080 || true # || true를 붙인 이유는 8080에 종료시킬 프로세스가 없더라도 실패로 처리하지 않음
            # jar 파일을 실행시키는 명령어. 그리고 발생하는 로그를 ./output.log 파일에 남김
            nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &

 

 

여기에서 script 하위에 있는 명령어들은 위 EC2에서 동작했던 내용들을 기반으로 작성했다.

 

그리고 보안 변수는 github repository settings 의 Secrets and variables/Actions Repository secrets 에 추가한다.

 

여기에서 EC2_USERNAME 는 EC2에서 whoami 를 입력했을 때 나오는 사용자 이름이고,

EC2_HOST 는 EC2의 PublicIPs, PRIVATE_KEY 는 EC2 key_par의 값으로, 터미널에서 확인할 수 있다.

 

이제 git push 를 진행한 뒤에 Actions 에서 확인하면 아래와 같이 성공한 것을 볼 수 있다.

 

이렇게 3번에서 코드 업데이트 후 배포하는 모든 과정을 yml을 통해서 자동으로 돌아가도록 만들어봤다.

 

4. gitignore 에 포함된 파일을 함께 관리하기

지금까지는 CI/CD 를 사용해서 배포를 진행해봤는데, 간혹 보안적인 이슈로 인해

.gitignore 에 포함된 파일이지만, 배포 시 반드시 필요한 파일인 경우가 있다.

 

예를 들어 src/main/resources/templates의 application.properties 가 .gitignore에 포함되어있다면

이를 배포할 때마다 직접 EC2에 접속해서 해당 파일을 생성해 줘야하기 때문에 번거롭다.

 

해당 내용을 secret 변수로 등록하고 script 안에서 secret 에 대한 변수를 사용하게끔 만들 수 있다. 

 

내용에 대해서 설명하자면 env 안에서 secret 변수 값을 APPLICATION_PROPERTIES 에 선언하고

script 안에서 기존에 있던 application.properties 파일을 삭제한 뒤에 다시 생성한다.

 

그러면 아래와 같이 github 에서는 application.properties 파일이 없지만 EC2에서는 해당 파일이 존재한다.

 

이렇게 개인 프로젝트에서 일반적으로 사용되는 CI/CD 프로세스에 대해서 공부해보았는데,

git pull을 사용하기 때문에 심플하긴 하지만 보안적으로 취약하다는 단점이 있다는 것을 알 수 있었다.

 

그렇다면 다음에 일반 프로젝트에서는 어떻게 사용되는지에 대해서 알아보고자 한다.