Helm and Kubernetes Operation and Troubleshooting
This section is the day-to-day operations reference for a deployed Zend Enterprise Web Platform. It covers installing and upgrading the chart, rolling back, inspecting Kubernetes resources, reading logs, changing secrets, restarting deployments, accessing services through port-forwarding, and a few quality-of-life shortcuts.
Throughout, <namespace> is the Kubernetes namespace, <release_name> is the Helm release name, and <app_name> is the identifier you gave an application in your values.
Most commands accept -n <namespace>. Setting a default namespace and exporting KUBECONFIG (see Simplify working with Helm and Kubernetes) lets you omit these arguments entirely.
Installing, upgrading, uninstalling, and getting status
Install or upgrade
A single command handles both install and upgrade. It installs the chart if the release doesn't exist yet, and creates a new revision if it does:
helm upgrade --install \
-n <namespace> --create-namespace \
--timeout 10m0s \
<release_name> \
oci://cr.zend.com/charts/platform/umbrella \
--version 1.2.3 \
-f first-override.values.yaml \
-f second-override.values.yaml # repeat -f as needed
You use this same command any time you deploy a change — a new image tag, updated environment values, a new application, and so on. Values files are merged left to right, with the last file winning on conflicts.
Bump the version in your Chart.yaml (when consuming the chart locally) before each upgrade, and commit your values override files and Chart.yaml. This makes it easy to see what changed in a revision, and therefore which revision to roll back to.
Get status
Ask Helm whether the release deployed successfully:
helm status <release_name> -n <namespace>
This is a quick health gate, it tells you whether the release is deployed, but little about individual pods. For real status, inspect the Kubernetes resources (see Get lists of resources).
Uninstall
Remove the entire release:
helm uninstall <release_name> -n <namespace>
Uninstalling often does not remove secrets or persistent volumes. Leftover secrets and PVCs can occasionally cause issues on a fresh re-install. If you intend a truly clean slate, review and delete leftover secrets and PVCs explicitly (see Get lists of resources).
Rolling back to a previous Helm revision
Helm tracks every deployment as a numbered revision. When a change breaks functionality, roll back to a known-good revision.
First, list the revision history:
helm history <release_name> -n <namespace>
Identify the revision number you want to return to, then roll back:
helm rollback <release_name> <revision> -n <namespace>
A rollback itself creates a new revision (recording the rollback), so you can always roll forward again.
Because every value change produces a new revision, committing your values files alongside a bumped Chart.yaml version makes the helm history output far easier to map back to "what changed."
Get lists of pods, services, deployments, persistent volumes, gateways, and more
The kubectl get family lists resources of a given type in the namespace:
kubectl -n <namespace> get pods # running pods (your app, ZendHQ, MySQL, etc.)
kubectl -n <namespace> get svc # services and the ports they expose
kubectl -n <namespace> get deployment # deployments (the scalable controllers)
kubectl -n <namespace> get pvc # persistent volume claims
kubectl -n <namespace> get pv # persistent volumes (cluster-scoped)
kubectl -n <namespace> get gateway # Gateway API gateways
A few notes on reading the output:
- Pods get randomized name suffixes and come and go; services keep a single stable name across the release lifecycle, which is how services address one another.
- The pod
READYcolumn (1/1,2/2, …) shows how many containers in the pod are ready. A web pod has both nginx and PHP-FPM containers; the MySQL pod has the database plus a sidecar. - The pod
STATUScolumn is your first diagnostic signal:
| Status | Meaning |
|---|---|
Init:<n>/<m>
|
Running init container n of m |
ContainerCreating
|
Pulling images / creating the container |
Running
|
Container started, no errors |
Completed
|
A Job finished successfully |
ImagePullBackOff
|
Cannot reach the registry, or a bad image tag |
CrashLoopBackOff
|
Container errors are causing repeated restarts |
Error
|
Errors preventing the pod from running |
Inspecting the Gateway
The gateway is what exposes your applications. To find it and see which hostnames and ports it listens on:
# Find the gateway name
kubectl -n <namespace> get gateway
# Inspect the hostname/port combinations it listens for
kubectl -n <namespace> get gateway <release_name>-gateway -o yaml | grep -A 7 "allowedRoutes"
Describe an individual Kubernetes resource
kubectl describe gives detailed information about a single resource, including events — which is where deployment problems surface.
# A pod, including init containers, containers, volume mounts, and lifecycle events
kubectl -n <namespace> describe pod <pod_name>
# A service, including cluster IPs and ports
kubectl -n <namespace> describe svc <service_name>
# A PVC or PV
kubectl -n <namespace> describe pvc <pvc_name>
kubectl -n <namespace> describe pv <pv_name>
You can describe a pod regardless of its status, which makes describe pod the go-to command when a pod fails to start. The events section reveals missing secrets, an unreachable registry, a non-existent image tag, a failed init container, an unbindable PVC, and similar problems.
Persistent Volumes are how you share data between containers and persist it between deployments. If data is mysteriously disappearing between upgrades, describe the PV to find its StorageClass, then describe that StorageClass to check its ReclaimPolicy.
Get logs for a service, pod, or container
# Logs for all pods/containers behind a service
kubectl -n <namespace> logs svc/<service_name>
# Logs for a pod's default container
kubectl -n <namespace> logs <pod_name>
# Logs for a specific container in a multi-container pod
kubectl -n <namespace> logs <pod_name> -c <container_name>
Because the zendphp-web pods run multiple containers, use -c to target one. For example, to read just the nginx logs:
kubectl -n <namespace> logs <pod_name> -c nginx
Two useful facts: logs are only available after the container is created (use describe pod before that), and <container_name> can be the name of an init container, letting you introspect exactly why initialization failed. Use describe pod to discover the container and init-container names available in a pod.
Changing secrets
Secrets are immutable once created and persist between chart revisions, so changing one is a delete-and-recreate operation followed by a restart of any consuming pods.
# 1. Delete the existing secret
kubectl -n <namespace> delete secret <secret-name>
# 2. Recreate it with the new values
kubectl -n <namespace> create secret generic <secret-name> \
--from-literal=<key>=<value> # repeat as needed
# 3. Restart the pods that consume it (see next section)
Pods do not pick up secret changes while running, so step 3 is required. See Required Platform Secrets for the specific secrets the platform expects.
Restarting a deployment
Restarting is how you pick up image changes and updated secrets. There are two approaches.
Rolling restart (preferred - zero downtime, best with autoscaling):
kubectl -n <namespace> rollout restart deployment/<deployment-name>
Kubernetes spins up new pods (which pick up the new secrets/image) and only then terminates the old ones. Find available deployments with kubectl -n <namespace> get deployment.
Delete the pod(s) (blunt, causes a brief gap for that pod):
kubectl -n <namespace> delete pod <pod_name> [<pod_name> ...]
Deleting a pod forces Kubernetes to redeploy it. You can pass multiple pod names to restart several replicas at once.
Accessing platform services via port-forward
Some services are not exposed through the gateway and must be reached with kubectl port-forward, which tunnels a port from the cluster to the machine running kubectl over the cluster's TLS-secured connection (closer to an SSH tunnel than a raw port forward and considered secure).
ZendHQ
ZendHQ is intentionally not exposed by the gateway. To reach its GUI, forward its websocket port (10091). First find the service:
kubectl -n <namespace> get svc
Look for a service named like <release_name>-zendphp-zendhq-<app_name>-zendhq, then forward its port:
kubectl -n <namespace> port-forward \
svc/<release_name>-zendphp-zendhq-<app_name>-zendhq \
10091:10091
The first 10091 is the local port; the second is the in-cluster port. If you need to expose several ZendHQ instances at once, vary the local port. You can then point your DevOps engineers or developers at that host and port. (See also Required Platform Secrets — ZendHQ.)
OpenSearch Dashboards
OpenSearch Dashboards can be reached either by port-forwarding its port (5601) or by exposing it through the gateway. To port-forward:
kubectl -n <namespace> port-forward <service_name> 5601:5601
Then browse to http://<hostname>:5601. See OpenSearch Dashboards Included with the Platform for both access methods and what the dashboards contain.
Opening a shell in a container
To run one-off scripts, enable debugging, perform backups, or seed data, open a shell in a container:
kubectl -n <namespace> exec -it <pod_name> -- /bin/sh
Use -c <container> to target a specific container in a multi-container pod. Common reasons to do this:
- MySQL: Perform backups, archive old data.
- OpenSearch: Perform backups, dump or archive data.
- OpenSearch Dashboards: Install pre-configured dashboards.
- PHP containers: Run one-off scripts, enable debugging.
Simplify working with Helm and Kubernetes
Passing --kubeconfig and --namespace to every command is tedious and error-prone. You could alias kubectl and helm, but that breaks shell autocompletion. Instead, set them as session/context defaults.
KUBECONFIG
Export the path to your cluster configuration so you can drop --kubeconfig from every helm and kubectl command:
export KUBECONFIG=path/to/cluster.pem
Default namespace
Set your working namespace as the default context so you can drop -n <namespace>:
kubectl config set-context --current --namespace=<namespace>
With both of these set in your shell session, you can run kubectl and helm without the --kubeconfig and -n/--namespace options while keeping full shell autocompletion.
Troubleshooting
"Helm chart is too large"
If Helm reports the chart exceeds your Kubernetes platform's size limits, there are likely extra files in the chart directory inflating its size, large files, zip or tarball archives, and the like. Move them out of the chart directory, or add them to the chart's .helmignore file, and try again.
Too many CLI arguments / typos
Mistyping --kubeconfig or -n on every command leads to errors. Use the KUBECONFIG export and default-namespace context described in Simplify working with Helm and Kubernetes to reduce both the typing and the error surface.
A pod won't start
Work through these in order:
kubectl -n <namespace> get pods: Read theSTATUS(e.g.ImagePullBackOff,CrashLoopBackOff,Init:).kubectl -n <namespace> describe pod <pod_name>: Read the events for the root cause (missing secret, bad image tag, unreachable registry, failed init container, unbindable PVC).kubectl -n <namespace> logs <pod_name> -c <container_name>: Read container (or init-container) logs once the container has been created.
Validating chart values before deploying
Two Helm utilities help you catch problems before they reach the cluster:
# Render the generated Kubernetes spec (fails if it cannot compile)
helm template <release_name> <chart> [flags]
# Lint the generated spec and warn about issues
helm lint <release_name> <chart> [flags]
helm template lets you introspect exactly what will be sent to Kubernetes, and helm lint flags likely problems. Run them with the same -f value files you'll deploy with.