인프라

github actions를 통한 CI/CD 구축 (+ 도커)

EVO. 2024. 2. 1. 02:52

CI(Continuous Integration): 지속적 통합, 배포 가능한 아티팩트(Jar / Image)를 빌드하는 단계 

CD(Continuous Delivery/Deployment): 지속적 배포, 실제 환경에 아티팩트를 배포하는 단계

 

CI/CD 파이프라인을 구축해야 하는 이유

파이프라인은 소스코드에서 시작해서 배포 환경 관리까지의 모든 프로세스를 자동화하는 것을 의미한다. 파이프라인이 없을 경우 사람이 직접 빌드 및 배포를 수행해야하고, 휴먼 에러가 발생하고 표준화가 어려워진다.

 

GitHub Actions 장점

- 깃허브는 파이프라인을 구성하고 자동화할 수 있는 깃허브 액션을 제공한다.

- 깃허브에 소스코드를 푸시하면 깃허브 액션에서 CI/CD 파이프라인을 자동으로 실행시킬 수 있다.

- 깃허브 액션을 사용하면 개발자의 PC나 별도의 빌드용 서버가 없이 파이프라인을 실행할 수 있다.

 


시작

간단한 스프링 프로젝트를  CI/CD 파이프라인 구축하는 것이 목표이다. 처음 구상한 내 CI/CD 아키텍처는 다음과 같다.

 

 

 

 

총 2개의 워크플로우를 작성할 생각이다. 

 

첫번째는 PR에 대한 테스트 워크플로우 : 이 워크플로우는 코드가 병합되기 전에 모든 테스트가 통과하는 지 확인한다. 

두번째는 main 브랜치에 push 할때 테스트,도커이미지 빌드, 도커 레포지토리에 푸시, CD 자동화 워크 플로우 : 이 워크플로우는 main 브랜치에 병합될 때 실행된다.  테스트를 통과한 코드가 도커이미지로 빌드되고, 이 이미지가 도커 레포지토리에 업로드되며, 이를 통해 배포 과정이 자동화 된다. 

 

AWS 인스턴스 생성하기

ec2 프리티어 t2.micro 스팩은 CPU 1GHz, 메모리 1Gib이고 OS(linux cli), docker 설치 후 가용 메모리는 약 330MB라 한다. 스왑메모리까지 설정하면 가능할것 같아서 일단 해보겠다. 

 

ec2 생성은 다음 글을 참고하면 되고 생성은 넘어가겠다.

https://babgeuleus.tistory.com/entry/%EB%A7%9D-%EA%B5%AC%EC%84%B1%ED%95%B4%EB%B3%B4%EA%B8%B0

 

망 구성하기(외부망/내부망)

이번 글은 목차대로 원하는 곳에 가서 보는 방식의 구성을 하기가 조금 어려웠다. 결국 이번엔 위를 수행해야 아래를 수행할 수 있는 실습 방식으로 글을 썼다. VPC 생성 EC2 인스턴스들이 그냥 서

babgeuleus.tistory.com

 

중요! 인바운드 규칙 생성은 다음과 같이 열어두었다. 추가적으로 사진에는 지워서 표현이 안됐지만 ssh-action을 사용하기 때문에 깃허브 서버의 IP가 ssh에 접속할수 있도록 잠깐 모든 IP에 ssh접속이 되도록 추가했다가 액션이 성공하면 인바운드 정책에서 지우는 방식으로 했다. (이 방식은 귀찮고 자동으로 IP를 인바운드 정책에 추가하는 워크플로우 작성하는 방법이 또 있다. 하지만 ssh을 열어두고 pem키와 함께 접속하는 방식은 보안면에서 아주 안좋은 방식이기 때문에 다음에는 ssh-action을 쓰지 않을 생각이다.)


우분투에 도커 설치

 

Step1. 패키지 리포지토리 업데이트

sudo apt update

 

Step2. 필수 패키지 설치: 적절한 패키지 관리자는 HTTPS를 통해 패키지를 사용하려면 필수 패키지를 설치하여 Ubuntu가 HTTPS를 통해 Docker 저장소에 엑세스할 수 있도록 다음 명령을 실행한다.

sudo apt install apt-transport-https ca-certificates curl software-properties-common -y

 

Step3. GPG 키 추가: GPG키는 소프트웨어 패키지의 신뢰성을 확인한다. 

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

 

Step4. Docker 저장소 추가: 공식 Docker 저장소를 apt 소스에 추가 

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

Step5. apt-cache 패키지 캐시를 쿼리 (이건 무슨 말인지 아직 잘 모르겠다)

apt-cache policy docker-ce

 

Step6. 도커 설치

sudo apt install docker-ce -y

 

Step7. Docker 상태 확인

sudo systemctl status docker

 

Step8. 권한 부여

sudo chmod 666 /var/run/docker.sock

 

Step9. 테스트

-> `docker version` `docker ps` `docker image ls` 

 

 

Dockerfile 작성

깃허브 액션의 workflows에 gradle 설치하고 jdk 설치하고 빌드까지 하는 모든 과정을 적을 수 있다.  하지만 나는 우분투러너 위에 docker buildx를 설치하고 도커파일에 적힌대로 이미지를 빌드할 생각이라서 Dockerfile위에 그 과정을 수행할 생각이다.

ubuntu기반의 ec2를 사용할 것이기 때문에 dockerbuildx가 어떤 플랫폼을 만들어야할지 인지할 수 있도록 platform을 추가하도록 하자 

멀티스테이지 빌드로 이미지 사이즈를 줄였다. 참고로 도커 컴포즈 파일은 스프링부트만 띄울 생각이기 때문에 작성안할 생각이다. 다음에 젠킨스를 사용하면서 도커컴포즈까지 구성해보도록 하겠다. (Nginx와 SpringBoot의 컨테이너를 한꺼번에 띄울 계획)


 

GitHub-Actions 스크립트 파일 생성하기

다음은 main 브랜치에 PR을 하거나 푸시를 할 때 Trigger을 줘서 이미지 빌드를 하고 도커 레지스트리에 푸시한다음 SSH 연결을 통해 EC2 인스턴스에 도커의 이미지를 가져와 배포할 스크립트를 작성해보겠다. 

 

name: Backend Build and Push and deploy

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
    paths:
      - '/**'
      # 지금은 현재 경로로 작성되어있지만 .github와 프로젝트를 다른 폴더에 위치시키자.. 안그러면 yml만 수정하고 푸시해도 자꾸 워크플로우 돌게된다.

jobs:
  build:
    runs-on: ubuntu-latest
    # 가장 최신의 Ubuntu 러너를 사용한다.

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v3
      # 현재 리포지토리를 체크아웃한다.
    
    - name: Setup Docker Buildx
      uses: docker/setup-buildx-action@v3
      # Docker Buildx를 설정한다

    - name: Login to Docker Hub
      uses: docker/login-action@v3.0.0
      with: 
        username: ${{secrets.DOCKERHUB_USERNAME}}
        # Github Secret에서 DockerHub 사용자 이름을 가져온다.
        
        password: ${{secrets.DOCKERHUB_TOKEN}}
        # Github Secret에서 DockerHub 액세스 토큰을 가져온다.

    - name: Build and Push
      uses: docker/build-push-action@v2
      with:
        context: ./  
        # Dockerfile이 있는 위치
        file: ./Dockerfile  
        # Dockerfile의 경로
        push: true  
        # 이미지를 레지스트리에 푸시.
        tags: ${{ secrets.DOCKERHUB_USERNAME }}/sum-backend:${{ github.sha }}  
        platforms: linux/amd64,linux/arm64,windows/amd64

    - name: WAS 인스턴스 접속 및 애플리케이션 실행 
      uses: appleboy/ssh-action@v1.0.3
      with:
        host: ${{ secrets.HOST_DEV }}
        username: ${{ secrets.WAS_USERNAME }}
        key: ${{ secrets.WAS_KEY }}
        port: 22
        script: |
          docker rm -f $(docker ps -qa)
          docker pull ${{ secrets.DOCKERHUB_USERNAME }}/sum-backend:${{ github.sha }}
          docker run -d -p 8080:8080 --name sum-backend ${{ secrets.DOCKERHUB_USERNAME }}/sum-backend:${{ github.sha }}

 

 

Actions secret 등록

DOCKERHUB_USERNAME: 도커허브사용자명

DOCKERHUB_TOKEN: 도커허브토큰

HOST_DEV: EC2 인스턴스의 IP

WAS_USERNAME: EC2 유저이름으로 EC2 인스턴스에서 whoami 명령으로 확인 가능하다.

WAS_KEY: .pem파일의 키 

 

등록하면 다음과 같다.

 

 

 

 


 

소스코드 배포하기

말이 안되는 코드니 가볍게 무시하자 (그냥 배포가 됐는지 확인하는 용도이다)

 

main 브랜치로 바로 푸시해보자. 그러면 이제 깃허브액션란에서 막 돌기 시작한다.

 

참고로 내 로컬 PC에서 먼저 이미지 빌드 테스트를 해보고 잘되면 푸시는게 좋다. 신기한점이 로컬에서 이미지 빌드할때는 3분넘게 걸렸는데 러너에서 이미지 빌드를 시키니 51초 밖에 안걸린다. 주노가 알려준 Self-hosted-Runner를 써볼까 싶었는데 내 맥보다 더 빠르니 안그래도 될것같다 ㅎ 

 

테스트

이렇게 build job이 성공하면 직접 접속해보자. 

 

내 ec2 ip로 경로와 함께 접속을 해보면 성공적으로 반환이 되었습니다.

 

 

지금까지 도커파일, 워크플로우를 작성하여 이미지 빌드부터 푸시 후 ec2에서 pull까지 전 과정(CI/CD)를 거쳤다. 진행하면서 platform 설정안해서 no match for platform in manifest: not found 오류가 생긴점. SSH접속을 모든 IP허용하지 않아서 dial tcp ***:22:i/o timeout 걸린점. 8080 포트 인바운드 정책 추가 안해서 IP에 접속했는데 응답시간이 오래 걸린점. 이런 이슈를 겪었다.