🗃️Usage Notes

Aug 7, 2024

Subsections of 🗃️Usage Notes

Subsections of Application

有状态or无状态应用

对应用“有状态”和“无状态”的清晰界定,直接决定了它在Kubernetes中的部署方式、资源类型和运维复杂度。


一、核心定义

1. 无状态应用

定义:应用实例不负责保存每次请求所需的上下文或数据状态。任何一个请求都可以被任何一个实例处理,且处理结果完全一致。

关键特征

  • 请求自包含:每个请求包含了处理它所需的所有信息(如认证Token、Session ID、操作数据等)。
  • 实例可替代:任何一个实例都是完全相同、可以随时被创建或销毁的。销毁一个实例不会丢失任何数据。
  • 无本地持久化:实例的本地磁盘不被用于保存需要持久化的数据。即使有临时数据,实例销毁后也无需关心。
  • 水平扩展容易:因为实例完全相同,所以直接增加实例数量就能实现扩展,非常简单。

典型例子

  • Web前端服务器:如Nginx, Apache。
  • API网关:如Kong, Tyk。
  • JWT令牌验证服务
  • 无状态计算服务:如图片转换、数据格式转换等。输入和输出都在请求中完成。

一个生动的比喻快餐店的收银员。 任何一个收银员都可以为你服务,你点餐(请求),他处理,完成后交易结束。他不需要记住你上次点了什么(状态),你下次来可以去任何一个窗口。

2. 有状态应用

定义:应用实例需要保存和维护特定的状态数据。后续请求的处理依赖于之前请求保存的状态,或者会改变这个状态。

关键特征

  • 状态依赖性:请求的处理结果依赖于该实例上保存的特定状态(如用户会话、数据库中的记录、缓存数据等)。
  • 实例唯一性:每个实例都是独特的,有唯一的身份标识(如ID、主机名)。不能随意替换。
  • 需要持久化存储:实例的状态必须被保存在持久化存储中,并且即使实例重启、迁移或重建,这个存储也必须能被重新挂载和访问。
  • 水平扩展复杂:扩展时需要谨慎处理数据分片、副本同步、身份识别等问题。

典型例子

  • 数据库:MySQL, PostgreSQL, MongoDB, Redis。
  • 消息队列:Kafka, RabbitMQ。
  • 有状态中间件:如Etcd, Zookeeper。
  • 用户会话服务器:将用户Session保存在本地内存或磁盘的应用。

一个生动的比喻银行的客户经理。 你有一个指定的客户经理(特定实例),他了解你的所有财务历史和需求(状态)。如果你换了一个新经理,他需要花时间从头了解你的情况,而且可能无法立即获得你所有的历史文件(数据)。


二、在Kubernetes中的关键差异

这个界定在K8s中至关重要,因为它决定了你使用哪种工作负载资源。

特性无状态应用有状态应用
核心K8s资源DeploymentStatefulSet
Pod身份完全可互换,无唯一标识。名字是随机的(如 app-7c8b5f6d9-abcde)。有稳定、唯一的标识符,按顺序生成(如 mysql-0, mysql-1, mysql-2)。
启动/终止顺序并行,无顺序。有序部署(从0到N-1),有序扩缩容(从N-1到0),有序滚动更新
网络标识不稳定的Pod IP。通过Service负载均衡访问。稳定的网络标识。每个Pod会有一个稳定的DNS记录:<pod-name>.<svc-name>.<namespace>.svc.cluster.local
存储使用PersistentVolumeClaim模板,所有Pod共享同一个PVC或各自使用独立的、无关联的PVC。使用稳定的、专用的存储。每个Pod根据它的身份标识,挂载一个独立的PVC(如 mysql-0 -> pvc-mysql-0)。
数据持久性Pod被删除,其关联的PVC通常也会被删除(取决于回收策略)。Pod即使被调度到其他节点,也能通过稳定标识重新挂载到属于它的那块持久化数据。
典型场景Web服务器、微服务、API数据库、消息队列、集群化应用(如Zookeeper)

三、一个常见的误区:“看似无状态,实则有状态”

有些应用初看像无状态,但深究起来其实是有状态的。

  • 误区:一个将用户Session保存在本地内存的Web应用。
    • 看似:它是一个Web服务,可以通过Deployment部署多个副本。
    • 实则:如果用户第一次请求被pod-a处理,Session保存在了pod-a的内存中。下次请求如果被负载均衡到pod-bpod-b无法获取到该用户的Session,导致用户需要重新登录。
    • 解决方案
      1. 改造为无状态:将Session数据外移到集中式的Redis或数据库中。
      2. 承认其有状态:使用StatefulSet,并配合Session亲和性,确保同一用户的请求总是被发到同一个Pod实例上。

总结

如何界定一个应用是有状态还是无状态?

问自己这几个问题:

  1. 这个应用的实例能被随意杀死并立即创建一个新的替代吗? 替代者能无缝接管所有工作吗?
    • -> 无状态
    • 不能 -> 有状态
  2. 应用的多个实例是完全相同的吗? 增加一个实例需要复制数据吗?
    • 是,不需要 -> 无状态
    • 否,需要 -> 有状态
  3. 处理请求是否需要依赖实例本地(内存/磁盘)的、非临时性的数据?
    • -> 无状态
    • -> 有状态

理解这个界定,是正确设计和部署云原生应用的基石。在K8s中,对于无状态应用,请首选 Deployment;对于有状态应用,请务必使用 StatefulSet

Mar 7, 2025

Subsections of Building Tool

Maven

1. build from submodule

You dont need to build from the head of project.

./mvnw clean package -DskipTests  -rf :<$submodule-name>

you can find the <$submodule-name> from submodule ’s pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

	<modelVersion>4.0.0</modelVersion>

	<parent>
		<groupId>org.apache.flink</groupId>
		<artifactId>flink-formats</artifactId>
		<version>1.20-SNAPSHOT</version>
	</parent>

	<artifactId>flink-avro</artifactId>
	<name>Flink : Formats : Avro</name>

Then you can modify the command as

./mvnw clean package -DskipTests  -rf :flink-avro
The result will look like this
[WARNING] For this reason, future Maven versions might no longer support building such malformed projects.
[WARNING] 
[INFO] ------------------------------------------------------------------------
[INFO] Detecting the operating system and CPU architecture
[INFO] ------------------------------------------------------------------------
[INFO] os.detected.name: linux
[INFO] os.detected.arch: x86_64
[INFO] os.detected.bitness: 64
[INFO] os.detected.version: 6.7
[INFO] os.detected.version.major: 6
[INFO] os.detected.version.minor: 7
[INFO] os.detected.release: fedora
[INFO] os.detected.release.version: 38
[INFO] os.detected.release.like.fedora: true
[INFO] os.detected.classifier: linux-x86_64
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] Flink : Formats : Avro                                             [jar]
[INFO] Flink : Formats : SQL Avro                                         [jar]
[INFO] Flink : Formats : Parquet                                          [jar]
[INFO] Flink : Formats : SQL Parquet                                      [jar]
[INFO] Flink : Formats : Orc                                              [jar]
[INFO] Flink : Formats : SQL Orc                                          [jar]
[INFO] Flink : Python                                                     [jar]
...

Normally, build Flink will start from module flink-parent

2. skip some other test

For example, you can skip RAT test by doing this:

./mvnw clean package -DskipTests '-Drat.skip=true'
Mar 11, 2024

Gradle

1. spotless

keep your code spotless, check more detail in https://github.com/diffplug/spotless

see how to configuration

there are several files need to configure.

  1. settings.gradle.kts
plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
}
  1. build.gradle.kts
plugins {
    id("com.diffplug.spotless") version "6.23.3"
}
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
    kotlinGradle {
        target("**/*.kts")
        ktlint()
    }
    java {
        target("**/*.java")
        googleJavaFormat()
            .reflowLongStrings()
            .skipJavadocFormatting()
            .reorderImports(false)
    }
    yaml {
        target("**/*.yaml")
        jackson()
            .feature("ORDER_MAP_ENTRIES_BY_KEYS", true)
    }
    json {
        target("**/*.json")
        targetExclude(".vscode/settings.json")
        jackson()
            .feature("ORDER_MAP_ENTRIES_BY_KEYS", true)
    }
}

And the, you can execute follwoing command to format your code.

./gradlew spotlessApply
./mvnw spotless:apply

2. shadowJar

shadowjar could combine a project’s dependency classes and resources into a single jar. check https://imperceptiblethoughts.com/shadow/

see how to configuration

you need moidfy your build.gradle.kts

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
    java // Optional 
    id("com.github.johnrengelman.shadow") version "8.1.1"
}

tasks.named<ShadowJar>("shadowJar") {
    archiveBaseName.set("connector-shadow")
    archiveVersion.set("1.0")
    archiveClassifier.set("")
    manifest {
        attributes(mapOf("Main-Class" to "com.example.xxxxx.Main"))
    }
}
./gradlew shadowJar

3. check dependency

list your project’s dependencies in tree view

see how to configuration

you need moidfy your build.gradle.kts

configurations {
    compileClasspath
}
./gradlew dependencies --configuration compileClasspath
./gradlew :<$module_name>:dependencies --configuration compileClasspath
Check Potential Result

result will look like this

compileClasspath - Compile classpath for source set 'main'.
+--- org.projectlombok:lombok:1.18.22
+--- org.apache.flink:flink-hadoop-fs:1.17.1
|    \--- org.apache.flink:flink-core:1.17.1
|         +--- org.apache.flink:flink-annotations:1.17.1
|         |    \--- com.google.code.findbugs:jsr305:1.3.9 -> 3.0.2
|         +--- org.apache.flink:flink-metrics-core:1.17.1
|         |    \--- org.apache.flink:flink-annotations:1.17.1 (*)
|         +--- org.apache.flink:flink-shaded-asm-9:9.3-16.1
|         +--- org.apache.flink:flink-shaded-jackson:2.13.4-16.1
|         +--- org.apache.commons:commons-lang3:3.12.0
|         +--- org.apache.commons:commons-text:1.10.0
|         |    \--- org.apache.commons:commons-lang3:3.12.0
|         +--- commons-collections:commons-collections:3.2.2
|         +--- org.apache.commons:commons-compress:1.21 -> 1.24.0
|         +--- org.apache.flink:flink-shaded-guava:30.1.1-jre-16.1
|         \--- com.google.code.findbugs:jsr305:1.3.9 -> 3.0.2
...
Mar 7, 2024

CICD

Articles

    FQA

    Q1: difference between docker\podmn\buildah

    You can add standard markdown syntax:

    • multiple paragraphs
    • bullet point lists
    • emphasized, bold and even bold emphasized text
    • links
    • etc.
    ...and even source code

    the possibilities are endless (almost - including other shortcodes may or may not work)

    Mar 7, 2025

    Container

    Articles

    FQA

    Q1: difference between docker\podmn\buildah

    You can add standard markdown syntax:

    • multiple paragraphs
    • bullet point lists
    • emphasized, bold and even bold emphasized text
    • links
    • etc.
    ...and even source code

    the possibilities are endless (almost - including other shortcodes may or may not work)

    Mar 7, 2025

    Subsections of Container

    Build Smaller Image

    减小 Dockerfile 生成镜像体积的方法

    1. 选择更小的基础镜像

    # ❌ 避免使用完整版本
    FROM ubuntu:latest
    
    # ✅ 使用精简版本
    FROM alpine:3.18
    FROM python:3.11-slim
    FROM node:18-alpine

    2. 使用多阶段构建 (Multi-stage Build)

    这是最有效的方法之一:

    # 构建阶段
    FROM golang:1.21 AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp
    
    # 运行阶段 - 只复制必要文件
    FROM alpine:3.18
    WORKDIR /app
    COPY --from=builder /app/myapp .
    CMD ["./myapp"]

    3. 合并 RUN 指令

    每个 RUN 命令都会创建一个新层:

    # ❌ 多层,体积大
    RUN apt-get update
    RUN apt-get install -y package1
    RUN apt-get install -y package2
    
    # ✅ 单层,并清理缓存
    RUN apt-get update && \
        apt-get install -y package1 package2 && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/*

    4. 清理不必要的文件

    RUN apt-get update && \
        apt-get install -y build-essential && \
        # 构建操作... && \
        apt-get purge -y build-essential && \
        apt-get autoremove -y && \
        rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

    5. 使用 .dockerignore 文件

    # .dockerignore
    node_modules
    .git
    *.md
    .env
    test/

    6. 只复制必要的文件

    # ❌ 复制所有内容
    COPY . .
    
    # ✅ 只复制需要的文件
    COPY package.json package-lock.json ./
    RUN npm ci --only=production
    COPY src/ ./src/

    7. 移除调试工具和文档

    RUN apk add --no-cache python3 && \
        rm -rf /usr/share/doc /usr/share/man

    8. 压缩和优化层

    # 在单个 RUN 中完成所有操作
    RUN set -ex && \
        apk add --no-cache --virtual .build-deps gcc musl-dev && \
        pip install --no-cache-dir -r requirements.txt && \
        apk del .build-deps

    9. 使用专门的工具

    • dive: 分析镜像层
      dive your-image:tag
    • docker-slim: 自动精简镜像
      docker-slim build your-image:tag

    实际案例对比

    优化前 (1.2GB):

    FROM ubuntu:20.04
    RUN apt-get update
    RUN apt-get install -y python3 python3-pip
    COPY . /app
    WORKDIR /app
    RUN pip3 install -r requirements.txt
    CMD ["python3", "app.py"]

    优化后 (50MB):

    FROM python:3.11-alpine AS builder
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir --user -r requirements.txt
    
    FROM python:3.11-alpine
    WORKDIR /app
    COPY --from=builder /root/.local /root/.local
    COPY app.py .
    ENV PATH=/root/.local/bin:$PATH
    CMD ["python", "app.py"]

    关键要点总结

    ✅ 使用 Alpine 或 slim 镜像
    ✅ 采用多阶段构建
    ✅ 合并命令并清理缓存
    ✅ 配置 .dockerignore
    ✅ 只安装生产环境依赖
    ✅ 删除构建工具和临时文件

    通过这些方法,镜像体积通常可以减少 60-90%!

    Mar 7, 2024

    Network Mode

    Docker的网络模式决定了容器如何与宿主机、其他容器以及外部网络进行通信。

    Docker主要提供了以下五种网络模式,默认创建的是 bridge 模式。


    1. Bridge 模式

    这是 默认 的网络模式。当你创建一个容器而不指定网络时,它就会连接到这个默认的 bridge 网络(名为 bridge)。

    • 工作原理:Docker守护进程会创建一个名为 docker0 的虚拟网桥,它相当于一个虚拟交换机。所有使用该模式的容器都会通过一个虚拟网卡(veth pair)连接到这个网桥上。Docker会为每个容器分配一个IP地址,并配置其网关为 docker0 的地址。
    • 通信方式
      • 容器间通信:在同一个自定义桥接网络下的容器,可以通过容器名(Container Name)直接通信(Docker内嵌了DNS)。但在默认的 bridge 网络下,容器只能通过IP地址通信。
      • 访问外部网络:容器数据包通过 docker0 网桥,再经过宿主机的IPtables进行NAT转换,使用宿主机的IP访问外网。
      • 从外部访问容器:需要做端口映射,例如 -p 8080:80,将宿主机的8080端口映射到容器的80端口。

    优劣分析

    • 优点
      • 隔离性:容器拥有独立的网络命名空间,与宿主机和其他网络隔离,安全性较好。
      • 端口管理灵活:通过端口映射,可以灵活地管理哪些宿主机端口暴露给外部。
      • 通用性:是最常用、最通用的模式,适合大多数应用场景。
    • 缺点
      • 性能开销:相比 host 模式,多了一层网络桥接和NAT,性能有轻微损失。
      • 复杂度:在默认桥接网络中,容器间通信需要使用IP,不如自定义网络方便。

    使用场景:绝大多数需要网络隔离的独立应用,例如Web后端服务、数据库等。

    命令示例

    # 使用默认bridge网络(不推荐用于多容器应用)
    docker run -d --name my-app -p 8080:80 nginx
    
    # 创建自定义bridge网络(推荐)
    docker network create my-network
    docker run -d --name app1 --network my-network my-app
    docker run -d --name app2 --network my-network another-app
    # 现在 app1 和 app2 可以通过容器名直接互相访问

    2. Host 模式

    在这种模式下,容器不会虚拟出自己的网卡,也不会分配独立的IP,而是直接使用宿主机的IP和端口

    • 工作原理:容器与宿主机共享同一个Network Namespace。

    优劣分析

    • 优点
      • 高性能:由于没有NAT和网桥开销,网络性能最高,几乎与宿主机原生网络一致。
      • 简单:无需进行复杂的端口映射,容器内使用的端口就是宿主机上的端口。
    • 缺点
      • 安全性差:容器没有网络隔离,可以直接操作宿主机的网络。
      • 端口冲突:容器使用的端口如果与宿主机服务冲突,会导致容器无法启动。
      • 灵活性差:无法在同一台宿主机上运行多个使用相同端口的容器。

    使用场景:对网络性能要求极高的场景,例如负载均衡器、高频交易系统等。在生产环境中需谨慎使用

    命令示例

    docker run -d --name my-app --network host nginx
    # 此时,直接访问 http://<宿主机IP>:80 即可访问容器中的Nginx

    3. None 模式

    在这种模式下,容器拥有自己独立的网络命名空间,但不进行任何网络配置。容器内部只有回环地址 127.0.0.1

    • 工作原理:容器完全与世隔绝。

    优劣分析

    • 优点
      • 绝对隔离:安全性最高,容器完全无法进行任何网络通信。
    • 缺点
      • 无法联网:容器无法与宿主机、其他容器或外部网络通信。

    使用场景

    1. 需要完全离线处理的批处理任务。
    2. 用户打算使用自定义网络驱动(或手动配置)来完全自定义容器的网络栈。

    命令示例

    docker run -d --name my-app --network none alpine
    # 进入容器后,使用 `ip addr` 查看,只能看到 lo 网卡

    4. Container 模式

    这种模式下,新创建的容器不会创建自己的网卡和IP,而是与一个已经存在的容器共享一个Network Namespace。通俗讲,就是两个容器在同一个网络环境下,看到的IP和端口是一样的。

    • 工作原理:新容器复用指定容器的网络栈。

    优劣分析

    • 优点
      • 高效通信:容器间通信直接通过本地回环地址 127.0.0.1,效率极高。
      • 共享网络视图:可以方便地为一个主容器(如Web服务器)搭配一个辅助容器(如日志收集器),它们看到的网络环境完全一致。
    • 缺点
      • 紧密耦合:两个容器的生命周期和网络配置紧密绑定,缺乏灵活性。
      • 隔离性差:共享网络命名空间,存在一定的安全风险。

    使用场景:Kubernetes中的"边车"模式,例如一个Pod内的主容器和日志代理容器。

    命令示例

    docker run -d --name main-container nginx
    docker run -d --name helper-container --network container:main-container busybox
    # 此时,helper-container 中访问 127.0.0.1:80 就是在访问 main-container 的Nginx服务

    5. Overlay 模式

    这是为了实现 跨主机的容器通信 而设计的,是Docker Swarm和Kubernetes等容器编排系统的核心网络方案。

    • 工作原理:它会在多个Docker宿主机之间创建一个虚拟的分布式网络(Overlay Network),通过VXLAN等隧道技术,让不同宿主机上的容器感觉像是在同一个大的局域网内。

    优劣分析

    • 优点
      • 跨节点通信:解决了集群环境下容器间通信的根本问题。
      • 安全:支持网络加密。
    • 缺点
      • 配置复杂:需要额外的Key-Value存储(如Consul、Etcd)来同步网络状态(Docker Swarm模式内置了此功能)。
      • 性能开销:数据包需要封装和解封装,有一定性能损耗,但现代硬件上通常可以接受。

    使用场景:Docker Swarm集群、Kubernetes集群等分布式应用环境。

    命令示例(在Swarm模式下):

    # 初始化Swarm
    docker swarm init
    
    # 创建Overlay网络
    docker network create -d overlay my-overlay-net
    
    # 在Overlay网络中创建服务
    docker service create --name web --network my-overlay-net -p 80:80 nginx

    总结对比

    网络模式隔离性性能灵活性适用场景
    Bridge(默认)良好通用场景,单机多容器应用
    Host最高对性能要求极致,不介意端口冲突
    None最高-离线任务,完全自定义网络
    Container容器紧密协作(如边车模式)
    Overlay良好集群场景,跨主机容器通信

    最佳实践建议

    1. 单机应用:优先使用 自定义的Bridge网络,它比默认Bridge网络提供了更好的DNS服务发现功能,方便容器间通过名称通信。
    2. 集群应用:必须使用 Overlay网络
    3. 性能极致追求:在确认端口安全和无冲突的前提下,可考虑 Host模式
    4. 安全隔离:对于无需网络的容器,使用 None模式
    5. 避免在生产环境大量使用默认的bridge网络和container模式,因为它们分别在DNS发现和容器耦合度上存在不足。
    Mar 7, 2024

    Container Principle

    Linux 容器技术的基础原理

    容器技术是现代云原生架构的基石。让我深入讲解 Linux 容器的核心原理。


    🎯 容器的本质

    容器 = 特殊的进程

    容器不是虚拟机,它本质上是宿主机上的一个受限进程,通过 Linux 内核的三大核心技术实现隔离和资源限制:

    ┌─────────────────────────────────────────┐
    │         Linux 容器三大支柱               │
    ├─────────────────────────────────────────┤
    │  1. Namespace (命名空间) - 隔离         │
    │  2. Cgroups (控制组) - 资源限制         │
    │  3. Union FS (联合文件系统) - 镜像分层   │
    └─────────────────────────────────────────┘

    🔒 Namespace (命名空间) - 视图隔离

    Namespace 是 Linux 内核提供的一种资源隔离机制,让进程只能看到属于自己的资源。

    七种 Namespace

    Namespace隔离内容内核版本示例
    PID进程 ID2.6.24容器内 PID 1 = 宿主机 PID 12345
    Network网络栈2.6.29独立的 IP、端口、路由表
    Mount文件系统挂载点2.4.19独立的根目录
    UTS主机名和域名2.6.19容器有自己的 hostname
    IPC进程间通信2.6.19消息队列、信号量、共享内存
    User用户和组 ID3.8容器内 root ≠ 宿主机 root
    CgroupCgroup 根目录4.6隔离 cgroup 视图

    1️⃣ PID Namespace (进程隔离)

    原理

    每个容器有独立的进程树,容器内看不到宿主机或其他容器的进程。

    演示

    # 在宿主机上查看进程
    ps aux | grep nginx
    # root  12345  nginx: master process
    
    # 进入容器
    docker exec -it my-container bash
    
    # 在容器内查看进程
    ps aux
    # PID   USER     COMMAND
    # 1     root     nginx: master process  ← 容器内看到的 PID 是 1
    # 25    root     nginx: worker process
    
    # 实际上宿主机上这个进程的真实 PID 是 12345

    手动创建 PID Namespace

    // C 代码示例
    #define _GNU_SOURCE
    #include <sched.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>
    
    int child_func(void* arg) {
        printf("Child PID: %d\n", getpid());  // 输出: 1
        sleep(100);
        return 0;
    }
    
    int main() {
        printf("Parent PID: %d\n", getpid());  // 输出: 真实 PID
        
        // 创建新的 PID namespace
        char stack[1024*1024];
        int flags = CLONE_NEWPID;
        
        pid_t pid = clone(child_func, stack + sizeof(stack), flags | SIGCHLD, NULL);
        waitpid(pid, NULL, 0);
        return 0;
    }

    核心特点

    • 容器内第一个进程 PID = 1 (init 进程)
    • 父进程(宿主机)可以看到子进程的真实 PID
    • 子进程(容器)看不到父进程和其他容器的进程

    2️⃣ Network Namespace (网络隔离)

    原理

    每个容器有独立的网络栈:独立的 IP、端口、路由表、防火墙规则。

    架构图

    宿主机网络栈
    ├─ eth0 (物理网卡)
    ├─ docker0 (网桥)
    └─ veth pairs (虚拟网卡对)
        ├─ vethXXX (宿主机端) ←→ eth0 (容器端)
        └─ vethYYY (宿主机端) ←→ eth0 (容器端)

    演示

    # 创建新的 network namespace
    ip netns add myns
    
    # 列出所有 namespace
    ip netns list
    
    # 在新 namespace 中执行命令
    ip netns exec myns ip addr
    # 输出: 只有 loopback,没有 eth0
    
    # 创建 veth pair (虚拟网卡对)
    ip link add veth0 type veth peer name veth1
    
    # 将 veth1 移到新 namespace
    ip link set veth1 netns myns
    
    # 配置 IP
    ip addr add 192.168.1.1/24 dev veth0
    ip netns exec myns ip addr add 192.168.1.2/24 dev veth1
    
    # 启动网卡
    ip link set veth0 up
    ip netns exec myns ip link set veth1 up
    ip netns exec myns ip link set lo up
    
    # 测试连通性
    ping 192.168.1.2

    容器网络模式

    Bridge 模式(默认)

    Container A                Container B
        │                          │
      [eth0]                    [eth0]
        │                          │
     vethA ←─────┬─────────→ vethB
                 │
            [docker0 网桥]
                 │
             [iptables NAT]
                 │
             [宿主机 eth0]
                 │
              外部网络

    Host 模式

    Container
        │
        └─ 直接使用宿主机网络栈 (没有网络隔离)

    3️⃣ Mount Namespace (文件系统隔离)

    原理

    每个容器有独立的挂载点视图,看到不同的文件系统树。

    演示

    # 创建隔离的挂载环境
    unshare --mount /bin/bash
    
    # 在新 namespace 中挂载
    mount -t tmpfs tmpfs /tmp
    
    # 查看挂载点
    mount | grep tmpfs
    # 这个挂载只在当前 namespace 可见
    
    # 退出后,宿主机看不到这个挂载
    exit
    mount | grep tmpfs  # 找不到

    容器的根文件系统

    # Docker 使用 chroot + pivot_root 切换根目录
    # 容器内 / 实际是宿主机的某个目录
    
    # 查看容器的根文件系统位置
    docker inspect my-container | grep MergedDir
    # "MergedDir": "/var/lib/docker/overlay2/xxx/merged"
    
    # 在宿主机上访问容器文件系统
    ls /var/lib/docker/overlay2/xxx/merged
    # bin  boot  dev  etc  home  lib  ...

    4️⃣ UTS Namespace (主机名隔离)

    演示

    # 在宿主机
    hostname
    # host-machine
    
    # 创建新 UTS namespace
    unshare --uts /bin/bash
    
    # 修改主机名
    hostname my-container
    
    # 查看主机名
    hostname
    # my-container
    
    # 退出后,宿主机主机名不变
    exit
    hostname
    # host-machine

    5️⃣ IPC Namespace (进程间通信隔离)

    原理

    隔离 System V IPC 和 POSIX 消息队列。

    演示

    # 在宿主机创建消息队列
    ipcmk -Q
    # Message queue id: 0
    
    # 查看消息队列
    ipcs -q
    # ------ Message Queues --------
    # key        msqid      owner
    # 0x52020055 0          root
    
    # 进入容器
    docker exec -it my-container bash
    
    # 在容器内查看消息队列
    ipcs -q
    # ------ Message Queues --------
    # (空,看不到宿主机的消息队列)

    6️⃣ User Namespace (用户隔离)

    原理

    容器内的 root 用户可以映射到宿主机的普通用户,增强安全性。

    配置示例

    # 启用 User Namespace 的容器
    docker run --userns-remap=default -it ubuntu bash
    
    # 容器内
    whoami
    # root
    
    id
    # uid=0(root) gid=0(root) groups=0(root)
    
    # 但在宿主机上,这个进程实际运行在普通用户下
    ps aux | grep bash
    # 100000  12345  bash  ← UID 100000,不是 root

    UID 映射配置

    # /etc/subuid 和 /etc/subgid
    cat /etc/subuid
    # dockremap:100000:65536
    # 表示将容器内的 UID 0-65535 映射到宿主机的 100000-165535

    📊 Cgroups (Control Groups) - 资源限制

    Cgroups 用于限制、记录、隔离进程组的资源使用(CPU、内存、磁盘 I/O 等)。

    Cgroups 子系统

    子系统功能示例
    cpu限制 CPU 使用率容器最多用 50% CPU
    cpuset绑定特定 CPU 核心容器只能用 CPU 0-3
    memory限制内存使用容器最多用 512MB 内存
    blkio限制块设备 I/O容器磁盘读写 100MB/s
    devices控制设备访问容器不能访问 /dev/sda
    net_cls网络流量分类为容器流量打标签
    pids限制进程数量容器最多创建 100 个进程

    CPU 限制

    原理

    使用 CFS (Completely Fair Scheduler) 调度器限制 CPU 时间。

    关键参数

    cpu.cfs_period_us  # 周期时间(默认 100ms = 100000us)
    cpu.cfs_quota_us   # 配额时间
    
    # CPU 使用率 = quota / period
    # 例如: 50000 / 100000 = 50% CPU

    Docker 示例

    # 限制容器使用 0.5 个 CPU 核心
    docker run --cpus=0.5 nginx
    
    # 等价于
    docker run --cpu-period=100000 --cpu-quota=50000 nginx
    
    # 查看 cgroup 配置
    cat /sys/fs/cgroup/cpu/docker/<container-id>/cpu.cfs_quota_us
    # 50000

    手动配置 Cgroups

    # 创建 cgroup
    mkdir -p /sys/fs/cgroup/cpu/mycontainer
    
    # 设置 CPU 限制为 50%
    echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
    echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
    
    # 将进程加入 cgroup
    echo $$ > /sys/fs/cgroup/cpu/mycontainer/cgroup.procs
    
    # 运行 CPU 密集任务
    yes > /dev/null &
    
    # 在另一个终端查看 CPU 使用率
    top -p $(pgrep yes)
    # CPU 使用率被限制在 50% 左右

    内存限制

    关键参数

    memory.limit_in_bytes        # 硬限制
    memory.soft_limit_in_bytes   # 软限制
    memory.oom_control           # OOM 行为控制
    memory.usage_in_bytes        # 当前使用量

    Docker 示例

    # 限制容器使用最多 512MB 内存
    docker run -m 512m nginx
    
    # 查看内存限制
    cat /sys/fs/cgroup/memory/docker/<container-id>/memory.limit_in_bytes
    # 536870912 (512MB)
    
    # 查看当前内存使用
    cat /sys/fs/cgroup/memory/docker/<container-id>/memory.usage_in_bytes

    OOM (Out of Memory) 行为

    # 当容器超过内存限制时
    # 1. 内核触发 OOM Killer
    # 2. 杀死容器内的进程(通常是内存占用最大的)
    # 3. 容器退出,状态码 137
    
    docker ps -a
    # CONTAINER ID   STATUS
    # abc123         Exited (137) 1 minute ago  ← OOM killed

    避免 OOM 的策略

    # 设置 OOM Score Adjustment
    docker run --oom-score-adj=-500 nginx
    # 数值越低,越不容易被 OOM Killer 杀死
    
    # 禁用 OOM Killer (不推荐生产环境)
    docker run --oom-kill-disable nginx

    磁盘 I/O 限制

    Docker 示例

    # 限制读取速度为 10MB/s
    docker run --device-read-bps /dev/sda:10mb nginx
    
    # 限制写入速度为 5MB/s
    docker run --device-write-bps /dev/sda:5mb nginx
    
    # 限制 IOPS
    docker run --device-read-iops /dev/sda:100 nginx
    docker run --device-write-iops /dev/sda:50 nginx

    测试 I/O 限制

    # 在容器内测试写入速度
    docker exec -it my-container bash
    
    dd if=/dev/zero of=/tmp/test bs=1M count=100
    # 写入速度会被限制在 5MB/s

    📦 Union FS (联合文件系统) - 镜像分层

    Union FS 允许多个文件系统分层叠加,实现镜像的复用和高效存储。

    核心概念

    容器可写层 (Read-Write Layer)     ← 容器运行时的修改
    ─────────────────────────────────
    镜像层 4 (Image Layer 4)          ← 只读
    镜像层 3 (Image Layer 3)          ← 只读
    镜像层 2 (Image Layer 2)          ← 只读
    镜像层 1 (Base Layer)             ← 只读
    ─────────────────────────────────
             统一挂载点
          (Union Mount Point)

    常见实现

    文件系统特点使用情况
    OverlayFS性能好,内核原生支持Docker 默认(推荐)
    AUFS成熟稳定,但不在主线内核早期 Docker 默认
    Btrfs支持快照,写时复制适合大规模存储
    ZFS企业级功能,但有许可问题高级用户
    Device Mapper块级存储Red Hat 系列

    OverlayFS 原理

    目录结构

    /var/lib/docker/overlay2/<image-id>/
    ├── diff/          # 当前层的文件变更
    ├── link           # 短链接名称
    ├── lower          # 指向下层的链接
    ├── merged/        # 最终挂载点(容器看到的)
    └── work/          # 工作目录(临时文件)

    实际演示

    # 查看镜像的层结构
    docker image inspect nginx:latest | jq '.[0].RootFS.Layers'
    # [
    #   "sha256:abc123...",  ← Layer 1
    #   "sha256:def456...",  ← Layer 2
    #   "sha256:ghi789..."   ← Layer 3
    # ]
    
    # 启动容器
    docker run -d --name web nginx
    
    # 查看容器的文件系统
    docker inspect web | grep MergedDir
    # "MergedDir": "/var/lib/docker/overlay2/xxx/merged"
    
    # 查看挂载信息
    mount | grep overlay
    # overlay on /var/lib/docker/overlay2/xxx/merged type overlay (rw,lowerdir=...,upperdir=...,workdir=...)

    文件操作的 Copy-on-Write (写时复制)

    # 1. 读取文件(从镜像层)
    docker exec web cat /etc/nginx/nginx.conf
    # 直接从只读的镜像层读取,无需复制
    
    # 2. 修改文件
    docker exec web bash -c "echo 'test' >> /etc/nginx/nginx.conf"
    # 触发 Copy-on-Write:
    # - 从下层复制文件到容器可写层
    # - 在可写层修改文件
    # - 下次读取时,从可写层读取(覆盖下层)
    
    # 3. 删除文件
    docker exec web rm /var/log/nginx/access.log
    # 创建 whiteout 文件,标记删除
    # 文件在镜像层仍存在,但容器内看不到

    Whiteout 文件(删除标记)

    # 在容器可写层
    ls -la /var/lib/docker/overlay2/xxx/diff/var/log/nginx/
    # c--------- 1 root root 0, 0 Oct 11 10:00 .wh.access.log
    # 字符设备文件,主次设备号都是 0,表示删除标记

    镜像分层的优势

    1. 共享层,节省空间

    # 假设有 10 个基于 ubuntu:20.04 的镜像
    # 不使用分层:10 × 100MB = 1GB
    # 使用分层:100MB (ubuntu base) + 10 × 10MB (应用层) = 200MB
    # 节省空间:80%

    2. 快速构建

    FROM ubuntu:20.04                    # Layer 1 (缓存)
    RUN apt-get update                   # Layer 2 (缓存)
    RUN apt-get install -y nginx         # Layer 3 (缓存)
    COPY app.conf /etc/nginx/            # Layer 4 (需要重建)
    COPY app.js /var/www/                # Layer 5 (需要重建)
    
    # 如果只修改 app.js,只需要重建 Layer 5
    # 前面的层都从缓存读取

    3. 快速分发

    # 拉取镜像时,只下载本地没有的层
    docker pull nginx:1.21
    # Already exists: Layer 1 (ubuntu base)
    # Downloading:    Layer 2 (nginx files)
    # Downloading:    Layer 3 (config)

    🔗 容器技术完整流程

    Docker 创建容器的完整过程

    docker run -d --name web \
      --cpus=0.5 \
      -m 512m \
      -p 8080:80 \
      nginx:latest

    内部执行流程

    1. 拉取镜像(如果本地没有)
       └─ 下载各层,存储到 /var/lib/docker/overlay2/
    
    2. 创建 Namespace
       ├─ PID Namespace (隔离进程)
       ├─ Network Namespace (隔离网络)
       ├─ Mount Namespace (隔离文件系统)
       ├─ UTS Namespace (隔离主机名)
       ├─ IPC Namespace (隔离进程间通信)
       └─ User Namespace (隔离用户)
    
    3. 配置 Cgroups
       ├─ cpu.cfs_quota_us = 50000 (50% CPU)
       └─ memory.limit_in_bytes = 536870912 (512MB)
    
    4. 挂载文件系统 (OverlayFS)
       ├─ lowerdir: 镜像只读层
       ├─ upperdir: 容器可写层
       ├─ workdir: 工作目录
       └─ merged: 统一视图挂载点
    
    5. 配置网络
       ├─ 创建 veth pair
       ├─ 一端连接到容器的 Network Namespace
       ├─ 另一端连接到 docker0 网桥
       ├─ 分配 IP 地址
       └─ 配置 iptables NAT 规则 (端口映射)
    
    6. 切换根目录
       ├─ chroot 或 pivot_root
       └─ 容器内看到的 / 是 merged 目录
    
    7. 启动容器进程
       ├─ 在新的 Namespace 中
       ├─ 受 Cgroups 限制
       └─ 使用新的根文件系统
       └─ 执行 ENTRYPOINT/CMD
    
    8. 容器运行中
       └─ containerd-shim 监控进程

    🛠️ 手动创建容器(无 Docker)

    完整示例:从零创建容器

    #!/bin/bash
    # 手动创建一个简单的容器
    
    # 1. 准备根文件系统
    mkdir -p /tmp/mycontainer/rootfs
    cd /tmp/mycontainer/rootfs
    
    # 下载 busybox 作为基础系统
    wget https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox
    chmod +x busybox
    ./busybox --install -s .
    
    # 创建必要的目录
    mkdir -p bin sbin etc proc sys tmp dev
    
    # 2. 创建启动脚本
    cat > /tmp/mycontainer/start.sh <<'EOF'
    #!/bin/bash
    
    # 创建新的 namespace
    unshare --pid --net --mount --uts --ipc --fork /bin/bash -c '
        # 挂载 proc
        mount -t proc proc /proc
        
        # 设置主机名
        hostname mycontainer
        
        # 启动 shell
        /bin/sh
    '
    EOF
    
    chmod +x /tmp/mycontainer/start.sh
    
    # 3. 启动容器
    chroot /tmp/mycontainer/rootfs /tmp/mycontainer/start.sh

    配置 Cgroups 限制

    # 创建 cgroup
    mkdir -p /sys/fs/cgroup/memory/mycontainer
    mkdir -p /sys/fs/cgroup/cpu/mycontainer
    
    # 设置内存限制 256MB
    echo 268435456 > /sys/fs/cgroup/memory/mycontainer/memory.limit_in_bytes
    
    # 设置 CPU 限制 50%
    echo 50000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us
    echo 100000 > /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us
    
    # 将容器进程加入 cgroup
    echo $CONTAINER_PID > /sys/fs/cgroup/memory/mycontainer/cgroup.procs
    echo $CONTAINER_PID > /sys/fs/cgroup/cpu/mycontainer/cgroup.procs

    🔍 容器 vs 虚拟机

    架构对比

    虚拟机架构:
    ┌─────────────────────────────────────┐
    │  App A  │  App B  │  App C          │
    ├─────────┼─────────┼─────────────────┤
    │ Bins/Libs│ Bins/Libs│ Bins/Libs      │
    ├─────────┼─────────┼─────────────────┤
    │ Guest OS│ Guest OS│ Guest OS        │  ← 每个 VM 都有完整 OS
    ├─────────┴─────────┴─────────────────┤
    │       Hypervisor (VMware/KVM)       │
    ├─────────────────────────────────────┤
    │         Host Operating System       │
    ├─────────────────────────────────────┤
    │         Hardware                    │
    └─────────────────────────────────────┘
    
    容器架构:
    ┌─────────────────────────────────────┐
    │  App A  │  App B  │  App C          │
    ├─────────┼─────────┼─────────────────┤
    │ Bins/Libs│ Bins/Libs│ Bins/Libs      │
    ├─────────────────────────────────────┤
    │  Docker Engine / containerd         │
    ├─────────────────────────────────────┤
    │    Host Operating System (Linux)    │  ← 共享内核
    ├─────────────────────────────────────┤
    │         Hardware                    │
    └─────────────────────────────────────┘

    性能对比

    维度虚拟机容器
    启动时间分钟级秒级
    资源占用GB 级内存MB 级内存
    性能开销5-10%< 1%
    隔离程度完全隔离(硬件级)进程隔离(OS 级)
    安全性更高(独立内核)较低(共享内核)
    密度每台物理机 10-50 个每台物理机 100-1000 个

    ⚠️ 容器的安全性考虑

    1. 共享内核的风险

    # 容器逃逸:如果内核有漏洞,容器可能逃逸到宿主机
    
    # 缓解措施:
    # - 使用 User Namespace
    # - 运行容器为非 root 用户
    # - 使用 Seccomp 限制系统调用
    # - 使用 AppArmor/SELinux

    2. 特权容器的危险

    # 特权容器可以访问宿主机所有设备
    docker run --privileged ...
    
    # ❌ 危险:容器内可以:
    # - 加载内核模块
    # - 访问宿主机所有设备
    # - 修改宿主机网络配置
    # - 读写宿主机任意文件
    
    # ✅ 最佳实践:避免使用特权容器

    3. Capability 控制

    # 只授予容器必要的权限
    docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx
    
    # 默认 Docker 授予的 Capabilities:
    # - CHOWN, DAC_OVERRIDE, FOWNER, FSETID
    # - KILL, SETGID, SETUID, SETPCAP
    # - NET_BIND_SERVICE, NET_RAW
    # - SYS_CHROOT, MKNOD, AUDIT_WRITE, SETFCAP

    💡 关键要点总结

    容器 = Namespace + Cgroups + Union FS

    1. Namespace (隔离)

      • PID: 进程隔离
      • Network: 网络隔离
      • Mount: 文件系统隔离
      • UTS: 主机名隔离
      • IPC: 进程间通信隔离
      • User: 用户隔离
    2. Cgroups (限制)

      • CPU: 限制处理器使用
      • Memory: 限制内存使用
      • Block I/O: 限制磁盘 I/O
      • Network: 限制网络带宽
    3. Union FS (分层)

      • 镜像分层存储
      • Copy-on-Write
      • 节省空间和带宽

    容器不是虚拟机

    • ✅ 容器是特殊的进程
    • ✅ 共享宿主机内核
    • ✅ 启动快、资源占用少
    • ⚠️ 隔离性不如虚拟机
    • ⚠️ 需要注意安全配置
    Mar 7, 2024

    Subsections of Database

    Elastic Search DSL

    Basic Query

    exist query

    Returns documents that contain an indexed value for a field.

    GET /_search
    {
      "query": {
        "exists": {
          "field": "user"
        }
      }
    }

    The following search returns documents that are missing an indexed value for the user.id field.

    GET /_search
    {
      "query": {
        "bool": {
          "must_not": {
            "exists": {
              "field": "user.id"
            }
          }
        }
      }
    }
    fuzz query

    Returns documents that contain terms similar to the search term, as measured by a Levenshtein edit distance.

    GET /_search
    {
      "query": {
        "fuzzy": {
          "filed_A": {
            "value": "ki"
          }
        }
      }
    }

    Returns documents that contain terms similar to the search term, as measured by a Levenshtein edit distance.

    GET /_search
    {
      "query": {
        "fuzzy": {
          "filed_A": {
            "value": "ki",
            "fuzziness": "AUTO",
            "max_expansions": 50,
            "prefix_length": 0,
            "transpositions": true,
            "rewrite": "constant_score_blended"
          }
        }
      }
    }

    rewrite:

    • constant_score_boolean
    • constant_score_filter
    • top_terms_blended_freqs_N
    • top_terms_boost_N, top_terms_N
    • frequent_terms, score_delegating
    ids query

    Returns documents based on their IDs. This query uses document IDs stored in the _id field.

    GET /_search
    {
      "query": {
        "ids" : {
          "values" : ["2NTC5ZIBNLuBWC6V5_0Y"]
        }
      }
    }
    prefix query

    The following search returns documents where the filed_A field contains a term that begins with ki.

    GET /_search
    {
      "query": {
        "prefix": {
          "filed_A": {
            "value": "ki",
             "rewrite": "constant_score_blended",
             "case_insensitive": true
          }
        }
      }
    }

    You can simplify the prefix query syntax by combining the <field> and value parameters.

    GET /_search
    {
      "query": {
        "prefix" : { "filed_A" : "ki" }
      }
    }
    range query

    Returns documents that contain terms within a provided range.

    GET /_search
    {
      "query": {
        "range": {
          "filed_number": {
            "gte": 10,
            "lte": 20,
            "boost": 2.0
          }
        }
      }
    }
    GET /_search
    {
      "query": {
        "range": {
          "filed_timestamp": {
            "time_zone": "+01:00",        
            "gte": "2020-01-01T00:00:00", 
            "lte": "now"                  
          }
        }
      }
    }
    regex query

    Returns documents that contain terms matching a regular expression.

    GET /_search
    {
      "query": {
        "regexp": {
          "filed_A": {
            "value": "k.*y",
            "flags": "ALL",
            "case_insensitive": true,
            "max_determinized_states": 10000,
            "rewrite": "constant_score_blended"
          }
        }
      }
    }
    term query

    Returns documents that contain an exact term in a provided field.

    You can use the term query to find documents based on a precise value such as a price, a product ID, or a username.

    GET /_search
    {
      "query": {
        "term": {
          "filed_A": {
            "value": "kimchy",
            "boost": 1.0
          }
        }
      }
    }
    wildcard query

    Returns documents that contain terms matching a wildcard pattern.

    A wildcard operator is a placeholder that matches one or more characters. For example, the * wildcard operator matches zero or more characters. You can combine wildcard operators with other characters to create a wildcard pattern.

    GET /_search
    {
      "query": {
        "wildcard": {
          "filed_A": {
            "value": "ki*y",
            "boost": 1.0,
            "rewrite": "constant_score_blended"
          }
        }
      }
    }
    Oct 7, 2024

    HPC

      Mar 7, 2024

      K8s

      Mar 7, 2024

      Subsections of K8s

      K8s的理解

      一、核心定位:云时代的操作系统

      我对 K8s 最根本的理解是:它正在成为数据中心/云环境的“操作系统”。

      • 传统操作系统(如 Windows、Linux):管理的是单台计算机的硬件资源(CPU、内存、硬盘、网络),并为应用程序(进程)提供运行环境。
      • Kubernetes:管理的是一个集群(由多台计算机组成)的资源,并将这些物理机/虚拟机抽象成一个巨大的“资源池”。它在这个池子上调度和运行的不再是简单的进程,而是容器化了的应用程序

      所以,你可以把 K8s 看作是一个分布式的、面向云原生应用的操作系统。


      二、要解决的核心问题:从“动物园”到“牧场”

      在 K8s 出现之前,微服务和容器化架构带来了新的挑战:

      1. 编排混乱:我有成百上千个容器,应该在哪台机器上启动?如何知道它们是否健康?挂了怎么办?如何扩容缩容?
      2. 网络复杂:容器之间如何发现和通信?如何实现负载均衡?
      3. 存储管理:有状态应用的数据如何持久化?容器漂移后数据怎么跟走?
      4. 部署麻烦:如何实现蓝绿部署、金丝雀发布?如何回滚?

      这个时期被称为“集装箱革命”后的“编排战争”时期,各种工具(如 Docker Swarm, Mesos, Nomad)就像是一个混乱的“动物园”。

      K8s 的诞生(源于 Google 内部系统 Borg 的经验)就是为了系统地解决这些问题,它将混乱的“动物园”管理成了一个井然有序的“牧场”。它的核心能力可以概括为:声明式 API 和控制器模式


      三、核心架构与工作模型:大脑与肢体

      K8s 集群主要由控制平面工作节点 组成。

      • 控制平面:集群的大脑

        • kube-apiserver:整个系统的唯一入口,所有组件都必须通过它来操作集群状态。它是“前台总机”。
        • etcd:一个高可用的键值数据库,持久化存储集群的所有状态数据。它是“集群的记忆中心”。
        • kube-scheduler:负责调度,决定 Pod 应该在哪个节点上运行。它是“人力资源部”。
        • kube-controller-manager:运行着各种控制器,不断检查当前状态是否与期望状态一致,并努力驱使其一致。例如,节点控制器、副本控制器等。它是“自动化的管理团队”。
      • 工作节点:干活的肢体

        • kubelet:节点上的“监工”,负责与控制平面通信,管理本节点上 Pod 的生命周期,确保容器健康运行。
        • kube-proxy:负责节点上的网络规则,实现 Service 的负载均衡和网络代理。
        • 容器运行时:如 containerd 或 CRI-O,负责真正拉取镜像和运行容器。

      工作模型的核心:声明式 API 与控制器模式

      1. 你向 kube-apiserver 提交一个 YAML/JSON 文件,声明你期望的应用状态(例如:我要运行 3 个 Nginx 实例)。
      2. etcd 记录下这个期望状态。
      3. 各种控制器会持续地“观察”当前状态,并与 etcd 中的期望状态进行对比。
      4. 如果发现不一致(例如,只有一个 Nginx 实例在运行),控制器就会主动采取行动(例如,再创建两个 Pod),直到当前状态与期望状态一致。
      5. 这个过程是自愈的、自动的

      四、关键对象与抽象:乐高积木

      K8s 通过一系列抽象对象来建模应用,这些对象就像乐高积木:

      1. Pod最小部署和管理单元。一个 Pod 可以包含一个或多个紧密关联的容器(如主容器和 Sidecar 容器),它们共享网络和存储。这是 K8s 的“原子”。
      2. Deployment定义无状态应用。它管理 Pod 的多个副本(Replicas),并提供滚动更新、回滚等强大的部署策略。这是最常用的对象。
      3. Service定义一组 Pod 的访问方式。Pod 是“ ephemeral ”的,IP 会变。Service 提供一个稳定的 IP 和 DNS 名称,并作为负载均衡器,将流量分发给后端的健康 Pod。它是“服务的门户”。
      4. ConfigMap & Secret:将配置信息和敏感数据与容器镜像解耦,实现配置的灵活管理。
      5. Volume:抽象了各种存储解决方案,为 Pod 提供持久化存储。
      6. Namespace:在物理集群内部创建多个虚拟集群,实现资源隔离和多租户管理。
      7. StatefulSet用于部署有状态应用(如数据库)。它为每个 Pod 提供稳定的标识符、有序的部署和扩缩容,以及稳定的持久化存储。
      8. Ingress:管理集群外部访问内部服务的入口,通常提供 HTTP/HTTPS 路由、SSL 终止等功能。它是“集群的流量总入口”。

      五、核心价值与优势

      1. 自动化运维:自动化了应用的部署、扩缩容、故障恢复(自愈)、滚动更新等,极大降低了运维成本。
      2. 声明式配置与不可变基础设施:通过 YAML 文件定义一切,基础设施可版本化、可追溯、可重复。这是 DevOps 和 GitOps 的基石。
      3. 环境一致性 & 可移植性:实现了“一次编写,随处运行”。无论是在本地开发机、测试环境,还是在公有云、混合云上,应用的行为都是一致的。
      4. 高可用性与弹性伸缩:轻松实现应用的多副本部署,并能根据 CPU、内存等指标或自定义指标进行自动扩缩容,从容应对流量高峰。
      5. 丰富的生态系统:拥有一个极其庞大和活跃的社区,提供了大量的工具和扩展(Helm, Operator, Istio等),能解决几乎所有你能想到的问题。

      六、挑战与学习曲线

      K8s 并非银弹,它也有自己的挑战:

      • 复杂性高:概念繁多,架构复杂,学习和运维成本非常高。
      • “配置”沉重:YAML 文件可能非常多,管理起来本身就是一门学问。
      • 网络与存储:虽然是核心抽象,但其底层实现和理解起来依然有相当的门槛。

      总结

      在我看来,Kubernetes 不仅仅是一个容器编排工具,它更是一套云原生应用的管理范式。它通过一系列精妙的抽象,将复杂的分布式系统管理问题标准化、自动化和简单化。虽然入门有门槛,但它已经成为现代应用基础设施的事实标准,是任何从事后端开发、运维、架构设计的人员都必须理解和掌握的核心技术。

      简单来说,K8s 让你能够像管理一台超级计算机一样,去管理一个由成千上万台机器组成的集群。

      Mar 7, 2024

      Cgroup在K8S中起什么作用

      Kubernetes 深度集成 cgroup 来实现容器资源管理和隔离。以下是 cgroup 与 K8s 结合的详细方式:

      1. K8s 资源模型与 cgroup 映射

      1.1 资源请求和限制

      apiVersion: v1
      kind: Pod
      spec:
        containers:
        - name: app
          resources:
            requests:
              memory: "64Mi"
              cpu: "250m"
            limits:
              memory: "128Mi"
              cpu: "500m"
              ephemeral-storage: "2Gi"

      对应 cgroup 配置:

      • cpu.shares = 256 (250m × 1024 / 1000)
      • cpu.cfs_quota_us = 50000 (500m × 100000 / 1000)
      • memory.limit_in_bytes = 134217728 (128Mi)

      2. K8s cgroup 驱动

      2.1 cgroupfs 驱动

      # kubelet 配置
      --cgroup-driver=cgroupfs
      --cgroup-root=/sys/fs/cgroup

      2.2 systemd 驱动(推荐)

      # kubelet 配置
      --cgroup-driver=systemd
      --cgroup-root=/sys/fs/cgroup

      3. K8s cgroup 层级结构

      3.1 cgroup v1 层级

      /sys/fs/cgroup/
      ├── cpu,cpuacct/kubepods/
      │   ├── burstable/pod-uid-1/
      │   │   ├── container-1/
      │   │   └── container-2/
      │   └── guaranteed/pod-uid-2/
      │       └── container-1/
      ├── memory/kubepods/
      └── pids/kubepods/

      3.2 cgroup v2 统一层级

      /sys/fs/cgroup/kubepods/
      ├── pod-uid-1/
      │   ├── container-1/
      │   └── container-2/
      └── pod-uid-2/
          └── container-1/

      4. QoS 等级与 cgroup 配置

      4.1 Guaranteed (最高优先级)

      resources:
        limits:
          cpu: "500m"
          memory: "128Mi"
        requests:
          cpu: "500m" 
          memory: "128Mi"

      cgroup 配置:

      • cpu.shares = 512
      • cpu.cfs_quota_us = 50000
      • oom_score_adj = -998

      4.2 Burstable (中等优先级)

      resources:
        requests:
          cpu: "250m"
          memory: "64Mi"
        # limits 未设置或大于 requests

      cgroup 配置:

      • cpu.shares = 256
      • cpu.cfs_quota_us = -1 (无限制)
      • oom_score_adj = 2-999

      4.3 BestEffort (最低优先级)

      # 未设置 resources

      cgroup 配置:

      • cpu.shares = 2
      • memory.limit_in_bytes = 9223372036854771712 (极大值)
      • oom_score_adj = 1000

      5. 实际 cgroup 配置示例

      5.1 查看 Pod 的 cgroup

      # 找到 Pod 的 cgroup 路径
      cat /sys/fs/cgroup/cpu/kubepods/pod-uid-1/cgroup.procs
      
      # 查看 CPU 配置
      cat /sys/fs/cgroup/cpu/kubepods/pod-uid-1/cpu.shares
      cat /sys/fs/cgroup/cpu/kubepods/pod-uid-1/cpu.cfs_quota_us
      
      # 查看内存配置
      cat /sys/fs/cgroup/memory/kubepods/pod-uid-1/memory.limit_in_bytes

      5.2 使用 cgroup-tools 监控

      # 安装工具
      apt-get install cgroup-tools
      
      # 查看 cgroup 统计
      cgget -g cpu:/kubepods/pod-uid-1
      cgget -g memory:/kubepods/pod-uid-1

      6. K8s 特性与 cgroup 集成

      6.1 垂直 Pod 自动缩放 (VPA)

      apiVersion: autoscaling.k8s.io/v1
      kind: VerticalPodAutoscaler
      spec:
        targetRef:
          apiVersion: "apps/v1"
          kind: Deployment
          name: my-app
        updatePolicy:
          updateMode: "Auto"

      VPA 根据历史使用数据动态调整:

      • 修改 resources.requestsresources.limits
      • kubelet 更新对应的 cgroup 配置

      6.2 水平 Pod 自动缩放 (HPA)

      apiVersion: autoscaling/v2
      kind: HorizontalPodAutoscaler
      spec:
        scaleTargetRef:
          apiVersion: apps/v1
          kind: Deployment
          name: my-app
        minReplicas: 1
        maxReplicas: 10
        metrics:
        - type: Resource
          resource:
            name: cpu
            target:
              type: Utilization
              averageUtilization: 50

      HPA 依赖 cgroup 的 CPU 使用率统计进行决策。

      6.3 资源监控

      # 通过 cgroup 获取容器资源使用
      cat /sys/fs/cgroup/cpu/kubepods/pod-uid-1/cpuacct.usage
      cat /sys/fs/cgroup/memory/kubepods/pod-uid-1/memory.usage_in_bytes
      
      # 使用 metrics-server 收集
      kubectl top pods
      kubectl top nodes

      7. 节点资源管理

      7.1 系统预留资源

      # kubelet 配置
      apiVersion: kubelet.config.k8s.io/v1beta1
      kind: KubeletConfiguration
      systemReserved:
        cpu: "100m"
        memory: "256Mi"
        ephemeral-storage: "1Gi"
      kubeReserved:
        cpu: "200m"
        memory: "512Mi"
        ephemeral-storage: "2Gi"
      evictionHard:
        memory.available: "100Mi"
        nodefs.available: "10%"

      7.2 驱逐策略

      当节点资源不足时,kubelet 根据 cgroup 统计:

      • 监控 memory.usage_in_bytes
      • 监控 cpuacct.usage
      • 触发 Pod 驱逐

      8. 故障排查和调试

      8.1 检查 cgroup 配置

      # 进入节点检查
      docker exec -it node-shell /bin/bash
      
      # 查看 Pod cgroup
      find /sys/fs/cgroup -name "*pod-uid*" -type d
      
      # 检查资源限制
      cat /sys/fs/cgroup/memory/kubepods/pod-uid-1/memory.limit_in_bytes
      cat /sys/fs/cgroup/cpu/kubepods/pod-uid-1/cpu.cfs_quota_us

      8.2 监控 OOM 事件

      # 查看内核日志
      dmesg | grep -i "killed process"
      
      # 查看 cgroup OOM 事件
      grep "kubepods" /var/log/kern.log | grep -i oom

      9. 最佳实践

      9.1 合理设置资源限制

      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
        limits:
          cpu: "200m"    # 不要设置过大
          memory: "256Mi" # 避免内存浪费

      9.2 使用 LimitRange

      apiVersion: v1
      kind: LimitRange
      metadata:
        name: mem-limit-range
      spec:
        limits:
        - default:
            memory: "256Mi"
          defaultRequest:
            memory: "128Mi"
          type: Container

      cgroup 是 K8s 资源管理的基石,通过精细的 cgroup 配置,K8s 实现了多租户环境下的资源隔离、公平调度和稳定性保障。

      Mar 7, 2024

      Headless VS ClusterIP

      Q: headless service 和 普通的service 有什么区别? 只是有没有clusterIP?

      “有没有 ClusterIP” 只是表面现象,其背后是根本不同的服务发现模式和适用场景。


      核心区别:服务发现模式

      • 普通 Service:提供的是 “负载均衡” 式的服务发现。
        • 它抽象了一组 Pod,你访问的是这个抽象的、稳定的 VIP(ClusterIP),然后由 kube-proxy 将流量转发到后端的某个 Pod。
        • 客户端不知道、也不关心具体是哪个 Pod 在处理请求。
      • Headless Service:提供的是 “直接 Pod IP” 式的服务发现。
        • 不会给你一个统一的 VIP,而是直接返回后端所有 Pod 的 IP 地址。
        • 客户端可以直接与任何一个 Pod 通信,并且知道它正在和哪个具体的 Pod 对话。

      详细对比

      特性普通 ServiceHeadless Service
      clusterIP 字段自动分配一个 VIP(如 10.96.123.456),或设置为 None必须设置为 None。这是定义 Headless Service 的标志。
      核心功能负载均衡。作为流量的代理和分发器。服务发现。作为 Pod 的 DNS 记录注册器,不负责流量转发
      DNS 解析结果解析到 Service 的 ClusterIP。解析到所有与 Selector 匹配的 Pod 的 IP 地址
      网络拓扑客户端 -> ClusterIP (VIP) -> (由 kube-proxy 负载均衡) -> 某个 Pod客户端 -> Pod IP
      适用场景标准的微服务、Web 前端/后端 API,任何需要负载均衡的场景。有状态应用集群(如 MySQL, MongoDB, Kafka, Redis Cluster)、需要直接连接特定 Pod 的场景(如 gRPC 长连接、游戏服务器)。

      DNS 解析行为的深入理解

      这是理解两者差异的最直观方式。

      假设我们有一个名为 my-app 的 Service,它选择了 3 个 Pod。

      1. 普通 Service 的 DNS 解析

      • 在集群内,你执行 nslookup my-app(或在 Pod 里用代码查询)。
      • 返回结果1 条 A 记录,指向 Service 的 ClusterIP。
        Name:      my-app
        Address 1: 10.96.123.456
      • 你的应用:连接到 10.96.123.456:port,剩下的交给 Kubernetes 的网络层。

      2. Headless Service 的 DNS 解析

      • 在集群内,你执行 nslookup my-app(注意:Service 的 clusterIP: None)。
      • 返回结果多条 A 记录,直接指向后端所有 Pod 的 IP。
        Name:      my-app
        Address 1: 172.17.0.10
        Address 2: 172.17.0.11
        Address 3: 172.17.0.12
      • 你的应用:会拿到这个 IP 列表,并由客户端自己决定如何连接。比如,它可以:
        • 随机选一个。
        • 实现自己的负载均衡逻辑。
        • 需要连接所有 Pod(比如收集状态)。

      与 StatefulSet 结合的“杀手级应用”

      Headless Service 最经典、最强大的用途就是与 StatefulSet 配合,为有状态应用集群提供稳定的网络标识。

      回顾之前的 MongoDB 例子:

      • StatefulSet: mongodb (3个副本)
      • Headless Service: mongodb-service

      此时,DNS 系统会创建出稳定且可预测的 DNS 记录,而不仅仅是返回 IP 列表:

      • 每个 Pod 获得一个稳定的 DNS 名称

        • mongodb-0.mongodb-service.default.svc.cluster.local
        • mongodb-1.mongodb-service.default.svc.cluster.local
        • mongodb-2.mongodb-service.default.svc.cluster.local
      • 查询 Headless Service 本身的 DNS (mongodb-service) 会返回所有 Pod IP。

      这带来了巨大优势:

      1. 稳定的成员身份:在初始化 MongoDB 副本集时,你可以直接用这些稳定的 DNS 名称来配置成员列表。即使 Pod 重启、IP 变了,它的 DNS 名称永远不变,配置也就永远不会失效。
      2. 直接 Pod 间通信:在 Kafka 或 Redis Cluster 这样的系统中,节点之间需要直接通信来同步数据。它们可以使用这些稳定的 DNS 名称直接找到对方,而不需要经过一个不必要的负载均衡器。
      3. 主从选举与读写分离:客户端应用可以通过固定的 DNS 名称(如 mongodb-0...)直接连接到主节点执行写操作,而通过其他名称连接到从节点进行读操作。

      总结

      你可以这样形象地理解:

      • 普通 Service 像一个公司的“总机号码”

        • 你打电话给总机(ClusterIP),说“我要找技术支持”,接线员(kube-proxy)会帮你转接到一个空闲的技术支持人员(Pod)那里。你不需要知道具体是谁在为你服务。
      • Headless Service 像一个公司的“内部通讯录”

        • 它不提供总机转接服务。它只给你一份所有员工(Pod)的姓名和直拨电话(IP)列表。
        • 特别是对于 StatefulSet,这份通讯录里的每个员工还有自己固定、专属的座位和分机号(稳定的 DNS 名称),比如“张三座位在 A区-001,分机是 8001”。你知道要找谁时,直接打他的分机就行。

      所以,“有没有 ClusterIP” 只是一个开关,这个开关背后选择的是两种截然不同的服务发现和流量治理模式。 对于需要直接寻址、有状态、集群化的应用,Headless Service 是必不可少的基石。

      Mar 7, 2024

      Creating A Pod

      描述 Kubernetes 中一个 Pod 的创建过程,可以清晰地展示了 K8s 各个核心组件是如何协同工作的。

      我们可以将整个过程分为两个主要阶段:控制平面的决策阶段工作节点的执行阶段


      第一阶段:控制平面决策(大脑决策)

      1. 用户提交请求

        • 用户使用 kubectl apply -f pod.yamlkube-apiserver 提交一个 Pod 定义文件。
        • kubectl 会验证配置并将其转换为 JSON 格式,通过 REST API 调用发送给 kube-apiserver。
      2. API Server 处理与验证

        • kube-apiserver 接收到请求后,会进行一系列操作:
          • 身份认证:验证用户身份。
          • 授权:检查用户是否有权限创建 Pod。
          • 准入控制:可能调用一些准入控制器来修改或验证 Pod 对象(例如,注入 Sidecar 容器、设置默认资源限制等)。
        • 所有验证通过后,kube-apiserver 将 Pod 的元数据对象写入 etcd 数据库。此时,Pod 在 etcd 中的状态被标记为 Pending
        • 至此,Pod 的创建请求已被记录,但还未被调度到任何节点。
      3. 调度器决策

        • kube-scheduler 作为一个控制器,通过 watch 机制持续监听 kube-apiserver,发现有一个新的 Pod 被创建且其 nodeName 为空。
        • 调度器开始为这个 Pod 选择一个最合适的节点,它执行两阶段操作:
          • 过滤:根据节点资源(CPU、内存)、污点、节点选择器、存储、镜像拉取等因素过滤掉不合适的节点。
          • 评分:对剩下的节点进行打分(例如,考虑资源均衡、亲和性等),选择得分最高的节点。
        • 做出决策后,kube-scheduler 补丁 的方式更新 kube-apiserver 中该 Pod 的定义,将其 nodeName 字段设置为选定的节点名称。
        • kube-apiserver 再次将这个更新后的信息写入 etcd

      第二阶段:工作节点执行(肢体行动)

      1. kubelet 监听到任务

        • 目标节点上的 kubelet 同样通过 watch 机制监听 kube-apiserver,发现有一个 Pod 被“分配”到了自己所在的节点(即其 nodeName 与自己的节点名匹配)。
        • kubelet 会从 kube-apiserver 读取完整的 Pod 定义。
      2. kubelet 控制容器运行时

        • kubelet 通过 CRI 接口调用本地的容器运行时(如 containerd、CRI-O)。
        • 容器运行时负责:
          • 从指定的镜像仓库拉取容器镜像(如果本地不存在)。
          • 根据 Pod 定义创建启动容器。
      3. 配置容器环境

        • 在启动容器前后,kubelet 还会通过其他接口完成一系列配置:
          • CNI:调用网络插件(如 Calico、Flannel)为 Pod 分配 IP 地址并配置网络。
          • CSI:如果 Pod 使用了持久化存储,会调用存储插件挂载存储卷。
      4. 状态上报

        • 当 Pod 中的所有容器都成功启动并运行后,kubelet 会持续监控容器的健康状态。
        • 它将 Pod 的当前状态(如 Running)和 IP 地址等信息作为状态更新,上报kube-apiserver
        • kube-apiserver 最终将这些状态信息写入 etcd

      总结流程图

      用户 kubectl -> API Server -> (写入) etcd -> Scheduler (绑定节点) -> API Server -> (更新) etcd -> 目标节点 kubelet -> 容器运行时 (拉镜像,启容器) -> CNI/CSI (配网络/存储) -> kubelet -> API Server -> (更新状态) etcd

      核心要点:

      • 声明式 API:用户声明“期望状态”,系统驱动“当前状态”向其靠拢。
      • 监听与协同:所有组件都通过监听 kube-apiserver 来获取任务并协同工作。
      • etcd 作为唯一信源:整个集群的状态始终以 etcd 中的数据为准。
      • 组件职责分离:Scheduler 只管调度,kubelet 只管执行,API Server 只管交互和存储。
      Mar 7, 2024

      Deleting A Pod

      删除一个 Pod 的流程与创建过程相对应,但它更侧重于如何优雅地、安全地终止一个运行中的实例。这个过程同样涉及多个组件的协同。

      下面是一个 Pod 的删除流程,但它的核心是体现 Kubernetes 的优雅终止机制。


      删除流程的核心阶段

      阶段一:用户发起删除指令

      1. 用户执行命令:用户执行 kubectl delete pod <pod-name>
      2. API Server 接收请求
        • kubectlkube-apiserver 发送一个 DELETE 请求。
        • kube-apiserver 会进行认证、授权等验证。
      3. “标记为删除”:验证通过后,kube-apiserver 不会立即从 etcd 中删除该 Pod 对象,而是会执行一个关键操作:为 Pod 对象设置一个“删除时间戳”(deletionTimestamp)并将其标记为 Terminating 状态。这个状态会更新到 etcd 中。

      阶段二:控制平面与节点的通知

      1. 组件感知变化
        • 所有监听 kube-apiserver 的组件(如 kube-scheduler, 各个节点的 kubelet)都会立刻感知到这个 Pod 的状态已变为 Terminating
        • Endpoint Controller 会立刻将这个 Pod 的 IP 从关联的 Service 的 Endpoints(或 EndpointSlice)列表中移除。这意味着新的流量不会再被负载均衡到这个 Pod 上

      阶段三:节点上的优雅终止

      这是最关键的阶段,发生在 Pod 所在的工作节点上。

      1. kubelet 监听到状态变化:目标节点上的 kubelet 通过 watch 机制发现它管理的某个 Pod 被标记为 Terminating

      2. 触发优雅关闭序列

        • 第1步:执行 PreStop Hook(如果配置了的话) kubelet 会首先执行 Pod 中容器定义的 preStop 钩子。这是一个在发送终止信号之前执行的特定命令或 HTTP 请求。常见用途包括:
          • 通知上游负载均衡器此实例正在下线。
          • 让应用完成当前正在处理的请求。
          • 执行一些清理任务。
        • 第2步:发送 SIGTERM 信号 kubelet 通过容器运行时向 Pod 中的每个容器的主进程发送 SIGTERM(信号 15)信号。这是一个“优雅关闭”信号,通知应用:“你即将被终止,请保存状态、完成当前工作并自行退出”。
          • 注意SIGTERMpreStop Hook 是并行执行的。Kubernetes 会等待两者中的一个先完成,再进入下一步。
      3. 等待终止宽限期

        • 在发送 SIGTERM 之后,Kubernetes 不会立即杀死容器。它会等待一个称为 terminationGracePeriodSeconds 的时长(默认为 30 秒)。
        • 理想情况下,容器内的应用程序捕获到 SIGTERM 信号后,会开始优雅关闭流程,并在宽限期内自行退出。

      阶段四:强制终止与清理

      1. 宽限期后的处理

        • 情况A:优雅关闭成功:如果在宽限期内,所有容器都成功停止,kubelet 会通知容器运行时清理容器资源,然后进行下一步。
        • 情况B:优雅关闭失败:如果宽限期结束后,容器仍未停止,kubelet 会触发强制杀死。它向容器的主进程发送 SIGKILL(信号 9) 信号,该信号无法被捕获或忽略,会立即终止进程。
      2. 清理资源

        • 容器被强制或优雅地终止后,kubelet 会通过容器运行时清理容器资源。
        • 同时,kubelet 会清理 Pod 的网络资源(通过 CNI 插件)和存储资源(卸载 Volume)。
      3. 上报最终状态

        • kubelet 向 kube-apiserver 发送最终信息,确认 Pod 已完全停止。
        • kube-apiserver 随后从 etcd正式删除该 Pod 的对象记录。至此,这个 Pod 才真正从系统中消失。

      总结流程图

      用户 kubectl delete -> API Server -> (在etcd中标记Pod为 Terminating) -> Endpoint Controller (从Service中移除IP) -> 目标节点 kubelet -> 执行 PreStop Hook -> 发送 SIGTERM 信号 -> (等待 terminationGracePeriodSeconds) -> [成功则清理 / 失败则发送 SIGKILL] -> 清理网络/存储 -> kubelet -> API Server -> (从etcd中删除对象)

      关键要点

      1. 优雅终止是核心:Kubernetes 给了应用一个自我清理的机会,这是保证服务无损发布和滚动更新的基石。
      2. 流量切断先行:Pod 被从 Service 的 Endpoints 中移除是第一步,这确保了在 Pod 开始关闭前,不会有新流量进来。
      3. 两个关键配置
        • terminationGracePeriodSeconds:决定了应用有多长时间来自行关闭。
        • preStop Hook:提供了一个主动执行关闭脚本的机会,比单纯等待 SIGTERM 更可靠。
      4. 强制终止作为保障:如果应用无法响应优雅关闭信号,Kubernetes 有最后的强制手段来保证资源被释放。

      理解这个流程对于设计健壮的、能够正确处理关闭信号的微服务至关重要。

      Mar 7, 2024

      Deployment VS ReplicaSet

      下面我会从 架构、工作流、控制循环、数据结构与事件链 等层面详细说明它们是怎么工作的。


      🧩 一、核心概念层次关系

      先看一下层级:

      Deployment → ReplicaSet → Pod
      层级职责控制器类型
      Deployment负责声明“应用版本”和“滚动更新策略”高级控制器(managing controller)
      ReplicaSet保证指定数量的 Pod 副本数基础控制器(ensuring controller)
      Pod最小可调度单元,运行实际容器工作负载对象

      可以理解为:

      Deployment 是策略控制器,ReplicaSet 是数量控制器,Pod 是执行单元。


      ⚙️ 二、Deployment 的工作原理(上层控制器)

      1️⃣ Deployment 对象定义

      你在创建一个 Deployment 时,例如:

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: webapp
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: webapp
        template:
          metadata:
            labels:
              app: webapp
          spec:
            containers:
            - name: nginx
              image: nginx:1.25

      这会创建一个 Deployment 对象并写入 etcd。


      2️⃣ Deployment Controller 发现新对象

      kube-controller-manager 中的 Deployment Controller 通过 Informer + Shared Indexer Cache 订阅(watch)Deployment 资源变化。

      一旦发现新 Deployment,它会执行以下逻辑:

      syncDeployment(deployment):
          rsList := list ReplicaSets matching deployment.selector
          newRS := findReplicaSetMatchingPodTemplate(deployment.spec.template)
          if newRS == nil:
              newRS = createReplicaSet(deployment.spec.template)
          adjustReplicaCounts(newRS, oldRSList)

      3️⃣ ReplicaSet 的创建与管理

      • 如果模板(spec.template)发生变化(例如镜像从 nginx:1.25 改为 nginx:1.26), Deployment Controller 会创建一个新的 ReplicaSet
      • 旧 ReplicaSet 会被缩容,新的被扩容,形成滚动更新

      例如:

      Deployment: webapp
       ├── ReplicaSet (nginx:1.25) — scale 2
       └── ReplicaSet (nginx:1.26) — scale 1

      当更新完成后:

      Deployment: webapp
       └── ReplicaSet (nginx:1.26) — scale 3

      4️⃣ 滚动更新策略(核心逻辑)

      Deployment 的滚动更新通过 RollingUpdateDeployment 类型实现:

      strategy:
        type: RollingUpdate
        rollingUpdate:
          maxUnavailable: 1
          maxSurge: 1

      控制循环每次检查:

      • 有多少可用副本?
      • 是否可以再启动新的 Pod(受 maxSurge 限制)?
      • 是否可以终止旧 Pod(受 maxUnavailable 限制)?

      Controller 会在每次 sync 时:

      1. 创建新 ReplicaSet 的一个 Pod;
      2. 等待其 Ready
      3. 再删除旧 ReplicaSet 的一个 Pod;
      4. 重复直到达到目标状态。

      5️⃣ ReplicaSet 与 Deployment 的“拥有关系”

      Deployment 创建 ReplicaSet 时会设置 OwnerReference:

      metadata:
        ownerReferences:
        - apiVersion: apps/v1
          kind: Deployment
          name: webapp
          uid: <deployment-uid>

      这样 kube-controller-manager 知道 该 ReplicaSet 属于哪个 Deployment, 而 Kubernetes 的垃圾回收器(GC Controller)会自动删除孤立的 ReplicaSet。


      🧮 三、ReplicaSet 的内部机制(下层控制器)

      ReplicaSet Controller 的逻辑相对简单:

      syncReplicaSet(rs):
          desired := rs.spec.replicas
          actual := countPodsMatchingSelector(rs.selector)
          if desired > actual:
              createPods(desired - actual)
          else if desired < actual:
              deletePods(actual - desired)

      也就是说,ReplicaSet 只关注Pod 数量是否符合期望。 它并不关心版本,也不关心策略。


      1️⃣ Pod 的创建

      ReplicaSet Controller 调用 PodTemplateSpec 生成新的 Pod:

      pod := newPodFromTemplate(rs.spec.template)
      pod.OwnerReferences = [rs.UID]

      然后通过 API Server 创建 Pod 对象。


      2️⃣ Pod 的调度与状态同步

      创建后的 Pod 会由 kube-scheduler 调度到某个 Node 上, kubelet 拉起容器后汇报状态,ReplicaSet 通过 Informer 感知到变化并更新 status.availableReplicas


      🧠 四、两者的控制循环(Control Loop)

      可以用伪代码总结整个层次的循环:

      while true:
        # Deployment Controller Loop
        for each Deployment:
          reconcile Deployment → ensure right ReplicaSets exist and scaled properly
      
        # ReplicaSet Controller Loop
        for each ReplicaSet:
          reconcile ReplicaSet → ensure correct number of Pods exist
      
        # Pod Controller Loop (via kubelet)
        for each Pod:
          reconcile Pod → ensure container running

      整个系统靠 Informer + WorkQueue + Reconcile 实现最终一致性。 Kubernetes 的控制循环是幂等的(idempotent)——无论运行多少次,结果都一致。


      🔁 五、事件链(从创建到Pod运行)

      阶段执行者操作
      用户kubectl apply提交 Deployment
      apiserver将 Deployment 写入 etcd
      controller-managerDeployment Controller创建 ReplicaSet
      controller-managerReplicaSet Controller创建 Pod
      schedulerkube-scheduler绑定 Node
      kubelet节点上拉取镜像并运行容器
      controller-managerDeployment Controller更新 ReplicaSet 状态,完成滚动更新

      🧰 六、垃圾回收与历史版本

      • Deployment 默认保留最近 10 个历史 ReplicaSet:

        revisionHistoryLimit: 10
      • 超过限制的旧 ReplicaSet 会自动被清理;

      • 你可以通过:

        kubectl rollout history deployment webapp
        kubectl rollout undo deployment webapp --to-revision=2

        实现回滚。


      🧩 七、关键点总结

      项目DeploymentReplicaSet
      职责管理版本与更新策略管理副本数量
      是否直接创建 Pod否,通过 ReplicaSet
      更新策略支持滚动、暂停、回滚不支持
      典型控制循环调整 ReplicaSet调整 Pod
      与 Pod 的关系间接控制直接控制

      💡 八、类比理解

      你可以这样比喻:

      • Deployment = “项目经理” 管理不同版本的 ReplicaSet,控制滚动更新节奏。
      • ReplicaSet = “小组长” 保证自己手下(Pods)的人数正确。
      • Pod = “员工” 实际干活的单位。
      Mar 7, 2024

      Endpoint VS EndpointSlice

      EndpointEndpointSlice 都是 Kubernetes 中用于管理服务后端端点的资源,但 EndpointSlice 是更现代、更高效的解决方案。以下是它们的详细区别:

      一、基本概念对比

      Endpoint(传统方式)

      apiVersion: v1
      kind: Endpoints
      metadata:
        name: my-service
      subsets:
        - addresses:
          - ip: 10.244.1.5
            targetRef:
              kind: Pod
              name: pod-1
          - ip: 10.244.1.6
            targetRef:
              kind: Pod
              name: pod-2
          ports:
          - port: 8080
            protocol: TCP

      EndpointSlice(现代方式)

      apiVersion: discovery.k8s.io/v1
      kind: EndpointSlice
      metadata:
        name: my-service-abc123
        labels:
          kubernetes.io/service-name: my-service
      addressType: IPv4
      ports:
        - name: http
          protocol: TCP
          port: 8080
      endpoints:
        - addresses:
          - "10.244.1.5"
          conditions:
            ready: true
          targetRef:
            kind: Pod
            name: pod-1
          zone: us-west-2a
        - addresses:
          - "10.244.1.6"
          conditions:
            ready: true
          targetRef:
            kind: Pod
            name: pod-2
          zone: us-west-2b

      二、核心架构差异

      1. 数据模型设计

      特性EndpointEndpointSlice
      存储结构单个大对象多个分片对象
      规模限制所有端点在一个对象中自动分片(默认最多100个端点/片)
      更新粒度全量更新增量更新

      2. 性能影响对比

      # Endpoint 的问题:单个大对象
      # 当有 1000 个 Pod 时:
      kubectl get endpoints my-service -o yaml
      # 返回一个包含 1000 个地址的庞大 YAML
      
      # EndpointSlice 的解决方案:自动分片
      # 当有 1000 个 Pod 时:
      kubectl get endpointslices -l kubernetes.io/service-name=my-service
      # 返回 10 个 EndpointSlice,每个包含 100 个端点

      三、详细功能区别

      1. 地址类型支持

      Endpoint

      • 仅支持 IP 地址
      • 有限的元数据

      EndpointSlice

      addressType: IPv4  # 支持 IPv4, IPv6, FQDN
      endpoints:
        - addresses:
          - "10.244.1.5"
          conditions:
            ready: true
            serving: true
            terminating: false
          hostname: pod-1.subdomain  # 支持主机名
          nodeName: worker-1
          zone: us-west-2a
          hints:
            forZones:
            - name: us-west-2a

      2. 拓扑感知和区域信息

      EndpointSlice 独有的拓扑功能

      endpoints:
        - addresses:
          - "10.244.1.5"
          conditions:
            ready: true
          # 拓扑信息
          nodeName: node-1
          zone: us-west-2a
          # 拓扑提示,用于优化路由
          hints:
            forZones:
            - name: us-west-2a

      3. 端口定义方式

      Endpoint

      subsets:
        - ports:
          - name: http
            port: 8080
            protocol: TCP
          - name: metrics
            port: 9090
            protocol: TCP

      EndpointSlice

      ports:
        - name: http
          protocol: TCP
          port: 8080
          appProtocol: http  # 支持应用层协议标识
        - name: metrics
          protocol: TCP  
          port: 9090
          appProtocol: https

      四、实际使用场景

      1. 大规模服务(500+ Pods)

      Endpoint 的问题

      # 更新延迟:单个大对象的序列化/反序列化
      # 网络开销:每次更新传输整个端点列表
      # 内存压力:客户端需要缓存整个端点列表

      EndpointSlice 的优势

      # 增量更新:只更新变化的切片
      # 并行处理:多个切片可以并行处理
      # 内存友好:客户端只需关注相关切片

      2. 多区域部署

      EndpointSlice 的拓扑感知

      apiVersion: discovery.k8s.io/v1
      kind: EndpointSlice
      metadata:
        name: multi-zone-service-1
        labels:
          kubernetes.io/service-name: multi-zone-service
      addressType: IPv4
      ports:
        - name: http
          protocol: TCP
          port: 8080
      endpoints:
        - addresses:
          - "10.244.1.10"
          conditions:
            ready: true
          zone: zone-a
          nodeName: node-zone-a-1
      ---
      apiVersion: discovery.k8s.io/v1
      kind: EndpointSlice  
      metadata:
        name: multi-zone-service-2
        labels:
          kubernetes.io/service-name: multi-zone-service
      addressType: IPv4
      ports:
        - name: http
          protocol: TCP
          port: 8080
      endpoints:
        - addresses:
          - "10.244.2.10"
          conditions:
            ready: true
          zone: zone-b
          nodeName: node-zone-b-1

      3. 金丝雀发布和流量管理

      EndpointSlice 提供更细粒度的控制

      # 金丝雀版本的 EndpointSlice
      apiVersion: discovery.k8s.io/v1
      kind: EndpointSlice
      metadata:
        name: canary-service-version2
        labels:
          kubernetes.io/service-name: my-service
          version: "v2"  # 自定义标签用于选择
      addressType: IPv4
      ports:
        - name: http
          protocol: TCP
          port: 8080
      endpoints:
        - addresses:
          - "10.244.3.10"
          conditions:
            ready: true

      五、运维和管理差异

      1. 监控方式

      Endpoint 监控

      # 检查单个 Endpoint 对象
      kubectl get endpoints my-service
      kubectl describe endpoints my-service
      
      # 监控端点数量
      kubectl get endpoints my-service -o jsonpath='{.subsets[0].addresses[*].ip}' | wc -w

      EndpointSlice 监控

      # 检查所有相关切片
      kubectl get endpointslices -l kubernetes.io/service-name=my-service
      
      # 查看切片详细信息
      kubectl describe endpointslices my-service-abc123
      
      # 统计总端点数量
      kubectl get endpointslices -l kubernetes.io/service-name=my-service -o jsonpath='{range .items[*]}{.endpoints[*].addresses}{end}' | jq length

      2. 故障排查

      Endpoint 排查

      # 检查端点状态
      kubectl get endpoints my-service -o yaml | grep -A 5 -B 5 "not-ready"
      
      # 检查控制器日志
      kubectl logs -n kube-system kube-controller-manager-xxx | grep endpoints

      EndpointSlice 排查

      # 检查切片状态
      kubectl get endpointslices --all-namespaces
      
      # 检查端点就绪状态
      kubectl get endpointslices -l kubernetes.io/service-name=my-service -o jsonpath='{range .items[*]}{.endpoints[*].conditions.ready}{end}'
      
      # 检查 EndpointSlice Controller
      kubectl logs -n kube-system deployment/endpointslice-controller

      六、迁移和兼容性

      1. 自动迁移

      Kubernetes 1.21+ 默认同时维护两者:

      # 启用 EndpointSlice 特性门控
      kube-apiserver --feature-gates=EndpointSlice=true
      kube-controller-manager --feature-gates=EndpointSlice=true
      kube-proxy --feature-gates=EndpointSlice=true

      2. 检查集群状态

      # 检查 EndpointSlice 是否启用
      kubectl get apiservices | grep discovery.k8s.io
      
      # 检查特性门控
      kube-apiserver -h | grep EndpointSlice
      
      # 验证控制器运行状态
      kubectl get pods -n kube-system -l k8s-app=endpointslice-controller

      七、性能基准对比

      场景EndpointEndpointSlice改进
      1000个Pod更新2-3秒200-300ms10倍
      网络带宽使用高(全量传输)低(增量传输)60-80% 减少
      内存使用高(大对象缓存)低(分片缓存)50-70% 减少
      CPU使用高(序列化成本)低(并行处理)40-60% 减少

      八、最佳实践

      1. 新集群配置

      # kube-apiserver 配置
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-apiserver
        namespace: kube-system
      spec:
        containers:
        - command:
          - kube-apiserver
          - --feature-gates=EndpointSlice=true
          - --endpointslice-updates-batch-period=1s  # 批量更新周期

      2. 应用程序适配

      // 使用 EndpointSlice 感知的客户端
      import (
          "k8s.io/client-go/kubernetes"
          "k8s.io/client-go/tools/cache"
          listers "k8s.io/client-go/listers/discovery/v1"
      )
      
      // 监听 EndpointSlice 变化
      endpointSliceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
          AddFunc: func(obj interface{}) {
              endpointSlice := obj.(*discoveryv1.EndpointSlice)
              if endpointSlice.Labels["kubernetes.io/service-name"] == "my-service" {
                  updateLoadBalancerConfig(endpointSlice)
              }
          },
      })

      总结

      维度EndpointEndpointSlice
      设计年代Kubernetes 早期Kubernetes 1.16+
      扩展性差(单对象限制)优秀(自动分片)
      性能一般(全量更新)优秀(增量更新)
      功能特性基础功能拓扑感知、多协议等
      推荐使用传统集群/兼容性新集群/大规模部署
      未来方向逐步淘汰标准解决方案

      简单建议

      • 新集群:直接使用 EndpointSlice
      • 现有集群:逐步迁移到 EndpointSlice
      • 大规模服务:必须使用 EndpointSlice
      • 小规模测试:两者均可,但建议使用 EndpointSlice 以获得更好的未来兼容性

      EndpointSlice 代表了 Kubernetes 服务发现架构的现代化方向,特别是在大规模和高动态环境中提供了显著的性能和改进。

      Mar 7, 2024

      ETCD如何调优

      好的,Kubernetes 集群的稳定性和性能极大地依赖于其数据存储组件 etcd。对 etcd 进行调优是保障生产环境 K8s 集群高效、稳定运行的关键步骤。

      下面我将从核心原则、性能调优参数、操作系统调优、Kubernetes 相关配置、监控与维护等多个维度,详细讲解如何对 K8s 上的 etcd 进行调优。

      一、核心原则与前提

      1. 硬件是基础:在考虑软件参数调优前,必须确保硬件资源充足且高性能。

        • CPU:需要足够的计算能力,特别是在高负载下进行压缩、序列化等操作时。
        • 内存:etcd 的内存消耗与总键值对数量和大小正相关。足够的内存是保证性能的关键。建议至少 8GB,生产环境推荐 16GB 或以上。
        • 磁盘这是最重要的因素必须使用高性能的 SSD(NVMe SSD 最佳)。etcd 的每次写入都需持久化到磁盘,磁盘的写入延迟(Write Latency)直接决定了 etcd 的写入性能。避免使用网络存储(如 NFS)。
        • 网络:低延迟、高带宽的网络对于 etcd 节点间同步至关重要。如果 etcd 以集群模式运行,所有节点应位于同一个数据中心或低延迟的可用区。
      2. 备份!备份!备份!:在进行任何调优或配置更改之前,务必对 etcd 数据进行完整备份。误操作可能导致数据损坏或集群不可用。

      二、etcd 命令行参数调优

      etcd 主要通过其启动时的命令行参数进行调优。如果你使用 kubeadm 部署,这些参数通常配置在 /etc/kubernetes/manifests/etcd.yaml 静态 Pod 清单中。

      1. 存储配额与压缩

      为了防止磁盘耗尽,etcd 设有存储配额。一旦超过配额,它将进入维护模式,只能读不能写,并触发告警。

      • --quota-backend-bytes:设置 etcd 数据库的后端存储大小上限。默认是 2GB。对于生产环境,建议设置为 8GB 到 16GB(例如 8589934592 表示 8GB)。设置过大会影响备份和恢复时间。
      • --auto-compaction-mode--auto-compaction-retention:etcd 会累积历史版本,需要定期压缩来回收空间。
        • --auto-compaction-mode:通常设置为 periodic(按时间周期)。
        • --auto-compaction-retention:设置保留多长时间的历史数据。例如 "1h" 表示保留 1 小时,"10m" 表示保留 10 分钟。对于频繁变更的集群(如 running many CronJobs),建议设置为较短的周期,如 "10m""30m"

      示例配置片段(在 etcd.yaml 中):

      spec:
        containers:
        - command:
          - etcd
          ...
          - --quota-backend-bytes=8589934592    # 8GB
          - --auto-compaction-mode=periodic
          - --auto-compaction-retention=10m     # 每10分钟压缩一次历史版本
          ...

      2. 心跳与选举超时

      这些参数影响集群的领导者选举和节点间的心跳检测,对网络延迟敏感。

      • --heartbeat-interval:领导者向追随者发送心跳的间隔。建议设置为 100300 毫秒之间。网络环境好可以设小(如 100),不稳定则设大(如 300)。
      • --election-timeout:追随者等待多久没收到心跳后开始新一轮选举。此值必须是心跳间隔的 5-10 倍。建议设置在 10003000 毫秒之间。

      规则:heartbeat-interval * 10 >= election-timeout

      示例配置:

          - --heartbeat-interval=200
          - --election-timeout=2000

      3. 快照

      etcd 通过快照来持久化其状态。

      • --snapshot-count:指定在制作一次快照前,最多提交多少次事务。默认值是 100,000。在内存充足且磁盘 IO 性能极高的环境下,可以适当调低此值(如 50000)以在崩溃后更快恢复,但这会略微增加磁盘 IO 负担。通常使用默认值即可。

      三、操作系统与运行时调优

      1. 磁盘 I/O 调度器

      对于 SSD,将 I/O 调度器设置为 nonenoop 通常能获得更好的性能。

      # 查看当前调度器
      cat /sys/block/[你的磁盘,如 sda]/queue/scheduler
      
      # 临时修改
      echo 'noop' > /sys/block/sda/queue/scheduler
      
      # 永久修改,在 /etc/default/grub 中添加或修改
      GRUB_CMDLINE_LINUX_DEFAULT="... elevator=noop"
      
      # 然后更新 grub 并重启
      sudo update-grub

      2. 文件系统

      使用 XFSext4 文件系统。它们对 etcd 的工作负载有很好的支持。确保使用 ssd 挂载选项。

      /etc/fstab 中为 etcd 数据目录所在分区添加 ssdnoatime 选项:

      UUID=... /var/lib/etcd ext4 defaults,ssd,noatime 0 0

      3. 提高文件描述符和进程数限制

      etcd 可能会处理大量并发连接。

      # 在 /etc/security/limits.conf 中添加
      * soft nofile 65536
      * hard nofile 65536
      * soft nproc 65536
      * hard nproc 65536

      4. 网络参数调优

      调整内核网络参数,特别是在高负载环境下。

      /etc/sysctl.conf 中添加:

      net.core.somaxconn = 1024
      net.ipv4.tcp_keepalive_time = 600
      net.ipv4.tcp_keepalive_intvl = 60
      net.ipv4.tcp_keepalive_probes = 10

      执行 sysctl -p 使其生效。

      四、Kubernetes 相关调优

      1. 资源请求和限制

      etcd.yaml 中为 etcd 容器设置合适的资源限制,防止其因资源竞争而饿死。

          resources:
            requests:
              memory: "1Gi"
              cpu: "500m"
            limits:
              memory: "8Gi"  # 根据你的 --quota-backend-bytes 设置,确保内存足够
              cpu: "2"

      2. API Server 的 --etcd-compaction-interval

      在 kube-apiserver 的启动参数中,这个参数控制它请求 etcd 进行压缩的周期。建议与 etcd 的 --auto-compaction-retention 保持一致或略大。

      五、监控与维护

      1. 监控关键指标

      使用 Prometheus 等工具监控 etcd,重点关注以下指标:

      • etcd_disk_wal_fsync_duration_seconds:WAL 日志同步到磁盘的延迟。这是最重要的指标,P99 值应低于 25ms。
      • etcd_disk_backend_commit_duration_seconds:后端数据库提交的延迟。
      • etcd_server_leader_changes_seen_total:领导者变更次数。频繁变更表明集群不稳定。
      • etcd_server_has_leader:当前节点是否认为有领导者(1 为是,0 为否)。
      • etcd_mvcc_db_total_size_in_bytes:当前数据库大小,用于判断是否接近存储配额。

      2. 定期进行碎片整理

      即使开启了自动压缩,etcd 的数据库文件内部仍会产生碎片。当 etcd_mvcc_db_total_size_in_bytes 接近 --quota-backend-bytes 时,即使实际数据量没那么多,也需要在线进行碎片整理。

      # 在任一 etcd 节点上执行
      ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
        --cacert=/path/to/ca.crt \
        --cert=/path/to/etcd-client.crt \
        --key=/path/to/etcd-client.key \
        defrag

      注意:执行 defrag 会阻塞所有请求,应在业务低峰期进行,并逐个对集群成员执行。

      调优总结与检查清单

      1. 硬件过关:确认使用 SSD,内存充足。
      2. 设置存储配额和自动压缩--quota-backend-bytes=8G, --auto-compaction-retention=10m
      3. 调整心跳与选举超时--heartbeat-interval=200, --election-timeout=2000
      4. 操作系统优化:I/O 调度器、文件系统挂载选项、文件描述符限制。
      5. 配置合理的资源限制:防止 etcd 容器因资源不足被 Kill。
      6. 开启并关注监控:特别是磁盘同步延迟和领导者变更。
      7. 定期维护:根据监控指标,在需要时进行碎片整理。

      对于大多数场景,调整存储配额与压缩心跳与选举超时以及确保高性能磁盘,就能解决绝大部分性能问题。调优是一个持续的过程,需要结合监控数据不断调整。

      Mar 7, 2024

      Flannel VS Calico

      Calico 和 Flannel 是 Kubernetes 中最著名和最常见的两种网络插件(CNI),但它们的设计哲学、实现方式和能力有显著区别。

      简单来说:

      • Flannel 追求的是简单和易用,提供足够的基础网络功能。
      • Calico 追求的是性能和功能,提供强大的网络策略和高性能网络。

      下面我们从多个维度进行详细对比。


      核心对比一览表

      特性FlannelCalico
      核心设计哲学简单、最小化高性能、功能丰富
      网络模型Overlay 网络纯三层路由(可选 Overlay)
      数据平面VXLAN(推荐)、Host-gw、UDPBGP(推荐)、VXLAN、Windows
      性能较好(VXLAN有封装开销)极高(BGP模式下无封装开销)
      网络策略不支持(需安装Cilium等)原生支持(强大的网络策略)
      安全性基础高级(基于标签的微隔离)
      配置与维护非常简单,几乎无需配置相对复杂,功能多配置项也多
      适用场景学习、测试、中小型集群,需求简单生产环境、大型集群、对性能和安全要求高

      深入剖析

      1. 网络模型与工作原理

      这是最根本的区别。

      • Flannel (Overlay Network)

        • 工作原理:它在底层物理网络之上再构建一个虚拟的“覆盖网络”。当数据包从一个节点的Pod发送到另一个节点的Pod时,Flannel会将它封装在一个新的网络包中(如VXLAN)。
        • 类比:就像在一封普通信件(Pod的原始数据包)外面套了一个标准快递袋(VXLAN封装),快递系统(底层网络)只关心快递袋上的地址(节点IP),不关心里面的内容。到达目标节点后,再拆开快递袋,取出里面的信。
        • 优势:对底层网络要求低,只要节点之间IP能通即可,兼容性好。
        • 劣势:封装和解封装有额外的CPU开销,并且会增加数据包的大小( overhead),导致性能略有下降。
      • Calico (Pure Layer 3)

        • 工作原理(BGP模式):它不使用封装,而是使用BGP路由协议。每个K8s节点都像一个路由器,它通过BGP协议向集群中的其他节点宣告:“发往这些Pod IP的流量,请送到我这里来”。
        • 类比:就像整个数据中心是一个大的邮政系统,每个邮局(节点)都知道去往任何地址(Pod IP)的最短路径,信件(数据包)可以直接投递,无需额外包装。
        • 优势性能高,无封装开销,延迟低,吞吐量高。
        • 劣势:要求底层网络必须支持BGP或者支持主机路由(某些云平台或网络设备可能需要特定配置)。

      注意:Calico也支持VXLAN模式(通常用于网络策略要求BGP但底层网络不支持的场景),但其最佳性能是在BGP模式下实现的。

      2. 网络策略

      这是两者功能性的一个巨大分水岭。

      • Flannel本身不提供任何网络策略能力。它只负责打通网络,让所有Pod默认可以相互通信。如果你需要实现Pod之间的访问控制(微隔离),你必须额外安装一个网络策略控制器,如 CiliumCalico本身(可以只使用其策略部分,与Flannel叠加使用)。

      • Calico原生支持强大的Kubernetes NetworkPolicy。你可以定义基于Pod标签、命名空间、端口、协议甚至DNS名称的精细规则,来控制Pod的入站和出站流量。这对于实现“零信任”安全模型至关重要。

      3. 性能

      • Calico (BGP模式):由于其纯三层的转发机制,无需封装,数据包是原生IP包,其延迟更低,吞吐量更高,CPU消耗也更少。
      • Flannel (VXLAN模式):由于存在VXLAN的封装头(通常50字节 overhead),最大传输单元会变小,封装/解封装操作也需要CPU参与,性能相比Calico BGP模式要低一些。但其 Host-gw 后端模式性能很好,前提是节点在同一个二层网络。

      4. 生态系统与高级功能

      • Calico:功能非常丰富,远不止基础网络。
        • 网络策略:如上所述,非常强大。
        • IPAM:灵活的IP地址管理。
        • 服务网格集成:与Istio有深度集成,可以实施全局的服务到服务策略。
        • Windows支持:对Windows节点有良好的支持。
        • 网络诊断工具:提供了 calicoctl 等强大的运维工具。
      • Flannel:功能相对单一,就是做好网络连通性。它“小而美”,但缺乏高级功能。

      如何选择?

      选择 Flannel 的情况:

      • 新手用户:想要快速搭建一个K8s集群,不想纠结于复杂的网络配置。
      • 测试或开发环境:需求简单,只需要Pod能通。
      • 中小型集群:对性能和高级网络策略没有硬性要求。
      • 底层网络受限:无法配置BGP或主机路由的环境(例如某些公有云基础网络)。

      选择 Calico 的情况:

      • 生产环境:对稳定性和性能有高要求。
      • 大型集群:需要高效的路由和可扩展性。
      • 安全要求高:需要实现Pod之间的网络隔离(微隔离)。
      • 对网络性能极度敏感:例如AI/ML训练、高频交易等场景。
      • 底层网络可控:例如在自建数据中心或云上支持BGP的环境。

      总结

      FlannelCalico
      核心价值简单可靠功能强大
      好比买车丰田卡罗拉:皮实、省心、够用宝马/奥迪:性能强劲、功能齐全、操控精准
      一句话总结“让我快速把网络打通”“我要一个高性能、高安全性的生产级网络”

      在现代Kubernetes部署中,尤其是生产环境,Calico因其卓越的性能和原生的安全能力,已经成为更主流和推荐的选择。而Flannel则在那些“只要能通就行”的简单场景中,依然保持着它的价值。

      Mar 7, 2024

      Headless Service VS ClusterIP

      Headless Service vs ClusterIP 详解

      这是 Kubernetes 中两种常见的 Service 类型,它们在服务发现和负载均衡方面有本质区别。


      🎯 核心区别总结

      维度ClusterIPHeadless Service
      ClusterIP 值有固定的虚拟 IPNone (无 ClusterIP)
      DNS 解析返回 Service IP直接返回 Pod IP 列表
      负载均衡✅ kube-proxy 自动负载均衡❌ 客户端自行选择 Pod
      适用场景无状态服务有状态服务、服务发现
      典型用例Web 应用、API 服务数据库集群、Kafka、Zookeeper

      📋 ClusterIP Service (默认类型)

      定义

      ClusterIP 是 Kubernetes 默认的 Service 类型,会分配一个虚拟 IP(Cluster IP),作为访问后端 Pod 的统一入口。

      YAML 示例

      apiVersion: v1
      kind: Service
      metadata:
        name: my-web-service
      spec:
        type: ClusterIP  # 默认类型,可以省略
        selector:
          app: web
        ports:
        - protocol: TCP
          port: 80        # Service 端口
          targetPort: 8080  # Pod 端口

      工作原理

      ┌─────────────────────────────────────────┐
      │          ClusterIP Service              │
      │     (虚拟 IP: 10.96.100.50)             │
      └────────────┬────────────────────────────┘
                   │ kube-proxy 负载均衡
                   │
           ┌───────┴───────┬──────────┐
           ▼               ▼          ▼
        Pod-1          Pod-2      Pod-3
        10.244.1.5     10.244.2.8  10.244.3.12
        (app=web)      (app=web)   (app=web)

      DNS 解析行为

      # 在集群内部查询 DNS
      nslookup my-web-service.default.svc.cluster.local
      
      # 输出:
      # Name:    my-web-service.default.svc.cluster.local
      # Address: 10.96.100.50  ← 返回 Service 的虚拟 IP
      
      # 客户端访问这个 IP
      curl http://my-web-service:80
      
      # 请求会被 kube-proxy 自动转发到后端 Pod
      # 默认使用 iptables 或 IPVS 做负载均衡

      特点

      统一入口:客户端只需知道 Service IP,不关心后端 Pod
      自动负载均衡:kube-proxy 自动在多个 Pod 间分发流量
      服务发现简单:通过 DNS 获取稳定的 Service IP
      屏蔽 Pod 变化:Pod 重启或扩缩容,Service IP 不变
      会话保持:可配置 sessionAffinity: ClientIP

      负载均衡方式

      apiVersion: v1
      kind: Service
      metadata:
        name: my-service
      spec:
        type: ClusterIP
        sessionAffinity: ClientIP  # 可选:会话保持(同一客户端固定到同一 Pod)
        sessionAffinityConfig:
          clientIP:
            timeoutSeconds: 10800   # 会话超时时间
        selector:
          app: web
        ports:
        - port: 80
          targetPort: 8080

      🔍 Headless Service (无头服务)

      定义

      Headless Service 是不分配 ClusterIP 的特殊 Service,通过设置 clusterIP: None 创建。

      YAML 示例

      apiVersion: v1
      kind: Service
      metadata:
        name: my-headless-service
      spec:
        clusterIP: None  # 🔑 关键:设置为 None
        selector:
          app: database
        ports:
        - protocol: TCP
          port: 3306
          targetPort: 3306

      工作原理

      ┌─────────────────────────────────────────┐
      │       Headless Service (无 ClusterIP)   │
      │              DNS 直接返回               │
      └────────────┬────────────────────────────┘
                   │ 没有负载均衡
                   │ DNS 返回所有 Pod IP
                   │
           ┌───────┴───────┬──────────┐
           ▼               ▼          ▼
        Pod-1          Pod-2      Pod-3
        10.244.1.5     10.244.2.8  10.244.3.12
        (app=database) (app=database) (app=database)

      DNS 解析行为

      # 在集群内部查询 DNS
      nslookup my-headless-service.default.svc.cluster.local
      
      # 输出:
      # Name:    my-headless-service.default.svc.cluster.local
      # Address: 10.244.1.5   ← Pod-1 IP
      # Address: 10.244.2.8   ← Pod-2 IP
      # Address: 10.244.3.12  ← Pod-3 IP
      
      # 客户端获得所有 Pod IP,自己选择连接哪个

      特点

      服务发现:客户端可以获取所有后端 Pod 的 IP
      自主选择:客户端自己决定连接哪个 Pod(负载均衡逻辑由客户端实现)
      稳定 DNS:每个 Pod 有独立的 DNS 记录
      适合有状态服务:数据库主从、集群成员发现
      无自动负载均衡:需要客户端或应用层实现

      与 StatefulSet 结合(最常见用法)

      # StatefulSet + Headless Service
      apiVersion: v1
      kind: Service
      metadata:
        name: mysql-headless
      spec:
        clusterIP: None
        selector:
          app: mysql
        ports:
        - port: 3306
          name: mysql
      ---
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: mysql
      spec:
        serviceName: mysql-headless  # 🔑 关联 Headless Service
        replicas: 3
        selector:
          matchLabels:
            app: mysql
        template:
          metadata:
            labels:
              app: mysql
          spec:
            containers:
            - name: mysql
              image: mysql:8.0
              ports:
              - containerPort: 3306

      每个 Pod 的独立 DNS 记录

      # StatefulSet 的 Pod 命名规则:
      # <statefulset-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local
      
      # 示例:
      mysql-0.mysql-headless.default.svc.cluster.local → 10.244.1.5
      mysql-1.mysql-headless.default.svc.cluster.local → 10.244.2.8
      mysql-2.mysql-headless.default.svc.cluster.local → 10.244.3.12
      
      # 可以直接访问特定 Pod
      mysql -h mysql-0.mysql-headless.default.svc.cluster.local -u root -p
      
      # 查询所有 Pod
      nslookup mysql-headless.default.svc.cluster.local

      🔄 实际对比演示

      场景 1:Web 应用(使用 ClusterIP)

      # ClusterIP Service
      apiVersion: v1
      kind: Service
      metadata:
        name: web-service
      spec:
        type: ClusterIP
        selector:
          app: nginx
        ports:
        - port: 80
          targetPort: 80
      ---
      # Deployment
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: nginx
      spec:
        replicas: 3
        selector:
          matchLabels:
            app: nginx
        template:
          metadata:
            labels:
              app: nginx
          spec:
            containers:
            - name: nginx
              image: nginx:latest
      # 测试访问
      kubectl run test --rm -it --image=busybox -- /bin/sh
      
      # 在 Pod 内执行
      nslookup web-service
      # 输出:只有一个 Service IP
      
      wget -q -O- http://web-service
      # 请求会被自动负载均衡到 3 个 nginx Pod

      场景 2:MySQL 主从(使用 Headless Service)

      # Headless Service
      apiVersion: v1
      kind: Service
      metadata:
        name: mysql
      spec:
        clusterIP: None
        selector:
          app: mysql
        ports:
        - port: 3306
      ---
      # StatefulSet
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: mysql
      spec:
        serviceName: mysql
        replicas: 3
        selector:
          matchLabels:
            app: mysql
        template:
          metadata:
            labels:
              app: mysql
          spec:
            containers:
            - name: mysql
              image: mysql:8.0
              env:
              - name: MYSQL_ROOT_PASSWORD
                value: "password"
      # 测试服务发现
      kubectl run test --rm -it --image=busybox -- /bin/sh
      
      # 在 Pod 内执行
      nslookup mysql
      # 输出:返回 3 个 Pod IP
      
      # 可以连接到特定的 MySQL 实例(如主节点)
      mysql -h mysql-0.mysql.default.svc.cluster.local -u root -p
      
      # 也可以连接到从节点
      mysql -h mysql-1.mysql.default.svc.cluster.local -u root -p
      mysql -h mysql-2.mysql.default.svc.cluster.local -u root -p

      📊 详细对比

      1. DNS 解析差异

      # ClusterIP Service
      $ nslookup web-service
      Server:    10.96.0.10
      Address:   10.96.0.10:53
      
      Name:      web-service.default.svc.cluster.local
      Address:   10.96.100.50  ← Service 虚拟 IP
      
      # Headless Service
      $ nslookup mysql-headless
      Server:    10.96.0.10
      Address:   10.96.0.10:53
      
      Name:      mysql-headless.default.svc.cluster.local
      Address:   10.244.1.5  ← Pod-1 IP
      Address:   10.244.2.8  ← Pod-2 IP
      Address:   10.244.3.12 ← Pod-3 IP

      2. 流量路径差异

      ClusterIP 流量路径:
      Client → Service IP (10.96.100.50)
             → kube-proxy (iptables/IPVS)
             → 随机选择一个 Pod
      
      Headless 流量路径:
      Client → DNS 查询
             → 获取所有 Pod IP
             → 客户端自己选择 Pod
             → 直接连接 Pod IP

      3. 使用场景对比

      场景ClusterIPHeadless
      无状态应用✅ 推荐❌ 不需要
      有状态应用❌ 不适合✅ 推荐
      数据库主从❌ 无法区分主从✅ 可以指定连接主节点
      集群成员发现❌ 无法获取成员列表✅ 可以获取所有成员
      需要负载均衡✅ 自动负载均衡❌ 需要客户端实现
      客户端连接池⚠️ 只能连接到 Service IP✅ 可以为每个 Pod 建立连接

      🎯 典型应用场景

      ClusterIP Service 适用场景

      1. 无状态 Web 应用

      apiVersion: v1
      kind: Service
      metadata:
        name: frontend
      spec:
        type: ClusterIP
        selector:
          app: frontend
        ports:
        - port: 80
          targetPort: 3000

      2. RESTful API 服务

      apiVersion: v1
      kind: Service
      metadata:
        name: api-service
      spec:
        type: ClusterIP
        selector:
          app: api
        ports:
        - port: 8080

      3. 微服务之间的调用

      # Service A 调用 Service B
      apiVersion: v1
      kind: Service
      metadata:
        name: service-b
      spec:
        type: ClusterIP
        selector:
          app: service-b
        ports:
        - port: 9090

      Headless Service 适用场景

      1. MySQL 主从复制

      apiVersion: v1
      kind: Service
      metadata:
        name: mysql
      spec:
        clusterIP: None
        selector:
          app: mysql
        ports:
        - port: 3306
      ---
      # 应用连接时:
      # 写操作 → mysql-0.mysql (主节点)
      # 读操作 → mysql-1.mysql, mysql-2.mysql (从节点)

      2. Kafka 集群

      apiVersion: v1
      kind: Service
      metadata:
        name: kafka
      spec:
        clusterIP: None
        selector:
          app: kafka
        ports:
        - port: 9092
      ---
      # Kafka 客户端可以发现所有 broker:
      # kafka-0.kafka:9092
      # kafka-1.kafka:9092
      # kafka-2.kafka:9092

      3. Elasticsearch 集群

      apiVersion: v1
      kind: Service
      metadata:
        name: elasticsearch
      spec:
        clusterIP: None
        selector:
          app: elasticsearch
        ports:
        - port: 9200
          name: http
        - port: 9300
          name: transport
      ---
      # 集群内部节点通过 DNS 发现彼此:
      # elasticsearch-0.elasticsearch
      # elasticsearch-1.elasticsearch
      # elasticsearch-2.elasticsearch

      4. Redis 集群模式

      apiVersion: v1
      kind: Service
      metadata:
        name: redis-cluster
      spec:
        clusterIP: None
        selector:
          app: redis
        ports:
        - port: 6379
          name: client
        - port: 16379
          name: gossip
      ---
      # Redis 客户端获取所有节点进行 cluster slots 查询

      🔧 混合使用:两种 Service 同时存在

      对于有状态服务,常见做法是同时创建两个 Service:

      # 1. Headless Service:用于 StatefulSet 和 Pod 间通信
      apiVersion: v1
      kind: Service
      metadata:
        name: mysql-headless
      spec:
        clusterIP: None
        selector:
          app: mysql
        ports:
        - port: 3306
      ---
      # 2. ClusterIP Service:用于客户端负载均衡访问(只读副本)
      apiVersion: v1
      kind: Service
      metadata:
        name: mysql-read
      spec:
        type: ClusterIP
        selector:
          app: mysql
          role: replica  # 只选择从节点
        ports:
        - port: 3306
      ---
      # StatefulSet
      apiVersion: apps/v1
      kind: StatefulSet
      metadata:
        name: mysql
      spec:
        serviceName: mysql-headless  # 使用 Headless Service
        replicas: 3
        # ...

      使用方式:

      # 写操作:直接连接主节点
      mysql -h mysql-0.mysql-headless -u root -p
      
      # 读操作:通过 ClusterIP 自动负载均衡到所有从节点
      mysql -h mysql-read -u root -p

      🛠️ 常见问题

      Q1: 如何选择使用哪种 Service?

      决策流程:

      应用是无状态的? 
        ├─ 是 → 使用 ClusterIP
        └─ 否 → 继续
      
      需要客户端感知所有 Pod?
        ├─ 是 → 使用 Headless Service
        └─ 否 → 继续
      
      需要区分不同 Pod(如主从)?
        ├─ 是 → 使用 Headless Service + StatefulSet
        └─ 否 → 使用 ClusterIP

      Q2: Headless Service 没有负载均衡怎么办?

      方案:

      1. 客户端负载均衡:应用层实现(如 Kafka 客户端)
      2. DNS 轮询:部分 DNS 客户端会自动轮询
      3. 混合方案:同时创建 ClusterIP Service 用于负载均衡

      Q3: 如何测试 Headless Service?

      # 创建测试 Pod
      kubectl run -it --rm debug --image=busybox --restart=Never -- sh
      
      # 测试 DNS 解析
      nslookup mysql-headless.default.svc.cluster.local
      
      # 测试连接特定 Pod
      wget -O- http://mysql-0.mysql-headless:3306
      
      # 测试所有 Pod
      for i in 0 1 2; do
        echo "Testing mysql-$i"
        wget -O- http://mysql-$i.mysql-headless:3306
      done

      Q4: ClusterIP Service 能否用于 StatefulSet?

      可以,但不推荐:

      • ✅ 可以提供负载均衡
      • ❌ 无法通过稳定的 DNS 名访问特定 Pod
      • ❌ 不适合主从架构(无法区分主节点)

      最佳实践:

      • StatefulSet 使用 Headless Service
      • 如需负载均衡,额外创建 ClusterIP Service

      💡 关键要点总结

      ClusterIP Service

      ✅ 默认类型,有虚拟 IP
      ✅ 自动负载均衡(kube-proxy)
      ✅ 适合无状态应用
      ✅ 客户端无需感知后端 Pod
      ✅ DNS 解析返回 Service IP

      Headless Service

      ✅ 设置 clusterIP: None
      ✅ DNS 解析返回所有 Pod IP
      ✅ 适合有状态应用
      ✅ 支持 Pod 级别的服务发现
      ✅ 常与 StatefulSet 配合使用

      选型建议

      • Web 应用、API 服务 → ClusterIP
      • 数据库、消息队列、分布式存储 → Headless Service
      • 有主从/分片的应用 → Headless Service + StatefulSet
      • 需要同时支持负载均衡和直接访问 → 两种 Service 都创建
      Mar 7, 2024

      Helm Principle

      Helm 是 Kubernetes 的包管理工具,类似于 Linux 的 apt/yum 或 Python 的 pip,它的核心作用是: 👉 用模板化的方式定义、安装和升级 Kubernetes 应用。


      🧩 一、Helm 的核心概念

      在理解原理前,先明确 Helm 的几个关键对象:

      概念说明
      Chart一个 Helm 包,描述一组 Kubernetes 资源的模板集合(即一个应用的安装包)
      Values.yamlChart 的参数配置文件,用于填充模板变量
      ReleaseHelm 将 Chart 安装到某个命名空间后的实例,每次安装或升级都是一个 release
      Repository存放打包后 chart (.tgz) 的仓库,可以是 HTTP/OCI 类型(如 Harbor, Artifactory)

      ⚙️ 二、Helm 的工作原理流程

      从用户角度来看,Helm Client 发出命令(如 helm install),Helm 会通过一系列步骤在集群中生成 Kubernetes 资源。

      下面是核心流程图概念(文字版):

             ┌────────────┐
             │ helm client│
             └─────┬──────┘
                   │
                   ▼
            1. 解析Chart与Values
                   │
                   ▼
            2. 模板渲染(Helm Template Engine)
                   │
                   ▼
            3. 生成纯YAML清单
                   │
                   ▼
            4. 调用Kubernetes API
                   │
                   ▼
            5. 创建/更新资源(Deployment、Service等)
                   │
                   ▼
            6. 记录Release历史(ConfigMap/Secret)

      🔍 三、Helm 工作机制分解

      1️⃣ Chart 渲染阶段

      Helm 使用 Go 的 text/template 模板引擎 + Sprig 函数库,将模板与 values.yaml 合并生成 Kubernetes YAML 清单。

      例如:

      # templates/deployment.yaml
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: {{ .Release.Name }}-app
      spec:
        replicas: {{ .Values.replicas }}

      通过:

      helm template myapp ./mychart -f myvalues.yaml

      Helm 会本地生成纯 YAML 文件(不部署到集群)。


      2️⃣ 部署阶段(Install/Upgrade)

      执行:

      helm install myapp ./mychart

      Helm Client 会将渲染好的 YAML 通过 Kubernetes API 提交到集群(相当于执行 kubectl apply)。

      Helm 同时在命名空间中创建一个 “Release 记录”,默认存放在:

      namespace: <your-namespace>
      kind: Secret
      name: sh.helm.release.v1.<release-name>.vN

      其中保存了:

      • Chart 模板和 values 的快照
      • 渲染后的 manifest
      • Release 状态(deployed、failed 等)
      • 版本号(v1, v2, …)

      3️⃣ 升级与回滚机制

      当执行:

      helm upgrade myapp ./mychart

      时,Helm 会:

      1. 读取旧版本 release secret
      2. 渲染新模板
      3. 比较新旧差异(Diff)
      4. 调用 Kubernetes API 更新对象
      5. 写入新的 release secret(版本号 +1)

      回滚时:

      helm rollback myapp 2

      Helm 会取出 v2 的记录,再次 kubectl apply


      4️⃣ 仓库机制(Helm Repository / OCI Registry)

      Helm 支持两种包分发方式:

      • HTTP 仓库(传统)

        • 有一个 index.yaml 索引文件
        • Chart 以 .tgz 格式存储
      • OCI 仓库(现代推荐)

        • Chart 存储在 OCI registry(如 Harbor, GHCR)

        • 推送方式:

          helm push mychart/ oci://harbor.example.com/helm
        • 拉取方式:

          helm pull oci://harbor.example.com/helm/mychart --version 1.0.0

      🧠 四、Helm 与 Kubernetes 的关系

      Helm 本身 不直接管理容器运行,它只是:

      • 模板引擎 + 应用生命周期管理器;
      • 所有资源最终仍由 Kubernetes 控制器(如 Deployment controller)调度、运行。

      Helm 类似于 “上层应用打包器”:

      Helm = Chart 模板系统 + Kubernetes API 客户端 + Release 历史追踪

      💡 五、常见命令原理对照

      命令Helm 行为
      helm install渲染模板 → 提交资源 → 创建 release
      helm upgrade渲染模板 → diff 旧版本 → 更新资源 → 新 release
      helm rollback获取旧版本记录 → 重新提交旧 manifest
      helm uninstall删除 Kubernetes 资源 + 删除 release secret
      helm template本地渲染模板,不与集群交互
      helm diff(插件)比较新旧渲染结果差异

      🧩 六、Helm 3 与 Helm 2 的区别(核心)

      Helm 2Helm 3
      需要 Tiller(集群内控制组件)无需 Tiller,完全 client-side
      安全模型复杂(基于 RBAC 授权)安全性更好,直接使用 kubeconfig 权限
      Release 存储在 ConfigMap默认存储在 Secret
      需要 Helm Server 部署纯客户端
      Mar 7, 2024

      HPA

      HPA(Horizontal Pod Autoscaler)是 Kubernetes 中实现自动水平扩缩容的核心组件。它的实现涉及多个 Kubernetes 组件和复杂的控制逻辑。

      一、HPA 架构组成

      1. 核心组件

      ┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
      │   HPA Controller │ ◄──│   Metrics API    │ ◄──│  Metrics Server │
      │   (kube-controller)│    │    (聚合层)     │    │   (cAdvisor)    │
      └─────────────────┘    └──────────────────┘    └─────────────────┘
               │                       │                       │
               ▼                       ▼                       ▼
      ┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
      │ Deployment/     │    │  Custom Metrics  │    │  External       │
      │ StatefulSet     │    │   Adapter        │    │  Metrics        │
      └─────────────────┘    └──────────────────┘    └─────────────────┘

      二、HPA 工作流程

      1. 完整的控制循环

      // 简化的 HPA 控制逻辑
      for {
          // 1. 获取 HPA 对象
          hpa := client.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get(name)
          
          // 2. 获取缩放目标(Deployment/StatefulSet等)
          scaleTarget := hpa.Spec.ScaleTargetRef
          target := client.AppsV1().Deployments(namespace).Get(scaleTarget.Name)
          
          // 3. 查询指标
          metrics := []autoscalingv2.MetricStatus{}
          for _, metricSpec := range hpa.Spec.Metrics {
              metricValue := getMetricValue(metricSpec, target)
              metrics = append(metrics, metricValue)
          }
          
          // 4. 计算期望副本数
          desiredReplicas := calculateDesiredReplicas(hpa, metrics, currentReplicas)
          
          // 5. 执行缩放
          if desiredReplicas != currentReplicas {
              scaleTarget.Spec.Replicas = &desiredReplicas
              client.AppsV1().Deployments(namespace).UpdateScale(scaleTarget.Name, scaleTarget)
          }
          
          time.Sleep(15 * time.Second) // 默认扫描间隔
      }

      2. 详细步骤分解

      步骤 1:指标收集

      # HPA 通过 Metrics API 获取指标
      kubectl get --raw "/apis/metrics.k8s.io/v1beta1/namespaces/default/pods" | jq .
      
      # 或者通过自定义指标 API
      kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .

      步骤 2:指标计算

      // 计算当前指标值与目标值的比率
      func calculateMetricRatio(currentValue, targetValue int64) float64 {
          return float64(currentValue) / float64(targetValue)
      }
      
      // 示例:CPU 使用率计算
      currentCPUUsage := 800m  # 当前使用 800 milli-cores
      targetCPUUsage := 500m   # 目标使用 500 milli-cores
      ratio := 800.0 / 500.0   # = 1.6

      三、HPA 配置详解

      1. HPA 资源定义

      apiVersion: autoscaling/v2
      kind: HorizontalPodAutoscaler
      metadata:
        name: myapp-hpa
        namespace: default
      spec:
        # 缩放目标
        scaleTargetRef:
          apiVersion: apps/v1
          kind: Deployment
          name: myapp
        # 副本数范围
        minReplicas: 2
        maxReplicas: 10
        # 指标定义
        metrics:
        - type: Resource
          resource:
            name: cpu
            target:
              type: Utilization
              averageUtilization: 50
        - type: Resource
          resource:
            name: memory
            target:
              type: Utilization
              averageUtilization: 70
        - type: Pods
          pods:
            metric:
              name: packets-per-second
            target:
              type: AverageValue
              averageValue: 1k
        - type: Object
          object:
            metric:
              name: requests-per-second
            describedObject:
              apiVersion: networking.k8s.io/v1
              kind: Ingress
              name: main-route
            target:
              type: Value
              value: 10k
        # 行为配置(Kubernetes 1.18+)
        behavior:
          scaleDown:
            stabilizationWindowSeconds: 300
            policies:
            - type: Percent
              value: 50
              periodSeconds: 60
            - type: Pods
              value: 5
              periodSeconds: 60
            selectPolicy: Min
          scaleUp:
            stabilizationWindowSeconds: 0
            policies:
            - type: Percent
              value: 100
              periodSeconds: 15
            - type: Pods
              value: 4
              periodSeconds: 15
            selectPolicy: Max

      四、指标类型和计算方式

      1. 资源指标(CPU/Memory)

      metrics:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization    # 利用率模式
            averageUtilization: 50
            
      - type: Resource  
        resource:
          name: memory
          target:
            type: AverageValue  # 平均值模式
            averageValue: 512Mi

      计算逻辑

      // CPU 利用率计算
      func calculateCPUReplicas(currentUsage, targetUtilization int32, currentReplicas int32) int32 {
          // 当前总使用量
          totalUsage := currentUsage * currentReplicas
          // 期望副本数 = ceil(当前总使用量 / (单个 Pod 请求量 * 目标利用率))
          desiredReplicas := int32(math.Ceil(float64(totalUsage) / float64(targetUtilization)))
          return desiredReplicas
      }

      2. 自定义指标(Pods 类型)

      metrics:
      - type: Pods
        pods:
          metric:
            name: http_requests_per_second
          target:
            type: AverageValue
            averageValue: 100

      计算方式

      期望副本数 = ceil(当前总指标值 / 目标平均值)

      3. 对象指标(Object 类型)

      metrics:
      - type: Object
        object:
          metric:
            name: latency
          describedObject:
            apiVersion: networking.k8s.io/v1
            kind: Ingress
            name: my-ingress
          target:
            type: Value
            value: 100

      五、HPA 算法详解

      1. 核心算法

      // 计算期望副本数
      func GetDesiredReplicas(
          currentReplicas int32,
          metricValues []metrics,
          hpa *HorizontalPodAutoscaler,
      ) int32 {
          ratios := make([]float64, 0)
          
          // 1. 计算每个指标的比率
          for _, metric := range metricValues {
              ratio := calculateMetricRatio(metric.current, metric.target)
              ratios = append(ratios, ratio)
          }
          
          // 2. 选择最大的比率(最需要扩容的指标)
          maxRatio := getMaxRatio(ratios)
          
          // 3. 计算期望副本数
          desiredReplicas := math.Ceil(float64(currentReplicas) * maxRatio)
          
          // 4. 应用边界限制
          desiredReplicas = applyBounds(desiredReplicas, hpa.Spec.MinReplicas, hpa.Spec.MaxReplicas)
          
          return int32(desiredReplicas)
      }

      2. 平滑算法和冷却机制

      // 考虑历史记录的缩放决策
      func withStabilization(desiredReplicas int32, hpa *HorizontalPodAutoscaler) int32 {
          now := time.Now()
          
          if isScaleUp(desiredReplicas, hpa.Status.CurrentReplicas) {
              // 扩容:通常立即执行
              stabilizationWindow = hpa.Spec.Behavior.ScaleUp.StabilizationWindowSeconds
          } else {
              // 缩容:应用稳定窗口
              stabilizationWindow = hpa.Spec.Behavior.ScaleDown.StabilizationWindowSeconds
          }
          
          // 过滤稳定窗口内的历史推荐值
          validRecommendations := filterRecommendationsByTime(
              hpa.Status.Conditions, 
              now.Add(-time.Duration(stabilizationWindow)*time.Second)
          )
          
          // 选择策略(Min/Max)
          finalReplicas := applyPolicy(validRecommendations, hpa.Spec.Behavior)
          
          return finalReplicas
      }

      六、高级特性实现

      1. 多指标支持

      当配置多个指标时,HPA 会为每个指标计算期望副本数,然后选择最大值

      func calculateFromMultipleMetrics(metrics []Metric, currentReplicas int32) int32 {
          desiredReplicas := make([]int32, 0)
          
          for _, metric := range metrics {
              replicas := calculateForSingleMetric(metric, currentReplicas)
              desiredReplicas = append(desiredReplicas, replicas)
          }
          
          // 选择最大的期望副本数
          return max(desiredReplicas...)
      }

      2. 扩缩容行为控制

      behavior:
        scaleDown:
          # 缩容稳定窗口:5分钟
          stabilizationWindowSeconds: 300
          policies:
          - type: Percent   # 每分钟最多缩容 50%
            value: 50
            periodSeconds: 60
          - type: Pods      # 或每分钟最多减少 5 个 Pod
            value: 5
            periodSeconds: 60
          selectPolicy: Min # 选择限制更严格的策略
          
        scaleUp:
          stabilizationWindowSeconds: 0  # 扩容立即执行
          policies:
          - type: Percent   # 每分钟最多扩容 100%
            value: 100
            periodSeconds: 60
          - type: Pods      # 或每分钟最多增加 4 个 Pod
            value: 4
            periodSeconds: 60
          selectPolicy: Max # 选择限制更宽松的策略

      七、监控和调试

      1. 查看 HPA 状态

      # 查看 HPA 详情
      kubectl describe hpa myapp-hpa
      
      # 输出示例:
      # Name: myapp-hpa
      # Namespace: default
      # Reference: Deployment/myapp
      # Metrics: ( current / target )
      #   resource cpu on pods  (as a percentage of request):  65% (130m) / 50%
      #   resource memory on pods:                             120Mi / 100Mi
      # Min replicas: 2
      # Max replicas: 10
      # Deployment pods: 3 current / 3 desired

      2. HPA 相关事件

      # 查看 HPA 事件
      kubectl get events --field-selector involvedObject.kind=HorizontalPodAutoscaler
      
      # 查看缩放历史
      kubectl describe deployment myapp | grep -A 10 "Events"

      3. 指标调试

      # 检查 Metrics API 是否正常工作
      kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | jq .
      
      # 检查自定义指标
      kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
      
      # 直接查询 Pod 指标
      kubectl top pods
      kubectl top nodes

      八、常见问题排查

      1. HPA 不扩容

      # 检查指标是否可用
      kubectl describe hpa myapp-hpa
      # 查看 Events 部分是否有错误信息
      
      # 检查 Metrics Server
      kubectl get apiservices | grep metrics
      kubectl logs -n kube-system -l k8s-app=metrics-server
      
      # 检查资源请求配置
      kubectl get deployment myapp -o yaml | grep resources -A 5

      2. HPA 频繁震荡

      # 调整行为配置
      behavior:
        scaleDown:
          stabilizationWindowSeconds: 600  # 延长缩容稳定窗口
          policies:
          - type: Pods
            value: 1                       # 每次只缩容 1 个 Pod
            periodSeconds: 300             # 每 5 分钟最多一次

      九、性能优化建议

      1. 大规模集群优化

      # kube-controller-manager 配置优化
      apiVersion: v1
      kind: Pod
      metadata:
        name: kube-controller-manager
        namespace: kube-system
      spec:
        containers:
        - command:
          - kube-controller-manager
          - --horizontal-pod-autoscaler-sync-period=30s           # 调整同步周期
          - --horizontal-pod-autoscaler-downscale-stabilization=5m0s
          - --horizontal-pod-autoscaler-initial-readiness-delay=30s
          - --horizontal-pod-autoscaler-cpu-initialization-period=5m0s

      总结

      HPA 的实现涉及:

      1. 指标收集:通过 Metrics API 聚合资源和使用情况
      2. 算法计算:基于指标值和目标值计算期望副本数
      3. 决策平滑:应用稳定窗口和策略避免震荡
      4. 执行缩放:更新目标资源的副本数
      5. 状态跟踪:记录缩放历史和当前状态

      HPA 的智能化体现在多指标决策、行为控制、冷却机制等方面,使得自动扩缩容既响应迅速又稳定可靠。

      Mar 7, 2024

      More than 1k Nodes

      在这个量级上,K8s 不再只是“能跑就行”,而是进入可扩展性、稳定性、可观测性和资源效率的工程化挑战。下面我从架构、控制面、节点管理、网络、存储、安全和运维几个方面系统讲解。


      🧠 一、总体思路:大规模集群的本质挑战

      当节点规模超过 500~1000 时,Kubernetes 的瓶颈通常出现在:

      • 控制平面(API Server / etcd)压力过大
      • 调度器吞吐不足
      • 资源对象(Pod / Node / Secret / ConfigMap 等)过多,导致 List/Watch 延迟
      • 网络和 CNI 插件在高并发下性能下降
      • 监控、日志、事件系统的数据量爆炸
      • 维护和升级变得极度复杂

      所以,大规模集群的重点是:

      控制平面分层、节点池分区、流量隔离、观测与调优。


      🏗️ 二、控制平面(Control Plane)

      1. etcd 优化

      • 独立部署:不要和 kube-apiserver 混布,最好是独立的高性能节点(NVMe SSD、本地盘)。
      • 使用 etcd v3.5+(性能改进明显),并开启压缩和快照机制。
      • 调大 --max-request-bytes--quota-backend-bytes,避免过载。
      • 定期 defrag:可用 CronJob 自动化。
      • 不要存放短生命周期对象(例如频繁更新的 CRD 状态),可以考虑用外部缓存系统(如 Redis 或 SQL)。

      2. API Server 扩展与保护

      • 使用 负载均衡(HAProxy、NGINX、ELB)在多 API Server 之间分流;

      • 调整:

        • --max-mutating-requests-inflight
        • --max-requests-inflight
        • --target-ram-mb
      • 合理设置 --request-timeout,防止 watch 卡死;

      • 限制大量 client watch 行为(Prometheus、controller-manager 等);

      • 对 client 侧使用 aggregatorread-only proxy 来降低负载。

      3. Scheduler & Controller Manager

      • 多调度器实例(leader election)

      • 启用 调度缓存(SchedulerCache)优化

      • 调整:

        • --kube-api-qps--kube-api-burst
        • 调度算法的 backoff 策略;
      • 对自定义 Operator 建议使用 workqueue with rate limiters 防止风暴。


      🧩 三、节点与 Pod 管理

      1. 节点分区与拓扑

      • 按功能/位置划分 Node Pool(如 GPU/CPU/IO 密集型);
      • 使用 Topology Spread Constraints 避免集中调度;
      • 考虑用 Cluster Federation (KubeFed)多个集群 + 集中管理(如 ArgoCD 多集群、Karmada、Fleet)

      2. 节点生命周期

      • 控制 kubelet 心跳频率 (--node-status-update-frequency);
      • 通过 Node Problem Detector (NPD) 自动标记异常节点;
      • 监控 Pod eviction rate,防止节点频繁漂移;
      • 启用 graceful node shutdown 支持。

      3. 镜像与容器运行时

      • 镜像预热(Image pre-pull);
      • 使用 镜像仓库代理(Harbor / registry-mirror)
      • 考虑 containerd 代替 Docker;
      • 定期清理 /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots

      🌐 四、网络(CNI)

      1. CNI 选择与调优

      • 大规模下优选:

        • Calico (BGP 模式)
        • Cilium (eBPF)
        • 或使用云原生方案(AWS CNI, Azure CNI)。
      • 降低 ARP / 路由表压力:

        • 使用 IPAM 子网分段
        • 开启 Cilium 的 ClusterMesh 分层;
      • 调整 conntrack 表大小(net.netfilter.nf_conntrack_max)。

      2. Service & DNS

      • 启用 CoreDNS 缓存

      • 对大规模 Service 场景,考虑 Headless Service + ExternalName

      • 优化 kube-proxy:

        • 使用 IPVS 模式
        • Cilium service LB
      • 如果 Service 数量非常多,可拆分 namespace 级 DNS 域。


      💾 五、存储(CSI)

      • 使用 分布式存储系统(Ceph、Longhorn、OpenEBS、CSI-HostPath);
      • 避免高频小 I/O 的 PVC;
      • 定期清理僵尸 PV/PVC;
      • 对 CSI driver 开启限流与重试机制。

      🔒 六、安全与访问控制

      • 开启 RBAC 严格控制
      • 限制 namespace 级资源上限(ResourceQuota, LimitRange);
      • 审计日志(Audit Policy)异步存储;
      • 对外接口统一走 Ingress Controller;
      • 如果有 Operator 或 CRD 资源暴涨,记得定期清理过期对象。

      📈 七、可观测性与维护

      1. 监控

      • Prometheus 集群化(Thanos / VictoriaMetrics);
      • 不直接监控所有 Pod,可抽样或聚合;
      • kube-state-metrics 与 cAdvisor 数据要限流。

      2. 日志

      • 统一日志收集(Loki / Elasticsearch / Vector);
      • 日志量控制策略(采样、压缩、清理)。

      3. 升级与测试

      • 使用 灰度升级 / Node pool rolling
      • 每次升级前跑 e2e 测试;
      • 对控制平面单独做快照和备份(etcd snapshot)。

      ⚙️ 八、性能调优与实践经验

      • 调整 kubelet QPS 限制:

        --kube-api-qps=100 --kube-api-burst=200
      • 合理的 Pod 数量控制:

        • 单节点不超过 110 Pods;
        • 单 namespace 建议 < 5000 Pods;
        • 总体目标:1k 节点 → 5~10 万 Pods 以内。
      • 使用 CRD Sharding / 缩减 CRD 状态字段

      • 避免大量短生命周期 Job,可用 CronJob + TTLController 清理。


      🧭 九、扩展方向

      当规模继续上升(>3000 节点)时,可以考虑:

      • 多集群架构(Cluster Federation / Karmada / Rancher Fleet)
      • 控制平面分层(cell-based control plane)
      • API Aggregation Layer + Custom Scheduler

      Mar 7, 2024

      Network Policy

      1. Network Policy 的设计原理

      Kubernetes Network Policy 的设计核心思想是:在默认允许的集群网络中,引入一个“默认拒绝”的、声明式的、基于标签的防火墙

      让我们来分解这个核心思想:

      1. 从“默认允许”到“默认拒绝”

        • 默认行为:在没有任何 Network Policy 的情况下,Kubernetes 集群内的 Pod 之间是可以自由通信的(取决于 CNI 插件),甚至来自外部的流量也可能直接访问到 Pod。这就像在一个没有防火墙的开放网络里。
        • Network Policy 的作用:一旦在某个 Namespace 中创建了一个 Network Policy,它就会像一个“开关”,将这个 Namespace 或特定 Pod 的默认行为变为 “默认拒绝”。之后,只有策略中明确允许的流量才能通过。
      2. 声明式模型

        • 和其他的 Kubernetes 资源(如 Deployment、Service)一样,Network Policy 也是声明式的。你只需要告诉 Kubernetes“你期望的网络状态是什么”(例如,“允许来自带有 role=frontend 标签的 Pod 的流量访问带有 role=backend 标签的 Pod 的 6379 端口”),而不需要关心如何通过 iptables 或 eBPF 命令去实现它。Kubernetes 和其下的 CNI 插件会负责实现你的声明。
      3. 基于标签的选择机制

        • 这是 Kubernetes 的核心设计模式。Network Policy 不关心 Pod 的 IP 地址,因为 IP 是动态且易变的。它通过 标签 来选择一组 Pod。
        • podSelector: 选择策略所应用的 Pod(即目标 Pod)。
        • namespaceSelector: 根据命名空间的标签来选择来源或目标命名空间。
        • namespaceSelectorpodSelector 可以组合使用,实现非常精细的访问控制。
      4. 策略是叠加的

        • 多个 Network Policy 可以同时作用于同一个 Pod。最终的规则是所有相关策略的 并集。如果任何一个策略允许了某条流量,那么该流量就是被允许的。这意味着你可以分模块、分层次地定义策略,而不会相互覆盖。

      2. Network Policy 的实现方式

      一个非常重要的概念是:Network Policy 本身只是一个 API 对象,它定义了一套规范。它的具体实现依赖于 Container Network Interface 插件。

      Kubernetes 不会自己实现网络策略,而是由 CNI 插件来负责。这意味着:

      • 如果你的 CNI 插件不支持 Network Policy,那么你创建的 Policy 将不会产生任何效果。
      • 不同的 CNI 插件使用不同的底层技术来实现相同的 Network Policy 规范。

      主流的实现方式和技术包括:

      1. 基于 iptables

        • 工作原理:CNI 插件(如 Calico 的部分模式、Weave Net 等)会监听 Kubernetes API,当有 Network Policy 被创建时,它会在节点上生成相应的 iptables 规则。这些规则会对进出 Pod 网络接口(veth pair)的数据包进行过滤。
        • 优点:成熟、稳定、通用。
        • 缺点:当策略非常复杂时,iptables 规则链会变得很长,可能对性能有一定影响。
      2. 基于 eBPF

        • 工作原理:这是更现代和高效的方式,被 Cilium 等项目广泛采用。eBPF 允许将程序直接注入到 Linux 内核中,在内核层面高效地执行数据包过滤、转发和策略检查。
        • 优点:高性能、灵活性极强(可以实现 L3/L4/L7 所有层面的策略)、对系统影响小。
        • 缺点:需要较新的 Linux 内核版本。
      3. 基于 IPVS 或自有数据平面

        • 一些 CNI 插件(如 Antrea,它底层使用 OVS)可能有自己独立的数据平面,并在其中实现策略的匹配和执行。

      常见的支持 Network Policy 的 CNI 插件:

      • Calico: 功能强大,支持复杂的网络策略,既可以使用 iptables 模式也可以使用 eBPF 模式。
      • Cilium: 基于 eBPF,原生支持 Network Policy,并扩展到了 L7(HTTP、gRPC 等)网络策略。
      • Weave Net: 提供了对 Kubernetes Network Policy 的基本支持。
      • Antrea: 基于 Open vSwitch,也提供了强大的策略支持。

      3. Network Policy 的用途

      Network Policy 是实现 Kubernetes “零信任”“微隔离” 安全模型的核心工具。其主要用途包括:

      1. 实现最小权限原则

        • 这是最核心的用途。通过精细的策略,确保一个 Pod 只能与它正常工作所 必需 的其他 Pod 或外部服务通信,除此之外的一切连接都被拒绝。这极大地减少了攻击面。
      2. 隔离多租户环境

        • 在共享的 Kubernetes 集群中,可以为不同的团队、项目或环境(如 dev, staging)创建不同的命名空间。然后使用 Network Policy 严格限制跨命名空间的访问,确保它们相互隔离,互不干扰。
      3. 保护关键基础服务

        • 数据库、缓存(如 Redis)、消息队列等后端服务通常不应该被所有 Pod 访问。可以创建策略,只允许特定的前端或中间件 Pod(通过标签选择)访问这些后端服务的特定端口。
        # 示例:只允许 role=api 的 Pod 访问 role=db 的 Pod 的 5432 端口
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        metadata:
          name: allow-api-to-db
        spec:
          podSelector:
            matchLabels:
              role: db
          policyTypes:
          - Ingress
          ingress:
          - from:
            - podSelector:
                matchLabels:
                  role: api
            ports:
            - protocol: TCP
              port: 5432
      4. 控制外部访问

        • 使用 ipBlock 字段,可以限制只有来自特定 IP 段(例如公司办公室的 IP)的流量才能访问集群内部的服务。这可以用来替代或补充传统的防火墙规则。
      5. 划分应用层次安全边界

        • 在一个典型的 Web 应用中,可以创建清晰的层次:
          • 前端层: 可以接收来自外部的流量(80/443端口),但只能与后端层通信。
          • 后端层: 只能接收来自前端层的流量,并只能与数据层通信。
          • 数据层: 只能接收来自后端层的流量,不接受任何其他来源的请求。

      总结

      特性描述
      设计原理在默认允许的网络中,通过声明式和基于标签的机制,实现“默认拒绝”的精细流量控制。
      实现方式由 CNI 插件负责实现,底层技术包括 iptableseBPF 等。策略本身是 Kubernetes 的 API 资源。
      主要用途实现微隔离最小权限原则多租户隔离保护关键服务控制外部访问,是 Kubernetes 网络安全的基石。

      简单来说,Network Policy 就是 Kubernetes 世界的防火墙规则,它让你能够定义“谁在什么条件下可以访问什么”,是生产环境中保障应用安全不可或缺的一部分。

      Mar 7, 2024

      Node NotReady

      当 Kubernetes 中某些 Node 节点状态变为 NotReady 时,这往往意味着 kubelet 无法与控制平面(API Server)正常通信,或该节点上某些关键组件/资源异常。

      我们可以从以下两个层面来分析: 1️⃣ 导致节点 NotReady 的常见原因 2️⃣ NotReady 状态对整个集群和业务的影响


      🧩 一、Node NotReady 的常见原因分类

      kubelet 每 10 秒(默认)向 API Server 报告一次心跳(NodeStatus)。 如果连续 40 秒(默认 --node-monitor-grace-period=40s)没有收到更新,Controller Manager 会将节点标记为 NotReady

      下面按类别详细分析👇


      🖧 1. 网络层异常(最常见)

      症状:节点能 ping 通外网,但与 control plane 交互超时。 原因包括:

      • 节点与 kube-apiserver 之间的网络中断(如防火墙、路由异常、VPC 问题);
      • API Server 负载均衡异常(L4/L7 LB 停止转发流量);
      • Pod 网络插件(CNI)崩溃,kubelet 无法汇报 Pod 状态;
      • 节点 DNS 解析异常(影响 kubelet 访问 API Server)。

      排查方式:

      # 在节点上检查 API Server 可达性
      curl -k https://<apiserver-ip>:6443/healthz
      # 检查 kubelet 日志
      journalctl -u kubelet | grep -E "error|fail|timeout"

      ⚙️ 2. kubelet 本身异常

      症状:节点长时间 NotReady,重启 kubelet 后恢复。

      原因包括:

      • kubelet 崩溃 / 死循环;
      • 磁盘满,导致 kubelet 无法写临时目录(/var/lib/kubelet);
      • 证书过期(/var/lib/kubelet/pki/kubelet-client-current.pem);
      • CPU/Mem 资源耗尽,kubelet 被 OOM;
      • kubelet 配置文件被改动,重启后加载失败。

      排查方式:

      systemctl status kubelet
      journalctl -u kubelet -n 100
      df -h /var/lib/kubelet

      💾 3. 节点资源耗尽

      症状:Node 状态为 NotReadyUnknown,Pod 被驱逐。

      可能原因:

      • 磁盘使用率 > 90%,触发 kubelet DiskPressure
      • 内存 / CPU 长期 100%,触发 MemoryPressure
      • inode 用尽(df -i);
      • 临时目录 /var/lib/docker/tmp/tmp 爆满。

      排查方式:

      kubectl describe node <node-name>
      # 查看 conditions
      # Conditions:
      #   Type              Status
      #   ----              ------
      #   MemoryPressure    True
      #   DiskPressure      True

      🧱 4. 控制面通信问题(API Server / Controller Manager)

      症状:多个节点同时 NotReady

      可能原因:

      • API Server 压力过大,导致心跳包无法及时处理;
      • etcd 异常(写延迟高);
      • Controller Manager 无法更新 NodeStatus;
      • 集群负载均衡器(如 haproxy)挂掉。

      排查方式:

      kubectl get componentstatuses
      # 或直接检查控制平面节点
      kubectl -n kube-system get pods -l tier=control-plane

      🔌 5. 容器运行时 (containerd/docker/crio) 异常

      症状:kubelet 报 “Failed to list pod sandbox”。

      原因包括:

      • containerd 服务挂掉;
      • 版本不兼容(kubelet 与 runtime 版本差异过大);
      • runtime socket 权限错误;
      • overlayfs 损坏;
      • /var/lib/containerd/run/containerd 文件系统只读。

      排查方式:

      systemctl status containerd
      journalctl -u containerd | tail
      crictl ps

      ⏱️ 6. 时间同步错误

      症状:kubelet 心跳被判定过期,但实际节点正常。

      原因:

      • 节点时间漂移(未启用 NTP / chrony);
      • 控制面和节点时间差 > 5 秒;
      • TLS 校验失败(证书时间不合法)。

      🧰 7. 节点维护或人为操作

      包括:

      • 节点被 cordon/drain;
      • 网络策略阻断 kubelet;
      • 人为停掉 kubelet;
      • 节点被重装后未清理旧状态(Node UID 冲突)。

      ⚠️ 二、Node NotReady 的后果与影响

      影响范围描述
      1️⃣ Pod 调度Scheduler 会避免调度新 Pod 到该节点。
      2️⃣ Pod 驱逐Controller Manager 默认在节点 NotReady 超过 300s--pod-eviction-timeout)后,会驱逐所有 Pod。
      3️⃣ Service Endpoint 缺失该节点上运行的 Pod 从 Service Endpoint 列表中移除,导致负载均衡流量下降。
      4️⃣ DaemonSet 中断DaemonSet Controller 不再在该节点上创建/管理 Pod。
      5️⃣ 数据丢失风险若节点上的 Pod 使用本地卷(emptyDir、hostPath),被驱逐后数据会丢失。
      6️⃣ 集群监控告警Prometheus / Alertmanager 触发告警(如 KubeNodeNotReadyKubeletDown)。
      7️⃣ 自动扩缩容失效Cluster Autoscaler 无法正确评估资源利用率。

      🧭 三、最佳实践与预防建议

      1. 启用 Node Problem Detector (NPD) 自动标记系统级异常;

      2. 监控 NodeConditionsReadyMemoryPressureDiskPressure);

      3. 统一节点健康检查策略(如通过 taintstolerations);

      4. 自动修复机制

        • 结合 Cluster API 或自研 Controller 实现 Node 自动替换;
        • 若节点 NotReady 超过 10 分钟,自动重建;
      5. 定期巡检:

        • kubelet、containerd 状态;
        • 系统时间同步;
        • 磁盘使用率;
        • API Server QPS 和 etcd 延迟。
      Mar 7, 2024

      Pause 容器

      Kubernetes Pause 容器的用途

      Pause 容器是 Kubernetes 中一个非常小但极其重要的基础设施容器。很多人会忽略它,但它是 Pod 网络和命名空间共享的核心。


      🎯 核心作用

      1. 作为 Pod 的"根容器"(Infrastructure Container)

      Pause 容器是每个 Pod 中第一个启动的容器,它的生命周期代表整个 Pod 的生命周期。

      Pod 生命周期:
      创建 Pod → 启动 Pause 容器 → 启动业务容器 → ... → 业务容器结束 → 删除 Pause 容器 → Pod 销毁

      2. 持有和共享 Linux 命名空间

      Pause 容器创建并持有以下命名空间,供 Pod 内其他容器共享:

      • Network Namespace (网络命名空间) - 最重要!
      • IPC Namespace (进程间通信)
      • UTS Namespace (主机名)
      # 查看 Pod 中的容器
      docker ps | grep pause
      
      # 你会看到类似输出:
      # k8s_POD_mypod_default_xxx  k8s.gcr.io/pause:3.9
      # k8s_app_mypod_default_xxx  myapp:latest

      🌐 网络命名空间共享(最关键的用途)

      工作原理

      ┌─────────────────── Pod ───────────────────┐
      │                                            │
      │  ┌─────────────┐                          │
      │  │   Pause     │ ← 创建网络命名空间        │
      │  │  Container  │ ← 拥有 Pod IP            │
      │  └──────┬──────┘                          │
      │         │ (共享网络栈)                     │
      │  ┌──────┴──────┬──────────┬──────────┐   │
      │  │ Container A │Container B│Container C│  │
      │  │  (业务容器)  │  (业务容器)│ (业务容器) │  │
      │  └─────────────┴──────────┴──────────┘   │
      │                                            │
      │  所有容器共享:                              │
      │  - 同一个 IP 地址 (Pod IP)                 │
      │  - 同一个网络接口                           │
      │  - 同一个端口空间                           │
      │  - 可以通过 localhost 互相访问              │
      └────────────────────────────────────────────┘

      实际效果

      # 示例 Pod
      apiVersion: v1
      kind: Pod
      metadata:
        name: multi-container-pod
      spec:
        containers:
        - name: nginx
          image: nginx
          ports:
          - containerPort: 80
        - name: sidecar
          image: busybox
          command: ['sh', '-c', 'while true; do wget -O- localhost:80; sleep 5; done']

      在这个例子中:

      • Pause 容器创建网络命名空间并获得 Pod IP (如 10.244.1.5)
      • nginx 容器加入这个网络命名空间,监听 80 端口
      • sidecar 容器也加入同一网络命名空间
      • sidecar 可以通过 localhost:80 访问 nginx,因为它们共享网络栈

      🔍 为什么需要 Pause 容器?

      问题场景:如果没有 Pause 容器

      假设 Pod 中有两个容器 A 和 B:

      场景 1:容器 A 先启动,创建网络命名空间
      ├─ 容器 A 持有网络命名空间 → 拥有 Pod IP
      └─ 容器 B 加入容器 A 的网络命名空间
      
      问题:如果容器 A 崩溃重启或被删除,网络命名空间消失
      → 容器 B 失去网络连接
      → Pod IP 改变
      → Service 路由失效 ❌

      解决方案:引入 Pause 容器

      Pause 容器(持有命名空间) ← 永远不会主动退出
      ├─ 容器 A 加入
      └─ 容器 B 加入
      
      优势:
      ✅ 容器 A 或 B 崩溃不影响网络命名空间
      ✅ Pod IP 始终保持稳定
      ✅ 业务容器可以独立重启
      ✅ 简化容器间的依赖关系

      📦 Pause 容器的特点

      1. 极其精简

      # pause 容器的代码只有几十行 C 代码
      // 核心功能就是:永远 sleep
      int main() {
          for (;;) pause();  // 无限暂停,等待信号
          return 0;
      }

      镜像大小:约 700KB (相比普通镜像动辄几百 MB)

      2. 资源占用极低

      # 查看 Pause 容器资源占用
      docker stats <pause-container-id>
      
      # 典型输出:
      # CPU: 0.00%
      # MEM: 0.5 MiB

      3. 生命周期管理

      • Kubelet 创建 Pod 时首先启动 Pause 容器
      • Pause 容器退出 = Pod 销毁
      • 业务容器重启不影响 Pause 容器

      🛠️ 实际用途场景

      场景 1:Sidecar 模式

      # 应用 + 日志收集器
      spec:
        containers:
        - name: app
          image: myapp
          volumeMounts:
          - name: logs
            mountPath: /var/log
        - name: log-collector
          image: fluentd
          volumeMounts:
          - name: logs
            mountPath: /var/log
      • Pause 容器保证两个容器可以通过共享卷和 localhost 通信
      • 即使 app 重启,log-collector 仍能正常工作

      场景 2:Service Mesh (如 Istio)

      # 应用 + Envoy 代理
      spec:
        containers:
        - name: app
          image: myapp
          ports:
          - containerPort: 8080
        - name: istio-proxy  # Envoy sidecar
          image: istio/proxyv2
      • Pause 容器持有网络命名空间
      • Envoy 代理拦截所有进出流量
      • 应用无需感知代理存在

      场景 3:初始化和主容器协作

      spec:
        initContainers:
        - name: init-config
          image: busybox
          command: ['sh', '-c', 'echo "config" > /config/app.conf']
          volumeMounts:
          - name: config
            mountPath: /config
        containers:
        - name: app
          image: myapp
          volumeMounts:
          - name: config
            mountPath: /config
      • Pause 容器在整个过程中保持网络和 IPC 命名空间稳定

      🔧 查看和调试 Pause 容器

      查看 Pod 中的 Pause 容器

      # 方式 1:通过 crictl (推荐)
      crictl pods
      crictl ps -a | grep pause
      
      # 方式 2:通过 docker (如果使用 Docker 运行时)
      docker ps -a | grep pause
      docker inspect <pause-container-id>
      
      # 方式 3:查看 Pod 的容器列表
      kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].name}'

      Pause 镜像版本

      # 查看 kubelet 使用的 pause 镜像
      kubectl get pod <pod-name> -o yaml | grep pause
      
      # 常见版本:
      # registry.k8s.io/pause:3.9 (最新)
      # registry.k8s.io/pause:3.8
      # k8s.gcr.io/pause:3.7

      ⚠️ 常见问题

      Q1: 为什么看不到 Pause 容器?

      A: 使用 kubectl get pods 不会显示 Pause 容器,它对用户是透明的。需要使用底层运行时命令(如 crictldocker ps)才能看到。

      Q2: Pause 容器会影响性能吗?

      A: 几乎不会。Pause 容器只占用约 0.5MB 内存,CPU 使用为 0,不执行任何业务逻辑。

      Q3: 可以删除 Pause 容器吗?

      A: 不能手动删除。删除 Pause 容器会导致整个 Pod 被销毁。

      Q4: 不同 Pod 的 Pause 容器是否共享?

      A: 不共享。每个 Pod 都有独立的 Pause 容器,确保 Pod 之间的网络和命名空间隔离。


      📝 总结

      用途说明
      命名空间持有者创建并持有 Network、IPC、UTS 命名空间
      网络基础使 Pod 内所有容器共享同一 IP 和网络栈
      生命周期锚点代表 Pod 的生命周期,容器可独立重启
      简化架构解耦容器间依赖,避免级联故障
      资源高效极小的镜像和资源占用

      核心价值: Pause 容器是 Kubernetes Pod 抽象的基石,让多个容器能像在同一主机上一样协作,同时保持各自的独立性和可重启性。

      需要我详细讲解某个具体场景或深入探讨实现原理吗? 🚀

      Mar 7, 2024

      Pod在K8S中DNS解析流程和顺序

      核心概念

      1. CoreDNS: 从Kubernetes 1.11开始,CoreDNS是默认的DNS服务。它作为一个或多个Pod运行在kube-system命名空间下,并配有一个Kubernetes Service(通常叫kube-dns)。
      2. resolv.conf 文件: 每个Pod的/etc/resolv.conf文件是DNS解析的蓝图。Kubelet会自动生成这个文件并挂载到Pod中。
      3. DNS策略: 你可以通过Pod Spec中的dnsPolicy字段来配置DNS策略。

      Pod 的 /etc/resolv.conf 解析

      这是一个典型的Pod内的/etc/resolv.conf文件内容:

      nameserver 10.96.0.10
      search <namespace>.svc.cluster.local svc.cluster.local cluster.local
      options ndots:5

      让我们逐行分析:

      1. nameserver 10.96.0.10

      • 这是CoreDNS Service的集群IP地址。所有Pod的DNS查询默认都会发送到这个地址。
      • 这个IP来自kubelet的--cluster-dns标志,在启动时确定。

      2. search <namespace>.svc.cluster.local svc.cluster.local cluster.local

      • 搜索域列表。当你使用不完整的域名(即不是FQDN)时,系统会按照这个列表的顺序,依次将搜索域附加到主机名后面,直到找到匹配的记录。
      • <namespace>是你的Pod所在的命名空间,例如default
      • 搜索顺序
        • <pod-namespace>.svc.cluster.local
        • svc.cluster.local
        • cluster.local

      3. options ndots:5

      • 这是一个关键的优化/控制选项。
      • 规则: 如果一个域名中的点(.)数量大于或等于这个值(这里是5),系统会将其视为绝对域名(FQDN),并首先尝试直接解析,不会走搜索域列表。
      • 反之,如果点数少于5,系统会依次尝试搜索域,如果都失败了,最后再尝试名称本身。

      DNS 解析流程与顺序(详解)

      假设你的Pod在default命名空间,并且resolv.conf如上所示。

      场景1:解析Kubernetes Service(短名称)

      你想解析同一个命名空间下的Service:my-svc

      1. 应用程序请求解析 my-svc
      2. 系统检查名称 my-svc,点数(0) < 5。
      3. 进入搜索流程
        • 第一次尝试: my-svc.default.svc.cluster.local -> 成功! 返回ClusterIP。
        • 解析结束。

      场景2:解析不同命名空间的Service

      你想解析另一个命名空间prod下的Service:my-svc.prod

      1. 应用程序请求解析 my-svc.prod
      2. 系统检查名称 my-svc.prod,点数(1) < 5。
      3. 进入搜索流程
        • 第一次尝试: my-svc.prod.default.svc.cluster.local -> 失败(因为该Service不在default命名空间)。
        • 第二次尝试: my-svc.prod.svc.cluster.local -> 成功! 返回ClusterIP。
        • 解析结束。

      场景3:解析外部域名(例如 www.google.com

      1. 应用程序请求解析 www.google.com
      2. 系统检查名称 www.google.com,点数(3) < 5。
      3. 进入搜索流程
        • 第一次尝试: www.google.com.default.svc.cluster.local -> 失败
        • 第二次尝试: www.google.com.svc.cluster.local -> 失败
        • 第三次尝试: www.google.com.cluster.local -> 失败
      4. 所有搜索域都失败了,系统最后尝试名称本身:www.google.com -> 成功! CoreDNS会将其转发给上游DNS服务器(例如宿主机上的DNS或网络中配置的DNS)。

      场景4:解析被认为是FQDN的域名(点数 >= 5)

      假设你有一个StatefulSet,Pod的FQDN是web-0.nginx.default.svc.cluster.local

      1. 应用程序请求解析 web-0.nginx.default.svc.cluster.local
      2. 系统检查名称,点数(4) < 5?注意:这里是4个点,仍然小于5! 所以它仍然会走搜索流程。
        • 这会先尝试 web-0.nginx.default.svc.cluster.local.default.svc.cluster.local,显然是错误的。
        • 为了避免这种低效行为,最佳实践是在应用程序中配置或使用绝对域名(尾部带点)。

      绝对域名示例: 应用程序请求解析 web-0.nginx.default.svc.cluster.local.(注意最后有一个点)。

      • 系统识别其为FQDN,直接查询,不经过任何搜索域。这是最有效的方式。

      DNS 策略

      Pod的dnsPolicy字段决定了如何生成resolv.conf

      • ClusterFirst(默认): DNS查询首先被发送到Kubernetes集群的CoreDNS。如果域名不在集群域内(例如cluster.local),查询会被转发到上游DNS。
      • ClusterFirstWithHostNet: 对于使用hostNetwork: true的Pod,如果你想让它使用集群DNS,就需要设置这个策略。
      • Default: Pod直接从宿主机继承DNS配置(即使用宿主的/etc/resolv.conf)。这意味着它不会使用CoreDNS。
      • None: 忽略所有默认的DNS设置。你必须使用dnsConfig字段来提供自定义的DNS配置。

      总结与流程图

      解析顺序可以简化为以下决策流程:

      flowchart TD
          A[应用程序发起DNS查询] --> B{查询名称的<br>点数 '.' >= 5?}
          
          B -- 是<br>(视为FQDN) --> C[直接查询该名称]
          C --> D{解析成功?}
          D -- 是 --> E[返回结果]
          D -- 否 --> F[解析失败]
          
          B -- 否<br>(视为短名称) --> G
          subgraph G [循环搜索域列表]
              direction LR
              H[依次将搜索域附加<br>到名称后并查询] --> I{解析成功?}
              I -- 是 --> J[返回结果]
          end
          
          I -- 循环结束仍失败 --> K[直接查询原始名称]
          K --> L{解析成功?}
          L -- 是 --> E
          L -- 否 --> F

      关键要点:

      1. 默认流向: Pod -> CoreDNS Service -> CoreDNS Pod -> (根据域判断)返回K8s记录或转发到上游DNS。
      2. 搜索域顺序: 命名空间 -> svc -> cluster.local
      3. ndots:5的影响: 这是为了在便利性和性能之间取得平衡。对于需要频繁访问的外部域名,为了性能最好在应用程序中配置FQDN(尾部带点)或调整ndots选项。
      4. 调试技巧: 进入Pod并执行cat /etc/resolv.confnslookupdig命令是诊断DNS问题的第一步。
      Mar 7, 2024

      当执行kubectl exec 命令时,发生了什么?

      kubectl exec 的实现原理涉及多个组件协同工作,以下是详细原理分析:

      1. 整体架构流程

      用户 -> kubectl -> API Server -> Kubelet -> 容器运行时 -> 目标容器

      2. 详细执行步骤

      步骤1:kubectl 客户端处理

      kubectl exec -it <pod-name> -- /bin/bash
      • kubectl 解析命令参数
      • 构造 Exec API 请求
      • 建立与 API Server 的长连接

      步骤2:API Server 处理

      // API 路径示例
      POST /api/v1/namespaces/{namespace}/pods/{name}/exec
      • 认证和授权检查
      • 验证用户是否有 exec 权限
      • 查找目标 Pod 所在节点
      • 将请求代理到对应节点的 Kubelet

      步骤3:Kubelet 处理

      // Kubelet 的 exec 处理逻辑
      func (h *ExecHandler) serveExec(w http.ResponseWriter, req *http.Request) {
          // 获取容器信息
          // 调用容器运行时接口
          // 建立数据流传输
      }
      • 通过 CRI(Container Runtime Interface)调用容器运行时
      • 创建到容器的连接
      • 管理标准输入、输出、错误流

      步骤4:容器运行时执行

      // CRI 接口定义
      service RuntimeService {
          rpc Exec(ExecRequest) returns (ExecResponse) {}
      }
      • Docker: 使用 docker exec 底层机制
      • Containerd: 通过 task 执行命令
      • CRI-O: 通过 conmon 管理执行会话

      3. 关键技术机制

      3.1 流式传输协议

      // 使用 SPDY 或 WebSocket 协议
      // 支持多路复用的数据流
      type StreamProtocol interface {
          Stream(stdin io.Reader, stdout, stderr io.Writer) error
      }

      3.2 终端处理(TTY)

      // 伪终端配置
      type ExecOptions struct {
          Stdin     io.Reader
          Stdout    io.Writer
          Stderr    io.Writer
          TTY       bool
          ptyMaster *os.File
      }

      3.3 会话管理

      // ExecSession 管理执行会话
      type ExecSession struct {
          id        string
          stdinPipe io.WriteCloser
          stdoutPipe io.ReadCloser
          stderrPipe io.ReadCloser
          done      chan struct{}
      }

      4. 网络通信流程

      客户端 (kubectl)
          ↓ HTTPS with SPDY/WebSocket
      API Server
          ↓ 代理连接
      Kubelet (节点)
          ↓ CRI gRPC
      容器运行时
          ↓ 容器命名空间
      目标容器进程

      5. 安全机制

      5.1 认证授权

      # RBAC 配置示例
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      metadata:
        name: pod-exec
      rules:
      - apiGroups: [""]
        resources: ["pods/exec"]
        verbs: ["create"]

      5.2 安全上下文

      // 安全配置
      securityContext := &v1.SecurityContext{
          RunAsUser:  &uid,
          RunAsGroup: &gid,
          Capabilities: &v1.Capabilities{
              Drop: []v1.Capability{"ALL"},
          },
      }

      6. 实际代码示例

      kubectl 端实现

      func (o *ExecOptions) Run() error {
          // 建立与 API Server 的连接
          executor, err := remotecommand.NewSPDYExecutor(
              o.Config, "POST", req.URL())
          
          // 执行命令
          return executor.Stream(remotecommand.StreamOptions{
              Stdin:  o.In,
              Stdout: o.Out,
              Stderr: o.ErrOut,
              Tty:    o.TTY,
          })
      }

      Kubelet 端处理

      func (h *ExecHandler) serveExec(w http.ResponseWriter, req *http.Request) {
          // 获取容器 ID
          containerID := podContainer.ContainerID
          
          // 通过 CRI 执行命令
          execRequest := &runtimeapi.ExecRequest{
              ContainerId: containerID.ID,
              Cmd:         cmd,
              Tty:         tty,
              Stdin:       stdin,
              Stdout:      stdout,
              Stderr:      stderr,
          }
          
          // 调用容器运行时
          runtimeService.Exec(execRequest)
      }

      7. 容器运行时差异

      Docker

      // 使用 Docker Engine API
      client.ContainerExecCreate()
      client.ContainerExecAttach()

      Containerd

      // 使用 CRI 插件
      task.Exec()

      8. 故障排查要点

      1. 权限问题: 检查 RBAC 配置
      2. 网络连通性: API Server ↔ Kubelet 网络
      3. 容器状态: 目标容器必须处于 Running 状态
      4. 资源限制: 容器资源是否充足
      5. 安全策略: Pod Security Policies 限制

      这种设计使得 kubectl exec 能够在分布式环境中安全、可靠地执行容器内命令,同时保持了良好的用户体验。

      Mar 7, 2024

      QoS 详解

      Kubernetes QoS (Quality of Service) 等级详解

      QoS 等级是 Kubernetes 用来管理 Pod 资源和在资源不足时决定驱逐优先级的机制。


      🎯 三种 QoS 等级

      Kubernetes 根据 Pod 的资源配置自动分配 QoS 等级,共有三种:

      1. Guaranteed (保证型) - 最高优先级

      2. Burstable (突发型) - 中等优先级

      3. BestEffort (尽力而为型) - 最低优先级


      📊 QoS 等级详解

      1️⃣ Guaranteed (保证型)

      定义条件(必须同时满足)

      • Pod 中每个容器(包括 Init 容器)都必须设置 requestslimits
      • 对于每个容器,CPU 和内存的 requests 必须等于 limits

      YAML 示例

      apiVersion: v1
      kind: Pod
      metadata:
        name: guaranteed-pod
      spec:
        containers:
        - name: app
          image: nginx
          resources:
            requests:
              memory: "200Mi"
              cpu: "500m"
            limits:
              memory: "200Mi"  # 必须等于 requests
              cpu: "500m"      # 必须等于 requests

      特点

      资源保证:Pod 获得请求的全部资源,不会被其他 Pod 抢占
      最高优先级:资源不足时最后被驱逐
      性能稳定:资源使用可预测,适合关键业务
      OOM 保护:不会因为节点内存压力被 Kill(除非超过自己的 limit)

      适用场景

      • 数据库(MySQL, PostgreSQL, Redis)
      • 消息队列(Kafka, RabbitMQ)
      • 核心业务应用
      • 有状态服务

      2️⃣ Burstable (突发型)

      定义条件(满足以下任一条件)

      • Pod 中至少有一个容器设置了 requestslimits
      • requestslimits 不相等
      • 部分容器设置了资源限制,部分没有

      YAML 示例

      场景 1:只设置 requests

      apiVersion: v1
      kind: Pod
      metadata:
        name: burstable-pod-1
      spec:
        containers:
        - name: app
          image: nginx
          resources:
            requests:
              memory: "100Mi"
              cpu: "200m"
            # 没有设置 limits,可以使用超过 requests 的资源

      场景 2:requests < limits

      apiVersion: v1
      kind: Pod
      metadata:
        name: burstable-pod-2
      spec:
        containers:
        - name: app
          image: nginx
          resources:
            requests:
              memory: "100Mi"
              cpu: "200m"
            limits:
              memory: "500Mi"  # 允许突发到 500Mi
              cpu: "1000m"     # 允许突发到 1 核

      场景 3:混合配置

      apiVersion: v1
      kind: Pod
      metadata:
        name: burstable-pod-3
      spec:
        containers:
        - name: app1
          image: nginx
          resources:
            requests:
              memory: "100Mi"
            limits:
              memory: "200Mi"
        - name: app2
          image: busybox
          resources:
            requests:
              cpu: "100m"
            # 只设置 CPU,没有内存限制

      特点

      弹性使用:可以使用超过 requests 的资源(burst)
      ⚠️ 中等优先级:资源不足时,在 BestEffort 之后被驱逐
      ⚠️ 可能被限流:超过 limits 会被限制(CPU)或 Kill(内存)
      成本优化:平衡资源保证和利用率

      适用场景

      • Web 应用(流量有波峰波谷)
      • 定时任务
      • 批处理作业
      • 微服务(大部分场景)

      3️⃣ BestEffort (尽力而为型)

      定义条件

      • Pod 中所有容器没有设置 requestslimits

      YAML 示例

      apiVersion: v1
      kind: Pod
      metadata:
        name: besteffort-pod
      spec:
        containers:
        - name: app
          image: nginx
          # 完全没有 resources 配置
        - name: sidecar
          image: busybox
          # 也没有 resources 配置

      特点

      无资源保证:能用多少资源完全看节点剩余
      最低优先级:资源不足时第一个被驱逐
      性能不稳定:可能被其他 Pod 挤占资源
      灵活性高:可以充分利用节点空闲资源

      适用场景

      • 开发测试环境
      • 非关键后台任务
      • 日志收集(可以容忍中断)
      • 临时性工作负载

      🔍 QoS 等级判定流程图

      开始
        │
        ├─→ 所有容器都没设置 requests/limits?
        │   └─→ 是 → BestEffort
        │
        ├─→ 所有容器的 requests == limits (CPU和内存)?
        │   └─→ 是 → Guaranteed
        │
        └─→ 其他情况 → Burstable

      🚨 资源不足时的驱逐顺序

      当节点资源不足(如内存压力)时,Kubelet 按以下顺序驱逐 Pod:

      驱逐优先级(从高到低):
      
      1. BestEffort Pod
         └─→ 超出 requests 最多的先被驱逐
      
      2. Burstable Pod
         └─→ 按内存使用量排序
         └─→ 超出 requests 越多,越先被驱逐
      
      3. Guaranteed Pod (最后才驱逐)
         └─→ 只有在没有其他选择时才驱逐

      实际驱逐示例

      # 节点内存不足场景:
      节点总内存: 8GB
      已用内存: 7.8GB (达到驱逐阈值)
      
      Pod 列表:
      - Pod A (BestEffort): 使用 1GB 内存 → 第一个被驱逐 ❌
      - Pod B (Burstable):  requests=200Mi, 使用 500Mi → 第二个 ❌
      - Pod C (Burstable):  requests=500Mi, 使用 600Mi → 第三个 ❌
      - Pod D (Guaranteed): requests=limits=1GB, 使用 1GB → 保留 ✅

      📝 查看 Pod 的 QoS 等级

      方法 1:使用 kubectl describe

      kubectl describe pod <pod-name>
      
      # 输出中会显示:
      # QoS Class:       Burstable

      方法 2:使用 kubectl get

      # 查看所有 Pod 的 QoS
      kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass
      
      # 输出:
      # NAME              QOS
      # nginx-guaranteed  Guaranteed
      # app-burstable     Burstable
      # test-besteffort   BestEffort

      方法 3:使用 YAML 输出

      kubectl get pod <pod-name> -o yaml | grep qosClass
      
      # 输出:
      # qosClass: Burstable

      🎨 QoS 配置最佳实践

      生产环境推荐配置

      关键业务 - Guaranteed

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: critical-app
      spec:
        replicas: 3
        template:
          spec:
            containers:
            - name: app
              image: myapp:v1
              resources:
                requests:
                  memory: "2Gi"
                  cpu: "1000m"
                limits:
                  memory: "2Gi"      # requests == limits
                  cpu: "1000m"

      一般业务 - Burstable

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: web-app
      spec:
        replicas: 5
        template:
          spec:
            containers:
            - name: web
              image: nginx:latest
              resources:
                requests:
                  memory: "256Mi"    # 保证最低资源
                  cpu: "200m"
                limits:
                  memory: "512Mi"    # 允许突发到 2 倍
                  cpu: "500m"

      后台任务 - BestEffort 或 Burstable

      apiVersion: batch/v1
      kind: CronJob
      metadata:
        name: cleanup-job
      spec:
        schedule: "0 2 * * *"
        jobTemplate:
          spec:
            template:
              spec:
                containers:
                - name: cleanup
                  image: cleanup:v1
                  resources:
                    requests:
                      memory: "128Mi"
                      cpu: "100m"
                    # 不设置 limits,允许使用空闲资源

      🔧 QoS 与资源限制的关系

      CPU 限制行为

      resources:
        requests:
          cpu: "500m"    # 保证至少 0.5 核
        limits:
          cpu: "1000m"   # 最多使用 1 核
      • requests:节点调度的依据,保证的资源
      • limits:硬限制,超过会被限流(throttle),但不会被 Kill
      • 超过 limits 时,进程会被 CPU throttle,导致性能下降

      内存限制行为

      resources:
        requests:
          memory: "256Mi"  # 保证至少 256Mi
        limits:
          memory: "512Mi"  # 最多使用 512Mi
      • requests:调度保证,但可以使用更多
      • limits:硬限制,超过会触发 OOM Kill 💀
      • Pod 会被标记为 OOMKilled 并重启

      🛠️ 常见问题

      Q1: 为什么我的 Pod 总是被驱逐?

      # 检查 QoS 等级
      kubectl get pod <pod-name> -o yaml | grep qosClass
      
      # 如果是 BestEffort 或 Burstable,建议:
      # 1. 设置合理的 requests
      # 2. 考虑升级到 Guaranteed(关键服务)
      # 3. 增加节点资源

      Q2: 如何为所有 Pod 设置默认资源限制?

      # 使用 LimitRange
      apiVersion: v1
      kind: LimitRange
      metadata:
        name: default-limits
        namespace: default
      spec:
        limits:
        - default:              # 默认 limits
            cpu: "500m"
            memory: "512Mi"
          defaultRequest:       # 默认 requests
            cpu: "100m"
            memory: "128Mi"
          type: Container

      Q3: Guaranteed Pod 也会被驱逐吗?

      会! 但只在以下情况:

      • 使用超过自己的 limits(OOM Kill)
      • 节点完全不可用(如节点宕机)
      • 手动删除 Pod
      • DaemonSet 或系统级 Pod 需要资源

      Q4: 如何监控 QoS 相关的问题?

      # 查看节点资源压力
      kubectl describe node <node-name> | grep -A 5 "Conditions:"
      
      # 查看被驱逐的 Pod
      kubectl get events --field-selector reason=Evicted
      
      # 查看 OOM 事件
      kubectl get events --field-selector reason=OOMKilling

      📊 QoS 等级对比表

      维度GuaranteedBurstableBestEffort
      配置要求requests=limitsrequests≠limits 或部分配置无配置
      资源保证✅ 完全保证⚠️ 部分保证❌ 无保证
      驱逐优先级最低(最后驱逐)中等最高(第一个驱逐)
      性能稳定性⭐⭐⭐⭐⭐⭐⭐⭐
      资源利用率低(固定资源)高(可突发)最高(充分利用)
      成本
      适用场景关键业务一般业务测试/临时任务

      🎯 选型建议

      使用 Guaranteed 的场景

      • 🗄️ 数据库(MySQL, MongoDB, Cassandra)
      • 📨 消息队列(Kafka, RabbitMQ)
      • 🔐 认证服务
      • 💰 支付系统
      • 📊 实时数据处理

      使用 Burstable 的场景

      • 🌐 Web 应用(80% 的场景)
      • 🔄 API 服务
      • 🎨 前端应用
      • 📦 微服务
      • ⚙️ 后台处理

      使用 BestEffort 的场景

      • 🧪 开发测试
      • 📝 日志收集(可容忍中断)
      • 🔍 数据探索
      • 🛠️ 一次性脚本

      💡 关键要点总结

      1. QoS 是自动分配的,不能手动指定,由资源配置决定
      2. Guaranteed ≠ 不会被驱逐,只是优先级最低
      3. 生产环境建议至少使用 Burstable,避免 BestEffort
      4. requests 影响调度,limits 影响运行时限制
      5. 内存超限会 OOM,CPU 超限会限流
      6. 使用 LimitRange 强制资源限制,避免 BestEffort Pod
      Mar 7, 2024

      Scheduler

      Kubernetes 调度器(kube-scheduler) 是整个系统中非常关键的组件,它负责决定 哪个 Pod 应该运行在哪个 Node 上

      下面我会分层、逐步详细说明 K8s 调度流程(以 v1.28+ 为例),并解释背后机制。


      🌐 整体架构概览

      Kubernetes 调度器主要完成以下职责:

      1. 监听待调度的 Pod(即 spec.nodeName 为空的 Pod)
      2. 为 Pod 选择最合适的 Node
      3. 将绑定结果写回到 apiserver

      🧩 一、调度总体流程

      Kubernetes 调度流程主要分为三个阶段:

      [Pending Pod] --> [Scheduling Queue] 
           ↓
       [PreFilter] → [Filter] → [PostFilter] → [Score] → [Reserve] → [Permit] → [Bind]

      1️⃣ 调度入口:监听未绑定的 Pod

      • Scheduler 通过 informer 监听所有 Pod 资源。
      • 当发现 Pod 没有 spec.nodeName 时,认为它是待调度的。
      • Pod 被放入 调度队列(SchedulingQueue) 中。

      🧮 二、调度核心阶段详解

      🧩 1. PreFilter 阶段

      在调度之前,对 Pod 进行一些准备性检查,例如:

      • 解析 Pod 所需的资源。
      • 检查 PVC、Affinity、Taint/Toleration 是否合理。
      • 计算调度所需的 topology spread 信息。

      🧠 类似于“预处理”,提前准备好过滤阶段要用的数据。


      🧩 2. Filter 阶段(Predicates)

      Scheduler 遍历所有可调度的 Node,筛选出满足条件的节点。

      常见的过滤插件包括:

      插件作用
      NodeUnschedulable过滤掉被标记 unschedulable 的节点
      NodeName如果 Pod 指定了 nodeName,只匹配该节点
      TaintToleration检查 taint / toleration 是否匹配
      NodeAffinity / PodAffinity检查亲和性/反亲和性
      NodeResourcesFit检查 CPU/Memory 等资源是否够用
      VolumeBinding检查 Pod 使用的 PVC 是否能在节点挂载

      🔎 输出结果:

      得到一个候选节点列表(通常是几十个或几百个)。


      🧩 3. PostFilter 阶段

      • 若没有节点符合条件(即调度失败),进入 抢占逻辑(Preemption)
      • 调度器会尝试在某些节点上“抢占”低优先级的 Pod,以便高优先级 Pod 能调度成功。

      🧩 4. Score 阶段(优选)

      对剩余候选节点进行打分。 每个插件给节点打分(0–100),然后汇总加权。

      常见的评分插件:

      插件作用
      LeastAllocated资源使用最少的节点得分高
      BalancedAllocationCPU 和内存使用更均衡的节点得分高
      NodeAffinity符合 affinity 的节点加分
      ImageLocality本地已缓存镜像的节点加分
      InterPodAffinity满足 Pod 间亲和性需求的节点加分

      这些分数会经过 NormalizeScore 标准化到统一范围后求和。

      最终输出:

      最优节点(Score最高)


      🧩 5. Reserve 阶段

      暂时在该节点上 预留资源(在调度器内部缓存中标记),防止并发调度冲突。

      如果后续失败,会执行 Unreserve 回滚。


      🧩 6. Permit 阶段

      某些插件可在绑定前再进行校验或等待,例如:

      • PodGroup(批调度)
      • Scheduler Framework 自定义策略

      可能返回:

      • Success → 继续绑定
      • Wait → 等待事件
      • Reject → 放弃调度

      🧩 7. Bind 阶段

      最终由 Bind 插件 调用 API 将 Pod 绑定到 Node:

      spec:
        nodeName: node-123

      绑定成功后,kubelet 监听到该 Pod,会拉起容器。


      ⚙️ 三、调度框架(Scheduler Framework)

      K8s 1.19+ 后,调度器是通过 插件化框架 实现的。 每个阶段都有对应的插件点:

      阶段插件接口示例插件
      PreFilterPreFilterPluginPodTopologySpread
      FilterFilterPluginNodeAffinity
      ScoreScorePluginLeastAllocated
      ReserveReservePluginVolumeBinding
      BindBindPluginDefaultBinder

      你可以通过写一个自定义调度插件(Go)扩展调度逻辑。


      🧭 四、调度失败的情况

      常见调度失败的原因:

      原因表现
      所有节点资源不足Pod 一直 Pending
      亲和性限制太严格Pod 无法找到符合要求的节点
      PVC 无法绑定VolumeBinding 阶段失败
      节点被打 taint没有 toleration
      镜像拉取失败Pod 已绑定但容器起不来(kubelet问题)

      🧠 五、总结

      阶段目的关键点
      SchedulingQueue缓冲待调度PodFIFO + 优先级调度
      PreFilter准备数据校验Pod需求
      Filter过滤节点资源与约束
      Score打分选优平衡与局部性
      Reserve预留资源防并发冲突
      Bind绑定Node调度结果落地

      Mar 7, 2024

      服务发现

      最常见的说法是 “两种核心机制”,但这指的是服务发现的两种基本模式,而不是具体的实现方式。


      维度一:两种核心模式

      这是从服务发现的基本原理上划分的。

      1. 基于客户端服务发现

        • 工作原理:客户端(服务消费者)通过查询一个中心化的服务注册中心(如 Consul、Eureka、Zookeeper)来获取所有可用服务实例的列表(通常是 IP 和端口),然后自己选择一个实例并直接向其发起请求。
        • 类比:就像你去餐厅吃饭,先看门口的电子菜单(服务注册中心)了解所有菜品和价格,然后自己决定点什么,再告诉服务员。
        • 特点:客户端需要内置服务发现逻辑,与服务注册中心耦合。这种方式更灵活,但增加了客户端的复杂性。
      2. 基于服务端服务发现

        • 工作原理:客户端不关心具体的服务实例,它只需要向一个固定的访问端点(通常是 Load Balancer 或 Proxy,如 Kubernetes Service)发起请求。这个端点负责去服务注册中心查询可用实例,并进行负载均衡,将请求转发给其中一个。
        • 类比:就像你去餐厅直接告诉服务员“来份招牌菜”,服务员(负载均衡器)帮你和后厨(服务实例)沟通,最后把菜端给你。
        • 特点:客户端无需知道服务发现的具体细节,简化了客户端。这是 Kubernetes 默认采用的方式

      维度二:Kubernetes 中具体的实现方式

      在 Kubernetes 内部,我们通常讨论以下几种具体的服务发现实现手段,它们共同构成了 Kubernetes 强大的服务发现能力。

      1. 环境变量

      当 Pod 被调度到某个节点上时,kubelet 会为当前集群中存在的每个 Service 添加一组环境变量到该 Pod 中。

      • 格式{SVCNAME}_SERVICE_HOST{SVCNAME}_SERVICE_PORT
      • 例子:一个名为 redis-master 的 Service 会生成 REDIS_MASTER_SERVICE_HOST=10.0.0.11REDIS_MASTER_SERVICE_PORT=6379 这样的环境变量。
      • 局限性:环境变量必须在 Pod 创建之前就存在。后创建的 Service 无法将环境变量注入到已运行的 Pod 中。因此,这通常作为辅助手段

      2. DNS(最核心、最推荐的方式)

      这是 Kubernetes 最主要和最优雅的服务发现方式。

      • 工作原理:Kubernetes 集群内置了一个 DNS 服务器(通常是 CoreDNS)。当你创建一个 Service 时,Kubernetes 会自动为这个 Service 注册一个 DNS 记录。
      • DNS 记录格式
        • 同一命名空间<service-name>.<namespace>.svc.cluster.local -> 指向 Service 的 Cluster IP。
          • 在同一个命名空间内,你可以直接使用 <service-name> 来访问服务。例如,前端 Pod 访问后端服务,只需使用 http://backend-service
        • 不同命名空间:需要使用全限定域名,例如 backend-service.production.svc.cluster.local
      • 优点:行为符合标准,应用无需修改代码,直接使用域名即可访问其他服务。

      3. Kubernetes Service

      Service 资源对象本身就是服务发现的载体。它提供了一个稳定的访问端点(VIP 或 DNS 名称),背后对应一组动态变化的 Pod。

      • ClusterIP:默认类型,提供一个集群内部的虚拟 IP,只能从集群内部访问。结合 DNS 使用,是服务间通信的基石。
      • NodePort:在 ClusterIP 基础上,在每个节点上暴露一个静态端口。可以从集群外部通过 <NodeIP>:<NodePort> 访问服务。
      • LoadBalancer:在 NodePort 基础上,利用云服务商提供的负载均衡器,将一个外部 IP 地址暴露给 Service。是向公网暴露服务的主要方式。
      • Headless Service:一种特殊的 Service,当你不需要负载均衡和单个 Service IP 时,可以通过设置 clusterIP: None 来创建。DNS 查询会返回该 Service 后端所有 Pod 的 IP 地址列表,而不是一个 VIP。这常用于有状态应用(如 Kafka、MySQL 集群)的自定义负载均衡或状态同步。

      4. Ingress

      虽然 Ingress 主要被用作 HTTP/HTTPS 路由规则的七层代理,但它也是一种高级的服务发现形式。

      • 它通过规则将外部流量路由到集群内部相应的 Service。
      • 客户端(外部用户)通过访问 Ingress Controller 的地址来发现和访问后端服务。

      总结与对比

      方式原理适用场景特点
      环境变量将 Service 信息注入 Pod 环境变量旧式应用,辅助手段简单,但有局限性(需先于 Pod 创建)
      DNS为 Service 自动注册域名服务间通信的标准方式推荐,符合惯例,无需代码改造
      Service提供稳定的虚拟 IP 或 DNS 名称服务暴露和负载均衡的核心抽象Kubernetes 服务发现的基石
      Ingress七层 HTTP 路由对外暴露 Web 服务,基于域名和路径路由更高级的 API 网关模式

      结论:

      • 从模式上讲,Kubernetes 主要采用基于服务端的服务发现模式。
      • 从具体实现上讲,Kubernetes 的服务发现是一个以 DNS 为核心、以 Service 为基石的完整体系,并辅以环境变量和 Ingress 等机制。

      所以,当有人问“K8s服务发现有哪些方式”时,最准确的回答是:主要有基于 DNS 和 环境变量这两种内部发现机制,而它们都依赖于 Service 这个核心抽象。同时,Ingress 提供了外部到内部的服务发现和路由能力。

      Mar 7, 2024

      Service VS Endpoint

      Service 和 Endpoint/EndpointSlice 在 Kubernetes 中有明确的功能分工,它们共同构成了服务发现和负载均衡的基础。以下是详细的区别分析:

      一、核心功能定位

      Service - 抽象服务层

      apiVersion: v1
      kind: Service
      metadata:
        name: web-service
      spec:
        selector:
          app: web-server
        ports:
          - protocol: TCP
            port: 80           # 服务端口
            targetPort: 8080   # 后端 Pod 端口
        type: ClusterIP        # 服务类型

      Service 的核心功能:

      • 服务抽象:提供稳定的虚拟 IP 和 DNS 名称
      • 访问入口:定义客户端如何访问服务
      • 负载均衡策略:指定流量分发方式
      • 服务类型:ClusterIP、NodePort、LoadBalancer、ExternalName

      Endpoint/EndpointSlice - 后端实现层

      apiVersion: v1
      kind: Endpoints
      metadata:
        name: web-service      # 必须与 Service 同名
      subsets:
        - addresses:
          - ip: 10.244.1.5
            targetRef:
              kind: Pod
              name: web-pod-1
          - ip: 10.244.1.6
            targetRef:
              kind: Pod  
              name: web-pod-2
          ports:
          - port: 8080
            protocol: TCP

      Endpoints 的核心功能:

      • 后端发现:记录实际可用的 Pod IP 地址
      • 健康状态:只包含通过就绪探针检查的 Pod
      • 动态更新:实时反映后端 Pod 的变化
      • 端口映射:维护 Service port 到 Pod port 的映射

      二、详细功能对比

      功能特性ServiceEndpoint/EndpointSlice
      抽象级别逻辑抽象层物理实现层
      数据内容虚拟IP、端口、选择器实际Pod IP地址、端口
      稳定性稳定的VIP和DNS动态变化的IP列表
      创建方式手动定义自动生成(或手动)
      更新频率低频变更高频动态更新
      DNS解析返回Service IP不直接参与DNS
      负载均衡定义策略提供后端目标

      三、实际工作流程

      1. 服务访问流程

      客户端请求 → Service VIP → kube-proxy → Endpoints → 实际 Pod
          ↓           ↓           ↓           ↓           ↓
        DNS解析     虚拟IP      iptables/   后端IP列表   具体容器
                   10.96.x.x   IPVS规则    10.244.x.x   应用服务

      2. 数据流向示例

      # 客户端访问
      curl http://web-service.default.svc.cluster.local
      
      # DNS 解析返回 Service IP
      nslookup web-service.default.svc.cluster.local
      # 返回: 10.96.123.456
      
      # kube-proxy 根据 Endpoints 配置转发
      iptables -t nat -L KUBE-SERVICES | grep 10.96.123.456
      # 转发到: 10.244.1.5:8080, 10.244.1.6:8080

      四、使用场景差异

      Service 的使用场景

      # 1. 内部服务访问
      apiVersion: v1
      kind: Service
      metadata:
        name: internal-api
      spec:
        type: ClusterIP
        selector:
          app: api-server
        ports:
          - port: 8080
      
      # 2. 外部访问
      apiVersion: v1
      kind: Service  
      metadata:
        name: external-web
      spec:
        type: LoadBalancer
        selector:
          app: web-frontend
        ports:
          - port: 80
            nodePort: 30080
      
      # 3. 外部服务代理
      apiVersion: v1
      kind: Service
      metadata:
        name: external-database
      spec:
        type: ExternalName
        externalName: database.example.com

      Endpoints 的使用场景

      # 1. 自动后端管理(默认)
      # Kubernetes 自动维护匹配 Pod 的 Endpoints
      
      # 2. 外部服务集成
      apiVersion: v1
      kind: Service
      metadata:
        name: legacy-system
      spec:
        ports:
          - port: 3306
      ---
      apiVersion: v1
      kind: Endpoints
      metadata:
        name: legacy-system
      subsets:
        - addresses:
          - ip: 192.168.1.100  # 外部数据库
          ports:
          - port: 3306
      
      # 3. 多端口复杂服务
      apiVersion: v1
      kind: Service
      metadata:
        name: complex-app
      spec:
        ports:
        - name: http
          port: 80
        - name: https
          port: 443
        - name: metrics
          port: 9090

      五、配置和管理差异

      Service 配置重点

      apiVersion: v1
      kind: Service
      metadata:
        name: optimized-service
        annotations:
          # 负载均衡配置
          service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
          # 会话保持
          service.kubernetes.io/aws-load-balancer-backend-protocol: "http"
      spec:
        type: LoadBalancer
        selector:
          app: optimized-app
        sessionAffinity: ClientIP
        sessionAffinityConfig:
          clientIP:
            timeoutSeconds: 10800
        ports:
        - name: http
          port: 80
          targetPort: 8080
        # 流量策略(仅对外部流量)
        externalTrafficPolicy: Local

      Endpoints 配置重点

      apiVersion: v1
      kind: Endpoints
      metadata:
        name: custom-endpoints
        labels:
          # 用于网络策略选择
          environment: production
      subsets:
      - addresses:
        - ip: 10.244.1.10
          nodeName: worker-1
          targetRef:
            kind: Pod
            name: app-pod-1
            namespace: production
        - ip: 10.244.1.11
          nodeName: worker-2  
          targetRef:
            kind: Pod
            name: app-pod-2
            namespace: production
        # 多端口定义
        ports:
        - name: http
          port: 8080
          protocol: TCP
        - name: metrics
          port: 9090
          protocol: TCP
        - name: health
          port: 8081
          protocol: TCP

      六、监控和调试差异

      Service 监控重点

      # 检查 Service 状态
      kubectl get services
      kubectl describe service web-service
      
      # Service 相关指标
      kubectl top services  # 如果支持
      kubectl get --raw /api/v1/namespaces/default/services/web-service/proxy/metrics
      
      # DNS 解析测试
      kubectl run test-$RANDOM --image=busybox --rm -it -- nslookup web-service

      Endpoints 监控重点

      # 检查后端可用性
      kubectl get endpoints
      kubectl describe endpoints web-service
      
      # 验证后端 Pod 状态
      kubectl get pods -l app=web-server -o wide
      
      # 检查就绪探针
      kubectl get pods -l app=web-server -o jsonpath='{.items[*].spec.containers[*].readinessProbe}'
      
      # 直接测试后端连通性
      kubectl run test-$RANDOM --image=busybox --rm -it -- 
      # 在容器内: telnet 10.244.1.5 8080

      七、性能考虑差异

      Service 性能优化

      apiVersion: v1
      kind: Service
      metadata:
        name: high-performance
        annotations:
          # 使用 IPVS 模式提高性能
          service.kubernetes.io/service.beta.kubernetes.io/ipvs-scheduler: "wrr"
      spec:
        type: ClusterIP
        clusterIP: None  # Headless Service,减少一层转发
        selector:
          app: high-perf-app

      Endpoints 性能优化

      # 使用 EndpointSlice 提高大规模集群性能
      apiVersion: discovery.k8s.io/v1
      kind: EndpointSlice
      metadata:
        name: web-service-abc123
        labels:
          kubernetes.io/service-name: web-service
      addressType: IPv4
      ports:
      - name: http
        protocol: TCP
        port: 8080
      endpoints:
      - addresses:
        - "10.244.1.5"
        conditions:
          ready: true
        # 拓扑感知,优化路由
        zone: us-west-2a
        hints:
          forZones:
          - name: us-west-2a

      八、总结

      维度ServiceEndpoint/EndpointSlice
      角色服务门面后端实现
      稳定性高(VIP/DNS稳定)低(IP动态变化)
      关注点如何访问谁能被访问
      配置频率低频高频自动更新
      网络层级L4 负载均衡后端目标发现
      扩展性通过类型扩展通过EndpointSlice扩展

      简单比喻:

      • Service 就像餐厅的接待台和菜单 - 提供统一的入口和访问方式
      • Endpoints 就像后厨的厨师列表 - 记录实际提供服务的人员和位置

      两者协同工作,Service 定义"什么服务可用",Endpoints 定义"谁可以提供这个服务",共同实现了 Kubernetes 强大的服务发现和负载均衡能力。

      Mar 7, 2024

      StatefulSet

      StatefulSet 如何具体解决有状态应用的挑战


      StatefulSet 的四大核心机制

      StatefulSet 通过一系列精心设计的机制,为有状态应用提供了稳定性和可预测性。

      1. 稳定的网络标识

      解决的问题:有状态应用(如数据库节点)需要稳定的主机名来相互发现和通信,不能使用随机名称。

      StatefulSet 的实现

      • 固定的 Pod 名称:Pod 名称遵循固定模式:<statefulset-name>-<ordinal-index>
        • 例如:redis-cluster-0redis-cluster-1redis-cluster-2
      • 稳定的 DNS 记录:每个 Pod 都会自动获得一个唯一的、稳定的 DNS 记录:
        • 格式<pod-name>.<svc-name>.<namespace>.svc.cluster.local
        • 例子redis-cluster-0.redis-service.default.svc.cluster.local

      应对场景

      • 在 Redis 集群中,redis-cluster-0 可以告诉 redis-cluster-1:“我的地址是 redis-cluster-0.redis-service",这个地址在 Pod 的一生中都不会改变,即使它被重新调度到其他节点。

      2. 有序的部署与管理

      解决的问题:像 Zookeeper、Etcd 这样的集群化应用,节点需要按顺序启动和加入集群,主从数据库也需要先启动主节点。

      StatefulSet 的实现

      • 有序部署:当创建 StatefulSet 时,Pod 严格按照索引顺序(0, 1, 2…)依次创建。必须等 Pod-0 完全就绪(Ready)后,才会创建 Pod-1
      • 有序扩缩容
        • 扩容:按顺序创建新 Pod(如从 3 个扩展到 5 个,会先创建 pod-3,再 pod-4)。
        • 缩容:按逆序终止 Pod(从 pod-4 开始,然后是 pod-3)。
      • 有序滚动更新:同样遵循逆序策略,确保在更新过程中大部分节点保持可用。

      应对场景

      • 部署 MySQL 主从集群时,StatefulSet 会确保 mysql-0(主节点)先启动并初始化完成,然后才启动 mysql-1(从节点),从节点在启动时就能正确连接到主节点进行数据同步。

      3. 稳定的持久化存储

      这是 StatefulSet 最核心的特性!

      解决的问题:有状态应用的数据必须持久化,并且当 Pod 发生故障或被调度到新节点时,必须能够重新挂载到它自己的那部分数据

      StatefulSet 的实现

      • Volume Claim Template:在 StatefulSet 的 YAML 中,你可以定义一个 volumeClaimTemplate(存储卷申请模板)。
      • 专属的 PVC:StatefulSet 会为每个 Pod 实例根据这个模板创建一个独立的、专用的 PersistentVolumeClaim (PVC)。
        • mysql-0 -> pvc-name-mysql-0
        • mysql-1 -> pvc-name-mysql-1
        • mysql-2 -> pvc-name-mysql-2

      工作流程

      1. 当你创建名为 mysql、副本数为 3 的 StatefulSet 时,K8s 会:
        • 创建 Pod mysql-0,并同时创建 PVC data-mysql-0,然后将它们绑定。
        • mysql-0 就绪后,创建 Pod mysql-1 和 PVC data-mysql-1,然后绑定。
        • 以此类推。
      2. 如果节点故障导致 mysql-1 被删除,K8s 调度器会在其他健康节点上重新创建一个同名的 Pod mysql-1
      3. 这个新 Pod mysql-1 会自动挂载到之前为它创建的、存有它专属数据的 PVC data-mysql-1 上。
      4. 这样,Pod 虽然"漂移"了,但数据依然跟随,应用可以无缝恢复。

      应对场景

      • 对于数据库,每个 Pod 都有自己独立的数据目录。mysql-0 的数据永远不会和 mysql-1 的数据混淆。这为数据分片(Sharding)和主从复制提供了基础。

      4. 稳定的启动顺序与唯一身份

      解决的问题:应用启动脚本或配置可能需要知道当前实例的索引号(如,判断自己是否是第一个节点,从而决定是否要初始化集群)。

      StatefulSet 的实现

      • Pod 的序号(0, 1, 2...)就是其稳定的、唯一的身份标识。
      • 这个序号可以通过环境变量注入到 Pod 的容器中,供应用程序使用。

      总结

      StatefulSet 通过「稳定的网络标识 + 有序的部署管理 + 稳定的专属存储」这三驾马车,将有状态应用在动态的、易变的容器环境中"锚定”,使其获得了类似物理机或虚拟机的稳定运行特性。

      它并没有让有状态应用本身变得简单,而是为这些应用的复杂需求(如集群化、数据持久化)提供了一个可以在 K8s 上稳定运行的框架和平台。对于运维人员来说,这意味着可以用声明式的方式像管理无状态应用一样,去管理数据库、消息队列等复杂的有状态服务。

      Mar 7, 2024

      StatefulSet 2

      答案是:

      StatefulSet 不使用 ReplicaSet 来创建 Pod。 ✅ StatefulSet Controller 直接创建并管理 Pod 对象,而且每个 Pod 都是有序的、具名的、不可替代的。

      接下来我会详细讲清楚 StatefulSet 与 Deployment 的架构差异、实现机制、核心控制循环,让你能在实现 Operator 或高可靠应用时真正理解它的行为。


      🧩 一、StatefulSet 与 Deployment 的根本区别

      项目DeploymentStatefulSet
      控制下层对象ReplicaSet → Pod直接管理 Pod
      Pod 命名随机固定命名(有序索引)
      Pod 身份可替换唯一、持久身份(Stable Identity)
      更新策略滚动更新(无序)有序更新(从 0 开始逐个)
      存储通常无状态绑定 PVC,数据与 Pod 一一对应
      常见场景Web 服务、API、Job数据库、Zookeeper、Kafka、Etcd 等

      ⚙️ 二、StatefulSet 控制器工作原理

      StatefulSet Controller 运行在 kube-controller-manager 中。 它同样是一个典型的 Controller + Informer + WorkQueue + Reconcile Loop 架构。

      但是它的 核心循环逻辑 与 Deployment/ReplicaSet 明显不同:

      1️⃣ Reconcile 流程概览

      伪代码如下:

      syncStatefulSet(set):
          pods := listPodsMatching(set.selector)
          sortPodsByOrdinal(pods)
      
          for i in 0..set.spec.replicas-1:
              ensurePodExists(set, ordinal=i)
              ensurePodPVCExists(set, ordinal=i)
              if updateRequired(set, pod[i]):
                  deletePodAndWaitForReplacement(set, ordinal=i)

      也就是说,StatefulSet controller 自己直接创建 Pod 和 PVC,并且顺序控制整个过程。


      2️⃣ Pod 命名与序号机制

      StatefulSet 会给每个 Pod 一个固定的命名模式:

      <prefix>-<ordinal>

      例如:

      web-0
      web-1
      web-2

      这些名字不是随机生成的 UID,而是根据序号确定的。 这让 Pod 可以拥有稳定的网络标识

      • Pod DNS:<podname>.<headless-service-name>.<namespace>.svc.cluster.local

      • 例如:

        web-0.web.default.svc.cluster.local
        web-1.web.default.svc.cluster.local

      这对 Zookeeper、MySQL 集群等“节点必须有固定身份”的应用至关重要。


      3️⃣ 存储(PVC)绑定机制

      每个 StatefulSet Pod 都可以声明一个 volumeClaimTemplate

      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          accessModes: ["ReadWriteOnce"]
          resources:
            requests:
              storage: 10Gi

      Controller 会为每个 Pod 创建一个独立的 PVC:

      data-web-0
      data-web-1
      data-web-2

      即使 Pod 被删除,这个 PVC 也不会被清理(除非手动删除), 这就实现了状态持久化


      4️⃣ Pod 创建顺序与滚动更新顺序

      StatefulSet 有严格的有序逻辑:

      创建顺序

      • 先创建 web-0
      • web-0 变成 Running + Ready
      • 再创建 web-1
      • 依次类推。

      删除顺序

      • 先删除最高序号 Pod;
      • 依次向下。

      更新顺序(滚动更新)

      • 先更新最高序号 Pod;

      • 等该 Pod Ready 后,再更新下一个;

      • 可配置 podManagementPolicy

        • OrderedReady(默认,有序)
        • Parallel(并行)

      5️⃣ StatefulSet 更新控制逻辑

      当你修改 StatefulSet 的模板(例如更换镜像),控制器会:

      1. 比较 spec.template hash;
      2. 找出需要更新的 Pod;
      3. 按序更新;
      4. 每个 Pod 更新完毕、Ready 后,再进行下一个。

      伪代码示例:

      for i = replicas-1 downTo 0:
          if pod[i].template != set.template:
              delete(pod[i])
              waitUntilNewPodReady(i)

      📦 三、StatefulSet 与 PVC 的生命周期

      对象何时创建何时删除
      Pod当 StatefulSet 第一次启动或扩容时缩容时(最高序号先删)
      PVCPod 第一次被创建时不会自动删除(除非使用 StorageClass Retain 策略外的设置)

      这意味着:

      StatefulSet 的 数据持久性是通过 PVC 与序号绑定实现的

      例如:

      data-web-0 → 永远属于 web-0
      data-web-1 → 永远属于 web-1

      🔁 四、StatefulSet 的控制链路(事件流)

      步骤执行者说明
      用户kubectl apply 创建 StatefulSet
      API Server写入 etcd
      StatefulSet Controller检测 StatefulSet 对象变化
      Controller创建第一个 Pod + PVC(ordinal=0)
      Kubelet调度并运行容器
      Controller检测 web-0 Ready 后,继续创建 web-1
      直到所有 replicas Ready
      更新按序滚动更新 Pod

      🧠 五、StatefulSet 为什么不复用 ReplicaSet?

      主要是因为 ReplicaSet 的特性与 StatefulSet 的设计目标相冲突:

      功能ReplicaSetStatefulSet
      Pod 匿名性✅ 所有 Pod 等价❌ 每个 Pod 唯一
      更新无序性✅ 任意替换❌ 必须有序
      PVC 管理❌ 无✅ 一一绑定
      网络身份❌ 无✅ 稳定 DNS 名称

      所以 Kubernetes 设计了专门的 StatefulSet Controller 来实现这一类“有状态副本控制”。


      💡 六、StatefulSet 小结

      特性描述
      控制对象直接管理 Pod
      Pod 命名固定命名(带序号)
      更新策略有序(OrderedReady)或并行(Parallel)
      持久化通过 PVC 一对一绑定
      典型场景数据库、分布式存储、消息队列、协调服务等
      优势稳定网络身份、持久存储、严格更新顺序
      缺点扩缩容慢,调度受限,不适合无状态应用

      🧭 七、视觉化理解(结构示意)

      StatefulSet: kafka
      │
      ├── Pod: kafka-0
      │   ├── PVC: data-kafka-0
      │   └── Hostname: kafka-0.kafka.default.svc
      │
      ├── Pod: kafka-1
      │   ├── PVC: data-kafka-1
      │   └── Hostname: kafka-1.kafka.default.svc
      │
      └── Pod: kafka-2
          ├── PVC: data-kafka-2
          └── Hostname: kafka-2.kafka.default.svc
      Mar 7, 2024

      Talk between 2 pods in different nodes

      好的,这是一个非常核心的 Kubernetes 网络问题。不同 Node 上的 Pod 之间的通信过程,清晰地展示了 Kubernetes 网络模型的核心思想:每个 Pod 都拥有一个独立的、扁平的 IP 地址空间,无论它运行在哪个节点上,Pod 之间都可以直接通过这个 IP 进行通信,而无需使用 NAT

      这个过程的实现完全依赖于容器网络接口(CNI)插件,如 Calico、Flannel、Weave Net 等。下面我们以最经典的 Flannel (VXLAN 模式)Calico (BGP 模式) 为例,来阐述这个通信过程。


      核心原则

      1. Pod IP 可达性:Kubernetes 网络模型要求,任何 Pod 的 IP 地址都能被任何其他 Pod 直接访问,无论它们是否在同一个节点上。
      2. 无 NAT:Pod 到 Pod 的通信不应该经过源地址转换(SNAT)或目的地址转换(DNAT)。Pod 看到的源 IP 和目标 IP 就是真实的 Pod IP。

      通用通信流程(抽象模型)

      假设有两个 Pod:

      • Pod A:在 Node 1 上,IP 为 10.244.1.10
      • Pod B:在 Node 2 上,IP 为 10.244.2.20

      Pod A 试图 ping Pod B 的 IP (10.244.2.20) 时,过程如下:

      1. 出站:从 Pod A 到 Node 1

      • Pod A 根据其内部路由表,将数据包从自己的网络命名空间内的 eth0 接口发出。
      • 目标 IP 是 10.244.2.20
      • Node 1 上,有一个网桥(如 cni0)充当了所有本地 Pod 的虚拟交换机。Pod A 的 eth0 通过一对 veth pair 连接到这个网桥。
      • 数据包到达网桥 cni0

      2. 路由决策:在 Node 1 上

      • Node 1内核路由表 由 CNI 插件配置。它查看数据包的目标 IP 10.244.2.20
      • 路由表规则大致如下:
        Destination     Gateway         Interface
        10.244.1.0/24   ...            cni0      # 本地 Pod 网段,走 cni0 网桥
        10.244.2.0/24   192.168.1.102  eth0      # 非本地 Pod 网段,通过网关(即 Node 2 的 IP)从物理网卡 eth0 发出
      • 路由表告诉内核,去往 10.244.2.0/24 网段的数据包,下一跳是 192.168.1.102(即 Node 2 的物理 IP),并通过 Node 1 的物理网络接口 eth0 发出。

      从这里开始,不同 CNI 插件的工作机制产生了差异。


      场景一:使用 Flannel (VXLAN 模式)

      Flannel 通过创建一个覆盖网络 来解决跨节点通信。

      1. 封装

        • 数据包(源 10.244.1.10,目标 10.244.2.20)到达 Node 1eth0 之前,会被一个特殊的虚拟网络设备 flannel.1 截获。
        • flannel.1 是一个 VXLAN 隧道端点
        • 封装flannel.1 会将整个原始数据包(作为 payload)封装在一个新的 UDP 数据包 中。
          • 外层 IP 头:源 IP 是 Node 1 的 IP (192.168.1.101),目标 IP 是 Node 2 的 IP (192.168.1.102)。
          • 外层 UDP 头:目标端口通常是 8472 (VXLAN)。
          • VXLAN 头:包含一个 VNI,用于标识不同的虚拟网络。
          • 内层原始数据包:原封不动。
      2. 物理网络传输

        • 这个封装后的 UDP 数据包通过 Node 1 的物理网络 eth0 发送出去。
        • 它经过底层物理网络(交换机、路由器)顺利到达 Node 2,因为外层 IP 是节点的真实 IP,底层网络是认识的。
      3. 解封装

        • 数据包到达 Node 2 的物理网卡 eth0
        • 内核发现这是一个发往 VXLAN 端口 (8472) 的 UDP 包,于是将其交给 Node 2 上的 flannel.1 设备处理。
        • flannel.1 设备解封装,剥掉外层 UDP 和 IP 头,露出原始的 IP 数据包(源 10.244.1.10,目标 10.244.2.20)。
      4. 入站:从 Node 2 到 Pod B

        • 解封后的原始数据包被送入 Node 2 的网络栈。
        • Node 2 的路由表查看目标 IP 10.244.2.20,发现它属于本地的 cni0 网桥管理的网段。
        • 数据包被转发到 cni0 网桥,网桥再通过 veth pair 将数据包送达 Pod Beth0 接口。

      简单比喻:Flannel 就像在两个节点之间建立了一条邮政专线。你的原始信件(Pod IP 数据包)被塞进一个标准快递信封(外层 UDP 包)里,通过公共邮政系统(物理网络)寄到对方邮局(Node 2),对方邮局再拆开快递信封,把原始信件交给收件人(Pod B)。


      场景二:使用 Calico (BGP 模式)

      Calico 通常不使用隧道,而是利用 BGP 协议纯三层路由,效率更高。

      1. 路由通告

        • Node 1Node 2 上都运行着 Calico 的 BGP 客户端 Felix 和 BGP 路由反射器 BIRD
        • Node 2 会通过 BGP 协议向网络中的其他节点(包括 Node 1)通告一条路由信息:“目标网段 10.244.2.0/24 的下一跳是我 192.168.1.102”。
        • Node 1 学习到了这条路由,并写入自己的内核路由表(就是我们之前在步骤2中看到的那条)。
      2. 直接路由

        • 数据包(源 10.244.1.10,目标 10.244.2.20)根据路由表,直接通过 Node 1 的物理网卡 eth0 发出。
        • 没有封装! 数据包保持原样,源 IP 是 10.244.1.10,目标 IP 是 10.244.2.20
        • 这个数据包被发送到 Node 2 的物理 IP (192.168.1.102)。
      3. 物理网络传输

        • 数据包经过底层物理网络。这就要求底层网络必须能够路由 Pod IP 的网段。在云环境中,这通常通过配置 VPC 路由表来实现;在物理机房,需要核心交换机学习到这些 BGP 路由或配置静态路由。
      4. 入站:从 Node 2 到 Pod B

        • 数据包到达 Node 2 的物理网卡 eth0
        • Node 2 的内核查看目标 IP 10.244.2.20,发现这个 IP 属于一个本地虚拟接口(如 caliXXX,这是 Calico 为每个 Pod 创建的),于是直接将数据包转发给该接口,最终送达 Pod B

      简单比喻:Calico 让每个节点都成为一个智能路由器。它们互相告知“哪个 Pod 网段在我这里”。当 Node 1 要发数据给 Node 2 上的 Pod 时,它就像路由器一样,根据已知的路由表,直接找到 Node 2 的地址并把数据包发过去,中间不拆包。


      总结对比

      特性Flannel (VXLAN)Calico (BGP)
      网络模型Overlay NetworkPure Layer 3
      原理隧道封装路由通告
      性能有封装/解封装开销,性能稍低无隧道开销,性能更高
      依赖对底层网络无要求,只要节点IP通即可依赖底层网络支持路由(云平台VPC或物理网络配置)
      数据包外层Node IP,内层Pod IP始终是Pod IP

      无论采用哪种方式,Kubernetes 和 CNI 插件共同协作,最终实现了一个对应用开发者透明的、扁平的 Pod 网络。开发者只需关心 Pod IP 和 Service,而无需理解底层复杂的跨节点通信机制。

      如果pod之间访问不通怎么排查?

      核心排查思路:从 Pod 内部到外部,从简单到复杂

      整个排查过程可以遵循下图所示的路径,逐步深入:

      flowchart TD
          A[Pod 之间访问不通] --> B[确认基础连通性<br>ping & telnet]
      
          B --> C{ping 是否通?}
          C -- 通 --> D[telnet 端口是否通?]
          C -- 不通 --> E[检查 NetworkPolicy<br>kubectl get networkpolicy]
      
          D -- 通 --> F[检查应用日志与配置]
          D -- 不通 --> G[检查 Service 与 Endpoints<br>kubectl describe svc]
      
          E --> H[检查 CNI 插件状态<br>kubectl get pods -n kube-system]
          
          subgraph G_ [Service排查路径]
              G --> G1[Endpoints 是否为空?]
              G1 -- 是 --> G2[检查 Pod 标签与 Selector]
              G1 -- 否 --> G3[检查 kube-proxy 与 iptables]
          end
      
          F --> Z[问题解决]
          H --> Z
          G2 --> Z
          G3 --> Z

      第一阶段:基础信息收集与初步检查

      1. 获取双方 Pod 信息

        kubectl get pods -o wide
        • 确认两个 Pod 都处于 Running 状态。
        • 记录下它们的 IP 地址所在节点
        • 确认它们不在同一个节点上(如果是,排查方法会略有不同)。
      2. 明确访问方式

        • 直接通过 Pod IP 访问? (ping <pod-ip>curl <pod-ip>:<port>)
        • 通过 Service 名称访问? (ping <service-name>curl <service-name>:<port>)
        • 这个问题决定了后续的排查方向。

      第二阶段:按访问路径深入排查

      场景一:直接通过 Pod IP 访问不通(跨节点)

      这通常是底层网络插件(CNI) 的问题。

      1. 检查 Pod 内部网络

        • 进入源 Pod,检查其网络配置:
        kubectl exec -it <source-pod> -- sh
        # 在 Pod 内部执行:
        ip addr show eth0 # 查看 IP 是否正确
        ip route # 查看路由表
        ping <destination-pod-ip> # 测试连通性
        • 如果 ping 不通,继续下一步。
      2. 检查目标 Pod 的端口监听

        • 进入目标 Pod,确认应用在正确端口上监听:
        kubectl exec -it <destination-pod> -- netstat -tulpn | grep LISTEN
        # 或者用 ss 命令
        kubectl exec -it <destination-pod> -- ss -tulpn | grep LISTEN
        • 如果这里没监听,是应用自身问题,检查应用日志和配置。
      3. 检查 NetworkPolicy(网络策略)

        • 这是 Kubernetes 的“防火墙”,很可能阻止了访问。
        kubectl get networkpolicies -A
        kubectl describe networkpolicy <policy-name> -n <namespace>
        • 查看是否有策略限制了源 Pod 或目标 Pod 的流量。特别注意 ingress 规则
      4. 检查 CNI 插件状态

        • CNI 插件(如 Calico、Flannel)的异常会导致跨节点网络瘫痪。
        kubectl get pods -n kube-system | grep -e calico -e flannel -e weave
        • 确认所有 CNI 相关的 Pod 都在运行。如果有 CrashLoopBackOff 等状态,查看其日志。
      5. 节点层面排查

        • 如果以上都正常,问题可能出现在节点网络层面。
        • 登录到源 Pod 所在节点,尝试 ping 目标 Pod IP。
        • 检查节点路由表
          # 在节点上执行
          ip route
          • 对于 Flannel,你应该能看到到其他节点 Pod 网段的路由。
          • 对于 Calico,你应该能看到到每个其他节点 Pod 网段的精确路由。
        • 检查节点防火墙:在某些环境中(如安全组、iptables 规则)可能阻止了 VXLAN(8472端口)或节点间 Pod IP 的通信。
          # 检查 iptables 规则
          sudo iptables-save | grep <pod-ip>

      场景二:通过 Service 名称访问不通

      这通常是 Kubernetes 服务发现kube-proxy 的问题。

      1. 检查 Service 和 Endpoints

        kubectl get svc <service-name>
        kubectl describe svc <service-name> # 查看 Selector 和 Port 映射
        kubectl get endpoints <service-name> # 这是关键!检查是否有健康的 Endpoints
        • 如果 ENDPOINTS 列为空:说明 Service 的 Label Selector 没有匹配到任何健康的 Pod。请检查:
          • Pod 的 labels 是否与 Service 的 selector 匹配。
          • Pod 的 readinessProbe 是否通过。
      2. 检查 DNS 解析

        • 进入源 Pod,测试是否能解析 Service 名称:
        kubectl exec -it <source-pod> -- nslookup <service-name>
        # 或者
        kubectl exec -it <source-pod> -- cat /etc/resolv.conf
        • 如果解析失败,检查 kube-dnscoredns Pod 是否正常。
        kubectl get pods -n kube-system | grep -e coredns -e kube-dns
      3. 检查 kube-proxy

        • kube-proxy 负责实现 Service 的负载均衡规则(通常是 iptables 或 ipvs)。
        kubectl get pods -n kube-system | grep kube-proxy
        • 确认所有 kube-proxy Pod 都在运行。
        • 可以登录到节点,检查是否有对应的 iptables 规则:
          sudo iptables-save | grep <service-name>
          # 或者查看 ipvs 规则(如果使用 ipvs 模式)
          sudo ipvsadm -ln

      第三阶段:高级调试技巧

      如果上述步骤仍未解决问题,可以尝试以下方法:

      1. 使用网络调试镜像

        • 部署一个包含网络工具的临时 Pod(如 nicolaka/netshoot)来进行高级调试。
        kubectl run -it --rm debug-pod --image=nicolaka/netshoot -- /bin/bash
        • 在这个 Pod 里,你可以使用 tcpdump, tracepath, dig 等强大工具。
        • 例如,在目标 Pod 的节点上抓包:
          # 在节点上执行,监听 Pod 网络对应的接口
          sudo tcpdump -i any -n host <source-pod-ip> and host <destination-pod-ip>
      2. 检查节点网络连接

        • 确认两个节点之间网络是通的(通过节点 IP)。
        • 确认 CNI 所需的端口(如 Flannel 的 VXLAN 端口 8472)在节点间是开放的。

      总结与排查命令清单

      当 Pod 间访问不通时,按顺序执行以下命令:

      # 1. 基本信息
      kubectl get pods -o wide
      kubectl get svc,ep -o wide
      
      # 2. 检查 NetworkPolicy
      kubectl get networkpolicies -A
      
      # 3. 检查核心插件
      kubectl get pods -n kube-system | grep -e coredns -e kube-proxy -e calico -e flannel
      
      # 4. 进入 Pod 测试
      kubectl exec -it <source-pod> -- ping <destination-pod-ip>
      kubectl exec -it <source-pod> -- nslookup <service-name>
      
      # 5. 检查目标 Pod 应用
      kubectl exec -it <destination-pod> -- netstat -tulpn
      kubectl logs <destination-pod>
      
      # 6. 节点层面检查
      # 在节点上执行
      ip route
      sudo iptables-save | grep <relevant-ip>

      记住,90% 的 Pod 网络不通问题源于 NetworkPolicy 配置、Service Endpoints 为空,或 CNI 插件故障。按照这个路径排查,绝大多数问题都能被定位和解决。

      Mar 7, 2024

      Talk with API Server

      Kubernetes 各模块与 API Server 通信详解

      这是理解 Kubernetes 架构的核心问题。API Server 是整个集群的"大脑",所有组件都通过它进行通信。


      🎯 Kubernetes 通信架构总览

      ┌─────────────────────────────────────────────────────────┐
      │                    API Server (核心)                     │
      │  - RESTful API (HTTP/HTTPS)                             │
      │  - 认证、授权、准入控制                                   │
      │  - etcd 唯一入口                                         │
      └───────┬─────────────────┬─────────────────┬─────────────┘
              │                 │                 │
          ┌───▼───┐         ┌───▼───┐        ┌───▼────┐
          │Kubelet│         │Scheduler│      │Controller│
          │(Node) │         │         │      │ Manager  │
          └───────┘         └─────────┘      └──────────┘
              │
          ┌───▼────┐
          │kube-proxy│
          └────────┘

      🔐 通信基础:认证、授权、准入

      1. 认证 (Authentication)

      所有组件访问 API Server 必须先通过认证。

      常见认证方式

      认证方式使用场景实现方式
      X.509 证书集群组件(kubelet/scheduler)客户端证书
      ServiceAccount TokenPod 内应用JWT Token
      Bootstrap Token节点加入集群临时 Token
      静态 Token 文件简单测试不推荐生产
      OIDC用户认证外部身份提供商

      X.509 证书认证示例

      # 1. API Server 启动参数包含 CA 证书
      kube-apiserver \
        --client-ca-file=/etc/kubernetes/pki/ca.crt \
        --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
        --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
      
      # 2. Kubelet 使用客户端证书
      kubelet \
        --kubeconfig=/etc/kubernetes/kubelet.conf \
        --client-ca-file=/etc/kubernetes/pki/ca.crt
      
      # 3. kubeconfig 文件内容
      apiVersion: v1
      kind: Config
      clusters:
      - cluster:
          certificate-authority: /etc/kubernetes/pki/ca.crt  # CA 证书
          server: https://192.168.1.10:6443                  # API Server 地址
        name: kubernetes
      users:
      - name: system:node:worker-1
        user:
          client-certificate: /var/lib/kubelet/pki/kubelet-client.crt  # 客户端证书
          client-key: /var/lib/kubelet/pki/kubelet-client.key          # 客户端密钥
      contexts:
      - context:
          cluster: kubernetes
          user: system:node:worker-1
        name: default
      current-context: default

      ServiceAccount Token 认证

      # Pod 内自动挂载的 Token
      cat /var/run/secrets/kubernetes.io/serviceaccount/token
      # eyJhbGciOiJSUzI1NiIsImtpZCI6Ij...
      
      # 使用 Token 访问 API Server
      TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
      curl -k -H "Authorization: Bearer $TOKEN" \
        https://kubernetes.default.svc/api/v1/namespaces/default/pods

      2. 授权 (Authorization)

      认证通过后,检查是否有权限执行操作。

      RBAC (Role-Based Access Control) - 最常用

      # 1. Role - 定义权限
      apiVersion: rbac.authorization.k8s.io/v1
      kind: Role
      metadata:
        namespace: default
        name: pod-reader
      rules:
      - apiGroups: [""]
        resources: ["pods"]
        verbs: ["get", "list", "watch"]
      
      ---
      # 2. RoleBinding - 绑定用户/ServiceAccount
      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: read-pods
        namespace: default
      subjects:
      - kind: ServiceAccount
        name: my-app
        namespace: default
      roleRef:
        kind: Role
        name: pod-reader
        apiGroup: rbac.authorization.k8s.io

      授权模式对比

      模式说明使用场景
      RBAC基于角色生产环境(推荐)
      ABAC基于属性复杂策略(已过时)
      Webhook外部授权服务自定义授权逻辑
      Node节点授权Kubelet 专用
      AlwaysAllow允许所有测试环境(危险)

      3. 准入控制 (Admission Control)

      授权通过后,准入控制器可以修改或拒绝请求。

      常用准入控制器

      # API Server 启用的准入控制器
      kube-apiserver \
        --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,\
      DefaultStorageClass,DefaultTolerationSeconds,MutatingAdmissionWebhook,\
      ValidatingAdmissionWebhook,ResourceQuota,PodSecurityPolicy
      准入控制器作用
      NamespaceLifecycle防止在删除中的 namespace 创建资源
      LimitRanger强制资源限制
      ResourceQuota强制命名空间配额
      PodSecurityPolicy强制 Pod 安全策略
      MutatingAdmissionWebhook修改资源(如注入 sidecar)
      ValidatingAdmissionWebhook验证资源(自定义校验)

      📡 各组件通信详解

      1. Kubelet → API Server

      Kubelet 是唯一主动连接 API Server 的组件。

      通信方式

      Kubelet (每个 Node)
          │
          ├─→ List-Watch Pods (监听分配给自己的 Pod)
          ├─→ Report Node Status (定期上报节点状态)
          ├─→ Report Pod Status (上报 Pod 状态)
          └─→ Get Secrets/ConfigMaps (拉取配置)

      实现细节

      // Kubelet 启动时创建 Informer 监听资源
      // 伪代码示例
      func (kl *Kubelet) syncLoop() {
          // 1. 创建 Pod Informer
          podInformer := cache.NewSharedIndexInformer(
              &cache.ListWatch{
                  ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
                      // 列出分配给当前节点的所有 Pod
                      options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", kl.nodeName).String()
                      return kl.kubeClient.CoreV1().Pods("").List(context.TODO(), options)
                  },
                  WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
                      // 持续监听 Pod 变化
                      options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", kl.nodeName).String()
                      return kl.kubeClient.CoreV1().Pods("").Watch(context.TODO(), options)
                  },
              },
              &v1.Pod{},
              0, // 不缓存
              cache.Indexers{},
          )
          
          // 2. 注册事件处理器
          podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
              AddFunc:    kl.handlePodAdditions,
              UpdateFunc: kl.handlePodUpdates,
              DeleteFunc: kl.handlePodDeletions,
          })
          
          // 3. 定期上报节点状态
          go wait.Until(kl.syncNodeStatus, 10*time.Second, stopCh)
      }
      
      // 上报节点状态
      func (kl *Kubelet) syncNodeStatus() {
          node := &v1.Node{
              ObjectMeta: metav1.ObjectMeta{Name: kl.nodeName},
              Status: v1.NodeStatus{
                  Conditions: []v1.NodeCondition{
                      {Type: v1.NodeReady, Status: v1.ConditionTrue},
                  },
                  Capacity: kl.getNodeCapacity(),
                  // ...
              },
          }
          
          // 调用 API Server 更新节点状态
          kl.kubeClient.CoreV1().Nodes().UpdateStatus(context.TODO(), node, metav1.UpdateOptions{})
      }

      Kubelet 配置示例

      # /var/lib/kubelet/config.yaml
      apiVersion: kubelet.config.k8s.io/v1beta1
      kind: KubeletConfiguration
      # API Server 连接配置(通过 kubeconfig)
      authentication:
        x509:
          clientCAFile: /etc/kubernetes/pki/ca.crt
        webhook:
          enabled: true
        anonymous:
          enabled: false
      authorization:
        mode: Webhook
      clusterDomain: cluster.local
      clusterDNS:
      - 10.96.0.10
      # 定期上报间隔
      nodeStatusUpdateFrequency: 10s
      nodeStatusReportFrequency: 1m

      List-Watch 机制详解

      ┌─────────────────────────────────────────┐
      │  Kubelet List-Watch 工作流程             │
      ├─────────────────────────────────────────┤
      │                                          │
      │  1. List(初始化)                         │
      │     GET /api/v1/pods?fieldSelector=...  │
      │     ← 返回所有当前 Pod                   │
      │                                          │
      │  2. Watch(持续监听)                      │
      │     GET /api/v1/pods?watch=true&...     │
      │     ← 保持长连接                         │
      │                                          │
      │  3. 接收事件                             │
      │     ← ADDED: Pod nginx-xxx created      │
      │     ← MODIFIED: Pod nginx-xxx updated   │
      │     ← DELETED: Pod nginx-xxx deleted    │
      │                                          │
      │  4. 本地处理                             │
      │     - 缓存更新                           │
      │     - 触发 Pod 生命周期管理              │
      │                                          │
      │  5. 断线重连                             │
      │     - 检测到连接断开                     │
      │     - 重新 List + Watch                  │
      │     - ResourceVersion 确保不丢事件       │
      └─────────────────────────────────────────┘

      HTTP 长连接(Chunked Transfer)

      # Kubelet 发起 Watch 请求
      GET /api/v1/pods?watch=true&resourceVersion=12345&fieldSelector=spec.nodeName=worker-1 HTTP/1.1
      Host: 192.168.1.10:6443
      Authorization: Bearer eyJhbGc...
      Connection: keep-alive
      
      # API Server 返回(Chunked 编码)
      HTTP/1.1 200 OK
      Content-Type: application/json
      Transfer-Encoding: chunked
      
      {"type":"ADDED","object":{"kind":"Pod","apiVersion":"v1",...}}
      {"type":"MODIFIED","object":{"kind":"Pod","apiVersion":"v1",...}}
      {"type":"DELETED","object":{"kind":"Pod","apiVersion":"v1",...}}
      ...
      # 连接保持打开,持续推送事件

      2. Scheduler → API Server

      Scheduler 也使用 List-Watch 机制。

      通信流程

      Scheduler
          │
          ├─→ Watch Pods (监听未调度的 Pod)
          │   └─ spec.nodeName == ""
          │
          ├─→ Watch Nodes (监听节点状态)
          │
          ├─→ Get PVs, PVCs (获取存储信息)
          │
          └─→ Bind Pod (绑定 Pod 到 Node)
              POST /api/v1/namespaces/{ns}/pods/{name}/binding

      Scheduler 伪代码

      // Scheduler 主循环
      func (sched *Scheduler) scheduleOne() {
          // 1. 从队列获取待调度的 Pod
          pod := sched.NextPod()
          
          // 2. 执行调度算法(过滤 + 打分)
          feasibleNodes := sched.findNodesThatFit(pod)
          if len(feasibleNodes) == 0 {
              // 无可用节点,标记为不可调度
              return
          }
          
          priorityList := sched.prioritizeNodes(pod, feasibleNodes)
          selectedNode := sched.selectHost(priorityList)
          
          // 3. 绑定 Pod 到 Node(调用 API Server)
          binding := &v1.Binding{
              ObjectMeta: metav1.ObjectMeta{
                  Name:      pod.Name,
                  Namespace: pod.Namespace,
              },
              Target: v1.ObjectReference{
                  Kind: "Node",
                  Name: selectedNode,
              },
          }
          
          // 发送 Binding 请求到 API Server
          err := sched.client.CoreV1().Pods(pod.Namespace).Bind(
              context.TODO(),
              binding,
              metav1.CreateOptions{},
          )
          
          // 4. API Server 更新 Pod 的 spec.nodeName
          // 5. Kubelet 监听到 Pod,开始创建容器
      }
      
      // Watch 未调度的 Pod
      func (sched *Scheduler) watchUnscheduledPods() {
          podInformer := cache.NewSharedIndexInformer(
              &cache.ListWatch{
                  ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
                      // 只监听 spec.nodeName 为空的 Pod
                      options.FieldSelector = "spec.nodeName="
                      return sched.client.CoreV1().Pods("").List(context.TODO(), options)
                  },
                  WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
                      options.FieldSelector = "spec.nodeName="
                      return sched.client.CoreV1().Pods("").Watch(context.TODO(), options)
                  },
              },
              &v1.Pod{},
              0,
              cache.Indexers{},
          )
          
          podInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
              AddFunc: func(obj interface{}) {
                  pod := obj.(*v1.Pod)
                  sched.queue.Add(pod)  // 加入调度队列
              },
          })
      }

      Binding 请求详解

      # Scheduler 发送的 HTTP 请求
      POST /api/v1/namespaces/default/pods/nginx-xxx/binding HTTP/1.1
      Host: 192.168.1.10:6443
      Authorization: Bearer eyJhbGc...
      Content-Type: application/json
      
      {
        "apiVersion": "v1",
        "kind": "Binding",
        "metadata": {
          "name": "nginx-xxx",
          "namespace": "default"
        },
        "target": {
          "kind": "Node",
          "name": "worker-1"
        }
      }
      
      # API Server 处理:
      # 1. 验证 Binding 请求
      # 2. 更新 Pod 对象的 spec.nodeName = "worker-1"
      # 3. 返回成功响应
      # 4. Kubelet 监听到 Pod 更新,开始创建容器

      3. Controller Manager → API Server

      Controller Manager 包含多个控制器,每个控制器独立与 API Server 通信。

      常见控制器

      Controller Manager
          │
          ├─→ Deployment Controller
          │   └─ Watch Deployments, ReplicaSets
          │
          ├─→ ReplicaSet Controller
          │   └─ Watch ReplicaSets, Pods
          │
          ├─→ Node Controller
          │   └─ Watch Nodes (节点健康检查)
          │
          ├─→ Service Controller
          │   └─ Watch Services (管理 LoadBalancer)
          │
          ├─→ Endpoint Controller
          │   └─ Watch Services, Pods (创建 Endpoints)
          │
          └─→ PV Controller
              └─ Watch PVs, PVCs (卷绑定)

      ReplicaSet Controller 示例

      // ReplicaSet Controller 的核心逻辑
      func (rsc *ReplicaSetController) syncReplicaSet(key string) error {
          // 1. 从缓存获取 ReplicaSet
          rs := rsc.rsLister.Get(namespace, name)
          
          // 2. 获取当前 Pod 列表(通过 Selector)
          allPods := rsc.podLister.List(labels.Everything())
          filteredPods := rsc.filterActivePods(rs.Spec.Selector, allPods)
          
          // 3. 计算差异
          diff := len(filteredPods) - int(*rs.Spec.Replicas)
          
          if diff < 0 {
              // 需要创建新 Pod
              diff = -diff
              for i := 0; i < diff; i++ {
                  // 调用 API Server 创建 Pod
                  pod := newPod(rs)
                  _, err := rsc.kubeClient.CoreV1().Pods(rs.Namespace).Create(
                      context.TODO(),
                      pod,
                      metav1.CreateOptions{},
                  )
              }
          } else if diff > 0 {
              // 需要删除多余 Pod
              podsToDelete := getPodsToDelete(filteredPods, diff)
              for _, pod := range podsToDelete {
                  // 调用 API Server 删除 Pod
                  err := rsc.kubeClient.CoreV1().Pods(pod.Namespace).Delete(
                      context.TODO(),
                      pod.Name,
                      metav1.DeleteOptions{},
                  )
              }
          }
          
          // 4. 更新 ReplicaSet 状态
          rs.Status.Replicas = int32(len(filteredPods))
          _, err := rsc.kubeClient.AppsV1().ReplicaSets(rs.Namespace).UpdateStatus(
              context.TODO(),
              rs,
              metav1.UpdateOptions{},
          )
      }

      Node Controller 心跳检测

      // Node Controller 监控节点健康
      func (nc *NodeController) monitorNodeHealth() {
          for {
              // 1. 列出所有节点
              nodes, _ := nc.kubeClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
              
              for _, node := range nodes.Items {
                  // 2. 检查节点状态
                  now := time.Now()
                  lastHeartbeat := getNodeCondition(&node, v1.NodeReady).LastHeartbeatTime
                  
                  if now.Sub(lastHeartbeat.Time) > 40*time.Second {
                      // 3. 节点超时,标记为 NotReady
                      setNodeCondition(&node, v1.NodeCondition{
                          Type:   v1.NodeReady,
                          Status: v1.ConditionUnknown,
                          Reason: "NodeStatusUnknown",
                      })
                      
                      // 4. 更新节点状态
                      nc.kubeClient.CoreV1().Nodes().UpdateStatus(
                          context.TODO(),
                          &node,
                          metav1.UpdateOptions{},
                      )
                      
                      // 5. 如果节点长时间 NotReady,驱逐 Pod
                      if now.Sub(lastHeartbeat.Time) > 5*time.Minute {
                          nc.evictPods(node.Name)
                      }
                  }
              }
              
              time.Sleep(10 * time.Second)
          }
      }

      4. kube-proxy → API Server

      kube-proxy 监听 Service 和 Endpoints,配置网络规则。

      通信流程

      kube-proxy (每个 Node)
          │
          ├─→ Watch Services
          │   └─ 获取 Service 定义
          │
          ├─→ Watch Endpoints
          │   └─ 获取后端 Pod IP 列表
          │
          └─→ 配置本地网络
              ├─ iptables 模式:更新 iptables 规则
              ├─ ipvs 模式:更新 IPVS 规则
              └─ userspace 模式:代理转发(已废弃)

      iptables 模式示例

      // kube-proxy 监听 Service 和 Endpoints
      func (proxier *Proxier) syncProxyRules() {
          // 1. 获取所有 Service
          services := proxier.serviceStore.List()
          
          // 2. 获取所有 Endpoints
          endpoints := proxier.endpointsStore.List()
          
          // 3. 生成 iptables 规则
          for _, svc := range services {
              // Service ClusterIP
              clusterIP := svc.Spec.ClusterIP
              
              // 对应的 Endpoints
              eps := endpoints[svc.Namespace+"/"+svc.Name]
              
              // 生成 DNAT 规则
              // -A KUBE-SERVICES -d 10.96.100.50/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-XXXX
              chain := generateServiceChain(svc)
              
              for _, ep := range eps.Subsets {
                  for _, addr := range ep.Addresses {
                      // -A KUBE-SVC-XXXX -m statistic --mode random --probability 0.33 -j KUBE-SEP-XXXX
                      // -A KUBE-SEP-XXXX -p tcp -m tcp -j DNAT --to-destination 10.244.1.5:8080
                      generateEndpointRule(addr.IP, ep.Ports[0].Port)
                  }
              }
          }
          
          // 4. 应用 iptables 规则
          iptables.Restore(rules)
      }

      生成的 iptables 规则示例

      # Service: nginx-service (ClusterIP: 10.96.100.50:80)
      # Endpoints: 10.244.1.5:8080, 10.244.2.8:8080
      
      # 1. KUBE-SERVICES 链(入口)
      -A KUBE-SERVICES -d 10.96.100.50/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-NGINX
      
      # 2. KUBE-SVC-NGINX 链(Service 链)
      -A KUBE-SVC-NGINX -m statistic --mode random --probability 0.5 -j KUBE-SEP-EP1
      -A KUBE-SVC-NGINX -j KUBE-SEP-EP2
      
      # 3. KUBE-SEP-EP1 链(Endpoint 1)
      -A KUBE-SEP-EP1 -p tcp -m tcp -j DNAT --to-destination 10.244.1.5:8080
      
      # 4. KUBE-SEP-EP2 链(Endpoint 2)
      -A KUBE-SEP-EP2 -p tcp -m tcp -j DNAT --to-destination 10.244.2.8:8080

      5. kubectl → API Server

      kubectl 是用户与 API Server 交互的客户端工具。

      通信流程

      kubectl get pods
          │
          ├─→ 1. 读取 kubeconfig (~/.kube/config)
          │      - API Server 地址
          │      - 证书/Token
          │
          ├─→ 2. 发送 HTTP 请求
          │      GET /api/v1/namespaces/default/pods
          │
          ├─→ 3. API Server 处理
          │      - 认证
          │      - 授权
          │      - 从 etcd 读取数据
          │
          └─→ 4. 返回结果
                 JSON 格式的 Pod 列表

      kubectl 底层实现

      // kubectl get pods 的简化实现
      func getPods(namespace string) {
          // 1. 加载 kubeconfig
          config, _ := clientcmd.BuildConfigFromFlags("", kubeconfig)
          
          // 2. 创建 Clientset
          clientset, _ := kubernetes.NewForConfig(config)
          
          // 3. 发起 GET 请求
          pods, _ := clientset.CoreV1().Pods(namespace).List(
              context.TODO(),
              metav1.ListOptions{},
          )
          
          // 4. 输出结果
          for _, pod := range pods.Items {
              fmt.Printf("%s\t%s\t%s\n", pod.Name, pod.Status.Phase, pod.Spec.NodeName)
          }
      }

      HTTP 请求详解

      # kubectl get pods 发送的实际 HTTP 请求
      GET /api/v1/namespaces/default/pods HTTP/1.1
      Host: 192.168.1.10:6443
      Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Ij...
      Accept: application/json
      User-Agent: kubectl/v1.28.0
      
      # API Server 响应
      HTTP/1.1 200 OK
      Content-Type: application/json
      
      {
        "kind": "PodList",
        "apiVersion": "v1",
        "metadata": {
          "resourceVersion": "12345"
        },
        "items": [
          {
            "metadata": {
              "name": "nginx-xxx",
              "namespace": "default"
            },
            "spec": {
              "nodeName": "worker-1",
              "containers": [...]
            },
            "status": {
              "phase": "Running"
            }
          }
        ]
      }

      🔄 核心机制:List-Watch

      List-Watch 是 Kubernetes 最核心的通信模式。

      List-Watch 架构

      ┌───────────────────────────────────────────────┐
      │              Client (Kubelet/Controller)      │
      ├───────────────────────────────────────────────┤
      │                                                │
      │  1. List(初始同步)                             │
      │     GET /api/v1/pods                          │
      │     → 获取所有资源                             │
      │     → 本地缓存(Informer Cache)                │
      │                                                │
      │  2. Watch(增量更新)                            │
      │     GET /api/v1/pods?watch=true               │
      │     → 长连接(HTTP Chunked)                    │
      │     → 实时接收 ADDED/MODIFIED/DELETED 事件    │
      │                                                │
      │  3. ResourceVersion(一致性保证)               │
      │     → 每个资源有版本号                         │
      │     → Watch 从指定版本开始                     │
      │     → 断线重连不丢失事件                       │
      │                                                │
      │  4. 本地缓存(Indexer)                         │
      │     → 减少 API Server 压力                    │
      │     → 快速查询                                 │
      │     → 自动同步                                 │
      └───────────────────────────────────────────────┘

      Informer 机制详解

      // Informer 是 List-Watch 的高级封装
      type Informer struct {
          Indexer   Indexer       // 本地缓存
          Controller Controller    // List-Watch 控制器
          Processor  *sharedProcessor  // 事件处理器
      }
      
      // 使用 Informer 监听资源
      func watchPodsWithInformer() {
          // 1. 创建 SharedInformerFactory
          factory := informers.NewSharedInformerFactory(clientset, 30*time.Second)
          
          // 2. 获取 Pod Informer
          podInformer := factory.Core().V1().Pods()
          
          // 3. 注册事件处理器
          podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
              AddFunc: func(obj interface{}) {
                  pod := obj.(*v1.Pod)
                  fmt.Printf("Pod ADDED: %s\n", pod.Name)
              },
              UpdateFunc: func(oldObj, newObj interface{}) {
                  pod := newObj.(*v1.Pod)
                  fmt.Printf("Pod UPDATED: %s\n", pod.Name)
              },
              DeleteFunc: func(obj interface{}) {
                  pod := obj.(*v1.Pod)
                  fmt.Printf("Pod DELETED: %s\n", pod.Name)
              },
          })
          
          // 4. 启动 Informer
          factory.Start(stopCh)
          
          // 5. 等待缓存同步完成
          factory.WaitForCacheSync(stopCh)
          
          // 6. 从本地缓存查询(不访问 API Server)
          pod, _ := podInformer.Lister().Pods("default").Get("nginx-xxx")
      }

      ResourceVersion 机制

      事件流:
      ┌────────────────────────────────────────┐
      │ Pod nginx-xxx created                  │ ResourceVersion: 100
      ├────────────────```
      ├────────────────────────────────────────┤
      │ Pod nginx-xxx updated (image changed)  │ ResourceVersion: 101
      ├────────────────────────────────────────┤
      │ Pod nginx-xxx updated (status changed) │ ResourceVersion: 102
      ├────────────────────────────────────────┤
      │ Pod nginx-xxx deleted                  │ ResourceVersion: 103
      └────────────────────────────────────────┘
      
      Watch 请求:
      1. 初始 Watch: GET /api/v1/pods?watch=true&resourceVersion=100
         → 从版本 100 开始接收事件
      
      2. 断线重连: GET /api/v1/pods?watch=true&resourceVersion=102
         → 从版本 102 继续,不会丢失版本 103 的删除事件
      
      3. 版本过期: 如果 resourceVersion 太旧(etcd 已压缩)
         → API Server 返回 410 Gone
         → Client 重新 List 获取最新状态,然后 Watch

      🔐 通信安全细节

      1. TLS 双向认证

      ┌────────────────────────────────────────┐
      │        API Server TLS 配置              │
      ├────────────────────────────────────────┤
      │                                         │
      │  Server 端证书:                         │
      │  - apiserver.crt (服务端证书)          │
      │  - apiserver.key (服务端私钥)          │
      │  - ca.crt (CA 证书)                    │
      │                                         │
      │  Client CA:                             │
      │  - 验证客户端证书                       │
      │  - --client-ca-file=/etc/kubernetes/pki/ca.crt │
      │                                         │
      │  启动参数:                              │
      │  --tls-cert-file=/etc/kubernetes/pki/apiserver.crt │
      │  --tls-private-key-file=/etc/kubernetes/pki/apiserver.key │
      │  --client-ca-file=/etc/kubernetes/pki/ca.crt │
      └────────────────────────────────────────┘
      
      ┌────────────────────────────────────────┐
      │        Kubelet TLS 配置                 │
      ├────────────────────────────────────────┤
      │                                         │
      │  Client 证书:                           │
      │  - kubelet-client.crt (客户端证书)     │
      │  - kubelet-client.key (客户端私钥)     │
      │  - ca.crt (CA 证书,验证 API Server)    │
      │                                         │
      │  kubeconfig 配置:                       │
      │  - certificate-authority: ca.crt       │
      │  - client-certificate: kubelet-client.crt │
      │  - client-key: kubelet-client.key      │
      └────────────────────────────────────────┘

      2. ServiceAccount Token 详解

      # 每个 Pod 自动挂载 ServiceAccount
      apiVersion: v1
      kind: Pod
      metadata:
        name: my-pod
      spec:
        serviceAccountName: default  # 使用的 ServiceAccount
        containers:
        - name: app
          image: nginx
          volumeMounts:
          - name: kube-api-access-xxxxx
            mountPath: /var/run/secrets/kubernetes.io/serviceaccount
            readOnly: true
        volumes:
        - name: kube-api-access-xxxxx
          projected:
            sources:
            - serviceAccountToken:
                path: token                    # JWT Token
                expirationSeconds: 3607
            - configMap:
                name: kube-root-ca.crt
                items:
                - key: ca.crt
                  path: ca.crt                 # CA 证书
            - downwardAPI:
                items:
                - path: namespace
                  fieldRef:
                    fieldPath: metadata.namespace  # 命名空间

      Pod 内访问 API Server

      # 进入 Pod
      kubectl exec -it my-pod -- sh
      
      # 1. 读取 Token
      TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
      
      # 2. 读取 CA 证书
      CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
      
      # 3. 读取命名空间
      NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
      
      # 4. 访问 API Server
      curl --cacert $CACERT \
           --header "Authorization: Bearer $TOKEN" \
           https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/pods
      
      # 5. 使用 kubectl proxy(简化方式)
      kubectl proxy --port=8080 &
      curl http://localhost:8080/api/v1/namespaces/default/pods

      ServiceAccount Token 结构

      # 解码 JWT Token
      TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
      echo $TOKEN | cut -d. -f2 | base64 -d | jq
      
      # 输出:
      {
        "aud": [
          "https://kubernetes.default.svc"
        ],
        "exp": 1696867200,        # 过期时间
        "iat": 1696863600,        # 签发时间
        "iss": "https://kubernetes.default.svc.cluster.local",  # 签发者
        "kubernetes.io": {
          "namespace": "default",  # 命名空间
          "pod": {
            "name": "my-pod",
            "uid": "abc-123"
          },
          "serviceaccount": {
            "name": "default",     # ServiceAccount 名称
            "uid": "def-456"
          }
        },
        "nbf": 1696863600,
        "sub": "system:serviceaccount:default:default"  # Subject
      }

      📊 通信模式总结

      1. 主动推送 vs 被动拉取

      组件通信模式说明
      Kubelet主动连接List-Watch API Server
      Scheduler主动连接List-Watch API Server
      Controller Manager主动连接List-Watch API Server
      kube-proxy主动连接List-Watch API Server
      kubectl主动请求RESTful API 调用
      API Server → etcd主动读写gRPC 连接 etcd

      重要: API Server 从不主动连接其他组件,都是组件主动连接 API Server。

      2. 通信协议

      ┌─────────────────────────────────────────┐
      │  API Server 对外暴露的协议               │
      ├─────────────────────────────────────────┤
      │                                          │
      │  1. HTTPS (主要协议)                     │
      │     - RESTful API                       │
      │     - 端口: 6443 (默认)                  │
      │     - 所有组件使用                       │
      │                                          │
      │  2. HTTP (不推荐)                        │
      │     - 仅用于本地测试                     │
      │     - 端口: 8080 (默认,已废弃)          │
      │     - 生产环境禁用                       │
      │                                          │
      │  3. WebSocket (特殊场景)                │
      │     - kubectl exec/logs/port-forward    │
      │     - 基于 HTTPS 升级                    │
      └─────────────────────────────────────────┘
      
      ┌─────────────────────────────────────────┐
      │  API Server 对 etcd 的协议               │
      ├─────────────────────────────────────────┤
      │                                          │
      │  gRPC (HTTP/2)                          │
      │  - 端口: 2379                            │
      │  - mTLS 双向认证                         │
      │  - 高性能二进制协议                      │
      └─────────────────────────────────────────┘

      🛠️ 实战:监控各组件通信

      1. 查看组件连接状态

      # 1. 查看 API Server 监听端口
      netstat -tlnp | grep kube-apiserver
      # tcp   0   0 :::6443   :::*   LISTEN   12345/kube-apiserver
      
      # 2. 查看连接到 API Server 的客户端
      netstat -anp | grep :6443 | grep ESTABLISHED
      # tcp   0   0 192.168.1.10:6443   192.168.1.11:45678   ESTABLISHED   (Kubelet)
      # tcp   0   0 192.168.1.10:6443   192.168.1.10:45679   ESTABLISHED   (Scheduler)
      # tcp   0   0 192.168.1.10:6443   192.168.1.10:45680   ESTABLISHED   (Controller Manager)
      
      # 3. 查看 API Server 日志
      journalctl -u kube-apiserver -f
      # I1011 10:00:00.123456   12345 httplog.go:89] "HTTP" verb="GET" URI="/api/v1/pods?watch=true" latency="30.123ms" userAgent="kubelet/v1.28.0" srcIP="192.168.1.11:45678"
      
      # 4. 查看 Kubelet 连接
      journalctl -u kubelet -f | grep "Connecting to API"

      2. 使用 tcpdump 抓包

      # 抓取 API Server 通信(6443 端口)
      tcpdump -i any -n port 6443 -A -s 0
      
      # 抓取特定主机的通信
      tcpdump -i any -n host 192.168.1.11 and port 6443
      
      # 保存到文件,用 Wireshark 分析
      tcpdump -i any -n port 6443 -w api-traffic.pcap

      3. API Server Audit 日志

      # API Server 审计配置
      apiVersion: v1
      kind: Policy
      rules:
      # 记录所有请求元数据
      - level: Metadata
        verbs: ["get", "list", "watch"]
      # 记录创建/更新/删除的完整请求和响应
      - level: RequestResponse
        verbs: ["create", "update", "patch", "delete"]
      # 启用 Audit 日志
      kube-apiserver \
        --audit-policy-file=/etc/kubernetes/audit-policy.yaml \
        --audit-log-path=/var/log/kubernetes/audit.log \
        --audit-log-maxage=30 \
        --audit-log-maxbackup=10 \
        --audit-log-maxsize=100
      
      # 查看审计日志
      tail -f /var/log/kubernetes/audit.log | jq
      
      # 示例输出:
      {
        "kind": "Event",
        "apiVersion": "audit.k8s.io/v1",
        "level": "Metadata",
        "auditID": "abc-123",
        "stage": "ResponseComplete",
        "requestURI": "/api/v1/namespaces/default/pods?watch=true",
        "verb": "watch",
        "user": {
          "username": "system:node:worker-1",
          "groups": ["system:nodes"]
        },
        "sourceIPs": ["192.168.1.11"],
        "userAgent": "kubelet/v1.28.0",
        "responseStatus": {
          "code": 200
        }
      }

      🔍 高级话题

      1. API Server 聚合层 (API Aggregation)

      允许扩展 API Server,添加自定义 API。

      ┌────────────────────────────────────────┐
      │       Main API Server (kube-apiserver) │
      │         /api, /apis                    │
      └───────────────┬────────────────────────┘
                      │ 代理请求
              ┌───────┴────────┐
              ▼                ▼
      ┌──────────────┐  ┌─────────────────┐
      │ Metrics API  │  │ Custom API      │
      │ /apis/metrics│  │ /apis/my.api/v1 │
      └──────────────┘  └─────────────────┘

      注册 APIService

      apiVersion: apiregistration.k8s.io/v1
      kind: APIService
      metadata:
        name: v1beta1.metrics.k8s.io
      spec:
        service:
          name: metrics-server
          namespace: kube-system
          port: 443
        group: metrics.k8s.io
        version: v1beta1
        insecureSkipTLSVerify: true
        groupPriorityMinimum: 100
        versionPriority: 100

      请求路由

      # 客户端请求
      kubectl top nodes
      # 等价于: GET /apis/metrics.k8s.io/v1beta1/nodes
      
      # API Server 处理:
      # 1. 检查路径 /apis/metrics.k8s.io/v1beta1
      # 2. 查找对应的 APIService
      # 3. 代理请求到 metrics-server Service
      # 4. 返回结果给客户端

      2. API Priority and Fairness (APF)

      控制 API Server 的请求优先级和并发限制。

      # FlowSchema - 定义请求匹配规则
      apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
      kind: FlowSchema
      metadata:
        name: system-nodes
      spec:
        priorityLevelConfiguration:
          name: system  # 关联到优先级配置
        matchingPrecedence: 900
        distinguisherMethod:
          type: ByUser
        rules:
        - subjects:
          - kind: Group
            group:
              name: system:nodes  # 匹配 Kubelet 请求
          resourceRules:
          - verbs: ["*"]
            apiGroups: ["*"]
            resources: ["*"]
            namespaces: ["*"]
      
      ---
      # PriorityLevelConfiguration - 定义并发限制
      apiVersion: flowcontrol.apiserver.k8s.io/v1beta3
      kind: PriorityLevelConfiguration
      metadata:
        name: system
      spec:
        type: Limited
        limited:
          assuredConcurrencyShares: 30  # 保证的并发数
          limitResponse:
            type: Queue
            queuing:
              queues: 64           # 队列数量
              queueLengthLimit: 50 # 每个队列长度
              handSize: 6          # 洗牌算法参数

      APF 工作流程

      请求进入 API Server
          │
          ├─→ 1. 匹配 FlowSchema (按 precedence 排序)
          │      - 检查 subject (user/group/serviceaccount)
          │      - 检查 resource (API 路径)
          │
          ├─→ 2. 确定 PriorityLevel
          │      - system (高优先级,Kubelet/Scheduler)
          │      - leader-election (中优先级,Controller Manager)
          │      - workload-high (用户请求)
          │      - catch-all (默认)
          │
          ├─→ 3. 检查并发限制
          │      - 当前并发数 < assuredConcurrencyShares: 立即执行
          │      - 超过限制: 进入队列等待
          │
          └─→ 4. 执行或拒绝
                 - 队列有空位: 等待执行
                 - 队列满: 返回 429 Too Many Requests

      查看 APF 状态

      # 查看所有 FlowSchema
      kubectl get flowschemas
      
      # 查看 PriorityLevelConfiguration
      kubectl get prioritylevelconfigurations
      
      # 查看实时指标
      kubectl get --raw /metrics | grep apiserver_flowcontrol
      
      # 关键指标:
      # apiserver_flowcontrol_current_inqueue_requests: 当前排队请求数
      # apiserver_flowcontrol_rejected_requests_total: 被拒绝的请求数
      # apiserver_flowcontrol_request_concurrency_limit: 并发限制

      3. Watch Bookmark

      优化 Watch 性能,减少断线重连的代价。

      // 启用 Watch Bookmark
      watch := clientset.CoreV1().Pods("default").Watch(
          context.TODO(),
          metav1.ListOptions{
              Watch:            true,
              AllowWatchBookmarks: true,  // 🔑 启用 Bookmark
          },
      )
      
      for event := range watch.ResultChan() {
          switch event.Type {
          case watch.Added:
              // 处理新增事件
          case watch.Modified:
              // 处理修改事件
          case watch.Deleted:
              // 处理删除事件
          case watch.Bookmark:
              // 🔑 Bookmark 事件(无实际数据变更)
              // 只是告诉客户端当前的 ResourceVersion
              // 用于优化断线重连
              pod := event.Object.(*v1.Pod)
              currentRV := pod.ResourceVersion
              fmt.Printf("Bookmark at ResourceVersion: %s\n", currentRV)
          }
      }

      Bookmark 的作用

      没有 Bookmark:
      ┌──────────────────────────────────────┐
      │ 客户端 Watch 从 ResourceVersion 100  │
      │ 长时间没有事件(如 1 小时)             │
      │ 连接断开                              │
      │ 重连时: Watch from RV 100            │
      │ API Server 需要回放 100-200 之间的    │
      │ 所有事件(即使客户端不需要)            │
      └──────────────────────────────────────┘
      
      有 Bookmark:
      ┌──────────────────────────────────────┐
      │ 客户端 Watch 从 ResourceVersion 100  │
      │ 每 10 分钟收到 Bookmark              │
      │   RV 110 (10 分钟后)                 │
      │   RV 120 (20 分钟后)                 │
      │   RV 130 (30 分钟后)                 │
      │ 连接断开                              │
      │ 重连时: Watch from RV 130 ✅         │
      │ 只需回放 130-200 之间的事件           │
      └──────────────────────────────────────┘

      4. 客户端限流 (Client-side Rate Limiting)

      防止客户端压垮 API Server。

      // client-go 的默认限流配置
      config := &rest.Config{
          Host: "https://192.168.1.10:6443",
          // QPS 限制
          QPS: 50.0,        // 每秒 50 个请求
          // Burst 限制
          Burst: 100,       // 突发最多 100 个请求
      }
      
      clientset := kubernetes.NewForConfig(config)
      
      // 自定义限流器
      import "golang.org/x/time/rate"
      
      rateLimiter := rate.NewLimiter(
          rate.Limit(50),  // 每秒 50 个
          100,             // Burst 100
      )
      
      // 在发送请求前等待
      rateLimiter.Wait(context.Background())
      clientset.CoreV1().Pods("default").List(...)

      📈 性能优化

      1. API Server 侧优化

      # API Server 启动参数
      kube-apiserver \
        # 增加 worker 线程
        --max-requests-inflight=400 \
        --max-mutating-requests-inflight=200 \
        \
        # Watch 缓存大小
        --watch-cache-sizes=pods#1000,nodes#100 \
        \
        # etcd 连接池
        --etcd-servers-overrides=/events#https://etcd-1:2379 \  # 分离 events
        \
        # 启用压缩
        --enable-aggregator-routing=true \
        \
        # 内存缓存
        --default-watch-cache-size=100

      2. Client 侧优化

      // 1. 使用 Informer (本地缓存)
      factory := informers.NewSharedInformerFactory(clientset, 30*time.Second)
      podInformer := factory.Core().V1().Pods()
      
      // 从本地缓存读取,不访问 API Server
      pod, _ := podInformer.Lister().Pods("default").Get("nginx")
      
      // 2. 使用 Field Selector 减少数据量
      listOptions := metav1.ListOptions{
          FieldSelector: "spec.nodeName=worker-1",  // 只获取特定节点的 Pod
      }
      
      // 3. 使用 Label Selector
      listOptions := metav1.ListOptions{
          LabelSelector: "app=nginx",  // 只获取特定标签的 Pod
      }
      
      // 4. 限制返回字段
      listOptions := metav1.ListOptions{
          Limit: 100,  // 分页,每次只返回 100 个
      }
      
      // 5. 批量操作
      // 不推荐: 循环创建 100 个 Pod(100 次 API 调用)
      for i := 0; i < 100; i++ {
          clientset.CoreV1().Pods("default").Create(...)
      }
      
      // 推荐: 使用 Job/Deployment(1 次 API 调用)
      deployment := &appsv1.Deployment{
          Spec: appsv1.DeploymentSpec{
              Replicas: int32Ptr(100),
              ...
          },
      }
      clientset.AppsV1().Deployments("default").Create(deployment)

      💡 关键要点总结

      通信模式

      1. 所有组件主动连接 API Server (API Server 从不主动推送)
      2. List-Watch 是核心机制 (初始 List + 持续 Watch)
      3. HTTP 长连接 (Chunked Transfer Encoding)
      4. ResourceVersion 保证一致性 (断线重连不丢事件)

      认证授权

      1. X.509 证书 (集群组件)
      2. ServiceAccount Token (Pod 内应用)
      3. RBAC 授权 (细粒度权限控制)
      4. 准入控制 (请求验证和修改)

      性能优化

      1. Informer 本地缓存 (减少 API Server 压力)
      2. Field/Label Selector (减少数据传输)
      3. APF 流量控制 (防止 API Server 过载)
      4. 客户端限流 (防止客户端压垮 API Server)

      最佳实践

      1. 使用 Informer 而不是轮询
      2. 合理设置 QPS 和 Burst
      3. 避免频繁的 List 操作
      4. 使用 Field Selector 过滤数据
      5. 启用 Watch Bookmark
      6. 监控 API Server 指标
      Mar 7, 2024

      Monitor

        Mar 7, 2025

        Subsections of Networking

        Ingress

        Kubernetes Ingress 原理详解

        Ingress 是 Kubernetes 中用于管理集群外部访问集群内服务的 API 对象,提供 HTTP/HTTPS 路由功能。


        🎯 Ingress 的作用

        没有 Ingress 的问题

        问题 1:每个服务需要一个 LoadBalancer
        ┌────────────────────────────────────┐
        │  Service A (LoadBalancer)  $$$     │
        │  Service B (LoadBalancer)  $$$     │
        │  Service C (LoadBalancer)  $$$     │
        └────────────────────────────────────┘
        成本高、管理复杂、IP 地址浪费
        
        问题 2:无法基于域名/路径路由
        客户端 → NodePort:30001 (Service A)
        客户端 → NodePort:30002 (Service B)
        需要记住不同的端口,不友好

        使用 Ingress 的方案

        单一入口 + 智能路由
        ┌───────────────────────────────────────┐
        │         Ingress Controller            │
        │    (一个 LoadBalancer 或 NodePort)    │
        └───────────┬───────────────────────────┘
                    │ 根据域名/路径路由
            ┌───────┴───────┬──────────┐
            ▼               ▼          ▼
        Service A       Service B   Service C
        (ClusterIP)     (ClusterIP) (ClusterIP)

        🏗️ Ingress 架构组成

        核心组件

        ┌─────────────────────────────────────────────┐
        │              Ingress 生态系统                │
        ├─────────────────────────────────────────────┤
        │  1. Ingress Resource (资源对象)             │
        │     └─ 定义路由规则(YAML)                   │
        │                                              │
        │  2. Ingress Controller (控制器)             │
        │     └─ 读取 Ingress,配置负载均衡器          │
        │                                              │
        │  3. 负载均衡器 (Nginx/Traefik/HAProxy)      │
        │     └─ 实际处理流量的组件                   │
        └─────────────────────────────────────────────┘

        📋 Ingress Resource (资源定义)

        基础示例

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: example-ingress
          annotations:
            nginx.ingress.kubernetes.io/rewrite-target: /
        spec:
          # 1. 基于域名路由
          rules:
          - host: example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: web-service
                    port:
                      number: 80
          
          # 2. TLS/HTTPS 配置
          tls:
          - hosts:
            - example.com
            secretName: example-tls

        完整功能示例

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: advanced-ingress
          namespace: default
          annotations:
            # Nginx 特定配置
            nginx.ingress.kubernetes.io/rewrite-target: /$2
            nginx.ingress.kubernetes.io/ssl-redirect: "true"
            nginx.ingress.kubernetes.io/rate-limit: "100"
            # 自定义响应头
            nginx.ingress.kubernetes.io/configuration-snippet: |
              add_header X-Custom-Header "Hello from Ingress";
        spec:
          # IngressClass (指定使用哪个 Ingress Controller)
          ingressClassName: nginx
          
          # TLS 配置
          tls:
          - hosts:
            - app.example.com
            - api.example.com
            secretName: example-tls-secret
          
          # 路由规则
          rules:
          # 规则 1:app.example.com
          - host: app.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: frontend-service
                    port:
                      number: 80
          
          # 规则 2:api.example.com
          - host: api.example.com
            http:
              paths:
              # /v1/* 路由到 api-v1
              - path: /v1
                pathType: Prefix
                backend:
                  service:
                    name: api-v1-service
                    port:
                      number: 8080
              
              # /v2/* 路由到 api-v2
              - path: /v2
                pathType: Prefix
                backend:
                  service:
                    name: api-v2-service
                    port:
                      number: 8080
          
          # 规则 3:默认后端(可选)
          defaultBackend:
            service:
              name: default-backend
              port:
                number: 80

        🎛️ PathType (路径匹配类型)

        三种匹配类型

        PathType匹配规则示例
        Prefix前缀匹配/foo 匹配 /foo, /foo/, /foo/bar
        Exact精确匹配/foo 只匹配 /foo,不匹配 /foo/
        ImplementationSpecific由 Ingress Controller 决定取决于实现

        示例对比

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: path-types-demo
        spec:
          rules:
          - host: example.com
            http:
              paths:
              # Prefix 匹配
              - path: /api
                pathType: Prefix
                backend:
                  service:
                    name: api-service
                    port:
                      number: 8080
              # 匹配:
              # ✅ /api
              # ✅ /api/
              # ✅ /api/users
              # ✅ /api/v1/users
              
              # Exact 匹配
              - path: /login
                pathType: Exact
                backend:
                  service:
                    name: auth-service
                    port:
                      number: 80
              # 匹配:
              # ✅ /login
              # ❌ /login/
              # ❌ /login/oauth

        🚀 Ingress Controller (控制器)

        常见 Ingress Controller

        Controller特点适用场景
        Nginx Ingress最流行,功能强大通用场景,生产推荐
        Traefik云原生,动态配置微服务,自动服务发现
        HAProxy高性能,企业级大流量,高并发
        KongAPI 网关功能API 管理,插件生态
        Istio Gateway服务网格集成复杂微服务架构
        AWS ALB云原生(AWS)AWS 环境
        GCE云原生(GCP)GCP 环境

        🔧 Ingress Controller 工作原理

        核心流程

        ┌─────────────────────────────────────────────┐
        │  1. 用户创建/更新 Ingress Resource          │
        │     kubectl apply -f ingress.yaml           │
        └────────────────┬────────────────────────────┘
                         │
                         ▼
        ┌─────────────────────────────────────────────┐
        │  2. Ingress Controller 监听 API Server      │
        │     - Watch Ingress 对象                    │
        │     - Watch Service 对象                    │
        │     - Watch Endpoints 对象                  │
        └────────────────┬────────────────────────────┘
                         │
                         ▼
        ┌─────────────────────────────────────────────┐
        │  3. 生成配置文件                             │
        │     Nginx:  /etc/nginx/nginx.conf          │
        │     Traefik: 动态配置                       │
        │     HAProxy: /etc/haproxy/haproxy.cfg      │
        └────────────────┬────────────────────────────┘
                         │
                         ▼
        ┌─────────────────────────────────────────────┐
        │  4. 重载/更新负载均衡器                      │
        │     nginx -s reload                         │
        └────────────────┬────────────────────────────┘
                         │
                         ▼
        ┌─────────────────────────────────────────────┐
        │  5. 流量路由生效                             │
        │     客户端请求 → Ingress → Service → Pod    │
        └─────────────────────────────────────────────┘

        📦 部署 Nginx Ingress Controller

        方式 1:使用官方 Helm Chart (推荐)

        # 添加 Helm 仓库
        helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
        helm repo update
        
        # 安装
        helm install ingress-nginx ingress-nginx/ingress-nginx \
          --namespace ingress-nginx \
          --create-namespace \
          --set controller.service.type=LoadBalancer
        
        # 查看部署状态
        kubectl get pods -n ingress-nginx
        kubectl get svc -n ingress-nginx

        方式 2:使用 YAML 部署

        # 下载官方 YAML
        kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
        
        # 查看部署
        kubectl get all -n ingress-nginx

        核心组件

        # 1. Deployment - Ingress Controller Pod
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        spec:
          replicas: 2  # 高可用建议 2+
          selector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
          template:
            metadata:
              labels:
                app.kubernetes.io/name: ingress-nginx
            spec:
              serviceAccountName: ingress-nginx
              containers:
              - name: controller
                image: registry.k8s.io/ingress-nginx/controller:v1.9.0
                args:
                - /nginx-ingress-controller
                - --election-id=ingress-nginx-leader
                - --controller-class=k8s.io/ingress-nginx
                - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
                ports:
                - name: http
                  containerPort: 80
                - name: https
                  containerPort: 443
                livenessProbe:
                  httpGet:
                    path: /healthz
                    port: 10254
                readinessProbe:
                  httpGet:
                    path: /healthz
                    port: 10254
        
        ---
        # 2. Service - 暴露 Ingress Controller
        apiVersion: v1
        kind: Service
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        spec:
          type: LoadBalancer  # 或 NodePort
          ports:
          - name: http
            port: 80
            targetPort: 80
            protocol: TCP
          - name: https
            port: 443
            targetPort: 443
            protocol: TCP
          selector:
            app.kubernetes.io/name: ingress-nginx
        
        ---
        # 3. ConfigMap - Nginx 全局配置
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        data:
          # 自定义 Nginx 配置
          proxy-body-size: "100m"
          proxy-connect-timeout: "15"
          proxy-read-timeout: "600"
          proxy-send-timeout: "600"
          use-forwarded-headers: "true"

        🌐 完整流量路径

        请求流程详解

        客户端
          │ 1. DNS 解析
          │    example.com → LoadBalancer IP (1.2.3.4)
          ▼
        LoadBalancer / NodePort
          │ 2. 转发到 Ingress Controller Pod
          ▼
        Ingress Controller (Nginx Pod)
          │ 3. 读取 Ingress 规则
          │    Host: example.com
          │    Path: /api/users
          │ 4. 匹配规则
          │    rule: host=example.com, path=/api
          │    backend: api-service:8080
          ▼
        Service (api-service)
          │ 5. Service 选择器匹配 Pod
          │    selector: app=api
          │ 6. 查询 Endpoints
          │    endpoints: 10.244.1.5:8080, 10.244.2.8:8080
          │ 7. 负载均衡(默认轮询)
          ▼
        Pod (api-xxxx)
          │ 8. 容器处理请求
          │    Container Port: 8080
          ▼
        应用响应
          │ 9. 原路返回
          ▼
        客户端收到响应

        网络数据包追踪

        # 客户端发起请求
        curl -H "Host: example.com" http://1.2.3.4/api/users
        
        # 1. DNS 解析
        example.com → 1.2.3.4 (LoadBalancer External IP)
        
        # 2. TCP 连接
        Client:54321 → LoadBalancer:80
        
        # 3. LoadBalancer 转发
        LoadBalancer:80 → Ingress Controller Pod:80 (10.244.0.5:80)
        
        # 4. Ingress Controller 内部处理
        Nginx 读取配置:
          location /api {
            proxy_pass http://api-service.default.svc.cluster.local:8080;
          }
        
        # 5. 查询 Service
        kube-proxy/iptables 规则:
          api-service:8080 → Endpoints
        
        # 6. 负载均衡到 Pod
        10.244.0.5 → 10.244.1.5:8080 (Pod IP)
        
        # 7. 响应返回
        Pod → Ingress Controller → LoadBalancer → Client

        🔒 HTTPS/TLS 配置

        创建 TLS Secret

        # 方式 1:使用自签名证书(测试环境)
        openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
          -keyout tls.key -out tls.crt \
          -subj "/CN=example.com"
        
        kubectl create secret tls example-tls \
          --cert=tls.crt \
          --key=tls.key
        
        # 方式 2:使用 Let's Encrypt (生产环境,推荐)
        # 安装 cert-manager
        kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
        
        # 创建 ClusterIssuer
        kubectl apply -f - <<EOF
        apiVersion: cert-manager.io/v1
        kind: ClusterIssuer
        metadata:
          name: letsencrypt-prod
        spec:
          acme:
            server: https://acme-v02.api.letsencrypt.org/directory
            email: admin@example.com
            privateKeySecretRef:
              name: letsencrypt-prod
            solvers:
            - http01:
                ingress:
                  class: nginx
        EOF

        配置 HTTPS Ingress

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: https-ingress
          annotations:
            # 自动重定向 HTTP 到 HTTPS
            nginx.ingress.kubernetes.io/ssl-redirect: "true"
            # 使用 cert-manager 自动申请证书
            cert-manager.io/cluster-issuer: "letsencrypt-prod"
        spec:
          ingressClassName: nginx
          tls:
          - hosts:
            - example.com
            - www.example.com
            secretName: example-tls  # cert-manager 会自动创建这个 Secret
          rules:
          - host: example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: web-service
                    port:
                      number: 80

        验证 HTTPS

        # 检查证书
        curl -v https://example.com
        
        # 查看 Secret
        kubectl get secret example-tls
        kubectl describe secret example-tls
        
        # 测试 HTTP 自动重定向
        curl -I http://example.com
        # HTTP/1.1 308 Permanent Redirect
        # Location: https://example.com/

        🎨 高级路由场景

        场景 1:基于路径的路由

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: path-based-routing
          annotations:
            nginx.ingress.kubernetes.io/rewrite-target: /$2
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              # /api/v1/* → api-v1-service
              - path: /api/v1(/|$)(.*)
                pathType: Prefix
                backend:
                  service:
                    name: api-v1-service
                    port:
                      number: 8080
              
              # /api/v2/* → api-v2-service
              - path: /api/v2(/|$)(.*)
                pathType: Prefix
                backend:
                  service:
                    name: api-v2-service
                    port:
                      number: 8080
              
              # /admin/* → admin-service
              - path: /admin
                pathType: Prefix
                backend:
                  service:
                    name: admin-service
                    port:
                      number: 3000
              
              # /* → frontend-service (默认)
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: frontend-service
                    port:
                      number: 80

        场景 2:基于子域名的路由

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: subdomain-routing
        spec:
          rules:
          # www.example.com
          - host: www.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: website-service
                    port:
                      number: 80
          
          # api.example.com
          - host: api.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: api-service
                    port:
                      number: 8080
          
          # blog.example.com
          - host: blog.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: blog-service
                    port:
                      number: 80
          
          # *.dev.example.com (通配符)
          - host: "*.dev.example.com"
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: dev-environment
                    port:
                      number: 80

        场景 3:金丝雀发布 (Canary Deployment)

        # 主版本 Ingress
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: production
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-v1
                    port:
                      number: 80
        
        ---
        # 金丝雀版本 Ingress
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: canary
          annotations:
            nginx.ingress.kubernetes.io/canary: "true"
            # 10% 流量到金丝雀版本
            nginx.ingress.kubernetes.io/canary-weight: "10"
            
            # 或基于请求头
            # nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
            # nginx.ingress.kubernetes.io/canary-by-header-value: "always"
            
            # 或基于 Cookie
            # nginx.ingress.kubernetes.io/canary-by-cookie: "canary"
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-v2-canary
                    port:
                      number: 80

        场景 4:A/B 测试

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: ab-testing
          annotations:
            # 基于请求头进行 A/B 测试
            nginx.ingress.kubernetes.io/canary: "true"
            nginx.ingress.kubernetes.io/canary-by-header: "X-Version"
            nginx.ingress.kubernetes.io/canary-by-header-value: "beta"
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-beta
                    port:
                      number: 80
        # 普通用户访问 A 版本
        curl http://myapp.com
        
        # Beta 用户访问 B 版本
        curl -H "X-Version: beta" http://myapp.com

        🔧 常用 Annotations (Nginx)

        基础配置

        metadata:
          annotations:
            # SSL 重定向
            nginx.ingress.kubernetes.io/ssl-redirect: "true"
            
            # 强制 HTTPS
            nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
            
            # 后端协议
            nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"  # 或 HTTP, GRPC
            
            # 路径重写
            nginx.ingress.kubernetes.io/rewrite-target: /$2
            
            # URL 重写
            nginx.ingress.kubernetes.io/use-regex: "true"

        高级配置

        metadata:
          annotations:
            # 上传文件大小限制
            nginx.ingress.kubernetes.io/proxy-body-size: "100m"
            
            # 超时配置
            nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
            nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
            nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
            
            # 会话保持 (Sticky Session)
            nginx.ingress.kubernetes.io/affinity: "cookie"
            nginx.ingress.kubernetes.io/session-cookie-name: "route"
            nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
            nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
            
            # 限流
            nginx.ingress.kubernetes.io/limit-rps: "100"  # 每秒请求数
            nginx.ingress.kubernetes.io/limit-connections: "10"  # 并发连接数
            
            # CORS 配置
            nginx.ingress.kubernetes.io/enable-cors: "true"
            nginx.ingress.kubernetes.io/cors-allow-origin: "*"
            nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
            
            # 白名单
            nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.0.0/16"
            
            # 基本认证
            nginx.ingress.kubernetes.io/auth-type: basic
            nginx.ingress.kubernetes.io/auth-secret: basic-auth
            nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
            
            # 自定义 Nginx 配置片段
            nginx.ingress.kubernetes.io/configuration-snippet: |
              more_set_headers "X-Custom-Header: MyValue";
              add_header X-Request-ID $request_id;

        🛡️ 安全配置

        1. 基本认证

        # 创建密码文件
        htpasswd -c auth admin
        # 输入密码
        
        # 创建 Secret
        kubectl create secret generic basic-auth --from-file=auth
        
        # 应用到 Ingress
        kubectl apply -f - <<EOF
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: secure-ingress
          annotations:
            nginx.ingress.kubernetes.io/auth-type: basic
            nginx.ingress.kubernetes.io/auth-secret: basic-auth
            nginx.ingress.kubernetes.io/auth-realm: "Authentication Required - Please enter your credentials"
        spec:
          rules:
          - host: admin.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: admin-service
                    port:
                      number: 80
        EOF

        2. IP 白名单

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: whitelist-ingress
          annotations:
            # 只允许特定 IP 访问
            nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8,192.168.1.100/32"
        spec:
          rules:
          - host: internal.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: internal-service
                    port:
                      number: 80

        3. OAuth2 认证

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: oauth2-ingress
          annotations:
            nginx.ingress.kubernetes.io/auth-url: "https://oauth2-proxy.example.com/oauth2/auth"
            nginx.ingress.kubernetes.io/auth-signin: "https://oauth2-proxy.example.com/oauth2/start?rd=$escaped_request_uri"
        spec:
          rules:
          - host: app.example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: protected-service
                    port:
                      number: 80

        📊 监控和调试

        查看 Ingress 状态

        # 列出所有 Ingress
        kubectl get ingress
        
        # 详细信息
        kubectl describe ingress example-ingress
        
        # 查看 Ingress Controller 日志
        kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -f
        
        # 查看生成的 Nginx 配置
        kubectl exec -n ingress-nginx <ingress-controller-pod> -- cat /etc/nginx/nginx.conf

        测试 Ingress 规则

        # 测试域名解析
        nslookup example.com
        
        # 测试 HTTP
        curl -H "Host: example.com" http://<ingress-ip>/
        
        # 测试 HTTPS
        curl -k -H "Host: example.com" https://<ingress-ip>/
        
        # 查看响应头
        curl -I -H "Host: example.com" http://<ingress-ip>/
        
        # 测试特定路径
        curl -H "Host: example.com" http://<ingress-ip>/api/users

        常见问题排查

        # 1. 检查 Ingress 是否有 Address
        kubectl get ingress
        # 如果 ADDRESS 列为空,说明 Ingress Controller 未就绪
        
        # 2. 检查 Service 和 Endpoints
        kubectl get svc
        kubectl get endpoints
        
        # 3. 检查 Ingress Controller Pod
        kubectl get pods -n ingress-nginx
        kubectl logs -n ingress-nginx <pod-name>
        
        # 4. 检查 DNS 解析
        kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup example.com
        
        # 5. 检查网络连通性
        kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- \
          curl -H "Host: example.com" http://web-service.default.svc.cluster.local

        🎯 Ingress vs Service Type

        对比表

        维度IngressLoadBalancerNodePort
        成本1 个 LB每个服务 1 个 LB免费
        域名路由✅ 支持❌ 不支持❌ 不支持
        路径路由✅ 支持❌ 不支持❌ 不支持
        TLS 终止✅ 支持⚠️ 需要额外配置❌ 不支持
        7 层功能✅ 丰富❌ 4 层❌ 4 层
        适用场景HTTP/HTTPS 服务需要独立 LB 的服务开发测试

        💡 关键要点总结

        Ingress 的价值

        1. 成本优化:多个服务共享一个 LoadBalancer
        2. 智能路由:基于域名、路径的 7 层路由
        3. TLS 管理:集中管理 HTTPS 证书
        4. 高级功能:限流、认证、重写、CORS 等
        5. 易于管理:声明式配置,统一入口

        核心概念

        • Ingress Resource:定义路由规则的 YAML
        • Ingress Controller:读取规则并实现路由的控制器
        • 负载均衡器:实际处理流量的组件(Nginx/Traefik/HAProxy)

        典型使用场景

        • ✅ 微服务 API 网关
        • ✅ 多租户应用(基于子域名隔离)
        • ✅ 蓝绿部署/金丝雀发布
        • ✅ Web 应用统一入口
        • ❌ 非 HTTP 协议(如 TCP/UDP,考虑使用 Gateway API)

        🚀 高级话题

        1. IngressClass (多 Ingress Controller)

        在同一集群中运行多个 Ingress Controller:

        # 定义 IngressClass
        apiVersion: networking.k8s.io/v1
        kind: IngressClass
        metadata:
          name: nginx
          annotations:
            ingressclass.kubernetes.io/is-default-class: "true"
        spec:
          controller: k8s.io/ingress-nginx
        
        ---
        apiVersion: networking.k8s.io/v1
        kind: IngressClass
        metadata:
          name: traefik
        spec:
          controller: traefik.io/ingress-controller
        
        ---
        # 使用特定的 IngressClass
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: my-ingress
        spec:
          ingressClassName: nginx  # 🔑 指定使用 nginx 控制器
          rules:
          - host: example.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: web-service
                    port:
                      number: 80

        使用场景:

        • 内部服务使用 Nginx,外部服务使用 Traefik
        • 不同团队使用不同的 Ingress Controller
        • 按环境划分(dev 用 Traefik,prod 用 Nginx)

        2. 默认后端 (Default Backend)

        处理未匹配任何规则的请求:

        # 创建默认后端服务
        apiVersion: v1
        kind: Service
        metadata:
          name: default-backend
        spec:
          selector:
            app: default-backend
          ports:
          - port: 80
            targetPort: 8080
        
        ---
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: default-backend
        spec:
          replicas: 1
          selector:
            matchLabels:
              app: default-backend
          template:
            metadata:
              labels:
                app: default-backend
            spec:
              containers:
              - name: default-backend
                image: registry.k8s.io/defaultbackend-amd64:1.5
                ports:
                - containerPort: 8080
        
        ---
        # 在 Ingress 中指定默认后端
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: ingress-with-default
        spec:
          defaultBackend:
            service:
              name: default-backend
              port:
                number: 80
          rules:
          - host: example.com
            http:
              paths:
              - path: /app
                pathType: Prefix
                backend:
                  service:
                    name: app-service
                    port:
                      number: 80

        效果:

        • 访问 example.com/app → app-service
        • 访问 example.com/other → default-backend(404 页面)
        • 访问 unknown.com → default-backend

        3. ExternalName Service 与 Ingress

        将 Ingress 路由到集群外部服务:

        # 创建 ExternalName Service
        apiVersion: v1
        kind: Service
        metadata:
          name: external-api
        spec:
          type: ExternalName
          externalName: api.external-service.com  # 外部域名
        
        ---
        # Ingress 路由到外部服务
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: external-ingress
          annotations:
            nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
            nginx.ingress.kubernetes.io/upstream-vhost: "api.external-service.com"
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /external
                pathType: Prefix
                backend:
                  service:
                    name: external-api
                    port:
                      number: 443

        使用场景:

        • 集成第三方 API
        • 混合云架构(部分服务在云外)
        • 灰度迁移(逐步从外部迁移到集群内)

        4. 跨命名空间引用 (ExternalName 方式)

        Ingress 默认只能引用同一命名空间的 Service,跨命名空间需要特殊处理:

        # Namespace: backend
        apiVersion: v1
        kind: Service
        metadata:
          name: api-service
          namespace: backend
        spec:
          selector:
            app: api
          ports:
          - port: 8080
        
        ---
        # Namespace: frontend
        # 创建 ExternalName Service 指向 backend 命名空间的服务
        apiVersion: v1
        kind: Service
        metadata:
          name: api-proxy
          namespace: frontend
        spec:
          type: ExternalName
          externalName: api-service.backend.svc.cluster.local
          ports:
          - port: 8080
        
        ---
        # Ingress 在 frontend 命名空间
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: cross-ns-ingress
          namespace: frontend
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /api
                pathType: Prefix
                backend:
                  service:
                    name: api-proxy  # 引用同命名空间的 ExternalName Service
                    port:
                      number: 8080

        5. TCP/UDP 服务暴露

        Ingress 原生只支持 HTTP/HTTPS,对于 TCP/UDP 需要特殊配置:

        Nginx Ingress Controller 的 TCP 配置

        # ConfigMap 定义 TCP 服务
        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: tcp-services
          namespace: ingress-nginx
        data:
          # 格式: "外部端口": "命名空间/服务名:服务端口"
          "3306": "default/mysql:3306"
          "6379": "default/redis:6379"
          "27017": "databases/mongodb:27017"
        
        ---
        # 修改 Ingress Controller Service,暴露 TCP 端口
        apiVersion: v1
        kind: Service
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        spec:
          type: LoadBalancer
          ports:
          - name: http
            port: 80
            targetPort: 80
          - name: https
            port: 443
            targetPort: 443
          # 添加 TCP 端口
          - name: mysql
            port: 3306
            targetPort: 3306
          - name: redis
            port: 6379
            targetPort: 6379
          - name: mongodb
            port: 27017
            targetPort: 27017
          selector:
            app.kubernetes.io/name: ingress-nginx
        
        ---
        # 修改 Ingress Controller Deployment,引用 ConfigMap
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        spec:
          template:
            spec:
              containers:
              - name: controller
                args:
                - /nginx-ingress-controller
                - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services
                # ...其他参数

        访问方式:

        # 连接 MySQL
        mysql -h <ingress-lb-ip> -P 3306 -u root -p
        
        # 连接 Redis
        redis-cli -h <ingress-lb-ip> -p 6379

        6. 灰度发布策略详解

        基于权重的流量分配

        # 生产版本 (90% 流量)
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: production
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-v1
                    port:
                      number: 80
        
        ---
        # 灰度版本 (10% 流量)
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: canary
          annotations:
            nginx.ingress.kubernetes.io/canary: "true"
            nginx.ingress.kubernetes.io/canary-weight: "10"
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-v2
                    port:
                      number: 80

        基于请求头的灰度

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: canary-header
          annotations:
            nginx.ingress.kubernetes.io/canary: "true"
            nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
            nginx.ingress.kubernetes.io/canary-by-header-value: "true"
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-v2
                    port:
                      number: 80

        测试:

        # 普通用户访问 v1
        curl http://myapp.com
        
        # 带特殊请求头的用户访问 v2
        curl -H "X-Canary: true" http://myapp.com
        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: canary-cookie
          annotations:
            nginx.ingress.kubernetes.io/canary: "true"
            nginx.ingress.kubernetes.io/canary-by-cookie: "canary"
        spec:
          rules:
          - host: myapp.com
            http:
              paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: app-v2
                    port:
                      number: 80

        使用:

        • Cookie canary=always → 路由到 v2
        • Cookie canary=never → 路由到 v1
        • 无 Cookie → 根据权重路由

        7. 性能优化

        Nginx Ingress Controller 优化配置

        apiVersion: v1
        kind: ConfigMap
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        data:
          # 工作进程数(建议等于 CPU 核心数)
          worker-processes: "auto"
          
          # 每个工作进程的连接数
          max-worker-connections: "65536"
          
          # 启用 HTTP/2
          use-http2: "true"
          
          # 启用 gzip 压缩
          use-gzip: "true"
          gzip-level: "6"
          gzip-types: "text/plain text/css application/json application/javascript text/xml application/xml"
          
          # 客户端请求体缓冲
          client-body-buffer-size: "128k"
          client-max-body-size: "100m"
          
          # Keepalive 连接
          keep-alive: "75"
          keep-alive-requests: "1000"
          
          # 代理缓冲
          proxy-buffer-size: "16k"
          proxy-buffers: "4 16k"
          
          # 日志优化(生产环境可以禁用访问日志)
          disable-access-log: "false"
          access-log-params: "buffer=16k flush=5s"
          
          # SSL 优化
          ssl-protocols: "TLSv1.2 TLSv1.3"
          ssl-ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
          ssl-prefer-server-ciphers: "true"
          ssl-session-cache: "true"
          ssl-session-cache-size: "10m"
          ssl-session-timeout: "10m"
          
          # 启用连接复用
          upstream-keepalive-connections: "100"
          upstream-keepalive-timeout: "60"
          
          # 限制
          limit-req-status-code: "429"
          limit-conn-status-code: "429"

        Ingress Controller Pod 资源配置

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: ingress-nginx-controller
          namespace: ingress-nginx
        spec:
          replicas: 3  # 高可用建议 3+
          template:
            spec:
              containers:
              - name: controller
                image: registry.k8s.io/ingress-nginx/controller:v1.9.0
                resources:
                  requests:
                    cpu: "500m"
                    memory: "512Mi"
                  limits:
                    cpu: "2000m"
                    memory: "2Gi"
                # 启用性能分析
                livenessProbe:
                  httpGet:
                    path: /healthz
                    port: 10254
                  initialDelaySeconds: 10
                  periodSeconds: 10
                readinessProbe:
                  httpGet:
                    path: /healthz
                    port: 10254
                  periodSeconds: 5

        8. 监控和可观测性

        Prometheus 监控集成

        # ServiceMonitor for Prometheus Operator
        apiVersion: monitoring.coreos.com/v1
        kind: ServiceMonitor
        metadata:
          name: ingress-nginx
          namespace: ingress-nginx
        spec:
          selector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx
          endpoints:
          - port: metrics
            interval: 30s

        查看 Ingress Controller 指标

        # 访问 metrics 端点
        kubectl port-forward -n ingress-nginx svc/ingress-nginx-controller-metrics 10254:10254
        
        # 浏览器访问
        http://localhost:10254/metrics
        
        # 关键指标:
        # - nginx_ingress_controller_requests: 请求总数
        # - nginx_ingress_controller_request_duration_seconds: 请求延迟
        # - nginx_ingress_controller_response_size: 响应大小
        # - nginx_ingress_controller_ssl_expire_time_seconds: SSL 证书过期时间

        Grafana 仪表盘

        # 导入官方 Grafana 仪表盘
        # Dashboard ID: 9614 (Nginx Ingress Controller)
        # Dashboard ID: 11875 (Nginx Ingress Controller Request Handling Performance)

        9. 故障排查清单

        问题 1: Ingress 没有分配 Address

        # 检查
        kubectl get ingress
        # NAME       CLASS   HOSTS         ADDRESS   PORTS   AGE
        # my-app     nginx   example.com             80      5m
        
        # 原因:
        # 1. Ingress Controller 未运行
        kubectl get pods -n ingress-nginx
        
        # 2. Service type 不是 LoadBalancer
        kubectl get svc -n ingress-nginx
        
        # 3. 云提供商未分配 LoadBalancer IP
        kubectl describe svc -n ingress-nginx ingress-nginx-controller

        问题 2: 502 Bad Gateway

        # 原因 1: 后端 Service 不存在
        kubectl get svc
        
        # 原因 2: 后端 Pod 不健康
        kubectl get pods
        kubectl describe pod <pod-name>
        
        # 原因 3: 端口配置错误
        kubectl get svc <service-name> -o yaml | grep -A 5 ports
        
        # 原因 4: 网络策略阻止
        kubectl get networkpolicies
        
        # 查看日志
        kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx --tail=100

        问题 3: 503 Service Unavailable

        # 原因: 没有健康的 Endpoints
        kubectl get endpoints <service-name>
        
        # 如果 ENDPOINTS 列为空:
        # 1. 检查 Service selector 是否匹配 Pod labels
        kubectl get svc <service-name> -o yaml | grep -A 3 selector
        kubectl get pods --show-labels
        
        # 2. 检查 Pod 是否 Ready
        kubectl get pods
        
        # 3. 检查容器端口是否正确
        kubectl get pods <pod-name> -o yaml | grep -A 5 ports

        问题 4: TLS 证书问题

        # 检查 Secret 是否存在
        kubectl get secret <tls-secret-name>
        
        # 查看证书内容
        kubectl get secret <tls-secret-name> -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -text -noout
        
        # 检查证书过期时间
        kubectl get secret <tls-secret-name> -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates
        
        # cert-manager 问题
        kubectl get certificate
        kubectl describe certificate <cert-name>
        kubectl get certificaterequests

        问题 5: 路由规则不生效

        # 1. 检查 Ingress 配置
        kubectl describe ingress <ingress-name>
        
        # 2. 查看生成的 Nginx 配置
        kubectl exec -n ingress-nginx <controller-pod> -- cat /etc/nginx/nginx.conf | grep -A 20 "server_name example.com"
        
        # 3. 测试域名解析
        nslookup example.com
        
        # 4. 使用 Host header 测试
        curl -v -H "Host: example.com" http://<ingress-ip>/path
        
        # 5. 检查 annotations 是否正确
        kubectl get ingress <ingress-name> -o yaml | grep -A 10 annotations

        10. 生产环境最佳实践

        ✅ 高可用配置

        # 1. 多副本 Ingress Controller
        spec:
          replicas: 3
          
          # 2. Pod 反亲和性(分散到不同节点)
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app.kubernetes.io/name
                    operator: In
                    values:
                    - ingress-nginx
                topologyKey: kubernetes.io/hostname
        
          # 3. PodDisruptionBudget(确保至少 2 个副本运行)
        ---
        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: ingress-nginx
          namespace: ingress-nginx
        spec:
          minAvailable: 2
          selector:
            matchLabels:
              app.kubernetes.io/name: ingress-nginx

        ✅ 资源限制

        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
          limits:
            cpu: "2"
            memory: "2Gi"
        
        # HPA 自动扩缩容
        ---
        apiVersion: autoscaling/v2
        kind: HorizontalPodAutoscaler
        metadata:
          name: ingress-nginx
          namespace: ingress-nginx
        spec:
          scaleTargetRef:
            apiVersion: apps/v1
            kind: Deployment
            name: ingress-nginx-controller
          minReplicas: 3
          maxReplicas: 10
          metrics:
          - type: Resource
            resource:
              name: cpu
              target:
                type: Utilization
                averageUtilization: 70
          - type: Resource
            resource:
              name: memory
              target:
                type: Utilization
                averageUtilization: 80

        ✅ 安全加固

        # 1. 只暴露必要端口
        # 2. 启用 TLS 1.2+
        # 3. 配置安全头
        metadata:
          annotations:
            nginx.ingress.kubernetes.io/configuration-snippet: |
              more_set_headers "X-Frame-Options: DENY";
              more_set_headers "X-Content-Type-Options: nosniff";
              more_set_headers "X-XSS-Protection: 1; mode=block";
              more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
        
        # 4. 配置 WAF(Web Application Firewall)
        nginx.ingress.kubernetes.io/enable-modsecurity: "true"
        nginx.ingress.kubernetes.io/enable-owasp-core-rules: "true"
        
        # 5. 限流保护
        nginx.ingress.kubernetes.io/limit-rps: "100"
        nginx.ingress.kubernetes.io/limit-burst-multiplier: "5"

        ✅ 监控告警

        # Prometheus 告警规则示例
        groups:
        - name: ingress
          rules:
          - alert: IngressControllerDown
            expr: up{job="ingress-nginx-controller-metrics"} == 0
            for: 5m
            annotations:
              summary: "Ingress Controller is down"
          
          - alert: HighErrorRate
            expr: rate(nginx_ingress_controller_requests{status=~"5.."}[5m]) > 0.05
            for: 5m
            annotations:
              summary: "High 5xx error rate"
          
          - alert: HighLatency
            expr: histogram_quantile(0.95, nginx_ingress_controller_request_duration_seconds_bucket) > 1
            for: 10m
            annotations:
              summary: "High request latency (p95 > 1s)"

        📚 总结对比:Ingress vs 其他方案

        Ingress vs LoadBalancer Service

        场景:部署 10 个微服务
        
        方案 A:每个服务一个 LoadBalancer
        - 成本:10 个 LoadBalancer × $20/月 = $200/月
        - 管理:10 个独立的 IP 地址
        - 路由:无智能路由
        - TLS:每个服务单独配置
        
        方案 B:一个 Ingress
        - 成本:1 个 LoadBalancer × $20/月 = $20/月 ✅
        - 管理:1 个 IP 地址 ✅
        - 路由:基于域名/路径智能路由 ✅
        - TLS:集中管理证书 ✅

        Ingress vs API Gateway

        功能IngressAPI Gateway (Kong/Tyk)
        基础路由
        认证鉴权⚠️ 基础✅ 完善
        限流熔断⚠️ 基础✅ 高级
        插件生态❌ 有限✅ 丰富
        学习曲线✅ 简单⚠️ 复杂
        性能✅ 高⚠️ 中等

        🎓 学习路径建议

        1. 入门 (1-2 周)

          • 理解 Ingress 概念
          • 部署 Nginx Ingress Controller
          • 创建基本的 Ingress 规则
          • 配置 HTTP/HTTPS 访问
        2. 进阶 (2-4 周)

          • 掌握各种路由策略
          • TLS 证书管理(cert-manager)
          • 金丝雀发布
          • 性能调优
        3. 高级 (1-2 月)

          • 多 Ingress Controller 管理
          • WAF 和安全加固
          • 监控和告警
          • 故障排查
        4. 专家 (持续学习)

          • 源码阅读
          • 自定义插件开发
          • Gateway API 迁移

        需要我详细展开某个特定主题,比如 cert-manager 自动化证书管理、Ingress Controller 源码分析,或者 Gateway API 新标准吗? 🚀

        Mar 7, 2024

        Nginx 性能优化

        通用优化、操作系统层、Nginx 配置层、架构层等多个维度,为你详细梳理的方式。


        一、操作系统与硬件层优化

        这是优化的基础,为 Nginx 提供一个高性能的运行环境。

        1. 增加文件描述符限制 Nginx 每个连接(尤其是静态文件)都会消耗一个文件描述符。如果并发高,默认限制很容易成为瓶颈。

          # 临时生效
          ulimit -n 65536
          
          # 永久生效,修改 /etc/security/limits.conf
          * soft nofile 65536
          * hard nofile 65536
          
          # 同时,确保 nginx.conf 中使用了足够的 worker_rlimit_nofile
          worker_rlimit_nofile 65536;
        2. 优化网络栈

          • 调整 net.core.somaxconn: 定义等待 Nginx 接受的最大连接队列长度。如果遇到 accept() 队列溢出的错误,需要增加这个值。
            sysctl -w net.core.somaxconn=65535
            并在 Nginx 的 listen 指令中显式指定 backlog 参数:
            listen 80 backlog=65535;
          • 启用 TCP Fast Open: 减少 TCP 三次握手的延迟。
            sysctl -w net.ipv4.tcp_fastopen=3
          • 增大临时端口范围: 当 Nginx 作为反向代理时,它需要大量本地端口来连接上游服务器。
            sysctl -w net.ipv4.ip_local_port_range="1024 65535"
          • 减少 TCP TIME_WAIT 状态: 对于高并发短连接场景,大量连接处于 TIME_WAIT 状态会耗尽端口资源。
            # 启用 TIME_WAIT 复用
            sysctl -w net.ipv4.tcp_tw_reuse=1
            # 快速回收 TIME_WAIT 连接
            sysctl -w net.ipv4.tcp_tw_recycle=0 # 注意:在 NAT 环境下建议为 0,否则可能有问题
            # 增大 FIN_WAIT_2 状态的超时时间
            sysctl -w net.ipv4.tcp_fin_timeout=30
        3. 使用高性能磁盘 对于静态资源服务,使用 SSD 硬盘可以极大提升 IO 性能。


        二、Nginx 配置优化

        这是优化的核心,直接决定 Nginx 的行为。

        1. 工作进程与连接数

          • worker_processes auto;: 设置为 auto,让 Nginx 自动根据 CPU 核心数设置工作进程数,通常等于 CPU 核心数。
          • worker_connections: 每个工作进程可以处理的最大连接数。它与 worker_rlimit_nofile 共同决定了 Nginx 的总并发能力。
            events {
                worker_connections 10240; # 例如,设置为 10240
                use epoll; # 在 Linux 上使用高性能的 epoll 事件模型
            }
        2. 高效静态资源服务

          • 启用 sendfile: 绕过用户空间,直接在内核中完成文件数据传输,非常高效。
            sendfile on;
          • 启用 tcp_nopush: 与 sendfile on 一起使用,确保数据包被填满后再发送,提高网络效率。
            tcp_nopush on;
          • 启用 tcp_nodelay: 针对 keepalive 连接,强制立即发送数据,减少延迟。通常与 tcp_nopush 一起使用。
            tcp_nodelay on;
        3. 连接与请求超时 合理的超时设置可以释放闲置资源,避免连接被长期占用。

          # 客户端连接保持超时时间
          keepalive_timeout 30s;
          # 与上游服务器的保持连接超时时间
          proxy_connect_timeout 5s;
          proxy_send_timeout 60s;
          proxy_read_timeout 60s;
          # 客户端请求头读取超时
          client_header_timeout 15s;
          # 客户端请求体读取超时
          client_body_timeout 15s;
        4. 缓冲与缓存

          • 缓冲区优化: 为客户端请求头和请求体设置合适的缓冲区大小,避免 Nginx 写入临时文件,降低 IO。
            client_header_buffer_size 1k;
            large_client_header_buffers 4 4k;
            client_body_buffer_size 128k;
          • 代理缓冲区: 当 Nginx 作为反向代理时,控制从上游服务器接收数据的缓冲区。
            proxy_buffering on;
            proxy_buffer_size 4k;
            proxy_buffers 8 4k;
          • 启用缓存
            • 静态资源缓存: 使用 expiresadd_header 指令为静态资源设置长时间的浏览器缓存。
              location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
                  expires 1y;
                  add_header Cache-Control "public, immutable";
              }
            • 反向代理缓存: 使用 proxy_cache 模块缓存上游服务器的动态内容,极大减轻后端压力。
              proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m;
              location / {
                  proxy_cache my_cache;
                  proxy_cache_valid 200 302 10m;
                  proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
              }
        5. 日志优化

          • 禁用访问日志: 对于极高并发且不关心访问日志的场景,可以关闭 access_log
          • 缓冲写入日志: 使用 buffer 参数让 Nginx 先将日志写入内存缓冲区,满后再刷入磁盘。
            access_log /var/log/nginx/access.log main buffer=64k flush=1m;
          • 记录关键信息: 精简日志格式,只记录必要字段。
        6. Gzip 压缩 对文本类型的响应进行压缩,减少网络传输量。

          gzip on;
          gzip_vary on;
          gzip_min_length 1024; # 小于此值不压缩
          gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
        7. 上游连接保持 当代理到后端服务时,使用 keepalive 保持一定数量的空闲连接,避免频繁建立和断开 TCP 连接的开销。

          upstream backend_servers {
              server 10.0.1.100:8080;
              keepalive 32; # 保持的空闲连接数
          }
          
          location / {
              proxy_pass http://backend_servers;
              proxy_http_version 1.1;
              proxy_set_header Connection "";
          }

        三、架构与部署优化

        1. 负载均衡 使用 Nginx 的 upstream 模块将流量分发到多个后端服务器,实现水平扩展和高可用。

          upstream app_cluster {
              least_conn; # 使用最少连接算法
              server 10.0.1.101:8080;
              server 10.0.1.102:8080;
              server 10.0.1.103:8080;
          }
        2. 动静分离 将静态资源(图片、CSS、JS)的请求与动态请求分开。可以让 Nginx 直接处理静态资源,而动态请求则代理给后端应用服务器(如 Tomcat, Node.js 等)。

        3. 启用 HTTP/2 HTTP/2 提供了多路复用、头部压缩等特性,能显著提升页面加载速度。

          listen 443 ssl http2;
        4. 使用第三方模块 根据需求编译第三方模块,如:

          • OpenResty: 基于 Nginx 和 LuaJIT,提供了强大的可编程能力。
          • ngx_brotli: 使用 Brotli 压缩算法,通常比 Gzip 压缩率更高。

        四、监控与调试

        优化不是一次性的,需要持续监控。

        1. 启用状态模块 使用 stub_status_module 来查看 Nginx 的基本状态信息。

          location /nginx_status {
              stub_status;
              allow 127.0.0.1; # 只允许本机访问
              deny all;
          }

          访问后可以看到活跃连接数、请求总数等信息。

        2. 分析日志 使用工具如 goaccessawstats 分析访问日志,了解流量模式和瓶颈。

        3. 性能剖析 在极端情况下,可以使用 debug 日志或系统工具(如 perfstrace)进行深度性能剖析。

        总结与建议

        1. 循序渐进: 不要一次性修改所有参数。一次只修改一两项,然后进行压测(如使用 wrk, ab, jmeter),观察效果。
        2. 监控先行: 在优化前、中、后都要有可靠的监控数据作为依据。
        3. 理解业务: 优化的策略很大程度上取决于业务类型。是高并发连接?是大文件下载?还是大量的短动态请求?
        4. 内核参数谨慎调整: 生产环境调整内核参数前,务必在测试环境充分验证。

        通过以上这些方式的组合运用,你可以显著提升 Nginx 的性能和稳定性,使其能够轻松应对百万级别的并发连接。

        Oct 7, 2024

        Traefik VS Nginx

        好的,这是一个非常经典的问题。Traefik 和 Nginx Ingress 都是 Kubernetes 生态中顶级的 Ingress Controller,但它们的设计哲学、使用体验和侧重点有显著不同。

        简单来说:

        • Traefik 更像一个为云原生和微服务而生的动态、自动化的 API 网关
        • Nginx Ingress 更像一个基于久经考验的 Nginx 的、高度可配置的强大、稳定的反向代理/负载均衡器

        下面我们详细对比一下 Traefik 相对于 Nginx Ingress 的主要优点。

        Traefik 的核心优点

        1. 极致的动态配置与自动化

        这是 Traefik 最核心的卖点。

        • 工作原理:Traefik 会主动监听 Kubernetes API Server,实时感知 Service、Ingress Route、Secret 等资源的变化。一旦你创建或修改了一个 Ingress 资源,Traefik 几乎在秒级内自动更新其路由配置,无需任何重启或重载操作。
        • Nginx Ingress 的对比:Nginx Ingress 通常需要一个名为 nginx-ingress-controller 的组件来监控变化,然后生成一个新的 nginx.conf 配置文件,最后通过向 Nginx 进程发送 reload 信号来加载新配置。虽然这个过程也很快,但它本质上是一个 “生成-重载” 模型,在超大流量或配置复杂时,重载可能带来微小的性能抖动或延迟。

        结论:在追求完全自动化和零重载的云原生环境中,Traefik 的动态模型更具吸引力。

        2. 简化的配置模型与 “IngressRoute” CRD

        Traefik 完美支持标准的 Kubernetes Ingress 资源,但它更推荐使用自己定义的 Custom Resource Definition (CRD),叫做 IngressRoute

        • 为什么更好:标准的 Ingress 资源功能相对有限,很多高级特性(如重试、限流、断路器、请求镜像等)需要通过繁琐的 annotations 来实现,可读性和可维护性差。
        • Traefik 的 IngressRoute:它提供了一种声明式的、结构化的 YAML/JSON 配置方式。所有配置(包括 TLS、中间件、路由规则)都以清晰的结构定义在同一个 CRD 中,更符合 Kubernetes 的原生哲学,也更容易进行版本控制和代码审查。

        示例对比: 使用 Nginx Ingress 的注解来实现路径重写:

        apiVersion: networking.k8s.io/v1
        kind: Ingress
        metadata:
          name: my-ingress
          annotations:
            nginx.ingress.kubernetes.io/rewrite-target: /

        使用 Traefik 的 IngressRoute 和中间件:

        apiVersion: traefik.containo.us/v1alpha1
        kind: IngressRoute
        metadata:
          name: my-ingressroute
        spec:
          routes:
          - match: PathPrefix(`/api`)
            kind: Rule
            services:
            - name: my-service
              port: 80
            middlewares:
            - name: strip-prefix # 使用一个独立的、可复用的中间件资源
        ---
        apiVersion: traefik.containo.us/v1alpha1
        kind: Middleware
        metadata:
          name: strip-prefix
        spec:
          stripPrefix:
            prefixes:
              - /api

        可以看到,Traefik 的配置更加模块化和清晰。

        3. 内置的、功能丰富的 Dashboard

        Traefik 自带一个非常直观的 Web UI 控制台。只需简单启用,你就可以在浏览器中实时查看所有的路由器(Routers)、服务(Services)和中间件(Middlewares),以及它们的健康状况和配置关系。

        • 这对于开发和调试来说是巨大的福音。你可以一目了然地看到流量是如何被路由的,而无需去解析复杂的配置文件或命令行输出。
        • Nginx Ingress 官方不提供图形化 Dashboard。虽然可以通过第三方工具(如 Prometheus + Grafana)来监控,或者使用 kubectl 命令来查询状态,但远不如 Traefik 的原生 Dashboard 直观方便。

        4. 原生支持多种后端提供者

        Traefik 的设计是多提供者的。除了 Kubernetes,它还可以同时从 Docker、Consul、Etcd、Rancher 或者一个简单的静态文件中读取配置。 如果你的技术栈是混合的(例如,部分服务在 K8s,部分服务使用 Docker Compose),Traefik 可以作为一个统一的入口点,简化你的架构。

        Nginx Ingress 虽然也可以通过其他方式扩展,但其核心是为 Kubernetes 设计的。

        5. 中间件模式的强大与灵活

        Traefik 的 “中间件” 概念非常强大。它允许你将各种功能(如认证、限流、头信息修改、重定向、断路器等)定义为独立的、可复用的组件。然后,你可以在任何路由规则上通过引用的方式组合使用这些中间件。

        这种模式极大地增强了配置的复用性和灵活性,是构建复杂流量策略的理想选择。

        Nginx Ingress 的优势领域(作为平衡参考)

        为了做出全面选择,了解 Nginx Ingress 的优势也很重要:

        1. 极致的性能与稳定性:基于世界上最成熟的 Web 服务器 Nginx,在处理超高并发静态内容和长连接方面,经过了几十年的实战考验,性能和稳定性极高。
        2. 功能极其丰富:Nginx 本身的功能集非常庞大,加上 Nginx Ingress Controller 提供了大量的注解来暴露这些功能,其能力上限在某些方面可能高于 Traefik。
        3. 庞大的社区与生态:Nginx 的用户基数巨大,你遇到的任何问题几乎都能在网上找到解决方案或经验分享。
        4. 精细化控制:对于深度 Nginx 专家,可以通过 ConfigMap 注入自定义的 Nginx 配置片段,实现几乎任何你想要的功能,可控性极强。
        5. Apache 许可:Nginx 是 Apache 2.0 许可证,而 Traefik v2 之后使用的是限制更多的 Source Available 许可证(虽然对大多数用户免费,但会引起一些大公司的合规顾虑)。Nginx Ingress 完全没有这个问题。

        总结与选型建议

        特性TraefikNginx Ingress
        配置模型动态、自动化,无需重载“生成-重载”模型
        配置语法声明式 CRD,结构清晰主要依赖 Annotations,较繁琐
        Dashboard内置,功能强大,开箱即用无官方 UI,需第三方集成
        设计哲学云原生优先,微服务友好功能与性能优先,稳健可靠
        学习曲线较低,易于上手和运维中等,需要了解 Nginx 概念
        性能优秀,足以满足绝大多数场景极致,尤其在静态内容和大并发场景
        可扩展性通过中间件,模块化程度高通过 Lua 脚本或自定义模板,功能上限高
        许可证Source Available(可能有限制)Apache 2.0(完全开源)

        如何选择?

        • 选择 Traefik,如果:

          • 你追求极致的云原生体验,希望配置简单、自动化。
          • 你的团队更青睐 Kubernetes 原生的声明式配置方式。
          • 你非常看重内置的 Dashboard 用于日常运维和调试。
          • 你的应用架构是动态的,服务频繁发布和变更。
          • 你的场景不需要压榨到极致的性能,更看重开发效率和运维简便性。
        • 选择 Nginx Ingress,如果:

          • 你对性能和稳定性有极致要求(例如,超大规模网关、CDN边缘节点)。
          • 你需要使用非常复杂或小众的 Nginx 功能,需要精细化的控制。
          • 你的团队已经对 Nginx 非常熟悉,有深厚的知识积累。
          • 你对开源许可证有严格要求,必须使用 Apache 2.0 等宽松许可证。
          • 你的环境相对稳定,不需要频繁更新路由配置。

        总而言之,Traefik 胜在“体验”和“自动化”,是现代微服务和云原生环境的理想伴侣。而 Nginx Ingress 胜在“性能”和“功能深度”,是一个经过千锤百炼的、可靠的强大引擎。

        Mar 7, 2024

        RPC

          Mar 7, 2025

          Subsections of Storage

          User Based Policy

          User Based Policy

          you can change <$bucket> to control the permission

          App:
          • ${aws:username} is a build-in variable, indicating the logined user name.
          {
              "Version": "2012-10-17",
              "Statement": [
                  {
                      "Sid": "AllowUserToSeeBucketListInTheConsole",
                      "Action": [
                          "s3:ListAllMyBuckets",
                          "s3:GetBucketLocation"
                      ],
                      "Effect": "Allow",
                      "Resource": [
                          "arn:aws:s3:::*"
                      ]
                  },
                  {
                      "Sid": "AllowRootAndHomeListingOfCompanyBucket",
                      "Action": [
                          "s3:ListBucket"
                      ],
                      "Effect": "Allow",
                      "Resource": [
                          "arn:aws:s3:::<$bucket>"
                      ],
                      "Condition": {
                          "StringEquals": {
                              "s3:prefix": [
                                  "",
                                  "<$path>/",
                                  "<$path>/${aws:username}"
                              ],
                              "s3:delimiter": [
                                  "/"
                              ]
                          }
                      }
                  },
                  {
                      "Sid": "AllowListingOfUserFolder",
                      "Action": [
                          "s3:ListBucket"
                      ],
                      "Effect": "Allow",
                      "Resource": [
                          "arn:aws:s3:::<$bucket>"
                      ],
                      "Condition": {
                          "StringLike": {
                              "s3:prefix": [
                                  "<$path>/${aws:username}/*"
                              ]
                          }
                      }
                  },
                  {
                      "Sid": "AllowAllS3ActionsInUserFolder",
                      "Effect": "Allow",
                      "Action": [
                          "s3:*"
                      ],
                      "Resource": [
                          "arn:aws:s3:::<$bucket>/<$path>/${aws:username}/*"
                      ]
                  }
              ]
          }
          • <$uid> is Aliyun UID
          {
              "Version": "1",
              "Statement": [{
                  "Effect": "Allow",
                  "Action": [
                      "oss:*"
                  ],
                  "Principal": [
                      "<$uid>"
                  ],
                  "Resource": [
                      "acs:oss:*:<$oss_id>:<$bucket>/<$path>/*"
                  ]
              }, {
                  "Effect": "Allow",
                  "Action": [
                      "oss:ListObjects",
                      "oss:GetObject"
                  ],
                  "Principal": [
                       "<$uid>"
                  ],
                  "Resource": [
                      "acs:oss:*:<$oss_id>:<$bucket>"
                  ],
                  "Condition": {
                      "StringLike": {
                      "oss:Prefix": [
                              "<$path>/*"
                          ]
                      }
                  }
              }]
          }
          Example:
          {
          	"Version": "1",
          	"Statement": [{
          		"Effect": "Allow",
          		"Action": [
          			"oss:*"
          		],
          		"Principal": [
          			"203415213249511533"
          		],
          		"Resource": [
          			"acs:oss:*:1007296819402486:conti-csst/test/*"
          		]
          	}, {
          		"Effect": "Allow",
          		"Action": [
          			"oss:ListObjects",
          			"oss:GetObject"
          		],
          		"Principal": [
          			"203415213249511533"
          		],
          		"Resource": [
          			"acs:oss:*:1007296819402486:conti-csst"
          		],
          		"Condition": {
          			"StringLike": {
          				"oss:Prefix": [
          					"test/*"
          				]
          			}
          		}
          	}]
          }
          Mar 14, 2024

          Mirrors

          Gradle Tencent Mirror

          https://mirrors.cloud.tencent.com/gradle/gradle-8.0-bin.zip

          PIP Tuna Mirror -i https://pypi.tuna.tsinghua.edu.cn/simple

          pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package

          Maven Mirror

          <mirror>
              <id>aliyunmaven</id>
              <mirrorOf>*</mirrorOf>
              <name>阿里云公共仓库</name>
              <url>https://maven.aliyun.com/repository/public</url>
          </mirror>