侧边栏壁纸
博主头像
路小飞博主等级

行动起来,活在当下

  • 累计撰写 72 篇文章
  • 累计创建 12 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

06- Kubernetes 服务发布

路小飞
2024-10-15 / 0 评论 / 0 点赞 / 42 阅读 / 38825 字

在传统架构中,配置多个相同服务的负载均衡器通常使用 Nginx 的 upstream 模块,而在 Kubernetes 中,则通过 Service 来实现这一功能。对于域名访问应用,传统架构可能使用 Nginx 的 Server 配置,在 Kubernetes 中则可用 Ingress 来实现域名路由的配置。

Ingress 配置最终会生成对应控制器的配置,例如,当使用 Nginx 作为 Ingress 控制器时,Ingress 资源将生成相应的 Nginx 配置文件。需要注意的是,Service 主要实现四层负载均衡,而 Ingress 实现的是七层负载均衡。这使得 Kubernetes 在处理流量时具备更高的灵活性和可扩展性。

6.1 标签和选择器

在 Kubernetes 中,为了实现资源的有效分组管理,我们通常会使用标签。通过将关键字(key=value)形式的标签附加到 Pods 等 API 对象上,可以根据不同功能对同一应用的 Pods 进行细分和区分。当 Kubernetes 对资源进行分组时,这些标签能够帮助准确识别和选择目标对象。此外,选择器则提供了一种查询机制,可用于匹配具有特定标签的对象,从而简化资源的管理与访问。

6.1.1 定义标签

假设你正在开发一个电商平台,该平台包含多个微服务,例如用户服务、产品服务和订单服务。为了方便管理和监控这些服务,你可以为每个服务添加标签,以便于在不同环境(开发、测试、生产)中进行筛选和管理。

用户服务的 Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ecommerce
      service: user-service
      
  template:
    metadata:
      labels:
        app: ecommerce
        service: user-service
    spec:
      containers:
      - image: nginx:1.22.1
        name: nginx
        ports:
        - containerPort: 80  # 暴露的端口

产品服务的 Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ecommerce
      service: product-service
  template:
    metadata:
      labels:
        app: ecommerce
        service: product-service
    spec:
      containers:
      - image: nginx:1.22.1
        name: nginx
        ports:
        - containerPort: 80  # 暴露的端口

订单服务的 Deploy:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ecommerce
      service: order-service
  template:
    metadata:
      labels:
        app: ecommerce
        service: order-service
    spec:
      containers:
      - image: nginx:1.22.1
        name: nginx
        ports:
        - containerPort: 80  # 暴露的端口
        
[root@master-01 ~]# kubectl get pod --show-labels 
NAME                            READY   STATUS    RESTARTS   AGE   LABELS
user-service-6779598469-shlht   1/1     Running   0          4m    app=ecommerce,pod-template-hash=6779598469,service=user-service
[root@master-01 ~]# kubectl get deploy --show-labels 
NAME           READY   UP-TO-DATE   AVAILABLE   AGE    LABELS
user-service   1/1     1            1           4m8s   <none>
[root@master-01 ~]# 

6.1.2 增加标签

使用 label 来增加标签:

kubectl label deploy product-service base=bj

6.1.3 修改标签

使用 --overwrite 参数来修改标签:

kubectl label deploy product-service base=sh --overwrite

6.1.4 删除标签

使用 - 来删除标签:

kubectl label deploy product-service base-
[root@master-01 ~]# kubectl label deploy product-service base=bj
deployment.apps/product-service labeled
[root@master-01 ~]# kubectl get deploy --show-labels 
NAME              READY   UP-TO-DATE   AVAILABLE   AGE     LABELS
order-service     1/1     1            1           45s     <none>
product-service   1/1     1            1           92s     base=bj
user-service      1/1     1            1           6m11s   <none>
[root@master-01 ~]# kubectl label deploy product-service base=sh --overwrite
deployment.apps/product-service labeled
[root@master-01 ~]# kubectl get deploy --show-labels 
NAME              READY   UP-TO-DATE   AVAILABLE   AGE     LABELS
order-service     1/1     1            1           72s     <none>
product-service   1/1     1            1           119s    base=sh
user-service      1/1     1            1           6m38s   <none>
[root@master-01 ~]# kubectl label deploy product-service base-
deployment.apps/product-service unlabeled
[root@master-01 ~]# kubectl get deploy --show-labels 
NAME              READY   UP-TO-DATE   AVAILABLE   AGE     LABELS
order-service     1/1     1            1           103s    <none>
product-service   1/1     1            1           2m30s   <none>
user-service      1/1     1            1           7m9s    <none>
[root@master-01 ~]# 

6.1.6 标签和污点的区别

特性标签 (Label)污点 (Taint)
用途标识和选择对象控制 Pods 的调度
作用范围附加于任意对象(Pods、Nodes 等)附加于节点
行为允许选择和查询阻止 Pods 被调度到有污点的节点
容忍机制Pods 必须具备相应的容忍才能调度到污点节点

6.1.7 选择器

选择器主要用于资源的匹配,只有符合条件的资源才会被调用或使用,可以使用该方式对集群中的各类资源进行分配。Kubernetes 的核心资源 Deployment 、StatefulSet 管理哪些 Pod 是通过选择器字段决定的,通过 Service 访问哪些后端也是通过选择器决定的。

1. 标签运算符

① 基于等值

基于等值或基于不等值的需求允许按标签键和值进行过滤。 匹配对象必须满足所有指定的标签约束,尽管它们也可能具有其他标签。 可接受的运算符有 ===!= 三种。 前两个表示相等(并且是同义词),而后者表示不相等。例如:

environment = production
tier != frontend

② 基于集合

基于集合的标签需求允许你通过一组值来过滤键。 支持三种操作符:innotinexists(只可以用在键标识符上)。例如:

environment in (production, qa)
tier notin (frontend, backend)
partition
!partition
2. 基于标签匹配资源

使用 --show-labels 查看 Pod 目前已有的标签:

kubectl get pod --show-labels

选择 service 为 user 的 Pod:

kubectl get pod -l service=user-service

选择 service 为 user 或者 product 的 Pod:

kubectl get pod -l 'service in (user-service,order-service)' --show-labels 
[root@master-01 ~]# kubectl get pod --show-labels
NAME                               READY   STATUS    RESTARTS   AGE     LABELS
order-service-5dddfb8448-gp9qx     1/1     Running   0          4m48s   app=ecommerce,pod-template-hash=5dddfb8448,service=order-service
product-service-6dd8b8d6d7-swsfw   1/1     Running   0          5m35s   app=ecommerce,pod-template-hash=6dd8b8d6d7,service=product-service
user-service-6779598469-shlht      1/1     Running   0          10m     app=ecommerce,pod-template-hash=6779598469,service=user-service
[root@master-01 ~]# kubectl get pod -l service=user-service
NAME                            READY   STATUS    RESTARTS   AGE
user-service-6779598469-shlht   1/1     Running   0          10m
[root@master-01 ~]# kubectl get pod -l 'service in (user-service,order-service)' --show-labels 
NAME                             READY   STATUS    RESTARTS   AGE     LABELS
order-service-5dddfb8448-gp9qx   1/1     Running   0          4m54s   app=ecommerce,pod-template-hash=5dddfb8448,service=order-service
user-service-6779598469-shlht    1/1     Running   0          10m     app=ecommerce,pod-template-hash=6779598469,service=user-service
[root@master-01 ~]# 

6.2 Service

在使用虚拟机或裸容器部署应用时,程序间的互相访问通常依赖宿主机的 IP 地址和端口,因为宿主机的 IP 地址一般不会轻易改变,因此这种方式相对稳定。然而,在 Kubernetes 中,Pod 的部署是动态的,通常会随机分配到最佳节点上,并且可能频繁被删除和重建,这意味着 Pod 的 IP 地址并不固定。在这种情况下,直接使用 Pod 的 IP:端口 进行服务间访问就不再可行。因此,Kubernetes 引入了 Service。

Kubernetes 的 Service 充当四层(L4)代理,主要通过 TCP 和 UDP 协议在网络层进行流量管理。

6.2.1 Service 是什么

Service 是一种抽象,用于定义一组 Pod 的逻辑集合以及访问这些 Pod 的策略。主要用于 Pod 之间的通信,提供一个预定义且稳定的资源类型,使其他 Pod 可以通过 Service 名称进行连接,而无需依赖变化的 IP 地址。这通常是通过标签选择器(Label Selector)实现的。这种做法在生产环境中被广泛认可为最佳实践,能够显著提高系统的稳定性和可维护性。

6.2.2 定义 Service

以下是一个 Service 示例 YAML 配置:

apiVersion: v1
kind: Service
metadata:
  name: my-service      # Service 的名称
spec:
  selector:
    app: my-app         # 标签选择器,选择属于 "my-app" 应用程序的 Pod
  ports:
    - protocol: TCP     # 端口协议,TCP 或 UDP
      port: 80          # Service 暴露的端口
      targetPort: 8080  # Pod 真实监听的端口
  type: ClusterIP       # Service 类型,可以是 ClusterIP、NodePort、LoadBalancer 等

创建一个名为 my-service 的 Service,它会选择所有标签为 app=my-app 的 Pod,并将对外提供的 80 端口的请求转发到这些 Pod 的 8080 端口上。这样,集群中的其他组件或服务可以通过 my-service 来访问这些 Pod,而无需关心 Pod 的具体 IP 地址或数量。

需要注意的是, Service 能够将一个接收端口映射到任意的 targetPort,如果 targetPort 为空,targetPort 将被设置为与 Port 字段相同的值。targetPort 也可以设置为一个字符串,引用 Pod 的一的端口的名称,这样的话即使更改了 Pod 的端口,也不会对 Service 的访问造成影响。

Service 支持 TCP、UDP、SCTP 等协议,默认为 TCP 协议。

使用以下命令创建:

kubectl apply -f my-svc.yaml

6.2.3 Endpoint 和无选择器的 Service

创建一个带有选择器(Selector字段)的 Service 后,集群会在该 Service 所在的 Namespace 创建一个同名的 Endpoint ,这个 Endpoint 记录了选择器匹配到的 Pod 的 IP地址和端口。当 Pod 被重建后,该 Endpoint 会被更新,而我们使用 Service 名称连接后端的 Pod时,并不会感知到这些变化。

无选择器的 Service 没有 selector 字段。这意味着 Kubernetes 不会自动创建任何 Endpoints(即不对应于任何 Pod 的网络地址),而是需要手动创建 Endpoints 对象来指向特定的 IP 地址或服务。

无选择器的 Service 主要用于以下几种场景:

  • 代理外部服务:可以将集群内部的请求代理到集群外部的某个服务。这对于访问外部 API 或微服务架构中集群外部的服务非常有用。
  • 手动控制 Endpoints:您可以精确控制与 Service 关联的 Endpoints,这对于将 Service 指向特定的 IP 地址或端口(例如在某些情况下使用静态 IP 的外部服务)很有用。
  • 与现有服务集成:在某些情况下,您可能希望将 Kubernetes 服务与已有的、并不在 Kubernetes 集群内的服务集成,而不想在 Kubernetes 中管理 Pod。
  • 服务分离:当需要将某些功能分离到外部服务而不是内部 Pods 时,可以使用无选择器的 Service 进行简化和分离。

创建无选择器的 Service:

apiVersion: v1
kind: Service
metadata:
  name: my-service 
spec:
  ports:
    - protocol: TCP
      port: 80 
      targetPort: 8080
  type: ClusterIP       

创建对应的 Endpoints:

apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.168.1.100  # 外部服务的 IP
    ports:
      - port: 80          # 对应的服务端口

当 Pod 需要访问外部服务时,只需使用 Kubernetes 服务的名称(例如 my-service),就像访问内部的 Kubernetes 服务一样。

例如,如果您有一个 Pod 需要连接到该外部服务,您可以直接在应用程序中访问:

  • 服务的 DNS 名称:my-external-service.default.svc.cluster.local(假设在 default 命名空间)
  • 或者使用 ClusterIP 地址。

6.2.4 Service 的代理模式

在 Kubernetes 集群中,每个节点都运行一个 Kube-proxy 组件,负责为 Service 提供虚拟 IP(VIP)功能。最初,Kube-proxy 使用用户空间(userspace)模式进行流量代理,但后来引入了基于 iptables 和 IPVS 的方法。默认情况下使用的是 iptables。当前,在生产环境中,建议使用 IPVS 作为流量代理的解决方案,以提高性能和可扩展性。

查看 kube-proxy 的代理模式:

curl 127.0.0.1:10249/proxyMode

使用如下命令修改 kube-proxy 的代理模式:

kubectl -n kube-system edit cm kube-proxy
apiVersion: v1
kind: ConfigMap
metadata:
  name: kube-proxy
  namespace: kube-system
data:
  config.conf: |
    ...
    mode: "iptables"  
    ...

1. iptables 模式

在 iptables 模式下,kube-proxy 通过监视 Kubernetes API 以获取 Services 和 Endpoints 的变化信息。来更新 iptables 规则,以便将流量引导到适当的后端 Pod。

这种模式适合于较小规模的集群,但在处理高并发连接时可能效率不如 IPVS 模式。

2. ipvs 模式

在 ipvs 模式下,kube-proxy 继续监视 Kubernetes API 服务器,以获取 Services 和 Endpoints 对象的变化。 通过调用 netlink 接口来创建和更新 IPVS 规则,每当Service 或 Endpoints 对象更新时,kube-proxy 也会相应地更新 IPVS 规则。

ipvs 基于 netfilter 钩子函数,但它使用哈希表作为底层数据结构并在内核空间中运行。这使得 ipvs 能更快地重定向流量,同时在同步代理规则时表现出更好的性能。

kube-proxy 使用 ipvs 模式必须在每个节点上上装 ipvs 模块,如果没有安装,将会默认使用iptables模式。

此外,kube-proxy 还会定期检查后端 Pod 的健康状态,确保只有健康的 Pod 会接收流量。如果某个 Pod 不可用,它会从 IPVS 规则(或 iptables 规则)中移除该 Pod。

6.2.5 Service 的类型

在 Kubernetes 中,Service 的类型决定了如何将流量路由到后端 Pods。不同的 Service 类型适用于不同的场景和需求。

1. ClusterIP
  • 描述: 默认类型,Service 仅在集群内部可用。
  • 用途: 适用于需要在集群内部分配流量的服务。外部无法直接访问。
  • 访问: 通过 Service 名称或 ClusterIP 在集群内部进行访问。
apiVersion: v1
kind: Service
metadata:
  name: my-clusterip-service
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
[root@master-01 ~]# kubectl get svc
NAME                   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes             ClusterIP   10.96.0.1       <none>        443/TCP        2d
my-clusterip-service   ClusterIP   10.106.59.139   <none>        80/TCP         2s

2. NodePort
  • 描述: 将 Service 暴露在每个 Node 的特定端口上(范围通常为 30000-32767)。
  • 用途: 允许外部流量通过每个 Node 的指定端口访问服务。
  • 访问: 使用 http://: 进行访问。
apiVersion: v1
kind: Service
metadata:
  name: my-nodeport-service
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - port: 80	# Service 端口
      targetPort: 8080	# Pod 端口
      nodePort: 30080  # 节点端口,可选,Kubernetes 会自动分配(30000-32767)

[root@master-01 ~]# kubectl get svc
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP        2d
my-nodeport-service   NodePort    10.101.165.92   <none>        80:30080/TCP   10s

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

3. LoadBalancer
  • 描述: 在云环境中,Kubernetes 会为 Service 自动配置外部负载均衡器。
  • 用途: 适用于需要将外部流量引入集群的服务。通常在公有云提供商中使用,如 AWS、GCP 和 阿里云等等。需要配置一个 Cloud Controller Manager (CCM),它负责与云服务提供商的 API 交互,以管理负载均衡器、节点等云资源。
  • 访问: 通过负载均衡器的外部 IP 地址进行访问。
apiVersion: v1
kind: Service
metadata:
  name: my-loadbalancer-service
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

[root@master-01 ~]# kubectl get svc 
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE

my-loadbalancer-service   LoadBalancer   10.108.200.158   203.0.113.5      80:32018/TCP   2m38s

在 Kubernetes 中,LoadBalancer 类型的 Service 实际上是对 NodePort 类型服务的一种封装和扩展,具体可以这样理解:

  • 外部用户可以通过 LoadBalancer 的外部 IP(如 203.0.113.5)和指定的端口(如 80)访问服务。此流量首先到达云提供商的负载均衡器(如 阿里云、AWS、GCP、Azure 等)。
  • 负载均衡器会将请求转发到集群中的任意节点的 NodePort。
4. ExternalName
  • 描述: 映射服务到外部 DNS 名称。不会分配 ClusterIP,类似于无选择器的 Service。
  • 用途: 用于将流量转发到外部服务(如外部 API)。
  • 访问: 通过 Service 名称访问,Kubernetes 会解析为外部 DNS 名称。
apiVersion: v1
kind: Service
metadata:
  name: my-externalname-service
spec:
  type: ExternalName
  externalName: www.baidu.com  # 外部服务的 DNS 名称

[root@master-01 ~]# kubectl run busybox --image=busybox --restart=Never --command -- sleep 3600
[root@master-01 ~]# kubectl exec -it busybox -- sh
[root@master-01 ~]# kubectl exec -it busybox -- sh
/ # ping  my-externalname-service
PING my-externalname-service (39.156.66.18): 56 data bytes
64 bytes from 39.156.66.18: seq=0 ttl=127 time=8.988 ms
64 bytes from 39.156.66.18: seq=1 ttl=127 time=9.269 ms
64 bytes from 39.156.66.18: seq=2 ttl=127 time=8.878 ms
^C
--- my-externalname-service ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 8.878/9.045/9.269 ms
/ # 

6.2.6 多端口 Service

在 Kubernetes 中,创建一个支持多端口的 Service 使得你能够将多个端口暴露给外部流量或在集群内部进行访问。这对于需要监听多个服务或协议的应用程序非常重要。

下面是一个示例 YAML 文件,展示了如何创建一个多端口 Service:

apiVersion: v1
kind: Service
metadata:
  name: my-multiport-service
spec:
  selector:
    app: my-app
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
    - name: https
      port: 443
      targetPort: 8443
      protocol: TCP
    - name: grpc
      port: 50051
      targetPort: 50051
      protocol: TCP
  type: ClusterIP  # 可以根据需求选择 NodePort 或 LoadBalancer

6.2.7 Kubernetes 服务发现

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

1. 基于环境变量的服务发现

当 Pod 部署到某个 Node 节点后,该节点上的 Kubelet 会为该 Pod 设置一组环境变量。这些环境变量根据活跃的 Service 生成,命名格式为 {SERVICE_NAME}_SERVICE_HOST{SERVICE_NAME}_SERVICE_PORT,其中服务名转换为大写字母,并将 .- 替换为 _

例如:

[root@master-01 ~]# kubectl get svc
NAME                      TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)        AGE
kubernetes                ClusterIP      10.96.0.1        <none>          443/TCP        2d
my-clusterip-service      ClusterIP      10.106.59.139    <none>          80/TCP         24m
my-externalname-service   ExternalName   <none>           www.baidu.com   <none>         13m
my-loadbalancer-service   LoadBalancer   10.108.200.158   <pending>       80:32018/TCP   23m
my-nodeport-service       NodePort       10.101.165.92    <none>          80:30001/TCP   27m
[root@master-01 ~]# kubectl exec -it busybox -- env |grep 10.101.165.92
MY_NODEPORT_SERVICE_PORT=tcp://10.101.165.92:80
MY_NODEPORT_SERVICE_PORT_80_TCP_ADDR=10.101.165.92
MY_NODEPORT_SERVICE_SERVICE_HOST=10.101.165.92
MY_NODEPORT_SERVICE_PORT_80_TCP=tcp://10.101.165.92:80
[root@master-01 ~]# 

这些变量可以被程序直接使用,用于发现同一命名空间下的其他服务。但在创建 Pod 之前,必须先创建其他应用的 Service,否则相应的 Service 变量将无法生成。

2. 基于 DNS 的服务发现

Kubernetes 进行服务发现的另一种方式是基于集群内部的 DNS 记录。新版 Kubernetes 默认使用 CoreDNS 作为内部 DNS,通常其 Service 地址为 Service 网段的第 10 个 IP(例如 10.96.0.10),端口为 53。集群内的 Pod 可以通过该地址和端口进行服务解析。DNS 服务器监听 Kubernetes 创建的 Service,并为每个 Service 添加一组 DNS 记录,使得集群中的 Pod 能通过内部 DNS 解析到 Service 的 IP,即 Service 的 ClusterIP。

Service 对象的完整域名格式为 ‘服务名.命名空间.svc.cluster.local’

[root@master-01 ~]# kubectl get svc -A -owide |grep -i dns
kube-system   kube-dns                  ClusterIP      10.96.0.10       <none>          53/UDP,53/TCP,9153/TCP   2d    k8s-app=kube-dns
[root@master-01 ~]# kubectl exec -it busybox -- sh
/ # nslookup 10.96.0.10
Server:		10.96.0.10
Address:	10.96.0.10:53

10.0.96.10.in-addr.arpa	name = kube-dns.kube-system.svc.cluster.local

/ # nslookup kube-dns.kube-system.svc.cluster.local
Server:		10.96.0.10
Address:	10.96.0.10:53

Name:	kube-dns.kube-system.svc.cluster.local
Address: 10.96.0.10

对于无头服务,其 Pod 也具有稳定的域名,格式为 ‘Pod名.服务名.命名空间.svc.cluster.local’。无头服务详细解释参考控制器的 sts 部分。

什么是无头服务?

Service 原本的目的是负载均衡,应该由它在 Pod 前面来转发流量,但是对 StatefulSet 来说,这项功能反而是不必要的,因为 Pod 已经有了稳定的域名,外界访问服务就不应该再通过 Service 这一层了。所以,从安全和节约系统资源的角度考虑,我们可以在 Service 里添加一个字段 clusterIP: None ,告诉 Kubernetes 不必再为这个对象分配 IP 地址。这种类型的 Service 对象也被称为 Headless Services (无头服务 )。

相较于 Kubernetes 的服务发现,业界更常用的工具如 Spring Cloud 的 Eureka 或 Consul 实现微服务的自动发现。然而,并非所有项目都基于 Spring Cloud 全家桶开发,许多项目使用其他工具或 Kubernetes 环境变量进行服务发现,这可能导致实现服务发现逻辑的代码复杂度和时间成本增加。因此,基于 Kubernetes DNS 的服务发现可能更加简单高效。

6.3 Ingress

6.3.1 Ingress 基础概念

1. 什么是 Ingress

通俗来说,Ingress 是 Kubernetes 的一种资源对象,与之前提到的 Service 和 Deployment 类似。Deployment 用于部署应用,而 Ingress 则实现通过域名访问这些应用。

Service 实质上是由 kube-proxy 控制的四层负载均衡器,但功能较为有限,只能基于 IP 地址和端口号进行简单的转发和组合。而当今大多数应用运行在七层的 HTTP/HTTPS 协议上,因此,Service 更适合集群内部的服务代理。若要将服务暴露到集群外部,通常只能使用 NodePort 或 LoadBalancer,但这两种方式都有局限,无法满足所有场景。为了解决这一问题,Kubernetes 引入了一种新的 API 对象,用于实现七层负载均衡。

Ingress 为 Kubernetes 集群中的服务提供外部访问入口,支持负载均衡、SSL 终止、基于域名的虚拟主机、灰度发布等功能,并能处理更复杂的路由规则,如主机名、URI、请求头和证书等。在生产环境中,常用的 Ingress 实现包括 Traefik、Nginx、HAProxy 和 Istio 等。

2. 什么是 Ingress Controller

Ingress Controller 是 Kubernetes 中的一个重要组件,Ingress Controller 在每个符合条件的宿主机上部署一个 Pod,该 Pod 运行 Nginx 进程。其实现逻辑与宿主机上部署 Nginx 的方式基本相同,主要区别在于:宿主机上部署的 Nginx 需要手动修改配置文件来设置域名,而 Ingress 则与其他 Kubernetes 资源一样,使用 YAML 文件进行配置。随后,Ingress Controller 根据 Ingress 的 YAML 文件自动生成相应的配置文件。

6.3.2 创建 Ingress Controller

本文使用的是 Kubernetes 官方维护的 Ingress NGINX Controller(v1.6.4 版本),请确保该版本与 Kubernetes 兼容。

在 Windows 机器上下载 ingress-nginx-controller-v1.6.4.tar.gz 文件后,将其传输到服务器并解压。

参考链接:

对于 Linux 服务器,可以使用以下命令进行下载:

curl -o deploy.yaml https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml

在 deploy.yaml 文件中,Controller 默认采用 LoadBalancer 方式暴露服务,本文将其修改为 NodePort,笔者可根据自己的环境自由选择 NodePort 或者 LoadBalancer。

apiVersion: v1
kind: Service
……
  namespace: ingress-nginx
……
    nodePort: 30080			# 节点端口,可选,Kubernetes 会自动分配(30000-32767)
……
  type: NodePort			#将 LoadBalancer 修改为 NodePort

然后使用以下命令将 Ingress Controller 部署到 Kubernetes 集群中。

kubectl apply -f deploy.yaml
[root@master-01 ~]# kubectl -n ingress-nginx get all
NAME                                            READY   STATUS      RESTARTS        AGE
pod/ingress-nginx-admission-create-c48f6        0/1     Completed   0               20h
pod/ingress-nginx-admission-patch-96sxf         0/1     Completed   2               20h
pod/ingress-nginx-controller-56cc97cf59-tj8qf   1/1     Running     1 (2m30s ago)   20h

NAME                                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/ingress-nginx-controller             NodePort    10.113.178.57    <none>        80:30080/TCP,443:30043/TCP   20h
service/ingress-nginx-controller-admission   ClusterIP   10.112.105.145   <none>        443/TCP                      20h

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ingress-nginx-controller   1/1     1            1           20h

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/ingress-nginx-controller-56cc97cf59   1         1         1       20h

NAME                                       COMPLETIONS   DURATION   AGE
job.batch/ingress-nginx-admission-create   1/1           47s        20h
job.batch/ingress-nginx-admission-patch    1/1           53s        20h

6.3.3 Ingress 初体验

1. 定义包含 Ingress 的资源文件
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-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';
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx-dep
  name: nginx-dep
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-dep
  template:
    metadata:
      labels:
        app: nginx-dep
    spec:
      volumes:
      - name: nginx-conf-vol
        configMap:
          name: nginx-conf
      containers:
      - image: nginx:1.22.1
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: /etc/nginx/conf.d
          name: nginx-conf-vol
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx-dep
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ing
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.example.com
    http:
      paths:
      - path: /test
        pathType: Exact
        backend:
          service:
            name: nginx-svc
            port:
              number: 80

spec.rules:定义了一组路由规则。每条规则通常与一个特定的主机名和路径匹配。

  • - host: nginx.example.com: 这是请求需要匹配的主机名。当请求的主机名为 nginx.example.com 时,将应用以下路由规则。
  • http: 指定 HTTP 路由的详细信息。
  • paths: 这是一个路径数组,定义了哪些路径请求将被路由到特定的后端服务。
    • - path: /test: 这是定义的路径规则,表示请求的路径必须以 /test 开头。
    • pathType: Exact: 这定义了路径匹配的方式。在这里,Exact 表示请求路径必须完全等于 /test,即 /test 本身,不包括其他路径(例如 /test//test/abc 不会匹配)。
    • backend: 定义了当匹配路径时请求将被转发到的后端服务。
      • service: 这里定义了后端服务的详细信息。
        • name: nginx-svc: 指定了后端服务的名称,这里是 nginx-svc
        • port: number: 80: 指定后端服务的端口,这里表示请求将转发到 nginx-svc 服务的 80 端口。
2. 创建资源

使用以下命令将以上资源清单创建出来:

kubectl apply -f ingress.yaml

查看资源状态:

[root@master-01 ~]# kubectl get svc
NAME         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.112.0.1       <none>        443/TCP   205d
nginx-svc    ClusterIP   10.120.212.176   <none>        80/TCP    73s
[root@master-01 ~]# kubectl get pod -owide
NAME                        READY   STATUS    RESTARTS   AGE   IP             NODE        NOMINATED NODE   READINESS GATES
busybox                     0/1     Error     0          20h   <none>         worker-01   <none>           <none>
nginx-dep-b4bfd684c-chl2j   1/1     Running   0          78s   10.20.171.13   worker-01   <none>           <none>
nginx-dep-b4bfd684c-kpsqs   1/1     Running   0          78s   10.20.171.12   worker-01   <none>           <none>
[root@master-01 ~]# kubectl get ingress
NAME        CLASS   HOSTS               ADDRESS         PORTS   AGE
nginx-ing   nginx   nginx.example.com   10.113.178.57   80      2m10s
[root@master-01 ~]# kubectl describe ingress 
Name:             nginx-ing
Labels:           <none>
Namespace:        default
Address:          10.113.178.57
Default backend:  default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
  Host               Path  Backends
  ----               ----  --------
  nginx.example.com  
                     /test   nginx-svc:80 (10.20.171.12:80,10.20.171.13:80)
Annotations:         <none>
Events:
  Type    Reason  Age                   From                      Message
  ----    ------  ----                  ----                      -------
  Normal  Sync    2m9s (x2 over 2m27s)  nginx-ingress-controller  Scheduled for sync
[root@master-01 ~]# kubectl -n ingress-nginx get svc 
NAME                                 TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.113.178.57    <none>        80:30080/TCP,443:30043/TCP   20h
ingress-nginx-controller-admission   ClusterIP   10.112.105.145   <none>        443/TCP                      20h

3. 验证

在 Ingress 中,我们将服务的域名设置为 nginx.example.com。在生产环境中,通常会使用云厂商提供的 LoadBalancer 来配合 nginx-controller;而本实验则采用了 NodePort。生产环境同样可以使用 NodePort,通过将域名解析到虚拟 IP,再由虚拟 IP 反向代理到 Kubernetes 集群节点的 30080 端口,从而有效解决单点故障问题。

类型IP地址端口
VIP192.168.17.20080
RIP1192.168.17.11031005
RIP1192.168.17.12031005

集群内部访问:

[root@master-01 ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.17.110 master-01
192.168.17.120 worker-01
192.168.17.120  nginx.example.com
[root@master-01 ~]# curl nginx.example.com:30080/test
srv : 10.20.171.13:80
host: nginx-dep-b4bfd684c-chl2j
uri : GET nginx.example.com /test
date: 2024-10-17T13:38:56+00:00
[root@master-01 ~]# curl nginx.example.com:30080/test
srv : 10.20.171.12:80
host: nginx-dep-b4bfd684c-kpsqs
uri : GET nginx.example.com /test
date: 2024-10-17T13:38:58+00:00

集群外部访问:

首先,宿主机hosts文件配置一条记录:C:\Windows\System32\drivers\etc\hosts

……
192.168.17.120  nginx.example.com
……

在浏览器输入nginx.example.com:30080/test

image-1.png

6.3.4 基于域名的虚拟主机

同样支持与 Nginx 类似的多域名配置,即基于域名的虚拟主机,能够将 HTTP 流量路由到同一 IP 地址下的多个主机名。

创建基于虚拟主机的 Ingress 资源需要定义一个 Ingress 对象,该对象会根据请求的主机名将流量路由到不同的服务。下面是一个基本示例,展示如何在 Kubernetes 中设置基于虚拟主机的 Ingress。

示例场景

假设我们有两个服务:

  1. service-a,其主机名为 service-a.example.com
  2. service-b,其主机名为 service-b.example.com
YAML 文件示例

以下是完整的 YAML 示例,包含服务和 Ingress 资源的定义:

# 创建 namespace
apiVersion: v1
kind: Namespace
metadata:
  name: example-namespace

---
# 创建服务 A
apiVersion: v1
kind: Service
metadata:
  name: service-a
  namespace: example-namespace
spec:
  selector:
    app: service-a
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

---
# 创建 Pod A
apiVersion: apps/v1
kind: Deployment
metadata:
  name: service-a
  namespace: example-namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: service-a
  template:
    metadata:
      labels:
        app: service-a
    spec:
      containers:
        - name: service-a
          image: nginx:latest
          ports:
            - containerPort: 8080
          # 假设服务 A 返回 200 OK
          # 可以替换为实际的应用程序镜像

---
# 创建服务 B
apiVersion: v1
kind: Service
metadata:
  name: service-b
  namespace: example-namespace
spec:
  selector:
    app: service-b
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8080

---
# 创建 Pod B
apiVersion: apps/v1
kind: Deployment
metadata:
  name: service-b
  namespace: example-namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: service-b
  template:
    metadata:
      labels:
        app: service-b
    spec:
      containers:
        - name: service-b
          image: nginx:latest
          ports:
            - containerPort: 8080
          # 假设服务 B 返回 200 OK
          # 可以替换为实际的应用程序镜像

---
# 创建 Ingress 资源
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: example-namespace
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: service-a.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: service-a
                port:
                  number: 80
    - host: service-b.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: service-b
                port:
                  number: 80

当然,你也可以为每个服务创建单独的 Ingress 文件。尤其是在处理多个服务时,可以更清晰地组织和管理每个 Ingress 资源。

6.3.5 基于 TLS 的 Ingress

在生产环境中,大多数域名使用 HTTPS,Ingress 也支持 HTTPS 类型的域名。首先,需要创建证书,通常生产环境中的证书由公司购买。

要配置一个支持 HTTPS 的 Ingress,您需要从创建 TLS 证书的 Secret 开始,接着定义 Ingress 资源。以下是一个详细的步骤说明:

1. 创建 TLS 证书的 Secret

首先,您需要将 TLS 证书和私钥创建为 Kubernetes Secret。假设您已经有了证书文件 tls.crt 和私钥文件 tls.key。

使用如下命令创建:

kubectl create secret tls my-tls-secret \
  --cert=path/to/tls.crt \
  --key=path/to/tls.key \
  --namespace example-namespace
2. 定义支持 HTTPS 的 Ingress 资源

接下来,您可以定义一个 Ingress 资源,以便通过 HTTPS 访问您的服务。以下是一个基本示例:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: example-namespace
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"  # 强制重定向 HTTP 到 HTTPS
spec:
  tls:
    - hosts:
        - example.com  # 替换为您的域名
      secretName: my-tls-secret  # 之前创建的 Secret 名称
  rules:
    - host: example.com  # 替换为您的域名
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-service  # 替换为您的服务名称
                port:
                  number: 80  # 替换为服务的端口
3. 应用 Ingress 配置

将上面的 YAML 文件保存为 ingress.yaml,然后使用以下命令应用该配置:

kubectl apply -f ingress.yaml
4. 验证配置

检查 Ingress 状态

kubectl get ingress -n example-namespace

访问服务

使用浏览器或 curl 命令通过 HTTPS 访问 https://example.com(将域名替换为您实际使用的域名)。

6.3.6 Ingrees Ngnix 的一些其他功能

Ingress NGINX 控制器利用 annotations 字段扩展其功能。

有关所有功能的详细信息,请参考以下链接:NGINX Ingress Annotations Documentation

以下是一些常用功能:

6.3. 6 Ingress 域名重定向
nginx.ingress.kubernetes.io/permanent-redirect: "http://newdomain.com$request_uri"

6.3.7 Ingress 实现前后端分离
nginx.ingress.kubernetes.io/rewrite-target: "/"

6.3.8 Ingress 错误代码重定向
nginx.ingress.kubernetes.io/error-pages: "http://example.com/custom-error-page"

6.3.9 Ingrees 匹配请求头
nginx.ingress.kubernetes.io/configuration-snippet: |
  if ($http_x_custom_header = "value") {
      set $service_name "serviceA";
  }

6.3.10 Ingress 网站认证
6.3.11 Ingress 黑白名单
6.3.12 Ingress 速率限制
6.3.12 Ingrees 实现灰度发布
nginx.ingress.kubernetes.io/canary: "true"

6.4 IngressClass

IngressClass 是 Kubernetes 中的一个资源对象,用于定义和管理不同类型的 Ingress Controller,从而使 Kubernetes 能够确定哪个 Controller 处理特定的 Ingress 资源,减少了 Ingress 与 Ingress Controller 之间的强耦合关系。

现在,Kubernetes 用户可以通过管理 Ingress Class 来定义不同的业务逻辑分组,从而简化 Ingress 规则的复杂性。例如,可以使用 Class A 来处理订单流量,Class B 来处理物流流量,以及 Class C 来处理购物流量。

下面是一个关于 IngressClass 的案例,展示如何在 Kubernetes 集群中使用它来管理不同的业务流量。

假设你有一个电子商务平台,包含以下三种主要功能:

  1. 订单处理(Order Processing)
  2. 物流管理(Logistics Management)
  3. 购物浏览(Shopping Browsing)

你希望根据不同的流量需求和业务逻辑,将这三种流量路由到不同的 Ingress Controller,以优化性能和管理。

  • Nginx Ingress Controller:用于处理订单流量。
  • Traefik Ingress Controller:用于处理物流流量。
  • HAProxy Ingress Controller:用于处理购物流量。

首先,创建三个 IngressClass,分别指向不同的 Ingress Controller。

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: order-ingress-class
spec:
  controller: nginx.org/ingress-controller

---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: logistics-ingress-class
spec:
  controller: traefik.io/ingress-controller

---
apiVersion: networking.k8s.io/v1
kind: IngressClass
*metadata*:
  name: shopping-ingress-class
spec:
  controller: haproxy.org/ingress-controller

接下来,定义具体的 Ingress 资源,并指定对应的 IngressClass。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress
  annotations:
    kubernetes.io/ingress.class: order-ingress-class
spec:
  rules:
  - host: orders.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: order-service
            port:
              number: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: logistics-ingress
  annotations:
    kubernetes.io/ingress.class: logistics-ingress-class
spec:
  rules:
  - host: logistics.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: logistics-service
            port:
              number: 80

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shopping-ingress
  annotations:
    kubernetes.io/ingress.class: shopping-ingress-class
spec:
  rules:
  - host: shopping.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: shopping-service
            port:
              number: 80

通过以上配置:

  • 订单流量(通过 orders.example.com)会被路由到 Nginx Ingress Controller,处理与订单相关的请求。
  • 物流流量(通过 logistics.example.com)会被路由到 Traefik Ingress Controller,处理与物流相关的请求。
  • 购物流量(通过 shopping.example.com)会被路由到 HAProxy Ingress Controller,处理与购物浏览相关的请求。
0

评论区