본문 바로가기

인프라 & 배포

무중단 CI/CD (Github Action + Elastic Beanstalk)

계속해서 배포에 실패하는 나···

다시 처음부터 차근차근 기록해가면서 놓친 부분 찾기 시작 !!

 

 

VPC 


vpc 생성

총 4개의 서브넷 (public 2개, private 2개) 를 만들 예정

 

public 2개 - Elastic Beanstalk이 요구

private 2개 - RDS에서 서브넷 그룹 생성 시 필요

 

 

 

서브넷 생성

public, private 각각 가용영역 a와 c에 하나씩 만들어준다

 

 

 

 

인터넷 게이트웨이 생성

인터넷 게이트웨이 생성 후 위에서 만든 VPC에 할당

 

 

인터넷 게이트웨이만 연결했다고 해서 퍼블릭 서브넷이 되는 것은 아님

라우팅 테이블 생성 필요

 

라우팅 테이블 생성

위에서 만든 VPC에 대한 라우팅테이블 생성 후

라우팅 편집 - 아웃바운드 설정해주기!

 

0.0.0.0/0 : 모든 ip 주소에 대해서

밖으로 나갈때 인터넷 게이트웨이를 통해 나가도록

 

 

그리고 라우팅 테이블 - 서브넷 연결 - 명시적 서브넷 연결에서

아까 만든 퍼블릭 서브넷 2개 연결해주기

 

 

보안 그룹 생성

보안그룹을 생성하고 인바운드 규칙을 추가

 

여기까지 하면 VPC 설정 끝!

 

 

CI/CD

CI : Continuous Integration 지속적 통합

CD: Continuous Delivery/Deployment 지속적 제공/배포

 

CI를 깃허브액션, CD를 Elastic Beanstalk을 이용해 구현할 것이다

 

 

브랜치 전략

개발 인프라 - develop 브랜치에 merge될 때 CI/CD가 돌아가야 함

릴리즈 인프라 - release 브랜치에 merge될 때 CI/CD가 돌아가야 함

 

* 프로젝트 초반에는 개발 인프라만 만들고 마지막에 릴리즈 인프라 만들기

 

 

프로젝트 최상단에 .github/workflows 디렉토리 생성 후

dev_deploy.yml 파일 생성

 

dev_deploy.yml 전체 코드

name: VitaMate Dev CI/CD

on:
  pull_request:
    types: [closed]
  workflow_dispatch: # (2).수동 실행도 가능하도록

jobs:
  build:
    runs-on: ubuntu-latest # (3).OS환경
    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'

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

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

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

      - name: Show Current Time
        run: echo "CurrentTime=$"
        shell: bash # (9).확보한 시간 보여주기

      - name: Generate deployment package
        run: |
          mkdir -p deploy
          cp build/libs/*.jar deploy/application.jar
          cp Procfile deploy/Procfile
          cp -r .ebextensions_dev 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_ACTION_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_ACTION_SECRET_ACCESS_KEY }}
          application_name: # Elastic Beanstalk 어플리케이션 이름
          environment_name: # Elastic Beanstalk 환경 이름
          version_label: github-action-${{ steps.current-time.outputs.formattedTime }}
          region: ap-northeast-2
          deployment_package: deploy/deploy.zip
          wait_for_deployment: false

 

 

on:
  pull_request:
    types: [closed]

PR이 closed 되었을때 돌아라! -> merge될 때 돌아라!

 

jobs:
  build:
    runs-on: ubuntu-latest # (3).OS환경
    if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'develop'

아무데나 merge 될 때 돌면 안되니까

develop 브랜치에 merge될 때 돌아라!

 

* 릴리즈 만들땐 release로 수정

 

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

이부분에서 Procfile, .ebextensions, .platform 등이 필요하다는걸 알 수 있음

일단 홀드하고 이제 Elastic Beanstalk 만들러 다시 AWS로 넘어가자

 

 

IAM - 역할 생성


Elastic Beanstalk이 정상적으로 작동하기 위해서는 두 가지 역할이 필요하다

1. Elastic Beanstalk 자체의 권한

 

2. Elastic Beanstalk이 만들 Ec2 권한

 

1. Elastic Beanstalk 자체 역할 생성

 

권한 정책은

AWSElasticBeanstalkEnhancedHealth

AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy

 

이렇게 두 개 선택

역할 이름 적고

설명 부분 꼭 넣기!!

 

신뢰 정책 건너뛰고 역할 생성

역할 생성 후 -> 신뢰 정책 편집

"Service": "elasticbeanstalk.amazonaws.com" 로 변경

 

2. EC2 역할 생성

앞 단계 동일

 

권한 정책은 

AWSElasticBeanstalkMulticontainerDocker

AWSElasticBeanstalkWebTier

AWSElasticBeanstalkWorkerTier

 

이렇게 3개 선택

 

Elastic Beanstalk 만들기


환경 생성

* 환경 이름은 알아서 만들어준다

 

플랫폼은 Java 17

 

 

사전 설정 - 사용자 지정 구성! ("무중단" 배포를 위해)

 

* 키 페어는 미리 만들어놓은 것

 

만들어둔 VPC 선택하고

 

중요!! 

퍼블릭 IP 주소 "활성화됨" 체크

 

인스턴스 서브넷은 public 2개 만들었던 것 체크

 

 

 

위에서 만든 보안그룹 체크

 

중요!!

오토스케일링 그룹 - 밸런싱된 로드

인스턴스 최솟값 1, 최댓값 2

* 최댓값 1로 하면 중단 배포가 됨

 

 

인스턴스 유형은 t3.micro만 남겨두자

* t3.small -> 돈나감

 

 

 

health check!

 

ec2안에 있는 웹서버가 잘 동작하는지 체크하는 것

-> GET 요청을 보내서 응답이 오는지 체크해봄

 

이 요청을 GET / 이 아닌 GET /health 로 보내도록 변경하자

 

+ /helath API 구현 해야함

 

모니터링 - 상태보고 "강화됨" 체크

 

관리형 업데이트 "비활성화"

 

"추가 배치를 위한 롤링" 선택 - 무중단 배포를 위함

 

환경 속성에

PORT 8080 추가

 

로드 밸런싱

외부 요청 시 뒤에 있는 여러 서버들 중 누구에게 갈지를 결정

 

Elastic Beanstalk의 로드밸런서는

Elastic Beanstalk의 WAS가 5000번 포트에 있을것이라고 생각함 (디폴트로 5000번 포트로 설정되어 있음)

 

Spring은 8080번 포트를 사용하니까 변경해주기

 

Elastic Beanstalk 생성 완료!

 

 


프로젝트 최상단에 필요한 파일, 디렉토리 추가

 

 

Procfile 파일 생성

web: appstart

 

 

.ebextensions-dev 디렉토리 생성

 

.github/workflows/dev_deploy.yml 파일에서 아래 표시한 부분에 디렉토리명 일치해야함!

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

cp -r .ebextensions-dev deploy/.ebextensions

 

 

 

.ebextensions-dev/00-makeFiles.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 -jar $JAR_PATH

 

.ebextensions-dev/01-set-timezone.config

commands:
    set_time_zone:
        command: ln -f -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime

 

 

.platform/nginx.conf 생성

 

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

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

      location / {
          proxy_pass          http://springboot;
                  # CORS 관련 헤더 추가
          add_header 'Access-Control-Allow-Origin' '*';
          add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
          add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
          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;
  }
}

 

build.gradle에 추가

 

 

IAM- 사용자 생성


AdministratorAccess-AWSElasticBeanstalk

권한 정책 추가하고 사용자 생성

 

 

방금 만든 사용자 -> 액세스키 만들기

 

깃허브에 액세스키 등록할거니까 "AWS 외부에서 실행되는 애플리케이션" 선택

 

 

설명 태그값 넣고 액세스 키 만들기

 

그리고 이 다음에 나오는 액세스 키, 비밀 키는 깃허브 액션에 추가 (페이지 벗어나면 비밀키 다시 못봄 !!)

 

Health Check API 만들기

 

이제 커밋 & 푸시 후

develop 브랜치에 merge 

 


502 Bad Gateway 에러 발생

 

Elastic Beanstalk 로그 파일을 다운받아서 var/log/messages 파일을 열어 'error'를 검색해서 찾아보았다

Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: 2024-07-08 05:16:49 WARN EC2RoleProvider Failed to connect to Systems Manager with instance profile role credentials. Err: retrieved credentials failed to report to ssm. RequestId: 4a822542-1bcf-40cd-b54c-8d2d393e92c8 Error: AccessDeniedException: User: arn:aws:sts::851725497504:assumed-role/aws-elaticbeanstalk-ec2-role/i-0be0bbe0ebd2fd37b is not authorized to perform: ssm:UpdateInstanceInformation on resource: arn:aws:ec2:ap-northeast-2:851725497504:instance/i-0be0bbe0ebd2fd37b because no identity-based policy allows the ssm:UpdateInstanceInformation action
Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: #011status code: 400, request id: 4a822542-1bcf-40cd-b54c-8d2d393e92c8
Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: 2024-07-08 05:16:49 ERROR EC2RoleProvider Failed to connect to Systems Manager with SSM role credentials. error calling RequestManagedInstanceRoleToken: AccessDeniedException: Systems Manager's instance management role is not configured for account: 851725497504
Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: #011status code: 400, request id: 80e71b90-be83-49a5-9942-39781a2efe2b
Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: 2024-07-08 05:16:49 ERROR [CredentialRefresher] Retrieve credentials produced error: no valid credentials could be retrieved for ec2 identity. Default Host Management Err: error calling RequestManagedInstanceRoleToken: AccessDeniedException: Systems Manager's instance management role is not configured for account: 851725497504
Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: #011status code: 400, request id: 80e71b90-be83-49a5-9942-39781a2efe2b
Jul  8 05:16:51 ip-172-31-13-31 amazon-ssm-agent[1567]: 2024-07-08 05:16:49 INFO [CredentialRefresher] Sleeping for 28m51s before retrying retrieve credentials

 

IAM 역할 권한 부족 문제라고 한다

EC2 인스턴스에 할당된 IAM 역할이 'ssm:UpdateInstanceInformation' 작업을 수행할 권한이 없어서 발생하는 오류

 

권한을 추가해주자!

 

AmazonSSMManagedInstanceCore 정책 추가

 

실패ㅜㅜ

 

다시 로그파일에서 에러 찾기,,

Jul  8 18:54:13 ip-10-0-2-29 web[2087]: 2024-07-08T18:54:13.812+09:00 ERROR 2087 --- [           main] j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.RuntimeException: Driver org.mariadb.jdbc.Driver claims to not accept jdbcUrl, jdbc:mysql://${AWS_DB_URL}:3306/vitamate
Jul  8 18:54:13 ip-10-0-2-29 web[2087]: 2024-07-08T18:54:13.814+09:00  WARN 2087 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.RuntimeException: Driver org.mariadb.jdbc.Driver claims to not accept jdbcUrl, jdbc:mysql://${AWS_DB_URL}:3306/vitamate
Jul  8 18:54:13 ip-10-0-2-29 web[2087]: 2024-07-08T18:54:13.817+09:00  INFO 2087 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
Jul  8 18:54:13 ip-10-0-2-29 web[2087]: 2024-07-08T18:54:13.842+09:00  INFO 2087 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger :
Jul  8 18:54:13 ip-10-0-2-29 web[2087]: Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
Jul  8 18:54:13 ip-10-0-2-29 web[2087]: 2024-07-08T18:54:13.877+09:00 ERROR 2087 --- [           main] o.s.boot.SpringApplication               : Application run failed
Jul  8 18:54:13 ip-10-0-2-29 web[2087]: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.RuntimeException: Driver org.mariadb.jdbc.Driver claims to not accept jdbcUrl, jdbc:mysql://${AWS_DB_URL}:3306/vitamate

 

문제 원인

환경 변수 미설정!!! 나 바보냐?

Elastic Beanstalk 새로 만들고 나서 환경변수 넣는걸 깜빡했다ㅜㅜ

 

이제 502 Bad Gateway는 뜨지 않는데

기본 Elastic Beanstalk 화면만 뜬다

 

 


let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });