每当我在本地或远程Kubernetes集群上工作时,我往往想连接到我的应用程序,向它发送HTTP请求或类似的东西。
这样做有很多选择,如果(像我一样),你每天都在使用Kubernetes,那么了解最有效的选择和最适合我们自己的需求是很重要的。
比方说,我们想访问一个Grafana仪表盘?我们可以在这里使用任何接收HTTP流量的应用程序。TCP流量也是类似的,但我将向你展示的一个选项(Ingress)对TCP流量不起作用。
我将使用arkade来获取例子中所需要的CLI,但如果你喜欢的话,欢迎你用 "长方法 "来做事情。(搜索README,跟随大量的链接,寻找最新的稳定版本,下载它,解压它等等)
arkade是一个开源的Kubernetes市场,用于制作舵手图、应用程序和DevOps CLI:
arkade get kind
arkade get kubectl@v1.22.1
kind create cluster
# Install Grafana via helm with sane defaults
arkade install grafana
这就创建了一个ClusterIP类型的服务,我们不能从集群外部访问它:
kubectl get svc -n grafana
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
grafana ClusterIP 10.96.19.104 <none> 80/TCP 9d
我们来看看LoadBalancers、NodePorts、port-forwarding和Ingress。作为一个破坏者:端口转发往往是最低限度的共同标准,它可以在任何云或本地分布中无缝工作。对于生产来说,你几乎总是部署一个Ingress控制器,并通过一个管理的LoadBalancer暴露80和443端口。
我不打算介绍本地分布的任何特定功能,如KinD的额外端口映射或Minikube插件。
让我们把重点放在Kubernetes API对象和在任何地方都适用的方法上,而不是暗示一个本地或远程分布比另一个更好。
顺便说一句,你知道minikube现在可以在Docker中运行而不是使用VirtualBox吗?对我来说,这使它与KinD和K3d齐名。minikube和KinD都能在苹果硅上用Docker Desktop运行得非常好。对我来说还有一个好处。
负载平衡器
大多数管理云都提供了一个TCP负载平衡器,你可以分配一个端口,如8080、443等,并创建一个基础设施,以允许访问你的服务。
对于Grafana来说,内部服务的端口是80
,所以我们会通过YAML或者通过kubectl expose
,创建一个LoadBalancer:
kubectl expose -n grafana deploy/grafana \
--target-port=80 \
--port=8080 \
--name grafana-external \
--type LoadBalancer
service/grafana-external exposed
这将为8080端口创建一个LB,我们必须为此向AWS或GKE支付~15-20美元/月。
下面是相等的YAML,通过kubectl get -n grafana service/grafana-external -o yaml
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: grafana
name: grafana-external
namespace: grafana
spec:
ports:
- nodePort: 30180
port: 8080
protocol: TCP
targetPort: 80
selector:
app.kubernetes.io/instance: grafana
app.kubernetes.io/name: grafana
type: LoadBalancer
status:
loadBalancer: {}
当使用管理的K8s引擎时,status
字段将被填入一个公共IP地址。
然而,如果我们在本地运行,我们将永远不会得到一个IP地址的填充,我们的LoadBalancer将是无用的。
这里有两个解决方案:
- 安装inlets-operator,它将在公共云上创建一个虚拟机,使用inlets Pro隧道连接我们。这样做的好处是,LB就像一个云LB一样,可以公开访问。inlets隧道可以在任何类型的网络上工作,因为它使用的是出站的websocket,甚至在WiFi门户、VPN和HTTP代理后面也可以。另见。
arkade install inlets-operator
- 选项二是使用类似MetalLB项目的东西(见
arkade install metallb
)。这个项目可以为LB分配一个本地IP地址,并通过ARP在我们的本地网络范围内公布它。在这一点上,我们可以在家里的局域网上访问它,但不能在其他地方。如果我们在路上,把笔记本电脑插到另一个网络的不同范围,或连接到VPN或去办公室,那么我们可能会遇到问题。
不幸的是,在LoadBalancer上暴露一个HTTP服务是不太理想的,因为它没有增加任何形式的加密,如TLS。我们的流量是明文的。在最后一节,我将把你链接到一个教程,把Ingress和LoadBalancer结合起来,在443端口上实现安全的HTTPS流量。
节点端口(NodePorts
NodePort与LoadBalancer密切相关,事实上,如果你看一下我们之前例子中的YAML,你会注意到LoadBalancer需要分配一个节点端口来运行。
节点端口是一个被分配在高端口范围内的端口,比如30080。你的集群中的任何机器如果收到端口30080
的流量,就会转发到相应的服务。
优点:在大多数公共集群上工作,不需要为LB付费。
缺点:由于网络配置的方式,在Docker Desktop上并不总是工作。与KinD不能很好地工作。端口号看起来很可疑:
kubectl expose -n grafana deploy/grafana \
--target-port=80 \
--port=8080 \
--name grafana-external \
--type NodePort
service/grafana-external exposed
NodePort会被随机分配。我的服务得到了:32181
.
下面是相等的YAML,通过kubectl get -n grafana service/grafana-external -o yaml
如果你写你自己的YAML文件,你可以为NodePort指定一个端口,但要确保它不与你创建的其他端口相冲突:
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: grafana
name: grafana-external
namespace: grafana
spec:
ports:
- nodePort: 30080
port: 8080
protocol: TCP
targetPort: 80
selector:
app.kubernetes.io/instance: grafana
app.kubernetes.io/name: grafana
type: NodePort
status:
loadBalancer: {}
请注意,我们仍在使用纯文本的HTTP连接,没有进行加密。
端口转发
端口转发是我访问本地或远程集群内任何HTTP或TCP流量的首选方案。为什么?
优点:适用于远程和本地集群,默认使用TLS进行加密。
缺点:笨重的用户体验,经常断开连接,当一个pod或服务被重新启动时,总是需要重新启动。不会在部署的副本之间进行负载平衡,只与一个pod绑定。
因此,这种方法有一些明显的缺点,即如果你要重新部署或重启你转发的服务,那么你必须杀死端口转发命令并重新启动它。这是一个非常手动的过程:
kubectl port-forward \
-n grafana \
svc/grafana 8080:80
你还必须注意端口,并避免冲突:
Unable to listen on port 8080: Listeners failed to create with the following errors: [unable to create listener: Error listen tcp4 127.0.0.1:8080: bind: address already in use unable to create listener: Error listen tcp6 [::1]:8080: bind: address already in use]
error: unable to listen on any of the requested ports: [{8080 3000}]
所以让我们把端口改为3001:
kubectl port-forward \
-n grafana \
svc/grafana 3001:80
Forwarding from 127.0.0.1:3001 -> 3000
Forwarding from [::1]:3001 -> 3000
现在通过以下方式访问该服务http://127.0.0.1:3000
但是,如果你想从网络上的另一台机器上访问这个端口转发的服务呢?默认情况下,出于安全考虑,它只绑定到localhost:
curl -i http://192.168.0.33:3001
curl: (7) Failed to connect to 192.168.0.33 port 3001: Connection refused
kubectl port-forward \
-n grafana \
svc/grafana 3001:80 \
--address 0.0.0.0
Forwarding from 0.0.0.0:3001 -> 3000
现在再试一下:
curl -i http://192.168.0.33:3001/login
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: text/html; charset=UTF-8
Expires: -1
Pragma: no-cache
X-Frame-Options: deny
Date: Fri, 04 Feb 2022 11:20:37 GMT
Transfer-Encoding: chunked
<!DOCTYPE html>
<html lang="en">
请谨慎使用--address
,它将允许你网络上的任何机器访问你的端口转发服务。
但这里仍有一个问题。每当我们因为配置改变而重启grafana时,看看会发生什么:
kubectl rollout restart -n grafana deploy/grafana
deployment.apps/grafana restarted
kubectl port-forward -n grafana svc/grafana 3001:80 --address 0.0.0.0
Handling connection for 3001
E0204 11:21:49.883621 2977577 portforward.go:400] an error occurred forwarding 3001 -> 3000: error forwarding port 3000 to pod 5ffadde834d4c96f1d0f634e23b87a6c3816faade8645a5f78715f27390929df, uid : network namespace for sandbox "5ffadde834d4c96f1d0f634e23b87a6c3816faade8645a5f78715f27390929df" is closed
现在唯一的解决办法是找到正在运行的标签kubectl
,用Control + C杀死这个进程,然后再重新开始执行命令。
当你像我经常做的那样一遍又一遍地重复一个服务时,这很快就会变得很乏味。而当你要移植3个不同的服务,比如OpenFaaS、Prometheus和Grafana时,就会有3种痛苦。
如果我们有三个OpenFaaS网关的副本,然后用kubectl进行端口转发呢?
arkade install openfaas
kubectl scale -n openfaas deploy/gateway --replicas=3
kubectl port-forward -n openfaas svc/gateway 8080:8080
不幸的是,如果你有三个Pod或部署的副本,kubectl port-forward
,也不会在它们之间实现负载平衡。那么有没有一个更聪明的选择呢?
更聪明的端口转发
通过inlets,你可以将你的本地机器(笔记本电脑)设置为隧道服务器,然后将一个Pod部署到运行客户端的集群中。这与inlets通常使用的方式相反。
inlets最初创建于2019年,为容器和Kubernetes带来了隧道,所以你可以从任何网络在互联网上暴露本地服务。
K8s集群将运行inlets客户端,这是一个反向代理。我本地机器上的服务器部分将把请求发送到集群中,然后它们将被发送到正确的pod上。
这种用inlets进行的端口转发只占用了你主机上的一个端口,并将允许你同时访问一些服务。如果你重新启动pod,这很好,你没有什么可做的,与kubectl port-forward
。
Inlets也会在集群中的任何pod或服务之间进行负载平衡,而kubectl port-forward
只绑定一个端口,这就是为什么如果该pod被终止,它必须被重新启动。
inlets.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: inlets-client
spec:
replicas: 1
selector:
matchLabels:
app: inlets-client
template:
metadata:
labels:
app: inlets-client
spec:
containers:
- name: inlets-client
image: ghcr.io/inlets/inlets-pro:0.9.3
imagePullPolicy: IfNotPresent
command: ["inlets-pro"]
args:
- "http"
- "client"
- "--url=wss://192.168.0.33:8123"
- "--upstream=prometheus.svc.local=http://prometheus.openfaas:9090"
- "--upstream=gateway.svc.local=http://gateway.openfaas:8080"
- "--upstream=grafana.svc.local=http://grafana.grafana:80"
- "--token=TOKEN_HERE"
- "--license=LICENSE_HERE"
---
设置--token
和--license
,然后运行:kubectl apply -f inlets.yaml
。把IP从192.168.0.33
改为你本地机器的IP。
检查日志,它已经检测到我们的上游URL并准备好了。你可以看到每个主机名在集群中的去向,包括命名空间:
kubectl logs deploy/inlets-client
2022/02/04 10:53:52 Licensed to: alex <alex@openfaas.com>, expires: 37 day(s)
2022/02/04 10:53:52 Upstream: prometheus.svc.local => http://prometheus.openfaas:9090
2022/02/04 10:53:52 Upstream: gateway.svc.local => http://gateway.openfaas:8080
2022/02/04 10:53:52 Upstream: grafana.svc.local => http://grafana.grafana:80
time="2022/02/04 10:53:52" level=info msg="Connecting to proxy" url="wss://192.168.0.33:8123/connect"
time="2022/02/04 10:53:52" level=info msg="Connection established" client_id=65e97620ff9b4d2d8fba29e16ee91468
现在我们在自己的主机上运行服务器。它被设置为使用TLS加密,所以我指定了我的机器的IP192.168.0.33
:
inlets-pro http server \
--auto-tls \
--auto-tls-san 192.168.0.33 \
--token TOKEN_HERE \
--port 8000
--port 8000
的值设置了8000端口,我们将在那里连接访问任何转发的服务。
现在,编辑/etc/hosts
,加入我们在inlets-client YAML文件中指定的三个虚拟主机,并保存它:
127.0.0.1 prometheus.svc.local
127.0.0.1 gateway.svc.local
127.0.0.1 grafana.svc.local
现在我可以从我的机器上访问所有三个服务:
curl http://prometheus.svc.local:8000
curl http://gateway.svc.local:8000
curl http://grafana.svc.local:8000
如果你有更多的服务需要添加,只需编辑上述步骤并重新部署inlets客户端。隧道服务器不需要重新启动。
请注意,这些流量都不在互联网上,对于有WSL、Docker Desktop和虚拟机的本地开发环境来说,这是很理想的。
锭子
Kubernetes Ingress是我们应该介绍的最后一个选项。
Ingress实际上不是一种直接访问服务的方式,它更像是一种复用连接到不同后端服务的方式。
想想Nginx的上游,或者Apache HTTP服务器的VirtualHosts。
为了使用Ingress,你需要安装一个IngressController,其中有几十个。
最流行的(在我看来)是:
还有很多其他的。
一旦部署了Ingress控制器,你就又有了一个鸡和蛋的问题。它有一个TCP 80和443端口,你必须以某种方式将其暴露出来。
你的选择和上面一样。
要公开访问它:管理云LoadBalancer或inlets-operator
本地访问。MetalLB、NodePorts或端口转发。
请记住,NodePorts不提供像80和443这样的好端口,你真的想去https://example.com:30443
吗?我不这么认为。
有了Kubernetes,很多事情都是可能的,但不可取。你实际上可以改变安全设置,以便你可以将NodePorts绑定到80和443端口。请不要这样做。
你可以用arkade安装ingress-nginx。
arkade install ingress-nginx
然后你会在默认命名空间中看到它的服务。
kubectl get svc ingress-nginx-controller
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.96.213.226 <pending> 80:30208/TCP,443:31703/TCP 3d20h
这是LoadBalancer的类型,因为它是预期的。
我准备安装inlet-operator,让它为我在DigitalOcean虚拟机上提供IP。
arkade install inlets-operator \
--provider digitalocean \
--region lon1 \
--token-file ~/do-token.txt
我从DigitalOcean的门户网站上得到了API令牌do-token.txt
,并创建了一个具有读写范围的令牌。
现在,几乎在输入这个命令的同时,我就得到了一个公共IP。
kubectl get svc -n default -w
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller LoadBalancer 10.96.213.226 178.128.36.66 80:30208/TCP,443:31703/TCP 3d20h
curl -s http://178.128.36.66
Not found
让我们创建一个基本的Ingress记录,它将永远引导我们到Grafana。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: grafana
namespace: grafana
spec:
ingressClassName: nginx
rules:
- http:
paths:
- backend:
service:
name: grafana
port:
number: 80
path: /
pathType: Prefix
status: {}
这个Ingress记录匹配任何流量,然后发送到我们集群内的grafana.grafana:80
。
通常情况下,当我们使用Ingress时,是为了获得反向代理的一些好处,比如能够。
- 应用速率限制
- 对请求进行认证和把关
- 在服务之间实现负载平衡
- 在一个IP或LoadBalancer下复用一些域
- 用TLS证书对流量进行加密
我只是给了你一个如何使用Ingress的简单例子,但你可以按照这个教程来看看如何使用多个域以及如何获得TLS记录。
那么 "服务网 "呢?Linkerd主张使用ingress-nginx或类似的方式来获得传入的网络访问。Istio提供了自己的网关,称为IstioGateway,可以取代Kubernetes上的Ingress。关于Istio与OpenFaaS的例子,见。了解Istio如何为你的功能提供一个服务网(根据你的工作负载进行必要的调整)。
总结
我们看了LoadBalancers、NodePorts、kubectl和inlets端口转发和Ingress。这不是一个广泛的指南,但我希望你能更好地掌握你可以利用的东西,并将去学习更多的知识和实验。
如果你像我一样做了很多应用开发,你可能会发现inlets很有用,而且比kubectl port-forward
。我们展示了如何从我们的集群中复用3个不同的HTTP服务,但inlets也支持TCP转发,英国政府的一个开发人员写信给我,解释他如何使用这个来从暂存环境调试NATS。