Files
Qiniu/technical/k8s/k8s-03-pod.md
T

726 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [k8s, pod, devops]
create time: 2026-07-07 15:48
---
# Pod — Kubernetes 最小调度单元
## 概述
Pod 是 Kubernetes 中**最小的可部署和调度单元**。它不是一个容器,而是一组(一个或多个)共享网络和存储资源的容器的逻辑封装。为什么 Kubernetes 不直接调度容器,而是引入了 Pod 这一抽象层?因为在实际业务中,多个紧密协作的进程往往需要共享同一个 IP 地址、端口空间和卷挂载——比如一个 Web 应用容器和一个负责收集日志的 sidecar 容器。Pod 正是为了解决这种「亲密耦合进程协同运行」的需求而设计的。理解 Pod,是掌握 Kubernetes 一切上层抽象(Deployment、StatefulSet、DaemonSet 等)的基础。
## Pod 的本质
### 共享网络命名空间
同一个 Pod 内的所有容器共享**同一个网络命名空间(Network Namespace)**,这意味着:
- 它们拥有**相同的 IP 地址**
- 它们可以**通过 localhost 互相通信**
- 端口空间是共享的,因此同一 Pod 内的容器**不能绑定相同的端口**
> [!question] 为什么不直接调度容器?
> 因为单个容器通常只运行一个进程(这是容器设计的最佳实践)。但在很多场景下,多个进程需要紧密协作——比如 Nginx + 日志收集器、主应用 + 配置热加载 sidecar。如果让调度器去理解「哪些容器应该放在一起」,复杂度会急剧上升。Pod 将这个决策交给了用户:你来决定哪些容器属于同一个 Pod。
### Pause 容器 — Pod 的「灵魂」
每个 Kubernetes Pod 启动时,都会先创建一个特殊的**Pause 容器**(也叫 infra 容器),它是 Pod 的第一个容器,也是整个 Pod 的「根进程」。其他业务容器通过加入 Pause 容器的 Network Namespace 和 PID Namespace 来实现共享。
```mermaid
graph TB
subgraph Pod
P["Pause 容器<br/>(infra container)"]
C1["业务容器 A"]
C2["业务容器 B<br/>(sidecar)"]
C3["业务容器 C"]
end
P -->|"提供 Network NS"| C1
P -->|"提供 Network NS"| C2
P -->|"提供 Network NS"| C3
P -->|"提供 PID NS (可选)"| C1
P -->|"提供 PID NS (可选)"| C2
P -->|"提供 PID NS (可选)"| C3
```
Pause 容器的职责非常简单:
1. **持有 Network Namespace** — 确保 Pod 的 IP 地址在所有容器生命周期内保持不变
2. **充当 PID 1** — 回收僵尸进程(zombie reaping)
3. **极轻量** — 通常只有几百 KB,使用 `registry.k8s.io/pause` 镜像
> [!info] Pause 容器的代码只有几百行
> 它的核心逻辑就是一个无限循环的 `pause()` 系统调用。正因为足够简单,它几乎不会出问题,可以稳定地为 Pod 内所有容器提供基础运行环境。
### 容器与 Pod 的关系(1:N)
一个 Pod 可以包含多个容器,典型模式如下:
| 模式 | 说明 | 示例 |
|------|------|------|
| 单容器 Pod | 最常见的场景,一个 Pod 运行一个应用容器 | 一个 Go Web 服务 |
| 多容器 Pod (sidecar) | 主容器 + 辅助容器,辅助容器扩展主容器功能 | Nginx + Filebeat 日志收集 |
| 多容器 Pod (adapter) | 辅助容器将主容器的输出转换为标准格式 | 应用日志 → Fluentd 标准格式 |
| 多容器 Pod (ambassador) | 辅助容器代理网络请求 | 应用 → Envoy Proxy → 外部服务 |
```yaml
# 一个多容器 Pod 的简单示例:Web 服务 + 日志收集 sidecar
apiVersion: v1
kind: Pod
metadata:
name: web-with-logging
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
- name: log-collector
image: fluent/fluent-bit:latest
volumeMounts:
- name: log-volume
mountPath: /var/log/nginx
readOnly: true
volumes:
- name: log-volume
emptyDir: {}
```
两个容器共享 `log-volume` 卷,Web 容器写入日志,Fluent Bit 容器读取并转发——这是经典的日志收集 sidecar 模式。
## Pod 生命周期
Pod 的生命周期由**阶段(Phase)**和**条件(Condition)**共同描述。从创建到销毁,Pod 经历以下主要阶段:
```mermaid
stateDiagram-v2
[*] --> Pending: kubectl apply / API 创建
Pending --> Running: 至少一个容器启动成功
Pending --> Failed: 拉取镜像失败 / 调度失败
Running --> Succeeded: 所有容器正常退出(exit 0)
Running --> Failed: 容器异常退出 / OOMKilled
Running --> Unknown: Node 失联, 无法获取状态
Succeeded --> [*]
Failed --> [*]
Unknown --> Running: Node 恢复通信
Unknown --> Failed: 确认 Pod 已丢失
```
### Init Containers
Init Containers 是在**主容器启动之前**按顺序运行的容器。它们主要用于完成一些前置条件的检查和准备工作。
**与主容器的区别:**
| 特性 | Init Container | 主容器 (App Container) |
|------|---------------|----------------------|
| 执行顺序 | 严格按顺序依次执行 | 并行启动 |
| 失败行为 | 失败后 Pod 重启(遵循 restartPolicy) | 失败后根据探针配置重启 |
| 探针支持 | 不支持 liveness/readiness probe | 支持 |
| 用途 | 前置条件检查、等待依赖、初始化配置 | 运行业务逻辑 |
| 资源计算 | 独立于主容器的资源请求 | 作为整体调度依据 |
> [!tip] 何时使用 Init Container?
> 当你需要在主容器启动前完成某些「只执行一次」的任务时——比如等待数据库就绪、下载配置文件、执行数据库迁移等——Init Container 是最佳选择。
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-init
spec:
initContainers:
# 第一个 Init Container:等待数据库就绪
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- |
until nc -z mysql-service 3306; do
echo "Waiting for MySQL..."
sleep 2
done
echo "MySQL is ready!"
# 第二个 Init Container:执行数据库迁移
- name: db-migration
image: myapp/migration:v1.2
env:
- name: DB_HOST
value: "mysql-service"
containers:
- name: app
image: myapp/api:v1.2
ports:
- containerPort: 8080
```
上述示例中,两个 Init Container **严格按顺序**执行:先等 MySQL 可达,再执行数据库迁移。只有两者都成功(exit 0)后,主容器 `app` 才会启动。
### 容器探针
Kubernetes 提供三种探针来监控容器的健康状态:
| 探针类型 | 作用 | 失败后果 | 典型场景 |
|---------|------|---------|---------|
| `livenessProbe` | 检测容器是否**存活** | 杀死容器并重启 | 进程死锁、无限循环 |
| `readinessProbe` | 检测容器是否**就绪**接收流量 | 从 Service 的 Endpoints 中移除 | 启动预热、临时过载 |
| `startupProbe` | 检测容器是否**启动完成** | 杀死容器并重启 | 启动耗时长的应用(如 Java) |
> [!warning] startupProbe 与 livenessProbe 的关系
> 当配置了 `startupProbe` 时,在它成功之前,`livenessProbe` 和 `readinessProbe` 都不会执行。这是为了防止启动时间较长的应用被误判为不健康。`startupProbe` 的 `failureThreshold * periodSeconds` 应该大于应用的最大启动时间。
**探针支持的检测方式:**
- **httpGet** — 发送 HTTP GET 请求,2xx/3xx 视为成功
- **tcpSocket** — 尝试 TCP 连接,端口可达视为成功
- **exec** — 执行命令,exit code 0 视为成功
- **grpc** — gRPC 健康检查协议(Kubernetes 1.24+)
```yaml
apiVersion: v1
kind: Pod
metadata:
name: probes-demo
spec:
containers:
- name: app
image: myapp:v1
ports:
- containerPort: 8080
# 启动探针:最多给 60 秒启动时间 (10 次 x 6 秒)
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 10
periodSeconds: 6
# 存活探针:每 15 秒检测一次,连续 3 次失败则重启
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0 # startupProbe 成功后才开始
periodSeconds: 15
failureThreshold: 3
timeoutSeconds: 5
# 就绪探针:每 5 秒检测一次,失败则从 Endpoints 移除
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 3
timeoutSeconds: 3
```
> [!tip] 最佳实践
> 生产环境中建议同时配置三种探针:`startupProbe` 保护慢启动应用、`livenessProbe` 检测死锁、`readinessProbe` 控制流量切换。`readinessProbe` 的检测频率可以比 `livenessProbe` 更高,因为它的失败不会触发重启。
### Pod 阶段(Phase)与条件(Condition)
**Pod Phase** 是对 Pod 在其生命周期中所处位置的高层概括:
| Phase | 说明 |
|-------|------|
| `Pending` | Pod 已被 API Server 接受,但尚未调度或容器镜像未拉取完成 |
| `Running` | Pod 已绑定到节点,且至少有一个容器处于运行状态 |
| `Succeeded` | 所有容器正常退出(exit 0),不会再重启 |
| `Failed` | 所有容器已终止,且至少有一个容器是非正常退出 |
| `Unknown` | 无法获取 Pod 状态,通常是与 Node 的通信中断 |
**Pod Conditions** 提供了更细粒度的状态信息,是一个数组,每个 Condition 包含以下字段:
```yaml
conditions:
- type: PodScheduled # 条件类型
status: "True" # True / False / Unknown
lastProbeTime: null # 上次探测时间
lastTransitionTime: "2026-07-07T07:00:00Z" # 上次状态转换时间
reason: "SchedulerCompletedSchedulingReason"
message: "Successfully assigned default/my-pod to node-1"
```
常见的 Condition Type:
| Type | 说明 |
|------|------|
| `PodScheduled` | Pod 已被调度到某个节点 |
| `Initialized` | 所有 Init Container 已成功完成 |
| `ContainersReady` | 所有容器都已就绪 |
| `Ready` | Pod 可以对外提供服务,被纳入 Service Endpoints |
```mermaid
graph LR
A["PodScheduled<br/>status: True"] --> B["Initialized<br/>status: True"]
B --> C["ContainersReady<br/>status: True"]
C --> D["Ready<br/>status: True"]
```
> [!info] Condition vs Phase
> Phase 是一个全局性的、互斥的状态值(一个 Pod 在同一时刻只有一个 Phase)。而 Conditions 是一组独立的布尔标志,一个 Pod 可以同时有多个 Condition 为 True。Conditions 通常用于精确排查 Pod 启动过程中的具体卡点。
## 资源管理
### Requests 与 Limits
Kubernetes 通过 `requests` 和 `limits` 两个维度来管理容器的资源:
- **requests(请求量)** — 调度器依据此值决定 Pod 放到哪个节点。是容器运行所需的**最小**资源量
- **limits(限制量)** — 容器可使用的**最大**资源量。超出限制时的行为取决于资源类型
```yaml
resources:
requests:
cpu: "250m" # 0.25 核
memory: "128Mi" # 128 MiB
limits:
cpu: "500m" # 0.5 核
memory: "256Mi" # 256 MiB
```
> [!question] CPU 超限和 Memory 超限的处理方式为什么不同?
> CPU 是**可压缩资源(compressible)**——超出 limit 时会被节流(throttle),进程不会被杀死。而内存是**不可压缩资源(incompressible)**——超出 limit 时进程会被 OOMKilled。这是因为 CPU 可以通过时间片轮转来降级服务,但内存一旦不足,系统无法「回收」已分配的内存页面。
### QoS(Quality of Service)三级分类
Kubernetes 根据 requests 和 limits 的配置将 Pod 分为三个 QoS 等级:
| QoS 等级 | 条件 | OOM 优先级 | 调度优先级 |
|----------|------|-----------|-----------|
| **Guaranteed** | 每个容器的 requests == limits | 最低(最后被杀) | 最高 |
| **Burstable** | 至少一个容器设置了 requests,且 requests < limits | 中等 | 中等 |
| **BestEffort** | 所有容器均未设置 requests 和 limits | 最高(最先被杀) | 最低 |
```yaml
# Guaranteed — requests 等于 limits
containers:
- name: critical-app
resources:
requests:
cpu: "1"
memory: "1Gi"
limits:
cpu: "1"
memory: "1Gi"
---
# Burstable — requests 小于 limits
containers:
- name: normal-app
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "1"
memory: "512Mi"
---
# BestEffort — 未设置任何 requests / limits
containers:
- name: batch-job
image: busybox
# 未配置 resources 字段
```
> [!warning] 生产环境严禁 BestEffort
> BestEffort Pod 在节点资源紧张时会被**最先驱逐**。在生产环境中,至少应为所有容器设置合理的 `requests`。
### OOM(Out of Memory)行为
当节点内存不足时,kubelet 会按照以下顺序选择 Pod 进行驱逐:
1. **BestEffort** Pod(OOM Score 最高)
2. **Burstable** Pod 中,实际内存使用量超过 requests 最多的
3. **Guaranteed** Pod(OOM Score 最低,最后被杀)
同时,Linux 内核的 OOM Killer 也会根据进程的 `oom_score_adj` 值来决定杀哪个进程。Kubernetes 为不同 QoS 等级的容器设置了不同的 `oom_score_adj`:
| QoS 等级 | oom_score_adj |
|----------|--------------|
| Guaranteed | -998 |
| Burstable | 2 ~ 1000(按比例) |
| BestEffort | 1000 |
### CPU 节流机制
当容器的 CPU 使用超过 limit 时,Linux 内核的 CFS(Completely Fair Scheduler)会通过 **CFS Bandwidth Throttling** 机制限制容器的 CPU 时间片分配。
表现症状:
- 容器进程运行变慢,响应延迟升高
- `container_cpu_cfs_throttled_periods_total` 指标持续增长
- 应用程序的 P99 延迟出现周期性毛刺
> [!tip] 避免 CPU 节流
> 如果发现应用延迟升高且 CPU throttling 严重,考虑:① 提高 CPU limit;② 将 limit 设置为与 request 相同(Guaranteed QoS,不会被节流但调度更严格);③ 使用 `CPU Manager Policy: static` 获得独占 CPU 核心。
## Sidecar 模式
Sidecar 模式是 Pod 多容器设计中最常用的架构模式。通过在 Pod 中添加辅助容器(sidecar),可以在不修改主应用代码的前提下扩展其功能。
### 日志收集
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-logging-sidecar
spec:
containers:
- name: app
image: myapp:v2
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: log-shipper
image: fluent/fluent-bit:2.2
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
readOnly: true
- name: fluentbit-config
mountPath: /fluent-bit/etc/
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
volumes:
- name: shared-logs
emptyDir: {}
- name: fluentbit-config
configMap:
name: fluentbit-config
```
### 服务网格代理(Service Mesh Proxy)
这是 Istio、Linkerd 等服务网格的典型数据面模式——每个应用 Pod 旁注入一个 Envoy sidecar 代理:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-with-proxy
annotations:
sidecar.istio.io/inject: "true"
spec:
containers:
- name: app
image: myapp:v3
ports:
- containerPort: 8080
# 以下通常由 Istio 自动注入,这里手动展示
- name: istio-proxy
image: envoyproxy/envoy:v1.28
args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
ports:
- containerPort: 15001 # Envoy 入口
- containerPort: 15006 # Envoy 出口
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
```
### 配置热更新(Reloader Sidecar)
某些应用不支持热加载配置文件,此时可以用一个 sidecar 监听 ConfigMap 变化并通知主容器:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: app-config-reload
spec:
containers:
- name: app
image: nginx:1.25
volumeMounts:
- name: config-volume
mountPath: /etc/nginx/conf.d
- name: config-reloader
image: jimmidyson/configmap-reload:v0.13
args:
- --volume-dir=/config
- --webhook-url=http://localhost:8080/-/reload
volumeMounts:
- name: config-volume
mountPath: /config
readOnly: true
volumes:
- name: config-volume
configMap:
name: nginx-config
```
## Pod 调度基础
Kubernetes 调度器(kube-scheduler)负责将 Pod 分配到合适的节点上。Pod 本身可以通过以下字段影响调度决策:
### nodeSelector
最简单的调度约束,通过标签匹配将 Pod 调度到特定节点:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: gpu-task
spec:
nodeSelector:
gpu: "true" # 必须调度到带有 gpu=true 标签的节点
disktype: "ssd"
containers:
- name: ml-trainer
image: pytorch:v2
```
### nodeName
直接指定节点名称,**跳过调度器**:
```yaml
spec:
nodeName: node-3 # 直接绑定到 node-3,不经过调度器
```
> [!warning] 慎用 nodeName
> 使用 `nodeName` 会绕过调度器的资源检查、亲和性计算和污点容忍判断。如果目标节点资源不足,Pod 会一直处于 Pending 状态。一般仅用于测试或特殊运维场景。
### 亲和性(Affinity)简介
亲和性提供了比 `nodeSelector` 更丰富的调度规则:
- **nodeAffinity** — 节点亲和性,支持硬性(required)和软性(preferred)规则
- **podAffinity** — Pod 亲和性,将 Pod 调度到与其他 Pod 相近的位置
- **podAntiAffinity** — Pod 反亲和性,将 Pod 调度到与其他 Pod 远离的位置
```yaml
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values:
- amd64
preferredDuringSchedulingIgnoredDuringExecution: # 软性偏好
- weight: 80
preference:
matchExpressions:
- key: zone
operator: In
values:
- cn-east-1a
```
> [!info] 深入阅读
> 调度器的完整机制(包括 Taint/Toleration、PriorityClass、拓扑分布约束等)将在 [[k8s-scheduler]] 中详细展开。
## 静态 Pod(Static Pod)
静态 Pod 是由 **kubelet 直接管理**的 Pod,不通过 API Server 创建。kubelet 会监视指定目录(默认为 `/etc/kubernetes/manifests/`)中的 YAML 文件,并自动创建和维护对应的 Pod。
### 特点
| 特性 | 静态 Pod | 普通 Pod |
|------|---------|---------|
| 管理者 | kubelet | API Server / Controller Manager |
| 存储位置 | 节点本地文件系统 | etcd |
| 是否出现在 API Server 中 | 是(kubelet 创建 mirror pod) | 是 |
| 是否受控制器管理 | 否 | 是(Deployment、StatefulSet 等) |
| 是否可被 kubectl 删除 | 否(需删除文件) | 是 |
### 用途
静态 Pod 最重要的用途是**部署控制平面组件**:
```bash
# kubeadm 部署的集群中,控制平面组件都是静态 Pod
/etc/kubernetes/manifests/
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
├── kube-scheduler.yaml
└── etcd.yaml
```
```yaml
# /etc/kubernetes/manifests/kube-apiserver.yaml(简化示例)
apiVersion: v1
kind: Pod
metadata:
name: kube-apiserver
namespace: kube-system
labels:
component: kube-apiserver
spec:
containers:
- name: kube-apiserver
image: registry.k8s.io/kube-apiserver:v1.30.0
command:
- kube-apiserver
- --advertise-address=192.168.1.10
- --etcd-servers=https://127.0.0.1:2379
- --service-cluster-ip-range=10.96.0.0/12
ports:
- containerPort: 6443
hostNetwork: true
```
> [!info] 为什么控制平面用静态 Pod?
> 这是一个「鸡生蛋」的问题:kube-apiserver 是 Kubernetes 集群的核心,如果 kube-apiserver 本身也依赖 API Server 来创建,那集群就无法启动。静态 Pod 由 kubelet 独立管理,不依赖 API Server,因此可以用来部署 API Server 本身。
## 常见陷阱与最佳实践
### 镜像拉取策略(imagePullPolicy)
| 策略 | 行为 | 适用场景 |
|------|------|---------|
| `Always` | 每次创建 Pod 都拉取镜像 | 使用 `:latest` 标签时(默认策略) |
| `IfNotPresent` | 本地不存在时才拉取 | 使用固定版本标签(如 `:v1.2.3`) |
| `Never` | 从不拉取,仅使用本地镜像 | 离线环境或测试场景 |
```yaml
containers:
- name: app
image: myapp:v1.2.3 # 使用固定标签
imagePullPolicy: IfNotPresent # 推荐:避免不必要的拉取
```
> [!warning] :latest 标签陷阱
> 当镜像标签为 `:latest` 或不指定标签时,`imagePullPolicy` 默认为 `Always`。这意味着每次 Pod 重建都会重新拉取镜像,带来不确定性。**生产环境必须使用固定版本标签**(如 `:v1.2.3` 或 SHA256 digest)。
### 优雅终止(Graceful Shutdown)
当 Kubernetes 删除一个 Pod 时,会经历以下流程:
```mermaid
sequenceDiagram
participant User as 用户/API
participant API as API Server
participant Kubelet as kubelet
participant App as 应用进程
User->>API: 删除 Pod
API->>Kubelet: 标记 Pod 为 Terminating
Note over API: 从 Service Endpoints 移除
Kubelet->>App: 执行 preStop hook
App-->>App: 执行清理逻辑
App-->>Kubelet: preStop 完成
Kubelet->>App: 发送 SIGTERM 信号
App-->>App: 开始优雅关闭
alt 在 terminationGracePeriodSeconds 内退出
App-->>Kubelet: 进程正常退出
else 超时未退出
Kubelet->>App: 发送 SIGKILL 强制杀死
end
```
**关键配置项:**
```yaml
apiVersion: v1
kind: Pod
metadata:
name: graceful-demo
spec:
terminationGracePeriodSeconds: 60 # 默认 30s,给足够时间清理
containers:
- name: app
image: myapp:v1
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- |
# 1. 通知应用停止接收新请求
curl -s http://localhost:8080/admin/drain
# 2. 等待正在进行的请求处理完成
sleep 15
# 3. 关闭数据库连接池
echo "Shutting down gracefully..."
```
> [!tip] preStop 的妙用
> preStop hook 在 SIGTERM 之前执行,可以用来做「通知式关闭」——比如先从注册中心注销、停止接收新连接、等待进行中的请求完成。配合 `terminationGracePeriodSeconds` 调大值,可以实现零请求丢失的优雅关闭。
### PID 1 信号问题
在 Linux 中,PID 1 进程有特殊行为:**默认不响应 SIGTERM 信号**(为了兼容 init 系统)。如果容器的入口进程是 shell 脚本,shell 不会将 SIGTERM 转发给子进程,导致容器无法优雅退出。
**问题示例:**
```dockerfile
# 有问题的 Dockerfile
FROM node:20
COPY server.js .
CMD node server.js # node 进程成为 PID 1,但不一定正确处理信号
```
**解决方案:**
```dockerfile
# 方案 1:使用 exec 形式(推荐)
CMD ["node", "server.js"] # node 直接成为 PID 1
# 方案 2:使用 tini 作为 init 进程
RUN apt-get update && apt-get install -y tini
ENTRYPOINT ["tini", "--"]
CMD ["node", "server.js"]
# 方案 3:在 Pod Spec 中设置(Kubernetes 1.28+)
# 通过 feature gate: SidecarContainers
```
```yaml
# 方案 3:Pod 级别的 init 进程设置
apiVersion: v1
kind: Pod
metadata:
name: signal-demo
spec:
# Kubernetes 1.28+ 支持设置 Pod 的 PID namespace
# 确保 PID 1 被正确处理
containers:
- name: app
image: myapp:v1
```
### 其他注意事项
- **不要在同一 Pod 内运行多个不相关的服务** — 这违反了 Pod 的设计初衷,应该拆分为多个 Pod
- **使用 Liveness Probe 要谨慎** — 错误的 liveness 探针配置会导致容器反复重启(restart loop),建议先用 readiness 验证探针逻辑
- **设置资源 requests** — 不设 requests 的 Pod 会变成 BestEffort QoS,在节点压力下最先被驱逐
- **避免使用 hostNetwork** — 除非必要(如 CNI 插件、Ingress Controller),否则不要使用 hostNetwork,它会占用宿主机端口并绕过 Kubernetes 网络策略
## 延伸阅读
- [[k8s-02-architecture]] — Kubernetes 整体架构,理解 Pod 在集群中的位置
- [[k8s-04-deployment-and-replicaset]] — Deployment 如何管理 Pod 的副本与滚动更新
- [[k8s-scheduler]] — 调度器完整机制,包括亲和性、污点容忍、拓扑分布等