docker-compose.yml
개발 서버에서 실행 중인 Compose 파일 전문과 서비스, 포트, 볼륨, 호스트 파일 정리
개발 서버에서 실행 중인 docker-compose.yml 전문. 서버 소스 루트에 있는 파일로, Compose 설치도 이 파일을 사용. 아래 블록은 서버 저장소의 파일에서 자동 생성(scripts/gen-compose.ts). 원본의 주석은 생략하고 각 항목의 의미는 아래 표에 정리.
name: geo-mlops-mlflow
x-logging: &default-logging
driver: json-file
options:
max-size: "50m"
max-file: "5"
services:
app:
build:
context: .
args:
GIT_COMMIT: ${GIT_COMMIT:-}
BUILT_AT: ${BUILT_AT:-}
container_name: geo-mlops-app
logging: *default-logging
restart: unless-stopped
mem_limit: ${GEO_APP_MEM_LIMIT:-16g}
network_mode: host
env_file:
- path: /etc/geo-mlops/app.env
required: false
environment:
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
volumes:
- /etc/rancher/k3s/k3s.yaml:/etc/rancher/k3s/k3s.yaml:ro
healthcheck:
test:
- CMD
- python3
- -c
- "import urllib.request as u; u.urlopen('http://localhost:10000/api/v1/health', timeout=3)"
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
depends_on:
mlflow-postgres:
condition: service_healthy
createbucket:
condition: service_completed_successfully
mlflow-postgres:
image: postgres:18-alpine
container_name: geo-mlops-mlflow-postgres
logging: *default-logging
restart: unless-stopped
environment:
POSTGRES_USER: mlflow
POSTGRES_PASSWORD: mlflow
POSTGRES_DB: mlflow
PGDATA: /var/lib/postgresql/data/pgdata
ports:
- "5433:5432"
volumes:
- mlflow-postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mlflow -d mlflow"]
interval: 5s
timeout: 5s
retries: 5
rustfs:
image: rustfs/rustfs:1.0.0-beta.7
container_name: geo-mlops-mlflow-rustfs
logging: *default-logging
restart: unless-stopped
environment:
RUSTFS_ACCESS_KEY: rustfsadmin
RUSTFS_SECRET_KEY: rustfsadmin
RUSTFS_VOLUMES: /data
RUSTFS_ADDRESS: 0.0.0.0:9000
RUSTFS_CONSOLE_ENABLE: "true"
RUSTFS_CONSOLE_ADDRESS: 0.0.0.0:9001
ports:
- "9000:9000"
- "9001:9001"
volumes:
- rustfs-data:/data
createbucket:
image: amazon/aws-cli:2.34.63
container_name: geo-mlops-mlflow-createbucket
logging: *default-logging
depends_on:
- rustfs
restart: "no"
environment:
AWS_ACCESS_KEY_ID: rustfsadmin
AWS_SECRET_ACCESS_KEY: rustfsadmin
AWS_DEFAULT_REGION: us-east-1
entrypoint: >
/bin/sh -c "
until aws --endpoint-url http://rustfs:9000 s3 ls; do
echo 'waiting for rustfs...'; sleep 2;
done;
aws --endpoint-url http://rustfs:9000 s3 mb s3://mlflow || true;
aws --endpoint-url http://rustfs:9000 s3 mb s3://datasets || true;
aws --endpoint-url http://rustfs:9000 s3 mb s3://registry || true;
echo 'artifact bucket ready: s3://mlflow/';
"
prometheus:
image: prom/prometheus:v2.55.1
container_name: geo-mlops-prometheus
logging: *default-logging
restart: unless-stopped
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --web.enable-lifecycle
ports:
- "9090:9090"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./monitoring/alert-rules.yml:/etc/prometheus/alert-rules.yml:ro
- prometheus-data:/prometheus
depends_on:
- alertmanager
alertmanager:
image: prom/alertmanager:v0.27.0
container_name: geo-mlops-alertmanager
logging: *default-logging
restart: unless-stopped
command:
- --config.file=/etc/alertmanager/alertmanager.yml
- --storage.path=/alertmanager
ports:
- "9093:9093"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager-data:/alertmanager
proxpi:
image: epicwink/proxpi:1.3.0@sha256:0748b92ddf75405d9d83fbd1705517f951e67e0540af86e0a9bf4de388ba86c0
container_name: geo-mlops-proxpi
logging: *default-logging
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- proxpi-cache:/var/cache/proxpi
environment:
PROXPI_CACHE_DIR: /var/cache/proxpi
apt-cacher-ng:
image: sameersbn/apt-cacher-ng@sha256:82f55f9c8f627cee8ef5f710c1745388d79d3a3f2f3150353e6500021bec11b4
container_name: geo-mlops-apt-cacher-ng
logging: *default-logging
restart: unless-stopped
ports:
- "3142:3142"
volumes:
- apt-cacher-ng-data:/var/cache/apt-cacher-ng
volumes:
mlflow-postgres-data:
name: geo-mlops-mlflow-postgres-data
proxpi-cache:
name: geo-mlops-proxpi-cache
apt-cacher-ng-data:
name: geo-mlops-apt-cacher-ng-data
rustfs-data:
name: geo-mlops-mlflow-rustfs-data
prometheus-data:
name: geo-mlops-prometheus-data
alertmanager-data:
name: geo-mlops-alertmanager-data
서비스
| 서비스 | 컨테이너 | 이미지 | 역할 | 포트 |
|---|---|---|---|---|
app | geo-mlops-app | 소스의 Dockerfile 로 빌드 | API 서버. 내장 MLflow 와 이미지 레지스트리(/v2) 포함 | 10000 (호스트 네트워크) |
mlflow-postgres | geo-mlops-mlflow-postgres | postgres:18-alpine | 앱과 MLflow 가 함께 쓰는 DB | 5433 → 5432 |
rustfs | geo-mlops-mlflow-rustfs | rustfs/rustfs:1.0.0-beta.7 | S3 호환 오브젝트 저장소 (MLflow 아티팩트, 데이터셋, 이미지 레지스트리) | 9000 (S3 API), 9001 (웹 콘솔) |
createbucket | geo-mlops-mlflow-createbucket | amazon/aws-cli:2.34.63 | 1회 실행. 버킷 mlflow·datasets·registry 생성 후 종료 | 없음 |
prometheus | geo-mlops-prometheus | prom/prometheus:v2.55.1 | 앱 지표(/api/v1/metrics) 수집과 경보 규칙 평가 | 9090 |
alertmanager | geo-mlops-alertmanager | prom/alertmanager:v0.27.0 | 발생한 경보를 앱 웹훅으로 전달 | 9093 |
proxpi | geo-mlops-proxpi | epicwink/proxpi:1.3.0 (다이제스트 고정) | 이미지 빌드 파드용 pip 패키지 캐시 | 5000 |
apt-cacher-ng | geo-mlops-apt-cacher-ng | sameersbn/apt-cacher-ng (다이제스트 고정) | 이미지 빌드 파드용 apt 패키지 캐시 | 3142 |
- 모든 서비스(
createbucket제외)는restart: unless-stopped. 서버를 재부팅해도 다시 기동. app은mlflow-postgres가 healthy 가 되고createbucket이 정상 종료된 뒤 기동. 헬스체크는 10초마다/api/v1/health호출(첫 30초는 실패해도 대기).- 모든 컨테이너 로그는 json-file 드라이버로 50 MB × 5개까지만 보관. 오래된 로그는 자동 삭제.
- 학습·파이프라인·서빙은 이 파일에 없음. 앱이 k3s 클러스터에 Kubernetes Job·Deployment 로 생성(k3s·GPU 준비).
앱 컨테이너의 네트워크
app 은 network_mode: host 로 호스트의 네트워크를 그대로 사용. 그래서 서버 설정의 기본값만으로 다른 서비스에 접속.
| 대상 | 앱이 사용하는 기본 주소 | 서버 설정 |
|---|---|---|
| DB | postgresql+psycopg2://mlflow:mlflow@localhost:5433/mlflow | GEO_MLOPS_DATABASE_URL, GEO_MLOPS_MLFLOW_BACKEND_URI |
| 오브젝트 저장소 | http://localhost:9000 | GEO_MLOPS_MLFLOW_S3_ENDPOINT_URL |
| Prometheus | http://localhost:9090 | GEO_MLOPS_PROMETHEUS_URL |
| Alertmanager | http://localhost:9093 | GEO_MLOPS_ALERTMANAGER_URL |
| k3s API | k3s.yaml 에 적힌 https://127.0.0.1:6443 | GEO_MLOPS_KUBERNETES_KUBECONFIG |
반대 방향(Prometheus·Alertmanager 에서 앱으로)은 extra_hosts 의 host.docker.internal:host-gateway 로 호스트에 접속. Prometheus 는 host.docker.internal:10000 을 수집하고, Alertmanager 는 같은 주소의 웹훅으로 경보 전송(Compose 설치의 Alertmanager 웹훅 설정 참고).
호스트에서 읽는 파일
| 호스트 경로 | 사용하는 서비스 | 내용 |
|---|---|---|
/etc/geo-mlops/app.env | app (env_file) | 앱의 비밀 설정(GEO_MLOPS_*). 없어도 기동(required: false). 이 경우 기본 서명 키 등으로 실행되고 시스템 진단 화면에 치명 항목으로 표시 |
/etc/rancher/k3s/k3s.yaml | app (읽기 전용) | k3s 접속 정보. 없으면 Docker 가 빈 디렉터리를 만들어 앱이 클러스터를 찾지 못하므로 k3s 를 먼저 설치 |
./monitoring/prometheus.yml | prometheus | 수집 대상과 경보 규칙 파일 위치 |
./monitoring/alert-rules.yml | prometheus | 경보 규칙 |
./monitoring/alertmanager.yml | alertmanager | 경보 전달 경로(앱 웹훅 주소와 토큰) |
./monitoring/… 은 docker-compose.yml 이 있는 디렉터리 기준. 실행 중인 컨테이너는 생성될 때의 마운트를 유지하므로, 설정 파일을 고친 뒤에는 컨테이너 재생성 필요(아래 배포 절차의 두 번째 명령).
실행할 때 지정하는 셸 변수
app.env 가 아니라 docker compose 를 실행하는 셸에서 읽는 값.
| 변수 | 기본값 | 용도 |
|---|---|---|
GIT_COMMIT | 빈 값 | 빌드한 커밋. 시스템 진단 화면의 배포 정보에 표시 |
BUILT_AT | 빈 값 | 빌드 시각(UTC, ISO 8601). 위와 같음 |
GEO_APP_MEM_LIMIT | 16g | 앱 컨테이너 메모리 상한. 초과하면 앱만 종료되고 자동 재기동(Compose 설치의 메모리 상한 참고) |
앱의 환경 변수
environment 의 두 값은 내장 MLflow 가 RustFS 로 아티팩트를 올릴 때 필요. 최신 S3 클라이언트는 기본으로 Content-Length 없이 체크섬을 붙여 업로드하는데, RustFS 가 이를 MissingContentLength 로 거부하기 때문.
| 변수 | 값 |
|---|---|
AWS_REQUEST_CHECKSUM_CALCULATION | when_required |
AWS_RESPONSE_CHECKSUM_VALIDATION | when_required |
볼륨
| 볼륨 | 서비스 | 내용 |
|---|---|---|
geo-mlops-mlflow-postgres-data | mlflow-postgres | 앱·MLflow DB. 백업 대상 |
geo-mlops-mlflow-rustfs-data | rustfs | 아티팩트, 데이터셋 파일, 이미지 레지스트리. 백업 대상 |
geo-mlops-prometheus-data | prometheus | 지표 시계열 |
geo-mlops-alertmanager-data | alertmanager | 사일런스, 알림 전송 상태 |
geo-mlops-proxpi-cache | proxpi | pip 패키지 캐시. 삭제해도 다음 빌드 때 다시 채워짐 |
geo-mlops-apt-cacher-ng-data | apt-cacher-ng | apt 패키지 캐시. 위와 같음 |
볼륨 이름을 고정(name:)해 두었으므로 소스 디렉터리를 옮겨도 같은 데이터 사용. Compose 는 백업을 수행하지 않음. DB 덤프와 오브젝트 복제는 백업 참고. docker compose down -v 는 모든 볼륨을 삭제.
패키지 캐시 (proxpi, apt-cacher-ng)
이미지 빌드 파드가 pip·apt 패키지를 내려받을 때 거치는 캐시. 빌드 캐시에 없는 레이어(새 버전, 새 모델)를 빌드할 때만 사용.
- 빌드 파드는 파드 네트워크의 게이트웨이
10.42.0.1로 접속(파드 안의localhost는 파드 자신). 서버 설정을 비워 두면 앱이http://10.42.0.1:5000/index/(pip),http://10.42.0.1:3142(apt)를 자동 사용. - 캐시는 미러가 아니므로, 이 두 서비스가 꺼져 있으면 빌드가 원본으로 우회하지 않고 실패. 캐시 없이 운영하는 환경은
GEO_MLOPS_BUILD_PACKAGE_PROXY_ENABLED=false지정. proxpi에는 PyPI 만 연결. PyTorch 패키지 인덱스를 추가하면 합쳐진 목록에서 다른 가속기용torch빌드가 선택될 수 있음.- 두 이미지는 태그 대신 다중 아키텍처 인덱스의 다이제스트로 고정. x86_64 와 ARM64 서버에서 같은 파일 사용.
자세한 내용은 빌드 캐시 참고.
개발 서버의 배포 절차
개발 서버는 서버 저장소의 기본 브랜치가 갱신될 때마다 소스 루트에서 다음을 실행.
export GIT_COMMIT=$(git rev-parse HEAD)
export BUILT_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
docker compose up -d --build --remove-orphans
docker compose up -d --force-recreate --no-deps prometheus alertmanager
docker image prune -f
- 앱 이미지를 다시 빌드하고 파일 전체를 반영. 서비스 이름을 지정하지 않아야 모든 서비스가 파일과 일치.
--remove-orphans는 파일에서 삭제된 서비스의 컨테이너 정리. - Prometheus·Alertmanager 컨테이너를 재생성해
monitoring/의 설정 파일 변경을 반영. 지표와 알림 상태는 볼륨에 있으므로 유지(재생성 동안 몇 초간 수집 공백). - 이전 빌드가 남긴 이름 없는 이미지 삭제.
이어서 앱 컨테이너가 healthy 인지, Prometheus 가 앱을 수집 중인지(up{job="geo-mlops"} == 1) 확인.
docker inspect --format='{{.State.Health.Status}}' geo-mlops-app
curl -s --get http://localhost:9090/api/v1/query --data-urlencode 'query=up{job="geo-mlops"} == 1'
app.env 가 없어도 기동하므로 배포 성공이 비밀 설정의 존재를 보장하지 않음. 배포 후 시스템 진단 화면에서 치명 항목과 배포 커밋 확인.