가수면

EC2로 Docker를 이용해 배포하기 (feat. Mysql) 본문

웹 개발/웹 개발

EC2로 Docker를 이용해 배포하기 (feat. Mysql)

니비앙 2024. 1. 12. 04:00

개발 환경 설정

문서는 다음 과정을 따른다.

Dockerfile을 생성해 도커 내부로 파일을 옮긴 뒤 도커 이미지를 생성하고,

Docker Compose로 컨테이너를 실행한다. (도커 컴포즈를 사용하지 않고 각각 실행하는 법은 후술)

실행이 완료되는 것을 확인하면 EC2에 적용한다.

 

예제는 스프링 부트와 Mysql을 포함한 예제를 사용한다.

+ Next 도커 배포 방법 추가

 

준비물:

1. 도커 설치

2. 도커 허브 회원가입

3. 스프링 부트 프로젝트

도커에서 실행시키기 위한 프로젝트 빌드 준비

1. jar 파일 이름 변경

스프링 부트의 빌드를 위해 편의상 jar파일의 이름을 app.jar로 변경한다.

build.gradle에 아래 코드 추가 후 리프레쉬

bootJar {
	archiveFileName = 'app.jar'
}

2. Dockerfile 생성

루트 경로에 스프링 부트를 위한 Dockerfile 생성

FROM openjdk:17				// 실행 환경(node 등)
ARG JAR_FILE=build/libs/app.jar		// jar파일 변수로 지정
COPY ${JAR_FILE} ./app.jar		// 도커 컨테이너 내부로 카피
ENV TZ=Asia/Seoul
ENTRYPOINT ["java","-jar","./app.jar"]	// 실행시킬 명령어(순서대로 명령어 입력됨)

루트 경로에 database폴더 생성 뒤 Dockerfile 생성 (redis 등 데이터베이스 외 추가 컨테이너만들 것이 있다면 같은 원리)

FROM mysql:8.0.34

ENV TZ=Asia/Seoul

 

도커에서 데이터베이스의 한글이 깨지는 경우를 대비한 설정

/database/config/mysql.cnf

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
skip-character-set-client-handshake

[mysqldump]
default-character-set=utf8mb4

3. application.yml 설정

사용자 프로필을 분리해서 작성한 예제)

spring:
  profiles:
    active: local
    group:
      local:
        - common
      prod:
        - common

---
spring:
  config:
    activate:
      on-profile: common

jwt:
  secret-key: ${JWT_SECRET_KEY}
  token:
    expired-time-ms: 2592000000

logging:
  level:
    org:
      springframework: info

---
spring:
  config:
    activate:
      on-profile: local
  datasource:
    url: jdbc:mysql://localhost:3306/todos
    username: jhchoi1182
    password: ${SPRING_DATASOURCE_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

todo:
  recommendation:
    base:
      url: http://localhost:8080/dir/

---
spring:
  config:
    activate:
      on-profile: prod
  datasource:
    url: jdbc:mysql://todo-database:3306/todos		// jdbc:mysql://(mysql의 컨테이너명):3306/(mysql의 데이터베이스 이름) 
    username: jhchoi1182
    password: ${SPRING_DATASOURCE_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

todo:
  recommendation:
    base:
      url: http://52.79.113.40/dir/

 

application.yml의 env 변수들은 .env에 설정해도 적용되지 않는다. (.env 파일 내 변수들은 도커 컴포즈를 통해 컨테이너로 실행될 때 docker-compose 파일을 통해 application.yml에 주입됨)

로컬 환경에서 환경 변수를 지정하려면 인텔리J IDE 자체에 변수를 설정하면 된다.

 

도커 컴포즈 구성

1. .env 파일 만들기

.env 파일에 환경 변수를 설정하면 컨테이너로 실행될 때 docker-compose 파일내 변수에 적용된다.

이때 중요한 것은 프로필을 local, prod 나눠서 관리할 경우 'SPRING_PROFILES_ACTIVE=prod'을 설정해줘야 컨테이너 실행 시 프로필이 prod로 스위칭 되어 실행된다.

.env 예시)

SPRING_DATASOURCE_PASSWORD=321321
SPRING_PROFILES_ACTIVE=prod
JWT_SECRET_KEY=aslkaslfmlqwknflkqnlwfnqlwnfqwlnfqlwfnqwf!!@$

2. 도커 컴포즈 파일 만들기

docker-compose-local.yml과 docker-compose.yml로 분리하여 개발 환경에서는 스프링 부트를 컨테이너로 만들지 않고 인텔리J로 로깅을 확인하는 것이 디버깅이나 여러 면에서 좋다.

docker-compose.yml 예시)

docker-compose-local.yml에는 todo-app을 빼면 됨

version: "3.8"                                          # 파일 규격 버전
services:                                               # 이 항목 밑에 실행하려는 컨테이너들을 정의
  todo-redis:                                           # 서비스명
    container_name: todo-redis                          # 컨테이너 명
    build:
      dockerfile: Dockerfile
      context: ./redis                                  # 도커 파일 경로
    image: jhchoi1182/todo-redis                        # 도커 허브아이디/사용할 이미지 이름
    ports:
      - "6379:6379"
  todo-database:
    container_name: todo-database
    build:
      dockerfile: Dockerfile
      context: ./database
    image: jhchoi1182/todo-database
    environment:
      - MYSQL_DATABASE=todos
      - MYSQL_ROOT_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
      - MYSQL_USER=jhchoi1182
      - MYSQL_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
    volumes:
      - ./database/config:/etc/mysql/conf.d
      - ./database/init:/docker-entrypoint-initdb.d
    ports:
      - "3306:3306"      # 접근 포트 설정 (컨테이너 외부:컨테이너 내부)
  todo-app:
    container_name: todo-app
    build: .
    depends_on:          # DB, REDIS 컨테이너가 실행된 다음 WEB을 실행시킨다.
      - todo-database
      - todo-redis
    image: jhchoi1182/todo-app
    environment:         # application.yml에 환경변수 주입
      - SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
      - SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE}
      - JWT_SECRET_KEY=${JWT_SECRET_KEY}
    ports:
      - "80:8080"
    restart: always # depends on은 실행 순서만 컨트롤 할뿐,
      # 컨테이너 안의 서비스가 실행가능한 상태인지까지는 확인 하지 않기 때문에
    # DB 또는 Redis가 아직 실행가능한 상태가 아니여서 실패하는 경우 재시작 하도록 설정
    # DB가 완전히 실행될 때까지 스프링 부트가 계속 오류가 발생하기 때문에 스프링 부트 실행까지 꽤 오래걸릴 수 있다.

3. 빌드 및 도커 컴포즈 파일 실행

소스 코드의 변화가 있을 때마다 이 부분을 반복하면 된다.

// gradle 캐쉬 삭제 및 빌드
./gradlew clean build
// docker-compose 파일 실행
docker-compose up --build

// (참고) 테스트 케이스 제외하고, jar 파일 빌드만 진행할 경우
./gradlew clean build -x test
// (참고) docker-compose-local 파일 실행
docker-compose -f docker-compose-local.yml up

도커 컴포즈로 생성한 컨테이너 삭제 명령어

docker-compose down

EC2 설정 및 컨테이너 실행

로컬에서 문제없이 작동하는 것을 확인했다면 EC2 서버 설정 후 로컬에서 했던 것처럼 실행시켜주면 된다.

 

인바운드 규칙 설정

 

※ ec2 파티션에 스왑 공간 할당하기

프리 티어를 사용할 경우 도커 컨테이너를 여러개 실행시키게 되면 cpu 사용량이 폭발하며 ec2가 렉걸리고 멈추는 현상이 발생한다...

이 경우 스왑 공간을 할당해 RAM을 확보 하여 허접한 메모리 문제를 해결할 수 있다. (렉이 사라진다!)

참고로 프리 티어의 경우 4GB보단 2GB를 추천한다.

// 4GB(128MB x 32)
sudo dd if=/dev/zero of=/swapfile bs=128M count=32

// 2GB(128MB x 16)
sudo dd if=/dev/zero of=/swapfile bs=128M count=16

https://repost.aws/ko/knowledge-center/ec2-memory-swap-file

 

스왑 파일을 사용하여 Amazon EC2 인스턴스의 스왑 공간으로 메모리 할당

Amazon Elastic Compute Cloud(Amazon EC2) 인스턴스에서 스왑 파일로 사용할 메모리를 할당하려고 합니다. 어떻게 해야 하나요?

repost.aws

 

EC2 인스턴스 생성 후 기본 설정

1. git 설치

#Perform a quick update on your instance:
$ sudo yum update -y

#Install git in your EC2 instance
$ sudo yum install git -y

#Check git version
$ git version

2. 도커 및 도커 컴포즈 설치

//도커 설치  
$ sudo yum install docker
$ docker -v

// 도커 컴포즈 설치 
$ sudo curl -L https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose   

// 도커 시작하기     
$ sudo systemctl start docker

// 실행 권한 적용   
$ sudo chmod +x /usr/local/bin/docker-compose    
$ sudo chmod 666 /var/run/docker.sock
$ docker-compose -v

만약

Error loading Python lib '/tmp/_MEITz7le8/libpython3.7m.so.1.0': dlopen: libcrypt.so.1: cannot open shared object file: No such file or directory

오류가 발생했을 경우 아래 명령어 입력한 후 docker-compose -v를 다시 해보면 설치된 것을 확인할 수 있다.

sudo dnf install libxcrypt-compat

3. JDK 설치

Amazon에서 제공하는 OpenJDK인 Amazon Coretto를 이용해 설치 진행

// aws coreetto11 다운로드 후 설치
sudo curl -L https://corretto.aws/downloads/latest/amazon-corretto-11-x64-linux-jdk.rpm -o jdk11.rpm
sudo yum localinstall jdk11.rpm

// aws coreetto17 다운로드 후 설치
sudo curl -L https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jdk.rpm -o jdk17.rpm
sudo yum localinstall jdk17.rpm

java -version

4. 프로젝트 clone 따오기

5. .env 파일 생성 후 설정하기

프로젝트 경로로 들어가 .env 생성해서 작성 후 저장

// .env 만들기
vi .env

// 작성 후 저장/나가기
:wq

환경 변수가 잘 주입되었는지 확인하는 법

docker-compose config

 

6. jar 파일 생성

./gradlew clean build
// ./gradlew clean build에 권한없다는 오류가 발생할 경우 아래 명령어 입력
chmod +x ./gradlew

// 테스트 케이스 제외하고, jar 파일 빌드만 진행할 경우
./gradlew clean build -x test

7. 도커 컴포즈 실행

docker-compose up --build

// 백그라운드에서 실행
docker-compose up --build -d
// 로그 확인
docker logs [컨테이너 ID 또는 이름]

8. 코드 변경 사항 적용

깃허브 원경 저장소에서 pull 이후 아래 명령어를 통해 빌드, 컨테이너 실행 재수행

// ec2 렉걸려서 멈출 경우 먼저 컨테이너 실행 중지
docker-compose stop

./gradlew clean build
docker-compose up --build
// 백그라운드에서 실행
docker-compose up --build -d

//도커 이미지 정리
docker image prune

도커 컴포즈 없이 각각 컨테이너를 실행시키는 경우

도커 컴포즈의 원리는 이렇다.

도커 환경에서 네트워크를 생성한 후 컨테이너를 실행시킬 때 컨테이너들을 해당 네트워크 환경으로 묶어서 실행시킨다.

 

이 케이스에선 그것을 수동으로 해주는 방법을 살펴본다.

 

프로젝트의 설정은 도커 컴포즈 설정없이 application.yml 설정만 하면 된다. 이때 주의할 점은 DB 연결 url을 jdbc:mysql://(mysql의 컨테이너명):3306/(mysql의 데이터베이스 이름)으로 설정해야 한다.

 

1. 네트워크 생성

// 도커 네트워크 생성
docker network create springboot-mysql-net(네트워크 이름)
// 도커 네트워크 확인
docker network ls

2. db 등 컨테이너 생성 및 실행

이때 네트워크 설정을 1번에서 생성한 네트워크로 설정한다.

docker run --detach --env MYSQL_ROOT_PASSWORD=321321 --env MYSQL_USER=jhchoi1182 --env MYSQL_PASSWORD=321321 --env MYSQL_DATABASE=todos --name todo-database --network springboot-mysql-net --publish 3306:3306 mysql:8.0.34

3. Dockerfile 생성

4. 빌드, 도커 이미지 생성, 컨테이너 실행

이때도 역시 스프링 부트의 네트워크 설정을 1번에서 생성한 네트워크로 설정한다.

필요한 경우 --env SPRING_PROFILES_ACTIVE=prod설정을 추가한다.

// 캐쉬 삭제 후 빌드
./gradlew clean build

// 스프링 부트 도커 이미지 생성
docker build -t jhchoi1182/todo-app .


// 스프링 부트 컨테이너 실행
docker run --detach --name springboot-mysql --network springboot-mysql-net --publish 8080:8080 jhchoi1182/todo-app

'웹 개발 > 웹 개발' 카테고리의 다른 글

SQL 쿼리  (0) 2024.03.27
쿠키 보안 설정하기 (withCredentials, httpOnly 등)  (0) 2024.02.28
IntelliJ 단축키 정리  (1) 2024.01.09
HTTP 상태 코드 정리  (0) 2023.12.19
MySQL  (0) 2023.12.14
Comments