본문 바로가기

기타

AWS Beanstalk로 spring프로젝트 배포 정리

이번 포스팅에서는 beanstalk로 spring 배포 이후, 실제 운영에서 필요한 설정들을 어떻게 할 수 있을지 모아 정리해보겠습니다.

0. 들어가며

목차

1. beanstalk에 배포할 zip파일 만들기
2. beanstalk 구축하여 zip파일 배포해보기
3. dev서버와 prod서버 구분하기
4. DB정보등 민감한 정보를 yml에 포함시키지 않는 방법
5. 스크립트로 배포 자동화
6. logback log를 cloudwatch로 전송

 

프로젝트 구성

Spring 버전: 3.1.1
언어: kotlin
빌드: gradle-kotlin

1. beantalk에 배포할 zip파일 만들기

1-1. 준비

먼저 간단한 spring 프로젝트를 준비합니다.

yml에서 property를 읽어 응답으로 내려주는 api를 포함하고 있습니다.

 

application.yml

 

local에서 테스트 화면

그리고 이번 beantalk배포의 핵심부분을 작성해줘야합니다.

이동욱님이 잘 정리를 해두신게 있어 링크를 참고해서 만들도록 합니다.

 

2. Github Action & AWS Beanstalk 배포하기 - profile=local로 배포하기

지난 시간에 만들어둔 Github Action을 통해 profile=local로 Beanstalk에 배포를 진행해보겠습니다. profile=local, 즉, 운영 DB와 구글&네이버 OAuth 를 사용하지 않는 간단한 테스트 용도로만 배포할 예정입니

jojoldu.tistory.com

1-2. zip 생성

./gradlew build로 jar파일을 만들고 deploy폴더에 application.jar라는 이름으로 위치시킵니다.

 

참고,
빌드 결과물로 plain.jar 파일이 생성될 수 있는데 이를 막기 위해서 build.gradle.kts 파일에 아래 코드를 추가합니다.
tasks {
    getByName<Jar>("jar") {
        enabled = false
    }

    getByName<BootJar>("bootJar") {
        enabled = true
    }
}​

그리고 deploy 파일로 이동하여 zip파일을 만들어줍니다.

cd deploy
zip -r deploy.zip .

 

아래와 같이 되었으면 완료입니다.

 

2. beantalk 구축하여 zip 파일 배포하기

이제 zip 파일을 beantalk에 배포할 차례입니다.

기본적인 beanstalk 환경을 만들어줍니다.(해당 부분의 자세한 내용은 생략하겠습니다. 다른 글들을 참고해주세요.)

 

저같은 경우에는 Aws에서 제공하는 sample 어플리케이션을 활용한 환경을 만들어 보았습니다.

환경이 생성되었으면 우리가 이전 단계에서 생성한 zip파일을 업로드해보겠습니다.

 

배포가 완료되고 beantalk의 도메인으로 접속하여 '/get-profile'가보니 local-profile이 정상적으로 출력되는 것을 확인할 수 있습니다.

3. dev서버와 prod서버 구분하기

dev와 prod의 가장 큰 차이라고 한다면 jar 구동시에 active profile을 각각 다르게 주는 것이 아닐까 합니다.

이번에는 dev와 prod환경에서 active profile을 다르게 설정하는 방법에 대해서 알아보겠습니다.

먼저 beanstalk에 prod환경을 추가로 생성해보겠습니다.

 

이때 환경 속성으로 SPRING_PROFILES_ACTIVE=prod를 추가해줍니다.

(dev환경에는 SPRING_PROFILES_ACTIVE=dev를 추가하시면 되겠죠?)

 

그리고 위 환경 속성을 사용하기 위해서 deploy/.ebextensions/00-makeFiles.config로 이동하여 다음과 같이 코드를 추가해줍니다.

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

            #빈스토크 환경속성 중, 'SPRING_PROFILES_ACTIVE'의 값으로 된 것을 profile 로 설정
            PROFILE=$(/opt/elasticbeanstalk/bin/get-config environment -k SPRING_PROFILES_ACTIVE)

            # run app
            sudo pkill -9 java
            java -jar -Dspring.profiles.active=$PROFILE $JAR_PATH

 

그럼 다시 위 설정이 적용된 deploy.zip을 배포하고 확인해보면,

우리가 application.yml에 작성한대로 'prod-profile'이라고 잘 나옵니다.

 

4. 민감한 정보 yml에 포함시키지 않기

DB 접근 정보같이 yml에 포함시키면 안되는 정보들이 있습니다.

beanstalk를 활용하는 경우 이런 정보들을 환경 속성에 저장해두고 java/kotlin에서는 System.getenv()메서드를 통해 접근할 수 있습니다.

예를 들어 다음과 같이 DB 접속 정보를 환경 속성으로 정의해두고,

prod 환경에서만 동작하는 DataSourceConfig를 만들 수 있습니다.

@Profile(value = ["prod"])
@Configuration
class DatasourceConfig {
    private val DRIVER_CLASS_NAME = "com.mysql.cj.jdbc.Driver"

    @PostConstruct
    fun setUp() {
        log.info("set up data source on prod profile")
    }

    @Bean
    fun dataSource(): DataSource {
        val dataSource = DriverManagerDataSource()
        dataSource.setDriverClassName(DRIVER_CLASS_NAME)
        dataSource.url = System.getenv("RDS_URL")
        dataSource.username = System.getenv("RDS_USERNAME")
        dataSource.password = System.getenv("RDS_PASSWORD")
        return dataSource
    }
}

5. 스크립트를 통한 배포 자동화

지금까지 배포 과정을 살펴보면 다음과 같습니다.

1. ./gradlew build를 통해 jar파일 생성
2. jar파일을 deploy폴더로 이동
3. 압출을 통해 deploy.zip파일 생성
4. beanstalk에 접속하여 deploy.zip파일 업로드

위 과정을 스크립트로 자동화하는 방법에 대해 알아보겠습니다.

먼저 aws cli를 사용해야하기 aws cli를 설치하고 aws configure 명령어로 로그인해줍니다.

(해당 부분과 관련해서 자세한 내용은 생략하겠습니다. 다른 글을을 참고해주세요.)

 

그럼 aws cli가 준비되었다고 가정하고 spring 프로젝트에 아래 스크립트를 추가합니다.

#!/bin/bash
EB_APP_NAME='beanstalk app 이름'
EB_ENV_NAME='beanstalk 환경 이름'
S3_BUKET_NAME='s3 버킷 이름'

VERSION=v$(date '+%Y_%m_%d_%H_%M')
S3FILE_NAME=${VERSION}_deploy.zip

echo "build application"
./gradlew build

echo "make deploy.zip"
DEPLOY_ZIP_FILE_NAME="deploy.zip"
JAR_FILE=$(ls build/libs/*.jar)
echo "JAR_FILE: $JAR_FILE"

cp $JAR_FILE ./deploy/application.jar
cd ./deploy
zip -r $DEPLOY_ZIP_FILE_NAME .

echo "upload s3"
aws s3 cp $DEPLOY_ZIP_FILE_NAME \
s3://$S3_BUKET_NAME/$S3FILE_NAME \
--region ap-northeast-2

echo "upload application to eb"
aws elasticbeanstalk create-application-version \
--application-name $EB_APP_NAME \
--version-label $VERSION \
--source-bundle S3Bucket=$S3_BUKET_NAME,S3Key=$S3FILE_NAME \
--no-cli-pager

echo "final deploy"
aws elasticbeanstalk update-environment --region ap-northeast-2 \
--environment-name $EB_ENV_NAME \
--version-label $VERSION \
--no-cli-pager

 

여기서 beanstalk의 app 이름과 환경 이름 그리고 s3 버킷이름을 입력해주시면 됩니다.

*s3 버킷이 갑자기 등장한 이유
beanstalk는 내부적으로 s3를 사용합니다.
새로운 배포를 진행할때마다 deploy.zip파일을 s3에 업로드하고 업로드된 파일을 가져다가 배포합니다.
s3에 가보면 지금까지 테스트하면서 배포했던 파일들이 업로드되어있는 것을 보실 수 있습니다.

 

각 변수들에 알맞는 값을 찾아 넣고 스크립트를 실행해보면 배포가 잘 되는 것을 확인할 수 있습니다.

 

위 스크립트를 활용해서 jenkins등 각자 배포 환경에 적용하시면 됩니다.

(*참고로 --no-cli-pager는 명령어가 실행될때 마다 사용자 입력을 요구하는 경우가 있는데 이를 생략하기 위해서 추가되었습니다.

경우에 따라 필요없을 수 있으니 상황에 맞게 처리해주세요.)

 

6. logback log를 cloudwatch로 전송하기

이번에는  logback으로 관리되는 로그를 cloudwatch로 전송하는 방법에 대해 알아보겠습니다.

테스트를 위해서 다음과 코드를 추가합니다.

 

WebRestController

import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class WebRestController(
    private val profileNameProperty: ProfileNameProperty,
) {
//  코드 추가
    companion object {
        inline val log: Logger
            get() = LoggerFactory.getLogger(WebRestController::class.java)
    }

    @GetMapping("/get-profile")
    fun check(): String {
        // 코드 추가 - log 출력
        log.info("test log")
        return profileNameProperty.profilename
    }
}

 

logback.spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-3level %logger{5} - %msg %n</pattern>
        </encoder>
    </appender>

    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${LOG_PATH}/info/info.%d{yyyy-MM-dd}-%i.log.gz</FileNamePattern>
            <maxHistory>30</maxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>10MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <springProfile name="local">
        <root level="INFO">
            <appender-ref ref="STDOUT"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <root level="INFO" additivity="false">
            <appender-ref ref="INFO"/>
        </root>
    </springProfile>
</configuration>

 

application.yml

spring:
  profiles:
   active: local
logging: #추가된 코드
  file: #추가된 코드
    path: ./log #추가된 코드

---
spring:
  config:
    activate:
      on-profile: local
profile-name: local-profile
---
spring:
  config:
    activate:
      on-profile: dev
profile-name: dev-profile
---
spring:
  config:
    activate:
      on-profile: prod
profile-name: prod-profile

 

수정된 버전으로 다시 배포합니다.

 

배포된 ec2 인스턴스로 접속하여 /var/app/current/log/info 경로로 가보면 log파일이 존재하고 '/get-profile'로 요청을 보낼때 마다 log가 찍히는 것을 확인할 수 있습니다.

 

그럼 이 log를 cloudwatch로 전송해보겠습니다.

먼저 아래보이는 EC2 인스턴트 프로파일에 'cloudwatch logStream을 생성할 수 있는 권한'을 부여해주어야 합니다.

 

'보안 자격 증명 > 역할'에서 EC2 인스턴스 프로파일에 해당하는 역할을 찾습니다.

 

권한 추가로 cloudwatch에서 logStream을 생성할 수 있는 권한을 부여해줍니다.

저는 간단하게 CloudWatchLogsFullAccess를 부여해주었습니다.

그런다음 .ebextensions 폴더에 파일을 추가해줍니다.

packages:
  yum:
    awslogs: []
option_settings:
  - namespace: aws:elasticbeanstalk:cloudwatch:logs
    option_name: StreamLogs
    value: false
files:
  "/etc/awslogs/awscli.conf" :
    mode: "000600"
    owner: root
    group: root
    content: |
      [plugins]
      cwlogs = cwlogs
      [default]
      region = `{"Ref":"AWS::Region"}`
  "/etc/awslogs/config/sample_log.conf" :
    mode: "060606"
    owner: root
    group: root
    content: |
      [/var/log/info/info.log]
      log_group_name = `{"Fn::Join":["/", ["/aws/elasticbeanstalk", { "Ref":"AWSEBEnvironmentName" }, "var/log/info.log"]]}`
      log_stream_name = {instance_id}
      file =/var/app/current/log/info/info.*.log
commands:
  "01":
    command: systemctl enable awslogsd.service
  "02":
    command: systemctl restart awslogsd

 

다시 배포해주고, cloudwatch로 들어가보면 로그 그룹이 생긴것을 확인할 수 있습니다.

들어가보면 info.log가 찍혀 있습니다.

 

 

참고 링크

https://jojoldu.tistory.com/549

https://aws.plainenglish.io/how-to-setup-aws-elasticbeanstalk-to-stream-your-custom-application-logs-to-cloudwatch-d5c877eaa242

반응형