初探访问Kubernetes中的服务

243 阅读13分钟

每当我在本地或远程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将是无用的。

这里有两个解决方案:

  1. 安装inlets-operator,它将在公共云上创建一个虚拟机,使用inlets Pro隧道连接我们。这样做的好处是,LB就像一个云LB一样,可以公开访问。inlets隧道可以在任何类型的网络上工作,因为它使用的是出站的websocket,甚至在WiFi门户、VPN和HTTP代理后面也可以。另见。arkade install inlets-operator
  2. 选项二是使用类似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