通过我们SRE从一次事件中学到的经验来理解ArgoCD的ApplicationSet(应用集)配置的安全设置。
在上一集中,我分享了我们遇到的一系列关于证书的问题以及我们如何通过改进数据来提升系统的经验。
在这期节目中,我将讲述2月24日晚上发生的故事,当时一个紧急呼叫在我值班时把我叫醒。我发现,我置身于我们团队可能遇到过的最严重的事件中。
扩展性要理解这是怎么回事,我需要提供一些背景信息。这里最关键的词是 “扩展性”。我们平台的SCHIP,这是一个多租户的Kubernetes平台,拥有超过30个活跃集群,托管了具有不同应用需求的客户。
https://medium.com/adevinta-tech-blog/achieve-a-smaller-blast-radius-with-highly-available-kubernetes-clusters-96f9a475544b — 这篇文章介绍了如何通过高可用的Kubernetes集群减少影响范围。
作为平台团队来说,我们需要确保这些集群之间保持一定的同质性。简单来说,客户与集群交互的接口在命名空间层面受到了限制。如果无法保持这种同质性,我们的维护工作将呈指数级增长,这对我们团队来说是不可持续的。
SCHIP最初只服务于一个用例,即“无状态微服务”。然而,随着我们引入了更多客户,应用场景不再局限于简单的无状态应用。
有一些我们称为平台小组的团队,在基于我们的集群上为其他客户提供服务。他们通常需要在命名空间中运行的不只是pod。
我们正在寻找一种可持续的方式来让这些平台团队能够做诸如“我需要这个CRD来做X”这样的操作,不干扰其他客户,也不增加我们团队的维护工作。于是,我们想到了实现一个称为扩展性的机制。
它采用了GitOps方法论,并结合了ArgoCD的“ApplicationSet”。
这是怎么回事:
- 可扩展仓库
为每个平台团队创建一个 Git 仓库,以定义权限、应用和其他相关对象。
这使我们的团队可以验证通过平台团队发起的拉取请求(pull requests)中请求的额外权限要求,并将其定义为代码。
2. 带有集群映射的应用集
ArgoCD 的 ApplicationSet 用于定义“集群/仓库(Cluster/Repository)”的映射关系。我们利用矩阵生成器,结合集群标签和 GitHub 仓库标签来指定哪些仓库会被安装到哪些集群或集群组中。
关于集群生成器以及更多关于我们如何实现这些细节的信息,但我更倾向于在以后的博客文章里讨论这些细节。
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: {platform-team}-extensibility
规范:
generators:
- matrix:
generators:
- clusters:
selector:
matchExpressions:
- key: 账号
操作符: 包含
values:
- a
- b
- scmProvider:
cloneProtocol: https
github:
组织: <redacted>
API: <redacted>
allBranches: true
tokenRef:
secretName: <redacted>
key: <redacted>
filters:
- 标签匹配: ^extensions$
分支匹配: "(master|main)"
路径存在: [deploy]
template:
metadata:
name: "{name}-{repository}"
规范:
项目: "extensibility"
源:
repoURL: "{url}"
targetRevision: "{branch}"
path: "deploy"
目标:
server: '{server}'
同步策略:
自动化:
自我修复: true
删除: true
3. ArgoCD 以实现每个集群的扩展性为目的
因为我们在运行 ApplicationSet 控制器的单个集群中定义了 ApplicationSet,我们将此集群称为工具集群。这个集群中,我们还在每个负责管理这些对象的集群中安装 ArgoCD 应用程序控制器。这使我们能够限定 ArgoCD 可管理的对象范围,并为平台团队提供一个单独的 RBAC 权限,以便他们能查看。
这个图解释了可扩展性是怎么运作的。
事件既然我们已经了解了什么是可扩展性(extensibility),我可以告诉你这次事件的情况。
2月24日深夜,我收到了我们警报系统的一条消息,内容是关于“ArgoCD 同步异常”的警报。起初,我并不认为这是一个紧急警报,因为可能存在诸如 ArgoCD 性能问题,这些问题可能会导致同步时间超过我们的警报阈值。
仔细一看,我发现这来自我们工具集群的警告,而且有一些应用程序正处于正在删除的状态。
行了,有些应用被删了,没什么大不了,但看到应用的名字,我心里一紧。
正在被删除的应用是最重要的应用
正在被删除的应用程序是所有平台团队使用的ApplicationSets(应用集)的核心管理者。
意识到可能发生的事后,我当时的模样是这样的。
这表示什么意思?最糟的情况是,这可能会清除平台团队使用的每一个权限和资源。只是想想就觉得,我的手就开始抖了起来!
现在完全清醒了。我宣布开始评估以评估影响,我的团队来帮忙。
影响是什么ApplicationSet 在其运行的集群内生成 Applications 来管理目标集群内的应用。例如,在工具集群里,会生成名为 {cluster-a}-extensibility 的一个 Application,以管理 {cluster-a} 集群中的应用。
当这个问题发生时,我们检查了工具集群环境,发现为平台团队生成的应用程序正在被删除,但并未完全删除。
我们在清理过程中发现这个问题,决定尽快灭火。由于ArgoCD Application Controller是负责传播删除操作到目标集群的关键组件,我们把它降级了。
如果 ArgoCD 没在运行,删除操作就会停了。
kubectl get applications.argoproj.io -o json | jq -r '.items[] | select(.metadata.deletionTimestamp != null) | .metadata.name'
# 获取正在删除的应用程序名称
使用此查询,我们找出了所有正待删除的应用程序,然而更好的问题是,那些已经删掉的呢?
负责创建资源的可扩展性应用在目标集群中已被删除,该应用包括权限设置和CRDs。这让我感到担忧。然而,这些由可扩展性应用管理的对象仍然存在,这让我感到困惑。
可扩展性功能被删除了,但生成的应用程序仍然保留。
它为啥没删掉所有东西?我们原本可能面临更严重的情况,还好幸好有这种情况帮我们脱离困境。
扩展应用创建的对象没有设置删除最终器。
根据 ArgoCD 的文档:
为了使 ArgoCD 执行级联删除操作以删除所有资源,应将 finalizers 设置到所有资源上(包括 ArgoCD 的所有子应用)。
没有最终化器时,这些受管理的对象只是原始的Kubernetes资源清单,包括NetworkPolicies、ClusterRoles等,以及ArgoCD的Applications。它们没有被移除,而是处于缺少父级ArgoCD应用的状态。
真正的原因在查看ApplicationSets控制器的日志中,期间发现了些挺有意思的信息。
2月24日星期日早上,Github Enterprise维护导致ApplicationSet控制器出现了多个错误。
在维护期间出现了各种错误,包括代码 500、502 和 503 状态,但我们发现最有趣的一个错误是:
"列出仓库时出错:为schip列出仓库时错误,查找值时遇到无效字符 '\u003c'"
这之后,出现了一行日志 “生成了 0 个应用程序”. ,表明错误让 ApplicationSet(ApplicationSet)认为没有应用程序仓库。
这是一个问题,因为我们正在使用一个矩阵生成器 矩阵生成器
- 矩阵:
生成器:
- 集群:
选择器:
匹配表达式:
- key: account
operator: In
values:
- a
- b
- scmProvider:
克隆协议: https
github:
组织: <redacted>
api: <redacted>
所有分支: true
tokenRef:
secretName: <redacted>
key: <redacted>
过滤器:
- 标签匹配: ^extensions$
分支匹配: "(master|main)"
路径存在: [deploy]
如果 SCM提供程序 发现没有符合条件的仓库,它将不会生成任何应用程序。
我们也发现了一个类似的问题,在https://github.com/argoproj/argo-cd/issues/14318可以查看这个问题。
这还不完全清楚,但我们认为问题可能出现在使用 go-github 列出仓库的那个 ApplicationSet 控制器上。如果在进行维护时,GitHub Enterprise 不返回错误,而是返回带有类似 HTML 内容的 200 状态码,那么 ApplicationSet 控制器可能就会误解这种响应,并生成零个应用程序。
我们是怎么恢复的?- 我们裁减了ApplicationSet和Application Controller的功能,确保不会触发任何删除事件。
- 由于已有标记为删除的应用程序,我们在计费集群中通过删除它们的最终状态标记来消除这些应用程序和ApplicationSets。当恢复Application Controller时,它停止了对这些对象的删除。
- 由于在此次清理期间Application Controller被裁减规模,因此没有产生任何影响。
- 我们扩大了ApplicationSet和Application Controller的规模,并触发所有ApplicationSets和应用程序的生成。
- 它们同步后,我们就恢复正常了。
因为这个问题还没有被修复,我们无法控制 GitHub Enterprise 的维护情况,所以我们不能保证这种情况不会再发生。
我们在这里采取的安全措施是确保所有的ApplicationSets都设置了这个参数。
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet # 应用集 (yīng yòng jí)
metadata:
name: {platform-team}-extensibility
spec:
syncPolicy: # 同步策略 (tóng bù cè lüè)
preserveResourcesOnDeletion: true # 删除时保留资源 (shān chú shí bǎo liú zī yuán)
根据官方文档:
在这种配置下,即使 ApplicationSet 的情况再次被移除,所有由它生成的 Applications 都不会被删除。因此, 即使没有 finalizer, 再次出现这种情况时也不会有任何删除。
此外,我们计划为 argoproj 做贡献,以实现一个检查,以避免在仓库列表响应中出现这个问题。
最后一点我希望这篇博客文章读起来很有趣,也希望它能提醒那些有类似设置的人,在像我这样半夜被惊醒之前,提前设置一些防护措施。