初级运维 / 云原生岗位 — 面试常问题目集合(中)容器篇

本册覆盖:Docker · Kubernetes
适用岗位:初级运维工程师、云原生运维、Kubernetes 运维、DevOps 初级工程师


四、Docker 容器

4.1 核心概念

  1. 什么是容器?容器和虚拟机的区别?
# 容器:操作系统级虚拟化,共享宿主机内核,通过 Namespace 隔离 + Cgroups 限制资源
# 虚拟机:硬件级虚拟化,每个 VM 有独立 Guest OS 内核,通过 Hypervisor 模拟硬件
#
# 核心区别:
# 1. 启动速度:容器秒级,VM 分钟级
# 2. 资源开销:容器 MB 级(共享内核),VM GB 级(独立 OS)
# 3. 隔离性:VM 强隔离(独立内核),容器进程级隔离(共享内核)
# 4. 移植性:容器镜像可跨环境运行,VM 依赖 Hypervisor 类型
# 5. 密度:同配置宿主机可运行更多容器(无 Guest OS 开销)
  1. Docker 的三大核心概念:镜像、容器、仓库分别是什么?
# 镜像(Image):只读模板,包含运行应用所需的文件系统、依赖、配置等,采用分层构建
# 容器(Container):镜像的运行实例,在镜像层上加一层可写层,拥有独立的进程/网络/文件系统
# 仓库(Registry):存储和分发镜像的服务,如 Docker Hub、Harbor;分为公开仓库和私有仓库
#
# 三者关系:docker build 构建镜像 -> docker run 从镜像启动容器 -> docker push/pull 与仓库交互
  1. 联合文件系统(UnionFS)是什么?镜像分层有什么好处?
# UnionFS:将多个目录/文件系统挂载到同一挂载点,呈现为单一文件系统的技术
# Docker 使用 Overlay2 等存储驱动实现镜像分层,每层对应 Dockerfile 中的一条指令
#
# 分层好处:
# 1. 复用共享层:多个镜像共享同一基础层,节省磁盘空间
# 2. 加速构建:未变更的层直接使用缓存,只重建变更层
# 3. 快速分发:pull 时只下载本地缺失的层,减少网络传输
# 4. 版本管理:每层有唯一 digest,便于追溯和回滚
  1. Namespace 和 Cgroups 分别在容器中起什么作用?
# Namespace(命名空间):实现资源隔离,让容器拥有"独立视角"
#   PID ns    - 进程隔离,容器内 PID 1 是容器内首个进程
#   NET ns    - 网络隔离,容器有独立的网卡、IP、端口空间
#   MNT ns    - 文件系统挂载点隔离,容器看到独立的根文件系统
#   UTS ns    - 主机名和域名隔离
#   IPC ns    - 进程间通信隔离(信号量、消息队列等)
#   USER ns   - 用户和用户组隔离
#
# Cgroups(控制组):实现资源限制与监控,防止单个容器耗尽宿主机资源
#   CPU       - 限制 CPU 使用率(--cpus)
#   内存      - 限制内存使用(-m/--memory),超出触发 OOM
#   磁盘 I/O  - 限制块设备读写速率(--blkio-weight)
#   进程数    - 限制容器内最大进程数(--pids-limit)

4.2 常用操作

  1. docker run 常用参数有哪些?-d-p-v-e--name--rm 分别做什么?
# -d  (--detach)          后台运行容器,打印容器 ID 后立即返回
# -p  (--publish)         端口映射:主机端口:容器端口,如 -p 8080:80
# -v  (--volume)          挂载数据卷:主机路径:容器路径 或 卷名:容器路径
# -e  (--env)             设置环境变量:-e MYSQL_ROOT_PASSWORD=123456
# --name                  给容器命名,便于后续引用(否则随机生成名称)
# --rm                    容器退出后自动删除容器(适合一次性任务)
#
# 其他常用参数:
# -it                     交互式终端(-i 保持 STDIN 打开,-t 分配伪终端)
# --restart=always        容器退出时自动重启
# --network               指定容器网络
# --cpus / -m             限制 CPU 核数 / 内存大小
  1. 如何查看容器日志?如何进入正在运行的容器?
# ========== 查看容器日志 ==========
docker logs <容器名/ID>           # 查看全部日志
docker logs -f <容器名/ID>        # 实时跟踪日志(类似 tail -f)
docker logs --tail 100 <容器名/ID> # 查看最近 100 行
docker logs --since 30m <容器名/ID> # 查看最近 30 分钟的日志
docker logs -f --tail 50 <容器名/ID> # 查看最近 50 行并持续跟踪

# ========== 进入正在运行的容器 ==========
docker exec -it <容器名/ID> /bin/bash   # 进入容器启动 bash(推荐)
docker exec -it <容器名/ID> /bin/sh     # Alpine 等精简镜像用 sh
docker attach <容器名/ID>               # 附着到容器主进程(不推荐,exit 会停止容器)
docker exec <容器名/ID> <命令>          # 在容器内执行单条命令,如 ls /etc
  1. ENTRYPOINTCMD 的区别?
# CMD:指定容器启动时的默认命令/参数,可被 docker run 后的命令完全覆盖
#   CMD ["executable","param1","param2"]  (exec 形式,推荐)
#   CMD command param1 param2             (shell 形式,以 /bin/sh -c 启动)
#
# ENTRYPOINT:指定容器的固定入口,docker run 后的命令作为参数追加给 ENTRYPOINT
#   ENTRYPOINT ["executable","param1"]    (exec 形式,推荐)
#   ENTRYPOINT command param1             (shell 形式)
#
# 核心区别:
#   同时存在时,CMD 的内容作为 ENTRYPOINT 的默认参数
#   docker run <img> 新命令 -- 覆盖 CMD,但不覆盖 ENTRYPOINT(除非用 --entrypoint)
#
# 典型组合:ENTRYPOINT ["nginx"] + CMD ["-g", "daemon off;"]
#   默认启动 nginx -g "daemon off;",docker run <img> -t 则变为 nginx -t
  1. COPYADD 的区别?
# COPY(推荐优先使用):
#   COPY <src> <dest>   仅复制本地文件/目录到镜像
#   简单、透明、可预期,适合绝大多数场景
#
# ADD(功能更强但不够透明):
#   ADD <src> <dest>    COPY 的超集,额外支持:
#   1. <src> 为 URL 时自动下载
#   2. <src> 为本地 tar 压缩包时自动解压到 <dest>
#
# 最佳实践:
#   普通文件复制 -> 用 COPY(明确、无副作用)
#   需要自动解压 tar -> 用 ADD
#   下载远程文件 -> 用 RUN curl/wget 替代 ADD URL(减少镜像层、更可控)
  1. 如何将容器提交为镜像?docker commit 和 Dockerfile 构建的区别?
# docker commit:将容器当前状态保存为新镜像
docker commit <容器名/ID> <新镜像名:tag>           # 基本用法
docker commit -m "描述信息" -a "作者" <容器> <镜像> # 带元信息
docker commit --change='CMD ["nginx","-g","daemon off;"]' <容器> <镜像>  # 同时修改配置

# docker commit(手动快照)vs Dockerfile(声明式构建):
#   维度        | docker commit          | Dockerfile
#   -----------|------------------------|--------------------------
#   可复现性    | 手动操作,不可复现      | 声明式,可复现,可版本控制
#   透明度      | 黑盒,不知道做了什么    | 每条指令可见,变更可审计
#   镜像层      | 所有变更在一层          | 每条指令一层,可缓存
#   适用场景    | 紧急调试/临时保存现场   | 生产构建,CI/CD 流水线
#   维护性      | 差                      | 好,可纳入 Git 管理
#
# 面试金句:docker commit 像手动配置一台服务器后打快照,Dockerfile 像写 Infrastructure as Code
  1. 如何给镜像打标签并推送到仓库?
# ========== 给镜像打标签 ==========
docker tag <源镜像:tag> <目标仓库/镜像名:tag>    # 基本格式
docker tag myapp:latest harbor.example.com/project/myapp:v1.0
docker tag <镜像ID> myapp:v2.0                    # 用镜像 ID 打标签

# 一个镜像可以有多个 tag,本质是给同一 Image ID 添加别名

# ========== 推送到仓库 ==========
docker login <仓库地址>                           # 先登录(Docker Hub 可省略地址)
docker login harbor.example.com -u admin -p 123456

docker push <仓库地址/镜像名:tag>                 # 推送指定 tag
docker push myapp:v1.0                            # 推送到 Docker Hub(默认)
docker push harbor.example.com/project/myapp:v1.0 # 推送到私有仓库

# 规范 tag 命名:registry/namespace/repo:version
#   例:docker.io/library/nginx:1.25
  1. 如何清理没用的镜像、容器、数据卷?
# ========== 一键清理(推荐定期执行)==========
docker system prune          # 清理停止的容器、未使用的网络、悬空镜像、构建缓存
docker system prune -a       # 额外清理所有未被使用的镜像(非仅悬空)
docker system prune -a --volumes # 连带清理未使用的数据卷(危险,确认后执行)

# ========== 分类清理 ==========
docker container prune       # 清理所有已停止的容器
docker image prune           # 清理悬空镜像(dangling,<none>:<none>)
docker image prune -a        # 清理所有未被任何容器引用的镜像
docker volume prune          # 清理未被任何容器使用的数据卷
docker network prune         # 清理未被任何容器使用的网络
docker builder prune         # 清理构建缓存

# ========== 查看磁盘占用 ==========
docker system df             # 查看镜像/容器/数据卷的磁盘占用
docker system df -v          # 详细模式,列出每个对象的占用

4.3 Dockerfile

  1. 写一个 Dockerfile,部署一个 Nginx 并替换主页。
# Dockerfile
FROM nginx:1.25-alpine                         # 使用轻量 Alpine 基础镜像
LABEL maintainer="admin@example.com"           # 元信息

# 方式一:COPY 直接覆盖默认首页
COPY index.html /usr/share/nginx/html/index.html

# 方式二:COPY 整个目录(推荐)
# COPY ./html/ /usr/share/nginx/html/

# 方式三:用 RUN echo 内联写入(简单场景)
# RUN echo '<h1>Hello from Docker!</h1>' > /usr/share/nginx/html/index.html

EXPOSE 80                                      # 声明端口(文档作用)
CMD ["nginx", "-g", "daemon off;"]             # 前台运行(容器内必须)

# ========== 构建与运行 ==========
# docker build -t my-nginx:v1 .
# docker run -d -p 8080:80 --name web my-nginx:v1
# curl http://localhost:8080
  1. Dockerfile 中的 RUNCMDENTRYPOINT 各有什么作用?
# ====== RUN(构建时执行)======
# 在镜像构建阶段执行,结果作为新的一层固化到镜像中
# 通常用于安装软件、修改配置等构建操作
RUN apt-get update && apt-get install -y curl \
    && rm -rf /var/lib/apt/lists/*              # 清理缓存,减小层大小
RUN ["/bin/bash", "-c", "echo hello"]           # exec 形式

# ====== CMD(运行时默认命令)======
# 容器启动时的默认行为,可被 docker run 后的命令覆盖
CMD ["nginx", "-g", "daemon off;"]              # exec 形式(推荐)
CMD nginx -g "daemon off;"                      # shell 形式

# ====== ENTRYPOINT(运行时固定入口)======
# 容器启动时的固定入口,docker run 后的参数作为追加参数
ENTRYPOINT ["docker-entrypoint.sh"]             # 适合做初始化脚本
ENTRYPOINT ["nginx"]                            # 固定命令 + CMD 提供默认参数
CMD ["-g", "daemon off;"]                       # -> 最终执行:nginx -g "daemon off;"
  1. 多阶段构建(multi-stage build)有什么好处?
# 多阶段构建:一个 Dockerfile 中定义多个 FROM,最终只复制产物到最终镜像
#
# 好处:
# 1. 减小镜像体积:编译工具链(gcc、maven 等)不进入最终镜像
# 2. 安全:减少攻击面(无编译器、无源码、无密钥残留)
# 3. 简化 CI/CD:一个 Dockerfile 完成构建+打包,无需外部脚本
# 4. 层复用:构建阶段层可单独缓存,加速重复构建

# ====== 示例:Go 应用多阶段构建 ======
# 阶段 1:编译
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server .

# 阶段 2:运行(最终镜像)
FROM alpine:3.19
COPY --from=builder /app/server /usr/local/bin/server
EXPOSE 8080
CMD ["server"]
# 最终镜像仅 ~10MB(Go 二进制 + Alpine),而构建阶段镜像 ~400MB

4.4 网络与存储

  1. Docker 的 bridge、host、none 网络模式各有什么特点?
# bridge(默认网桥模式):
#   容器通过 docker0 虚拟网桥连接,NAT 访问外网
#   端口映射 docker run -p 8080:80
#   容器间通过 IP 通信(自定义 bridge 网络可通过容器名通信)
#   适用:绝大多数场景

# host(主机网络模式):
#   容器与宿主机共享网络栈,容器端口直接暴露在宿主机
#   无端口映射开销,性能最高
#   端口冲突风险:容器端口不能与宿主机端口重复
#   适用:高性能网络需求(如网络密集型应用)

# none(无网络模式):
#   容器只有 lo 回环网卡,完全隔离
#   适用:不需要网络的批处理任务、安全计算场景

# ========== 查看与操作 ==========
docker network ls                         # 列出所有网络
docker network inspect bridge             # 查看网桥详细信息
docker run --network=host <镜像>           # 使用 host 模式
docker run --network=none <镜像>           # 使用 none 模式
docker network create my-net              # 创建自定义 bridge 网络
docker run --network=my-net <镜像>         # 使用自定义网络(支持 DNS 容器名解析)
  1. Docker 的数据卷有几种方式?bind mountvolume 的区别?
# Docker 三种挂载方式:
# 1. Volume(数据卷)       Docker 管理的存储,位于 /var/lib/docker/volumes/
# 2. Bind Mount(绑定挂载)  挂载宿主机任意路径到容器
# 3. tmpfs(内存挂载)       挂载到内存,容器停止即消失(适合临时敏感数据)

# ====== Volume vs Bind Mount ======
# 特性      | Volume                      | Bind Mount
# ----------|-----------------------------|---------------------------
# 管理方    | Docker 管理                 | 宿主机文件系统直接管理
# 路径      | /var/lib/docker/volumes/    | 宿主机任意路径
# 创建      | docker volume create        | 手动创建目录或让 Docker 创建
# 可移植性  | 好(与宿主机解耦)           | 差(依赖宿主机目录结构)
# 容器写入  | 隔离,不污染宿主机           | 直接修改宿主机文件
# 适用场景  | 数据库持久化、生产环境       | 开发时热更新代码、共享配置

# ====== 使用示例 ======
docker volume create mydata                          # 创建 Volume
docker run -v mydata:/data <镜像>                    # 使用 Volume
docker run -v /home/user/app:/app <镜像>             # Bind Mount
docker run --mount type=volume,src=mydata,dst=/data <镜像>  # --mount 语法(推荐)
docker run --tmpfs /tmp:rw,noexec,nosuid,size=64m <镜像>    # tmpfs 挂载
  1. 两个容器之间如何通信?
# 方式一:自定义 bridge 网络(推荐,支持 DNS 容器名解析)
docker network create my-net
docker run -d --name web --network my-net nginx
docker run -d --name app --network my-net myapp
# app 容器内可以直接 ping web(Docker 内置 DNS 自动解析容器名)

# 方式二:默认 bridge 网络(只能通过 IP 通信,不推荐)
docker run -d --name web nginx
docker inspect web | grep IPAddress          # 获取容器 IP
docker run --rm alpine ping 172.17.0.2       # 用 IP 访问

# 方式三:端口映射(外部/主机访问容器)
docker run -d -p 8080:80 nginx               # 主机 8080 -> 容器 80
curl http://localhost:8080

# 方式四:共享网络栈(--network container:<容器名>)
docker run -d --name web nginx
docker run --network container:web alpine sh  # 共享 web 容器的网络

# 方式五:Docker Compose(自动创建网络,服务名互相访问,推荐生产用)

4.5 Docker Compose

  1. Docker Compose 是做什么的?和 docker run 比有什么优势?
# Docker Compose:通过 YAML 文件定义和管理多容器应用的工具
# 一条命令启动/停止/重建整个应用栈的所有服务
#
# ====== 与 docker run 的对比 ======
# 维度       | docker run                  | Docker Compose
# -----------|-----------------------------|---------------------------
# 配置方式   | 命令行参数,散落在脚本中      | 集中在 docker-compose.yml
# 多容器管理 | 需逐个手动启动,手动处理依赖  | 一键编排,自动处理启动顺序
# 网络       | 需手动创建和管理网络         | 自动创建网络,服务名即 DNS
# 数据卷     | 手动创建和管理               | 在 YAML 中声明,自动管理
# 环境一致性 | 依赖脚本,不同环境可能不同    | 一个 YAML 文件保证一致性
# 版本控制   | 不便                        | YAML 纳入 Git,声明式
# 适用场景   | 单个容器、临时调试           | 微服务本地开发、集成测试

# 面试金句:docker run 像一个个手动启动进程,Compose 像 systemd 统一管理一组服务
  1. docker-compose up -d 中的 -d 代表什么?
# -d (--detach):后台运行模式(detached mode)
#   服务在后台启动,终端不阻塞,打印容器名后立即返回命令行
#
# 不加 -d:前台模式,所有容器日志输出到终端,Ctrl+C 停止所有服务
# 加 -d:后台模式,日志不输出到终端,需用 docker-compose logs 查看
#
# ====== 常用命令 ======
docker-compose up -d         # 后台启动所有服务(生产/开发环境常用)
docker-compose up            # 前台启动(调试时用,实时看日志)
docker-compose up -d nginx   # 后台启动指定服务
docker-compose logs -f       # 跟踪查看所有服务日志
docker-compose logs -f web   # 跟踪查看指定服务日志
docker-compose ps            # 查看服务状态
docker-compose down          # 停止并移除所有服务(加 -v 同时删除数据卷)
docker-compose restart       # 重启所有服务
  1. 如何用 Docker Compose 编排一个 LNMP 环境?
# docker-compose.yml — LNMP (Linux + Nginx + MySQL + PHP-FPM)
version: '3.8'
services:
  # ====== Nginx ======
  nginx:
    image: nginx:1.25-alpine
    container_name: lnmp-nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d          # Nginx 配置
      - ./www:/usr/share/nginx/html                # 静态文件
    depends_on:
      - php
    networks:
      - lnmp-net

  # ====== PHP-FPM ======
  php:
    image: php:8.2-fpm-alpine
    container_name: lnmp-php
    volumes:
      - ./www:/usr/share/nginx/html                # 代码共享目录
    networks:
      - lnmp-net

  # ====== MySQL ======
  mysql:
    image: mysql:8.0
    container_name: lnmp-mysql
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: appdb
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql                  # 数据持久化
      - ./mysql/init:/docker-entrypoint-initdb.d    # 初始化脚本
    networks:
      - lnmp-net

volumes:
  mysql-data:                                       # 声明数据卷

networks:
  lnmp-net:                                         # 自定义网络
    driver: bridge

# ====== Nginx 配置(./nginx/conf.d/default.conf) ======
# server {
#     listen 80;
#     server_name localhost;
#     root /usr/share/nginx/html;
#     index index.php index.html;
#
#     location ~ \.php$ {
#         fastcgi_pass php:9000;                    # 通过服务名解析 PHP 容器
#         fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#         include fastcgi_params;
#     }
# }

# ====== 启动 ======
# docker-compose up -d
# ###### 验证 ======
# curl http://localhost
# docker-compose ps

五、Kubernetes

5.1 架构与核心概念

  1. Kubernetes 的核心组件有哪些?各自的作用?
# ==================== Master 节点组件 ====================
# 1. API Server:集群统一入口,REST API,认证/授权/准入控制
# 2. etcd:分布式 KV 存储,保存集群所有状态数据
# 3. Controller Manager:运行各种控制器(Deployment/Node/Endpoint 等),确保实际状态向期望状态收敛
# 4. Scheduler:负责将 Pod 调度到合适的 Node 上

# ==================== Worker 节点组件 ====================
# 5. kubelet:节点上的 Agent,负责管理 Pod 生命周期,上报节点状态
# 6. kube-proxy:实现 Service 的网络代理和负载均衡(iptables/IPVS)
# 7. Container Runtime:容器运行时(containerd/CRI-O),负责拉取镜像、运行容器

# ==================== 插件/Addon ====================
# 8. CoreDNS:集群内 DNS 服务,解析 Service 和 Pod 域名
# 9. CNI 插件(Calico/Flannel):实现 Pod 网络互通
  1. Master 节点上的组件:API Server、etcd、Controller Manager、Scheduler 各做什么?
# API Server
# 所有组件交互的中心枢纽,提供 RESTful API,负责认证(Authentication)、
# 授权(Authorization)、准入控制(Admission Control),唯一与 etcd 直连的组件

# etcd
# 分布式 KV 数据库,基于 Raft 共识协议,存储集群所有对象状态。
# 是集群的"数据库",集群恢复的关键

# Controller Manager
# 运行一组控制循环(Deployment Controller/Node Controller 等),
# 持续监控 API Server 中的资源状态,将实际状态向声明式期望状态对齐
# 例如:Deployment → ReplicaSet → Pod 这一级联管理链条的驱动者

# Scheduler
# 监听未调度 Pod(nodeName 为空),通过预选(Filter)+ 优选(Score)
# 两阶段打分,选择最优 Node,然后更新 Pod 的 nodeName 字段
  1. Worker 节点上的组件:kubelet、kube-proxy、Container Runtime 各做什么?
# kubelet
# 节点上的核心 Agent,接收 PodSpec,确保容器按声明的状态运行;
# 上报 Node 状态和 Pod 状态到 API Server;执行 liveness/readiness 探针
# 启动命令:kubelet --config=/var/lib/kubelet/config.yaml

# kube-proxy
# 在每个 Node 上运行,监听 Service 和 Endpoints,维护网络规则
# (iptables 规则或 IPVS 规则),将访问 Service ClusterIP 的流量
# 负载均衡到后端 Pod IP

# Container Runtime
# 通过 CRI 接口与 kubelet 交互,负责镜像拉取、容器创建/启停等操作
# 常见:containerd(默认)、CRI-O、Docker(dockershim 已移除)
# 检查运行时状态:crictl ps / crictl images
  1. Pod 是什么?Pod 和容器的关系?一个 Pod 里为什么要放多个容器?
# Pod 是什么?
# Pod 是 K8s 最小调度单元,是一组共享命名空间(Network/IPC)和存储卷的容器的集合。
# 生命周期短暂、可替换,Pod 中所有容器总是运行在同一 Node 上。

# Pod 和容器的关系
# Pod 是容器的"逻辑主机"——一个 Pod 可包含一个或多个容器。
# 同一个 Pod 内的容器共享:
#   - 同一个网络命名空间(同一个 IP、共享 localhost)
#   - 同一个 IPC 命名空间
#   - 可以通过 shared volumes 共享文件

# 为什么一个 Pod 里放多个容器?(Sidecar 模式)
# 1. Sidecar 辅助模式:主容器 + 日志收集(filebeat)/ 服务网格代理(envoy/istio-proxy)
# 2. Ambassador 代理模式:主容器 + 外部代理(连接外部服务)
# 3. Adapter 适配器模式:主容器 + 格式转换容器(统一监控指标格式)

# 示例:带 Sidecar 的 Pod YAML
# spec.containers[0] 是主容器 nginx,containers[1] 是日志收集 fluentd
  1. Label 和 Selector 的作用?Annotation 和 Label 的区别?
# Label 和 Selector
# Label:附加在 K8s 对象上的 key/value 对(如 app=nginx, env=prod)
# Selector:根据 Label 筛选/关联资源的条件表达式
# 作用:Service 通过 Selector 关联 Pod,Deployment 通过 Selector 管理 Pod
# 示例:kubectl get pods -l 'app=nginx,env in (staging,prod)'

# Annotation 和 Label 的区别
# | 特性       | Label                          | Annotation                       |
# |-----------|--------------------------------|----------------------------------|
# | 用途       | 标识、分组、筛选资源            | 存储非标识性元数据               |
# | 被查询     | 是(通过 Selector)             | 否                                |
# | 典型场景   | Service 选 Pod、调度约束        | 构建信息、git commit、联系邮箱   |
# | 大小限制   | 63 字符以内                     | 可更大                           |

5.2 工作负载资源

  1. Deployment、StatefulSet、DaemonSet、Job、CronJob 各适用于什么场景?
# Deployment:无状态应用
# 场景:Web 服务(nginx/tomcat)、API 服务、微服务
# 特点:Pod 可互换、无固定标识、支持滚动更新/回滚

# StatefulSet:有状态应用
# 场景:数据库(MySQL/PostgreSQL/MongoDB)、消息队列(Kafka/RabbitMQ)、
# 分布式存储(Ceph/GlusterFS)、ZooKeeper/etcd 集群
# 特点:Pod 有固定网络标识和持久存储、按序创建删除

# DaemonSet:每个节点运行一个 Pod
# 场景:日志收集(fluentd/filebeat)、监控 Agent(node-exporter/datadog)、
# CNI 插件(Calico node)、kube-proxy、存储插件
# 特点:新节点加入自动创建,节点移除自动清理

# Job:一次性任务
# 场景:数据迁移、备份、批处理、ETL 任务
# 特点:执行完退出、可设置并行数和重试次数

# CronJob:定时任务
# 场景:定期备份、定时报表、证书续期、定期清理
# 特点:Cron 表达式调度、每个周期创建新的 Job
  1. Deployment 和 StatefulSet 的核心区别?(对比列表)
# | 特性               | Deployment              | StatefulSet              |
# |-------------------|------------------------|--------------------------|
# | Pod 标识           | 随机名称(随机后缀)     | 固定序号(如 mysql-0,-1) |
# | 网络标识           | 无固定 DNS              | 有固定 DNS(Headless Svc)|
# | 存储               | 共享 PVC/无状态          | 每个 Pod 独立 PVC         |
# | 启动/停止顺序      | 并行/随机                | 按序(0→1→2)             |
# | 滚动更新           | 默认支持                 | 按序反向更新(N-1→0)     |
# | 扩缩容             | 随机                    | 按序(扩容 0→N,缩容 N→0)|
# | 删除 Pod           | 删除随机 Pod             | 按序从最高序号开始        |

# 一句话:Deployment 管理无状态的可互换 Pod,StatefulSet 管理需要
# 稳定网络标识和独立持久存储的有状态 Pod
  1. 什么是滚动更新(Rolling Update)?maxSurgemaxUnavailable 怎么设置?
# 滚动更新(Rolling Update)
# 逐步用新版本 Pod 替换旧版本 Pod,实现零停机更新。
# Deployment 的默认更新策略(默认 maxSurge=25%, maxUnavailable=25%)

# maxSurge
# 更新过程中允许超出期望副本数的最大 Pod 数量(百分比或绝对数)
# 默认 25%,设为 1 表示最多临时多出 1 个新 Pod
# maxSurge 越大 → 更新越快,但占用资源更多

# maxUnavailable
# 更新过程中允许不可用 Pod 的最大数量(百分比或绝对数)
# 默认 25%,设为 0 表示必须保证所有 Pod 可用(即"先启动新的再停旧的")
# maxUnavailable 越小 → 对服务影响越小

# 示例策略:
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1           # 最多多出 1 个 Pod(或 25%)
      maxUnavailable: 0     # 不允许任何 Pod 不可用(先启新后停旧)
  1. 如何回滚一个 Deployment?如何查看历史版本?
# 回滚到上一个版本
kubectl rollout undo deployment/nginx-deployment

# 回滚到指定版本
kubectl rollout undo deployment/nginx-deployment --to-revision=3

# 查看历史版本(前提:.spec.revisionHistoryLimit > 0,默认 10)
kubectl rollout history deployment/nginx-deployment

# 查看某个版本的详细信息
kubectl rollout history deployment/nginx-deployment --revision=3

# 查看回滚状态
kubectl rollout status deployment/nginx-deployment

# 暂停/恢复滚动更新
kubectl rollout pause deployment/nginx-deployment
kubectl rollout resume deployment/nginx-deployment
  1. StatefulSet 的 Pod 名称有什么规律?为什么需要 Headless Service?
# Pod 名称规律
# StatefulSet Name + 从 0 开始的序号:<statefulset-name>-<ordinal-index>
# 例如:mysql-0, mysql-1, mysql-2
# 创建顺序:0 → 1 → 2;删除顺序:2 → 1 → 0

# 每个 Pod 有稳定的网络标识(DNS):
# <pod-name>.<headless-service-name>.<namespace>.svc.cluster.local
# 例如:mysql-0.mysql.default.svc.cluster.local

# 为什么需要 Headless Service?
# Headless Service (clusterIP: None) 不分配 ClusterIP,DNS 直接返回 Pod IP 列表。
# 作用:
# 1. 给每个 Pod 提供稳定的网络标识(固定 DNS 名称)
# 2. 客户端可直接通过 DNS 发现所有 Pod(如 ZooKeeper 集群成员发现)
# 3. Pod 重启后虽然 IP 可能变,但 DNS 名称不变

# 示例:
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  clusterIP: None          # Headless Service
  selector:
    app: mysql
  ports:
    - port: 3306
  1. DaemonSet 的典型使用场景有哪些?
# 1. 日志收集 Agent
#    每个节点运行 Filebeat/Fluentd,收集该节点所有 Pod 日志
#    kubectl apply -f https://download.elastic.co/beats/filebeat/filebeat-kubernetes.yaml

# 2. 监控 Agent
#    node-exporter(Prometheus 生态)收集节点级别指标
#    Datadog Agent、Grafana Agent 等

# 3. CNI 网络插件
#    Calico node(calico-node)、Flannel(kube-flannel-ds)
#    需要在每个节点创建网络接口和路由规则

# 4. kube-proxy
#    K8s 原生的 kube-proxy 就是 DaemonSet 部署
#    kubectl -n kube-system get ds kube-proxy

# 5. 存储插件
#    CSI 驱动的 Node Plugin(如 Ceph CSI、NFS CSI node-plugin)

# 6. 节点维护任务
#    节点清理(清理旧镜像)、安全扫描(漏洞扫描Agent)

# 特点:自动适配集群扩缩,新节点加入自动创建 Pod
  1. Job 和 CronJob 的区别?CronJob 的并发策略有哪些?
# Job vs CronJob
# | 特性     | Job                      | CronJob                          |
# |---------|--------------------------|----------------------------------|
# | 触发方式 | 一次性 / 手动创建          | 定时触发(Cron 表达式)            |
# | 运行次数 | 执行完成即停止             | 按周期重复创建 Job                 |
# | 典型场景 | 数据迁移、一次性批处理      | 定期备份、定时报表、证书续期        |

# Job 关键参数
spec:
  completions: 5        # 需要成功完成 5 个 Pod
  parallelism: 2        # 最多 2 个 Pod 并行运行
  backoffLimit: 4       # 失败重试次数
  activeDeadlineSeconds: 100  # Job 最大运行时间

# CronJob 并发策略(.spec.concurrencyPolicy)
# Allow(默认):允许多个 Job 同时运行
# Forbid:如果上一个 Job 未完成,跳过本次
# Replace:如果上一个 Job 未完成,取消它并启动新 Job

# 示例 CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: backup-job
spec:
  schedule: "0 2 * * *"            # 每天凌晨 2 点
  concurrencyPolicy: Forbid        # 不允许并发
  successfulJobsHistoryLimit: 3    # 保留成功 Job 数
  failedJobsHistoryLimit: 1        # 保留失败 Job 数
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: backup
            image: backup-tool
          restartPolicy: OnFailure

5.3 网络

  1. Service 有哪几种类型?ClusterIP、NodePort、LoadBalancer、ExternalName 的区别?
# Service 四种类型

# 1. ClusterIP(默认)
#    分配一个集群内部 IP,只能在集群内访问
#    场景:微服务内部互相调用
#    spec.type: ClusterIP

# 2. NodePort
#    在每个 Node 上开放一个端口(30000-32767),通过 <NodeIP>:<NodePort> 从外部访问
#    底层自动创建 ClusterIP Service
#    场景:开发测试、简单外部访问
#    spec.type: NodePort

# 3. LoadBalancer
#    调用云厂商(或 MetalLB)的 LB 接口,创建一个外部负载均衡器
#    底层自动创建 NodePort + ClusterIP
#    场景:生产环境对外暴露服务
#    spec.type: LoadBalancer

# 4. ExternalName
#    不创建 Pod 选择器,通过 CNAME 将 Service 映射到外部 DNS
#    场景:将外部服务(如数据库)映射为集群内部 Service
#    spec.type: ExternalName
#    spec.externalName: mydb.example.com

# 快速查看:kubectl get svc -A
  1. ClusterIP 的虚拟 IP 是怎么实现的?(iptables / IPVS)
# ClusterIP 是虚拟 IP,不存在任何网络接口上,由 kube-proxy 通过 iptables/IPVS 规则实现

# iptables 模式(默认模式):
# 1. kube-proxy 在 iptables 的 PREROUTING/OUTPUT 链中添加规则
# 2. 匹配到 ClusterIP:Port 的包被 DNAT 到后端 Pod IP
# 3. 使用 iptables 的 statistic 模块做随机负载均衡(概率轮询)
# 4. 缺点:规则数量与 Service/Pod 成正比,大规模集群规则匹配性能差

# IPVS 模式:
# 1. 在 PREROUTING 链将包转发到 IPVS,IPVS 在内核层做负载均衡
# 2. 支持多种负载均衡算法:rr、lc、dh、sh、sed、nq
# 3. 优点:性能远优于 iptables(哈希查找 vs 线性匹配)

# 查看 kube-proxy 模式:
kubectl -n kube-system logs ds/kube-proxy | head -5
# 或查看 ConfigMap
kubectl -n kube-system describe cm kube-proxy | grep mode

# 查看 iptables 规则示例:
iptables -t nat -L KUBE-SERVICES -n | grep <ClusterIP>
  1. kube-proxy 的三种模式?iptables 和 IPVS 的区别?
# kube-proxy 三种模式

# 1. userspace 模式(已淘汰)
#    kube-proxy 监听端口,用户态转发,性能最差

# 2. iptables 模式(默认)
#    在内核态通过 iptables 规则做 DNAT + 随机负载均衡
#    优点:无需额外内核模块、通用性好
#    缺点:线性匹配规则,大规模集群严重性能下降;
#         非增量更新(全量重刷规则引起瞬时中断)

# 3. IPVS 模式
#    使用 Linux 内核 IPVS 模块做四层负载均衡
#    优点:Hash 表 O(1) 查找、支持多种调度算法、性能远优于 iptables
#    缺点:需要加载 ip_vs 内核模块;某些场景与 CNI 可能有兼容性问题

# iptables vs IPVS 对比
# | 特性         | iptables            | IPVS                       |
# |-------------|---------------------|----------------------------|
# | 查找方式     | 线性匹配            | Hash 表 O(1)                |
# | 规则更新     | 全量替换            | 增量更新                    |
# | 负载均衡算法 | 随机(概率轮询)     | rr/lc/dh/sh/sed/nq 等       |
# | 大规模集群   | 性能急剧下降         | 性能稳定                    |

# 启用 IPVS 模式
# kube-proxy --proxy-mode=ipvs --ipvs-scheduler=rr
  1. Endpoints 是什么?为什么 Endpoints 为空会导致 Service 不可用?
# Endpoints
# Endpoints 是 K8s 的一种资源对象,记录了 Service 对应的后端 Pod IP:Port 列表。
# Service 通过 Selector 自动创建并维护 Endpoints。

# 为什么 Endpoints 为空会导致 Service 不可用?
# kube-proxy 根据 Endpoints 中的 IP 列表生成转发规则
# 如果 Endpoints 为空 → 规则中没有后端目标 → 流量无处可去 → Service 不通

# 常见 Endpoints 为空的原因:
# 1. Selector 不匹配任何 Pod(Label 写错、Pod 没有对应 Label)
# 2. Readiness Probe 失败,Pod 被移出 Endpoints
# 3. Pod 所在的 Namespace 与 Service 不同

# 排查命令:
kubectl get endpoints <svc-name> -n <namespace>
kubectl describe endpoints <svc-name>
# 检查 Selector 是否匹配
kubectl get pods -l <selector> -n <namespace>

# EndpointSlice(v1.21+)是新一代 Endpoints 实现,支持更大规模集群
  1. Ingress 是什么?Ingress 和 Service 的关系?
# Ingress 是什么?
# Ingress 是 K8s 的七层负载均衡资源,提供 HTTP/HTTPS 路由规则管理。
# 支持:域名路由、路径路由、TLS 终止、重定向、自定义规则等。
# Ingress 本身只是"规则定义",需要 Ingress Controller 来实现。

# Ingress 和 Service 的关系
# Ingress → 将外部流量路由到 → Service → 转发到 → Pod
# Ingress 本身不是 Service 类型,而是建立在 Service 之上的七层路由
# 一个 Ingress 可以路由到多个 Service(不同域名/路径映射到不同 Service)

# Ingress vs Service(NodePort/LB)
# | 特性       | Service (NodePort/LB)  | Ingress                      |
# |-----------|----------------------|------------------------------|
# | 层级       | L4(TCP/UDP)        | L7(HTTP/HTTPS)              |
# | 路由方式   | IP:Port 级别         | 域名/路径级别                  |
# | TLS       | 需要额外配置          | 内置 TLS 终止                  |
# | 多服务     | 每个 Service 一个 LB  | 一个 Ingress 路由多个 Service |

# 示例 Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /users
        pathType: Prefix
        backend:
          service:
            name: user-service
            port:
              number: 80
  1. Ingress Controller 是什么?Nginx Ingress Controller 的工作流程?
# Ingress Controller 是什么?
# Ingress Controller 是实际处理 Ingress 规则的组件,监听 Ingress 资源变化,
# 动态配置 L7 代理(Nginx/HAProxy/Traefik/Envoy 等)。
# Ingress 只是规则,Controller 才是干活的人。

# Nginx Ingress Controller 工作流程:
# 1. Controller 监听 K8s API Server 中的 Ingress/Service/Endpoints/Secret 变化
# 2. 检测到变化后,根据 Ingress 规则生成 nginx.conf 配置
# 3. 将配置写入内部的 nginx 进程并 reload(或热更新 Lua 插件)
# 4. 外部请求 → NodePort/LoadBalancer → Nginx Pod → 按规则路由 → 后端 Service Pod

# 常见 Ingress Controller:
#   - ingress-nginx (Kubernetes 社区维护)
#   - nginx-ingress (NGINX Inc 维护)
#   - Traefik、HAProxy Ingress、Kong Ingress、Istio Gateway
#   - 云厂商:ALB Ingress Controller (AWS)、AGIC (Azure)

# 部署 ingress-nginx:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.0/deploy/static/provider/cloud/deploy.yaml
  1. CNI 是什么?Calico 和 Flannel 的主要区别?
# CNI(Container Network Interface)
# K8s 网络插件的标准接口规范,定义了容器网络配置的标准 API。
# kubelet 调用 CNI 插件为新创建的 Pod 配置网络(分配 IP、创建网络接口、设置路由)。
# CNI 插件只需实现 ADD/DEL/CHECK 等操作。

# 常见 CNI 插件分类:
#   - Overlay 模式:Flannel (vxlan)、Calico (IPIP)
#   - 路由模式(BGP):Calico (BGP)
#   - 二层模式:Flannel (host-gw)
#   - 其他:Cilium (eBPF)、Weave、Macvlan

# Calico vs Flannel 主要区别
# | 特性         | Calico                   | Flannel                   |
# |-------------|--------------------------|---------------------------|
# | 网络模式     | BGP / IPIP / VXLAN       | VXLAN / host-gw           |
# | 网络策略     | 完整支持 NetworkPolicy    | 不原生支持(需额外组件)     |
# | 性能         | BGP 模式接近裸金属        | VXLAN 有一定封装开销       |
# | 适用场景     | 中大型集群、需要网络策略   | 小集群、简单场景           |
# | 复杂度       | 较高                     | 低,开箱即用               |

# 一句话:Flannel 简单易用适合小集群,Calico 功能强大(网络策略+高性能)适合生产环境
  1. NetworkPolicy 是做什么的?Ingress 和 Egress 分别控制什么?
# NetworkPolicy
# 定义 Pod 间网络访问规则的资源(Pod 级别防火墙),基于 CNI 实现
# 注意:不是所有 CNI 都支持 NetworkPolicy(Flannel 不原生支持)

# Ingress(入站规则)
# 控制"哪些来源"可以访问当前 Pod
# 规则三要素:来源 Pod(podSelector)、来源 Namespace(namespaceSelector)、允许的 IP 段(ipBlock)
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    - namespaceSelector:
        matchLabels:
          env: production
    - ipBlock:
        cidr: 10.0.0.0/8
    ports:
    - protocol: TCP
      port: 6379

# Egress(出站规则)
# 控制当前 Pod 可以访问"哪些目标"
spec:
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432

# 默认策略:没有 NetworkPolicy 时所有流量开放
# 只要有一条 NetworkPolicy 选中 Pod,该 Pod 默认拒绝所有,只允许策略中声明的流量

# 查看:kubectl get networkpolicy -A
  1. 客户访问 Pod 不通,你的排查思路?(完整链路排查)
# 完整链路排查:客户端 → Ingress/LB → Service → Endpoints → Pod → 容器应用

# 步骤 1:确认 Pod 自身正常
kubectl get pod <pod-name> -n <ns> -o wide          # 看 Pod 状态、IP
kubectl logs <pod-name> -n <ns> --tail=50            # 看应用日志
kubectl exec -it <pod-name> -n <ns> -- curl localhost:8080  # 本地测试端口

# 步骤 2:检查 Readiness Probe 是否通过
kubectl describe pod <pod-name> -n <ns> | grep -A5 Readiness
kubectl get endpoints <svc-name> -n <ns>             # 确认 Pod IP 在 Endpoints 中

# 步骤 3:检查 Service 和 Endpoints
kubectl get svc <svc-name> -n <ns>                   # 确认 ClusterIP/端口
kubectl describe svc <svc-name> -n <ns>              # 检查 Selector 与 Pod Labels
kubectl get endpoints <svc-name> -n <ns>             # 确认 Endpoints 非空

# 步骤 4:从另一个 Pod 测试 Service 连通性
kubectl run test-pod --rm -it --image=busybox -- sh
# 测试 ClusterIP
wget -O- http://<svc-cluster-ip>:<port>
# 测试 DNS 解析
nslookup <svc-name>.<namespace>.svc.cluster.local

# 步骤 5:检查 DNS
kubectl -n kube-system get pods -l k8s-app=kube-dns  # CoreDNS 是否正常
kubectl exec -it <dns-pod> -n kube-system -- nslookup <svc-name>

# 步骤 6:检查 kube-proxy 规则
# iptables 模式
iptables -t nat -L KUBE-SERVICES -n | grep <cluster-ip>
# IPVS 模式
ipvsadm -L -n | grep <cluster-ip>

# 步骤 7:检查 CNI 网络
kubectl -n kube-system get pods -l k8s-app=calico-node
# 检查 Pod 网络接口
kubectl exec <pod-name> -- ip addr
# ping 测试 Pod 间通信(需 CNI 支持或 Pod 有 ICMP 能力)

# 步骤 8:检查 NetworkPolicy
kubectl get networkpolicy -n <ns>                    # 是否有策略阻止流量

# 步骤 9:外部入口层(如 Ingress)
kubectl get ingress -n <ns>
kubectl describe ingress <name> -n <ns>              # 检查规则和 backend
kubectl -n ingress-nginx logs <ingress-controller-pod> --tail=100

# 步骤 10:节点/系统层面
kubectl describe node <node-name>                    # 节点状态
kubectl top node / kubectl top pod                   # 资源消耗

5.4 存储

  1. Volume 的作用是什么?emptydir、hostPath、PV、PVC 各有什么特点?
# Volume 的作用
# 容器文件系统是临时的(容器重启数据丢失),Volume 提供持久化存储和 Pod 内
# 容器间数据共享。Volume 的生命周期与 Pod 绑定(除 PV 外)。

# emptydir
# Pod 创建时分配的空目录,Pod 删除即销毁
# 数据存在 Node 本地磁盘(或内存 ramdisk)
# 场景:临时缓存、暂存数据、同 Pod 容器间共享
spec:
  volumes:
  - name: cache
    emptyDir:
      medium: Memory     # 可选 Memory 模式(tmpfs,更快)
      sizeLimit: 256Mi

# hostPath
# 挂载 Node 上的文件或目录到 Pod
# Pod 删除后数据仍保留在 Node 上
# 场景:访问 Docker socket (/var/run/docker.sock)、节点日志、DaemonSet 收集数据
# 风险:Pod 可能被重调度到其他节点丢失数据;多副本数据不一致
spec:
  volumes:
  - name: host-data
    hostPath:
      path: /data
      type: DirectoryOrCreate

# PV(PersistentVolume)
# 集群管理员创建的存储资源(存储抽象),独立于 Pod 存在
# 支持:NFS/Ceph/GlusterFS/本地磁盘/云存储 等
# 生命周期与 Pod 无关

# PVC(PersistentVolumeClaim)
# 用户对存储的请求(容量、访问模式),与 PV 绑定后使用
# 类比:PV 是存储池,PVC 是从池中申请一块
  1. PV(PersistentVolume)和 PVC(PersistentVolumeClaim)的关系?
# PV(PersistentVolume)和 PVC(PersistentVolumeClaim)的关系

# PV:集群级别的存储资源,由管理员预创建或由 StorageClass 动态创建
# PVC:用户对存储的"请求",指定需要的容量和访问模式

# 工作流程:
# 1. 管理员创建 PV(或配置 StorageClass 动态供应)
# 2. 用户创建 PVC,声明需要的存储大小和访问模式
# 3. K8s 根据匹配条件(容量 >= 请求、访问模式一致、StorageClass 相同、
#    Selector 匹配)自动将 PVC 绑定到合适的 PV
# 4. Pod 引用 PVC 名称挂载存储

# 类比理解:
# PV  = 运维准备的存储资源(像停车场里的停车位)
# PVC = 开发者申请的存储请求(像一张停车券)
# K8s 自动匹配最合适的 PV 给 PVC

# 绑定条件:
#   - PV 容量 >= PVC 请求容量
#   - PV 访问模式满足 PVC 需求
#   - PV 的 StorageClass = PVC 的 StorageClass
#   - PV 的 Selector 匹配 PVC 的 Label
# 一旦绑定,PV 进入 Released 状态,不能复用(1:1 绑定关系)

# 查看绑定状态
kubectl get pv
kubectl get pvc -A
  1. PV 的回收策略(Retain、Delete、Recycle)有什么区别?
# PV 的三种回收策略(persistentVolumeReclaimPolicy)

# 1. Retain(保留)—— 默认策略
#    删除 PVC 后,PV 保留(状态变为 Released)
#    数据保留在存储后端,需管理员手动清理 PV 和数据
#    管理员操作:
#    kubectl delete pv <pv-name>          # 删除 PV(后端存储可能还需手动清理)
#    重新创建同配置 PV 供新 PVC 绑定

# 2. Delete(删除)
#    删除 PVC 后,PV 和关联的后端存储(如云盘/NFS子目录)自动删除
#    适用场景:云环境 + StorageClass 动态供应
#    注意:生产环境慎用,可能导致数据丢失

# 3. Recycle(已弃用,v1.25 移除)
#    对 PV 的数据执行 rm -rf /,然后 PV 变为 Available 状态可重新绑定
#    注意:不适用于 HostPath 之外的存储后端

# 示例:创建 Retain 策略的 PV
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-retain
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /data/nfs
    server: 10.1.8.10

# 命令:修改已有 PV 的回收策略
kubectl patch pv <pv-name> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
  1. PV 的访问模式(RWO、ROX、RWX、RWOP)分别代表什么?
# PV 访问模式(Access Modes)
# 注意:是否支持某模式取决于底层存储能力,不是 K8s 决定的

# RWO(ReadWriteOnce)
# 可读写,但只能被单个 Node 上的 Pod 挂载
# 典型存储:云盘(AWS EBS/GCE PD)、本地磁盘

# ROX(ReadOnlyMany)
# 只读,可被多个 Node 上的 Pod 同时挂载
# 场景:共享配置文件、静态资源

# RWX(ReadWriteMany)
# 可读写,可被多个 Node 上的 Pod 同时挂载
# 典型存储:NFS、CephFS、GlusterFS、Azure File
# 场景:多个 Pod 需要共享读写同一存储(如共享上传文件)

# RWOP(ReadWriteOncePod)—— v1.22 GA
# 可读写,只能被单个 Pod 挂载(强调单 Pod 而非单 Node)
# 比 RWO 更严格:同一个 Node 上也只允许一个 Pod 挂载
# 场景:需要确保存储独占的任务

# 查看存储支持的访问模式:
kubectl describe pv <pv-name> | grep "Access Modes"

# | 缩写   | 全称                | 粒度 | 读写 |
# |-------|--------------------|------|------|
# | RWO   | ReadWriteOnce       | 单Node | 读写 |
# | ROX   | ReadOnlyMany        | 多Node | 只读 |
# | RWX   | ReadWriteMany       | 多Node | 读写 |
# | RWOP  | ReadWriteOncePod    | 单Pod  | 读写 |
  1. StorageClass 是什么?动态卷供应和静态卷供应的区别?
# StorageClass 是什么?
# StorageClass 定义了存储"类别"(如 SSD/HDD、不同性能/可用区),
# 配合 provisioner 实现 PV 的自动创建。用户只需创建 PVC 并指定 StorageClass,
# Provisioner 自动创建 PV 并绑定给 PVC。

# 动态卷供应(Dynamic Provisioning)
# 管理员只需创建 StorageClass,用户创建 PVC 时自动创建对应 PV
# PV 按需创建、自动绑定,运维成本低
# 示例流程:
#   创建 StorageClass (带 provisioner: nfs) →
#   用户创建 PVC (指定 StorageClass) →
#   Provisioner 自动创建 PV → 自动绑定 PVC
# 适用:云环境、生产环境大规模集群

# 静态卷供应(Static Provisioning)
# 管理员需手动创建 PV,然后 PVC 匹配绑定
# 流程:
#   管理员手动创建 PV(指定 NFS 服务器/路径、云盘 ID 等)→
#   用户创建 PVC(不能指定 PV 名字,通过条件匹配)
# 缺点:需要预先知道所有需求、PV 管理繁琐
# 适用:小规模、存储资源固定

# 示例 StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-sc-fast
provisioner: nfs.csi.k8s.io
parameters:
  server: 10.1.8.10
  path: /data/nfs
  mountOptions:
    - nfsvers=4.1
reclaimPolicy: Delete
volumeBindingMode: Immediate    # Immediate / WaitForFirstConsumer
  1. NFS 动态卷制备的部署流程(provisioner + StorageClass)?
# NFS 动态卷制备部署流程(以 nfs-subdir-external-provisioner 为例)

# 步骤 1:确保有 NFS 服务器并导出目录
# NFS 服务器端:
yum install -y nfs-utils
mkdir -p /data/nfs
echo "/data/nfs *(rw,sync,no_root_squash,no_subtree_check)" >> /etc/exports
exportfs -r
systemctl enable --now nfs-server

# 步骤 2:所有 K8s 节点安装 nfs-utils(需要挂载能力)
yum install -y nfs-utils   # CentOS/RHEL
apt install -y nfs-common  # Ubuntu/Debian

# 步骤 3:通过 Helm 部署 nfs-subdir-external-provisioner
helm repo add nfs-subdir-external-provisioner \
  https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  --set nfs.server=10.1.8.10 \
  --set nfs.path=/data/nfs \
  --set storageClass.name=nfs-client \
  --set storageClass.reclaimPolicy=Retain \
  --set storageClass.archiveOnDelete=false \
  -n kube-system

# 步骤 4:验证部署
kubectl -n kube-system get pods -l app=nfs-subdir-external-provisioner
kubectl get storageclass

# 步骤 5:创建 PVC 测试
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-nfs-pvc
spec:
  storageClassName: nfs-client
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
EOF

# 步骤 6:验证 PVC 和自动创建的 PV
kubectl get pvc test-nfs-pvc
kubectl get pv | grep test-nfs-pvc

# 步骤 7:Pod 中使用 PVC
# spec:
#   containers:
#   - volumeMounts:
#     - name: data
#       mountPath: /data
#   volumes:
#   - name: data
#     persistentVolumeClaim:
#       claimName: test-nfs-pvc

5.5 调度

  1. Kube-scheduler 的调度过程是怎样的?(Filter + Score)
# Kube-scheduler 调度过程:两步走

# 第一步:Filter(预选 / 过滤)
# 从所有 Node 中排除不满足条件的节点:
#   1. 节点资源不够(CPU/内存不足)
#   2. 节点有 Pod 不匹配的 Taint(且无 Toleration)
#   3. 不满足 NodeSelector / NodeAffinity
#   4. 不满足 PodAffinity / PodAntiAffinity
#   5. 节点磁盘压力 / 内存压力 / PID 压力
#   6. 端口冲突(hostPort 冲突)
#   7. 存储卷能否挂载(VolumeZone 等)
# 结果:剩余一组"可行节点"

# 第二步:Score(优选 / 打分)
# 对每个可行节点打分,选择总分最高的节点:
#   1. LeastRequestedPriority:资源剩余越多分越高(倾向均衡)
#   2. MostRequestedPriority:资源利用率越高分越高(倾向装箱)
#   3. BalancedResourceAllocation:CPU 和内存比例均衡的分高
#   4. NodeAffinityPriority:亲和性匹配加权
#   5. ImageLocality:镜像已存在该节点的分高
#   6. InterPodAffinityPriority:Pod 间亲和性加权
# 最终选择得分最高的节点,同分则随机选

# 查看调度事件:
kubectl describe pod <pod-name> | grep -A10 Events
# 查看调度器日志:
kubectl -n kube-system logs <scheduler-pod> | tail

# PriorityClass 指定 Pod 优先级(高优先级可抢占低优先级):
# kubectl get priorityclass
  1. nodeSelector 和 nodeAffinity 的区别?
# nodeSelector
# 最简单的节点选择方式,Pod 只在匹配 Label 的 Node 上调度
# 只能做"等于"匹配,无法做复杂表达式
spec:
  nodeSelector:
    disktype: ssd
    gpu: "true"
# 缺点:功能有限、无法表达"不等于"或"存在性"条件

# nodeAffinity
# 更灵活的节点亲和性调度,支持丰富的匹配表达式
# 分为 preferredDuringScheduling(软亲和性)和 requiredDuringScheduling(硬亲和性)
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:  # 硬要求
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
          - key: disktype
            operator: NotIn       # 支持 NotIn
            values:
            - hdd
      preferredDuringSchedulingIgnoredDuringExecution:  # 软偏好
      - weight: 1
        preference:
          matchExpressions:
          - key: zone
            operator: In
            values:
            - zone-a

# 对比总结
# | 特性       | nodeSelector     | nodeAffinity                      |
# |-----------|------------------|-----------------------------------|
# | 匹配操作   | 仅 In (=)        | In/NotIn/Exists/DoesNotExist/Gt/Lt |
# | 软硬区分   | 无               | required + preferred               |
# | 复杂条件   | 不支持           | 支持多条件组合                     |
# | 选择       | 简单场景         | 复杂调度策略                       |
  1. 污点(Taint)和容忍度(Toleration)是做什么的?与 nodeAffinity 的区别?
# Taint(污点)和 Toleration(容忍度)
# Taint 打在 Node 上,表示"排斥"Pod;Toleration 在 Pod 上配置,表示"接受"该 Node 的 Taint
# Taint 效果(effect):
#   NoSchedule:不允许未容忍的 Pod 调度到该节点
#   PreferNoSchedule:尽量不调度未容忍的 Pod
#   NoExecute:驱逐节点上未容忍的 Pod,且不允许调度

# 给 Node 打 Taint
kubectl taint nodes node1 key=value:NoSchedule
# 移除 Taint
kubectl taint nodes node1 key=value:NoSchedule-

# Toleration 示例(Pod 上配置)
spec:
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "gpu"
    effect: "NoSchedule"

# Taint/Toleration 与 nodeAffinity 的核心区别
# Taint + Toleration:拒绝式调度("此节点不接受你",除非你有 Toleration)
# nodeAffinity:吸引力调度("我希望/要求调度到这些特征的节点上")
# 
# 类比:
#   Taint = 节点说"闲人免进"
#   Toleration = Pod 说"我有通行证"
#   nodeAffinity = Pod 说"我偏好去什么地方"

# 两者可以组合使用:
# Taint 确保节点专用(如 GPU 节点),nodeAffinity 引导 Pod 过去
  1. cordon 和 drain 的区别?什么场景下用?
# cordon(封锁 / 暂停调度)
# 标记节点不可调度,已运行的 Pod 不受影响(不会被驱逐)
kubectl cordon <node-name>
# 效果:Scheduler 不会再调度新 Pod 到此节点
# 场景:节点维护前暂时停调、故障排查

# uncordon(解锁 / 恢复调度)
kubectl uncordon <node-name>

# drain(排水 / 驱逐)
# 驱逐节点上所有 Pod(除了 DaemonSet 的 Pod 和镜像 Pod),
# 并标记节点不可调度(等同 cordon + 驱逐)
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data
# 参数:
#   --ignore-daemonsets:跳过 DaemonSet Pod
#   --delete-emptydir-data:允许删除使用 emptyDir 的 Pod
#   --force:强制驱逐(不加 --force 已足够)
# 场景:节点下线、节点升级、换机器

# 对比:
# | 操作   | 驱逐 Pod | 新调度 | 已运行 Pod         | 场景               |
# |-------|---------|--------|-------------------|--------------------|
# | cordon| 否      | 禁止    | 正常运行           | 临时停调、排障     |
# | drain | 是      | 禁止    | 全部驱逐           | 节点下线、维护升级 |

# 完整节点维护流程:
kubectl drain node1 --ignore-daemonsets --delete-emptydir-data
# 执行维护操作...
kubectl uncordon node1
  1. podAffinity 和 podAntiAffinity 的应用场景?
# podAffinity(Pod 亲和性)
# 让 Pod 调度到"与特定 Pod 位于同一拓扑域"的节点上
# 场景:
# 1. 缓存服务和 Web 服务部署在一起(减少延迟)
# 2. 前后端服务部署在同一节点/可用区

spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - redis-cache
        topologyKey: "kubernetes.io/hostname"  # 同一节点

# podAntiAffinity(Pod 反亲和性)
# 让 Pod 调度到"不与特定 Pod 位于同一拓扑域"的节点上
# 场景:
# 1. 高可用部署:同应用多副本分散到不同节点/可用区
# 2. 避免资源竞争:将资源密集型 Pod 分开
# 3. 故障隔离:避免单点故障影响多个副本

spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - nginx
        topologyKey: "kubernetes.io/hostname"

# 常用 topologyKey:
#   kubernetes.io/hostname      → 同一节点
#   topology.kubernetes.io/zone → 同一可用区
#   topology.kubernetes.io/region → 同一区域
  1. topologyKey 的作用是什么?
# topologyKey 的作用
# 定义了 Pod 亲和性/反亲和性中的"拓扑域"边界。
# 具有相同 topologyKey 值(同一个 Node Label 值)的节点属于同一拓扑域。

# 工作原理:
# 当计算 podAffinity 时,K8s 会找"目标 Pod 所在节点的 topologyKey 标签值",
# 然后将"具有相同标签值的节点"都视为同一拓扑域。
# 如果找不到任何节点满足条件,Pod 调度失败(required 模式)。

# 常用 topologyKey 值:
#   kubernetes.io/hostname           — 同节点(每个节点的 hostname 唯一)
#   topology.kubernetes.io/zone      — 同可用区(云环境,如 us-east-1a)
#   topology.kubernetes.io/region    — 同区域
#   failure-domain.beta.kubernetes.io/zone  — 旧版标签(兼容)

# 示例 1:限制 Web 和缓存在同一节点
# podAffinity + topologyKey: kubernetes.io/hostname

# 示例 2:确保 MySQL 副本分散到不同节点(反亲和)
# podAntiAffinity + topologyKey: kubernetes.io/hostname

# 示例 3:限制 Kafka 副本在不同可用区(高可用)
# podAntiAffinity + topologyKey: topology.kubernetes.io/zone

# 查看节点相关标签:
kubectl get nodes --show-labels | grep topology

5.6 配置与存储

  1. ConfigMap 和 Secret 分别做什么?如何挂载到 Pod 中?
# ConfigMap
# 存储非敏感配置数据(key-value 形式),如:配置文件、环境变量、命令行参数
# 最大 1MB(etcd 限制),明文存储

# Secret
# 存储敏感数据,如:密码、Token、密钥、TLS 证书
# 数据以 base64 编码存储(注意:base64 不是加密,默认不加密需额外安全措施)
# 最大 1MB

# ConfigMap 挂载方式(4 种):
# 方式 1:环境变量(指定 key)
spec.containers[].env:
  - name: DB_HOST
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: database.host

# 方式 2:envFrom 批量注入所有 key
spec.containers[].envFrom:
  - configMapRef:
      name: app-config

# 方式 3:Volume 挂载为文件(最常用,支持热更新)
spec.volumes:
  - name: config
    configMap:
      name: app-config
spec.containers[].volumeMounts:
  - name: config
    mountPath: /etc/app/config

# 方式 4:命令行参数
spec.containers[].command:
  - /bin/app
  - --config=/etc/app/config.yaml

# Secret 挂载方式与 ConfigMap 相同(env/valueFrom/volumeMount)
# 注意:Secret 用 Volume 挂载时自动解密(tmpfs 内存文件系统)

# 创建示例:
kubectl create configmap app-config --from-file=app.properties --from-literal=ENV=prod
kubectl create secret generic db-secret --from-literal=password=MyP@ssw0rd
kubectl create secret docker-registry my-registry --docker-server=... --docker-username=... --docker-password=...
kubectl create secret tls tls-secret --cert=cert.pem --key=key.pem
  1. Secret 的三种类型:Opaque、dockerconfigjson、tls 分别用于什么场景?
# Secret 三种主要类型

# 1. Opaque(默认类型,通用密钥)
#    存储任意键值对,数据是 base64 编码的
#    场景:数据库密码、API Key、应用密钥
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password=S3cr3t!
#    查看(base64 解码):kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d

# 2. kubernetes.io/dockerconfigjson(镜像仓库认证)
#    存储 Docker 私有仓库的认证信息(~/.docker/config.json 的内容)
#    场景:从私有镜像仓库拉取镜像时提供认证
kubectl create secret docker-registry regcred \
  --docker-server=my-registry.example.com \
  --docker-username=admin \
  --docker-password=password123 \
  --docker-email=admin@example.com
#    Pod 中引用:
spec:
  imagePullSecrets:
  - name: regcred

# 3. kubernetes.io/tls(TLS 证书)
#    存储 TLS 私钥和证书(PEM 格式),key 必须是 tls.key 和 tls.crt
#     场景:Ingress TLS 终止、服务间 mTLS
kubectl create secret tls tls-cert \
  --cert=server.crt \
  --key=server.key
#    验证:openssl x509 -in <(kubectl get secret tls-cert -o jsonpath='{.data.tls\.crt}' | base64 -d) -noout -text

# 其他类型:
#   kubernetes.io/service-account-token — ServiceAccount Token(自动创建)
#   kubernetes.io/basic-auth — 基本认证凭据
#   kubernetes.io/ssh-auth — SSH 认证凭据
#   bootstrap.kubernetes.io/token — Bootstrap Token
  1. ConfigMap 更新后,Pod 中的配置会自动更新吗?
# 答案:取决于挂载方式

# 1. Volume 挂载为文件 → 会自动更新(有延迟)
#    挂载到 Pod 内文件系统中的 ConfigMap/Secret 会自动同步更新
#    但 K8s kubelet 同步有延迟(默认约 60-90 秒,由 kubelet syncFrequency 控制)
#    注意:如果 subPath 挂载,不会自动更新!
#    注意:应用进程可能不会自动重新加载配置文件

# 2. 环境变量注入 → 不会更新
#    环境变量只在 Pod 创建时注入一次,更新 ConfigMap 不影响已运行的 Pod
#    必须重启/重建 Pod 才能生效

# 3. 命令行参数 → 不会更新(同环境变量)

# 测试自动更新:
# 更新 ConfigMap
kubectl edit cm app-config
# 等待一段时间后查看 Pod 内文件
kubectl exec <pod> -- cat /etc/app/config/database.host

# 查看文件更新时间(确认已同步)
kubectl exec <pod> -- ls -la /etc/app/config/

# 最佳实践:
# 1. 应用支持动态重载配置(如 SIGHUP 信号 / 监听文件变化)
# 2. 使用 deploy 工具(如 Reloader)检测 ConfigMap 变更自动滚动重启 Pod
# 3. 生产环境建议:配置变更 → 滚动更新 Deployment 确保一致性
# 4. 使用 immutable ConfigMap 可以避免意外修改

5.7 资源管理

  1. Requests 和 Limits 的区别?如果不设置会怎样?
# Requests(请求值)
# 容器"保证"能获得的最小资源量
# Scheduler 根据 Requests 的总和决定将 Pod 调度到哪个节点
# 容器运行时至少分配这么多资源
spec.containers[].resources.requests:
  cpu: "500m"        # 500 毫核 = 0.5 CPU
  memory: "256Mi"

# Limits(限制值)
# 容器"最多"能使用的资源量(上限)
# CPU 达到 Limit:被 CPU throttling(限流),不会被 Kill
# 内存达到 Limit:触发 OOMKill,容器被终止(restartPolicy OnFailure)
spec.containers[].resources.limits:
  cpu: "1000m"       # 最多使用 1 个 CPU
  memory: "512Mi"    # 最多使用 512MiB 内存

# 不设置会怎样?
# 1. 资源不受控制:容器可无限使用节点资源,影响其他 Pod
# 2. Scheduler 无法合理调度:不知道 Pod 需要多少资源,可能调度到资源不足的节点
# 3. QoS 等级降低:成为 BestEffort(最低优先级,OOM 时最先被 Kill)
# 4. HPA 无法工作:HPA 需要设置 Requests 才能计算利用率

# 生产环境最佳实践:
# 1. CPU Requests = Limits(或接近),CPU 是可压缩资源
#    过高的 Limits 会导致不必要的 throttling 影响性能
# 2. Memory Requests = Limits,内存不可压缩,避免 OOMKilled
# 3. 设置合理的资源不要过度分配(overcommit)
  1. ResourceQuota 和 LimitRange 的作用分别是什么?
# ResourceQuota(资源配额)
# 限制 Namespace 级别的资源使用总量
# 防止某个 Namespace 过度消耗集群资源
# 可以限制:
#   - 计算资源:CPU 总量、内存总量
#   - 存储资源:PVC 总数量、总存储量
#   - 对象数量:Pod/Service/ConfigMap/Secret/Deployment 数量
#   - Scope:仅 BestEffort / NotBestEffort / Terminating / NotTerminating

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: dev-team
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    limits.cpu: "40"
    limits.memory: 80Gi
    persistentvolumeclaims: "10"
    pods: "50"
    services: "10"

# LimitRange(范围限制)
# 限制 Namespace 内单个 Pod/Container/PVC 的资源最大值和最小值
# 防止创建超大或超小的资源
# 可以设置:容器的默认 Requests/Limits、最小值、最大值、最大最小比例

apiVersion: v1
kind: LimitRange
metadata:
  name: container-limits
  namespace: dev-team
spec:
  limits:
  - type: Container
    default:               # 容器默认 Limits(不设置时使用)
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:         # 容器默认 Requests
      cpu: "250m"
      memory: "256Mi"
    min:                   # 最小限制
      cpu: "100m"
      memory: "128Mi"
    max:                   # 最大限制
      cpu: "2"
      memory: "4Gi"

# 总结:
# ResourceQuota → 限制整个 Namespace 总量(宏观)
# LimitRange → 限制单个 Pod/容器 的资源范围(微观)
# 两者配合使用,形成 Namespace 完整的资源管控体系
  1. QoS 的三种等级(Guaranteed、Burstable、BestEffort)如何确定?
# QoS(Quality of Service)等级由 K8s 根据 Pod 中容器的资源设置自动划分
# 影响 OOM 时的 Kill 优先级:BestEffort > Burstable > Guaranteed

# 1. Guaranteed(保证级)—— 最高优先级,最后被 Kill
#    条件:Pod 中的每个容器都必须同时满足:
#    - 设置了 Memory Requests 和 Memory Limits
#    - 设置了 CPU Requests 和 CPU Limits
#    - Requests == Limits(内存和 CPU 都要等值)
spec:
  containers:
  - name: app
    resources:
      requests:
        cpu: "500m"
        memory: "512Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"

# 2. Burstable(弹性级)—— 中等优先级
#    条件:至少有一个容器设置了 Requests(CPU 或内存)
#    但至少有一个容器不满足 Guaranteed 条件
spec:
  containers:
  - name: app
    resources:
      requests:
        memory: "256Mi"
      limits:
        memory: "512Mi"   # Requests != Limits,所以是 Burstable

# 3. BestEffort(尽力级)—— 最低优先级,最先被 Kill
#    条件:没有任何容器设置 Requests 或 Limits
#    Pod 在资源紧张时最先被驱逐
#    生产环境强烈不建议使用!

# 查看 Pod QoS 等级:
kubectl get pod <pod-name> -o jsonpath='{.status.qosClass}'
kubectl describe pod <pod-name> | grep "QoS Class"

# OOM Score 计算(简化):
# Guaranteed: 低 oom_score,最后被杀
# Burstable: 中等 oom_score,按超出 Requests 比例 Kill
# BestEffort: 高 oom_score,最先被杀
  1. OOMKilled 是什么原因导致的?怎么排查?
# OOMKilled 是什么?
# 容器的内存使用超过了 Memory Limits,Linux 内核的 OOM Killer 终止了容器进程
# exit code = 137(128 + SIGKILL 信号 9)

# 常见原因:
# 1. Memory Limits 设置太小,实际内存需求超过限制
# 2. 内存泄漏(应用 bug 导致内存持续增长)
# 3. 突发流量导致内存飙升
# 4. 没设置 Memory Limits(但节点内存紧张时仍可能被内核 Kill)

# 排查思路:

# 步骤 1:确认 OOMKilled
kubectl describe pod <pod-name> | grep -A5 "State"
# 输出:State: Terminated, Reason: OOMKilled, Exit Code: 137

# 步骤 2:查看 Pod 事件
kubectl get events --field-selector involvedObject.name=<pod-name> -n <ns>

# 步骤 3:查看历史资源使用
kubectl top pod <pod-name> -n <ns>
# 如果 Pod 已不存在,查看历史指标(Prometheus/Grafana)

# 步骤 4:查看容器日志(最后时刻)
kubectl logs <pod-name> -n <ns> --previous    # 查看上一个容器的日志

# 步骤 5:检查资源配置是否合理
kubectl get pod <pod-name> -o yaml | grep -A10 resources

# 步骤 6:使用 PromQL 分析(如有 Prometheus)
# container_memory_working_set_bytes{pod="<pod-name>"}
# 查看内存使用趋势,找到泄漏或突增时间点

# 解决方案:
# 1. 调大 Memory Limits
# 2. 修复应用内存泄漏
# 3. 使用 HPA 分担负载
# 4. 设置合理的 Requests == Limits(获得 Guaranteed QoS)
# 5. 添加 liveness probe 在内存异常前重启

5.8 健康检查

  1. LivenessProbe、ReadinessProbe、StartupProbe 各有什么作用?区别是什么?
# ==================== 三种探针 ====================

# 1. LivenessProbe(存活探针)
#    检查"容器是否还活着"(是否正常运行)
#    失败 → kubelet 杀掉容器,根据 restartPolicy 重启
#    场景:检测死锁、应用卡住但不退出

# 2. ReadinessProbe(就绪探针)
#    检查"容器是否准备好接收流量"
#    失败 → Pod 从 Service 的 Endpoints 中移除,不再接收流量
#    成功 → 重新加入 Endpoints
#    场景:应用启动慢、依赖服务未就绪、暖机(warmup)

# 3. StartupProbe(启动探针)—— v1.16 beta,v1.20 stable
#    检查"容器应用是否已完成启动"
#    在 StartupProbe 成功之前,LivenessProbe 和 ReadinessProbe 不会执行
#    失败 → 同 LivenessProbe(杀容器重启)
#    场景:启动特别慢的应用(避免 Liveness 过早介入导致误杀)

# ==================== 关键区别 ====================
# | 探针            | 失败后果                        | 典型场景                     |
# |----------------|-------------------------------|-----------------------------|
# | LivenessProbe  | 杀容器 + 重启                  | 死锁、应用卡住               |
# | ReadinessProbe | 从 Endpoints 移除,停止流量    | 应用未就绪、依赖未建连       |
# | StartupProbe   | 杀容器 + 重启                  | 启动慢,保护 Liveness 探针   |

# 时序关系:
# Pod Start → StartupProbe(如有)→ 成功 → LivenessProbe + ReadinessProbe 开始
#                        → 失败 → 杀容器重启(重置计数)

# 生产环境建议:
# 1. 启动慢的应用一定加 StartupProbe
# 2. ReadinessProbe 的 failureThreshold 可以比 LivenessProbe 高
#    (允许临时不健康但不杀 Pod)
# 3. LivenessProbe 不要依赖外部服务(避免级联故障)
  1. 探针的三种检测方式(httpGet、exec、tcpSocket)各适用于什么场景?
# 1. httpGet —— 最常用
#    向容器的指定路径发起 HTTP GET 请求,2xx/3xx 状态码 = 健康
#    场景:Web 服务、API 服务、带 HTTP 接口的任何服务
spec:
  containers:
  - name: nginx
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:          # 可选自定义 Header
        - name: X-Custom-Header
          value: Awesome
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080

# 2. tcpSocket —— 网络层检测
#    对指定端口发起 TCP 连接,连接成功 = 健康(不进行三次握手之外的操作)
#    场景:没有 HTTP 接口的服务(如数据库、Redis、gRPC)
spec:
  containers:
  - name: redis
    livenessProbe:
      tcpSocket:
        port: 6379
      initialDelaySeconds: 15

# 3. exec —— 自定义命令检测
#    在容器内执行命令,命令返回码 0 = 健康
#    场景:复杂的健康检查逻辑、gRPC 服务(grpc_health_probe)、文件检查
spec:
  containers:
  - name: app
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
    readinessProbe:
      exec:
        command:
        - /usr/local/bin/grpc_health_probe
        - -addr=:50051

# 通用配置参数:
#   initialDelaySeconds: 启动后等 N 秒才开始探测(有 StartupProbe 后可省略)
#   periodSeconds:       每隔 N 秒探测一次
#   timeoutSeconds:      单次探测超时时间
#   failureThreshold:    连续失败 N 次才算失败
#   successThreshold:    连续成功 N 次才算成功
  1. ReadinessProbe 和 LivenessProbe 的失败分别会导致什么结果?
# ReadinessProbe 失败的结果:
# 1. Pod 的 Ready 状态变为 False
# 2. Pod 从对应 Service 的 Endpoints 中移除(不接收流量)
# 3. 容器不会重启!!!Pod 继续运行
# 4. 恢复后(连续成功 successThreshold 次),重新加入 Endpoints 接收流量
# 关键理解:Readiness 失败不代表容器有问题,只是"暂时不适合接收流量"

# LivenessProbe 失败的结果:
# 1. kubelet 认定容器不健康
# 2. 杀掉该容器(发送 SIGTERM → 等 terminationGracePeriodSeconds → SIGKILL)
# 3. 根据 Pod 的 restartPolicy 创建新容器(Always / OnFailure)
# 4. restartCount +1,容器重新经历 Startup → Readiness 流程
# 关键理解:Liveness 失败是致命的,意味容器无法自愈,只能重启

# 两者失败的链式影响:
# Readiness 失败:只有流量层受影响(Service 不会转发给不 ready 的 Pod)
# Liveness 失败:容器被重启,所有状态丢失,若有 PVC 则数据可保留

# 示例:不当配置导致的级联故障
# 如果 LivenessProbe 检查数据库连接:
#   数据库短暂故障 → 所有 Pod LivenessProbe 失败 → 全部重启
#   → 重启后仍需连接数据库 → 可能雪崩
# 正确做法:Liveness 检查进程存活,Readiness 检查数据库连接
  1. initialDelaySecondsperiodSecondsfailureThreshold 各控制什么?
# 探针通用配置参数说明

# initialDelaySeconds(初始延迟秒数)
# 容器启动后等待多少秒才开始第一次探测
# 用来给应用启动留时间(避免启动未完成就探测导致失败)
# 默认 0 秒
# 如果有 StartupProbe,Liveness/Readiness 的此参数可设小或不设

# periodSeconds(探测间隔秒数)
# 两次探测之间的间隔时间
# 默认 10 秒
# 太短:增加系统负载;太长:故障检测延迟大

# timeoutSeconds(超时秒数)
# 单次探测的超时时间
# 默认 1 秒
# httpGet/tcpSocket/exec 如果超过此时间未响应,视为失败

# failureThreshold(失败阈值)
# 连续多少次失败后,才认定探测失败
# LivenessProbe:默认 3 次 → 连续失败 3 次才重启容器
# ReadinessProbe:默认 3 次 → 连续失败 3 次才从 Endpoints 移除
# 实际触发时间 ≈ periodSeconds × failureThreshold

# successThreshold(成功阈值)
# 连续多少次成功后,才认定探测成功/恢复
# LivenessProbe:默认 1 次(固定不可改)
# ReadinessProbe:默认 1 次

# 配置示例(生产环境推荐):
spec:
  containers:
  - name: app
    startupProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 0     # 立即开始探测启动状态
      periodSeconds: 5           # 每 5 秒探测一次
      failureThreshold: 20       # 最多等 5×20=100 秒启动
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      periodSeconds: 10          # 每 10 秒探测
      timeoutSeconds: 3          # 3 秒超时
      failureThreshold: 3        # 连续 3 次失败即重启(30s 内)
    readinessProbe:
      httpGet:
        path: /ready
        port: 8080
      periodSeconds: 5           # 每 5 秒探测
      failureThreshold: 2        # 连续 2 次失败就摘流量(快速摘除)

5.9 自动扩缩容

  1. HPA(Horizontal Pod Autoscaler)的原理是什么?支持哪些指标?
# HPA(Horizontal Pod Autoscaler)原理
# 1. HPA Controller 定期(默认 15s)从 Metrics API 获取 Pod 指标
# 2. 计算当前指标值和目标值的比率:desiredReplicas = ceil(currentReplicas * (currentMetricValue / targetMetricValue))
# 3. 根据计算结果自动调整 Deployment/StatefulSet/ReplicaSet 的副本数
# 4. 有缩容冷却窗口(默认 5min)和扩容冷却窗口(默认 3min,但 3min 内可继续扩)

# HPA 支持的指标:
# 1. Resource Metrics(资源指标)
#    CPU 使用率、Memory 使用率(基于 requests 的百分比)
spec:
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70    # 目标 CPU 使用率 70%

# 2. Pod Metrics(自定义 Pod 指标)
#    Pod 级别的自定义指标(如 QPS、请求延迟)
spec:
  metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "100"       # 每个 Pod 平均 100 QPS

# 3. Object Metrics(对象指标)
#    K8s 对象级别的指标(如 Ingress 的 QPS)
spec:
  metrics:
  - type: Object
    object:
      metric:
        name: requests-per-second
      describedObject:
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        name: main-ingress
      target:
        type: Value
        value: "1000"

# 4. External Metrics(外部指标)
#    集群外部的指标(如云负载均衡的 QPS、消息队列长度)
spec:
  metrics:
  - type: External
    external:
      metric:
        name: queue_messages_ready
        selector:
          matchLabels:
            queue: worker-queue
      target:
        type: AverageValue
        averageValue: "30"

# 创建 HPA:
kubectl autoscale deployment nginx --cpu-percent=70 --min=2 --max=10
# 完整 YAML:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: nginx
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  1. VPA 和 HPA 的区别?能不能同时开启?
# HPA(Horizontal Pod Autoscaler)—— 横向扩缩
# 调整 Pod 副本数量(replicas)
# 场景:无状态服务(Web/API),增加实例数分担负载
# 公式:副本数 × 单 Pod 容量 = 总容量

# VPA(Vertical Pod Autoscaler)—— 纵向扩缩
# 调整 Pod 的资源 Requests/Limits(不改变副本数)
# 场景:有状态服务(数据库)、单体应用
# 三种模式:
#   - Off: 只推荐值,不执行
#   - Initial: 只在 Pod 创建时设值
#   - Auto: 自动调整并重启 Pod

# HPA vs VPA 对比
# | 特性       | HPA                   | VPA                 |
# |-----------|----------------------|---------------------|
# | 调整对象   | 副本数               | CPU/内存 Requests/Limits |
# | 对应用影响 | 几乎无感知(水平扩)  | 需要重启 Pod          |
# | 适用架构   | 分布式/无状态         | 有状态/单体           |
# | 弹性速度   | 快(秒级)           | 慢(分级别)          |
# | 成熟度     | GA                   | Beta                |

# 能不能同时开启 HPA 和 VPA?
# 可以,但有限制:
#  1. HPA 用 Resource Metrics(CPU/Mem),VPA 不可也用 CPU/Mem 的 Auto 模式
#     → 因为 VPA 修改了 Requests 后,HPA 的利用率计算基准会变化,互相干扰
#  2. 推荐组合:HPA(资源指标)+ VPA(自定义指标,仅推荐 Off 模式)
#  3. 或者:HPA 用 CPU/Mem,VPA 用 Off 模式只生成推荐值,人工审批执行
#  4. 社区有 KEDA(事件驱动)+ HPA 替代 VPA 的趋势

# 查看 VPA 推荐值(如已安装):
kubectl get vpa <vpa-name> -o yaml | grep -A10 recommendation
  1. HPA 的缩容稳定窗口期默认是多少?扩容冷却时间呢?
# HPA 关键时间参数(可通过 behavior 字段自定义)

# 缩容稳定窗口期(Downscale Stabilization Window)
# 默认:5 分钟(300 秒)
# 含义:HPA 在缩容前回顾过去 5 分钟的指标推荐值,取"最大值"作为实际缩容目标
# 作用:防止指标短期波动导致频繁缩容→扩容(抖动)
# 举例:突然流量下降 → 等 5 分钟确认 → 确实持续低 → 才缩容

# 扩容冷却时间(Upscale Cooldown)
# 默认:0 秒(无冷却,但扩容后有 3 分钟的隐性稳定期)
# 含义:上次扩容后至少过 3 分钟才能再次扩容
# 作用:给新 Pod 的指标一个稳定的窗口期

# 缩容冷却时间(Downscale Cooldown)
# 默认:5 分钟
# 含义:上次缩容后至少过 5 分钟才能再次缩容

# 通过 behavior 自定义(k8s v1.18+):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300   # 缩容稳定窗口(默认 300)
      policies:
      - type: Percent
        value: 10                        # 每 60 秒最多缩 10%
        periodSeconds: 60
      - type: Pods
        value: 2                         # 每 60 秒最多缩 2 个 Pod
        periodSeconds: 60
      selectPolicy: Min                  # 选限制更严格的(缩更少)
    scaleUp:
      stabilizationWindowSeconds: 0      # 扩容稳定窗口(默认 0)
      policies:
      - type: Percent
        value: 100                       # 每 60 秒最多扩 100%(翻倍)
        periodSeconds: 60
      - type: Pods
        value: 4                         # 每 60 秒最多扩 4 个
        periodSeconds: 60
      selectPolicy: Max                  # 选限制更宽松的(扩更多)
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

5.10 认证授权

  1. Kubernetes 中的用户分为哪两类?(普通用户 vs 服务账号)
# Kubernetes 用户分为两大类:

# 1. 普通用户(Normal User / Human User)
#    - 人类操作者(管理员、开发者)
#    - 由外部身份系统管理(K8s 本身不管理用户)
#    - 认证方式:X509 客户端证书、Bearer Token、OIDC、Webhook
#    - 没有对应的 K8s API 对象(不能用 kubectl get users)
#    常见管理方式:
#    - 静态 Token 文件(--token-auth-file)
#    - X509 证书(通过 kubeconfig 使用)
#    - OpenID Connect(OIDC)集成(对接 LDAP/AD)
#    - Webhook 认证(自定义认证服务)

# 2. 服务账号(ServiceAccount)
#    - Pod 内运行的应用程序使用(非人类身份)
#    - 由 K8s API 管理(有对应的 API 对象)
#    - 存储在 Namespace 内(每个 NS 有默认 default SA)
#    - 认证方式:自动挂载到 Pod 的 Token(/var/run/secrets/kubernetes.io/serviceaccount/token)
#    查看和使用:
kubectl get serviceaccounts -n <ns>
kubectl describe sa default -n <ns>

# | 特性       | 普通用户               | ServiceAccount       |
# |-----------|----------------------|----------------------|
# | 管理方式   | 外部系统              | K8s API              |
# | 范围       | 集群级别              | Namespace 级别        |
# | 对象       | 无 API 对象           | 有 API 对象           |
# | 使用场景   | 人工操作 kubectl      | Pod 内程序调用 API    |
  1. Role 和 ClusterRole 的区别?RoleBinding 和 ClusterRoleBinding 的区别?
# Role vs ClusterRole
# | 特性     | Role                           | ClusterRole                |
# |---------|--------------------------------|----------------------------|
# | 作用域   | Namespace 级别                  | 集群级别                    |
# | 适用资源 | Namespace 资源(Pod/Deploy 等) | 所有资源(含 Node/PV 等集群资源)|
# | 典型场景 | 某 NS 开发者读写权限             | 集群管理员、只读用户           |

# Role 示例(default NS 读写 Pod)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]

# ClusterRole 示例(集群只读)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-reader
rules:
- apiGroups: [""]
  resources: ["*"]
  verbs: ["get", "list", "watch"]

# RoleBinding vs ClusterRoleBinding
# 共同点:都是将角色(Role/ClusterRole)绑定到主体(用户/组/SA)
# 区别:
#   RoleBinding 在 Namespace 内生效,可绑定 Role 或 ClusterRole
#   ClusterRoleBinding 在集群级别生效,只能绑定 ClusterRole

# 重要技巧:用 RoleBinding 绑定 ClusterRole 到某个 Namespace
# 效果:用户在这个 Namespace 获得 ClusterRole 的权限,但在其他 NS 没有
# 场景:用同一个 ClusterRole 模板,通过 RoleBinding 授予不同 NS 的权限
# 好处:不需要在每个 NS 都创建一个同名 Role(复用 ClusterRole)
  1. RBAC 的核心三要素是什么?(Subject、Role/ClusterRole、RoleBinding/ClusterRoleBinding)
# RBAC(Role-Based Access Control)核心三要素

# 1. Subject(主体)—— 谁?
#    被授权的主体:
#    - User(普通用户,X509 CN / Token User)
#    - Group(用户组,X509 O 字段)
#    - ServiceAccount(服务账号)
subjects:
- kind: User
  name: "zhangsan"
  apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
  name: default
  namespace: kube-system

# 2. Resource + Verb(资源 + 操作)—— 能做什么?
#    定义在 Role / ClusterRole 中
#    - Resource: pods, deployments, services, nodes...
#    - Verb: get, list, watch, create, update, patch, delete, deletecollection
#    - APIGroup: "", apps, batch, networking.k8s.io...
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]

# 3. Binding(绑定)—— 在哪些范围?
#    - RoleBinding:Namespace 级别绑定
#      - 将 Role/ClusterRole 绑定到 Subject → 在特定 NS 生效
#    - ClusterRoleBinding:集群级别绑定
#      - 将 ClusterRole 绑定到 Subject → 集群范围生效
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view

# 关联关系:
# Subject(User/SA/Group)
#     + RoleBinding/ClusterRoleBinding(绑定)
#         + Role/ClusterRole(权限定义)
#             → Subject 获得 Role 中定义的权限

# 验证权限:
kubectl auth can-i create deployments --as=zhangsan -n default
kubectl auth can-i get pods --as=system:serviceaccount:default:my-sa
  1. ServiceAccount 是做什么的?Pod 如何使用 SA 的 Token 访问 API Server?
# ServiceAccount 是做什么的?
# SA 是 Pod 在集群内的"身份",让 Pod 内的进程能以特定身份访问 K8s API Server。
# 每个 Namespace 有默认的 default SA。
# SA 由两部分组成:
#   1. SA 对象本身(资源对象)
#   2. 关联的 Secret(Token),用于认证

# Pod 如何使用 SA 的 Token 访问 API Server?

# 方式 1:自动挂载(默认方式,v1.24 前)
# Pod 创建时,kubelet 自动将 SA 的 Token 挂载到 Pod 内
# 挂载路径:/var/run/secrets/kubernetes.io/serviceaccount/
# 文件:
#   token      → JWT Token(认证用)
#   ca.crt     → API Server CA 证书(验证 API Server 身份)
#   namespace  → 当前 Namespace 名

# Pod 内测试访问 API Server:
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
APISERVER=https://kubernetes.default.svc

# 使用 curl 访问 API
curl -s --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" \
  $APISERVER/api/v1/namespaces/$NAMESPACE/pods

# 指定 Pod 使用的 SA:
spec:
  serviceAccountName: my-sa          # 指定 SA(否则用 default)
  automountServiceAccountToken: true # 自动挂载(默认 true)

# 方式 2:绑定 ServiceAccount Token Volume(v1.22+ / v1.24 GA)
# 使用 projected volume 注入有时间限制的 Token(更安全)
spec:
  volumes:
  - name: token
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 3600    # 1 小时后过期

# 方式 3:禁用自动挂载(安全最佳实践)
spec:
  automountServiceAccountToken: false  # 不需要访问 API Server 时禁用
  1. 如何创建一个用户并给他某个 namespace 的只读权限?
# 场景:创建用户 developer,授予 dev namespace 的只读权限

# 步骤 1:生成用户证书(模拟一个新用户)
# 生成私钥
openssl genrsa -out developer.key 2048
# 生成证书签名请求(CSR),CN=用户名,O=组名
openssl req -new -key developer.key -out developer.csr -subj "/CN=developer/O=dev-team"
# 用 K8s CA 签发证书(需要集群 CA 证书和私钥)
openssl x509 -req -in developer.csr \
  -CA /etc/kubernetes/pki/ca.crt \
  -CAkey /etc/kubernetes/pki/ca.key \
  -CAcreateserial -out developer.crt -days 365

# 步骤 2:创建 kubeconfig 文件(配置用户凭据)
kubectl config set-credentials developer \
  --client-certificate=developer.crt \
  --client-key=developer.key \
  --embed-certs=true \
  --kubeconfig=developer.kubeconfig

# 步骤 3:设置上下文(集群 + 用户 + namespace)
kubectl config set-context developer-context \
  --cluster=kubernetes \
  --user=developer \
  --namespace=dev \
  --kubeconfig=developer.kubeconfig

# 步骤 4:创建 Role(dev NS 只读权限)
kubectl create role dev-reader \
  --verb=get,list,watch \
  --resource=pods,deployments,services,configmaps,secrets \
  -n dev
# 或使用 ClusterRole view(K8s 内置只读角色)
# kubectl get clusterrole view -o yaml

# 步骤 5:绑定 Role 到用户(RoleBinding)
kubectl create rolebinding dev-reader-binding \
  --role=dev-reader \
  --user=developer \
  -n dev
# 或者绑定内置 view ClusterRole(更简便)
kubectl create rolebinding dev-view-binding \
  --clusterrole=view \
  --user=developer \
  -n dev

# 步骤 6:验证权限
kubectl auth can-i get pods --as=developer -n dev        # yes
kubectl auth can-i create pods --as=developer -n dev     # no
kubectl auth can-i get pods --as=developer -n kube-system # no

# 步骤 7:将 kubeconfig 交给用户
# developer.kubeconfig 文件可直接使用
# kubectl --kubeconfig=developer.kubeconfig get pods -n dev

# 快捷方式(使用已有用户证书):
kubectl config view --kubeconfig=developer.kubeconfig

# 生产环境建议:使用 OIDC(对接 LDAP/AD)而非证书认证,更易管理
  1. X509 客户端证书认证的流程是怎样的?
# X509 客户端证书认证流程

# 1. 用户创建证书签名请求(CSR)
#    CN(Common Name)= 用户名
#    O(Organization)= 用户组
openssl req -new -key user.key -out user.csr -subj "/CN=zhangsan/O=dev-team"

# 2. 集群 CA 签发证书
openssl x509 -req -in user.csr -CA /etc/kubernetes/pki/ca.crt \
  -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial \
  -out user.crt -days 365

# 3. 用户使用签发后的证书访问 API Server
#    curl 方式:
curl https://<apiserver>:6443/api/v1/pods \
  --cert user.crt --key user.key --cacert ca.crt

# 4. API Server 认证过程:
#    a) 验证客户端证书是否由受信任的 CA 签发(验证证书链)
#    b) 检查证书是否在有效期内(NotBefore ~ NotAfter)
#    c) 提取 CN 作为用户名、O 作为用户组
#    d) 认证通过后,将用户名和组传递给授权模块(RBAC 检查)

# 5. 授权(RBAC 检查):
#    根据 CN(用户名/O(用户组)查找对应的 RoleBinding/ClusterRoleBinding
#    确定用户是否有权限执行该操作

# 证书认证的优缺点:
# 优点:证书由集群 CA 签发,安全可靠,无需额外组件
# 缺点:
#   - 难以吊销(除非 CRL/OCSP,K8s 支持有限)
#   - 无法动态管理用户(需重新签发证书)
#   - 大规模用户管理困难
#   - 证书过期后需重新签发和分发

# 查看 API Server 启动参数:
# --client-ca-file=/etc/kubernetes/pki/ca.crt      # 客户端证书的 CA

# 生产环境建议:X509 适用于小组集群或 CI/CD;大规模推荐 OIDC

5.11 集群搭建与管理

  1. 用 kubeadm 部署 K8s 集群的完整流程?(7 个关键步骤)
# ==================== 用 kubeadm 部署 K8s 集群(完整流程) ====================

# === 所有节点(Master + Worker) ===

# 1. 系统基础配置
# 关闭 swap
swapoff -a
sed -i '/swap/d' /etc/fstab

# 关闭 SELinux(或设为 permissive)
setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

# 关闭防火墙(或开放必要端口)
systemctl stop firewalld && systemctl disable firewalld

# 配置内核参数
cat <<EOF > /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables=1
net.bridge.bridge-nf-call-ip6tables=1
net.ipv4.ip_forward=1
EOF
sysctl --system

# 2. 安装容器运行时(containerd)
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y containerd.io
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
# 修改 SystemdCgroup = true(cgroup 驱动一致)
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd

# 3. 安装 kubeadm、kubelet、kubectl
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF
yum install -y kubeadm-1.28.0 kubelet-1.28.0 kubectl-1.28.0
systemctl enable kubelet   # 先 enable,等 init 后会自动启动

# === Master 节点 ===

# 4. kubeadm init(初始化集群)
kubeadm init \
  --apiserver-advertise-address=10.1.8.10 \
  --image-repository registry.aliyuncs.com/google_containers \
  --pod-network-cidr=10.244.0.0/16 \
  --kubernetes-version=v1.28.0

# 5. 配置 kubectl(管理员使用)
mkdir -p $HOME/.kube
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config

# 6. 安装 CNI 网络插件
# Flannel:
kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
# 或 Calico:
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.26.0/manifests/calico.yaml

# === Worker 节点 ===

# 7. 加入集群(使用 init 输出的 join 命令)
kubeadm join 10.1.8.10:6443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash>

# === 验证集群 ===
kubectl get nodes
kubectl get pods -A
# 所有节点 Ready,所有 Pod Running,集群部署完成
  1. kubeadm init 之前需要做哪些前置环境准备?
# kubeadm init 前置环境准备清单(所有节点)

# 1. 主机名和 hosts
hostnamectl set-hostname master01   # 每节点不同
cat >> /etc/hosts <<EOF
10.1.8.10 master01
10.1.8.11 worker01
10.1.8.12 worker02
EOF

# 2. 关闭 swap(必须)
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
# kubelet 要求关闭 swap(v1.28+ 开始支持 swap 但有限制)

# 3. 关闭 SELinux
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

# 4. 关闭防火墙或开放端口
# 控制面:6443, 2379-2380, 10250, 10259, 10257
# Worker:10250, 30000-32767
# 简单方式(测试环境):
systemctl stop firewalld && systemctl disable firewalld

# 5. 内核参数配置
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system

# 6. 加载内核模块(containerd 需要)
cat <<EOF | tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter

# 7. 安装容器运行时(containerd)
yum install -y containerd.io
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
# 关键:确保 cgroup 驱动为 systemd
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl enable --now containerd
systemctl status containerd

# 8. 安装 kubeadm / kubelet / kubectl
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
EOF
yum install -y kubeadm kubelet kubectl
systemctl enable kubelet

# 9. 时间同步
yum install -y chrony
systemctl enable --now chronyd
chronyc sources -v

# 10. 验证
lsmod | grep br_netfilter
sysctl net.bridge.bridge-nf-call-iptables
crictl info | grep -i cgroup
kubeadm version
  1. kubeadm token create --print-join-command 什么时候用?
# kubeadm token create --print-join-command
# 作用:生成 Worker 节点加入集群的完整命令(含 token 和证书 hash)
# 在 Master 节点上执行

# 使用场景:

# 1. kubeadm init 时未保存 join 命令
#    init 输出的 join 命令丢失了,重新生成
kubeadm token create --print-join-command

# 2. Token 过期(默认 24 小时)
#    之前的 Token 已过期,需要创建新 Token 让节点加入
kubeadm token create --print-join-command
#    Token 有效期管理:
kubeadm token list                        # 查看所有 Token
kubeadm token create --ttl 0              # 创建永不过期的 Token
kubeadm token delete <token-id>           # 删除指定 Token

# 3. 扩容集群(新增 Worker 节点)
#    在新 Worker 上执行生成的命令即可

# 4. Master 节点也加入集群
#    kubeadm init 后,如果需要添加额外的控制面节点:
kubeadm token create --print-join-command \
  --certificate-key <key>                 # 加控制面证书密钥

# 输出示例:
# kubeadm join 10.1.8.10:6443 --token abcdef.0123456789abcdef \
#   --discovery-token-ca-cert-hash sha256:1234567890abcdef...

# 注意事项:
#   - Token 格式:<6位>.<16位>(如 abcdef.0123456789abcdef)
#   - --discovery-token-ca-cert-hash:确保加入的节点连接到正确的 Control Plane
#   - 获取 CA 证书 hash 的备用方法:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \
  openssl rsa -pubin -outform der 2>/dev/null | \
  openssl dgst -sha256 -hex | sed 's/^.* //'
  1. etcd 备份怎么做?如何恢复?
# ==================== etcd 备份 ====================

# 方法 1:使用 etcdctl snapshot save(推荐)
# 设置 etcd 连接参数
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d-%H%M%S).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# 验证备份文件
ETCDCTL_API=3 etcdctl --write-out=table snapshot status /backup/etcd-snapshot-xxx.db

# 方法 2:kubeadm 编排备份(如果 etcd 是 kubeadm 部署的 Pod)
# 先查看 etcd Pod 信息
kubectl -n kube-system get pod -l component=etcd
# etcdctl 在 etcd 容器内执行
kubectl -n kube-system exec etcd-master01 -- etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  --endpoints=https://127.0.0.1:2379 \
  snapshot save /var/lib/etcd/snapshot.db

# 方法 3:定时备份 CronJob
# 创建 CronJob 定期备份 etcd 到持久存储/S3

# ==================== etcd 恢复 ====================

# 恢复流程(需在所有 Master 节点停止 etcd/kube-apiserver)

# 步骤 1:停止 kube-apiserver 和 etcd(所有 Master 节点)
# kubeadm 部署的集群:
mkdir /tmp/etcd-backup
mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/etcd-backup/
mv /etc/kubernetes/manifests/etcd.yaml /tmp/etcd-backup/
# 等待 kubelet 自动停止这些静态 Pod

# 步骤 2:从备份恢复 etcd(在一台 Master 上执行)
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-snapshot-xxx.db \
  --name=master01 \
  --data-dir=/var/lib/etcd-restore \
  --initial-cluster=master01=https://10.1.8.10:2380 \
  --initial-advertise-peer-urls=https://10.1.8.10:2380

# 步骤 3:更新 etcd 数据目录
mv /var/lib/etcd /var/lib/etcd.bak
mv /var/lib/etcd-restore /var/lib/etcd
chown -R etcd:etcd /var/lib/etcd

# 步骤 4:恢复 etcd 和 kube-apiserver 的 manifest
mv /tmp/etcd-backup/kube-apiserver.yaml /etc/kubernetes/manifests/
mv /tmp/etcd-backup/etcd.yaml /etc/kubernetes/manifests/
# kubelet 会自动重新启动 etcd 和 kube-apiserver

# 步骤 5:验证恢复
kubectl get nodes
kubectl get pods -A

# 关键注意事项:
# 1. 备份文件中包含集群所有数据,务必安全存储
# 2. 恢复会覆盖当前所有集群状态(不可逆)
# 3. 备份恢复后,原有 PVC/PV/Secret 等资源仍存在(etcd 中),
#    但 Pod 等运行时对象重新创建
# 4. 建议定期备份 + 验证备份可恢复性
  1. 节点 NotReady 了怎么排查?
# 节点 NotReady 排查(完整思路)

# 步骤 1:查看节点状态和条件
kubectl describe node <node-name>
# 重点关注 Conditions 部分:
#   - MemoryPressure(内存压力)
#   - DiskPressure(磁盘压力)
#   - PIDPressure(PID 压力)
#   - NetworkUnavailable(网络不可用)
#   - Ready(节点就绪状态,True/False/Unknown)
# 哪个 Condition 为 False 或报错,就是根因方向

# 步骤 2:检查 kubelet 状态
# SSH 到问题节点
systemctl status kubelet
journalctl -u kubelet -f --no-pager  # 实时查看日志
journalctl -u kubelet --since "10 min ago" --no-pager

# 常见 kubelet 问题:
# - kubelet 进程挂了 / OOM
# - 证书过期
# - 磁盘空间不足(kubelet 无法写入)
# - 与 API Server 通信失败

# 步骤 3:检查容器运行时
systemctl status containerd   # 或 docker
crictl ps                     # 查看运行中的容器
crictl info                   # 运行时信息

# 步骤 4:检查磁盘空间
df -h
# 特别关注:/var(镜像、容器存储)、/(根分区)
du -sh /var/lib/containerd/
du -sh /var/log/
# 清理:crictl rmi --prune  /  journalctl --vacuum-size=500M

# 步骤 5:检查网络
# 节点能否 ping 通 API Server
ping <apiserver-ip>
# CNI 插件是否正常
kubectl -n kube-system get pods -l k8s-app=calico-node -o wide
# 查看 CNI 相关 Pod 日志

# 步骤 6:检查节点资源
kubectl top node <node-name>   # CPU/内存使用
free -h                        # 系统内存

# 步骤 7:未知原因,尝试重启 kubelet
systemctl restart kubelet

# 步骤 8:极端情况 — 移除节点并重新加入
kubectl drain <node-name> --ignore-daemonsets
kubectl delete node <node-name>
# 在节点上重置 kubeadm
kubeadm reset -f
# 重新加入集群
kubeadm join <master-ip>:6443 --token ... --discovery-token-ca-cert-hash ...

5.12 排错实战

  1. Pod 一直 Pending 怎么排查?
# Pod Pending 排查(调度失败或资源不足)

# 步骤 1:看 Pod 详情和 Events
kubectl describe pod <pod-name> -n <ns>
# 重点看 Events 部分,一般会明确指出原因

# 步骤 2:常见原因及解决方案

# 原因 1:资源不足(CPU/内存不足)
# Events: "0/3 nodes are available: insufficient cpu"
# 解决:减少 requests、扩容节点、删除不用的 Pod
kubectl top nodes
kubectl describe nodes | grep -A5 "Allocated resources"

# 原因 2:nodeSelector / nodeAffinity 不匹配
# Events: "0/3 nodes are available: node(s) didn't match node selector"
# 解决:检查 Pod 的 nodeSelector 是否与 Node Labels 匹配
kubectl get nodes --show-labels

# 原因 3:Taint 排斥
# Events: "0/3 nodes are available: node(s) had taint..."
# 解决:添加 Toleration 或移除 Taint
kubectl describe nodes | grep Taint
kubectl taint nodes <node-name> key=value:NoSchedule-

# 原因 4:PVC 无法绑定
# Events: "persistentvolumeclaim 'xxx' not found" 或 "pod has unbound immediate PVC"
# 解决:检查 PVC/PV/StorageClass 状态
kubectl get pvc -n <ns>
kubectl get pv
kubectl get storageclass

# 原因 5:端口冲突(hostPort 已被占用)
# Events: "host port is already allocated"
# 解决:检查同节点其他 Pod 的 hostPort

# 原因 6:镜像问题(ImagePullBackOff → 但可能仍显示 Pending)
# Events: 可能是镜像拉取失败
kubectl describe pod <pod-name> | grep -A5 "Failed"

# 原因 7:PodAntiAffinity 限制
# Events: "node(s) didn't match pod anti-affinity rules"

# 原因 8:PriorityClass 抢占
# 低优先级 Pod 被高优先级 Pod 抢占了节点

# 步骤 3:全局视图
kubectl get events -n <ns> --sort-by='.lastTimestamp'
kubectl get pods -n <ns> -o wide
  1. Pod CrashLoopBackOff 怎么排查?
# CrashLoopBackOff 含义
# 容器启动后很快崩溃退出,kubelet 重复重启,每次重启间隔以指数级增长
# (10s → 20s → 40s → ... → 最多 5 分钟)

# 排查步骤:

# 步骤 1:看 Pod 状态和重启次数
kubectl get pod <pod-name> -n <ns>
kubectl describe pod <pod-name> -n <ns> | grep -E "State|Restart|Exit|Reason|OOM"

# 步骤 2:看容器日志(包括上一个已崩溃的容器)
kubectl logs <pod-name> -n <ns> --previous   # 上一个崩溃容器的日志
kubectl logs <pod-name> -n <ns> --tail=100   # 当前容器日志
kubectl logs <pod-name> -n <ns> -f           # 实时跟踪

# 步骤 3:常见原因及排查

# 原因 1:应用启动失败(配置错误 / 依赖缺失)
# 日志关键词:connection refused / file not found / config parse error
# 解决:检查 ConfigMap、Secret、环境变量是否正确

# 原因 2:OOMKilled(内存不足)
# describe 输出:Reason: OOMKilled, Exit Code: 137
# 解决:增加 Memory Limits 或排查内存泄漏

# 原因 3:探针配置不当(Liveness/Startup Probe 失败)
# describe 输出:Liveness probe failed: ...
# 解决:调整 initialDelaySeconds、periodSeconds、failureThreshold

# 原因 4:启动命令/参数错误
# 容器启动命令本身有问题
# 解决:检查 command/args 配置,可手动测试
kubectl run test --rm -it --image=<same-image> --command -- /bin/sh

# 原因 5:权限问题
# 日志关键词:permission denied
# 解决:调整 securityContext 或容器内用户

# 原因 6:外部依赖不可用
# 日志关键词:timeout / connection refused
# 解决:检查数据库、缓存等外部服务连通性

# 步骤 4:临时禁止重启,进入调试模式
kubectl debug <pod-name> -n <ns> --image=busybox --target=<container-name>
# 或者修改 Pod 命令为 sleep infinity 保持运行
kubectl edit pod <pod-name> -n <ns>
# 将 command 改为:["sleep", "3600"]
  1. ImagePullBackOff 怎么排查?
# ImagePullBackOff 含义
# kubelet 尝试拉取镜像失败,并进入指数退避重试(BackOff)
# 常见错误:ImagePullBackOff / ErrImagePull / InvalidImageName

# 排查步骤:

# 步骤 1:查看详细错误原因
kubectl describe pod <pod-name> -n <ns> | grep -A10 "Events"
# 关键信息:
#   - Failed to pull image ... → 拉取失败
#   - pull access denied → 权限问题
#   - repository does not exist → 镜像不存在
#   - connection refused / no such host → 网络问题

# 步骤 2:常见原因及解决

# 原因 1:镜像名或 Tag 错误
# Events: "manifest for xxx:latest not found" 或 "not found"
# 解决:确认镜像名和 Tag 在镜像仓库中存在
# 检查:docker pull <image>  /  crictl pull <image>

# 原因 2:私有仓库认证问题
# Events: "pull access denied" 或 "authentication required"
# 解决:创建 imagePullSecrets
kubectl create secret docker-registry regcred \
  --docker-server=my-registry.com \
  --docker-username=admin \
  --docker-password=pass123 \
  -n <ns>
# 在 Pod 中引用
spec:
  imagePullSecrets:
  - name: regcred
# 或添加到 ServiceAccount
kubectl patch serviceaccount default -n <ns> \
  -p '{"imagePullSecrets": [{"name": "regcred"}]}'

# 原因 3:网络问题(无法连接到镜像仓库)
# Events: "dial tcp: lookup xxx: no such host" 或 "timeout"
# 解决:检查节点网络、DNS、防火墙
#   从节点测试:crictl pull docker.io/library/nginx:latest
#   检查 /etc/resolv.conf
#   检查 /etc/containerd/config.toml 中的 mirror 配置

# 原因 4:镜像仓库宕机或限流
# Docker Hub 免费账户有拉取速率限制
# 解决:配置镜像代理/缓存(如 Harbor)、使用私有仓库

# 原因 5:containerd 配置问题
# containerd 的 registry mirror 配置不正确
# 解决:检查 /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
    endpoint = ["https://registry-1.docker.io"]

# 步骤 3:手动在节点上拉取测试
crictl pull <image-name>
echo $?  # 检查返回码
crictl images | grep <image-name>

# 步骤 4:查看 kubelet 日志
journalctl -u kubelet --since "5 min ago" | grep -i pull

# 步骤 5:检查磁盘空间
df -h /var/lib/containerd   # 镜像存储路径
# 磁盘满也会导致镜像拉取失败
  1. kubectl 连接 API Server 报 connection refused 怎么排查?
# kubectl 连接 API Server 报 connection refused 排查

# 现象:
# The connection to the server <ip>:6443 was refused - did you specify the right host or port?

# 步骤 1:检查 API Server 是否运行
# kubeadm 部署的(静态 Pod):
ssh <master-node>
crictl ps | grep kube-apiserver
# 或查看文件是否存在
ls -la /etc/kubernetes/manifests/kube-apiserver.yaml

# 步骤 2:检查 API Server 进程/容器日志
# 容器运行时方式查看
crictl logs <kube-apiserver-container-id>
# 或查看 kubelet 日志
journalctl -u kubelet --since "5 min ago" | grep apiserver

# 步骤 3:检查端口是否监听
ss -tlnp | grep 6443
# 如果端口没监听 → API Server 没启动
# 如果监听在 127.0.0.1:6443 → 检查 --bind-address 参数

# 步骤 4:检查 kubeconfig 配置
kubectl cluster-info
cat ~/.kube/config | grep server
# 确认 server 地址正确

# 步骤 5:检查网络连通性(从 kubectl 客户端到 API Server)
ping <apiserver-ip>
telnet <apiserver-ip> 6443
nc -zv <apiserver-ip> 6443
curl -k https://<apiserver-ip>:6443/healthz

# 步骤 6:API Server 自身原因排查
# - etcd 不可用 → API Server 无法启动
ETCDCTL_API=3 etcdctl endpoint health --endpoints=...
# - 证书过期
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates
kubeadm certs check-expiration
# - CPU/内存不足导致被 OOM Kill
# 查看节点资源:kubectl top node <master>

# 步骤 7:负载均衡器问题(如有外部 LB)
# 如果经过 LB(如 HAProxy / Nginx)访问
# 检查 LB 后端健康检查、LB 配置

# 步骤 8:防火墙/安全组
iptables -L -n | grep 6443
firewall-cmd --list-all

# 步骤 9:重建 API Server(最后手段 + 需谨慎)
# 检查并修正 /etc/kubernetes/manifests/kube-apiserver.yaml
# kubelet 会自动检测并重启该 Pod
  1. Service 不通怎么排查?(DNS → Endpoints → kube-proxy → Pod)
# Service 不通完整排查链路:DNS → Endpoints → kube-proxy → iptables/IPVS → Pod

# ====== 1. 确认 Service 和 Endpoints ======
kubectl get svc <svc-name> -n <ns> -o wide
kubectl get endpoints <svc-name> -n <ns>
# Endpoints 为空 → 检查 Selector 是否匹配 Pod 的 Labels
kubectl describe svc <svc-name> -n <ns> | grep Selector
kubectl get pods -n <ns> -l <selector-label> --show-labels

# ====== 2. 检查 Pod 就绪状态 ======
kubectl get pods -n <ns> -l <selector-label>
# READY 列:1/1 才是就绪,0/1 说明 ReadinessProbe 未通过
kubectl describe pod <pod-name> -n <ns> | grep -A5 Readiness

# ====== 3. 检查 DNS 解析 ====
# 从测试 Pod 中测试 DNS
kubectl run dns-test --rm -it --image=busybox:1.28 -- nslookup <svc-name>.<ns>.svc.cluster.local
# DNS 解析不成功:
# a) 检查 CoreDNS Pod
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50
# b) 检查 Service 的 ClusterIP 是否能解析
# c) 测试 Pod 的 /etc/resolv.conf 是否正确
kubectl exec <test-pod> -- cat /etc/resolv.conf

# ====== 4. 从 Pod 内直接测试 Service IP ======
kubectl run curl-test --rm -it --image=curlimages/curl -- sh
# 在容器内执行:
curl -v http://<cluster-ip>:<port>
curl -v http://<svc-name>.<ns>:<port>

# ====== 5. 检查 kube-proxy 规则 ======
# 到发生问题的 Node 上检查
# iptables 模式:
iptables-save | grep <cluster-ip>
iptables -t nat -L KUBE-SERVICES -n | grep <cluster-ip>
iptables -t nat -L KUBE-SVC-XXX -n    # 查看后端 Pod 转发链

# IPVS 模式:
ipvsadm -L -n | grep <cluster-ip>
# 如果规则没用 → kube-proxy 有问题
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=100

# ====== 6. 检查 Service 端口和 TargetPort ======
kubectl describe svc <svc-name> -n <ns>
# 确认:port(Service端口)→ targetPort(容器端口)→ containerPort 一致
# 确认容器内实际监听端口
kubectl exec <pod-name> -n <ns> -- netstat -tlnp

# ====== 7. 检查 NetworkPolicy ======
kubectl get networkpolicy -n <ns>
# 有 NetworkPolicy 时默认拒绝,必须显示允许

# ====== 8. 测试 Pod 直连 ======
# 绕过 Service,直接访问 Pod IP:Port
kubectl run curl-direct --rm -it --image=curlimages/curl -- curl <pod-ip>:<port>
# Pod 直连通但 Service 不通 → kube-proxy/iptables 问题
# Pod 直连也不通 → CNI 网络或应用问题

# ====== 9. 跨节点问题 ======
# 不同节点的 Pod 是否通
# 检查 CNI Pod 日志
kubectl -n kube-system logs -l k8s-app=calico-node --tail=100
  1. 节点 NotReady 怎么排查?(kubelet → containerd → CNI)
# 节点 NotReady 排查(从三个核心组件入手)

# ====== 1. 先看节点状态 ======
kubectl get node <node-name> -o wide
kubectl describe node <node-name> | grep -A10 Conditions
# 哪个 Condition 为 False 就是根因

# ====== 2. kubelet 层排查 ======
# SSH 到该节点
systemctl status kubelet
journalctl -u kubelet --since "10 min ago" --no-pager | tail -50
# 关键词搜索:
journalctl -u kubelet | grep -i error
journalctl -u kubelet | grep -i "failed"
journalctl -u kubelet | grep -i "certificate"
# 常见问题:
#   - kubelet 与 API Server 失去联系(网络/证书)
#   - kubelet 自身 OOM / 进程退出
#   - /var/lib/kubelet 磁盘满
# 解决:
systemctl restart kubelet  # 尝试重启

# ====== 3. 容器运行时层排查(containerd) ======
systemctl status containerd
crictl info                  # 运行时信息和配置
crictl ps                    # 运行中的容器
crictl images                # 本地镜像
# 常见问题:
#   - containerd 挂了
#   - 磁盘空间不足(/var/lib/containerd)
#   - cgroup 驱动不一致(systemd vs cgroupfs)
# 磁盘清理:
crictl rmi --prune           # 清理未使用的镜像
# 检查配置:
cat /etc/containerd/config.toml | grep SystemdCgroup
# 应该为:SystemdCgroup = true

# ====== 4. CNI 层排查 ======
# CNI Pod 状态
kubectl -n kube-system get pods -o wide | grep -E "calico|flannel|weave|cilium"
# CNI Pod 日志
kubectl -n kube-system logs <cni-pod-on-problem-node>
# 检查 CNI 配置(节点上)
ls /etc/cni/net.d/
cat /etc/cni/net.d/*.conf    # CNI 配置文件
# 检查 CNI 二进制
ls /opt/cni/bin/
# 检查 Pod 网络接口
crictl inspect <container-id> | grep -i network
# 检查路由
ip route
route -n

# ====== 5. 节点资源检查 ======
df -h                        # 磁盘
free -h                      # 内存
top -bn1 | head -20          # CPU
# 磁盘压力(DiskPressure) → 清理 /var、/tmp
# 内存压力(MemoryPressure) → 限制或驱逐 Pod
# PID 压力(PIDPressure) → 排查僵尸进程或限制进程数

# ====== 6. 证书检查 ======
kubeadm certs check-expiration
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates

# ====== 7. 终极排查:对比法 ======
# 与正常节点对比关键输出
diff <(ssh normal-node 'systemctl status kubelet') \
     <(ssh problem-node 'systemctl status kubelet')

5.13 进阶

  1. Helm 是什么?和直接 kubectl apply 相比有什么优势?
# Helm 是什么?
# Helm 是 K8s 的"包管理器"(类似 yum/apt),用于管理 K8s 应用的定义、
# 安装和升级。Helm 使用 Chart(模板 + 值)来定义应用。

# 核心概念:
#   Chart:包含应用所需 K8s 资源模板的打包文件
#   Release:Chart 在集群中的运行实例(同一个 Chart 可多次安装 = 多个 Release)
#   Repository:Chart 的存储和分发仓库
#   Values:模板变量值(values.yaml / --set)

# 与 kubectl apply 对比

# | 特性           | kubectl apply       | Helm                          |
# |---------------|--------------------|-------------------------------|
# | 模板化         | 无                 | 支持(Go Template)                |
# | 版本管理       | 需手动             | 内置(Release revision)       |
# | 回滚           | 困难               | helm rollback 一键回滚          |
# | 依赖管理       | 无                 | Chart 依赖自动安装              |
# | 参数化部署     | 需修改 YAML         | --set / -f values.yaml         |
# | 生命周期钩子   | 无                 | 支持(pre-install/post-upgrade 等)|
# | 复用/共享      | YAML 文件           | Chart 仓库分发                  |
# | 多环境管理     | 多份 YAML 文件      | 不同 values 文件                |
# | 干运行         | --dry-run          | helm template / --dry-run      |
# | 发布状态       | 不明确             | helm list / helm status        |

# Helm 常用命令:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo nginx
helm install my-release bitnami/nginx --set service.type=NodePort
helm upgrade my-release bitnami/nginx --set replicaCount=3
helm rollback my-release 1
helm uninstall my-release
helm list -A
helm history my-release
  1. Operator 模式是什么?解决了什么问题?
# Operator 模式是什么?
# Operator 是 K8s 的扩展模式。它将"人类运维知识"编码成软件(自动化运维),
# 通过自定义控制器管理自定义资源(CRD + Custom Controller)。
# Operator = CRD(声明期望状态) + Controller(实现期望状态)

# Operator 工作流程:
# 1. 用户创建自定义资源 CR 实例(如创建 MySQL 集群)
# 2. Operator Controller 监听到 CR 创建
# 3. Controller 执行运维逻辑(创建 StatefulSet + Service + PVC + 配置主从复制...)
# 4. 持续监控,确保实际状态 = 期望状态(自动处理故障、扩缩容、备份等)

# 解决了什么问题?
# 1. 复杂应用自动化管理
#    传统:人工管理数据库集群部署/主从切换/备份/扩容
#    Operator:声明式定义 + 自动执行

# 2. 领域知识编码
#    将有状态应用(DB/Kafka/Redis 集群)的最佳运维实践固化到代码中
#    减少人为操作失误

# 3. 生命周期自动化 Day 2 运维
#    不仅是 Day 1(部署),更解决 Day 2(升级、备份、故障恢复、扩缩容)
#    k8s 原生 Deployment/StatefulSet 无法覆盖复杂 Day 2 场景

# 4. 自愈能力
#    检测到异常状态(如从库同步延迟、脑裂),自动修复

# 常见 Operator:
#   - Prometheus Operator:自动管理 Prometheus/Alertmanager/Grafana
#   - etcd Operator:管理 etcd 集群
#   - MySQL Operator / Postgres Operator:数据库集群管理
#   - Strimzi Kafka Operator:Kafka 集群管理
#   - cert-manager:证书自动化管理
#   - Istio Operator:服务网格管理

# Operator 框架:
#   - Operator SDK(Go/Ansible/Helm)
#   - Kubebuilder
#   - Kopf (Python)
  1. Kubernetes Dashboard 的作用?如何对外暴露访问?
# Kubernetes Dashboard 的作用
# K8s 官方 Web UI,提供图形化管理界面:
# - 查看集群资源(Node/Pod/Deploy/Service 等)
# - 创建/修改/删除资源
# - 查看资源日志和事件
# - 查看资源使用(CPU/内存)
# - 执行命令(Pod 内)
# - 管理 RBAC

# 部署 Dashboard
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aist/deploy/recommended.yaml

# 对外暴露访问的几种方式:

# 方式 1:kubectl proxy(开发测试)
kubectl proxy --address='0.0.0.0' --accept-hosts='^*$'
# 访问:http://<master-ip>:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
# 缺点:不安全、性能差、仅限临时使用

# 方式 2:NodePort
kubectl -n kubernetes-dashboard patch svc kubernetes-dashboard \
  -p '{"spec":{"type":"NodePort"}}'
kubectl -n kubernetes-dashboard get svc kubernetes-dashboard | grep NodePort
# 访问:https://<node-ip>:<nodeport>
# 需要处理 TLS 证书和认证

# 方式 3:Ingress(推荐生产环境)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: dashboard-ingress
  namespace: kubernetes-dashboard
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  tls:
  - hosts:
    - dashboard.example.com
    secretName: dashboard-tls
  rules:
  - host: dashboard.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: kubernetes-dashboard
            port:
              number: 443

# 方式 4:API Server 代理(kubectl proxy)
# 通过 API Server 作为反向代理访问(适合开发测试)

# ====== 认证配置 ======
# 创建管理员 ServiceAccount 获取 Token(测试用)
kubectl -n kubernetes-dashboard create serviceaccount admin-user
kubectl -n kubernetes-dashboard create clusterrolebinding admin-user \
  --clusterrole=cluster-admin --serviceaccount=kubernetes-dashboard:admin-user
kubectl -n kubernetes-dashboard create token admin-user
# 生产环境建议:集成 OIDC/LDAP 或使用 RBAC 细粒度权限

# 安全建议:
# 1. 生产环境必须用 Ingress + TLS
# 2. 集成 OIDC 认证(对接 LDAP/AD)
# 3. 配置 RBAC 细粒度权限(cluster-admin 仅限管理员)
# 4. 可配合 oauth2-proxy 增加额外认证层
# 5. 考虑使用 NetworkPolicy 限制访问来源
  1. MetalLB 是什么?解决什么问题?
# MetalLB 是什么?
# MetalLB 是为裸金属(Bare-metal / 非云环境)K8s 集群提供
# LoadBalancer 类型 Service 的实现。让没有云厂商 LB 的私有环境也能用
# kubectl expose --type=LoadBalancer 获得外部负载均衡 IP。

# 解决了什么问题?
# 在公有云,创建 type=LoadBalancer Service 时,云控制器自动创建外部 LB,
# 分配公网 IP。但在裸金属/私有数据中心,没有这个能力,LoadBalancer Service
# 永远处于 Pending 状态(EXTERNAL-IP 显示 <pending>)。
# MetalLB 解决了"裸金属集群如何获得 LoadBalancer 类型的 Service 外部 IP"的问题。

# MetalLB 两种工作模式:

# 1. Layer 2 模式(ARP/NDP)
#    选举一个节点为"Leader",响应所有 LB IP 的 ARP 请求
#    优点:无需路由器支持、简单
#    缺点:单节点承载所有流量(带宽瓶颈)、无故障转移时短暂中断
#    场景:小型/开发环境

# 2. BGP 模式
#    通过 BGP 协议与路由器对等,向路由器宣告 LB IP 路由
#    优点:真正的多节点负载均衡、故障转移快、适合生产
#    缺点:需要支持 BGP 的路由器、配置复杂
#    场景:生产环境、需要跨节点负载均衡

# 部署 MetalLB:
# 前提:K8s 集群网络已配好(CNI),kube-proxy 禁用 strict ARP
kubectl -n kube-system get cm kube-proxy -o yaml
# 确保 strictARP: true(IPVS 模式)或改 iptables 相关配置

# 安装 MetalLB
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml

# 配置 IP 地址池(IPAddressPool) - Layer 2 模式
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: my-pool
  namespace: metallb-system
spec:
  addresses:
  - 10.1.8.100-10.1.8.120   # 可分配的 IP 段

---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: my-l2-adv
  namespace: metallb-system
spec:
  ipAddressPools:
  - my-pool

# 测试:
kubectl create deploy nginx --image=nginx --replicas=3
kubectl expose deploy nginx --port=80 --type=LoadBalancer
kubectl get svc nginx
# EXTERNAL-IP 应显示 10.1.8.100(自动分配)

# 直接访问:
curl http://10.1.8.100
# 底层流程:请求 → MetalLB ARP 响应 → 节点 → kube-proxy → Pod

# 同类型替代:
#   - OpenELB(原 PorterLB)
#   - PureLB
#   - kube-vip (强大的 VIP 方案)
#   - HAProxy + keepalived (传统方案)


本册共 88 题,覆盖 Docker · Kubernetes

更多推荐