K8s 云原生技术 2

6140 字
31 分钟
K8s 云原生技术 2

1. 概述#

在 Kubernetes 中,资源类型(Resource Type) 是 API 对象的抽象,用于描述集群中的各种实体,例如 Pod、Service、Deployment、ConfigMap 等。理解这些资源类型是高效管理集群的基础,可通过 kubectl api-resources 查看所有类型及缩写。

主要分类包括:

  • 工作负载类:如 Pod(最小调度单元)、Deployment(无状态应用部署)、StatefulSet(有状态服务)、DaemonSet(每节点运行一个Pod)、Job/CronJob(一次性或定时任务)。
  • 服务发现与负载均衡Service(集群内访问入口)、Ingress(七层路由)、Endpoints(Pod IP 列表)。
  • 存储管理PersistentVolumePersistentVolumeClaimStorageClass
  • 配置与密钥ConfigMap(非敏感配置)、Secret(敏感信息)。
  • 权限与安全ServiceAccountRole/ClusterRoleNetworkPolicy
  • 集群管理Namespace(资源隔离)、Node(节点信息)。
  • 扩展类型CRD(自定义资源)、AdmissionWebhook(动态准入控制)。

2. 资源类型#

2.1 Pod — 最小调度单元#

注意:一个Pod可以包含多个容器,但是一般情况下一个Pod内只含一个容器,我遇到一个Pod包含多个容器的情况目前只有Pod中同时包含主容器和一个 Init 容器的情况

  • 是什么:可以理解成一台临时的”逻辑主机”,里面可以跑一个或多个容器。这些容器共享网络、存储。
  • 通常不直接创建,而是由更上层的控制器管理,如 DeploymentStatefulSet
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80

一个 Pod 是一个运行实例,挂了就没了,所以需要有东西帮我们保持数量

带有 Init 容器的 Pod 示例:

Init 容器在 Pod 启动时先于主容器运行,常用于初始化操作(如等待依赖服务就绪、下载配置文件、初始化数据库等)。Init 容器必须成功退出后,主容器才会启动:

apiVersion: v1
kind: Pod
metadata:
name: pod-with-init
spec:
initContainers:
- name: wait-for-db # 这个先跑
image: busybox
command: ['sh', '-c', 'until nc -z db-service 3306; do echo waiting for db; sleep 2; done']
- name: init-config # 然后跑这个
image: busybox
command: ['sh', '-c', 'echo “server=db-service” > /config/db.conf']
volumeMounts:
- name: config-vol
mountPath: /config
containers:
- name: main-app # 最后才跑主容器
image: my-app:latest
volumeMounts:
- name: config-vol
mountPath: /etc/app
volumes:
- name: config-vol
emptyDir: {}

Init 容器可以有多个,会按顺序依次执行。它们可以共享同一个 volume,很适合作配置准备工作

2.2 Deployment — 无状态应用#

Deployment 是一个用于管理无状态应用的资源,用来监控和调度属于他的Pod并将其维持在规定数量

  • 解决的问题:保证指定数量的 Pod 副本一直运行,支持滚动更新、回滚。
  • 适用:Web 服务、API 等无状态应用(任意 Pod 可互换)。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 3 # 我要3个完全相同的Pod
selector:
matchLabels:
app: nginx # 管理带这个标签的Pod
template: # Pod模板
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25

Deployment 会创建 ReplicaSet,ReplicaSet 再来确保 Pod 数量。我们一般直接操作 Deployment

滚动更新策略示例:

Deployment 的一大优势是支持滚动更新——逐步替换旧版本 Pod,避免服务中断。你可以在 Deployment 中配置更新策略:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 5
strategy:
type: RollingUpdate # 滚动更新
rollingUpdate:
maxSurge: 2 # 最多临时多出 2 个 Pod
maxUnavailable: 1 # 最多允许 1 个 Pod 不可用
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.26 # 改镜像版本就会触发滚动更新

更新过程:先起 2 个新版 Pod → 等它们 Ready → 停 1 个旧版 Pod → 循环直到全换完。整个过程服务不中断。

回滚操作:

Terminal window
# 查看历史版本
kubectl rollout history deployment/nginx-deploy
# 发现新版本有问题,一键回滚到上一个版本
kubectl rollout undo deployment/nginx-deploy
# 回滚到指定版本
kubectl rollout undo deployment/nginx-deploy --to-revision=2

2.3 StatefulSet — 有状态应用集#

数据库存储 这样的服务要依赖于稳定的数据源,并且要对外提供稳定的访问入口点,这样的服务就是有状态的

  • 是什么:当你需要稳定的网络标识(固定的 Pod 名称如 mysql-0,mysql-1)和稳定的持久化存储(每个 Pod 绑自己的硬盘)时使用。
  • 适用:数据库(MySQL、MongoDB)、消息队列等。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 必须有一个无头Service,后面讲
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # 为每个Pod自动创建独立PVC
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi

每个 Pod 有稳定名字和专属硬盘,重启/迁移后数据不丢

StatefulSet 依赖的 Headless Service:

上面 StatefulSet 的 serviceName: mysql 对应一个 Headless Service(无头服务),它不分配 ClusterIP,而是为每个 Pod 提供固定的 DNS 记录:

apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
clusterIP: None # 关键:None 表示 "无头"
selector:
app: mysql
ports:
- port: 3306

有了这个 Service,mysql-0.mysql 永远指向第 0 号 Pod,mysql-1.mysql 永远指向第 1 号 Pod——Pod 就算重启、漂移,DNS 名字也不变。这就是有状态服务的基石。

2.4 DaemonSet — 守护进程#

这个可以理解成一个在每个节点上都跑的Pod

  • 是什么:确保集群中每个(或某些)节点上都运行一个 Pod。
  • 典型用途:日志收集器(Fluentd)、监控代理(Node Exporter)、CNI 网络插件。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
spec:
selector:
matchLabels:
name: node-exporter
template:
metadata:
labels:
name: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter

默认情况下 DaemonSet 只会在工作节点上部署 Pod。如果你的集群只有 master/control-plane 节点,需要加上 toleration 才能让 Pod 调度上去:

spec:
template:
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: node-exporter
image: prom/node-exporter

另一个常用配置:通过 nodeSelector 限制只在某些节点上跑。比如你有一批带 SSD 的节点,想只在上面跑日志收集:nodeSelector: disktype: ssd

2.5 Job 与 CronJob — 一次性任务和定时任务#

  • Job:运行一个或多个 Pod,成功完成就结束。适用于数据迁移、模型训练等。
  • CronJob:在指定时间(Cron 表达式)创建 Job,像 Linux 的 crontab。
apiVersion: batch/v1
kind: Job
metadata:
name: data-import
spec:
template:
spec:
containers:
- name: import
image: my-import-tool
restartPolicy: Never # 失败不重启
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-cleanup
spec:
schedule: "0 3 * * *" # 每天凌晨3点
jobTemplate:
spec:
template:
spec:
containers:
- name: cleanup
image: cleanup-tool
restartPolicy: Never

刚刚我说的哪个 Init 容器可以看作是一个 Job , 在Pod被创建时执行

2.6 Service — 服务的访问入口#

Pod 随时都会被调度,被销毁,被创建,没有稳定的访问入口,Service 正是用来解决这个问题

四种类型

  • ClusterIP(默认):仅集群内部访问。
  • NodePort:在每个节点上开一个端口,<节点IP>:<端口> 即可访问。
  • LoadBalancer(贵):调用云厂商的负载均衡器,对外暴露服务。
  • ExternalName(没用过):将服务映射到外部域名。
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
type: ClusterIP
selector:
app: nginx # 流量发给带此标签的Pod
ports:
- port: 80 # Service的端口
targetPort: 80 # 容器的端口

NodePort 示例——适合开发/调试,直接通过节点 IP 访问:

apiVersion: v1
kind: Service
metadata:
name: nginx-nodeport
spec:
type: NodePort
selector:
app: nginx
ports:
- port: 80
targetPort: 80
nodePort: 30080 # 不指定也会自动分配 30000-32767 间的端口

创建后,任意节点 IP + :30080 都能访问到 nginx。访问路径:<你的IP>:30080 → Service → nginx Pod:80

LoadBalancer 示例——生产环境对外暴露(云厂商会分配公网 IP):

apiVersion: v1
kind: Service
metadata:
name: nginx-lb
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- port: 443
targetPort: 80

创建后 kubectl get svc nginx-lb 可以看到 EXTERNAL-IP,那就是你的公网入口。注意:LoadBalancer 通常要花钱,本地 minikube/kind 可以用 minikube tunnel 模拟。

2.7 Ingress — 反向代理#

Service 只做四层负载均衡,Ingress 是七层(HTTP/HTTPS),可基于域名、路径将流量导到不同 Service

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
spec:
rules:
- host: www.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80

需要安装 Ingress Controller(如 treafik)才能工作。

配置 HTTPS/TLS:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress-tls
spec:
tls:
- hosts:
- www.example.com
secretName: example-tls-secret # TLS 证书存在 Secret 里
rules:
- host: www.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80

TLS Secret 可以从证书文件创建kubectl create secret tls example-tls-secret --cert=cert.pem --key=key.pem

IngressClass:如果集群有多个 Ingress Controller(如同时装了 traefik 和 nginx-ingress),需要在 Ingress 中通过 ingressClassName 字段指定用哪一个,否则可能不工作。

2.8 ConfigMap 和 Secret — 配置管理#

  • ConfigMap:存明文配置,如应用参数、环境变量。
  • Secret:存敏感信息,如密码、令牌。内容是 Base64 编码的,不是加密,使用时需配合 RBAC 等权限控制。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
database_url: "db.internal:3306"
log_level: "debug"
---
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: dXNlcg== # base64编码的"user"
password: cGFzc3dvcmQ= # "password"

在 Pod 中使用 ConfigMap 和 Secret 的三种方式:

方式一:注入为环境变量(整批导入)

apiVersion: v1
kind: Pod
metadata:
name: app-env-demo
spec:
containers:
- name: app
image: my-app:latest
envFrom:
- configMapRef:
name: app-config # ConfigMap 中所有 key 变成环境变量
- secretRef:
name: db-credentials # Secret 中所有 key 变成环境变量

Pod 启动后,容器里就能直接拿到 $database_url$log_level$username$password 这些环境变量了。

方式二:注入为环境变量(逐个挑选)

有时候你不想把所有 key 都导进去,可以只选你需要的:

spec:
containers:
- name: app
image: my-app:latest
env:
- name: DB_URL
valueFrom:
configMapKeyRef:
name: app-config
key: database_url # 只取这一个 key
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password # 只取这一个 key

方式三:挂载为文件

ConfigMap/Secret 里的每个 key 会变成容器里的一个文件,文件名就是 key,文件内容就是 value:

apiVersion: v1
kind: Pod
metadata:
name: app-file-demo
spec:
containers:
- name: app
image: my-app:latest
volumeMounts:
- name: config-volume
mountPath: /etc/app/config # ConfigMap 内容出现在这个目录
readOnly: true
- name: secret-volume
mountPath: /etc/app/secrets # Secret 内容出现在这个目录
readOnly: true
volumes:
- name: config-volume
configMap:
name: app-config
- name: secret-volume
secret:
secretName: db-credentials

挂载后,cat /etc/app/config/database_url 输出 db.internal:3306cat /etc/app/secrets/password 输出解码后的密码。应用无需重启就能感知 ConfigMap 的更新(kubelet 会定期同步,但有延迟)。

从命令行快速创建:

Terminal window
# 从字面量创建 ConfigMap
kubectl create configmap app-config \
--from-literal=database_url=db.internal:3306 \
--from-literal=log_level=debug
# 从文件创建 ConfigMap
kubectl create configmap nginx-conf --from-file=nginx.conf
# 从字面量创建 Secret
kubectl create secret generic db-credentials \
--from-literal=username=user \
--from-literal=password=s3cret!

2.9 Volume、PV 与 PVC — 持久化存储#

PV 是 Node 上事先准备好的存储资源,PVC 是 Pod 对这些资源的请求凭证

  • Volume:Pod 内的临时存储(Pod 在数据在,Pod 删数据没)。
  • PersistentVolume(PV):管理员提前准备好的存储资源(一块云硬盘、NFS共享等),独立于 Pod 存在。
  • PersistentVolumeClaim(PVC):你(用户)对存储的需求声明,例如“我要 5Gi 的读写一次的磁盘”,K8s 会自动匹配或通过 StorageClass 动态创建 PV。

2.9.1 Volume — Pod 级,不是 K3s 内的资源类型#

Volume 就是挂载到 Pod 内的一个目录(或文件)。它解决了同一 Pod 内多个容器共享文件的问题,但它的生命周期基本等于 Pod

  • Pod 删,Volume 就没了(除非你用了特殊的底层卷,如云盘)。
  • 它是直接定义在 Pod spec.volumes 里的,不需要另外创建资源。

常见的内置 Volume 类型:

  • emptyDir:Pod 创建时生成一个空目录,Pod 删除就清空。适合缓存/临时文件。
  • hostPath:把宿主机(Node)上的一个目录挂进去。Pod 漂移到别的 Node 就找不到原数据了,一般只给 DaemonSet 日志采集等用。
  • configMap / secret:把配置或密钥“投射”成文件,只读。
  • 云厂商类型(awsElasticBlockStore、gcePersistentDisk):可把云硬盘挂入 Pod,这样数据独立于 Pod。但由于每个 Pod 都要写死云盘 ID,太僵化,基本不这么用了。

举个 emptyDir 例子,你可以在 Pod 里这样写:

apiVersion: v1
kind: Pod
metadata:
name: temp-demo
spec:
containers:
- name: writer
image: busybox
command: ['sh', '-c', 'echo hello > /data/message && sleep 3600']
volumeMounts:
- name: cache-vol
mountPath: /data
volumes:
- name: cache-vol
emptyDir: {}

Volume 的局限:数据随着 Pod 消亡而消失,无法应对数据库、持久化日志等需求。所以我们需要独立于 Pod 存在的存储对象

2.9.2 PV 和 PVC — 供给侧 和 需求侧#

Kubernetes 将存储资源抽象成一对核心概念:**PersistentVolume(PV)**和 PersistentVolumeClaim(PVC),分别代表存储的“供给方”和“需求方”。这就像管理员提前挖了几个水池(PV),用户拿着水票(PVC)去认领

2.9.3 PersistentVolume(PV)— 集群级存储资源#

  • 是什么:集群管理员预先准备好的一块存储,比如云厂商的一块云硬盘、NFS 的一个共享目录、Ceph 的一个 RBD 块设备
  • 独立于任何 Pod 存在,生命周期完全独立。即使没有 Pod 使用,PV 也依然存在,呃,除非你和我一样不小心kubectl delete -f .
  • 是集群范围的对象(不属于某个 Namespace)

示例 PV 定义(使用 hostPath,适合本地测试):

apiVersion: v1
kind: PersistentVolume
metadata:
name: my-local-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath: # 使用宿主机上的目录
path: "/mnt/data"

⚠️ hostPath 只适合单节点/测试环境:Pod 一旦漂移到其他节点就找不到数据了。生产环境请用下面的 NFS 或云盘。

示例 PV 定义(使用 NFS,适合生产):

apiVersion: v1
kind: PersistentVolume
metadata:
name: my-nfs-pv
spec:
capacity:
storage: 10Gi # 存储容量
accessModes:
- ReadWriteMany # NFS 支持多节点同时读写
persistentVolumeReclaimPolicy: Retain
nfs: # 实际的存储后端
server: 192.168.1.100
path: "/exports/data"

这个 PV 告诉 Kubernetes:我这儿有个 10GB 的 NFS 共享,多节点读写,不用的时候别删。

下一节将介绍怎么用 StorageClass 配合 NFS Provider 实现存储自动配给

2.9.4 PersistentVolumeClaim(PVC)— 用户的“需求单”#

  • 是什么:你作为用户(开发者)不需要关心底层具体用什么存储,只需要声明你需要多大、什么访问模式的存储。
  • 属于某个 Namespace

示例 PVC 定义:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce # 我要求单节点读写
resources:
requests:
storage: 5Gi # 我只要 5GB

一旦你创建这个 PVC,Kubernetes 会在已有的 PV 中自动匹配一个符合要求(容量 ≥ 5Gi,访问模式匹配)的 PV,并将两者绑定。绑定后,这个 PV 就专属于你的 PVC,其他 PVC 不能再用了

2.9.5 绑定机制与生命周期#

  1. 静态供给:管理员手动创建了一堆 PV,用户在 Namespace 里创建 PVC,Kubernetes 寻找 容量足够、访问模式匹配、存储类一致 的 PV 绑定。
  2. 绑定后,PV 的状态变成 Bound,PVC 状态也是 Bound
  3. 当 PVC 被删除时,PV 怎么处理,由 persistentVolumeReclaimPolicy 决定:
    • Retain(保留):PV 变为 Released 状态,但数据还在,需要管理员手动清理并让 PV 重新可用。
    • Delete(删除):PV 底层的存储资源(如云盘)也会被删除,数据彻底消失。
    • Recycle(回收):基本废弃,就是执行 rm -rf 清空数据。

2.9.6 在 Pod 里使用 PVC#

Pod 不需要知道底层的云盘或 NFS,只需引用 PVC

apiVersion: v1
kind: Pod
metadata:
name: app-with-storage
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: persistent-storage
mountPath: /usr/share/nginx/html
volumes:
- name: persistent-storage
persistentVolumeClaim:
claimName: my-pvc # 就是刚才声明的 PVC 名字

现在 Pod 不管漂移到哪个节点,挂载的都是同一块 PV 存储,数据不会因为 Pod 重启而丢失

2.9.7 访问模式(Access Modes)与使用场景速查#

模式缩写含义典型存储后端
ReadWriteOnceRWO只能被一个节点以读写方式挂载云盘(块存储)
ReadOnlyManyROX可以被多个节点只读挂载无实际产品常用
ReadWriteManyRWX可以被多个节点同时读写挂载NFS、CephFS、GlusterFS 等文件系统

大部分数据库(MySQL、PostgreSQL)用 RWO 块存储,性能好。多个 Web 容器要共享上传文件时,才需要 RWX 文件系统

ReadWriteMany 在某些场景下会引发脑裂,视情况使用

2.10 StorageClass — 动态存储供给#

手动创建 PV 太累了:每来一个新应用需求,管理员就得提前去云平台创建一块云盘,再写成 PV。StorageClass 把这个过程自动化了,它定义了 ”什么样的存储类型” 以及 ”创建这种存储时该怎么做”

StorageClass 是”存储模板”,PVC 引用它就能自动创建 PV:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs # 存储驱动(这里以 AWS 为例)
parameters:
type: gp3 # 驱动特有参数:SSD 类型
iops: “3000”
reclaimPolicy: Delete # PVC 删除时自动删 PV
volumeBindingMode: WaitForFirstConsumer # 等 Pod 真正要用了再创建
allowVolumeExpansion: true # 允许在线扩容

provisioner 决定了用谁家的存储:kubernetes.io/aws-ebs 是 AWS、kubernetes.io/gce-pd 是 Google Cloud、nfs.csi.k8s.io 是 NFS CSI 驱动。本地测试可以用 rancher.io/local-path(k3s 自带)或 k8s.io/minikube-hostpath

StorageClass 自动工作的全过程:

flowchart LR A[创建 PVC<br/>指定 storageClassName] --> B[StorageClass<br/>读取 provisioner 参数] B --> C[Provisioner<br/>自动创建云盘/NFS] C --> D[自动创建 PV<br/>并绑定到你的 PVC] D --> E[Pod 挂载 PVC<br/>直接使用存储]

没有 StorageClass 之前,你需要:登录云平台 → 创建云盘 → 记下 ID → 写 PV YAML → kubectl apply。有了 StorageClass 之后,只需要写一个 PVC,创建时带上 storageClassName,剩下的全自动完成。

如何与 PVC 配合使用

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: fast-ssd # 指定 StorageClass 名字

在 PVC 中通过 storageClassName 指定即可。注意:如果 PVC 不指定 storageClassName,会使用集群默认的 StorageClass(kubectl get storageclass 看哪个后面有 (default))。

常见 Provisioner 速查:

环境provisioner备注
AWS EBSkubernetes.io/aws-ebs块存储,RWO
Google Cloudkubernetes.io/gce-pd块存储,RWO
Azurekubernetes.io/azure-disk块存储,RWO
NFS(通过 CSI)nfs.csi.k8s.io文件系统,RWX
k3s 默认rancher.io/local-path本地路径,适合开发/测试
minikubek8s.io/minikube-hostpath本地路径,适合开发/测试
CephFScephfs.csi.ceph.com分布式文件系统,RWX

2.11 Namespace — 资源隔离#

将资源逻辑分组,实现多租户隔离、资源配额限制、避免名字冲突

Terminal window
kubectl create namespace dev
kubectl get pods -n dev

2.12 ResourceQuota 与 LimitRange — 资源限制#

  • ResourceQuota:限制某个 Namespace 能使用的 CPU/内存总量、对象数量。
  • LimitRange:限制该 Namespace 内单个 Pod 或容器的默认和边界资源。
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: dev
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
pods: "20"

ResourceQuota 管的是 整个 Namespace 的总量上限。如果某个 Pod 没声明自己的资源需求,它可以”偷”用很多资源。这时候就需要 LimitRange 来约束单个 Pod。

LimitRange——限制该 Namespace 内单个 Pod/容器的资源边界:

apiVersion: v1
kind: LimitRange
metadata:
name: dev-limits
namespace: dev
spec:
limits:
- type: Container
default: # 不声明时的默认值
cpu: "500m"
memory: "256Mi"
defaultRequest: # 不声明 request 时的默认 request
cpu: "200m"
memory: "128Mi"
max: # 单个容器能设的最大值(防流氓 Pod)
cpu: "2"
memory: "2Gi"
min: # 单个容器的最小值
cpu: "100m"
memory: "64Mi"

ResourceQuota + LimitRange 配合:前者管总量(Namespace 级别),后者管单体(Pod/容器级别),两者搭配才能真正管住资源。

2.13 [ ⭐ ] HPA(HorizontalPodAutoscaler)— 自动扩缩容#

根据 CPU 使用率(或自定义指标)自动调整 Deployment/StatefulSet 的副本数

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: nginx-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: nginx-deploy
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70

需要安装 Metrics Server 来收集指标才能使用

HPA 的工作原理:HPA 每隔 15 秒(默认)检查目标 Pod 的 CPU/内存指标,如果实际使用率超过目标值(如上面的 70%),就增加副本;低于目标值,就减少副本。

用命令行直接创建更方便:

Terminal window
# 基于 CPU 自动伸缩,2-10 个副本,目标 CPU 利用率 70%
kubectl autoscale deployment nginx-deploy --cpu-percent=70 --min=2 --max=10

基于内存的 HPA 示例:

metrics:
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80

⚠️ 注意:CPU 弹性伸缩和内存弹性伸缩是独立配置的,可以同时使用。但内存不像 CPU 那样会被释放——进程用过的内存通常不会主动还给 OS,所以基于内存的 HPA 要谨慎使用,容易误触发扩容。

HPA+同时限制资源,推荐做法:

# Deployment 中给 Pod 设定 requests 和 limits
containers:
- name: nginx
image: nginx:1.25
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"

HPA 的 averageUtilization 百分比是基于 requests 计算的,不是 limits。比如 requests.cpu=200m,实际用到 150m,利用率就是 75%。不设置 resources.requests 会数据不准,建议必配

2.14 ServiceAccount — Pod 的”身份证”#

Pod 通过 ServiceAccount 访问 K8s API,每个 Namespace 有默认的,可自建

apiVersion: v1
kind: ServiceAccount
metadata:
name: my-sa
namespace: dev

Pod 里指定 serviceAccountName: my-sa 即可

ServiceAccount 和 RBAC 配合使用示例:

ServiceAccount 本身只是一个身份,需要配合 RBAC(下一节)才能真正”做事”。完整的流程是:

  1. 创建 ServiceAccount
  2. 创建 Role 定义权限
  3. 用 RoleBinding 把 SA 和 Role 绑在一起
  4. Pod 使用这个 SA
apiVersion: v1
kind: Pod
metadata:
name: api-client
spec:
serviceAccountName: my-sa # 使用自定义 SA
containers:
- name: client
image: curlimages/curl
command: ['sleep', '3600']

这个 Pod 启动后会自动挂载 SA 的 token(在 /var/run/secrets/kubernetes.io/serviceaccount/token),用这个 token 就能访问 K8s API 了。验证:kubectl exec -it api-client -- curl -k https://kubernetes.default.svc

2.15 RBAC — 谁能做什么#

  • Role / ClusterRole:定义一组权限(能对哪些资源做什么操作)。
  • RoleBinding / ClusterRoleBinding:把权限授予用户、组或 ServiceAccount。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: dev
subjects:
- kind: ServiceAccount
name: my-sa
namespace: dev
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

2.16 NetworkPolicy — 网络防火墙#

定义 Pod 之间、Pod 与外部之间的访问规则(需要 CNI 插件支持,如 Calico)

⚠️ 重要前提:Kubernetes 默认允许所有 Pod 之间通信。只有当你创建了 NetworkPolicy 后,被选中的 Pod 才会受限——且是”默认拒绝,显式允许”模式。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
spec:
podSelector: # 这个策略作用在哪些 Pod 上?
matchLabels:
role: db # 选中带 role=db 标签的 Pod
policyTypes:
- Ingress # 控制入站流量(别人访问数据库)
ingress:
- from:
- podSelector: # 允许哪些 Pod 进来?
matchLabels:
role: frontend # 只有带 role=frontend 标签的 Pod
ports:
- protocol: TCP
port: 3306 # 而且只能访问 3306 端口

翻译成人话:只有 frontend Pod 可以通过 TCP 3306 端口访问数据库 Pod,其他一律拒绝。

更实用的示例——拒绝所有入站,只允许特定来源:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-isolation
spec:
podSelector:
matchLabels:
app: mysql
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector: # 允许某个 Namespace 下所有 Pod
matchLabels:
kubernetes.io/metadata.name: backend
- ipBlock: # 允许某个 IP 段
cidr: 10.0.0.0/24
ports: # ports 属于这个 ingress 规则
- protocol: TCP
port: 3306

建议:生产环境中,数据库的 NetworkPolicy 应该只放行业务 Pod 所在的 Namespace + 运维跳板机的 IP 段,形成最小权限访问。

2.17 自定义资源(CRD)— 让 K8s 懂得你的业务#

如果上面所有原生资源都满足不了你,可以通过 CustomResourceDefinition 定义自己的资源类型,然后用 Operator 模式去管理。

什么时候需要 CRD? 假设你想用 K8s 管理数据库用户——原生资源只有 Pod、Service 等,没有”数据库用户”这个概念。这时你可以定义一个 CRD:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databaseusers.example.com # 格式:<复数名>.<组名>
spec:
group: example.com # API 组
names:
kind: DatabaseUser # 资源类型名(驼峰)
plural: databaseusers # 复数,会出现在 URL 路径
singular: databaseuser # 单数
shortNames:
- dbu # kubectl get dbu 就能查到
scope: Namespaced # Namespace 级别
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
username:
type: string
database:
type: string
privileges:
type: array
items:
type: string
enum: [SELECT, INSERT, UPDATE, DELETE]
required: [username, database]

创建这个 CRD 后,就可以 kubectl apply -f 一个 kind: DatabaseUser 的 YAML 了。配合 Operator(一个持续监控 CRD 的控制器),就能在创建 DatabaseUser 时自动去 MySQL 里建用户。

使用自定义资源的示例:

apiVersion: example.com/v1
kind: DatabaseUser
metadata:
name: app-reader
spec:
username: reader
database: myapp
privileges:
- SELECT

K8s 原生不懂 DatabaseUser 是什么,但 Operator 会 Watch 这个资源的创建/更新/删除事件,然后自动操作 MySQL。这就是 K8s 扩展性的核心——你可以让 K8s 管理任何东西

常用的 Operator 例子:Prometheus Operator(自动管理监控)、cert-manager(自动管理证书)、Istio(服务网格)。它们都依赖 CRD。

3. 总结#

拿一个典型的 Web 应用举例

  1. Namespace 创建一个独立的 prod 空间。
  2. ConfigMap/Secret 存好数据库地址和密码。
  3. PersistentVolumeClaim 申请一块云硬盘。
  4. StatefulSet 启动 MySQL,挂载 PVC,保证数据持久。
  5. Deployment 启动无状态的业务 Pod,注入配置。
  6. Service 分别给 MySQL 和业务 Pod 提供固定访问端点。
  7. Ingress 对外暴露域名,把流量指向业务 Service。
  8. HPA 监控业务 Pod CPU,自动加减副本。
  9. NetworkPolicy 限制只有业务 Pod 能访问数据库。

你可以用 kubectl api-resources 随时查看集群支持的所有资源类型。刚学习时重点掌握 Pod、Deployment、Service、ConfigMap、Secret、PVC、IngressNamespace,基本就能跑起大部分应用了。

4. 结尾#

没了喵,谢谢欣赏

gopher
gopher

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

K8s 云原生技术 2
https://blog.tuf3i.cc/posts/k8s-learning-2/
作者
TuF3i
发布于
2026-06-20
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
TuF3i
一只区,什么也不想写
分类
标签
站点统计
文章
9
分类
7
标签
13
总字数
26,233
运行时长
0
最后活动
0 天前

目录