云原生学习04-服务、网络与存储资源

时隔多日,终于开始学习其他资源类型了,又摆了好几天,好舒服

服务网络资源

Service

Service 是 Kubernetes 中的一种抽象资源,用于为一组 Pod 提供稳定的访问入口。

为什么需要 Service?

  • Pod 是临时的(重启、扩缩容后 IP 会变)
  • Service 提供一个固定的 ClusterIP 和 DNS 名称
  • 通过 selector 自动发现后端 Pod,并通过 Endpoints / EndpointSlice 维护实时列表
  • 内置负载均衡(iptables / IPVS 模式)

K8s 集群内的 DNS 解析规则

(以 Service my-app-service、命名空间 default 为例):

场景 访问形式 示例
同命名空间 ${service-name} my-app-service
不同命名空间 ${service-name}.${namespace} my-app-service.default
全限定域名 (FQDN) ${service-name}.${namespace}.svc.cluster.local my-app-service.default.svc.cluster.local

spec.type

类型 说明 访问方式
ClusterIP(默认) 仅集群内部访问 ClusterIP + DNS
NodePort 在每个节点开一个端口 <NodeIP>:<NodePort>,端口范围默认 30000–32767
LoadBalancer 云厂商提供外部 LB 外部 IP
ExternalName 映射到外部 DNS 名称(CNAME) 无 selector,返回 CNAME

基础yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
type: ClusterIP # 可省略,默认类型
selector:
app: nginx # 选择拥有 app=my-app 标签的 Pod
ports:
- name: http # 端口名称
port: 80 # Service 端口
targetPort: 80 # Pod 端口
protocol: TCP # 协议

常用排查命令

1
2
3
4
5
kubectl get svc                          # 查看 Service
kubectl describe svc my-app-service # 查看详情(含 Endpoints)
kubectl get endpoints my-app-service # 确认后端 Pod
kubectl get endpointslices # 新版 Endpoints 资源
kubectl get pods -l app=nginx # 确认 selector 匹配

headless service

Headless Service 是一种特殊的 Service,它不为 Pod 提供负载均衡,而是直接将请求转发到后端的 Pod。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: headless-service
spec:
type: ClusterIP # 可省略,默认类型
clusterIP: None #与普通service的区别
selector:
app: nginx # 选择拥有 app=nginx 标签的 Pod
ports:
- name: http # 端口名称
port: 90 # Service 端口, 在headless中,port字段是不生效的
targetPort: 80 # Pod 端口
protocol: TCP # 协议

存储资源

volume

Volume 是 Kubernetes 中用于解决容器内数据存储与共享的资源对象。它直接挂载到 Pod 上,生命周期通常与 Pod 绑定,核心作用是让容器内的数据在容器重启后不丢失,或者让同一个 Pod 内的多个容器能够共享文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deploy
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: test-container
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- name: cache-volume
mountPath: /cache
- name: hostpath-volume
mountPath: /hostpath
volumes:
- name: cache-volume
emptyDir: {} # 临时空目录,Pod 删除时清理
- name: hostpath-volume
hostPath:
path: / # 节点上的目录
type: DirectoryOrCreate # 目录不存在时创建

常用的有两种类型

1
2
- name: cache-volume
emptyDir: {} # 临时空目录,Pod 删除时清理

临时空目录,和pod共存亡,删除pod后里面的内容也全部都删除

1
2
3
4
- name: hostpath-volume
hostPath:
path: / # 节点上的目录
type: DirectoryOrCreate # 目录不存在时创建

与宿主机共用目录,在任何一方的修改都会一起显示

configmap

如果把配置(数据库连接串、日志级别、功能开关)写死在代码或镜像里,那么开发、测试、生产环境就得各打一个镜像,改一次配置就要走一遍构建发布流程,既慢又容易出错。ConfigMap 让配置变成独立的 K8s 资源,镜像保持纯净,部署时把配置“注入”进去即可。

ConfigMap 是 Kubernetes 中用来存储非机密配置数据的 API 对象,以键值对形式保存,目的是把配置信息从容器镜像中解耦出来。它和你之前了解的 Volume 有直接配合关系:ConfigMap 可以被“投射”成 Volume 里的文件,供 Pod 读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
# 键值对形式
name: zhangsan
mode: production

# 文件形式
config.json: |
{
"database": {
"host": "localhost",
"port": 5432
}
}

如何使用

1. 注入为环境变量(单个键)

1
2
3
4
5
6
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL

2. 批量注入为环境变量(所有键)

1
2
3
envFrom:
- configMapRef:
name: app-config

3. 挂载为文件(Volume)

如果想要实时更新不建议采用这种方法,因为挂在为文件存在时延

1
2
3
4
5
6
7
8
9
volumes:
- name: config-vol
configMap:
name: app-config
containers:
- name: app
volumeMounts:
- name: config-vol
mountPath: /etc/config

Secret

与ConfigMap在用法上几乎一模一样,区别在于存储的键值的value为密文存储(base64编码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: ZGIxdXNlcg== # db1user
password: cGFzc3dvcmQxMjM= # password123
host: ZGIuZXhhbXBsZS5jb20= # db.example.com
port: MzMwNg== # 3306
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
spec:
replicas: 1
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: webapp
image: nginx:latest
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
envFrom:
- secretRef:
name: db-credentials
volumeMounts:
- name: db-credentials
mountPath: /etc/db-credentials
readOnly: true

volumes:
- name: db-credentials
secret:
secretName: db-credentials

几种常见的 Secret 类型

type 用途
Opaque 通用,默认类型,存任意键值对
kubernetes.io/dockerconfigjson 拉私有镜像仓库的凭证(imagePullSecrets)
kubernetes.io/tls TLS 证书 + 私钥(Ingress 用)
kubernetes.io/service-account-token ServiceAccount 的 token
kubernetes.io/basic-auth / ssh-auth 基础认证 / SSH 密钥

如何使用

前三种和configmap一样,额外多了一种

拉私有镜像

1
2
3
spec:
imagePullSecrets:
- name: regcred

和相邻概念的关系

资源 存什么 特点
ConfigMap 非机密配置 明文,可挂文件或环境变量
Secret 密码、token、证书 base64 编码(非加密),用法和 ConfigMap 几乎一样
Volume 持久化数据 ConfigMap 可以作为 Volume 的一种来源

pv-pvc

Pod 默认是无状态的,容器里的文件随 Pod 删除而消失。如果数据库、日志、上传文件要持久保存,就需要把存储挂进 Pod。但直接让 Pod 写“用某个 NFS 目录”有两个问题:一是不同环境存储实现不同(本地盘、EBS、NFS、Ceph),二是应用开发者不该关心底层用什么存储。

于是 K8s 把存储拆成两层:集群管理员准备 PV(提供方),应用声明 PVC(使用方),由 K8s 把两者匹配起来。这就是“存储与计算解耦”。

PV(PersistentVolume)和 PVC(PersistentVolumeClaim)是 Kubernetes 解决持久化存储的一对核心概念。理解它们的关键在于一个比喻:PV 是“仓库里的储物柜”,PVC 是“你要租储物柜的申请单”。

1280X1280

pv

PV 是集群管理员视角的资源,代表一块真实的、可用的存储。它不属于任何命名空间,是集群级对象。它描述“这里有一块 100Gi 的 EBS 卷,读写模式是 ReadWriteOnce”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-ebs-001
spec:
capacity: #容量
storage: 100Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: standard
awsElasticBlockStore: # 具体存储实现
volumeID: vol-0abc123
fsType: ext4
  • capacity:容量
  • accessModes:访问模式(下面详述)
  • persistentVolumeReclaimPolicy:回收策略,Retain(保留)、Delete(删除)、Recycle(已废弃)
  • storageClassName:存储类,用于和 PVC 匹配
  • 存储实现:awsElasticBlockStore、nfs、hostPath、csi 等

pvc

PVC 是应用开发者视角的资源,属于某个命名空间。开发者不关心底层是什么存储,只声明“我要 50Gi、可读写、用 standard 这个类”。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Gi
storageClassName: standard

绑定过程

K8s 会找一个满足 PVC 要求的 PV,把两者绑定(Bind)。绑定后:

  • PVC 状态从 Pending 变成 Bound
  • Pod 通过 PVC 名引用存储,不直接碰 PV
  • 一个 PV 只能绑定一个 PVC,一对一

匹配条件包括:容量 ≥ 请求、accessModes 兼容、storageClassName 相同。如果找不到,PVC 一直 Pending。

两种供给模式:静态 vs 动态

静态供给:管理员手动创建 PV,PVC 来匹配。适合存储资源有限、需要精细控制的场景,但管理麻烦。

动态供给(更常用):管理员只创建 StorageClass(存储类),不手动建 PV。PVC 声明 storageClassName 后,K8s 通过 CSI 驱动自动调用云 API 创建一块存储(比如在 AWS 上自动建一个 EBS 卷),并自动生成对应的 PV。PVC 删除时按回收策略自动清理。

访问模式(accessModes)

这是常混淆的点,它描述的是挂载能力,不是文件系统权限:

模式 缩写 含义
ReadWriteOnce RWO 只能被单个节点读写挂载(同节点多 Pod 可共享)
ReadOnlyMany ROX 多个节点只读挂载
ReadWriteMany RWX 多个节点读写挂载(需要 NFS/CephFS 等支持)
ReadWriteOncePod RWOP 只能被单个 Pod 读写(更严格,1.22+)

StorageClass

StorageClass 是 PV/PVC 体系里“自动造存储的模板”。上一轮提到动态供给,核心就是这个对象:管理员不用手动建一个个 PV,只需定义一个 StorageClass,PVC 一申请,K8s 就按这个模板自动调云 API 创建存储并生成 PV。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com # 用哪个 CSI 驱动
parameters: # 传给驱动的参数
type: gp3
iops: "3000"
throughput: "125"
reclaimPolicy: Delete # 默认 Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true # 允许扩容 PVC
  • provisioner:谁负责创建存储。云上一般是 CSI 驱动,比如 ebs.csi.aws.com、efs.csi.aws.com、pd.csi.storage.gke.io。这是 StorageClass 的灵魂,决定“调用哪家 API”。
  • parameters:驱动专属参数,不同云不同。AWS EBS 可指定 type(gp2/gp3/io2)、iops、encrypted、kmsKeyId 等。
  • reclaimPolicy:PVC 删除后底层存储怎么办。Delete 自动删(动态供给默认),Retain 保留。
  • volumeBindingMode:Immediate(PVC 一创建就建存储)或 WaitForFirstConsumer(等 Pod 调度后再建)。云上几乎都用后者,原因见下。
  • allowVolumeExpansion:是否允许后来扩大 PVC 容量。

pvc如何使用

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd # 指定模板
resources:
requests:
storage: 50Gi

指定后,K8s 就按 fast-ssd 的 provisioner 和 parameters 去创建存储,生成一个 PV 并自动绑定。PVC 和 PV 的 storageClassName 必须一致才能绑。

Contents