【性能革命】揭秘:在Kubernetes上部署Varnish反向代理缓存,让你的应用性能狂飙突进!

发布时间:2024年01月08日

?

引言

随着微服务架构的普及,反向代理服务器在提高Web应用程序性能方面发挥着越来越重要的作用。而Varnish作为一款高性能的HTTP反向代理服务器,凭借其强大的缓存功能和灵活的配置,成为了许多企业和开发者的首选。

然而,仅仅部署Varnish还不足以实现最佳的性能效果。为了最大化Varnish的性能潜力,我们需要将其与Kubernetes这样的容器编排平台相结合。在本文中,我们将深入探讨如何在Kubernetes上部署Varnish反向代理缓存,以实现应用性能的狂飙突进。

一、Varnish Cache:性能加速的利器

Varnish Cache是一款开源、高性能的HTTP加速器,适用于大规模Web应用和服务。其基于内存操作,具备极高的读写速度,并能通过灵活且强大的Varnish Configuration Language(VCL)允许开发者细粒度地控制缓存策略,包括缓存哪些内容、何时缓存、如何刷新等。Varnish可以轻松应对高并发访问和大流量场景,减少对后端服务器的压力,尤其是对于大量静态内容和一些可缓存的动态内容,效果尤为显著。

Varnish核心特性

  1. 内存缓存:Varnish采用纯内存存储方式来缓存响应内容,这种设计使其在处理大量并发请求时能够提供极低的延迟响应,尤其适合高负载网站及API服务场景。

  2. VCL配置语言:Varnish Configuration Language (VCL)是一种专门用于定义缓存策略的语言。通过VCL,开发者可以精细控制缓存何种请求、如何缓存以及何时过期等行为,实现高度定制化的缓存逻辑。

  3. 事件驱动架构:Varnish基于事件驱动模型构建,能高效地处理网络I/O,充分利用现代硬件资源,以满足大规模、高并发环境下的性能需求。

  4. 健康检查与故障转移:Varnish能够对后端服务器进行健康检查,并根据结果自动调整流量分配,确保服务的稳定性和可用性。

Varnish缓存机制

  1. 缓存命中与未命中:当请求到达Varnish时,它首先会检查缓存中是否存在匹配的响应。如果存在,则直接从缓存返回响应,称为“缓存命中”;反之则需要向后端服务器发起请求,获取响应后再缓存并返回给客户端,此过程为“缓存未命中”。

  2. 缓存对象管理:Varnish使用LRU(Least Recently Used)算法管理缓存空间,当缓存满载时,最久未被访问的对象会被替换出去。同时,可以通过设置不同的缓存期限(TTL)或依据HTTP头部信息自定义缓存有效期。

  3. EVM(Varnish内核):Varnish使用一种名为EVM(Varnish Executive VM)的虚拟机来执行VCL脚本,允许开发者在运行时动态改变缓存策略,这使得Varnish具备了强大的灵活性和适应能力。

  4. Grace模式与Keep模式:在缓存项即将过期但尚未刷新的情况下,Varnish支持Grace模式(继续提供已过期但仍可用的缓存)和Keep模式(延长缓存项的生存时间),这些策略有助于降低后端服务器压力,提高整体性能。

二、Kubernetes上的Varnish部署实战

1.创建Varnish Dockerfile 文件

部署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"]

注意:

  • 默认用户:用户组 varnish:varnish ,若想在容器内使用安装软件调试,可使用 root:root ,否则提示无权限
  • 默认缓存时间120s,如何修改默认缓存有效时间,使用 "-t", "1d" (默认缓存一天)
  • default.vcl 配置文件可放在 kubernets ConfigMap

2. 创建Varnish Deployment

在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

注意:

  • 根据实际需求调整CPU和内存限制、请求以及其他参数。

3. 创建Varnish VCL?ConfigMap

创建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配置展示了以下内容:

  1. 后端服务器定义:默认后端服务器的IP地址和端口,以及健康检查配置。
  2. 清除操作:通过BAN请求方法执行清除操作,限制执行清除操作的客户端IP地址。
  3. 请求处理:根据请求的URL进行缓存处理,对首页和资讯页面进行缓存,其他请求不进行缓存处理。
  4. 后端响应处理:将请求的URL和主机名复制到响应中,根据URL设置TTL值,并启用grace模式和keep模式。

通过这些配置,可以实现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"

4. 创建Varnish Service

  • 创建Service资源(供外部访问),对外暴露Varnish集群的服务端点,并确保流量能够正确路由至Varnish实例:
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等类型,依据具体应用场景

  • 创建Headless?Service资源(供内部访问),由于Varnish服务中缓存需要手动清除,为了安全只能在集群内部限制访问,达到安全的清除的目的:
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缓存策略,不仅可以大幅度减轻后端系统的负担,还能极大地提升用户的访问体验,助力企业打造快速、稳定、高效的应用服务体系。

文章来源:https://blog.csdn.net/qq_34272964/article/details/135468467
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。