@@ -42,9 +42,101 @@ MLFLOW_STATIC_PREFIX="/mlflow"
4242MLFLOW_ENDPOINT=" https://${MLFLOW_HOST} :${MLFLOW_PORT}${MLFLOW_STATIC_PREFIX} "
4343echo " MLflow service: $MLFLOW_SVC port=$MLFLOW_PORT endpoint=$MLFLOW_ENDPOINT "
4444
45- MLFLOW_PATCH=$( jq -n --arg endpoint " $MLFLOW_ENDPOINT " ' {
45+ # --- Extract CA certificate from the MLflow TLS secret ---
46+ CA_MOUNT_PATH=" /etc/mlflow/tls/ca.crt"
47+ CA_CERT_FILE=" /tmp/mlflow-ca.crt"
48+
49+ echo " Extracting MLflow CA certificate..."
50+ TLS_SECRET=$( kubectl get secret -n " $MLFLOW_NAMESPACE " -o custom-columns=" :metadata.name" --no-headers | grep -i " mlflow.*tls\|tls.*mlflow" | head -1)
51+ if [ -z " $TLS_SECRET " ]; then
52+ TLS_SECRET=$( kubectl get secret -n " $MLFLOW_NAMESPACE " --field-selector type=kubernetes.io/tls -o custom-columns=" :metadata.name" --no-headers | head -1)
53+ fi
54+ if [ -z " $TLS_SECRET " ]; then
55+ echo " ERROR: No TLS secret found in namespace $MLFLOW_NAMESPACE "
56+ exit 1
57+ fi
58+ echo " Found TLS secret: $TLS_SECRET "
59+
60+ # cert-manager stores the CA in the ca.crt key; fall back to tls.crt if ca.crt is absent
61+ CA_DATA=$( kubectl get secret -n " $MLFLOW_NAMESPACE " " $TLS_SECRET " -o jsonpath=' {.data.ca\.crt}' 2> /dev/null)
62+ if [ -z " $CA_DATA " ]; then
63+ echo " ca.crt not found in secret, falling back to tls.crt"
64+ CA_DATA=$( kubectl get secret -n " $MLFLOW_NAMESPACE " " $TLS_SECRET " -o jsonpath=' {.data.tls\.crt}' )
65+ fi
66+ if [ -z " $CA_DATA " ]; then
67+ echo " ERROR: Could not extract CA certificate from secret $TLS_SECRET "
68+ exit 1
69+ fi
70+ echo " $CA_DATA " | base64 -d > " $CA_CERT_FILE "
71+ echo " CA certificate extracted to $CA_CERT_FILE ($( wc -l < " $CA_CERT_FILE " ) lines)"
72+
73+ # Create a ConfigMap with the CA cert in the KFP namespace for workflow pods
74+ kubectl create configmap mlflow-ca-cert -n " $KFP_NAMESPACE " \
75+ --from-file=ca.crt=" $CA_CERT_FILE " --dry-run=client -o yaml | kubectl apply -f -
76+
77+ # --- Patch Argo workflow-controller-configmap to mount the CA cert into all workflow pods ---
78+ echo " Patching workflow-controller-configmap to inject MLflow CA into workflow pods..."
79+
80+ EXISTING_MAIN_CONTAINER=$( kubectl get configmap workflow-controller-configmap -n " $KFP_NAMESPACE " \
81+ -o jsonpath=' {.data.mainContainer}' 2> /dev/null || echo " " )
82+ if [ -n " $EXISTING_MAIN_CONTAINER " ]; then
83+ MAIN_CONTAINER_PATCH=$( echo " $EXISTING_MAIN_CONTAINER " | \
84+ python3 -c "
85+ import sys, json
86+ raw = sys.stdin.read().strip()
87+ try:
88+ cfg = json.loads(raw)
89+ except json.JSONDecodeError:
90+ import yaml
91+ cfg = yaml.safe_load(raw) or {}
92+ vms = cfg.get('volumeMounts', [])
93+ vms = [vm for vm in vms if vm.get('name') != 'mlflow-ca']
94+ vms.append({'name': 'mlflow-ca', 'mountPath': '/etc/mlflow/tls', 'readOnly': True})
95+ cfg['volumeMounts'] = vms
96+ print(json.dumps(cfg))
97+ " )
98+ else
99+ MAIN_CONTAINER_PATCH=' {"volumeMounts":[{"name":"mlflow-ca","mountPath":"/etc/mlflow/tls","readOnly":true}]}'
100+ fi
101+
102+ EXISTING_WF_DEFAULTS=$( kubectl get configmap workflow-controller-configmap -n " $KFP_NAMESPACE " \
103+ -o jsonpath=' {.data.workflowDefaults}' 2> /dev/null || echo " " )
104+ if [ -n " $EXISTING_WF_DEFAULTS " ]; then
105+ WF_DEFAULTS_PATCH=$( echo " $EXISTING_WF_DEFAULTS " | \
106+ python3 -c "
107+ import sys, yaml
108+ cfg = yaml.safe_load(sys.stdin) or {}
109+ spec = cfg.setdefault('spec', {})
110+ volumes = spec.setdefault('volumes', [])
111+ volumes = [v for v in volumes if v.get('name') != 'mlflow-ca']
112+ volumes.append({'name': 'mlflow-ca', 'configMap': {'name': 'mlflow-ca-cert'}})
113+ spec['volumes'] = volumes
114+ cfg['spec'] = spec
115+ print(yaml.dump(cfg, default_flow_style=False))
116+ " )
117+ else
118+ WF_DEFAULTS_PATCH=$( cat << 'YAML '
119+ spec:
120+ volumes:
121+ - name: mlflow-ca
122+ configMap:
123+ name: mlflow-ca-cert
124+ YAML
125+ )
126+ fi
127+
128+ kubectl patch configmap workflow-controller-configmap -n " $KFP_NAMESPACE " --type=merge \
129+ -p " $( jq -n --arg mc " $MAIN_CONTAINER_PATCH " --arg wd " $WF_DEFAULTS_PATCH " \
130+ ' {"data":{"mainContainer":$mc,"workflowDefaults":$wd}}' ) "
131+
132+ # Restart workflow controller to pick up configmap changes
133+ kubectl rollout restart deployment/workflow-controller -n " $KFP_NAMESPACE "
134+ kubectl rollout status deployment/workflow-controller -n " $KFP_NAMESPACE " --timeout=120s
135+
136+ # --- Build the MLflow plugin config with caBundlePath ---
137+ MLFLOW_PATCH=$( jq -n --arg endpoint " $MLFLOW_ENDPOINT " --arg caBundlePath " $CA_MOUNT_PATH " ' {
46138 endpoint: $endpoint,
47- tls: { insecureSkipVerify: true },
139+ tls: { caBundlePath: $caBundlePath },
48140 settings: { workspacesEnabled: true }
49141}' )
50142
@@ -54,10 +146,11 @@ jq --argjson mlflow "$MLFLOW_PATCH" '. + { plugins: { mlflow: $mlflow } }' \
54146echo " Patched config.json plugins.mlflow:"
55147jq ' .plugins.mlflow' /tmp/kfp-config.json
56148
149+ # --- Deploy the config and mount the CA cert into the API server ---
57150kubectl create configmap kfp-mlflow-config -n " $KFP_NAMESPACE " \
58151 --from-file=config.json=/tmp/kfp-config.json --dry-run=client -o yaml | kubectl apply -f -
59152kubectl patch deployment ml-pipeline -n " $KFP_NAMESPACE " --type=strategic -p \
60- ' {"spec":{"template":{"spec":{"volumes":[{"name":"mlflow-cfg","configMap":{"name":"kfp-mlflow-config"}}],"containers":[{"name":"ml-pipeline-api-server","volumeMounts":[{"name":"mlflow-cfg","mountPath":"/config/config.json","subPath":"config.json"}]}]}}}}'
153+ ' {"spec":{"template":{"spec":{"volumes":[{"name":"mlflow-cfg","configMap":{"name":"kfp-mlflow-config"}},{"name":"mlflow-ca","configMap":{"name":"mlflow-ca-cert"}} ],"containers":[{"name":"ml-pipeline-api-server","volumeMounts":[{"name":"mlflow-cfg","mountPath":"/config/config.json","subPath":"config.json"},{"name":"mlflow-ca","mountPath":"/etc/mlflow/tls","readOnly":true }]}]}}}}'
61154kubectl rollout status deployment/ml-pipeline -n " $KFP_NAMESPACE " --timeout=180s
62155
63156pkill -f " kubectl port-forward.*ml-pipeline.*8888" || true
0 commit comments