エラーの概要
プライベートレジストリからイメージをプル、Pod内でAPIコール、またはWebhookの設定をしているときに、このエラーが発生します:
x509: certificate signed by unknown authority
より詳しいコンテキストが表示される場合もあります:
Error response from daemon: Get "https://registry.internal/v2/": x509: certificate signed by unknown authority
Post "https://webhook.internal/validate": x509: certificate signed by unknown authority
根本的な原因は常に同じです:GoのTLSスタック(Docker、kubectl、kubelet — すべてGoで書かれています)が証明書チェーンを検証できないのです。証明書に署名したCAがシステムのトラストストアに含まれていないだけです。それだけです。
診断
設定を変更する前に、サーバーが実際に提供している証明書を確認してください:
# サーバーが提供している証明書を確認する
openssl s_client -connect registry.internal:443 -showcerts </dev/null 2>&1 | openssl x509 -noout -issuer -subject
# またはcurlでフルチェーンを確認する
curl -v https://registry.internal/v2/ 2>&1 | grep -A5 "SSL connection"
CN=My Corp CA や CN=registry.internal のような発行者が見えますか?それはプライベートまたは自己署名証明書です。公的なCAによる署名がないため、自分でそのCAをトラストストアに追加する必要があります。
修正1:DockerデーモンにCA証明書を追加する(プライベートレジストリ)
自己署名またはプライベートCA証明書を使った内部レジストリからプルしていますか?この修正で対応できます。Dockerはシステムストアとは別に、レジストリごとの証明書ディレクトリを持っています。
# レジストリ用のディレクトリを作成する
sudo mkdir -p /etc/docker/certs.d/registry.internal:5000
# CA証明書をコピーする(インフラチームから入手するか、サーバーから抽出する)
sudo cp ca.crt /etc/docker/certs.d/registry.internal:5000/ca.crt
# Dockerを再起動する
sudo systemctl restart docker
ディレクトリ名は正確に一致している必要があります:/etc/docker/certs.d/<host>:<port>/ca.crt。ポート443の場合は、ポート番号を省略できます。
すぐに修正を確認してください:
docker pull registry.internal:5000/myimage:latest
修正2:システムトラストストアにCAを追加する(Linux)
kubelet、kubectlプラグイン、カスタムオペレーター — ホスト上のGoバイナリはDockerの証明書ディレクトリではなく、システムトラストストアを参照します。この修正はそこに適用します。
# Ubuntu/Debian
sudo cp ca.crt /usr/local/share/ca-certificates/my-corp-ca.crt
sudo update-ca-certificates
# RHEL/CentOS/Fedora
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/my-corp-ca.crt
sudo update-ca-trust extract
# 追加されたことを確認する
certutil -L -d /etc/pki/nssdb | grep "My Corp" # RHEL
# またはcurlでテストする
curl https://registry.internal/v2/
修正3:Kubernetes — containerdにCAを追加する(ノードレベル)
Podがイメージをプルできない、またはkubeletがこのエラーをログに記録していますか?修正は各ノードのcontainerd設定に施します — Kubernetes自体ではありません。
# 各ノードでcontainerdの設定を編集する
sudo mkdir -p /etc/containerd/certs.d/registry.internal:5000
cat <<EOF | sudo tee /etc/containerd/certs.d/registry.internal:5000/hosts.toml
[host."https://registry.internal:5000"]
ca = "/etc/ssl/certs/my-corp-ca.crt"
EOF
# CA証明書をコピーする
sudo cp ca.crt /etc/ssl/certs/my-corp-ca.crt
# containerdを再起動する
sudo systemctl restart containerd
マネージドクラスター(EKS、GKE、AKS)では、ノードへのSSHが自由にできません。代わりにDaemonSetを使用するか、ノードプールのブートストラップスクリプトにCAを組み込んでください。
修正4:Kubernetes — ConfigMap経由でPodにCAを注入する
エラーが実行中のPod内で発生していますか?アプリが内部HTTPSサービスを呼び出し、GoのHTTPクライアントが証明書を拒否しているのです。CAをボリュームとしてマウントし、環境変数でGoに指定してください。
apiVersion: v1
kind: ConfigMap
metadata:
name: corp-ca-bundle
data:
ca.crt: |
-----BEGIN CERTIFICATE-----
MIIBxTCCAW+gAwIBAgIJA...
-----END CERTIFICATE-----
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
spec:
volumes:
- name: ca-bundle
configMap:
name: corp-ca-bundle
containers:
- name: myapp
image: myapp:latest
volumeMounts:
- name: ca-bundle
mountPath: /etc/ssl/certs/corp-ca.crt
subPath: ca.crt
env:
- name: SSL_CERT_FILE
value: /etc/ssl/certs/corp-ca.crt
SSL_CERT_FILE はデフォルトのCAバンドルパスを上書きする、Go固有の環境変数です。別のアプローチとして:デフォルトのバンドルディレクトリに証明書をマウントし、アプリ起動前にinitコンテナで update-ca-certificates を実行する方法もあります。
修正5:Kubernetesアドミッションwebhook
webhookが発火したときに kube-apiserver のログでこのエラーが出ていますか?MutatingWebhookConfiguration または ValidatingWebhookConfiguration の caBundle フィールドが間違っているか、完全に欠落しています。APIサーバーはそのバンドルを使ってwebhookサーバーの証明書を検証します。
# webhookが現在使用しているCAバンドルを確認する
kubectl get mutatingwebhookconfiguration my-webhook -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | base64 -d | openssl x509 -noout -text
# 正しいCAにパッチを当てる
CA_BUNDLE=$(cat ca.crt | base64 -w0)
kubectl patch mutatingwebhookconfiguration my-webhook \
--type='json' \
-p="[{'op': 'replace', 'path': '/webhooks/0/clientConfig/caBundle', 'value': '${CA_BUNDLE}'}]"
cert-managerを使っていますか?cert-manager.io/inject-ca-from アノテーションを追加すれば、ローテーションが自動的に処理されます — 手動でパッチを当てる必要はありません。
CA証明書の取得方法
手元にCA証明書ファイルがありませんか?サーバーから直接取得してください。最も安全なアプローチは、フルチェーンを保存してCAバンドルとして使用することです:
# フルチェーンを保存してCAバンドルとして使用する
openssl s_client -connect registry.internal:443 -showcerts </dev/null 2>&1 \
| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > chain.crt
ネット上でよく見かけるチェーン抽出のワンライナー(ネストされた openssl コールと awk のオフセットを使うもの)は壊れやすく、2つ以上の証明書チェーンでは動作しなくなります。スクリプトでは使わないでください。CA証明書はインフラチームに直接依頼しましょう — それが正しい対処法です。
確認方法
# Dockerレジストリ
docker pull registry.internal:5000/myimage:latest
# システムトラストストアでcurlを使う
curl https://registry.internal/v2/
# Pod内から
kubectl run -it --rm debug --image=alpine --restart=Never -- wget -O- https://internal-service.default.svc/health
# GoバイナリはSSL_CERT_FILEを参照する
SSL_CERT_FILE=/path/to/ca.crt your-go-binary
クイックリファレンス
- Dockerプルが失敗する →
/etc/docker/certs.d/<registry>/ca.crt - kubelet/containerdのプルが失敗する → ノード上のcontainerd
hosts.toml - Pod内のアプリが失敗する → ConfigMap経由でCAをマウント +
SSL_CERT_FILE - Webhookの検証が失敗する → webhook設定の
caBundleを修正する - CLIツールが失敗する → システムトラストストアにCAを追加 +
update-ca-certificates

