k8s可以通过三种方式将集群内服务暴露到外网,分别是NodePort、LoadBalancer、Ingress,其中NodePort作为基础通信形式我们在《k8s网络模型与集群通信》中进行了介绍,这里我们主要关注LoadBalancer和Ingress
loadbalancer是服务暴露到因特网的标准形式,和nodeport一样我们只需在创建service是指定type为loadbalancer即可,接着Service 的通过status.loadBalancer
字段将需要创建的负载均衡器信息发布供负载均衡服务创建。不过loadbalancer是云服务商”专属“,像腾讯云CLB、阿里云SLB,这样在创建service时会自动帮我们创建一个负载均衡器。
大多数云上负载均衡也是基于nodeport,他们的结构如下:
如果要在本地创建一个负载均衡器如何实现呢?
MetalLB[1],一个CNCF沙箱项目,使用标准路由协议(ARP/BGP),实现裸机K8s集群的负载均衡器。
安装方式可参考官方文档:installation[2]
L2(子网)模式的结构,图源[3]
安装后我们获得如下两个组件:
•metallb-system/controller
deployment。用于处理IP分配的控制器。•metallb-system/speaker
daemonset。集群中每个节点启动一个协议服务守护进程。
接着添加一个configmap配置metallb IP池。
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: default
protocol: layer2
addresses:
- 192.168.1.240-192.168.1.250
这样当我们创建一个loadbalancer类型的service时,EXTERNAL-IP将会从地址池中获取一个用于外部访问的IP 192.168.1.243 当外部流量进入时,ARP将我们的请求地址广播获取所属的service,接着k8s内部 通过iptables
规则和 kube-proxy
,将流量从服务端点引导到后端。
#nginx_deployment_service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: metallb-system
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: metallb-system
name: nginx
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
查看service kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.96.243.159 192.168.1.243 80:31052/TCP 40h
测试访问:curl 192.168.1.243
# curl 192.168.1.243
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
负载均衡可以建立在 OSI 网络模型的不同级别上,主要是在 L4(传输层,例如 TCP/UDP)和 L7(应用层,例如 HTTP)上。在 Kubernetes 中,Services
是 L4 的抽象,LoadBalancer类型负载均衡依然有局限性,同时我们看到每创建一个service对应的负载均衡器都会消耗一个静态IP,这并不合理。当然k8s中的另一种资源对象ingress可工作在 L7 层实现应用程序协议(HTTP/HTTPS)的负载均衡。
Ingress[4] 公开了从集群外部到集群内服务[5]的 HTTP 和 HTTPS 路由。流量路由由 Ingress 资源上定义的规则控制。我们可以将 Ingress 配置为服务提供外部可访问的 URL、负载均衡流量、终止 SSL/TLS,以及提供基于名称的虚拟主机等能力。
我们所说的Ingress包含两个部分:
•ingress k8s资源对象:流量路由规则的控制•ingress-controller控制器:控制器的实现有非常多,可参考官方文档中列表Ingress 控制器[6],这里我们使用k8s官方维护的控制器NGINX Ingress Controller[7]
外部流量进入集群时先经过ingress-controller,然后根据ingress配置的路由规则将请求转发到后端service。
ingress-controller其实就是守护进程加一个反向代理的应用,守护进程不断监听集群中资源的变化,将ingress中的配置信息生成反向代理配置。在nginx-ingress controller中即生成nginx.conf
的配置文件。
在本文中因为我们上面已经配置好了loadbalancer的服务,这样我们创建一个type为LoadBalancer的service关联这组pod,再把域名解析指向该地址,就实现了集群服务的对外暴露。当然你也可以使用NodePort
、Hostnetwork
的方式,感兴趣的小伙伴可以进行测试。
ingress-controller不是k8s内部组件,可以通过helm或资源清单方式安装,可查看ingress-nginx deploy[8]
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-
nginx/controller-v1.1.0/deploy/static/provider/cloud/deploy.yaml
然后我们编辑service
kubectl edit service/ingress-nginx-controller -n ingress-nginx
修改spec.type为LoadBalancer即可。
这样我们创建好了nginx-ingress controller,下一步就要配置ingress路由规则。
host:k8s.com
基于url的路由:
•/api/v1•/api/v2
这两个url分别路由到不同的service中
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test
namespace: training
annotations:
ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: k8s.com
http:
paths:
- path: /api/v1
backend:
serviceName: service-apiv1
servicePort: 80
- path: /api/v2
backend:
serviceName: service-apiv2
servicePort: 80
ingress.kubernetes.io/rewrite-target
是nginx-ingress controller的一个注解,当后端服务中暴露的 URL 与 Ingress 规则中指定的路径不同时可以通过此重定向。
查看svc可以看到此时控制器已经获得了一个EXTERNAL-IP
#kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.96.87.23 192.168.1.245 80:32603/TCP,443:31906/TCP 621d
ingress-nginx-controller-admission ClusterIP 10.96.109.70 <none> 443/TCP 621d
现在nginx-ingress controller和ingress路由规则都有了。
我们可以进入到nginx-ingress controller pod中查看nginx.conf可以看到此时我们的ingress配置已经被生成为路由规则。
接下来就是指定我们的backend,即上面的server-apiv1/2
我们添加两个用于暴露的service和deployment,和loadbalancer中测试清单一样,我们稍稍修改一下名称即可。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-apiv1
namespace: training
spec:
selector:
matchLabels:
app: nginx-apiv1
template:
metadata:
labels:
app: nginx-apiv1
spec:
containers:
- name: nginx-apiv1
image: nginx:latest
ports:
- name: http
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: training
name: service-apiv1
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx-apiv1
type: NodePort
将nginx-apiv1换成nginx-apiv2创建出另一个service和deployment。
最后修改hosts解析k8s.com
192.168.1.245 k8s.com
使用curl命令测试url路由(记得在pod中添加测试文件,否则虽然url进行了路由但会出现404)。
# curl k8s.com/api/v1/index.html
api v1
# curl k8s.com/api/v2/index.html
api v2
这样我们对ingress有了初步了解,ingress的路由规则可自定项较多也比较繁杂,可通过官方文档进一步学习。
[1]
MetalLB: https://metallb.universe.tf/
[2]
installation: https://metallb.universe.tf/installation/
[3]
图源: https://banzaicloud.com/tags/metallb/
[4]
Ingress: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#ingress-v1beta1-networking-k8s-io
[5]
服务: https://kubernetes.io/zh/docs/concepts/services-networking/service/
[6]
Ingress 控制器: https://kubernetes.io/zh/docs/concepts/services-networking/ingress-controllers/
[7]
NGINX Ingress Controller: https://kubernetes.github.io/ingress-nginx/
[8]
ingress-nginx deploy: https://kubernetes.github.io/ingress-nginx/deploy/#quick-start