Files
service-catalog/manifests/helm/mlflow/1.9.0/CUSTOM-README.md
T
2026-04-27 11:10:33 +09:00

390 lines
11 KiB
Markdown

# MLflow 배포 (OIDC Auth + Multi-Tenant Workspace)
MLflow 3.11.1 + mlflow-oidc-auth v7.0.3을 Keycloak과 연동하고, 팀별 workspace로 멀티테넌시를 구성하는 가이드이다.
커스텀 이미지 `paasup/mlflow:v3.11.1-oidc`에 OIDC 플러그인이 포함되어 있다.
---
## 1. 사전 준비
### 1.1 Keycloak 설정
#### Client 생성
- Realm: `paasup`, Client ID: `mlflow`, Protocol: `openid-connect`
- Access Type: `confidential`
- Valid Redirect URIs: `https://mlflow.example.org/*`
- Web Origins: `https://mlflow.example.org`
#### Groups Mapper 추가
Client → Mappers → Create:
| 항목 | 값 |
|------|----|
| Mapper type | `Group Membership` |
| Token Claim Name | `groups` |
| Full group path | `false` |
| Add to ID token | `true` |
| Add to access token | `true` |
| Add to userinfo | `true` ← 반드시 true |
#### Groups 생성
| 그룹명 | 역할 |
|--------|------|
| `mlflow` | **모든 사용자 필수 소속** — 미소속 시 로그인 거부 |
| `mlflow-admin` | 관리자 권한 |
| `mlflow-team-<팀명>` | workspace 매핑용 팀 그룹 (예: `mlflow-team-ds`, `mlflow-team-mlops`) |
사용자는 반드시 `mlflow` 그룹과 소속 팀 그룹 **모두에 추가**해야 한다.
```
예시:
mlflow-test → mlflow, mlflow-team-ds
mlflow-ops-test → mlflow, mlflow-team-mlops
mlflow-admin → mlflow, mlflow-admin
```
---
### 1.2 Keycloak CA 인증서 ConfigMap 생성
Keycloak이 사설 CA 인증서를 사용하는 경우 필수이다.
```sh
# Keycloak TLS secret에서 CA 인증서 추출
kubectl get secret keycloak.example.org-tls -n platform \
-o jsonpath='{.data.ca\.crt}' | base64 -d > /tmp/keycloak-ca.crt
# ConfigMap 생성
kubectl create configmap keycloak-ca-cert -n mlflow \
--from-file=ca.crt=/tmp/keycloak-ca.crt
```
---
### 1.3 OIDC Secret 생성
`SECRET_KEY`는 uvicorn 멀티워커 환경에서 세션 공유를 위해 반드시 포함해야 한다.
```sh
FERNET_KEY=$(python3 -c "import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())")
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
kubectl create secret generic mlflow-oidc-secret -n mlflow \
--from-literal=OIDC_CLIENT_SECRET="<keycloak-client-secret>" \
--from-literal=MLFLOW_WEBHOOK_SECRET_ENCRYPTION_KEY="$FERNET_KEY" \
--from-literal=SECRET_KEY="$SECRET_KEY"
```
Keycloak Client Secret은 Keycloak Admin Console → Client → Credentials 탭에서 확인한다.
---
### 1.4 Workspace Detection Plugin ConfigMap 생성
팀 그룹(`mlflow-team-*`)을 workspace 이름으로 변환하는 Python 플러그인을 ConfigMap으로 배포한다.
```python
# mlflow_workspace_detector.py
import base64
import json
def get_user_workspaces(access_token: str) -> list:
"""
JWT access_token의 groups 클레임에서 mlflow-team-* 그룹을 읽어
workspace 이름 목록을 반환한다.
예: ["mlflow-team-ds", "mlflow"] → ["team-ds"]
"""
try:
payload = access_token.split(".")[1]
payload += "=" * (4 - len(payload) % 4)
claims = json.loads(base64.b64decode(payload))
except Exception:
return []
groups = claims.get("groups", [])
return [
g[len("mlflow-"):]
for g in groups
if g.startswith("mlflow-team-")
]
```
```sh
kubectl create configmap mlflow-workspace-plugin -n mlflow \
--from-file=mlflow_workspace_detector.py=mlflow_workspace_detector.py
```
---
## 2. 배포 방법
```sh
git clone https://github.com/paasup/dip-catalog.git
cd dip-catalog
# 신규 설치
helm install mlflow manifests/helm/mlflow/1.9.0/ \
-f manifests/helm/mlflow/1.9.0/custom-values.yaml \
-n mlflow --create-namespace
# 업그레이드
helm upgrade mlflow manifests/helm/mlflow/1.9.0/ \
-f manifests/helm/mlflow/1.9.0/custom-values.yaml \
-n mlflow
```
---
## 3. custom-values.yaml 설명
### 3.1 이미지 설정
OIDC 플러그인이 포함된 커스텀 이미지를 사용한다.
```yaml
image:
repository: paasup/mlflow
tag: "v3.11.1-oidc"
initImages:
mlflowDbMigration:
repository: paasup/mlflow
tag: "v3.11.1-oidc"
```
---
### 3.2 OIDC 환경변수
```yaml
extraEnvVars:
# --- 기본 설정 ---
MLFLOW_S3_ENDPOINT_URL: "http://minio.minio.svc.cluster.local:9000"
MLFLOW_S3_IGNORE_TLS: "true"
SSL_CERT_FILE: "/etc/ssl/certs/custom-ca.crt"
# --- OIDC 설정 ---
OIDC_CLIENT_ID: "mlflow"
OIDC_DISCOVERY_URL: "https://keycloak.example.org/realms/paasup/.well-known/openid-configuration"
OIDC_REDIRECT_URI: "https://mlflow.example.org/callback"
OIDC_SCOPE: "openid email profile"
OIDC_GROUPS_ATTRIBUTE: "groups"
OIDC_GROUP_NAME: "mlflow"
OIDC_ADMIN_GROUP_NAME: "mlflow-admin"
OIDC_USERS_DB_URI: "postgresql://mlflow:mlflow1234@mlflow-postgresql:5432/mlflow"
DEFAULT_MLFLOW_PERMISSION: "READ"
AUTOMATIC_LOGIN_REDIRECT: "true"
OIDC_ALEMBIC_VERSION_TABLE: "mlflow_oidc_alembic_version"
```
**주의사항**
| 항목 | 올바른 값 | 잘못된 값 | 이유 |
|------|-----------|-----------|------|
| `SSL_CERT_FILE` | `SSL_CERT_FILE` | `REQUESTS_CA_BUNDLE` | mlflow-oidc-auth는 httpx를 사용하며 httpx는 `REQUESTS_CA_BUNDLE`을 인식하지 않음 |
| `OIDC_SCOPE` | `"openid email profile"` | `"openid,email,profile"` | OAuth2 RFC 6749 표준: 스코프는 공백으로 구분 |
| `OIDC_REDIRECT_URI` | `.../callback` | `.../oidc/callback` | auth_router에 prefix가 없어 실제 경로는 `/callback` |
| `OIDC_ALEMBIC_VERSION_TABLE` | `"mlflow_oidc_alembic_version"` | 기본값(`alembic_version`) | MLflow와 mlflow-oidc-auth가 동일한 테이블 사용 시 마이그레이션 충돌 |
---
### 3.3 Workspace 환경변수
```yaml
extraEnvVars:
# --- Workspace 설정 ---
MLFLOW_ENABLE_WORKSPACES: "true"
OIDC_WORKSPACE_DEFAULT_PERMISSION: "EDIT"
OIDC_WORKSPACE_DETECTION_PLUGIN: "mlflow_workspace_detector"
PYTHONPATH: "/opt/mlflow-plugins"
WORKSPACE_CACHE_MAX_SIZE: "1024"
WORKSPACE_CACHE_TTL_SECONDS: "300"
PERMISSION_SOURCE_ORDER: "user,group,regex,group-regex"
```
| 항목 | 설명 |
|------|------|
| `MLFLOW_ENABLE_WORKSPACES` | workspace 기능 활성화 |
| `OIDC_WORKSPACE_DEFAULT_PERMISSION` | workspace 첫 접근 시 자동 부여 권한. `EDIT` 설정 시 팀원 전원 편집 가능 |
| `OIDC_WORKSPACE_DETECTION_PLUGIN` | workspace 감지 플러그인 모듈명 (함수명 미포함, 모듈명만 지정) |
| `PYTHONPATH` | ConfigMap으로 마운트된 플러그인 경로를 Python import 경로에 추가 |
| `WORKSPACE_CACHE_MAX_SIZE` | workspace 목록 캐시 최대 항목 수 |
| `WORKSPACE_CACHE_TTL_SECONDS` | workspace 목록 캐시 TTL (초) |
| `PERMISSION_SOURCE_ORDER` | 권한 판단 순서: 개인 → 그룹 → 정규식 → 그룹-정규식 |
**플러그인 주의사항**
- `OIDC_WORKSPACE_DETECTION_PLUGIN`에는 **모듈명만** 지정한다. (`mlflow_workspace_detector.get_user_workspaces` 형태 불가)
- 호출되는 함수 이름은 `get_user_workspaces`로 고정되어 있다.
- 함수 인자로 전달되는 `access_token`은 **JWT 문자열**이며, 플러그인 내부에서 base64 디코딩이 필요하다.
- 플러그인 오류 발생 시 exception이 조용히 처리되어 workspace 목록이 빈 배열로 반환된다. 로그에서 `WARNING` 레벨로 확인 가능하다.
---
### 3.4 OIDC App 활성화
```yaml
extraArgs:
appName: "oidc-auth"
uvicornOpts: "--timeout-keep-alive 600"
allowedHosts: "mlflow.example.org"
log:
enabled: false # uvicornOpts 사용 시 반드시 false (gunicorn/uvicorn 충돌 방지)
auth:
enabled: false # mlflow-oidc-auth가 자체 인증 처리
```
---
### 3.5 Secret 참조
```yaml
extraSecretNamesForEnvFrom:
- mlflow-oidc-secret # OIDC_CLIENT_SECRET, SECRET_KEY, MLFLOW_WEBHOOK_SECRET_ENCRYPTION_KEY 포함
```
---
### 3.6 볼륨 마운트
```yaml
extraVolumes:
- name: keycloak-ca-cert
configMap:
name: keycloak-ca-cert
- name: workspace-plugin
configMap:
name: mlflow-workspace-plugin
extraVolumeMounts:
- name: keycloak-ca-cert
mountPath: /etc/ssl/certs/custom-ca.crt
subPath: ca.crt
readOnly: true
- name: workspace-plugin
mountPath: /opt/mlflow-plugins
```
---
### 3.7 OIDC Auth Middleware 패치
`mlflow-oidc-auth` 플러그인의 `auth_middleware.py`를 차트에 포함된 버전으로 교체한다.
워크스페이스 지원(`x-mlflow-workspace` 헤더 처리) 등 업스트림 수정 사항을 반영한다.
```yaml
oidcAuthPatch:
enabled: true
mountPath: "/usr/local/lib/python3.11/site-packages/mlflow_oidc_auth/middleware/auth_middleware.py"
```
파일 소스: `files/auth_middleware.py`
> **Python 버전 확인**: 컨테이너 이미지의 Python 버전이 다를 경우 `mountPath`를 수정한다.
>
> ```sh
> kubectl exec -n mlflow <pod> -- python -c \
> "import mlflow_oidc_auth.middleware.auth_middleware as m; print(m.__file__)"
> ```
---
### 3.8 Ingress 설정
```yaml
ingress:
enabled: true
className: "kong"
annotations:
cert-manager.io/cluster-issuer: "selfsigned-issuer"
cert-manager.io/duration: 8760h
cert-manager.io/renew-before: 720h
hosts:
- host: mlflow.example.org
paths:
- path: /
pathType: ImplementationSpecific
tls:
- secretName: mlflow-tls-secret
hosts:
- mlflow.example.org
```
`mlflow.example.org`를 실제 도메인으로 변경한다.
---
### 3.9 PostgreSQL 설정
내장 PostgreSQL을 사용한다.
```yaml
postgresql:
enabled: true
auth:
username: mlflow
password: mlflow1234 # 변경 권장
database: mlflow
image:
repository: bitnamilegacy/postgresql
primary:
persistence:
enabled: true
```
`OIDC_USERS_DB_URI`도 동일한 접속 정보를 사용한다.
```yaml
extraEnvVars:
OIDC_USERS_DB_URI: "postgresql://mlflow:mlflow1234@mlflow-postgresql:5432/mlflow"
```
---
### 3.10 S3 (MinIO) 설정
```yaml
artifactRoot:
proxiedArtifactStorage: true
defaultArtifactsDestination: "s3://mlflow/artifacts"
s3:
enabled: true
bucket: mlflow
path: artifacts
awsAccessKeyId: "adminuser" # MinIO access key
awsSecretAccessKey: "adminuser" # MinIO secret key
extraEnvVars:
MLFLOW_S3_ENDPOINT_URL: "http://minio.minio.svc.cluster.local:9000"
MLFLOW_S3_IGNORE_TLS: "true"
```
외부 MinIO 사용 시 `MLFLOW_S3_ENDPOINT_URL`을 해당 엔드포인트로 변경한다.
---
## 4. 배포 검증
### 4.1 OIDC 인증 검증
1. `https://mlflow.example.org` 접속 → Keycloak 로그인 페이지로 자동 리다이렉트 확인
2. `mlflow` 그룹 사용자로 로그인 → MLflow UI 정상 진입 확인
3. `mlflow-admin` 그룹 사용자로 로그인 → 관리자 메뉴 접근 확인
4. `mlflow` 그룹 미소속 사용자 로그인 → 접근 거부 확인
### 4.2 Workspace 격리 검증
| 사용자 | 소속 그룹 | 기대 workspace |
|--------|-----------|----------------|
| `mlflow-test` | `mlflow`, `mlflow-team-ds` | `team-ds` |
| `mlflow-ops-test` | `mlflow`, `mlflow-team-mlops` | `team-mlops` |
1. `mlflow-test`로 로그인 → `team-ds` workspace 자동 생성 확인
2. `mlflow-ops-test`로 로그인 → `team-mlops` workspace 자동 생성 확인
3. 각 사용자가 상대방 workspace의 실험·모델에 접근 불가 확인