Kubernetes 本地持久化存储方案 OpenEBS LocalPV 落地实践下——原理篇

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: Kubernetes 本地持久化存储方案 OpenEBS LocalPV 落地实践下——原理篇

本篇文章我将讲解 OpenEBS Device-LocalPV 实现原理,如果还不了解了 OpenEBS Device-LocalPV 如何使用,可以移步至本系列上篇文章 Kubernetes 本地持久化存储方案 OpenEBS LocalPV 落地实践上——使用篇 进行学习。

CSI

在深入理解 OpenEBS Device-LocalPV 原理之前,我们不得不了解一个新的概念 CSI,它规定了 K8s 存储插件实现方式。

在 K8s 中,如果 K8s 内置的存储功能不满足我们的生产需求,则可以通过一种叫作 CSI 的插件机制来对其进行扩展,而 Device-LocalPV 正是采用这种机制来实现的。

CSI 是 Container Storage Interface 的简称,是 K8s 官方定义的容器存储接口规范,它试图定义一个统一的业界标准,专门用来扩展容器编排系统的存储能力。

基础架构

CSI 基本架构如下:

CSI

一个 CSI 插件包含两个主体部分 External ComponentsCustom Components,其中 External Components 由 K8s 官方提供,而 Custom Components 则由编写插件的作者来提供。这两个 Components 又各自包含 3 个组件,一起协同工作。

工作流程

我先在这里大概描述一下 CSI 插件工作流程,方便稍后分析 Device-LocalPV 的实现原理。

首先,在 CSI 插件启动时,External Components 中的 Driver Registrar 组件最先开始工作,它通过与 Custom Components 中的 Identity 组件进行通信,获取到 CSI 插件的基本信息,并将其注册到 kubelet 中。

External Components 中的 Provisioner 组件则通过 Watch 机制,监听了 APIServer 中 PVC 对象的创建,一旦有新的 PVC 被创建,Provisioner 就会与 Custom Components 中的 Controller 组件进行通信,让其创建 PV 相关资源。

创建完 PV,下一步就到了 Attach 阶段,而 Attach 操作正对应了 External Components 中的 Attacher 组件,它同样会与 Custom Components 中的 Controller 组件进行通信,协同完成 Attach 操作。

而最后一步 Mount 操作,则由 Node 节点上的 kubelet 直接调用 External Components 中的 Node 组件来完成。至此,Pod 内部应用就可以使用主机节点上挂载的 LocalPV 了。

Device-LocalPV

以上我们简单的过了一遍 CSI 插件基本流程,接下来我们将通过对 OpenEBS Device-LocalPV 运行流程的分析,更深入的理解使用 CSI 插件机制实现 LocalPV 的原理。

部署

要分析一个程序的执行流程,当然要从程序启动入口开始,而 OpenEBS 是一个面向云原生的应用,那么我们首先要看的,就是项目的部署方式,OpenEBS Device-LocalPV 项目部署 yaml 文件也在其项目的 git 仓库中。

可以看到,部署文件中最重要的两个资源分别是一个名为 openebs-device-controller 的 StatefulSet,和一个名为 openebs-device-node 的 DaemonSet。

在 StatefulSet 中启动了两个容器,分别是 K8s 官方提供的 External Provisioner 组件,和由 OpenEBS 开发的 Custom Controller 组件,这两个组件被放在一个 Pod 中协同工作。

而在 DaemonSet 中同样也启动了两个容器,分别是 K8s 官方提供的 External Driver Registrar 组件,和由 OpenEBS 开发的 Custom Node 组件。

那么,CSI 机制中的 External Attacher 组件去哪里了?实际上 LocalPV 并没有 Attach 操作,创建出来后只需要一步 Mount 操作即可使用,所以也就没有必要部署这个组件了。

你是否好奇 External 组件和 Custom 对应组件之间如何来进行通信?如果你详细看过我上面提供的 Device-LocalPV 项目部署 yaml 文件中的内容,就不难发现,里面有 unix:///xxx/csi.sock 字样,实际上它们之间的通信都是依靠基于 Unix socket 的 gRPC 来进行的,这样即实现了组件间的解耦,又能高效进行通信。

组件

知道了 OpenEBS Device-LocalPV 项目都部署了哪些组件,接下来我们通过阅读源码来分析程序执行流程。

无论是 StatefulSet 还是 DaemonSet,Device-LocalPV 程序启动入口文件都是同一个,程序启动后,在 main 函数里会调用 run 函数,run 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
funcrun(config *config.Config) {
if config.Version == "" {
                config.Version = version.Current()
        }
        klog.Infof("Device Driver Version :- %s - commit :- %s", version.Current(), version.GetGitCommit())
        klog.Infof(
"DriverName: %s Plugin: %s EndPoint: %s NodeID: %s",
                config.DriverName,
                config.PluginType,
                config.Endpoint,
                config.NodeID,
        )
iflen(config.IgnoreBlockDevicesRegex) > 0 {
                device.DeviceConfiguration.IgnoreBlockDevicesRegex = regexp.MustCompile(config.IgnoreBlockDevicesRegex)
        }
        err := driver.New(config).Run()
if err != nil {
                log.Fatalln(err)
        }
        os.Exit(0)
}

值得注意的是 err := driver.New(config).Run() 这句代码,通过 config 参数启动了一个 Driver 并执行 Run 方法,我们可以跟踪到 New 函数内部来查看其实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
funcNew(config *config.Config) *CSIDriver {
   driver := &CSIDriver{
      config: config,
cap:    GetVolumeCapabilityAccessModes(),
   }
switch config.PluginType {
case"controller":
      driver.cs = NewController(driver)
case"agent":
      driver.ns = NewNode(driver)
   }
   driver.ids = NewIdentity(driver)
return driver
}

可以发现,New 函数内部会通过 config 创建一个 CSIDriver 对象,这个对象会根据配置参数注册 Controller 组件或 Node 组件(在 Device-LocalPV 项目中 agentNode 等价),而这两个组件分别对应的就是 StatefulSet 和 DaemonSet,也就是说,Controller 组件会以 StatefulSet 的方式启动,Node 组件则会以 DaemonSet 方式启动。

Identity 组件

根据上面的源码可以发现,不管启动 ControllerNode 中的哪个组件,Identity 组件都会被注册进来(driver.ids = NewIdentity(driver)),因为我们将 Device-LocalPV 实现的 CSI 插件分开成两个工作负载来部署,而它们都需要注册给 K8s,Identity 组件正是干这件事情的。

Identity 代码实现在这里:identity.go,这个文件中为 Identity 组件定义了 3 个方法,分别是 GetPluginInfoProbeGetPluginCapabilities

  • 其中 GetPluginInfo 方法返回插件的名称和版本号。
  • Probe 顾名思义是一个探针程序,K8s 可以根据这个探针检查插件是否正常工作。
  • GetPluginCapabilities 方法返回当前插件的能力,用告诉 K8s 这个 CSI 插件实现了哪些功能,比如 Device-LocalPV 项目没有实现 Attach 功能,当我们在创建 PVC 时指定了这个 CSI 插件作为存储类的 provisioner 时,K8s 就会自动跳过 Attach 阶段,直接进入 Mount 阶段。

细心的读者可能已经发现每个方法上都有一行 // This implements csi.ControllerServer 注释,实际上这些方法的名称都是固定的,已经被 CSI 规范所定义,而编写 CSI 插件的作者只需要按照规范实现对应方法即可。CSI spec 详细定义了每个组件需要实现的方法,其地址为:spec.md

Controller 组件

上面分析 CSI 基本架构的时候,讲到 External Provisioner 组件通过 Watch 机制监听 APIServer 中 PVC 对象的创建,一旦有新的 PVC 被创建,Provisioner 组件就会与 Custom Controller 组件进行通信,让其创建 PV 相关资源。这里我说的是创建 PV 相关资源,而不是创建 PV。所谓的 PV 相关资源则是一个 CRD,是编写 CSI 组件的开发人员自定义的一种资源类型,一个 CRD 会与一个 PV 对应,其生命周期也基本相同。这么做的原因是 PV 属于 K8s 内部资源,而 CSI 是一个通用规范,不止适用于 K8s,还适用于任何容器编排系统。所以 CSI 插件应该自己定义一种资源类型,来与 PV 进行对应。

而这个与 PV 相对应的 CRD 叫作 devicevolumes.local.openebs.io,它定义在 Device-LocalPV 项目部署文件中,相当于 Device-LocalPV 自己管理的 PV 资源。一个典型的 devicevolumes.local.openebs.io 资源定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion:v1
items:
-apiVersion:local.openebs.io/v1alpha1
kind:DeviceVolume
metadata:
creationTimestamp:"2022-08-01T08:45:51Z"
finalizers:
-device.openebs.io/finalizer
generation:3
labels:
kubernetes.io/nodename:minikube-m02
name:pvc-8e659633-e052-439a-85eb-30a6d385a12b
namespace:openebs
resourceVersion:"1715"
uid:540aa699-4f6f-487c-b38b-38f8b414bd7c
spec:
capacity:"1073741824"
devname:device-localpv
ownerNodeID:minikube
status:
state:Ready
kind:List
metadata:
resourceVersion:""

为了方便对节点块设备进行管理,Device-LocalPV 还定义了一个叫 devicenodes.local.openebs.io 的 CRD,这个 CRD 对应的是 K8s 工作节点,我们有几个供 Device-LocalPV 使用的节点,就有几个 devicenodes.local.openebs.io 资源,这个 CRD 中记录了当前节点上所有可用块设备。

那么 devicevolumes.local.openebs.io 是在什么时候创建的呢?在 Custom Controller 组件中有一个关键方法叫 Createvolume,正是这个方法负责 PV 相关 CRD 的创建。

External Provisioner 组件监听到有新的 PVC 创建,就会执行组件内部的 Provision 方法,而在 Provision 方法内部,会通过 gRPC 的方式调用 Custom Controller 组件的 Createvolume 方法来创建 CRD,CRD 创建完成后,会由 Provisioner 来创建 PV 对象。相关代码实现如下:

https://github.com/kubernetes-csi/external-provisioner/blob/master/pkg/controller/controller.go#L741

Controller

Agent 组件

最后一个要介绍的 Device-LocalPV 组件是 Agent,它实际上就是 CSI 插件中的 Node 组件,从这个名称不难猜测,所有在节点宿主机上的操作,都会通过这个组件来完成。

Provisioner 组件和 Controller 组件分别创建好 PV 和 CRD 以后,那么还剩下 2 个步骤,分别是在块设备上创建分区和将分区 Mount 到容器内部,这两个操作正是 Agent 组件的职责。

Agent 组件其构造函数如下:

https://github.com/openebs/device-localpv/blob/develop/pkg/driver/agent.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// NewNode returns a new instance
// of CSI NodeServer
funcNewNode(d *CSIDriver)csi.NodeServer {
var ControllerMutex = sync.RWMutex{}
// set up signals so we handle the first shutdown signal gracefully
        stopCh := signals.SetupSignalHandler()
// start the device node resource watcher
gofunc() {
                err := devicenode.Start(&ControllerMutex, stopCh)
if err != nil {
                        klog.Fatalf("Failed to start Device node controller: %s", err.Error())
                }
        }()
// start the device volume  watcher
gofunc() {
                err := volume.Start(&ControllerMutex, stopCh)
if err != nil {
                        klog.Fatalf("Failed to start Device volume management controller: %s", err.Error())
                }
        }()
if d.config.ListenAddress != "" {
                exposeMetrics(d.config, stopCh)
        }
return &node{
                driver: d,
        }
}

可以发现在 Agent 组件内部,启动了两个 Goroutine,它们分别用来监听 devicenodes.local.openebs.iodevicevolumes.local.openebs.io 这两个 CRD,其内部都实现了 syncHandler 方法,根据 CRD 状态进行相应操作。

volume 的 syncHandler 主要逻辑如下:

https://github.com/openebs/device-localpv/blob/develop/pkg/mgmt/volume/volume.go#L39

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func(c *VolController)syncHandler(key string)error {
   ...
// Get the Vol resource with this namespace/name
   Vol, err := c.VolLister.DeviceVolumes(namespace).Get(name)
if K8serror.IsNotFound(err) {
      runtime.HandleError(fmt.Errorf("devicevolume '%s' has been deleted", key))
returnnil
   }
if err != nil {
return err
   }
   VolCopy := Vol.DeepCopy()
   err = c.syncVol(VolCopy)
return err
}
func(c *VolController)syncVol(vol *apis.DeviceVolume)error {
   ...
// if the status Pending means we will try to create the volume
if vol.Status.State == device.DeviceStatusPending {
      err = device.CreateVolume(vol)
if err == nil {
         err = device.UpdateVolInfo(vol, device.DeviceStatusReady)
      } elseif custError, ok := err.(*apis.VolumeError); ok && custError.Code == apis.InsufficientCapacity {
         vol.Status.Error = custError
return device.UpdateVolInfo(vol, device.DeviceStatusFailed)
      }
   }
return err
}

可以看到,syncHandler 方法会将查询到的 devicevolumes.local.openebs.io 信息传递给 syncVol 方法,而这个方法中有一行非常关键的代码 device.CreateVolume(vol),调用此方法的作用正是根据 CRD 的信息,在块设备上创建出真正的分区。

分区一旦被成功创建,那么就只剩下最后一个步骤 Mount 操作了,Mount 操作由 PV 所在节点的 kubelet 直接调用 Agent 组件的 NodePublishVolume 方法来完成。值得注意的是,在部署 Device-LocalPV 项目的 yaml 文件中,Agent 组件所在容器的 volumeMounts 属性中有一个 mountPropagation: "Bidirectional" 配置,其作用是为了使在容器内部执行的 Mount命令能够向上传播到宿主机上。所以尽管Agent 组件运行在容器中,但在其内部执行的 Mount 命令依然能够在节点上生效。

至此,整个 OpenEBS Device-LocalPV 项目的基本原理,我们就一起分析完成了。细心的你应该能够发现,CSI 插件其实就是一个 CRD + Custom Controller 的实践。

调度策略

接下来我还想跟你分享下 Device-LocalPV 的调度原理。

Device-LocalPV 提供了两种调度策略:CapacityWeightedVolumeWeighted,其相关源码实现在:schd_helper.go,我们可以在存储类中通过参数指定调度策略:

1
2
3
parameters:
scheduler:"VolumeWeighted"
devname:"test-device"
CapacityWeighted

CapacityWeighted 为默认调度策略,即根据使用容量调度,它会查找已部署了 OpenEBS 的节点,按节点上块设备已使用容量进行打分,优先调度到已使用容量较小的节点。

下图中,Node1 节点上已经存在 3 个 PV,Node2 节点上存在 2 个 PV,Node3 节点上没有部署 OpenEBS,如果此时新创建一个 PVC,那么 PV 会如何调度呢?

CapacityWeighted

根据 CapacityWeighted 调度策略来看,Node3 节点第一个被排除,尽管 Node1 节点上已经存在的 PV 数量比 Node2 节点上的多,但已使用容量较少,故根据已使用容量来排序,显然这个新创建的 PV 将会被调度到 Node1 节点。

VolumeWeighted

VolumeWeighted 调度策略则根据使用卷数量来进行调度,查找已部署了 OpenEBS 的节点,按节点上块设备已分配卷数量进行打分,优先调度到已使用卷数量较小的节点。

那么分析下来,在跟上图中同样的情况下,使用 VolumeWeighted 调度策略后,新创建的 PV 将会被调度到 Node2 节点。

VolumeWeighted

自定义调度策略

如果事情按照理想化方向发展,那么上面两种 Device-LocalPV 提供的调度策略没有任何问题,但真实场景中,也许你会遇到如下情况:

FreeCapacityWeighted

现在部署了 OpenEBS 的节点从 2 个扩展成 3 个,但是 Node3 上仅仅部署了 OpenEBS,还没有插入块设备供 OpenEBS 使用。

在这种情况下,如果新创建 PVC,那么这个 PVC 将一直处于 Pending 状态,PV 无法完成创建。

因为无论是哪种调度策略,Device-LocalPV 在为节点打分阶段,总是会将使块设备用量为 0 的节点排序在最前面,所以两种策略最终都会将 PV 调度到 Node3 节点,而又因为 Node3 节点上没有供 OpenEBS 使用的块设备,无法进行磁盘分区,PV 也就无法成功创建。

这也是 Device-LocalPV 支持的调度策略存在问题的地方,如果想解决这个问题,我们可以参考官方提供的调度策略,实现自己的调度策略。我实现了一个 FreeCapacityWeighted 策略,根据节点上块设备剩余容量大小来调度 PV,这样就能够保证 Node3 这种情况在节点打分阶段使其排序在最后面。如果你感兴趣可以进入到这个 issue 查看。

故障处理

如果你想在生产环境中使用 OpenEBS Device-LocalPV 来提供 LocalPV 的支持,那么就要考虑 LocalPV 的故障处理,根据 LocalPV 特点,我这里主要分析下如下两种情况的故障处理思路。

节点故障

如果某个正在被 LocalPV 使用的节点出现故障,可以通过迁移块设备来让 LocalPV 恢复使用,具体步骤如下:

  1. 将数据盘移动到新节点上
  2. 如果新节点没有部署 OpenEBS 则需要先部署 Device-LocalPV 的 DaemonSet 到新节点上
  3. 修改 CRD 资源 devicevolumes.local.openebs.io 所属的节点,即修改 spec.ownerNodeID 属性到新的节点
  4. 修改 PV 的节点信息,即修改 spec.nodeAffinity 属性到新的节点
  5. 删除使用 PV 的 Pod 让其自动重启,并调度到 PV 所指定的新节点上
磁盘故障

如果某个正在被 LocalPV 使用的块设备出现故障,则会造成数据丢失。

需要注意的是,当某个 PV 所使用的磁盘分区出现故障时,PV 无法感知,只有部署在 Pod 内的程序可以感知到,可以尝试在出现故障的 Pod 容器内部执行读写操作,会得到如下错误:

1
2
/mnt/store # echo abc > a.txt
sh: can't create a.txt: Input/output error

所以使用 LocalPV 的程序要有比较完善的异常处理机制以应对可能出现的故障问题。

总结

本篇文章我们一起学习了 CSI 的概念并对 OpenEBS Device-LocalPV 原理进行了剖析。其中 CSI 不仅可以用来实现 LocalPV,其他任何存储扩展都可以用它的规范来实现。通过分析 Device-LocalPV 源码,我们知道它其实是一个 CRD + Custom Controller 的实践。

由于篇幅所限,文中不能将所有细节都涵盖,如果有问题,欢迎一起交流学习。

参考:

https://openebs.io/

https://github.com/container-storage-interface/spec

https://github.com/openebs/device-localpv

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
23天前
|
Kubernetes Cloud Native 网络协议
Kubernetes 高可用性与灾难恢复方案
【8月更文第29天】随着业务的不断增长,保持应用程序的高可用性和灾难恢复能力变得越来越重要。Kubernetes 作为现代云原生应用的主要平台,提供了丰富的工具和方法来保证应用的高可用性以及快速恢复的能力。本文将详细介绍如何利用 Kubernetes 的功能来构建高可用性的系统,并实施有效的灾难恢复策略。
59 1
|
23天前
|
Kubernetes 负载均衡 网络安全
Kubernetes 网络模型与实践
【8月更文第29天】Kubernetes(K8s)是当今容器编排领域的佼佼者,它提供了一种高效的方式来管理容器化应用的部署、扩展和运行。Kubernetes 的网络模型是其成功的关键因素之一,它支持服务发现、负载均衡和集群内外通信等功能。本文将深入探讨 Kubernetes 的网络模型,并通过实际代码示例来展示服务发现和服务网格的基本概念及其实现。
37 1
|
25天前
|
Kubernetes Devops 持续交付
DevOps实践:使用Docker和Kubernetes实现持续集成和部署网络安全的守护盾:加密技术与安全意识的重要性
【8月更文挑战第27天】本文将引导读者理解并应用DevOps的核心理念,通过Docker和Kubernetes的实战案例,深入探讨如何在现代软件开发中实现自动化的持续集成和部署。文章不仅提供理论知识,还结合真实示例,旨在帮助开发者提升效率,优化工作流程。
|
2天前
|
Kubernetes 持续交付 开发者
探索并实践Kubernetes集群管理与自动化部署
探索并实践Kubernetes集群管理与自动化部署
19 4
|
21天前
|
存储 Kubernetes Cloud Native
探索Python编程的奥秘云原生时代的容器编排:Kubernetes入门与实践
【8月更文挑战第30天】本文以浅显易懂的方式,探讨了Python编程的核心概念和技巧。从基础语法到高级特性,再到实际应用案例,逐步引导读者深入理解Python编程的精髓。通过本文的学习,读者将能够掌握Python编程的基本技能,并激发进一步探索的兴趣。
29 13
|
16天前
|
Cloud Native 持续交付 Docker
云原生技术实践:Docker容器化部署教程
【9月更文挑战第4天】本文将引导你了解如何利用Docker这一云原生技术的核心工具,实现应用的容器化部署。文章不仅提供了详细的步骤和代码示例,还深入探讨了云原生技术背后的哲学,帮助你理解为何容器化在现代软件开发中变得如此重要,并指导你如何在实际操作中运用这些知识。
|
21天前
|
运维 Kubernetes Cloud Native
云原生之旅:Kubernetes 集群的搭建与实践Python 编程入门:从零基础到编写实用脚本
【8月更文挑战第30天】在数字化转型的大潮中,云原生技术以其弹性、可扩展性及高效运维能力成为企业IT架构升级的关键。本文将通过实际操作演示如何在本地环境搭建一个简易的Kubernetes集群,带你领略云原生的魅力所在。从集群规划到服务部署,每一步都是对云原生理念的深刻理解和应用。让我们共同探索,如何通过Kubernetes集群的搭建和运维,提升业务灵活性和创新能力。
|
20天前
|
运维 Kubernetes 监控
自动化运维:使用Python脚本实现系统监控云原生技术实践:Kubernetes在现代应用部署中的角色
【8月更文挑战第31天】在现代IT运维管理中,自动化已成为提高效率和准确性的关键。本文将通过一个Python脚本示例,展示如何实现对服务器的自动监控,包括CPU使用率、内存占用以及磁盘空间的实时监测。这不仅帮助运维人员快速定位问题,也减轻了日常监控工作的负担。文章以通俗易懂的语言,逐步引导读者理解并实践自动化监控的设置过程。 【8月更文挑战第31天】本文旨在探索云原生技术的核心—Kubernetes,如何革新现代应用的开发与部署。通过浅显易懂的语言和实例,我们将一窥Kubernetes的强大功能及其对DevOps文化的影响。你将学会如何利用Kubernetes进行容器编排,以及它如何帮助你的
|
20天前
|
运维 Kubernetes Cloud Native
拥抱云原生:Kubernetes 在现代应用部署中的实践
【8月更文挑战第31天】在数字化转型的浪潮中,云原生技术成为推动企业创新和效率提升的关键力量。本文将深入探讨如何利用 Kubernetes,这一强大的容器编排工具,来部署和管理现代应用。我们将从基础架构搭建开始,一步步引导您配置集群,并通过实际代码示例演示如何部署一个简单的应用。无论您是云原生新手还是希望深化理解,这篇文章都将为您提供实操经验和理论知识的融合之旅。
|
20天前
|
Kubernetes Cloud Native 应用服务中间件
云原生技术入门与实践:Kubernetes的简易部署
【8月更文挑战第31天】云原生技术已成为现代软件部署的黄金标准,而Kubernetes作为其核心组件之一,在容器编排领域独领风骚。本文将引导您通过简单的步骤,快速部署一个Kubernetes集群,并运行一个简单的应用,让您轻松迈入云原生的世界。