HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 手撸容器系统

    • 完整手撸容器技术文档系列
    • 01-容器本质与基础概念
    • 02-Namespace隔离机制
    • 03-CGroup资源控制
    • 04-Capabilities与安全机制
    • 05-容器网络原理
    • 06-网络模式与实现
    • 07-CNI插件开发
    • 08-RootFS与文件系统隔离
    • 09-OverlayFS镜像分层
    • 10-命令行手撸容器
    • 11-Go实现最小容器
    • 12-Go实现完整容器
    • 13-容器生命周期管理
    • 14-调试技术与工具
    • 15-OCI规范与标准化
    • 16-进阶场景与优化
    • 常见问题与故障排查
    • 参考资料与延伸阅读

23-容器面试深度指南

概述

本章汇总容器技术相关的高频面试题,覆盖 Namespace、Cgroup、内存管理、OOM、Kubernetes 资源模型等核心知识点,适合 AI Infra / AIOps / SRE / 云原生工程师等岗位面试准备。


一、Namespace 相关

1.1 基础问题

Q1: Linux 有哪几种 Namespace?各自隔离什么?

┌──────────────┬────────────────────────────────────────────────┐
│  Namespace   │  隔离内容                                       │
├──────────────┼────────────────────────────────────────────────┤
│  PID         │  进程 ID,容器内 PID 从 1 开始                  │
│  Network     │  网络设备、IP 地址、端口、路由表                 │
│  Mount       │  文件系统挂载点                                 │
│  UTS         │  主机名和域名                                   │
│  IPC         │  信号量、消息队列、共享内存                      │
│  User        │  用户和组 ID (rootless 容器)                    │
│  Cgroup      │  Cgroup 根目录视图                              │
│  Time        │  系统时间 (较新)                                │
└──────────────┴────────────────────────────────────────────────┘

Q2: 容器和虚拟机的本质区别?

容器:
- 共享宿主机内核
- 通过 Namespace 隔离进程视图
- 通过 Cgroup 限制资源
- 轻量级,启动快 (毫秒级)
- 隔离性相对较弱

虚拟机:
- 运行独立的 Guest OS 内核
- 通过 Hypervisor 虚拟化硬件
- 资源开销大
- 启动慢 (分钟级)
- 隔离性强

1.2 进阶问题

Q3: 如何查看容器进程在宿主机上的真实 PID?

# 方法 1: 通过 docker inspect
docker inspect --format '{{.State.Pid}}' <container_id>

# 方法 2: 通过 /proc
cat /proc/<host_pid>/status | grep NSpid
# NSpid: 12345  1  (宿主机 PID 12345,容器内 PID 1)

# 方法 3: 进入容器的 PID namespace
nsenter -t <host_pid> -p ps aux

Q4: 容器内能修改系统时间吗?

默认不能,因为:
1. 需要 CAP_SYS_TIME 能力
2. 系统时间不属于任何 Namespace (在旧内核中)
3. Time Namespace 是 Linux 5.6 才引入

解决方案:
- 添加 --cap-add SYS_TIME (不推荐)
- 使用 libfaketime 在应用层模拟
- 使用 Time Namespace (需要新内核)

二、Cgroup 相关

2.1 基础问题

Q5: Cgroup v1 和 v2 的主要区别?

┌────────────────┬─────────────────────┬─────────────────────┐
│  特性          │  Cgroup v1          │  Cgroup v2          │
├────────────────┼─────────────────────┼─────────────────────┤
│  层级结构      │  多层级,每个控制器   │  单一统一层级        │
│                │  独立的层级          │                     │
│  挂载点        │  多个               │  单个 /sys/fs/cgroup │
│  进程绑定      │  可属于多个层级      │  只能属于一个节点    │
│  内存保护      │  无                 │  memory.low/min     │
│  PSI 压力监控  │  无                 │  支持               │
│  Buffer I/O    │  不支持             │  支持               │
└────────────────┴─────────────────────┴─────────────────────┘

Q6: memory.limit_in_bytes 限制的是什么?

限制的是:
1. RSS (匿名内存) - 堆、栈、匿名 mmap
2. Page Cache (文件缓存) - 该 Cgroup 进程读写的文件
3. 共享内存 (tmpfs/shmem)
4. 页表 (可选,需要 kmem 配置)

不限制的是:
1. 虚拟内存中未分配的部分
2. Swap (除非设置 memsw 限制)
3. 其他 Cgroup 的 Page Cache

2.2 进阶问题

Q7: 容器 RSS 很小,为什么还是 OOM?

常见原因:
1. Page Cache 占用
   - 容器读写文件会产生 Page Cache
   - Page Cache 也计入 memory.limit

2. 共享内存占用
   - tmpfs 挂载
   - 进程间共享内存

3. 内核内存占用
   - 网络缓冲区
   - inode/dentry 缓存

排查方法:
cat /sys/fs/cgroup/memory/<cgroup>/memory.stat
# 查看 cache、rss、mapped_file 等值

Q8: 如何禁止容器使用 Swap?

# Cgroup v1
echo $LIMIT > memory.limit_in_bytes
echo $LIMIT > memory.memsw.limit_in_bytes  # 设置相同值

# Cgroup v2
echo 0 > memory.swap.max

# Docker
docker run --memory=1g --memory-swap=1g myimage  # 相同值 = 禁用 swap

# Kubernetes
# Kubelet 默认禁用 Swap (failSwapOn: true)

三、内存管理与 OOM

3.1 基础问题

Q9: RSS、PSS、USS 的区别?

RSS (Resident Set Size):
- 进程实际占用的物理内存
- 包含共享库的完整大小
- 多个进程的 RSS 之和会重复计算共享库

PSS (Proportional Set Size):
- 共享内存按进程数平分
- 所有进程的 PSS 之和 = 实际物理内存使用

USS (Unique Set Size):
- 进程独占的内存
- 不包含任何共享部分
- 杀死进程后能释放的内存

关系: USS ≤ PSS ≤ RSS

Q10: OOM Killer 如何选择杀哪个进程?

1. 计算每个进程的 oom_score:
   基础分 = (进程内存 / 系统总内存) × 1000
   最终分 = 基础分 + oom_score_adj

2. 选择分数最高的进程

3. oom_score_adj 范围:
   -1000: 永不杀死
   0: 默认
   1000: 优先杀死

4. Kubernetes 中:
   Guaranteed: oom_score_adj = -997
   Burstable:  oom_score_adj = 2~999
   BestEffort: oom_score_adj = 1000

3.2 进阶问题

Q11: 操作系统是怎么触发 OOM 的?

触发流程:
1. 进程请求分配内存 (malloc/mmap)
2. 内核尝试分配物理页
3. 分配失败,触发直接回收 (direct reclaim)
4. 回收 Page Cache、Swap out 匿名页
5. 回收失败,唤醒 kswapd 继续尝试
6. 仍然失败,检查 OOM 条件
7. 调用 out_of_memory()
8. select_bad_process() 选择 victim
9. oom_kill_process() 发送 SIGKILL

Cgroup OOM 的区别:
- 在 try_charge() 时检查限制
- 只从 Cgroup 内选择 victim
- 不影响其他 Cgroup 的进程

Q12: 如何防止关键进程被 OOM Kill?

# 1. 设置 oom_score_adj
echo -1000 > /proc/<pid>/oom_score_adj

# 2. Systemd 服务配置
[Service]
OOMScoreAdjust=-1000

# 3. Docker
docker run --oom-score-adj=-500 myimage

# 4. Kubernetes - 使用 Guaranteed QoS
resources:
  requests:
    memory: "1Gi"
    cpu: "1"
  limits:
    memory: "1Gi"   # 必须等于 requests
    cpu: "1"        # 必须等于 requests

四、Kubernetes 资源模型

4.1 基础问题

Q13: requests 和 limits 的区别?

requests:
- 用于调度决策
- 节点 allocatable >= sum(pod requests)
- 不创建内存 Cgroup 限制
- CPU requests 转换为 cpu.shares

limits:
- 运行时强制限制
- 创建 Cgroup 限制
- 内存超限 → OOM Kill
- CPU 超限 → Throttling

Q14: 三种 QoS 类别的判定条件?

Guaranteed:
- 每个容器都设置了 CPU 和内存的 requests 和 limits
- requests == limits

Burstable:
- 不满足 Guaranteed
- 至少一个容器设置了 requests 或 limits

BestEffort:
- 没有任何容器设置 requests 或 limits

4.2 进阶问题

Q15: kubectl top 显示的内存是什么?

kubectl top 显示的是 Working Set:
working_set = memory.usage - inactive_file

解释:
- memory.usage: Cgroup 内存使用量
- inactive_file: 非活跃文件页 (可回收的 Page Cache)
- working_set: 实际工作所需的内存

为什么不用 usage?
- usage 包含可回收的 Page Cache
- working_set 更接近真实需求

Q16: 应该设置 CPU limits 吗?

争议话题,两种观点:

不设置 (只设置 requests):
优点:
- 可以充分利用空闲 CPU
- 避免不必要的 throttling
缺点:
- 单个 Pod 可能影响其他 Pod
- 行为不可预测

设置 limits:
优点:
- 行为可预测
- 避免 noisy neighbor
缺点:
- 可能导致资源浪费
- 会产生 CPU throttling

推荐:
- 在线服务:考虑不设置
- 批处理任务:建议设置
- 根据实际情况决定

五、场景设计题

5.1 设计题

Q17: 设计一个容器内存监控告警系统

方案设计:

1. 数据采集层
   - 使用 cAdvisor 采集容器指标
   - 或直接读取 /sys/fs/cgroup/memory/*/
   - 采集频率: 10-30s

2. 关键指标
   - memory.usage_in_bytes (当前使用)
   - memory.limit_in_bytes (限制)
   - memory.stat 中的 rss/cache/inactive_file
   - 计算 working_set = usage - inactive_file

3. 告警规则
   - 使用率 > 80%: Warning
   - 使用率 > 90%: Critical
   - OOM 事件: Immediate

4. 告警策略
   - 持续时间判断 (避免抖动)
   - 分级告警 (Warning/Critical)
   - 告警收敛

5. 存储与展示
   - Prometheus 存储
   - Grafana 展示
   - AlertManager 告警

代码示例:
# Prometheus 告警规则
groups:
- name: container-memory
  rules:
  - alert: ContainerMemoryUsageHigh
    expr: |
      (container_memory_working_set_bytes /
       container_spec_memory_limit_bytes) > 0.8
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Container memory usage > 80%"

  - alert: ContainerOOMKilled
    expr: kube_pod_container_status_restarts_total > 0
          and kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
    for: 0m
    labels:
      severity: critical

Q18: 如何排查容器频繁 OOM 的问题?

排查步骤:

1. 确认 OOM 事件
   kubectl describe pod <pod> | grep -A5 "Last State"
   kubectl logs <pod> --previous
   dmesg | grep -i "oom\|killed"

2. 分析内存配置
   kubectl get pod <pod> -o yaml | grep -A10 resources
   检查 requests 和 limits 是否合理

3. 查看实际使用
   kubectl top pod <pod>
   进入节点查看 /sys/fs/cgroup/memory/...

4. 分析内存组成
   cat memory.stat
   分析 rss、cache、mapped_file

5. 应用层排查
   - Java: 检查堆设置 (-Xmx)
   - Node.js: 检查 --max-old-space-size
   - 检查是否有内存泄漏

解决方案:
- 增加内存 limits
- 优化应用内存使用
- 合理设置 JVM 参数
- 检查文件读写是否过多 (Page Cache)

六、源码级问题

6.1 内核源码

Q19: oom_badness 的计算逻辑?

// mm/oom_kill.c
long oom_badness(struct task_struct *p, unsigned long totalpages)
{
    long points;
    long adj;

    // 不可杀的进程返回最小值
    if (oom_unkillable_task(p))
        return LONG_MIN;

    // 获取 oom_score_adj
    adj = (long)p->signal->oom_score_adj;
    if (adj == OOM_SCORE_ADJ_MIN)  // -1000
        return LONG_MIN;

    // 计算进程使用的页数
    points = get_mm_rss(p->mm) +           // RSS
             get_mm_counter(p->mm, MM_SWAPENTS) +  // Swap
             mm_pgtables_bytes(p->mm) / PAGE_SIZE; // 页表

    // 归一化并加上调整值
    adj *= totalpages / 1000;
    points += adj;

    return points > 0 ? points : 1;
}

Q20: Cgroup 内存 charge 的流程?

// mm/memcontrol.c
int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
               unsigned int nr_pages)
{
    // 1. 尝试增加计数器
    page_counter_try_charge(&memcg->memory, batch, &counter);

    // 2. 检查是否超过 max 限制
    if (page_counter_read(&memcg->memory) > memcg->memory.max) {
        // 3. 尝试回收
        mem_cgroup_reclaim(memcg, gfp_mask);

        // 4. 回收失败触发 OOM
        if (still_over_limit)
            mem_cgroup_oom(memcg, gfp_mask, order);
    }

    // 5. 检查 high 水位 (v2)
    if (page_counter_read(&memcg->memory) > memcg->high) {
        // 限流,让出 CPU
        schedule();
    }

    return 0;
}

七、快速复习清单

┌─────────────────────────────────────────────────────────────────────────┐
│                         面试前快速复习                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Namespace:                                                             │
│  □ 8种类型: PID, Network, Mount, UTS, IPC, User, Cgroup, Time          │
│  □ 容器 = 进程 + Namespace 隔离 + Cgroup 限制                           │
│  □ 查看: /proc/<pid>/ns/                                               │
│                                                                         │
│  Cgroup:                                                                │
│  □ v1 多层级,v2 统一层级                                               │
│  □ memory.limit = RSS + Page Cache                                      │
│  □ v2 新增: memory.high, memory.low, memory.min                        │
│  □ 路径: /sys/fs/cgroup/                                               │
│                                                                         │
│  内存:                                                                   │
│  □ RSS/PSS/USS 区别                                                     │
│  □ Page Cache 计入限制                                                  │
│  □ working_set = usage - inactive_file                                  │
│                                                                         │
│  OOM:                                                                   │
│  □ oom_score = (内存/总内存)×1000 + oom_score_adj                       │
│  □ -1000 永不杀, 1000 优先杀                                            │
│  □ Cgroup OOM 只杀组内进程                                              │
│                                                                         │
│  Kubernetes:                                                            │
│  □ requests: 调度用                                                     │
│  □ limits: 创建 Cgroup 限制                                             │
│  □ QoS: Guaranteed > Burstable > BestEffort                            │
│  □ oom_score_adj: -997 / 2~999 / 1000                                  │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

相关链接

  • 19-Linux内存管理深度剖析
  • 20-OOM-Killer机制详解
  • 21-Cgroup内存控制深度
  • 22-Kubernetes资源模型
  • 02-Namespace隔离机制
  • 03-CGroup资源控制

祝面试顺利!