본문 바로가기

BackEnd : Spring

[AWS] SpringBoot 프로젝트 AWS CodeDeploy + S3 + Github Actions를 이용하여 CI/CD 구축, Docker + EC2 + RDS 로 배포하기

프로젝트 아키텍처

1. EC2 서버 만들기

참고한 글 : https://velog.io/@jonghyun3668/SpringBoot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-EC2-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0

 

SpringBoot 프로젝트 EC2 배포하기

깃헙에 있는 프로젝트를 AWS EC2에 git clone을 통해 내려받아 jar 파일 빌드를 해보겠습니다.(EC2 인스턴스는 우분투로 진행합니다. 또한 따로 배포 시스템을 구축하지 않고 수동으로 배포하는것만

velog.io

 

2. RDS 데이터베이스 생성

Docker로 스프링부트 jar 파일과 mariadb를 이미지화 하여 EC2 서버에 배포 하려고 하였으나,

java.lang.NullPointerException: Cannot invoke "org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(java.sql.SQLException, String)" because the return value of "org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.sqlExceptionHelper()" is null
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 org.hibernate.exception.JDBCConnectionException: Unable to open JDBC Connection for DDL execution [Socket fail to connect to host:address=(host=mariadb)(port=3306)(type=primary). mariadb] [n/a]

mariadb가 연결이 되지 않는 수많은 오류에 마주했었다..

우리가 참고한 자료들은 모두 mariadb 설정 부분이 빠져있었고 팀원들과 상의 후 DB를 RDS에 올리는 게 맞다는 결론을 내렸다.

RDS 프리티어 생성은 다음 글을 참고하였다.

https://velog.io/@dudqls5271/AWS-RDS-%ED%94%84%EB%A6%AC%ED%8B%B0%EC%96%B4-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

[AWS] RDS 프리티어 사용하기

RDS 프리티어로 EC2연결 및 외부 연결 하기

velog.io

 

3. RDS 연결

springboot 프로젝트의 application.properties 코드이다.

spring.application.name=<your-app-name>
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb:<RDS 엔드포인트>/<DB 이름>
spring.datasource.username=사용자이름
spring.datasource.password=패스워드
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

application.name, url, username, password는 각자 데이터베이스 설정에 맞게 바꾸어야 한다.

 

RDS 연결 후에도 Github Actions에서 RDS mariadb 접근을 하지 못하는 오류가 발생했었다.

EC2 인바운드 규칙을 Anywhere-ipv4 로 수정했더니 RDS 접근이 가능해졌다.

Github Actions에서 접근하려면 Github Actions ip주소를 인바운드 규칙에 저장해야 한다고 한다.

https://makethree.tistory.com/19

 

AWS EC2 인스턴스 인바운드 IP 제한 상태에서 Github Action 배포

AWS EC2 인스턴스 인바운드 보안 설정 상태에서 Github Action 배포 자동화하기 일단 먼저 인스턴스를 만드는 방법이 궁금하신 분들은 AWS 프리티어 인스턴스 만들기 에 자세하고 쉽게 설명해놨으니

makethree.tistory.com

IAM 정책 권한 설정

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
                "ec2:AuthorizeSecurityGroupIngress",
                "ec2:RevokeSecurityGroupIngress"
            ],
			"Resource": "arn:aws:ec2:ap-northeast-2:259091037774:security-group/sg-0bbc295bdc0f23bb3"
		}
	]
}

4. CI/CD 구축 과정

https://velog.io/@chanmin/CICD-Github-Actions%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-AWS-EC2%EC%97%90-Spring-Boot-%EB%B0%B0%ED%8F%AC-with-Docker

 

[CI/CD] Github Actions : AWS EC2에 Spring Boot 배포 with Docker

AWS Spring boot 배포부터 CI/CD 구축까지 한방에 끝내기

velog.io

EC2, S3 IAM 역할 설정, CodeDeploy 설정은 위 글을 참고해서 세팅하였다. 내 글은 위 글에서 우리가 수정한 부분들만 거의 담고 있으니 위 글을 참고하길 바란다. 너무 설명이 잘되어있어서 큰 도움이 되었다..

우리는 RDS 데이터베이스S3에 이미지를 저장하는 기능을 구현한 웹페이지를 배포해야 했기 때문에 많은 시행착오를 겪었다.

 

4-1. Github secrets 지정하기

자신의 github repository의 settings에 들어가면 이렇게 secrets and variables를 지정할 수 있다.

우리는 IAM에서 발급한 access key, secret key, aws region를 저장하는 secrets을 생성했다.

또한 따로 S3에 이미지(png, jpg)를 저장하기 위한 AWS S3 계정을 따로 생성해 두었었는데,

기존 프로젝트에서 application-private.properties를 생성하여 gitignore에 저장해 두었던 터라 이 key들을 어떻게 가져올지 고민이 많았었다.

기존에 설정했던 내가 쓴 블로그 정리글 : https://nymagicshop16.tistory.com/110

 

[AWS] SpringBoot + AWS S3에 이미지 업로드하기

AWS S3 버킷 생성내 pc로 접근할 것이기 때문에 모든 퍼블릭 액세스 차단 해제나머지 설정은 건드리지 않음버킷이 생성되었다유저의 프로필 사진들을 저장할 profile_imgs 폴더를 만들어준다IAM 만들

nymagicshop16.tistory.com

 

지피티 선생님이 알려준 방법은 Github secrets에 저장된 key들을 환경변수로 선언하여 application.properties에 전달하는 것이었는데, 작동하지 않았다..

지피티가 알려준 코드였던 것

      - name: Set environment variables for S3 Image Bucket
        run: |
          echo "AWS_IMG_ACCESS_KEY_ID=${{ secrets.AWS_IMG_ACCESS_KEY }}" >> $GITHUB_ENV
          echo "AWS_IMG_SECRET_ACCESS_KEY=${{ secrets.AWS_IMG_SECRET_KEY }}" >> $GITHUB_ENV
          echo "AWS_IMG_REGION=${{ secrets.AWS_IMG_REGION }}" >> $GITHUB_ENV
          echo "AWS_IMG_BUCKET_NAME=${{ secrets.AWS_IMG_BUCKET_NAME }}" >> $GITHUB_ENV
      - name: Create application.properties from secrets
        run: |
          echo "cloud.aws.credentials.secretKey=${AWS_IMG_SECRET_ACCESS_KEY}" >> ./src/main/resources/application.properties
          echo "cloud.aws.region.static=${AWS_IMG_REGION}" >> ./src/main/resources/application.properties
          echo "cloud.aws.bucket.name=${AWS_IMG_BUCKET_NAME}" >> ./src/main/resources/application.properties
          echo "cloud.aws.stack.auto=false" >> ./src/main/resources/application.properties

 

결론적으로 application-properties를 base64로 인코딩하여 (인코딩 링크 첨부합니다)

https://www.convertstring.com/ko/EncodeDecode/Base64Encode

Github secret에 저장해 두고 

- run: touch ./src/main/resources/application.properties
- run: echo ${{ secrets.APPLICATION }}| base64 --decode > ./src/main/resources/application.properties
- run: cat ./src/main/resources/application.properties

 이렇게 Github Actions에서 application.properties를 생성하도록 하였다. 이 코드는 아래의 main.yml의 일부이다.

application.properties를 인코딩 한 방법은 이 글을 참고했다.

https://developing-mango.tistory.com/65

 

[GitHub Actions] CI/CD 주요정보 Secrets로 관리하기

안녕하세요~ 오늘은 Git Action 을 공부하는중. 처음엔 자동 재배포를 할때 SpringBoot 기준으로 민감한 정보를 가진 application-secret.yml 같은 파일은 deploy과정에서 어떻게 추가할까? 라는 의문이 들었고

developing-mango.tistory.com

우리가 생성한 secrets 들이다.

최종적으로 인코딩했던 application.properties 코드는 이런 형태였다.

4-2. application.properties

spring.application.name=<your-app-name>
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb:<RDS 엔드포인트>/<DB 이름>
spring.datasource.username=사용자이름
spring.datasource.password=패스워드
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

server.tomcat.response-body-encoding-chunked=true

# swagger custom ??(default ??? /swagger-ui/index.html ? ??? ? ??)
springdoc.swagger-ui.path=/swagger-ui
# /api-docs endpoint custom path
springdoc.api-docs.path=/api-docs

cloud.aws.credentials.accessKey=AWS_IMG_ACCESS_KEY_ID
cloud.aws.credentials.secretKey=AWS_IMG_SECRET_ACCESS_KEY
cloud.aws.region.static=AWS_IMG_REGION
cloud.aws.bucket.name=AWS_IMG_BUCKET_NAME
cloud.aws.stack.auto=false

 

4-3. Github Actions workflow 스크립트 작성

main.yml

Github Actions의 workflow를 정의한 파일이다.

# workflow의 이름
name: Deploy to Amazon EC2 / Spring Boot with Maven

# 환경 변수 $변수명으로 사용
env:
  PROJECT_NAME: portfolio
  BUCKET_NAME: portfolio-actions-s3-bucket
  CODE_DEPLOY_APP: portfolio-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP: portfolio-codedeploy-deployment-group

# 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

# workflow는 한개 이상의 job을 가지며, 각 job은 여러 step에 따라 단계를 나눌 수 있습니다.
jobs:
  build:
    name: CI/CD
    # 해당 jobs에서 아래의 steps들이 어떠한 환경에서 실행될 것인지를 지정합니다.
    runs-on: ubuntu-latest

    steps:
      # 작업에서 액세스할 수 있도록 $GITHUB_WORKSPACE에서 저장소를 체크아웃합니다.
      - uses: actions/checkout@v3
        # (2) application.properties 설정
      - uses: actions/checkout@v3
      - run: touch ./src/main/resources/application.properties
      - run: echo ${{ secrets.APPLICATION }}| base64 --decode > ./src/main/resources/application.properties
      - run: cat ./src/main/resources/application.properties

      # Spring 구동을 위한 JDK 11을 세팅합니다.
      - name: Set up JDK 17
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'corretto'
     
      # Caching dependencies (디펜던시를 캐싱하여 반복적인 빌드 작업의 시간을 단축할 수 있다.)
      - name: Cache Maven packages
        uses: actions/cache@v2
        with:
          path: ~/.m2
          key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
          restore-keys: ${{ runner.os }}-m2

      # Github Action IP
      - name: Get Github action IP
        id: ip
        uses: haythem/public-ip@v1.2

      - name: Setting environment variables
        run: |
          echo "AWS_DEFAULT_REGION=ap-northeast-2" >> $GITHUB_ENV
          echo "AWS_SG_NAME=Web-portfolio" >> $GITHUB_ENV
      
      # AWS 인증서비스
      # github repository에서 Setting에서 사용할 암호화된 변수
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
            aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
            aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
            aws-region: ${{ secrets.AWS_REGION}}

      - name: Add Github Actions IP to Security group
        run: |
          aws ec2 authorize-security-group-ingress --group-name ${{ env.AWS_SG_NAME }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION}}

      # Build
      - name: Build with Maven
        run: mvn -B package --file pom.xml

      # Build한 후 프로젝트 압축
      - name: Make zip file
        run: zip -r ./$PROJECT_NAME.zip .
        shell: bash

      # Upload to S3 stroage
      - name: Upload to S3
        run: aws s3 cp $PROJECT_NAME.zip s3://$BUCKET_NAME/deploy/$PROJECT_NAME.zip --region ap-northeast-2

      # CodeDeploy에게 배포 명령을 내린다.
      - name: Code Deploy
        run: >
         aws deploy create-deployment --application-name $CODE_DEPLOY_APP
         --deployment-config-name CodeDeployDefault.AllAtOnce
         --deployment-group-name $CODE_DEPLOY_DEPLOYMENT_GROUP
         --s3-location bucket=$BUCKET_NAME,bundleType=zip,key=deploy/$PROJECT_NAME.zip

      - name: Remove Github Actions IP from security group
        run: |
          aws ec2 revoke-security-group-ingress --group-name ${{ env.AWS_SG_NAME }} --protocol tcp --port 22 --cidr ${{ steps.ip.outputs.ipv4 }}/32
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ${{ secrets.AWS_REGION}}

 

 

4-4. Codedeploy 배포 명령

appspec.yml

Codedeploy가 Github Actions를 통해 코드 배포 명령을 받았을 때 실행하는 파일이다.

version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/portfolio
    overwrite: yes
permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  AfterInstall:
    - location: deploy.sh
      timeout: 500
      runas : root

deploy.sh

Docker를 이용해 배포하는 스크립트이다.

#!/usr/bin/env bash

APP_NAME=shleeb/portfolio_app
REPOSITORY=/home/ubuntu/portfolio
WEB_NAME=portfolio_app

echo "> Check the currently running container"
CONTAINER_ID=$(docker ps -aqf "name=$WEB_NAME")
if [ -z "$CONTAINER_ID" ];
then
  echo "> No such container is running."
else
  echo "> Stop and remove container: $CONTAINER_ID"
  docker stop "$CONTAINER_ID"
  docker rm "$CONTAINER_ID"
fi

echo "> Remove previous Docker image"
docker rmi "$APP_NAME"

echo "> Build Docker image"
docker build -t "$APP_NAME" "$REPOSITORY"

echo "> Run the Docker container"
docker run -d -p 3000:8080 --name "$WEB_NAME" "$APP_NAME"

APP_NAME은 도커 이미지 이름, WEB_NAME은 도커 컨테이너 이름이다.

Repository를 처음에 "." 로 지정했었는데, Docker가 dockerfile을 읽지 못하는 이슈가 발생해서 

/home/ubuntu 밑에 portfolio 폴더를 두어 repository를 지정해주었다.

 

4-4. Docker

Dockerfile

# Use an official Maven image to build the application
FROM maven:3-amazoncorretto-17 AS build

# Set the working directory in the container
WORKDIR /portfolio

# Copy the pom.xml and download the dependencies
COPY pom.xml ./
RUN mvn dependency:go-offline

# Copy the source code and build the application
COPY src ./src
RUN mvn clean package -DskipTests

# Use an official OpenJDK image to run the application
FROM amazoncorretto:17.0.11

# Set the working directory in the container
WORKDIR /portfolio

# Copy the built application from the build stage
COPY --from=build /portfolio/target/portfolio-0.0.1-SNAPSHOT.jar /portfolio/portfolio-0.0.1-SNAPSHOT.jar

# Expose the port the application runs on
EXPOSE 8080

# Run the application
ENTRYPOINT ["java", "-jar", "portfolio-0.0.1-SNAPSHOT.jar"]

 

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:8080"
    environment:
      SPRING_DATASOURCE_URL: 
      SPRING_DATASOURCE_USERNAME: 
      SPRING_DATASOURCE_PASSWORD:

 

우리의 프로젝트에는 이런 파일들이 필요했다.

5. Github Actions 구동

구동 성공 확인

 

6. AWS에서 확인

S3 Bucket에 나의 코드 zip 파일이 올라가 있는지 확인한다.

Codedeploy에서 배포 상태를 확인한다.

배포 상태에서 문제가 발생하면 View Events를 통해 상세히 확인 할 수 있다. 우리는 주로 deploy.sh에서 발생한 오류들을 상세히 확인할 수 있었고, 주로 docker와 관련된 오류를 수정하였다.

 

7. EC2 접속, Docker 배포 확인

SSH로 EC2의 인스턴스에 접속하여 확인해본다.

발급받은 pem 키를 통해 SSH 클라이언트에 접속할 수 있다.

windows cmd 창에서 명령어를 통해 접속하면 된다.

디렉토리 확인

배포 경로로 들어가서 배포가 잘 되었는지 확인한다.

우리의 배포 경로 : /home/ubuntu/portfolio

도커 이미지 확인

docker image ls

컨테이너 구동 확인

docker ps

위 명령어를 통해 구동중인 컨테이너를 확인한다.
컨테이너 포트가 3000번으로 열려있는 것을 확인한다.

Codedeploy까지 모두 배포 명령이 다 잘 되었는데 배포가 되지 않는다면, docker에서 log를 확인하여 내가 빌드한 코드들 중 문제가 있지 않은지, docker container가 죽어있는지 확인해 보길 바란다.

Github Actions에선 오류가 나지 않았던 부분들이 마지막에 가서 발견되어 수정할 수 있었다.

 

8. 배포 완료 !!

외부에서 주소로 요청

EC2 인스턴스의 퍼블릭 IPv4 주소에 포트번호로 접속하여 배포가 무사히 잘 되었는지 확인한다.

 

 

오늘 배포를 완료하여서 아키텍처와 상세한 설명은 더욱 추가할 나갈 예정이다!

'BackEnd : Spring' 카테고리의 다른 글

[AWS] SpringBoot + AWS S3에 이미지 업로드하기  (0) 2024.06.18