使用Kubernetes Ingress: Contour
@TOC
推荐超级课程:
在处理Kubernetes时会遇到许多挑战,其中一个最常见的情况就是将用户流量引导至您的应用程序。而且,从 外部 Kubernetes 集群获取用户流量可能会更加困难。这时候我们就可以开始规划 Ingress。我最喜欢的之一就是 Contour 。Contour 是基于 Envoy (一7 层 代理)的高性能 Ingress 控制器。Contour 和 Envoy 都是 CNCF 项目。
在我们开始深入探讨 Contour 具体实现之前,让我们先简要讨论一下 Ingress。不过,进入 Ingress 之前,让我们往回退一步,看看没有 Ingress 会是怎样。
没有 Ingress 控制器的情况
实际上,并不一定非要有 Ingress 控制器来将外部流量引导至 Kubernetes 集群内运行的 pod。事实上,一种常见的方法是为您的应用程序 pod(s) 创建一个类型为 LoadBalancer
的服务,并由您喜欢的云提供商提供必要的基础设施来将流量引导至您的应用程序。
但是,这可能会导致一些问题:
- 拥有的应用程序越多,需要的负载均衡器就越多。这直接转化为更多的资金投入。这种方法的成本增长非常迅速。
- 您对云资源的管理和维护也会增加,因为您可能需要处理多个负载均衡器。
- TLS 终止必须在您的应用程序 pod 处理。这不是一项轻松设置,更不用说维护了。
- 这种方法只能提供简单的场景。一旦开始涉及更复杂的需求,您将不得不设计自己的解决方案。
以下是这种方法的外观:
正如您所看到的支持此配置的外部负载均衡器可能很多。这就是 Ingress 控制器可以帮助的地方。
Ingress 控制器的优势
有许多不同的 Ingress 控制器,但它们大多数实现相同的需求:简化将流量引导至您的 Kubernetes 集群并为更复杂的网络场景提供功能和好处。一旦在我们的集群中添加了 Ingress 控制器,流量的流向将改变为以下内容:
现在,您的外部流量将通过单个负载均衡器流向您的 Ingress 控制器,后者将采用 Ingress 配置确定要将流量转发到哪个服务。
部署 Contour
现在我们知道为什么可能需要一个 Ingress 控制器了,接下来一个大问题是:我应该使用哪个 Ingress 控制器? 好问题。市面上有许多选择,但对于我来说 Contour 是我最喜欢的选择,原因如下:
- 作为首选 Kubernetes Ingress 控制器开发
- 支持动态配置(多亏了 Envoy)
- 高性能(同样感谢 Envoy)
- 为未来的 Gateway API 提供开发和支持工作
- 直观而强大的 HTTPProxy API
说了这么多,现在我准备在我的 Kubernetes 集群中安装 Contour。有几种方式可以做到这一点,但最简单的方式就是应用 Contour 的清单:
$ kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
namespace/projectcontour created
serviceaccount/contour created
serviceaccount/envoy created
configmap/contour created
customresourcedefinition.apiextensions.k8s.io/contourconfigurations.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/contourdeployments.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/extensionservices.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/httpproxies.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/tlscertificatedelegations.projectcontour.io created
serviceaccount/contour-certgen created
rolebinding.rbac.authorization.k8s.io/contour created
role.rbac.authorization.k8s.io/contour-certgen created
job.batch/contour-certgen-v1.20.1 created
clusterrolebinding.rbac.authorization.k8s.io/contour created
clusterrole.rbac.authorization.k8s.io/contour created
service/contour created
service/envoy created
deployment.apps/contour created
daemonset.apps/envoy created
如您所见,创建了许多 Kubernetes 对象。让我们进行一些检查以验证安装情况:
$ kubectl get po,svc,deploy,daemonset -n projectcontour
NAME READY STATUS RESTARTS AGE
pod/contour-76cbc54fbf-jl2t2 1/1 Running 0 74s
pod/contour-76cbc54fbf-rwnsk 1/1 Running 0 74s
pod/contour-certgen-v1.20.1-27m46 0/1 Completed 0 74s
pod/envoy-7mrw7 2/2 Running 0 73s
pod/envoy-9vwhp 2/2 Running 0 73s
pod/envoy-zrn2w 2/2 Running 0 73s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/contour ClusterIP 10.0.170.78 <none> 8001/TCP 74s
service/envoy LoadBalancer 10.0.3.217 52.224.190.106 80:31665/TCP,443:32016/TCP 74s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/contour 2/2 2 2 74s
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/envoy 3 3 3 3 3 <none> 74s
一切看起来都很好!这个输出突出了安装 Contour 的推荐方法,即将 Envoy 应用为 daemonset 到集群中。这意味着 Envoy 代理将在集群中的所有节点上运行。这绝对是一个很好的建议,但在某些情况下可能存在一些例外情况,除此之外您可以将 Envoy 安装为 deployment 并在那里控制副本数。
看到 Contour 在运行中
为了查看这个 Ingress 控制器起作用,让我们考虑下面的期望配置:
这里有两个不同的应用程序:App 1 和 App 2。我们希望通过负载均衡器和 Ingress 控制器导向单个端点,但我们希望 /app1
的流量通往 App 1,而 /app2
的流量通往 App 2。
但是首先,我需要创建一个快速简单的网络服务器,可以接受环境变量以自定义其输出的消息(以便我知道自己访问的是哪个应用程序):
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func main() {
fmt.Println("Starting web server...")
message := os.Getenv("MESSAGE")
fmt.Printf("Using message: %s
", message)
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
fmt.Println("Request received")
fmt.Fprintf(res, "Message: %s", message)
})
log.Fatal(http.ListenAndServe(":8000", nil))
}
现在我将这个打包成一个镜像:
FROM golang:1.17 AS builder
COPY . /var/app
WORKDIR /var/app
RUN ["go", "build", "-o", "app", "."]
FROM debian:bullseye
WORKDIR /var/app
COPY --from=builder /var/app/app .
CMD ["./app"]
并将其推送到我的容器注册表(在我的情况下,我使用 Azure 容器注册表):
$ docker build -t trstringeraks1.azurecr.io/basic-web-server:latest .
$ az acr login -n trstringeraks1
$ docker push trstringeraks1.azurecr.io/basic-web-server:latest
现在我将在 Kubernetes 集群中创建这些应用程序。首先是 App 1:
kind: Namespace
apiVersion: v1
metadata:
name: app1 ---
kind: Deployment
apiVersion: apps/v1
metadata:
name: app1
namespace: app1
spec:
replicas: 2
selector:
matchLabels:
app: app1
template:
metadata:
labels:
app: app1
spec:
containers: - name: app1
image: trstringeraks1.azurecr.io/basic-web-server:latest
imagePullPolicy: Always
ports: - containerPort: 8000
env: - name: MESSAGE
value: hello from app1 ---
kind: Service
apiVersion: v1
metadata:
name: app1
namespace: app1
spec:
selector:
app: app1
ports: - port: 80
targetPort: 8000
接下来让我们创建 App 2:
kind: Namespace
apiVersion: v1
metadata:
name: app2 ---
kind: Deployment
apiVersion: apps/v1
metadata:
name: app2
namespace: app2
spec:
replicas: 2
selector:
matchLabels:
app: app2
template:
metadata:
labels:
app: app2
spec:
containers: - name: app2
image: trstringeraks1.azurecr.io/basic-web-server:latest
imagePullPolicy: Always
ports: - containerPort: 8000
env: - name: MESSAGE
value: hello from app2 ---
kind: Service
apiVersion: v1
metadata:
name: app2
namespace: app2
spec:
selector:
app: app2
ports: - port: 80
targetPort: 8000
现在我们设置了应用程序,我们需要设置路由,这样我们的 Contour Ingress 控制器知道将流量发送到哪里。我们通过创建一个单一的根代理并包括另外两个代理来完成。让我们先创建我们的应用程序代理:
kind: HTTPProxy
apiVersion: projectcontour.io/v1
metadata:
name: app1
namespace: app1
spec:
routes: - services: - name: app1
port: 80 ---
kind: HTTPProxy
apiVersion: projectcontour.io/v1
metadata:
name: app2
namespace: app2
spec:
routes: - services: - name: app2
port: 80
这些内容没有太多,它们几乎只是能够被引用并将所有流量路由到其服务和端口的目标代理。真正的路由内容涉及根代理:
kind: HTTPProxy
apiVersion: projectcontour.io/v1
metadata:
name: main
spec:
virtualhost:
fqdn: myapps
includes: - name: app1
namespace: app1
conditions: - prefix: /app1 - name: app2
namespace: app2
conditions: - prefix: /app2
这个根代理定义了一个虚拟主机 myapps
,并包括两个应用程序的代理,条件基于前缀路由。/app1
将路由到 app1
代理,/app2
将路由到 app2
代理。创建这三个代理后,我们应该检查确保 Contour 已接受它们并且它们是有效的:
$ kubectl get proxy -A
NAMESPACE NAME FQDN TLS SECRET STATUS STATUS DESCRIPTION
app1 app1 valid Valid HTTPProxy
app2 app2 valid Valid HTTPProxy
default main myapps valid Valid HTTPProxy
既然我们创建了所有的连接,让我们发出一个请求!首先让我们获取负载均衡器服务入口点到集群的公共 IP 地址:
$ INGRESS_IP=$(kubectl get svc -n projectcontour envoy -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
最后,让我们 curl App 1:
$ curl -H "host:myapps" $INGRESS_IP/app1
Message: hello from app1
成功了!我们的请求被路由到我们的 App 1 服务和 pods。现在来验证 App 2:
$ curl -H "host:myapps" $INGRESS_IP/app2
Message: hello from app2
太棒了!一切都按预期运行。
总结
当您的 Kubernetes 集群需要接受来自外部的流量时,引入一个 Ingress 控制器来处理这个是一种典型的方法。它为您提供许多好处(成本、安全等)。Contour 是一个非常好的现代 Ingress 控制器的很好例子,性能出色且具有直观的 API。我强烈推荐使用它,希望这篇博文能帮助您了解 Contour!