从奇安信运维岗JD看生产环境Kubernetes与containerd落地实践 1. 先看清这个标题背后的岗位信号2020年4月8日奇安信放出了一个运维工程师的岗位。单看标题这就是一条普通的招聘信息但如果你真把它当普通岗位去投大概率连面试机会都拿不到。为什么因为运维工程师这个title在2020年前后已经发生了本质变化而奇安信作为安全厂商对运维岗位的要求跟互联网公司、传统企业完全不是一个画风。我当时特意把奇安信2020年的运维岗位JD翻出来对照过虽然每个渠道挂出来的措辞略有不同但核心要求基本都指向这几个方向Kubernetes容器编排、生产环境高可用架构、安全合规基线、自动化运维体系建设。尤其是“想知道kubernetes是如何调用containerd的从原理到实体调用架”这种技术点几乎成了那个阶段运维面试的高频追问方向。你如果只会装个K8s集群、跑几个Pod根本应付不了这类问题。这篇文章我就从2020年这个时间切片出发把奇安信运维工程师背后真正需要的能力拆开讲清楚重点覆盖三个部分生产环境从零搭建系统的完整链路、Kubernetes调用containerd的底层原理、以及上线之后那套维护体系的搭建思路。内容按我实际做过的项目来写你能直接拿去参考。2. 生产环境从零搭建先活下来再谈架构2.1 基础设施规划里的取舍逻辑很多新手一上来就想着上Kubernetes、上微服务、上Service Mesh恨不得把所有新潮技术全堆上去。但在奇安信这类安全公司的生产环境里第一步根本不是选架构而是想清楚这套系统要扛什么、能扛多久、挂了会怎样。2020年那个阶段大多数业务线的交付节奏是先有业务需求再有系统设计最后才补安全能力。运维如果不在最开始介入后面补起来会非常痛苦。我自己的做法是先列出基础设施的三个核心决策点网络规划业务网、管理网、存储网是否隔离跨机房通信走什么链路防火墙策略怎么放行。存储选型块存储、文件存储、对象存储分别给谁用容量、性能、成本怎么平衡。计算资源物理机还是虚拟机CPU和内存配比怎么定有没有GPU需求。这三个点没有标准答案但有一个共同的判断逻辑能不能在故障时快速恢复而不是能不能在正常情况下跑得飞快。生产环境的底层逻辑永远是反脆弱优先。以网络为例2020年我们有一次业务迁移因为前期没规划好管理网和业务网的隔离导致一次误操作直接在业务网段开启了dhcp服务整个网段内上百台机器IP冲突线上业务中断了将近一个小时。后来我们才在基础设施规范里强制要求管理网段业务网段物理隔离所有交换机端口做MAC绑定dhcp服务只允许部署在独立的网络命名空间里。2.2 系统初始化阶段的坑与规范系统从裸机到能跑业务中间这一步最容易被忽视也最容易埋雷。我见过太多人直接拿默认安装的操作系统就跑业务结果过段时间要么被挖矿程序盯上要么被安全扫描出一堆高危漏洞。安全公司的生产环境对这块是零容忍的。我的初始化基线大致是这样的操作系统版本统一最小化安装不用的服务一概不启。SSH配置调整禁止root直接登录、密钥认证只允许特定用户、登录失败锁定阈值。系统内核参数按业务场景调优比如文件句柄数、TCP连接复用、内存回收策略。全部机器纳入统一的配置管理任何修改都有记录可回滚。举个例子SSH这块我踩过很大的坑。有一次为了图方便在测试环境把root远程登录打开了结果第二天发现机器被爆破成功直接被人装了一个挖矿程序。虽然测试环境没跑核心业务但是这台机器的IP跟生产环境在同一个网段安全扫描发现后我们整个安全团队被通报。从那之后凡是生产环境的机器root远程登录一律禁止登录全部走堡垒机跳板机这是写进基线规范里的硬性要求。系统内核参数这块我推荐关注这几个vm.swappiness、net.ipv4.tcp_tw_reuse、net.core.somaxconn、fs.file-max。具体数值得根据你的业务类型来调没有放之四海而皆准的配置。比如对高并发短连接场景tcp_tw_reuse打开能明显减少TIME_WAIT堆积但如果你跑的是长连接业务这个参数就没意义。2.3 应用层的部署形态怎么选2020年这个时间点容器化已经是明确的趋势但并非所有业务都适合一开始就上Kubernetes。我见过不少团队业务还没搞明白呢先把K8s集群搭起来结果运维成本暴增最后又退回虚拟机部署。这属于典型的用战术勤奋掩盖战略懒惰。从我的经验看选部署形态可以参考这个判断路径单机应用、无状态服务直接容器化用Docker Compose或者单机Kubernetes就能搞定。有状态服务、数据库类优先物理机或虚拟机部署别急着容器化运维成本真的不在一个量级。微服务集群、需要弹性伸缩的业务上Kubernetes它的价值在于编排、调度、自愈而不是让你把服务塞进容器就跑。奇安信的运维岗面试题里经常问到“如何在生产环境从零搭建一个系统”按我的理解面试官要的答案不是一个具体的工具链而是你那套判断逻辑什么场景选什么架构为什么这么选风险在哪里回滚方案是什么。面试的时候直接背工具名没有意义得能讲清楚每一层的取舍。3. Kubernetes调用containerd从kubelet到runc的完整链路3.1 为什么Kubernetes要引入CRI容器运行时这个事儿在Kubernetes早期是一段混乱期。Kubernetes最初直接内嵌了对Docker的支持代码里硬编码了Docker的API调用。但问题是容器运行时不只是Docker一家还有containerd、CRI-O、rkt等等如果Kubernetes每支持一个运行时就要重写一遍接口那这个项目根本维护不下去。所以Kubernetes推出了CRIContainer Runtime Interface。CRI不是一个新的容器运行时它是一套接口规范相当于Kubernetes跟容器运行时之间的一个翻译层。Kubernetes这边定义好接口你只要实现了这套接口就能成为Kubernetes支持的容器运行时。这个设计思路可以用一个生活化的类比来理解CRI就是电源插座的标准Docker、containerd、CRI-O这些运行时都是不同品牌的电器只要插头符合标准插上去就能用。而containerd之所以成为Kubernetes最主流的运行时选择根本原因是它在设计之初就专门为Kubernetes做了适配再加上Docker自身的运行时底层就是containerd所以生态兼容性最好。2020年的时候Kubernetes宣布逐步弃用dockershim这件事直接加速了containerd的普及。3.2 一条Pod从提交到运行的完整调用链为了回答“kubernetes是如何调用containerd的”我把整条链路按调用顺序拆给你看。第一步用户通过kubectl向API Server提交一个Pod的定义API Server经过认证、授权、准入控制之后把Pod对象写入etcd。第二步kubelet通过API Server的监听机制ListAndWatch发现有一个新的Pod被调度到了自己所在的节点于是开始执行Pod的创建流程。第三步kubelet调用自己内部的CRI插件通过gRPC连接节点上的containerd进程发送RunPodSandbox请求。这一步先创建的是一个沙箱PodSandbox也就是Pod级别的隔离环境包括网络命名空间、IPC命名空间等。然后在沙箱里再调用CreateContainer创建具体的容器。第四步containerd收到创建容器的请求后把镜像从仓库拉取到本地如果本地已经有就不需要拉取。然后containerd把容器配置转换成OCIOpen Container Initiative规范定义的runtime spec再通过containerd内部的一个叫task service的模块调用底层的运行时组件来真正启动容器进程。第五步底层运行时组件比如runc根据runtime spec里的配置通过Linux内核的命名空间、cgroups、rootfs等机制把容器进程隔离出来并启动。如果你用的containerd配置了gVisor或者Kata Containers这类安全运行时那这一步还会经过一层虚拟机隔离安全性更高但性能有损耗。整条链路可以用一个更直观的路径来表示kubectl - kube-apiserver - etcd - kubelet - CRI plugin - gRPC - containerd - containerd task service - runc或其它OCI运行时 - 容器进程2020年面试如果让我现场画这条调用链我通常还会在关键节点上标出排查命令比如在kubelet那层看kubectl describe pod在containerd那层用crictl命令查看容器状态在runc那层直接查看宿主机进程。这样画出来面试官基本就能确认你不只是背过概念是真的排过障。3.3 从kubelet到runc的过程里各组件各自干了什么光画链路还不够还得理解这条链路上每个组件的职责边界。我按实际排查问题的顺序来讲。kubelet在做什么kubelet是Kubernetes在每个节点上的代理它的核心职责是确保节点上Pod的状态跟API Server里期望的状态一致。它盯着的不是容器进程本身而是Pod这个抽象层级。当它发现某个Pod里的容器挂了会根据重启策略决定要不要重新拉起。它要管的事情包括挂载存储卷、配置网络、健康检查、收集监控指标。containerd在做什么containerd承担的是镜像管理和容器生命周期管理。它维护镜像的拉取、解压、本地缓存也负责创建容器、启动容器、停止容器、销毁容器。containerd本身分为两个部分一个是用gRPC对外提供API的daemon进程另一个是负责跟上层交互的shim进程。每个容器都会有一个独立的shim进程它把containerd daemon和容器运行时的生命周期隔离开这样即使containerd daemon重启已经运行的容器也不会受影响。runc在做什么runc是真正使用Linux内核特性来创建和运行容器进程的工具。它做的事情可以概括为解析OCI规范配置调用clone系统调用创建新进程同时设置命名空间、cgroups、挂载点、rootfs最后启动容器进程。runc是跟内核打交道的最底层组件再往下就是capabilities、seccomp、SELinux这些内核安全机制了。对运维来说理解这层职责边界最大的好处是排障时能快速定位问题在哪一层。比如容器起不来你得先判断是kubelet的调度问题、containerd的镜像问题、还是runc的系统调用权限问题。不同层的排查方式和工具完全不同。3.4 一份可直接抄的CRI启用与验证清单如果你用的Kubernetes版本比较新默认的容器运行时很可能已经切换到了containerd。但如果你还在用旧版本或者自己做二进制部署可能需要手动配置。这里给一份我在生产环境启用containerd并完成验证的清单。containerd的配置在Linux上默认位于/etc/containerd/config.toml。Kubernetes要求我们启用SystemdCgroup因为Kubernetes本身的cgroup driver默认是systemd如果containerd用的还是cgroupfs两者对cgroup的管理方式不一致会导致资源统计异常甚至节点不稳定。配置示例如下version 2 [plugins.io.containerd.grpc.v1.cri] # 启用systemd cgroup [plugins.io.containerd.grpc.v1.cri.containerd] default_runtime_name runc [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true改完配置后重启containerd服务然后用ctr version或crictl version确认版本。crictl是Kubernetes社区推荐的CRI排查工具用法跟Docker命令很像。我一般会用这几条命令做基本验证crictl version crictl images crictl ps -a crictl logs container-id还有一个需要特别注意的点containerd的默认镜像仓库是docker.io如果你在国内环境直接拉镜像网络会很慢甚至超时。需要配置镜像加速器在config.toml里找到[plugins.io.containerd.grpc.v1.cri.registry.mirrors]把加速地址写进去。这样Pod调度到节点后镜像拉取不会卡住。4. 系统上线之后那套维护体系才是真正的护城河4.1 监控告警不是装上Prometheus就结束了很多运维工程师理解的监控就是装个Prometheus配上Grafana面板展示几个漂亮的大屏就觉得自己监控体系很完善了。但实际上生产环境的监控体系要解决的核心问题是故障发生后你多久能发现多久能找到根因多久能恢复到正常。我倾向于把监控体系拆成四个层次来建设。基础设施监控CPU、内存、磁盘、网络IO这一层用Prometheus的node_exporter就能覆盖。监控项不要贪多每个指标都要能回答一个具体问题。比如磁盘使用率超过80%意味着什么超过90%又意味着什么要有明确的阈值和响应动作。应用监控不只是看进程在不在要深入到业务指标。Http请求量、错误率、响应延迟P99、队列积压量这些指标才能反映业务真实健康状态。一般用Micrometer、Spring Boot Actuator这类的工具暴露指标再让Prometheus去拉取。日志监控日志里往往藏着监控指标看不到的线索。我们当时用ELK搭了一套集中日志平台所有应用日志统一采集到Elasticsearch里Kibana上做检索和告警。这里有个很关键的习惯关键日志必须结构化比如JSON格式输出否则后面想按字段检索会非常痛苦。链路监控如果系统是微服务架构Pinpoint或SkyWalking这类链路追踪工具是必需品。一次请求经过多个服务任何一个环节慢了都会影响整体响应时间没有链路追踪几乎是拿着放大镜找针。监控告警还有一个比技术更重要的点告警质量。我们当时统计过告警噪音太高的时候真实的告警很容易被淹没。后来定了一条策略宁可漏报不要误报。误报会让值班的人产生“反正都是假告警”的心理真出事的时候反而没人响应了。告警规则要精每条规则都要经过实际故障复盘验证。4.2 备份、日志和变更管理运维的三大基本功备份这个事儿几乎所有运维都知道重要但真正做好的没几个。2020年有一次磁盘故障直接让我们意识到备份策略的严重问题。当时数据库做了每日全量备份但备份文件和数据库在同一个物理磁盘上磁盘坏了备份也一起没了。这个教训非常深刻。后来的备份规范是这样的备份必须异地存储本地保留一份之外还要同步到独立的备份服务器或者对象存储。数据库备份除了每日全量还要做binlog实时同步确保数据可以恢复到任意时间点。每季度做一次恢复演练不演练的备份等于没有备份因为你不确定备份文件到底能不能正常恢复。日志管理这块除了采集和检索更重要的一个是日志保留周期。生产环境日志至少保留180天一方面是排障和审计需要另一方面安全合规要求也得满足。日志权限也必须分级管控不是所有人都能查线上日志涉及敏感信息的日志还需要脱敏处理。变更管理是最不讨喜但最不能跳的流程。我见过一次线上事故就是因为有人跳过了变更审批直接在业务高峰期重启了一个数据库连接池服务结果连接数瞬间被打满整个业务链路雪崩。后来我们在变更管理上严格推行了一套流程变更前评估影响面变更中执行方案和安全回退方案变更后观察至少30分钟指标。没有走完这套流程的变更一律不批准执行。4.3 终端安全与安全软件的合规管理奇安信作为安全厂商运维岗位日常工作里绕不开安全软件的管理问题。经常有人搜索“奇安信天擎怎么卸载”、“强制卸载奇安信天擎要密码”这类问题这里我从一个运维工程师的角度讲清楚这个事儿在企业环境里该怎么处理。天擎这类终端安全软件在企业的部署逻辑是由安全团队统一管理通过管理后台下发防护策略终端上的软件受策略约束卸载必须有管理员授权或者输入验证码。这个设计本身就是安全策略的一部分目的是防止终端被随意解除安全防护。如果你是企业内部员工觉得安全软件影响了电脑性能或者某些软件运行正确的做法不是想尽办法绕过卸载验证而是联系IT或安全部门说明你的场景由管理员评估后走正规流程处理。作为运维和管理员我们在部署安全软件时要注意的是不要在未经同意的情况下强制静默安装要提前告知员工安全软件的用途、占用的资源情况以及可能会对哪些类型软件产生误报这些沟通能减少大量不必要的卸载诉求。从运维的角度管理安全软件有几条经验值得记录安全软件的策略不能一刀切开发测试环境的终端和生产环境的策略严格程度应该不同。开发机上频繁编译打包误杀率高会影响研发效率。安全软件的病毒库更新要尽量分流避免所有终端同时从一台服务器拉取更新导致内网带宽被占满。安全软件自身也需要监控如果发现某个终端的天擎进程异常退出必须能第一时间感知否则很容易出现防护空窗期。4.4 容量管理与成本控制运维也要懂业务2020年的时候云原生的思路已经比较成熟但很多运维工程师对容量管理的理解仍然停留在“加机器”的层面。系统卡了加CPU内存不够加内存磁盘满了加磁盘这种反应式运维的代价是成本不可控而且加配置只能缓解无法根治。容量管理的正确姿势是先回答这几个问题当前系统的瓶颈到底在哪一层CPU密集型、IO密集型还是内存密集型业务高峰在什么时间段峰值持续多久平时负载有多低系统未来三个月的增长预期是多少需要预留多少冗余我做过一个比较典型的案例一个内部系统每天凌晨做批量任务白天负载很低。如果按白天的负载来看4核8G的配置完全够用。但批量任务一跑CPU直接飙到90%以上任务耗时翻倍。后来我们没有简单地把机器配置翻倍而是把批量任务改成分片执行利用消息队列做异步削峰任务总时长反而缩短了配置完全不用动。这个案例说明容量管理一定要结合业务特征而不是盲目堆资源。成本控制方面云环境里一个很容易被忽视的点是存储成本。很多人喜欢把日志、备份全都放在高性能存储上实际上这些数据可以按访问频率分层。热数据放SSD冷数据放普通磁盘备份归档直接扔到对象存储的低频存储里成本能降一大截。5. 常见故障与排查实录5.1 Pod一直Pending调度卡在哪个环节有一次线上反馈新提交的Pod一直起不来kubectl get pod看状态是Pending。我第一反应是用kubectl describe pod查看事件结果发现提示0/3 nodes are available: 1 Insufficient cpu, 2 node(s) had taint。这是一个非常典型的调度失败场景。排查思路是这样的Insufficient cpu说明节点上的CPU资源已经被其他Pod占满了要么是业务量确实上来了要么是某个Pod异常占用CPU导致其他Pod调度不进来。taint则说明节点被打上了污点需要检查污点是怎么来的。我们当时的处理是先用kubectl top nodes看节点整体负载结果发现有一台节点上有个Pod处于Running状态但CPU使用率持续飙高顺着Pod定位到是某个业务服务的bug不断空转消耗CPU。把Pod所在服务发版修复之后节点负载降下来新的Pod自动被调度上去了。排查这类问题最重要的是不要只看一个面。kubectl describe pod能看到最直接的调度失败原因但因为什么导致资源不足需要往节点层面、Pod资源使用情况逐层分析。5.2 容器启动失败镜像拉不下来containerd环境的镜像拉取问题比Docker多得多主要原因是配置差异。我遇到过很多次Pod报ErrImagePullcrictl images一看本地确实没有镜像手动拉取直接超时。排查下来基本都是镜像加速器没配或者配错了。还有一个高频场景私有镜像仓库的证书问题。当containerd拉取自建的Harbor仓库时如果Harbor用的是自签名证书containerd默认会拒绝连接。这个问题的处理方式有两个要么在节点上把Harbor的CA证书加到系统信任区要么在config.toml的registry配置里显式跳过证书校验。生产环境我推荐用前者后者虽然省事但是安全隐患很大等于把中间的加密通道全部放弃了。5.3 kubelet证书过期导致节点NotReadyKubernetes节点NotReady的问题里kubelet证书过期绝对占一大块。2020年好多用kubeadm搭的集群证书有效期默认一年很多人搭完集群就忘了这回事直到某天节点全部NotReady才想起来。排查的时候先看节点状态kubectl get nodes然后登录节点看kubelet服务状态systemctl status kubelet日志里如果出现certificate has expired or is not yet valid基本就是证书过期了。kubeadm的证书可以用kubeadm alpha certs renew all重新生成然后重启kubelet。Kubernetes新版本里这个命令变成了kubeadm certs renew all但原理是一样的。后来我在所有Kubernetes集群里都接入了证书到期监控提前30天告警再也没出现过因为证书过期导致的集群事故。6. 几个值得反复打磨的运维习惯文章最后我把自己这几年做运维沉淀下来的几个习惯分享出来未必适用于所有团队但至少能在多数场景下帮你少踩坑。第一个习惯是一切操作留痕。不管是手动改配置文件还是敲命令执行变更都要有记录。用脚本自动执行的话脚本要进版本库手动操作的话至少要在工单系统里留痕。很多人觉得这多此一举但真出事故复盘的时候你能准确说出昨天几点在哪个机器上执行过什么命令这本身就是一种核心竞争力。第二个习惯是故障复盘不能只到表层原因就停了。比如磁盘写满导致Pod驱逐表层原因是日志量太大那需要再往深层追问为什么日志会暴涨是不是业务程序有死循环为什么磁盘告警没有提前触发为什么没有配置日志轮转把一层层问下去才能真正修掉问题而不是每次都做救火队员。曾经我们在复盘一个事故时连续追问了五次为什么最后发现根因是上游系统的一个接口超时时间设置不合理导致下游服务不断重试日志量暴增。如果只停留在“磁盘满了”这个层面这个根因永远挖不出来。第三个习惯是把重复的事情自动化把自动化的事情文档化。手动操作的次数越多出错概率越大自动化脚本如果没人能看明白那它就是下一个事故的源头。所以我的自动化脚本都会写上清晰的注释和使用说明就算将来交接给别的同事也不会两眼一抹黑。为了做到这一点目前我每隔一段时间就会把最新的问题排查经验沉淀到团队的知识库里而不是让这些经验只存在个人脑子里。运维工程师这个岗位表面上看是跟机器、系统、网络打交道实际上核心处理的是不确定性。你永远不知道下一秒是磁盘坏、网络抖动、还是某个服务的bug突然爆发。能依赖的只有扎实的基础原理、完备的监控体系和一套可以回滚的操作流程。希望这篇从2020年奇安信运维岗位延伸出来的经验整理能给你在生产环境从零搭建系统、深入理解Kubernetes运行时原理、搭建可靠运维体系这些方向上提供一些真正用得上的参考。