前言
Kubernetes Service
https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/
在平时开发或者本地环境中,我们常用的服务类型可能是:ClusterIP
, NodePort
。
但是,还有两种重要的服务类型:
LoadBalancer
:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的NodePort
服务和ClusterIP
服务上。ExternalName
: 通过返回CNAME
记录和对应值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。 无需创建任何类型代理。
这里先简单说一下 ExternalName
的使用情景,如下面的 yaml 定义:
apiVersion: v1
kind: Service
metadata:
name: backend-service
namespace: default
spec:
type: ExternalName
externalName: backend-service.example.svc.cluster.local
与其他 Service 类型不同,ExternalName
类型不通过 Pod 选择器关联 Pod ,而是将 Service 和另外一个域名关联起来。这种方式可以实现:
- 配置服务管理另一个命名空间中的服务,屏蔽服务在不同命名空间的调用差异,使得两个服务看起来像是在同一个命名空间下;
- 通过 Service 名称的方式请求外部服务时,例如请求在集群外的数据库服务,也可以使用这种方法
- 将微服务依赖的中间件地址配置为
ExternalName
类型,进一步把服务和使用的中间件解耦(将一些中间件服务地址直接用集群访问地址表示),在实际部署的时候通过ExternalName
方式让微服务访问其他中间件 - …
Ingress
“Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。可以提供负载均衡、SSL 终结和基于名称的虚拟托管。”
https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/
在生产中,Ingress 常与 LoadBalancer 配合使用。
Loadbalancer
Loadbalancer 通过负载均衡器来暴露 Service,通过云厂商的负载均衡器提供的外网 IP 来进行访问业务。
而在实际的业务中,通常将 Loadbalancer 与 Ingress 结合使用。原因有以下几点:
- 云厂商的 Loadbalancer 提供的公网 IP 和流量带宽是收费的,通常有多个服务需要暴露,没有必要创建多个 Loadbalancer ,这会增加开销。
- 每一个服务都用一个公网 IP 也不灵活。
- 而 Ingress 通过配置域名和路径规则,在一个入口统一接收外部请求,能够转发到不同的集群内部服务中,可以灵活实现集群服务的暴露。
Loadbalancer 类型的服务定义如下:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
它由 type: LoadBalancer
确定。LoadBalancer 的具体实现依赖云厂商。
那么在本地集群想使用 LoadBalancer 怎么办呢?我们都知道 LoadBalancer 都是作为云厂商 IaaS 平台的一部分实现的,如果我们的集群没有使用这些公有云,LoadBalancers 类型的服务就会无限处于 Pending
状态。
MetalLB 解决的就是这个问题。
MetalLB
目前,对于本地 Kubernetes 集群的一种称呼是 “裸金属集群”,或许 MetalLB 就是这个意思,在裸金属集群上配置本地的 LoadBalancer。它目前是 CNCF 的沙盒项目,使用标准路由协议实现裸金属集群的 load-balancer。
通过 MetalLB,我们在本地集群或者私有云中,也可以使用到 LoadBalancer 的能力。
概念
Address Allocation
公有云会为 LoadBalancer 类型的服务自动分配一个 IP 地址。MetalLB 不能凭空把这些地址创建出来,我们需要为 MetalLB 分配一个 IP 地址池,这通常取决于我们的内网环境,例如在 192.168.0.0/24 中分配 192.168.0.128/26 作为可用 IP 地址池。这些本地地址都是内网地址,因此需要符合 RFC 1918 规范。
这样 MetalLB 就可以自动把地址池中的地址分配给服务。
External Announcement
在 LoadBalancer 类型的服务被分到地址之后,我们必须让内网的其他设备知道,这个 IP 的存在。这就像是内网的两个服务器可以知道彼此的 IP 一样。即:此时,这个内网服务器的,k8s集群中的,Service 对外的网络应该表现为,一个内网服务器在网络中的位置。
MetalLB 使用标准的网络或路由协议来实现这一点,具体取决于使用哪种模式,如:ARP、NDP或BGP协议。
安装 MetalLB
前置条件
注意 Kubernetes 网络插件的兼容性,常用的网络组件 Cilium、Flannel等都是兼容的。
使用 Manifest 安装
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.7/config/manifests/metallb-native.yaml
在对应命名空间下有两种组件:
$ k get po -n metallb-system
NAME READY STATUS RESTARTS AGE
controller-7597dd4f7b-kzhqd 1/1 Running 0 7h54m
speaker-g4rhm 1/1 Running 0 7h54m
speaker-m6xtr 1/1 Running 1 (6h38m ago) 7h54m
speaker-pmt5w 1/1 Running 0 7h54m
speaker-wvvz6 1/1 Running 0 7h54m
metallb-system/controller
:这是处理 IP 地址分配的,集群维度的控制器。metallb-system/speaker
:是 daemonset 类型。这个组件使用我们所选择的协议,使服务可达。
配置二层模式下的 LoadBalancer
这是最简单且常用的模式。我们只需要分配一个 IP 空间作为 IP 池,然后将其告知 L2Advertisement
即可,L2Advertisement
将这个 IP 池发布出去:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
# 我的内网环境是 192.168.0.0/24
# 分配地址范围
- 192.168.0.201-192.168.0.233
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
创建 LoadBalancer 类型的服务
这里使用 Tekton 的 Dashboard 为例:
apiVersion: v1
kind: Service
metadata:
labels:
...
name: tekton-dashboard
namespace: tekton-pipelines
spec:
ports:
- name: http
port: 9097
protocol: TCP
targetPort: 9097
# 配置为 LoadBalancer 类型
type: LoadBalancer
创建后:
$ k get svc -n tekton-pipelines
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tekton-dashboard LoadBalancer 10.100.157.67 192.168.0.201 9097:32176/TCP 18s
Metallb 为其分配了 192.168.0.201
这个地址,有两个端口 9097
和 32176
。当然,9097
是我们指定的,那 32176
是怎么来的呢?
先试着访问一下 192.168.0.201:9097
和192.168.0.201:32176
:
发现,192.168.0.201:32176
这个地址不能访问到我们的服务,那它为什么会存在呢?
LoadBalancer 的端口
为了解决这个疑惑,我重新检查了我的 yaml 文件,发现确实没有 32176
这个端口相关的配置,它像是一个 NodePort
类型服务所有的端口。
在查询文档后,我想应该是这个原因:
“要实现
type: LoadBalancer
的服务,Kubernetes 通常首先进行与请求type: NodePort
服务等效的更改。 cloud-controller-manager 组件然后配置外部负载均衡器以将流量转发到已分配的节点端口。”https://kubernetes.io/zh-cn/docs/concepts/services-networking/service/#type-nodeport
我们的负载均衡器先是使用一个 NodePort 端口,然后将 LB 服务从 EXTERNAL IP 来的外部流量转发到这个 NodePort ,也就是 32176
中。
为什么需要一个 NodePort 作为 LB 的中间端口呢?如果不用这个端口,那么 LB 会直接将流量转发到 Pod 中。我觉得应该是需要节点的 NodePort 做一层负载均衡吧。
于是,我尝试访问集群中工作节点的 32176
,端口,果然,所有节点的该端口都可以访问到服务:
它就是 NodePort。
LB 类型服务将来自 EXTERNAL-IP:Port 的流量,通过 NodePort 负载均衡到 Pod 中。这可能也是 LoadBalancer 名字的意思?
spec: ports: - name: http port: 9097 # EXTERNAL-IP 的端口是这个 protocol: TCP targetPort: 9097
我进一步尝试 禁用负载均衡器节点端口分配,首先把之前的 Service 删除,修改 yaml,再 apply:
apiVersion: v1
kind: Service
metadata:
,,,
name: tekton-dashboard
namespace: tekton-pipelines
spec:
# 在 spec 中加上这个字段 不再为其分配 NodePort
allocateLoadBalancerNodePorts: false
ports:
- name: http
port: 9097
protocol: TCP
targetPort: 9097
type: LoadBalancer
...
果然,NodePort 没有了,也进一步验证了之前猜想:
$ k get svc -n tekton-pipelines
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tekton-dashboard LoadBalancer 10.111.86.40 192.168.0.201 9097/TCP 3s
小结
我们稍微讨论了关于 Loadbalancer 这一类型的服务,包括:
- 本地集群/裸金属/私有云通过 MetaLB 实现 Loadbalancer;
- MetaLB 的部署、配置、使用;
- Loadbalancer 它的两个端口,以及通过 NodePort 进行负载均衡;
- 还有不常用的
ExternalName
类型服务。
Ingress
从官网这张图来看,Ingress 的作用就比较清晰了,通过一组路由规则,将集群外的请求负载均衡到内部服务,实现集群流量统一接入。
我们将上文中 tekton-dashboard 服务修改为 ClusterIP,然后通过 Ingress 暴露服务,完成我们这部分的实践。
$ k get svc -n tekton-pipelines
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tekton-dashboard ClusterIP 10.101.20.90 <none> 9097/TCP 5s
这里使用 Ingress-Nginx 作为我们集群的 ingress。
Ingress-Nginx
安装 Ingress-Nginx
Nginx 实现的 Ingress Controller 提供了 Cloud 和 Metal 版本的 manifest。主要的区别是 Cloud 版本使用 LB 暴露服务,而 Metal 使用 NodePort 暴露服务。如果你跟随上文,部署了 MetaLB 在本地集群,就推荐使用 Cloud 版本的 manifest。
注意,原始 manifest 中包含 registry.k8s.io 的镜像,可能不方便拉取,我更换了 docker hub 中对应的镜像,主要有三处地方,共两个镜像:
- dyrnq/ingress-nginx-controller:v1.4.0
- liangjw/kube-webhook-certgen:v1.1.1
$ kubectl apply -f https://ghproxy.com/https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.4.0/deploy/static/provider/cloud/deploy.yam
如果镜像无法拉取,注意修改其中的镜像仓库。
$ k get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-ptg6f 0/1 Completed 0 19m
ingress-nginx-admission-patch-lndpj 0/1 Completed 0 19m
ingress-nginx-controller-8445cd49cf-l4q4r 1/1 Running 0 19m
$ k get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.106.83.156 192.168.0.201 80:31527/TCP,443:32376/TCP 20m
ingress-nginx-controller-admission ClusterIP 10.97.1.125 <none> 443/TCP 20m
部署完成后,看到 LB 分配的 IP 是 192.168.0.201
。接下来我们配置 ingress,从 192.168.0.201:80
端口访问我们想要的服务。
配置 Ingress 暴露 Tekton Dashboard 服务
首先确保我们的 Tekton Dashboard 服务以及创建。
$ k get svc -n tekton-pipelines
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
tekton-dashboard ClusterIP 10.101.20.90 <none> 9097/TCP 5s
一个 Ingress 的配置文件如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-resource
namespace: tekton-pipelines
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
# 我们为其设置了域名 但是公网 DNS 可不会解析它
# 需要在我们本地配置 host 来正确解析
- host: tekton.k8s.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tekton-dashboard
port:
number: 9097
部署:
$ k apply -f dashboard-ingress.yaml
部署完成之后,我们需要在本地配置域名解析,让 tekton.k8s.local
能够解析到 192.168.0.201
。
然后通过浏览器访问 http://tekton.k8s.local/
。在浏览器中默认解析到 192.168.0.201:80
的根 /
路由路径下。
这样就实现了通过 ingress 暴露集群内部服务。
关于配置 host
我们在 ingress 的配置中指定了 host 字段,并配置了本地域名解析去访问这个域名:
spec:
rules:
- host: tekton.k8s.local
...
虽然我们知道 ingress 暴露的 IP 是 192.168.0.201:80
,但是直接访问这个地址并不能找到服务:
如果我们先通过 IP:Port 的形式,而不通过域名,就不能在 spec.rules
中为其配置 host,也就是使用这种配置:
https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/#ingress-rules
“未指定
host
的规则适用于通过指定 IP 地址的所有入站 HTTP 通信”。
apiVersion: networking.k8s.io/v1
kind: Ingress
...
spec:
rules:
# 不指定 host
#- host: tekton.k8s.local
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: tekton-dashboard
port:
number: 9097
这样进行部署,就可以直接通过 ingress 控制器的 LB 地址和端口 (默认 http 为 80),从其根路径下访问到我们的服务了。
小结
本部分我们简单介绍和实践了 Ingress,主要有以下内容:
-
简要阐述 cloud 和 metal 环境下,ingress-nginx 控制器 service 类型的不同;
-
安装 LoadBalancer 类型的 ingress-nginx 控制器;
-
为 ClusterIP 类型的 Tekton Dashboard 服务配置 ingress 规则;
-
关于配置 ingress 中 host,使用 域名/IP 访问的小坑。