Inspecting a deployment
Pod status
kubectl get pods
kubectl get pods -l component=worker # all worker instances
kubectl describe pod <pod> # events, probe failures, scheduling problems
The Events section at the end of describe is usually where the answer is: image pull failures, unschedulable resource requests, and probe failures all appear there.
Logs
# Follow a service
kubectl logs -f deploy/control-center
kubectl logs -f deploy/identity-service
# A specific StatefulSet replica
kubectl logs -f file-storage-0
kubectl logs -f ometascan-0
# The Control Center's installer initContainer
kubectl logs deploy/control-center -c installers
# The previous container, after a crash loop
kubectl logs ometascan-0 --previous
# A pre-upgrade Job
kubectl logs job/ometascan-pre-upgrade
Run a health check by hand
Every service container carries the same health check script that its probes use. Running it directly shows you why a probe is failing:
kubectl exec ometascan-0 -- sh -c '${WORK_DIR}/healthcheck.sh readiness'
kubectl exec ometascan-0 -- sh -c '${WORK_DIR}/healthcheck.sh liveness'
kubectl exec deploy/control-center -- sh -c '${WORK_DIR}/healthcheck.sh readiness'
Readiness detail
The MetaDefender Cluster Control Center reports per-component readiness:
kubectl port-forward deploy/control-center 8892:8892
curl -s 'http://127.0.0.1:8892/readyz?verbose=true' | jq
This names the component that is not ready — database, cache, broker or MetaDefender Cluster Identity Service — which is faster than reading the log.
Configuration actually in effect
kubectl get configmap mdcluster-config -o yaml
kubectl get secret mdcluster-secrets -o jsonpath='{.data}' | jq 'keys' # key names only
helm get values services
helm get values instances
helm get values shows what the release really holds, which is not always what your values file says — particularly after an upgrade with --reuse-values.
Common problems
Symptom | Check |
|---|
Pods stuck in ImagePullBackOff | DOCKER_REPO and MDCLS_VERSION resolve to a tag that exists, and the pull secret named in imagePullSecrets exists in this namespace.
|
MetaDefender Cluster Control Center never becomes ready | Work through its pre-start dependencies in order, all visible in the container log: the MetaDefender Cluster Control Center database port → database creation and migration (logged, but its failure does not stop the container) → the MetaDefender Cluster Identity Service port → every MetaDefender Cluster File Storage endpoint. After the service starts, GET /readyz?verbose=true names the component still failing — datalake and warehouse problems surface here, not in the pre-start checks. Verify CONTROL_CENTER_DB_HOST, DATALAKE_SERVICES, WAREHOUSE_SERVICES, the credentials, and that the role can create databases. Raise env.GLOBAL_WAIT_TIMEOUT on slow clusters — but note it also lengthens MetaDefender Cluster Worker shutdown, which shares the same bound. |
MetaDefender Cluster Control Center restarts, and one MetaDefender Cluster File Storage pod is missing | The MetaDefender Cluster Control Center waits for every endpoint in its list before it starts. One MetaDefender Cluster File Storage replica that never comes up keeps the MetaDefender Cluster Control Center in a restart loop rather than merely reducing capacity. |
MetaDefender Cluster Control Center crash-loops immediately | CONTROL_CENTER_ENCRYPTION_KEY or ADMIN_APIKEY is empty. The container refuses to start with either unset.
|
MetaDefender Cluster Control Center CrashLoopBackOff with Failed to encrypt configuration | CONTROL_CENTER_ENCRYPTION_KEY is not exactly 32 characters. The length is never validated, so the container launches, then fails to encrypt every service registration — no service registers, /readyz never returns 200, and the container is killed after GLOBAL_WAIT_TIMEOUT. kubectl logs deploy/control-center | grep -i encrypt.
|
MetaDefender Cluster Workers CrashLoopBackOff right after install | MetaDefender Cluster Workers depend on the MetaDefender Cluster Control Center. Confirm mdcluster-config and mdcluster-secrets exist — installing md-cluster-instances without md-cluster-services leaves them missing — and that the MetaDefender Cluster Control Center is ready. |
MetaDefender Cluster Worker pod Running but never Ready | Readiness covers the agent and the deployed instance: the agent's own /readyz on 8893, an installed instance directory on disk, then the instance's /readyz. Run the check by hand to see which of the three fails. For ometascan, engine initialisation legitimately takes several minutes on first start. |
MetaDefender Cluster Worker instance never deploys | The MetaDefender Cluster Control Center must hold an installer of this instance type and list this MetaDefender Cluster Worker as eligible to deploy it (a version-compatibility check). The MetaDefender Cluster Worker retries the installer search only three times, five seconds apart, then unregisters itself and restarts — so a slow installer upload appears as a MetaDefender Cluster Worker crash loop, not a wait. Check the installers initContainer log, confirm the MetaDefender Cluster Control Center finished its startup sequence, and confirm the installer version supports the MetaDefender Cluster Worker version. |
MetaDefender Cluster Worker pod is Ready but scans fail as unlicensed | Liveness only fails on failed and disconnected, so an unlicensed worker is never restarted or flagged. kubectl logs <pod> | grep -i licen and check activation slots. |
Cannot reach the MetaDefender Cluster Control Center console | The NodePort is assigned by Kubernetes unless you set control-center.service.nodePort. Run kubectl get svc control-center to see the actual port, use kubectl port-forward, or switch the Service to LoadBalancer. |
MetaDefender Cluster File Storage data lost on restart | file-storage.persistence.enabled must be true. The default emptyDir is deleted with the pod.
|
MetaDefender Cluster Control Center CrashLoopBackOff roughly every GLOBAL_WAIT_TIMEOUT seconds | env.FILE_STORAGE_MAX_REPLICA exceeds the number of reachable MetaDefender Cluster File Storage instances. Log line: Max replica must be less than the number of instances. It must be ≤ file-storage.replicas.
|
MetaDefender Cluster Control Center dies seconds after becoming ready | env.FILE_STORAGE_MIN_REPLICA is greater than env.FILE_STORAGE_MAX_REPLICA. Rejected immediately with no retry. Log line: Min replica must be less than or equal to max replica.
|
MetaDefender Cluster File Storage scaled, but new capacity not used | Scaling must go through helm upgrade, not kubectl scale — the MetaDefender Cluster Control Center's endpoint list is built at render time. All new pods must be Running and registered before the capacity activates. See Scaling. |
PVC stuck Pending | No default StorageClass, or the named class cannot provision the requested size. kubectl get storageclass and kubectl describe pvc <name>. |
Pod stuck Pending with no PVC involved | No node satisfies the resource requests. ephemeral-storage is the usual culprit. kubectl describe pod <pod>. |
MetaDefender Cluster Worker pod evicted mid-scan | It exceeded its ephemeral-storage limit. Raise it, or set one if absent. See Resource requirements. |
Redis READONLY errors | MetaDefender Cluster cannot use a Sentinel endpoint, so writes land on replicas. Front Redis with a single writable endpoint — the redis-ha chart's own HAProxy (haproxy.enabled=true in that chart's values, not in override-values.yaml), or a managed Redis. See Redis. |
Authentication failures against external infrastructure | Every *_DB_PASSWORD must equal the database role's password, and RABBITMQ_PASSWORD must equal the broker chart's password. See High availability |
Instances unlicensed after a rollout or scale-out | Not enough activation slots, or activations stranded by MetaDefender Cluster Workers that did not shut down gracefully. See Licensing in Kubernetes. |
helm upgrade fails on a pre-upgrade Job
| The Jobs have backoffLimit: 0, so one failure fails the upgrade. Read kubectl logs job/<instance>-pre-upgrade before retrying. |
MetaDefender Cluster Worker killed before unregistering | terminationGracePeriodSeconds expired during shutdown. It must exceed isolate.timeout plus GLOBAL_WAIT_TIMEOUT, since the undeploy step is bounded separately. See Graceful shutdown.
|
Diagnosing specific areas
Database connectivity
The MetaDefender Cluster Control Center creates four databases on first start. Confirm they exist:
kubectl exec deploy/postgres -- psql -U <db-user> -d postgres -c '\l'
Expect md_cluster_control_center, md_cluster_datalake, md_cluster_datawarehouse and md_cluster_identity_service.
If they are missing, the database role most likely cannot create databases. With the bundled PostgreSQL this happens when the four credential pairs in secrets: disagree — the bundled instance takes its superuser credentials from CONTROL_CENTER_DB_USER and CONTROL_CENTER_DB_PASSWORD, so the MetaDefender Cluster Identity, datalake and warehouse users must match. With an external database, the role needs createdb.
Check connection exhaustion when running many replicas:
kubectl exec deploy/postgres -- psql -U <db-user> -d postgres -c \
'SELECT count(*), setting FROM pg_stat_activity, pg_settings WHERE name = $$max_connections$$ GROUP BY setting;'
Raise postgres.maxConnections if you are near the limit.
Service discovery
Verify a name resolves from inside a pod:
kubectl exec deploy/control-center -- getent hosts identity-service
kubectl exec deploy/control-center -- getent hosts file-storage-0.file-storage
If components live in different namespaces, endpoints must be fully qualified as <service>.<namespace>.svc.cluster.local.
kubectl get pvc
kubectl exec file-storage-0 -- df -h /app/storage
kubectl exec deploy/postgres -- df -h /var/lib/postgresql/data
Permission errors writing to /app/storage normally mean the volume does not honour the pod's fsGroup. This is the expected failure mode with NFS, which is not supported — see Storage configuration.
Collecting information for support
# Everything in the namespace
kubectl get all -o wide > cluster-state.txt
kubectl describe pods > pod-details.txt
# Effective configuration (Secret values are not included)
kubectl get configmap mdcluster-config -o yaml > config.yaml
helm get values services > values-services.yaml
helm get values instances > values-instances.yaml
# Logs
for p in $(kubectl get pods -o name); do
kubectl logs "$p" --all-containers --tail=5000 > "logs-${p#pod/}.txt"
done
Include your MetaDefender Cluster version (MDCLS_VERSION), your Kubernetes version (kubectl version), and whether you use the bundled or external infrastructure.
Review the files before sharing them — logs and ConfigMap contents may include hostnames and account names from your environment.