Github Action과 배포 자동화

2024. 11. 29. 10:56Server

이때 동안 AWS EC2에 배포를 진행 할때 Gradle로 빌드를 진행 한 뒤
Docker Image로 만들고

그걸 Dokcer Hub에 올린 뒤

AWS EC2 인스턴스에서 Docker Hub에서 Image Pull 받은 뒤 배포하는 방식으로

수작업을 통해서 배포를 진행했다.

 

그런데 이렇게 수작업으로 진행하니 귀찮아서 점점 주기가 늘어나 이제는 자동화를 할 때라고 생각하게 되었다.

 

그래서 어떻게 자동화를 하나 알아보니

무료 툴은 Github Action과 Jenkins이 많이 보였고

 

그 중 클라우드에서 동작해서 호스팅을 따로 해줄 필요가 없는 Github Action을 선택하게 되었다.

 

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle

name: Java CI with Gradle

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

jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      contents: write

    steps:
    - uses: actions/checkout@v4
    - name: Set up JDK 21
      uses: actions/setup-java@v4
      with:
        java-version: '21'
        distribution: 'temurin'
    - name : Make application.properties
      run: |
         cd ./src/main/resources
         touch ./application.properties
         touch ./jwt.properties        
         echo "${{ secrets.PROPERTIES }}" > ./application.properties
         echo "${{ secrets.JWT_KEY }}" > ./jwt.properties
      shell: bash

    - name: Decode and create keystore.p12
      env:
        KEY_P12_BASE64: ${{ secrets.SSL_KEY }}
      run: |
        cd ./src/main/resources
        touch ./keystore.p12
        echo -n "$KEY_P12_BASE64" | base64 --decode > ./keystore.p12
      shell : bash
    
    - name: give permission
      run : chmod +x ./gradlew
      
    - name: Build with Gradle Wrapper
      run: ./gradlew build

    - name: docker image build
      run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc .

    - name: docker login
      uses: docker/login-action@v2
      with:
          username: ${{ secrets.DOCKERHUB_USERID }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}

    - name: docker Hub push
      run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc

        # GitHub Actions VM 환경의 IP를 받아온다.
    - name: Get Public IP
      id: ip
      uses: haythem/public-ip@v1.3

# AWS 인증 관련 옵션을 추가한다.
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: 'ap-northeast-2'

# GitHub Actions VM 환경의 IP를 인바운드 규칙에 추가한다.
    - name: Add GitHub Actions IP
      run: |
        aws ec2 authorize-security-group-ingress \
        --group-id ${{ secrets.SECURITY_GROUP_ID }} \
        --protocol tcp \
        --port 22 \
        --cidr ${{ steps.ip.outputs.ipv4 }}/32

    - name: Deploy to EC2
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.EC2_HOST }}
        username: ${{ secrets.EC2_SSH_USER }}
        key: ${{ secrets.EC2_PRIVATE_KEY }}
        script: |
          CONTAINER_ID=$(sudo docker ps -q --filter "publish=8080-8080")
          
          if [ ! -z "$CONTAINER_ID" ]; then
            sudo docker stop mvccon
            sudo docker rm $CONTAINER_ID
          fi
          sudo docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
          sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc
          sudo docker run --name mvccon -d -p 8080:8080 -e TZ=Asia/Seoul --add-host host.docker.internal:host-gateway ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc

      # GitHub Actions VM 환경의 IP를 인바운드 규칙에서 제거한다.
    - name: Remove GitHub Actions IP
      run: |
        aws ec2 revoke-security-group-ingress \
        --group-id ${{ secrets.SECURITY_GROUP_ID }} \
        --protocol tcp \
        --port 22 \
        --cidr ${{ steps.ip.outputs.ipv4 }}/32

 

하나 하나 알아보도록 하겠다.

 

 

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

해당 구문은 Github Action의 동작 조건을 설정하는 것이다.

위 구문은 main 브랜치에 push나 pull request가 일어날 때 동작하도록 했다.

 

 

runs-on: ubuntu-latest
permissions:
  contents: write

 

최신 우분투에서 돌아가고 권한은 쓰기 권한을 주었다 기본적으로 쓰기 권한은 읽기 권한을 포함하고 있기 때문에 읽기 권한은 따로 설정하지 않아도 된다

 

 

- uses: actions/checkout@v4
- name: Set up JDK 21
  uses: actions/setup-java@v4
  with:
    java-version: '21'
    distribution: 'temurin'

현재 작업 공간으로 데이터를 복사해오고 자바를 설치해준다

 

- name : Make application.properties
  run: |
     cd ./src/main/resources
     touch ./application.properties
     touch ./jwt.properties        
     echo "${{ secrets.PROPERTIES }}" > ./application.properties
     echo "${{ secrets.JWT_KEY }}" > ./jwt.properties
  shell: bash

 

application.properties파일을 만들어 준다

기본적으로 해당 파일에는 노출되면 안 되는 비밀 번호 등이 있어서 .gitignore파일에 등록해두었다.

따라서 git의 관리를 받고 있지 않고 빌드할때 포함되지 않았다.

그래서 따로 만들어주어야 하는데 이때 쓰는 것이 Secrets이다.

 

Setting에 들어가면 비밀로 하지만 사용해야 하는 데이터를 이렇게 등록해서 사용할 수 있다

 

 

- name: Decode and create keystore.p12
  env:
    KEY_P12_BASE64: ${{ secrets.SSL_KEY }}
  run: |
    cd ./src/main/resources
    touch ./keystore.p12
    echo -n "$KEY_P12_BASE64" | base64 --decode > ./keystore.p12
  shell : bash

 

마찬가지로 가지고 있는 SSH 인증서도 base64로 인코딩해서 Secrets에 저장한 다음 decode해서 인증서를 생성해 주었다.

 

- name: give permission
  run : chmod +x ./gradlew
  
- name: Build with Gradle Wrapper
  run: ./gradlew build

 

이제 Gradle을 이용해서 Spring Boot 프로젝트를 빌드해준다.

 

 

- name: docker image build
  run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc .

 

그걸 원하는 태크를 가진 Docker Image로 빌드하고 

 

- name: docker login
  uses: docker/login-action@v2
  with:
      username: ${{ secrets.DOCKERHUB_USERID }}
      password: ${{ secrets.DOCKERHUB_PASSWORD }}

 

Docker에 로그인한 뒤

 

- name: docker Hub push
  run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc

 

Docker Hub에 Push한다

 

 

- name: Get Public IP
  id: ip
  uses: haythem/public-ip@v1.3

 

그 후 현재 Public Ip를 받아왔는데 이는 AWS EC2의 보안 설정 때문이다.

 

AWS EC2에 SSH로 접속하기 위해서는

기본적으로 보안 그룹을 통해 IP를 등록해주어야 하는데

Github Action은 고정 IP에서 동작하지 않기 때문에

아예 모든 접속에 대해서 SSH 포트를 열어주거나

그 때마다 등록을 해주어야 한다.

 

당연히 모든 접속에 대해서 열면 보안성이 떨어지므로 동작 할 때마다 IP를 등록하고 삭제하도록 설정했다.

 

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: 'ap-northeast-2'

 

EC2에 접속하기 위해서는 AWS에 EC2 접속용 User를 만든다

 

권한은 EC2 Full Access하나만 준다

 

 

그 후 액세스 키를 만든다 

 

이제 이 액세스 키를 Secrets에 등록해서 Github Action에서 AWS EC2에 접속할 수 있다.

 

 

- name: Add GitHub Actions IP
  run: |
    aws ec2 authorize-security-group-ingress \
    --group-id ${{ secrets.SECURITY_GROUP_ID }} \
    --protocol tcp \
    --port 22 \
    --cidr ${{ steps.ip.outputs.ipv4 }}/32

 

보안 그룹에 아까 얻은 IP를 22번 포트에서 허가한다.

 

- name: Deploy to EC2
  uses: appleboy/ssh-action@master
  with:
    host: ${{ secrets.EC2_HOST }}
    username: ${{ secrets.EC2_SSH_USER }}
    key: ${{ secrets.EC2_PRIVATE_KEY }}
    script: |
      CONTAINER_ID=$(sudo docker ps -q --filter "publish=8080-8080")
      
      if [ ! -z "$CONTAINER_ID" ]; then
        sudo docker stop mvccon
        sudo docker rm $CONTAINER_ID
      fi
      sudo docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_PASSWORD }}
      sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc
      sudo docker run --name mvccon -d -p 8080:8080 -e TZ=Asia/Seoul --add-host host.docker.internal:host-gateway ${{ secrets.DOCKERHUB_USERNAME }}/server:springmvc

 

이제는 SSH를 통해 AWS EC2 Instance에 접속할 수 있기 때문에

SSH로 접속하고 기존 실행중인 컨테이너를 멈추고 삭제 한 뒤

Docker Hub에 로그인 한 뒤

아까 올린 이미지를 Pull하고

실행시켜준다.

 

- name: Remove GitHub Actions IP
  run: |
    aws ec2 revoke-security-group-ingress \
    --group-id ${{ secrets.SECURITY_GROUP_ID }} \
    --protocol tcp \
    --port 22 \
    --cidr ${{ steps.ip.outputs.ipv4 }}/32

 

이제 마지막으로 아까 보안 그룹에 등록한 IP를 삭제해주고 끝이다.

'Server' 카테고리의 다른 글

Nginx Load Balancing  (0) 2024.03.12
JWT  (0) 2024.01.13
Http의 한계와 WebSocket  (0) 2023.08.16
Session, Cookie, Token  (0) 2023.08.16
Winsock2.h를 이용한 TCP 통신  (0) 2023.08.11