On Managed Kubernetes
Try OpenChoreo on managed Kubernetes services (GKE, EKS, AKS, etc.) with automatic TLS certificates. This guide uses nip.io for free wildcard DNS based on your LoadBalancer IP.
What you'll get:
- OpenChoreo with real TLS certificates (Let's Encrypt)
- Single cluster deployment
- Access via
*.nip.iodomains - ~30 minutes to complete
Prerequisitesβ
- Kubernetes 1.32+ cluster with at least 3 nodes (4 CPU, 8GB RAM each)
- LoadBalancer support (cloud provider or MetalLB)
- Public IP accessible from the internet (for Let's Encrypt HTTP-01 validation)
- kubectl v1.32+ configured to access your cluster
- Helm v3.12+
- cert-manager installed in your cluster
kubectl version
helm version --short
kubectl get nodes
kubectl auth can-i '*' '*' --all-namespaces
Install cert-manager
helm upgrade --install cert-manager oci://quay.io/jetstack/charts/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true
Wait for cert-manager to be ready:
kubectl wait --for=condition=available deployment/cert-manager -n cert-manager --timeout=120s
Step 1: Setup Control Planeβ
Deploy the Control Plane Chartβ
helm upgrade --install openchoreo-control-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-control-plane \
--version 0.8.0 \
--namespace openchoreo-control-plane \
--create-namespace \
--set global.baseDomain=placeholder.nip.io \
--set thunder.configuration.server.publicUrl=http://thunder.openchoreo.placeholder.nip.io \
--set thunder.configuration.gateClient.hostname=thunder.openchoreo.placeholder.nip.io
Get LoadBalancer IP
- Standard (GKE, AKS, etc.)
- AWS EKS
Wait for LoadBalancer to get an external IP (press Ctrl+C once EXTERNAL-IP appears):
kubectl get svc openchoreo-traefik -n openchoreo-control-plane -w
Get the IP address:
CP_LB_IP=$(kubectl get svc openchoreo-traefik -n openchoreo-control-plane -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
EKS LoadBalancers are private by default and return a hostname instead of an IP.
Make the LoadBalancer internet-facing:
kubectl patch svc openchoreo-traefik -n openchoreo-control-plane \
-p '{"metadata":{"annotations":{"service.beta.kubernetes.io/aws-load-balancer-scheme":"internet-facing"}}}'
Wait for the new LoadBalancer to be provisioned (this may take 1-2 minutes). Press Ctrl+C once EXTERNAL-IP (hostname) appears:
kubectl get svc openchoreo-traefik -n openchoreo-control-plane -w
Get the hostname and resolve to IP:
CP_LB_HOSTNAME=$(kubectl get svc openchoreo-traefik -n openchoreo-control-plane -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
CP_LB_IP=$(dig +short $CP_LB_HOSTNAME | head -1)
Configure Domain and TLS
Set domain variable (converts IP to nip.io format):
export CP_DOMAIN="openchoreo.${CP_LB_IP//./-}.nip.io"
echo "Control Plane Domain: $CP_DOMAIN"
Create Issuer and Certificate:
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-http01
namespace: openchoreo-control-plane
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: tryout@openchoreo.dev
privateKeySecretRef:
name: letsencrypt-http01
solvers:
- http01:
ingress:
ingressClassName: openchoreo-traefik
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: control-plane-tls
namespace: openchoreo-control-plane
spec:
secretName: control-plane-tls
issuerRef:
name: letsencrypt-http01
kind: Issuer
dnsNames:
- "${CP_DOMAIN}"
- "api.${CP_DOMAIN}"
- "thunder.${CP_DOMAIN}"
EOF
Wait until READY shows True (may take 1-2 minutes):
kubectl get certificate control-plane-tls -n openchoreo-control-plane -w
Upgrade with TLS
helm upgrade --install openchoreo-control-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-control-plane \
--version 0.8.0 \
--namespace openchoreo-control-plane \
--reuse-values \
--set global.baseDomain=${CP_DOMAIN} \
--set global.tls.enabled=true \
--set global.tls.secretName=control-plane-tls \
--set thunder.configuration.server.publicUrl=https://thunder.${CP_DOMAIN} \
--set thunder.configuration.gateClient.hostname=thunder.${CP_DOMAIN} \
--set thunder.configuration.gateClient.port=443 \
--set thunder.configuration.gateClient.scheme="https"
Step 2: Setup Data Planeβ
helm upgrade --install openchoreo-data-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-data-plane \
--version 0.8.0 \
--namespace openchoreo-data-plane \
--set gateway.httpPort=19080 \
--set gateway.httpsPort=19443 \
--create-namespace
Get Gateway LoadBalancer IP
- Standard (GKE, AKS, etc.)
- AWS EKS
Wait for LoadBalancer to get an external IP (press Ctrl+C once EXTERNAL-IP appears):
kubectl get svc gateway-default -n openchoreo-data-plane -w
Get the IP address:
DP_LB_IP=$(kubectl get svc gateway-default -n openchoreo-data-plane -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
Make the LoadBalancer internet-facing:
kubectl patch svc gateway-default -n openchoreo-data-plane \
-p '{"metadata":{"annotations":{"service.beta.kubernetes.io/aws-load-balancer-scheme":"internet-facing"}}}'
Wait for the new LoadBalancer to be provisioned (this may take 1-2 minutes). Press Ctrl+C once EXTERNAL-IP (hostname) appears:
kubectl get svc gateway-default -n openchoreo-data-plane -w
Get the hostname and resolve to IP:
DP_LB_HOSTNAME=$(kubectl get svc gateway-default -n openchoreo-data-plane -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
DP_LB_IP=$(dig +short $DP_LB_HOSTNAME | head -1)
Configure Domain
export DP_DOMAIN="apps.openchoreo.${DP_LB_IP//./-}.nip.io"
echo "Data Plane Domain: $DP_DOMAIN"
Configure TLS
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: openchoreo-gateway-tls
namespace: openchoreo-data-plane
spec:
secretName: openchoreo-gateway-tls
issuerRef:
name: openchoreo-selfsigned-issuer
kind: ClusterIssuer
dnsNames:
- "${DP_DOMAIN}"
EOF
The data plane gateway requires a wildcard hostname (*.apps.openchoreo...nip.io) since each deployed component gets its own subdomain. HTTP-01 validation cannot issue wildcard certificates. The helm chart uses a self-signed certificate by default, which is sufficient for this tryout. For production with trusted certificates, configure DNS-01 validation with your DNS provider.
Upgrade with Domain
helm upgrade --install openchoreo-data-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-data-plane \
--version 0.8.0 \
--namespace openchoreo-data-plane \
--reuse-values \
--set gateway.tls.hostname=${DP_DOMAIN}
Register
CA_CERT=$(kubectl get secret cluster-agent-tls -n openchoreo-data-plane -o jsonpath='{.data.ca\.crt}' | base64 -d)
kubectl apply -f - <<EOF
apiVersion: openchoreo.dev/v1alpha1
kind: DataPlane
metadata:
name: default
namespace: default
spec:
agent:
enabled: true
clientCA:
value: |
$(echo "$CA_CERT" | sed 's/^/ /')
gateway:
organizationVirtualHost: "openchoreoapis.internal"
publicVirtualHost: "${DP_DOMAIN}"
secretStoreRef:
name: default
EOF
Verify:
kubectl get dataplane -n default
kubectl get pods -n openchoreo-data-plane
Step 3: Setup Build Plane (Optional)β
Create Namespace
kubectl create namespace openchoreo-build-plane --dry-run=client -o yaml | kubectl apply -f -
Configure TLS
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: letsencrypt-http01
namespace: openchoreo-build-plane
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: tryout@openchoreo.dev
privateKeySecretRef:
name: letsencrypt-http01
solvers:
- http01:
ingress:
ingressClassName: openchoreo-traefik
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: registry-tls
namespace: openchoreo-build-plane
spec:
secretName: registry-tls
issuerRef:
name: letsencrypt-http01
kind: Issuer
dnsNames:
- "registry.${CP_DOMAIN}"
EOF
Wait for certificate:
kubectl get certificate registry-tls -n openchoreo-build-plane -w
Install
helm upgrade --install openchoreo-build-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-build-plane \
--version 0.8.0 \
--namespace openchoreo-build-plane \
--create-namespace \
--set clusterAgent.enabled=true \
--set global.baseDomain=${CP_DOMAIN} \
--set global.tls.enabled=true \
--set global.tls.secretName=registry-tls \
--set external-secrets.enabled=false
Register
BP_CA_CERT=$(kubectl get secret cluster-agent-tls -n openchoreo-build-plane -o jsonpath='{.data.ca\.crt}' | base64 -d)
kubectl apply -f - <<EOF
apiVersion: openchoreo.dev/v1alpha1
kind: BuildPlane
metadata:
name: default
namespace: default
spec:
agent:
enabled: true
clientCA:
value: |
$(echo "$BP_CA_CERT" | sed 's/^/ /')
EOF
Verify:
kubectl get buildplane -n default
kubectl get pods -n openchoreo-build-plane
Step 4: Setup Observability Plane (Optional)β
helm upgrade --install openchoreo-observability-plane oci://ghcr.io/openchoreo/helm-charts/openchoreo-observability-plane \
--version 0.8.0 \
--namespace openchoreo-observability-plane \
--create-namespace \
--set openSearch.enabled=true \
--set openSearchCluster.enabled=false \
--set external-secrets.enabled=false \
--set clusterAgent.enabled=true \
--timeout 10m
Register with the control plane:
OP_CA_CERT=$(kubectl get secret cluster-agent-tls -n openchoreo-observability-plane -o jsonpath='{.data.ca\.crt}' | base64 -d)
kubectl apply -f - <<EOF
apiVersion: openchoreo.dev/v1alpha1
kind: ObservabilityPlane
metadata:
name: default
namespace: default
spec:
agent:
enabled: true
clientCA:
value: |
$(echo "$OP_CA_CERT" | sed 's/^/ /')
observerURL: http://observer.openchoreo-observability-plane.svc.cluster.local:8080
EOF
Link the Data Plane (and Build Plane if installed) to use observability:
kubectl patch dataplane default -n default --type merge -p '{"spec":{"observabilityPlaneRef":"default"}}'
kubectl patch buildplane default -n default --type merge -p '{"spec":{"observabilityPlaneRef":"default"}}'
Verify:
kubectl get observabilityplane -n default
kubectl logs -n openchoreo-observability-plane -l app=cluster-agent --tail=10
Access OpenChoreoβ
| Service | URL |
|---|---|
| Console | https://${CP_DOMAIN} |
| API | https://api.${CP_DOMAIN} |
| Deployed Apps | https://<component>-<env>.${DP_DOMAIN} |
| Registry | https://registry.${CP_DOMAIN} (if Build Plane installed) |
Default credentials: admin@openchoreo.dev / Admin@123
The Control Plane and Data Plane have separate LoadBalancer IPs. nip.io resolves based on the IP in the hostname, so each service uses its corresponding IP.
Next Stepsβ
- Deploy your first component
- When ready for production, see Deployment Planning
Cleanupβ
Delete plane registrations:
kubectl delete dataplane default -n default 2>/dev/null
kubectl delete buildplane default -n default 2>/dev/null
kubectl delete observabilityplane default -n default 2>/dev/null
Uninstall OpenChoreo components:
helm uninstall openchoreo-observability-plane -n openchoreo-observability-plane 2>/dev/null
helm uninstall openchoreo-build-plane -n openchoreo-build-plane 2>/dev/null
helm uninstall openchoreo-data-plane -n openchoreo-data-plane
helm uninstall openchoreo-control-plane -n openchoreo-control-plane
helm uninstall cert-manager -n cert-manager
Delete namespaces:
kubectl delete namespace openchoreo-control-plane openchoreo-data-plane openchoreo-build-plane openchoreo-observability-plane cert-manager 2>/dev/null
Delete CRDs:
kubectl get crd -o name | grep -E '\.openchoreo\.dev$' | xargs -r kubectl delete
kubectl get crd -o name | grep -E '\.cert-manager\.io$' | xargs -r kubectl delete
Troubleshootingβ
Certificate not issuingβ
kubectl describe certificate control-plane-tls -n openchoreo-control-plane
kubectl get challenges -A
kubectl describe issuer letsencrypt-http01 -n openchoreo-control-plane
Common issues:
- LoadBalancer not publicly accessible (HTTP-01 validation requires public access)
- Firewall blocking port 80
- Rate limits (Let's Encrypt has rate limits)
Agent not connectingβ
kubectl logs -n openchoreo-data-plane -l app=cluster-agent --tail=20
kubectl logs -n openchoreo-control-plane -l app=cluster-gateway --tail=20
Look for "connected to control plane" messages.
Wildcard certificates with HTTP-01 validationβ
HTTP-01 validation cannot be used for wildcard certificates (*.domain.com). The data plane gateway uses a wildcard hostname by default.
Options:
- Self-signed certificates: Use the default self-signed issuer (no public validation needed)
- DNS-01 validation: Configure a DNS provider for Let's Encrypt DNS-01 validation
- Non-wildcard hostname: Configure a specific hostname instead of wildcard