首页 >后端开发 >Golang >我们如何使用 Kubernetes 作业来扩展 OpenSSF Scorecard

我们如何使用 Kubernetes 作业来扩展 OpenSSF Scorecard

WBOY
WBOY原创
2024-08-09 10:20:521158浏览

How we use Kubernetes jobs to scale OpenSSF Scorecard

我们最近在 OpenSauced 平台上发布了与 OpenSSF Scorecard 的集成。 OpenSSF Scorecard 是一个功能强大的 Go 命令行界面,任何人都可以使用它来开始了解其项目和依赖项的安全状况。它会对危险的工作流程、CICD 最佳实践、项目是否仍在维护等等进行多项检查。这使得软件开发者和消费者能够了解他们的整体安全状况,推断项目是否可以安全使用,以及需要对安全实践进行改进。

但我们将 OpenSSF 记分卡集成到 OpenSauced 平台的目标之一是使其可供更广泛的开源生态系统使用。如果它是 GitHub 上的存储库,我们希望能够显示它的分数。这意味着将 Scorecard CLI 扩展到 GitHub 上的几乎所有存储库。说起来容易做起来难!

在这篇博文中,让我们深入了解如何使用 Kubernetes 做到这一点,以及我们在实现此集成时做出了哪些技术决策。

我们知道我们需要构建一个 cron 类型的微服务,它可以频繁地更新无数存储库中的分数:真正的问题是我们如何做到这一点。临时运行记分卡 CLI 是没有意义的:该平台很容易被淹没,我们希望能够对整个开源生态系统的分数进行更深入的分析,即使 OpenSauced 存储库页面尚未被访问最近访问过。最初,我们考虑使用 Scorecard Go 库作为直接依赖代码,并在单个整体微服务中运行记分卡检查。我们还考虑使用无服务器作业来运行一次性记分卡容器,该容器将返回各个存储库的结果。

我们最终采用的方法结合了简单性、灵活性和强大功能,是大规模使用 Kubernetes 作业,所有这些都由“调​​度程序”Kubernetes 控制器微服务管理。运行一个 Kubernetes 作业无需与记分卡构建更深层次的代码集成,它为我们提供了与使用无服务器方法相同的好处,但成本更低,因为我们直接在 Kubernetes 集群上管理这一切。作业的运行方式也提供了很大的灵活性:它们可以有很长的超时时间,可以使用磁盘,并且像任何其他 Kubernetes 范例一样,它们可以让多个 pod 执行不同的任务。

让我们分解这个系统的各个组件,看看它们是如何深入工作的:

这个系统的第一个也是最大的部分是“scorecard-k8s-scheduler”;类似 Kubernetes 控制器的微服务,可在集群上启动新作业。虽然此微服务遵循构建传统 Kubernetes 控制器或操作器时使用的许多原则、模式和方法,但它不会监视或改变集群上的自定义资源。它的功能是简单地启动运行 Scorecard CLI 的 Kubernetes 作业并收集完成的作业结果。

我们首先看一下Go代码中的主控制循环。该微服务使用 Kubernetes Client-Go 库直接与运行微服务的集群进行交互:这通常称为集群上配置和客户端。在代码中,引导集群上的客户端后,我们轮询数据库中需要更新的存储库。一旦找到一些存储库,我们就会在各个工作“线程”上启动 Kubernetes 作业,这些作业将等待每个作业完成。

// buffered channel, sort of like semaphores, for threaded working
sem := make(chan bool, numConcurrentJobs)

// continuous control loop
for {
    // blocks on getting semaphore off buffered channel
    sem <- true

    go func() {
        // release the hold on the channel for this Go routine when done
        defer func() {
            <-sem
        }()

        // grab repo needing update, start scorecard Kubernetes Job on-cluster,
        // wait for results, etc. etc.

        // sleep the configured amount of time to relieve backpressure
        time.Sleep(backoff)
    }()
}

这种具有缓冲通道的“无限控制循环”方法是 Go 中连续执行某些操作但仅使用配置数量的线程的常见方法。在任何给定时间运行的并发 Go 函数的数量取决于“numConcurrentJobs”变量的配置值。这将缓冲通道设置为充当工作池或信号量,表示在任何给定时间运行的并发 Go 函数的数量。由于缓冲通道是所有线程都可以使用和检查的共享资源,因此我经常将其视为信号量:一种资源,很像互斥体,多个线程可以尝试锁定和访问。在我们的生产环境中,我们扩展了该调度程序中同时运行的线程数量。由于实际的调度程序的计算量不是很大,只会启动作业并等待结果最终浮出水面,因此我们可以突破该调度程序可以管理的范围。我们还有一个内置的退避系统,可以在需要时尝试缓解压力:如果出现错误或没有找到可以计算分数的存储库,该系统将增加配置的“退避”值。这确保了我们不会不断地用查询猛击我们的数据库,并且记分卡调度程序本身可以保持在“等待”状态,而不占用集群上宝贵的计算资源。

在控制循环中,我们做了一些事情:首先,我们在数据库中查询需要更新记分卡的存储库。这是一个简单的数据库查询,它基于我们监视的一些时间戳元数据并具有索引。自计算存储库的最后一个分数以来,一旦经过配置的时间量,它将冒泡并由运行记分卡 CLI 的 Kubernetes 作业进行处理。

接下来,一旦我们有了可以获取分数的存储库,我们就使用“gcr.io/openssf/scorecard”镜像启动 Kubernetes 作业。使用 Client-Go 在 Go 代码中引导这项工作看起来与使用 yaml 非常相似,只需使用通过“k8s.io”导入提供的各种库和 API 并以编程方式执行:

// defines the Kubernetes Job and its spec
job := &batchv1.Job{
    // structs and details for the actual Job
    // including metav1.ObjectMeta and batchv1.JobSpec
}

// create the actual Job on cluster
// using the in-cluster config and client
return s.clientset.BatchV1().Jobs(ScorecardNamespace).Create(ctx, job, metav1.CreateOptions{})

作业创建后,我们等待它发出已完成或出错的信号。与 kubectl 非常相似,Client-Go 提供了一种有用的方式来“监视”资源并观察它们发生变化时的状态:

// watch selector for the job name on cluster
watch, err := s.clientset.BatchV1().Jobs(ScorecardNamespace).Watch(ctx, metav1.ListOptions{
    FieldSelector: "metadata.name=" + jobName,
})

// continuously pop off the watch results channel for job status
for event := range watch.ResultChan() {
        // wait for job success, error, or other states
}

最后,一旦我们成功完成作业,我们就可以从作业的 pod 日志中获取结果,其中将包含来自记分卡 CLI 的实际 json 结果!获得这些结果后,我们可以将分数更新插入数据库并更改任何必要的元数据,以向我们的其他微服务或 OpenSauced API 发出信号,表明有一个新分数!

如前所述,scorecard-k8s-scheduler 可以同时运行任意数量的并发作业:在我们的生产环境中,我们同时运行大量作业,所有作业均由该微服务管理。目的是能够每两周更新一次 GitHub 上所有存储库的分数。凭借这种规模,我们希望能够为任何开源维护者或消费者提供强大的工具和见解!

“调度程序”微服务最终成为整个系统的一小部分:任何熟悉 Kubernetes 控制器的人都知道,系统运行还需要额外的 Kubernetes 基础设施。在我们的例子中,我们需要一些基于角色的访问控制(RBAC)来使我们的微服务能够在集群上创建作业。

首先,我们需要一个服务帐户:这是调度程序将使用的帐户,并具有与其绑定的访问控制:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: scorecard-sa
  namespace: scorecard-ns

我们将此服务帐户放置在所有这些运行的“scorecard-ns”命名空间中。

接下来,我们需要为服务帐户进行角色和角色绑定。这包括实际的访问控制(包括能够创建作业、查看 Pod 日志等)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: scorecard-scheduler-role
  namespace: scorecard-ns
rules:
- apiGroups: ["batch"]
  resources: ["jobs"]
  verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]

—

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: scorecard-scheduler-role-binding
  namespace: scorecard-ns
subjects:
- kind: ServiceAccount
  name: scorecard-sa
  namespace: scorecard-ns
roleRef:
  kind: Role
  name: scorecard-scheduler-role
  apiGroup: rbac.authorization.k8s.io

您可能会问自己“为什么我需要授予此服务帐户访问权限才能获取 pod 和 pod 日志?这不是访问控制的过度延伸吗?”记住!作业有 pod,为了获取包含记分卡 CLI 实际结果的 pod 日志,我们必须能够列出作业中的 pod,然后读取它们的日志!

第二部分“RoleBinding”,是我们实际将角色附加到服务帐户的地方。然后可以在集群上启动新作业时使用此服务帐户。

向 Alex Ellis 和他出色的运行作业控制器致敬:这是正确使用 Client-Go 与 Jobs 的巨大启发和参考!

大家保持俏皮!

以上是我们如何使用 Kubernetes 作业来扩展 OpenSSF Scorecard的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn