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:

Copy
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:

Copy
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:

Copy
helm history <release_name> -n <namespace>

Identify the revision number you want to return to, then roll back:

Copy
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:

Copy
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 READY column (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 STATUS column 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:

Copy
# 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.

Copy
# 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

Copy
# 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.

Copy
# 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):

Copy
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):

Copy
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:

Copy
kubectl -n <namespace> get svc

Look for a service named like <release_name>-zendphp-zendhq-<app_name>-zendhq, then forward its port:

Copy
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:

Copy
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:

Copy
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:

Copy
export KUBECONFIG=path/to/cluster.pem

Default namespace

Set your working namespace as the default context so you can drop -n <namespace>:

Copy
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:

  1. kubectl -n <namespace> get pods: Read the STATUS (e.g. ImagePullBackOff, CrashLoopBackOff, Init:).
  2. 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).
  3. 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:

Copy
# 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.