Github Actions + Docker + AWS

김준영·2023년 6월 20일
1

Pre-Project

목록 보기
2/3

서론


Pre-Project 스택오버플로우 클론을 진행하면서 프론트엔드와 협업을 진행했고, API 통신을 위해 배포를 진행해야했다.
배포를 진행할 때 선택할 수 있는 선택지가 2가지가 있었다.

  • ngrok을 통한 임시 도메인 & 로컬 배포
  • AWS를 사용하여 배포

이 두 가지 중 하나를 선택하여야했다. 간단한건 첫 번째이지만 추후 Main-Project를 들어가서 배포를 진행하기 위해선 두 번째가 필수아닌 필수라고 생각했다.

AWS를 사용한 배포도 여러가지가 있다.

  1. AWS + S3 + Code Deploy
  2. AWS + S3 + Github Actions
  3. AWS + Docker + Github Actions
  4. etc...

위보다 더 많겠지만 내가 아는건 여기까지라 세 가지 중에 하나를 선택해서 배포를 진행하고 환경을 구성해야했다.

그래서 Docker를 EC2에서도 사용해보고 싶고, CI/CD에서 활용해보고 싶어 3번을 선택했다.

최소한으로 과금을 줄이고 AWS를 적게 사용하고 싶기도 했다. 전에 임시로 하나 만들고 삭제했는데 통장에서 300원씩 나갔다... 상당히 신경쓰였다..

일단 서론은 여기서 마치고

많은 블로그들을 참조하여 간단하게 배포를 진행할 것이다.

틀린 부분들도 있을 수 있다. 그땐 댓글로 조언을 해주시면 감사하겠습니다.
물론 제가 또 까먹고 다시 구글링을 할 경우를 대비하여 작성하는 글입니다!
그래서 최대한 쉽게 작성할 계획입니다!

순서는

  1. 애플리케이션 프로젝트 생성 및 도커 레포 생성
  2. AWS EC2 생성 및 docker & docker compose 설치
  3. Github actions 설정
  4. EC2 docker 설정
  5. 테스트

본론

1. 애플리케이션 생성 & 도커 레포 생성


1번 애플리케이션 생성에선 Spring Boot를 통해 애플리케이션을 생성할 것입니다. 다른 애플리케이션을 생성하는 프레임워크를 사용해도 됩니다.. 워낙 간단하게 만들 것입니다.

1.1 애플리케이션 생성

  • 언어는 자바로 진행
  • 스프링 부트 버전은 2.7.12로 진행 (그 외 버전을 사용하셔도 됩니다. 다만 3버전 이상으로 가면 자바 17이 default로 되서 전 2.7.12버전으로 진행하였습니다.)
  • 메타데이터는 간단하게 작성해주시면 됩니다.
  • Dependencies에서 간단한 테스트를 진행하기 위해 Spring Web & Lombok을 선택하였습니다.
  • 다 선택하시면 GENERATE를 눌러 파일을 받아주시고 압축해제 후, IDE를 통해 들어가주시면 됩니다.

1.2 애플리케이션 구성

  • 간단하게 구성하기 위해 @RestController 어노테이션을 붙여줍니다.
  • @GetMapping과 간단한 String을 클라이언트로 응답하는 코드를 작성합니다.

프로젝트 폴더 구조입니다.
여기서 Dockerfile이라는 이름으로 파일을 생성 후 밑에 코드를 작성합니다.

FROM openjdk:11
ARG JAR_FILE=build/libs/cicdTest-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

도커파일은 이미지를 빌드하기 위해 작성한 파일이고 이 이미지를 기반으로 컨테이너가 실행됩니다.

  • base이미지를 openjdk:11로 만듭니다.
  • JAR_FILE에 빌드된 파일의 위치를 등록합니다.(*.jar 말고 위 파일명을 입력한 이유는 스프링에서 빌드를 진행하면 파일이 2개가 생깁니다. 그래서 정확한 위치와 파일을 작성했습니다.)
  • JAR_FILE 위치에 있는 파일을 app.jar로 카피합니다. (해당경로 파일 -> 컨테이너의 app.jar)
  • 컨테이너에서 java -jar /app.jar 실행

즉, '프로젝트 빌드 파일을 실행하겠다' 입니다.

여기서 openjdk:11로 제가 만든 프로젝트는 11번이므로 다른 버전을 사용하시면 위 버전을 맞게 변경하셔야합니다.

애플리케이션을 구성 및 생성을 다하시면 깃헙 저장소에 등록 및 푸시를 한번해주세요! 추후 깃헙 레퍼지토리를 만들고 연결하기 귀찮아서 IDE에서 깃헙과 연결하여 간단하게 만들고 푸시까지 진행하였습니다.

1.3 Docker hub

도커를 올릴 허브를 만들기 위해 새로운 레포지토리를 만들겁니다.
간단하므로 글로 설명하겠습니다.

  1. 회원가입 및 로그인
  2. create repository를 선택하여 뒤 레포지토리 이름 작성해주시고 생성누르시면 끝입니다. 예를들어 junyoungs7/repo라는 이름으로 계속 사용할 것입니다.

2. EC2 생성 및 Docker & Compose 설치

이 부분은 구글링을 조금만하시면 많이 찾을 수 있는 부분이라 간단하게 알려드립니다.

2.1 인스턴스 생성

검색을 통해 ec2 인스턴스 창으로 넘어갑니다.
인스턴스 생성 혹은 시작을 통해 만들 준비를 합니다.

작성할 부분은 아주 간단하게 두 가지정도 됩니다.

인스턴스 이름을 적어줍니다. 아무거나 혹은 사용자 입맛에 맞게 작성해주세요.


저는 프리티어를 사용하기위해 기본 설정으로 되어있는 이 부분은 건들지않고 넘어갔습니다.

키 페어는 꼭 안전하게 보관하셔야합니다. 이 부분도 이름은 각자 알맞게 작성해주시고, RSA 유형에 .pem형식으로 키 페어를 생성해주세요.

스토리지를 프리티어 또한 30GB까지 사용할 수 있어 이 부분은 해도되고 안해도되고? 입니다만 전 했습니다. 앞 빈칸에 30을 작성했습니다.

그 후 인스턴스 시작을 누르시면 인스턴스가 생성됩니다.

2.2 인스턴스 로그인

인스턴스에 로그인하기 위해선 우선 인스턴스 체크박스에서 체크 혹은 인스턴스 ID를 눌러 선택하시고 우측 상단 연결 버튼을 누르시면

이런 창이 나옵니다. 그 후 키 페어를 통해 로그인을 할 것이기 때문에 SSH 클라이언트를 선택 후 밑에 있는 설명을 기반으로 설정 후 로그인을 진행해줍니다.

간단하게 설명하면 (전 맥으로 진행했습니다.)

  1. 터미널에서 다운받은 키 페어를 chmod를 통해 파일의 권한을 변경 후,
  2. ssh -i "키 페어가 있는 경로" ec2-user@{퍼블릭 DNS}를 입력하여 로그인을 합니다.

2.3 EC2에 docker 설치

sudo yum update -y

위 명령어를 통해 인스턴스에 있는 모든 패키지를 업데이트했습니다.

sudo yum install docker -y

도커를 설치하고

docker -v

도커 버전 확인하고

sudo service docker start

도커 실행하고

sudo usermod -aG docker ec2-user

도커 그룹에 sudo 추가하여 인스턴스 접속 후 도커를 바로 제어할 수 있도록 했습니다.

인스턴스를 재접속한 후

docker run hello-world

를 통해 도커의 실행을 테스트하였습니다.

2.4 docker compose 설치

sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

위 명령어를 통해 Docker Compose 설치 스크립트를 다운로드합니다.

sudo chmod +x /usr/local/bin/docker-compose

다운로드한 파일에 실행 권한을 부여합니다.

docker-compose --version

설치가 완료되었는지 확인합니다.

이제 Docker Compose가 EC2 인스턴스에 설치되었습니다. Docker Compose를 사용하여 애플리케이션의 컨테이너 구성을 정의하고 실행할 수 있습니다. 일반적으로 docker-compose.yml 또는 비슷한 이름의 YAML 파일을 생성하고, 컨테이너 및 네트워크 구성, 볼륨 마운트 등을 정의합니다. 그런 다음 docker-compose up 명령을 사용하여 애플리케이션을 실행하고 docker-compose down 명령을 사용하여 중지할 수 있습니다.

2.5 인바운드 설정

포트 8080을 열어주어 테스트시 요청을 보내기위해 설정을 한다.

해당 인스턴스를 밑으로 내리다보면 인바운드 규칙이있는데 규칙을 추가하여 포트범위를 8080과 0.0.0.0/0으로 설정하여 추가한다.

3. Github Actions 설정

이 부분에 들어가기전, 1번에서 만든 애플리케이션을 깃헙 저장소에 등록합니다.
저장소 생성 및 등록은 넘어가겠습니다.

3.1 Actions설정

우선 깃헙으로 넘어가 레포지토리를 확인합니다.

Java with Gradle을 선택해줍니다.

만약 안보이신다면 검색을 통해 선택해주세요.


선택하시면 위 사진처럼 저 경로에 파일이 생기고, 작성할 수 있는 스크립트가 생깁니다. 여기서 바로 작성하셔도 되지만 전 여기서 초록색 버튼 Commit changes를 선택한 후 IDE에서 pull받고 거기서 작성하겠습니다.

여기서 작성하셔도 상관없습니다!! 작성하고 pull받으셔도 됩니다.

요런 폴더 구조가 생기고 클릭해보시면 아까 그대로 스크립트가 작성되어있습니다.

스크립트 작성

# 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" ]

permissions:
  contents: read

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
    # gradle caching - 빌드 시간 향상
    - name: Gradle Caching
      uses: actions/cache@v3
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          ${{ runner.os }}-gradle-
    - name: Build with Gradle
      run: |
           chmod +x ./gradlew
           ./gradlew clean build -x test
    - name: Docker build & push to docker repo
      run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/ci-cd_test .
          docker push ${{ secrets.DOCKER_REPO }}/ci-cd_test:latest
          
    - name: Deploy to server
      uses: appleboy/ssh-action@master
      id: deploy
      with:
       host: ${{ secrets.HOST }}
       username: ec2-user
       key: ${{ secrets.KEY }}
       envs: GITHUB_SHA
       script: |
        sudo docker rm -f $(docker ps -qa)
        sudo docker pull ${{ secrets.DOCKER_REPO }}/ci-cd_test
        docker-compose up -d
        docker image prune -f

나누어 설명드리겠습니다.

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  • push 이벤트가 발생하거나 main 브랜치에 대한 pull_request 이벤트가 발생할 때 워크플로우가 실행됩니다.
permissions:
  contents: read
  • permissions 섹션에서 contents: read 권한을 설정합니다. 이 권한은 리포지토리의 내용을 읽을 수 있는 권한을 나타냅니다
jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
  • build라는 작업을 정의하고, ubuntu-latest 운영 체제에서 실행되도록 설정합니다.
  • actions/checkout@v3 액션을 사용하여 리포지토리의 코드를 체크아웃합니다.
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'temurin'
  • actions/setup-java@v3 액션을 사용하여 JDK 11을 설정합니다.
  • java-version: '11'을 통해 JDK 11을 선택합니다.
  • distribution: 'temurin'은 Temurin 배포판을 사용한다는 것을 나타냅니다.
    - name: Gradle Caching
      uses: actions/cache@v3
      with:
        path: |
          ~/.gradle/caches
          ~/.gradle/wrapper
        key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
        restore-keys: |
          ${{ runner.os }}-gradle-
  • actions/cache@v3 액션을 사용하여 Gradle 캐싱을 설정합니다.
  • path에 Gradle 캐시 디렉토리의 경로를 지정합니다.
  • key는 캐시를 식별하는데 사용되는 값으로, 실행 중인 운영 체제 및 Gradle 파일의 해시 값을 포함합니다.
  • restore-keys는 캐시를 복원하는 데 사용되는 키 목록을 나타냅니다.
    - name: Build with Gradle
      run: |
           chmod +x ./gradlew
           ./gradlew clean build -x test
  • ./gradlew 파일에 실행 권한을 부여한 후, clean build -x test 명령을 실행하여 Gradle로 빌드합니다.
  • -x test는 테스트를 실행하지 않도록 설정합니다.
    - name: Docker build & push to docker repo
      run: |
          docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}/ci-cd_test .
          docker push ${{ secrets.DOCKER_REPO }}/ci-cd_test:latest
  • Docker 이미지를 빌드하고 Docker 저장소로 푸시하는 단계입니다.
  • ${{ secrets.DOCKER_USERNAME }}과 ${{ secrets.DOCKER_PASSWORD }}를 사용하여 Docker에 로그인합니다.
  • Dockerfile을 사용하여 이미지를 빌드하고, 해당 이미지를 ${{ secrets.DOCKER_REPO }}/ci-cd_test:latest로 푸시합니다.
    - name: Deploy to server
      uses: appleboy/ssh-action@master
      id: deploy
      with:
       host: ${{ secrets.HOST }}
       username: ec2-user
       key: ${{ secrets.KEY }}
       envs: GITHUB_SHA
       script: |
        sudo docker rm -f $(docker ps -qa)
        sudo docker pull ${{ secrets.DOCKER_REPO }}/ci-cd_test
        docker-compose up -d
        docker image prune -f
  • 서버에 배포하는 단계입니다. appleboy/ssh-action@master 액션을 사용합니다.
  • ${{ secrets.HOST }}, ${{ secrets.KEY }} 등을 사용하여 SSH 연결을 설정합니다.
  • ${{ secrets.DOCKER_REPO }}/ci-cd_test 이미지를 서버로 가져오고, docker-compose up -d 명령을 사용하여 컨테이너를 시작합니다.
  • 마지막으로 docker image prune -f를 사용하여 불필요한 Docker 이미지를 삭제합니다.

이렇게 주어진 GitHub Actions 스크립트는 Gradle을 사용하여 Java 프로젝트를 빌드하고, Docker 이미지를 빌드하고 푸시한 후 서버에 배포하는 작업을 자동화합니다.

${{ }}로 표시된 것들은
여기서 키-값 형태로 저장하여 사용합니다.

다른 분들은 스크립트에 properties와 같은 설정 파일들을 secret에 저장하여 파일을 copy를 통해 생성하는 경우도 있습니다. 저는 현재 간단하게 배포를 진행 중이고, 숨길 파일 혹은 값들도 없어 위처럼 진행했습니다.

3.2 주의사항

secret에 저장할 때 값들을 잘 넣어주셔야 추후 푸시하고 테스트할때 에러가 나지않습니다. secret에 저장한 후 수정할때에는 원래 알던 수정이 아니라 재입력을 받게되어 어디가 틀렸는지 알기 어렵습니다. 처음 등록할때 제대로 한번 더 더블체크 후 넣어주세요!!! 저 여기서 삽질 엄청 많았습니다!!!

4. EC2 Docker 설정

우선 서버의 루트 디렉토리로 이동합니다.
docker compose를 사용하기 위해서 yml파일을 세팅해줍니다.

version: '3'

  services:

  web:
    container_name: web
    image: junyoungs7/test // 처음 도커허브에 레포지토리를 만든 부분을 써주시면 됩니다.
    expose:
      - 8080
    ports:
      - 8080:8080   
  • web이라는 이름의 서비스를 정의합니다.
  • container_name은 컨테이너의 이름을 지정합니다. 여기서는 web로 설정되어 있습니다.
  • image는 컨테이너가 사용할 Docker 이미지를 지정합니다. junyoungs7/test 이미지를 사용합니다.
  • expose는 컨테이너가 노출할 포트를 지정합니다. 여기서는 8080 포트를 노출합니다.
  • ports는 호스트와 컨테이너 간의 포트 매핑을 정의합니다. 여기서는 호스트의 8080 포트를 컨테이너의 8080 포트로 매핑합니다.

컨테이너는 호스트의 8080 포트와 컨테이너의 8080 포트를 매핑하여 외부로부터 접근할 수 있도록 합니다.

5. 테스트

마지막으로 간단하게 코드를 변경하고 푸시를 한 후, 해당 깃헙 레퍼지토리에 들어가 Actions탭을 클릭해보면

요렇게 푸시가 되고 빌드가 되고 배포가 될것입니다.

5.1 테스트시 에러들

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

라는 오류는 ec2의 /etc/ssh/sshd_config위치에

PubkeyAuthentication yes
PubkeyAcceptedKeyTypes=+ssh-rsa

코드 두줄을 추가한후 ssh server를 재시동해주면 된다.

만약 빌드, 배포, ec2에 image가 저장은 되었는데 실행이 되지 않으면

docker run -d -p 8080:8080 junyoungs7/ci-cd_test를 통해 도커를 실행한다.

혹은

docker-compose.yml의 들여쓰기를 다시 확인하길 바랍니다. 또는

sudo docker rm -f $(docker ps -qa)
sudo docker pull junyoungs7/ci-cd_test
cd /usr/local/bin
sudo docker-compose up -d
docker image prune -f

저도 자동실행이 되지않아 docker-compose파일을 /usr/local/bin에 다시 만들고 스크립트를 위와 같이 디렉토리를 옮기는 식으로 진행했다.

결론


미치는줄 알았다...... 너무 많은 에러들과 싸웠다.... 토 나온다.....

Ref

https://velog.io/@rmswjdtn/Spring-Docker-Github-Action-Spring-Boot-%EC%9E%90%EB%8F%99%EB%B0%B0%ED%8F%AC%ED%99%98%EA%B2%BD-%EB%A7%8C%EB%93%A4%EA%B8%B0#4-docker-compose
https://velog.io/@tilsong/%EC%BD%94%EB%93%9C-Push%EB%A1%9C-%EB%B0%B0%ED%8F%AC%EA%B9%8C%EC%A7%80-Github-Actions-Docker#4-%EB%B9%8C%EB%93%9C-%EA%B2%B0%EA%B3%BC%EB%AC%BC%EC%9D%84-%EB%B0%B0%ED%8F%AC%ED%95%A0-%EC%84%9C%EB%B2%84-%EC%84%B8%ED%8C%85
https://zzang9ha.tistory.com/404#%F0%9F%8C%88%C2%A0-deploy-to-aws-ec2using-ssh
https://velog.io/@leeeeeyeon/Github-Actions%EA%B3%BC-Docker%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-CICD-%EA%B5%AC%EC%B6%95#github-actions-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%8C%8C%EC%9D%BC-%EC%9E%91%EC%84%B1
https://jinjinyang.tistory.com/46

많이도 참조했다...ㅋㅋㅋㅋㅋㅋ

profile
ㅎㅎ

0개의 댓글