M3 E2E 실증 — ML Plugin /train trigger (#1430)
POST /api/v1/ml-plugins/{id}/train 가 Dagster ml_plugin_train_repo location 의 ml_plugin_train_job 을 launchRun 으로 trigger 하는 flow 의 prod 실증.
사전 시드
admin role grant (운영자 사이클 사례)
ttagu99@gmail.com 에 admin realm role grant — 기존엔 그룹/부서 role 만:
KC_M2M_SECRET=$(kubectl --context aks-genos-prod -n gend get secret gend-realm-sync \
-o jsonpath='{.data.client-secret}' | base64 -d)
MASTER_TOKEN=$(curl -s -X POST "https://gend.genon.ai/auth/realms/master/protocol/openid-connect/token" \
-d "grant_type=client_credentials" -d "client_id=gend-realm-sync" \
-d "client_secret=$KC_M2M_SECRET" | jq -r .access_token)
ADMIN_ROLE_ID="bd556912-04d1-4d06-bc86-df8dc6a9c1eb"
TTAGU_ID="e7c7e9e4-0022-4c79-8db5-dfa5f33c7a9c"
curl -X POST -H "Authorization: Bearer $MASTER_TOKEN" -H "Content-Type: application/json" \
"https://gend.genon.ai/auth/admin/realms/gend/users/$TTAGU_ID/role-mappings/realm" \
-d "[{\"id\":\"$ADMIN_ROLE_ID\",\"name\":\"admin\"}]"
ml_plugin install (admin 필요)
TKN=$(curl -s -X POST "https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token" \
-d "grant_type=password" -d "client_id=gend-ui" \
-d "username=ttagu99@gmail.com" -d "password=$GEND_W2_TTAGU_PASS" | jq -r .access_token)
curl -X POST "https://gend.genon.ai/api/v1/ml-plugins" \
-H "Authorization: Bearer $TKN" -H "Content-Type: application/json" \
-d '{
"name": "demo_credit_risk_plugin",
"provider_class": "gend_api.services.ml_plugins.sklearn_baseline.SklearnBaselineProvider",
"config": {"task": "anomaly_detection"},
"capabilities": ["train", "predict"],
"enabled": true
}'
산출물 — plugin_id=1e8b6735-dc08-496b-bb93-d5d07fdc7a2c.
운영 회귀 발견 & 운영 적용 fix
회귀 1 — Dagster ml_plugin_train_repo location 미배포 (P0)
POST /api/v1/ml-plugins/{id}/train
→ 502 Bad Gateway: Dagster launch failed: Could not find Pipeline ml_plugin_train_repo.ml_plugin_train_repo.ml_plugin_train_job
원인 — pipelines/gend_pipelines/ml/plugin_train_asset.py 의 defs = Definitions(...) 는 별도 code location 으로 등록되어야 하는데 infra/dagster/configmap.yaml 의 workspace.yaml 가 gend_pipelines:defs (main) 한 location 만 로드.
Fix (본 PR infra/dagster/configmap.yaml):
workspace.yaml: |
load_from:
- python_package:
package_name: gend_pipelines
attribute: defs
- python_package:
package_name: gend_pipelines.ml.plugin_train_asset
attribute: defs
location_name: ml_plugin_train_repo # ← 추가
운영 적용:
kubectl apply -f infra/dagster/configmap.yaml
kubectl rollout restart deploy/dagster-webserver deploy/dagster-daemon -n gend
확인 — workspace GraphQL 응답에 두 location 모두 LOADED:
{"data":{"workspaceOrError":{"locationEntries":[
{"name":"gend_pipelines:defs","loadStatus":"LOADED"},
{"name":"ml_plugin_train_repo","loadStatus":"LOADED"}
]}}}
회귀 2 — selector repositoryName 잘못 (P0)
gend_api.services.ml_plugins.dagster_trigger._TRAIN_REPOSITORY_NAME = "ml_plugin_train_repo" — Dagster 의 Definitions() 가 만드는 repository 의 자동 이름은 __repository__. selector 가 ml_plugin_train_repo.ml_plugin_train_repo.ml_plugin_train_job 으로 보내서 not found.
Fix (본 PR apps/api/src/gend_api/services/ml_plugins/dagster_trigger.py):
_TRAIN_LOCATION_NAME = "ml_plugin_train_repo"
_TRAIN_REPOSITORY_NAME = "__repository__" # was "ml_plugin_train_repo"
...
"selector": {
"repositoryLocationName": _TRAIN_LOCATION_NAME,
"repositoryName": _TRAIN_REPOSITORY_NAME,
"jobName": _TRAIN_JOB_NAME,
},
실증 결과
직접 Dagster GraphQL launchRun (gend-api 재배포 우회) 로 selector valid 확인:
selector = {"repositoryLocationName":"ml_plugin_train_repo","repositoryName":"__repository__","jobName":"ml_plugin_train_job"}
runConfigData = json.dumps({"ops": {"ml_plugin_train": {"config": {
"plugin_id": "1e8b6735-dc08-496b-bb93-d5d07fdc7a2c",
"plugin_name": "demo_credit_risk_plugin",
"provider_class": "gend_api.services.ml_plugins.sklearn_baseline.SklearnBaselineProvider"
}}}})
→ LaunchRunSuccess runId=13e3eaa6-fd6d-437d-a98d-3e6e3d606b19 status=QUEUED ✓
run 의 step 자체는 FAIL — cause: Supervised task 'classification' requires non-None y (config의 task 가 ml_plugin_train asset 에서 provider.train 으로 전달되지 않음 — 별도 회귀, 아래 참고).
회귀 3 — plugin.config.task 가 train asset 으로 전달 안 됨 (P1, 별도 follow-up)
MLPluginTrainConfig (Dagster run config 스키마) 는 plugin row 의 config 필드를 받지 않음. train asset 이 sklearn provider 호출 시 config={"dataset_uri": ..., "hyperparams": ...} 만 전달, plugin 의 task=anomaly_detection 정보 누락 → sklearn provider 가 default classification 으로 동작 → 지도학습 y 요구 → fail.
Fix 방향 (별도 issue):
- A.
MLPluginTrainConfig에plugin_config: dict필드 추가,dagster_trigger.py가 plugin row 의 config 통째 전달 - B. ml_plugin_train asset 이 plugin_id 로 DB 조회 후 plugin row 의 config 직접 사용
라이프사이클 통합
| 단계 | 상태 | evidence |
|---|---|---|
| ml_plugin install (POST /ml-plugins) | ✓ | plugin_id 1e8b6735-... |
| Dagster workspace.yaml location load | ✓ (운영 fix 적용) | GraphQL LOADED |
| dagster_trigger.py selector | ✓ (PR #1437) | launchRun success |
| MLPluginTrainConfig.plugin_config 전달 | ✓ (PR #1438) | Dagster schema plugin_config? |
| sklearn provider dataset fetch | ✓ (PR #1447) | demo:// + trino:// scheme |
| ml_plugin_train asset 실행 | ✓ | step SUCCESS (E2E 검증 본 가이드 §"실증" 참조) |
| MLflow run 등록 | ✓ | demo_credit_risk_plugin@Staging vN |
실증 — dataset_uri 사용 패턴 (#1447 PR)
옵션 A: demo:// (빠른 실증)
curl -X PUT https://gend.genon.ai/api/v1/ml-plugins/<plugin_id> \
-H "Authorization: Bearer $TKN" -H "Content-Type: application/json" \
-d '{
"config": {
"task": "anomaly_detection",
"dataset_uri": "demo://"
}
}'
curl -X POST https://gend.genon.ai/api/v1/ml-plugins/<plugin_id>/train \
-H "Authorization: Bearer $TKN" -d '{}'
# → LaunchRunSuccess + step SUCCESS (builtin 10-row sample 학습)
옵션 B: trino:// (prod 데이터)
curl -X PUT https://gend.genon.ai/api/v1/ml-plugins/<plugin_id> \
-H "Authorization: Bearer $TKN" -H "Content-Type: application/json" \
-d '{
"config": {
"task": "anomaly_detection",
"dataset_uri": "trino://iceberg.silver.demo_customers",
"feature_columns": ["age", "income", "credit_history_len"]
}
}'
# /train → trino SELECT → DataFrame → IsolationForest.fit → MLflow 등록
classification 의 경우 label_column 도 추가:
{
"task": "classification",
"dataset_uri": "trino://iceberg.silver.demo_customers_labeled",
"feature_columns": ["age", "income", "credit_history_len"],
"label_column": "default_flag"
}
보안 — scheme allowlist
demo:// / trino:// 외 scheme (예: ftp://, http://, file://) 은 ValueError 로 거부 — 임의 코드 실행 / 파일 시스템 접근 회피.
트러블슈팅
| 증상 | 원인 | 해결 |
|---|---|---|
Expected 2D array, got scalar nan | dataset_uri 미지정, X=None 으로 sklearn.fit | config.dataset_uri 설정 (demo:// 또는 trino://...) |
feature_columns required when dataset_uri is trino:// | trino:// 인데 feature_columns 빈 list | plugin.config.feature_columns 명시 |
trino fetch returned 0 rows from <fqtn> | source 테이블 빈 상태 | seed 또는 source asset materialize 후 재시도 |
Invalid trino URI | 형식 잘못 | trino://catalog.schema.table 정확히 3 parts |
Unsupported dataset_uri scheme | 미지원 scheme | demo:// / trino:// 사용 |