在传统架构中,配置多个相同服务的负载均衡器通常使用 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
② 基于集合
基于集合的标签需求允许你通过一组值来过滤键。 支持三种操作符:in、notin 和 exists(只可以用在键标识符上)。例如:
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地址 | 端口 |
|---|---|---|
| VIP | 192.168.17.200 | 80 |
| RIP1 | 192.168.17.110 | 31005 |
| RIP1 | 192.168.17.120 | 31005 |
集群内部访问:
[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

6.3.4 基于域名的虚拟主机
同样支持与 Nginx 类似的多域名配置,即基于域名的虚拟主机,能够将 HTTP 流量路由到同一 IP 地址下的多个主机名。
创建基于虚拟主机的 Ingress 资源需要定义一个 Ingress 对象,该对象会根据请求的主机名将流量路由到不同的服务。下面是一个基本示例,展示如何在 Kubernetes 中设置基于虚拟主机的 Ingress。
示例场景
假设我们有两个服务:
service-a,其主机名为service-a.example.comservice-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 集群中使用它来管理不同的业务流量。
假设你有一个电子商务平台,包含以下三种主要功能:
- 订单处理(Order Processing)
- 物流管理(Logistics Management)
- 购物浏览(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,处理与购物浏览相关的请求。
评论区