没有Kubernetes也可以玩转Dapr?

2024-01-04 10:20
文章标签 kubernetes 玩转 没有 dapr

本文主要是介绍没有Kubernetes也可以玩转Dapr?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、NameResolution组件

虽然Dapr提供了一系列的编程模型,比如服务调用、发布订阅和Actor模型等,被广泛应用的应该还是服务调用。我们知道微服务环境下的服务调用需要解决服务注册与发现、负载均衡、弹性伸缩等问题,其实Dapr在这方面什么都没做,正如上面所说,Dapr自身不提供基础设施,它将这些功能交给具体的部署平台(比如K8S)来解决。Dapr中于此相关唯有一个简单得不能再简单的NameResolution组件而已。

从部署的角度来看,Dapr的所有功能都体现在与应用配对的Sidecar上。我们进行服务调用得时候只需要指定服务所在得目标应用的ID(AppID)就可以了。服务请求(HTTP或者gRPC)从应用转到sidecar,后者会将请求“路由”到合适的节点上。如果部署在Kubernetes集群上,如果指定了目标服务的标识和其他相关的元数据(命名空间和集群域名等),服务请求的寻址就不再是一个问题。实际上NameResolution组件体现的针对“名字(Name)”的“解析(Resolution)”解决的就是如将Dapr针对应用的标识AppID转换成基于部署环境的应用标识的问题。从dapr提供的代码来看,它目前注册了如下3种类型的NameResolution组件:

  • mdns:利用mDNS(Multicast DNS)实现服务注册与发现,如果没有显式配置,默认使用的就是此类型。由于mDNS仅仅是在小规模网络中采用广播通信实现的一种DNS,所以根本不适合正式的生成环境。
  • kubernetes:适配Kubernetes的名字解析,目前提供稳定的版本。
  • consul: 适配HashiCorp Consul的名字解析,目前最新为Alpha版本。

二、Resolver

一个注册的NameResolution组件旨在提供一个Resolver对象,该对象通过如下的接口来表示。如下面的代码片段所示,Resolver接口提供两个方法,Init方法会在应用启动的时候调用,作为参数的Metadata会携带于当前应用实例相关的元数据(包括应用标识和端口,以及Sidecar的HTTP和gRPC端口等)和针对当前NameResolution组件的配置。对于每一次服务调用,目标应用标识和命名空间等相关信息会被Sidecar封装成一个ResolveRequest 接口,并最为参数调用Resolver对象的ReolveID方法,最终得到一个于当前部署环境相匹配的表示,并利用此标识借助基础设施的利用完整目标服务的调用。

package nameresolutiontype Resolver interface {Init(metadata Metadata) errorResolveID(req ResolveRequest) (string, error)
}type Metadata struct {Properties    map[string]string `json:"properties"`Configuration interface{}
}type ResolveRequest struct {ID        stringNamespace stringPort      intData     map[string]string
}

三、模拟服务注册与负载均衡

假设我们具有一套私有的微服务平台,实现了基本的服务注册、负载均衡,甚至是弹性伸缩的功能,如果希望在这个平台上使用Dapr,我们只需要利用自定义的NameResolution组件提供一个对应的Resolver对象就可以了。我们利用一个ASP.NET Core MVC应用来模拟我们希望适配的微服务平台,如下这个HomeController利用静态字段_applications维护了一组应用和终结点列表(IP+端口)。对于针对某个应用的服务调用,我们通过轮询对应终结点的方式实现了简单的负载均衡。便于后面的叙述,我们将该应用简称为“ServiceRegistry”。

public class HomeController: Controller
{private static readonly ConcurrentDictionary<string, EndpointCollection> _applications = new();[HttpPost("/register")]public IActionResult Register([FromBody] RegisterRequest request){var appId = request.Id;var endpoints = _applications.TryGetValue(appId, out var value) ? value : _applications[appId] = new();endpoints.TryAdd(request.HostAddress, request.Port);Console.WriteLine($"Register {request.Id} =>{request.HostAddress}:{request.Port}");return Ok();}[HttpPost("/resolve")]public IActionResult Resolve([FromBody] ResolveRequest request){if (_applications.TryGetValue(request.ID, out var endpoints) && endpoints.TryGet(out var endpoint)){Console.WriteLine($"Resolve app {request.ID} =>{endpoint}");return Content(endpoint!);}return NotFound();}
}public class EndpointCollection
{private readonly List<string> _endpoints = new();private int _index = 0;private readonly object _lock = new();public bool TryAdd(string ipAddress, int port){lock (_lock){var endpoint = $"{ipAddress}:{port}";if (_endpoints.Contains(endpoint)){return false;}_endpoints.Add(endpoint);return true;}}public bool TryGet(out string? endpoint){lock (_lock){if (_endpoints.Count == 0){endpoint = null;return false;}_index++;if (_index >= _endpoints.Count){_index = 0;}endpoint = _endpoints[_index];return true;}}
}

HomeController提供了两个Action方法,Register方法用来注册应用,自定义Resolver的Init方法会调用它。另一个方法Resolve则用来完成根据请求的应用表示得到一个具体的终结点,自定义Resolver的ResolveID方法会调用它。这两个方法的参数类型RegisterRequest和ResolveRequest定义如下,后者和前面给出的同名接口具有一致的定义。两个Action都会在控制台输出相应的文字显示注册的应用信息和解析出来的终结点。

public class RegisterRequest
{public string Id { get; set; } = default!;public string HostAddress { get; set; } = default!;public int Port { get; set; }
}public class ResolveRequest
{public string ID { get; set; } = default!;public string? Namespace { get; set; }public int Port { get; }public Dictionary<string, string> Data { get; } = new();
}

四、自定义NameResolution组件

由于Dapr并不支持组件的动态注册,所以我们得将其源代码拉下来,修改后进行重新编译。这里涉及到两个git操作,dapr和components-contrib,前者为核心运行时,后者为社区驱动贡献得组件。我们将克隆下来的源代码放在同一个目录下。

image

我们将自定义的NameResolution组件命名为“svcreg”(服务注册之意),所我们在components-contrib/nameresolution目录(该目录下我们会看到上面提到的几种NameResolution组件的定义)下创建一个同名的目录,并组件代码定义在该目录下的svcreg.go文件中。如下所示的就是该NameResolution组件的完整定义。

package svcregimport ("bytes""encoding/json""errors""fmt""io/ioutil""net/http""strconv""github.com/dapr/components-contrib/nameresolution""github.com/dapr/kit/logger"
)type Resolver struct {logger           logger.LoggerregisterEndpoint stringresolveEndpoint  string
}type RegisterRequest struct {Id, HostAddress stringPort            int64
}func (resolver *Resolver) Init(metadata nameresolution.Metadata) error {var endpoint, appId, hostAddress stringvar ok bool// Extracts register & resolve endpointif dic, ok := metadata.Configuration.(map[interface{}]interface{}); ok {endpoint = fmt.Sprintf("%s", dic["endpointAddress"])resolver.registerEndpoint = fmt.Sprintf("%s/register", endpoint)resolver.resolveEndpoint = fmt.Sprintf("%s/resolve", endpoint)}if endpoint == "" {return errors.New("service registry endpoint is not configured")}// Extracts AppID, HostAddress and Portprops := metadata.Propertiesif appId, ok = props[nameresolution.AppID]; !ok {return errors.New("AppId does not exist in the name resolution metadata")}if hostAddress, ok = props[nameresolution.HostAddress]; !ok {return errors.New("HostAddress does not exist in the name resolution metadata")}p, ok := props[nameresolution.DaprPort]if !ok {return errors.New("DaprPort does not exist in the name resolution metadata")}port, err := strconv.ParseInt(p, 10, 32)if err != nil {return errors.New("DaprPort is invalid")}// Register service (application)var request = RegisterRequest{appId, hostAddress, port}payload, err := json.Marshal(request)if err != nil {return errors.New("fail to marshal register request")}_, err = http.Post(resolver.registerEndpoint, "application/json", bytes.NewBuffer(payload))if err == nil {resolver.logger.Infof("App '%s (%s:%d)' is successfully registered.", request.Id, request.HostAddress, request.Port)}return err
}func (resolver *Resolver) ResolveID(req nameresolution.ResolveRequest) (string, error) {// Invoke resolve service and get resolved target app's endpoint ("{ip}:{port}")payload, err := json.Marshal(req)if err != nil {return "", err}response, err := http.Post(resolver.resolveEndpoint, "application/json", bytes.NewBuffer(payload))if err != nil {return "", err}defer response.Body.Close()result, err := ioutil.ReadAll(response.Body)if err != nil {return "", err}return string(result), nil
}func NewResolver(logger logger.Logger) *Resolver {return &Resolver{logger: logger,}
}

如上面的代码片段所示,我们定义核心的Resolver结构,该接口除了具有一个用来记录日志的logger字段,还有两个额外的字段registerEndpoint和resolveEndpoint,分别代表ServiceRegistry提供的两个API的URL。在为Resolver结构实现的Init方法中,我们从作为参数的元数据中提取出配置,并进一步从配置中提取出ServiceRegistry的地址,并在此基础上添加路由路径“/register”和“/resolve”对Resolver结构的registerEndpoint和resolveEndpoint字段进行初始化。接下来我们从元数据中提取出AppID、IP地址和内部gRPC端口号(外部应用通过此端口调用当前应用的Sidecar),它们被封装成RegisterRequest结构之后被序列化成JSON字符串,并作为输入调用对应的Web API完成对应的服务注册。

在实现的ResolveID中,我们直接将作为参数的ResolveRequest结构序列化成JSON,调用Resolve API。响应主体部分携带的字符串就是为目标应用解析出来的终结点(IP+Port),我们直接将其作为ResolveID的返回值。

五、注册自定义NameResolution组件

自定义的NameResolution组件需要显式注册到代表Sidecar的可以执行程序daprd中,入口程序所在的源文件为dapr/cmd/daprd/main.go。我们首先按照如下的方式导入svcreg所在的包”github.com/dapr/components-contrib/nameresolution/svcreg”。

// Name resolutions.
nr "github.com/dapr/components-contrib/nameresolution"
nr_consul "github.com/dapr/components-contrib/nameresolution/consul"
nr_kubernetes "github.com/dapr/components-contrib/nameresolution/kubernetes"
nr_mdns "github.com/dapr/components-contrib/nameresolution/mdns"
nr_svcreg "github.com/dapr/components-contrib/nameresolution/svcreg"

在main函数中,我们找到用来注册NameResolution组件的那部分代码,按照其他NameResolution组件注册那样,依葫芦画瓢完成针对svcreg的注册即可。注册代码中用来提供Resolver的NewResolver函数定义在上述的svcreg.go文件中。

runtime.WithNameResolutions(nr_loader.New("svcreg", func() nr.Resolver {return nr_svcreg.NewResolver(logContrib)}),nr_loader.New("mdns", func() nr.Resolver {return nr_mdns.NewResolver(logContrib)}),nr_loader.New("kubernetes", func() nr.Resolver {return nr_kubernetes.NewResolver(logContrib)}),nr_loader.New("consul", func() nr.Resolver {return nr_consul.NewResolver(logContrib)}),
),

六、编译部署daprd.exe

到目前为止,所有的编程工作已经完成,接下来我们需要重新编译代表Sidecar的daprd.exe。从上面的代码片段可以看出,dapr的包路径都以“github.com/dapr”为前缀,所以我们需要修改go.mod文件(dapr/go.mod)将依赖路径重定向到本地目录,所以我们按照如下的方式添加了针对“github.com/dapr/components-contrib”的替换规则。

replace (go.opentelemetry.io/otel => go.opentelemetry.io/otel v0.20.0gopkg.in/couchbaselabs/gocbconnstr.v1 => github.com/couchbaselabs/gocbconnstr v1.0.5k8s.io/client => github.com/kubernetes-client/go v0.0.0-20190928040339-c757968c4c36github.com/dapr/components-contrib => ../components-contrib
)

在将当前目录切换到“dapr/cmd/daprd/”后,以命令行的方式执行“go build”后会在当前目录下生成一个daprd.exe可执行文件。现在我们需要使用这个新的daprd.exe将当前使用使用的替换掉,该文件所在的目录在“%userprofile%.dapr\bin”。

image

七、配置svcreg

我们之间已经说过,Dapr默认使用的是基于mDNS的NameResolution组件(对于的注册名为为“mdns”)。若要使我们自定义的组件“svcreg”生效,需要修改Dapr的配置文件(%userprofile%.dapr\config.yaml)。如下面的代码片段所示,我们不仅将使用的组件名称设置为“svcreg”(在dapr/cmd/daprd/main.go中注册NameResolution组件时提供的名称),还将服务注册API的URL(http://127.0.0.1:3721)放在了配置中(Resolver的Init方法提取的URL就来源于这里)。

apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:name: daprConfig
spec:
  nameResolution:component: "svcreg"configuration:endpointAddress: http://127.0.0.1:3721tracing:samplingRate: "1"zipkin:endpointAddress: http://localhost:9411/api/v2/spans

八、测试效果

我们现在编写一个Dapr应用来验证一下自定义的NameResolution组件是否有效。具有如下定义的App2是一个ASP.NET Core应用,它利用路由提供了用来进行加、减、乘、除运算的API。

 using Microsoft.AspNetCore.Mvc;using Shared;var app = WebApplication.Create(args);app.MapPost("{method}", Calculate);app.Run("http://localhost:9999");static IResult Calculate(string method, [FromBody] Input input){var result = method.ToLower() switch{"add" => input.X + input.Y,"sub" => input.X - input.Y,"mul" => input.X * input.Y,"div" => input.X / input.Y,_ => throw new InvalidOperationException($"Invalid method {method}")};return Results.Json(new Output { Result = result });}
public class Input
{public int X { get; set; }public int Y { get; set; }
}public class Output
{public int 		Result { get; set; }public DateTimeOffset 	Timestamp { get; set; } = DateTimeOffset.Now;
}

具有如下定义的App1是一个控制台程序,它利用Dapr客户端SDK调用了上诉四个API。

 using Dapr.Client;using Shared;HttpClient client = DaprClient.CreateInvokeHttpClient(appId: "app2");var input = new Input(2, 1);await InvokeAsync("add", "+");await InvokeAsync("sub", "-");await InvokeAsync("mul", "*");await InvokeAsync("div", "/");async Task InvokeAsync(string method, string @operator){var response = await client.PostAsync(method, JsonContent.Create(input));var output = await response.Content.ReadFromJsonAsync<Output>();Console.WriteLine( $"{input.X} {@operator} {input.Y} = {output.Result} ({output.Timestamp})");}

在启动ServiceRegistry之后,我们启动App2,控制台上会阐述如下的输出。从输出的NameResolution组件名称可以看出,我们自定义的svcreg正在被使用。

image

由于应用启动的时候会调用Resolver的Init方法进行注册,这一点也反映在ServiceRegistry如下所示的输出上。可以看出注册实例的AppID为”app2”,对应的终结点为“10.181.22.4:60840”。

image

然后我们再启动App1,如下所示的输出表明四次服务调用均成功完成。

image

启动的App1的应用实例同样会在ServiceRegistry中注册。而四次服务调用会导致四次针对Resolver的ResolveID方法的调用,这也体现在ServiceRegistry的输出上。

image

这篇关于没有Kubernetes也可以玩转Dapr?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/569005

相关文章

豆包 MarsCode 不允许你还没有女朋友

在这个喧嚣的世界里,爱意需要被温柔地唤醒。为心爱的她制作每日一句小工具,就像是一场永不落幕的浪漫仪式,每天都在她的心田播撒爱的种子,让她的每一天都充满甜蜜与期待。 背景 在这个瞬息万变的时代,我们都在寻找那些能让我们慢下来,感受生活美好的瞬间。为了让这份浪漫持久而深刻,我们决定为女朋友定制一个每日一句小工具。这个工具会在她意想不到的时刻,为她呈现一句充满爱意的话语,让她的每一天都充满惊喜和感动

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

什么是Kubernetes PodSecurityPolicy?

@TOC 💖The Begin💖点点关注,收藏不迷路💖 1、什么是PodSecurityPolicy? PodSecurityPolicy(PSP)是Kubernetes中的一个安全特性,用于在Pod创建前进行安全策略检查,限制Pod的资源使用、运行权限等,提升集群安全性。 2、为什么需要它? 默认情况下,Kubernetes允许用户自由创建Pod,可能带来安全风险。

容器编排平台Kubernetes简介

目录 什么是K8s 为什么需要K8s 什么是容器(Contianer) K8s能做什么? K8s的架构原理  控制平面(Control plane)         kube-apiserver         etcd         kube-scheduler         kube-controller-manager         cloud-controlle

【Kubernetes】K8s 的安全框架和用户认证

K8s 的安全框架和用户认证 1.Kubernetes 的安全框架1.1 认证:Authentication1.2 鉴权:Authorization1.3 准入控制:Admission Control 2.Kubernetes 的用户认证2.1 Kubernetes 的用户认证方式2.2 配置 Kubernetes 集群使用密码认证 Kubernetes 作为一个分布式的虚拟

秒变高手:玩转CentOS 7软件更换的方法大全

在 CentOS 7 中更换软件源可以通过以下步骤完成。更换源可以加快软件包的下载速度,特别是当默认源速度较慢时。以下是详细步骤: 前言 为了帮助您解决在使用CentOS 7安装不了软件速度慢的问题,我们推出了这份由浪浪云赞助的教程——“CentOS7如何更换软件源加快下载速度”。 浪浪云,以他们卓越的弹性计算、云存储和网络服务受到广泛好评,他们的支持和帮助使得我们可以将最前沿的技术知识分

kubernetes集群部署Zabbix监控平台

一、zabbix介绍 1.zabbix简介 Zabbix是一个基于Web界面的分布式系统监控的企业级开源软件。可以监视各种系统与设备的参数,保障服务器及设备的安全运营。 2.zabbix特点 (1)安装与配置简单。 (2)可视化web管理界面。 (3)免费开源。 (4)支持中文。 (5)自动发现。 (6)分布式监控。 (7)实时绘图。 3.zabbix的主要功能

【Kubernetes】常见面试题汇总(三)

目录 9.简述 Kubernetes 的缺点或当前的不足之处? 10.简述 Kubernetes 相关基础概念? 9.简述 Kubernetes 的缺点或当前的不足之处? Kubernetes 当前存在的缺点(不足)如下: ① 安装过程和配置相对困难复杂; ② 管理服务相对繁琐; ③ 运行和编译需要很多时间; ④ 它比其他替代品更昂贵; ⑤ 对于简单的应用程序来说,可能不

安装SQL2005后SQL Server Management Studio 没有出来的解决方案

一种情况,在安装 sqlServer2005 时 居然出现两个警告: 1 Com+ 目录要求 2 Edition change check 郁闷!网上说出现两个警告,是肯定装不成功的!我抱着侥幸的态度试了下,成功了。 安装成功后,正准备 “ 仅工具、联机丛书和示例(T)” 但是安装不了,他提示我“工作站组件”安装过了对现有组件无法更新或升级。 解决办法: 1 打开“控