k8s基础-Pod入门与实战

1.创建pod

使用 kubectl run 命令创建一个pod:

1
2
3
4
5
6
7
8
# 基本命令格式
kubectl run <pod-name> --image=<image-name> [options]

# 示例:
kubectl run nginx-pod --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
# nginx-pod 是创建pod的名字,所在命名空间唯一
# --image=nginx:1.24.0默认从dockerhub拉取镜像,现在已经无法拉取
# --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0指定地址拉取

利用命令快速导出一个yaml:

1
kubectl run  nginx-pod --image=registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine --dry-run=client -oyaml >nginx-pod.yaml

通过yaml文件定义pod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#一个pod如果管理多个容器,多个容器之间的端口不能相同,容器的名字也不能一样
cat nginx-pod.yaml
apiVersion: v1 #pod 的apiVersion版本号是V1,不同资源所属的apiVersion组是不一样的,可以使用kubectl api-resources查看
kind: Pod #指定要创建资源的类型是Pod,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #定义pod的元数据,可以包含名称、标签、注释等
labels: #标签
run: nginx-pod #key=value的格式,可以根据标签筛选过滤出不同的pod
name: nginx-pod # 指定pod的名字是nginx-run,pod的名字要唯一
spec: #必选,定义pod的详细信息
containers: #定义 Pod 里运行的全部容器,数组形式,可以配置一个或多个容器
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0 #指定容器的镜像
name: nginx #指定容器的名字
ports: #可选,声明容器暴露的端口号,可以有个多。
- containerPort: 80 #声明容器内部监听的端口号是80(必选)
protocol: TCP #协议类型,默认是 TCP,也可以是 UDP 或 SCTP (可选)
name: http # 端口的名称,便于在 Service 等资源中引用(可选)
- containerPort: 443
protocol: TCP
name: https

#提示:如果在创建资源时忘记该资源对应的apiVersion是什么,可以使用kubectl api-resources查看
#kubectl api-resources #查看所有资源对应的apiVersion
#kubectl api-resources|grep pod #查看pod对应的apiVersion,使用|grep过滤要找到的资源

通过yaml创建pod:

1
kubectl create -f pod.yaml

查看创建的pod:

1
2
3
[root@k8s-master01 10-pod]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-pod 1/1 Running 0 63s

2.一个pod多个容器

通过yaml文件定义一个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
cat pod.yaml
# 下面是pod.yaml文件中的配置
apiVersion: v1 #pod 的apiVersion版本号是V1,不同资源所属的apiVersion组是不一样的,可以使用kubectl api-resources查看
kind: Pod #指定要创建资源的类型是Pod,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #定义pod的元数据,可以包含名称、标签、注释等
labels: #标签
run: nginx-redis #key=value的格式,可以根据标签筛选过滤出不同的pod
name: nginx-redis-pod # 指定pod的名字是nginx-redis-pod ,名字要唯一
spec: #必选,定义pod的详细信息
containers: #定义 Pod 里运行的全部容器,数组形式,可以配置一个或多个容器
- name: nginx #指定容器的名字
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0 #指定容器的镜像
ports: #可选,声明容器暴露的端口号,可以有个多。
- containerPort: 80 #声明容器内部监听的端口号是80(必选)
protocol: TCP #协议类型,默认是 TCP,也可以是 UDP 或 SCTP (可选)
name: http # 端口的名称,便于在 Service 等资源中引用(可选)
- containerPort: 443
protocol: TCP
name: https
- name: redis #指定第二个容器的名字
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/redis:5.0.14 #指定第二个容器的镜像
ports: #可选,声明容器暴露的端口号
- containerPort: 6379 #声明容器内部监听的端口号是6379(必选)
protocol: TCP #协议类型,默认是 TCP,也可以是 UDP 或 SCTP (可选)
name: redis # 端口的名称,便于在 Service 等资源中引用(可选)

创建上述pod:

1
kubectl create -f pod.yaml

查看创建的pod:

1
2
3
4
[root@k8s-master01 10-pod]# kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-redis-pod 2/2 Running 0 25s
# READY 字段中的 2/2 表示: 2/2 = 就绪容器数/总容器数,表示该pod中一共有两个容器,就绪容器数量也是2

查看pod中容器的日志:

如果pod中只有一个容器,直接使用kubectl logs -f [pod的名字]

如果pod中有多个容器,那么就要使用-c指定查看的容器:kubectl logs -f pods/nginx-redis-pod -c nginx kubectl logs -f pods/nginx-redis-pod -c redis;-c参数中的nginx和redis是pod配置中pod.spec.containers.name中定义的容器名字

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
[root@k8s-master01 10-pod]# kubectl logs -f pods/nginx-redis-pod -c nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2025/10/23 12:50:00 [notice] 1#1: using the "epoll" event method
2025/10/23 12:50:00 [notice] 1#1: nginx/1.24.0
2025/10/23 12:50:00 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2025/10/23 12:50:00 [notice] 1#1: OS: Linux 5.14.0-503.15.1.el9_5.x86_64
2025/10/23 12:50:00 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1073741816:1073741816
2025/10/23 12:50:00 [notice] 1#1: start worker processes
2025/10/23 12:50:00 [notice] 1#1: start worker process 30
2025/10/23 12:50:00 [notice] 1#1: start worker process 31
^C
[root@k8s-master01 10-pod]# kubectl logs -f pods/nginx-redis-pod -c redis
1:C 23 Oct 2025 12:50:12.303 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:C 23 Oct 2025 12:50:12.303 # Redis version=5.0.14, bits=64, commit=00000000, modified=0, pid=1, just started
1:C 23 Oct 2025 12:50:12.303 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
1:M 23 Oct 2025 12:50:12.305 * Running mode=standalone, port=6379.
1:M 23 Oct 2025 12:50:12.305 # Server initialized
1:M 23 Oct 2025 12:50:12.305 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
1:M 23 Oct 2025 12:50:12.307 * Ready to accept connections

小技巧:(使用kubectl explain 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
30
31
32
33
34
35
36
[root@k8s-master01 10-pod]# kubectl explain pod
KIND: Pod
VERSION: v1

DESCRIPTION:
Pod is a collection of containers that can run on a host. This resource is
created by clients and scheduled onto hosts.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

metadata <ObjectMeta>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

spec <PodSpec>
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

status <PodStatus>
Most recently observed status of the pod. This data may not be up to date.
Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

#一层一层的往下查看都有哪些参数可以配置,例如:
kubectl explain pod.spec.containers

查看pod状态,默认查看的是default命名空间下的pod,如果要查看其他命名空间下的pod加-n参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master01 10-pod]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-68fb4dc4d4-6cnj9 1/1 Running 1 (9h ago) 31h
nginx-68fb4dc4d4-9c67f 1/1 Running 8 (9h ago) 67d
nginx-68fb4dc4d4-n8wwl 1/1 Running 1 (9h ago) 31h
nginx-deployment-7b778c4d44-5nmfm 1/1 Running 13 (9h ago) 90d
nginx-deployment-7b778c4d44-sk42r 1/1 Running 13 (9h ago) 90d
nginx-redis-pod 2/2 Running 0 25m
rabbitmq-6f8f9b4d4f-c2q5l 1/1 Running 8 (9h ago) 67d
redis-679fcfb58-wtd89 1/1 Running 1 (9h ago) 31h
redis-cli-57cc5fd584-6ndnb 1/1 Running 29 (9h ago) 177d
redis-master-0 1/1 Running 33 (9h ago) 184d
test-nginx-7c589fc9f6-pn8nc 1/1 Running 21 (9h ago) 130d
test-nginx-7c589fc9f6-wbv9k 1/1 Running 21 (9h ago) 130d

查看kube-system命名空间下的pod需要通过-n参数来指定命名空间:

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master01 10-pod]# kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-7d67d4b9c7-78t8t 1/1 Running 56 312d
calico-node-7wmww 1/1 Running 56 (9h ago) 312d
calico-node-88nrk 1/1 Running 54 (9h ago) 312d
calico-node-cmkbl 1/1 Running 54 (99m ago) 312d
calico-node-ft6c4 1/1 Running 54 (9h ago) 312d
calico-node-lxjwd 1/1 Running 55 (9h ago) 312d
calico-node-zn86s 1/1 Running 54 (9h ago) 312d
coredns-6fbc9bdf46-dlrxh 1/1 Running 8 (9h ago) 67d
coredns-6fbc9bdf46-g59vh 1/1 Running 1 (9h ago) 31h
metrics-server-6c79fcf899-9vll2 1/1 Running 1 (9h ago) 31h

获取pod的更多信息使用-o wide参数:

1
2
3
4
5
kubectl get pod -owide
# get表示获取某个资源
# pod表示获取的资源是pod
# -o(或--output)用于指定命令的输出格式
# wide 这是-o参数的一个可选值,表示以宽格式输出。

删除pod:

通过yaml文件删除:kubectl delete -f pod.yaml

通过pod名字删除:kubectl delete pod nginx-redis-pod , nginx-redis-pod是指定要删除pod的名字。

3.pod自定义容器的启动命令和参数

在 Kubernetes 中,commandargs 是用来覆盖容器镜像中的默认命令和参数的。它们都可以在 spec.containers 字段下定义,分别用于设置容器启动时执行的命令和传递给该命令的参数。

  • command:覆盖容器镜像的 ENTRYPOINT
  • args:覆盖容器镜像的 CMD

与 Dockerfile 的对应关系:

Dockerfile Kubernetes 说明
ENTRYPOINT command 主命令
CMD args 命令参数
1
2
3
4
5
6
command:
覆盖容器镜像中的默认命令。它可以是一个单独的命令(如 /bin/bash)或一个完整的命令路径(如 /usr/bin/env)
如果未指定,容器将使用镜像中的默认入口点(ENTRYPOINT)。如果指定了 command,它会覆盖默认的 ENTRYPOINT。
args:
传递给 command 的参数。
如果未指定,容器将使用镜像中的默认参数(CMD)。如果指定了 args,它会覆盖默认的 CMD。
1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master01 pra]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx-run
name: nginx
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
name: nginx-run
command: ["sleep","10"] #覆盖容器ENTRYPOINT启动命令,可选参数

4.为容器分配cpu和内存

在 Kubernetes(简称 K8s)中,Pod 是最小的可部署单元,通常包含一个或多个容器。资源分配是指为 Pod 中的容器指定 CPU、内存等资源的请求(requests)和限制(limits)。这有助于 Kubernetes 调度器合理分配节点资源,避免资源争用,并确保应用稳定运行。如果一个pod中有多个容器,那么需要针对每个容器进行分配的。

4.1为什么需要分配资源?

  • 调度优化:Kubernetes 根据容器的资源请求(requests)决定将 Pod 调度到哪个节点。如果节点资源不足,Pod 可能无法启动。
  • 资源隔离:通过限制(limits),防止单个容器消耗过多资源,导致其他容器或节点崩溃。
  • QoS(服务质量):根据 requests 和 limits 的配置,Pod 被分为 Guaranteed、Burstable 或 BestEffort 三类 QoS 级别,影响优先级和驱逐策略。
    • Guaranteed:requests = limits,所有资源均设置。
    • Burstable:requests < limits 或部分设置。
    • BestEffort:无 requests 和 limits。

如果不分配资源,容器可能被视为 BestEffort,在资源紧张时容易被驱逐。

4.2.资源类型

Kubernetes 支持的主要资源:

  • CPU:单位为“核”(cores),如 1 表示 1 个 CPU 核,0.5 表示半核。也可以用毫核(millicores),如 500m = 0.5 核。
  • 内存:单位为字节,如 Mi(兆字节)、Gi(吉字节),例如 512Mi 或 1Gi。
  • 其他:如 GPU(nvidia.com/gpu)、临时存储(ephemeral-storage)等,但最常见的是 CPU 和内存。

4.3为容器分配资源

资源配置在 Pod 的 YAML 文件中,位于容器的 spec.containers[].resources 字段下。分为:

  • requests:容器启动时保证的最小资源。如果节点无法满足,Pod 不会调度。
  • limits:容器可使用的最大资源上限。如果超过,CPU 会节流,内存可能导致 OOM(Out of Memory)杀死容器。

示例 YAML 配置(一个简单的 Nginx Pod):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: my-container
image: nginx:latest
resources: #资源配置,是容器级别的字段,用于定义 资源请求(requests) 和 资源限制(limits)。
requests: #容器启动和调度时最少需要的资源量
memory: "128Mi" # 最小内存请求,十进制单位E、P、T、G、M、K;二进制单位Ei、Pi、Ti、Gi、Mi、Ki,建议使用二进制单位
cpu: "500m" # 最小 CPU 请求(0.5 核),一核cpu=1000m
limits: #容器运行时最多能使用的资源上限
memory: "256Mi" # 最大内存限制
cpu: "1" # 最大 CPU 限制(1 核)

注意:在为容器分配cpu和内存时,requests中配置的memory和cpu的值不能大于节点的总容量,limits中配置的memory和cpu可以超过节点的总容量。同时配置的requests不能大于limits

4.4pod调度不成功

使用 free -m 查看节点内存使用情况,明明节点上的内存和cpu资源是够的,但是在创建pod时却无法调度到该节点,describe查看pod信息提示资源不够,出现这种情况的原因是pod是否能成功调度并不是由free -m 所显示的内存来决定的。详细解释如下:

为了让一个配置了 requests 的 Pod 成功调度到耨个节点,必须关注节点的剩余可用资源

Kubernetes 调度器会执行一个非常简单的计算:

1
[节点的可分配资源 (Allocatable)]` - `[节点上已分配的请求 (Allocated resources - Requests)]` >= `[新 Pod 的请求 (Pod's requests)]

查看节点的资源使用情况:kubectl describe nodes k8s-node02

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[root@k8s-master01 10-pod]# kubectl describe nodes k8s-node02
Name: k8s-node02
Roles: <none>
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
ingress=true
kubernetes.io/arch=amd64
kubernetes.io/hostname=k8s-node02
kubernetes.io/os=linux
node.kubernetes.io/node=
Annotations: node.alpha.kubernetes.io/ttl: 0
projectcalico.org/IPv4Address: 192.168.0.85/24
projectcalico.org/IPv4IPIPTunnelAddr: 172.16.58.192
volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp: Sat, 14 Dec 2024 21:15:58 +0800
Taints: <none>
Unschedulable: false
Lease:
HolderIdentity: k8s-node02
AcquireTime: <unset>
RenewTime: Fri, 24 Oct 2025 14:21:17 +0800
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
NetworkUnavailable False Fri, 24 Oct 2025 11:17:59 +0800 Fri, 24 Oct 2025 11:17:59 +0800 CalicoIsUp Calico is running on this node
MemoryPressure False Fri, 24 Oct 2025 14:21:25 +0800 Wed, 22 Oct 2025 14:21:33 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Fri, 24 Oct 2025 14:21:25 +0800 Wed, 22 Oct 2025 14:21:33 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
PIDPressure False Fri, 24 Oct 2025 14:21:25 +0800 Wed, 22 Oct 2025 14:21:33 +0800 KubeletHasSufficientPID kubelet has sufficient PID available
Ready True Fri, 24 Oct 2025 14:21:25 +0800 Wed, 22 Oct 2025 14:21:33 +0800 KubeletReady kubelet is posting ready status
Addresses:
InternalIP: 192.168.0.85
Hostname: k8s-node02
Capacity: #表示节点的总资源
cpu: 2
ephemeral-storage: 101308Mi
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 7835728Ki
pods: 110
Allocatable: #表示节点可分配的资源,这是这个节点真正能用来分配给 Pod 的资源总量。它不等于 Capacity (总容量),因为 Capacity 中有一部分资源被 Kubernetes 系统组件(如 kubelet)和操作系统本身预留了。
cpu: 2
ephemeral-storage: 95606223509
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 7733328Ki
pods: 110
System Info:
Machine ID: fe7f9ee8ad6f4e929dbbdfa31836b572
System UUID: 15204d56-ebfb-e466-0fa8-a448673bb632
Boot ID: cbd57ca5-5485-4d7d-9282-0a0c80fc84d8
Kernel Version: 5.14.0-503.15.1.el9_5.x86_64
OS Image: Rocky Linux 9.5 (Blue Onyx)
Operating System: linux
Architecture: amd64
Container Runtime Version: containerd://1.7.24
Kubelet Version: v1.31.4
Kube-Proxy Version: v1.31.4
PodCIDR: 172.16.3.0/24
PodCIDRs: 172.16.3.0/24
Non-terminated Pods: (4 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
default nginx-pod 1 (50%) 0 (0%) 1Gi (13%) 0 (0%) 39s
ingress-nginx ingress-nginx-controller-hst99 100m (5%) 0 (0%) 90Mi (1%) 0 (0%) 142d
kube-system calico-node-ft6c4 250m (12%) 0 (0%) 0 (0%) 0 (0%) 313d
monitoring node-exporter-z8xsm 112m (5%) 270m (13%) 200Mi (2%) 220Mi (2%) 32d
Allocated resources: #已经分配的配置,这是k8s-node02节点上所有Pod的 Requests 总和。调度器就是看这个值来计算剩余资源的。
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 1462m (73%) 270m (13%)
memory 1314Mi (17%) 220Mi (2%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
Events: <none>

来算一下 k8s-node02 节点当前还剩下多少资源可供分配:

  1. 剩余 CPU:
    • 可分配 (Allocatable): 2 (即 2000m)
    • 已请求 (Requests): 1462m
    • 剩余 CPU = 2000m - 1462m = 538m
  2. 剩余内存:
    • 可分配 (Allocatable): 7733328Ki (约 7552Mi)
    • 已请求 (Requests): 1314Mi
    • 剩余内存 ≈ 7552Mi - 1314Mi = 6238Mi

结论:

新 Pod,其所有容器的 requests 资源总和必须 小于或等于 538m CPU6238Mi 内存,才有可能被调度到 k8s-node02 节点上。

最后一点: 还应该看一下 Taints (污点)。这个节点是 <none> (没有污点),所以它对所有 Pod 开放。如果这里有污点,你的 Pod 还

必须有相应的 Tolerations (容忍) 才能被调度上来。

5.为pod配置环境变量

在 Kubernetes Pod 中为容器配置环境变量是非常核心的一个操作。主要有五种方式,下面会结合实际案例逐一讲解:

  1. **直接定义 (Key-Value)**:最简单的方式,直接在 Pod 定义中写入。
  2. **引用 ConfigMap (单个 Key)**:从 ConfigMap 中提取一个特定的 Key 作为环境变量。
  3. **引用 Secret (单个 Key)**:从 Secret 中提取一个特定的 Key(如密码)。
  4. **批量引用 (envFrom)**:把整个 ConfigMap 或 Secret 导入为环境变量。
  5. **引用 Pod 自身信息 (Downward API)**:把 Pod 的 IP、名称等元数据注入为环境变量。

5.1直接定义环境变量 (env)

这是最基础的方式,直接在 Pod 的 spec.containers[].env 字段中指定键值对。适用于静态配置,不涉及敏感数据或动态注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@k8s-master01 10-pod]# cat env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx-env
name: nginx-env
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
name: nginx-env
env: #env 里的变量名和值完全由你自定义,但必须与应用程序所期望的变量名对应,否则程序不会识别这些值。
- name: APP_MODE #环境变量的名称,这里设置的字段应该与应用程序中定义的保持一致,这样程序才会识别
value: "prod" #环境变量的值,字符串类型
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

创建pod并验证容器中环境变量:

1
2
3
4
5
6
7
8
9
10
# 创建pod
kubectl create -f env-pod.yaml

#验证环境变量,两种验证方式:一种是进入到pod中,另一种不进入pod,无论哪种验证方式APP_MODE输出的值都是yaml中定义的prod值
[root@k8s-master01 10-pod]# kubectl exec -ti nginx-env -- bash
root@nginx-env:/# echo $APP_MODE
prod

[root@k8s-master01 10-pod]# kubectl exec nginx-env -- printenv APP_MODE
prod

5.2引用 Pod 自身字段(fieldRef)

有时候,应用程序需要知道它自己运行时的信息,比如它自己的 IP 地址、它所在的命名空间或它自己的名字。

使用场景

  • 服务注册:应用启动时需要告诉注册中心(如 Nacos、Consul)自己的 IP 地址。
  • 监控:应用需要将自己的 Pod 名称作为标签附加到监控数据上。
  • 集群感知:应用需要知道自己在哪个 Namespace。

案例:将 Pod 的名称、IP 和命名空间注入为环境变量。

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
[root@k8s-master01 10-pod]# cat env-pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx-env
name: nginx-env
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
name: nginx-env
env:
- name: APP_MODE
value: "prod"
- name: POD_NAME #定义变量的名字
valueFrom:
fieldRef:
fieldPath: metadata.name # 引用 Pod 元数据中的 'name'
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace # 引用 Pod 元数据中的 'namespace'
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP # 引用 Pod 状态中的 'podIP'
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

创建pod并验证容器中环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 创建pod
kubectl create -f env-pod.yaml

#验证环境变量,两种验证方式:一种是进入到pod中,另一种不进入pod
[root@k8s-master01 10-pod]# kubectl exec -ti nginx-env -- bash
root@nginx-env:/# echo $POD_NAME $POD_NAMESPACE $POD_IP
nginx-env default 172.16.58.241

[root@k8s-master01 10-pod]# kubectl exec nginx-env -- printenv POD_NAME POD_NAMESPACE POD_IP
nginx-env
default
172.16.58.241

#查看pod的IP
[root@k8s-master01 10-pod]# kubectl get po -owide|grep nginx-env
nginx-env 1/1 Running 0 2m23s 172.16.58.241 k8s-node02 <none> <none>

常用的内置字段:

1
2
3
4
5
6
7
8
9
10
11
metadata.name 
metadata.namespace
metadata.uid
metadata.labels['key']
metadata.annotations['key']
spec.nodeName
spec.serviceAccountName
status.hostIP
status.hostIPs
status.podIP
status.podIPs

常用的内置字段中metadata.labels['key']metadata.annotations['key'] 用法相同,这里只演示metadata.labels[xxx]的用法:

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
#假设 Pod 标签如下:
labels:
app: web-ui
version: v1
env: prod
run: nginx-pod
#你想在容器里通过环境变量拿到标签中 env 值(即 prod),就可以用:fieldPath: metadata.labels['env'],pod中定义如下:
cat nginx-pod.yaml
#yaml文件内容如下:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
app: web-ui
version: v1
env: prod
run: nginx-pod
name: nginx-pod
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine
name: nginx-pod
env:
- name: ENV #容器中的变量名是ENV,ENV的值是从pod标签中获取:metadata.labels['env'],也就是ENV=prod
valueFrom:
fieldRef:
fieldPath: metadata.labels['env'] #通过metadata.labels['env']拿到pod标签中的值作为环境变量,也就是把pod标签中env: prod的prod赋值给ENV这个环境变量,必须单引号包裹key
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}

创建pod并验证容器中环境变量:

1
2
3
4
5
6
# 创建pod
kubectl create -f nginx-pod.yaml

#验证容器中环境变量,ENV是否等于prod
[root@k8s-master01 10-pod]# kubectl exec nginx-pod -- printenv|grep ENV
ENV=prod

5.3从 ConfigMap 加载环境变量

用于加载外部配置项(例如数据库地址、服务 URL 等)。
ConfigMap 可以集中管理配置,多个 Pod 可复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#app-config.yaml配置如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# 应用配置
APP_MODE: "prod"
APP_PORT: "8080"
API_URL: "https://api.example.com"
# 数据库配置
DB_HOST: "mysql-prod.default.svc.cluster.local"
DB_NAME: "mydatabase"


# 创建 ConfigMap
kubectl apply -f app-config.yaml

方式一:从ConfigMap 逐个引用键值:

app-config 这个 ConfigMap 中,只取出 DB_HOST 的值,并将其注入到容器中。

使用场景:你只需要 ConfigMap 中的某几个特定配置项,或者你需要重命名环境变量(例如 ConfigMap 中的 Key 叫 DB_HOST,但容器内需要叫 DATABASE_HOSTNAME)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# cm-env.yaml 配置如下:
apiVersion: v1
kind: Pod
metadata:
name: env-cm-demo
spec:
containers:
- name: my-app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
env:
- name: DATABASE_HOSTNAME # 在容器内最终的环境变量名,这里定义的环境变量要与程序中设置的值保持一致。
valueFrom:
configMapKeyRef:
name: app-config # 引用上面创建的 ConfigMap的名字
key: DB_HOST # 引用该 ConfigMap 里的 'DB_HOST' 这个 Key,也就是把DB_HOST的值赋值给DATABASE_HOSTNAME

创建pod并验证容器中环境变量:

1
2
3
4
5
6
# 创建pod
[root@k8s-master01 10-pod]# kubectl create -f cm-env.yaml

#验证容器中的环境变量DATABASE_HOSTNAME是否等于mysql-prod.default.svc.cluster.local
[root@k8s-master01 10-pod]# kubectl exec env-cm-demo -- printenv DATABASE_HOSTNAME
mysql-prod.default.svc.cluster.local #可以看到环境变量设置成功

方式二:批量导入ConfigMap所有键值

1
2
3
4
5
6
7
8
9
10
11
12
# cm-env.yaml配置示例如下:
apiVersion: v1
kind: Pod
metadata:
name: env-cm-demo
spec:
containers:
- name: my-app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
envFrom: # 使用 envFrom 批量导入
- configMapRef: #从configMap中导入
name: app-config # 导入名字是app-config 的ConfigMap中所有的 Key-Value

创建pod并验证容器中环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
# 创建pod
[root@k8s-master01 10-pod]# kubectl create -f cm-env.yaml

#验证容器中的环境变量, 这样 ConfigMap 中的所有 key 都会自动变成环境变量
[root@k8s-master01 10-pod]# kubectl exec -ti env-cm-demo -- bash
root@env-cm-demo:/# env|grep -E '^(APP_MODE|APP_PORT|API_URL|DB_HOST|DB_NAME)='
API_URL=https://api.example.com #可以看到环境变量就是ConfigMap中定义的key和value
DB_HOST=mysql-prod.default.svc.cluster.local
APP_MODE=prod
DB_NAME=mydatabase
APP_PORT=8080

5.4从 Secret 加载环境变量(用于敏感信息)

从Secret 加载环境变量和5.3中从ConfigMap加载环境变量几乎一样,只是把 configMapKeyRef 换成了 secretKeyRef

使用场景必须用于注入密码、API 密钥、Token 等敏感信息。

定义一个 Secret (mysql-secret) 这个 Secret 存储了数据库的用户和密码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# mysql-secret.yaml配置如下
apiVersion: v1
kind: Secret
metadata:
name: mysql-secret
type: Opaque
stringData:
# 数据库凭据
DB_USER: "root"
DB_PASSWORD: "S!p3rS3cretPa$$w0rd"


# 创建 Secret
kubectl apply -f mysql-secret.yaml

有图pod从Secret 加载环境变量和5.3中从ConfigMap加载环境变量几乎一样,只是把 configMapKeyRef 换成了 secretKeyRef,所以这里就不验证了,配置示例如下:

方式一:从 Secret 逐个引用键值示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# secret-env.yaml配置示例如下:
apiVersion: v1
kind: Pod
metadata:
name: env-cm-demo
spec:
containers:
- name: my-app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
env:
- name: DATABASE_USER # 在容器内最终的环境变量名,这里定义的环境变量要与程序中设置的值保持一致。
valueFrom:
secretKeyRef:
name: mysql-secret # 引用上面创建的 Secret 的名字
key: DB_USER # 引用该 Secret 里的 'DB_USER' 这个 Key,也就是把DB_USER的值赋值给DATABASE_USER

方式二:批量导入 Secret 所有键值示例:

1
2
3
4
5
6
7
8
9
10
11
12
# secret-env.yaml配置示例如下:
apiVersion: v1
kind: Pod
metadata:
name: env-cm-demo
spec:
containers:
- name: my-app
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
envFrom: # 使用 envFrom 批量导入
- secretRef: #从Secret中导入
name: mysql-secret #从名字是mysql-secret的Secret中所有的 Key-Value,容器中的环境变量就是mysql-secret中定义的所有key和value

5.5pod中混合使用多种方式

配置示例参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: v1
kind: Pod
metadata:
name: env-demo
spec:
containers:
- name: nginx
image: nginx:latest
envFrom: #定义批量导入环境变量
- configMapRef: #从ConfigMap批量导入
name: app-config # app-config是引用的ConfigMap的名字
- secretRef: #从Secret批量导入
name: mysql-secret #mysql-secret 是引用的Secret的名字
env:
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: CUSTOM_MESSAGE
value: "Hello from mixed env"

6.Pod镜像拉取策略

通过 spec.containers[].imagePullPolicy 参数可以指定镜像的拉取策略,目前支持的策略如下:

操作方式 说明
Always 总是拉取,当镜像 tag 为 latest 时,且 imagePullPolicy 未配置,默认为 Always
IfNotPresent 镜像不存在时拉取镜像,如果 tag 为非 latest,且 imagePullPolicy 未配置,默认为 IfNotPresent
Never 不管是否存在都不会拉取

指定镜像的拉取策略为Always

1
2
3
4
5
6
7
8
9
10
11
12
vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx-run
name: nginx
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
name: nginx-run
imagePullPolicy: Always #镜像拉取策略,Always表示总是拉取

验证当tag不是latest是,不配置imagePullPolicy,默认是IfNotPresent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pod的yaml文件如下:
[root@k8s-master01 pra]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx-run
name: nginx
spec:
containers:
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0
name: nginx-run

#创建pod
[root@k8s-master01 pra]# kubectl create -f pod.yaml
pod/nginx created

查看pod的yaml文件的imagePullPolicy是否为Always
[root@k8s-master01 pra]# kubectl get po nginx -oyaml|grep "imagePullPolicy"
imagePullPolicy: IfNotPresent

案例:为什么节点有镜像还是提示镜像下载失败?

现象:为什么节点上明明有镜像,还是会去下载镜像,并且还下载失败了。

情况一:当 Kubernetes 使用 Docker 作为 Runtime 时,如果集群节点无法从外网拉取镜像,即使在 master01 节点上通过 docker load 导入了镜像,Pod 在调度到其他节点上时仍会尝试从网上下载镜像。 因此,镜像必须在 所有可调度 Pod 的节点上 都进行导入(docker load),才能保证 Pod 创建时不再报错或尝试下载镜像。

情况二:当 Kubernetes 使用 Containerd 作为 Runtime 时,如果节点无法从外网拉取镜像,不能使用 docker load 导入镜像,而应使用 **nerdctl load**。 因为 Docker 与 Containerd 的镜像存储目录和管理机制不互通,两者导入的镜像互相不可见。

情况三:即使使用 nerdctl load 并在每个节点都导入了镜像,仍可能出现找不到镜像的情况。原因是 Containerd 存在命名空间隔离机制,导入镜像时需要 指定正确的命名空间(如 --namespace k8s.io,否则 Kubernetes 无法识别该镜像。

例如:在 Kubernetes 使用 Containerd 作为 Runtime 的环境中,即使在 Kubernetes 里创建的 Pod 属于自定义命名空间(例如 prod),Containerd 实际使用的命名空间仍然是固定的:k8s.io

也就是说:无论你的 Pod 在 Kubernetes 的哪个命名空间(defaultproddev 等), Containerd 都会在 k8s.io 命名空间下查找镜像。

因此,如果你要手动导入镜像,正确的做法是:

1
nerdctl --namespace k8s.io load -i your-image.tar

Kubernetes 在所有命名空间(如 prod、dev、default)中创建的 Pod,其底层 Containerd 镜像都统一存放在 k8s.io 命名空间中。因此,手动导入镜像时应始终使用 --namespace k8s.io

情况四:Kubernetes 使用 Containerd 作为 Runtime 的环境中,即使使用 nerdctl --namespace k8s.io load 将镜像导入到所有节点,仍可能出现 Pod 创建时提示下载镜像或找不到镜像的情况。原因在 Pod 的 YAML 配置中,imagePullPolicy 被设置为 Always。当 imagePullPolicy: Always 时,Kubernetes 会 强制从镜像仓库重新拉取镜像,而不会使用本地已存在的镜像。解决方法:将镜像策略改为 IfNotPresentNever

7.Pod重启策略

在 Kubernetes 中,Pod 的重启策略(Restart Policy)定义了当容器崩溃或退出时,Kubernetes 如何处理该 Pod 中的容器。Pod 的重启策略可以帮助确保容器在运行期间的高可用性和稳定性。

可以使用 spec.restartPolicy 指定容器的重启策略

操作方式 说明
Always 默认策略。无论容器退出的原因是什么,都会重启容器。对于大多数控制器(如 Deployment、ReplicaSet),默认使用 Always
OnFailure 仅当容器以非零状态码退出时才 重启。
Never 当容器无论以任何状态退出时,都不会重启。

指定pod的重启策略为Always

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#一个pod如果管理多个容器,多个容器之间的端口不能相同,容器的名字也不能一样
apiVersion: v1 #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
labels: #标签
run: nginx-run #key=value的格式,可以根据标签筛选过滤出不同的pod
name: nginx-run # 指定pod的名称
spec: #用于指定期望pod的规格
restartPolicy: Always #不配置也默认是Always策略
containers: #定义配置容器,复数表示可以配置一个或多个容器
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0 #指定容器的镜像
name: nginx-run #指定容器的名字
ports: #可选,容器需要暴露的端口号列表
- containerPort: 80 #指定了容器内部的 Nginx 服务监听 80 端口
- image: redis:6.0.0
name: redis

指定pod的重启策略为OnFailure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#一个pod如果管理多个容器,多个容器之间的端口不能相同,容器的名字也不能一样
apiVersion: v1 #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
labels: #标签
run: nginx-run #key=value的格式,可以根据标签筛选过滤出不同的pod
name: nginx-run # 指定pod的名称
spec: #用于指定期望pod的规格
restartPolicy: OnFailure #配置重启策略为OnFailure
containers: #定义配置容器,复数表示可以配置一个或多个容器
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0 #指定容器的镜像
name: nginx-run #指定容器的名字
ports: #可选,容器需要暴露的端口号列表
- containerPort: 80 #指定了容器内部的 Nginx 服务监听 80 端口
- image: redis:6.0.0
name: redis

指定pod的重启策略为Never

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#一个pod如果管理多个容器,多个容器之间的端口不能相同,容器的名字也不能一样
apiVersion: v1 #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
labels: #标签
run: nginx-run #key=value的格式,可以根据标签筛选过滤出不同的pod
name: nginx-run # 指定pod的名称
spec: #用于指定期望pod的规格
restartPolicy: Never #配置重启策略为Never
containers: #定义配置容器,复数表示可以配置一个或多个容器
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.24.0 #指定容器的镜像
name: nginx-run #指定容器的名字
ports: #可选,容器需要暴露的端口号列表
- containerPort: 80 #指定了容器内部的 Nginx 服务监听 80 端口
- image: redis:6.0.0
name: redis

8.Pod生命周期

8.1pod启动过程

pod启动过程技术总结 (含探针):

  1. Pending (待处理)
    • API Server 收到请求后将pod信息写入etcd
    • kube-scheduler (调度器) 寻找合适的 Node。
    • Scheduler 将 nodeName 写入 etcd,完成“绑定”。
  2. ContainerCreating (容器创建中)
    • 目标 Node 上的 kubelet 监测到 Pod。
    • kubelet 开始准备:拉取镜像 (Pulling)、配置网络 (CNI)、挂载卷 (Volumes)等。
  3. Init (初始化)
    • kubelet 检查 initContainers
    • 按顺序启动并运行 Init 容器。
    • 所有 Init 容器必须成功退出 (exit code 0)。探针(Probes)不适用于 Init 容器
  4. Running (运行中)
    • 所有 Init 容器成功后,kubelet 并行启动 所有主容器 (containers),如果有多个容器会同时启动。
    • 容器进程启动后,Pod 状态变为 Running
    • kubelet 开始执行三种探针(如果已配置):
    • Startup Probe (启动探针):
      • 首先被执行。
      • 在它成功之前,Liveness 和 Readiness 探针都不会启动。
      • 如果它失败达到 failureThreshold 次,kubelet重启容器
      • 一旦成功,它就功成身退,不再运行。
    • Liveness Probe (存活探针):
      • 在 Startup Probe 成功后(或在 initialDelaySeconds 后,如果无 Startup 探针)开始执行。
      • 在容器的整个生命周期内定期运行。
      • 如果失败达到 failureThreshold 次,kubelet重启容器
    • Readiness Probe (就绪探针):
      • 与 Liveness 探针并行启动(也是在 Startup Probe 成功后或 initialDelaySeconds 后)。
      • 在容器的整个生命周期内定期运行。
      • 如果失败,kubelet 会将 Pod 的 Ready 状态设为 False它不会重启容器
  5. Ready (就绪)
    • Startup Probe(如果存在) Readiness Probe 都首次成功时,kubelet 将 Pod 的 Ready 状态设置为 True
    • Service 对应的 Endpoint 会包含此 Pod 的 IP。
    • K8s 开始向该 Pod 转发流量。

关键点梳理:

  • Startup Probe (启动探针)只在启动时运行一次。它是“启动期”的保护者,防止启动慢的应用被误杀。它会“暂停”另两个探针。
  • Liveness Probe (存活探针)贯穿整个生命周期。检查“是否僵死”。失败 = 重启容器
  • Readiness Probe (就绪探针)贯穿整个生命周期。检查“是否能接客”。失败 = 停止流量(不重启)。

8.2pod退出过程

退出过程技术总结

  1. 用户删除 Podkubectl delete pod ...
  2. API Server 更新状态:Pod 对象在 etcd 中被标记为 Terminating 状态,并记录 deletionTimestamp
  3. Endpoint 移除EndpointController 监听到 Pod 状态变化,将其从 ServiceEndpoint 列表中移除。新的网络流量停止
  4. Kubelet 响应:节点上的 kubelet 监听到 Pod 变为 Terminating
  5. preStop 钩子执行kubelet 首先执行 Pod 中定义的 preStop 钩子。
  6. SIGTERM 信号preStop 钩子执行完毕后,kubelet 向容器内的主进程 (PID 1) 发送 SIGTERM 信号,要求其优雅关闭。
  7. **等待优雅关闭 (Grace Period)**:
    • kubelet 开始等待,最长时间为 Pod 定义的 terminationGracePeriodSeconds(默认为 30 秒)。
    • 如果容器在此期间内自行退出(理想情况),kubelet 立即进入步骤 9。
  8. SIGKILL 信号
    • 如果优雅关闭期限已到,但容器进程仍在运行。
    • kubelet 会(通过 CRI)向容器发送 SIGKILL 信号,强制杀死进程。
  9. 资源清理:容器进程退出后,kubelet 清理该 Pod 占用的所有资源(如网络、存储卷等)。
  10. API Server 删除对象kubelet 通知 API Server Pod 已被成功删除。API Server 最终从 etcd 中移除该 Pod 对象。

关键点梳理:

  • 第一步是断流:K8s 首先确保新流量不再进入即将死亡的 Pod,这是通过将其从 ServiceEndpoint 移除来实现的。
  • preStop 优先preStop 钩子在 SIGTERM 信号之前执行,用于执行“必须完成”的收尾工作。
  • **SIGTERM vs SIGKILL**:
    • SIGTERM:是“通知”,允许应用优雅关闭(比如保存数据)。
    • SIGKILL:是“强杀”,应用无法捕获,强制终止,可能导致数据丢失。
  • terminationGracePeriodSeconds:这是从 preStop 开始到 SIGKILL 发生的总时限。如果你的 preStop 任务要执行 10 秒,那么你的应用就只剩下 20 秒(假设总时限30秒)来响应 SIGTERM 并退出。

9.Pod的三种探针

在 Kubernetes 中,探针(Probes)用于检测 Pod 中容器的健康状态和可用性。探针有三种类型:存活探针(Liveness Probe)、就绪探针(Readiness Probe)和启动探针(Startup Probe)。这些探针可以帮助 Kubernetes 自动恢复不健康的容器,并在应用启动或准备就绪时将流量引导到健康的实例。

健康检查是针对容器进行的,所以配置在spec.containers中的

三种探针类型:

  1. Liveness Probe(存活探针):检测容器是否处于健康状态。如果存活探针失败,Kubernetes 会重启该容器。
  2. Readiness Probe(就绪探针):检测容器是否准备好接受流量。如果就绪探针失败,Kubernetes 会将该容器从服务的负载均衡器中移除。
  3. Startup Probe(启动探针):检测容器是否已经成功启动。如果配置了启动探针,存活探针会被禁用,直到启动探针成功为止。适用于启动时间较长的应用。
种类 说明
startupProbe(只启动一次,适用于启动较慢的程序) Kubernetes1.16 新加的探测方式,用于判断容器内的应用程序是否已经启动。如果配置了 startupProbe,就会先禁用其他两种探测,直到它成功为止。如果探测失败,Kubelet会杀死容器,之后根据重启策略进行处理,如果探测成功,或没有配置 startupProbe,则状态为成功,之后就不再探测。
livenessProbe(循环探测) 用于探测容器是否在运行,如果探测失败,kubelet 会“杀死”容器并根据重启策略进行相应的处理。如果未指定该探针,将默认为 Success。(该策略会触发pod重启)
readinessProbe(循环探测) 一般用于探测容器内的程序是否健康,即判断容器是否为就绪(Ready)状态。如果是,则可以处理请求,反之Endpoints Controller将从所有的 Service 的 Endpoints中删除此容器所在 Pod 的 IP 地址。如果未指定,将默认为 Success;失败 = 停止流量(不会重启pod )

不配置健康检查的影响:如果pod没有配置任何健康检查,同时程序启动的又非常慢,此时pod会很快设置为就绪(Ready)状态,当pod状态为Ready时表示可以正常接收流量了,但是由于程序启动慢,实际上程序还没有启动完成是没法处理分发过来的流量,这样用户体验很不友好,生产环境中不应出现这种情况,下面是案例:

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
# pod的yaml配置如下:
cat slow-pod.yaml
apiVersion: v1 #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
name: slow-pod #定义pod的名字是slow-pod
labels: #定义pod的标签
app: slow
spec: #用于指定期望pod的规格
containers: ##定义配置容器,可以配置一个或多个容器
- name: slow-container #定义容器的名字
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/slow:latest #定义容器使用的镜像,slow 这个程序启动很慢
imagePullPolicy: IfNotPresent #镜像的下载策略,IfNotPresent表示如果镜像存在就不拉取
ports: # 声明容器的端口
- containerPort: 8080 #声明容器的端口,这里指定的端口号不会改变容器运行的实际端口,同时要和容器的真实端口保持一致
name: http


# 上述pod没有配置任何健康检查,创建该pod
[root@k8s-master01 10-pod]# kubectl create -f slow-pod.yaml
pod/slow-pod created

#pod的状态是Running,READY也是 1/1 ,此时service就会向该pod分发流量,但是通过kubectl logs -f slow-pod查看该容器还没有启动成功
[root@k8s-master01 10-pod]# kubectl get pod -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
slow-pod 1/1 Running 0 22s 172.16.58.247 k8s-node02 <none> <none>


#kubectl logs -f slow-pod看到日志还没有启动成功
[root@k8s-master01 10-pod]# kubectl logs -f slow-pod
Starting...
Start initializing basic data...

#此时通过pod的ip地址请求也是不可用的,程序无法处理流量,生产环境会造成宕机风险,所以生产中必须要配置健康检查
[root@k8s-master01 ~]# curl 172.16.195.51:8080/ping
curl: (7) Failed to connect to 172.16.195.51 port 8080: Connection refused

结论:当一个程序启动很慢,又没有健康检查,容器创建完状态会立马变为Running状态,此时就会为该容器分配流量,但是由于程序实际没有成功启动,所以无法处理流量,就会拒绝服务

9.1探针的四种检测方式

每种探针只能配置一种检测方式,根据情况在exec、tcpSocket、httpGet、grpc中四选一进行配置,暂时不支持配置多种探测方式。

探测方式 说明
exec 在容器内执行一个指定的命令,如果命令执行成功,则返回值是 0,就认为容器健康(返回值是否为0 根据echo $?判断)
tcpSocket(程序可能存在假死状态) 通过 TCP 连接检查容器指定的端口,如果端口开放,则认为容器健康(适用数据库、Redis、gRPC 等)
httpGet(最可靠,但是需要开发提供一个接口) 适用Web 应用、REST 接口,对指定的 URL 进行 Get 请求,如果状态码在 200~400 之间,则认为容器健康,(该方式需要开发在程序提供一个接口)
grpc GRPC协议的健康检查,如果响应的状态是”SERVING”,则认为容器健康。(K8s v1.24+)

9.2liveness Probe(存活探针)和readinessProbe(就绪探针)

创建一个有健康检查的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
30
31
32
33
34
35
36
37
#一个pod如果管理多个容器,多个容器之间的端口不能相同,容器的名字也不能一样
cat slow-pod.yaml
apiVersion: v1 #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
name: slow-pod #定义pod的名字是slow-pod
labels: #定义pod的标签
app: slow
spec: #用于指定期望pod的规格
containers: ##定义配置容器,可以配置一个或多个容器
- name: slow-container #定义容器的名字
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/slow:latest #定义容器使用的镜像,slow 这个程序启动很慢
imagePullPolicy: IfNotPresent #镜像的下载策略,IfNotPresent表示如果镜像存在就不拉取
ports: # 声明容器的端口
- containerPort: 8080 #声明容器的端口,这里指定的端口号不会改变容器运行的实际端口,同时要和容器的真实端口保持一致
name: http
readinessProbe: # 就绪探针,可选,该探针检测不通过会重启pod,pod状态“Running”并不代表健康,只代表容器进程还在运行。
httpGet: # 接口检测方式,注意4种检查方式同时只能使用一种,例如httpGet、tcpsocket只能选一种。
path: /index.html # 检查路径
port: 80
scheme: HTTP # HTTP or HTTPS
#httpHeaders: # 可选, 检查的请求头,这里请求到用法见下文《生产案例》
# - name: X-Auth
# value: SecretToken123
initialDelaySeconds: 10 # 初始化时间, 执行第一次健康检查前等待的时间,也就是10秒后再执行健康检查
timeoutSeconds: 2 # 超时时间,两秒内得不到相应就认为超时
periodSeconds: 5 # 检测间隔,即每 periodSeconds(这里是5秒) 秒执行一次探针检查。
successThreshold: 2 # 检查成功为 2 次表示就绪,连续2次成功则认为合格
failureThreshold: 2 # 检测失败 2 次表示未就绪,连续2次失败则认为不合格,不会重启pod,只是不接受流量
livenessProbe: # 存活探针,可选,readinessProbe 决定 Pod 是否 Ready。只有该探针通过K8s会把这个 Pod 加入到 Service 的 endpoint 列表;接受流量。
tcpSocket: # 端口检测方式
port: 8080
initialDelaySeconds: 10 # 初始化时间,等待10秒执行第一次探测
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 检测间隔
successThreshold: 1 # 检查成功为 1次表示就绪
failureThreshold: 2 # 检测失败 2次表示未就绪,会重启pod

创建并查看pod状态:

1
2
3
4
[root@k8s-master01 10-pod]# kubectl create -f slow-pod.yaml
[root@k8s-master01 10-pod]# kubectl get po
NAME READY STATUS RESTARTS AGE
slow-pod 0/1 Running 6 (99s ago) 4m19s

通过 describe po 查看pod的事件:

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-master01 10-pod]# kubectl describe po  slow-pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 2m7s default-scheduler Successfully assigned default/slow-pod to k8s-node01
Normal Killing 87s (x2 over 107s) kubelet Container slow-container failed liveness probe, will be restarted
Normal Pulled 86s (x3 over 2m6s) kubelet Container image "registry.cn-beijing.aliyuncs.com/k8s-liujunwei/slow:latest" already present on machine
Normal Created 86s (x3 over 2m6s) kubelet Created container slow-container
Normal Started 86s (x3 over 2m6s) kubelet Started container slow-container
Warning Unhealthy 67s (x8 over 112s) kubelet Readiness probe failed: Get "http://172.16.85.220:80/index.html": dial tcp 172.16.85.220:80: connect: connection refused
Warning Unhealthy 67s (x6 over 112s) kubelet Liveness probe failed: dial tcp 172.16.85.220:8080: connect: connection refused

pod状态看到:pod的STATUS(状态)字段是Running,但是READY字段是0/1,RESTARTS字段为6;

describe pod看到事件中提示Liveness probe 失败了,原因是程序中的端口是8080,没有/index.html路径,readinessProbe中配置错误;Readiness probe失败原因是由于程序启动很慢,探测时间参数配置不合理导致探测不成功,pod一致重启。

总结:livenessProbe当探测失败后会根据容器的重启策略对容器进行重启,readinessProbe当探测失败时不会对容器进行重启,只是该pod服务不可用,service不会将流量分发给该pod。如果程序启动很慢要使用startupProbe探针进行探测

生产案例:

程序里定义了一个健康检查接口(例如 /healthz),
但为了防止外部随便访问这个接口(比如黑客探测、扫描器、监控误触发等),
程序要求所有访问 /healthz 的请求必须带上一个特定的 HTTP 请求头(header)。

Kubernetes 的探针配置中通过 httpHeaders: 字段带上这个 header,
这样只有探针能访问成功,而其他人由于不知道我们的请求头,所以就会被拒绝。这就是典型的 “受保护的健康检查” 模式。

应用程序(Spring Boot)端配置示例:

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/healthz")
public ResponseEntity<String> health(@RequestHeader(value = "X-Auth", required = false) String token) {
if (!"SecretToken123".equals(token)) {
return ResponseEntity.status(403).body("Forbidden");
}
return ResponseEntity.ok("OK");
}

#说明:
#程序只接受带有请求头 X-Auth: SecretToken123 的访问;
#其他请求一律返回 403

pod的健康检查就要配置请求头,示例如下:

1
2
3
4
5
6
7
8
9
readinessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders: #配置带有请求头进行探测请求
- name: X-Auth
value: SecretToken123
initialDelaySeconds: 5
periodSeconds: 10

这样:

  • kubelet 在探测时会自动带上正确的 header;
  • 探针可以访问成功;
  • 其他任何人(比如访问 http://<PodIP>:8080/healthz)没有带头部,访问会被拒绝。

9.3startupProbe(启动探针)

通过startupProbe探针解决程序启动的影响(程序启动慢要从程序上解决)

下面的配置是:当一个程序启动很慢,通过startupProbe进行探测。

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
cat slow-pod.yaml
apiVersion: v1 #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
name: slow-pod #定义pod的名字是slow-pod
labels: #定义pod的标签
app: slow
spec: #用于指定期望pod的规格
restartPolicy: Always #配置pod重启策略为Always
containers: ##定义配置容器,可以配置一个或多个容器
- name: slow-container #定义容器的名字
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/slow:latest #定义容器使用的镜像,slow 这个程序启动很慢
imagePullPolicy: IfNotPresent #镜像的下载策略,IfNotPresent表示如果镜像存在就不拉取
ports: # 声明容器的端口
- containerPort: 8080 #声明容器的端口,这里指定的端口号不会改变容器运行的实际端口,同时要和容器的真实端口保持一致
name: http
startupProbe: #先执行启动探针,它执行时会先禁用readinessProbe和livenessProbe探针,直到它探测通过。
tcpSocket: # startupProbe建议使用tcpSocket检测方式,端口起来了证明程序已经启动
port: 8080 #定义探测的端口,端口起来了表示程序成功启动
initialDelaySeconds: 20 # 初始化时间, 创建pod后等待20秒再开始执行健康检查
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 检测间隔时间
successThreshold: 1 # 探测成功 1 次表示通过健康检查
failureThreshold: 20 # 探测失败 20 次表示未通过检查,根据重启策略重启pod
readinessProbe: # 就绪探针,可选,该探针检测不通过会重启pod,pod状态“Running”并不代表健康,只代表容器进程还在运行。
httpGet: # 接口检测方式,注意4种检查方式同时只能使用一种,例如httpGet、tcpsocket只能选一种。
path: /ping # 检查路径
port: 8080
scheme: HTTP # HTTP or HTTPS
#httpHeaders: # 可选, 检查的请求头,这里请求到用法见下文《生产案例》
# - name: X-Auth
# value: SecretToken123
initialDelaySeconds: 3 # 初始化时间, 执行第一次健康检查前等待的时间,也就是3秒后再执行健康检查,如果配置了startupProbe,这里就可以缩短初始化的时间
timeoutSeconds: 2 # 超时时间,两秒内得不到相应就认为超时
periodSeconds: 5 # 检测间隔,即每 periodSeconds(这里是5秒) 秒执行一次探针检查。
successThreshold: 2 # 检查成功为 2 次表示就绪,连续2次成功则认为合格
failureThreshold: 2 # 检测失败 2 次表示未就绪,连续2次失败则认为不合格,不会重启pod,只是不接受流量
livenessProbe: # 存活探针,可选,readinessProbe 决定 Pod 是否 Ready。只有该探针通过K8s会把这个 Pod 加入到 Service 的 endpoint 列表;接受流量。
tcpSocket: # 端口检测方式
port: 8080
initialDelaySeconds: 3 # 初始化时间,等待3秒执行第一次探测,如果配置了startupProbe,这里就可以缩短初始化的时间
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 检测间隔
successThreshold: 1 # 检查成功为 1次表示就绪
failureThreshold: 2 # 检测失败 2次表示未就绪,会重启pod

创建该pod:

1
[root@k8s-master01 10-pod]# kubectl create -f slow-pod.yaml

查看pod状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 状态正常,可以处理流量
[root@k8s-master01 10-pod]# kubectl get pod
NAME READY STATUS RESTARTS AGE
slow-pod 1/1 Running 0 2m10s

#通过 describe po 查看pod的事件,readinessProbe和livenessProbe探测成功信息不会显示在Events,Events只显示异常信息
[root@k8s-master01 10-pod]# kubectl describe po slow-pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 12m default-scheduler Successfully assigned default/slow-pod to k8s-node02
Normal Pulled 12m kubelet Container image "registry.cn-beijing.aliyuncs.com/k8s-liujunwei/slow:latest" already present on machine
Normal Created 12m kubelet Created container slow-container
Normal Started 12m kubelet Started container slow-container
Warning Unhealthy 11m (x4 over 12m) kubelet Startup probe failed: dial tcp 172.16.58.255:8080: connect: connection refused

补充知识点:进入容器内部

1
2
3
4
5
6
7
8
9
#如果想进入容器的 shell 以便进行交互式操作,可以使用以下命令:
kubectl exec -it <pod-name> -c <container-name> -- /bin/sh
# -it:表示以交互模式运行,-i 表示交互模式,-t 表示分配一个伪终端。
#<pod-name>:指定 Pod 的名称。
#-c <container-name>:指定容器的名称(如果 Pod 中有多个容器)。
#-- /bin/sh:指定要执行的命令。在大多数容器中,/bin/sh 是可用的 shell,如果容器使用的是其他 shell(如 bash),你可以相应地替换它。

#案例:如果你有一个名为 slow-pod 的 Pod,容器名称为 slow-container,并且想进入 bash 环境(如果容器中安装了 bash),可以使用:
[root@k8s-master01 pra]# kubectl exec -ti slow-pod -c slow-container -- /bin/sh

总结:当使用的kubernetes版本>1.16,程序启动时间超过30,要配置startupProbe,而不是用livenessProbe(存活探针)和readinessProbe配置等待时间。

10.容器生命周期-postStart和preStop

在 Kubernetes 中,preStoppostStart 是容器生命周期钩子,用于在容器启动和终止时执行特定的操作。这些钩子可以帮助你在容器生命周期的关键时刻执行一些重要的任务,从而确保应用程序的平滑运行和关闭。

preStop和postStart都支持exec、httpGet、tcpSocket三种配置方式。需要注意的是这三种配置方式需要容器中有相关的命令。

创建postStart和preStop的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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
apiVersion: v1  #apiVersion版本号
kind: Pod #指定资源的类型,告诉Kubernetes这个YAML文件创建的是一个Pod资源
metadata: #pod的元数据
labels: #标签
run: nginx-run #key=value的格式,可以根据标签筛选过滤出不同的pod
name: nginx # 指定pod的名称
spec: #用于指定期望pod的规格
restartPolicy: Always #配置重启策略为Never
containers: #定义配置容器,复数表示可以配置一个或多个容器
- image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/nginx:1.27-alpine #指定容器的镜像
name: nginx-run #指定容器的名字
ports: #可选,容器需要暴露的端口号列表
- containerPort: 80 #指定了容器内部的 Nginx 服务监听 80 端口
lifecycle:
postStart: #postStart不会等容器完全 Ready 在执行,而是在容器进程启动后立即执行。生产中该参数使用的不多,如果容器启动需要初始化用initContainers做初始化更好。
exec:
command:
- sh
- -c
- mkdir /data/postStart -p
preStop: #preStop 一定在容器被 kill 前执行(默认会等待一段 terminationGracePeriodSeconds 时间);
exec:
command:
- sh
- -c
- sleep 20 #生产场景可能根据开发提供的接口配置
startupProbe:
tcpSocket: # 接口检测方式
port: 80
initialDelaySeconds: 10 # 初始化时间, 健康检查延迟执行时间
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 检测间隔
successThreshold: 1 # 检查成功为 2 次表示就绪
failureThreshold: 2 # 检测失败 1 次表示未就绪
readinessProbe: # 可选,健康检查。注意三种检查方式同时只能使用一种。
httpGet: # 接口检测方式
path: /index.html # 检查路径
port: 80
scheme: HTTP # HTTP or HTTPS
#httpHeaders: # 可选, 检查的请求头
#- name: end-user
# value: Jason
initialDelaySeconds: 10 # 初始化时间, 健康检查延迟执行时间
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 检测间隔
successThreshold: 1 # 检查成功为 2 次表示就绪
failureThreshold: 2 # 检测失败 1 次表示未就绪
livenessProbe: # 可选,健康检查
httpGet:
path: /index.html
port: 80
scheme: HTTP
initialDelaySeconds: 10 # 初始化时间
timeoutSeconds: 2 # 超时时间
periodSeconds: 5 # 检测间隔
successThreshold: 1 # 检查成功为 2 次表示就绪
failureThreshold: 2 # 检测失败 1 次表示未就绪

创建pod:

1
[root@k8s-master01 pra]# kubectl create -f pod.yaml

进入容器内部检查/data/postStart是否被创建:

1
2
3
4
5
6
7
8
[root@k8s-master01 pra]# kubectl exec -ti nginx -c nginx-run -- /bin/sh
/ # ls -l /data/
total 0
drwxr-xr-x 2 root root 6 Jul 13 07:04 postStart

#nginx是pod的名字
#-c 指定要进入容器的名字,这里要进入的容器是nginx-run
#/data/postStart目录已经存在

删除pod:

1
2
3
4
[root@k8s-master01 pra]# kubectl delete -f pod.yaml
pod "nginx" deleted

#删除该pod的时候前根据preStop的策略执行,比如案例中的sleep 20表示在删除该pod前等待20秒在进行删除操作

10.1preStop 钩子

  • 作用:在容器被终止前执行,通常用于优雅关闭(Graceful Shutdown)。

  • 触发时机:当 Pod 被删除、滚动更新或缩容时,Kubernetes 会先发送 SIGTERM 信号,然后等待 terminationGracePeriodSeconds(默认 30 秒)后强制终止容器。preStop 钩子会在 SIGTERM 信号发送前执行。

  • 典型场景

    • 优雅关闭 Java 服务,确保正在处理的请求完成。

    • 从注册中心(如 Eureka、Nacos)注销服务实例。

    • 清理临时文件或释放资源。

      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
      apiVersion: v1
      kind: Pod
      metadata:
      name: java-service
      spec:
      terminationGracePeriodSeconds: 40 # 延长优雅关闭时间,确保 preStop 钩子有足够时间执行。
      containers:
      - name: java-app
      image: my-java-app:latest
      env:
      - name: SERVICE_NAME
      value: "user-service" #user-service 是“服务级别”的名字,由程序逻辑决定,不是k8s资源的名字
      - name: SERVICE_PORT
      value: "8080" #8080程序运行的端口
      - name: REGISTRY_URL #
      value: "http://registry:8500" #是注册中心的地址
      lifecycle:
      preStop: #preStop 中先摘流量再关闭应用
      exec:
      command:
      - /bin/sh
      - -c
      - |
      # 1. 从注册中心摘除(停止接收新流量)
      curl -X DELETE ${REGISTRY_URL}/deregister?service=${SERVICE_NAME} || true
      # 2. 等待现有连接处理完成
      sleep 5
      # 3. 触发应用关闭
      curl -X POST http://localhost:8080/actuator/shutdown || true
      # 4. 最后等待确保清理完成
      sleep 2
      echo "[$(date)] 优雅关机已完成"

10.2postStart 钩子

  • 作用:在容器启动后立即执行,通常用于初始化操作。
  • 触发时机:容器启动后,postStart 钩子会与主进程并行执行。
  • 典型场景
    • 向注册中心注册服务实例。
    • 加载配置文件或初始化数据库连接。
    • 检查依赖服务是否可用。
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
apiVersion: v1
kind: Pod
metadata:
name: java-service
spec:
containers:
- name: java-app
image: my-java-app:latest
env:
- name: SERVICE_NAME
value: "user-service" #user-service 是“服务级别”的名字,由程序逻辑决定,不是k8s资源的名字
- name: SERVICE_PORT
value: "8080" #8080程序运行的端口
- name: REGISTRY_URL #
value: "http://registry:8500" #是注册中心的地址
lifecycle:
postStart:
exec:
command:
- /bin/sh
- -c
- |
# 1. 等待主进程启动
until curl -f http://localhost:8080/actuator/health; do
echo "Waiting for app to be ready..."
sleep 1
done
# 注册到服务中心
curl -X POST ${REGISTRY_URL}/register \
-d "service=${SERVICE_NAME}&port=${SERVICE_PORT}" || true

完整案例:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
spec:
replicas: 2
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
terminationGracePeriodSeconds: 40
containers:
- name: user-service
image: user-service:latest
env:
- name: SERVICE_NAME
value: "user-service"
- name: SERVICE_PORT
value: "8080"
- name: REGISTRY_URL
value: "http://registry:8500"
lifecycle:
postStart:
exec:
command:
- "/bin/sh"
- "-c"
- |
# 等待应用真正就绪
until curl -f http://localhost:8080/actuator/health; do
echo "Waiting for app to be ready..."
sleep 1
done
# 注册到服务中心
curl -X POST ${REGISTRY_URL}/register \
-d "service=${SERVICE_NAME}&port=${SERVICE_PORT}" || true

preStop:
exec:
command:
- "/bin/sh"
- "-c"
- |
echo "[$(date)] Starting graceful shutdown..."

# 1. 从注册中心摘除(停止接收新流量)
curl -X DELETE ${REGISTRY_URL}/deregister?service=${SERVICE_NAME} || true

# 2. 等待现有连接处理完成
sleep 5

# 3. 触发应用关闭
curl -X POST http://localhost:8080/actuator/shutdown || true

# 4. 最后等待确保清理完成
sleep 2

echo "[$(date)] Graceful shutdown completed"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10

11.宽限期terminationGracePeriodSeconds

使用宽限期真正实现服务优雅平滑退出。

terminationGracePeriodSeconds 是 Kubernetes 优雅停机机制(Graceful Shutdown) 的核心参数之一, 在生产环境中控制着 Pod 在被删除或重启时的“优雅退出时间窗口”,指 Pod 从接收到终止信号到被强制杀死之间的宽限期(单位:秒)

在 Pod 的规范(spec)中:

1
2
spec:
terminationGracePeriodSeconds: 30

含义:

当 Pod 被删除、升级、缩容或节点重启时,Kubernetes 会给容器一个“宽限期”(grace period),
在这段时间里允许应用优雅地完成收尾工作(比如关闭连接、保存状态、注销注册中心等)。

简单理解:它是“Pod 死之前的缓冲时间”。

执行流程(时序图):

假设 terminationGracePeriodSeconds: 30

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
kubectl delete pod myapp-pod

1️⃣ Kubelet 发送 SIGTERM 给容器内主进程(PID 1)

2️⃣ 如果配置了 preStop:
→ 执行 preStop 脚本或 HTTP 请求

3️⃣ 应用收到 SIGTERM,开始优雅关闭(停止接收新请求、完成未完成任务、注销注册中心)

4️⃣ 等待最多 30 秒

5️⃣ 如果应用没退出:
→ Kubelet 发送 SIGKILL(强制杀死容器)

6️⃣ Pod 状态变为 Terminated

在什么场景使用?

如果程序的退出清理工作比较耗时,超过了默认时间(如 30 秒),就可以通过调高这个参数(如 50 秒),来给程序充足的时间完成退出,避免被强制“杀死”。

11.gRPC探测(1.24版本就默认开启)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master01 pra]# cat grpc-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: etcd-with-grpc
spec:
containers:
- name: etcd
image: registry.cn-beijing.aliyuncs.com/k8s-liujunwei/etcd:3.5.14
command: [ "/opt/bitnami/etcd/bin/etcd", "--data-dir", "/var/lib/etcd", "--listen-client-urls", "http://0.0.0.0:2379", "--advertise-client-urls", "http://127.0.0.1:2379", "--log-level", "debug"]
ports:
- containerPort: 2379
livenessProbe:
grpc: #配置grpc健康检查
port: 2379
initialDelaySeconds: 10 #10秒后开始执行健康检查
volumeMounts:
- name: etcd-data
mountPath: /var/lib/etcd # 将 emptyDir 挂载到容器内的 /var/lib/etcd 目录
volumes:
- name: etcd-data
emptyDir: {} # 使用 emptyDir 类型的卷,生命周期与 Pod 相同

pod启动成功后查看日志会看到请求健康检查的日志:

1
2
3
4
5
[root@k8s-master01 pra]# kubectl logs -f etcd-with-grpc
#以下是日志输出
{"level":"debug","ts":"2024-07-13T09:13:34.414727Z","caller":"v3rpc/interceptor.go:182","msg":"request stats","start time":"2024-07-13T09:13:34.414699Z","time spent":"12.948µs","remote":"192.168.0.203:55386","response type":"/grpc.health.v1.Health/Check","request count":-1,"request size":-1,"response count":-1,"response size":-1,"request content":""}
{"level":"info","ts":"2024-07-13T09:13:34.415292Z","caller":"zapgrpc/zapgrpc.go:174","msg":"[transport] [server-transport 0xc000956000] Closing: EOF"}
{"level":"info","ts":"2024-07-13T09:13:34.415466Z","caller":"zapgrpc/zapgrpc.go:174","msg":"[transport] [server-transport 0xc000956000] loopyWriter exiting with error: connection error: desc = \"transport is closing\""}