Docker + CI,CD/Docker

Docker? 그게 도대체 뭔데?

JuNo_12 2025. 6. 17. 23:24

프롤로그: Docker를 마주하다

Spring Boot 기반의 TaskFlow 프로젝트를 진행하던 중, Docker라는 기술을 접하게 되었습니다. "Docker? 그게 뭔데?"라는 궁금증에서 시작된 학습 여정을 정리해보려 합니다.


첫 번째 질문: "Docker가 대체 뭔가요?"

 

처음 Docker를 접했을 때 가장 먼저 든 의문은 "Docker가 정확히 무엇인가?"였습니다.

Docker는 애플리케이션을 컨테이너로 패키징하는 기술입니다. 이를 이해하기 위해 이사용 컨테이너에 비유해보겠습니다.

 

기존 방식의 문제:

개발자 A의 컴퓨터: Java 17, MySQL 8.0, Ubuntu 22.04
개발자 B의 서버: Java 11, MySQL 5.7, CentOS 7

결과: "내 컴퓨터에서는 잘 되는데..." 현상 발생

 

Docker의 해결책:

Docker 컨테이너: [애플리케이션 + 실행환경] 모두 포함
결과: 어느 환경에서든 동일하게 실행

마치 이사할 때 모든 물건을 컨테이너에 넣어두면 어디로 이사를 가든 그대로 꺼내서 사용할 수 있는 것처럼, Docker는 애플리케이션과 실행 환경을 하나로 묶어서 어느 서버에서든 동일하게 실행할 수 있게 해줍니다.


두 번째 궁금증: "Docker는 어떻게 생긴 건가요?"

Docker의 장점은 이해했지만, 내부가 어떻게 구성되어 있는지 궁금했습니다.

Docker의 전체 아키텍처

내 컴퓨터 (Host OS)
├── Docker CLI (명령어 도구)
├── Docker Engine (핵심)
│   ├── Docker API
│   ├── containerd (컨테이너 관리)
│   └── runC (실제 실행기)
└── Linux Kernel
    ├── Namespaces (격리 기술)
    ├── Cgroups (자원 제한)
    └── Union FS (파일시스템)

 

Linux 커널 기술의 활용

Docker의 "마법"은 실제로는 Linux 커널의 세 가지 기술을 조합한 것입니다:

 

1. Namespaces (격리 기술)

Host 시스템에서 실행 중인 프로세스들:
- PID 100: bash
- PID 200: mysql
- PID 300: nginx

컨테이너 A 내부에서 보는 프로세스:
- PID 1: mysql (실제로는 Host의 PID 200)

컨테이너 B 내부에서 보는 프로세스:
- PID 1: nginx (실제로는 Host의 PID 300)

각 컨테이너는 자신만의 독립된 프로세스 공간을 가진 것처럼 보이지만, 실제로는 Host에서 돌아가는 프로세스들입니다.

 

2. Cgroups (자원 제한)

Host 시스템: CPU 8코어, RAM 16GB

컨테이너 A: CPU 2코어, RAM 1GB로 제한
컨테이너 B: CPU 1코어, RAM 512MB로 제한

 

3. Union File System (레이어 시스템)

mysql:8.0 이미지 구조:
├── MySQL 설정 레이어
├── MySQL 바이너리 레이어  
├── 기본 라이브러리 레이어
└── 베이스 Ubuntu 레이어

컨테이너 실행 시:
├── 쓰기 가능 레이어 (컨테이너별 고유)
└── 위의 모든 레이어들 (읽기 전용, 공유)

세 번째 의문: "가상 공간이라는데, 실제로는 어디에 저장되나요?"

"가상의 공간"이라는 표현 때문에 처음에는 뭔가 허공에 떠있는 것처럼 느껴졌습니다. 하지만 실제로는 모든 것이 물리적 하드디스크에 저장됩니다.

실제 저장 위치

Linux 시스템의 경우:
/var/lib/docker/
├── overlay2/         # 실제 컨테이너 파일들
├── containers/       # 컨테이너 설정
├── images/          # 이미지 정보
├── volumes/         # 볼륨 데이터
└── networks/        # 네트워크 설정

예를 들어, MySQL 컨테이너에서 /tmp/test.txt 파일을 생성하면, 실제로는 Host의 /var/lib/docker/overlay2/abc123.../merged/tmp/test.txt에 저장됩니다.

 

"가상"의 진짜 의미

Docker의 "가상 공간"이란:

  • 존재하지 않는 것: X
  • 실제로는 공유하지만 독립된 것처럼 보이게 하는 것: O

Linux 커널의 Namespace 기능이 각 컨테이너에게 "마치 독립된 컴퓨터인 것처럼" 보여주는 것입니다.


네 번째 탐구: "왜 굳이 독립된 것처럼 보이게 하나요?"

이론적으로는 이해가 되었지만, 왜 굳이 "독립된 것처럼" 속이는 건지 궁금했습니다.

격리 없는 현실의 문제들

문제 1: 포트 충돌

첫 번째 앱: ./app1 --port 8080  ✅ 성공
두 번째 앱: ./app2 --port 8080  ❌ Error: Port 8080 already in use

 

문제 2: 라이브러리 버전 충돌

시스템 Python: 3.8
├── 머신러닝 앱: Python 3.11 필요 ❌
├── 웹 크롤러: Python 3.7 필요 ❌  
└── API 서버: Python 3.8 사용 ✅

결과: 하나 업그레이드하면 다른 앱이 깨짐

 

문제 3: 메모리 독점

정상 상황: 웹서버 1GB + DB 2GB + 캐시 1GB = 4GB ✅
문제 상황: 위 3개 + 버그있는 앱 8GB = 서버 다운 ❌

 

실제 회사 사례

Case 1: 신입 개발자의 실수

격리 없는 환경:
신입이 메모리 누수 코드 배포 → 서버 전체 다운 → 수억원 손실

격리된 환경 (Docker):
신입이 메모리 누수 코드 배포 → 해당 컨테이너만 재시작 → 문제 해결

결국 "모든 앱이 안전하고 안정적으로 돌아가게 하려고" 독립된 것처럼 보이게 하는 거였습니다.


다섯 번째 발견: "이거 MSA 구조 같은데?"

Docker의 구조를 이해하면서 문득 "이거 마이크로서비스 아키텍처(MSA) 같은데?"라는 생각이 들었습니다.

Docker = 작은 MSA

Docker Host (가상 공간)
├── 컨테이너 A (독립 서버 1)
│   ├── DNS: user-service
│   ├── Port: 8080  
│   └── Tech: Java + Spring Boot
├── 컨테이너 B (독립 서버 2)
│   ├── DNS: order-service
│   ├── Port: 8081
│   └── Tech: Python + Flask
└── 컨테이너 C (독립 서버 3)
    ├── DNS: database
    ├── Port: 3306
    └── Tech: MySQL

정말로 Docker는 하나의 컴퓨터 안에서 여러 개의 독립된 서버를 시뮬레이션하는 "미니 MSA" 환경이었습니다.


여섯 번째 궁금증: "컨테이너끼리는 어떻게 통신하나요?"

MSA처럼 독립된 서버들이라면, 서로 다른 언어/환경에서도 HTTP 통신이 되는 게 신기했습니다.

HTTP 프로토콜의 표준화

서로 다른 언어로 작성된 컨테이너끼리도 HTTP 통신이 가능한 이유는 HTTP가 국제 표준이기 때문입니다.

 

Java 서비스에서 전송:

POST /api/users HTTP/1.1
Host: python-service:5000
Content-Type: application/json

{"name": "홍길동", "email": "hong@example.com"}

 

Python 서비스에서 수신:

POST /api/users HTTP/1.1  
Host: python-service:5000
Content-Type: application/json

{"name": "홍길동", "email": "hong@example.com"}

모든 언어의 HTTP 라이브러리가 동일한 RFC 표준을 따르므로, 어떤 조합이든 통신이 가능합니다.

 

Docker 네트워크의 마법

그런데 더 신기한 건 컨테이너들이 어떻게 서로를 찾아가는가였습니다.

 

일반적인 MSA:

서버 A: http://192.168.1.10:8080/api/users  # 실제 IP 필요
서버 B: http://192.168.1.20:8081/api/orders

 

Docker 네트워크:

컨테이너 A: http://user-service:8080/api/users    # 이름으로 접근!
컨테이너 B: http://order-service:8081/api/orders

 

Docker DNS의 동작 원리

Docker는 내장 DNS 서버를 제공합니다:

1단계: order-service에서 "user-service에 요청 보낼게"
2단계: Docker DNS (127.0.0.11): "user-service = 172.18.0.2야!"  
3단계: 실제 HTTP 요청: http://172.18.0.2:8080/api/users
# 컨테이너 내부에서 확인
nslookup user-service
# Server: 127.0.0.11 ← Docker 내장 DNS
# Address: 172.18.0.2 ← 자동 변환된 IP

마지막 질문: "독립적인데 왜 docker-compose.yml이 필요한가요?"

각 컨테이너가 독립적이라면 굳이 설정을 맞춰줄 필요가 없을 것 같았는데, 왜 docker-compose.yml이 필요한지 궁금했습니다.

 

독립적 ≠ 혼자서 모든 것

아파트에 비유해보겠습니다:

각 세대는 독립적이지만...
❌ 완전히 혼자서는 살 수 없습니다:
- 전기가 필요 (공통 인프라)
- 수도가 필요 (공통 인프라)
- 인터넷이 필요 (공통 인프라)

✅ 아파트 관리사무소가 필요한 이유:
- 모든 세대가 함께 쓸 인프라 관리
- 입주 순서 조율 (전기 먼저, 그 다음 수도...)
- 문제 발생시 통합 관리

 

Docker도 마찬가지입니다:

각 컨테이너는 독립적이지만...
❌ 완전히 혼자서는 작동 안 됩니다:
- 네트워크가 필요 (서로 통신용)
- 볼륨이 필요 (데이터 저장용)
- 실행 순서가 중요 (DB 먼저, 그 다음 앱)

✅ docker-compose.yml이 필요한 이유:
- 모든 컨테이너가 함께 쓸 인프라 관리
- 실행 순서 조율 (depends_on)
- 설정값들 통합 관리

 

수동 vs Compose 비교

docker-compose.yml 없이 수동으로:

# 매번 이런 긴 명령어들...
docker network create taskflow-network
docker run -d --name mysql --network taskflow-network -e MYSQL_ROOT_PASSWORD=secret123...
docker run -d --name redis --network taskflow-network redis:7.0
sleep 30  # DB 준비될 때까지 기다리기...
docker run -d --name app --network taskflow-network taskflow:latest

# 새 팀원: "이걸 다 외워야 하나요?"

 

docker-compose.yml 있으면:

version: '3.8'
services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
  app:
    image: taskflow:latest
    depends_on:
      - mysql
docker-compose up -d
# 새 팀원: "이게 끝이에요?"

 

오케스트라 비유

독립적인 각 악기(컨테이너)들이 아름다운 음악을 연주하려면 지휘자(docker-compose)가 필요합니다:

지휘자 없이 연주: 각자 다른 속도, 다른 키 → 소음
지휘자와 함께 연주: 같은 속도, 같은 키 → 아름다운 음악

Epilogue: Docker 여정을 마치며

처음 "Docker가 뭔가요?"라는 질문에서 시작된 여정이 "Docker = 노트북 안의 미니 MSA"라는 깨달음으로 마무리되었습니다.

 

핵심 깨달음들

  1. Docker의 본질: 애플리케이션과 환경을 함께 패키징하는 "이사용 컨테이너"
  2. 격리의 실체: Linux 커널 기술로 구현된 "선한 속임수"
  3. 네트워크의 마법: 내장 DNS로 컨테이너 이름을 IP로 자동 변환
  4. MSA의 시뮬레이션: 하나의 컴퓨터에서 여러 독립 서버 구현
  5. 협력의 필요성: 독립적이지만 공통 인프라는 함께 관리

 

실무에서의 가치

전통적 MSA: 서버 5대 + 몇 주 설정 + 수백만원
Docker MSA: 노트북 1대 + 몇 분 설정 + 무료

같은 결과, 다른 방법!

 

이 여정을 통해 단순히 도구 사용법을 배운 것이 아니라, 현대적인 소프트웨어 아키텍처의 핵심 개념들을 이해할 수 있었습니다. Docker는 단순한 컨테이너 기술으로만 이해되기엔, 유용한 아키텍처를 지니고있었습니다.