프로그램 개발시 로컬에서는 돌아가지만 서버에서는 안되는 경우가 빈번하게 발생합니다
개발자의 로컬환경과 운영 서버의 환경이 달라서 발생하는 문제입니다
그렇다면 개발 환경을 통째로 박스에 담아서 가져갈 수 있다면?
⇒ 그 박스가 바로 Docker입니다
개발자의 환경
- Windows 10
- Java 8
- MySQL 5.7
- Node.js 14.x
서버 환경
- Ubuntu 20.04
- Java 11
- MySQL 8.0
- Node.js 16.x
→ 개발 환경이 다르기 때문에 코드가 같더라도 다르게 동작하거나 실행이 안될 수 있음
라이브러리 버전 충돌
운영체제 의존성
복잡한 설치 과정
“Build once, Run anyWhere”
Docker는 애플리케이션과 그 실행환경을 하나의 패키지로 묶어서 어디서든 동일하게 실행하도록 함
[애플리케이션 + 실행환경] = Docker 컨테이너
전통적인 가상화(Virtual Machine)
물리 서버
├── 호스트 OS (Windows/Linux)
├── 하이퍼바이저 (VMware, VirtualBox)
└── VM1
├── 게스트 OS (완전한 Ubuntu)
├── 라이브러리들
└── 애플리케이션
└── VM2
├── 게스트 OS (완전한 CentOS)
├── 라이브러리들
└── 애플리케이션
Docker 컨테이너화
물리 서버
├── 호스트 OS (Linux)
├── Docker Engine
└── 컨테이너1
├── 애플리케이션 라이브러리만
└── 애플리케이션
└── 컨테이너2
├── 애플리케이션 라이브러리만
└── 애플리케이션
Docker Image(도커 이미지)
Docker Container(컨테이너)
Dockerfile
# Java 17 베이스 이미지
FROM openjdk:17-jdk-slim
# 작업 디렉터리 설정
WORKDIR /app
# Gradle wrapper와 설정 파일 복사
COPY gradlew ./
COPY gradle ./gradle
COPY build.gradle ./
COPY settings.gradle ./
# gradlew 실행 권한 부여
RUN chmod +x gradlew
# 의존성 다운로드 (캐시 최적화)
RUN ./gradlew dependencies --no-daemon
# 소스 코드 복사
COPY src ./src
# 애플리케이션 빌드 (테스트 제외)
RUN ./gradlew clean build -x test --no-daemon
# JAR 파일 위치 확인 및 복사 (plain.jar 제외)
RUN find /app/build/libs -name "*.jar" ! -name "*plain.jar" -exec cp {} /app/app.jar \;
# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
Docker Registry
클래스와 인스턴스(=객체) 같은 관계
이미지 (설계도) → 컨테이너 (실행체)
Ubuntu:20.04 → ubuntu_container1
→ ubuntu_container2
→ ubuntu_container3
Node.js:16 → web_server1
→ web_server2
마이크로서비스 아키텍처
전체 서비스를 작은 단위로 분할
- 사용자 인증 서비스 (컨테이너)
- 결제 서비스 (컨테이너)
- 알림 서비스 (컨테이너)
- 상품 관리 서비스 (컨테이너)
CI/CD 파이프라인
개발 → 테스트 → 배포 과정 자동화
각 단계마다 동일한 Docker 환경 사용
클라우드 마이그레이션
온프레미스 → AWS/Azure/GCP
Docker 컨테이너로 쉽게 이전
# 베이스 이미지 선택
FROM node:16-alpine
# 작업 디렉토리 설정
WORKDIR /app
# 패키지 파일 복사
COPY package*.json ./
# 의존성 설치
RUN npm install
# 소스 코드 복사
COPY . .
# 포트 노출
EXPOSE 3000
# 실행 명령
CMD ["npm", "start"]
FROM
WORKDIR
COPY vs ADD
RUN
EXPOSE
CMD vs ENTRYPOINT
# 변경이 적은 것을 먼저
COPY package*.json ./
RUN npm install
# 변경이 많은 것을 나중에
COPY . .
node_modules
.git
.env
*.log
# 빌드 스테이지
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# 실행 스테이지
FROM nginx:alpine
COPY --from=builder /app/build /usr/share/nginx/html
복잡한 애플리케이션의 문제
웹 애플리케이션 실행시
→ 개별적으로 실행하기에는 너무 복잡함
Docker Compose의 해결책
→ 하나의 YAML 파일로 여러 컨테이너를 정의하고 함께 관리
name: returnwork
services:
app:
image: ${DOCKER_IMAGE}
ports:
- "8080:8080"
depends_on:
- mysql
- redis
deploy:
resources:
limits:
memory: 400M
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE:-prod,jwt,redis}
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/${DB_NAME:-returnWork}
- SPRING_DATASOURCE_USERNAME=${DB_USERNAME:-root}
- SPRING_DATASOURCE_PASSWORD=${DB_PASSWORD:-1234}
- JWT_SECRET=${JWT_SECRET:-}
- SPRING_DATA_REDIS_HOST=redis
- SPRING_DATA_REDIS_PORT=6379
networks:
- app-network
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD:-1234}
- MYSQL_DATABASE=${DB_NAME:-returnWork}
- MYSQL_USER=${DB_USERNAME:-chusammin}
- MYSQL_PASSWORD=${DB_PASSWORD:-1234}
ports:
- "3306:3306"
deploy:
resources:
limits:
memory: 400M
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
deploy:
resources:
limits:
memory: 100M
volumes:
- redis_data:/data
networks:
- app-network
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
mysql_data:
redis_data:
networks:
app-network:
driver: bridge
전체 구조 분석
┌─────────────────────────────────────┐
│ app-network │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │ app │ │ mysql │ │ redis │ │
│ │ :8080 │ │ :3306 │ │ :6379 │ │
│ └──────────┘ └──────────┘ └────────┘ │
└─────────────────────────────────────┘
app:
image: ${DOCKER_IMAGE} # 환경변수로 이미지 지정
ports: "8080:8080" # 웹 서버 포트
memory: 400M # 메모리 제한
restart: unless-stopped # 자동 재시작
${DOCKER_IMAGE} - CI/CD에서 동적으로 변경 가능depends_on 으로 DB/캐시 서버 먼저 실행prod , jwt , redis - 운영환경 설정mysql:
image: mysql:8.0 # 안정적인 8.0 버전
environment:
- MYSQL_DATABASE=returnWork # 자동 DB 생성
- MYSQL_USER=chusammin # 별도 사용자 생성
volumes:
- mysql_data:/var/lib/mysql # 데이터 지속성
redis:
image: redis:7-alpine # 가벼운 Alpine 버전
command: redis-server --appendonly yes # 데이터 지속성 활성화
memory: 100M # 적은 메모리 할당
volumes:
- redis_data:/data # 캐시 데이터 지속성
--appendonly yes 로 데이터 손실 방지# 모든 서비스 시작
docker-compose up
# 백그라운드 실행
docker-compose up -d
# 모든 서비스 중지
docker-compose down
# 특정 서비스만 실행
docker-compose up web
# 로그 확인
docker-compose logs
# 스케일링 (컨테이너 개수 조절)
docker-compose up --scale web=3
Bridge Network(기본)
Host Network
Overlay Network
None Network
컨테이너 이름으로 통신
services:
web:
image: nginx
api:
image: node:16
# web 컨테이너에 접근할 때
# http://web:80 으로 접근 가능
포트 매핑
# 호스트의 8080 포트를 컨테이너의 80 포트에 연결
docker run -p 8080:80 nginx
컨테이너는 기본적으로 임시적인 특징
Named Volumne(권장)
# 볼륨 생성
docker volume create my_data
# 볼륨 사용
docker run -v my_data:/app/data nginx
Bind Mount
# 호스트 디렉토리를 컨테이너에 마운트
docker run -v /host/path:/container/path nginx
tmpfs Mount
# 메모리에만 저장 (임시 데이터용)
docker run --tmpfs /tmp nginx
컨테이너 격리
권한 관리
이미지 보안
FROM node:16-alpine
# 사용자 생성
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# 사용자 전환
USER nextjs
COPY --chown=nextjs:nodejs . .
CMD ["npm", "start"]
# 환경변수로 비밀번호 전달 (비추천)
docker run -e DB_PASSWORD=secret myapp
# Docker Secrets 사용 (권장)
echo "secret_password" | docker secret create db_password -
# 보안 취약점 스캔
docker scan myapp:latest
| 구분 | Docker | Virtual Machine |
|---|---|---|
| 리소스 사용 | 적음 | 많음 |
| 시작 시간 | 초 단위 | 분 단위 |
| 격리 수준 | 프로세스 레벨 | 하드웨어 레벨 |
| 호환성 | Linux 기반 | OS 무관 |
Docker
Kubernetes
Docker 사용 권장
Docker 사용 주의
이제 Docker의 기본 개념을 이해했으니, 다음편에서는 실제로 Docker Compose를 사용해서
4개의 웹서버 환경을 구축하고 로드밸런싱 테스트를 위한 기반을 마련하겠습니다.
Phase 1: 계획 및 이론
Phase 2: 환경 구축 및 설계
Phase 3: 구현 및 테스트
Phase 4: ML 확장 및 마무리