Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ make package
- `config/` β€” ConfigMaps and Secrets shared across components: MLRun env config, Jupyter env config, storage credentials secret, Pipelines config, Spark config, Grafana dashboards
- `seaweedfs/` β€” SeaweedFS-specific resources: S3 IAM config secret, bucket init job, admin UI NodePort service, ingress
- `kafka/` β€” Kafka Strimzi custom resources: KafkaNodePool, Kafka cluster CR, bootstrap alias Service, RBAC, NetworkPolicy
- `mlrun-migrations/` β€” upgrade-only Job (+ ServiceAccount/Role/RoleBinding) that triggers MLRun DB migrations once the new API chief is up
- `timescaledb/` β€” TimescaleDB Deployment, Service, PVC
- `jupyter-notebook/` β€” Jupyter Deployment and supporting resources
- `pipelines/` β€” Kubeflow Pipelines resources
Expand Down Expand Up @@ -90,6 +91,7 @@ From a fresh clone to a linted chart:
| Kafka | `kafka.enabled` | Strimzi CRDs β€” `strimzi-kafka-operator` sub-chart must also be enabled as a prerequisite; CRs use post-install hooks to wait for CRDs | `templates/kafka/` |
| Pipelines | `pipelines.enabled` | SeaweedFS (`seaweedfs.enabled` checked at render time; adds init container to wait for it), `mlrun-pipelines-config` ConfigMap | `templates/pipelines/`, `templates/config/mlrun-pipelines-config.yaml` |
| TimescaleDB | `timescaledb.enabled` | none; uses its own `<release>-timescaledb-secret` for the DB password | `templates/timescaledb/` β€” custom StatefulSet, not a sub-chart |
| MLRun migrations | `mlrun.enabled` and `mlrunMigrations.enabled` | MLRun API chief (`mlrun-api-chief` Deployment/Service). Regular resource (not a hook, so `helm upgrade` doesn't wait for it), rendered only when `.Release.IsUpgrade`, named `<release>-mlrun-migrations-<revision>`: waits for the chief rollout and for all chief pods to run `mlrun.api.image`, then POSTs `/api/v1/operations/migrations` once `/api/v1/client-spec` reports the expected version, and polls the background task | `templates/mlrun-migrations/` |
| Prometheus + Grafana | `kube-prometheus-stack.enabled` | none at runtime; model monitoring dashboards are pre-loaded as static JSON ConfigMaps | sub-chart config in `values.yaml`; dashboards in `templates/config/model-monitoring-*.yml` |

## How to Add a New Component
Expand Down
2 changes: 1 addition & 1 deletion charts/mlrun-ce/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v1
name: mlrun-ce
version: 0.12.1-rc.4
version: 0.12.1-rc.5
appVersion: 1.12.0
description: MLRun Open Source Stack
home: https://iguazio.com
Expand Down
17 changes: 17 additions & 0 deletions charts/mlrun-ce/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,23 @@ import mlrun
mlrun.get_run_db().trigger_migrations()
```

Alternatively, let the chart trigger them for you by enabling `mlrunMigrations`.
On `helm upgrade` only, the chart creates a `<release>-mlrun-migrations-<revision>` Job. It waits until the MLRun API chief
has rolled out and runs the new `mlrun.api.image`, then sends the migrations request and waits for it to finish:
```bash
helm --namespace mlrun get values my-mlrun > previous-values.yaml
helm --namespace mlrun upgrade my-mlrun mlrun/mlrun-ce -f previous-values.yaml \
--set mlrunMigrations.enabled=true
```
Pass your previous values with `-f` rather than `--reuse-values`: `--reuse-values` doesn't load the new chart defaults,
so `mlrunMigrations` settings other than `enabled` would be missing when upgrading from a chart version without them.
The Job isn't a Helm hook, so `helm upgrade` (also with `--wait`, unless `--wait-for-jobs` is set) doesn't wait for the migrations.
Check the result with:
```bash
kubectl --namespace mlrun get jobs -l app.kubernetes.io/component=mlrun-migrations
kubectl --namespace mlrun logs job/my-mlrun-mlrun-migrations-<revision>
```

> **Note:** Once the database schema is upgraded there is no way to downgrade it

## Uninstalling the Chart
Expand Down
3 changes: 3 additions & 0 deletions charts/mlrun-ce/admin_installation_values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ nuclio:
mlrun:
enabled: false

mlrunMigrations:
enabled: false

jupyterNotebook:
enabled: false
serviceAccount:
Expand Down
4 changes: 4 additions & 0 deletions charts/mlrun-ce/non_admin_cluster_ip_installation_values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ mlrun:
type: ClusterIP
nodePort: ""

# Set to true to trigger MLRun DB migrations automatically on helm upgrade
mlrunMigrations:
enabled: false

jupyterNotebook:
service:
type: ClusterIP
Expand Down
5 changes: 5 additions & 0 deletions charts/mlrun-ce/non_admin_installation_values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ mlrun:
service:
type: NodePort
nodePort: 30060

# Set to true to trigger MLRun DB migrations automatically on helm upgrade
mlrunMigrations:
enabled: false

jupyterNotebook:
service:
type: NodePort
Expand Down
6 changes: 6 additions & 0 deletions charts/mlrun-ce/templates/NOTES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,11 @@ These pods receive OTel auto-instrumentation (runtime metrics, traces, HTTP metr
{{- end }}
{{- end }}

{{- if and .Values.mlrun.enabled (.Values.mlrunMigrations | default dict).enabled .Release.IsUpgrade }}
{{- "\n" }}
MLRun DB migrations are running in the background (helm doesn't wait for them). Check the result with:
kubectl -n {{ .Release.Namespace }} logs -f job/{{ include "mlrun-ce.mlrunMigrations.jobName" . }}
{{- end }}

Happy MLOPSing!!! :]
{{- end }}
61 changes: 61 additions & 0 deletions charts/mlrun-ce/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,67 @@ Mlrun API selector labels
app.kubernetes.io/component: {{ .Values.mlrun.api.name | quote }}
{{- end -}}

{{/*
Mlrun chart name, delegates to the mlrun chart helper (honors mlrun.nameOverride).
Like the helpers below, only usable when mlrun.enabled, since disabled subchart templates aren't loaded.
*/}}
{{- define "mlrun-ce.mlrun.name" -}}
{{- include "mlrun.name" (dict "Values" .Values.mlrun "Chart" (dict "Name" "mlrun") "Release" .Release) -}}
{{- end -}}

{{/*
Mlrun API chief name, delegates to the mlrun chart helper
*/}}
{{- define "mlrun-ce.mlrun.api.chief.fullname" -}}
{{- include "mlrun.api.chief.fullname" (dict "Values" .Values.mlrun "Chart" (dict "Name" "mlrun") "Release" .Release) -}}
{{- end -}}

{{/*
Mlrun API chief container name, as set by the mlrun chart
*/}}
{{- define "mlrun-ce.mlrun.api.containerName" -}}
{{- printf "%s-%s" (include "mlrun-ce.mlrun.name" .) .Values.mlrun.api.name -}}
{{- end -}}

{{/*
Mlrun API chief pods label selector, mirrors mlrun.api.chief.selectorLabels from the mlrun chart
*/}}
{{- define "mlrun-ce.mlrun.api.chief.selector" -}}
{{- printf "app.kubernetes.io/name=%s,app.kubernetes.io/instance=%s,app.kubernetes.io/component=%s,app.kubernetes.io/sub-component=chief" (include "mlrun-ce.mlrun.name" .) .Release.Name .Values.mlrun.api.name -}}
{{- end -}}

{{/*
Mlrun API image, as deployed by the mlrun chart
*/}}
{{- define "mlrun-ce.mlrun.api.image" -}}
{{- printf "%s:%s" .Values.mlrun.api.image.repository (.Values.mlrun.api.image.tag | toString) -}}
{{- end -}}

{{/*
Mlrun migrations Job name: <release>-mlrun-migrations-<revision>, prefix truncated to fit the 63 chars limit
*/}}
{{- define "mlrun-ce.mlrunMigrations.jobName" -}}
{{- $suffix := printf "-%d" (int .Release.Revision) -}}
{{- printf "%s-mlrun-migrations" .Release.Name | trunc (int (sub 63 (len $suffix))) | trimSuffix "-" -}}
{{- $suffix -}}
{{- end -}}

{{/*
Mlrun migrations labels
*/}}
{{- define "mlrun-ce.mlrunMigrations.labels" -}}
{{ include "mlrun-ce.common.labels" . }}
{{ include "mlrun-ce.mlrunMigrations.selectorLabels" . }}
{{- end -}}

{{/*
Mlrun migrations selector labels
*/}}
{{- define "mlrun-ce.mlrunMigrations.selectorLabels" -}}
{{ include "mlrun-ce.common.selectorLabels" . }}
app.kubernetes.io/component: mlrun-migrations
{{- end -}}


{{/*
Common labels
Expand Down
139 changes: 139 additions & 0 deletions charts/mlrun-ce/templates/mlrun-migrations/migrations-job.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
{{- if and .Values.mlrun.enabled (.Values.mlrunMigrations | default dict).enabled .Release.IsUpgrade }}
# Triggers MLRun DB migrations after `helm upgrade`.
# After an upgrade that requires migrations, the chief starts in `waiting_for_migrations` state and
# rejects most requests until POST /api/v1/operations/migrations is sent. The job:
# 1. waits for the chief rollout and for all chief pods to run the new image
# 2. waits for the chief to report the expected version via client-spec
# 3. triggers the migrations and polls the returned background task until it finishes
#
# Deliberately not a Helm hook: Helm waits for hooks, so long migrations would fail the upgrade on timeout.
# As a regular resource `helm upgrade` (also with --wait, unless --wait-for-jobs) doesn't wait for it to finish.
# The revision suffix creates a new Job on every upgrade (Job specs are immutable); Helm deletes the
# previous revision's Job since it is no longer part of the release.
{{- $chief := include "mlrun-ce.mlrun.api.chief.fullname" . }}
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mlrun-ce.mlrunMigrations.jobName" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "mlrun-ce.mlrunMigrations.labels" . | nindent 4 }}
spec:
# No ttlSecondsAfterFinished: keep the Job and its pods for inspection until the next upgrade replaces it
backoffLimit: {{ .Values.mlrunMigrations.backoffLimit }}
activeDeadlineSeconds: {{ .Values.mlrunMigrations.timeoutSeconds }}
template:
metadata:
labels:
{{- include "mlrun-ce.mlrunMigrations.selectorLabels" . | nindent 8 }}
spec:
restartPolicy: Never
serviceAccountName: {{ .Release.Name }}-mlrun-migrations
containers:
- name: trigger-migrations
image: {{ .Values.mlrunMigrations.kubectlImage.repository }}:{{ .Values.mlrunMigrations.kubectlImage.tag }}
resources:
{{- toYaml .Values.mlrunMigrations.resources | nindent 12 }}
env:
- name: NAMESPACE
value: {{ .Release.Namespace }}
- name: CHIEF_DEPLOYMENT
value: {{ $chief }}
- name: CHIEF_SELECTOR
value: {{ include "mlrun-ce.mlrun.api.chief.selector" . }}
- name: CHIEF_CONTAINER
value: {{ include "mlrun-ce.mlrun.api.containerName" . }}
- name: EXPECTED_IMAGE
value: {{ include "mlrun-ce.mlrun.api.image" . | quote }}
- name: MLRUN_CHIEF_URL
value: http://{{ $chief }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.mlrun.api.chief.service.port }}
- name: EXPECTED_VERSION
value: {{ .Values.mlrunMigrations.expectedVersion | default .Values.mlrun.api.image.tag | toString | quote }}
- name: POLL_INTERVAL_SECONDS
value: {{ .Values.mlrunMigrations.pollIntervalSeconds | quote }}
- name: TIMEOUT_SECONDS
value: {{ .Values.mlrunMigrations.timeoutSeconds | quote }}
command:
- /bin/sh
- -c
- |
set -eu
api_url="${MLRUN_CHIEF_URL%/}/api/v1"
deadline=$(( $(date +%s) + TIMEOUT_SECONDS ))
body_file=/tmp/response.json

fail() {
echo "ERROR: $*" >&2
exit 1
}

sleep_or_timeout() {
[ "$(date +%s)" -lt "${deadline}" ] || fail "Timed out waiting for $1"
sleep "${POLL_INTERVAL_SECONDS}"
}

# Sends a request, writes the body to $body_file and prints the HTTP status ("000" if unreachable)
call() {
curl -sS -o "${body_file}" -w '%{http_code}' -X "$1" "${api_url}$2" --max-time 30 || true
}

# Prints the first string value of "$1" in the response body (no jq in this image);
# fails when the key is missing so a changed response doesn't go unnoticed
json_string() {
value=$(grep -o "\"$1\" *: *\"[^\"]*\"" "${body_file}" | head -n 1 | sed 's/.*: *"\(.*\)"$/\1/')
[ -n "${value}" ] || fail "\"$1\" not found in response: $(cat "${body_file}")"
echo "${value}"
}

echo "Waiting for deployment ${CHIEF_DEPLOYMENT} rollout to complete..."
kubectl -n "${NAMESPACE}" rollout status "deployment/${CHIEF_DEPLOYMENT}" --timeout="${TIMEOUT_SECONDS}s"

# Chief uses the Recreate strategy - also wait for old (terminating) pods to be gone,
# so the chief service can only route to pods running the new image
echo "Waiting for all chief pods to run ${EXPECTED_IMAGE}..."
while true; do
images=$(kubectl -n "${NAMESPACE}" get pods -l "${CHIEF_SELECTOR}" \
-o jsonpath='{range .items[*]}{.spec.containers[?(@.name=="'"${CHIEF_CONTAINER}"'")].image}{"\n"}{end}' | sort -u)
[ "${images}" = "${EXPECTED_IMAGE}" ] && break
echo "Chief pods images: $(echo ${images}), waiting..."
sleep_or_timeout "chief pods to run ${EXPECTED_IMAGE}"
done

# client-spec is served by the chief in waiting_for_migrations state as well
while true; do
status=$(call GET /client-spec)
if [ "${status}" = "200" ]; then
version=$(json_string version)
echo "Chief reports version ${version}"
[ "${version}" = "${EXPECTED_VERSION}" ] && break
else
echo "Chief is not ready yet (client-spec status: ${status})"
fi
sleep_or_timeout "chief to report version ${EXPECTED_VERSION}"
done

status=$(call POST /operations/migrations)
case "${status}" in
200)
echo "No migrations needed"
exit 0
;;
202) ;;
*) fail "Failed triggering migrations: status=${status}, body=$(cat "${body_file}")" ;;
esac

task_name=$(json_string name)
echo "Migrations triggered, background task: ${task_name}"
while true; do
status=$(call GET "/background-tasks/${task_name}")
[ "${status}" = "200" ] || fail "Failed getting migrations task: status=${status}, body=$(cat "${body_file}")"
state=$(json_string state)
echo "Migrations state: ${state}"
case "${state}" in
succeeded) break ;;
failed) fail "Migrations failed: $(cat "${body_file}")" ;;
esac
sleep_or_timeout "migrations to finish"
done
echo "Migrations completed successfully"
{{- end }}
52 changes: 52 additions & 0 deletions charts/mlrun-ce/templates/mlrun-migrations/rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{{- if and .Values.mlrun.enabled (.Values.mlrunMigrations | default dict).enabled }}
# Rendered on install as well, so they already exist when the upgrade-only Job runs
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Release.Name }}-mlrun-migrations
Comment thread
GiladShapira94 marked this conversation as resolved.
Resolved
namespace: {{ .Release.Namespace }}
labels:
{{- include "mlrun-ce.mlrunMigrations.labels" . | nindent 4 }}
---
# Read-only access needed by `kubectl rollout status` and the chief pods image check
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Release.Name }}-mlrun-migrations
namespace: {{ .Release.Namespace }}
labels:
{{- include "mlrun-ce.mlrunMigrations.labels" . | nindent 4 }}
rules:
- apiGroups:
- apps
resources:
- deployments
- replicasets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Release.Name }}-mlrun-migrations
namespace: {{ .Release.Namespace }}
labels:
{{- include "mlrun-ce.mlrunMigrations.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Release.Name }}-mlrun-migrations
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-mlrun-migrations
namespace: {{ .Release.Namespace }}
{{- end }}
25 changes: 25 additions & 0 deletions charts/mlrun-ce/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,31 @@ mlrun:
ce:
mode: full

# Job that triggers MLRun DB migrations (POST /api/v1/operations/migrations) on `helm upgrade` only.
# Before triggering, it waits until the mlrun-api-chief rollout completed, every chief pod runs the
# expected mlrun-api image, and the chief reports the expected version via /api/v1/client-spec.
# Not a Helm hook, so `helm upgrade` doesn't wait for the migrations to finish. Check the result with:
# kubectl -n <namespace> logs job/<release>-mlrun-migrations-<revision>
mlrunMigrations:
enabled: false
# Version the chief must report before migrations are triggered. Defaults to mlrun.api.image.tag.
expectedVersion: ""
pollIntervalSeconds: 5
# Max time to wait for the new chief and for the migrations to finish
timeoutSeconds: 3600
backoffLimit: 2
# Needs kubectl, curl and sh
kubectlImage:
repository: alpine/kubectl
tag: "1.33.4"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi

jupyterNotebook:
serviceAccount:
create: true
Expand Down