本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。

使用 Kubernetes 事件报告从控制平面到应用程序的错误

Box,我们管理着几个大规模的 Kubernetes 集群,这些集群为数百个已部署的微服务提供内部平台即服务 (PaaS)。这些微服务中的大多数是为超过 80,000 名客户提供 box.com 支持的应用程序。PaaS 团队还部署了几个与平台基础设施相关的服务作为控制平面

Box 控制平面的一个用例是公钥基础设施 (PKI) 处理。在我们的基础设施中,需要新 SSL 证书的应用程序还需要在控制平面中触发一些处理。由于安全原因,我们的大多数应用程序不允许生成新的 SSL 证书。控制平面具有不同的安全边界和网络访问权限,因此允许生成证书。

| | | 图 1:PKI 流的框图 |

如果应用程序需要新证书,则应用程序所有者会将一个自定义资源定义 (CRD) 显式添加到应用程序的 Kubernetes 配置 [1] 中。此 CRD 指定 SSL 证书的参数:名称、通用名称和其他。控制平面中的一个微服务监视 CRD,并触发一些 SSL 证书生成的处理 [2]。证书准备好后,同一个控制平面服务会将其发送到 Kubernetes Secret 中的 API 服务器 [3]。之后,应用程序容器可以使用 Kubernetes Secret VolumeMounts 访问其证书 [4]。您可以在 GitHub 上的示例应用程序中查看此系统的运行演示。

本文的其余部分涵盖了控制平面中这种“触发式”处理中的错误场景。特别是,我们特别关注用户输入错误。由于 SSL 证书参数来自 CRD 格式的应用程序配置文件,如果该 CRD 规范中存在错误,会发生什么?即使是拼写错误也会导致 SSL 证书创建失败。错误信息在控制平面中可用,即使根本原因很可能在应用程序的配置文件中。应用程序所有者无法访问控制平面的状态或日志。

向应用程序所有者提供正确的诊断信息,以便她可以修复错误,这在大规模情况下成为一个严重的生产力问题。Box 快速迁移到微服务导致每周有几个新的部署。许多第一次使用的用户不了解基础设施的每个细节,他们需要成功部署他们的服务并轻松排除故障。作为基础设施的所有者,我们不希望成为读取控制平面日志中的错误并将其传递给应用程序所有者的瓶颈。如果所有者的配置中的某些内容导致其他地方发生错误,则所有者需要完全授权的诊断。此错误数据必须自动流动,无需任何人为干预。

经过深思熟虑和实验,我们发现Kubernetes 事件非常适合自动传达此类错误。如果错误信息放置在 Pod 的事件流中,它会显示在 kubectl describe 输出中。即使是初学者用户也可以执行 kubectl describe pod 并获得错误诊断。

我们尝试使用控制平面服务的状态网页作为 Kubernetes 事件的替代方案。我们确定状态页面可以在处理完 SSL 证书后每次更新,并且应用程序所有者可以探测状态页面并从中获取诊断信息。在最初尝试使用状态页面后,我们发现它不如 Kubernetes 事件解决方案有效。状态页面成为应用程序所有者需要学习的新界面,需要记住的新网址,并且在故障排除工作中需要切换到不同的工具。另一方面,Kubernetes 事件会清晰地显示在 kubectl describe 输出中,这很容易被开发人员识别。

这是一个简化的示例,展示了我们如何使用 Kubernetes 事件跨不同的服务进行错误报告。我们已经开源了一个示例 golang 应用程序,它代表了前面提到的控制平面服务。它监视 CRD 的更改并进行输入参数检查。如果发现错误,则会生成 Kubernetes 事件并更新相关 Pod 的事件流。

示例应用程序执行此代码来设置 Kubernetes 事件生成

// eventRecorder returns an EventRecorder type that can be  
// used to post Events to different object's lifecycles.  
func eventRecorder(  
   kubeClient \*kubernetes.Clientset) (record.EventRecorder, error) {  
   eventBroadcaster := record.NewBroadcaster()  
   eventBroadcaster.StartLogging(glog.Infof)  
   eventBroadcaster.StartRecordingToSink(  
      &typedcorev1.EventSinkImpl{  
         Interface: kubeClient.CoreV1().Events("")})  
   recorder := eventBroadcaster.NewRecorder(  
      scheme.Scheme,  
      v1.EventSource{Component: "controlplane"})  
   return recorder, nil  
}

一次性设置后,以下代码生成与 Pod 关联的事件

ref, err := reference.GetReference(scheme.Scheme, &pod)  
if err != nil {  
   glog.Fatalf("Could not get reference for pod %v: %v\n",  
      pod.Name, err)  
}  
recorder.Event(ref, v1.EventTypeWarning, "pki ServiceName error",  
   fmt.Sprintf("ServiceName: %s in pki: %s is not found in"+  
      " allowedNames: %s", pki.Spec.ServiceName, pki.Name,  
      allowedNames))

可以通过运行示例应用程序来了解更多实现细节。

如前所述,以下是应用程序所有者的相关 kubectl describe 输出。

Events:  
  FirstSeen   LastSeen   Count   From         SubObjectPath   Type      Reason         Message  
  ---------   --------   -----   ----         -------------   --------   ------     
  ....  
  1d      1m      24   controlplane            Warning      pki ServiceName error   ServiceName: appp1 in pki: app1-pki is not found in allowedNames: [app1 app2]  
  ....  

我们已经演示了 Kubernetes 事件的一个实际用例。在出现配置错误时向程序员提供自动化反馈,这大大改善了我们的故障排除工作。将来,我们计划在类似用例下的各种其他应用程序中使用 Kubernetes 事件。最近创建的sample-controller示例也在类似场景中使用了 Kubernetes 事件。很高兴看到有更多的示例应用程序来指导社区。我们很高兴继续探索事件和其他 Kubernetes API 的其他用例,以使我们的工程师更容易进行开发。

如果您有想要分享的 Kubernetes 经验,请提交您的故事。如果您在组织中使用 Kubernetes,并且希望更直接地表达您的经验,请考虑加入 CNCF 最终用户社区,Box 和数十家志同道合的公司都是该社区的一部分。

特别感谢 Greg Lyons 和 Mohit Soni 的贡献。