Kubernetes (K8s) 从入门到实战:命名空间、Pod、Controller、Service,图文并茂

简介: 本文是K8s初学者的实战笔记,系统讲解命名空间(隔离资源、环境划分、权限控制)、Pod(最小调度单元、多容器、标签、生命周期、探针、资源限制)、控制器(Deployment灰度发布/回滚、StatefulSet/Job/DaemonSet)及Service(ClusterIP/NodePort、负载均衡、多端口)等核心概念与操作,附带丰富命令示例和原理剖析。

1 前言

我也是最近才学习的 K8s,所以把我学习的顺序、经验、踩过的坑、教程给记录下来,加深自己印象的同时也能帮助他人

2 命名空间

2.1 背景

为什么先讲 namespace,这是一个贯穿全文的概念,创建 PodServiceController 都会需要用到它,所以必须要先讲它。每讲到一个新的技术、新的知识时,我们都需要带着疑问去看待它,比如:

  • 它是什么意思,它的概念是什么
  • 它有什么作用,如果没有它,会怎么样,有了它之后,能解决什么问题
  • 怎么使用它,它有哪些用法
  • 笔者对它的了解是来源于哪里,有没有官方文档去介绍它。这样才能知道笔者讲解的知识是真实的,还是胡编乱造的

2.2 简介

Kubernetes 中命名空间(Namespace)是用来隔离 Kubernetes 集群内的不同资源对象的一种方式。每个 Kubernetes 对象都必须被分配到一个命名空间中,而且默认情况下,一个对象只能被同一个命名空间内的其他对象访问,Kubernetes 可以帮助用户在同一集群内部部署多个独立的应用程序,每个应用程序都在自己的命名空间内运行

命名空间(NameSpace)的核心作用就是 隔离资源,体现在以下几个地方:

  • 同一命名空间下,资源是共享的。不同命名空间下,资源不共享,类似于 dev 一个环境,prod 一个环境
  • 逻辑分组与环境划分,比如 devprod 分组管理
  • 避免命名冲突,资源名称只需在同一个命名空间内唯一。不同命名空间中可以存在同名的资源
  • 精细化权限控制(RBAC):可以为不同用户授予仅针对特定命名空间的读写权限,实现最小权限原则
  • 轻量级多租户支持:在共享物理集群的场景下,命名空间是划分租户边界的基础。

试想一下,如果没有它,就存在以下的情况:

  • 你部署的 PodServiceController 等,别人都能直接看到,别人部署的你也能直接看到。
  • 所有人部署的 Pod 都在一起展示,哪个是你的?你还得费劲去找一下
  • 当你想杀某个 Pod 时,你可能会不小心把别人部署的 Pod 给杀了,这样就很危险

2.3 基本操作

2.3.1 创建命名空间

创建命名空间有 2 种方式。一种是:直接通过命令行

# 全写,创建一个名叫 testapp 的命名空间
kubectl create namespace testapp

# 简写
kubectl create ns testapp

另一种是:使用 yml 文件来创建命名空间,比如下面的脚本就是创建一个命名空间

apiVersion: v1
kind: Namespace
metadata:
  # 命名空间的名字
  name: ems

image-20251129140236178

2.3.2 列出所有命名空间

# 全写
kubectl get namespaces

# 简写
kubectl get ns

# 使用插件
kubens

注意:kubens 是一个插件,需要下载

image-20251129140250984

如上图所示,绿色高亮显示的是当前正在使用的命名空间

default 是默认的命名空间,是 k8s 自带的。

2.3.3 查看命名空间的详细信息

# 全写,查看命名空间详情  your_namespace 为你自己的命名空间名称
kubectl describe namespaces your_namespace

# 简写
kubectl describe ns your_namespace

2.3.4 删除命名空间

# 全写
kubectl delete namespaces your_namespace

# 简写
kubectl delete ns your_namespace

2.3.5 切换命名空间

# 切换命名空间
kubens ems(命名空间的名字)

image-20251129140320542

切换命名空间后,再列出所有命名空间,可以看到,绿色高亮的已经变了,说明 ems 才是当前正在使用的命名空间

2.3.6 创建 Pod 时指定命名空间

# 部署应用到指定的命名空间
kubectl apply -f app.yml --namespace testapp

也可以在创建 Pod 的脚本中指定命名空间

image-20251129140517474

2.3.6 查询指定命名空间下的 Pod

# 查询指定命名空间下的Pod
kubectl get pod --namespace kube-system

# 或者
kubectl get pod -n kube-system

2.4 说明

下面是一些常见的可以跨命名空间的资源对象

  • Node :节点(服务器),是不受命名空间约束的
  • Namespace
  • ClusterRole
  • ClusterRoleBinding
  • CustomResourceDefinition

下面是一些不能跨命名空间的资源对象

  • Pod
  • ReplicaSet
  • Deployment
  • Service
  • ConfigMap
  • Secret
  • Ingress
  • PersistentVolume
  • PersistenVolumeClaim
  • Role
  • RoleBinding
  • ServiceAccount

2.5 官方文档

K8s 官方文档中有 2 个地方介绍命名空间,官方文档地址如下:

https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/namespaces/

https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/namespaces/

1 个介绍出现在官方文档中的 概念 → 概述 → 使用 Kubernetes 对象 → 命名空间(Namespaces)

image-20260524204715474

这一章节主要介绍 命名空间 的概念

2 个出现在:任务(Tasks)→ 管理集群 → 使用命名空间共享集群

image-20260524212626314

这一章节,主要介绍怎么怎么命名空间,比如如何创建、查看、删除等

需要额外注意的是:

image-20260524212737476

k8s4 个默认的命名空间

3 pod

3.1 简介

K8s 官网:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/#working-with-pods

Pod 是可以在 k8s 中创建和管理的、最小的可部署的计算单元

Pod 是一组(一个或多个容器),这些容器共享存储、网络、以及怎样运行这些容器的声明。这些容器在共享的上下文中运行。

Pod 的核心作用:

  1. 统一调度单位k8s 不直接调度单个容器,而是调度 Pod
  2. 共享网络命名空间Pod 内所有容器共享同一个 IP 地址、端口空间和路由表。容器之间可通过 localhost 直接通信,无需跨网络
  3. 共享存储卷:通过定义 Volumes,多个容器可挂载同一份存储
  4. 统一生命周期管理Pod 作为一个整体被创建、健康检查、重启、扩缩容、销毁

3.2 Pod 基本操作

3.2.1 查看 Pod

1 查看有哪些 Pod

# 有三种写法,每种效果都一样
kubectl get pod
kubectl get pods
kubectl get po

这 3 个命令是一样的效果

image-20251118210904320

上图中,表示在默认的命名空间中没有 Pod

2 查询指定命名空间下的 Pod

# 标准语法为:
kubectl get pod -n 命名空间名称

# 实战演示
kubectl get pod -n kube-system

-n:表示要查指定命名空间

kube-system :表示 k8s 自带的系统命名空间

image-20251118211517770

3 查询所有命名空间下的 Pod

kubectl get pod -A

image-20251118211610837

4 查看所有 Pod 的详细信息

# 查看默认命名空间下的pod的详细信息
kubectl get pod -o wide

# 查看系统命名空间下的pod信息
kubectl get pod -o wide -n kube-system

#查看所有命名空间下的所有pod的详细信息
kubectl get pod -o wide -A

image-20251118211812389

ip:表示容器的 IP

5 查看指定 pod 的详细信息

# 查看指定pod的详细信息,比如pod名字是nginx
kubectl describe pod nginx

image-20251118214105676

image-20251118214518625

给大家解析一下关键信息

Name: nginx,表示 pod 的名称是 nginx

Namespaces: default,表示命名空间的名称是 default

IP:10.244.2.5,表示容器内的 IP

最后的 Events 表示事件,这个比较重要,因为很多时候排查问题需要用到它

image-20251118214932333

Events 这几行信息到底是什么意思呢?

default 命名空间下的 pod: nginx,调度到 k8s-n3 节点上运行

拉取镜像 nginx:1.19

成功拉取镜像 nginx:1.19,花了 19s 的时间

创建 nginx 容器

启动 nginx 容器

6 实时监控 pod 的状态

kubectl get pod -w

-w:表示监控的意思

image-20251118212158808

7 Pod 相位状态

  1. PendingPod 创建过程中,但它尚未被调度完成
  2. RunningPod 中所有容器都被创建成功
  3. CompletedsuccessedPod 中所有容器都已经成功终止,并不会被重启
  4. FailedPod 中的容器至少有一个容器退出是非 0 状态
  5. Unknow:无法正常获取 Pod 对象的状态信息

3.2.2 创建 Pod

官网参考地址:https://kubernetes.io/zh-cn/docs/reference/kubernetes-api/workload-resources/pod-v1

下面写一个最简单的 pod ,写在了配置文件中,配置文件名字叫 nginx-pod.yml

apiVersion: v1                       #代表使用的 api 版本
kind: Pod                             #代表创建类型
metadata:                             #元数据信息,指定 pod 名称以及 namespace
  name: nginx                           #pod 的名称
spec:                                 # 对Pod预期行为的规约,可以理解成描述容器的
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80

创建 pod 的命令:

kubectl create -f nginx-pod.yml

kubectl apply -f nginx-pod.yml

注意:create 仅仅是不存在时创建,如果已经存在则报错。apply 不存在时创建,存在时更新配置

官方文档中对 pod 也做了介绍

image-20251118215551118

  • metadata:对象的元数据信息,表示对对象的描述,前面的脚本中,对象是 Pod,那 metadata 就是对 Pod 的描述,比如这个 Pod 的名字叫什么、命名空间是什么、标签是什么
  • spec :是规约,是对 Pod 预期行为的规约。是用来描述容器信息的,比如用什么镜像、数据卷是什么、端口是什么

思考问题比较有深度的同学可能会想到,metadata 除了可以描述 Pod 名称、命名空间、标签,还能描述什么呢?

image-20260526220746223

点进 ObjectMeta

image-20260526220940321

上图发现,还有 generateName,但是这个东西几乎用不上,所以大家了解一下即可

image-20260526221106396

spec 下面有哪些属性可以设置,也可以点击 PodSpec

image-20260526221314214

上图可以看到,spec 下面有很多属性

image-20260526221608234

常用的存储卷、节点选择、节点名称等,都是 spec 下的属性。这些属性有什么作用,等后面再讲。这里先对 Podmetadataspec 混个脸熟

3.2.3 删除 Pod

删除指定 pod ,比如 pod 名字是 nginx

kubectl delete pod nginx

# 删除某命名空间下的 Pod
kubectl delete pod <pod名称> -n <命名空间名称>

也可以根据配置文件来删

kubectl delete -f nginx-pod.yml

由于 Pod 是隶属于控制器下,控制器会监控 Pod 的健康值,出现问题会重启几次失败后删除重新创建,所以会出现删除后又出现的情况

3.2.4 进入 Pod 容器中

比如 Pod 名字叫 nginx ,怎么进入 Pod

kubectl exec -it nginx (pod名称) -- bash

# 进入指定 pod 中指定容器
kubectl exec -it pod名称 -c 容器名称 -- bash

# 比如进入nginx的pod中的nginx的容器
kubectl exec -it nginx -c nginx -- bash

3.2.5 查看 Pod 日志

# 查看pod中第一个容器日志
kubectl logs -f nginx (pod名称)

# 查看pod中指定容器的日志
kubectl logs -f pod名称 -c 容器名称

3.2.6 查看 Pod 描述信息

kubectl describe pod nginx(pod名称)

# 查看某命名空间下的 Pod 详情
kubectl describe pod nginx(pod名称) -n your_namespace

image-20251118221947086

3.3 Pod 运行多个容器

3.3.1 创建 Pod

apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
      app: mypod
spec:
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80

    - name: redis
      image: redis:5.0.10
      ports:
        - containerPort: 6379

文件名叫 mypod.yml

创建 pod

kubectl apply -f mypod.yml

查看 pod

kubectl get pod

image-20251118223216478

如上图所示,ready 的值为 2 ,表示 Pod2 个容器

3.3.2 查看 pod 日志

# 查看pod中第一个容器的日志
kubectl logs -f mypod

image-20251118223413709

查看 pod 中指定容器的日志,比如查看 redis 的日志

kubectl logs -f mypod -c redis

image-20251118223530964

3.3.3 进入容器

# 默认进入pod中第一个容器
kubectl exec -it mypod -- bash

进入 Pod 中指定容器,比如进入 redis 容器

kubectl exec -it mypod -c redis - bash

image-20251118223740302

3.4 Pod 的 Labels 标签

标签(Labels)是附加到 Kubernetes 对象(比如 Pod)上的键值对。标签旨在用于指定对用户有意义且相关的对象的标识属性

标签的作用:就是用来给 k8s 中对象起别名,有了别名就可以过滤和筛选。

下面创建一个 Pod,脚本为:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80

    - name: redis
      image: redis:5.0.10
      ports:
        - containerPort: 6379

下图中可以看到,执行 kubectl get pods,是看不到标签的

image-20251119210338437

3.4.1 查看标签

kubectl get pods --show-labels

image-20251119210603174

3.4.2 添加标签

kubectl label pod myapp env=dev

kubectl label :固定写法,标识打标签

pod :给谁打标签呢?给 pod 打标签

myapp :给哪个 pod 打标签呢?名字叫 myapppod

env=dev :标签的键值对

image-20251119210850485

3.4.3 覆盖标签

比如标签写错了,写成了 env=dev,但是实际上想打的标签是 env=test

kubectl label --overwrite pod myapp env=test

--overwrite : 表示覆盖

3.4.4 删除标签

kubectl label pod myapp env-

- 号代表删除标签

env - : 表示删除 keyenv 的键值对

3.4.5 根据标签筛选

# 筛选出标签为env=test的pod
kubectl get pod -l env=test

# 筛选出所有带env的pod
kubectl get pod -l env

# 筛选出key不为env的pod
kubectl get pod -l '!env'

# 筛选出所有key为env,值为test或prod的pod
kubectl get pod -l 'env in (test,prod)'

# 筛选出所有key为env,值不为test和prod的pod
kubectl get pod -l 'env notin (test,prod)'

3.5 Pod 的生命周期

参考官网:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/pod-lifecycle/

Pod 本身不具有自愈能力,挂了就是挂了,不会自动重启。只是在控制器的管理下,才具有更高级的能力

image-20251119212630202

3.6 容器特性

3.6.1 容器的生命周期

一旦调度器将 Pod 分派给某个节点,kubelet 就通过容器运行时开始为 Pod 创建容器,容器的状态有 3 种:Waiting(等待)、Running(运行中)和 Terminated(已终止)

要检查 Pod 中容器的状态,可以使用 kubectl describe pod 。其输出中包含 Pod 中每个容器的状态

Waiting(等待):处于 Waiting 状态的容器仍在运行它完成启动所需要的操作,比如从某个镜像仓库拉取镜像

Running(运行中):表明容器正在执行状态并且没有问题发生,如果配置了 postStart 回调,那么该回调已经执行且已完成

Terminated(已终止):处于 Terminated 状态的容器已经开始执行并且或者正常结束或者因为某些原因失败。如果容器配置了 preStop 回调,则该回调会在容器进入 Terminated 状态之前执行

3.6.2 容器生命周期回调/事件/钩子

PostStart:这个回调在容器被创建之后立即被执行。但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。没有参数传递给处理程序

PreStop:在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等)而被终止之前,此回调会被调用。如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。没有参数传递给处理程序

下面举个例子

image-20251119215514228

创建 pod 后进入 nginx 容器,发现根目录下有个 start.txt 文件

image-20251119215646267

删除容器

kubectl delete -f lifecycle

会睡 30s,容器才会真的删除

image-20251119220017813

上图中这 2 行展示出来,等了 30s

3.6.3 容器重启策略

restartPolicy 表示重启策略,它跟 containers 是同级的

apiVersion: v1
kind: Pod
metadata:
  name: mypod
  labels:
    name: mypod
spec:
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80
  restartPolicy: Always

容器的重启策略有 3 种:

Always:总是重启

OnFailure:容器异常退出状态非 0 重启

Never:永不重启

下图演示 Always:已经启动了一个 Pod,进入 Pod,关掉 nginx 容器,在执行 nginx -s stop 令之前,Pod 的状态还是 Running

image-20251119220854742

执行 nginx -s stop 之后,Pod 停止了,但又立马重启了

image-20251119221103403

3.6.4 自定义容器启动命令

自定义容器的启动命令:就是修改容器默认的启动命令

和 Docker 容器一样,k8s 中容器也可以通过 command、args 用来修改容器启动默认执行命令以及传递相关参数。但一般推荐使用 command 修改启动命令,使用 args 为启动命令传递参数

command:表示指令

args:表示参数

image-20251119222213903

3.6.5 容器探针

定义:容器探针 就是用来定期对容器进行健康检查的

image-20251119224059823

探针参数

initialDelaySeconds:5    # 初始化时间5s,表示容器容器5s之后才开始第一次探测
periodSeconds: 4         # 检测时间间隔4s,隔多长时间进行一次探针
timeoutSeconds: 1        # 默认检测超时时间为1s,一旦超过几秒之后,超时了,就认为失败
failureThreshold: 3      # 默认失败次数为3次,达到3次后重启pod
successThreshold: 1      # 默认成功次数为1次,1次检测成功代表成功

image-20251119223246590

容器启动后睡 7s,

initialDelaySeconds: 5,表示初始探针为 5s,也就是容器启动 5s 之后去检测一次。检测时还处于睡眠状态,肯定检测失败

periodSeconds: 5,表示每 4s 检测一下。前面容器启动 5s 后检测一次,现在每 4s 又检测一下,那 4s 过后,相当于第 9s 了,此时容器苏醒,正常运行了,nginx.pid 文件已经存在了

使用 tcpSocket,如果 tcp 检测到了 80 端口,表示 nginx 容器已经启动成功了,没有检测到 80 端口,说明 nginx 没启动成功,检测失败,如下图所示:

image-20251119224859586

使用 httpGet,也就是去发送一个 http 请求,如果给我一个 200 之类的响应,则我认为是容器启动成功了

image-20251119225448817

nginx 有一个静态页面,访问 80 端口,路径是 index.html,如果能拿到结果,则认为这个 http 是请求成功的,拿不到说明这个 http 请求是失败的

3.6.6 资源限制

容器的资源限制,就是对容器的内存、CPU 进行资源限制。超过最大资源之后,容器会被 kill,oom 错误。如果不限制,容器是可以不限制地使用当前节点(服务器)的内存和 CPU 的

对内存的请求和限制

apiVersion: v1
kind: Pod
metadata:
  name: nginx
​
spec:
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80
      resources:
        requests:
          memory: 100M
        limits:
          memory: 200M

  restartPolicy: Always

上面是一个资源限制的脚本:

requests:表示请求,申请资源是基础资源,memory: "100Mi"表示内存 100M,实际使用内存可以超过 100M,也可以少于 100M

limits:表示最大限制,memory: "200Mi",最大内存不能超过 200M,可以等于 200M

怎么查看 pod 当前使用了多少资源呢?

kubectl top pod nginx(pod名称)

image-20251120204507680

内存请求和资源限制的目的:

可以有效利用集群节点上可用的内存资源。将 Pod 的内存请求保持在较低水平,可以更好地安排 Pod 调度。通过让内存限制大于内存请求,可以完成两件事:

Pod 可以进行一些突发活动,从而更好地利用可用内存

Pod 在突发活动期间,可使用的内存被限制为合理的数量

对 CPU 的请求和限制:

为容器之前 CPU 请求

apiVersion: v1
kind: Pod
metadata:
  name: nginx-memory-demo

spec:
  containers:
    - name: nginx-memory-demo
      image: nginx:1.19
      ports:
        - containerPort: 80
      resources:
        requests:
          cpu: 1m
        limits:
          cpu: 4m

  restartPolicy: Always

CPU 资源以 CPU 单位度量。小数值是可以使用的,一个请求 0.5CPU 的容器保证会获取请求 1 个 CPU 的容器的一半。可以使用后缀 m 表示毫。例如 100m CPU、100 milliCPU 和 0.1CPU 都相同。CPU 请求只能使用绝对数量,而不是相对数量。0.1 在单核、双核或 48 核计算机上的 CPU 数量值是一样的

3.7 Pod 中 init 容器

官网地址:https://kubernetes.io/zh-cn/docs/concepts/workloads/pods/init-containers/

init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本

init 容器与普通的容器非常像,除了以下特点:

1、它们总是运行到完成。如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。然而,如果 Pod 对应的 restartPolicy 值为 never,并且 pod 的 init 容器失败,则 kubernetes 会将整个 Pod 的状态设置为失败

2、每个都必须在下一个启动之前成功完成

3、同时 Init 容器不支持 lifecycle、livenessProbe、readinessProbe 和 startupProbe,因为它们必须在 Pod 就绪之前运行完成

4、如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。每个 Init 容器必须运行成功,下一个才能运行。当所有的 Init 容器运行完成时,Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行

5、Init 容器支持应用容器的全部字段和特性,包括资源限制、数据卷和安全设置。然而,Init 容器对资源请求和限制的处理稍有不同

3.8 节点亲和性分配 pod

官网地址:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node.html

可以约束 Pod 以便限制其只能在特定的节点上运行,或优先在特定的节点上运行。例如:将两个不同的服务且有大量通信的 `Pod 放在同一可用区

3.8.1 给节点加标签

官方文档:https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-pods-nodes/#add-a-label-to-a-node

# 获取所有集群中所有节点
kubectl get nodes

image-20251120214224078

# 查询节点的标签
kunectl get nodes --show-labels

image-20251120214327570

上图中是 k8s 自动给节点打的标签(默认标签)

# 给节点添加标签,比如打标签disk=ssd
kubectl label nodes k8s-n2(节点名) disk=ssd

image-20251120214701895

3.8.2 选择节点标签指派 Pod

nodeSelector :节点选择

apiVersion: v1
kind: Pod
metadata:
  name: nginx-memory-demo
​
spec:
  containers:
    - name: nginx-memory-demo
      image: nginx:1.19
      ports:
        - containerPort: 80
  restartPolicy: Always
  nodeSelector:  # 明确指定当前pod指派到哪个标签对应节点上
    disk: ssd    # 将pod指派到标签为disk=ssd的标签上

如果有多个节点都带有 disk=ssd 的标签呢?则会先找到所有带有 disk=ssd 标签的节点,然后将 pod 随机分配到其中一个节点里

如果没有节点带有 disk=ssd 标签,或者有节点带有 disk 标签,但是值都不为 ssd ,怎么办?则 Pod 一直处于 Pending 状态,无法调度,因为找不到对应节点

image-20251120215631040

3.8.3 指派 Pod 到指定节点

如果我就是只想把 Pod 指派到某个节点上,怎么做?使用 nodeName

apiVersion: v1
kind: Pod
metadata:
  name: nginx-memory-demo
​
spec:
  containers:
    - name: nginx-memory-demo
      image: nginx:1.19
      ports:
        - containerPort: 80
  restartPolicy: Always
  # 根据节点名称指派Pod到指定节点
  nodeName: k8s-n2

同样,找不到节点名的时候,Pod 会一直处于 Pending 状态

3.8.4 根据亲和性和反亲和性指派 Pod 到指定节点

官网地址:http://kubernetes.p2hp.com/docs/concepts/scheduling-eviction/assign-pod-node.html

使用 亲和性反亲和性 的好处:

  1. 亲和性、反亲和性语言的表达能力更强。nodeSelect 只能选择拥有所有指定标签的节点。亲和性、反亲和性可以为你提供选择逻辑的更强控制能力
  2. 你可以标明某规则是“软需求”或者“偏好”,这样调度器在无法找到匹配节点时仍然调度该 Pod
  3. 你可以使用节点上(或其他拓扑中)运行的其他 Pod 的标签来实施调度约束,而不是只能使用节点本身的标签。这个能力让你能够定义规则允许哪些 Pod 可以被放置在一起

亲和性由两种类型的亲和性组成:

  • 节点亲和性,功能类似于 nodeSelector 字段,使你可以根据节点上的标签来约束 Pod 可以调度上哪些节点上
  • Pod 间亲和性/反亲和性,运行根据其他 Pod 的标签来约束 Pod

节点亲和性有 2 种:

  • requiredDuringSchedulingIgnoredDuringExecution:调度器只有在规则被满足的时候才能执行调度。此功能类似于 nodeSelectorrequired 就是必须的意思
  • preferredDuringSchedulingIgnoredDuringExecution:调度器会尝试寻找满足对应规则的节点。如果找不到匹配的节点,调度器仍然会调度该 Podpreferred 就是喜欢、尝试的意思

注意:IgnoredDuringExecution 意味着如果节点标签在 k8s 上调度该 Pod 后发生了变更,Pod 将继续运行

3.8.5 节点亲和性

使用 Pod 规约中的 .spec.affinity.nodeAffinity 字段来设置 节点亲和性

affinity :亲和性的意思

nodeAffinity :节点亲和性的意思

apiVersion: v1
kind: Pod
metadata:
  name: nginx
​
# 规约
spec:
  # 亲和性
  affinity:
    # 节点亲和性
    nodeAffinity:
      # 必须满足。节点必须包含一个键名为ssd的标签,且该标签的值必须为fast或superfast
      requiredDuringSchedulingIgnoredDuringExecution:
        # 节点选择
        nodeSelectorTerms:
            # 匹配表达式
          - matchExpressions:
                # 匹配什么样的标签呢呢?key为ssd
              - key: ssd
                operator: In
                values:
                  - fast
                  - superfast
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80
  restartPolicy: Always

上面这个示例中,所应用的规则如下:节点必须包含一个名为 ssd 的键,并且该标签的取值必须为 fastsuperfast

注意:可以使用 InNotInExistsDoesNotExistGtLt 之一作为操作符。NotInDoesNotExist 可用来实现节点反亲和性行为

requiredDuringSchedulingIgnoredDuringExecution 是强制的,required 是必须的意思,如果满足不了怎么办?

比如没有哪个节点的标签为 fastsuperfast,找不到这种节点,怎么办?Pod 会一直处于 Pending 状态

image-20251120223423335

apiVersion: v1
kind: Pod
metadata:
  name: nginx

spec:
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80
  restartPolicy: Always
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - preference:
            matchExpressions:
              - key: disk
                operator: In
                values:
                  - ssd
          weight: 2
        - preference:
            matchExpressions:
              - key: disk
                operator: In
                values:
                  - machinery
          weight: 90

preferredDuringSchedulingIgnoredDuringExecutionpreferred 是偏好、尝试的意思。也就说找不到节点也没关系,最后 Pod 也能分配到节点上进行调度

weight 是权重,取值范围是 1-100 。当满足多个条件时,Pod 分配到哪个节点上呢?看哪个权重最高,就分配到哪个节点

3.8.6 Pod 间亲和性

官方文档:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity

与节点亲和性类似,Pod 的亲和性与反亲和性也有 2 种类型:

  • requiredDuringSchedulingIgnoredDuringExecution
  • preferredDuringSchedulingIgnoredDuringExecution

例如:可以使用 requiredDuringSchedulingIgnoredDuringExecution 亲和性来告诉调度器,将 2 个服务的 Pod 放到同一个云提供商可用区内,因为它们彼此之间通信非常频繁

要使用 Pod 间亲和性,可以使用 Pod 规约中的 spec.affinity.podAffinity 字段。对应 Pod 间反亲和性,可以使用 Pod 规约中的 spec.affinity.podAntiAffinity

apiVersion: v1
kind: Pod
metadata:
  name: redis
​
spec:
  containers:
    - name: redis
      image: redis:5.1.10
      ports:
        - containerPort: 6379
  restartPolicy: Always
  # 亲和性
  affinity:
    # Pod间亲和性
    podAffinity:
      # 必须满足
      requiredDuringSchedulingIgnoredDuringExecution:
        - topologyKey: bj
          # 标签选择器,Pod怎么知道跟哪个Pod要亲和、要部署在一起呢?就是通过Pod的标签
          labelSelector:
            # 匹配表达式
            matchExpressions:
              # Pod标签的key
              - key: app
                # 操作符
                operator: In
                # Pod标签的值是哪些
                values:
                  - nginx

topologyKey :拓扑域参数,值是节点的标签,比如集群中有 20 个节点( 20 台服务器),20 个节点上都部署了 nginx ,其中 10 个节点在北京(节点标签是 bj=bj ),10 个在杭州(节点标签是 hz=hz )。但是我想把新的 Pod 部署到北京的节点上,而不是杭州的节点上,因为新的 Pod 需要频繁跟北京节点上的 Pod 通信。所以 bj 就是北京节点的标签,表示我选择北京这个域

为什么 topologyKey 的值是节点的标签?

image-20260531221200004

上图是官方文档中的一段话,总结就是:Pod 会继承节点的拓扑标签( topology.kubernetes.io/zonetopolopy.kubernetes.io/region

apiVersion: v1
kind: Pod
metadata:
  name: nginx

spec:
  containers:
    - name: nginx
      image: nginx:1.19
      ports:
        - containerPort: 80
  restartPolicy: Always
  # 亲和性
  affinity:
    # Pod间亲和性
    podAffinity:
      # 尝试
      preferredDuringSchedulingIgnoredDuringExecution:
        - podAffinityTerm:
            topologyKey: bj
            labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - nginx
          weight: 10

        - podAffinityTerm:
            topologyKey: bj
            labelSelector:
              matchExpressions:
                - key: app
                  operator: In
                  values:
                    - logs
          weight: 90

也是去北京域的 10 台节点上去部署,具体去哪个节点上部署呢?如果多个节点上都有标签为 app=nginxapp=logsPod ,则根据权重来调度

Pod 规约中的 .affinity.podAffinity 来表示 亲和性affinity.podAntiAffinity 表示 反亲和性

3.8.7 污点容忍度

参考官网:https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration/

3.8.8 Pod 拓扑分布约束

参考官网:http://kubernetes.p2hp.com/docs/concepts/scheduling-eviction/topology-spread-constraints

4 Controller 控制器

4.1 Controller 控制器

4.1.1 什么是 Controller

参考官网:http://kubernetes.p2hp.com/docs/concepts/architecture/controller.html

Kubernetes 通常不会直接创建 Pod,而是通过 Controller 来管理 Pod。Controller 中定义了 Pod 的部署特性,比如有几个副本、在什么样的 Node 上运行等。通俗的说可以认为 Controller 就是用来管理 Pod 的。核心作用就是:通过监控集群的公共状态,并致力于将当前状态转变为期望的状态

4.1.2 常见的 Controller 控制器

Deployment:是最常用的 Controller,可以管理 Pod 的多个副本,并确保 Pod 按照期望的状态运行。ReplicaSet 以前也是一种 Controller,被合并到 Deployment 中了

Daemonset:是守护的意思,用于每个 Node 最多只运行一个 Pod 副本的场景。Daemonset 通常用于运行 daemon

Satefuleset:能保证 Pod 的每个副本在整个生命周期中名称是不变的,而其它 Controller 不提供这个功能。当某个 Pod 发生故障需要删除并重新启动时,Pod 的名称会发生变化,同时 StatefulSet 会保证副本按照固定的顺序启动、更新或删除

Job:用于运行结束就删除的应用,而其它 Controller 中的 Pod 通常是长期持续运行

4.1.3 Controller 如何管理 Pod

Controller 通过 label(标签)关联 Pods

image-20251122214126416

4.2 Deployment

官网地址:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/

作用:官网原话:你负责描述 Deployment 中的目标状态,而 Deployment 控制器(Controller)以受控速率更改实际状态,使其变为期望状态

这句话听着有点蒙,你只需要知道 Deployment 作为控制器的一种,自然也是用来管理 Pod 的。

什么叫 描述目标状态,更改实际状态,使其变成期望状态呢?

  • 描述目标状态:比如我希望 Pod3 个副本
  • 实际状态:但是现在只有一个 Pod
  • 变成期望状态:Deployment 会自动创建 2Pod ,这样就有 3 个副本,从而达到了期望状态

下面是一个创建 Deployment 的脚本

apiVersion: apps/v1
# 类型,已经不再是Pod了
kind: Deployment
# 控制器的元数据
metadata:
  # 控制器的名字
  name: nginx-deployment
  # 控制器的标签,必须和要管理的Pod的标签保持一致,不然创建控制器会报错
  labels:
    app: nginx
# 规约,针对控制器的规约,而不是Pod的规约
spec:
  # 副本数量,也就是说通过这个控制器创建的Pod,默认有几个副本。值为3,表示一下子就会创建3个Pod
  replicas: 3
  # 选择器,定义Deployment如何查找要管理的Pod,也就是定义Deployment要控制哪些带有哪些标签的Pod
  selector:
    matchLabels:
      app: nginx
  # 控制器的模板,描述控制器所管理的Pod
  template:
    # pod的元数据
    metadata:
      # Pod的名字
      name: nginx
      # Pod的标签
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:11.14.2
          imagePullPolicy: IfNotPresent
      restartPolicy: Always

注意:Deployment 的标签 = Pod 的标签 = selector 的标签

4.2.1 创建 deployment

创建控制器的命令跟创建 Pod 的命令一模一样

# 创建控制器
kubectl apply -f nginx-deployment.yml

4.2.2 查看 deployment

创建后怎么查看控制器呢

# 查看控制器
kubectl get deployment
或者
kubectl get deployments
​
# 只想查看某一个控制器
kubectl get deployment nginx-deployment(deployment的名称)

image-20251122220917719

Namedeployment 的名称

Ready:显示应用程序的可用的副本数,显示模式是”就绪个数 / 期望个数“

UP-TO-DATE :显示为了达到期望状态已经更新的副本数

AVAILABLE:显示应用可供用户使用的副本数

AGE :显示应用程序运行的时间

查看控制器的标签

# 查看控制器的标签
kubectl get deployment --show-labels

image-20251122221104429

如何查看控制器创建的 Pod 呢?

# 查看控制器创建的Pod
kubectl get pod

# 查看pod的标签
kubectl get pod --show-labels

image-20251122221147857

上图可以看到,Pod 的名称是 nginx-deployment-6f4f7cc4d8-nfntm ,其中 nginx-deployment 是控制器的名称,6f4f7cc4d8-nfntm 是自动生成的唯一标识。

如何查看 Deployment 创建的副本(ReplicaSet)呢 ?ReplicaSet 简称 rs

# 查看 Deployment 创建的副本
kubectl get rs

官网介绍了其输出:

image-20260602224923020

重点要记住 ReplicaSet 的名字,名字的组成是:[Deploymentm 名称]-[哈希]

Pod 的名称也是有规律的,名字的组成是:[ReplicaSet]-[随机 5 字符后缀]

4.2.3 更新 Deployment

官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/#pod-template-hash-label

为什么要更新 Deployment ?比如模板的标签、容器镜像需要被更新时,就需要更新 Deployment

image-20260603211824380

官方文档中给了更严格的定义:仅当 Deployment Pod 模板(即 .spec.template)发生改变时,才会触发 Deployment 上线

下面回忆下创建 Deployment 的脚本

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:            #从这里开始,一旦有改变,就会触发 Deployment 上线
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:11.14.2
          imagePullPolicy: IfNotPresent
      restartPolicy: Always

比如我想修改镜像名字,那我得修改脚本,然后删除 Deployment ,然后重新创建,看着有点麻烦,有没有简单一点的办法呢?还真有

image-20260603212738853

官方文档中给了 2 个命令来更新 DeploymentPod 的镜像。我觉得第 2 个命令更容易理解,更容易记住

kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
  • set image:修改镜像
  • deployment/nginx-deployment :修改谁的镜像?修改控制器中的 nxginx-deployment 这个 Pod 镜像
  • nginx=nginx:1.16.1 :修改 Pod 中哪个容器的镜像,改成什么镜像?修改名为 nginx 的容器的镜像,镜像名改成 nginx:1.16.1

如果想查看 Deployment 的上线状态,则:

kubectl rollout status deployment/nginx-deployment

得到的结果类似于:

Waiting for rollout to finish: 2 out of 3 new replicas have been updated...

deployment "nginx-deployment" successfully rolled out

这两行日志的意思也很简单:3 个副本中的 2 个已经被更新了、deployment 上线成功

image-20260603214531782

官方文档中也有介绍怎么查看上线状态

image-20260603214630746

再去查看 rs ,可以看到,rs 的名称也变了,后面的哈希字符串明显不一样了

image-20260603214753781

官方文档中也有介绍,重新查看 Pod,名称也变了

image-20260603215308204

官方文档中上面这段话特别重要,Deploymeny 在更新时,不是先把旧的 Pod 全部删掉,重新创建。而是先创建一部分,然后删除一部分,确保一直都有 Pod 可以用,这不就是典型的 灰度发布

有个细节很重点:Deployment 所创建的 Pod 数可能比期望的 Pod 数高一点点

image-20260603220446011

官方文档中,这两段话也很重要。

4.2.4 查看 deployment 的描述/细节

Pod 的标签中有 pod-template-hash=6f4f7cc4d8 ,这是控制器创建 Pod 时自动给 Pod 加的,不建议删除和修改

# 查看deployment的详细信息
kubectl describe deployment nginx-deployment(deployment的名称)
​
# 或者
kubectl describe deployment/nginx-deployment(deployment的名称)
​
# 查看某一控制器的详细信息,并以yaml的格式在屏幕上展示出来
kubectl get deployment nginx-deployment(deployment的名称) -o yaml
​
# 查看某一控制器的详细信息,并以yaml的格式,保存到test.yaml这个文件中
kubectl get deployment nginx-deployment(deployment的名称) -o yaml >> test.yaml
​
# 查看某一控制器的详细信息,并以json的格式展示出来
kubectl get deployment nginx-deployment(deployment的名称) -o json

image-20251122223202956

4.2.5 删除 deployment

# 删除deployment
kubectl delete -f nginx-deployment.yml         (推荐,删除的更干净)
或者
kubectl delete deployment nginx-deployment
​
# 删除默认命名空间下的全部资源
kubectl delete all --all
​
# 删除指定命名空间下的全部资源
kubectl delete all --all -n 命名空间名称

建议上述命令删除 deployment ,因为这样可以把 deploymentPod 一起删除干净

4.2.6 扩缩 deployment

官方文档:https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/#scaling-a-deployment

deployment 和 replicaset 合二为一了,deployment 具备 replicaset 的所有功能

查询副本
kubectl get rs
或者
kubectl get replicaset

image-20251122223739653

Name:Pod 名称

Desired:期望有几个副本

Current:当前有几个副本

Ready:已经准备好了几个副本

# 伸缩扩展副本
kubectl scale deployment nginx-deployment -replicas=3
或者
kubectl scale deployment/nginx-deployment -replicas=3

scale:扩展、调节的意思

deployment:扩展什么呢?扩展 deployment

nginx-deployment:扩展哪个 deployment 呢?扩展名字叫 nginx-deployment 的 deployment

replicas:副本数

image-20251122224348926

上图中可以看到,扩展副本数为 3 个,查看 Pod,确实有 3 个 Pod

3 个一模一样的 Pod,不会端口冲突吗?不会。3 个 Pod 都是管理独立的容器,端口又没对外暴露,怎么会端口冲突呢

所以,现在通过浏览器是访问不了容器的,因为端口都在容器内部,都没映射出来,肯定访问不了

注意:3 个副本,不会全都分配在一台服务器上,而是随机分配到多台服务器上的

image-20251122224838857

上图中设置 10 个副本,但是 Pod 有些是分配在 n2 节点上,有些是分配在 n3 节点上

注意:副本数可扩展、也可以伸缩

4.2.7 回滚 deployment

为什么要有回滚呢?比如我的项目升级到了 1.2 版本,但是上线后有 bug ,我想到原来的 1.1 版本是个稳定版本,我想回退到 1.1 版本

k8s 以什么结点认为版本该记录一下呢?我的项目从 1.1 升级到 1.2K8s 怎么知道我更新了版本呢?仅当 Deployment Pod 模板(即 .sepc.template)发生改变时,例如模板的标签或容器镜像被更新,才会触发 Deployment 上线,其他更新(例如对 Deployment 执行扩缩的操作)不会触发上线动作

# 查看上线状态
kubectl rollout status deploymnet nginx-deployment(deployment的名称)
#或者
kubectl rollout status deploymnet/nginx-deployment(deployment的名称)

rollout :展示

status :状态

deploymnet :固定写法,

nginx-deploymnetdeploymnet 名称。要展示哪个 deployment 的上线状态呢,展示名字叫 nginx-deploymnet 的上线状态

image-20251123162624252

如上图所示:一个名字叫 nginx-deploymnetdeploymnet ,成功上线

# 查看历史版本, deployment 的名称是 nginx-deploymnet
kubectl rollout history deploymnet nginx-deploymnet
# 或者
kubectl rollout history deploymnet/nginx-deploymnet

image-20251123162906620

可以看到只有一个版本。现在我修改下镜像版本,把原来的 1.19 版本改成 1.21 版本

image-20251123162958794

重新创建 deployment ,这里不需要删除再重新创建,直接创建就行

image-20251123163052559

再查看 deployment 的上线状态

image-20251123163221855

上图可以看到,deployment 已经上线完成,一个老的副本正在终止。termination 是终止的意思。稍等几秒再执行一下

image-20251123163403540

上图可以看到,deployment 已经上线成功了。此时查看 Pod ,发现 Pod 的名称中的尾号已经变了,说明已经是个新的 Pod

image-20251123163534042

再查看 Deployment 的详细信息

kubectl get deployment nginx-deployment -o yaml

可以看到,nginx 的镜像版本确实变成了 1.21

image-20251123164016076

现在看下这个 deployment 有几个版本呢?

# 查看历史版本
kubectl rollout history deploymnet nginx-deploymnet(deploymnet的名称)

可以看到,有 2 个版本了,一个是 nginx:1.19 ,一个是 nginx:1.21

image-20251123164202541

现在想查看某个历史版本的详细信息

# 查看某个历史版本的详细信息
kubectl rollout history deployment nginx-deployment --revision=1

1 :表示版本号

可以看到 1 版本的 nginx 镜像的版本号是 1.19

image-20251123164544794

如果想看 2 版本,可以看到 2 版本的 nginx 镜像是 1.21

image-20251123164626541

那现在我想回退到 1 版本怎么办?

# 回退到指定版本
kubectl rollout undo deployment nginx-deployment --to-revision=1

# 回退到上个版本
kubectl rollout undo deployment nginx-deployment

1 :表示想回退到 1 版本

image-20251123164936376

上图可以看到,deployment 回退成功。但是再次查看历史版本,发现只有 23 ,没有 1 了。这是因为 13 是一样的,所以 1 就不保留了

查看 3 版本的详细信息,可以看到 3 版本的 nginx 镜像版本是 1.19 ,说明回退真的成功了

image-20251123165229272

# 重新部署
kubectl rollout restart deployment nginx-deployment

尽管我的文件中写的是 1.21 版本,但是前面我已经回退到了 1.19 版本,但是文件中还是 1.21,此时我重新部署,镜像会是哪个版本呢?肯定还是 1.19 版本的

4.3 StatefulSet

4.3.1 什么是 StatefulSet

官网地址:http://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/statefulset

StatefulSet 是用来管理有状态应用的工作负载 API 对象

什么是无状态应用?什么是有状态应用呢?

无状态应用:应用本身不存储任何数据的应用

有状态应用:应用本身需要存储相关数据的应用

StatefulSet 用来管理某 Pod 集合的部署和扩缩(所以 StatefulSet 具备 Deployment 的一些特性),并为这些 Pod 提供持久存储和持久标识符号。

和 Deployment 类似,StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是,StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的,但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID

4.4 DaemonSet

官网地址:http://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/daemonset

daemonSet 是守护的意思。比如守护进程表示当前机器上必须运行的一个进程,系统开机就会运行

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。当有节点加入集群时,也会为它们新增一个 Pod。当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod

DaemonSet 的一些典型用法:

1、在每个节点上运行集群守护进程

2、为每个节点上运行日志收集守护进程

3、为每个节点上运行监控守护进程

5.5 Job

官网地址:http://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/job

Job 是任务,类似定时任务,执行完了就结束了

Job 会创建一个或多个 Pod,并将继续重试 Pod 的执行,直到指定数量的 Pod 成功终止。随着 Pod 成功结束,Job 的跟踪记录成功完成的 Pod 个数。当数量达到指定的成功个数阈值时,任务(即 Job)结束。删除 Job 的操作会清除所创建的全部 Pod。挂起 Job 的操作会删除 Job 的所有活跃 Pod,直到 Job 被再次恢复执行

比如:运行一个 π 镜像,让它打印小数点后 2 千位,那打印完,Pod 就会成功终止

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  # ttl机制,完成任务之后多少秒之后,删除Pod
  ttlSecondsAfterFinished: 100
  template:
    spec:
      containers:
        - name: pi
          image: perl:5.34.0
          command: [ "perl", "-Mbignum=bpi", "-wle", "print bpi(2000)" ]
          imagePullPolicy: IfNotPresent
  # 当前任务出现失败,最大的重试次数
  backoffLimit: 4

没有 ttlSecondsAfterFinished,则 Pod 还在,只是是终止状态

image-20251123204420742

有了 ttlSecondsAfterFinished,则 Pod 会自动删除

5 Service

控制器无法为 Pod 提供网络服务,service 的作用就是为 Pod 提供网络服务

5.1 什么是 Service

官网地址:https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/

定义:将运行在一个或一组 Pod 上的网络应用程序公开为网络服务的方法。说人话:就是为 Pod 提供网络服务,让我们在浏览器上可以访问

问题:如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能,那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?

image-20251123205828744

如果这是一个图片处理后端,它运行了 3 个副本。这些副本是可互换的,前端不需要关心它调用了哪个后端副本。然而组成这一组后端程序的 Pod 实际上可能会发生变化,前端客户端不应该也没必要知道,而且也不需要跟踪这一组后端的状态。Service 定义的抽象能够解耦这种关联

image-20251123210111946

5.2 特性

1、Service 通过 label 关联对应的 Pod ,这点跟控制器一样

2、Service 生命周期不跟 Pod 绑定,不会因为 Pod 重新创建而改变 IP

3、提供了负载均衡功能,自动转发流量到不同 Pod

4、可对集群外部提供访问端口

5、集群内部可通过服务名字访问

5.3 Service 和 Pod 的关系

Service 能够将一个接收 port 映射到任意的 targetPort。也就是说:port 是对外开放的端口,targetPortPod 的端口

一般 Service 使用会和 Deployment 放在一个 yml 文件里,使用 3 个横杠分开,这样 K8s 集群就知道我们要创建另外一个对象

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:                 # pod 的模板
    metadata:
      name: nginx           # pod 的名称
      labels:
        app: nginx
    spec:                   # pod 的规约
      containers:
        - name: nginx
          image: nginx:1.19
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
      restartPolicy: Always
​
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
  # Service服务的名字
  name: nginx
spec:
  # 选择哪些Pod呢?选择标签为app=nginx的Pod
  selector:
    app: nginx
  ports:
    - protocol: TCP
      # Service服务的端口,只能在集群内部访问,外部(比如浏览器)是访问不了的
      port: 80
      # 容器的端口
      targetPort: 80
      #nodePort 30124       # 节点端口,范围 30000-32767,注意此时没有指定节点端口,k8s会自动生成一个节点端口
  type: NodePort

下图可以看到,创建时,不仅创建了 Deployment ,还创建了 Service

image-20251123211916143

查看 Pod ,发现 Pod 也创建成功了,如下图所示:

image-20251123212011906

但是查看 Service ,发现有 2 个,第一个是集群默认的,第二个才是我们新创建的。

NodePort :既可以集群内访问,也可以集群外访问。集群内访问:进入一个 Pod 中,访问另一个 Pod 的端口。上图中,集群内端口是 8010.107.63.196 是集群内 IP ,只能集群内部访问,外网是访问不了 10.107.63.196 这个 IP

10.107.63.196 是集群内的 IP ,只能集群内访问,集群外无法访问

下图是进入一个 Pod

image-20251123214511041

Pod 中访问 nginx 所在的 PodIP 和端口,可以看到,在一个 Pod 内访问另外一个 Pod ,是可以访问的

image-20251123214641117

外部(比如浏览器)怎么访问 nginx 呢?下图中的 80K8s 集群内部的端口,30124 才是虚拟机(或服务器)的端口,那浏览器想要访问 nginx ,就得通过服务器 IP + 30124 端口才能访问。可以看到 nginx 是运行在 k8s-n2 节点上,所以通过 k8s-n2 节点的 IP (也就是服务器 IP ,注意不是 10.244.1.42 )+30124 端口才能访问 nginx

10.244.1.41 也不是服务器的 IP ,外网也无法访问

image-20251123213904819

30124 是自动生成的端口,如果想自定义节点端口(虚拟机或服务器的端口),可以在 yml 文件中这样写

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.19
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
      restartPolicy: Always

# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
  # Service服务的名字
  name: nginx
spec:
  # 选择哪些Pod呢?选择标签为app=nginx的Pod
  selector:
    app: nginx
  ports:
    - protocol: TCP
      # Service服务的端口,只能在集群内部访问,外部(比如浏览器)是访问不了的
      port: 80
      # 容器的端口
      targetPort: 80
      # 节点端口,供外部(比如浏览器)访问的。节点端口固定在30000 - 32767之间
      nodePort: 31001
  type: NodePort

重新创建 Service 之后,发现节点端口已经变了

注意:10.244.1.4110.244.1.42,都不是节点 IP(虚拟机或服务器的 IP

5.4 查看 Service

查看 service 的命令如下:

kubectl get service
​
# 或者简写
kubectl get svc

5.4 负载均衡

先修改 yml 文件,设置副本为 3

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  # Pod的副本数
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.19
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
      restartPolicy: Always
​
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
  # Service服务的名字
  name: nginx
spec:
  # 选择哪些Pod呢?选择标签为app=nginx的Pod
  selector:
    app: nginx
  ports:
    - protocol: TCP
      # Service服务的端口,只能在集群内部访问,外部(比如浏览器)是访问不了的
      port: 80
      # 容器的端口
      targetPort: 80
      # 节点端口,供外部(比如浏览器)访问的。节点端口固定在30000 - 32767之间
      nodePort: 30124
  type: NodePort

创建后发现,有 3nginxPod

image-20251123215624635

查看 Service ,集群内部端口是 80 ,集群外部端口是 30124 。并且 3nginx1 个运行在 k8s-n2 节点上,2 个运行在 k8s-n3 节点上

image-20251123215748078

现在先后进入 3nginx 容器,修改 index.html 文件,第 1 个改成 “nginx-1”,第 2 个改成 “nginx-2” ,第 3 个改成 “nginx-3”

image-20251123220345262

现在一直访问 k8s-n3 节点的服务器 + 30124 端口,就会出现负载均衡的效果

10.15.0.23 是服务器的 ip

下图中可以看到,我访问的是同一机器的 ip ,但是出现了不同的效果,说明负载均衡生效了。但是生效时间有点长,需要多等待一下,再去刷新浏览器

image-20251123220759853

用服务器访问效果可能更明显,我在服务器上一直使用 curl 命令,也能出现不同效果,说明负载均衡确实生效了

image-20251123221025825

5.5 多端口

Service 可以暴露(或监听)多个端口

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.19
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 80
      restartPolicy: Always
​
# service和deployment是不同的对象,所以用3个横杠分开,这样K8s就知道我们要创建一个新的对象了
---
apiVersion: v1
kind: Service
metadata:
  # Service服务的名字
  name: nginx
spec:
  # 选择哪些Pod呢?选择标签为app=nginx的Pod
  selector:
    app: nginx
  ports:
    # Service服务的端口
    - protocol: TCP
      port: 8080
      # 给端口起个自定义的名字,表示端口的唯一标识,多端口不加name字段,创建时会报错
      name: write
      # 容器的端口
      targetPort: 80
      # 节点端口,固定在30000 - 32767 之间
      nodePort: 31001

    - protocol: TCP
      port: 8081
      name: read
      targetPort: 80
      nodePort: 31002
  type: NodePort

Service 对集群内部暴露了 2 个端口,80808081 ,只不过都是对应 nginx 容器的容一个端口 80 。在别的 Pod中 ,访问 80808081 都一样,都能访问 nginx

Service 对节点暴露了 2 个端口,3100131002 ,浏览器访问这 2 个端口都一样,都能访问 nginx

注意:Service 监听多端口的时候,必须给端口起个名字,表示端口的唯一标识,多端口不加 name 字段,创建时会报错

5.6 类型 type

官网地址:https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#publishing-services-service-types

前面写的 yml 文件中,最后一行 type=nodePort ,它会把当前节点的端口跟 Service 进行映射。访问流程是:访问节点端口,通过节点端口访问到对应的 Service 端口,再通过 Service 找到对应的 Pod

Kubernetes ServiceTypes 允许指定你所需要的 Service 类型

  • ClusterIP:在集群内部暴露 Service ,只能被集群内部的其他对象访问,通常用于内部服务发现,不会向集群外部暴露
  • NodePort:将 Service 暴露在 Node 的某个端口上,从而可以通过 NodeIP 地址和端口来访问 Service ,通常用于开发和测试环境
  • LoadBalancer:通过云服务商提供的负载均衡器来将 Service 暴露到公网上,使得外部用户可以访问 Service
  • ExternalName:将 Service 映射到一个 DNS 名称上,从而可以通过 DNS 名称来访问 Service ,通常用于访问外部服务

5.6.1 ClusterIP 类型

  • 这是最常见的 Service 类型之一。在集群内部创建一个虚拟 IP 地址,它可以被其它在同一集群内的 Pod 访问,但不同被集群外部的请求所访问。这种类型的服务通常用于内部服务的暴露,例如数据库或者缓存服务。比如在一个 Web 应用中,你可能需要连接到一个数据库,但是这个数据库并不需要在应用之外暴露。这时候,你可以使用 ClusterIP 类型的 Service 。让应用可以访问到数据库

image-20260607213309733

如上图所示,type 已经改成了 ClusterIP

image-20260607213426821

创建 service 之后查看 service ,出现了 CLUSTER-IP ,也就是 10.111.47.177 ,这就是集群内 IP ,只能集群内访问,外网是访问不了的

5.6.2 NodePort 类型

  • 这种类型的 Service 会创建一个端口,并绑定到每个集群节点上,从而允许外部流量访问 Service 。这个类型通常用于公共服务的暴露,例如 Web 应用或者 API 。比如你需要在集群外部访问到一个运行在集群中的 Web 应用,你就可以创建一个 NodePort 类型的 Service ,通过指定 ServiceNodePort 字段,来将 Service 暴露给集群外部
  • 如果你将 type 字段设置为 NodePort ,则 Kubernetes 控制平面将在 --service-node-port-range 标志指定的范围内分配端口(默认值:30000 - 32767

5.6.3 LoadBalancer

  • 这种类型的 Service 类似于 NodePort ,但是会在云厂商中创建一个负载均衡器。这个类型通常用于在云平台上部署应用。云平台的负载均衡器将流量分发到集群中的节点。这个类型的 Service 只能在云平台上使用,并且需要云厂商提供支持

5.6.4 ExternalName 类型

  • 这种类型的 Service 允许 Service 到任何需要访问的 ANAME DNS 条目的转发。与其它类型的 Service 不同,它并不会代理请求到任何 Pod 。相反,它将请求转发到配置的外部地址。这种类型的 Service 通常用于将服务代理到集群外部的其他服务。比如你有一个运行在外部网络上的服务,你希望在 Kubernets 集群中使用该服务,这时候你可以创建一个 ExternalName 类型的 Service ,将服务的 DNS 解析到 Kubernets 集群中

5.7 Pod 之间通过 Service 通信

创建 PodPod 内是 MySQL ,文件名为 mysql-1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql/mysql-server:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: root
        ports:
        - name: mysql
          containerPort: 3306
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  selector:
    app: mysql
  ports:
  - name: mysql
    port: 3306
    targetPort: 3306
  type: ClusterIP

创建 PodPod 内是 nginx ,文件名为 nginx-1.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-test
  labels:
    app: nginx-test
spec:
  selector:
    matchLabels:
      app: nginx-test
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx-test
    spec:
      hostNetwork: true
      containers:
      - name: nginx-test
        image: nginx:latest
        #command: ["/bin/sh", "-c"]
        #args:
        #- apt-get update && apt-get install -y mysql-client && nginx -g 'daemon off;'
        ports:
        - name: http
          containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-test
spec:
  selector:
    app: nginx-test
  ports:
  - name: http
    port: 8081
    targetPort: 80
  type: ClusterIP

同时创建 2Deployment2Service2Pod

kubectl apply -f mysql-1.yaml nginx-1.yaml

image-20260607222005984

上图可以看到,mysqlnginxPod 已经创建成功,中间的那个 nginx 是别的脚本创建的,不用管它

image-20260607222632758

进入 mysql 这个 Pod ,然后访问 nginx ,是可以访问的

这就实现了在 mysqlPod 中访问 nginx

相关文章
人工智能 缓存 前端开发
5897 14
人工智能 JavaScript 开发工具
2453 2
|
11天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
2027 121
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
缓存 JavaScript Shell
1022 1
|
12天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1577 13
|
9天前
|
编解码 弹性计算 云计算
MiniMax-H3 视频生成模型 — 一键部署与使用指南
MiniMax-H3是MiniMax开源的33B全模态视频生成模型,支持文生视频、图生视频、参考生视频三种模式,原生输出2K/15秒带立体声音频视频,已原生适配ComfyUI,并可通过阿里云计算巢一键部署。(239字)
缓存 人工智能 算法
572 0
|
18天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1979 10
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
10天前
|
人工智能 API 开发工具
2026 零基础本地 AI 漫剧完整实操教程(8G 笔记本显卡可用|附可直接复制命令与代码)
本方案提供完全离线、本地运行的漫剧全自动制作流程:RTX3060/4050 8G显卡即可驱动,涵盖Qwen写分镜→ComfyUI统一角色绘图→LTX2.3图生微动画→Qwen3-TTS本地配音→FFmpeg自动合成,全程无水印、免API、不限次。专为低显存优化,解决变脸、闪烁、爆内存三大痛点。(239字)