Upgrading from v1.1 to v1.2
This guide covers upgrading an OpenChoreo installation from v1.1 to v1.2 across every plane. Review the upgrade overview — including the upgrade policy and backup recommendations — before you begin, and upgrade the control plane first.
v1.2 introduces the project release lifecycle (ProjectType, ClusterProjectType, ProjectRelease, ProjectReleaseBinding) and makes Project.spec.type a required, immutable field. Upgrading the control plane requires a staged migration — do not run a plain helm upgrade on the control plane. See Upgrading the Control Plane. The data and workflow planes upgrade normally.
Prerequisites
-
kubectlaccess to the cluster(s) with permission to manage CRDs. -
jqandyq(v4) on the machine running the upgrade. -
The extracted v1.2 charts for the planes you run, for the CRDs under each chart's
crds/directory. At minimum the control-plane chart is required for the migration:helm pull oci://ghcr.io/openchoreo/helm-charts/openchoreo-control-plane --version 1.2.0 --untarThis unpacks the chart into
./openchoreo-control-plane/, which the commands below reference. Pull theopenchoreo-data-plane,openchoreo-workflow-plane, andopenchoreo-observability-planecharts the same way if you have those planes installed. -
A maintenance window. Nothing may perform full-object writes to
Projectresources while the field is being backfilled (see step 0 of the control-plane migration).
Every command targets the current kubeconfig context. To target a different cluster, add --context <ctx> to each kubectl command and --kube-context <ctx> to each helm command, and set -n <namespace> where a plane's namespace is not the default.
Gateway API and kgateway
v1.1 runs Gateway API v1.4.1 (experimental channel) and kgateway v2.2.1; v1.2 aligns on Gateway API v1.5.1 (standard channel) and kgateway v2.3.1. Do this for each plane you will upgrade. You can complete it upfront as a prerequisite:
- Single-cluster: do this once, on the control plane. The data plane and observability plane reuse the control plane's gateway controller and the cluster's (cluster-scoped) Gateway API CRDs, so they need no separate action.
- Multi-cluster: do this on each plane you run — the control plane, data plane, and observability plane since each cluster has its own gateway controller and Gateway API CRDs.
Gateway API CRDs
The control-plane and observability-plane releases include a TLSRoute rendered at gateway.networking.k8s.io/v1alpha2. In the v1.5.1 standard channel TLSRoute is served at v1 only, so once you apply the v1.5.1 CRDs, helm upgrade can no longer decode the current release manifest and fails with no matches for kind "TLSRoute" in version "gateway.networking.k8s.io/v1alpha2". Keep v1alpha2 served across the upgrade so Helm can read the old manifest; the v1.2 chart then re-renders the route at v1.
On each cluster you are upgrading (see above):
# Apply the v1.5.1 standard CRDs (this stops serving TLSRoute v1alpha2)...
kubectl apply --server-side --force-conflicts \
-f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml
# ...then re-serve v1alpha2 so `helm upgrade` can still decode the current release manifest.
idx=$(kubectl get crd tlsroutes.gateway.networking.k8s.io -o json \
| jq '.spec.versions | map(.name == "v1alpha2") | index(true)')
kubectl patch crd tlsroutes.gateway.networking.k8s.io --type=json \
-p "[{\"op\": \"replace\", \"path\": \"/spec/versions/${idx}/served\", \"value\": true}]"
After the plane upgrade succeeds, its release references only TLSRoute/v1. You may then re-apply the standard CRDs to return v1alpha2 to served: false (optional cleanup — the extra served version is otherwise harmless):
kubectl apply --server-side --force-conflicts \
-f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.1/standard-install.yaml
The data plane serves no TLSRoute, so it never needs the v1alpha2 re-serve step. Where the data plane runs its own gateway controller (multi-cluster), upgrade its Gateway API CRDs to v1.5.1 only if the API gateway module you run supports Kubernetes Gateway API 1.5. If it does not, skip the data-plane Gateway API upgrade and keep the data plane on Kubernetes Gateway API 1.4 — check your module's supported version in the API Gateway modules in the Ecosystem Catalog.
kgateway
Upgrade kgateway on each cluster you are upgrading (see above). Add -n <namespace> for the plane (openchoreo-control-plane, openchoreo-data-plane, or openchoreo-observability-plane):
helm upgrade kgateway-crds oci://cr.kgateway.dev/kgateway-dev/charts/kgateway-crds \
--version v2.3.1 -n <namespace>
helm upgrade kgateway oci://cr.kgateway.dev/kgateway-dev/charts/kgateway \
--version v2.3.1 --reset-values -n <namespace>
kubectl rollout status deployment/kgateway -n <namespace> --timeout=120s
--reset-values drops the v1.1 KGW_ENABLE_GATEWAY_API_EXPERIMENTAL_FEATURES flag, which v1.2 no longer needs now that TLSRoute is GA in the standard channel. If you are keeping a plane on Gateway API 1.4 (see the data plane note above), keep the flag with --reuse-values instead.
Upgrading the Control Plane
The control plane carries this release's breaking change. Instead of the standard "apply CRDs, then helm upgrade" flow, the control plane upgrade is a staged migration that keeps reconciliation working while Project.spec.type is backfilled across existing projects.
What breaks
v1.2 adds a required, immutable field to Project:
spec:
type: # NEW: required, immutable
kind: ClusterProjectType # defaults to ProjectType if omitted
name: default
Existing Project objects in etcd do not have spec.type. CRD schema validation runs on every write, including status-subresource writes, so once the strict v1.2 CRD is installed, any write to a Project that lacks spec.type is rejected with a required-field error. The still-running v1.1 controller updates Project status on every reconcile, which means installing the strict CRD before backfilling the field breaks reconciliation for every existing project.
The upgrade therefore stages the CRD change: first install a relaxed Project CRD (identical to v1.2 except spec.type is optional), backfill spec.type on all projects, verify, then install the strict CRD set and upgrade the control plane.
Are you affected?
Any project that predates v1.2 lacks spec.type. List them:
kubectl get projects.openchoreo.dev -A -o json \
| jq -r '.items[] | select(.spec.type == null) | "\(.metadata.namespace)/\(.metadata.name)"'
If this prints nothing, every project already carries spec.type and you can skip the backfill (steps 1 and 2). If it prints project names, follow every step in order.
How deployed state carries over
In v1.2, a project is deployed to an environment only where a ProjectReleaseBinding exists for the (project, environment) tuple. The controller never creates bindings; clients do. For projects created on v1.1 no client ever did, so this upgrade creates them.
Step 5 creates unpinned bindings (spec.projectRelease left empty). Pinning is handled by the v1.2 Project controller's seeding contract: on first reconcile it resolves spec.type, cuts a ProjectRelease snapshot, records it in status.latestRelease, and fills the pin on any binding of the project whose pin is empty. A set pin is never touched; advancing it later is a promotion action (occ, GitOps, kubectl edit). You cannot pin bindings by hand because release names embed a server-computed hash.
By default step 5 creates one binding per environment in the project's deployment pipeline. This matches v1.1 behavior, where a component could be promoted to any pipeline environment without a project-level gate. If you would rather a project deploy to only some of its environments, create bindings for just those environments (see step 5); a component can then reach the remaining environments only after the project is promoted there.
Why this is safe for running workloads
The ProjectReleaseBinding controller computes the namespace name with the same function and the same inputs as the v1.1 ReleaseBinding controller: dp-{orgNamespace}-{project}-{env}-{hash}. After the upgrade, the binding's RenderedRelease server-side-applies a Namespace manifest with exactly the name your workloads already run in. The existing namespace is adopted, not replaced. Components and component bindings are untouched throughout. Only control-plane reconciliation pauses during the upgrade window; running workloads on data planes are not affected by the migration steps.
One behavior change to be aware of: in v1.1 the RenderedRelease controller implicitly created the cell namespace (dp-{orgNamespace}-{project}-{env}-{hash}). v1.2 removes that. The namespace is now part of the project's released manifests, applied through the ProjectReleaseBinding. For migrated projects the namespace is created through the project release manifest, which adopts the existing namespace, so nothing changes. For projects created after the upgrade, component deployments wait until the project is released to the environment.
Migration steps
Step 0: Pause writers to Project resources
The v1.1 controller manager and openchoreo-api use typed clients that do not know spec.type. Any full-object spec update they perform after the backfill (for example a project update through the v1.1 API or console) serializes the spec without the field and silently erases it while the relaxed CRD is in place. Scale them down until step 6 resumes them:
kubectl -n openchoreo-control-plane scale deployment controller-manager openchoreo-api --replicas=0
Confirm both deployments have no running pods before continuing:
kubectl -n openchoreo-control-plane get pods -l 'app.kubernetes.io/name in (controller-manager, openchoreo-api)'
GitOps users: if Project manifests are managed by Flux or ArgoCD, suspend syncing for them now. See GitOps installations to update GitOps repositories.
Step 1: Install the relaxed (migration) Project CRD
Generate a relaxed CRD from the v1.2 chart by removing type from the spec's required list, then apply it server-side. Nothing else changes; in particular the immutability rule (self == oldSelf) stays, because CEL transition rules on an optional field are skipped when the field is absent from the old object, so the unset-to-set backfill in step 2 still passes.
yq 'del(.spec.versions[].schema.openAPIV3Schema.properties.spec.required[] | select(. == "type"))' \
openchoreo-control-plane/crds/openchoreo.dev_projects.yaml > projects-crd-relaxed.yaml
kubectl apply --server-side --force-conflicts -f projects-crd-relaxed.yaml
The apply uses --server-side because the generated CRD exceeds the client-side-apply annotation size limit. It passes --force-conflicts because on a v1.1 cluster the CRD fields are owned by the helm field manager from the original install, and server-side apply refuses to overwrite another manager's fields without it. Taking ownership is intended and safe: Helm never writes crds/ on helm upgrade, so no later Helm operation contends for the fields, and Update-based writers (Helm, controllers) bypass field-ownership checks entirely.
After this step, existing projects remain valid and writable, and spec.type is accepted when present.
Step 2: Backfill spec.type on all existing projects
If any project should use a (Cluster)ProjectType other than default, patch it manually before running the generic backfill below. spec.type is immutable once set: once the backfill stamps default on a project, moving it to a different type requires deleting and recreating the project.
kubectl patch projects.openchoreo.dev <name> -n <namespace> --type merge \
-p '{"spec":{"type":{"kind":"ClusterProjectType","name":"<custom-type>"}}}'
Patch every remaining project that lacks spec.type to reference the default ClusterProjectType shipped with v1.2. Preview first, then apply:
# Preview: list the projects that would be patched
kubectl get projects.openchoreo.dev -A -o json \
| jq -r '.items[] | select(.spec.type == null) | "\(.metadata.namespace)/\(.metadata.name)"'
# Apply
kubectl get projects.openchoreo.dev -A -o json \
| jq -r '.items[] | select(.spec.type == null) | [.metadata.namespace, .metadata.name] | @tsv' \
| while IFS=$'\t' read -r ns name; do
echo "Patching ${ns}/${name}"
kubectl patch projects.openchoreo.dev "$name" -n "$ns" --type merge \
-p '{"spec":{"type":{"kind":"ClusterProjectType","name":"default"}}}'
done
Verification gate. Do not proceed until this prints nothing:
kubectl get projects.openchoreo.dev -A -o json \
| jq -r '.items[] | select(.spec.type == null) | "\(.metadata.namespace)/\(.metadata.name)"'
Step 3: Install the full v1.2 CRD set
Apply every CRD from the extracted v1.2 chart. This replaces the relaxed Project CRD with the strict one (spec.type required) and installs the new CRDs (projecttypes, clusterprojecttypes, projectreleases, projectreleasebindings) plus any other CRD changes in the release. Helm does not upgrade files under crds/ on helm upgrade, so this manual apply is required regardless of the breaking change.
kubectl apply --server-side --force-conflicts -f openchoreo-control-plane/crds/
--force-conflicts is required for the same reason as in step 1: every pre-existing CRD is still field-managed by helm. The strict schema is enforced on writes only; the backfilled objects already satisfy it.
Step 4: Apply the default ClusterProjectType
The default ClusterProjectType is the target of every backfilled spec.type reference. It provisions only the namespace per environment:
kubectl apply -f https://raw.githubusercontent.com/openchoreo/openchoreo/v1.2.0/samples/getting-started/cluster-project-types/default.yaml
Apply it before upgrading the control plane. The new Project controller watches ClusterProjectType and recovers if it arrives late, but applying it first avoids a wave of transient ProjectTypeNotFound conditions on startup. Apply any custom (Cluster)ProjectTypes referenced in step 2 now as well.
Step 5: Create ProjectReleaseBindings for existing projects
For each project, this resolves its deployment pipeline, expands the environments from spec.promotionPaths (sources and targets, deduplicated, in pipeline order), and creates one unpinned binding named {project}-{env} per environment. It skips any (project, environment) tuple that already has a binding, so it is idempotent and coexists with bindings authored via GitOps.
kubectl get projects.openchoreo.dev -A -o json \
| jq -r '.items[] | [.metadata.namespace, .metadata.name, (.spec.deploymentPipelineRef.name // "")] | @tsv' \
| while IFS=$'\t' read -r ns project pipeline; do
if [ -z "$pipeline" ]; then
echo "skip ${ns}/${project}: no deploymentPipelineRef"; continue
fi
# Environments in promotion-path order, deduplicated
envs="$(kubectl get deploymentpipelines.openchoreo.dev "$pipeline" -n "$ns" -o json \
| jq -r '.spec.promotionPaths[]? | (.sourceEnvironmentRef.name, (.targetEnvironmentRefs[]?.name))' \
| awk '!seen[$0]++')"
# A while/read loop (not `for env in $envs`) so word splitting is by
# line in both bash and zsh; zsh does not split unquoted $envs.
while IFS= read -r env; do
[ -n "$env" ] || continue
# Skip if a binding for this (project, environment) already exists
if kubectl get projectreleasebindings.openchoreo.dev -n "$ns" -o json \
| jq -e --arg p "$project" --arg e "$env" \
'any(.items[]; .spec.owner.projectName == $p and .spec.environment == $e)' >/dev/null; then
echo "skip (${project}, ${env}): binding already exists"; continue
fi
echo "Creating ProjectReleaseBinding ${ns}/${project}-${env}"
cat <<EOF | kubectl create -f -
apiVersion: openchoreo.dev/v1alpha1
kind: ProjectReleaseBinding
metadata:
name: ${project}-${env}
namespace: ${ns}
labels:
openchoreo.dev/project: ${project}
openchoreo.dev/environment: ${env}
spec:
owner:
projectName: ${project}
environment: ${env}
EOF
done <<< "$envs"
done
The bindings stay unpinned and pending until step 6; the v1.2 Project controller cuts each project's ProjectRelease and seeds the pins.
Creating bindings for a subset of environments
The loop above creates a binding for every environment in the project's deployment pipeline. If you do not want a project to deploy to all of its environments, create ProjectReleaseBindings only for the environments you need instead: skip the loop for that project and apply one binding per desired environment.
Step 6: Resume the writers and upgrade the control-plane Helm release
Scale the control-plane writers back up before running the upgrade, and wait for the rollout:
kubectl -n openchoreo-control-plane scale deployment controller-manager openchoreo-api --replicas=1
kubectl -n openchoreo-control-plane rollout status deployment controller-manager --timeout=180s
kubectl -n openchoreo-control-plane rollout status deployment openchoreo-api --timeout=180s
This is recommended, not just cleanup: resuming the writers before the upgrade keeps the control plane's downtime to the rollout window itself, rather than extending it by however long the pause lasted. Resuming is safe at this point because the reasons for pausing have expired: every project now carries spec.type, and the strict CRD from step 3 rejects any write that would strip it instead of losing it silently.
Make sure the Gateway API and kgateway prerequisites are done for the control plane (v1.5.1 standard CRDs applied and TLSRoute v1alpha2 re-served). Otherwise this upgrade fails with no matches for kind "TLSRoute" in version "gateway.networking.k8s.io/v1alpha2".
Then upgrade:
helm upgrade openchoreo-control-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-control-plane \
--version 1.2.0 --reset-then-reuse-values -n openchoreo-control-plane
The rollout replaces the v1.1 pods with the v1.2 image while the old pods keep serving webhooks. On startup the new controllers converge each project:
- The Project controller resolves
spec.type, cuts aProjectReleasesnapshot, and records it instatus.latestRelease. - It seeds
spec.projectReleaseon the unpinned bindings created in step 5. - Each binding renders the inlined type through a
RenderedRelease, which server-side-applies the namespace manifest and adopts the existing namespace on the data plane.
On large installations this is a one-time reconcile wave; it is idempotent and safe.
v1.2 seeds default authorization roles and bindings through a post-install/post-upgrade hook Job instead of applying them as part of the tracked Helm release, avoiding race conditions against admission webhook changes during upgrades. The trade-off: these objects briefly don't exist right after helm upgrade returns, until the hook Job finishes.
Anything that depends on those default roles/bindings, for example Backstage's catalog sync, which relies on the backstage-catalog-reader binding, can see authorization failures if it runs during that window. This is expected and self-resolving: the hook Job (*-authz-bootstrap in the control-plane namespace) retries on its own until the webhook backend is serving, and normally finishes in well under a minute.
Step 7: Post-upgrade verification
Reconciliation may take a minute to converge. Each check below should print nothing when the upgrade has settled; re-run until all three are clean.
# Every project has cut a ProjectRelease
kubectl get projects.openchoreo.dev -A -o json \
| jq -r '.items[] | select(.status.latestRelease == null) | "\(.metadata.namespace)/\(.metadata.name)"'
# Every binding is pinned to a release
kubectl get projectreleasebindings.openchoreo.dev -A -o json \
| jq -r '.items[] | select((.spec.projectRelease // "") == "") | "\(.metadata.namespace)/\(.metadata.name)"'
# Every binding is Ready
kubectl get projectreleasebindings.openchoreo.dev -A -o json \
| jq -r '.items[]
| select(([.status.conditions[]? | select(.type == "Ready" and .status == "True")] | length) == 0)
| "\(.metadata.namespace)/\(.metadata.name)"'
Additionally confirm on each data plane that namespaces were adopted, not recreated (creation timestamps predate the upgrade), and workloads are undisturbed:
kubectl get namespaces -l openchoreo.dev/project \
-o custom-columns=NAME:.metadata.name,CREATED:.metadata.creationTimestamp
kubectl get pods -A -l openchoreo.dev/project
GitOps installations
If Project manifests live in Git and are synced by Flux or ArgoCD, the cluster-side backfill alone is not enough: a later sync from a manifest without spec.type would attempt to remove the field and be rejected by the strict CRD (or silently strip it under the relaxed one).
-
Suspend syncing for
Projectmanifests before step 1 (flux suspend kustomization <name>or disable ArgoCD auto-sync). -
Add
spec.typeto everyProjectmanifest in the source repository, mirroring what step 2 applies in-cluster:spec:
type:
kind: ClusterProjectType
name: default -
Add
ProjectReleaseBindingmanifests to the repository. Instead of piping each manifest tokubectl createin step 5, redirect it to a file (one document per binding, separated by---) and commit it:printf -- '---\n' >> project-release-bindings.yaml
cat <<EOF >> project-release-bindings.yaml
apiVersion: openchoreo.dev/v1alpha1
kind: ProjectReleaseBinding
...
EOFManifests that omit
spec.projectReleaseare safe under GitOps: the controller seeds the pin server-side and, under server-side apply, a manifest that does not set the field never contends for it. To drive promotion from Git instead, setspec.projectReleaseexplicitly; the controller then never touches it. -
Resume syncing after step 6, once the source of truth includes both changes.
Rolling back the control plane
Before step 3, rollback is trivial: scale the v1.1 deployments back up (step 6's scale command). The relaxed CRD is a superset of the v1.1 schema and the backfilled spec.type values are inert to the v1.1 controller.
After step 6, roll back with helm rollback and re-apply the relaxed Project CRD from step 1 (not the v1.1 CRD: re-applying a schema without spec.type would prune the backfilled field on the next write and forfeit the migration work). Leave the new CRDs and their objects in place; they are inert without the v1.2 controllers. The v1.1 RenderedRelease controller restores implicit namespace creation, so workloads keep functioning either way.
Control plane failure modes
| Symptom | Cause | Fix |
|---|---|---|
Apply failed with N conflicts: conflicts with "helm" on a CRD apply | v1.1 CRDs are field-managed by helm from the original install | Apply with --force-conflicts (steps 1 and 3 already do); safe because Helm never writes crds/ after install |
helm upgrade fails with no endpoints available for service "controller-manager-webhook-service" | Writers still scaled to 0 from step 0; the admission webhooks have no backend | Run step 6's scale-up, wait for the rollout, re-run the upgrade (re-upgrading a FAILED release is safe) |
spec.type: Required value on project writes | Step 3 ran before the backfill completed | Re-apply the relaxed CRD from step 1, finish step 2, re-run step 3 |
spec.type cannot be changed after creation during backfill | Project already has a different spec.type set | Intentional; immutable once set. Skip it, or recreate the project if the type is wrong |
Project shows ProjectTypeNotFound after upgrade | Step 4 skipped or the referenced type named differently | Apply the missing (Cluster)ProjectType; the controller recovers via its watch |
| Binding stays unpinned after upgrade | Project controller has not cut the release yet, or the project's type is unresolvable | Check the project's conditions; fix the type reference, the controller seeds the pin on the next reconcile |
| Binding missing for an environment | Environment not in the pipeline's promotion paths, or bindings were created for a subset of environments | Re-run step 5 (idempotent) or create the binding manually / via occ |
| GitOps sync fails on Project manifests | Source manifests lack spec.type | Add spec.type to the manifests in the source repository |
A project lost spec.type between steps 2 and 3 | A v1.1 writer performed a full-object update during the window | Re-run step 2; keep writers scaled down (step 0) |
helm upgrade fails: no matches for kind "TLSRoute" in version "gateway.networking.k8s.io/v1alpha2" | Gateway API CRDs were upgraded to v1.5.1 standard (which stops serving TLSRoute v1alpha2), but the current release manifest still references it | Re-serve v1alpha2 before upgrading — see Gateway API and kgateway |
Upgrading the other planes
The data, workflow, and observability planes have no backward-incompatible changes in v1.2. Upgrade each plane you run with the standard upgrade process — apply the plane's updated CRDs, then helm upgrade with --version 1.2.0 --reset-then-reuse-values — after the control plane is on v1.2.
Make sure the Gateway API and kgateway prerequisites are done for each plane you upgrade. The observability plane renders a TLSRoute and needs the v1alpha2 re-serve; the data plane does not, and its Gateway API upgrade is conditional on the API gateway module (see that section).
v1.2 removes the localhost defaults the observability-plane chart previously shipped for the URLs the Observer uses to reach the control plane. Those fields now carry .invalid placeholders guarded by a chart-side validation gate, so helm upgrade fails before rendering:
Error: UPGRADE FAILED: execution error at (openchoreo-observability-plane/templates/validate.yaml:1:4): Placeholder domains found. Set real URLs for:
- observer.controlPlaneApiUrl contains placeholder domain (.invalid)
- observer.extraEnvs contains placeholder domain (.invalid) (e.g. OBSERVER_BASE_URL)
This affects only installations that relied on the old defaults. If you set these values explicitly at v1.1 install time, --reset-then-reuse-values carries them forward and the upgrade proceeds unchanged. If you did not, supply real values on upgrade.
The same change also cleared the default CORS origins (cors.allowedOrigins) on these APIs to []: which does not fail the upgrade, but it disables CORS, so any browser client that calls the Observer cross-origin — for example the OpenChoreo Backstage Console — is blocked until you restore the origins.
For a single-cluster or local install, all three fields take the values the v1.1 chart used to default to:
| Field | v1.1 default | If left unset in v1.2 |
|---|---|---|
observer.controlPlaneApiUrl | http://api.openchoreo.localhost:8080 | Upgrade fails (placeholder gate) |
observer.extraEnvs → OBSERVER_BASE_URL | http://observer.openchoreo.localhost:11080 | Upgrade fails (placeholder gate) |
observer.cors.allowedOrigins | ["http://openchoreo.localhost:8080"] | Upgrade succeeds, but CORS is disabled and browser clients are blocked |
helm upgrade openchoreo-observability-plane \
oci://ghcr.io/openchoreo/helm-charts/openchoreo-observability-plane \
--version 1.2.0 --reset-then-reuse-values -n openchoreo-observability-plane \
-f - <<'EOF'
observer:
controlPlaneApiUrl: "http://api.openchoreo.localhost:8080"
extraEnvs:
- name: OBSERVER_BASE_URL
value: "http://observer.openchoreo.localhost:11080"
- name: AUTHZ_TIMEOUT
value: "30s"
cors:
allowedOrigins:
- "http://openchoreo.localhost:8080"
EOF
Replace the hostnames and origins with the values valid for your deployment; the sample restores the previous single-cluster defaults, and in a multi-cluster install the URLs must be routable from the observability plane to the control plane while allowedOrigins must list the origins your browser clients are served from.
If you enable the RCA or FinOps agents, add their openchoreoApiUrl and cors.allowedOrigins under rca and finOpsAgent too; the URLs are gated the same way and the CORS defaults were cleared identically.
Community module versions
If you use the default logs, metrics, and traces modules at the versions from the OpenChoreo v1.1 documentation, upgrade them to the versions compatible with v1.2 as well.
- Observability Logs OpenSearch module: v0.4.1 -> v0.5.3
helm upgrade --install observability-logs-opensearch \
oci://ghcr.io/openchoreo/helm-charts/observability-logs-opensearch \
--create-namespace \
--namespace openchoreo-observability-plane \
--version 0.5.3 \
--reset-then-reuse-values \
--set openSearchSetup.openSearchSecretName="opensearch-admin-credentials" \
--set adapter.openSearchSecretName="opensearch-admin-credentials"
- Observability Metrics Prometheus module: v0.6.1 -> v0.6.2 (replace the LoadBalancer service with ClusterIP)
helm upgrade --install observability-metrics-prometheus \
oci://ghcr.io/openchoreo/helm-charts/observability-metrics-prometheus \
--create-namespace \
--namespace openchoreo-observability-plane \
--version 0.6.2 \
--reset-then-reuse-values
- Observability Traces OpenSearch module: v0.4.2 -> v0.5.0
helm upgrade --install observability-traces-opensearch \
oci://ghcr.io/openchoreo/helm-charts/observability-tracing-opensearch \
--create-namespace \
--namespace openchoreo-observability-plane \
--version 0.5.0 \
--reset-then-reuse-values \
--set openSearch.enabled=false \
--set openSearchSetup.openSearchSecretName="opensearch-admin-credentials"
- Observability Events OTEL Collector module: (new in v1.2)
v1.2 adds the persisted Kubernetes events feature, served by a new observability-events-otel-collector module. It is not part of any existing release, so an in-place upgrade does not add it — install it explicitly.
The example below stores events in the same OpenSearch backend the other modules use, authenticating with the opensearch-admin-credentials secret:
helm upgrade --install observability-events-otel-collector \
oci://ghcr.io/openchoreo/helm-charts/observability-events-otel-collector \
--namespace openchoreo-observability-plane --version 0.1.1 \
-f - <<'EOF'
collector:
extraEnv:
- name: OPENSEARCH_USERNAME
valueFrom:
secretKeyRef:
name: opensearch-admin-credentials
key: username
- name: OPENSEARCH_PASSWORD
valueFrom:
secretKeyRef:
name: opensearch-admin-credentials
key: password
extraExtensions:
basicauth/opensearch:
client_auth:
username: ${env:OPENSEARCH_USERNAME}
password: ${env:OPENSEARCH_PASSWORD}
exporters:
opensearch:
logs_index: "k8s-events"
logs_index_time_format: "yyyy-MM-dd"
http:
endpoint: "https://opensearch:9200"
tls:
insecure_skip_verify: true
auth:
authenticator: basicauth/opensearch
pipelineExporters:
- opensearch
EOF
Verifying and rolling back
Verify the control-plane migration with the checks in step 7, and verify the remaining planes as described in the standard process.
For rollback: the control plane has version-specific caveats — see Rolling back the control plane. Roll back the other planes with the standard rollback steps.