Geo-MLOps 는 소스 디렉터리의 docker-compose.yml 하나로 뜹니다. 앱 이미지는 같은 디렉터리의 Dockerfile 로 그 자리에서 빌드되고, Postgres·RustFS·Prometheus·Alertmanager·패키지 캐시가 함께 올라옵니다. 이 페이지에서는 소스가 /opt/geo-mlops 에 있다고 가정합니다.

1. 설정 파일 만들기

앱의 비밀 설정은 소스 디렉터리 밖의 /etc/geo-mlops/app.env 에 둡니다. 소스를 새로 받거나 자동 배포가 소스 디렉터리를 정리해도 이 파일은 지워지지 않습니다. 형식은 GEO_MLOPS_키=값 한 줄씩입니다.

  1. 디렉터리와 파일을 만들고 권한을 좁힙니다.

    sudo install -d -m 0750 /etc/geo-mlops
    sudo install -m 0600 /dev/null /etc/geo-mlops/app.env
  2. 서명 키·pepper·웹훅 토큰으로 쓸 난수를 서로 다르게 세 개 만듭니다.

    openssl rand -hex 32   # GEO_MLOPS_AUTH_SECRET 용
    openssl rand -hex 32   # GEO_MLOPS_EDGE_TOKEN_PEPPER 용
    openssl rand -hex 32   # GEO_MLOPS_ALERTMANAGER_WEBHOOK_TOKEN 용
  3. sudoedit /etc/geo-mlops/app.env 로 아래 내용을 넣습니다. <your-secret> 자리에 방금 만든 값을 넣고, 주소는 실제 도메인으로 바꿉니다.

# --- 서명 키 (설치 직후 반드시 바꾼다) ---------------------------------
GEO_MLOPS_AUTH_SECRET=<your-secret>
GEO_MLOPS_EDGE_TOKEN_PEPPER=<your-secret>

# --- 첫 전역 관리자 ----------------------------------------------------
GEO_MLOPS_RBAC_BOOTSTRAP_ADMIN_EMAIL=[email protected]
GEO_MLOPS_RBAC_BOOTSTRAP_ADMIN_PASSWORD=<your-secret>

# --- 개발용 시드 끄기 (운영 설치에서는 반드시 비운다) ---------------------
GEO_MLOPS_RBAC_BOOTSTRAP_TENANTS=
GEO_MLOPS_RBAC_BOOTSTRAP_ROLE_PASSWORD=

# --- Alertmanager 웹훅 공유 비밀 ----------------------------------------
GEO_MLOPS_ALERTMANAGER_WEBHOOK_TOKEN=<your-secret>

# --- 접속 주소 ----------------------------------------------------------
# HTTPS 로 접속하면 true(기본값). 사내망에서 http:// 로만 쓴다면 false.
GEO_MLOPS_AUTH_COOKIE_SECURE=true
# 웹 화면이 다른 도메인에서 API 를 부를 때, 그 화면의 주소
GEO_MLOPS_CORS_ALLOW_ORIGINS=https://mlops.example.com
# MLflow 가 받아 줄 Host 헤더 (아래 설명 참고)
GEO_MLOPS_MLFLOW_ALLOWED_HOSTS=api.mlops.example.com,localhost:*,127.0.0.1:*,10.*

각 줄의 뜻과 바꾸지 않으면 무엇이 위험한지는 필수 보안 설정에서 자세히 다룹니다. 여기서 꼭 알아 둘 것만 적습니다.

  • RBAC_BOOTSTRAP_TENANTSRBAC_BOOTSTRAP_ROLE_PASSWORD 를 비워 두는 이유: 기본값은 개발용입니다. 비우지 않으면 첫 기동 때 개발용 테넌트와 역할별 데모 계정(비밀번호가 기본값)이 만들어집니다. 시드는 기동할 때마다 돌기 때문에, 나중에 그 테넌트를 지워도 다음 재시작 때 다시 생깁니다. 첫 기동 전에 비워 두세요. 테넌트는 설치 뒤 화면에서 만듭니다.
  • MLFLOW_ALLOWED_HOSTS: 내장 MLflow 는 DNS 리바인딩(주소를 속여 브라우저가 내부 서버에 요청하게 만드는 공격)을 막으려고 Host 헤더를 검사합니다. 모르는 호스트에는 403 을 돌려줍니다. 비워 두면 localhost 와 사설 IP(10.*, 192.168.* 등)만 통과합니다. 값을 지정하면 이 기본 목록이 통째로 대체되므로, 도메인과 함께 localhost:*, 127.0.0.1:* 과 빌드 파드가 쓰는 10.* 도 넣어야 합니다. 도메인으로 MLflow 를 쓰지 않는다면 이 줄은 빼도 됩니다.

2. Alertmanager 웹훅 맞추기

monitoring/alertmanager.yml 은 Alertmanager 가 경보를 앱으로 되돌려 보내는 주소를 담고 있습니다. 앱은 GEO_MLOPS_ALERTMANAGER_WEBHOOK_TOKEN 이 비어 있으면 웹훅을 503 으로 거절하고, 값이 틀리면 401 을 돌려줍니다. 두 곳을 고칩니다.

  1. url 의 포트가 앱 포트 10000 인지 확인합니다. 배포본에 :8000 으로 적혀 있으면 :10000 으로 고칩니다.
  2. 주석 처리된 http_config.headers 블록을 풀고, app.env 에 넣은 것과 같은 값을 넣습니다.
receivers:
  - name: geo-mlops-webhook
    webhook_configs:
      - url: http://host.docker.internal:10000/api/v1/integrations/alertmanager/webhook
        send_resolved: true
        http_config:
          headers:
            X-Webhook-Token: <your-secret>   # GEO_MLOPS_ALERTMANAGER_WEBHOOK_TOKEN 과 같은 값

3. 스택 띄우기

  1. 이미지를 빌드하고 모든 서비스를 띄웁니다. 첫 빌드는 인터넷에서 패키지를 받으므로 몇 분 걸립니다.

    docker compose -f /opt/geo-mlops/docker-compose.yml up -d --build --remove-orphans
  2. 앱 컨테이너가 healthy 가 될 때까지 기다립니다(첫 기동은 30초 남짓).

    docker inspect --format='{{.State.Health.Status}}' geo-mlops-app
    docker compose -f /opt/geo-mlops/docker-compose.yml ps
  3. 두 헬스체크를 부릅니다. 둘 다 인증 없이 열려 있습니다.

    curl -s http://localhost:10000/api/v1/health
    # {"status":"ok","version":"…"}
    curl -s http://localhost:10000/api/v1/readyz
    # {"status":"ok","database":"up","object-store":"up","prometheus":"up","alertmanager":"up"}
  • /api/v1/health프로세스가 살아 있는지만 봅니다(의존 서비스를 건드리지 않음). Compose 의 헬스체크가 이것을 씁니다.
  • /api/v1/readyz 는 Postgres·오브젝트 저장소·Prometheus·Alertmanager 에 실제로 붙어 봅니다. 하나라도 down 이면 전체가 degraded 입니다. 로드밸런서의 준비 확인에는 이쪽을 씁니다.

서버가 떠 있으면 http://<서버>:10000/docs 에서 API 문서를 볼 수 있습니다.

API 문서(/docs): 서버가 떠 있으면 바로 열린다

4. 첫 기동에서 저절로 일어나는 일

따로 명령을 칠 필요가 없습니다. 앱 컨테이너의 start-uvicorn.sh 와 앱 시작 과정이 다음을 합니다.

순서하는 일로그에서 확인
1DB 마이그레이션 alembic upgrade head (실패하면 서버가 뜨지 않음)Running upgrade …
2부트스트랩 관리자 계정 생성 + 전역 관리자 권한 부여created bootstrap admin account [email protected]
3공유 빌드 이미지 자리(학습 런타임·스테이저·서빙 빌더) 생성(따로 없음)
4테넌트별 MLflow 워크스페이스 준비, 끊긴 작업 정리(따로 없음)
docker logs geo-mlops-app 2>&1 | grep -E "upgrade|bootstrap|seeded"

부트스트랩 관리자는 계정이 없을 때만 만들어집니다. 이미 있는 계정의 비밀번호는 바꾸지 않습니다. 시드가 실패해도 서버는 뜨고, 실패 내용은 startup seeding failed 로 로그에 남습니다.

5. 앱 컨테이너의 메모리 상한

앱 컨테이너는 기본 16 GB 로 제한됩니다(넘으면 커널이 앱만 강제로 끝내고, Compose 의 restart 설정이 다시 띄웁니다). GPU 가 시스템 메모리를 함께 쓰는 장비에서는 학습 파드 몫을 남기려는 장치입니다. 바꾸려면 Compose 를 부를 때 셸 변수로 줍니다.

GEO_APP_MEM_LIMIT=24g docker compose -f /opt/geo-mlops/docker-compose.yml up -d

6. 웹 화면 올리기

웹 화면(프론트엔드)은 앱과 따로 배포하는 정적 파일 묶음입니다. 빌드할 때 API 주소를 번들 안에 넣습니다(VITE_API_BASE_URL). 두 가지 배치 중 하나를 고릅니다.

배치빌드 값앱 설정
같은 도메인: 웹 서버가 정적 파일을 서빙하고 /api·/auth·/users·/mlflow·/v2 를 10000 으로 넘김VITE_API_BASE_URL= (비움)CORS 설정 불필요
다른 도메인(예: 화면 https://mlops.example.com, API https://api.mlops.example.com)VITE_API_BASE_URL=https://api.mlops.example.comGEO_MLOPS_CORS_ALLOW_ORIGINS=https://mlops.example.com
# 프론트엔드 소스 디렉터리에서
npm ci
VITE_API_BASE_URL=https://api.mlops.example.com npm run build   # 결과: dist/

dist/ 를 정적 호스팅(웹 서버, CDN 등)에 올립니다. 화면 주소(/training, /system-health …)는 모두 브라우저 쪽 라우팅이므로, 없는 경로는 index.html 을 돌려주도록(SPA fallback) 설정해야 새로고침해도 404 가 나지 않습니다.

7. 부속 서비스의 기본 계정

Compose 파일의 Postgres(mlflow/mlflow)와 RustFS(rustfsadmin/rustfsadmin) 계정은 파일에 고정되어 있고, 포트(5433·9000·9001)는 서버의 모든 주소에 열립니다. 방화벽으로 이 포트들을 서버 밖에서 막으세요. 계정을 바꾸려면 Compose 파일과 app.env 의 해당 설정(GEO_MLOPS_DATABASE_URL, GEO_MLOPS_MLFLOW_BACKEND_URI, GEO_MLOPS_MLFLOW_S3_ACCESS_KEY_ID, GEO_MLOPS_MLFLOW_S3_SECRET_ACCESS_KEY)과 백업 설정을 함께 바꿔야 합니다.

자주 쓰는 명령

C="docker compose -f /opt/geo-mlops/docker-compose.yml"
$C ps                       # 상태
$C logs -f --tail=100 app   # 앱 로그
$C up -d app                # app.env 를 고친 뒤: 설정을 다시 읽어 앱만 재생성
$C down                     # 멈춤 (데이터 유지)

다음: k3s·GPU 준비

2026-09-21 기준 플랫폼에 맞춰 작성했습니다.

© Geo-MLOps