개요
이전 글Github Actions로 CI 구성하기 with Spring Boot 을 작성하면서, CI를 구성해보았다. CD에 앞서서, Spring Boot 서버로서 기능하는 인스턴스를 구성해야한다.
현재 진행하고 있는 반려동물 SNS 프로젝트는 국제화를 구현한 상태이다. 이에 따라 늘어날 수 있는 사용자들에 대해서 유연하게 스케일링 할 수 있는 클라우드 환경의 인스턴스를 이용하고자 하였고, AWS의 EC2를 이용하기로 결정했다.
EC2 - 서버 인스턴스
Elastic Compute Cloud(EC2)는 AWS 클라우드에서 on-demand인 확장 가능한 컴퓨팅 용량을 제공한다(스케일링이 가능하다는 말). EC2를 사용하면 하드웨어 비용이 절감되므로 애플리케이션을 더욱 빠르게 개발하고 배포할 수 있다.
EC2를 사용하여 원하는 수의 가상 서버를 구축하고 보안 및 네트워킹을 구성하며 스토리지를 관리할 수 있다.
용량을 추가(스케일 업)하여 월간 또는 연간 프로세스 또는 웹 사이트 트래픽 급증 등 컴퓨팅 사용량이 많은 작업을 처리할 수 있다. 사용량이 감소하면 용량을 다시 축소(스케일 다운)할 수 있다.
즉 EC2는 AWS에서 제공하는 VM(Virtual Machine)이고, 호스팅을 제공받는 인스턴스다.
우리는 EC2를 간편하게 구성하고, 클라우드 컴퓨터인 EC2에 서버 프로그램을 동작시키고, 서버로서 기능하게끔 할 수 있다.
이 글에서는 EC2를 Spring Boot 이용한 서버로서 기능하게끔 구성하는 방법을 다루므로, 인스턴스 생성과 같은 자세한 설명은 생략한다.
EC2 - Spring Boot 서버로 세팅하기
현재 첫 배포를 앞두고 있고, 급격한 사용은 많지 않을 것 같아서 별도로 DB 서버를 구성할 필요는 없을 것 같다고 생각했다.
이에 따라, EC2 자체에서 Docker로 해당 DB 서버를 연결하고 사용하도록 구성하고자 했다. 또, Docker를 내부에서 실행하고 WAS도 실행하는 만큼, 너무 작지는 않은 인스턴스를 사용해야겠다고 판단했고, t3.medium을 선택했다.
t3.medium의 스펙
EC2 - Docker, JDK 구성하기
OS는 Amazon Linux를 사용했다.
패키지 매니저로 apt나 apt-get이 아닌 yum을 기본적으로 사용하므로, yum으로 필요한 의존성들을 설치한다.
sudo yum install vim #텍스트 편집기
sudo yum install java-17-amazon-corretto-headless #자바 환경을 실행하기 위한 JDK
Shell
복사
사용하는 Java의 버전에 맞게 설치하면 된다. 우리 프로젝트의 경우 17을 사용한다.
JDK에서 headless는 gui에 대한 의존성이 없는 환경을 의미한다.
서버 내부에서 Docker를 실행하기 위해 다음과 같이 Docker를 설치해보자.
#docker 설치
sudo yum install docker -y #docker 설치
sudo serivce docker start #service로 docker를 등록, 인스턴스 시작 시에 docker를 실행한다.
sudo usermod -aG docker ec2-user #EC2의 기본 사용자인 ec2-user에 docker 그룹을 추가한다 - 권한 부여
docker run hello-world #테스트. 안 되면 터미널을 재시작하거나, sudo로 해보자.
#docker compose 설치
sudo curl -L https://github.com/docker/compose/releases/download/1.25.0-rc2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose #바이너리 실행권한 부여
docker-compose --version # 버전 체크
#docker-compose --version가
#[4792] Error loading Python lib '/tmp/_MEIolw2Ru/libpython3.7m.so.1.0': dlopen: libcrypt.so.1: cannot open shared object file: No such file or directory
#와 같은 이유로 실행되지 않을때 설치하자.
sudo dnf install libxcrypt-compat
Shell
복사
위 구성을 마치면 spring boot도, docker를 통한 db서버도 구동할 준비가 완료된 것이다.
직접적으로 빌드된 프로젝트가 실행가능한지, docker가 작동하는지 보고 싶다면, 요구되는 파일들을 직접 EC2에 업로드해서 실행해보면 된다.
업로드 방법
이제 본 게임인 서버 얹기에 들어가보자.
Spring Boot JAR, DB서버 Docker로 구성하기
결국 CI를 통과한 build의 결과물은 결국 JAR 파일로 산출된다.
우리는 해당 JAR를 EC2 인스턴스에서 구동하고, 외부에서 요청을 받아들일 수 있도록 설정해주면 되는 것이다.
JAR는 build만 된다면 구성되어 있으니, EC2에 DB 서버를 얹는 작업을 해보자. 우리의 경우 MariaDB를 사용한다.
docker-compose로 컨테이너를 구성하고, Dockerfile로 해당 컨테이너에 DB를 직접적으로 설치한 다음, 쉘스크립트와 SQL 데이터를 이용해서 compose에서 정의하는대로 DB를 구성하게끔 파일들을 작성해보자.
docker-compose.yml
version: "3.3"
services:
mariadb:
container_name: [칸테이너 이름]
build: ./ #Dockerfile의 디렉토리 설정
restart: always
environment:
MARIADB_DATABASE_HOST: [EC2의 로컬 도메인]
MARIADB_DATABASE: [사용하고자 하는 DB의 이름]
MARIADB_USER: [유저 이름] # 편의상 root로 설정했다.
MARIADB_PASSWORD: [유저 비밀번호]
MARIADB_ROOT_PASSWORD: [루트 계정 비밀번호]
ports:
- "3306:3306" # 호스트 시스템이 연결하고자 하는 포트 : 도커 컨테이너 내의 포트
tty: true # 직접적으로 터미널을 사용하는 옵션.
YAML
복사
Dockerfile
FROM debian:buster #사용하는 OS
RUN apt-get update -y; \
apt-get upgrade -y; \
apt-get install -y \ # 패키지 매니저 업데이트
mariadb-server \ # 사용하고자 하는 db 서버를 설치한다.
vim # 직접 컨테이너에서 텍스트 편집을 하는 경우에 사용한다.
WORKDIR / # 작업이 이뤄지는 디렉토리 - 기준점
RUN mkdir -p /var/run/mysqld
RUN chown -R mysql:mysql /var/run/mysqld
RUN chmod 777 /var/run/mysqld #mariaDB 서버 실행 권한 설정
RUN mkdir -p /var/lib/mysql
RUN chown -R mysql:mysql /var/lib/mysql #mariaDB 클라이언트 실행 권한 설정
RUN mkdir -p /database #미리 준비한 파일들을 담아둔 디렉토리
COPY ./database/ /database/ #옮겨준다.
RUN chmod 777 /database/init.sh #원하는 작업을 수행할 쉘 스크립트
ENTRYPOINT ["bash", "/database/init.sh"] # 컨테이너 가동시 항상 실행되도록 지정한다.
CMD ["mysqld", "--bind-address=0.0.0.0"] # 컨테이너 실행시 mariaDB 서버가 모든 클라이언트의 연결을 수락하도록 한다.
Docker
복사
init.sh
#!/bin/bash
GREEN='\033[0;32m' # 프롬프트에 출력되는 문자의 색 설정
RED='\033[0;31m'
RESET='\033[0m'
service mysql start # service로 등록하여 컨테이너 실행시 실행되게 설정한다.
if [ -d "/var/lib/mysql/${MARIADB_DATABASE}" ] # docker-compose에서 지정된 DB가 이미 있는지 확인한다.
then
echo -e "${RED} Database already exist!! ${RESET}"
else
# root user를 생성한다.
echo "Create root user..."
# 모든 네트워크에 대해 root user에게 DB 서버의 모든 권한을 부여한다.
echo "GRANT ALL ON *.* TO 'root'@'%' IDENTIFIED BY '$MARIADB_PASSWORD'; FLUSH PRIVILEGES;" | mysql -u$MARIADB_USER -p$MARIADB_PASSWORD
# 한번 더 체크하고, docker-compose에서 지정한 이름으로 DB를 생성하고 유저를 생성, 해당 DB에 대해 권한을 부여한다.
echo "DROP DATABASE IF EXISTS $MARIADB_DATABASE; CREATE DATABASE $MARIADB_DATABASE; GRANT ALL ON $MARIADB_DATABASE.* TO '$MARIADB_USER'@'%' IDENTIFIED BY '$MARIADB_PASSWORD'; FLUSH PRIVILEGES;" | mysql -u$MARIADB_USER -p$MARIADB_PASSWORD
# Import database
mysql -u$MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE < /database/import하려는.sql
fi
# 이 쉘 스크립트에 전달된 모든 인자를 실행하는 설정.
exec "$@"
Shell
복사
위 구성을 마치면 다음과 같은 디렉토리 구조를 가지게 된다.
작업경로/에서 docker compose up을 실행하면, 위와 같은 프로세스를 거쳐 docker에 DB 서버를 구성할 수 있다.
이제 DB서버가 구성되었으니, Spring Boot의 yml에서 해당 DB의 url에 알맞은 설정만 해놓는다면, DB서버와 연결된 Spring Boot 서버를 띄울 수 있게된다!
서버를 구동시켜보자.
서버 구동하기
위에서 작성한 DB 서버 docker-compose를 실행하고, 컨테이너를 구동한다.
#컨테이너를 실행하기 전에 이미지를 build하고, daemon으로 백그라운드에서 컨테이너를 실행한다.
#안 되면 docker-compose로 해보자.
docker compose up --build -d
Shell
복사
빌드해둔 Spring Boot JAR 파일을 다음과 같이 실행한다.
# application-prod.yml로 production용 환경변수를 별도로 구성해서 build한 뒤 사용한다.
# &를 뒤에 붙임으로써 해당 EC2에서 백그라운드로 실행하게 한다. - 도커로 실행하는 방법도 있다.
java -jar -Dspring.profiles.active=prod ./path_to_jar/jar_name.jar &
Shell
복사
이러면 서버(port:4242)가 켜진다..!! 이제 Postman으로 테스트를 해보면..
timeout이 나타난다. 왜 일까?
EC2 - 인바운드 규칙
한 줄 요약하자면, ‘EC2에 구성한 백엔드 서버에 대해 접근할 수 있는 Port를 열어놓지 않았기 때문 - 인바운드 규칙을 설정하지 않았기 때문’이다.
보안 그룹은 EC2에 대한 인바운드와 아웃바운드 트래픽을 제어하는 가상 방화벽이다. IP 주소, 포트 범위, 프로토콜 등을 기반으로 트래픽을 허용하거나 거부하는 규칙을 정의할 수 있는데, 기본 설정이 제한적으로 개방되어 있기 때문에, 서버 프로세스의 포트에 해당하는 인바운드 규칙을 설정해주어야 한다.
인바운드 규칙
인바운드 규칙(Inbound Rule)은 외부에서 EC2 인스턴스로 향하는 트래픽을 제어하는 규칙이다.
이 규칙은 허용되는 소스 IP 주소, 포트 범위, 프로토콜 등을 설정하여 인스턴스로 들어오는 트래픽을 관리한다.
AWS 콘솔에서 EC2 인스턴스 - 보안 - 세부정보를 보면 기본으로 지정된 보안 그룹이 있다. 별도의 그룹에 규칙들을 설정해서 적용해도 되지만, 현재는 기본 그룹에 다른 규칙을 추가하는 방식으로 설정해보자.
이미 설정이 좀 되있어서 많지만, 아무 설정도 하지 않았다면 ssh 포트만 열려 있을 것이다.
보안 그룹으로 이동해서 인바운드 규칙 편집에 들어가고, 구동하고자 하는 서버의 포트번호를 규칙에 추가해주자.
우리의 경우 4242번 포트가 백엔드 서버의 포트다.
이후에 규칙을 저장하고 다시 Postman으로 요청을 보내보면…
요청에 응답이 반환된다!
정리
단순히 포트를 열어서 통신해보았지만, 보안이나 도메인등을 고려하면 EC2와 클라이언트 간의 통신하는 부분에 더 많은 규칙이나 방법들이 추가되어야 할 것이다.
이제 고정적으로 동작하는 서버 인스턴스를 구성했으니, 우리가 구동하는 백엔드 서버의 배포를 자동화(CD)해보자.