효습
서버 배포 과정 이해하기 본문
배포하는 과정을 캡쳐한 게 없어 자세한 배포 과정을 정리할 수 없지만 대략적인 과정을 정리하겠다.
이번 Spring Boot 프로젝트를 배포할 때는 Github Actions를 사용하여 자동으로 서버 배포 과정이 실행되게 하였고,
AWS의 EC2 Ububtu 인스턴스 , RDS(DB) , S3(이미지 저장용)를 사용하였고
EC2 내의 웹 서버는 Nginx , 도커 컨테이너를 사용하여 일관된 환경을 제공하도록 하였다.
프로젝트 구조는 위와 같았다.
1. EC2 생성
- 프리티어 계정을 사용하고 있기 때문에 Ubuntu 서버를 사용했고 인스턴스 유형은 t2.micro 유형을 사용했다.
올해부터는 프리티어 계정이 t4g.small까지 지원한다고 하니 원하는 대로 하면 될 것 같다. - 키 페어도 생성했다 <- 나중에 github actions 설정할 때 필요하다.
- 인스턴스 생성 후 , 보안그룹 인바운드 규칙에서 8080 포트를 열어주는 규칙을 추가했다.
2. RDS 생성
- DB는 MySQL을 사용했고 AWS RDS와 연결했다.
- RDS를 생성할 때 , 유의했던 점은 Public IPv4를 생성하지 않았다. 어차피 EC2를 통해서 RDS에 연결할 것이기 때문에 필요하지 않았고 무엇보다 RDS IP를 생성하면 과금이 된다.
- DB 인스턴스 구성을 위해 RDS 파라미터 그룹도 설정해줬다.
- RDS를 생성했으면 이를 MySQL Workbench와 연결해줘야한다.
- 앞서 RDS 의 IP를 발급받지 않았기때문에 RDS와 내 로컬의 MySQL Workbench와 연결할 때 , EC2 인스턴스의 원격서버와 연결해야한다.
- SSH Hostname : EC2 Public IPv4
- SSH Username : Ubuntu
- SSH Password는 아무것도 넣지 않는다.
- SSH Key File은 EC2 인스턴스 만들 때 같이 생성한 키 페어 파일을 넣어준다.
- MySQL Hostname : RDS의 엔드포인트
- Username : DB의 Username
- Password : DB의 Password
- 앞서 RDS 의 IP를 발급받지 않았기때문에 RDS와 내 로컬의 MySQL Workbench와 연결할 때 , EC2 인스턴스의 원격서버와 연결해야한다.
3. 무료 도메인 발급
내도메인.한국에서 무료 도메인을 발급받았다.
4. Docker repository 생성
Github Actions를 사용하여 배포 자동화를 하였는데, 전반적인 과정은 다음과 같았다.
- 리포지토리의 지정된 브랜치로 코드가 push되면 이게 트리거가 되어 워크플로우를 시작한다.
- 워크플로우는 워크플로우 yml 파일에 따라 실행되는데 워크플로우가 도커 이미지를 빌드하여 이를 Docker hub의 내가 미리 생성해둔 repository에 push한다.
- SSH로 EC2 서버가 Docker hub에서 새로운 도커 이미지를 가져와서 컨테이너를 실행한다.
도커 이미지를 올릴 repository가 필요했기 때문에 Docker hub에 repository를 생성했다. 그냥 일단 repository를 생성해놓기만 하면 됨
5. EC2에 Nginx 설치
Nginx란?
간단히 요약하자면
- Nginx는 웹 서버로 HTTP 프로토콜을 사용하여 클라이언트와 통신한다.
- 스프링 부트는 톰캣을 내장하고 있는데, 톰캣은 WAS(Web Application Server)로 사용자가 어떠한 요청을 보내면 동적으로 요청을 처리해주는 요리사와 같은 존재다.
- WAS를 요리사로 본다면 Nginx나 Apache와 같은 웹 서버는 주문을 넣어주고 요리를 가져다주는 서버로 볼 수 있다.
- Apache와 Nginx의 차이점
- 아파치는 NPM(멀티 모듈 프로세스) 방식으로 동작하기 때문에 둘 다 자원을 많이 소모한다.
- 요청이 올 때마다 프로세스를 새로 생성하는 방식
- 요청이 올 때마다 한 프로세스 내에 스레드를 새로 생성하는 방식
- Nginx는 Event driven 방식으로 동작하여 하나의 스레드 안에서 여러 요청을 처리한다.
- Event-Handler를 통해 비동기 방식으로 여러 요청을 처리한다.(큐에서 하나씩 일을 꺼내오는 거 처럼)
- 아파치에 비해 적은 양의 스레드를 사용하기 때문에 자원 소모량이 적은 편이다.
- 단점
- 동적 컨텐츠를 기본적으로 처리할 수 없다.
(Nginx는 동적 컨텐츠를 처리할 수 없기 때문에 이를 처리하기 위해 외부 프로세스와 통신해야하는데 때문에 성능이 저하될 수 있음)
- 동적 컨텐츠를 기본적으로 처리할 수 없다.
- 아파치는 NPM(멀티 모듈 프로세스) 방식으로 동작하기 때문에 둘 다 자원을 많이 소모한다.
- 이런 웹 서버 말고 WAS 만으로도 웹사이트가 필요한 기능을 제공할 수 있는데 왜 Nginx를 사용할까?
- reverse proxy를 사용하여 서버의 보안을 강화하거나 로드밸런싱 , 캐싱 기능을 사용할 수 있다.
- 톰캣과 같은 WAS만 사용하는 것보다 고성능이다.
나는 80번 포트로 들어오는 요청을 8080번 포트로 전달하기 위해 사용했다.(포트포워딩)
이런 이유로 EC2 서버에 Nginx를 설치했다.
설치한 이후에 다음 명령어를 실행하여 default.conf 파일을 수정해주면 된다.
sudo vi /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name {domain_name};
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
{domain_name}
에 3번에서 발급받은 도메인 넣으면 된다.
EC2 서버에서 도커 컨테이너도 실행해야하니까 도커도 설치해줌
6. Github Repository Secrets
- 프로젝트가 있는 Repository의 Settings -> Security -> Secrets and Variables -> actions에 가보면 Repository Secrets가 있다.
- 여기에 워크플로우에 사용해야하지만 보안상 숨겨야 하는 것들은 저장해놓을 수 있다. (워크플로우가 실행되면 지정된 이름에 따라 가져다 씀)
- 나는 application.yml , 도커 아이디 , 도커 패스워드 , 도커 repository, EC2 IPv4 , EC2 key , EC2 username을 넣었다.
application.yml 파일은 그대로 넣으면 된다. 나는 다른 방식으로 인코딩해서 넣었다가 에러가 뭔지 알아내느라 시간을 좀 썼다.
7. Dockerfile 작성
Docker Image를 생성하기 위한 설정파일이다.
이걸 프로젝트의 루트 디렉토리에 넣으면 된다.
# 사용할 Base 이미지
FROM openjdk:17-alpine
# build/libs/ 에 있는 jar 파일을 JAR_FILE 변수에 저장
ARG JAR_FILE=build/libs/*.jar
# JAR_FILE을 app.jar로 복사
COPY ${JAR_FILE} app.jar
# Docker 컨테이너가 시작될 때 /app.jar 실행 , 컨테이너가 시작될 때마다 실행함
# 애플리케이션 timezone을 대한민국으로 설정
ENTRYPOINT ["java","-jar","-Duser.timezone=Asia/Seoul","/app.jar"]
8. Github Workflow 작성
프로젝트가 있는 github repository에 가면 Actions 페이지에서 생성할 수 있다.
name: Java CI with Gradle
on:
push:
branches: [ "deploy" ]
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'
# repository secrets에 올린 application.yml을 빌드 시 생성
- name: Make application.yml
run: |
mkdir ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION }}" > ./application.yml
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build with Gradle
run: |
chmod +x ./gradlew
./gradlew build -x test
# ID, PW를 이용해 Docker hub에 로그인
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Docker 이미지 빌드 후 푸시
- name: Docker build & push
uses: docker/build-push-action@v2
with:
# 빌드 컨텍스트 지정: 지정한 디렉토리 안에 Dockerfile이 있어야 함
context: .
# 빌드에 사용할 Dockerfile의 경로 지정
file: ./Dockerfile
# 빌드할 이미지의 플랫폼 지정
platforms: linux/amd64/v8
# 빌드 후 Docker 레지스트리에 푸시할지 여부 지정
push: true
# 이미지 태그 지정
tags: ${{ secrets.DOCKER_REPO }}:latest
# SSH를 사용하여 EC2에 명령을 전달
- name: Deploy to Server
uses: appleboy/ssh-action@master
with:
# 원격 서버의 호스트 주소 지정
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.KEY }}
envs: GITHUB_SHA
# 아래 명령들을 실행
script: |
sudo docker rm -f $(sudo docker ps -qa)
sudo docker pull ${{ secrets.DOCKER_REPO }}:latest
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_REPO }}:latest
sudo docker image prune -f
이렇게 작성했다.
워크플로우 파일을 작성한 후에 Github Actions를 실행하면 EC2 서버에서 도커가 실행되어 http://domain으로 접속할 수 있다.
잠깐 왜 도커를 사용했을까??
배포한 서버 환경과 내 개발 서버 환경을 모두 동일한 환경에서 동작하게 하기 위해 사용하였다
위의 과정까지 하면 http로 접속할 수 있는데 보안상 더 좋은 HTTPS를 사용하기 위해 SSL 인증서를 발급 받아야한다.
9. Cerbot으로 SSL 인증서 발급받기
인증서 발급과 관련해서는 https://velog.io/@haru/certbot <- 이 블로그를 참고했다.
인증서를 발급받고 Nginx의 default.conf 파일을 80번 포트로 들어오는 요청을 443번 포트로 리다이렉트 되도록 수정해주면 된다.
HTTP는 80번 포트를 사용하고 HTTPS는 443번 포트를 사용한다.
그럼 이제 HTTPS로도 접속 가능!
참고 블로그
'프로젝트' 카테고리의 다른 글
redis 배포하기 (0) | 2025.01.02 |
---|---|
QueryDSL 사용하기 (1) | 2024.10.05 |
Java에서 equals() 와 == 의 차이 (0) | 2024.08.16 |