본문 바로가기
Cloud/AWS

Github Actions와 Docker Hub를 활용해서 CI/CD 파이프라인 구축하기

by 민휘 2023. 10. 29.

기존 프로젝트를 리팩토링하기 전에 도커, AWS, Github Actions를 이용해 배포 파이프라인을 만들어보려고 한다. 이 글을 그중 두번째인 Github Actions와 Docker Hub로 CI/CD 파이프라인 구축하는 부분을 다룬다.

 

 

이 블로그를 참고하여 만들었습니다. 👍

[CI/CD] Github Actions를 활용한 CI/CD 파이프라인 구축 (+ Docker hub)

 

[CI/CD] Github Actions를 활용한 CI/CD 파이프라인 구축 (+ Docker hub)

Github Actions를 활용한 CI/CD 파이프라인 구축 개념 EC2 Instance 생성, EC2 Instance에 Docker 설치, Docker Hub 회원가입이 되어있다고 가정 Github Repository에 프로젝트의 추가사항이나 변경사항 push 혹은 merge Git

chb2005.tistory.com

 

프로젝트 배포할 때마다 받는 스트레스를 줄이기 위해 도커로 빌드하고 CI CD 파이프라인을 붙여서 프로젝트의 초기 세팅을 하고자 한다. 도커(를 비롯한 컨테이너)를 사용하면 “로컬에서는 됐는데 서버에서는 안돼요” 사태를 막을 수 있다. CI CD 파이프라인을 구축하면 깃헙에 푸시하고 ec2에 접속해서 스크립트를 실행하는 번거로운 작업을 하지 않아도 된다.

 

이전 게시글에서 ec2 인스턴스를 만들고 고정 IP를 할당했고, Hello World API를 찍는 스프링 부트 프로젝트에 도커파일을 작성해 로컬에서 배포해보았다. 이제 Github Action으로 간단하게 파이프라인을 만들어서 깃헙에 머지하면 ec2에 해당 내용이 반영된 프로그램이 뜨도록 만들어보자.

 

구현할 파이프라인

딱 이 블로그의 그림에 나오는 간단한 플로우를 구축한다.

GithubAction, Docker, Ec2를 이용해 배포 자동화

 

GithubAction, Docker, Ec2를 이용해 배포 자동화

푸쉬만 해도 알아서 배포되게 만들어버리자

velog.io

  • Developer : 코드 변경 사항을 깃허브 레포지토리에 푸시(머지나 풀리퀘 등)한다.
  • Github Repo : 커밋을 기반으로 Github Actions 워크플로우를 트리거한다.
  • Github Action : 워크플로우에서 소스 코드를 가져와 빌드, 테스트, 패키징하여 컨테이너 이미지를 만들고 Docker hub에 푸시한다.
  • EC2 : Github Action의 트리거에 의해 Dokcer hub에서 이미지를 가져와서 실행한다.

깃허브 액션에서 벌어지는 많은 일(빌드, 테스트, 패키징, 푸시, ec2 접속해서 스크립트 실행)은 Github Actions의 Configure 파일에 정의한다.

시작하기 전에, 현재 나의 깃허브 레포지토리에 도커파일이 포함된 스프링 부트 프로젝트가 업로드 되어있는 상태이다.

 

 

Github Actions Workflow 정의하기

Configure 파일 추가

우리는 스프링 부트 프로젝트에 내장된 Gradle wrapper script를 사용해서 프로젝트를 빌드하므로 Java with Gradle의 Configure를 선택한다.

 

트리거 이벤트 정의

이 프로젝트는 Github Flow 전략을 따를 것이므로, master 브랜치에 merge가 발생하면 워크플로우가 발동되도록 한다. (혼자 하는 작업인데 브랜치 전략이 무슨 의미가 있겠냐만은.. 깔끔하고 예쁜 그래프를 위해)

Github Actions Doc을 보고 on 조건을 고쳤다. PR이 머지되어 closed된 경우에만 트리거한다.

on:
  pull_request:
    types:
      - closed

jobs:
  build: # job id
    if: github.event.pull_request.merged == true

 

작업 정의

이제 이벤트가 발생하면 트리거할 작업을 정의한다. 깃허브의 체크아웃 액션을 사용해서 원격 레포의 소스 코드를 CI 서버로 다운 받아 빌드하고, 도커 허브에 로그인해서 푸시하고, ec2 인스턴스에 접속해서 도커 허브의 이미지를 땡겨 실행한다.

그 전에 삽질한 내용부터 먼저 공유!

  • 도커 빌드 : COPY build.gradle settings.gradle gradlew $APP_HOME → When using COPY with more than one source file, the destination must be a directory and end with a / 🤔o0(로컬에선 오류 안났는데 여기선 오류 남.. 암튼 고치고 머지했음)
  • Application Run(EC2 접속 부분) : 2023/07/04 11:31:46 dial tcp 3.39.110.9:22: i/o timeout 😩o0(얘가 문제)
  • docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?. 🥺o0(ec2 접속 후 nohup dockerd 추가)

 

으아아아아 i/o timeout 도대체 뭔데!! 누가 로그를 저렇게 성의없이 찍어주는데!!

 

그렇게 끝도 없이 구글링하다가 우연히 이 글을 발견했다.(감사합니다 뉴비 하나를 살리셨어요) 생각해보니 github actions의 IP가 ec2에 접근할 수 있도록 보안 그룹을 설정해주지 않았다. 그러니 당연히 EC2에 접속하지 못하고 timeout이 발생한다.😅

글에 나와있는 대로 우선 Ec2FullAccess 정책을 가진 IAM 유저를 만들고, aws cli를 사용하기 위한 access key를 받는다. 이후 config 파일에서 IAM 권한을 취득하고 github ip를 추가한다. 도커 런을 실행한 후에는 Github IP를 제거한다.

 

시크릿으로 다음 항목들을 추가했다.

  • AWS_ACCESS_KEY_ID : IAM에서 발급받았던 Access Key
  • AWS_SECRET_ACCESS_KEY : IAM에서 발급 받았던 Secret Key (csv)
  • AWS_SG_ID : EC2 보안그룹으로 지정되어있는 보안그룹의 ID

 

최종 Config 파일!

  • Github Action에서 프로젝트 jar와 도커 이미지 빌드
  • Github Action에서 빌드한 이미지를 도커 허브에 푸시
  • EC2에 접근하기 위해 Github IP를 얻어서 보안 그룹에 추가
  • EC2에 접속해서 도커데몬 실행하고 이미지 땡겨서 컨테이너 실행
  • EC2 보안그룹에서 Github IP 제거
on:
  pull_request:
    branches: [ "master" ]
    types:
      - closed

jobs:
  build:
    if: github.event.pull_request.merged == true
    
    # VM의 실행 환경 지정 => Amazon Linux2 AMI (HVM)
    runs-on: ubuntu-latest

    # 실행될 jobs를 순서대로 명시
    steps:
    - name: Checkout
      uses: actions/checkout@v3

    # JDK 17 설치
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'adopt'

    # Gradle Build를 위한 권한 부여
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew

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

    # DockerHub 로그인 
    - name: DockerHub Login
      uses: docker/login-action@v2
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_PASSWORD }}

    # Docker 이미지 빌드 
    - name: Docker Image Build
      run: docker build -t ${{ secrets.DOCKERHUB_USERNAME}}/${{ secrets.PROJECT_NAME }} .

    # DockerHub Push 
    - name: DockerHub Push
      run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}

    # GET GitHub IP 
    - name: get GitHub IP 
      id: ip
      uses: haythem/public-ip@v1.2
      
    # Configure AWS Credentials - AWS 접근 권한 취득(IAM) 
    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with: 
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ap-northeast-2
        
    # Add github ip to AWS 
    - name: Add GitHub IP to AWS
      run: |
        aws ec2 authorize-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

    # EC2 인스턴스 접속 및 애플리케이션 실행 
    - name: Application Run
      uses: appleboy/ssh-action@v0.1.6
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_USERNAME }}
        key: ${{ secrets.EC2_KEY }}

        script: |
          sudo nohup dockerd >/dev/null 2>&1 &
          sudo docker kill ${{ secrets.PROJECT_NAME }}
          sudo docker rm -f ${{ secrets.PROJECT_NAME }}
          sudo docker rmi ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
          sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}

          sudo docker run -p ${{ secrets.PORT }}:${{ secrets.PORT }} \\
          --name ${{ secrets.PROJECT_NAME }} \\
          -d ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.PROJECT_NAME }}
      
    # REMOVE Github IP FROM security group 
    - name: Remove IP FROM security group
      run: |
        aws ec2 revoke-security-group-ingress --group-id ${{ secrets.AWS_SG_ID }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32

 

Projects > Settings > Secrets and variables > Actions에서 위의 Config 파일에서 사용한 환경변수들을 secret repository로 생성한다.

  • DOCKERHUB_USERNAME
  • DOCKERHUB_PASSWORD
  • PROJECT_NAME
  • PORT : 컨테이너를 실행시킬 포트 번호 (ex: 8080)
  • EC2_HOST : AWS EC2 인스턴스의 퍼블릭 IPv4 DNS
  • EC2_USERNAME : AWS EC2 인스턴스의 Username (ex: ec2-user)
  • EC2_KEY : AWS EC2 인스턴스를 생성할 때 저장된 pem 키 내용(BEGIN, END 부분까지 포함. 마지막 %는 포함하지 않음)
  • AWS_ACCESS_KEY_ID : IAM에서 발급받았던 Access Key
  • AWS_SECRET_ACCESS_KEY : IAM에서 발급 받았던 Secret Key (csv)
  • AWS_SG_ID : EC2 보안그룹으로 지정되어있는 보안그룹의 ID

 

8트만에 초록불이 떴다 이예에에!!

 

ec2의 퍼블릭 IP 주소로 웹서버에 hello 요청을 보내면 수정한 내용이 뜬다. 빠밤.

 

이번에는 hello world에 사과 이모티콘을 추가하고 PR 날리고 머지했다. 3분 정도 기다리면 수정한 내용이 배포된 것을 볼 수 있다. 끝!! 이제 자자