Skip to main content

Upgrade to UXP quickstart

This quickstart runs an open source Crossplane cluster on your laptop, upgrades it to Upbound Crossplane (UXP), and connects it to a Hub. It takes about 20 minutes and uses two local kind clusters that you delete when you're done.

You start with a Crossplane control plane running a real workload, so you can watch that workload come through the upgrade unchanged and then appear in the Hub Console.

To upgrade a control plane you already run, follow Upgrade Crossplane v2 to Upbound Crossplane instead.

Prerequisites​

Before you begin, make sure you have:

  • Docker
  • kind
  • kubectl
  • helm version v3.8.0 or later

Create a Crossplane cluster​

Create a kind cluster and install open source Crossplane. You'll install a configuration on this control plane to walk through the upgrade process.

kind create cluster --name uxp-upgrade-demo

When you create the cluster, your kubecontext switches to kind-uxp-upgrade-demo. Now, install Crossplane on your demo cluster.

helm repo update

helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system --create-namespace \
--version 2.3.4 --wait

Install a Configuration​

This demo uses configuration-app which offers an App API that deploys a Ghost blog as a Helm release.

kubectl apply -f - <<'EOF'
apiVersion: pkg.crossplane.io/v1
kind: Configuration
metadata:
name: configuration-app
spec:
package: xpkg.upbound.io/upbound/configuration-app:v2.1.0
EOF

Installing the Configuration also installs what it depends on, including provider-helm. Wait for both to become healthy:

kubectl wait --for=condition=healthy configuration/configuration-app --timeout=5m
kubectl wait --for=condition=healthy provider/upbound-provider-helm --timeout=5m

Configure the Helm provider​

provider-helm needs a ProviderConfig, permission to create resources, and the database credentials the Ghost chart reads.

kubectl apply -f - <<'EOF'
apiVersion: helm.m.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
namespace: default
spec:
credentials:
source: InjectedIdentity
EOF
kubectl create clusterrolebinding crossplane-provider-helm-cluster-admin \
--clusterrole=cluster-admin \
--group=system:serviceaccounts:crossplane-system

kubectl -n default create secret generic ghost-db \
--from-literal=host=mariadb.default.svc.cluster.local \
--from-literal=username=ghost \
--from-literal=password=ghost
warning

This grants cluster-admin to Crossplane's ServiceAccounts to keep the quickstart short. Scope the provider's permissions down in a real cluster.

Create a composite resource​

Create an App. Crossplane composes it into a Helm Release that deploys Ghost.

kubectl apply -f - <<'EOF'
apiVersion: platform.upbound.io/v1alpha1
kind: App
metadata:
name: quickstart
namespace: default
spec:
parameters:
providerConfigName: default
helm:
chart:
name: ghost
repo: "oci://registry-1.docker.io/bitnamicharts"
version: 25.0.4
wait: false
passwordSecretRef:
namespace: default
name: ghost-db
EOF

Record the state you're upgrading from​

helm list -n crossplane-system
kubectl get pkg
kubectl get app,releases.helm.m.crossplane.io -n default

helm list shows chart crossplane-2.3.4. The App and its Release both show SYNCED: True and READY: True. You compare against these same commands after the upgrade.

Upgrade to UXP​

UXP needs permission to create the Kubernetes resources its added controllers manage. Grant it before you upgrade.

kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: crossplane-clusteradmin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: crossplane
namespace: crossplane-system
EOF

UXP versions match the Crossplane version they build on, up to an -up.N suffix. This cluster runs Crossplane 2.3.4, so upgrade it to 2.3.4-up.2.

export UXP_VERSION=2.3.4-up.2

helm repo add upbound-stable https://charts.upbound.io/stable && helm repo update

helm upgrade --install crossplane --namespace crossplane-system \
upbound-stable/crossplane --version "${UXP_VERSION}" --wait

Confirm the workload survived​

helm list -n crossplane-system
kubectl get pkg
kubectl get app,releases.helm.m.crossplane.io -n default
kubectl get composite -A

helm list now shows chart crossplane-2.3.4-up.2. Every package is still INSTALLED: True and HEALTHY: True, and the same App and Release are still READY: True. The upgrade replaced the control plane underneath your resources without touching the resources themselves.

Create a Hub cluster​

The Hub gives you one API and Console for every control plane you run. Run one on a second kind cluster in demo mode, which bundles the Postgres, OIDC, and Gateway that a production install expects you to bring yourself.

Demo mode publishes the Hub gateway on node port 30443, so map it to 8443 on your machine when you create the cluster.

kind create cluster --name hub-quickstart --config - <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: hub-quickstart
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30443
hostPort: 8443
protocol: TCP
EOF

When your create this cluster your kubecontext switches to kind-hub-quickstart. The remaining commands name their context explicitly.

helm upgrade --install hub oci://xpkg.upbound.io/upbound/hub \
--kube-context kind-hub-quickstart \
--namespace hub --create-namespace --version 1.2.0 \
--set global.demo.enabled=true --wait
warning

The demo PostgreSQL writes to an emptyDir, so its pod losing its node loses your Hub data. Demo mode is for trying the Hub out. Don't run it in production.

Create a registration token​

Every control plane gets its own registration token. The connector presents that token the first time it contacts the Hub API, which tells the Hub which control plane the connector speaks for.

  1. Open the control planes page and sign in as admin with the password admin. The demo uses a self-signed certificate, so accept your browser's warning to continue.
  2. Register a control plane in the default realm and name it upgraded.
  3. Copy the registration token. The Hub displays it once.
  4. Save it for the next step:
export REG_TOKEN=<paste-registration-token-here>

Connect the upgraded control plane​

Install hub-connector on the UXP cluster. The connector reaches the Hub at https://hub.127.0.0.1.nip.io:8443, the same address as your browser, because the Hub's tokens name that address as their issuer. Inside the UXP cluster, that name resolves to the Pod itself, so route it to the Hub cluster's node over the kind Docker network.

important

These routing steps exist only because both clusters run on your laptop. A production install gives the Hub a hostname every cluster resolves.

Create a Service that forwards port 8443 to the Hub node's gateway port 30443:

HUB_NODE_IP=$(docker inspect hub-quickstart-control-plane \
--format '{{.NetworkSettings.Networks.kind.IPAddress}}')

kubectl --context kind-uxp-upgrade-demo create namespace hub

kubectl --context kind-uxp-upgrade-demo -n hub apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: hub-gateway
spec:
ports:
- name: https
port: 8443
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: hub-gateway
labels:
kubernetes.io/service-name: hub-gateway
addressType: IPv4
ports:
- name: https
port: 30443
endpoints:
- addresses:
- "$HUB_NODE_IP"
EOF

Add a CoreDNS rule that resolves the Hub hostname to that Service. This replaces kind's default CoreDNS configuration with the same configuration plus one rewrite line:

kubectl --context kind-uxp-upgrade-demo -n kube-system replace -f - <<'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
}
ready
rewrite name exact hub.127.0.0.1.nip.io hub-gateway.hub.svc.cluster.local answer auto
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30 {
disable success cluster.local
disable denial cluster.local
}
loop
reload
loadbalance
}
EOF

kubectl --context kind-uxp-upgrade-demo -n kube-system rollout restart deployment coredns
kubectl --context kind-uxp-upgrade-demo -n kube-system rollout status deployment coredns

The connector only talks to the Hub over HTTPS and verifies its certificate. Copy the demo gateway's self-signed certificate into the UXP cluster, store the registration token, and write the connector values:

kubectl --context kind-hub-quickstart -n hub get secret hub-demo-gateway-tls \
-o jsonpath='{.data.tls\.crt}' | base64 --decode > hub-ca.crt

kubectl --context kind-uxp-upgrade-demo -n hub create configmap hub-ca \
--from-file=ca.crt=hub-ca.crt

kubectl --context kind-uxp-upgrade-demo -n hub \
create secret generic hub-connector-credentials \
--from-literal=registrationToken="$REG_TOKEN"

cat > hub-connector-values.yaml <<'EOF'
connector:
hub:
url: https://hub.127.0.0.1.nip.io:8443
extraVolumes:
- name: hub-ca
configMap:
name: hub-ca
extraVolumeMounts:
- name: hub-ca
mountPath: /etc/hub-ca
readOnly: true
extraEnv:
- name: SSL_CERT_FILE
value: /etc/hub-ca/ca.crt
EOF

extraVolumes and extraEnv mount the certificate and point the connector's trust store at it. Install the connector:

helm install hub-connector oci://xpkg.upbound.io/upbound/hub-connector \
--kube-context kind-uxp-upgrade-demo \
--version 1.2.0 --namespace hub \
--values hub-connector-values.yaml \
--wait --timeout 3m

Confirm the connector is running:

kubectl --context kind-uxp-upgrade-demo -n hub get pods

See the upgraded control plane in the Hub​

The control planes page now lists upgraded with a Ready status.

Open the resources page and filter by that control plane. The App you created before the upgrade, the Release it composes, and the packages UXP now manages all appear there.

Clean up​

kind delete cluster --name uxp-upgrade-demo
kind delete cluster --name hub-quickstart

Next steps​