본문 바로가기

DevOps/AWS

[AWS] AWS EC2에 스프링 부트 서비스 배포하기

AWS EC2에 스프링 부트 서비스를 jar 파일을 통해 배포할 것입니다. 

뿐만 아니라 배포 스크립트를 통해 배포하는 과정을 단순화할 것입니다.

 

목차

  1. 스프링 부트 기반 API 서비스
  2. 서버 생성 및 EIP 할당
  3. 서버 생성 시 꼭 해야 할 설정들
  4. EC2에서 Git과 Github 사용하기
  5. 코드들이 잘 수행되는 지 테스트로 검증
  6. 배포 스크립트 만들기

1. 스프링 부트 기반 API 서비스

배포할 서비스는 특정 문자열을 응답하는 API를 가진 매우 간단한 서비스입니다.

 

코드는 다음과 같습니다.

@RestController
public class HelloController {
    @GetMapping("/")
    public String hello(){
        return "안녕하세요~~~";
    }
}

버전 정보는 다음과 같습니다.

  • Java 11
  • Spring Boot 2.7.0

 

2. 서버 생성 및 EIP 할당

  • EC2 인스턴스를 생성

EC2 인스턴스를 생성합니다. 생성하는 방법은 이전 글에서 다뤘으니 참고하시면 됩니다.

참고사항은 8080 포트를 허용하는 보안 그룹을 설정합니다.

  • EIP 할당

AWS의 고정 IP를 Elastic IP(탄력적 IP)라고 합니다. 왼쪽 탭에서 “탄력적 IP” > “탄력적 IP 주소 할당”을 클릭해 IP 주소를 할당받습니다.

작업 > 주소 연결을 통해 EC2와 인스턴스를 연결해줍니다.

주의할 점은 탄력적 IP는 생성하고 EC2 서버에 연결하지 않으면 비용이 발생합니다. 즉 탄력적 IP를 생성하고 바로 EC2에 연결해야 합니다.

ec2에 탄력적 IP가 연결된 모습

ssh를 통해 EC2 인스턴스에 접속합니다.

 

3. 서버 생성시 꼭 해야 할 설정들

아마존 리눅스 서버를 처음 설치한 이후에 자바 웹 애플리케이션을 구동하기 위해 필수로 해야 하는 설정들이 있습니다.

  • Java 11 설치 : 현재 프로젝트의 Java 버전은 11입니다.
  • 타임존 변경 : 기본 서버의 시간은 미국 시간대(UTC)입니다. 한국 시간대가 되어야만 우리가 사용하는 시간이 모두 한국 시간으로 등록되고 사용됩니다.
  • 호스트 네임 변경 : 현재 접속한 서버의 별명을 등록합니다. 실무에서는 한 대의 서버가 아닌 수십 대의 서버가 작동되는데, IP만으로 어떤 서버가 어떤 역할을 하는지 알 수 없습니다. 이를 구분하기 위해 필수로 호스트 네임을 등록합니다.

 

Java 11 설치

yum 으로 설치 가능한 Java는 버전 8까지입니다.

아마존에서 제공하는 OpenJDK인 Amazon Coretto를 다운로드하여 설치하겠습니다.

# aws coreetto 다운로드
sudo curl -L https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.rpm -o jdk11.rpm

# jdk11 설치
sudo yum localinstall jdk11.rpm

# jdk version 선택
sudo /usr/sbin/alternatives --config java

# java 버전 확인
java --version

# 다운받은 설치키트 제거
rm -rf jdk11.rpm

Java 버전 선택 화면
java 11이 잘 설치된 모습

 

타임존 변경

기본 서버의 시간은 미국 시간대(UTC)입니다. 한국 시간과는 9시간 차이가 발생합니다.

이렇게 되면 서버에서 수행되는 Java 애플리케이션에서 생성되는 시간도 모두 9시간씩 차이 나기 때문에 꼭 수정해야 할 설정입니다.

서버 시간을 한국 시간(KST)로 변경하겠습니다.

sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime

date 명령어로 타임존이 KST로 변경된 것을 확인할 수 있습니다.

 

Hostname 변경

수십 대의 서버가 작동되는데, IP만으로 어떤 서버가 어떤 역할을 하는지 알 수 없습니다.

172-31-47-151 만으로 어떤 서비스인지 알 수 없습니다.

다음 명령어로 hostname을 변경합니다. “springboot-service” 부분을 원하는 hostname으로 설정하면 됩니다.

sudo hostnamectl set-hostname --static springboot-service

sudo reboot으로 서버를 재부팅합니다.

다시 서버에 접속하면 hostname이 잘 변경된 것을 확인할 수 있습니다.

 

호스트 주소를 찾을 때 가장 먼저 검색해 보는 /etc/hosts/에 변경한 hostname을 등록합니다.

sudo vim /etc/hosts
127.0.0.1 등록한 hostname

 

잘 등록했다면 curl로 호스트 네임에 접근해보빈다.

80번 포트로 접근이 안된다고 에러가 발생합니다. 아직 80 포트로 실행된 서비스가 없기 때문입니다. 즉 curl 호스트 이름으로 실행은 잘 되었음을 의미합니다.

 

4. EC2에서 Git과 Github 사용하기

4-1. EC2에서 ssh 공개키와 개인키 생성

  • ssh 공개키와 개인키란??
더보기

안전하게 외부 Git 서버에서 코드를 Clone 하거나 Push 하려면 SSH 프로토콜을 사용합니다.

SSH는 Git에서도 사용하지만, 원래는 멀리 떨어져 있지만 인터넷이나 네트워크를 통해 연결되어있는 컴퓨터에 안전하게 연결해주는 프로토콜로 안전한 셸(Secure Shell)이라고도 부릅니다

SSH는 사용자, 패스워드나 여러 가지 인증 방법을 지원합니다만, 그중에서도 편리성이나 안정성 면에서 추천하는 방식이 공개키 인증 방식입니다.

공개키 인증 방식을 사용하려면 공개키와 개인키를 한 쌍 만들어야 합니다. 아주 간단하게 설명하면, 공개키는 접속하고자 하는 서버에 등록해놓는 용도로 사용합니다. 이름에서 알 수 있지만 공개키는 외부에 공개되어도 괜찮습니다. 사용자는 개인키를 통해서 SSH에 접속하고, 연결 요청을 받은 SSH 서버에서는 서버에 등록된 공개키 중에 요청받은 개인키 정보와 매치가 되는 공개키가 있는지 찾습니다. 없으면 서버 접속(인증)에 실패합니다. 등록된 공개키가 있다면? 인증에 성공하고 서버에 접속이 됩니다.

공개키는 어디에 공개되어도 문제가 없습니다만, 개인키는 비밀번호와 마찬가지로 반드시 나만 접근할 수 있도록 안전하게 보관해야 합니다. 절대로 다른 사람에게 노출되어서는 안 됩니다. 개인키가 있으면 내가 공개키를 등록해놓은 SSH 서버나 Git 서버에 접속할 수 있기 때문입니다.

 

4-2. 공개키, 개인키 생성

cd ~/.ssh
ssh-keygen -C "example@gmail.com"

 

Enter passphrase (empty for no passphrase):
Enter same passphrase again:

다음으로 SSH 키에 대한 비밀번호를 추가로 지정할지 물어봅니다. 패스워드를 설정해도 되고, 추가 패스워드 없이 사용하려면 엔터를 두 번 입력해줍니다.

id_rsa은 개인키고, id_rsa.pub 은 공개키입니다.

cat id_ed25519.pub을 입력하면 공개키에 대한 내용이 나옵니다.

 

4-3. ssh Key Github에 등록

cat id_ed25519.pub으로 나온 값을 복사합니다.

깃헙 설정 > SSH and GPG keys > New SSH Key 클릭해서 SSH 키 등록 화면으로 이동합니다.

임의의 title 값을 넣어주고 복사한 공개키 값을 Key에 넣어줍니다.

이렇게 SSH 키 등록이 완료됐습니다.

 

프로젝트 clone

깃을 설치합니다.

sudo yum install git
git —version

 

프로젝트를 저장할 디렉터리를 생성하고 이동합니다.

mkdir ~/app && mkdir ~/app/step1
cd ~/app/step1

 

본인의 깃허브 웹페이지에서 ssh 주소를 복사합니다. 

git clone 복사한 주소

 

 

5. 코드들이 잘 수행되는지 테스트로 검증

프로젝트로 이동한 다음 코드들이 잘 수행되는지 테스트로 검증합니다.

./gradlew test

테스트가 성공하면 다음과 같은 화면이 나옵니다.

만약 테스트가 실패해서 코드를 수정하고 푸시했다면 프로젝트 폴더 안에서 다음 명령어를 입력하면 됩니다.

git pull

깃을 통해 프로젝트를 클론과 풀까지 진행했습니다.

이제 프로젝트의 테스트, 빌드, 실행까지 진행하겠습니다.

 

6. 배포 스크립트 만들기

작성한 코드를 실제 서버에 반영하는 것을 배포라고 합니다.

배포라 하면 다음의 과정을 모두 포괄하는 의미라고 보면 됩니다.

  • git clone 혹은 git pull을 통해 새 버전의 프로젝트 받음
  • Gradle이나 Maven을 통해 프로젝트 테스트와 빌드
  • EC2 서버에서 해당 프로젝트 실행 및 재실행

앞선 과정을 배포할 때마다 개발자가 하나하나 명령어를 실행하는 것은 불편함이 많습니다.

그래서 이를 쉘 스크립트로 작성해 스크립트만 실행하면 앞의 과정이 차례로 진행되도록 하겠습니다.

~/app/step1/에 deploy.sh 파일을 하나 생성합니다.

vim ~/app/step1/deploy.sh

다음의 코드를 추가합니다.

#!/bin/bash

# 1
REPOSITORY=/home/ec2-user/app/step1
PROJECT_NAME=springboot-service

# 2
cd $REPOSITORY/$PROJECT_NAME/

# 3
echo "> Git Pull"
git pull

echo "> 프로젝트 Build 시작"

# 4
./gradlew build

echo "> step1 디렉토리로 이동"

cd $REPOSITORY

echo "> Build 파일 복사"

# 5
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/

echo "> 현재 구동중인 애플리케이션 pid 확인"

# 6
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)

echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"

# 7
if [ -z "$CURRENT_PID" ]; then
    echo "> 현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
    echo "> kill -15 $CURRENT_PID"
    kill -15 $CURRENT_PID
    sleep 5
fi

echo "> 새 애플리케이션 배포"

# 8
JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)

echo "> JAR Name: $JAR_NAME"

# 9
nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
  1. REPOSITORY=/home/ec2-user/app/step1
    • 프로젝트 디렉터리 주소는 스크립트 내에서 자주 사용되는 값이기 때문에 이를 변수로 저장합니다.
    • 마찬가지로 PROJECT_NAME=springboot-service도 동일하게 변수로 저장합니다.
    • 쉘에서는 타입 없이 선언하여 저장합니다.
    • 쉘에서는 $변수명으로 변수를 사용할 수 있습니다.
  2. cd $REPOSITORY/$PROJECT_NAME/
    • 제일 처음 git clone 받았던 디렉터리로 이동합니다.
    • 바로 위의 쉘 변수 설명을 따라 /home/ec2-user/app/step1/springboot-service 주소로 이동합니다.
  3. git pull origin master
    • 디렉토리 이동 후, main 브랜치의 최신 내용을 받습니다.
  4. ./gradlew build
    • 프로젝트 내부의 gradlew로 build를 수행합니다.
  5. cp ./build/libs/*.jar $REPOSITORY/
    • build의 결과물인 jar 파일을 복사해 jar 파일을 모아둔 위치로 복사합니다.
  6. CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar)
    • 기존에 수행 중이던 스프링 부트 애플리케이션을 종료합니다.
    • pgrep은 process id만 추출하는 명령어입니다.
    • f 옵션은 프로세스 이름으로 찾습니다.
  7. if ~ else ~ fi
    • 현재 구동 중인 프로세스가 있는지 없는지를 판단해서 기능을 수행합니다.
    • process id 값을 보고 프로세스가 있으면 해당 프로세스를 종료합니다.
  8. JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)
    • 새로 실행할 jar 파일명을 찾습니다.
    • 여러 jar 파일이 생기기 때문에 tail -n으로 가장 나중의 jar 파일(최신 파일)을 변수에 저장합니다.
  9. nohup java -jar $REPOSITORY/$JAR_NAME 2>&1 &
    • 찾은 jar 파일명으로 해당 jar 파일을 nohup으로 실행합니다.
    • 스프링 부트의 장점으로 특별히 외장 톰캣을 설치할 필요가 없습니다.
    • 내장 톰캣을 사용해서 jar 파일만 있으면 바로 웹 애플리케이션 서버를 실행할 수 있습니다.
    • 일반적으로 자바를 실행할 때는 java -jar라는 명령어를 사용하지만, 이렇게 하면 사용자가 터미널 접속을 끊을 때 애플리케이션도 같이 종료됩니다.
    • 애플리케이션 실행자가 터미널을 종료해도 애플리케이션은 계속 구동될 수 있도록 nohup 명령어를 사용합니다.

 

이렇게 생성한 스크립트에 실행 권한을 추가합니다.

chmod +x ./deploy.sh

잘 실행되었으니 nohup.out 파일을 열어 로그를 보겠습니다

nohup.out은 실행되는 애플리케이션에서 출력되는 모든 내용을 갖고 있습니다.

vim nohup.out

public IP로 접근하면 EC2 안에서 Spring boot 서비스에 있는 API가 잘 동작하는 것을 알 수 있습니다.