notebook notebook
首页
  • 计算机网络
  • 计算机系统
  • 数据结构与算法
  • 计算机专业课
  • 设计模式
  • 前端 (opens new window)
  • Java 开发
  • Python 开发
  • Golang 开发
  • Git
  • 软件设计与架构
  • 大数据与分布式系统
  • 常见开发工具

    • Nginx
  • 爬虫
  • Python 数据分析
  • 数据仓库
  • 中间件

    • MySQL
    • Redis
    • Elasticsearch
    • Kafka
  • 深度学习
  • 机器学习
  • 知识图谱
  • 图神经网络
  • 应用安全
  • 渗透测试
  • Linux
  • 云原生
面试
  • 收藏
  • paper 好句
GitHub (opens new window)

学习笔记

啦啦啦,向太阳~
首页
  • 计算机网络
  • 计算机系统
  • 数据结构与算法
  • 计算机专业课
  • 设计模式
  • 前端 (opens new window)
  • Java 开发
  • Python 开发
  • Golang 开发
  • Git
  • 软件设计与架构
  • 大数据与分布式系统
  • 常见开发工具

    • Nginx
  • 爬虫
  • Python 数据分析
  • 数据仓库
  • 中间件

    • MySQL
    • Redis
    • Elasticsearch
    • Kafka
  • 深度学习
  • 机器学习
  • 知识图谱
  • 图神经网络
  • 应用安全
  • 渗透测试
  • Linux
  • 云原生
面试
  • 收藏
  • paper 好句
GitHub (opens new window)
  • Linux

  • 云原生

    • Docker

    • 专栏-深入容器

    • Kubernetes入门实战课-罗剑锋

      • 初识容器
      • Kubernetes 的安装与基本架构
      • YAML、Pod、Job、CronJob、ConfigMap、Secret
      • Deployment、Daemonset、Service、Ingress
        • 1. Deployment:让应用永不宕机
          • 1.1 为什么需要 Deployment
          • 1.2 使用 YAML 描述 Deployment
          • 1.3 Deployment 的关键字段
          • 1.4 用 kubectl 操作 Deployment
          • 1.5 补充:Pod 中 labels 的用法
          • 1.6 小结
        • 2. Daemonset:忠实可靠的看门狗
          • 2.1 为什么需要 DaemonSet
          • 2.2 使用 YAML 描述 DaemonSet
          • 2.3 在 Kubernetes 里使用 DaemonSet
          • 2.4 污点(taint)和容忍度(toleration)
          • 2.5 静态 Pod
          • 2.6 小结
        • 3. Service:微服务架构的应对之道
          • 3.1 为什么要有 Service
          • 3.2 使用 YAML 描述 Service
          • 3.3 在 Kubernetes 里使用 Service
          • 3.4 以域名的方式使用 Service
          • 3.5 如何让 Service 对外暴露服务
          • 3.6 小结
        • 4. Ingress:集群进出流量的总管
          • 4.1 为什么要有 Ingress
          • 4.2 为何要有 Ingress Controller
          • 4.3 为何要有 Ingress Class
          • 4.4 用 YAML 描述 Ingress/Ingress Class
          • 4.5 在 Kubernetes 里使用 Ingress/Ingress Class
          • 4.6 在 Kubernetes 里使用 Ingress Controller
          • 4.7 小结
      • PV、PVC、StorageClass、Provisioner、StatefulSet
      • 滚动更新、应用保障、集群管理
      • MetricsServer、Prometheus、CNI
    • 深入剖析 Kubernetes-张磊

    • Jenkins

  • 运维
  • 云原生
  • Kubernetes入门实战课-罗剑锋
yubin
2023-04-23
目录

Deployment、Daemonset、Service、Ingress

参考:Kubernetes 入门实战课 | 极客时间 (opens new window) 第 18-23 讲

# 1. Deployment:让应用永不宕机

# 1.1 为什么需要 Deployment

除了离线业务,另一大类业务就是在线业务。单独的 Pod 使用 containers 可以任意编排容器,它可以保证容器正常工作,但如果 Pod 本身出错了怎么办呢?比如不小心使用 kubectl delete 误删了 Pod,或者节点发生了断电故障,那么 Pod 就会消失,对 container 的控制也就无从谈起了。另外,在线业务也远不止单纯启动一个 Pod 那么简单,还有多实例、高可用、版本更新等许多复杂操作,比如我们需要应对突发流量的压力、需要多个应用副本、需要监控应用状态。如果只是使用 Pod,那么就又走回手工管理的老路了。

Kubernetes 处理这种在线业务问题的思路是创建一个新的 API 对象:Deployment,通过对象套对象的方式,让它来管理 Pod。

# 1.2 使用 YAML 描述 Deployment

看一下 Deployment 的基本信息:

$ kubectl api-resources

NAME         SHORTNAMES   APIVERSION   NAMESPACED   KIND
deployments  deploy       apps/v1      true        Deployment
1
2
3
4

可以看出,Deployment的简称是“deploy”,它的apiVersion是“apps/v1”,kind是“Deployment”。所以我们知道它的 YAML 的开头该怎么写了:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: xxx-dep
1
2
3
4

当然了,我们也可以使用如下命令来创建一个 YAML 样板:

export out="--dry-run=client -o yaml"
kubectl create deploy ngx-dep --image=nginx:alpine $out
1
2

得到的 Deployment YAML 大概如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: ngx-dep
  name: ngx-dep

spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      containers:
      - image: nginx:alpine
        name: nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

对比一下 Job/CronJob 可以发现,有许多相同的地方,比如都有 spec、template 字段,template 下面也有一个 Pod,不同的地方是它的 spec 多了 replicas、selector 两个字段,而这两个字段就是 Deployment 实现多实例、高可用等功能的关键所在。

# 1.3 Deployment 的关键字段

# 1.3.1 replicas 字段

replicas 字段:Pod 实例的副本数量。

Kubernetes 会持续监控 Pod 的运行状态,万一有 Pod 发生意外而消失,他就会通过 apiserver、scheduler 等核心组件去选择新的节点,创建出新的 Pod,直到数量与“期望状态”一致。

# 1.3.2 selector 字段

selector 字段,用于“筛选”出要被 Deployment 管理的 Pod 对象。

下属字段“matchLabels”定义了Pod对象应该携带的label,它必须和“template”里Pod定义的“labels”完全相同,否则Deployment就会找不到要控制的Pod对象,apiserver也会告诉你YAML格式校验错误无法创建。

这个 selector 字段的用法初看起来好像是有点多余,为了保证Deployment成功创建,我们必须在YAML里把label重复写两次:一次是在“ selector.matchLabels”,另一次是在“ template.matadata”。像在这里,你就要在这两个地方连续写 app: ngx-dep :

spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    ...
1
2
3
4
5
6
7
8
9
10
11

你也许会产生疑问:为什么要这么麻烦?为什么不能像Job对象一样,直接用“template”里定义好的Pod就行了呢?

这是因为在线业务和离线业务的应用场景差异很大。离线业务中的Pod基本上是一次性的,只与这个业务有关,紧紧地绑定在Job对象里,一般不会被其他对象所使用。而在线业务就要复杂得多了,因为Pod永远在线,除了要在Deployment里部署运行,还可能会被其他的API对象引用来管理,比如负责负载均衡的Service对象。

所以 Deployment 和 Pod 实际上是一种松散的组合关系,Deployment 实际上并不“持有” Pod 对象,它只是帮助 Pod 对象能够有足够的副本数量运行,仅此而已。如果像 Job 那样,把 Pod 在模板里“写死”,那么其他的对象再想要去管理这些 Pod 就无能为力了。

Kubernetes采用的是这种“贴标签”的方式,通过在API对象的“metadata”元信息里加各种标签(labels),我们就可以使用类似关系数据库里查询语句的方式,筛选出具有特定标识的那些对象。通过标签这种设计,Kubernetes就解除了Deployment和模板里Pod的强绑定,把组合关系变成了“弱引用”。

下图展示了 Deployment 与被他管理的 Pod 的关系:

20230425202026

# 1.4 用 kubectl 操作 Deployment

写好 Deployment 的 YAML 后,就可以用 kubectl apply 来创建对象了:

kubectl apply -f deploy.yml
1

然后使用 kubectl get deploy 来查看 Deployment 的状态:

20230425202339
  • READY表示运行的Pod数量,前面的数字是当前数量,后面的数字是期望数量,所以“2/2”的意思就是要求有两个Pod运行,现在已经启动了两个Pod。
  • UP-TO-DATE指的是当前已经更新到最新状态的Pod数量。因为如果要部署的Pod数量很多或者Pod启动比较慢,Deployment完全生效需要一个过程,UP-TO-DATE就表示现在有多少个Pod已经完成了部署,达成了模板里的“期望状态”。
  • AVAILABLE要比READY、UP-TO-DATE更进一步,不仅要求已经运行,还必须是健康状态,能够正常对外提供服务,它才是我们最关心的Deployment指标。
  • 最后一个AGE就简单了,表示Deployment从创建到现在所经过的时间,也就是运行的时间。

因为 Deployment 管理的是 Pod,所以也可以用 kubectl get pod 来查看 Pod 的状态,可以看到,这些被 Deployment 管理的 Pod 自动带上了名字,命名的规则是 Deployment 的名字加上两串随机数:

20230425202539

这时候如果我们尝试删除某个 Pod,Kubernetes 在 Deployment 的管理下又会很快创建一个新的 Pod 来保证满足期望状态。

在 Deployment 部署成功后,你还可以使用 kubectl scale 来随时调整 Pod 的数量,从而实现应用伸缩。通过指定 --replicas 参数可以指定副本的数量,比如下面的命令将 Nginx 扩容到 5 个:

kubectl scale --replicas=5 deploy ngx-dep
1

注意,kubectl scale 只是临时的措施。

# 1.5 补充:Pod 中 labels 的用法

之前我们通过 labels 为对象“贴”了各种“标签”,在使用 kubectl get 命令的时候,加上参数 -l,使用 ==、 !=、 in、 notin 的表达式,就能够很容易地用“标签”筛选、过滤出所要查找的对象(有点类似社交媒体的 #tag 功能),效果和Deployment里的 selector 字段是一样的。

看两个例子,第一条命令找出“app”标签是 nginx 的所有Pod,第二条命令找出“app”标签是 ngx、 nginx、 ngx-dep 的所有Pod:

kubectl get pod -l app=nginx
kubectl get pod -l 'app in (ngx, nginx, ngx-dep)'
1
2

# 1.6 小结

这一大节学习的 Deployment 对象包装了 Pod,通过添加额外的控制功能实现了应用永不宕机。Pod 只能管理容器,不能管理自身,所以就出现了 Deployment,由它来管理 Pod。

学了Deployment这个API对象,我们今后就不应该再使用“裸Pod”了。即使我们只运行一个Pod,也要以Deployment的方式来创建它,虽然它的 replicas 字段值是1,但Deployment会保证应用永远在线。

另外,作为Kubernetes里最常用的对象,Deployment的本事还不止这些,它还支持滚动更新、版本回退,自动伸缩等高级功能,这些将在后面学习。

# 2. Daemonset:忠实可靠的看门狗

Deployment 无法满足在线业务的所有场景,这一大节来看另一类代表在线业务的 API 对象:DaemonSet,它会在 Kubernetes 集群的每个节点上都运行一个 Pod,就好像是 Linux 系统里的“守护进程”(Daemon)。

# 2.1 为什么需要 DaemonSet

Deployment 可以创建任意多个 Pod 实例,但不关心这些 Pod 在哪些节点上运行,因为在它看来,Pod 的运行环境与功能是无关的。这个假设对于大多数业务是没问题的,比如 Nginx、MySQL 等。

但有一些业务比较特殊,它们不是完全独立于系统运行的,而是与主机存在“绑定”关系,必须要依附于节点才能产生价值,比如说:

  • 网络应用(如kube-proxy),必须每个节点都运行一个Pod,否则节点就无法加入Kubernetes网络。
  • 监控应用(如Prometheus),必须每个节点都有一个Pod用来监控节点的状态,实时上报信息。
  • 日志应用(如Fluentd),必须在每个节点上运行一个Pod,才能够搜集容器运行时产生的日志数据。
  • 安全应用,同样的,每个节点都要有一个Pod来执行安全审计、入侵检查、漏洞扫描等工作。

这些业务如果用Deployment来部署就不太合适了,因为Deployment所管理的Pod数量是固定的,而且可能会在集群里“漂移”,但,实际的需求却是要在集群里的每个节点上都运行Pod,也就是说Pod的数量与节点数量保持同步。

所以 Kubernetes 就定义了新的 API 对象:DaemonSet,它在形式上和 Deployment 类似,都是管理控制 Pod,但管理调度策略却不同。DaemonSet 的目标是在集群的每个节点上运行且仅运行一个 Pod,就好像是为节点配上一只“看门狗”,忠实地“守护”着节点,这就是 DaemonSet 名字的由来。

# 2.2 使用 YAML 描述 DaemonSet

DaemonSet 简称 ds,YAML 文件头信息应该是:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: xxx-ds
1
2
3
4

它的样板信息无法通过 kubectl create 创建出来,但可以去官网 https://kubernetes.io/zh/docs/concepts/workloads/controllers/daemonset/ (opens new window) 找一份 DaemonSet 的 YAML 示例,拷贝下来并简单修改就可以做成一份自己的文件,大概如下:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: redis-ds
  labels:
    app: redis-ds

spec:
  selector:
    matchLabels:
      name: redis-ds

  template:
    metadata:
      labels:
        name: redis-ds
    spec:
      containers:
      - image: redis:5-alpine
        name: redis
        ports:
        - containerPort: 6379
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

这个DaemonSet对象的名字是 redis-ds,镜像是 redis:5-alpine。把它与 Deployment 的 YAML 对比一下就可以发现,两者结构基本一样,spec 部分中都有 selector 字段和 template 字段。不同之处是 DaemonSet 在 spec 里没有 replicas 字段,因为它不会在集群里创建多个 Pod 副本,而是要在每个节点上只创建出一个 Pod 实例。

也就是说,DaemonSet 仅仅是在 Pod 的部署调度策略上和 Deployment 不同,其他的都是相同的,某种程度上我们也可以把 DaemonSet 看做是 Deployment 的一个特例。两者的 YAML 如下图:

20230426153810

# 2.3 在 Kubernetes 里使用 DaemonSet

可以使用 kubectl apply 将 YAML 发给 Kubernetes 并创建 DaemonSet 对象,再用 kubectl get ds 来查看状态:

20230426154046

看这张截图,虽然我们没有指定DaemonSet里Pod要运行的数量,但它自己就会去查找集群里的节点,在节点里创建Pod。因为我们的实验环境里有一个Master一个Worker,而Master默认是不跑应用的,所以DaemonSet就只生成了一个Pod,运行在了“worker”节点上。

但有点不对劲。按照DaemonSet的本意,应该在每个节点上都运行一个Pod实例才对,但Master节点却被排除在外了,这就不符合我们当初的设想了。显然 DaemonSet 没有尽到“看门”的职责,它的设计与 Kubernetes 集群的工作机制发生了冲突,有没有办法解决呢?

当然,Kubernetes早就想到了这点,为了应对Pod在某些节点的“调度”和“驱逐”问题,它定义了两个新的概念:污点(taint)和容忍度(toleration)。

# 2.4 污点(taint)和容忍度(toleration)

“污点”是 Kubernetes 节点的一个属性,也有点类似于给节点贴上一些标签。“容忍度”是 Pod 的属性,表示 Pod 能否容忍一个节点上污点的存在。

我们把它俩放在一起就比较好理解了。集群里的节点各式各样,有的节点“纯洁无瑕”,没有“污点”;而有的节点因为某种原因粘上了“泥巴”,也就有了“污点”。Pod也脾气各异,有的“洁癖”很严重,不能容忍“污点”,只能挑选“干净”的节点;而有的Pod则比较“大大咧咧”,要求不那么高,可以适当地容忍一些小“污点”。

Kubernetes 在创建集群的时候会自动给节点 Node 加上一些 taint,方便 Pod 的调度和部署。这可以通过 kubectl describe node 来查看 Master 和 Worker 的状态:

$ kubectl describe node master
Name:     master
Roles:    control-plane,master
...
Taints:   node-role.kubernetes.io/master:NoSchedule
...

$ kubectl describe node worker
Name:     worker
Roles:    <none>
...
Taints:   <none>
...
1
2
3
4
5
6
7
8
9
10
11
12
13

可以看到,Master节点默认有一个 taint,名字是 node-role.kubernetes.io/master,它的效果是 NoSchedule,也就是说这个污点会拒绝Pod调度到本节点上运行,而Worker节点的 taint 字段则是空的。

这正是Master和Worker在Pod调度策略上的区别所在,通常来说Pod都不能容忍任何“污点”,所以加上了 taint 属性的Master节点也就会无缘Pod了。

明白了“污点”和“容忍度”的概念,你就知道该怎么让 DaemonSet 在 Master 节点(或者任意其他节点)上运行了,方法有两种。

# 1)方法一:去掉 Master 上的 taint

去掉 Master 上的 taint,让它变得像 worker 一样纯洁无瑕,DaemonSet 自然就不需要再区分 Master/Worker。

给 Master 去掉 taint 的方法

操作Node上的“污点”属性需要使用命令 kubectl taint,然后指定节点名、污点名和污点的效果,去掉污点要额外加上一个 -。

比如要去掉Master节点的“NoSchedule”效果,就要用这条命令:

kubectl taint node master node-role.kubernetes.io/master:NoSchedule-
1

命令执行后,原本因为这个 taint 而不在这里运行的 Pod 就会立刻在上面创建并运行起来了。

但是,这种方法修改的是 Node 的状态,影响面会比较大,可能会导致很多 Pod 都跑到这个节点上运行,所以我们可以保留 Node 的“污点”,为需要的 Pod 添加“容忍度”,只让某些 Pod 运行在个别节点上,实现“精细化”调度。

# 2)方法二:为 Pod 增加容忍度(toleration)

为Pod添加字段 tolerations,让它能够“容忍”某些“污点”,就可以在任意的节点上运行了。

tolerations 是一个数组,里面可以列出多个被“容忍”的“污点”,需要写清楚“污点”的名字、效果。比较特别是要用 operator 字段指定如何匹配“污点”,一般我们都使用 Exists,也就是说存在这个名字和效果的“污点”。

如果我们想让DaemonSet里的Pod能够在Master节点上运行,就要写出这样的一个 tolerations,容忍节点的 node-role.kubernetes.io/master:NoSchedule 这个污点:

tolerations:
- key: node-role.kubernetes.io/master
  effect: NoSchedule
  operator: Exists
1
2
3
4

重新 kubectl apply 之后,效果就起作用了。

需要特别说明一下,容忍度并不是DaemonSet独有的概念,而是从属于Pod,所以理解了“污点”和“容忍度”之后,你可以在Job/CronJob、Deployment里为它们管理的Pod也加上 tolerations,从而能够更灵活地调度应用。

官方文档 https://kubernetes.io/zh/docs/concepts/scheduling-eviction/taint-and-toleration/ (opens new window) 罗列了各污点的效果,可以参考。

# 2.5 静态 Pod

DaemonSet 是在 Kubernetes 里运行节点专属 Pod 最常用的方式,但它不是唯一的方式,Kubernetes 还支持另外一种叫静态 Pod 的应用部署手段。

“静态Pod”非常特殊,它不受Kubernetes系统的管控,不与apiserver、scheduler发生关系,所以是“静态”的。

但既然它是Pod,也必然会“跑”在容器运行时上,也会有YAML文件来描述它,而唯一能够管理它的Kubernetes组件也就只有在每个节点上运行的kubelet了。

“静态Pod”的YAML文件默认都存放在节点的 /etc/kubernetes/manifests 目录下,它是Kubernetes的专用目录。下图展示了 Master 节点的目录情况:

20230426160006

你可以看到,Kubernetes 的 4 个核心组件 apiserver、etcd、scheduler、controller-manager 原来都以静态 Pod 的形式存在的,这也是为什么它们能够先于 Kubernetes 集群启动的原因。

如果你有一些 DaemonSet 无法满足的特殊的需求,可以考虑使用静态 Pod,编写一个 YAML 文件放到这个目录里,节点的 kubelet 会定期检查目录里的文件,发现变化就会调用容器运行时创建或者删除静态 Pod。

# 2.6 小结

  1. DaemonSet的目标是为集群里的每个节点部署唯一的Pod,常用于监控、日志等业务。
  2. DaemonSet的YAML描述与Deployment非常接近,只是没有 replicas 字段。
  3. “污点”和“容忍度”是与DaemonSet相关的两个重要概念,分别从属于Node和Pod,共同决定了Pod的调度策略。
  4. 静态Pod也可以实现和DaemonSet同样的效果,但它不受Kubernetes控制,必须在节点上纯手动部署,应当慎用。

课外小贴士:

  • Linux 系统里最出名的守护进程应该就是 systemd 了它是系统里的 1号进程,管理其他所有的进程。
  • 网络插件 Fannel 就是一个 DaemonSet,它位于名字空间“kube-system”,可以用“kubectl get ds -n kube-system”看到。
  • 虽然静态 Pod 在 Kubernetes 里已经存在了很长时间但 Kubernetes 官网对它的态度却是 “ 模棱两可”,毕竟它游离在系统之外,不好管理,所以将来有被废弃的可能。
  • 与污点、容忍度相关的另一个概念是“节点亲和性”(nodeAfnity),作用是更“偏好”选择哪种节点但用法略复杂一些
  • 在 Kubernetes v1.24 中,master 节点将不再使用污点 node-role.kubernetes.io/master,而是改成 node-role.kubernetes.io/control-plane

# 3. Service:微服务架构的应对之道

为了更好地支持微服务以及服务网格这样的应用架构,Kubernetes 又专门定义了一个新的对象:Service,它是集群内部的负载均衡机制,用来解决服务发现的关键问题。

# 3.1 为什么要有 Service

借助 Kubernetes 强大的自动化运维能力,我们可以把应用的更新上线频率由以前的月、周级别提升到天、小时级别,让服务质量更上一层楼。不过,在应用程序快速版本迭代的同时,另一个问题也逐渐显现出来了,就是服务发现。

Deployment 和 DaemonSet 管理下的 Pod 经常处于动态的变化之中,所以这些 IP 地址也是经常变来变去。业内的解决方案就是负载均衡,典型的应用有LVS、Nginx等等。它们在前端与后端之间加入了一个“中间层”,屏蔽后端的变化,为前端提供一个稳定的服务。但LVS、Nginx毕竟不是云原生技术,所以Kubernetes就按照这个思路,定义了新的API对象:Service。

Service 的工作原理就与 Nginx 差不多,Kubernetes 会给它分配一个静态 IP 地址,然后它再去自动管理、维护后面动态变化的 Pod 集合,当客户端访问 Service,它就根据某种策略,把流量转发给后面的某个 Pod。下图清楚地展示了 Service 的工作原理:

20230426161226

你可以看到,这里Service使用了iptables技术,每个节点上的kube-proxy组件自动维护iptables规则,客户不再关心Pod的具体地址,只要访问Service的固定IP地址,Service就会根据iptables规则转发请求给它管理的多个Pod,是典型的负载均衡架构。

不过 Service 并不是只能使用 iptables 来实现负载均衡,它还有另外两种实现技术:性能更差的 userspace 和性能更好的 ipvs,但这些都属于底层细节,我们不需要刻意关注。

# 3.2 使用 YAML 描述 Service

Service 的简称是 svc,apiVersion 是 v1。注意,这说明它与 Pod 一样,属于 Kubernetes 的核心对象,不关联业务应用,与 Job、Deployment 是不同的。

一个 Service 的 YAML 开头如下所示:

apiVersion: v1
kind: Service
metadata:
  name: xxx-svc
1
2
3
4

Kubernetes 可以使用 kubectl expose 来创建样板 YAML。因为在Kubernetes里提供服务的是Pod,而Pod又可以用Deployment/DaemonSet对象来部署,所以 kubectl expose 支持从多种对象创建服务,Pod、Deployment、DaemonSet都可以。

使用 kubectl expose 指令时还需要用参数 --port 和 --target-port 分别指定映射端口和容器端口,而Service自己的IP地址和后端Pod的IP地址可以自动生成,用法上和Docker的命令行参数 -p 很类似,只是略微麻烦一点。比如如果想为我们之前创建的 ngx-dep 这个 Deployment 对象生成 Service,命令就要这么写:

export out="--dry-run=client -o yaml"
kubectl expose deploy ngx-dep --port=80 --target-port=80 $out
1
2

生成的 Service YAML 如下:

apiVersion: v1
kind: Service
metadata:
  name: ngx-svc

spec:
  selector:
    app: ngx-dep

  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
1
2
3
4
5
6
7
8
9
10
11
12
13

你会发现,Service 的定义非常简单,在“spec”里只有两个关键字段,selector 和 ports:

  • selector:和 Deployment/DaemonSet 里的作用是一样的,用来过滤出要代理的那些 Pod。因为我们指定要代理Deployment,所以Kubernetes就为我们自动填上了ngx-dep的标签,会选择这个Deployment对象部署的所有Pod。
  • ports:里面的三个字段分别表示外部端口、内部端口和使用的协议,在这里就是内外部都使用80端口,协议是TCP。

为了让你看清楚Service与它引用的Pod的关系,我把这两个YAML对象画在了下面的这张图里,需要重点关注的是 selector、 targetPort 与Pod的关联:

20230426162530

# 3.3 在 Kubernetes 里使用 Service

在创建 Service 对象之前,我们先对之前的 Deployment 做一点改造,方便观察 Service 的效果。

首先,我们创建一个ConfigMap,定义一个Nginx的配置片段,它会输出服务器的地址、主机名、请求的URI等基本信息:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ngx-conf

data:
  default.conf: |
    server {
      listen 80;
      location / {
        default_type text/plain;
        return 200
          'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
      }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

然后我们在 Deployment 的“template.volumes”里定义存储卷,再用“volumeMounts”把配置文件加载进 Nginx 容器里:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-dep

spec:
  replicas: 2
  selector:
    matchLabels:
      app: ngx-dep

  template:
    metadata:
      labels:
        app: ngx-dep
    spec:
      volumes:
      - name: ngx-conf-vol
        configMap:
          name: ngx-conf

      containers:
      - image: nginx:alpine
        name: nginx
        ports:
        - containerPort: 80

        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: ngx-conf-vol
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

部署这个Deployment之后,我们就可以创建Service对象了,用的还是 kubectl apply:

kubectl apply -f svc.yml
1

创建之后,用命令 kubectl get 就可以看到它的状态:

20230426163114

你可以看到,Kubernetes 为 Service 对象自动分配了一个 IP 地址“10.96.240.115”,这个地址段是独立于 Pod 地址段的。而且 Service 对象的 IP 地址还有一个特点,它是一个“虚地址”,不存在实体,只能用来转发流量。

想要看Service代理了哪些后端的Pod,你可以用 kubectl describe 命令:

kubectl describe svc ngx-svc
1
20230426163219

截图里显示Service对象管理了两个endpoint,分别是“10.10.0.232:80”和“10.10.1.86:80”,初步判断与Service、Deployment的定义相符,那么这两个IP地址是不是Nginx Pod的实际地址呢?

我们还是用 kubectl get pod 来看一下,加上参数 -o wide:

20230426163318

把Pod的地址与Service的信息做个对比,我们就能够验证Service确实用一个静态IP地址代理了两个Pod的动态IP地址。


那怎么测试 Service 的负载均衡效果呢?因为Service、 Pod的IP地址都是Kubernetes集群的内部网段,所以我们需要用 kubectl exec 进入到Pod内部(或者ssh登录集群节点),再用curl等工具来访问Service:

20230426203323
  • 我们 curl 的是 Service 的固定 IP 地址,然后可以看到它把请求转发给了后端的某个 Pod,输出信息显示了是哪个 Pod 响应了这个请求,就表名 Service 确实完成了对 Pod 的负载均衡任务。

我们再试着删除一个 Pod,在看看 Service 是否会更新它的后端的 Pod,从而实现自动化的服务发现:

20230426203544
  • 可以看到,Service 可以通过 controller-manager 来实时监控 Pod 的变化情况,所以就会立即更新它的代理的 IP 地址,从上图可以看到,Endpoints 的 IP 列表随着 Pod 的变化也发生了改变。

# 3.4 以域名的方式使用 Service

Service 对象的 IP 地址是静态的,保持了稳定,这在微服务里确实很重要,不过以数字形式的 IP 地址用起来还是不太方便。这个时候 Kubernetes 的 DNS 插件就派上了用处,它可以为 Service 创建容易记忆的域名,让 Service 更容易使用。

在这之前,我们先介绍一个概念:名字空间(namespace)。Kubernetes 使用 namespace 用来在集群中实现对 API 对象的隔离和分组。

这里所说的 namespace 与 Linux 的 namespace 技术不一样。

namespace 简写为 ns,可以使用 kubectl get ns 来查看当前集群里都有哪些 namespace,也就是说 API 对象有哪些分组:

$ kubectl get ns
NAME                   STATUS   AGE
default                Active   4d1h
kube-node-lease        Active   4d1h
kube-public            Active   4d1h
kube-system            Active   4d1h
kubernetes-dashboard   Active   3d23h
1
2
3
4
5
6
7

Kubernetes 有一个默认的 namespace:default,如果不显式指定,API 对象都会在这个 default namespace 中,而其他的 namespace 都有各自的用途,比如 kube-system 就包含了 apiserver、etcd 等核心组件的 Pod。

因为 DNS 是一种层次结构,为了避免太多的域名导致冲突,Kubernetes 就把 namespace 作为域名的一部分,减少了重名的可能性。

Service 对象的域名完全形式是:对象.名字空间.svc.cluster.local,但很多时候也可以省略后面的部分,直接写“对象.名字空间”,甚至“对象名”就足够了,默认会使用 default namespace。

现在我们来试验一下 DNS 域名的用法,还是先 kubectl exec 进入 Pod,然后用 curl 访问 ngx-svc、 ngx-svc.default 等域名:

20230426205533

可以看到,现在我们就不再关心 Service 对象的IP地址,只需要知道它的名字,就可以用 DNS 的方式去访问后端服务。

比起 Docker,这无疑是一个巨大的进步,而且对比其他微服务框架(如 Dubbo、Spring Cloud),由于服务发现机制被集成在了基础设施里,也会让应用的开发更加便捷。

顺便说一下,Kubernetes也为每个Pod分配了域名,形式是“ IP地址.名字空间.pod.cluster.local”,但需要把IP地址里的 . 改成 - 。比如地址 10.10.1.87,它对应的域名就是 10-10-1-87.default.pod。

# 3.5 如何让 Service 对外暴露服务

由于 Service 是一种负载均衡技术,所以它不仅能够管理 Kubernetes 集群内部的服务,还能够担当向集群外部暴露服务的重任。

Service对象有一个关键字段“type”,表示 Service 是哪种类型的负载均衡。前面我们看到的用法都是对集群内部 Pod 的负载均衡,所以这个字段的值就是默认的“ClusterIP”,Service 的静态 IP 地址只能在集群内访问。

除了“ClusterIP”,Service 还支持其他三种类型,分别是“ExternalName”“LoadBalancer”“NodePort”。不过前两种类型一般由云服务商提供,我们的实验环境用不到,所以接下来就重点看“NodePort”这个类型。

当在 YAML 里添加字段 type: NodePort 的话,那么 Service 除了会对后端的 Pod 做负载均衡之外,还会在集群里的每个节点上创建一个独立的端口,用这个端口对外提供服务,这也正是“NodePort”这个名字的由来。

让我们修改一下 Service 的 YAML 文件,加上字段“type”:





 

apiVersion: v1
...
spec:
  ...
  type: NodePort
1
2
3
4
5

然后创建对象,再用 kubectl get svc 查看它的状态:

20230426210301

就会看到“TYPE”变成了“NodePort”,而在“PORT”列里的端口信息也不一样,除了集群内部使用的“80”端口,还多出了一个“30651”端口,这就是Kubernetes在节点上为Service创建的专用映射端口。

因为这个端口号属于节点,外部能够直接访问,所以现在我们就可以不用登录集群节点或者进入Pod内部,直接在集群外使用任意一个节点的IP地址,就能够访问Service和它代理的后端服务了。

比如我现在所在的服务器是“192.168.10.208”,在这台主机上用curl访问Kubernetes集群的两个节点“192.168.10.210”“192.168.10.220”,就可以得到Nginx Pod的响应数据:

20230426211750

我把NodePort与Service、Deployment的对应关系画成了图,你看了应该就能更好地明白它的工作原理:

20230426211909

学到这里,你是不是觉得 NodePort 类型的 Service 很方便呢。不过它也有一些缺点:

  • 第一个缺点是它的端口数量很有限。Kubernetes为了避免端口冲突,默认只在“30000~32767”这个范围内随机分配,只有2000多个,而且都不是标准端口号,这对于具有大量业务应用的系统来说根本不够用。
  • 第二个缺点是它会在每个节点上都开端口,然后使用kube-proxy路由到真正的后端Service,这对于有很多计算节点的大集群来说就带来了一些网络通信成本,不是特别经济。
  • 第三个缺点,它要求向外界暴露节点的IP地址,这在很多时候是不可行的,为了安全还需要在集群外再搭一个反向代理,增加了方案的复杂度。

虽然有这些缺点,但NodePort仍然是Kubernetes对外提供服务的一种简单易行的方式,在其他更好的方式出现之前,我们也只能使用它。

# 3.6 小结

这一大节学习的 Service 对象实现了负载均衡和服务发现技术,是 Kubernetes 应对微服务、服务网格等现代流行应用架构的解决方案。

  1. Pod的生命周期很短暂,会不停地创建销毁,所以就需要用Service来实现负载均衡,它由Kubernetes分配固定的IP地址,能够屏蔽后端的Pod变化。
  2. Service对象使用与Deployment、DaemonSet相同的“selector”字段,选择要代理的后端Pod,是松耦合关系。
  3. 基于DNS插件,我们能够以域名的方式访问Service,比静态IP地址更方便。
  4. 名字空间是Kubernetes用来隔离对象的一种方式,实现了逻辑上的对象分组,Service的域名里就包含了名字空间限定。
  5. Service的默认类型是“ClusterIP”,只能在集群内部访问,如果改成“NodePort”,就会在节点上开启一个随机端口号,让外界也能够访问内部的服务。

课外小贴士:

  • iptables 基于 Linux 内核里的 netflter 模块,用来处理网络数据包,实现修改、过滤、地址转换等功能。
  • 实际上 Service 并不直接管理 Pod,而是使用代表 IP 地址的 Endpoint 对象,但我们一般不会直接使用 Endpoint,除非是检查错误。

# 4. Ingress:集群进出流量的总管

Service 是 Kubernetes 内置的负载均衡机制,支持域名访问和服务发现,是微服务架构必需的基础设施,但也只能说是“基础设施”,它对网络流量的管理方案还是太简单,所以 Kubernetes 又在 Service 之上提出了新的概念:Ingress。相比 Service,Ingress 更接近实际业务,对它的开发、应用和讨论也是社区里最火爆的。

# 4.1 为什么要有 Ingress

Service 存在的问题:

上一节我们了解到,Service 的功能本质上就是一个由 kube-proxy 控制的四层均衡负载,在 TCP/IP 协议栈上转发流量:

20230426161226

但在四层上的负载均衡功能还是太有限了,只能够依据IP地址和端口号做一些简单的判断和组合,而我们现在的绝大多数应用都是跑在七层的HTTP/HTTPS协议上的,有更多的高级路由条件,比如主机名、URI、请求头、证书等等,而这些在TCP/IP网络栈里是根本看不见的。

Service 还有一个缺点,它比较适合代理集群内部的服务。如果想要把服务暴露到集群外部,就只能使用NodePort或者LoadBalancer这两种方式,而它们都缺乏足够的灵活性,难以管控,这就导致了一种很无奈的局面:我们的服务空有一身本领,却没有合适的机会走出去大展拳脚。


该怎么解决这个问题呢?

既然 Service 是四层的负载均衡,那 Kubernetes 再引入一个新的 API 对象,在七层上做负载均衡不是就可以了吗?

不过除了七层负载均衡,Ingress 还应该承担更多的职责,也就是作为流量的总入口,统管集群的进出口数据。Ingress 扇入、扇出流量,让外部用户能够安全、顺畅、便捷地访问内部服务:

20230427101739

这个 API 对象被命名为 Ingress,意思就是集群内外边界上的入口。

# 4.2 为何要有 Ingress Controller

Ingress 可以说是在七层上另一种形式的 Service,它同样会代理一些后端的 Pod,也有一些路由规则来定义流量应该如何分配、转发,只不过这些规则都使用的是 HTTP/HTTPS 协议。

你应该知道,Service 本身是没有服务能力的,它只是一些 iptables 规则,真正配置、应用这些规则的实际上是节点里的kube-proxy组件。如果没有 kube-proxy,Service 定义得再完善也没有用。

同样的,Ingress 也只是一些 HTTP 路由规则的集合,相当于一份静态的描述文件,真正要把这些规则在集群里实施运行,还需要有另外一个东西,这就是 Ingress Controller,它的作用就相当于 Service 的 kube-proxy,能够读取、应用 Ingress 规则,处理、调度流量。

按理来说,Kubernetes 应该把 Ingress Controller 内置实现,作为基础设施的一部分,就像 kube-proxy 一样。不过 Ingress Controller 要做的事情太多,与上层业务联系太密切,所以 Kubernetes 把 Ingress Controller 的实现交给了社区,任何人都可以开发 Ingress Controller,只要遵守 Ingress 规则就好。这就造成了Ingress Controller“百花齐放”的盛况。

这些实现中最著名的,就是老牌的反向代理和负载均衡软件Nginx了。从Ingress Controller的描述上我们也可以看到,HTTP层面的流量管理、安全控制等功能其实就是经典的反向代理,而Nginx则是其中稳定性最好、性能最高的产品,所以它也理所当然成为了Kubernetes里应用得最广泛的Ingress Controller。不过,因为Nginx是开源的,谁都可以基于源码做二次开发,所以它又有很多的变种,比如社区的Kubernetes Ingress Controller( https://github.com/kubernetes/ingress-nginx (opens new window))、Nginx公司自己的Nginx Ingress Controller( https://github.com/nginxinc/kubernetes-ingress (opens new window))、还有基于OpenResty的Kong Ingress Controller( https://github.com/Kong/kubernetes-ingress-controller (opens new window))等等。

根据 DockerHub 上的统计,Nginx公司的开发实现是下载量最多的 Ingress Controller,所以下面将以它为例,讲解 Ingress 和 Ingress Controller 的用法。

下面这张图展示了 Ingress Controller 在 Kubernetes 集群里的地位:

20230427102716

# 4.3 为何要有 Ingress Class

到现在,有了Ingress和Ingress Controller,我们是不是就可以完美地管理集群的进出流量了呢?最初Kubernetes也是这么想的,一个集群里有一个Ingress Controller,再给它配上许多不同的Ingress规则,应该就可以解决请求的路由和分发问题了。

但随着 Ingress 在实践中的大量应用,很多用户发现这种用法会带来一些问题,比如:

  • 由于某些原因,项目组需要引入不同的Ingress Controller,但Kubernetes不允许这样做;
  • Ingress规则太多,都交给一个Ingress Controller处理会让它不堪重负;
  • 多个Ingress对象没有很好的逻辑分组方式,管理和维护成本很高;
  • 集群里有不同的租户,他们对Ingress的需求差异很大甚至有冲突,无法部署在同一个Ingress Controller上。

所以,Kubernetes 就又提出了一个 Ingress Class 的概念,让它插在 Ingress 和 Ingress Controller 中间,作为流量规则和控制器的协调人,解除了 Ingress 和 Ingress Controller 的强绑定关系。

现在,Kubernetes 用户可以转向管理 Ingress Class,用它来定义不同的业务逻辑分组,简化 Ingress 规则的复杂度。比如说,我们可以用 Class A 处理博客流量、Class B 处理短视频流量、Class C 处理购物流量。

20230427103038

这些 Ingress 和 Ingress Controller 彼此独立,不会发生冲突,所以上面的那些问题也就随着 Ingress Class 的引入迎刃而解了。

# 4.4 用 YAML 描述 Ingress/Ingress Class

Ingress、 Ingress Controller、Ingress Class 这三个对象是社区经过长期讨论后达成的一致结论,是我们目前能获得的最佳解决方案。

现在来看一下如何编写他们的 YAML 描述文件。

使用 kubectl api-resources 可以查看他们的基本信息:

$ kubectl api-resources
NAME            SHORTNAMES   APIVERSION             NAMESPACED   KIND
ingresses       ing          networking.k8s.io/v1   true         Ingress
ingressclasses               networking.k8s.io/v1   false        IngressClass
1
2
3
4

你可以看到,Ingress和Ingress Class的apiVersion都是“networking.k8s.io/v1”,而且Ingress有一个简写“ing”,但Ingress Controller怎么找不到呢?

这是因为Ingress Controller和其他两个对象不太一样,它不只是描述文件,是一个要实际干活、处理流量的应用程序,而应用程序在Kubernetes里早就有对象来管理了,那就是Deployment和DaemonSet,所以我们只需要再学习Ingress和Ingress Class的的用法就可以了。

# 4.4.1 Ingress

Ingress也是可以使用 kubectl create 来创建样板文件的,和Service类似,它也需要用两个附加参数:

  • --class,指定Ingress从属的Ingress Class对象。
  • --rule,指定路由规则,基本形式是“URI=Service”,也就是说是访问HTTP路径就转发到对应的Service对象,再由Service对象转发给后端的Pod。

好,现在我们就执行命令,看看Ingress到底长什么样:

export out="--dry-run=client -o yaml"
kubectl create ing ngx-ing --rule="ngx.test/=ngx-svc:80" --class=ngx-ink $out
1
2

生成的 YAML 如下:








 

 











apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ngx-ing

spec:

  ingressClassName: ngx-ink

  rules:
  - host: ngx.test
    http:
      paths:
      - path: /
        pathType: Exact
        backend:
          service:
            name: ngx-svc
            port:
              number: 80
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

这里有两个关键字段:ingressClassName 和 rules,分别对应了命令行参数,含义还是比较好理解的。

只是“rules”的格式比较复杂,嵌套层次很深。不过仔细点看就会发现它是把路由规则拆散了,有host和http path,在path里又指定了路径的匹配方式,可以是精确匹配(Exact)或者是前缀匹配(Prefix),再用backend来指定转发的目标Service对象。个人觉得(作者觉得),Ingress YAML 里的描述还不如 kubectl create 命令行里的 --rule 参数来得直观易懂,而且 YAML 里的字段太多也很容易弄错,建议你还是让 kubectl 来自动生成规则,然后再略作修改比较好。

# 4.4.2 Ingress Class

有了 Ingress 对象,那么与它关联的 Ingress Class 是什么样的呢?

其实Ingress Class本身并没有什么实际的功能,只是起到联系Ingress和Ingress Controller的作用,所以它的定义非常简单,在“spec”里只有一个必需的字段“ controller”,表示要使用哪个Ingress Controller,具体的名字就要看实现文档了。

比如,如果我要用 Nginx 开发的 Ingress Controller,那么就要用名字“nginx.org/ingress-controller”:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: ngx-ink

spec:
  controller: nginx.org/ingress-controller
1
2
3
4
5
6
7

Ingress 和Service、Ingress Class 的关系画成一张图,如下:

20230427110038

# 4.5 在 Kubernetes 里使用 Ingress/Ingress Class

因为 Ingress Class 很小,所以我把它与 Ingress 合成了一个 YAML 文件,让我们用 kubectl apply 创建这两个对象:

kubectl apply -f ingress.yml
1

然后我们用 kubectl get 来查看对象的状态:

kubectl get ingressclass
kubectl get ing
1
2
20230427110613

命令 kubectl describe 可以看到更详细的 Ingress 信息:

kubectl describe ing ngx-ing
1
20230427110803

可以看到,Ingress 对象的路由规则 Host/Path 就是在 YAML 里设置的域名“ngx.test/”,而且已经关联了之前创建的 Service 对象,还有 Service 后面的两个 Pod。

另外,不要对Ingress里“Default backend”的错误提示感到惊讶,在找不到路由的时候,它被设计用来提供一个默认的后端服务,但不设置也不会有什么问题,所以大多数时候我们都忽略它。

# 4.6 在 Kubernetes 里使用 Ingress Controller

准备好了 Ingress 和 Ingress Class,接下来我们就需要部署真正处理路由规则的 Ingress Controller。

你可以在GitHub上找到Nginx Ingress Controller的项目( https://github.com/nginxinc/kubernetes-ingress (opens new window)),因为它以Pod的形式运行在Kubernetes里,所以同时支持Deployment和DaemonSet两种部署方式。这里我选择的是Deployment,相关的YAML也都在我们课程的项目( https://github.com/chronolaw/k8s_study/tree/master/ingress (opens new window))里复制了一份。

Nginx Ingress Controller的安装略微麻烦一些,有很多个YAML需要执行,但如果只是做简单的试验,就只需要用到4个YAML:

kubectl apply -f common/ns-and-sa.yaml
kubectl apply -f rbac/rbac.yaml
kubectl apply -f common/nginx-config.yaml
kubectl apply -f common/default-server-secret.yaml
1
2
3
4

前两条命令为Ingress Controller创建了一个独立的名字空间“nginx-ingress”,还有相应的账号和权限,这是为了访问apiserver获取Service、Endpoint信息用的;后两条则是创建了一个ConfigMap和Secret,用来配置HTTP/HTTPS服务。

部署Ingress Controller不需要我们自己从头编写Deployment,Nginx已经为我们提供了示例YAML,但创建之前为了适配我们自己的应用还必须要做几处小改动:

  • metadata里的name要改成自己的名字,比如 ngx-kic-dep。
  • spec.selector和template.metadata.labels也要修改成自己的名字,比如还是用 ngx-kic-dep。
  • containers.image可以改用apline版本,加快下载速度,比如 nginx/nginx-ingress:2.2-alpine。
  • 最下面的args要加上 -ingress-class=ngx-ink,也就是前面创建的Ingress Class的名字,这是让Ingress Controller管理Ingress的关键。

修改完之后,Ingress Controller的YAML大概是这个样子:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ngx-kic-dep
  namespace: nginx-ingress

spec:
  replicas: 1
  selector:
    matchLabels:
      app: ngx-kic-dep

  template:
    metadata:
      labels:
        app: ngx-kic-dep
    ...
    spec:
      containers:
      - image: nginx/nginx-ingress:2.2-alpine
        ...
        args:
          - -ingress-class=ngx-ink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

有了Ingress Controller,这些API对象的关联就更复杂了,你可以用下面的这张图来看出它们是如何使用对象名字联系起来的:

20230427111457

确认Ingress Controller 的YAML修改完毕之后,就可以用 kubectl apply 创建对象:

kubectl apply -f kic.yml
1

注意Ingress Controller位于名字空间“nginx-ingress”,所以查看状态需要用“-n”参数显式指定,否则我们只能看到“default”名字空间里的Pod:

kubectl get deploy -n nginx-ingress
kubectl get pod -n nginx-ingress
1
2
20230427111703

现在Ingress Controller就算是运行起来了。

不过还有最后一道工序,因为Ingress Controller本身也是一个Pod,想要向外提供服务还是要依赖于Service对象。所以你至少还要再为它定义一个Service,使用NodePort或者LoadBalancer暴露端口,才能真正把集群的内外流量打通。这个工作就交给你课下自己去完成了。

这里使用 kubectl port-forward 来把本地的端口映射到Kubernetes集群的某个Pod里,在测试验证的时候非常方便。

下面这条命令就把本地的8080端口映射到了Ingress Controller Pod的80端口:

kubectl port-forward -n nginx-ingress ngx-kic-dep-8859b7b86-cplgp 8080:80 &
1
20230427111937

我们在curl发测试请求的时候需要注意,因为Ingress的路由规则是HTTP协议,所以就不能用IP地址的方式访问,必须要用域名、URI。

你可以修改 /etc/hosts 来手工添加域名解析,也可以使用 --resolve 参数,指定域名的解析规则,比如在这里我就把“ngx.test”强制解析到“127.0.0.1”,也就是被 kubectl port-forward 转发的本地地址:

curl --resolve ngx.test:8080:127.0.0.1 http://ngx.test:8080
1
20230427112003

把这个访问结果和上一节课里的Service对比一下,你会发现最终效果是一样的,都是把请求转发到了集群内部的Pod,但Ingress的路由规则不再是IP地址,而是HTTP协议里的域名、URI等要素。

# 4.7 小结

这一大节学习了Kubernetes里七层的反向代理和负载均衡对象,包括Ingress、Ingress Controller、Ingress Class,它们联合起来管理了集群的进出流量,是集群入口的总管。

  1. Service是四层负载均衡,能力有限,所以就出现了Ingress,它基于HTTP/HTTPS协议定义路由规则。
  2. Ingress只是规则的集合,自身不具备流量管理能力,需要Ingress Controller应用Ingress规则才能真正发挥作用。
  3. Ingress Class解耦了Ingress和Ingress Controller,我们应当使用Ingress Class来管理Ingress资源。
  4. 最流行的Ingress Controller是Nginx Ingress Controller,它基于经典反向代理软件Nginx。

再补充一点,目前的Kubernetes流量管理功能主要集中在Ingress Controller上,已经远不止于管理“入口流量”了,它还能管理“出口流量”,也就是 egress,甚至还可以管理集群内部服务之间的“东西向流量”。

此外,Ingress Controller通常还有很多的其他功能,比如TLS终止、网络应用防火墙、限流限速、流量拆分、身份认证、访问控制等等,完全可以认为它是一个全功能的反向代理或者网关,感兴趣的话你可以找找这方面的资料。

课外小贴士:

  • 所谓的“四层”、“七层”指的是 OSI 网络参考模型里的层次,简单来说,四层就是 TCP/IP 协议,七层就是 HTTP/HTTPS 等应用协议。
  • 因为 Ingress Controller 的名字比较长,所以有的时候会缩写成 IC 或者 KIC。
  • 为了提升路由效率,降低网络成本,IngressController 通常不会走 Service 流量转发,而是访问 apiserver 直接获得 Service 代理的 Pod 地址,从而绕过了 Service 的 iptables 规则。
  • Ingress 的能力较弱,路由规则不灵活,所以 IngressController 基本上都有各自的功能扩展,比如 Nginx 就增加了自定义资源对象 VirtualServer、Transport-Server 等。
编辑 (opens new window)
上次更新: 2023/04/27, 03:23:27
YAML、Pod、Job、CronJob、ConfigMap、Secret
PV、PVC、StorageClass、Provisioner、StatefulSet

← YAML、Pod、Job、CronJob、ConfigMap、Secret PV、PVC、StorageClass、Provisioner、StatefulSet→

最近更新
01
Deep Reinforcement Learning
10-03
02
误删数据后怎么办
04-06
03
MySQL 一主多从
03-22
更多文章>
Theme by Vdoing | Copyright © 2021-2024 yubincloud | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×