k8s的基础知识and面试题

K8s组件全景分类图

┌─────────────────────────────────────────────────────────────┐
│ Kubernetes 组件分类体系 │
├─────────────┬─────────────┬─────────────┬─────────────┤
│ 工作负载 │ 网络 │ 存储 │ 配置 │
├─────────────┼─────────────┼─────────────┼─────────────┤
│ 服务发现 │ 安全 │ 调度 │ 扩展 │
└─────────────┴─────────────┴─────────────┴─────────────┘

核心依赖关系:

1
2
3
4
5
6
Pod → (被) Deployment/StatefulSet/DaemonSet 管理
Pod → (使用) Volume 挂载存储
Pod → (通过) Service 暴露服务
Service → (依赖) Endpoints 指向Pod
PVC → (绑定) PV 提供存储
PV → (由) StorageClass 动态创建

数据流向:

1
2
3
4
外部请求 → Ingress → Service → Pod
Pod日志 → Sidecar容器 → 日志收集系统
监控数据 → cAdvisor → Prometheus → Grafana
配置更新 → ConfigMap/Secret → Pod自动重载

从你kubectl apply命令到pod创建的过程

  1. kubectl 会将yaml文件转json,创建http post请求,发给api server.
  2. api server接收请求,然后进行认证(bear token),授权(这个账号能做啥不能做啥),控制,然后将deployment等对象信息存储到etcd。
  3. etcd存储的所有的k8s对象状态,是强一致性。
  4. API Server作为唯一能操作etcd的组件,其他组件都通过API Server进行通信。
  5. 其他组件(如Controller Manager、Scheduler、kubelet)通过API Server的Watch API监听资源变化。它会监听k8s对象们的变化,确认状态批量期望。
  6. contrl manager发现一个新deployment,然后control manager创建对应的ReplicaSet,replicaset control去创建pod,所以是先有ReplicaSet后有pod。
  7. scheduler 为pod选择合适的节点,主要通过以下几个属性:
    过滤:通过资源的情况、污点、不匹配、亲和性
    打分:亲和性、均衡性
    绑定
  8. kubelet创建pod:创建pod沙箱,拉镜像,创建容器,然后用dockerfile的容器启动命令来启动容器。
1
2
3
4
5
6
7
8
9
t0: kubectl apply
t1: API Server接收
t2: etcd存储
t3: Controller Manager创建ReplicaSet
t4: Controller Manager创建Pod
t5: Scheduler选择Node
t6: kubelet接收Pod
t7: 容器运行时创建容器
t8: 容器进程运行

为啥我delete一个pod,马上又生成一个新的?

  1. replicaset才是管理pod的对象.
  2. deployment时理想状态管理器,它会不断地检查,一旦发现当前的pod状态与期望不一致就会修复,所以scale是修改期望,delete pod是直接删掉pod,但是期望没变,所以你手动delete,k8s会马上再生成一个。

pod 的状态是completed是啥意思?

说明这个pod圆满完成了任务,pod有五个生命周期:
pending:拉取镜像或者调度镜像
running:容器已经就绪并正常运行
succeeded or completed:容器成功运行结束并退出
failed:容器以非零状态码退出
unknown:node挂了,master联系不上pod了。
Error:这里不是指pod错误了,是“Pod已经被删除,但kubectl还在尝试获取其状态。显示”Error”是因为Pod已经不存在,kubectl无法连接到它”

node断网10mins后,会发生啥?

  1. 由于node-monitor-grace-period 默认是40秒,kubelet超过40秒没有报心跳,node变成notready。然后这个node会被打上污点,不会有新的pod调度进来了。
  2. pod-eviction-timeout 默认是5分钟,node变成notready后,controller-manager再等5分钟就开始驱逐pod。
  3. 如果是depolyment,那么宿主机会在其他健康的node创建pod,将老的pod的状态设为terminating。
  4. endpoint-controller 监听到pod变化,将pod ip从service 的endpoints 列表里移除,coreDNS也同步更新,流量回切走。
  5. Node恢复后,kubelet恢复上报心跳,杀掉最后terminating的pod,然后已经迁移走的deployment pod不会自动返回。

我有一个service,clusterip 是10.0.0.2,我发现在pod里ping不通这个ip,但是可以curl通对应的接口,为啥?

Cluster IP是一个虚拟ip,它没有一个真实的网络接口,它只是一个iptables的规则。
但是k8s不让Cluster IP来响应ping,是因为“kube-proxy职位service定义了针对TCP UDP SCTP接口的转发规则。iptables转发规则是带接口的,而ICMP是三层协议,没有端口,所以kube-proxy默认不会给他生成NAT转换逻辑”。

如果我有一个服务器,它只有内网IP,可以与你的macos电脑相连,现在它上面启动了一个grafana,你怎么看它的grafana web页面?

开发阶段:可以通过”kubectl port-forward + ssh隧道”把远程的接口转发到本地浏览器,这个是目前最安全零开销的私密访问模式:

1
2
1. kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80 --address 127.0.0.1  # 将grafana pod的80接口映射到这个宿主机的3000接口,这个命令就不要动了,新开一个终端
2. ssh -L 3000:localhost:3000 你的宿主机帐号@宿主机ip #把远程服务器的localhost:3000接口映射到本地macos电脑的localhost:3000上

如果是生产阶段:用一个ingress controller + 云厂商的loadbalancer 来解决。

我这个是中国大陆的服务器,发现启动nginx这样的pod的时候,提示镜像拉取失败,应该怎么修?

  1. 修改 /etc/rancher/k3s/registries.yaml 文件,如果没有就创建一个,然后把中国大陆的镜像地址写进去就行。
  2. pause容器比较特殊(k8s在启动任何pod之前,都会先去拉一个叫pause的基础镜像),它不归registries.yaml 管,需要在/etc/systemd/system/k3s.service的ExecStart后面加上–pause-image:
1
2
3
4
# 修改前示例:
# ExecStart=/usr/local/bin/k3s server
# 修改后示例:
ExecStart=/usr/local/bin/k3s server --pause-image registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9

为啥etcd磁盘变慢也会导致全集群的pod异常重启?

etcd需要将心跳和数据持久化到日志里,如果磁盘太慢导致fsync超时,etcd就会进去选主状态or停止响应。
kubelet无法定期更新nodestatus,就认为nodes not ready。然后就开始大量驱逐。
此时会尝试在其他nodes上重新创建pod,但是etcd还是写入很慢,这些创建请求就堆积在api server内存里,这样新的创建不出来,老的被驱逐,整个集群都瘫痪了。
如果此时etcd恢复了,那么之前堆积的大量请求,etcd扛不住这么大的全量读压力,再次被高并发压垮,所以启动的时候,请求尽量从api server的内存缓存中读取,不要直接穿透到etcd。

如果pod的request设置的很小(为了塞下更多的pod),但是limit设置的很大,那么高流量来的时候,会发生啥?

这是资源超卖的定时炸弹,当流量洪峰来到,所有cpu会冲向limit,CPU是可压缩资源,然后出发CPU节流,链接池爆满。但是内存不可压缩,会大量的oom killer,然后大量pod挂掉,如果还有重试机制,那么整个系统雪崩了。

建议生产环境requests和limits设置相同,然后核心应用采用guaranteed级别,k8s给他最高的oom优先级,并在节点压力大时最后才驱逐他们。也可以用priorityclass来打标签,这个值越高,pod越不会被强制驱逐。

高并发场景下,为啥pod滚动更新会有502/504错误?

api server将pod状态改成terminating,然后endpoint收到变更,将service 的endpoint列表里移除这个pod id。kubelet向pod发送SIGTERM信号,进程开始退出。
现在可能是ingress还没有刷新转发规则,pod进程就已经收到了SIGTERM信号,停止接收请求或者干脆退出,那么没有更新规则的endpoint就把流量转发到“已经不干活”的IP上,就502/504 .
可以在容器销毁前手动加一个prestop延迟(这个延迟要小于terminationGracePeriodSeconds,默认30秒),让它在关闭前sleep一会。这样kubelet杀掉pod时,先执行prestop,此时pod状态已经是terminating,endpoint已经开始移除了,但是容器进程还在,实现优雅停机。

A部门是核心利润部门,B是边缘创新部门,但是A的架构师把A部门里所有pod的priorityClass改成10亿,导致A部门一压测或者扩容就把B部门的业务pod都驱逐了。现在A部门的priorityClass不能改,毕竟人家是核心利润部门,怎么操作来保护B部门基础生存空间?

通过限制高优先级的总量ResouceQuota, 或者给 B部门配置一个PDB,留一个minAvailable:1,这样调度器试图驱逐这个pod的时候,pdb会拦截这次行为。最后划分node affinity,大家分开池子。

为啥hbase rds这些有状态的应用,反而不建议用statefulset而是用CR?

CR(Custom Resource)是实际的自定义资源实例,是火山云的Hbase和RDS的管理实例。CRD提供了更高层次的抽象、简化操作、集成平台能力,相比StatefulSet更适合管理复杂的云服务实例。

感谢你请我喝咖啡~

Welcome to my other publishing channels