• 처음 해본 CI/CD
    세명이서 머리 맞대고 하루 내내 씨름했다 그래도 성공(너무 뿌-듯)


이번 프로젝트에서 CI/CD가 필요한 이유

  • 여러명이 여러 변경사항들을 빌드하고 테스트하며
    repository에 올려 통합하는 작업을 반복하게 될텐데
    이를 수동으로 계속 한다면 이는 단순한 반복작업이다
    시간적, 비용적 측면을 고려하여 개발에만 집중할 수 있도록, 반복되는 과정의 자동화 필요성을 느꼈다


가볍게 용어 정리

  • 컴파일 : 소스코드(사람) → 기계어
    = 영어 번역
  • 빌드 : 소스코드 파일 → 소프트웨어 산출물(실행 할 수 있음)
    = 책
  • 배포 : 빌드의 결과물 → 사용자 접근가능한 환경에 배치
    = 서점에 배치


CI(Continuous Integration)

지속적 통합

  • 여러명의 개발자가 하나의 코드를 수정해도 통합하면서 관리 가능 = 개발진행 + 품질관리 같이되는
  • 분기 소스코드 병합
    • CI tool에 코드병합 요청 → CI tool이 build, test → 결과에 따라 코드 병합 or 피드백

ci


Github Action vs Jenkins

  Github Actio Jenkins
서버 설치 필요 불필요(클라우드에서 동작)
환경 호환성 도커 이미지에서 실행해야 함 모든 환경과 호환
설정(어려운정도) 학습 비용 필요(호스팅부터 해야함) 쉬운편(workflow만 설정하면 됨)
동기/비동기 동기적(제품 배포하는데 더 많은 시간 소요) 비동기 CI/CD 가능
캐싱 플러그인 제공 캐싱 매커니즘 직접 작성해야함
비용 무료 public 무료, private 사용량에 따라 과금
관련 문서 많은 편 적은 편


Jenkins가 후보가 된 이유 & 제외한 이유

  • 후보가 된 이유
    • 유명하고 오랜시간 사용되어 왔다. 그렇기 때문에 관련 문서가 많아 도움을 받기가 쉽다.
  • 제외한 이유
    • 프로젝트의 규모에 비해 설정하는데 드는 시간적 리소스가 크다.
      • 별도의 서버 설치가 필요하다.
      • 그리고 설정에 학습비용이 많이 들어갈 것으로 보인다.
    • 이번 프로젝트를 위해 학습해야 할 양이 이미 많아, 서버를 띄우고 설정 공부를 하는데에 투자하기 어려울 것이라 판단했다.


Github Actions를 선택한 이유

  • 선택한 이유
    • 첫번째 이유는 클라우드에서 동작하기 때문에 별도의 서버 설치가 불필요해 편하고 시간을 아낄 수 있다.
    • 두번째 이유는 Github에서 직접 제공하는 CI/CD 도구이기 때문에 보유한 repository에서 바로 진행이 가능하며 Github과 사용하기 좋다. 우리는 형상관리 툴로 Github을 사용할 것이기 때문에 큰 장점이라고 생각했다.
    • 세번째 이유는 Jenkins에 비해 설정이 쉽다. workflow만 설정하면 CI 기능을 쉽게 구현할 수 있다. 이 때, 많은 언어와 프레임 워크를 지원하기도 하고, 무엇보다 YAML로도 작성할 수 있다.
    • 네번째 이유는 Jenkins와 비교해 상대적으로 문서는 적지만 도움을 받을 수 있는 문서의 양으로는 충분하다고 판단했다. 또한, GitHub marketplace에 출시되는 GitHub Actions의 수를 보며 빠르게 성장하고 있음을 느꼈다.



CD

  • Continuous Delivery 지속적 제공
    • staging 환경에 release 자동 + (QA) + Deploy 수동
  • Continuous Deployment 지속적 배포
    • 둘 다 자동

=> 마지막 production으로 release하는 deploy 작업의 자동화 여부

cd


AWS CodeDeploy를 선택한 이유

  • 선택한 이유
    • AWS에서 제공하는 서비스이기 때문에 우리가 사용할 EC2와 연동이 쉽다. 간단한 명령으로 코드를 옮길 수 있어 효율적이라고 판단했다.



CI/CD 과정

ci_cd

ci_cd_code_deploy

  1. 개발자가 GitHub repository로 source code push
  2. GitHub Actions에서 build와 test(CI) 후 압축해서 s3로 업로드
  3. 업로드한 압축파일을 넘겨주면서 CodeDeploy에 배포 요청(CD)
  4. EC2에서 SpringBoot Server 실행



직접 해보기

CI test

일단, CI test를 먼저 진행했다

  • CI test 시의 최종 ci-cd.yml
name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Generate environment file
        run: |
          echo "$" >> db.env
          echo "$" >> spring.env
          
      - name: Run docker-compose
        uses: isbang/compose-action@v1.4.1
        with:
          compose-file: "./docker-compose.yml"

      - name: Get execution permission to gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build


변수들은 github secrets로 보관했다

secrets



이슈1 :: Github Action Workflow에서 MySQL이 없는 문제

  • .github/workflows/ci-cd.yml에서 ubuntu-latest 환경에서 빌드
  • 빌드 과정에서 본 프로젝트의 데이터베이스 의존성이 MySQL 밖에 없기 때문에 MySQL 연결 시도


해결 :: docker-compose를 통한 MySQL 이미지 실행

  • docker-compose.yml
version : "3"
services:
  db:
    container_name: wumo-db
    image: mysql
    ports:
      - "3306:3306"
    volumes:
      - ./mysqldata:/var/lib/mysql
    restart: always


  • ci-cd.yml에 step 추가
- name: Run docker-compose
  uses: isbang/compose-action@v1.4.1
  with:
    compose-file: "./docker-compose.yml"



이슈 2 :: MySQL 및 Spring Application Build Test 시 읽을 환경변수 설정 누락

  • MySQL 이미지를 다운로드 받고 실행하는 과정에서 데이터베이스 이름이나 비밀번호 초기 설정 누락


해결 :: 환경변수를 위한 설정 추가 및 환경변수 파일 추출

  • Settings - Secrets and variables - Secrets - New에서 DB_ENV, SPRING_ENV 파일에 변수 담기
- name: Generate environment file
  run: |
    echo "$" >> db.env
    echo "$" >> spring.env
  • docker-compose.yml
version : "3"
services:
  db:
    container_name: wumo-db
    image: mysql
    
    #추가
    env_file:
      - db.env
      
    ports:
      - "3306:3306"
    volumes:
      - ./mysqldata:/var/lib/mysql
    restart: always
  • application.yml
    • local과 github action 서버에서 사용할 yml 분리 필요
spring:
  config:
    import: optional:file:spring.env[.properties]
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${DATASOURCE_URL}
    username: ${DATASOURCE_USERNAME}
    password: ${DATASOURCE_PASSWORD}



CD test

1. AWS EC2 인스턴스 생성
AWS EC2 인스턴스 ssh 접속하기


2. Amazon RDS 인스턴스 생성

  • private subnet
  • public access 허용하기 <- 이 부분때문에 계속 rds 연결이 안되었다


3. Amazon S3


4. AWS CodeDeploy

  • CodeDeploy에게 역할 부여(AWS CodeDeployRole)
  • AWS CodeDeploy의 애플리케이션 생성
  • 배포그룹 생성(live, qa, dev)
    • 환경 구성 : EC2



5. EC2에 CodeDeploy, S3에 접근할 수 있는 역할 설정

  • AmazonS3FullAccess
  • AWSCodeDeployFullAccess



6. CodeDeploy 관련 코드 추가(appspec.yml)

  • appspec.yml : CodeDeploy의 설정 파일
    • 배포 시점에 특정한 스크립트 실행시킬 수 있음
      • deploy.sh
version: 0.0
os: linux
files:
 - source: /
   destination: /home/ec2-user/app  # 인스턴스에서 파일이 저장될 위치
   overwrite: yes
permissions:
 - object: /
   pattern: "**"
   owner: ec2-user
   group: ec2-user
   mode: 755
hooks:
  AfterInstall:
  - location: deploy.sh
    timeout: 200
    runas: ec2-user
  • deploy.sh : 인스턴스 배포하는 쉘 스크립트
    • (현재 인스턴스에서 애플리케이션이 구동중인지 확인하고, 구동 중이면 종료 후) 애플리케이션 실행
#!/usr/bin/env bash
REPOSITORY=/home/ec2-user/app
cd $REPOSITORY
APP_NAME=wumo  # 애플리케이션이 구동 중인지 확인하기 위해 사용
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME
CURRENT_PID=$(pgrep -f $APP_NAME)
echo "> Kill Previous Process" >> /home/ec2-user/app/deploy.log
if [ -z "$CURRENT_PID" ]
then
 echo "> No Process to Kill" >> /home/ec2-user/app/deploy.log
else
 echo "> Kill $CURRENT_PID" >> /home/ec2-user/app/deploy.log
 kill -15 "$CURRENT_PID" >> /home/ec2-user/app/deploy.log 2>&1
 sleep 5
fi
echo "> $JAR_PATH Deploy"
nohup java -jar $JAR_PATH >> /home/ec2-user/nohup.log 2>&1 &



7. EC2 인스턴스에 Java, CodeDeploy agent 설치

$ sudo yum list | grep jdk
$ sudo yum install -y java-17
$ sudo yum update

$ sudo yum install ruby
$ sudo yum install wget

$ cd /home/ec2-user
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

$ chmod +x ./install
$ sudo ./install auto

$ sudo service codedeploy-agent status

# 실행이 되고 있지 않다면
$ sudo service codedeploy-agent start



8. Github에 AWS access key와 secret key 변수화

  • Github Repository - Setting - Secrets



9. Giuthub Action workflow에 CD 추가

  • ci-cd.yml
    • $GITHUB_SHA은 자체적으로 제공해주는 변수 -> 실제 진행 시에는 고정적으로 할 예정
name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Generate environment file
        run: |
          echo "$" >> db.env
          echo "$" >> spring.env
      - name: Run docker-compose
        uses: isbang/compose-action@v1.4.1
        with:
          compose-file: "./docker-compose.yml"

      - name: Get execution permission to gradlew
        run: chmod +x ./gradlew

      - name: Build with Gradle
        run: ./gradlew clean build
        
      - name: Make zip file    # 파일 압축
        run: sudo zip -qq -r ./$GITHUB_SHA.zip .

      - name: Setting for AWS    # Github Sercrets에 설정한 값들을 가져와 AWS에 인증
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: $
          aws-secret-access-key: $
          aws-region: ap-northeast-2

      - name: Upload to S3    # S3에 해당 압축 파일을 업로드
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://wumo-bucket/$GITHUB_SHA.zip

      - name: Code Deploy    # CodeDeploy 시작
        run: aws deploy create-deployment --application-name wumo-deploy
          --deployment-config-name CodeDeployDefault.OneAtATime    # 배포 방법 설정(한 번에 배포하는 방식)
          --deployment-group-name wumo-deploy-group
          --s3-location bucket=wumo-bucket,bundleType=zip,key=$GITHUB_SHA.zip


결과

cicd_git

develop branch는 CI만 되도록 설정했고
main branch는 CICD까지 되도록 설정했다


ec2에서는 환경변수를 서버에 직접 들어가서 설정해주었다

ec2_pem

이것도 github에는 안올라가게 하면서 파일로 관리하는 방법이 있을듯한데 알아봐야겠다



Reference
카카오엔터프라이즈가 GitHub Actions를 사용하는 이유
무신사 스토어 watcher CI/CD 도입기
Github Actions냐 Jenkins냐! 올바른 선택을 해봅시다


업데이트: