[배포] AWS EC2 배포 + RDS 연결 + ElastiCache 연결 + 프로젝트에 도메인 연결 및 HTTPS 적용하기

최혜원·2024년 9월 28일
1

프로젝트 배포

spring 프로젝트 🐬 docker로 배포하기

AWS EC2 서버와 local에 docker를 설치하고 local에서 프로젝트 jar 파일을 실행하는 이미지를 만들어
docker hub에 업로드하고 EC2 서버에서 받아와 실행하는 방식이다.

※ EC2 : AWS에서 사용자가 가상 컴퓨터를 임대 받아 그 위에 자신만의 컴퓨터 애플리케이션들을 실행할 수 있게 한다.

환경

  • Spring Boot
  • java 17
  • gradle
  • EC2: Ubuntu

순서

  1. local에 docker 설치
  2. EC2 인스턴스를 Ubuntu로 생성
  3. 1에서 생성한 서버에 docker 설치
  4. 설정 파일 추가(Dockerfile,build.gradle)
  5. 빌드하여 jar 파일 생성
  6. docker hub 회원가입& local에서 로그인
  7. jar 파일를 이미지로 생성 & 만든 이미지를 docker hub에 push
  8. EC2 서버에서 다운로드
  9. 배포 확인

1. EC2 Ubuntu 인스턴스 생성

EC2 셋팅하기 - 기본 설정

1.이름 및 태그

2.Application and OS Images (Amazon Machine Image)

✅ Ubuntu 22.04 LTS 선택

OS를 선택하는 단계이다. OS(운영체제)란 Mac, Windows 7, Windows 10, Windows 11 같은 것들이 OS이다.
하지만 Windows나 Mac OS는 생각보다 용량도 많이 차지하고 성능도 많이 잡아먹는다.
그래서 서버를 배포할 컴퓨터의 OS는 훨씬 가벼운 Ubuntu를 많이 사용한다.

3. 인스턴스 유형

우선 인스턴스라는 뜻부터 정리하고 가자. 인스턴스란, AWS EC2에서 빌리는 컴퓨터 1대를 의미한다. 그럼 인스턴스 유형은 무슨 뜻일까? 컴퓨터 사양을 의미한다. 컴퓨터 사양이 좋으면 좋을수록 많은 수의 요청을 처리할 수 있고, 무거운 서버나 프로그램을 돌릴 수 있다.
프리 티어에 해당하는 t2.micro를 사용할 것이다.

4. 키 페어(로그인)

키 페어(Key Pair)는 무슨 뜻일까? EC2 컴퓨터에 접근할 때 사용하는 비밀번호라고 생각하면 된다. 말 그대로 열쇠(Key, 키)의 역할을 한다.

탄력적 IP 연결하기

2. EC2 Ubuntu 서버에 JDK 설치

root 계정으로

sudo su

Spring Boot는 3.x.x 버전을 사용할 예정이고, JDK는 17버전을 사용할 예정이다.

sudo apt update && /
sudo apt install openjdk-17-jdk -y

2-1. 잘 설치됐는지 확인하기

java -version

3. Docker, Docker Compose 설치

sudo apt-get update && \
	sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common && \
	curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - && \
	sudo apt-key fingerprint 0EBFCD88 && \
	sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" && \
	sudo apt-get update && \
	sudo apt-get install -y docker-ce && \
	sudo usermod -aG docker ubuntu && \
	newgrp docker && \
	sudo curl -L "https://github.com/docker/compose/releases/download/2.27.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && \
	sudo chmod +x /usr/local/bin/docker-compose && \
	sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

3-1. 잘 설치됐는지 확인하기

docker -v # Docker 버전 확인
docker compose version # Docker Compose 버전 확인

4. 설정 파일 추가(Dockerfile,build.gradle)

프로젝트 폴더 안에 Dockerfile 파일 생성
(* 대소문자까지 동일해야 하고 확장자는 없이 만든다.)

Dockerfile은 인프라스트럭쳐의 프로비저닝(서버 환경 셋팅)이라고 생각하면 된다.
docker build라는 명령어를 통해서 Docker가 Dockerfile을 읽어서 자동으로 도커 이미지를 빌드한다.

🐬 Dockerfile

spring

FROM openjdk:17-jdk
WORKDIR /app
COPY build/libs/test.jar app.jar
EXPOSE 80

CMD ["java", "-jar", "app.jar"]
  • FROM : 토대가 되는 이미지 지정
    • 반드시 FROM 키워드로 시작해야 한다.
  • 사용하는 java 버전으로 맞출것
  • WORKDIR : RUN, CMD, ENTRYPOINT, ADD, COPY에 정의된 명령어를 실행하는 작업 디렉터리를 지정
  • COPY : 기존 파일을 복사하여 이미지에 파일이나 폴더를 추가
    • build/libs/test.jar => jar 파일 위치와 파일명
    • app.jar => 복사된 파일명
  • EXPOSE : 통신에 사용할 포트
  • CMD : 컨테이너를 실행할 때 실행할 명령어를 지정
    • app.jar 파일 실행
    • CMD 키워드도 무조건 한번만 사용할 수 있다.

react

# Step 1: Build the React app
FROM node:18 AS build
WORKDIR /app
# 소스 코드 복사
COPY . .

RUN npm install --legacy-peer-deps # 종속성 충돌 무시하고 설치

RUN npm run build

# Step 2: Nginx로 서비스
FROM nginx:alpine

# 기본 nginx 설정 파일을 삭제한다. (custom 설정과 충돌 방지)
RUN rm /etc/nginx/conf.d/default.conf
# custom 설정파일을 컨테이너 내부로 복사한다.
COPY conf/conf.d/default.conf /etc/nginx/conf.d/default.conf

# nginx 가 동작하기 위해 필요한 파일들을 시스템으로 복사
COPY --from=build /app/build /usr/share/nginx/html

# 포트 노출
EXPOSE 80
EXPOSE 443

# Nginx 실행
CMD ["nginx", "-g", "daemon off;"]

build.gradle

jar {
    enabled = false
}

위 설정 없이 빌드를 하면 기본 .jar / plain.jar 파일 두개가 생성되는데 jar가 2개면 도커 이미지가 생성되지 않을 수도 있기 때문에 추가해줘야 한다.

6. jar 파일 생성

CMD에서 프로젝트 폴더로 위치를 이동하고 빌드하여 jar 파일을 생성한다.
IDE의 힘을 빌릴 수 없는 경우가 발생할 수 있다 (배포 서버 내부에서 작업해야 하는경우)

spring
-x test : test를 진행 X

./gradlew clean build -x test
chmod +x gradlew 

현재 nginx 때문에 자바11버전. 테스트 끝나고 17로 변경하기

react

sudo npm run build

✅ 리액트 빌드 전 주의사항!

  • 도메인 변경
    "proxy": "http://localhost:8080",
    로컬 테스트용
    "proxy": "https://tmarket.store",
    배포용
  • index.html 주석 해제하기
    <meta http-equiv="content-security-policy" content="upgrade-insecure-requests" />
  • marketApi
    주석처리
    //export const API_SERVER_HOST = `http://localhost:8080`
    주석해제
    export const API_SERVER_HOST = `https://tmarket.store`

7. docker hub 회원가입& local에서 로그인

local 로그인

CMD에서 'docker login -u {ID}' 를 입력하면 Password를 입력할 수 있다.

docker login -u won1110218

둘 다 맞게 입력해주면 내 docker hub에 작업이 가능하다.
(* ID 또는 로그인 ID(이메일)을 입력해주면 된다.)

8. jar 파일을 이미지로 생성 & 만든 이미지를 docker hub에 push

OS에 맞게 도커 이미지 빌드 & 푸시

⭐️spring

docker build -t {ID} / {이미지명}:{태그} {DockerFile 위치}
docker buildx build --push --platform linux/amd64 -t won1110218/tmarket-back .

docker desktop에 아래 내용으로 만들어진다.
Name => won1110218/myblog_image
Tag => 1.0

※ tag를 지정하지 않으면 자동으로 latest 으로 저장되며 push할 때 tag를 입력하지 않아도 된다.

⭐️react

docker buildx build --push --platform linux/amd64 -t won1110218/tmarket-front .   

or

docker buildx build --push --platform linux/amd64 --no-cache -t won1110218/tmarket-front . 
  • --no-cache: 이전 빌드의 캐시를 사용하지 않고 모든 단계를 새로 실행한다. 빌드 시간은 더 오래 걸릴 수 있지만 모든 변경사항이 확실히 반영된다.

올라간 건 docker hub - Repositories에서 확인 할 수 있다.

9. EC2 서버에서 다운로드

이제 docker hub에 있는 이미지를 서버에 받아오기만 하면 된다.
다만, 지금까지 오류 없던 이미지가 다운로드 후에 오류가 나는 경우에 많으니 local에 한 번 다운로드 & 실행해보고 하는 것이 좋다.

※ 리눅스에서는 꼭 먼저 sudo를 써줘야 하는데 번거롭다면 sudo su- 을 써서 root로 이동하면 sudo를 쓰지 않아도 된다.

sudo su-

docker login -u {ID}
Password : 

docker pull won1110218/tmarket-back

이전에 pull 받은 이미지가 존재한다면 삭제

docker compose down
docker rmi won1110218/tmarket-back

10. Compose.yml EC2로 복사

scp -i /Users/choihyewon/Desktop/Work/tmarket-key.pem /Users/choihyewon/Desktop/Work/tmarket/compose.yml ubuntu@3.36.33.206:/home/ubuntu

에러 해결

이 오류는 SSH 키 파일의 권한이 너무 개방적이어서 발생하는 보안 관련 문제이다. SSH는 보안상의 이유로 키 파일의 권한이 너무 개방적이면 해당 키를 무시한다.

  • 키 파일의 권한을 변경한다. 소유자만 읽고 쓸 수 있도록 설정한다.
  • 권한이 올바르게 변경되었는지 확인한다. (출력은 다음과 유사해야 한다: -rw-------)

11. Compose.yml 실행

docker compose up -d --build

docker ps 로 컨테이너를 조회 해봤을 때 만든 이미지가 Up 상태로 나오면 된다.
※ 처음에는 Up 상태인데 몇 초 정도 지나면 Exited 상태로 바뀌는 경우가 많다. 여러 번 조회 해보는 게 좋다.

12. 배포 확인

아래 URL로 들어가 페이지가 나오면 성공이다.


💥 EC2 서버 멈춤(메모리 부족)

사유를 찾아보니 메모리 부족으로 인한 서버 멈춤 현상이었고 Swap을 통해 메모리를 할당하여 해결하였다.
※ 멈춘 서버를 다시 기동하려면 AWS - EC2 - 인스턴스 상태 - 재부팅하고 기다려주면 된다.

1. 로그 확인

메모리 이슈가 아닐 수 도 있으니 로그부터 확인해보는 것이 좋다.

  • 컨테이너
docker logs 컨테이너명
  • 인스턴스
    인스턴스 상태 - 모니터링 및 문제 해결 - 시스템 로그 가져오기

2. Swap 이란?

메모리 공간 부족 방지를 위한 임시 방편으로 하드 디스크의 일부를 RAM 처럼 사용할 수 있게 만드는 것이다.
리눅스 커널은 실제 메모리에 올라와 있는 메모리 불록 중 당장 사용하지 않는 것을 디스크에 저장하고 이를 통해 사용 가능한 메모리 영역을 늘린다.
메모리가 부족하던 옛날에 사용하는 방법이라고 하는데 AWS에서 프리티어로 사용중인 서버라 메모리가 넉넉하지 않으니 사용하는 방법이라고 한다.
AWS 에 swap 사용 공식 문서가 있다. > 공식 문서

  • 현재 swap 확인
    free -m

3. 스왑 늘리는 방법 순서대로 명령어를 입력해주면 된다!

  • dd 명령을 사용하여 루트 파일 시스템에 스왑 파일을 생성

    • bs = 블록크기
    • count = 블록 수 = 스왑 파일 크기
    sudo dd if=/dev/zero of=/swapfile bs=128M count=32

    or

    sudo dd if=/dev/zero of=/swapfile bs=128M count=16
  • 스왑 파일의 읽기 및 쓰기 권한을 업데이트

    sudo chmod 600 /swapfile
  • Linux 스왑 영역을 설정

    sudo mkswap /swapfile
  • 스왑 공간에 스왑 파일을 추가 -> 스왑 파일을 즉시 사용

    sudo swapon /swapfile
  • 프로시저가 성공적인지 확인

    sudo swapon -s
  • /etc/fstab 파일을 편집하여 부팅 시 스왑 파일을 시작

    sudo vi /etc/fstab
  • 파일 끝에 다음 줄을 새로 추가하고 파일을 저장한 다음 종료

    /swapfile swap swap defaults 0 0
  • 7번까지 진행하고free -m명령어를 입력해주면 0 이었던 스왑 메모리에 값이 할당 된 것을 볼 수 있다.

  1. 컨테이너 실행
    서버 재부팅을 하면서 컨테이너가 전부 중지 되었기 때문에 start 명령어를 통해 다시 실행 시켜준다.
    docker start {컨테이너명}

상태가 up 으로 유지되면 해결!



RDS 연결

RDS를 생성한 뒤 EC2가 포함되어 있는 VPC 안에 포함시킬 것!

1. RDS 생성 및 접속

퍼블릭 액세스를 허용해 주면 외부에서 누구나 접근이 가능한 상태가 된다. 이를 로컬 컴퓨터에서만 외부 접근이 가능하도록 설정할 것이다.
RDS 생성이 완료되면 엔드포인트를 확인할 수 있다. 엔드포인트가 RDS의 주소이다.

RDS가 3360 포트로 개방되어야 하는데 이 포트를 닫아두면 EC2 서버에서도 접근이 불가능하다.
하나의 VPC에 EC2와 RDS를 둘 것이다.
VPC는 외부 접근이 가능하기 때문에 외부에서 EC2로 접근했을 때 내부적으로 RDS로 접근할 수 있도록 설정해야 한다.
RDS는 EC2에서만 접근이 가능하고 외부에서 다이렉트한 접근이 안 되도록 설정이 필요하다.
그리고 RDS에 하나의 설정이더 필요하다. 나의 로컬 컴퓨터에서 접근이 가능하도록 설정해야 한다.
로컬 컴퓨터에서 3306 포트로 RDS에 접근하여 테이블 생성 등의 과정이 필요하기 때문이다.
RDS에 EC2만 접근이 가능하게 하기 위해서는 EC2의 공인 IP, 사설 IP 에서만 접근 가능하게 설정할 수 있지만 더 간단한 방법이 있다.

2. 보안 그룹 생성

인바운드 규칙 편집

보안 그룹 탭에서 RDS의 3306 포트를 열어줘야 한다.
이 때 3306 포트를 내 IP에만 열어준다. 그러면 3306 포트로는 내 로컬 컴퓨터에서만 접근이 가능하게 된다.
그 다음 EC2가 RDS에 연결이 가능해야 하기 때문에 EC2(의 보안그룹)에게도 3306 포트를 열여줘야 한다.
연결을 시켜줘야 내 프로젝트와 데이터베이스가 연결이 가능하기 때문이다.
같은 보안 그룹에서는 모두 접근이 가능하게 설정해 주도록 한다. 이렇게 하면 다른 그룹에서는 접근이 불가능하다.

✅ EC2 상황

3. 생성한 보안그룹을 RDS에 붙이기

3. 정리

  1. VPC가 외부 접근 가능한 상태
  2. EC2는 22, 80 포트가 열려있는 상태
  • 22, 80 포트는 누구나 접근 가능
  1. EC2가 RDS에 접근, RDS는 EC2와 같은 보안 그룹으로 묶여있는 상태
  • 3306 포트는 내 컴퓨터, 같은 보안 그룹만 접근 가능

현재 보안 그룹에는 EC2만 있는데 RDS를 이 보안 그룹으로 포함시키면 된다. 같은 보안 그룹끼리만 접근 가능하도록 설정할 수 있기 때문이다.
그리고 이 보안 그룹에 3306 포트를 개방한다.

4. EC2에서 RDS 접속 확인

MySQL 설치

sudo apt install mysql-server

ubuntu에서 RDS MySQL 서버 접속확인

다음 명령어를 입력하여 서버에 접속한다.

mysql -u {계정이름} -p -h {복사한 엔드포인트}
mysql -u root -p -h tmarket.crss0282yqaj.ap-northeast-2.rds.amazonaws.com
Enter password: 비밀번호 입력
mysql> exit // mysql 접속 종료

5. MySQLWorkbench 에서 RDS 접속 및 테이블 생성

6. RDS 인텔리제이에 연결

Name : @ + AWS RDS 엔드포인트
Host : AWS RDS 엔드포인트
User : mysql username
Password : mysql password
Database : 연결할 Database 이름

5. applicaion-prod.yml 수정

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${DB_ENDPOINT}:3306/${DB_NAME} 
    username: ${DB_ID}
    password: ${DB_PW}

6. compose.yml DB_ENDPOINT 환경변수 추가



ElastiCache 연결

프리티어 무료 해당 노드는 cache.t2.micro 또는 cache.t3.micro 노드이다.
프리티어 환경에서도 ElastiCache를 사용할 수 있다. 월 750 시간 ElastiCache의 cache.t2.micro 또는 cache.t3.micro 노드 사용량을 할당 받는다. ElastiCache 노드 자체에서 송수신한 트래픽에 대해서는 데이터 전송 요금이 부담되지 않는다. 다만 일부 설정에 대해 요금이 청구될 수 있으므로 유의가 필요하다.

1. 보안그룹 생성

ElastiCache에서 사용할 보안그룹을 생성한다. EC2 인스턴스가 속해있는 VPC를 선택하인바운드 규칙에 EC2의 보안그룹을 선택한다. 포트는 아래에서 적용할 6379 포트를 사용한다.

✅ EC2 상황

2. ElasticCache 클러스터 생성

현재 프로젝트에서는 Redis를 사용 중이므로 클러스터 유형은 Redis, 커스텀을 선택하고 클러스터 모드는 비활성화한다. 추가 요금이 발생할 수 있기 때문이다.
이름과 엔진은 임의로 정해주고, 노드 유형은 프리티어에서 사용 가능한 cache.t2.micro 또는 cache.t3.micro을 선택한다. 복제본 수는 0으로 설정한다. 마찬가지로 추가 요금 때문이다.

연결 설정 사용자 지정을 선택하고 포트는 Redis에서 기본으로 사용하는 6379 포트, 서브넷 그룹은 새로 생성해준다.
EC2 인스턴스가 속해있는 VPC를 선택한다.

위에서 생성했던 EC2 인스턴스의 인바운드 규칙을 설정한 보안그룹을 선택한다.

3. EC2 + redis 연결 테스트

EC2에서 정상적으로 Redis에 접속이 가능한지 확인하기 위해서 redis를 설치해볼 것이다.

  • 다음 명령을 실행하여 인스턴스의 모든 패키지를 업데이트
    sudo apt-get update
  • 다음 명령을 실행하여 gcc 설치
    sudo apt-get install gcc
  • 다음 명령을 실행하여 redis-stable 버전 설치
    wget http://download.redis.io/redis-stable.tar.gz
  • 다음 명령을 실행하여 압축풀기
    tar xvzf redis-stable.tar.gz  
  • /redis-stable 폴더로
    cd redis-stable
  • 다음 명령을 실행하여 make 다운
    sudo apt-get install make
  • 다음 명령을 실행하여 make-gui 다운
    sudo apt-get install make-guile  
  • 다음 명령을 실행하여 make 실행
    make  
  • Redis 캐시 기본 엔드포인트 복사
  • : 지우고 -p
    src/redis-cli -c -h redis.c59je1.ng.0001.apn2.cache.amazonaws.com -p 6379
  • 연결 및 테스트

5. applicaion-prod.yml 수정

    data:
    redis:
      host: ${ELASTICACHE_ENDPOINT}
      port: 6379

6. compose.yml ELASTICACHE_ENDPOINT 환경변수 추가

주의!!
ElastiCache 엔드포인트를 설정할 때 주의해야 할 주요 사항은 다음과 같다.
올바른 엔드포인트 사용:
• 클러스터 모드 비활성화: 기본 엔드포인트 사용
• 클러스터 모드 활성화: 구성 엔드포인트 사용
(포트 번호 빼고!)



도메인 생성

DNS 관리

DNS 레코드 수정



SSL 발급

  • name,point to 복사

DNS 레코드 추가

  • name의 . 앞까지 복사해서 붙여넣기
  • point to의 전체 복사해서 붙어넣고 마지막에 . 입력!

Certificate .zip 파일 배포 EC2에 복사

scp -i /Users/choihyewon/Desktop/Work/tmarket-key.pem /Users/choihyewon/Deskt
op/Work/tmarket.store.zip ubuntu@3.36.33.206:/home/ubuntu

zip 파일 압축 해제 -> certificate.crt, private.key 파일 생성

NginX 파일 수정

server {
    listen 80;
    listen [::]:80;
    server_name tmarket.store;
    # 모든 HTTP 요청을 HTTPS로 리디렉션
    return 301 https://$host$request_uri;
}

server {
	listen 443 ssl;
   	server_name tmarket.store;
   	root /usr/share/nginx/html;
	#server_name 3.36.96.0;

    include /etc/nginx/default.d/*.conf;

	# 추가
	ssl_certificate /home/ubuntu/certificate.crt; 
    ssl_certificate_key /home/ubuntu/private.key;

    # SSI 프로토 버전 설정 (권장 사항)
    ssl_protocols TLSv1.2 TLSv1.3;  # 수정: TLS 버전

    location / {
        root   /usr/share/nginx/html; # 리액트 파일 경로
        index  index.html index.htm;
        try_files $uri $uri/ /index.html; # 리액트의 SPA 라우팅을 위해 index.html로 요청 전달

        # location 안에 넣어줘야함
        proxy_buffer_size          128k;
        proxy_buffers              4 256k;
        proxy_busy_buffers_size    256k;
    }

    location /static/ {
         alias /usr/share/nginx/html/static/;
     }

		location /api/ {
				proxy_pass http://spring-app:8080;  # HTTP로 Spring 애플리케이션에 연결
				proxy_set_header Host $http_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;

				# CORS 헤더 설정
        add_header 'Access-Control-Allow-Origin' 'https://tmarket.store';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Cache-Control, X-Requested-With';

        # Preflight 요청(OPTIONS)을 위한 응답
         if ($request_method = OPTIONS) {
             add_header 'Access-Control-Allow-Origin' 'https://tmarket.store';
             add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
             add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Cache-Control, X-Requested-With';
             add_header 'Access-Control-Allow-Credentials' 'true';
             add_header 'Access-Control-Max-Age' 1728000;  # Preflight 요청을 캐시하는 시간 (20일)
             return 204;  # No Content 응답
         }
}

성공!

갱신 시 추가 작업 (nginx 재시작)

profile
어제보다 나은 오늘

0개의 댓글