[Infra] Spring + EB + Github Actions + Docker + ACM을 통한 CI/CD 자동화 및 https 적용 방법 (feat. 내도메인 한국) - 2편

Hyunjoon Choi·2023년 8월 23일
0

Infra

목록 보기
2/3
post-thumbnail

저번 글에서 RDS와 스프링 프로젝트를 설정했으니, 이제는 CI/CD 작업을 해 보겠다. 사실 이 과정에서도 되게 많이 애를 먹었다.

Docker 설정

도커 환경이 CI/CD에 적합한 이유는 이곳을 참고하자.

도커 허브 계정 및 레포지토리가 없다면 이곳을 참고하자.

Dockerfile 정의

Dockerfile은 애플리케이션을 도커 허브에 올리고 빌드 및 실행할 때 어떤 방식으로 진행할지를 정의하는 파일이다.
프로젝트 루트 위치에 다음과 같이 작성하면 된다.

FROM openjdk:17-jdk # 도커 허브의 openjdk:17-jdk 설치
WORKDIR /app # 도커 디렉터리 이름을 app으로 설정함
EXPOSE 8080 # 사용할 포트 (드러낼 포트)
COPY build/libs/{스프링 프로젝트 이름}-0.0.1-SNAPSHOT.jar /app/app.jar # 빌드 jar 파일을 /app/app.jar로 복사
CMD ["java", "-jar", "app.jar"] # 빌드 후 실행 명령어 지정

Dockerrun.aws.json 정의

Dockerrun.aws.json 파일은 도커 컴포즈 (docker compose)를 사용하지 않고 도커로 배포할 때 작성하는 파일이다. 관련 내용은 이곳을 참고하면 된다. 이것을 사용한 덕분인지는 모르겠으나 Nginx 설정 및 기타 다른 파일들도 모두 작성하지 않을 수 있게 되었다.
나는 아직 도커에 대해 미숙한 상태이기 때문에 이 방식을 활용하였다. 이 파일 또한 프로젝트 루트 위치에 작성해둔다!

{
  "AWSEBDockerrunVersion": "1",
  "Image": {
    "Name": "{도커 ID}/{도커 레파짓 이름}:{도커 태그 이름}",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": 8080,
      "HostPort": 5000
    }
  ]
}

커밋 푸시 까먹지 말자!

AWS IAM 설정

Elasticbeanstalk에 접근할 때, AWS IAM으로부터 발급받은 Access key와 Secret key를 필요로 하기 때문에 이 키들을 발급받아야 한다.

IAM > 사용자 > 사용자 생성

사용자에 다음 정책들을 등록시킨다. (직접 정책 연결)

  • AmazonS3FullAccess
  • AWSElasticBeanstalkWebTier
  • AWSElasticBeanstalkWorkerTier
  • AWSElasticBeanstalkMulticontainerDocker
  • AWSElasticBeanstalkEnhancedHealth
  • AutoScalingFullAccess
  • ElasticLoadBalancingFullAccess
  • AWSElasticBeanstalkRoleRDS
  • AdministratorAccess-AWSElasticBeanstalk
  • AWSElasticBeanstalkManagedUpdatesCustomerRolePolicy

키 획득

사용자를 생성했다면, 보안 자격 증명 > 액세스 키 만들기를 통해 키를 발급받는다.
(AWS 컴퓨팅 서비스에서 실행되는 애플리케이션) 선택

깃허브에 등록

깃허브에 위 값들을 시크릿 변수로 등록해야 한다.
깃허브 레포지토리 > Settings > Security > Secrets and variables > Actions에 등록하자.

도커 허브 비밀번호 등록

변수를 등록하는 김에 도커 허브 비밀번호도 등록해둔다.

AWS Elasticbeanstalk 설정

AWS Elasticbeanstalk 설정을 하자.

보안 그룹 설정

EB에서 사용할 보안 그룹을 설정해둔다. (ec2 > 보안 그룹)

IAM 역할 설정

EB를 사용하기 위해서는 역할 설정이 되어 있어야 한다. 사용자를 만들었을 때 쓴 정책들을 그대로 추가해주자. (신뢰할 수 있는 엔티티 유형은 AWS 서비스, 사용 사례는 EC2)

주의: 역할의 신뢰 관계를 다음과 같이 작성해야 한다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "elasticbeanstalk.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

꽤나 애먹었던 부분이다. 역할에서 신뢰 관계를 elasticbeanstalk, ec2에 대해 모두 정의해야 한다. 그렇지 않으면 접속은 되지만 아래와 같은 오류가 뜰 수 있다.

Service role "arn:aws:am::101677557996:role/elasticbeanstalk" is missing permissions required to check for managed updates. Verify the role's policies.

스택 오버플로우 글을 봤을 때는 ec2를 elasticbeanstalk으로 바꾸면 된다고 했었지만, 에러 메시지가 나오지 않는 대신 도메인에 들어갈 수 없는 오류가 생기게 되었어서 다음과 같은 생각을 해 보았다.

기존에는 IAM 역할을 ec2.amazonaws.com으로 설정했었을 때 권한 관련 에러가 뜬 대신 접속은 잘 되었다.
그리고 지금은 권한 관련 에러가 뜨지 않는 대신 접속이 되지 않는다.
그렇다면, 이 둘을 모두 한꺼번에 적용시키면 문제를 해결할 수 있지 않을까?

때문에 나온 게 위의 결과이다. 결과적으로 잘 되었고, 이 둘을 모두 작성해야 한다.

키 페어 설정

그리고 키 페어 또한 준비되어 있어야 한다. 키 페어 발급 (맥)은 이곳을 참고한다.

나머지 설정

  • 환경 티어: 웹 서버 환경
  • 애플리케이션 이름: 이름 설정
  • 환경 이름: 이름 설정
  • 플랫폼: Docker (Docker running on 64bit Amazon Linux 2023, 4.0.0)
  • 애플리케이션 코드: 샘플 애플리케이션
  • 구성 사전 설정: 단일 인스턴스 (프리 티어 사용 가능)


이후 역할을 기존 서비스 역할, EC2 인스턴스 프로파일에 연결하고, 키 페어를 EC2 키 페어에 연결한다.

참고: 검토 단계로 건너뛰기를 하지 않고 아래와 같이 커스텀으로 설정했더니 EB가 너무 오랫동안 생성되지 않는 이슈 (무한 No Data..) 가 발생했습니다. 아래 설정들은 검토 단계로 건너뛰기를 통해 빠르게 EB 인스턴스를 생성한 다음 구성에서 편집해주시면 됩니다.

네트워킹, 데이터베이스 및 태그 설정 - 선택 사항

퍼블릭 IP 주소를 활성화하고 넘긴다.

인스턴스 트래픽 및 크기 조정 구성 - 선택 사항

  • 보안 그룹을 위에서 미리 정의한 보안 그룹으로 연결한다.
  • 용량 부분에서는 환경 유형을 단일 인스턴스, 플릿 구성은 온디맨드 인스턴스, 아키텍처는 x86_64, 인스턴스 유형은 t2.micro로 했다.

업데이트, 모니터링 및 로깅 구성 - 선택 사항

관리형 플랫폼 업데이트를 해제한 것 말고는 기본값으로 했다.
다만 환경 변수를 등록해야 한다!

환경 변수 등록

SPRING_DATASOURCE_DEIVER_CLASS_NAMEcom.mysql.cj.jdbc.Driver
SPRING_DATASOURCE_PASSWORDDB 비밀번호
SPRING_DATASOURCE_URLjdbc:mysql://{rds 엔드포인트 주소}:{rds 포트}/{rds 데이터베이스 이름}?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
SPRING_DATASOURCE_USERNAME관리자 이름
SPRING_JPA_HIBERNATE_DDL_AUTOnone

다른 글들을 보면 RDS_HOSTNAME, RDS_DB_NAME 이런 식으로 작성한 경우가 많을 것이다. 그러나 나는 그런 방식으로 했을 경우에 incorrect application version expected version elastic beanstalk과 같은 오류가 생겼다. eb-engine.log를 뜯어본 결과, 다음과 같은 로그가 있었다.

2023/08/18 08:09:50.753780 [ERROR] An error occurred during execution of command [config-deploy] - [Track pids in healthd]. Stop running the command. Error: update processes [eb-docker eb-docker-log healthd cfn-hup nginx docker eb-docker-events] pid symlinks failed with error read pid source file /var/pids/eb-docker-log.pid failed with error:open /var/pids/eb-docker-log.pid: no such file or directory

혹시 도커, 즉 띄워진 스프링에서 오류가 난 것인가? 라는 생각이 들었고, eb-docker/containers/eb-current-app에 있는 로그를 뜯어봤다.

2023-08-18T08:09:20.405Z WARN 1 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

원인은 도커로 띄워진 스프링에 있었다. 나는 분명 다른 블로거들과 AWS의 내용을 보면서 RDS_HOSTNAME로 RDS의 엔드포인트를 적고, 나머지 값들을 작성했는데 url이라는 속성을 찾을 수 없다고 나왔다.

그러다 문득, 내가 평소에 application.yml에 작성해두고 있는 형식과 같게 하면 올바르게 인식할 수 있지 않을까? 라는 생각이 들었다. “url”이라는 속성을 보면, application.yml에 있는 spring.datasource.url과 유사하기 때문이다.

이후 위 테이블에 작성한 대로 변경하니 성공할 수 있었다. application.yml에 붙일 추가적인 변수들도 위와 같은 형식으로 작성하면 될 것 같다.

AWS EB 결과

위의 설정대로 진행하면 다음과 같이 기본 EB 페이지가 나온다.

Github Actions 설정

이제 Github Actions 코드를 작성하자. 레포지토리 > Actions > Java with Gradle에서 yml 코드를 작성하면 된다.

name: deploy

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Build with Gradle
        run: ./gradlew build -x test

      - name: Docker build and push to Docker hub
        run: |
          docker login -u 도커_계정_이름 -p ${{ secrets.DOCKER_PASSWORD }}
          docker build -t 스프링_프로젝트_이름 .
          docker tag 스프링 프로젝트 이름 도커_계정_이름/도커_레포지토리_이름:도커_태그
          docker push 도커_계정_이름/도커_레포지토리_이름:도커_태그

      - name: Get timestamp
        uses: gerred/actions/current-time@master
        id: current-time

      - name: Run string replace
        uses: frabert/replace-string-action@master
        id: format-time
        with:
          pattern: '[:\.]+'
          string: "${{ steps.current-time.outputs.time }}"
          replace-with: '-'
          flags: 'g'

      - name: AWS 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: EB_애플리케이션_이름
          environment_name: EB_환경_이름
          version_label: "Version ${{steps.build-number.outputs.BUILD_NUMBER}} deployed via github actions ${{ github.sha }}"
          region: ap-northeast-2
          deployment_package: Dockerrun.aws.json

기다리면 다음과 같이 CI/CD가 잘 이루어진 것을 확인할 수 있다.

이제 다음 편에서는 https를 적용해보겠다.


부족하거나 보완할 점이 있다면 댓글 부탁드립니다 😃

profile
개발을 좋아하는 워커홀릭

1개의 댓글

comment-user-thumbnail
2023년 8월 25일

어제 umc 에서 블로그 주소 받았습니다 글 좋아요!

답글 달기