首页
  • 监控

    • grafana
    • prometheus
  • 学习笔记

    • 《核心系统命令实战》
    • 《MySQL 是怎样运行的:从根儿上理解 MySQL》
    • 《Ansible权威指南》
  • 博客搭建
  • git
  • python
  • 友情链接
  • 文档编写规范
  • 我用过的电脑
  • 喷涂相关
  • 每日一溜
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

小刘说

砥砺前行
首页
  • 监控

    • grafana
    • prometheus
  • 学习笔记

    • 《核心系统命令实战》
    • 《MySQL 是怎样运行的:从根儿上理解 MySQL》
    • 《Ansible权威指南》
  • 博客搭建
  • git
  • python
  • 友情链接
  • 文档编写规范
  • 我用过的电脑
  • 喷涂相关
  • 每日一溜
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • zabbix

  • docker

  • kubernetes

    • 二进制安装高可用k8s集群(v1.25.0)
    • 二进制安装高可用k8s集群(v1.27.2)
    • kubernetes架构解析
    • kubernetes基础概念-Pod
    • kubernetes调度基础
    • kubernetes控制器-Deployment
    • kubernetes控制器-Service
      • 1. 创建Service
        • 1.1. 带有标签选择器的Service
        • 1.2. 不带标签选择器的Service
        • 1.2.1. Endpoints
        • 1.2.2. EndpointSlice
      • 2. Service的类型
        • 2.1. NodePort
        • 2.2. LoadBalancer
        • 2.3. ExternalName
        • 2.4. externalIPs
      • 3. 服务发现
        • 3.1. 环境变量
        • 3.2. DNS
    • Kubernetes(k8s) YAML文件详解
  • harbor

  • mysql

  • nexus

  • jenkins

  • elasticsearch

  • 学习笔记

  • apache2.2升级2.4
  • heredoc(cat EOF)
  • Rocky linux 9 初始化
  • 运维
  • kubernetes
小刘
2023-08-18
目录

kubernetes控制器-Service

# kubernetes控制器-Service

Service 是 将运行在一个或一组 Pod (opens new window) 上的网络应用程序公开为网络服务的方法。

使用Deployment部署Pod时,Pod大部分都是随机地部署到节点上,并且经常被删除重建,这就导致了容器间访问需要一种固定不变的资源类型来访问。

而kubernetes定义了一种对象类型,通过Service来访问Pod,而Service充当负载均衡器来把连接转发到其中任意一个Pod当中。

# 1. 创建Service

# 1.1. 带有标签选择器的Service

与Deployment相同,Service也通过标签选择器进行匹配Pod,新建 Service.yml :

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
1
2
3
4
5
6
7
8
9
10
11

这个yml文件创建了一个名称为 my-service 的Service,并匹配所有标签为 app.kubernetes.io/name: MyApp 的Pod,并把Pod的9376端口(协议类型为TCP)反向代理到80端口。

创建这个Service,并查看Service的状态:

kubectl apply -f Service.yml
kubectl get services
1
2

  • TYPE:Service 类型,默认为ClusterIP,即只能在集群内部进行访问,通过 CLUSTER-IP:PORT 进行访问,或者通过 NAME:PORT 进行访问。

也可以在Pod中 .spec.containers[].ports[].name 自定义端口的名称,之后在Service中根据名称进行调用,通过自定义端口的名称可以极大提高Service配置的灵活性,例如我更新了Pod的端口,就不再需要再次更新Service的targetPort,减少了配置的工作量:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app.kubernetes.io/name: proxy
spec:
  containers:
  - name: nginx
    image: nginx:stable
    ports:
      - containerPort: 80
        name: http-web-svc
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app.kubernetes.io/name: proxy
  ports:
  - name: name-of-service-port
    protocol: TCP
    port: 80
    targetPort: http-web-svc
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

# 1.2. 不带标签选择器的Service

通过不设置标签选择器可以自定义匹配Service的后端服务,相当于自定义了一个负载均衡器。

而这个负载均衡器又通过定义 EndpointSlice 对象和 Endpoints 对象来把连接转发到其中任意一个IP地址当中。

可以应用在以下场景:

  • 希望在生产环境中使用某个固定的名称而非IP地址访问外部的中间件服务;

  • 使用其他命名空间或者其他集群的服务;

  • 从基础环境迁移到kubernetes时,需要将一部分流量转发到外部服务当中;

# 1.2.1. Endpoints

Endpoints (opens new window) (该资源类别为复数)定义了网络端点的列表,通常由 Service 引用,以定义可以将流量发送到哪些 Pod。

简单来说,Endpoints对象包含了需要进行反向代理的IP和端口。而针对Endpoints对象,又根据Service的情况分为两种Endpoints:

  • Service带有标签选择器:由kubernetes管理,有与Service匹配的Pod新建或者删除时,kubernetes都会自行更新Endpoints对象中的信息,来做到动态的负载均衡。
  • Service不带标签选择器:如果创建了不包含标签选择器的Service,kubernetes将不会自动创建Endpoints对象,而这种方式可以通过手动创建Endpoints对象来实现Service与Endpoints的对象相匹配。

下面我们主要看一下不带标签选择器的Service是如何与Endpoints建立连接的,新建 ServiceWithoutSelector_Endpoints.yml:

apiVersion: v1
kind: Service
metadata:
  name: my-service        # Service的名称必须与Endpoints的名称相匹配
spec:
  ports:
    - protocol: TCP
      port: 80            # Service对外暴露的端口
---
apiVersion: v1
kind: Endpoints
metadata:
  name: my-service        # Endpoints的名称必须与Service的名称相匹配
subsets:
  - addresses:
    - ip: 10.1.1.1        # 被反向代理的ip地址
    - ip: 10.1.1.2        # 被反向代理的ip地址
    ports:
    - port: 80            # 被反向代理的端口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

创建这个Service,并查看Service和Endpoints的状态:

kubectl apply -f ServiceWithoutSelector_Endpoints.yml
kubectl get services
kubectl get endpoints
1
2
3

通过访问 CLUSTER-IP:PORT 即可正常访问对应的Service。

# 1.2.2. EndpointSlice

EndpointSlice (opens new window) 对象表示某个服务的后端网络端点的子集(切片)。

例如电商行业在进行促销活动或秒杀抢购活动时,业务流量相对较大。为了应对这种场景,通常会设置弹性扩容。在活动进行时,服务会进行弹性伸缩直到能够承载流量,这时会基于弹性扩容的策略,为业务增加副本数,也就是 Pod 会变多。

每个 Pod 都有各自唯一的 IP ,但同时 Pod 的 IP 也不是固定的。为了及时追踪 Pod IP 的变化,从而进行负载均衡,Endpoints API 提供了在 Kubernetes 中跟踪网络端点的一种简单而直接的方法。

但随着 Kubernetes 集群和服务逐渐开始为更多的后端 Pod 进行处理和发送请求,比如上文提到大流量场景下,Pod 数量会被不断扩容,Endpoints API 也将变得越大。这种情况下,Endpoints API 局限性变得越来越明显,甚至成为性能瓶颈。

为了解决这个局限性问题,在 Kubernetes v1.21 的版本中引入了对 Endpointslice API 的支持,解决了 Endpoints API 处理大量网络端点带来的性能问题,同时提供了可扩展和可伸缩的能力。

Endpoints 在流量高峰时的变化:

Endpointslices 在流量高峰时的变化:

下面我们主要看一下不带标签选择器的Service是如何与EndpointSlice建立连接的,新建 ServiceWithoutSelector_EndpointSlice.yml:

apiVersion: v1
kind: Service
metadata:
  name: my-service                    # Service的名称必须与Endpoints的标签kubernetes.io/service-name相匹配
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8443
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: my-service-1                  # 按惯例将Service的名称用作EndpointSlice名称的前缀
  labels:
    # Endpoints的标签kubernetes.io/service-name必须与Service的名称相匹配
    kubernetes.io/service-name: my-service
addressType: IPv4                     # 地址类型,可以配置IPv4,IPv6,FQDN三种参数
ports:
  - appProtocol: http                 # 关联的服务的协议
    protocol: TCP                     # 协议类型
    port: 8443
endpoints:
  - addresses:
      - "10.1.1.1"                    # 被反向代理的ip地址
      - "10.1.1.2"                    # 被反向代理的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

需要注意的是,Service的名称与Endpoints的标签 kubernetes.io/service-name 相匹配,与Endpoints匹配方式略有不同。

同样创建这个Service,并查看Service和EndpointSlice的状态:

kubectl get services
kubectl get endpointslices
1
2

Service与EndpointSlice都成功被创建并且可以正常访问。

# 2. Service的类型

Service Type(服务类型)主要包括以下几种:

  • ClusterIP:默认值,只能在集群内部进行访问。通过 CLUSTER-IP:PORT 进行访问,或者通过 NAME:PORT 进行访问。
  • NodePort:在所有安装了 kube-proxy 的节点上打开一个端口,此端口可以代理至后端Pod,可以通过NodePort从集群外部访问集群内的服务,格式为 NodeIP:NodePort 。
  • LoadBalancer:使用云提供商的负载均衡器公开Service。
  • ExternalName:通过返回定义的CNAME别名,将Service映射到可被DNS解析的其他域名。

ClusterIP这里省略,直接分析下Service对外暴露的三种类型,NodePort,LoadBalancer和ExternalName。

# 2.1. NodePort

通过创建Service,并将Type设置为NodePort来实现通过NodePort从集群外部访问集群内的服务。

通过图片我们可以看出来,将Type设置为NodePort可以实现在每一个集群节点上都会打开一个NodePort,而通过访问任意一个集群节点的IP+NodePort就可以从外部访问到后端的Pod。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: NodePort
  selector:
    app.kubernetes.io/name: MyApp
  ports:
    # 为了方便起见,targetPort被设置为与port字段相同的值
    - port: 80
      targetPort: 80
      # 可选字段,不指定此字段的情况下,kubernetes会从某个范围内分配一个端口号(默认:30000-32767)
      nodePort: 30007
1
2
3
4
5
6
7
8
9
10
11
12
13
14

可以通过如下方式访问此Sercice:

  • 集群内部:通过 CLUSTER-IP:PORT 进行访问,或者通过 NAME:PORT 进行访问。
  • 集群外部:任意一个集群节点的宿主机IP+NodePort进行访问。

# 2.2. LoadBalancer

LoadBalancer底层依赖于NodePort。

使用NodePort就又涉及到负载均衡的问题,而LoadBalancer就是为了解决K8s将内部服务对外暴露时如何进行负载均衡的问题而出现的。

假设我们有一套阿里云K8s环境,要将K8s内部的一个服务通过LoadBalancer方式暴露出去,可以将Service type设定为LoadBalancer。服务发布后,阿里云K8s不仅会自动创建服务的NodePort端口转发,同时会自动帮我们申请一个SLB,有独立公网IP,并且阿里云K8s会帮我们自动把SLB映射到后台K8s集群的对应NodePort上。这样,通过SLB的公网IP,我们就可以访问到K8s内部服务,阿里云SLB负载均衡器会在背后做负载均衡。

值得一提的是,如果是在本地开发测试环境里头搭建的K8s,一般不支持LoadBalancer,因为通过NodePort做测试访问就够了。但是在生产环境或者公有云上的K8s,例如GCP或者阿里云K8s,基本都支持自动创建LoadBalancer。

如果是自建集群,可以通过安装 MetalLB (opens new window) 来使用 LoadBalancer。这里暂时不跟进讲解自建集群的LoadBalancer相关配置。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: my-nginx-svc
  namespace: default
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: LoadBalancer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

公有云具体配置可以参考阿里云官方提供的 通过使用自动创建SLB的服务公开应用 (opens new window) 这篇文章进行配置。

# 2.3. ExternalName

通过返回定义的CNAME别名,将Service映射到可被DNS解析的其他域名。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: ExternalName
  externalName: my.database.example.com
1
2
3
4
5
6
7

这个yml把名称为my-service的Service映射到了my.database.example.com这个域名。

# 2.4. externalIPs

定义 Service 时,你可以为任何服务类型 (opens new window)指定 externalIPs。

externalIPs可以说是Service类型的扩充。

如果有外部可达的 IP(如网络通过BGP,OSPF等路由协议进行宣告路由可达集群节点) ,即集群外能通过这个IP访问到集群内的Node(至少其中一台节点),那我们就可以通过这些Node将流量转发到Service,并提供负载均衡。

🔔 external IP 一般由数据中心或者网络管理员管理,独立于kubernetes之外。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app.kubernetes.io/name: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  externalIPs:
    - 192.168.7.30
1
2
3
4
5
6
7
8
9
10
11
12
13

这个叫 my-service 的服务可以在 192.168.7.30:80 上被正常访问。

# 3. 服务发现

kubernetes支持两种类型的服务发现:环境变量和DNS。

# 3.1. 环境变量

当Pod部署到一个Node节点后,kubelet会在其中为每个活跃的Service添加一组环境变量,并以 {SERVICE_NAME}_SERVICE_HOST 和 {SERVICE_NAME}_SERVICE_PORT 格式命名的变量,其中服务名都转换为大写字母,. 和 - 转换为 _ 。

# 3.2. DNS

参考文章及书籍:

  • Service (opens new window)

  • Kubernetes in Action (opens new window)

  • Kubernetes in Action, Second Edition (opens new window)

  • 深入剖析Kubernetes (opens new window)

  • 云原生Kubernetes全栈架构师实战 (opens new window)

  • Kubernetes上千规模Pod最佳实践 (opens new window)

  • Kubernetes网络三部曲之三 ~ NodePort vs LoadBalancer vs Ingress (opens new window)

  • [译] 基于 BPF/XDP 实现 K8s Service 负载均衡 (LPC, 2020) (opens new window)

上次更新: 2024/05/11, 03:55:33

← kubernetes控制器-Deployment Kubernetes(k8s) YAML文件详解→

最近更新
01
kubernetes控制器-Deployment
08-08
02
kubernetes调度基础
07-27
03
kubernetes基础概念-Pod
07-25
更多文章>
Theme by Vdoing | Copyright © 2023-2024 本站支持IPv6访问 本站支持SSL安全访问
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式