Profile picture

[Github Actions] 개인 프로젝트에서 많이 쓰이는 CI/CD 구축 방법

JaehyoJJAng2023년 06월 01일

▶ 개요

배포되고 있는 서비스를 업데이트 하려면 어떤 과정을 거쳐야 할까?

가장 흔한 배포 플로우를 살펴보자.

  • 1. 새로운 기능에 대한 코드 작성
  • 2. git commit 하여 브랜치에 Merge를 하고 배포(git push)
  • 3. 배포 서버에 원격 접속하여 새로운 코드를 업데이트(git pull)하고 애플리케이션 재실행

보통 단순한 서비스를 배포한다면 위와 같은 과정을 거칠 것이다.

하지만, 이 과정을 코드의 수정이 일어날 때마다 수행한다는 것은 너무 귀찮은 일 아닐까.

그래서 이런 반복적인 과정을 해결하기 위해 여러 환경에서의 CI/CD 구축 방법에 대해서 시리즈 형식으로 작성해보려고 한다.

예정되어 있는 포스팅은 다음과 같다.

위 4단계로 거쳐 각각 상황에 맞는 CI/CD 구축 방법에 대해서 이해해보자.


▶ 전체적인 흐름

image


▸ 장점

  • git pull을 활용하여 변경된 부분의 프로젝트에 대해서만 코드 업데이트를 하기에 CI/CD 속도가 빠름.
  • github actions만 사용하기 때문에 인프라 구조가 비교적 간단.

▸ 단점

  • 빌드 작업을 EC2에서 진행하기 때문에, 빌드 진행 시 운영하고 있는 서버의 성능에 영향을 미침.
  • Github 계정 정보가 EC2 서버 내에 저장되기 때문에 보안이 취약.

▸ 이 방법이 쓰이는 이유

  • 주로 개인 프로젝트에서 CI/CD를 간단하고 빠르게 적용시키고 싶을 때 사용함.

▶ 프로젝트 생성

https://start.spring.io/ 사이트로 들어가서 간단하게 자바 스프링 프로젝트를 생성
image


그리고 아래와 같이 간단한 코드를 추가해주자. (생성한 프로젝트명에 따라 경로는 달라질 수 있음.) src/main/java/com/example/instagram_server/

package com.example.instagram_server;

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

@RestController
public class AppController {
    @GetMapping("/")
    public String home() {
        return "Hello, World!";
    }

▶ 깃허브에 프로젝트 배포

1. Github repository 생성

Private repository로 생성하자.


2. Github repository에 프로젝트 코드 배포

cd <프로젝트 경로>
git init
git add .
git commit -m "first commit"
git branch -M main
git remoad add origin <github repo url>
git push -u origin main

▸ github 비밀번호 치는 과정 없애기

Private 레포지토리로 pull을 받거나 clone을 하거나 push를 하는 등의 작업을 하려면 해당 레포지토리 소유자의 토큰 값을 입력하여 진행해야 한다.

근데 매번 push, pull 마다 토큰을 입력하는 것은 CI/CD 취지에 맞지 않다.

그러므로 아래와 같이 계정과 비밀번호를 입력하지 않아도 되게 만들어보자.

git config --global credential.helper store
git pull origin main
# Github 계정 및 비밀번호 입력

git pull origin main # 더 이상 비밀번호를 묻지 않음.

그리고 계정과 토큰 값의 경우 ~/.git-credentials에서 확인이 가능하다.


▶ EC2 접속하여 기본 환경 구성

  • EC2 생성 과정은 생략함.

1. 보안 그룹 8080번 포트 활성화


2. JDK 설치

sudo apt-get update -y && sudo apt-get install -y openjdk-17-jdk

# 잘 설치되었는지 확인
java -version

3. 프로젝트 clone

git clone <git repository clone 주소>

4. EC2에서 해당 프로젝트를 빌드하고 실행하여 애플리케이션이 정상적으로 작동하는지 확인

cd <프로젝트 경로>
chmod u+x gradlew && ./gradlew clean build
cp build/libs/*SNAPSHOP.jar ./app.jar

# 애플리케이션 실행
nohup java -jar app.jar 1>/dev/null 2>&1 &

# 8080번 포트에 Spring boot가 실행 중인지 확인
sudo lsof -i:8080

▶ 실제 코드가 업데이트 될 때 어떤 과정을 거치는지 짚어보기

이제 CI/CD를 왜 사용해야하는지에 대해서 더 명확하게 이해해보기 위해서

위 프로젝트 코드인 Hello, WorldHi,World로 변경하고 다시 실행해보자.

배포 과정을 복기하면 아래와 같다.

  • 1. 개발 서버에서 새로운 코드 작성
  • 2. Commit 후 github에 배포
  • 3. EC2에 들어가서 git pull
  • 4. 빌드 후 재배포하기.
# 2
git commit -m "hello to hi"
git push -u origin main

# 3 (EC2 접속 후 git pull)
ssh -i ~/.ssh/ec2.pem ubuntu@x.x.x.x
cd <프로젝트 경로>
git pull origin main

# 4. 빌드 후 배포
sudo fuser -k -n tcp 8080 # 8080번 포트에서 실행되고 있는 프로세스 종료
./gradlew clean build
cp build/libs/*SNAPSHOP.jar ./app.jar
nohup java -jar app.jar 1>/dev/null 2>&1 & # 애플리케이션 실행

▶ 지금까지의 배포 과정 자동화하기

이제 위 과정을 CI/CD를 도입하여 자동화해보자.

먼저 프로젝트 최상단에서 아래와 같은 폴더와 파일을 만들어주자.

mkdir -p .github/workflows
touch .github/workflows/deploy.yaml

.yaml 파일의 이름은 아무거나 지정해도 상관없지만 위 폴더 경로는 지켜져야 한다.


아래와 같이 deploy.yaml을 작성하자.

name: Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: "1. 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/cicd-practice # 여기 경로는 자신의 EC2에 맞는 경로로 재작성
            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 & 

▸ Secret 값 주입하기

image
image


▸ 민감한 정보 처리 방법

Spring Boot에서는 민감한 값을 application.yaml 또는 application.properties 라는 파일로 분리하는 경우가 많다.

말 그대로 민감한 값이기에 .gitignore에 추가하여 application.yaml가 버전 관리 되지 않도록 설정을 하는데,

이 때문에 배포할 때 application.yaml 파일을 따로 서버에 주입 해줘야 하는 수동 작업이 포함되게 된다.

그 이유는 git pull 시 버전 관리에서 제외된 파일(application.yaml)이 업데이트 되지 않기 때문이다.

그래서 이 과정을 자동화 시켜보려고 한다.


1. .gitignore에 application.yaml 추가
.gitignore

...
application.yml

2. application.yml 파일 만들기
src/main/resources/application.yml

# 아래 값은 테스트를 위해 임의로 넣은 값임.
aws:
  access-key: xxxx
  secret-key: xxxx

3. Github에 resources 폴더가 push 되도록 임의의 파일 하나 만들기

touch src/main/resources/any.txt

4. Github Actions 코드 수정하기
.github/workflows/deploy.yaml

name: Deploy To EC2

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: SSH로 EC2에 접속하기
        uses: appleboy/ssh-action@v1.0.3
        env:
          APPLICATION_PROPERTIES: ${{ secrets.APPLICATION_PROPERTIES }}
        with:
          host: ${{ secrets.EC2_HOST }} # EC2의 주소
          username: ${{ secrets.EC2_USERNAME }} # EC2 접속 username
          key: ${{ secrets.EC2_PRIVATE_KEY }} # EC2의 Key 파일의 내부 텍스트
          envs: APPLICATION_PROPERTIES
          script_stop: true # 아래 script 중 실패하는 명령이 하나라도 있으면 실패로 처리
          script: |
            cd /home/ubuntu/instagram-server # 여기 경로는 자신의 EC2에 맞는 경로로 재작성하기
            rm -rf src/main/resources/application.yml
            git pull origin main
            echo "$APPLICATION_PROPERTIES" > src/main/resources/application.yml
            ./gradlew clean build
            sudo fuser -k -n tcp 8080 || true # || true를 붙인 이유는 8080에 종료시킬 프로세스가 없더라도 실패로 처리하지 않기 위해서이다. 
            nohup java -jar build/libs/*SNAPSHOT.jar > ./output.log 2>&1 &

5. github에 Secret 값(APPLICATION_PROPERTIES) 주입하기


6. 실제 EC2에 application.yaml 파일도 같이 배포 되었는지 확인하기


▸ Github Actions 테스트

이제 작성한 deploy.yaml을 github로 push해서 Github Actions가 정상적으로 작동하는지 확인해뵈자.

git add .
git commit -m "feat: 테스트"
git push -u origin main

image


Loading script...