'tls: certificate signed by unknown authority' を修正 — kubectl が API サーバーに接続できない

intermediate☸️ Kubernetes2026-04-23| Kubernetes 1.20 以降、kubectl(全バージョン)、Linux/macOS/Windows、kubeadm クラスター、EKS、GKE、オンプレミス

Error Message

Unable to connect to the server: tls: certificate signed by unknown authority
#tls#証明書#kubectl#kubeconfig#api-server#kubernetes

昨日まではクラスターが正常だった

いつも通り kubectl get pods を実行すると、こんなエラーが返ってきた:

Unable to connect to the server: tls: certificate signed by unknown authority

何も変えていないはずなのに。kubectlがAPIサーバーから提示された証明書を信頼できず、TLSハンドシェイクがクラスターデータを1バイトも返す前に失敗している。

原因は主に4つ考えられる: クラスターの再構築、証明書のローテーション、別マシンからkubeconfigをコピーした、またはAPIサーバーの証明書が夜間にひっそりと期限切れになっていた場合だ。

デバッグ: 問題のある証明書を特定する

ステップ1 — 実際に使用しているkubeconfigを確認する

kubectl config view --raw | grep server
echo $KUBECONFIG

KUBECONFIG環境変数が設定されていると、デフォルトの~/.kube/configより優先される。先に進む前に、正しいファイルを参照しているか確認しよう。

ステップ2 — TLS接続を直接テストする

# kubeconfigからAPIサーバーのアドレスを取得
kubectl config view --raw -o jsonpath='{.clusters[0].cluster.server}'

# opensslで直接確認
openssl s_client -connect <api-server-host>:6443 2>&1 | head -30

出力の証明書チェーンを確認する。証明書が自己署名かどうか、CN/SANフィールドの内容、正確な有効期限がわかる。

ステップ3 — 証明書の有効期限を確認する

# コントロールプレーンノード上で実行
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates

# または全証明書を一括確認(kubeadmクラスターの場合)
kubeadm certs check-expiration

Not Afterの日付が過去になっている? それが原因だ。

ステップ4 — kubeconfigに埋め込まれたCA証明書を確認する

# kubeconfigからCA証明書を抽出してデコード
kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' \
  | base64 -d | openssl x509 -noout -text | grep -A2 'Validity\|Issuer\|Subject'

ここに表示された発行者と、APIサーバーが実際に提示しているものを比較する。不一致があれば、kubeconfigが古くなっている証拠だ — クラスター側が更新されて、kubeconfigが取り残されている。

解決策 — 状況に合ったものを選ぶ

シナリオA: kubeconfigが古い(最もよくあるケース)

クラスターのCAまたはAPIサーバーの証明書がローテーションされたが、ローカルのkubeconfigには古いCAデータが残っている。

# コントロールプレーンノードへのSSHアクセスがある場合
scp root@<control-plane-ip>:/etc/kubernetes/admin.conf ~/.kube/config

# またはコントロールプレーン上で直接実行
cat /etc/kubernetes/admin.conf

新しいadmin.confを取得してローカルのkubeconfigと置き換える。このリストの中で最も手っ取り早い修正方法だ。

シナリオB: APIサーバーの証明書が期限切れ(kubeadm)

# 全証明書を更新
sudo kubeadm certs renew all

# 更新を確認
kubeadm certs check-expiration

# コントロールプレーンのコンポーネントを再起動して新しい証明書を反映
sudo systemctl restart kubelet

# kubeconfigを更新
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config

APIサーバーのスタティックPodは自動的に再起動される — 通常60秒以内。再起動しない場合は強制的に行う:

sudo crictl ps | grep kube-apiserver
# コンテナIDを確認してから:
sudo crictl stop <container-id>

シナリオC: 自己署名証明書が信頼されない(新規クラスター構築時)

クラスターへの初回接続で、CA証明書がkubeconfigに含まれていない場合。または生のエンドポイントURLだけを渡されて、CAバンドルが添付されていない場合。

# オプション1: CA証明書をkubeconfigに追加
kubectl config set-cluster <cluster-name> \
  --certificate-authority=/path/to/ca.crt \
  --embed-certs=true

# オプション2: insecure-skip-tls-verifyを使用(デバッグ時のみ・一時的な使用に限る)
kubectl --insecure-skip-tls-verify get nodes

insecure-skip-tls-verify: trueをkubeconfigに恒久的に残してはならない。このフラグは証明書の問題を解決する間、サーバーに到達できるか確認するためだけに使い、その後は必ず削除すること。

シナリオD: APIサーバーのSAN不一致

接続先のIPやホスト名が証明書のSubject Alternative Namesに記載されていない。APIサーバーの前にロードバランサーを追加した後や、コントロールプレーンノードのIPが変わった後に発生する。

# 現在の証明書がカバーするSANを確認
openssl s_client -connect <api-server-ip>:6443 2>/dev/null \
  | openssl x509 -noout -text | grep -A1 'Subject Alternative'

IPやホスト名が含まれていない? 正しいSANで証明書を再発行する。kubeadmの場合:

# 既存のapiserverの証明書を削除
sudo rm /etc/kubernetes/pki/apiserver.{crt,key}

# 新しいIP/ホスト名を追加して再生成
sudo kubeadm init phase certs apiserver \
  --apiserver-cert-extra-sans=<new-ip>,<new-hostname>

# kubeletを再起動
sudo systemctl restart kubelet

シナリオE: マネージドクラスター(EKS/GKE/AKS)— kubeconfigが古い

# EKS — kubeconfigを更新
aws eks update-kubeconfig --name <cluster-name> --region <region>

# GKE
gcloud container clusters get-credentials <cluster-name> --zone <zone>

# AKS
az aks get-credentials --resource-group <rg> --name <cluster-name>

マネージドクラスターは独自のスケジュールでCAデータをローテーションする。クラウドCLIから常に最新のkubeconfigを取得するようにしよう。マシン間でkubeconfigをコピーするのは、また同じ問題に戻る原因になる。

修正を確認する

# 基本的な接続確認
kubectl cluster-info

# TLSエラーなしでノード一覧が返ってくるはず
kubectl get nodes

# 証明書の有効性を再確認
kubeadm certs check-expiration  # kubeadmクラスターの場合

kubectl cluster-infoでAPIサーバーのURLとCoreDNSの行がTLS警告なしで表示されれば、修正完了だ。

再発防止のためにリマインダーを設定する

kubeadmのデフォルト証明書有効期間は1年——365日、時間単位で正確に。自動化がなければ、最悪のタイミングで来年また同じエラーが発生する。

# コントロールプレーンノードのcrontabに追加 — 毎月証明書チェックを実行
0 9 1 * * kubeadm certs check-expiration 2>&1 | mail -s "K8s cert check" ops@yourcompany.com

# または有効期限30日前に自動更新
0 9 1 * * bash -c 'kubeadm certs check-expiration 2>&1 | grep -q "<30d" && kubeadm certs renew all'

多くのチームは四半期ごとのメンテナンス作業としてkubeadm certs renew allを実行するだけで済ませている。それでも十分だ。証明書の期限切れで深夜2時に呼び出されるよりはるかにましだろう。

Related Error Notes