?
随着微服务架构的普及,反向代理服务器在提高Web应用程序性能方面发挥着越来越重要的作用。而Varnish作为一款高性能的HTTP反向代理服务器,凭借其强大的缓存功能和灵活的配置,成为了许多企业和开发者的首选。
然而,仅仅部署Varnish还不足以实现最佳的性能效果。为了最大化Varnish的性能潜力,我们需要将其与Kubernetes这样的容器编排平台相结合。在本文中,我们将深入探讨如何在Kubernetes上部署Varnish反向代理缓存,以实现应用性能的狂飙突进。
Varnish Cache是一款开源、高性能的HTTP加速器,适用于大规模Web应用和服务。其基于内存操作,具备极高的读写速度,并能通过灵活且强大的Varnish Configuration Language(VCL)允许开发者细粒度地控制缓存策略,包括缓存哪些内容、何时缓存、如何刷新等。Varnish可以轻松应对高并发访问和大流量场景,减少对后端服务器的压力,尤其是对于大量静态内容和一些可缓存的动态内容,效果尤为显著。
内存缓存:Varnish采用纯内存存储方式来缓存响应内容,这种设计使其在处理大量并发请求时能够提供极低的延迟响应,尤其适合高负载网站及API服务场景。
VCL配置语言:Varnish Configuration Language (VCL)是一种专门用于定义缓存策略的语言。通过VCL,开发者可以精细控制缓存何种请求、如何缓存以及何时过期等行为,实现高度定制化的缓存逻辑。
事件驱动架构:Varnish基于事件驱动模型构建,能高效地处理网络I/O,充分利用现代硬件资源,以满足大规模、高并发环境下的性能需求。
健康检查与故障转移:Varnish能够对后端服务器进行健康检查,并根据结果自动调整流量分配,确保服务的稳定性和可用性。
缓存命中与未命中:当请求到达Varnish时,它首先会检查缓存中是否存在匹配的响应。如果存在,则直接从缓存返回响应,称为“缓存命中”;反之则需要向后端服务器发起请求,获取响应后再缓存并返回给客户端,此过程为“缓存未命中”。
缓存对象管理:Varnish使用LRU(Least Recently Used)算法管理缓存空间,当缓存满载时,最久未被访问的对象会被替换出去。同时,可以通过设置不同的缓存期限(TTL)或依据HTTP头部信息自定义缓存有效期。
EVM(Varnish内核):Varnish使用一种名为EVM(Varnish Executive VM)的虚拟机来执行VCL脚本,允许开发者在运行时动态改变缓存策略,这使得Varnish具备了强大的灵活性和适应能力。
Grace模式与Keep模式:在缓存项即将过期但尚未刷新的情况下,Varnish支持Grace模式(继续提供已过期但仍可用的缓存)和Keep模式(延长缓存项的生存时间),这些策略有助于降低后端服务器压力,提高整体性能。
部署Kubernetes上,需要生成项目镜像放在仓库中,以下构建一个基于Varnish的Docker镜像的Dockerfile文件示例:
FROM varnish:7.3.0-alpine
# 添加 VCL 文件
# COPY deploy/docker/varnish/etc/default.vcl /etc/varnish/
# 暴露容器内的 Varnish 服务端口和的管理端口
EXPOSE 8080 6000
# 设置用户用户组
#USER root:root
# 安装调试工具
#RUN apk update && apk add vim curl
# 设置容器启动时的默认命令
CMD ["varnishd", "-F", "-a", "0.0.0.0:8080", "-T", "0.0.0.0:6000", "-f", "/etc/varnish/default.vcl", "-p", "thread_pools=2" ,"-s", "malloc,1g"]
注意:
在Kubernetes中,我们可以借助Deployment
资源来实现Varnish实例的自动化部署和管理。以下是一个详尽的YAML配置示例:
apiVersion: apps/v1 # API 版本声明,使用apps/v1版本来定义StatefulSet资源
kind: StatefulSet # 资源类型声明,这里是创建一个StatefulSet资源
metadata: # 定义StatefulSet的元数据信息
labels: # 给StatefulSet打上'app: varnish'标签
app: varnish
name: varnish # StatefulSet的名称为varnish
namespace: demo # 运行在demo这个命名空间中
spec: # 定义StatefulSet的具体规格
replicas: 2 # 指定副本数量,这里设置为2个Pod副本,更具你的业务调整
selector: # 通过标签选择器关联目标Pod
matchLabels:
app: varnish # 必须与spec.template.metadata.labels相同
serviceName: varnish-svc-headless # 关联的Headless Service名称
template: # 定义Pod模板
metadata: # Pod模板的元数据
labels: # 给Pod打上'app: varnish'标签
app: varnish # 必须与spec.selector.matchLabels相同
spec: # Pod模板的具体规格
containers: # 容器列表
image: harbor.xxx.com/demo/varnish:1.0.0 # 使用特定镜像
imagePullPolicy: Always # 总是尝试拉取最新镜像
name: varnish # 容器名称为varnish
resources: # 容器资源限制与请求
limits:
cpu: '1' # 最大CPU为1核
memory: 1Gi # 最大内存为1Gi
requests:
cpu: 500m # 请求最小CPU为500m核
memory: 512Mi # 请求最小内存为512Mi
volumeMounts: # 容器卷挂载
- mountPath: /etc/varnish/default.vcl # Varnish配置文件挂载路径
name: volume-varnish
subPath: default.vcl # 挂载ConfigMap中的default.vcl子路径
- mountPath: /etc/localtime # 主机时间文件挂载路径
name: volume-localtime
dnsPolicy: ClusterFirst # DNS策略,使用集群默认的DNS策略
restartPolicy: Always # Pod重启策略,任何情况下都应重启容器
terminationGracePeriodSeconds: 30 # 容器优雅退出等待时间
volumes: # 定义使用的持久化卷
- configMap: # 使用ConfigMap挂载Varnish配置文件
defaultMode: 420 # 设置文件权限模式
name: varnish-vcl # 引用ConfigMap资源名称
name: volume-varnish
- hostPath: # 使用HostPath方式将主机的/etc/localtime文件挂载到Pod中
path: /etc/localtime # 主机路径
type: '' # 不指定类型,表示任何类型
name: volume-localtime
updateStrategy: # 更新策略
type: RollingUpdate # 使用滚动更新的方式升级StatefulSet中的Pod
注意:
创建ConfigMap用于存储Varnish的VCL配置文件,下面是一个包含后端定义、缓存策略和健康检查的示例:
vcl 4.1;
# 默认后端服务器定义。将其指向您的内容服务器。
backend default {
.host = "XXX.XXX.XXX.XXX"; # 后端服务器IP地址
.port = "80"; # 后端服务器端口
.probe = { # 健康检查配置
.url = "/"; # 健康检查URL
.interval = 5s; # 健康检查间隔
.timeout = 30s; # 健康检查超时时间
.window = 5; # 健康检查窗口大小
.threshold = 3; # 健康检查阈值
}
}
# 定义ACL以限制哪些IP可以执行清除操作。
acl purge {
"localhost"; # 允许本地主机
"XX.XX.XX.0"/24; # 允许指定Ip范围
}
# 当请求到达时调用。
sub vcl_recv {
# 如果是BAN请求方法,执行清除操作。
if (req.method == "BAN") {
# 如果客户端IP不在purge ACL中,返回403。
if (!client.ip ~ purge) {
return(synth(403, "Not allowed."));
}
# 执行清除操作。
ban("req.url ~ " + req.url);
# 返回200状态码表示清除成功。
return(synth(200, "Ban added"));
}
# 可以选择为发往后端的请求添加X-Forwarded-For首部。
# if (req.http.X-Forwarded-For) {
# set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
# } else {
# set req.http.X-Forwarded-For = client.ip;
# }
# 对首页和资讯页面进行缓存。
if (req.url ~ "^/$" || req.url ~ "^/news/(.*)_(.*)_(.*)$") {
# 标记请求进行哈希处理,以便根据URL进行缓存。
return (hash);
}
# 对于其他请求,不进行缓存处理。
return (pass);
}
# 当从后端服务器获取响应时调用。
sub vcl_backend_response {
# 设置grace模式以缓存未过期的页面。
set beresp.grace = 30m; # 在后端服务器不可用时保持缓存30分钟。
# 将请求的URL和主机名复制到响应中。
set beresp.http.url = bereq.url;
set beresp.http.host = bereq.http.host;
# 对首页和资讯页面设置30天的TTL。
if (bereq.url ~ "^/$" || bereq.url ~ "^/news/(.*)_(.*)_(.*)$") {
set beresp.ttl = 30d;
}
}
# 当生成合成响应时调用。
sub vcl_synth {
}
# 当准备向客户端发送响应时调用。
sub vcl_deliver {
# 根据是否命中缓存设置Via-Cache响应头。
if (obj.hits > 0) {
set resp.http.Via-Cache = "hit";
} else {
set resp.http.Via-Cache = "miss";
}
# 移除Age响应头。
unset resp.http.Age;
}
上述VCL配置展示了以下内容:
通过这些配置,可以实现Varnish缓存的基本功能,包括缓存控制、清除操作、URL哈希处理等。同时,还展示了如何根据需求进行定制化配置,例如设置TTL值、启用grace模式和keep模式等。
如何清除缓存呢?下面提供命令在容器内清除缓存:
# 清除首页缓存
curl -X BAN?
"varnish-0.varnish-svc-headless.demo.svc.cluster.local:8080/"
curl -X BAN?"varnish-1.varnish-svc-headless.demo.svc.cluster.local:8080/"
# 清除指定新闻
curl -X BAN?"varnish-0.varnish-svc-headless.demo.svc.cluster.local:8080/news/20230823_2284_2919"
curl -X BAN?"varnish-1.varnish-svc-headless.demo.svc.cluster.local:8080/news/20230823_2284_2919"
apiVersion: v1 # 定义Kubernetes API的版本,这里是版本1。
kind: Service # 定义要创建的Kubernetes资源的类型,这里是Service。
metadata: # 定义元数据,与资源实例关联的信息。
labels: # 定义资源的标签。
app: varnish-svc # 定义标签,这里是app标签,值为varnish-svc。
name: varnish-svc # 定义资源的名称,这里是varnish-svc。
namespace: demo # 定义资源的命名空间,这里是demo。
spec: # 定义资源的规格或配置。
selector: # 定义选择器,用于匹配哪些Pods应该被这个Service暴露。
app: varnish # 定义选择器的条件,这里是app标签值为varnish的Pods。
ports: # 定义Service的端口配置。
- port: 80 # 定义端口号是80。
protocol: TCP # 定义协议是TCP。
targetPort: 8080 # 定义目标端口是8080,这意味着流量将通过80端口进入,然后被路由到端口8080的Pods上。
type: LoadBalancer # 定义Service的类型,这里是LoadBalancer。LoadBalancer类型的Service会在每个节点上创建一个外部负载均衡器,用于暴露服务给外部客户端。或者选择NodePort、ClusterIP等类型,依据具体应用场景
apiVersion: v1 # 定义Kubernetes API的版本,这里是版本1。
kind: Service # 定义要创建的Kubernetes资源的类型,这里是Service。
metadata: # 定义元数据,与资源实例关联的信息。
labels: # 定义资源的标签。
app: varnish-svc-headless # 定义标签,这里是app标签,值为varnish-svc-headless。
name: varnish-svc-headless # 定义资源的名称,这里是varnish-svc-headless。
namespace: demo # 定义资源的命名空间,这里是demo。
spec: # 定义资源的规格或配置。
clusterIP: None # 定义Service的集群IP为None,表示这是一个Headless Service。
clusterIPs: # 定义Service的集群IPs。
- None # 只有一个集群IP,值为None。无头服务,不会进行负载均衡,也不会为该服务分配集群IP,自动配置DNS
internalTrafficPolicy: Cluster # 定义内部流量策略为Cluster。
ports: # 定义Service的端口配置。
- name: varnish-svc-hs # 定义端口的名字为varnish-svc-hs。
port: 80 # 定义端口号是80。
protocol: TCP # 定义协议是TCP。
targetPort: 8080 # 定义目标端口是8080,这意味着流量将通过80端口进入,然后被路由到端口8080的Pods上。
selector: # 定义选择器,用于匹配哪些Pods应该被这个Service暴露。
app: varnish # 定义选择器的条件,这里是app标签值为varnish的Pods。
type: ClusterIP # 定义Service的类型,这里是ClusterIP。ClusterIP类型的Service会为每个选择器在集群中创建一个唯一的IP地址,并路由到后端的Pods。
注意:
在Kubernetes中,当创建一个Headless Service时,系统会为每个Pod生成一个DNS条目,这些DNS条目的格式遵循特定的域名格式:
<service-name>.<namespace>.svc.cluster.local
对于Headless Service而言,它不会被分配Cluster IP,而是为Service中的每一个Pod提供一个独立的DNS A记录。例如,我们有一个名为varnish的Headless Service,并且它位于demo命名空间下,那么对应Pods的DNS条目将会是这样的格式:
varnish-0.varnish-svc-headless.demo.svc.cluster.local:8080
varnish-1.varnish-svc-headless.demo.svc.cluster.local:8080
...
varnish-N.varnish-svc-headless.demo.svc.cluster.local:8080
由于这个老项目是PHP项目,顺便给一个PHP清除缓存的方法:
function curlVarnishPurge(string $path = ''): bool
{
// 本地无 Varnish 服务,直接跳过
if (SGS::$app_config['env'] == 'local') {
return true;
}
// 循环清除指定路由缓存
try {
// 获取环境变量中配置的 VARNISH 服务器地址
$varnishServerIps = explode(' ', getenv("VARNISH_SERVICES"));
foreach ($varnishServerIps as $varnishServerIp) {
$url = $varnishServerIp . '/' . $path;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'BAN');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_HTTPGET, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); # 禁止回显返回数据
$response = curl_exec($ch);
if ($response === false) {
throw new Exception(curl_error($ch));
}
curl_close($ch);
}
} catch (Exception $e) {
throw new Exception($e);
}
return true;
}
自动伸缩与弹性:除了上述的Deployment配置外,还可以结合Horizontal Pod Autoscaler根据CPU或内存使用率自动扩缩Varnish实例数量。
缓存清理与刷新:利用Varnish内置的purge机制或BAN指令,配合后台任务或事件驱动的方式定期清理或实时刷新缓存。
监控与告警:集成Prometheus Exporter收集Varnish Metrics,并利用Grafana展示丰富的可视化监控图表;同时配置Alertmanager进行异常情况的通知。
安全加固:限制Varnish管理接口的访问权限,仅允许授权IP或内部服务访问;同时考虑启用SSL/TLS加密传输,确保数据的安全性。
跨域资源共享(CORS)支持:若应用涉及跨域访问,可在VCL配置中添加CORS相关头部设置。
通过上述架构调整,项目性能得到很大提升,经过压测和实际运行,使用比之前更少的资源,抗压至少提升5倍(压测机器的极限),在压测情况服务访问响应还是非常快,服务器压力显示很小。
在Kubernetes上部署Varnish Cache不仅能实现应用性能的巨大飞跃,还充分体现了云原生架构的优势,即弹性、可扩展性和自动化管理。通过合理配置和不断优化Varnish缓存策略,不仅可以大幅度减轻后端系统的负担,还能极大地提升用户的访问体验,助力企业打造快速、稳定、高效的应用服务体系。