AWS EC2 서버와 local에 docker를 설치하고 local에서 프로젝트 jar 파일을 실행하는 이미지를 만들어
docker hub에 업로드하고 EC2 서버에서 받아와 실행하는 방식이다.
※ EC2 : AWS에서 사용자가 가상 컴퓨터를 임대 받아 그 위에 자신만의 컴퓨터 애플리케이션들을 실행할 수 있게 한다.
✅ Ubuntu 22.04 LTS 선택
OS를 선택하는 단계이다. OS(운영체제)란 Mac, Windows 7, Windows 10, Windows 11 같은 것들이 OS이다.
하지만 Windows나 Mac OS는 생각보다 용량도 많이 차지하고 성능도 많이 잡아먹는다.
그래서 서버를 배포할 컴퓨터의 OS는 훨씬 가벼운 Ubuntu를 많이 사용한다.
우선 인스턴스라는 뜻부터 정리하고 가자. 인스턴스란, AWS EC2에서 빌리는 컴퓨터 1대를 의미한다. 그럼 인스턴스 유형은 무슨 뜻일까? 컴퓨터 사양을 의미한다. 컴퓨터 사양이 좋으면 좋을수록 많은 수의 요청을 처리할 수 있고, 무거운 서버나 프로그램을 돌릴 수 있다.
프리 티어에 해당하는 t2.micro
를 사용할 것이다.
키 페어(Key Pair)는 무슨 뜻일까? EC2 컴퓨터에 접근할 때 사용하는 비밀번호라고 생각하면 된다. 말 그대로 열쇠(Key, 키)의 역할을 한다.
root 계정으로
sudo su
Spring Boot는 3.x.x 버전을 사용할 예정이고, JDK는 17버전을 사용할 예정이다.
sudo apt update && /
sudo apt install openjdk-17-jdk -y
java -version
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
docker -v # Docker 버전 확인
docker compose version # Docker Compose 버전 확인
프로젝트 폴더 안에 Dockerfile 파일 생성
(* 대소문자까지 동일해야 하고 확장자는 없이 만든다.)
Dockerfile은 인프라스트럭쳐의 프로비저닝(서버 환경 셋팅)이라고 생각하면 된다.
docker build라는 명령어를 통해서 Docker가 Dockerfile을 읽어서 자동으로 도커 이미지를 빌드한다.
spring
FROM openjdk:17-jdk
WORKDIR /app
COPY build/libs/test.jar app.jar
EXPOSE 80
CMD ["java", "-jar", "app.jar"]
FROM
: 토대가 되는 이미지 지정WORKDIR
: RUN, CMD, ENTRYPOINT, ADD, COPY에 정의된 명령어를 실행하는 작업 디렉터리를 지정COPY
: 기존 파일을 복사하여 이미지에 파일이나 폴더를 추가EXPOSE
: 통신에 사용할 포트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개면 도커 이미지가 생성되지 않을 수도 있기 때문에 추가해줘야 한다.
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`
CMD에서 'docker login -u {ID}' 를 입력하면 Password를 입력할 수 있다.
docker login -u won1110218
둘 다 맞게 입력해주면 내 docker hub에 작업이 가능하다.
(* ID 또는 로그인 ID(이메일)을 입력해주면 된다.)
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에서 확인 할 수 있다.
이제 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
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-------)
docker compose up -d --build
docker ps 로 컨테이너를 조회 해봤을 때 만든 이미지가 Up 상태로 나오면 된다.
※ 처음에는 Up 상태인데 몇 초 정도 지나면 Exited 상태로 바뀌는 경우가 많다. 여러 번 조회 해보는 게 좋다.
아래 URL로 들어가 페이지가 나오면 성공이다.
사유를 찾아보니 메모리 부족으로 인한 서버 멈춤 현상이었고 Swap을 통해 메모리를 할당하여 해결하였다.
※ 멈춘 서버를 다시 기동하려면 AWS - EC2 - 인스턴스 상태 - 재부팅하고 기다려주면 된다.
메모리 이슈가 아닐 수 도 있으니 로그부터 확인해보는 것이 좋다.
docker logs 컨테이너명
메모리 공간 부족 방지를 위한 임시 방편으로 하드 디스크의 일부를 RAM 처럼 사용할 수 있게 만드는 것이다.
리눅스 커널은 실제 메모리에 올라와 있는 메모리 불록 중 당장 사용하지 않는 것을 디스크에 저장하고 이를 통해 사용 가능한 메모리 영역을 늘린다.
메모리가 부족하던 옛날에 사용하는 방법이라고 하는데 AWS에서 프리티어로 사용중인 서버라 메모리가 넉넉하지 않으니 사용하는 방법이라고 한다.
AWS 에 swap 사용 공식 문서가 있다. > 공식 문서
free -m
dd 명령을 사용하여 루트 파일 시스템에 스왑 파일을 생성
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 이었던 스왑 메모리에 값이 할당 된 것을 볼 수 있다.
docker start {컨테이너명}
상태가 up 으로 유지되면 해결!
RDS를 생성한 뒤 EC2가 포함되어 있는 VPC 안에 포함시킬 것!
퍼블릭 액세스를 허용해 주면 외부에서 누구나 접근이 가능한 상태가 된다. 이를 로컬 컴퓨터에서만 외부 접근이 가능하도록 설정할 것이다.
RDS 생성이 완료되면 엔드포인트를 확인할 수 있다. 엔드포인트가 RDS의 주소이다.
RDS가 3360 포트로 개방되어야 하는데 이 포트를 닫아두면 EC2 서버에서도 접근이 불가능하다.
하나의 VPC에 EC2와 RDS를 둘 것이다.
VPC는 외부 접근이 가능하기 때문에 외부에서 EC2로 접근했을 때 내부적으로 RDS로 접근할 수 있도록 설정해야 한다.
RDS는 EC2에서만 접근이 가능하고 외부에서 다이렉트한 접근이 안 되도록 설정이 필요하다.
그리고 RDS에 하나의 설정이더 필요하다. 나의 로컬 컴퓨터에서 접근이 가능하도록 설정해야 한다.
로컬 컴퓨터에서 3306 포트로 RDS에 접근하여 테이블 생성 등의 과정이 필요하기 때문이다.
RDS에 EC2만 접근이 가능하게 하기 위해서는 EC2의 공인 IP, 사설 IP 에서만 접근 가능하게 설정할 수 있지만 더 간단한 방법이 있다.
보안 그룹 탭에서 RDS의 3306 포트를 열어줘야 한다.
이 때 3306 포트를 내 IP에만 열어준다. 그러면 3306 포트로는 내 로컬 컴퓨터에서만 접근이 가능하게 된다.
그 다음 EC2가 RDS에 연결이 가능해야 하기 때문에 EC2(의 보안그룹)에게도 3306 포트를 열여줘야 한다.
연결을 시켜줘야 내 프로젝트와 데이터베이스가 연결이 가능하기 때문이다.
같은 보안 그룹에서는 모두 접근이 가능하게 설정해 주도록 한다. 이렇게 하면 다른 그룹에서는 접근이 불가능하다.
✅ EC2 상황
- VPC가 외부 접근 가능한 상태
- EC2는 22, 80 포트가 열려있는 상태
- 22, 80 포트는 누구나 접근 가능
- EC2가 RDS에 접근, RDS는 EC2와 같은 보안 그룹으로 묶여있는 상태
- 3306 포트는 내 컴퓨터, 같은 보안 그룹만 접근 가능
현재 보안 그룹에는 EC2만 있는데 RDS를 이 보안 그룹으로 포함시키면 된다. 같은 보안 그룹끼리만 접근 가능하도록 설정할 수 있기 때문이다.
그리고 이 보안 그룹에 3306 포트를 개방한다.
sudo apt install mysql-server
다음 명령어를 입력하여 서버에 접속한다.
mysql -u {계정이름} -p -h {복사한 엔드포인트}
mysql -u root -p -h tmarket.crss0282yqaj.ap-northeast-2.rds.amazonaws.com
Enter password: 비밀번호 입력
mysql> exit // mysql 접속 종료
Name
: @ + AWS RDS 엔드포인트
Host
: AWS RDS 엔드포인트
User
: mysql username
Password
: mysql password
Database
: 연결할 Database 이름
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://${DB_ENDPOINT}:3306/${DB_NAME}
username: ${DB_ID}
password: ${DB_PW}
DB_ENDPOINT
환경변수 추가프리티어 무료 해당 노드는 cache.t2.micro 또는 cache.t3.micro 노드이다.
프리티어 환경에서도 ElastiCache를 사용할 수 있다. 월 750 시간 ElastiCache의 cache.t2.micro 또는 cache.t3.micro 노드 사용량을 할당 받는다. ElastiCache 노드 자체에서 송수신한 트래픽에 대해서는 데이터 전송 요금이 부담되지 않는다. 다만 일부 설정에 대해 요금이 청구될 수 있으므로 유의가 필요하다.
ElastiCache에서 사용할 보안그룹을 생성한다. EC2 인스턴스가 속해있는 VPC를 선택하고 인바운드 규칙에 EC2의 보안그룹을 선택한다. 포트는 아래에서 적용할 6379 포트를 사용한다.
현재 프로젝트에서는 Redis를 사용 중이므로 클러스터 유형은
Redis
, 커스텀을 선택하고 클러스터 모드는 비활성화한다. 추가 요금이 발생할 수 있기 때문이다.
이름과 엔진은 임의로 정해주고, 노드 유형은 프리티어에서 사용 가능한cache.t2.micro
또는cache.t3.micro
을 선택한다. 복제본 수는 0으로 설정한다. 마찬가지로 추가 요금 때문이다.
연결 설정 사용자 지정
을 선택하고 포트는 Redis에서 기본으로 사용하는 6379
포트, 서브넷 그룹은 새로 생성해준다.
EC2 인스턴스가 속해있는 VPC를 선택한다.
위에서 생성했던 EC2 인스턴스의 인바운드 규칙을 설정한 보안그룹을 선택한다.
EC2에서 정상적으로 Redis에 접속이 가능한지 확인하기 위해서 redis를 설치해볼 것이다.
sudo apt-get update
sudo apt-get install gcc
wget http://download.redis.io/redis-stable.tar.gz
tar xvzf redis-stable.tar.gz
cd redis-stable
sudo apt-get install make
sudo apt-get install make-guile
make
:
지우고 -p
src/redis-cli -c -h redis.c59je1.ng.0001.apn2.cache.amazonaws.com -p 6379
data:
redis:
host: ${ELASTICACHE_ENDPOINT}
port: 6379
ELASTICACHE_ENDPOINT
환경변수 추가주의!!
ElastiCache 엔드포인트를 설정할 때 주의해야 할 주요 사항은 다음과 같다.
올바른 엔드포인트 사용:
• 클러스터 모드 비활성화: 기본 엔드포인트 사용
• 클러스터 모드 활성화: 구성 엔드포인트 사용
(포트 번호 빼고!)
www
-> http://www.tmarket.store@
-> http://tmarket.store (이것으로 수정함!!)scp -i /Users/choihyewon/Desktop/Work/tmarket-key.pem /Users/choihyewon/Deskt
op/Work/tmarket.store.zip ubuntu@3.36.33.206:/home/ubuntu
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 응답
}
}