使用 C# 开发 Kubernetes 组件,获取集群资源信息

本文涉及的产品
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
传统型负载均衡 CLB,每月750个小时 15LCU
简介: 使用 C# 开发 Kubernetes 组件,获取集群资源信息

写什么呢


前段时间使用 C# 写了个项目,使用 Kubernetes API Server,获取信息以及监控 Kubernetes 资源,然后结合 Neting 做 API 网关。


体验地址 http://neting.whuanle.cn:30080/

账号 admin,密码 admin123

微信图片_20220505201636.png

本篇文章主要介绍,如何通过 C# 开发基于 Kuberetes 的应用,实现获取 Kubernets 中各种资源的信息,以及实现 Conroller 的前提知识。而在下一篇中则会讲解如何实现 Conroller 和 Kubernetes Operator。


Kubernetes API Server


kube-apiserver 是 k8s 主要进程之一,apiserver 组件公开了 Kubernetes API (HTTP API),apiserver 是 Kubernetes 控制面的前端,我们可以用 Go、C# 等编程语言写代码,远程调用 Kubernetes,控制集群的运行。apiserver 暴露的 endiont 端口是 6443。

为了控制集群的运行,Kubernetes 官方提供了一个名为 kubectl 的二进制命令行工具,正是 apiserver 提供了接口服务,kubectl 解析用户输入的指令后,向 apiserver 发起 HTTP 请求,再将结果反馈给用户。


kubectl

kubectl 是 Kubernetes 自带的一个非常强大的控制集群的工具,通过命令行操作去管理整个集群。


Kubernetes 有很多可视化面板,例如 Dashboard,其背后也是调用 apiserver 的 API,相当于前端调后端。


总之,我们使用的各种管理集群的工具,其后端都是 apiserver,通过 apiserver,我们还可以定制各种各样的管理集群的工具,例如网格管理工具 istio。腾讯云、阿里云等云平台都提供了在线的 kubernetes 服务,还有控制台可视化操作,也是利用了 apiserver。


你可以参考笔者写的 Kubernetes 电子书,了解更多:https://k8s.whuanle.cn/1.basic/5.k8s.html


简而言之, Kubernetes API Server 是第三方操作 Kubernetes 的入口。


暴露 Kubernetes API Server


首先查看 kube-system 中运行的 Kubernetes 组件,有个 kube-apiserver-master 正在运行。

root@master:~# kubectl get pods -o wide  -n kube-system
NAME                             READY   STATUS    RESTARTS         AGE   IP          NODE     NOMINATED NODE   READINESS GATES
... ...
kube-apiserver-master            1/1     Running   2 (76d ago)      81d   10.0.0.4    master   <none>           <none>
... ...


虽然这些组件很重要,但是只会有一个实例,并且以 Pod 形式运行,而不是 Deployment,这些组件只能放在 master 节点运行。


然后查看 admin.conf 文件,可以通过 /etc/kubernetes/admin.conf$HOME/.kube/config 路径查看到。

微信图片_20220505201640.png

admin.conf 文件是访问 Kubernetes API Server 的凭证,通过这个文件,我们可以使用编程访问 Kubernetes 的 API 接口。


但是 admin.conf 是很重要的文件,如果是开发环境开发集群,那就随便造,如果是生产环境,请勿使用,可通过角色绑定等方式限制 API 访问授权。


然后把 admin.conf 或 config 文件下载到本地。

你可以使用 kubectl edit pods kube-apiserver-master -n kube-system 命令,查看 Kubernetes API Server 的一些配置信息。


由于 Kubernetes API Server 默认是通过集群内访问的,如果需要远程访问,则需要暴露到集群外(与是否都在内网无关,与是否在集群内有关)。


将 API Server 暴露到集群外:

kubectl expose pod  kube-apiserver-master --type=NodePort --port=6443 -n kube-system


查看节点随机分配的端口:

root@master:~# kubectl get svc -n kube-system
NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                  AGE
kube-apiserver-master   NodePort    10.101.230.138   <none>        6443:32263/TCP           25s


32263 端口是 Kubernetes 自动分配,每个人的都不一样。

然后通过 IP:32263 即可测试访问。


微信图片_20220505201645.png

如果你的集群安装了 CoreDNS,那么通过其他节点的 IP,也可以访问到这个服务。

然后将下载的 admin.conf 或者 config 文件(请改名为 admin.conf),修改里面的 server 属性,因为我们此时是通过远程访问的。


连接到 API Server


新建一个 MyKubernetes 控制台项目,然后将 admin.conf 文件复制放到项目中,随项目生成输出。

微信图片_20220505201953.png

然后在 Nuget 中搜索 KubernetesClient 包,笔者当前使用的是 7.0.1。

然后在项目中设置环境变量:

微信图片_20220505201956.png

这个环境变量本身是 ASP.NET Core 自带的,控制台程序中没有。

下面写一个方法,用于实例化和获取 Kubernetes 客户端:

private static Kubernetes GetClient()
    {
        KubernetesClientConfiguration config;
        if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development")
        {
            // 通过配置文件
            config = KubernetesClientConfiguration.BuildConfigFromConfigFile("./admin.conf");
        }
        else
        {
            // 通过默认的 Service Account 访问,必须在 kubernetes 中运行时才能使用
            config = KubernetesClientConfiguration.BuildDefaultConfig(); 
        }
        return new Kubernetes(config);
    }


逻辑很简单,如果是开发环境,则使用 admin.conf 文件访问,如果是非开发环境,则 BuildDefaultConfig() 自动获取访问凭证,此方式只在 Pod 中运行时有效,利用 Service Account 认证。


下面测试一下,获取全部命名空间:

static async Task Main()
    {
        var client = GetClient();
        var namespaces  = await client.ListNamespaceAsync();
        foreach (var item in namespaces.Items)
        {
            Console.WriteLine(item.Metadata.Name);
        }
    }


微信图片_20220505202001.png

好了!你已经会获取 Kubernetes 资源了,打开入门的第一步!秀儿!

客户端小知识

虽然打开了入门的第一步,但是不要急着使用各种 API ,这里我们来了解一下 Kubernetes 各种资源在客户端中的定义,和如何解析结构。


首先,在 Kubernetes Client C# 的代码中,所有 Kubernetes 资源的模型类,都在 k8s.Models 中记录。


如果我们要在 Kubernetes 中,查看一个对象的定义,如 kube-systtem 命名空间的:

kubectl get namespace kube-system -o yaml


apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2021-11-03T13:57:10Z"
  labels:
    kubernetes.io/metadata.name: kube-system
  name: kube-system
  resourceVersion: "33"
  uid: f0c1f00d-2ee4-40fb-b772-665ac2a282d7
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

C# 中,模型的结构与其一模一样:

微信图片_20220505202044.png

在客户端中,模型的名称以 apiVersion 版本做前缀,并且通过 V1NamespaceList 获取这类对象的列表。

如果要获取某类资源,其接口都是以 List 开头的,如 client.ListNamespaceAsync()client.ListAPIServiceAsync()client.ListPodForAllNamespacesAsync() 等。

看来,学习已经步入正轨了,让我们来实验练习吧!


如何解析一个 Service


这里笔者贴心给读者准备了一些练习,第一个练习是解析一个 Service 的信息出来。

查看前面创建的 Servicie:

kubectl get svc  kube-apiserver-master -n kube-system -o yaml


对应结构如下:

apiVersion: v1
kind: Service
metadata:
  creationTimestamp: "2022-01-24T12:51:32Z"
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver-master
  namespace: kube-system
  resourceVersion: "24215604"
  uid: ede0e3df-8ef6-45c6-9a8d-2a2048c6cb12
spec:
  clusterIP: 10.101.230.138
  clusterIPs:
  - 10.101.230.138
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - nodePort: 32263
    port: 6443
    protocol: TCP
    targetPort: 6443
  selector:
    component: kube-apiserver
    tier: control-plane
  sessionAffinity: None
  type: NodePort
status:
  loadBalancer: {}


我们在 C# 中定义一个这样的模型类:

public class ServiceInfo
    {
        /// <summary>
        /// SVC 名称
        /// </summary>
        public string Name { get; set; } = null!;
        /// <summary>
        /// 三种类型之一 <see cref="ServiceType"/>
        /// </summary>
        public string? ServiceType { get; set; }
        /// <summary>
        /// 命名空间
        /// </summary>
        public string Namespace { get; set; } = null!;
        /// <summary>
        /// 有些 Service 没有此选项
        /// </summary>
        public string ClusterIP { get; set; } = null!;
        /// <summary>
        /// 外网访问 IP
        /// </summary>
        public string[]? ExternalAddress { get; set; }
        public IDictionary<string, string>? Labels { get; set; }
        public IDictionary<string, string>? Selector { get; set; }
        /// <summary>
        /// name,port
        /// </summary>
        public List<string>? Ports { get; set; }
        public string[]? Endpoints { get; set; }
        public DateTime? CreationTime { get; set; }
        // 关联的 Pod 以及 pod 的 ip
    }


下面,指定获取哪个命名空间的 Service 及其关联的 Endpoint 信息。

static async Task Main()
    {
        var result = await GetServiceAsync("kube-apiserver-master","kube-system");
        Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result));
    }
    public static async Task<ServiceInfo> GetServiceAsync(string svcName, string namespaceName)
    {
        var client = GetClient();
        var service = await client.ReadNamespacedServiceAsync(svcName, namespaceName);
        // 获取 service 本身的信息
        ServiceInfo info = new ServiceInfo
        {
            Name = service.Metadata.Name,
            Namespace = service.Metadata.NamespaceProperty,
            ServiceType = service.Spec.Type,
            Labels = service.Metadata.Labels,
            ClusterIP = service.Spec.ClusterIP,
            CreationTime = service.Metadata.CreationTimestamp,
            Selector = service.Spec.Selector.ToDictionary(x => x.Key, x => x.Value),
            ExternalAddress = service.Spec.ExternalIPs?.ToArray(),
        };
        // service -> endpoint 的信息
        var endpoint = await client.ReadNamespacedEndpointsAsync(svcName, namespaceName);
        List<string> address = new List<string>();
        foreach (var sub in endpoint.Subsets)
        {
            foreach (var addr in sub.Addresses)
            {
                foreach (var port in sub.Ports)
                {
                    address.Add($"{addr.Ip}:{port.Port}/{port.Protocol}");
                }
            }
        }
        info.Endpoints = address.ToArray();
        return info;
    }


输出结果如下:

微信图片_20220505202135.png

亲,如果你对 Kubernetes 的网络知识不太清楚,请先打开 https://k8s.whuanle.cn/4.network/1.network.html 了解一下呢。


实践 2


我们知道,一个 Service 可以关联多个 Pod,为多个 Pod 提供负载均衡等功能。同时 Service 有 externalIP、clusterIP 等属性,要真正解析出一个 Service 是比较困难的。例如 Service 可以只有端口,没有 IP;也可以只使用 DNS 域名访问;也可以不绑定任何 Pod,可以从 Service A DNS -> Service B IP 间接访问 B;


Service 包含的情况比较多,读者可以参考下面这个图,下面我们通过代码,获取一个 Service 的 IP 和端口信息,然后生成对应的 IP+端口结构。


微信图片_20220505202140.png


单纯获取 IP 和 端口是没用的,因为他们是分开的,你获取到的 IP 可能是 Cluter、Node、LoadBalancer 的,有可能只是 DNS 没有 IP,那么你这个端口怎么访问呢?这个时候必须根据一定的规则,解析信息,筛选无效数据,才能得出有用的访问地址。


首先定义一部分枚举和模型:


public enum ServiceType
    {
        ClusterIP,
        NodePort,
        LoadBalancer,
        ExternalName
    }
    /// <summary>
    /// Kubernetes Service 和 IP
    /// </summary>
    public class SvcPort
    {
        // LoadBalancer -> NodePort -> Port -> Target-Port
        /// <summary>
        /// 127.0.0.1:8080/tcp、127.0.0.1:8080/http
        /// </summary>
        public string Address { get; set; } = null!;
        /// <summary>
        /// LoadBalancer、NodePort、Cluster
        /// </summary>
        public string Type { get; set; } = null!;
        public string IP { get; set; } = null!;
        public int Port { get; set; }
    }
    public class SvcIpPort
    {
        public List<SvcPort>? LoadBalancers { get; set; }
        public List<SvcPort>? NodePorts { get; set; }
        public List<SvcPort>? Clusters { get; set; }
        public string? ExternalName { get; set; }
    }


编写解析代码:

static async Task Main()
    {
        var result = await GetSvcIpsAsync("kube-apiserver-master","kube-system");
        Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result));
    }
    public static async Task<SvcIpPort> GetSvcIpsAsync(string svcName, string namespaceName)
    {
        var client = GetClient();
        var service = await client.ReadNamespacedServiceAsync(svcName, namespaceName);
        SvcIpPort svc = new SvcIpPort();
        // LoadBalancer
        if (service.Spec.Type == nameof(ServiceType.LoadBalancer))
        {
            svc.LoadBalancers = new List<SvcPort>();
            var ips = svc.LoadBalancers;
            // 负载均衡器 IP
            var lbIP = service.Spec.LoadBalancerIP;
            var ports = service.Spec.Ports.Where(x => x.NodePort != null).ToArray();
            foreach (var port in ports)
            {
                ips.Add(new SvcPort
                {
                    Address = $"{lbIP}:{port.NodePort}/{port.Protocol}",
                    IP = lbIP,
                    Port = (int)port.NodePort!,
                    Type = nameof(ServiceType.LoadBalancer)
                });
            }
        }
        if (service.Spec.Type == nameof(ServiceType.LoadBalancer) || service.Spec.Type == nameof(ServiceType.NodePort))
        {
            svc.NodePorts = new List<SvcPort>();
            var ips = svc.NodePorts;
            // 负载均衡器 IP,有些情况可以设置 ClusterIP 为 None;也可以手动设置为 None,只要有公网 IP 就行
            var clusterIP = service.Spec.ClusterIP;
            var ports = service.Spec.Ports.Where(x => x.NodePort != null).ToArray();
            foreach (var port in ports)
            {
                ips.Add(new SvcPort
                {
                    Address = $"{clusterIP}:{port.NodePort}/{port.Protocol}",
                    IP = clusterIP,
                    Port = (int)port.NodePort!,
                    Type = nameof(ServiceType.NodePort)
                });
            }
        }
        // 下面这部分代码是正常的,使用 {} 可以隔离部分代码,避免变量重名
        // if (service.Spec.Type == nameof(ServiceType.ClusterIP))
        // 如果 Service 没有 Cluster IP,可能使用了无头模式,也有可能不想出现 ClusterIP
        //if(service.Spec.ClusterIP == "None")
        {
            svc.Clusters = new List<SvcPort>();
            var ips = svc.Clusters;
            var clusterIP = service.Spec.ClusterIP;
            var ports = service.Spec.Ports.ToArray();
            foreach (var port in ports)
            {
                ips.Add(new SvcPort
                {
                    Address = $"{clusterIP}:{port.Port}/{port.Protocol}",
                    IP = clusterIP,
                    Port = port.Port,
                    Type = nameof(ServiceType.ClusterIP)
                });
            }
        }
        if (!string.IsNullOrEmpty(service.Spec.ExternalName))
        {
            /* NAME            TYPE           CLUSTER-IP       EXTERNAL-IP          PORT(S)     AGE
               myapp-svcname   ExternalName   <none>           myapp.baidu.com      <none>      1m
               myapp-svcname ->  myapp-svc 
               访问 myapp-svc.default.svc.cluster.local,变成 myapp.baidu.com
             */
            svc.ExternalName = service.Spec.ExternalName;
        }
        return svc;
    }


规则解析比较复杂,这里就不详细讲解,读者如有疑问,可联系笔者讨论。

主要规则:LoadBalancer -> NodePort -> Port -> Target-Port


最终结果如下:


微信图片_20220505202148.png

通过这部分代码,可以解析出 Service 在 External Name、LoadBalancer、NodePort、ClusterIP 等情况下可真正访问的地址列表。


实践3 Endpoint 列表


如果对 Endpoint 不太了解,请打开 https://k8s.whuanle.cn/4.network/2.endpoint.html 看一下相关知识。

微信图片_20220505202248.png

在 Kubernetes 中,Service 不是直接关联 Pod 的,而是通过 Endpoint 间接代理 Pod。当然除了 Service -> Pod,通过 Endpoint,也可以实现接入集群外的第三方服务。例如数据库集群不在 Kubernetes 集群中,但是想通过 Kubernetes Service 统一访问,则可以利用 Endpoint 进行解耦。这里不多说,读者可以参考 https://k8s.whuanle.cn/4.network/2.endpoint.html


这里这小节中,笔者也将会讲解如何在 Kubernetes 中分页获取资源。

首先定义以下模型:

public class SvcInfoList
    {
        /// <summary>
        /// 分页属性,具有临时有效期,具体由 Kubernetes 确定
        /// </summary>
        public string? ContinueProperty { get; set; }
        /// <summary>
        /// 预计剩余数量
        /// </summary>
        public int RemainingItemCount { get; set; }
        /// <summary>
        /// SVC 列表
        /// </summary>
        public List<SvcInfo> Items { get; set; } = new List<SvcInfo>();
    }
    public class SvcInfo
    {
        /// <summary>
        /// SVC 名称
        /// </summary>
        public string Name { get; set; } = null!;
        /// <summary>
        /// 三种类型之一 <see cref="ServiceType"/>
        /// </summary>
        public string? ServiceType { get; set; }
        /// <summary>
        /// 有些 Service 没有 IP,值为 None
        /// </summary>
        public string ClusterIP { get; set; } = null!;
        public DateTime? CreationTime { get; set; }
        public IDictionary<string, string>? Labels { get; set; }
        public IDictionary<string, string>? Selector { get; set; }
        /// <summary>
        /// name,port
        /// </summary>
        public List<string> Ports { get; set; }
        public string[]? Endpoints { get; set; }
    }


Kubernetes 中的分页,没有 PageNo、PageSize、Skip、Take 、Limit 这些,并且分页可能只是预计,不一定完全准确。

第一次访问获取对象列表时,不能使用 ContinueProperty 属性。

第一次访问 Kubernets 后,获取 10 条数据,那么 Kubernetes 会返回一个 ContinueProperty 令牌,和剩余数量 RemainingItemCount。

那么我们可以通过 RemainingItemCount 计算大概的分页数字。因为 Kubernetes 是不能直接分页的,而是通过类似游标的东西,记录当前访问的位置,然后继续向下获取对象。ContinueProperty 保存了当前查询游标的令牌,但是这个令牌有效期是几分钟。


解析方法:

public static async Task<SvcInfoList> GetServicesAsync(string namespaceName, 
                                                           int pageSize = 1, 
                                                           string? continueProperty = null)
    {
        var client = GetClient();
        V1ServiceList services;
        if (string.IsNullOrEmpty(continueProperty))
        {
            services = await client.ListNamespacedServiceAsync(namespaceName, limit: pageSize);
        }
        else
        {
            try
            {
                services = await client.ListNamespacedServiceAsync(namespaceName, 
                                                                   continueParameter: continueProperty, 
                                                                   limit: pageSize);
            }
            catch (Microsoft.Rest.HttpOperationException ex)
            {
                throw ex;
            }
            catch
            {
                throw;
            }
        }
        SvcInfoList svcList = new SvcInfoList
        {
            ContinueProperty = services.Metadata.ContinueProperty,
            RemainingItemCount = (int)services.Metadata.RemainingItemCount.GetValueOrDefault(),
            Items = new List<SvcInfo>()
        };
        List<SvcInfo> svcInfos = svcList.Items;
        foreach (var item in services.Items)
        {
            SvcInfo service = new SvcInfo
            {
                Name = item.Metadata.Name,
                ServiceType = item.Spec.Type,
                ClusterIP = item.Spec.ClusterIP,
                Labels = item.Metadata.Labels,
                Selector = item.Spec.Selector,
                CreationTime = item.Metadata.CreationTimestamp
            };
            // 处理端口
            if (item.Spec.Type == nameof(ServiceType.LoadBalancer) || item.Spec.Type == nameof(ServiceType.NodePort))
            {
                service.Ports = new List<string>();
                foreach (var port in item.Spec.Ports)
                {
                    service.Ports.Add($"{port.Port}:{port.NodePort}/{port.Protocol}");
                }
            }
            else if (item.Spec.Type == nameof(ServiceType.ClusterIP))
            {
                service.Ports = new List<string>();
                foreach (var port in item.Spec.Ports)
                {
                    service.Ports.Add($"{port.Port}/{port.Protocol}");
                }
            }
            var endpoint = await client.ReadNamespacedEndpointsAsync(item.Metadata.Name, namespaceName);
            if (endpoint != null && endpoint.Subsets.Count != 0)
            {
                List<string> address = new List<string>();
                foreach (var sub in endpoint.Subsets)
                {
                    if (sub.Addresses == null) continue;
                    foreach (var addr in sub.Addresses)
                    {
                        foreach (var port in sub.Ports)
                        {
                            address.Add($"{addr.Ip}:{port.Port}/{port.Protocol}");
                        }
                    }
                }
                service.Endpoints = address.ToArray();
            }
            svcInfos.Add(service);
        }
        return svcList;
    }


规则解析比较复杂,这里就不详细讲解,读者如有疑问,可联系笔者讨论。

调用方法:

static async Task Main()
    {
        var result = await GetServicesAsync("default", 2);
        Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result.Items));
        if (result.RemainingItemCount != 0)
        {
            while (result.RemainingItemCount != 0)
            {
                Console.WriteLine($"剩余 {result.RemainingItemCount} 条数据,{result.RemainingItemCount / 3 + (result.RemainingItemCount % 3 == 0 ? 0 : 1)} 页,按下回车键继续获取!");
                Console.ReadKey();
                result = await GetServicesAsync("default", 2, result.ContinueProperty); 
                Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result.Items));
            }
        }
    }



微信图片_20220505202254.png

微信图片_20220505202257.png

上面的实践中,代码较多,建议读者启动后进行调试,一步步调试下来,慢慢检查数据,对比 Kubernetes 中的各种对象,逐渐加深理解。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
前端开发 JavaScript 安全
C#一分钟浅谈:Blazor WebAssembly 开发
Blazor WebAssembly 是一个客户端框架,允许开发者使用C#和Razor语法构建Web应用。本文介绍了Blazor WebAssembly的基本概念、常见问题及解决方案,包括路由配置、数据绑定、异步操作、状态管理和性能优化等方面的内容,并分享了一些易错点及如何避免的方法。希望这些内容能帮助你在Blazor WebAssembly开发中少走弯路,提高开发效率。
98 51
|
6天前
|
存储 Kubernetes 关系型数据库
阿里云ACK备份中心,K8s集群业务应用数据的一站式灾备方案
本文源自2024云栖大会苏雅诗的演讲,探讨了K8s集群业务为何需要灾备及其重要性。文中强调了集群与业务高可用配置对稳定性的重要性,并指出人为误操作等风险,建议实施周期性和特定情况下的灾备措施。针对容器化业务,提出了灾备的新特性与需求,包括工作负载为核心、云资源信息的备份,以及有状态应用的数据保护。介绍了ACK推出的备份中心解决方案,支持命名空间、标签、资源类型等维度的备份,并具备存储卷数据保护功能,能够满足GitOps流程企业的特定需求。此外,还详细描述了备份中心的使用流程、控制台展示、灾备难点及解决方案等内容,展示了备份中心如何有效应对K8s集群资源和存储卷数据的灾备挑战。
|
27天前
|
Kubernetes 监控 Cloud Native
Kubernetes集群的高可用性与伸缩性实践
Kubernetes集群的高可用性与伸缩性实践
60 1
|
1月前
|
开发框架 缓存 .NET
C# 一分钟浅谈:Blazor Server 端开发
Blazor Server 是基于 ASP.NET Core 的框架,允许使用 C# 和 Razor 语法构建交互式 Web 应用。本文介绍 Blazor Server 的基本概念、快速入门、常见问题及解决方案,帮助开发者快速上手。涵盖创建应用、基本组件、数据绑定、状态管理、跨组件通信、错误处理和性能优化等内容。
38 1
|
1月前
|
缓存 C# 开发者
C# 一分钟浅谈:Blazor Server 端开发
本文介绍了 Blazor Server,一种基于 .NET 的 Web 开发模型,允许使用 C# 和 Razor 语法构建交互式 Web 应用。文章从基础概念、创建应用、常见问题及解决方案、易错点及避免方法等方面详细讲解,帮助开发者快速上手并提高开发效率。
50 2
|
1月前
|
测试技术 Go C#
C#一分钟浅谈:ReSharper 插件增强开发效率
【10月更文挑战第25天】ReSharper 是 JetBrains 开发的一款 Visual Studio 插件,旨在提高 .NET 开发者的生产力。它通过代码分析、重构、导航等功能,帮助开发者避免常见错误,提升代码质量和开发效率。本文将通过具体代码案例,详细介绍 ReSharper 的常见功能及其应用。
42 1
|
2月前
|
JSON Kubernetes 容灾
ACK One应用分发上线:高效管理多集群应用
ACK One应用分发上线,主要介绍了新能力的使用场景
|
1月前
|
C# Python
使用wxpython开发跨平台桌面应用,对wxpython控件实现类似C#扩展函数处理的探究
【10月更文挑战第30天】使用 `wxPython` 开发跨平台桌面应用时,可以通过创建辅助类来模拟 C# 扩展函数的功能。具体步骤包括:1. 创建辅助类 `WxWidgetHelpers`;2. 在该类中定义静态方法,如 `set_button_color`;3. 在应用中调用这些方法。这种方法提高了代码的可读性和可维护性,无需修改 `wxPython` 库即可为控件添加自定义功能。但需要注意显式调用方法和避免命名冲突。
|
2月前
|
Kubernetes 持续交付 开发工具
ACK One GitOps:ApplicationSet UI简化多集群GitOps应用管理
ACK One GitOps新发布了多集群应用控制台,支持管理Argo CD ApplicationSet,提升大规模应用和集群的多集群GitOps应用分发管理体验。
|
2月前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
38 1