Skip to main content

helm 打包 istio 网关与 k8s 命令行工具

上一章中,我们讲解了如何在 k8s 中部署简单的无状态服务。本章比较琐碎,有三个部分,分别是如何使用 helm 打包 k8s 的配置文件,如何使用网关实现微服务的访问,k8s 的命令行工具的使用。

集群打包

前面的章节中,我们会发现,k8s 会产生大量的配置文件,用户配置起来也很麻烦,要一个个找,而且这些配置文件会有很多重复的地方。为了解决这个问题,我们可以使用 helm 这个工具。

helm 工具是一个 k8s 的打包工具,它可以将 k8s 的配置文件打包成一个 Chart,然后通过 helm 这个工具来进行部署。这样我们就可以将所有的配置文件打包成一个 Chart,然后通过 helm 这个工具来进行部署。helm 的安装方法见官方文档

首先,我们把上一章的配置文件打包成一个 Chart。使用helm create demo来创建一个 Chart,名字叫 demo。

Chart 下有三个文件很重要。

  • template文件夹,其中存放了所有的配置文件模版。这里存放的就是我们之前编写的 k8s 配置文件。不同的是,我们可以在这里面使用 go 语言的模版引擎语法,例如{{ .Values.image }},就会从这个文件获取变量 image 的值。
  • values.yaml文件,其中存放了所有的配置文件的值。这里的值会被用于模版的渲染。
  • Chart.yaml文件,其中存放了 Chart 的元数据,例如 Chart 的名字,版本等。

例如,如果我们用户可以自己配置 RabbitMQ 的用户名和密码,我们可以在values.yaml文件中添加这两个变量。

rabbitmq:
username: guest
password: guest

然后,在template文件夹中的配置文件中,我们可以使用这两个变量。

apiVersion: apps/v1
kind: Deployment
metadata:
name: mq
spec:
selector:
matchLabels:
app: mq
template:
metadata:
labels:
app: mq
spec:
containers:
- name: mq
image: rabbitmq:4.0-rc-management
resources:
limits:
memory: "128Mi"
cpu: "500m"
env:
- name: RABBITMQ_DEFAULT_USER
value: {{ .Values.rabbitmq.username }}
- name: RABBITMQ_DEFAULT_PASS
value: {{ .Values.rabbitmq.password }}
ports:
- containerPort: 5672
- containerPort: 15672

这样,用户就有了配置 RabbitMQ 用户名和密码的能力。

Go 的模版引擎十分强大,可以进行循环,条件判断等操作。更多的语法可以参考官方文档

现在,我们已经写完了 Chart,接下来我们可以使用helm install demo ./demo来安装这个 Chart。install 后,第一个参数是 Chart 的名称,后续对 Chart 操作都要基于这个名称。第二个参数是 Chart 的路径。

如果我们做了更改,之前是使用kubectl apply -f .来更新配置文件,现在我们可以使用helm upgrade demo ./demo来更新 Chart。

Chart 之间可以互相依赖,依赖关系可以在Chart.yaml文件中指定。这样,我们就可以将一些公共的配置文件打包成 Chart,然后在其他 Chart 中引用。例如,

dependencies:
- name: common
version: 0.1.0
repository: file://../common

这样,我们就可以在当前 Chart 中引用 common Chart。

如果要修改依赖的配置,可以在 values.yaml 中,添加,

common:
rabbitmq:
username: admin
password: admin

common 代表了前面 dependencies 中的服务名,在这个 key 下的所有参数都会被传递到 common Chart 中。

要加载依赖,使用helm dependency update命令。之后的安装,更新操作和之前一样。

网关

在学习 Spring Cloud 时,我们讲解了如何使用 Spring Gateway 来实现网关。在 k8s 中,我们也可以使用网关来实现微服务的访问。

在 k8s 中,通常称集群内部的流量为东西流量(East-West Traffic),集群外部的流量为北南流量(North-South Traffic)。东西流量是指集群内部的流量,例如一个服务调用另一个服务。北南流量是指集群外部的流量,例如用户访问服务。网关即是服务南北流量的入口,而东西流量则是由 k8s 的 Service 来处理的。

当然,你也可以建立一个 Service,并使用 LoadBalancer 类型。然后在这个 Service 上暴露一个 Spring Gateway 的端口。

截至目前,k8s 的网关是 Gateway API,尽管已经生产可用,但仍是比较早期的阶段。然而,此前负责网关的 Ingress 组件已经已经停止更新,功能将会被迁移到 Gateway API 中。Ingress 仍然可以使用,但已经被标记为过时。Ingress 只支持 HTTP 和 HTTPS 协议,而 Gateway API 支持更多的协议,例如 TCP,gRPC 等,而且更加灵活强大。

注意,k8s 的 Gateway API 只规定了 API,没有规定实现。目前,有很多实现了 Gateway API 的网关,例如 Istio,Kong,Traefik 等。这里我们以 Istio 为例。

Gateway 目前不是 k8s 的原生资源,而是通过 CRD 来实现的。CRD 是 k8s 的一种扩展机制,可以通过 CRD 来定义新的资源。CRD 全称是 Custom Resource Definition,即自定义资源定义。

根据文档,使用下文命令安装 Istio 的 CRD。

kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
{ kubectl kustomize "github.com/kubernetes-sigs/gateway-api/config/crd?ref=v1.1.0" | kubectl apply -f -; }

在 helm 中,可以将获取到的 CRD 安装文件放在与templates同级的crds文件夹中。CRD 会在安装 Chart 时自动安装。

但是,现在只是安装了 CRD,还没有实际的网关工作的 Pod。要实现网关,还需要安装 Istio 的网关组件。Istio 的网关组件是一个 Envoy 的实例,Envoy 是一个高性能的代理,用于处理所有的流量。Istio 的网关组件会将流量转发到对应的服务上。

工程上,我们首先要安装istioctl,这是 Istio 的命令行工具。安装方法参考文档。然后,我们可以使用istioctl install --set profile=minimal来把 Istio 安装到集群中。这里的minimal是指最小的配置,只包含了 Istio 的核心组件。它会向我们的集群添加一个 Service,一个 Deployment。

简单全转发

首先我们来实现一个全转发网关。全转发网关是指,所有的请求都会被转发到一个服务上。

我们首先先建立好我们之前构建的服务集群,然后添加 Istio 的 CRD。

Gateway API 规定了三类组件,Gateway Class,网关的提供者;Gateway,网关实例;Route 路由。Gateway Class 在安装 Istio 时已经安装好了,我们只需要定义 Gateway 和 Route。

首先定义 Gateway,

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: gateway
spec:
gatewayClassName: istio
listeners:
- name: default
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: All

listeners 是网关要监听的位置,这里是监听 80 端口,协议是 HTTP。allowedRoutes 是允许的路由,这里是所有的路由。

然后定义 Route,

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: producer-router
spec:
parentRefs:
- name: gateway
rules:
- backendRefs:
- name: producer-service
port: 3001

parentRefs 是指定这个 Route 属于哪个 Gateway。rules 是路由规则,这里是所有的请求都转发到 producer-service 的 3001 端口。

经过上面的配置,所有到达网关 80 端口的请求都会被转发到 producer-service 的 3001 端口。现在,我们可以像访问其他服务一样访问网关的服务,而网关会将请求转发到对应的服务上。

路径匹配与重写

全转发网关是将所有的请求都转发到一个服务上,而路径匹配网关是根据请求的路径来转发到不同的服务上。

假设我们希望通过网关路径/producer的请求转发到 producer-service,路径/consumer的请求转发到 consumer-service。只要修改 Route 的规则即可。我们需要首先匹配路径和服务,然后重写请求的路径。

前面我们的 Rules 没有匹配规则,所以转发了所有的请求。现在我们需要添加匹配规则。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: router
spec:
parentRefs:
- name: gateway
rules:
- matches:
- path:
type: PathPrefix
value: /producer
backendRefs:
- name: producer-service
port: 3001
- matches:
- path:
type: PathPrefix
value: /consumer
backendRefs:
- name: consumer-service
port: 3002

这里,我们添加了两个匹配规则,第一个规则是匹配路径前缀为/producer的请求,转发到 producer-service 的 3001 端口。第二个规则是匹配路径前缀为/consumer的请求,转发到 consumer-service 的 3002 端口。matches 里的 path 表示路径匹配,路径匹配有三种类型,ExactPrefixRegex,这里我们使用了Prefix类型。

此外,还可以匹配 Host,Header,Method 等。

注意,现在尽管请求能被转发,但是路径不会发生变化。/producer/apple被转发到 producer-service 的 3001 端口,但是路径仍然是/producer/apple。如果我们希望路径发生变化,我们可以使用rewrite

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: router
spec:
parentRefs:
- name: gateway
rules:
- matches:
- path:
type: PathPrefix
value: /producer
backendRefs:
- name: producer-service
port: 3001
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: ""

- matches:
- path:
type: PathPrefix
value: /consumer
backendRefs:
- name: consumer-service
port: 3002
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: ""

filters 可以用于修改请求的内容,这里我们使用了 URLRewrite,用于重写 URL。path 模式可以使用ReplacePrefixMatch,来替换前缀。这里我们把匹配的 prefix 删除,即删除/producer/consumer,这样,我们的请求就会被转发到对应的服务上,而路径也正常了。

当然,filter 不止有这些功能。如果这些功能不够用,我们还可以自定义 filter。filter 是一个 Envoy 的配置,用户可以通过 filter 来修改请求的内容。filter 有两种类型,一种是HTTPFilter,一种是NetworkFilter。前者是用于 HTTP 请求的,后者是用于 TCP 请求的。不过,这里我们就不详细介绍了。

命令行工具

前面我们只是简单地使用了 k8s 的命令行工具 kubectl 和 minikube。现在我们系统地总结一下 k8s 的命令行工具。

minikube

  • minikube start 启动 minikube 集群。
  • minikube stop 停止 minikube 集群。
  • minikube delete 删除 minikube 集群。
  • minikube dashboard 打开 minikube 的 dashboard。
  • minikube service <service-name> 打开服务的 UI。
  • minikube ip 获取 minikube 的 IP 地址。
  • minikube ssh SSH 到 minikube 集群。
  • minikube tunnel 创建一个隧道,用于访问 minikube 集群中 LoadBalancer 的服务。

kubectl

  • kubectl get <resource> 获得资源列表。resource 我们目前介绍过的有 Pod,Service,ReplicaSet,Deployment,Gateway,HTTPRoute。这里 resource 可以简写,例如 pod 可以简写为 po,service 可以简写为 svc,deployment 可以简写为 deploy。此外,resource 还有 event,node,namespace。
  • kubectl describe <resource> <resource-name> 描述资源。可以获得资源的详细信息。
  • kubectl apply -f <file> 应用配置文件。可以应用配置文件,例如kubectl apply -f pod.yaml
  • kubectl exec -it <pod-name> -- /bin/bash 进入 Pod 的 shell。
  • kubectl logs <pod-name> 查看 Pod 的日志。
  • kubectl port-forward <pod-name> <local-port>:<pod-port> 将 Pod 的端口映射到本地端口。注意,这个命令映射的是 Pod 的端口,而不是 Service 的端口。
  • kubectl delete <resource> <resource-name> 删除资源。
  • kubectl edit <resource> <resource-name> 编辑资源。
  • kubectl config <command> 配置 kubectl。例如kubectl config get-contexts可以查看所有的 context。kubectl config use-context <context>可以切换 context。kubectl config current-context可以查看当前的 context。Context 即 k8s 的运行时。
  • --all-namespaces 选项,可以查看所有的 namespace。
  • --namespace <namespace> 选项,可以指定 namespace。
  • kubectl expose deployment <deployment-name> 命令,可以将 Deployment 暴露为 Service。例如kubectl expose deployment producer --type=LoadBalancer --port=3001 --target-port=3001。这样本质上是创建了一个 Service,将 Service 的 3001 端口映射到 Deployment 的 3001 端口。
  • kubectl scale deployment <deployment-name> --replicas=<replicas> 命令,可以扩展 Deployment 的副本数量。例如kubectl scale deployment producer --replicas=3。其实就是修改了配置文件。
  • kubectl annotate <resource> <resource-name> <key>=<value> 命令,可以给资源添加注解。例如kubectl annotate pod producer app=producer
  • kubectl label <resource> <resource-name> <key>=<value> 命令,可以给资源添加标签。例如kubectl label pod producer app=producer
  • kubectl rollout <command> 命令,可以管理 Deployment 的滚动更新。例如kubectl rollout status deployment producer可以查看 Deployment 的更新状态。kubectl rollout history deployment producer可以查看 Deployment 的更新历史。kubectl rollout undo deployment producer可以回滚 Deployment。kubectl rollout restart deployment producer可以重启 Deployment。
  • kubectl port-forward <pod-name> <local-port>:<pod-port> 命令,可以将 Pod 的端口映射到本地端口。例如kubectl port-forward producer-7d7b7d4b7b-7z7z7 3001:3001