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

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

Box,我们管理着多个大型 Kubernetes 集群,它们作为内部平台即服务(PaaS)为数百个已部署的微服务提供支持。这些微服务中的大多数都是为超过8万客户提供 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 事件跨不同服务进行错误报告。我们已经开源了一个代表上述控制平面服务的示例 Go 语言应用程序。它监视 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 并希望更直接地表达您的经验,请考虑加入 Box 和数十家志同道合的公司所属的 CNCF 最终用户社区

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