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 表示路径匹配,路径匹配有三种类型,Exact
,Prefix
,Regex
,这里我们使用了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
。