Github Action + Elastic Beanstalk으로 CI/CD

길지운·2023년 10월 18일
4

AWS

목록 보기
1/2
post-thumbnail
AWS 배포 관련 포스팅 주제
Github Action + Elastic Beanstalk으로 CI/CD
deploy.yml 뜯어보기 (예정)
AWS Route 53 / ACM 을 활용한 사설 도메인 연결 및 HTTPS 적용 (예정)
AWS RDS 설정 (예정)
AWS S3 설정 (예정)
AWS 프리티어 과금 요소 / 배포 과정에서의 트러블 슈팅 (예정)

Elastic Beanstalk 설정

  • 들어가기 전에..
    틀린 정보에 대한 지적을 해주신다면 감사하겠습니다!

1. 환경 구성

환경 생성

  • 토이프로젝트로 웹/앱 개발을 하는 것이므로 기본값인 '웹 서버 환경'을 선택
  • 애플리케이션 이름 및 환경 이름, 도메인 등은 알아서 적당한 이름을 작성

환경 구성 1
환경 구성 2

  • 필자는 Spring 백엔드 코드를 배포할 것이기 때문에 플랫폼을 Java로 선택
  • 사전 설정을 반드시 '사용자 지정 구성' 선택

환경 구성 3

2. 서비스 액세스 구성

  • 이 단계에 앞서, 서비스 역할(IAM) 생성 필요

IAM 이란?
↳ AWS의 리소스에 대한 접근제어, 권한을 "개별적으로" 가지도록 계정이나 그룹을 생성, 관리하는 서비스
↳ 처음 AWS 계정 생성 시 루트 사용자 하나가 AWS의 모든 서비스 및 리소스에 대한 권한을 갖게 됨 ➡ 보안 취약

IAM을 사용함으로써 얻는 이점
↳ 암호나 access key의 공유 없이 AWS 계정의 특정 리소스 관리 및 사용 권한을 타인에게 부여 가능, 권한의 세분화 등

2단계 1

2-1. IAM 생성

  • 잠시 Beanstalk 진행상황에서 멈추고, IAM에 들어가서 역할 생성 진행

IAM 1

  • 신뢰할 수 있는 엔터티는 기본값인 'AWS 서비스' 선택

IAM 2

  • Elastic Beanstalk을 통해 EC2 인스턴스에 배포를 진행해야 하므로 Elastic Beanstalk에 대한 권한을 갖는 역할이 필요 (EB 2단계의 "서비스 역할" 해당)
  • 따라서 서비스 또는 사용 사례에 'EC2' 선택

IAM 3

  • 다음 단계로 넘어가면 권한을 추가할 수 있는 창 표시
  • 아래의 권한들을 추가
    AWSElasticBeanstalkEnhancedHealth, AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy

IAM 4

  • 역할명, 설명 작성 후 역할 생성

IAM 5

  • 역할이 생성되면 해당 역할 정보에 들어가서 신뢰 정책 편집

IAM 6

  • 다음과 같이 수정 후 정책 업데이트
    "Service":"ec2.amazonaws.com" ➡ "elasticbeanstalk.amazonaws.com"
  • Elastic Beanstalk으로 자동 배포를 진행해야하므로 Elastic Beanstalk이 역할을 가질 수 있도록 하는 과정

IAM 7

  • 마찬가지 방법으로 EC2에 대한 권한을 갖는 역할을 하나 더 생성 (EB 2단계의 "EC2 인스턴스 프로파일" 해당)
  • 과정은 동일하며, 2단계에서 다음의 권한들을 추가
    AWSElasticBeanstalkMulticontainerDocker, AWSElasticBeanstalkWebTier, AWSElasticBeanstalkWorkerTier
  • EC2에 대한 신뢰정책이므로 이 역할에 대해서는 역할 생성 후 신뢰 정책을 따로 편집해 줄 필요 X

IAM 8

2-2. EB 2단계

  • IAM을 생성한 뒤, 다시 EB 2단계로
  • 앞서 만든 두 개의 역할을 각각 선택, EC2 키 페어(암호) 선택

2단계 2

3. 네트워킹, 데이터베이스 및 태그 설정(선택사항)

  • 선택사항이지만, 한 걸음 더 나아가 VPC 설정까지 진행

VPC 란?
↳ 사용자만의 "가상 네트워크", AWS 클라우드의 다양한 사용자들로부터 논리적으로 분리된 독자적인 네트워크 환경을 가질 수 있음

VPC 1

- 목표1 : Blue Green 배포를 위해, Public Subnet을 생성
- 목표2 : RDS는 Private Subnet으로 두고 이 RDS에 접근할 수 있는 Bastion host를 따로 두어, 외부로부터 RDS 접근을 할 수 없으며 관리자만 Bastion host를 통해 RDS에 접근할 수 있도록 보안 강화

VPC 2

3-1. VPC 생성

  • 잠시 Beanstalk 진행상황에서 멈추고, VPC에 들어가서 진행

VPC 3

지원하는 VPC 대역 및 서브넷 마스크 지정 후 VPC 생성

VPC 4

VPC - 인터넷 게이트웨이 생성

VPC 5

VPC - 서브넷 생성
가용영역을 다르게 선택해서 두개 생성

VPC 6

VPC - 라우팅 테이블 생성

VPC 7

VPC의 리소스 맵이 다음과 같이 구성되면 성공

VPC 8

3-2. EB 3단계

  • VPC 작업을 완료한 뒤, 다시 EB 3단계로
  • 앞서 만든 VPC를 선택

3단계 1

  • 앞서 구성한 두 개의 서브넷 선택

3단계 2

  • RDS 설정도 한번에 진행할 수 있지만, 이 글의 양이 너무 많은 관계로 EB 생성한 뒤 따로 설정과정 진행

4. 인스턴스 트래픽 및 크기 조정 구성

  • 기본값으로 설정

4단계 1

  • EC2 인스턴스의 보안 그룹에 대한 설정 필요
    보안그룹 이란?
    ↳ EC2 인스턴스로 들어오고 나가는(인바운드, 아웃바운드) 트래픽에 대한 제어

4-1. EC2 보안그룹 생성

  • 잠시 Beanstalk 진행상황에서 멈추고, EC2 - 보안그룹에 들어가서 진행
  • 보안그룹 생성

보안그룹 1

  • 인바운드 규칙을 다음과 같이 설정
    MYSQL/Aurora (필자가 DB로 MySQL을 사용) : TCP : 3306
    사용자 지정 TCP : TCP : 8080
    HTTP : TCP : 80
    HTTPS : TCP : 443
    SSH : TCP : 22
  • 소스를 0.0.0.0/0 으로 지정 시 모든 IP주소를 의미

4-2. EB 4단계

  • EC2 보안그룹 생성을 완료한 뒤, 다시 EB 4단계로
  • 앞서 만든 보안그룹 선택

4단계 2

  • 환경 유형 : '밸런싱된 로드', 인스턴스 : '1' ~ '2'
  • 밸런싱된 로드가 즉 무중단 배포를 의미하게 됨, 토이 프젝 수준이므로 1개의 배포된 인스턴스가 최소이며 Blue Green 배포를 위해서는 최대 2개의 인스턴스 필요

4단계 3

  • 인스턴스 유형 : 't3.micro' 제외하고 모두 삭제 (초기에는 t3.micro, t3.small이 기본값으로 지정되어 있음)
  • t3.micro 인스턴스만 프리티어 제공 (다른 인스턴스 쓰면 과금!)

4단계 4

  • 기본값으로 설정

4단계 5

  • 앞서 생성해두었던 VPC 서브넷 선택

4단계 6

  • 리스너의 경우, HTTPS 등을 사용할 때 추가 필요

4단계 7

  • 프로세스에서, HTTP : 80일 때 상태 확인 경로를 /health로 지정 (추후 /health API 생성하면 배포가 정상적으로 이루어졌는지 확인하는 용도로 사용) 후 아래의 나머지 설정은 기본값으로 설정

4단계 8

5. 업데이트, 모니터링 및 로깅 구성(선택 사항)

  • 기본값으로 설정

5단계 1

  • 관리형 업데이트 비활성화 (기본값 : 활성화이므로 체크 해제)

5단계 2

  • 배포 정책 : "추가 배치를 사용한 롤링" (기본값 : 한번에 모두)
  • 추가 배치를 사용한 롤링 > 무중단 배포를 의미

5단계 3

  • 환경 속성에서 "PORT : 8080" 추가
  • 기본값은 5000이며, 필자는 SPRING을 사용하므로 SPRING 기본 포트로 지정

5단계 4

6. 검토

  • 1~5의 사항을 확인 후 제출하면 EB 완성

Github Action과 연결

1. IAM 사용자 생성

  • AWS의 IAM - 사용자에 들어가서 사용자 생성
    Github Action이 배포를 위해 Elastic Beanstalk에 접근할 수 있도록 하는 사용자를 만드는 과정

11단계 1

  • 1단계에서는 이름 기입
  • 2단계 권한 설정 - 권한 옵션 : "직접 정책 연결" 후 다음의 권한 정책 추가
    AdministratorAccess-AWSElasticBeanstalk
  • 3단계 검토 후 사용자 생성

11단계 2

  • 생성한 사용자 정보 - 보안 자격 증명 - 액세스 키 에서 액세스 키 만들기
  • "AWS 외부에서 실행되는 애플리케이션" 선택
    2단계 태그(선택사항) 기본값으로 설정

11단계 3

  • 3단계에서 액세스 키 발급됨
  • .csv 파일 다운로드 시 액세스 키, 비밀 액세스 키를 로컬에 파일로 저장

11단계 4

2. Github Action 설정

  • 연결할 깃허브 레포 - Settings - Secrets and variables - Actions

12단계 1

  • New Repository Secret 후 앞서 받았던 두 개의 키 값을 적당한 키 이름과 함께 저장

12단계 2

3. 배포 파일 생성

  • 필자는 Spring을 사용 중이므로 IntelliJ 환경을 캡처
  • 디렉토리, 파일이 없는 경우 생성

3-1. MakeFile 생성

  • 다음 경로에 아래의 코드를 추가

13단계 1

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 -jar $JAR_PATH

3-2. Nginx 설정 파일 생성

  • 다음 경로에 아래의 코드를 추가

13단계 2

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 127.0.0.1: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;
  }
}

3-3. Procfile 생성

  • 프로젝트 최상위폴더에 아래의 코드 추가

13단계 3

web: appstart

3-4. Deploy 파일 생성

  • 다음 경로에 아래의 코드를 추가
  • Github Action이 동작할 때 실제로 이 파일의 명령 순서로 동작하기 때문에 파일의 자세한 내용은 추가 포스팅 진행 예정

13단계 4

name: carGive Dev CI/CD

permissions:
  checks: write
  pull-requests: write

on:
  pull_request:
    types:
      [ opened, synchronize, reopened, closed ]
  workflow_dispatch: # (2) 수동 실행

jobs:
  test:
    runs-on: ubuntu-latest # (3) OS환경
    env:
      S3_BUCKET_NAME: ${{secrets.S3_BUCKET_NAME}}
      S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}}
      S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}}
    if: startsWith(github.head_ref, 'feature/')

    steps:
      - name: Checkout
        uses: actions/checkout@v2 # (4) 코드 check out : 스프링부트 프로젝트의 소스코드를 내려받는다

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17
          distribution: adopt-hotspot # (5) 자바 설치

      - name: Cache Gradle packages
        uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash # (6) 권한 부여

      - name: Build with Gradle
        run: ./gradlew clean build
        shell: bash # (8) build 시작

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss
          utcOffset: "+09:00" # (9) build 시점의 시간확보

      - name: Show Current Time
        run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}"
        shell: bash # (10) 확보한 시간 보여주기

      - name: Code Test
        run: ./gradlew test
        shell: bash # (11) 테스트 코드 실행

      - name: Publish Unit Test Results
        uses: EnricoMi/publish-unit-test-result-action@v1
        if: ${{ always() }}  # 테스트가 실패하여도 Report를 보기 위해 `always`로 설정
        with:
          files: build/test-results/**/*.xml

      - name: Cleanup Gradle Cache
        if: ${{ always() }}
        run: |
          rm -f ~/.gradle/caches/modules-2/modules-2.lock
          rm -f ~/.gradle/caches/modules-2/gc.properties
  build:
    runs-on: ubuntu-latest # (3) OS환경
    env:
      S3_BUCKET_NAME: ${{secrets.S3_BUCKET_NAME}}
      S3_ACCESS_KEY: ${{secrets.S3_ACCESS_KEY}}
      S3_SECRET_KEY: ${{secrets.S3_SECRET_KEY}}
    if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop'

    steps:
      - name: Checkout
        uses: actions/checkout@v2 # (4) 코드 check out : 스프링부트 프로젝트의 소스코드를 내려받는다

      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: 17 # (5) 자바 설치
          distribution: adopt-hotspot

      - name: Cache Gradle packages
        uses: actions/cache@v2
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant execute permission for gradlew
        run: chmod +x ./gradlew
        shell: bash # (6) 권한 부여

      - name: Build with Gradle
        run: ./gradlew clean bootjar -Pprofile=prod
        shell: bash # (8) build 시작

      - name: Get current time
        uses: 1466587594/get-current-time@v2
        id: current-time
        with:
          format: YYYY-MM-DDTHH-mm-ss
          utcOffset: "+09:00" # (9) build 시점의 시간확보

      - name: Show Current Time
        run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}"
        shell: bash # (10) 확보한 시간 보여주기

      - 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}}
          aws_secret_key: ${{secrets.AWS_SECRET_KEY}}
          application_name: carGiveBeanstalk
          environment_name: CarGiveBeanstalk-env
          version_label: github-action-${{steps.current-time.outputs.formattedTime}}
          region: ap-northeast-2
          deployment_package: deploy/deploy.zip
          wait_for_environment_recovery: 60

      - name: Cleanup Gradle Cache
        if: ${{ always() }}
        run: |
          rm -f ~/.gradle/caches/modules-2/modules-2.lock
          rm -f ~/.gradle/caches/modules-2/gc.properties

결과

  • 이제 다음과 같이 Github Action을 통해 무중단 배포까지 이어짐

21단계 1
22단계 2

  • 참고로 EB 설정의 4-2 마지막 부분에서 /health로 상태 확인 경로를 지정해주었기에, 아래와 같이 API를 만들어둔 모습

22단계 3

profile
Copyright 2022. JUG All rights reserved.

0개의 댓글