Docker Compose 명분작과 함정 피하는 더 좋은 방법

텐저린티·2024년 4월 3일
0

🎯 사건의 발단

저번 Docker Compose 포스팅 2편이다.

글 쓴 목적은 두 가지.

  • Docker Compose 써야할 이유를 보강해왔다
  • DB 컨테이너랑 Server 컨테이너랑 실행 시점 함정 해결법을 보강해왔다
  • Flyway 적용한 걸 보여주고 싶었다

한마디로, 도커를 써야하는 명분작을 하러왔다.

문제는 돈

돈이 없다.
서버는 띄워야 하는데 돈이 없다.
지난번보다 가난해졌다.

어떻게든 값싸게 프로젝트를 만들고 싶은 나는 고민했고, 답을 얻었다.

Docker 컨테이너로 필요한 모든 프로그램을 띄워 AWS EC2 하나에 몰아넣는것.

AWS EC2 의 비용정책은 두 가지로 나뉜다.

구분인바운드 통신아웃바운드 통신
설명EC2 인스턴스로 요청EC2 인스턴스에서 응답
비용 비교비쌈 (상대적)쌈 (상대적)

RDS나 다른 서비스를 이용해서 발생하는 인/아웃바운드 비용 부담에서 벗어날 수 있다.

또, 하나의 EC2 인스턴스 내에서 Docker 끼리의 통신에 대해서는 비용이 발생하지 않는다고 한다.
안 쓸 이유가 없다.

AWS 에서 Micro 버전으로 3개월은 꽁짜라고 하니, 이걸 활용해서 MVP 구현하고 올려볼 생각이다.

트래픽이 많을까?

이번 플젝 도메인은 독립출판이다.

기획자 친구랑 오래 이야기를 나눠봤는데, 도메인 자체가 마이너라는게 중론이다.
애초에 독립출판이 뭔지도 모르는 사람이 더 많다고 한다.

그렇다면 예상컨데 유저는 많아야 20명 정도가 될거다.

스레드 풀

나는 스프링 부트로 백엔드 서버를 구현할거다.
스프링 부트의 최대 스레드 풀은 200이고, 항상 활성화되어 있는 스레드 풀은 10개다.

20명 정도야 아주 가볍게 소화 가능하다는 소리가 된다.

내부 통신이 더 빠를것

Docker 컨테이너끼리 통신하는 것은 로컬 네트워크를 이용한다.
EC2와 다른 서비스끼리 통신은 네트워크를 이용한다.
따라서 후자가 네트워크 지연이 발생할 가능성이 크다.

예상하는 문제점

  • 디도스 공격에 취약
  • RDS 장점 활용 불가

단일 서버니까 디도스 공격이나 서버 장애가 발생하면 서비스 전체가 셧다운된다.
일단 디도스는 HTTP 상태코드 활용해서 요청 지연으로 처리할 생각이다.
서버 안정성은 중요하게 생각하지만, 지금 나는 돈이 더 급해서 어쩔 수 없다.

RDS 장점을 활용할 수 없다는 것도 좀 크다.
저번 플젝에서 RDS 모니터링을 확인하면서 Connection Pool 문제나, CI/CD 문제를 해결했다.
이것 이외에도 장점이 많은데, 이걸 활용하지 못하게 된다.
그래도 모니터링 기능은 직접 Grafana 로 모니터링 하는 방법을 적용할 생각이고,
MySQL로 직접 쿼리 작성하면서 성능 튜닝도 해볼겸 사용하지 않기로 했다.

🏗️ 구조

저번 구조에서 달라진건

  • DB 벤더 변경
  • Redis 추가
  • Flyway 추가

📺 진행과정

아래에는 각 설정별로 Docker Compose 파일을 달리 작성한 것처럼 나눠놨다.
이렇게 나눠놓은 경우에는

$ docker compose -f 도커파일1.yaml -f 경로/도커파일2.yaml up

이렇게 해서 여러 개의 파일을 한꺼번에 띄울 수 있다.

하지만 나는 관리가 어려워서 하나로 합쳤다.
예시용으로 나눠뒀단 소리.

환경변수 사용하기

이번에 .evn 파일에 필요한 환경변수를 담아서 사용했다.
첨에 멘토님이 환경변수 사용한다길래 OS 레벨에서 명령어로 하는건줄 알았는데 아니었다.
Jaypt로 설정 보안해주는 것보다 훨씬 나은 방법인거 같다.
다만, 실제로 배포할때 환경변수도 함께 넣어주는걸 까먹고 삽질할까봐 조금 걱정된다.

위치는 상관없다.

MySQL DB Docker Container 설정

version: '3.8'  
  
services:  
  db:  
    image: mysql:8.0  
    container_name: db  
    restart: always  
    env_file:  
      - .env  
    environment:  
      MYSQL_DATABASE: ${MYSQL_DATABASE}  
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}  
    ports:  
      - '3306:3306'

지난번 포스팅과 동일하다

Flyway Docker Container 설정

DDL 버전 관리를 위해 Flyway 를 써봤다.

지난 플젝에선 DDL을 구글드라이브에 올려서 복붙으로 관리했었다.
아무래도 관리가 많이 힘들었다.

그런 고민을 이야기했을때 멘토님이 추천해준 방식이었는데, 매우 만족한다.
일단 최고 장점은 버전 관리가 된다는 것.
또 버전을 바탕으로 마이그레이션이 가능하다는 것.

진짜 효자다.
버전 별로 관리되니까 정상적으로 실행될때랑 아닐때랑 파일로 비교도 가능하다.
심지어 DB에 형상 정보까지 저장되니까 관리가 엄청 편하다.

JPA 의 ddl-auto 속성을 이용하지 않고, 직접 개발자 답게 쿼리를 작성하는 것도 마음에 든다.

적용

별도로 빌드 의존성을 추가할 필요는 없다.

  • flyway 설정 파일 작성
flyway.url=${FLYWAY_DB_URL}  
flyway.user=${FLYWAY_DB_USER}  
flyway.password=${FLYWAY_DB_PASSWORD}  
  
flyway.encoding=UTF-8  
flyway.outOfOrder=true  
flyway.validateOnMigrate=true  
flyway.connectRetries=60  
flyway.connectRetriesInterval=5
  • SQL 파일 작성
CREATE TABLE IF NOT EXISTS members  
(  
    member_id BIGINT NOT NULL COMMENT '멤버아이디' AUTO_INCREMENT,  
    name VARCHAR(255) NOT NULL COMMENT '멤버 실명',  
    social_id VARCHAR(255) NOT NULL COMMENT '로그인 소셜 아이디',  
    client_provider VARCHAR(255) NOT NULL COMMENT '로그인 클라이언트',  
    role_type enum('USER', 'AUTHOR', 'ADMIN') NOT NULL COMMENT '멤버 타입',  
    nickname VARCHAR(255) COMMENT '멤버 닉네임',  
    profile_img VARCHAR(255) COMMENT '멤버 프로필',  
    default_address VARCHAR(255) COMMENT '기본 배송지',  
    default_address_detail VARCHAR(255) COMMENT '기본 배송지 상세',  
    default_cash_receipt_type ENUM('PERSON', 'COMPANY') COMMENT '기본 현금영수증 타입',  
    default_cash_receipt_number VARCHAR(255) COMMENT '기본 현금영수증 번호',  
    PRIMARY KEY (member_id),  
    UNIQUE KEY unique_socialid_and_clientprovider (social_id, client_provider)  
) ENGINE = InnoDB  
  DEFAULT CHARSET = utf8mb4;
  • 도커 컴포즈 파일 작성
version: "3.8"

services:
	db-migrate:  
	  image: flyway/flyway:7  
	  container_name: db-migrate  
	  env_file:  
	    - .env  
	  environment:  
	    - FLYWAY_DB_URL=jdbc:mysql://db/${MYSQL_DATABASE}  
	    - FLYWAY_DB_USER=${FLYWAY_DB_USER}  
	    - FLYWAY_DB_PASSWORD=${FLYWAY_DB_PASSWORD}  
	  command: migrate  
	  volumes:  
	    - ./goguma-bookstore-server/db/flyway.conf:/flyway/conf/flyway.conf  
	    - ./goguma-bookstore-server/db/migration:/flyway/sql

결과

Redis Docker Container 설정

JWT 토큰 관리 방식을 변경했다.
지난번에 단일 서버라는 이유로 카페인 캐시를 활용해서 토큰 관리를 했다.

단일 서버 한정으로 좋은 방법이겠지만,
캐시의 본래 의도를 제대로 사용하지 않은 것 아니냐는 말에도 일리가 있다고 생각했다.

그래서 오버디벨롭이라고 하더라도 REDIS 를 활용해서 토큰관리를 하고,
캐시는 조회 성능 향상이라는 원래 목적에 사용해보려고 한다.

build.gradle 설정

dependencies {
	// REDIS  
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'  
	testImplementation "com.redis.testcontainers:testcontainers-redis-junit:1.6.4"
}

application.yaml 설정

spring:
	data:  
	  redis:  
	    host: localhost  # Docker 내부에서 돌리니까 로컬 호스트
	    port: 6379

docker-compose.yaml 설정

version: "3.8"

services:
	redis:  
		  image: redis:6  
		  container_name: redis  
		  ports:  
		    - '6379:6379'

결과

Spring Boot Jar Docker Container 설정

여기가 핵심이다.
지난 포스팅 내내 도커가 함정을 파놨다느니 뭐니 칭얼댔던 문제를 쉽게 해결하는 방법을 알아냈다.

구분변경 후변경 전
방식restart로 컨테이너 실행healthcheck 로 실행시점 결정
설명server가 on_failure이면 컨테이너 재시작의존하는 컨테이너가 정상적으로 실행되어야 다음 컨테이너 실행
가독성좋음나쁨
성능계속 컨테이너 시작을 하기 때문에 성능상 안 좋음이미 시작한 컨테이너에 ping 같은 명령어만 주기적으로 실행하는 것이므로 성능상 이점
의존성docker compose 가 제공하는 기능이므로 DB 벤더에 영향 없음healthcheck를 위해 DB 벤더 별로 다른 명령어를 사용해야 함

이정도로 요약해봤다.

docker-compose.yaml

  • 변경 후
    - restart 기능으로 컨테이너 실행
version: "3.8"

services:
	server:  
		container_name: server  
		# 여기부터
		restart: on-failure  
		depends_on:  
		    - db 
		# 여기까지 
		env_file:  
		    - .env  
		build:  
		    context: ./goguma-bookstore-server  
		    dockerfile: Dockerfile  
		ports:  
		    - "8080:8080"  
		links:  
		    - db  
		environment:  
		    SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/${MYSQL_DATABASE}  
		    SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}  
		    SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
  • 변경 전
    - healthcheck 기능 기반으로 컨테이너 실행 시점 결정
services:  
  db:  
    container_name: db 
    image: postgres:${version} 
    environment:  
      - 'POSTGRES_USER=${username}'  
      - 'POSTGRES_PASSWORD=${password}'  
      - 'POSTGRES_DB={schema_name}'  
    ports:  
      - "5432:5432"  
    healthcheck:  
      test: [ "CMD-SHELL", "pg_isready -h db -p 5432" ]  
  
  server:  
    container_name: server  
    depends_on:  # 내 삽질의 주인공
      db:  # db 서비스에 의존성이 있음
        condition: service_healthy  # db 서비스의 헬스체크가 성공한 경우에 해당 서비스 실행
    build:  
      context: ./happyparking-server 
      dockerfile: Dockerfile 
    environment: 
      - SPRING_PROFILES_ACTIVE=default,auth,docs,monitor,prod  
    ports: 
      - "8080:8080"  
    healthcheck:  # prometheus 를 위한 헬스 체크
      test: [ "CMD-SHELL", "curl -f http://localhost:9292/actuator/health || exit 1" ]  
      interval: 10s  
      timeout: 5s  
      retries: 3

Docker Compose 띄우기

이렇게 띄워진다.

profile
개발하고 말테야

0개의 댓글