加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱制作网_沈阳站长网 (https://www.024zz.cn/)- 视觉智能、大数据、智能搜索、CDN、边缘计算!
当前位置: 首页 > 百科 > 正文

建站新手必踩的7个运维坑:第5个90%人忽略的容器安全盲区

发布时间:2026-11-03 11:14:51 所属栏目:百科 来源:DaWei
导读:文章配图,仅供参考  去年十二月份,我蹲在客户机房里换SSD,风扇啸叫得像要散架,手边那杯美式凉透了,杯底结了一圈浅褐色印子——就那会儿运维主管推门进来,后脖颈还沾着片纸屑,搓了两下没搓掉,直接抬手抹进头发里。他递过来

文章配图,仅供参考

  去年十二月份,我蹲在客户机房里换SSD,风扇啸叫得像要散架,手边那杯美式凉透了,杯底结了一圈浅褐色印子——就那会儿运维主管推门进来,后脖颈还沾着片纸屑,搓了两下没搓掉,直接抬手抹进头发里。他递过来一台笔记本,说“你看看这个新站的Dockerfile”。我扫了一眼,RUN apt-get update && apt-get install -y curl wget git,后面跟了个chmod 777 /tmp,再往后就是COPY ./startup.sh /usr/local/bin/,最后CMD ["/usr/local/bin/startup.sh"]。


  我原以为是镜像层混乱的问题,结果拉出容器一查进程树:/bin/sh -c /usr/local/bin/startup.sh —— startup.sh第一行#!/bin/bash,第二行su - nobody -c 'python3 app.py',第三行直接echo "root:password123" >> /etc/shadow。这玩意儿压根没进容器扫描流程,CI卡点全靠那个git tag的版本号,tag一打,流水线就跑。我指着屏幕问:“谁写的启动脚本?”对面穿灰卫衣的实习生抬头,把耳机线从左耳摘下来半截,说“是我,照着某云文档抄的”。旁边戴银丝眼镜的DBA正低头看表,表带松了,他用指甲把扣子往里顶了一格。


  第九百二十三次重装镜像时,我发现他们在base镜像里硬编码了SSH密钥——不是挂载,是COPY进去的。key文件名还叫id_rsa_prod_bak_2023(记不清是bak还是bak2),路径写在.env里,ENV指令明文挂在Dockerfile里。我试过删掉那行ENV,容器照样跑,因为startup.sh里grep -q 'prod' /etc/hostname && cat /root/.ssh/id_rsa >> /home/nobody/.ssh/authorized_keys。没人动过/etc/hostname,但那个字符串它就躺在那儿,像块发霉的蛋糕糖霜。


  对,就是那个“90%人忽略的容器安全盲区”——不是镜像签名、不是K8s RBAC、不是seccomp策略。是他们根本没意识到:容器不是一次性快照,它是活着的、会呼吸的、能自我篡改的活物。你以为你锁死了entrypoint?它可以在runtime里curl下载一个新脚本覆盖自己。你以为你禁止了privileged?它早用cap_add混进sys_admin,mknod造设备节点,mount --bind把宿主机proc挂进自己/proc。这些操作不需要root权限,只需要docker run时那几行不起眼的--cap-add和--security-opt=...,而新手抄yaml,连注释里的# remove this line都当真删了。


  那天下午三点十七分,窗外开始下雨。雨点砸在机柜玻璃门上,声音像豆子倒进铁盆。我盯着日志里一条被截断的curl -fsSL http://172.20.12.55:8080/update.sh | sh,来源IP是容器内部IP段,目标域名却是内网DNS未解析的假地址——后来翻出来,那是个上周被下线的测试API网关,IP早已回收,但脚本缓存在容器层里,没重启,没清理,没触发liveness probe失效。整个集群三百七十二个Pod,十六台Node,全靠那个每小时轮询一次的健康检查存活。没人敢动probe timeout阈值,怕误杀。我试过改成十五秒,立刻飘红。又试回三十秒,红色退了,但Pod状态栏浮着一层灰边——readiness探针失败,但liveness还亮着。它活着,但它已经不是你部署的那个东西了。


  李工当时在隔壁机柜换电源模块,他徒弟小陈端着盒椒盐排骨蹲在地上吃,油滴在静电地板上。我喊他帮我盯一眼kubectl get events -n default --watch,他就把筷子夹着的排骨搁在服务器导轨边缘,用另一只手划手机屏幕。二十秒后他抬头,“有个Event叫FailedAttachVolume”,我说“不是那个”。他哦了声,又去舔筷子尖上的酱汁。我继续翻kubelet日志,发现pod创建后第三秒,pause容器还没完全初始化,sidecar就已经在/proc/self/fd/0里读到了宿主机/etc/passwd的副本。这不可能——除非initContainer里用了hostPath挂载,或者…有人在构建时用multi-stage把宿主机文件打包进了镜像缓存。


  我敢打包票:他们build时用的是docker build . --no-cache=false,但没清builder cache,没重置DOCKER_BUILDKIT=0,更没禁用cache-from。所以那个含密码的env变量,早在三个月前第一次CI失败时就被存进远程registry缓存层里,之后每次build都默认复用。没人查缓存层sha256值,因为kubectl describe pod不显示这个,helm list也不报。直到有天审计组要镜像签名链,他们才打开harbor控制台,发现v2.1.4-alpha标签底下压着七个不同digest的layer,其中三个标注“from cache”——最后一个,日期是去年十一月十九号凌晨两点四十一分,上传人ID是devops-robot-1,权限级别为admin。这ID我认识,是当年配自动部署脚本时生成的,密码还写在Confluence首页第三个表格里。


  说到Confluence……王姐前两天问我借充电线,她电脑蓝屏频发,说是显卡驱动冲突。我没给线,顺手把她工位上那张贴着“重启解决90%问题”的便利贴撕了,贴在她显示器右下角。她说谢谢。我说不用谢,你刚删的那个cron job,其实是清理/tmp下临时镜像的——不是清理容器,是清理build cache本地残留。现在那堆cache还在/dev/shm里,占着一百零三G,而df -h显示/dev/shm已满,所以所有新build都fallback到磁盘临时目录,然后…然后你就看到那些奇怪的permission denied错误了吧?


  优胜劣汰。这话我十年前听过一次,在深圳湾边那个集装箱改造的办公室里。那时候我们管它叫“血肉筛选器”——跑不过的配置崩掉,扛不住的脚本漏掉,藏不住的密钥爆掉。现在不叫这个了,叫“最小可行攻击面”,听起来体面些。但本质上没变。只是从前死的是机器,现在死的是信任链。你没法教所有人记住cap_add sys_admin有多危险,就像没法教每个人别把手伸进绞肉机。只能等他们亲手拧开那个容器,闻到里面的血腥味,才记得缩手。


  我今天出门时踩碎了一片干梧桐叶,咔嚓声特别响。刚才想补一句关于OCI runtime hooks的事,算了。那个东西我去年在无锡客户那儿调了十八秒没搞定,最后降级用runc了事。细节我记不清了。

(编辑:我爱制作网_沈阳站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!