🏭 Infra/🥑 AWS

[AWS] Elastic Beanstalk + Github Actions 사용해서 Spring boot CI/CD 파이프라인 구축하기

gengminy 2022. 9. 21. 23:34

동아리 스터디의 일환으로 시작된

Beanstalk 으로 CI/CD 파이프라인 구축하기

 

처음 써보는거라 확실히 어렵지만

EC2 생성하고 RDS 생성하고 https 설정해주고... 하던 시절과 비교하면

확실히 배포 속도가 엄청 빠르긴 하다

 

물론 그 과정에서 삽질을 엄청나게 하긴 했지만...

 

하지만 결론적으로는 빌드 시간이 많이 오래 걸리기도 하고

배포서버와 개발서버 그리고 혹시 모르지만 웹까지

하나의 EC2 환경에서 배포하고 싶어서

JAR 환경에서 Docker 환경으로 이동할 것이다

 

JAR 이나 WAR 로 배포하는 것은 물론 좋지만

여러 EC2 인스턴스를 사용하기엔 돈이 모자란 ^^.... 가난한 대학생에겐

하나의 EC2에서 멀티 컨테이너로 돌리는게 더 나을 것이라는 판단이다

 

그래도 공부한게 아까우니까 까먹기 전에 과정을 올려본다

 

 

 

🚀 Elastic Beanstalk 환경 생성

 

Elastic Beanstalk 은 EC2 를 한번 더 추상화 한 개념이다

하나의 환경 내에 여러 EC2 인스턴스를 두고 Auto Scaling 으로 관리도 할 수 있다

S3나 RDS, ELB 등 여러 요소를 하나의 환경에서 관리할 수 있다

 

환경 -> 새 환경 생성 -> 웹 서버 환경 선택하고 다음으로

 

 

어플리케이션 이름 지어주면

환경 이름은 첫 문자를 대문자로 바꾼 것에 -env 가 붙어서 자동으로 만들어진다

물론 바꿀 수도 있다

 

아래 도메인은 기본 도메인이 생성되는데

나중에 구매해서 연결할 도메인이랑 별개이니까 놔둬도 상관 없다

 

 

관리형 플랫폼 -> Java -> 자바 버전 선택 -> 플랫폼 버전 선택

환경에서 사용할 플랫폼을 지정해준다

 

JAR 파일을 그대로 사용하려면 Java 플랫폼을 선택해주면 된다

Docker, Go, Node.js, Python 등등 웹서버로 많이 활용하는 플랫폼도 당연히 있다

 

Java 는 8, 11, 17이 지원되는데

보통 8이나 11버전을 많이 사용하더라

 

나는 간지나게 새삥 17을 선택했다

 

그 다음 중요한 추가 옵션 구성으로 들어가준다

 

 

새 인스턴스를 생성하고 삭제하는 것을 통해 CI 환경을 만들기 때문에

단일 인스턴스 대신 사용자 지정 구성을 선택

 

그리고 EC2 프리티어 기준이

한달 EC2의 총합 사용 시간이 750시간을 안넘기면 되기 때문에

여러 EC2 인스턴스가 존재하는 것은 사실 상관이 없다

어차피 배포가 끝나면 바로 삭제되기 때문

 

 

그리고 EC2 보안 그룹을 설정해야 하는데

환경 구성 메뉴에서는 설정을 할 수가 없다

 

그러니까 EC2 서비스 -> 보안 그룹으로 가서 설정해야한다

새 탭 열어서 HTTP, HTTPS 포트 여는 등 설정을 끝내고 다시 오자

 

 

용량 설정에서는 Auto Scaling 을 중요하게 보면 된다

왜냐면 돈과 직결되는 부분이기 때문

 

최소 최대 인스턴스를 모두 1로 설정해주면

Auto Scaling 이 동작하지 않아서

새 EC2 인스턴스가 생기지 않기 때문에 과금 우려가 없어진다

 

 

인스턴스 유형에서 t2.small 기본 선택이 되어있을텐데

프리티어인 t2.micro 만 빼고 없애버린다

 

우리는 가난한 대학생이니까...

 

 

HTTPS 설정이 필요하다면 리스너 -> 리스너 추가에서

HTTPS 와 SSL 인증서를 달아주면 된다

 

인증서가 없다면 Route 53 서비스를 이용하는 조건으로

무료 인증서를 발급받을 수 있다

(무료라면서 Route 53 에 도메인 하나 연결 당 매달 500원 정도 나온다)

 

 

 

다음으로 중요한 롤링 업데이트와 배포 설정

배포 방식을 추가 배치를 사용한 롤링으로 바꿔준다

 

배포할 때마다 새 EC2 인스턴스를 생성한 후 배포 작업을 진행하며

배포 작업이 끝나면 로드밸런서가 새 EC2 인스턴스를 가리키게 하여

CI 환경을 구성하는 것이다

이후 구 버전 EC2 인스턴스를 삭제하여 배포 작업을 끝낸다

 

 

그 다음 중요한 보안 수정

 

서비스 역할과 IAM 인스턴스 프로파일은 IAM 서비스에서 정의하고

EC2 키 페어는 EC2 서비스로 들어가서 만들어야 한다

 

이미 있다면 선택 후 저장

 

없다면 IAM 서비스로 진입

 

IAM -> 엑세스 관리 -> 역할 -> 역할 만들기 메뉴로 진입해서

위와 같은 권한을 주었다

 

AdministratorAccess 만 추가했을 때는

권한이 없다면서 안되어서 몇 개 더 넣었더니 되더라

 

 

그 다음은 사용자 추가

엑세스 키를 선택하고 이름 지정

 

 

나는 이 2가지의 권한을 주었다

 

다 만들면 이렇게 엑세스 키 ID 와 비밀 엑세스 키가 제공되는데

이 다음에는 더 이상 값을 보여주지 않기 때문에 이를 메모해두자

 

Github Actions 정의할 때 필요하다

 

여기까지 하고 환경 생성

 

문제가 없다면 이런식으로 상태에 파란 불이 들어와야 한다

보통 처음에 안되는건 업로드한 서버 파일 문제 또는 권한 문제

 

 

📚 Beanstalk 배포 세팅

Beanstalk 배포 설정을 위해서 내 프로젝트에 파일을 추가해야 한다

 

📝 .ebextensions/00-makeFile.config

files:
    "/sbin/appstart" :
        mode: "000755"
        owner: webapp
        group: webapp
        content: |
            #!/usr/bin/env bash
            JAR_PATH=/var/app/current/application.jar

            # run app
            killall java
            java -Dfile.encoding=UTF-8 -Dspring.profiles.active=$SPRING_PROFILE -jar $JAR_PATH

Beanstalk 내부에서 배포 작업 중 실행할 명령어를 정의했다

 

현재 실행중인 자바를 종료하고 배포시키는 명령어이다

 

 

SPRING_PROFILE 이라는 환경 변수는 Beanstalk 의 소프트웨이 -> 환경 속성에 정의했다

 

📝 ./Procfile

web: appstart

 

아까 .ebextensions 에서 정의한 명령어를 배포 작업이 종료되면 즉시 실행한다

 

 

 

📝 .platform/nginx/nginx.conf

user                    nginx;
error_log               /var/log/nginx/error.log warn;
pid                     /var/run/nginx.pid;
worker_processes        auto;
worker_rlimit_nofile    33282;

events {
    use epoll;
    worker_connections  1024;
    multi_accept on;
}

http {
  include       /etc/nginx/mime.types;
  default_type  application/octet-stream;

  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

  include       conf.d/*.conf;

  map $http_upgrade $connection_upgrade {
      default     "upgrade";
  }

  upstream springboot {
    server localhost:8080;
    keepalive 1024;
  }

  server {
      listen        80 default_server;
      listen        [::]:80 default_server;

      location / {
          proxy_pass          http://springboot;
          proxy_http_version  1.1;
          proxy_set_header    Connection          $connection_upgrade;
          proxy_set_header    Upgrade             $http_upgrade;

          proxy_set_header    Host                $host;
          proxy_set_header    X-Real-IP           $remote_addr;
          proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
      }

      access_log    /var/log/nginx/access.log main;

      client_header_timeout 60;
      client_body_timeout   60;
      keepalive_timeout     60;
      gzip                  off;
      gzip_comp_level       4;

      # Include the Elastic Beanstalk generated locations
      include conf.d/elasticbeanstalk/healthd.conf;
  }
}

Nginx 설정 정의 파일

 

Elastic Beanstalk은 기본적으로 Nginx 를 통해 호스팅 해주는데

Nginx 의 특장점중 하나인 리버스 프록시를 통해

80번 포트 -> 8080 포트로 전달해주는 설정이다

 

Beanstalk 에 내장된 Nginx 는

기본 설정으로 localhost:5000 포트로 전달해주기 때문에

재정의를 통해 8080 포트로 전달해주도록 하는 것이다

 

 

 

 

 

🚀 Github Actions 정의

📝 .github/workflows/deploy.yml

name: beanstalk-springboot-deploy

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3

      #JDK Setting
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      #Create dotenv file
      - name: Make env properties
        run: |
          touch ./src/main/resources/application-dev.yml
          touch ./src/main/resources/application-prod.yml
          echo "$ENV_PROPERTIES_DEV" > ./src/main/resources/application-dev.yml
          echo "$ENV_PROPERTIES_PROD" > ./src/main/resources/application-prod.yml
        env:
          ENV_PROPERTIES_DEV: ${{ secrets.ENV_PROPERTIES_DEV }}
          ENV_PROPERTIES_PROD: ${{ secrets.ENV_PROPERTIES_PROD }}

      #Grant gradlew Permission
      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash

      - name: Build with Gradle
        run: ./gradlew clean build
        shell: bash

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss
          utcOffset: "+09:00"

      - name: Show Current Time
        run: echo "CurrentTime=$"
        shell: bash

      - name: Generate deployment package
        run: |
          mkdir -p deploy
          cp build/libs/*.jar deploy/application.jar
          cp Procfile deploy/Procfile
          cp -r .ebextensions deploy/.ebextensions
          cp -r .platform deploy/.platform
          cd deploy && zip -r deploy.zip .

      - name: Beanstalk Deploy
        uses: einaregilsson/beanstalk-deploy@v20
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: naechinso
          environment_name: Naechinso-env-1
          version_label: github-action-${{ steps.current-time.outputs.formattedTime }}
          region: ap-northeast-2
          deployment_package: deploy/deploy.zip

github actions 는 반드시 루트 폴더의 .github/workflows 내부에 정의해야 한다

나는 민감한 변수들을 application.yml 을 나누어 관리하기 때문에

빌드 과정에서 가상 환경에 생성하는 방식으로 만들었다

 

마지막 application_name 과 environment_name 은

아까 beanstalk 에서 구성한 어플리케이션 이름과 환경 이름을 넣어주면 된다

 

secrets 변수들은 내 레포지토리 -> setting -> secrets -> actions 에서 관리한다

 

여기에 아까 만든 IAM 프로파일 값을 넣어주고 사용한다

 

 

 

설정에 따라 main 브랜치에 푸시를 하면 액션 실행

배포가 진행된다

 

 

✨ Log 보는 법

Elastic Beanstalk 이 이렇게 설정하는 것이 간단하지만

답답한 것중 하나가 로그를 직접 보지 못한다는 것...

 

로그는 환경 -> 로그에서 로그 요청을 한 후에 다운받아서 볼 수 있다

다만 이것도 배포 과정 중에 실시간으로 볼 수 없고

기다렸다가 아니면 중지해야지만 볼 수 있다

 

이래서 나는 너무 추상화 된 것을 안좋아한다...

 

 

이제 Docker 로 새 환경 구성하러 가야겠다