본문으로 건너뛰기

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.comadmin 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.pydefs = Definitions(...) 는 별도 code location 으로 등록되어야 하는데 infra/dagster/configmap.yamlworkspace.yamlgend_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. MLPluginTrainConfigplugin_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 nandataset_uri 미지정, X=None 으로 sklearn.fitconfig.dataset_uri 설정 (demo:// 또는 trino://...)
feature_columns required when dataset_uri is trino://trino:// 인데 feature_columns 빈 listplugin.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미지원 schemedemo:// / trino:// 사용