从零搭建一个单节点 K8S 可观测实验室(四):修复 kubectl exec 失败问题——一次 cri-dockerd Streaming API 排查
这篇文章并不在原计划中。
我原本正在写下一篇:
安装 Loki + Fluent Bit,构建 Kubernetes 日志采集链路
为了给 Nginx 制造一些访问日志,我临时创建了一个 curl Pod:
kubectl run curl \
-n production \
--image=curlimages/curl \
--rm -it \
-- curl nginx
没想到,命令等待一段时间后失败:
pod "curl" deleted from production namespace
error: timed out waiting for the condition
进一步测试发现,Pod 明明处于 Running,kubectl logs 也完全正常,但就是无法进入容器:
kubectl exec -it curl -n production -- sh
报错:
error sending request:
Post "//[::]:42367/cri/exec/...":
http: server gave HTTP response to HTTPS client
后续的 Loki、Tempo、OpenTelemetry 和故障注入实验都可能用到 kubectl exec。与其在每篇文章中绕过去,不如先把实验室的地基修好。
于是,原计划中的 Loki 篇暂时顺延,这篇意外出现的排障记录成为了系列第四篇。
一、奇怪的现象:Pod 正常,exec 却失败

当前实验环境为:
Ubuntu Server 24.04.4 LTS
Kubernetes 1.36.3
Docker 29.6.2
cri-dockerd 0.4.4
CNI Flannel
创建一个长时间运行的测试 Pod:
kubectl create namespace production \
--dry-run=client -o yaml | kubectl apply -f -
kubectl delete pod curl \
-n production \
--ignore-not-found
kubectl run curl \
-n production \
--image=curlimages/curl \
--restart=Never \
--command -- \
sleep 3600
Pod 状态正常:
kubectl get pod curl -n production
NAME READY STATUS RESTARTS
curl 1/1 Running 0
日志读取也正常:
kubectl logs deployment/nginx \
-n production
但执行:
kubectl exec -it curl \
-n production \
-- sh
仍然失败:
Post "//[::]:42367/cri/exec/...":
http: server gave HTTP response to HTTPS client
这说明问题并不在 Pod 本身。
二、为什么 logs 正常,exec 却失败
kubectl logs 和 kubectl exec 看起来相似,实际走的链路并不完全相同。
普通容器管理主要使用 CRI Runtime API:
kubelet
|
v
cri-dockerd
|
v
Docker Engine
而 exec、attach 和 port-forward 还需要使用 Streaming API:
kubectl exec
|
v
kube-apiserver
|
v
kubelet
|
v
CRI Streaming API
|
v
cri-dockerd
|
v
Container
当前现象可以概括为:
创建 Pod 正常
查看 Pod 正常
读取日志 正常
进入容器 失败

问题范围已经缩小到 Streaming 链路。
三、在社区中找到几乎一模一样的问题
继续搜索完整错误信息后,我找到了 cri-dockerd 官方仓库中的 Issue #569:
Streaming exec/attach fails with kubelet 1.36.x: relative BaseURL (no scheme) misinterpreted as HTTPS
这个 Issue 于 2026 年 7 月 26 日提交,使用的环境是:
cri-dockerd 0.4.4
Docker 29.6.2
Kubernetes 1.36.2
与我的环境几乎完全一致。
Issue 中的错误同样是:
Post "//127.0.0.1:随机端口/cri/attach/...":
http: server gave HTTP response to HTTPS client
最值得注意的是 URL:
//127.0.0.1:35417/cri/...
它只有主机和路径,却没有明确的协议:
http://
Issue 提交者分析认为,新版 kubelet 的代理处理把这个无 Scheme 的地址当成了 HTTPS,但 cri-dockerd 的 Streaming Server 实际使用的是普通 HTTP,最终造成协议不匹配。
四、根因就在一小段代码里

cri-dockerd 0.4.4 的 cmd/server.go 中,Streaming 配置如下:
// Initialize streaming configuration. (Not using TLS now)
streamingConfig := &streaming.Config{
// Use a relative redirect (no scheme or host).
BaseURL: &url.URL{Path: "/cri/"},
Addr: resolvedAddr,
}
代码已经明确说明:
Not using TLS now
但 BaseURL 只有:
Path: "/cri/"
没有设置:
Scheme
Host
因此 cri-dockerd 返回的地址类似:
//[::]:42367/cri/exec/...
Issue #569 给出的修复方案,是显式补上 http Scheme 和 Host。
修改后:
streamingConfig := &streaming.Config{
BaseURL: &url.URL{
Scheme: "http",
Host: resolvedAddr,
Path: "/cri/",
},
Addr: resolvedAddr,
}
代码改动很小,却正好对应错误中的协议错配。
五、准备编译环境
cri-dockerd 0.4.4 的 go.mod 要求 Go 1.26.0,而 Ubuntu 24.04 当前安装的是 Go 1.22.2。
我保留系统原有的 Go,通过 update-alternatives 管理两个版本,而不是直接删除系统版本。
1. 安装编译环境
Ubuntu 24.04:
sudo apt update
sudo apt install -y \
git \
make \
gcc \
golang
确认 Go:
go version
这里可能有坑。
cri-dockerd 0.4.4 的 go.mod 要求:
go >= 1.26
而 Ubuntu 24.04 自带:
go1.22.x
2. 安装 Go 1.26
下载:
cd /tmp
wget https://go.dev/dl/go1.26.0.linux-amd64.tar.gz
如果 /usr/local/go 已经存在,先改名备份:
if [ -e /usr/local/go ]; then
sudo mv /usr/local/go \
"/usr/local/go.backup.$(date +%Y%m%d-%H%M%S)"
fi
解压:
sudo tar -C /usr/local \
-xzf go1.26.0.linux-amd64.tar.gz
3. 使用 alternatives 管理版本
先记录系统 Go 的真实路径:
SYSTEM_GO=$(readlink -f /usr/bin/go)
SYSTEM_GOFMT=$(readlink -f /usr/bin/gofmt)
echo "$SYSTEM_GO"
echo "$SYSTEM_GOFMT"
注册系统版本:
sudo update-alternatives \
--install /usr/bin/go go "$SYSTEM_GO" 50
sudo update-alternatives \
--install /usr/bin/gofmt gofmt "$SYSTEM_GOFMT" 50
注册 Go 1.26:
sudo update-alternatives \
--install /usr/bin/go go \
/usr/local/go/bin/go 100
sudo update-alternatives \
--install /usr/bin/gofmt gofmt \
/usr/local/go/bin/gofmt 100
切换:
sudo update-alternatives \
--set go /usr/local/go/bin/go
sudo update-alternatives \
--set gofmt /usr/local/go/bin/gofmt
验证:
go version
go env GOROOT
应该看到:
go version go1.26.0 linux/amd64
/usr/local/go

六、下载源码并应用补丁
下载 cri-dockerd 0.4.4:
mkdir -p ~/Codes
cd ~/Codes
git clone \
--branch v0.4.4 \
--depth 1 \
https://github.com/Mirantis/cri-dockerd.git
cd cri-dockerd
编辑:
nano cmd/server.go
找到:
streamingConfig := &streaming.Config{
// Use a relative redirect (no scheme or host).
BaseURL: &url.URL{Path: "/cri/"},
Addr: resolvedAddr,
改为:
streamingConfig := &streaming.Config{
BaseURL: &url.URL{
Scheme: "http",
Host: resolvedAddr,
Path: "/cri/"},
Addr: resolvedAddr,
确认修改:
git diff -- cmd/server.go
编译:
make cri-dockerd
官方 Makefile 中的 cri-dockerd 目标会直接调用 go build,生成项目根目录下的同名二进制文件。

检查:
ls -lh ./cri-dockerd
./cri-dockerd --buildinfo
七、替换 cri-dockerd 修复版本
为了验证修复效果,这里直接替换当前运行的 cri-dockerd。
不过在替换之前,先保留官方版本备份,方便后续回退验证。
查看当前版本:
which cri-dockerd
cri-dockerd --version
备份原始二进制:
sudo cp /usr/bin/cri-dockerd \
/usr/bin/cri-dockerd.backup
停止服务:
sudo systemctl stop cri-docker
复制编译后的修复版本:
sudo cp ./cri-dockerd \
/usr/bin/cri-dockerd
确认权限:
sudo chmod 755 /usr/bin/cri-dockerd
重新启动:
sudo systemctl start cri-docker
sudo systemctl restart kubelet
确认服务正常:
systemctl status cri-docker --no-pager
查看当前运行进程:
ps -ef | grep '[c]ri-dockerd'
确认已经加载新的二进制。
八、验证修复效果
修复完成后,重新创建一个最简单的测试 Pod。
创建测试 Pod:
kubectl delete pod curl \
--ignore-not-found
kubectl run curl \
--image=curlimages/curl \
--restart=Never \
--command -- \
sleep 3600
查看状态:
kubectl get pod curl
等待:
NAME READY STATUS
curl 1/1 Running
尝试进入容器:
kubectl exec -it curl -- sh
如果修复成功,可以正常进入:
~ $
此时说明:
kubectl exec
|
v
kube-apiserver
|
v
kubelet Streaming API
|
v
cri-dockerd
|
v
container
整个链路已经恢复。
退出:
exit

九、回退验证
为了确认修复不是偶然,需要恢复原始版本进行验证。
停止服务:
sudo systemctl stop cri-docker
恢复备份:
sudo cp /usr/bin/cri-dockerd.backup \
/usr/bin/cri-dockerd
重新启动:
sudo systemctl start cri-docker
sudo systemctl restart kubelet
删除旧测试 Pod:
kubectl delete pod curl
重新创建:
kubectl run curl \
--image=curlimages/curl \
--restart=Never \
--command -- \
sleep 3600
再次执行:
kubectl exec -it curl -- sh
恢复原始版本后,错误重新出现:
error sending request:
Post "//[::]:xxxxx/cri/exec/...":
http: server gave HTTP response to HTTPS client
重新切换到修复版本后:
kubectl exec -it curl -- sh
恢复正常。
通过正向和反向验证,可以确认:
cri-dockerd Streaming API 的 BaseURL 问题就是导致 kubectl exec 失败的原因。

十、清理测试环境
测试完成后删除临时 Pod:
kubectl delete pod curl
恢复实验环境干净状态。
最终验证结果:
| 运行版本 | kubectl exec |
|---|---|
| 原始 cri-dockerd 0.4.4 | 失败 |
| 增加 Scheme 和 Host | 成功 |
| 回退原版 | 再次失败 |
| 恢复补丁版 | 再次成功 |
这组正向、反向验证说明:
在当前环境中,Issue #569 给出的根因分析和修复补丁是正确的。
十、这次排障带来的启发
这次问题最迷惑的地方是:
Pod Running
kubectl logs 正常
kubectl exec 失败
原因是这些操作背后并不是完全相同的链路。
创建 Pod 正常,只能说明 CRI Runtime API 可以工作;kubectl exec 还依赖单独的 Streaming API。
而真正帮助我快速找到根因的,并不是继续盲目调整 DNS、Flannel 或 kubelet 参数,而是搜索完整错误信息,并找到与当前版本组合高度一致的社区 Issue。
截至 2026 年 7 月 30 日,Issue #569 仍处于 Open 状态,也没有关联正式修复 PR。因此,本文使用的是经过本地 A/B 验证的临时源码补丁,而不是已经发布的官方修复。未来 cri-dockerd 正式版本包含修复后,应优先恢复官方二进制。
这个意外插曲处理完成后,实验室的交互式调试能力终于恢复正常。
下一篇再回到原计划:
从零搭建一个单节点 K8S 可观测实验室(五):安装 Loki + Fluent Bit,构建 Kubernetes 日志采集链路
更多推荐

所有评论(0)