diff --git a/manifests/helm/mlflow/1.9.0/README.md b/manifests/helm/mlflow/1.9.0/README.md index 364e973..b5c8ddc 100644 --- a/manifests/helm/mlflow/1.9.0/README.md +++ b/manifests/helm/mlflow/1.9.0/README.md @@ -1,7 +1,6 @@ -# MLflow 배포 (OIDC Auth) +# MLflow 배포 (OIDC Auth + Multi-Tenant Workspace) -MLflow 3.11.1 + mlflow-oidc-auth v7.0.3를 Keycloak과 연동하여 배포하는 가이드이다. -커스텀 이미지 `wbsong111/mlflow:v3.11.1-oidc`에 OIDC 플러그인이 포함되어 있다. +MLflow 3.11.1 + mlflow-oidc-auth v7.0.3을 Keycloak과 연동하고, 팀별 workspace로 멀티테넌시를 구성하는 가이드이다. --- @@ -9,14 +8,14 @@ MLflow 3.11.1 + mlflow-oidc-auth v7.0.3를 Keycloak과 연동하여 배포하는 ### 1.1 Keycloak 설정 -**Client 생성** +#### 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 추가** (userinfo 포함 필수) +#### Groups Mapper 추가 Client → Mappers → Create: @@ -29,12 +28,22 @@ Client → Mappers → Create: | Add to access token | `true` | | Add to userinfo | `true` ← 반드시 true | -**Groups 생성** +#### Groups 생성 -- `mlflow` — 일반 사용자 접근 허용 그룹 -- `mlflow-admin` — 관리자 그룹 +| 그룹명 | 역할 | +|--------|------| +| `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 +``` --- @@ -59,10 +68,7 @@ kubectl create configmap keycloak-ca-cert -n mlflow \ `SECRET_KEY`는 uvicorn 멀티워커 환경에서 세션 공유를 위해 반드시 포함해야 한다. ```sh -# Fernet key 생성 (webhook secret 암호화용) FERNET_KEY=$(python3 -c "import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())") - -# Session key 생성 (멀티워커 세션 공유용) SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") kubectl create secret generic mlflow-oidc-secret -n mlflow \ @@ -75,6 +81,43 @@ 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 @@ -82,13 +125,13 @@ git clone https://github.com/paasup/dip-catalog.git cd dip-catalog # 신규 설치 -helm install mlflow manifests/helm/mlflow/1.8.1/ \ - -f manifests/helm/mlflow/1.8.1/custom-values.yaml \ +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.8.1/ \ - -f manifests/helm/mlflow/1.8.1/custom-values.yaml \ +helm upgrade mlflow manifests/helm/mlflow/1.9.0/ \ + -f manifests/helm/mlflow/1.9.0/custom-values.yaml \ -n mlflow ``` @@ -96,24 +139,7 @@ helm upgrade mlflow manifests/helm/mlflow/1.8.1/ \ ## 3. custom-values.yaml 설명 -### 3.1 이미지 설정 - -OIDC 플러그인이 포함된 커스텀 이미지를 사용한다. - -```yaml -image: - repository: wbsong111/mlflow - tag: "v3.11.1-oidc" - -initImages: - mlflowDbMigration: - repository: wbsong111/mlflow - tag: "v3.11.1-oidc" -``` - ---- - -### 3.2 OIDC 환경변수 설정 +### 3.1 OIDC 환경변수 ```yaml extraEnvVars: @@ -127,7 +153,6 @@ extraEnvVars: OIDC_ADMIN_GROUP_NAME: "mlflow-admin" OIDC_USERS_DB_URI: "postgresql://mlflow:mlflow1234@mlflow-postgresql:5432/mlflow" DEFAULT_MLFLOW_PERMISSION: "READ" - MLFLOW_ENABLE_WORKSPACES: "false" AUTOMATIC_LOGIN_REDIRECT: "true" OIDC_ALEMBIC_VERSION_TABLE: "mlflow_oidc_alembic_version" ``` @@ -136,10 +161,49 @@ extraEnvVars: | 항목 | 올바른 값 | 잘못된 값 | 이유 | |------|-----------|-----------|------| -| `SSL_CERT_FILE` | `SSL_CERT_FILE` | `REQUESTS_CA_BUNDLE` | mlflow-oidc-auth는 httpx를 사용하며 httpx는 `SSL_CERT_FILE` 환경변수를 인식 | +| `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` | mlflow-oidc-auth auth_router에 prefix가 없어 실제 경로는 `/callback` | -| `OIDC_ALEMBIC_VERSION_TABLE` | `"mlflow_oidc_alembic_version"` | 기본값(`alembic_version`) | MLflow와 mlflow-oidc-auth가 동일한 alembic_version 테이블을 사용하면 마이그레이션 충돌 발생 | +| `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.2 Multi-Tenant Workspace 환경변수 + +```yaml +extraEnvVars: + 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` | 권한 판단 순서: 개인 → 그룹 → 정규식 → 그룹-정규식 | + +#### Workspace 동작 방식 + +1. 사용자가 로그인하면 `OIDC_WORKSPACE_DETECTION_PLUGIN`으로 지정된 모듈의 `get_user_workspaces(access_token)` 함수 호출 +2. 함수가 반환한 workspace 이름 목록으로 workspace 자동 생성 (최초 접근 시) +3. `OIDC_WORKSPACE_DEFAULT_PERMISSION` 권한을 해당 사용자에게 자동 부여 +4. 이후 사용자는 자신의 workspace 내에서만 실험·모델 접근 가능 + +#### 플러그인 주의사항 + +- `OIDC_WORKSPACE_DETECTION_PLUGIN`에는 **모듈명만** 지정한다. (`mlflow_workspace_detector.get_user_workspaces` 형태 불가) +- 호출되는 함수 이름은 `get_user_workspaces`로 고정되어 있다. +- 함수 인자로 전달되는 `access_token`은 **JWT 문자열**이며, 플러그인 내부에서 base64 디코딩이 필요하다. +- 플러그인 오류 발생 시 exception이 조용히 처리되어 workspace 목록이 빈 배열로 반환된다. 로그에서 `WARNING` 레벨로 확인 가능하다. --- @@ -147,7 +211,7 @@ extraEnvVars: ```yaml extraArgs: - appName: "oidc-auth" # OIDC 플러그인 활성화 + appName: "oidc-auth" uvicornOpts: "--timeout-keep-alive 600" allowedHosts: "mlflow.example.org" @@ -169,24 +233,69 @@ extraSecretNamesForEnvFrom: --- -### 3.5 CA 인증서 볼륨 마운트 +### 3.5 볼륨 마운트 ```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.6 Ingress 설정 +### 3.6 PostgreSQL 설정 + +```yaml +postgresql: + enabled: true + auth: + username: mlflow + password: mlflow1234 # 변경 권장 + database: mlflow + image: + repository: bitnamilegacy/postgresql + primary: + persistence: + enabled: true +``` + +`OIDC_USERS_DB_URI`도 동일한 접속 정보를 사용한다. + +--- + +### 3.7 S3 (MinIO) 설정 + +```yaml +artifactRoot: + proxiedArtifactStorage: true + defaultArtifactsDestination: "s3://mlflow/artifacts" + s3: + enabled: true + bucket: mlflow + path: artifacts + awsAccessKeyId: "adminuser" + awsSecretAccessKey: "adminuser" + +extraEnvVars: + MLFLOW_S3_ENDPOINT_URL: "http://minio.minio.svc.cluster.local:9000" + MLFLOW_S3_IGNORE_TLS: "true" +``` + +--- + +### 3.8 Ingress 설정 ```yaml ingress: @@ -211,58 +320,22 @@ ingress: --- -### 3.7 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.8 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. 배포 검증 -1. `https://mlflow.example.org` 접속 → Keycloak 로그인 페이지로 자동 리다이렉트 +### 4.1 OIDC 인증 검증 + +1. `https://mlflow.example.org` 접속 → Keycloak 로그인 페이지로 자동 리다이렉트 확인 2. `mlflow` 그룹 사용자로 로그인 → MLflow UI 정상 진입 확인 3. `mlflow-admin` 그룹 사용자로 로그인 → 관리자 메뉴 접근 확인 -4. 미가입 사용자 로그인 → 접근 거부 확인 +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의 실험·모델에 접근 불가 확인 diff --git a/manifests/helm/mlflow/1.9.0/custom-values.yaml b/manifests/helm/mlflow/1.9.0/custom-values.yaml index 5d72460..b1c61b7 100644 --- a/manifests/helm/mlflow/1.9.0/custom-values.yaml +++ b/manifests/helm/mlflow/1.9.0/custom-values.yaml @@ -7,7 +7,10 @@ initImages: tag: "1.37" iniFileInitializer: tag: "1.37" - + mlflowDbMigration: + repository: paasup/mlflow + tag: "v3.11.1-oidc" + backendStore: databaseMigration: true databaseConnectionCheck: true @@ -39,9 +42,12 @@ artifactRoot: # keyOfSecretAccessKey: AWS_SECRET_ACCESS_KEY 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" @@ -51,10 +57,18 @@ extraEnvVars: OIDC_ADMIN_GROUP_NAME: "mlflow-admin" OIDC_USERS_DB_URI: "postgresql://mlflow:mlflow1234@mlflow-postgresql:5432/mlflow" DEFAULT_MLFLOW_PERMISSION: "READ" - MLFLOW_ENABLE_WORKSPACES: "false" AUTOMATIC_LOGIN_REDIRECT: "true" OIDC_ALEMBIC_VERSION_TABLE: "mlflow_oidc_alembic_version" + # --- 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" + extraSecretNamesForEnvFrom: - mlflow-oidc-secret @@ -102,12 +116,17 @@ 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 serviceMonitor: enabled: false