使用 KEDA 扩展 Kubernetes Selenium Grid

在 Kubernetes 集群中借助 KEDA 扩展 Selenium Grid

问题

如果您有使用 Selenium Grid 和 Kubernetes 的经验,您可能会遇到扩展问题。Kubernetes (K8S) 在基于 CPU 和内存使用率扩展和缩小应用程序方面效果显著,但在处理像 Selenium Grid 这样的应用程序时并不那么直接。

这篇博客文章中很好地描述了这个问题。简而言之,Kubernetes 内置的水平 Pod 自动伸缩器 (HPA)(默认情况下)检查资源消耗,以确定是否需要扩展或缩小部署。对于 Selenium Grid 来说,这会成为一个问题,原因有以下几点

  1. 浏览器 pod 使用的资源量会根据当前测试的需求而变化。这意味着您的所有浏览器 pod 可能都在使用中,但没有足够的 CPU 使用率让 HPA 决定需要扩展,从而导致测试不必要地在队列中等待。
  2. 当 Kubernetes 决定缩小部署规模时,它(在很大程度上)会随机进行。您可能在 20 个 pod 上运行 10 个测试,并且需要缩小规模。很可能至少有一个被要求终止的 pod 仍在运行测试,从而导致连接失败。

KEDA 如何帮助

KEDA 是一个免费开源的 Kubernetes 事件驱动的自动缩放解决方案,它扩展了 K8S 的 HPA 的功能集。这是通过社区编写的插件完成的,这些插件将 KEDA 的指标服务器需要的信息馈送给它,以扩展和缩小特定的部署。

特别针对 Selenium Grid,我们有一个插件,它将连接到我们的 grid 以获取所需的信息。使用的触发器示例

triggers:
  - type: selenium-grid
    metadata:
      url: 'http://selenium-grid-url-or-ip:4444/graphql'
      browserName: 'chrome'
      platformName: 'Linux'

所有这些都将保存为像这样的缩放对象

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: selenium-chrome-scaledobject
  namespace: <namespace of your browser pods>
  labels:
    deploymentName: selenium-chrome-node-deployment
spec:
  minReplicaCount: 0
  maxReplicaCount: 80
  scaleTargetRef:
    name: selenium-chrome-node-deployment
  triggers:
    - type: selenium-grid
      metadata:
        url: 'http://selenium-grid-url-or-ip:4444/graphql'
        browserName: 'chrome'
        platformName: 'Linux'

将请求发送到 Grid,例如在 Python 客户端中

options = ChromeOptions()
options.set_capability('platformName', 'Linux')
driver = webdriver.Remote(options=options, command_executor='http://selenium-grid-url-or-ip:4444/wd/hub')

作为额外的好处,KEDA 允许我们在不使用时将部署缩减到 0,这是普通的开箱即用的 HPA 所不允许的。

KEDA 中有关缩放对象的文档中查看更多详细信息。

本文稍后将提供如何实现此功能的完整示例,但 KEDA 解决了我们的两个问题之一。现在,我们可以根据 Selenium Grid 上的实际负载进行适当的扩展和缩减。不幸的是,缩减仍然可能导致 pod 仍在运行测试,并在完成之前被告知终止。

使用 PreStop 和 Drain

为了解决这个问题,我们将结合使用 K8s 的 PreStop 和 Selenium Grid 的 Drain 功能。

  • PreStop 允许我们设置一个命令或命令链,该命令或命令链在容器被告知停止之前运行完成。
  • Drain 告诉 selenium 浏览器 pod 完成其当前测试,然后关闭。

在我们的浏览器 pod yaml 文件中,它们一起看起来像这样:

spec:
  template:
    spec:
      terminationGracePeriodSeconds: 3600
      ...
      ...
      containers:
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "curl --request POST 'localhost:5555/se/grid/node/drain' --header 'X-REGISTRATION-SECRET;'; tail --pid=$(pgrep -f '[n]ode --bind-host false --config /opt/selenium/config.toml') -f /dev/null; sleep 30s"]

分解如下:

  • terminationGracePeriodSeconds 被设置为您希望给 pod 的优雅终止的最大时间,之后会被强制终止。在本例中,我给 pod 60 分钟的时间来完成其测试,当被要求终止时。如果您也将集群节点作为此过程的一部分进行扩展,您可能还需要增加集群节点的终止宽限期。
  • 当 pod 被告知停止时,首先运行 PreStop 命令。
  • 我们 curl 我们 pod 的 localhost 以告知它进行 Drain。该 pod 将不再接受新的会话请求,并且将完成其当前测试。有关此操作的更多信息,可以在 Selenium Grid 文档中找到
  • 然后我们 tail 内部节点进程,该进程将继续运行,直到节点被 Drain。
  • 之后,我们给 pod 30 秒的时间来完成其他任何事情,然后再发出完整的终止命令。

这样,我们的应用程序现在可以安全地缩减我们的 Selenium 浏览器部署了!

从开始到结束

安装 KEDA

  • 您需要使用 2.8.0 或更高版本,您可以在 Selenium Grid Scaler 文档中找到最新版本号。
  • kubectl apply -f https://github.com/kedacore/keda/releases/download/<Version_Number_Here>/keda-<Version_Number_Here>.yaml

创建并应用您的缩放对象

如前所述,您的缩放对象 将如下所示:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: selenium-chrome-scaledobject
  namespace: <namespace of your browser pods>
  labels:
    deploymentName: selenium-chrome-node-deployment
spec:
  minReplicaCount: 0
  maxReplicaCount: 80
  scaleTargetRef:
    name: selenium-chrome-node-deployment
  triggers:
    - type: selenium-grid
      metadata:
        url: 'https://selenium-grid-url-or-ip:4444/graphql'
        browserName: 'chrome'

对于您希望缩放的每个浏览器,您都需要其中一个。

需要编辑的内容:

  1. namespace 应该是您的 selenium 浏览器 pod 所在的命名空间
  2. deploymentName 是您的浏览器部署的名称
  3. name(在 spec 中)也是您的浏览器部署的名称
  4. url 是您的 selenium grid 的 url
  5. browserName 是您正在使用的浏览器的名称
  6. minReplicaCountmaxReplicaCount 是您希望拥有的最小和最大 pod 计数

如果您计划使用 Edge 进行缩放,则至少需要 KEDA 的 2.8.0 版本,并且还需要在触发器元数据中包含 sessionBrowserName

triggers:
    - type: selenium-grid
      metadata:
        url: 'https://selenium-grid-url-or-ip:4444/graphql'
        browserName: 'MicrosoftEdge'
        sessionBrowserName: 'msedge'

这是由于队列中的 Edge 会话与活动会话之间的名称更改导致的,并且 通过此拉取请求解决

准备好后,将其保存为 yaml 文件并使用以下命令应用:

  • kubectl apply -f ./<scaled-object-file-name>.yaml --namespace=<browser_namespace>

将 PreStop 命令添加到您的浏览器 pod

  1. 将您的部署的 terminationGracePeriodSeconds 设置为您希望给 pod 优雅终止的最大时间。同样,您可能还需要增加您的节点池的宽限期,这会因您的 K8s 提供商而异。
  2. PreStop 命令添加到容器生命周期规范中
spec:
  template:
    spec:
      terminationGracePeriodSeconds: 3600
      ...
      ...
      containers:
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "curl --request POST 'localhost:5555/se/grid/node/drain' --header 'X-REGISTRATION-SECRET;'; tail --pid=$(pgrep -f '[n]ode --bind-host false --config /opt/selenium/config.toml') -f /dev/null; sleep 30s"]

就是这样,您的 Selenium Grid pod 现在应该可以正确地向上和向下缩放,而不会丢失任何会话!