CI/CD를 구축해보자

YoungHo-Cha·2022년 7월 24일
15

Catch Bug Project

목록 보기
5/12
post-thumbnail

오늘은 CI/CD를 구현하면서 정리한 모든 것을 해볼 것이다. 스프링 프로젝트는 이미 구현해놓았다.

해당 글은 "무중단 배포"에 대한 내용은 없습니다.

해당 프로젝트 Git 주소: https://github.com/Cha-Young-Ho/docker_mysql_ec2


🚗 목차

  • Git Flow 작성
  • CI 구축
  • CD 구축
  • 확인

🌈 Git Flow 작성

Git Flow를 통해서 모든 작업을 총괄해야 한다. 사실상 가장 중요한 부분이 될 것이다.

작성한 Git Flow를 살펴보자.

on:
  push: # feature/*와 develop 브랜치에서 push가 일어났을 때 github action 동작
    branches:
      - 'master'
      - 'develop'
  pull_request: # feature/*와 develop 브랜치에서 PR이 일어났을 때 github action 동작
    branches:
      - 'master'
      - 'develop'

# 참고사항
# push가 일어난 브랜치에 PR이 존재하면, push에 대한 이벤트와 PR에 대한 이벤트 모두 발생합니다.

jobs:
  build: 
    runs-on: ubuntu-latest # 실행 환경 지정

    steps:
      - uses: actions/checkout@v2 # github action 버전 지정(major version)

      - name: Set up JDK 11 # JAVA 버전 지정
        uses: actions/setup-java@v1
        with:
          java-version: 11 

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew
      
       ## create application-database.yaml
      - name: make application-database.yaml
        run: |
          # create application-database.yaml
           cd ./src/main/resources

          # application-database.yaml 파일 생성
           touch ./application-database.yaml

          # GitHub-Actions 에서 설정한 값을 application-database.yaml 파일에 쓰기
          echo "${{ secrets.DATABASE }}" >> ./application-database.yaml
        shell: bash
      
      
      - name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외)
        run: ./gradlew build -x test

      - name: Test with Gradle # test application build
        run: ./gradlew test

      - name: Publish Unit Test Results # test 후 result를 보기 위해 추가
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: ${{ always() }} # test가 실패해도 report를 남기기 위해 설정
        with:
          files: build/test-results/**/*.xml
          
          
      # 배포 Job
      # 도커허브 push
      - name: Docker build
        run: |
          docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t app .
          docker tag app ${{ secrets.DOCKER_REPO }}/cicd
          docker push ${{ secrets.DOCKER_REPO }}/cicd
      # ec2 접속 및 도커 pull
      # 도커 실행
      - name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.AWS_HOST }} # EC2 인스턴스 퍼블릭 DNS
          username: ubuntu
          key: ${{ secrets.AWS_SSH_KEY }} # pem 키--
          # 도커 작업
          script: |
            docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
            docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
            docker stop server
            docker network create -d bridge test
            docker run -d --network test --network-alias mysql -v  /build/DB/mysql:/var/lib/mysql --name mysqlDB -e MYSQL_DATABASE=anonymous_board -e MYSQL_USER=user01 -e MYSQL_PASSWORD=user01 -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql

            docker run -d --rm --name app --network test -p 8080:8080 ${{ secrets.DOCKER_ID }}/cicd:latest

이제 구문 by 구문 살펴보자!


🌈 CI 구축

🐳 이벤트 명시

on:
  push: # feature/*와 develop 브랜치에서 push가 일어났을 때 github action 동작
    branches:
      - 'master'
      - 'develop'
  pull_request: # feature/*와 develop 브랜치에서 PR이 일어났을 때 github action 동작
    branches:
      - 'master'
      - 'develop'

master, develop 브랜치에 "push", "pr" 이벤트가 생겼을 때 git action을 동작시킨다.

🐳 Job 명시

jobs:
  build: 
    runs-on: ubuntu-latest # 실행 환경 지정

job을 명시하며 ubuntu 환경에서 수행된다고 명시

🐳 Steps

Job의 구체적인 동작을 명시한다. 순차적으로 재생이 된다.

steps:
      - uses: actions/checkout@v2

git action의 버전을 명시해준다.

🐳 name

자바 버전 세팅

구체적인 행동 하나하나를 명시해준다.

- name: Set up JDK 11 # JAVA 버전 지정
        uses: actions/setup-java@v1
        with:
          java-version: 11 

git action runner에 jdk 버전을 세팅해준다.

실행 권한 주기

- name: Grant execute permission for gradlew
        run: chmod +x gradlew

gradlew에 실행 권한을 준다.

그래들 빌드

 - name: Build with Gradle # 실제 application build(-x 옵션을 통해 test는 제외)
        run: ./gradlew build -x test

test 빌드

 - name: Test with Gradle # test application build
        run: ./gradlew test

빌드 repost 남기기

 - name: Publish Unit Test Results # test 후 result를 보기 위해 추가
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: ${{ always() }} # test가 실패해도 report를 남기기 위해 설정
        with:
          files: build/test-results/**/*.xml

여기까지가 완료되면 CI가 완료되었다.


🌈 CD 구축

CD를 구축하는 방식은 수없이 많다.

  1. 프로젝트를 git hub를 통해서 넘겨서 build를 한다.
  2. Docker로 이미지를 생성해서 Git, S3, Docker Hub를 통해서 넘겨서 Docker를 run한다.
  3. runner에서 ghcr을 이용하여 배포

나는 2번의 방법으로 할 것이다.

그리고 Docker image를 컨테이너화 시키는데도 방법이 2가지가 존재한다.

  1. Docker image를 run
  2. Docker-Compose를 실행

나는 둘 다 해볼 것이다.

🐳 Docker 빌드

먼저 runner에서 수행된 내용을 이미지로 만들어야 한다.

# 배포 Job
      # 도커허브 push
      - name: Docker build
        run: |
          docker login -u ${{ secrets.DOCKER_ID }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t app .
          docker tag app ${{ secrets.DOCKER_REPO }}/cicd
          docker push ${{ secrets.DOCKER_REPO }}/cicd

환경변수 살펴보기.

  • ${{ secrets.DOCKER_ID }} : 도커 허브 아이디이다.
  • ${{ secrets.DOCKER_PASSWORD }} : 도커 허브 비밀번호다.
  • ${{ secrets.DOCKER_REPO }} : 도커 레포지토리의 아이디이다.

순서를 살펴보면

  1. 도커 허브 로그인
  2. 도커 이미지 빌드
  3. 도커 tag 주기
  4. 도커 허브에 이미지 push

🐳 배포

- name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.AWS_HOST }} # EC2 인스턴스 퍼블릭 DNS
          username: ubuntu
          key: ${{ secrets.AWS_SSH_KEY }} # pem 키--
          # 도커 작업
          script: |
            docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
            docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
            docker stop server
            docker network create -d bridge test
            docker run -d --network test --network-alias mysql -v  /build/DB/mysql:/var/lib/mysql --name mysqlDB -e MYSQL_DATABASE=anonymous_board -e MYSQL_USER=user01 -e MYSQL_PASSWORD=user01 -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql
            docker run -d --rm --name app --network test -p 8080:8080 ${{ secrets.DOCKER_ID }}/cicd:latest

먼저 ssh로 나의 AWS EC2로 접속을 한다.

  • ${{ secrets.AWS_HOST }} : 퍼블릭 DNS를 말한다.

아래 사진을 보면 프라이빗 IP 아래에 있다.

  • ${{ secrets.AWS_SSH_KEY }} : AWS에서 인스턴스를 생성할 때, 설정해준 키페어 값이다. 해당 키페어를 확인하는 방법은
cat <키 페어 파일>

다음과 같이 나온다.

을 하면 위와 같이 나온다.

위에서 ---를 다 포함하여 마지막 ---까지 복사해서 등록해주어야 한다.

❗️ %는 빼야함!!!!!!!!

이렇게하면 러너가 EC2에 SSH로 접속을 한다. 그리고 script를 실행한다.

다음으로는 도커 작업을 보자.

script: |
            docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
            docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
            docker stop server
            docker network create -d bridge test
            docker run -d --network test --network-alias mysql -v  /build/DB/mysql:/var/lib/mysql --name mysqlDB -e MYSQL_DATABASE=anonymous_board -e MYSQL_USER=user01 -e MYSQL_PASSWORD=user01 -e MYSQL_ROOT_PASSWORD=password -p 3306:3306 mysql
            docker run -d --rm --name app --network test -p 8080:8080 ${{ secrets.DOCKER_ID }}/cicd:latest
  • docker pull : 도커 허브에서 내가 runner로 올린 docker image를 받아온다.

  • docker tag ~ : 받아온 이미지에 태그를 명시 한다.

  • docker stop server : 기존에 존재하던 도커를 중지해준다.

  • docker network create : spring과 mysql이 같은 space에 동작하도록 network를 생성해준다.

  • docker run mysql : 도커에서 제공해주는 mysql을 사용한다.

  • docker run app : 내가 받아온 image를 실행해준다.


🌈 확인

인스턴스 공개IP의 8080포트로 접속해보자.

위의 사진대로 나오면 성공!


🌈 Docker-Compose

Docker-Compose를 EC2로 전달하고 해당 Docker-Compose를 실행해준다.

Flow는

- name: Deploy
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.AWS_HOST }} # EC2 인스턴스 퍼블릭 DNS
          username: ubuntu
          key: ${{ secrets.AWS_SSH_KEY }} # pem 키--
          # 도커 작업
          script: |
            docker pull ${{ secrets.DOCKER_ID }}/cicd:latest
            docker tag app ${{ secrets.DOCKER_ID }}/cicd:latest
            docker stop server
            docker compose-up -d
            

이렇게 수정해준다.

그리고 docker-compose는 다음과 같이 구성되어있다.

version: "3"
services:
  mysqlDB:
    image: mysql
    container_name: mysqlDB
    environment:
      - MYSQL_DATABASE=anonymous_board
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_USER=user01
      - MYSQL_PASSWORD=user01

    ports:
      - 3306:3306
    networks:
      - test

  app:
    build: .
    ports:
      - 8080:8080
    restart: on-failure
    depends_on:
      - mysqlDB
    networks:
      - test
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://mysqlDB:3306/anonymous_board?serverTimezone=UTC&characterEncoding=UTF-8
      SPRING_DATASOURCE_USERNAME: user01
      SPRING_DATASOURCE_PASSWORD: user01

networks:
  test:

완료


🌈 에러

아마 구현하면서 flow를 돌리면 다음의 에러를 만날 것이다.

🐳 SSH 정책 문제

ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain

이 에러는 우분투 최신 버전에서 ssh에 대한 기본 정책때문에 생긴 에러이다.

다음의 경로로 이동해서 설정값을 하나 추가해주자!

경로 : /etc/ssh/sshd_config

해당 파일을

sudo vi sshd_config

하여, 다음의 라인을 추가해주자.

PubkeyAcceptedKeyTypes=+ssh-rsa

해당 문제 깃 이슈 페이지

🐳 도커 권한

도커 권한 때문에 에러가 난다.

다음의 명령어를 수행하자

sudo groupadd docker

sudo usermod -aG docker ${USER}

이렇게하고 인스턴스를 재부팅 해야 적용이 된다.

끝!

profile
관심많은 영호입니다. 궁금한 거 있으시면 다음 익명 카톡으로 말씀해주시면 가능한 도와드리겠습니다! https://open.kakao.com/o/sE6T84kf

0개의 댓글