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

使用 Kubernetes Events 从控制平面向应用报告错误

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

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

| | | 图 1:PKI 流程框图 |

如果应用需要新证书,应用所有者会显式地向应用的 Kubernetes 配置中添加一个自定义资源定义 (CRD) [1]。此 CRD 指定了 SSL 证书的参数:名称、通用名等。控制平面中的一个微服务会观察 CRD 并触发 SSL 证书生成的处理 [2]。证书准备好后,同一个控制平面服务会将其作为 Kubernetes Secret 发送给 API 服务器 [3]。之后,应用容器使用 Kubernetes Secret VolumeMounts 访问其证书 [4]。您可以在我们的 GitHub 示例应用中看到该系统的演示。

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

为应用所有者提供正确的诊断信息以便他们能够修复错误,在大规模环境下成为了一个严重的生产力问题。Box 快速迁移到微服务导致每周都有几个新的部署。许多首次用户,他们不了解基础设施的每一个细节,需要能够成功部署他们的服务并轻松排查问题。作为基础设施的所有者,我们不想成为瓶颈,需要从控制平面日志中读取错误并将它们传递给应用所有者。如果所有者配置中的某些内容导致其他地方出错,所有者需要获得充分的诊断信息。这些错误数据必须自动流动,无需人工干预。

经过深思熟虑和反复试验,我们发现 Kubernetes Events 非常适合自动沟通这类错误。如果错误信息放在 Pod 的事件流中,它就会显示在 kubectl describe 的输出中。即使是初级用户也可以执行 kubectl describe pod 并获得错误诊断。

我们尝试将控制平面服务的状态网页作为 Kubernetes Events 的替代方案。我们认为状态页面可以在每次处理 SSL 证书后更新,应用所有者可以探测状态页面并从中获取诊断信息。在最初尝试状态页面后,我们发现它不如 Kubernetes Events 解决方案有效。状态页面对应用所有者来说是一个新的需要学习的界面,一个新的需要记住的网址,以及在排查问题时需要切换到的另一个独立工具。另一方面,Kubernetes Events 清晰地显示在 kubectl describe 的输出中,这很容易被开发者识别。

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

示例应用执行此代码来设置 Kubernetes Event 生成

// 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 Events 的一个实际用例。在配置错误的情况下,向程序员提供自动化反馈显著提高了我们的排查效率。将来,我们计划在其他类似用例的各种应用中使用 Kubernetes Events。最近创建的sample-controller 示例也在类似场景中使用了 Kubernetes Events。很高兴看到有更多的示例应用来指导社区。我们很高兴继续探索 Events 和 Kubernetes API 其他部分的用例,以便让我们的工程师开发更轻松。

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

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