如何用GitOps和vCluster构建一个多租户的内部开发平台
注意: 在这篇博客中,我们将探讨如何通过自助服务的方式建立一个多租户平台。目标是理解自助服务方式背后的逻辑,并强调内部开发者平台(IDP)不仅仅是UI和API——更重要的是构建支持它们的核心功能。观看网络研讨会录音和实时演示!
图0:如图所示,内部开发者平台的三层结构
关于内部开发者平台(IDP)的话题一直很火热,但总感觉只有少数人真正明白IDP的重要性。同样的,平台工程也常被提及,但许多人对其核心原则仍然是一知半解。我个人并不喜欢“Developer”这个词用在“内部开发者平台”中的用法,我坚持认为应该称之为“内部平台”。稍后我将解释原因。
这个博客里你都能找到这些内容:
- Kubernetes 及内部开发者平台(IDP)介绍
- 平台工程在构建和管理 IDP 中的角色
- 使用 Argo CD 实施 GitOps 以轻松管理您的 IDP
- 多租户 IDP 的节省成本策略
- 实战指南和 GitHub 资源
- 网络研讨会推广:2025 年 2 月 6 日参加我们的网络研讨会,有机会赢取 10 本《用 Kubernetes 实施 GitOps》书籍中的一本(详情请见文末)。
咱们直接切入正题,在进入实际操作前先理清一些关键术语。
平台工程:它是什么,又不是什么这里有我喜欢的两个定义:
这是关于抽象和组合单个服务,构建一个叠加层,并使其成为自助使用的,供普通用户使用。
这并不是关于不去抽象或组合单个服务,而是构建一个叠加层服务,并内部使用这些服务来提供基础设施。如果你采取这种方法,你不是一个平台团队——你是一个基础设施管理团队。
什么是管理基础设施团队?图1:团队管理的基础设施.
一个管理基础设施团队的工作方式是这样的:开发人员提出请求——通常是通过电子邮件或工单——来申请基础设施服务,如 PostgreSQL 数据库、Vault 实例或托管的 Kubernetes。团队随后部署这些服务并提供支持,负责处理这些服务的升级、扩展和维护工作。例如,当开发人员需要一个新的 Vault 实例或数据库升级时,这些任务由管理基础设施团队处理,而不是由开发人员自己来处理。
几年前,这种全托管服务模式当时被认为是一项重大成就。公司因此感到自豪,这是合情合理的。然而,虽然时代变了,但很多公司却没有跟上步伐。他们仍然在提供托管基础设施服务,但为了迎合客户对平台工程的需求,他们给它重新命名了。
事实: 一个负责管理基础设施的团队并不是一个平台小组。许多组织错误地称它为平台小组,但实际上并非如此。
了解更多关于这里的区别的信息,请参阅这篇优秀的文章:关于基础设施团队和平台团队的区别。
平台工程学到底对我来说意味着什么?图2:平台工程技术
一个平台工程团队采用的是不同的思维方式。它构建和管理可重复使用的模板或应用,将服务抽象化或组合化,并以产品形式交付。这些产品遵循预定的发布周期,拥有适当文档,并处理功能需求。平台通常集成到一个门户中或通过API暴露出来,让开发团队能够使用和管理服务,并通过自助服务的方式独立地进行。
平台工程团队专注于构建和维护这些模板,确保它们始终是最新的并且集成良好。开发人员使用这些模板,并在新版本发布时使用新版本。然而,一旦通过这些模板提供基础设施,开发人员将全面负责管理它,包括基于提供的模板进行更新、扩展和维护。
让我们用一个现实世界的类比来让这更容易理解:
拿Google Cloud Platform (GCP)作为例子。GCP 提供了诸如 Google Kubernetes Engine (GKE) 这样的托管服务。当用户通过控制台或API创建GKE时,他们需要自己负责更新服务以保持支持。没有开发者会想到要让Google的内部平台团队为他们的特定GKE实例管理升级。同样的逻辑也适用于现代内部开发平台。
要点:
平台工程团队提供产品,而不是服务。开发人员可以通过自助服务平台来获取和管理他们所需的资源,并完全负责他们所使用的资源。
还是不同意吗? 你有什么想法,可以在评论区留言哦!
内部开发者平台:为什么IDP中的“Developer”误解了重点如我之前所说,我对IDP中的“开发者”这个词不太感冒。为什么这么说呢?因为开发者并非唯一使用平台服务的群体。所有使用GCP的人都算是开发者吗?当然不是。不论是基础设施团队、安全团队,还是运营团队,都依赖于GCP。在此语境下,提到“开发者”,我指的其实是指任何可能成为平台使用者的人。
按定义来说,IDP通常这样描述:
一个内部开发者平台(IDP)是一个集中的系统,提供开发人员自助工具,让他们可以自助部署、管理和监控应用,简化了基础设施的复杂性并简化了开发流程。
虽然这听起来很明白,实际情况往往恰恰相反。这里有几个为什么。
我经常看到这样的话。
- “是的,我们有一个身份提供平台(IDP)——我们使用Backstage。”
- “是的,我们有一个身份提供平台(IDP)——我们使用Port作为SaaS解决方案。”
- “是的,我们有一个身份提供平台(IDP)——我们建立了一个Helm图表库,每个人都可以自取所需。”
- “是的,我们有一个身份提供平台(IDP)——有一个蓝图供用户克隆并根据自己的需求定制参数以实现自助服务。”
- “是的,我们有一个身份提供平台(IDP)——我们使用GitOps,利用Argo CD作为编排工具。”
等等…
但这里有一点要说:IDP远不止上述提到的那些。
内部开发者平台(IDP,Internal Developer Platform的简称)的层面:不仅仅是一些工具这是许多团队常常卡住的地方。感到困惑吗?不用担心——我会帮你的。我们来看一下下面的图,来澄清这些事情。
图3:内部开发者平台:三层结构
说实话——如果你使用的是像Backstage这样的工具,它作为一个开源框架来构建开发者门户,但不提供现成的内部开发者平台,那么你基本上就拿到了汉堡的上面的面包片。而且嘿,如果你觉得这样也行,那就享受你的免费小食吧!
图3:内部开发平台:上层架构
现在,让我们来谈谈来自各种超大规模和公共云提供商的API,它们为我们提供了许多便捷的管理服务,让我们的生活更简单。这就是你的底部面包层。再次强调,如果你喜欢只有一上一下两层的汉堡,那就祝你享用!但我个人更喜欢更多风味(我们这里也有素食和无麸质选项可供选择。记住,这里是你IDP的选择,你来做主!)。
图4:内部开发者平台:底层架构
真正的魔法发生在中间层部分——这个可以根据你公司的自动化程度、工具集和整体战略灵活定制的中心部分。正是这个中间层部分让每个IDP与众不同并定义了它的价值。有个关键点是:你不能直接购买一个现成的IDP解决方案。你需要根据你组织的实际需求来定制它。
但这正是挑战:在这么多可选配料中,怎样做出一个完美的汉堡,同时让它既简单又有效呢?
图5:内部开发平台:内部中间层
聊够了吧,再聊我就要饿了,点个外卖又赢了,哈哈 :)
现在,我们来关注一下中间层。为了更有趣,我们马上用一些必备的工具。
图6:如图所示,选择服务项并构建核心功能!
这并不是关于构建一个理想的IDP——而是让你了解如何构建一个IDP以及它能解决哪些问题或满足哪些需求。所以,我们需要一个具体的例子或需求。
图6:用户故事图
好的,我听到了,各位 minions,我们造起来吧。
图7:Python中的模板引擎
我们将从一个简单的Python模板生成器开始,生成所需的基础设施组件。
图8:一个简单的YAML配置文件
我们将使用一个简单的**config.yaml**
文件,该文件会被用来通过模板工具包含,以创建所需的文件夹结构和配置文件。
图9:vCluster(vCluster):
我们将使用vCluster来创建临时专用集群(ephemeral dedicated clusters)。这些集群不仅节省成本,减少资源消耗,还能加快开发速度,同时提供自助服务。开发人员可以轻松启动虚拟的Kubernetes集群,并在隔离的etcd存储库中安装第三方工具。
图 10: Helm Charts (用于管理 Kubernetes 应用的 Helm 图表)
我们将部署Argo CD和vCluster这样的服务,并使用简单的umbrella Helm图表叠加用户特定配置。
图11:Terraform(点击访问https://www.terraform.io)
我们将使用Terraform来搭建必要的基础设施,比如托管的Kubernetes集群和节点池,同时配置污点标签、标签等。
图12:STACKIT Kubernetes Engine (SKE)(STACKIT Kubernetes 引擎)(https://www.stackit.de/en/product/kubernetes/)
我们将采用作为主机的集群,并将使用STACKIT Kubernetes 引擎 (SKE),一个管理的Kubernetes解决方案。
图13:操作员 / 调度器
我们将选用Argo CD,它会根据需要部署和清理资源。
图14: GitHub操作
我们将之后集成 GitHub Actions 来自动化整个过程——因为谁都不想手动操作这些流程。
图15:BASH(一种命令行shell)(了解更多)
我们将使用BASH脚本来处理一些任务,例如生成初始的config.yaml
文件和其他辅助任务。
准备好了,出发!
选好了工具,明确了设置之后,让我们进入这篇博客的下一节,看看实操演示。敬请继续关注!
动手吧,一起造!提示: 很快你就能自己尝试一个 GitHub 仓库。你只需要一个支持节点池、污点和节点选择器的托管 Kubernetes 平台。因为 STACKIT Kubernetes Engine (SKE) 符合主权云合规性,所以我选择了它——关于这一点,稍后再详细说明。想提前体验吗?快来参加我们的网络研讨会吧!
我们来看看这个图,看看我们要建的是什么。
图16:抽象的工作流
想法
仓库中将包含一个简单的 config.yaml
文件(1),开发人员可以根据具体项目需求来自定义它。当有拉取请求或是合并事件触发时,将启动相关的工作流。
- 通过自定义构建的Python模板器(2)运行必要的步骤,以验证资源、生成资源并执行必要的测试。
- 使用Terraform创建所需的节点池。
- 在主机集群上使用Argo CD在专用命名空间中部署vCluster(虚拟集群),并将其连接到特定的节点池。
- 在每个项目中部署独立的Argo CD实例,以支持多租户、防止冲突并提供独立管理。
这种设置确保开发人员可以在5分钟内就能快速搭建一个临时的测试环境,从而进行快速且独立的测试。
我们将一步步手动完成这些步骤,因为这样做最重要的是你能理解背后发生的事情。如果你懂了!你可以轻松地把这些步骤加入到你偏好的工作流中!
这样的文件到底是什么样的呢?不会太复杂了吧?还是自己看看吧,然后做出判断。
第一步:修改配置文件config.yaml
这里是最简单的 config.yaml
配置
集群:
- 项目: 项目-x
节点池:
- 名称: 「workload」
如果新增了一个项目(比如,project-z),配置可以像这样轻松更新:
集群:
- 项目: 项目-x
节点池:
- 名称: "工作负载"
- 项目: 项目-z
节点池:
- 名称: "工作负载-2"
步骤2:运行Templater
运行模板以生成所需的资源:
32cd3c08af1f:..../git/webinar/webinar-gitops-vcluster-kubernetes-platform$ 配置模板 --all
正在渲染 APPLICATIONS 模板...
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/applications/app-vcluster-project-x.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/applications/applicationset-vargocd.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/applications/app-vcluster-project-z.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/applications/applicationset-vargocd.yaml
正在渲染 TERRAFORM 模板...
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/terraform/outputs.tf
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/terraform/providers.tf
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/terraform/ske.tf
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/terraform/terraform.tfvars
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/terraform/variables.tf
正在渲染 VCLUSTER 模板...
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-x/appproject.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-x/helm/values.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-x/namespace.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-x/rbac.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-x/system/argocd/values.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-x/vcluster-loadbalancer.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-z/appproject.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-z/helm/values.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-z/namespace.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-z/rbac.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-z/system/argocd/values.yaml
存储模板文件 ..../git/webinar/webinar-gitops-vcluster-kubernetes-platform/vcluster/project-z/vcluster-loadbalancer.yaml
模板生成器将为应用程序、Terraform 配置和 vCluster 模板生成 YAML 格式的文件,比如:
和 vCluster 模板
- 应用定义, Helm 参数, Terraform 模块,* vCluster 配置文件,
使用Terraform启动并规划部署基础设施如下。
terraform init
terraform plan --var-file=terraform.tfvars
terraform apply --var-file=terraform.tfvars
以下符号表示资源操作:
~ 就地更新
Terraform 将执行以下任务:
# stackit_ske_cluster.ske 将就地更新
~ resource "stackit_ske_cluster" "ske" {
id = "....id...,ske-poc"
~ kubernetes_version_used = "1.31.4" -> (应用后已知)
name = "ske-poc"
~ node_pools = [
~ {
name = "pool-infra"
~ os_version_used = "4081.2.0" -> (应用后已知)
# (14 个未变的属性被省略)
},
~ {
name = "pool-workload"
~ os_version_used = "4081.2.0" -> (应用后已知)
# (14 个未变的属性被省略)
},
+ {
+ allow_system_components = true
+ availability_zones = [
+ "eu01-2",
]
+ cri = "containerd"
+ labels = {
+ "project" = "project-z"
}
+ machine_type = "c1.2"
+ max_surge = (应用后已知)
+ max_unavailable = (应用后已知)
+ maximum = 2
+ minimum = 1
+ name = "pool-workload-2"
+ os_name = "flatcar"
+ os_version_min = "4081.2.0"
+ os_version_used = (应用后已知)
+ taints = [
+ {
+ effect = "不可调度"
+ key = "project"
+ value = "project-z"
},
]
+ volume_size = 20
+ volume_type = "storage_premium_perf1"
},
]
# (3 个未变的属性被省略)
}
计划:无添加,1 个更新,无删除。
第四步:启动vCluster(Project-Z项目)
我们将创建并使用一个Argo CD的应用程序,在专属的命名空间中部署vCluster。这里有一个项目-z的示例YAML。
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: vcluster-project-z
spec:
destination:
name: ""
namespace: vcluster-project-z
server: "https://kubernetes.default.svc"
sources:
- repoURL: https://github.com/....
path: vcluster/project-z
targetRevision: main
- repoURL: https://github.com/....
targetRevision: main
ref: valuesRepo
- repoURL: https://github.com/....
targetRevision: main
path: "./kubernetes-service-catalog/optimization/vcluster"
helm:
releaseName: "vcluster"
valueFiles:
- "values.yaml"
- "$valuesRepo/vcluster/project-z/helm/values.yaml"
project: default
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- 不创建命名空间=false
第五步:连接到vCluster:
我们将通过一个 LoadBalancer 服务来访问创建的 vCluster。我目前正在研究仅通过 Argo CD 内部的 svc
服务来建立连接的方案。如果您有任何想法,欢迎随时联系我!目前,Argo CD 无法通过内部 DNS 解析 svc
名。
请运行如下:
vcluster connect vcluster -n vcluster-project-z --server=https://192.XXX.XXX.XXX --insecure --kube-config-context-name project-z
04:36:28 已完成 vCluster 已启动并运行
04:36:28 已完成 切换到项目z的活动kube上下文
- 使用 `vcluster disconnect` 返回到之前的kube上下文
- 使用 `kubectl get namespaces` 访问vcluster
现在你应该能看到在vcluster内运行的服务:
名称 状态 年龄
default Active 2m19s
kube-node-lease Active 2m19s
kube-public Active 2m19s
kube-system Active 2m19s
我们使用vcluster CLI并使用负载均衡器的IP地址建立连接。这将自动更新我们的kubeconfig,以加入新的Kubernetes vCluster上下文环境。
步骤 6:登录后访问 Argo CD
启动两个终端窗口:
执行端口转发,如下所示:
通过`kubectl`(Kubernetes命令行工具)将`svc/argocd-server`(服务名)的443端口转发到本地的8080端口,使用`-n argocd`指定命名空间为`argocd`。这行命令是用来将远程服务的443端口映射到本地的8080端口。
在另一个选项中,使用 CLI 登录 Argo CD 时:
argocd 登录 localhost:8080 \
--username admin \
--password $(kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo) \
--insecure
注:此命令用于登录 Argo CD 服务,其中 --password
参数通过 kubectl 命令获取初始管理员密码,并使用 base64 解码。
第七步:将 vCluster 注册到 Argo CD 中 (注:vCluster 是指虚拟集群)
注册 vCluster 并给它打上适当标签:
argocd 添加集群 project-z --label env=vdev --upsert
WARNING: 这将在由 context `project-z` 指定的集群上创建一个名为 `argocd-manager` 的服务账户,并赋予其完整的集群级权限。是否继续?[y/N]? y
信息提示[0001] 服务账户 `argocd-manager` 已在 `kube-system` 命名空间中创建
信息提示[0001] 集群角色 `argocd-manager-role` 已创建
信息提示[0001] 集群角色绑定 `argocd-manager-role-binding` 已创建
信息提示[0006] 已为服务账户 `argocd-manager` 创建访问令牌
集群 'https://192.XXX.XXX.XXX' 已加入
使用 ApplicationSet 自动化部署多个集群中的应用,通过根据标签进行目标选择。
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: argocd
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
env: vdev
values:
branch: main
template:
metadata:
name: "{{name}}-argocd"
annotations:
argocd.argoproj.io/manifest-generate-paths: ".;.."
spec:
project: default
sources:
- repoURL: https://github.com/...
targetRevision: main
ref: valuesRepo
- repoURL: https://github.com/..
targetRevision: "{{values.branch}}"
path: "./kubernetes-service-catalog/system/argocd"
helm:
releaseName: "argocd"
valueFiles:
- "values.yaml"
- "$valuesRepo/vcluster/{{name}}/system/argocd/values.yaml"
destination:
name: "{{name}}"
namespace: "argocd"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=false
retry:
limit: 5
现在你应该在Host-Cluster上的Argo CD vCluster中看到部署的Argo CD Stack,如下所示:
获取当前配置上下文 (hūnqǔ dāngqián bīnzhèng shàngxià wén)
当前 名称 集群 认证信息 命名空间
project-x project-x project-x
* project-z project-z project-z argocd
ske-poc ske-poc ske-poc vcluster-project-z
查询命名空间 argocd 中的 pod 状态 (cháxún míngzhì kōngjiān argocd zhōng de pod zhuàngtài)
名称 就绪 状态 重启次数 年龄
argocd-application-controller-0 1/1 运行中 0 3分58秒
argocd-applicationset-controller-544d978cb7-hffts 1/1 运行中 0 3分59秒
argocd-dex-server-5bd7d459d6-n58gx 1/1 运行中 5 (2分12秒前) 3分59秒
argocd-notifications-controller-695fdb4f47-6zr8x 1/1 运行中 0 3分59秒
argocd-redis-65bf88bf45-2t952 1/1 运行中 0 3分59秒
argocd-repo-server-8dfc47756-xk9bj 1/1 运行中 0 3分59秒
argocd-server-797fd5c8c4-tp64k 1/1 运行中 0 3分59秒
这不是很简单吗?一旦你有了一个类似的核心系统设置,就可以将其集成到一个门户,比如getport.io。但请记住,关键是要先建立一个稳定且可靠的核心系统!换句话说:不要错过‘健肌日’!
图17:别错过腿部训练日!
快速回顾一下幕后情况都在发生什么:
我们从下面这个初始设定开始:
图18:最初的状况
在完成新项目(project-z)后,情况变成了这样,
图19:项目-z部署后的情况
我为什么选择了这个技术组合和方式
我选择了这个技术栈和方法有以下几个原因,特别适合德国、奥地利和瑞士等地区的实际企业环境。
- 较少依赖超大规模供应商: 这些地区的许多企业环境并不依赖于超大规模供应商,在这些环境中,你通常无法访问诸如 Crossplane 之类的工具。
- 工单操作和审批流程繁琐: 实际的企业环境通常依赖于基于工单的工作流,并缺乏真正的自助服务。即使是在 API 驱动的设置中,大多数操作仍然需要正式审批。避免这些繁琐的流程,更聪明地服务平台用户!
- 大规模多租户: 一个类似的解决方案已经成功地在一个更复杂的客户环境中实施,通过让开发者能够快速高效地开始和测试项目,使多租户成为可能,同时在真实的数据中心环境中运行。
- 成本效益的隔离: 开发者可以完全体验 Kubernetes 而不会相互干扰,即使当开发人员需要为不同版本的第三方工具使用不同的 自定义资源定义(CRDs) 时也能够做到。
- 主权云需求: 该解决方案必须运行在主权云提供商上,因为客户不允许使用像 Azure、AWS 和 Google Cloud 这样的超大规模供应商。
所以 vCluster 非常契合需求,这也是我选择它的原因所在。此外,在为客户创造了实际业务价值后,能够有一个整体概览。当然,我也很喜欢 Kamaji 这样的解决方案,它是来自 CLASTIX 的。
如果你想了解更多如何使用类似 Crossplane 的工具以及查看 vCluster.pro 功能,请看 Kurt Madel 的这个超赞视频!
视频0:使用 Crossplane、Argo CD 和 vCluster.Pro 创建瞬时拉取请求环境 — Kurt Madel 主讲
注意:
声明:
当然,这还不止这些。你还需要考虑几个重要的因素:
- 关闭教育差距: 确保团队熟悉平台及其工作流程。
- 与业务目标对齐: 确保IDP的功能符合你们组织的战略目标。
- 优化工作流程: 不断改进流程,提高效率和可扩展性。
- 等等。
CNCF App Delivery TAG社群在他们最新的版本中做得非常出色,值得进一步了解。
🔥 别错过这个!🔥 错过的话 -> 看下面的视频!2025年2月6日,下午5点(即当地时间下午17点,阿姆斯特丹、柏林时间),我将与Loft Labs(vCluster的创造者)举办一场专为在线研讨会。我们将深入探讨技术设置,并通过现场演示展示如何构建一个可扩展的多租户Kubernetes环境。
💡 这对您有什么好处呢?
- 来自 vCluster 背后专家的实践见解
- 关于如何在多租户环境中实施 GitOps 的实用指南
- 有机会赢得其中一份 使用 Kubernetes 实施 GitOps 由 Pitro Libro 和 Artem Lajko 编写
- 当然还有……在实时演示中见证我的精彩失误(我们都有过类似的经历,不是吗?)
不要错过这个机会!点击下面现在就注册,别等了。
………👉 点击这里注册👈………快来注册吧!
图20:注册即可有机会赢取《使用Kubernetes实现GitOps》10本中的任意1本
网络研讨会抢先看:
避开构建IDP时常见的五大坑
这是网络研讨会的录音:
联系方式有疑问或想聊天,想保持联系吗?别在 Medium 下评论了,我们可以在 LinkedIn 上聊 🤙。别忘了订阅 Medium 的更新,这样你就不会错过任何更新。
共同学习,写下你的评论
评论加载中...
作者其他优质文章