containerd TLS: Choosing a Trusted Chain or Temporary Verification Bypass
ENGINEERING NOTES
Published / DavidHLPL
This page answers one decision: when a private registry reports an x509 pull failure, should the node and containerd trust a CA, or should verification be skipped only long enough to isolate the fault? It separates containerd configuration, the node OS trust store, Kubernetes Secret/ServiceAccount credentials, and cached image state so that failures from different layers are not conflated.
Core mechanism
1. Pull path and ownership boundaries
Pod → kubelet → imagePullSecrets/ServiceAccount → CRI → containerd
→ config_path in config.toml → certs.d/<registry>/hosts.toml
→ Registry TLS → manifest/layer
| Observation | First layer | Reusable interpretation |
|---|---|---|
x509: certificate signed by unknown authority |
Registry TLS / containerd | Check the certificate chain, SAN, config_path, and the node’s ca.crt. |
certificate is valid for xxx, not yyy |
Registry address | The image address must match the certificate SAN; do not substitute an uncovered IP. |
TLS succeeds but unauthorized remains |
Kubernetes credentials | Inspect Pod imagePullSecrets and the ServiceAccount inherited when none is explicit. |
crictl pull succeeds but the Pod fails |
Kubernetes or cache | Check Secret, ServiceAccount, namespace, image name, and stale layers before changing CA settings. |
2. The two TLS choices
| Choice | Minimal setting | Decision | Cost |
|---|---|---|---|
| Trusted CA (production path) | hosts.toml uses ca = "ca.crt" |
The private registry must run long term and the node must authenticate its server | Every Worker node that pulls images needs the certificate and configuration. |
| Temporary bypass | skip_verify = true |
Test, demo, or temporary internal registry; useful to prove the fault is certificate verification | It bypasses certificate-origin checks and carries man-in-the-middle risk; it is not a production default. |
The skip_verify switch changes only TLS verification. It does not repair a wrong hostname, port, credential, DNS path, or proxy path. The minimal production fragment is:
server = "https://harbor.example.com"
[host."https://harbor.example.com"]
capabilities = ["pull", "resolve", "push"]
ca = "ca.crt"
For a temporary diagnostic, replace only the last setting with skip_verify = true. With a port, harbor.example.com:8443 must appear both in the registry address and in the certs.d directory name.
3. containerd, node CA, Kubernetes credentials, and cache are different
- containerd layer: newer configuration reads
hosts.tomlunder/etc/containerd/certs.d. The source examples useversion = 2andio.containerd.grpc.v1.crifor containerd 1.x, andversion = 3withio.containerd.cri.v1.imagesfor 2.x. This is version-sensitive; confirm withcontainerd --version. - Node OS CA layer: placing
ca.crtin the Debian/Ubuntu, RHEL-family, or Arch trust location mainly letscurl,openssl, and other system services trust it. It does not replace a working containerdconfig_pathandhosts.toml. - Kubernetes credential layer:
imagePullSecretsor a ServiceAccount solves registry authentication. If TLS passes but the result isunauthorized, rebuild or bind the correctdocker-registrySecret instead of changing TLS. - Cache layer: a failed image or stale layer on a node can make a direct pull and a Pod appear inconsistent. Cache removal is a verification step, not a trust-chain fix.
Applicable conditions
- Use a trusted CA for a production registry; keep
hosts.toml,ca.crt, andconfig.tomlconsistent on every Worker that can pull images. - Use
skip_verifyonly to isolate a test or demonstration failure, record that it is temporary, and return toca = "ca.crt"afterward. - Preserve the exact path for a port-bearing registry, for example
/etc/containerd/certs.d/harbor.example.com:8443/. - Decide the Kubernetes layer only after
crictl pullsucceeds. If it fails, return to TLS, CA, hosts, DNS, proxy, or network checks. - Prefer
certs.d/hosts.tomlover the olderregistry.configsexamples for newer releases; compatibility of the old form depends on the unconfirmed version.
Not applicable and risks
- Do not leave
skip_verify = truein a production cluster: it accepts a server without authenticating its certificate origin. - Do not treat “the OS CA was updated” as “containerd has reloaded it”; confirm the correct
config_pathand restart the service. - Do not replace CA files in response to
unauthorized; that is a Secret, ServiceAccount, or registry-account boundary. - Do not delete
/root/.docker/config.jsonor/var/lib/kubelet/config.jsonwithout checking the environment. The raw source lists them only as possible historical residue. - Like the source’s
EXPLAINexamples,containerd 1.x/2.xplugin paths and distribution-specific CA commands have version/platform conditions; do not extrapolate to unverified environments.
Minimal verification
Keep only checks that distinguish the layers:
containerd --version
sudo systemctl status containerd --no-pager
sudo crictl pull harbor.example.com/project/app:v1
- An
x509result fromcrictl pullpoints back tohosts.toml, CA, SAN, and the port directory; inspect withsudo journalctl -u containerd -n 200 --no-pager. - An authentication result points to the Pod’s ServiceAccount and Secret:
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.serviceAccountName}'
kubectl get sa <service-account> -n <namespace> -o yaml
- Verify the CA’s subject, issuer, and dates with:
openssl x509 -in /etc/containerd/certs.d/harbor.example.com/ca.crt -noout -subject -issuer -dates
- If
crictl pullsucceeds while the Pod fails, inspectkubectl describe podEvents and, only if needed, usecrictl images/crictl rmito rule out stale cache.
Evidence and uncertainty
- Source facts:
legacy-containerd-tls-troubleshootingrecords the x509/SAN/port/credential/cache symptoms, containerd 1.x/2.x examples, bothcerts.dTLS choices, ServiceAccount/Secret checks, and thecrictl pullsplit. - Synthesis in this page: the source is compressed into “classify the failing layer, then choose a CA or a temporary bypass”; “OS CA is not containerd configuration” is an explicit boundary derived from that chain.
- Unconfirmed: the current cluster’s exact containerd version, complete Registry CA, certificate SAN, node proxy/NO_PROXY, Secret bindings, and cache state still require live checks.
Related pages
Sources and related documents
Research notes
- Original document: src/content/note/en/containerd-tls-troubleshooting.md
- Source revision: f49953fb0d4bd52a39bf81167b0c4b7dd7d78a64
- Evidence status: active