├── README.md └── pkg ├── actors.md ├── components.md ├── concurrency_fswatcher_logger.md ├── configuration.md ├── health.md ├── http01.md ├── http02.md ├── injector.md ├── messaging.md ├── middleware.md ├── operator.md ├── placement.md ├── sentry01.md ├── sentry02.md └── validation_scopes_signals.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## dapr文档说明 3 | 4 | 本项目主要用于了解和精读dapr,方便大家更好的理解dapr 5 | -------------------------------------------------------------------------------- /pkg/components.md: -------------------------------------------------------------------------------- 1 | apis、client、http、metrics、proto、components、diagnostics以及testing包还没有进行源码阅读 2 | 3 | 本文针对components讲解, 首先针对整个components的加载和启动流程,以middleware/http为例,做一个整体流程的闭环梳理,然后在针对components每个类别做一个注册和使用的讲解。重点在components的加载、启动和使用流程讲解. 4 | 5 | 6 | 1. 针对components中的middleware/http类别组件,做一个整体的闭环梳理; 7 | 2. 在针对components包中的各个组件列表,进行注册源码阅读。 8 | 9 | ## middleware/http闭环 10 | 11 | dapr为了使业务方可以可插拔使用任意组件类型和实例,采用了初始化全部一次性加载,然后业务方按需使用原则,进行创建。那么初始化加载过程, 即为组件**注册**过程;业务方通过yaml配置组件,则称为组件**创建**过程; 12 | 13 | ### 组件注册 14 | 15 | 组件注册,即为dapr runtime初始化阶段,一次性注册所有组件类型和实例到内存中。这个过程是通过`cmd/daprd/main.go`和`contrib-components`两部分相结合完成的, 在contrib-components中的middleware目前只有http,该middleware也是在fasthttp中装载拦截器链表使用。 16 | 17 | 下面先介绍contrib-components的middleware注册过程: 18 | 19 | ```golang 20 | // cmd/daprd/main.go: main函数中加载所有components, 其中就有middleware的组件加载过程 21 | // 22 | // 下面uppercase, oauth2, oauth2clientcredentials, ratelimit, bearer和opa 23 | // 这六种middleware/http拦截器,用于在处理dapr runtime接收外部http请求时,对请求数据进行拦截处理 24 | // 25 | // 这里注意两个方法:WithHTTPMiddleware和New 26 | runtime.WithHTTPMiddleware( 27 | http_middleware_loader.New("uppercase", func(metadata middleware.Metadata) http_middleware.Middleware { 28 | return func(h fasthttp.RequestHandler) fasthttp.RequestHandler { 29 | return func(ctx *fasthttp.RequestCtx) { 30 | body := string(ctx.PostBody()) 31 | ctx.Request.SetBody([]byte(strings.ToUpper(body))) 32 | h(ctx) 33 | } 34 | } 35 | }), 36 | http_middleware_loader.New("oauth2", func(metadata middleware.Metadata) http_middleware.Middleware { 37 | handler, _ := oauth2.NewOAuth2Middleware().GetHandler(metadata) 38 | return handler 39 | }), 40 | http_middleware_loader.New("oauth2clientcredentials", func(metadata middleware.Metadata) http_middleware.Middleware { 41 | handler, _ := oauth2clientcredentials.NewOAuth2ClientCredentialsMiddleware(log).GetHandler(metadata) 42 | return handler 43 | }), 44 | http_middleware_loader.New("ratelimit", func(metadata middleware.Metadata) http_middleware.Middleware { 45 | handler, _ := ratelimit.NewRateLimitMiddleware(log).GetHandler(metadata) 46 | return handler 47 | }), 48 | http_middleware_loader.New("bearer", func(metadata middleware.Metadata) http_middleware.Middleware { 49 | handler, _ := bearer.NewBearerMiddleware(log).GetHandler(metadata) 50 | return handler 51 | }), 52 | http_middleware_loader.New("opa", func(metadata middleware.Metadata) http_middleware.Middleware { 53 | handler, _ := opa.NewMiddleware(log).GetHandler(metadata) 54 | return handler 55 | }), 56 | ), 57 | ``` 58 | 59 | 上面http_middleware_loader.New方法用于创建一个Middleware对象={组件列表实例名称Name={uppercase, ...}, FactorMethod}, 然后再通过WithHTTPMiddleware函数式传参方式,传入到dapr runtime对象中。就如下所示: 60 | 61 | ```golang 62 | // WithHTTPMiddleware adds HTTP middleware components to the runtime. 63 | func WithHTTPMiddleware(httpMiddleware ...http.Middleware) Option { 64 | return func(o *runtimeOpts) { 65 | o.httpMiddleware = append(o.httpMiddleware, httpMiddleware...) 66 | } 67 | } 68 | ``` 69 | 70 | 再把各个类别组件,作为多参数传入到DaprRuntime的Run方法中,所以最终这里有两层,一层为组件类别列表;另一层为具体组件类别下的组件实例列表,那么整个初始化源码,如下所示: 71 | 72 | ```golang 73 | func (a *DaprRuntime) Run(opts ...Option) error { 74 | ...... 75 | var o runtimeOpts 76 | // 根据传多参,进行组件类别列表的初始化,如: state、binding、middleware、pubsub...... 77 | for _, opt := range opts { 78 | opt(&o) 79 | } 80 | 81 | // 在进行具体组件类别的实例列表注册过程, 如:middleware/http下的uppercase, oauth2, opa, ratelimit...... 82 | err := a.initRuntime(&o) 83 | if err != nil { 84 | return err 85 | } 86 | ...... 87 | } 88 | 89 | func (a *DaprRuntime) initRuntime(opts *runtimeOpts) error { 90 | ...... 91 | a.pubSubRegistry.Register(opts.pubsubs...) 92 | a.secretStoresRegistry.Register(opts.secretStores...) 93 | a.stateStoreRegistry.Register(opts.states...) 94 | a.bindingsRegistry.RegisterInputBindings(opts.inputBindings...) 95 | a.bindingsRegistry.RegisterOutputBindings(opts.outputBindings...) 96 | // 这里就是针对middleware/http的各个实例进行初始化注册过程 97 | // 98 | // 这里的opts.httpMiddleware...,就是前面的使用http_middleware_loader.New方法初始化的组件实例列表 99 | // 然后再把所有初始化的组件实例列表注册到dapr runtime内存中 100 | a.httpMiddlewareRegistry.Register(opts.httpMiddleware...) 101 | ...... 102 | // 业务方加载yaml的创建和监听配置过程 103 | go a.processComponents() 104 | ...... 105 | // 业务方对middleware/http各个组件实例的创建和监听配置过程 106 | pipeline, err := a.buildHTTPPipeline() 107 | ...... 108 | } 109 | ``` 110 | 111 | 针对`a.httpMiddlewareRegistry.Register`各个实例注册过程,就是components/middeware/http的组件实例列表注册过程 112 | 113 | ```golang 114 | type ( 115 | // Middleware 要注册的middleware/http组件实例类型,包括组件实例名称, 如oauth2, uppercase,ratelimit等 116 | // 和初始化组件实例所需要的配置参数 117 | // 118 | // 这里重点讲解下FactoryMethod作用: 119 | // 初始化http_middleware.Middleware实例,可能需要配置参数,拿oauth2组件实例来说: 120 | // 当http header中的state参数值与目标state不匹配时,表示这个请求非法,可以重定向到指定的URL,或者 121 | // 通过http传入的code,和目标state,token_url, 或者auth_url, 生成一个新的token 122 | // 那么这些重定向其他服务,或者参数校验都需要借助于业务方在使用某个组件实例时,通过component yaml文件指定.spec.metadata来创建component实例对象 123 | // 而业务方使用过程,传入的yaml组件对象如下所示: 124 | /* 125 | apiVersion: dapr.io/v1alpha1 126 | kind: Component 127 | metadata: 128 | name: oauth2 129 | spec: 130 | ignoreErrors: true 131 | version: v1 132 | type: middleware.http.oauth2 133 | metadata: 134 | - name: clientID 135 | value: "xxx" 136 | - name: clientSecret 137 | value: "xxx" 138 | - name: scopes 139 | value: "*" 140 | - name: authURL 141 | value: "xxx" 142 | - name: tokenURL 143 | value: "xxx" 144 | - name: redirectURL 145 | value: "xxx" 146 | */ 147 | // 那么上面.spec.metadata就是作为middleware/http中oauth2实例对象初始化的配置参数 148 | // 但并不是所有的组件类别,或者类别下的各个组件都需要配置参数。所以有些组件类别的FactoryMethod的初始化组件传入参数为空 149 | // 如下:FactoryMethod func() bindings.InputBinding 150 | Middleware struct { 151 | Name string 152 | FactoryMethod func(metadata middleware.Metadata) http_middleware.Middleware 153 | } 154 | 155 | // Registry 作为组件实例初始化注册和业务方使用组件实例的创建使用接口 156 | Registry interface { 157 | Register(components ...Middleware) 158 | Create(name, version string, metadata middleware.Metadata) (http_middleware.Middleware, error) 159 | } 160 | 161 | // httpMiddlewareRegistry 作为Registry实现类,用来存储middleware/http各个组件 162 | httpMiddlewareRegistry struct { 163 | middleware map[string]func(middleware.Metadata) http_middleware.Middleware 164 | } 165 | ) 166 | 167 | // New 创建一个组件实例 168 | func New(name string, factoryMethod func(metadata middleware.Metadata) http_middleware.Middleware) Middleware { 169 | return Middleware{ 170 | Name: name, 171 | FactoryMethod: factoryMethod, 172 | } 173 | } 174 | 175 | // NewRegistry 返回一个middle/http实现类实例对象 176 | func NewRegistry() Registry { 177 | return &httpMiddlewareRegistry{ 178 | middleware: map[string]func(middleware.Metadata) http_middleware.Middleware{}, 179 | } 180 | } 181 | 182 | // Register 把daprd main初始化加载所有的middeware/http组件实例列表,并放入到daprRuntime的initRuntime进行注册,并最终调用Register完成oauth2, uppercase, ratelimit, opa等组件实例的注册过程 183 | func (p *httpMiddlewareRegistry) Register(components ...Middleware) { 184 | // 遍历middleware列表,注册到httpMiddlewareRegistry的middleware map内存中 185 | for _, component := range components { 186 | p.middleware[createFullName(component.Name)] = component.FactoryMethod 187 | } 188 | } 189 | 190 | // createFullName 组件实例唯一名称,它通过与路径保持一致,来达到唯一性 191 | // 比如: 192 | // middeware.http.upper,这个作为类型名称,而uppercase作为实例对象名 193 | // 在业务方通过component或者configuration CRDs全局配置中的.spec.httpPipeline列表来创建实例并使用该组件实例 194 | func createFullName(name string) string { 195 | return strings.ToLower("middleware.http." + name) 196 | } 197 | ``` 198 | 199 | 上面这一节以component-contrib中的middleware为例,介绍了dapr runtime对所有middleware/http组件实例列表的初始化和注册过程,接下来,我们再看业务方动态使用和加载middleware/http实例的使用过程。 200 | 201 | ### 组件创建 202 | 203 | 首先,先回顾下前面文章说到的dapr-system命名空间下dapr-operator服务提供的功能。它用于接收dapr CRDs创建dapr service,以及处理CRDs的自定义参数特性,并部署serivice和deployment。同时也对dapr runtime提供grpc服务,获取component列表、获取configuration配置、获取subscriptions、以及当component配置更新时主动通过grpc stream流推送给dapr runtime进行即使的更新和动态加载过程。 204 | 205 | 那么所有组件的创建过程则和dapr-operator强相关, 它通过拉取以及监听所有CRDs的动态变化,来获取yaml配置实现业务方的组件动态加载和删除、修改能力。搞清楚这点后,我们再看组件的整体创建过程 206 | 207 | 前面我们在daprRuntime的initRuntime方法中看到了下面两行代码,至于这两行的区别是什么,后面再说。目前只需要知道这两个都是针对业务需要的组件,进行创建。这里先讲解buildHTTPPipeline方法,它是对middlewear/http各个组件实例的创建过程, 而上文configuration文章已经对buildHTTPPipeline方法进行了分析 208 | 209 | ```golang 210 | // 业务方加载yaml的创建和监听配置过程 211 | go a.processComponents() 212 | ...... 213 | // 业务方对middleware/http各个组件实例的创建和监听配置过程 214 | pipeline, err := a.buildHTTPPipeline() 215 | ...... 216 | ``` 217 | 218 | 我们只需要关注buildHTTPPipeline中的两行代码: 219 | 220 | ```golang 221 | // 如果configuration yaml配置文件中自定义了一种dapr runtime不支持的组件类型或者具体的组件实例,则直接报错,说该组件暂不支持 222 | component := a.getComponent(middlewareSpec.Type, middlewareSpec.Name) 223 | ...... 224 | // 然后再使用前面httpMiddlewareRegistry初始化好的Registry实例对象,进行创建。该Registry实例中已经注册了所有middlwware/http支持的组件实例 225 | handler, err := a.httpMiddlewareRegistry.Create(middlewareSpec.Type, middle wareSpec.Version, 226 | middleware.Metadata{Properties: a.convertMetadataItemsToProperties(component.Spec.Metadata)}) 227 | ``` 228 | 229 | 接下来,就针对业务方要加载middleware/http各个实例的创建过程: 230 | 231 | ```golang 232 | // Create 查找在httpMiddlewareRegistry中是否存在name和对应的version,如果存在,则把从dapr-system命名空间下通过dapr-operator服务拿到的component yaml配置下的.spec.metdata给到该实例组件,并初始化该实例 233 | // 234 | // 这样处理后,middleware/http中的各个组件实例,就可以通过pkg/http创建http服务时通过拦截器链表来对外提供http服务 235 | func (p *httpMiddlewareRegistry) Create(name, version string, metadata middleware.Metadata) (http_middleware.Middleware, error) { 236 | // 注意,创建服务过程中,是通过middle.http.uppercase/version作为key 237 | if method, ok := p.getMiddleware(name, version); ok { 238 | return method(metadata), nil 239 | } 240 | return nil, errors.Errorf("HTTP middleware %s/%s has not been registered", name, version) 241 | } 242 | 243 | // getMiddleware 就是查找组件实例是否存在,这个传入的name就是configuration配置文件中的type,它是完整的路径,唯一性 244 | // middleware.http.uppercase 245 | func (p *httpMiddlewareRegistry) getMiddleware(name, version string) (func(middleware.Metadata) http_middleware.Middleware, bool) { 246 | nameLower := strings.ToLower(name) 247 | versionLower := strings.ToLower(version) 248 | // 这里比较有意思的一个点:key与版本号有关系 249 | // dapr 认为版本号为空,v0或者v1的版本,可以默认为空。表示不参与版本控制 250 | // 当版本号大于等于v2时,则需要强制带上版本号,则有具体实现就有了版本概念 251 | middlewareFn, ok := p.middleware[nameLower+"/"+versionLower] 252 | if ok { 253 | return middlewareFn, true 254 | } 255 | if components.IsInitialVersion(versionLower) { 256 | middlewareFn, ok = p.middleware[nameLower] 257 | } 258 | return middlewareFn, ok 259 | } 260 | ``` 261 | 262 | 这样,我们就完成了业务方加载和动态使用configuration具体实例过程。 263 | 264 | ## 其他components的注册过程 265 | 266 | 接下来,对其他component其他类别进行源码介绍和分析。通过上面middleware/http的注册和创建介绍,就比较容易理解其他components类型的注册和创建流程 267 | 268 | ### bindings 269 | 270 | 下面是针对InputBinding和OutputBinding,输入资源绑定和输出资源绑定,分别就对应着InputBinding的组件实例化和Outputbinding组件的实例化注册和创建过程,流程和middleware是完全一样,只是Registry接口多了一个组件实例是否存在的方法:`HasXXXBinding` 271 | 272 | ```golang 273 | type ( 274 | // InputBinding is an input binding component definition. 275 | InputBinding struct { 276 | Name string 277 | FactoryMethod func() bindings.InputBinding 278 | } 279 | 280 | // OutputBinding is an output binding component definition. 281 | OutputBinding struct { 282 | Name string 283 | FactoryMethod func() bindings.OutputBinding 284 | } 285 | 286 | // Registry is the interface of a components that allows callers to get registered instances of input and output bindings 287 | Registry interface { 288 | RegisterInputBindings(components ...InputBinding) 289 | RegisterOutputBindings(components ...OutputBinding) 290 | HasInputBinding(name, version string) bool 291 | HasOutputBinding(name, version string) bool 292 | CreateInputBinding(name, version string) (bindings.InputBinding, error) 293 | CreateOutputBinding(name, version string) (bindings.OutputBinding, error) 294 | } 295 | 296 | bindingsRegistry struct { 297 | inputBindings map[string]func() bindings.InputBinding 298 | outputBindings map[string]func() bindings.OutputBinding 299 | } 300 | ) 301 | ``` 302 | 303 | ### nameresolution 304 | 305 | 与middleware组件的注册和创建完全一样,只是nameresolution用于名字服务,用来解析appid||namespace,获取被调服务地址 306 | 307 | ### pubsub 308 | 309 | 同上 310 | 311 | ### secretstore 312 | 313 | 同上 314 | 315 | ### state 316 | 317 | 同上 318 | 319 | 但是state数据存储,需要使用到key,那么如何保证key的唯一性,这里就使用了state config来实现。接下来就是对state key的存储。目前key的存储,有四种方式: 320 | 321 | 1. 传入的key; 322 | 2. storeName||key 323 | 3. appId||key 324 | 4. 自定义的key前缀 325 | 326 | 至于采用那种方式作为state的key,则取决于component配置中的state.XXX 中的.spec.metdata中的keyPrefix值来决定的,keyPrefix值如下所示: 327 | 1. appid, 则state key最终存储的key为:appid||key; 328 | 2. none, 则state key最终存储的key为: key; 329 | 3. name, 则state key最终存储的key为:name||key; 330 | 4. 自定义值,则state key最终存储的key为: keyPrefix值||key. 331 | 332 | yaml文件如下所示: 333 | 334 | ```yaml 335 | apiVersion: dapr.io/v1alpha1 336 | kind: Component 337 | metadata: 338 | name: statestore01 339 | spec: 340 | type: state.redis 341 | version: v1 342 | metadata: 343 | - keyPrefix: name 344 | - name: redisHost 345 | value: localhost:6379 346 | - name: redisPassword 347 | value: "" 348 | - name: actorStateStore 349 | value: "true" 350 | ``` 351 | 352 | 接下来的源码分析就是针对上面的流程: 353 | 354 | ```golang 355 | const ( 356 | // 也就是components中的state类型 keyPrefix存储的state key前缀 357 | strategyKey = "keyPrefix" 358 | 359 | // state key存储前缀的三种值,用来表示key的唯一性 360 | strategyAppid = "appid" 361 | strategyStoreName = "name" 362 | strategyNone = "none" 363 | // 如果配置文件中没有keyPrefix,则使用appid作为state key的前缀 364 | strategyDefault = strategyAppid 365 | 366 | daprSeparator = "||" 367 | ) 368 | 369 | // 存储dapr集群中所有业务components state实例对象 370 | // 371 | // map key作为.metdata.name, 也就是组件类型名称,而不是具体组件名称。 372 | // 所以这里的key,是针对state组件类型名称的。只有一种statestore 373 | // 374 | // 而不能针对每个state具体实例单独设置key 375 | var statesConfiguration = map[string]*StoreConfiguration{} 376 | 377 | type StoreConfiguration struct { 378 | keyPrefixStrategy string 379 | } 380 | 381 | // SaveStateConfiguration 解析components中的.spec.metdata 前缀key值,并写入到statesConfiguration内存中 382 | func SaveStateConfiguration(storeName string, metadata map[string]string) { 383 | // 如果component的statestore配置中,不带keyPrefix值, 则key使用appid值作为key的前缀 384 | strategy := metadata[strategyKey] 385 | strategy = strings.ToLower(strategy) 386 | if strategy == "" { 387 | strategy = strategyDefault 388 | } 389 | 390 | // 如果.spec.metadata中存在keyPrefix,则表示使用指定的key作为前缀,可以自定义key; 391 | statesConfiguration[storeName] = &StoreConfiguration{keyPrefixStrategy: strategy} 392 | } 393 | 394 | // GetModifiedStateKey 通过传入参数,来构建state存储key 395 | func GetModifiedStateKey(key, storeName, appID string) string { 396 | // 通过yaml配置实例名称,来获取stateConfiguration 397 | stateConfiguration := getStateConfiguration(storeName) 398 | // 如果是strategyNone,则使用原生的key 399 | // 如果是strategyStoreName,则使用配置实例名称${storename}||key, 作为key 400 | // 如果是strategyAppid, 则使用业务APPID${appid}||key, 作为最终state存储的key 401 | // 如果上面三者都不是,则使用用户自定义的key作为state存储的最终key 402 | switch stateConfiguration.keyPrefixStrategy { 403 | case strategyNone: 404 | return key 405 | case strategyStoreName: 406 | return fmt.Sprintf("%s%s%s", storeName, daprSeparator, key) 407 | case strategyAppid: 408 | if appID == "" { 409 | return key 410 | } 411 | return fmt.Sprintf("%s%s%s", appID, daprSeparator, key) 412 | default: 413 | // 用户自定义的key前缀 414 | return fmt.Sprintf("%s%s%s", stateConfiguration.keyPrefixStrategy, daprSeparator, key) 415 | } 416 | } 417 | 418 | // GetOriginalStateKey 通过传入的state key,解析出用户传入的最初key 419 | // 当前存储的key,有keyPrefix和用户传入的key,通过||字符串连接而成,那么索引为1的就是用户传入的原始key 420 | func GetOriginalStateKey(modifiedStateKey string) string { 421 | splits := strings.Split(modifiedStateKey, daprSeparator) 422 | if len(splits) <= 1 { 423 | return modifiedStateKey 424 | } 425 | return splits[1] 426 | } 427 | 428 | // 解析statestore的唯一实例名称 429 | func getStateConfiguration(storeName string) *StoreConfiguration { 430 | c := statesConfiguration[storeName] 431 | if c == nil { 432 | c = &StoreConfiguration{keyPrefixStrategy: strategyDefault} 433 | statesConfiguration[storeName] = c 434 | } 435 | 436 | return c 437 | } 438 | ``` 439 | 440 | ## 总结 441 | 442 | 本文主要是讲解components各个类型组件以及其下的各个具体实现组件的注册和用户创建过程,然后结合middleware/http的注册和创建来详细说明其整个流程,方便用户更容易理解。最后在components/state部分分析来state key的形成过程,用户可以通过component组件中的.spec.metdata.keyPrefix来设置state key的前缀,比如,redis key前缀可以是appid(微服务appid),name(component配置实例唯一名称)、不含前缀的用户原生key,或者用户自定义的前缀key。 443 | -------------------------------------------------------------------------------- /pkg/concurrency_fswatcher_logger.md: -------------------------------------------------------------------------------- 1 | 本文主要分析三个dapr包,一个为concurrency, 另一个为fswatcher,最后一个为logger 2 | 3 | 1. concurrency用于dapr中相关的批量操作替代操作。比如:如果某个第三方软件服务提供商,不支持批量操作,则可以通过concurrency包,来执行并发批量操作。 4 | 2. fswatcher用于监听文件系统的订阅文件目录或者文件路径的变更,并告诉订阅者事件和相应地处理。比如:证书文件的监听和重新创建, 过期文件删除和重新创建新的证书,使得证书永远保证有效 5 | 3. logger作为dapr的通用模块,到处都在使用,保障日志文件的落地,包括文件的大小、日期和保留周期等策略 6 | 7 | ## concurrency 8 | 9 | 这个concurrency包,是对[limiter](https://github.com/korovkin/limiter)的简化,它最主要的作用,是对并发进行控制,比如:只允许10个请求并发,则100请求的到来,则会保证运行的goroutine数据最多等于10个,超过并发数,则等待运行队列上的其他任务执行完成后,再执行阻塞的其他等待请求任务。 10 | 11 | 下面的代码,包含三个方法: 12 | 1. NewLimiter,初始化创建一个limiter,设置请求并发数 13 | 2. Execute,把请求放入到阻塞队列或者运行队列中,等待请求任务被执行。 14 | 3. Wait,等待阻塞队列和运行队列上的所有请求任务,都处于空闲状态. 15 | 16 | ```golang 17 | // Limiter 并发控制类 18 | type Limiter struct { 19 | // 最大任务执行并发量 20 | limit int 21 | // 运行队列,长度为limit 22 | tickets chan int 23 | // 当前运行队列中,正在运行的任务数量 24 | numInProgress int32 25 | } 26 | 27 | // NewLimiter创建一个Limit并发控制实例对象 28 | func NewLimiter(limit int) *Limiter { 29 | // 如果limit输入参数不合法,直接给个默认并发任务最大值100 30 | if limit <= 0 { 31 | limit = DefaultLimit 32 | } 33 | 34 | // 初始化Limiter实例,设置运行任务队列的最大长度 35 | c := &Limiter{ 36 | limit: limit, 37 | tickets: make(chan int, limit), 38 | } 39 | 40 | // 首先生产10个票据,放入队列中,使得队列变成可运行的任务队列 41 | for i := 0; i < c.limit; i++ { 42 | c.tickets <- i 43 | } 44 | 45 | return c 46 | } 47 | 48 | // Execute 把请求任务放在阻塞队列或者运行队列中,等待被执行 49 | func (c *Limiter) Execute(job func(param interface{}), param interface{}) int { 50 | ticket := <-c.tickets 51 | atomic.AddInt32(&c.numInProgress, 1) 52 | go func(param interface{}) { 53 | defer func() { 54 | c.tickets <- ticket 55 | atomic.AddInt32(&c.numInProgress, -1) 56 | }() 57 | 58 | // run the job 59 | job(param) 60 | }(param) 61 | return ticket 62 | } 63 | 64 | // Wait 等待阻塞队列和运行队列的任务数量为空,则表示一次并发的所有请求通过多批次的运行,最终执行完成了。 65 | func (c *Limiter) Wait() { 66 | for i := 0; i < c.limit; i++ { 67 | <-c.tickets 68 | } 69 | } 70 | ``` 71 | 72 | 通过这个简单的concurrency包,在grpc和http中,在处理GetBulkState中,获取批量存储数据时,如果使用到的第三方软件存储服务提供商,不支持批量操作,则只能采用降级地方式,通过concurrency包,并发执行任务,来批量操作。这个操作并不是事务型。 73 | 74 | 这里比较有意思的一个点:[golang 下面有无下划线占位符的区别是什么?](https://mk.woa.com/q/269412/answer/71677) 75 | 76 | 我发现 `_ = <-c.tickets`, 在静态检查时不能通过,报下面的错误: S1005 77 | 78 | 如果改为`<-c.tickets`,则静态检查不会报错,但是我们的CodeCC会报需要修复吗? 79 | 80 | ```errors 81 | Running [/home/runner/golangci-lint-1.31.0-linux-amd64/golangci-lint run --out-format=github-actions] in [] ... 82 | Error: S1023: redundant `return` statement (gosimple) 83 | Error: S1005: unnecessary assignment to the blank identifier (gosimple) 84 | Error: S1005: unnecessary assignment to the blank identifier (gosimple) 85 | 86 | Error: issues found 87 | Ran golangci-lint in 19135ms 88 | ``` 89 | 90 | ## fswatcher 91 | fswatcher包实现对文件系统文件或者目录的监控,是通过[fsnotify](github.com/fsnotify/fsnotify)第三方库实现的, 当`dir`下有文件变更,包括创建、删除、改变文件属性、写入等等操作,都会发生事件订阅通知 92 | 93 | 94 | ```golang 95 | func Watch(ctx context.Context, dir string, eventCh chan<- struct{}) error { 96 | // 创建一个watcher监听对象 97 | watcher, err := fsnotify.NewWatcher() 98 | if err != nil { 99 | return errors.Wrap(err, "failed to create watcher") 100 | } 101 | defer watcher.Close() 102 | 103 | // 把要监听的目录或者文件,订阅给watcher。当有事件产生时会通过channel管道传出。并给发送给订阅的消费者。 104 | if err := watcher.Add(dir); err != nil { 105 | return errors.Wrap(err, "watcher error") 106 | } 107 | 108 | LOOP: 109 | for { 110 | select { 111 | // watch for events 112 | case event := <-watcher.Events: 113 | // 这里订阅了dir下的文件创建和文件写入订阅消息 114 | if event.Op == fsnotify.Create || event.Op == fsnotify.Write { 115 | // 校验是否为自己订阅的dir 116 | if strings.Contains(event.Name, dir) { 117 | // 当发生订阅事件通知后,等待1s,保证文件完全有时间更新完成。 118 | // 以保证可以读取到完整的数据,该库目前主要是给dapr sentry服务使用。 119 | time.Sleep(time.Second * 1) 120 | eventCh <- struct{}{} 121 | } 122 | } 123 | case <-watcher.Errors: 124 | break LOOP 125 | case <-ctx.Done(): 126 | break LOOP 127 | } 128 | } 129 | return nil 130 | } 131 | ``` 132 | 133 | 比如,在dapr/sentry服务创建过程中,会指定CA根证书和Issuer证书颁布者的证书和密钥路径,然后再监听该目录,如果该目录有文件创建或者写入,则会重启该dapr sentry服务。 134 | 135 | 下面,我们看看dapr sentry服务在启动过程中,有关fswatcher监听包的操作和使用 136 | 137 | ```golang 138 | cmd/sentry/main.go 139 | 140 | // 启动dapr sentry服务 141 | func main(){ 142 | // credsPath 表示CA机构和Issuer证书颁布者的证书和私钥文件信息,如果文件不存在则会使用CA机构进行自签署和签署 143 | configName := flag.String("config", defaultDaprSystemConfigName, "Path to config file, or name of a configuration object") 144 | // credsPath 默认证书路径:/var/run/dapr/credentials 145 | credsPath := flag.String("issuer-credentials", defaultCredentialsPath, "Path to the credentials directory holding the issuer data") 146 | // trustDomain 表示信任域,通过SPIFFE标准保护内部服务RPC不受恶意攻击 147 | trustDomain := flag.String("trust-domain", "localhost", "The CA trust domain") 148 | flag.Parse() 149 | ...... 150 | 151 | // 证书目录下三个文件,分别是CA机构公钥证书: ca.crt 152 | // Issuer证书颁布者 公钥证书:issuer.crt 和私钥证书: issuer.key 153 | issuerCertPath := filepath.Join(*credsPath, credentials.IssuerCertFilename) 154 | issuerKeyPath := filepath.Join(*credsPath, credentials.IssuerKeyFilename) 155 | rootCertPath := filepath.Join(*credsPath, credentials.RootCertFilename) 156 | 157 | // 监听程序挂掉的信息列表,当捕获到订阅的信号时,再做后续dapr sentry服务的优雅退出处理 158 | stop := make(chan os.Signal, 1) 159 | signal.Notify(stop, os.Interrupt, syscall.SIGTERM) 160 | 161 | ctx := signals.Context() 162 | config, err := config.FromConfigName(*configName) 163 | if err != nil { 164 | log.Warn(err) 165 | } 166 | config.IssuerCertPath = issuerCertPath 167 | config.IssuerKeyPath = issuerKeyPath 168 | config.RootCertPath = rootCertPath 169 | config.TrustDomain = *trustDomain 170 | 171 | // 如果是默认配置,则监听/var/run/dapr/credentials目录, 主要就是对CA机构证书、Issuer证书颁布者的公钥证书和私钥证书进行监听 172 | watchDir := filepath.Dir(config.IssuerCertPath) 173 | // 创建dapr sentry服务 174 | ca := sentry.NewSentryCA() 175 | 176 | log.Infof("starting watch on filesystem directory: %s", watchDir) 177 | issuerEvent := make(chan struct{}) 178 | ready := make(chan bool) 179 | 180 | // 启动dapr sentry服务,为其他grpc服务颁发和签署数字证书 181 | go ca.Run(ctx, config, ready) 182 | 183 | // 服务创建成功后,通过ready表示服务启动成功,之前处于阻塞状态 184 | <-ready 185 | 186 | // 服务启动成功后,开始对CA机构证书和Issuer证书和私钥文件进行监听 187 | // 188 | // 并通过issuerEvent反馈事件, 只要有订阅的事件发生,就重启dapr sentry服务重新加载证书或者创建证书 189 | go fswatcher.Watch(ctx, watchDir, issuerEvent) 190 | 191 | go func() { 192 | // 如果有监听的目录,有证书创建和写入,则重启dapr sentry服务, 这样可以保证永久有效 193 | for range issuerEvent { 194 | monitoring.IssuerCertChanged() 195 | log.Warn("issuer credentials changed. reloading") 196 | ca.Restart(ctx, config) 197 | } 198 | } 199 | ...... 200 | <-stop 201 | ...... 202 | } 203 | ``` 204 | 205 | ## logger 206 | 207 | dapr logger的默认实现方式只有一种,且目前没有对外提供注册和实现。并且采用[logrus](github.com/sirupsen/logrus), 但是目前市面上有更优秀的[zaplog](github.com/uber-go/zap),后面应该是会插件化实现log注册的。 208 | 209 | 目前dapr logger的默认实现方式,有以下几个特点: 210 | 1. 日志输出只能是终端的方式,不支持文件落地; 211 | 2. 日志输出格式,支持json或者文本输出; 212 | 3. 可以动态调整日志输出级别。 213 | 4. 日志支持的级别,包括:debug, info, warn, error, fatal 214 | 5. 同时增加了日志的类型(默认为log,可以设置为request请求类型日志等),设置日志app服务名称(如:dapr), 主机名,dapr版本等参数。 215 | 216 | 这里只简单地介绍下如何使用, 下面这个例子,首先设置日志相关参数Options,并调整日志输出级别。注意,当设置日志参数后,需要应用到所有的日志服务实例中,方法:**logger.ApplyOptionsToLoggers** 217 | 218 | ```golang 219 | package main 220 | 221 | import "github.com/dapr/dapr/pkg/logger" 222 | 223 | func main() { 224 | option := logger.DefaultOptions() 225 | option.SetOutputLevel("error") 226 | option.JSONFormatEnabled = true 227 | instance := logger.NewLogger("dapr") 228 | logger.ApplyOptionsToLoggers(&option) 229 | instance.Info("dapr-info ", "dapr hello,world") 230 | instance.Warn("dapr-warn ", "dapr hello,world") 231 | instance.Error("dapr-error ", "dapr hello,world") 232 | } 233 | ``` 234 | 235 | 目前在dapr中,日志的初始化主要在下面的代码目录中: 236 | 237 | ```golang 238 | ./cmd/sentry/main.go:43: loggerOptions := logger.DefaultOptions() 239 | ./cmd/operator/main.go:50: loggerOptions := logger.DefaultOptions() 240 | ./cmd/injector/main.go:65: loggerOptions := logger.DefaultOptions() 241 | ./cmd/placement/config.go:70: cfg.loggerOptions = logger.DefaultOptions() 242 | ./pkg/runtime/cli.go:50: loggerOptions := logger.DefaultOptions() 243 | ``` 244 | 245 | 前四个结果分别是,dapr-system命名空间下的dapr-sentry, dapr-operator, dapr-sidecar-injector和dapr-placement四个POD系统服务。 246 | 247 | 最后一个是业务POD中的daprd runtime sidecar容器服务。 248 | 249 | 所以在dapr产品中,所有的服务都是使用了dapr/pkg/logger包下的日志组件,来实现自己的日志输出能力。 250 | 251 | ## 总结 252 | 253 | 通过前面介绍的concurrency、fswatcher和logger,分别实现: 254 | 1. dapr http/grpc的GetBulkState批量操作能力(当第三方软件服务提供商不支持批量操作时,则通过单个批量执行达到);而concurrency用来控制并发能力 255 | 2. fswatcher借助第三方库,来实现对CA根证书、Issuer证书颁布者的证书和私钥进行文件监控。当证书发生变化时,重启dapr sentry服务,并更新CA根证书和Issuer证书颁布者和的证书和私钥; 256 | 3. logger组件用于为dapr-system命名空间下的四个系统服务提供日志能力,并且为业务pod中的daprd runtime sidecar容器服务提供日志能力 257 | 258 | **留下一个问题**: 当dapr sentry系统服务重启后,则原来这个服务已经为grpc外部服务颁布了数字签名,这些签名失效了吗?怎么办? 259 | -------------------------------------------------------------------------------- /pkg/configuration.md: -------------------------------------------------------------------------------- 1 | 目前还剩下apis、config、grpc、messages、channel、messaging、client、http、metrics、proto、components、diagnostics和middleware,以及testing包还没有进行源码阅读 2 | 3 | credentials在dapr sentry有引用 4 | messages在dapr messages包中表示errors列表,显示目前可以使用的错误变量列表 5 | 6 | ## config 7 | 8 | config是对dapr configuration yaml配置的解析和参数校验, 完整的configuration yaml配置文件所有参数如下所示: 9 | 10 | ```yaml 11 | apiVersion: dapr.io/v1alpha1 12 | kind: Configuration 13 | metadata: 14 | name: secretappconfig 15 | spec: 16 | secrets: 17 | scopes: 18 | - storeName: "local" 19 | defaultAccess: "allow" 20 | allowedSecrets: ["daprsecret","redissecret"] 21 | deniedSecrets: ["xxx", "xxx"] 22 | metric: 23 | enabled: false 24 | tracing: 25 | samplingRate: 0.8 26 | stdout: true 27 | zipkin: 28 | endpointAddress: "127.0.0.1:XXXX" 29 | mtls: 30 | allowedClockSkew: "xx" 31 | enabled: true 32 | workloadCertTTL: "xxx" 33 | httpPipeline: 34 | handlers: 35 | - name: "xxx" 36 | selector: 37 | fields: 38 | - field: "xxx" 39 | value: "xxx" 40 | type: "xxx" 41 | version: "xxx" 42 | accessControl: 43 | defaultAction: "xx" 44 | policies: 45 | defaultAction: "xxx" 46 | policies: 47 | - appId: "xxx" 48 | defaultAction: "xxx" 49 | namespace: "xxx" 50 | trustDomain: "xxx" 51 | operations: 52 | - action: "xxx" 53 | httpVerb: ["xxx", "xxx"] 54 | name: "xxx" 55 | trustDomain: "xxx" 56 | ``` 57 | 58 | 针对上面这个完整的配置文件, 进行解析并构建成内存对象,接下来,就针对这个yaml文件进行解析 59 | 60 | ```golang 61 | // LoadDefaultConfiguration 加载创建一个Configuration实例对象 62 | func LoadDefaultConfiguration() *Configuration { 63 | // 它主要是对configuration yaml CRDs文件的spec数据部分进行初始化创建 64 | // 包括:tracing, metric和accessControl 65 | // 而并没有对mtls, serets两个数据部分进行初始化 66 | return &Configuration{ 67 | Spec: ConfigurationSpec{ 68 | // tracing,是指zipkin分布式跟踪采样率和采集服务端口地址 69 | TracingSpec: TracingSpec{ 70 | SamplingRate: "", 71 | }, 72 | // metric 指标采集是否开启 73 | MetricSpec: MetricSpec{ 74 | Enabled: true, 75 | }, 76 | // accessControl 访问控制,用于内部dapr runtime之间的访问控制,并与dapr sentry中的spiffed标准结合 77 | // 目前所有的访问或者secrets,是否可以通过,由两个值控制,一个allow,一个deny。再无其他 78 | AccessControlSpec: AccessControlSpec{ 79 | DefaultAction: AllowAccess, 80 | TrustDomain: "public", 81 | }, 82 | }, 83 | } 84 | } 85 | 86 | // LoadStandaloneConfiguration 加载dapr configuration配置文件, 并对参数进行校验和排序 87 | func LoadStandaloneConfiguration(config string) (*Configuration, string, error) { 88 | // 校验config path configuration.yaml文件是否存在 89 | _, err := os.Stat(config) 90 | if err != nil { 91 | return nil, "", err 92 | } 93 | 94 | // 读取config文件数据流 95 | b, err := ioutil.ReadFile(config) 96 | if err != nil { 97 | return nil, "", err 98 | } 99 | 100 | // 通过LoadDefaultConfiguration方法创建一个Configuration空对象 101 | conf := LoadDefaultConfiguration() 102 | // 在把config文件内容反序列化到configuration对象中 103 | err = yaml.Unmarshal(b, conf) 104 | if err != nil { 105 | return nil, string(b), err 106 | } 107 | // 并对configuration.yaml文件内容进行校验,主要就是spec数据部分内容校验 108 | err = sortAndValidateSecretsConfiguration(conf) 109 | if err != nil { 110 | return nil, string(b), err 111 | } 112 | 113 | // 返回 114 | return conf, string(b), nil 115 | } 116 | 117 | // sortAndValidateSecretsConfiguration 方法用于对configuration配置对象的secrets部分数据校验 118 | func sortAndValidateSecretsConfiguration(conf *Configuration) error { 119 | // 目前只针对spec部分的serets字段校验 120 | scopes := conf.Spec.Secrets.Scopes 121 | // set类似于map[string]struct{},集合校验key是否存在 122 | set := sets.NewString() 123 | // 遍历secrets中的每个scope,并对旗下的数据进行校验 124 | for _, scope := range scopes { 125 | // 验证scope的storename是否已经存在,如果已存在,说明存在冲突报错 126 | if set.Has(scope.StoreName) { 127 | return errors.Errorf("%q storeName is repeated in secrets configuration", s cope.StoreName) 128 | } 129 | // 校验scope下默认访问权限如果不为空,那么DefaultAccess必须是allow或者deny二者其一 130 | // 否则,报错 131 | if scope.DefaultAccess != "" && 132 | !strings.EqualFold(scope.DefaultAccess, AllowAccess) && 133 | !strings.EqualFold(scope.DefaultAccess, DenyAccess) { 134 | return errors.Errorf("defaultAccess %q can be either allow or deny", scope. DefaultAccess) 135 | } 136 | // 临时storename写入到内存中 137 | set.Insert(scope.StoreName) 138 | 139 | // 并对scope下的allowedsecrets和deniedsecrets分别排序 140 | // 这个接下来会用于二分法查找 141 | sort.Strings(scope.AllowedSecrets) 142 | sort.Strings(scope.DeniedSecrets) 143 | } 144 | 145 | return nil 146 | } 147 | 148 | // LoadKubernetesConfiguration 用于从k8s获取configuration CRDs对象数据 149 | func LoadKubernetesConfiguration(config, namespace string, operatorClient operatorv1pb. OperatorClient) (*Configuration, error) { 150 | // 在k8s dapr-system命名空间下,dapr-operator服务对外提供了grpc服务,dapr runtime可以通过GetConfiguration方法获取指定namespace和name的configuration配置对象 151 | resp, err := operatorClient.GetConfiguration(context.Background(), &operatorv1pb.Ge tConfigurationRequest{ 152 | Name: config, 153 | Namespace: namespace, 154 | }, grpc_retry.WithMax(operatorMaxRetries), grpc_retry.WithPerRetryTimeout(operatorC allTimeout)) 155 | if err != nil { 156 | return nil, err 157 | } 158 | if resp.GetConfiguration() == nil { 159 | return nil, errors.Errorf("configuration %s not found", config) 160 | } 161 | // 创建一个空对象Configuration,并通过yaml反序列化把grpc 得到的数据流解析到conf对象中 162 | conf := LoadDefaultConfiguration() 163 | err = json.Unmarshal(resp.GetConfiguration(), conf) 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | // 并对spec部分的serets数据对象进行校验和验证 169 | err = sortAndValidateSecretsConfiguration(conf) 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | return conf, nil 175 | } 176 | 177 | // IsSecretAllowed 校验key值在secret下的scope中是否被允许 178 | func (c SecretsScope) IsSecretAllowed(key string) bool { 179 | // 如果scope下的allowedserets包含key,则表示storename允许该key通过 180 | if len(c.AllowedSecrets) != 0 { 181 | return containsKey(c.AllowedSecrets, key) 182 | } 183 | 184 | // 如果scope下的deniedserets包含可以,则表示storename拒绝该key通过 185 | if deny := containsKey(c.DeniedSecrets, key); deny { 186 | return !deny 187 | } 188 | 189 | // 如果该key既不属于allow,也不属于deny,则看看scope的默认访问测试是否为拒绝 190 | // 如果是拒绝,表示该key不允许通过 191 | // 否则,允许该key通过。 192 | return !strings.EqualFold(c.DefaultAccess, DenyAccess) 193 | } 194 | 195 | // containsKey 因为scope下的AllowedSerets和DeniedSerets已经是排序OK的,则通过二分法查找key是否存在 196 | func containsKey(s []string, key string) bool { 197 | // 二分法超着key是否存在,并返回key所在的索引位置 198 | // 如果key不存在,则返回要插入的位置 199 | index := sort.SearchStrings(s, key) 200 | 201 | // 如果返回的索引小于s长度,再看索引位置是否等于key,如果是表示在s列表中存在key。 202 | return index < len(s) && s[index] == key 203 | } 204 | 205 | *************************************************** 206 | apiVersion: dapr.io/v1alpha1 207 | kind: Configuration 208 | metadata: 209 | name: allowlistsappconfig 210 | spec: 211 | accessControl: 212 | defaultAction: deny 213 | trustDomain: "public" 214 | policies: 215 | - appId: "allowlists-caller" 216 | defaultAction: deny 217 | trustDomain: 'public' 218 | namespace: "dapr-tests" 219 | operations: 220 | - name: /opAllow 221 | httpVerb: ['POST', 'GET'] 222 | action: allow 223 | - name: /opDeny 224 | httpVerb: ["*"] 225 | action: deny 226 | *************************************************** 227 | 228 | // ParseAccessControlSpec 解析.spec.accesscontrol部分数据, 并校验数据是否合理 229 | func ParseAccessControlSpec(accessControlSpec AccessControlSpec, protocol string) (*Acc essControlList, error) { 230 | // accessControl是一个dapr runtime之间的RPC访问控制策略,它通过配置和spiffed标准相结合来实现内部访问控制. 231 | 232 | // 如果accessControl下的信任域, 默认为public,默认命名空间为default 233 | // 如果trustDomain、defaultAction和appPolicies都为空,则accessControl为无效配置 234 | if accessControlSpec.TrustDomain == "" && 235 | accessControlSpec.DefaultAction == "" && 236 | len(accessControlSpec.AppPolicies) == 0 { 237 | // No ACL has been specified 238 | log.Debugf("No Access control policy specified") 239 | return nil, nil 240 | } 241 | 242 | // 这里我们把accessControl配置对象转换成AccessControlList内存对象 243 | var accessControlList AccessControlList 244 | accessControlList.PolicySpec = make(map[string]AccessControlListPolicySpec) 245 | 246 | // 如果配置对象的信任域为空,则为默认信任域public 247 | accessControlList.TrustDomain = accessControlSpec.TrustDomain 248 | if accessControlSpec.TrustDomain == "" { 249 | accessControlList.TrustDomain = DefaultTrustDomain 250 | } 251 | 252 | // 目前配置对象的action有两种,一种为allowed,一种为deny。 253 | accessControlList.DefaultAction = strings.ToLower(accessControlSpec.DefaultAction) 254 | // 如果默认动作为空,且apppolicies长度大于0,则accessControl的action为deny 255 | if accessControlSpec.DefaultAction == "" { 256 | if len(accessControlSpec.AppPolicies) > 0 { 257 | // Some app level policies have been specified but not default global actio n is set. Default to more secure option - Deny 258 | log.Warnf("No global default action has been specified. Setting default glo bal action as Deny") 259 | accessControlList.DefaultAction = DenyAccess 260 | } else { 261 | // 如果默认动作为空,且policies长度也为0,则表示默认允许通过 262 | accessControlList.DefaultAction = AllowAccess 263 | } 264 | } 265 | 266 | var ( 267 | invalidTrustDomain []string 268 | invalidNamespace []string 269 | invalidAppName bool 270 | ) 271 | // 再对accesscontrol下一层appPolicies对象进行校验和对象转换 272 | accessControlList.PolicySpec = make(map[string]AccessControlListPolicySpec) 273 | for _, appPolicySpec := range accessControlSpec.AppPolicies { 274 | // 如果appPolicies的appid为空,则需要统计appid的错误 275 | if appPolicySpec.AppName == "" { 276 | invalidAppName = true 277 | } 278 | 279 | // 如果appPolicies的信任域为空,也需要统计该错误,统计为appid的类别错误 280 | invalid := false 281 | if appPolicySpec.TrustDomain == "" { 282 | invalidTrustDomain = append(invalidTrustDomain, appPolicySpec.AppName) 283 | invalid = true 284 | } 285 | // 如果appPolices的命名空间为空,也需要统计该错误,统计为appid的类别错误 286 | if appPolicySpec.Namespace == "" { 287 | invalidNamespace = append(invalidNamespace, appPolicySpec.AppName) 288 | invalid = true 289 | } 290 | 291 | // 这个用于汇总三类错误:appId为空,信任域和命名空间为空; 292 | // 这里是通过appPolicies列表的所有汇总错误 293 | if invalid || invalidAppName { 294 | // An invalid config was found for this app. No need to continue parsing th e spec for this app 295 | continue 296 | } 297 | 298 | // 运行到这里,表示appPolicies列表下的appId、信任域和namespace参数没有错误 299 | operationPolicy := make(map[string]AccessControlListOperationAction) 300 | 301 | // 然后再针对每个appPolicy下的actions进行对象转换 302 | for _, appPolicy := range appPolicySpec.AppOperationActions { 303 | // appPolicy={name, httpVerb, action}, 如 304 | /* 305 | - name: /opAllow 306 | httpVerb: ["POST", "GET"] 307 | action: allow 308 | */ 309 | // 首先把name值转换成/opAllow和/, 前者为operationPrefix,后者为operationPostfix 310 | operation := appPolicy.Operation 311 | if !strings.HasPrefix(operation, "/") { 312 | operation = "/" + operation 313 | } 314 | operationPrefix, operationPrefix := getOperationPrefixAndPostfix(operation ) 315 | 316 | // 如果为http协议,则把operationPrefix和operationPrefix转换为小写 317 | if protocol == HTTPProtocol { 318 | operationPrefix = strings.ToLower(operationPrefix) 319 | operationPostfix = strings.ToLower(operationPostfix) 320 | } 321 | 322 | // 把路由和动作放在operationPrefix下 323 | operationActions := AccessControlListOperationAction{ 324 | OperationPostFix: operationPostfix, 325 | VerbAction: make(map[string]string), 326 | } 327 | 328 | // 针对每个请求动作,比如POST或者GET请求动作,存储动作的对应策略 329 | // 比如GET,允许;POST,允许等 330 | for _, verb := range appPolicy.HTTPVerb { 331 | operationActions.VerbAction[verb] = appPolicy.Action 332 | } 333 | 334 | // 并把动作和对应的策略存储到OperationAction中 335 | operationActions.OperationAction = appPolicy.Action 336 | 337 | // 最后在统一存储在appPolicy的前缀路由下 338 | operationPolicy[operationPrefix] = operationActions 339 | } 340 | // 然后在把appPolicies列表转换amp后的数据对象,存储到appPolicy 341 | aclPolicySpec := AccessControlListPolicySpec{ 342 | AppName: appPolicySpec.AppName, 343 | DefaultAction: appPolicySpec.DefaultAction, 344 | TrustDomain: appPolicySpec.TrustDomain, 345 | Namespace: appPolicySpec.Namespace, 346 | AppOperationActions: operationPolicy, 347 | } 348 | 349 | // 并把appId||namespace作为key,存储到accessControlList的policy策略中 350 | // 这样任何一个dapr runtime之间的RPC访问,都可以通过该key查看某个资源是否具有访问权限 351 | key := getKeyForAppID(aclPolicySpec.AppName, aclPolicySpec.Namespace) 352 | accessControlList.PolicySpec[key] = aclPolicySpec 353 | } 354 | 355 | // 如果accessControl对象中的信任域为空、或者无效appid,又或者无效命名空间,则返回错误 356 | if len(invalidTrustDomain) > 0 || len(invalidNamespace) > 0 || invalidAppName { 357 | return nil, errors.Errorf( 358 | "invalid access control spec. missing trustdomain for apps: %v, missing nam espace for apps: %v, missing app name on at least one of the app policies: %v", 359 | invalidTrustDomain, 360 | invalidNamespace, 361 | invalidAppName) 362 | } 363 | 364 | // 最终我们把accessControl配置对象,转换成了accesssControllList。它将来会用于内部dapr runtime之间的内部RPC调用访问权限控制, 资源控制粒度细。 365 | return &accessControlList, nil 366 | } 367 | 368 | // GetAndParseSpiffeID 通过grpc context上下文获取证书,并从证书中获取spiffe,这个在dapr sentry源码阅读中已经说明过, 然后在对spiffeID进行反序列化为SpifffID 369 | func GetAndParseSpiffeID(ctx context.Context) (*SpiffeID, error) { 370 | // 首先从grpc context的证书扩展字段中获取spiffed内容 371 | spiffeID, err := getSpiffeID(ctx) 372 | if err != nil { 373 | return nil, err 374 | } 375 | 376 | // spiffeID格式如下: 377 | // spiffe://trustDomain/ns/namespace/appID 378 | // 然后再把spiffeID字符串转换为标准的SpiffeID 379 | id, err := parseSpiffeID(spiffeID) 380 | return id, err 381 | } 382 | 383 | // parseSpiffeID 把spiffe://trustDomain/ns/namespace/appID转换为SpiffeID 384 | func parseSpiffeID(spiffeID string) (*SpiffeID, error) { 385 | // 如果spiffeID为空,则直接返回错误 386 | if spiffeID == "" { 387 | return nil, errors.New("input spiffe id string is empty") 388 | } 389 | 390 | // 校验spiffeID是否以spiffe://开头 391 | if !strings.HasPrefix(spiffeID, SpiffeIDPrefix) { 392 | return nil, errors.New(fmt.Sprintf("input spiffe id: %s is invalid", spiffeID)) 393 | } 394 | 395 | // The SPIFFE Id will be of the format: spiffe:/// 396 | parts := strings.Split(spiffeID, "/") 397 | if len(parts) < 6 { 398 | return nil, errors.New(fmt.Sprintf("input spiffe id: %s is invalid", spiffeID)) 399 | } 400 | 401 | // 解析spiffeID,并填充到SpiffeID对象中返回 402 | var id SpiffeID 403 | // 获取TrustDomain、Namespace和AppID,表示该证书允许访问的dapr runtime服务 404 | id.TrustDomain = parts[2] 405 | id.Namespace = parts[4] 406 | id.AppID = parts[5] 407 | 408 | return &id, nil 409 | } 410 | 411 | // getSpiffeID 从grpc context上下文的证书扩展字段中获取spiffe数据 412 | func getSpiffeID(ctx context.Context) (string, error) { 413 | var spiffeID string 414 | // p, ok = ctx.Value(peerKey{}).(*Peer) 获取Peer 415 | peer, ok := peer.FromContext(ctx) 416 | if ok { 417 | if peer == nil || peer.AuthInfo == nil { 418 | return "", errors.New("unable to retrieve peer auth info") 419 | } 420 | 421 | // 断言是否为credentials.TLSInfo 422 | tlsInfo := peer.AuthInfo.(credentials.TLSInfo) 423 | 424 | // https://www.ietf.org/rfc/rfc3280.txt 425 | oid := asn1.ObjectIdentifier{2, 5, 29, 17} 426 | 427 | // 查找每个证书中扩展字段列表中每个扩展对象ID,与标准的spiffe oid相等的扩展字段extension 428 | for _, crt := range tlsInfo.State.PeerCertificates { 429 | for _, ext := range crt.Extensions { 430 | // 如果extension的ID与OID相等,则表示该extension存储了spiffe信息 431 | if ext.Id.Equal(oid) { 432 | // 通过asn1标准协议解析extension的value值 433 | var sequence asn1.RawValue 434 | if rest, err := asn1.Unmarshal(ext.Value, &sequence); err != nil { 435 | log.Debug(err) 436 | continue 437 | } else if len(rest) != 0 { 438 | log.Debug("the SAN extension is incorrectly encoded") 439 | continue 440 | } 441 | 442 | if !sequence.IsCompound || sequence.Tag != asn1.TagSequence || sequ ence.Class != asn1.ClassUniversal { 443 | log.Debug("the SAN extension is incorrectly encoded") 444 | continue 445 | } 446 | 447 | // 把解析到的数据流转换成字符串,如果字符串前缀为spiffe:// 448 | // 则表示grpc context上下文中从证书列表中找到了spiffe 449 | for bytes := sequence.Bytes; len(bytes) > 0; { 450 | var rawValue asn1.RawValue 451 | var err error 452 | 453 | bytes, err = asn1.Unmarshal(bytes, &rawValue) 454 | if err != nil { 455 | return "", err 456 | } 457 | 458 | spiffeID = string(rawValue.Bytes) 459 | if strings.HasPrefix(spiffeID, SpiffeIDPrefix) { 460 | return spiffeID, nil 461 | } 462 | } 463 | } 464 | } 465 | } 466 | } 467 | 468 | return "", nil 469 | } 470 | 471 | // IsOperationAllowedByAccessControlPolicy 校验该请求是否可以访问指定的dapr runtime服务 472 | func IsOperationAllowedByAccessControlPolicy(spiffeID *SpiffeID, srcAppID string, inputOperation string, httpVerb common.HTTPExtension_Verb, appProtocol string, accessControlList *AccessControlList) (bool, string) { 473 | // 如果accessControlList为空,表示访问不限制,直接返回true 474 | if accessControlList == nil { 475 | // No access control list is provided. Do nothing 476 | return isActionAllowed(AllowAccess), "" 477 | } 478 | 479 | // 否则,表示accessControlList不为空,则需要进行访问权限控制查询 480 | 481 | // accessControllList的总默认动作两种:一个allow,一个为deny 482 | action := accessControlList.DefaultAction 483 | // 动作策略是全局的 484 | actionPolicy := ActionPolicyGlobal 485 | 486 | // 如果主调appid为空,表示执行默认的策略,如果accessControlList默认动作为deny,则表示不允许访问目标appid 487 | if srcAppID == "" { 488 | // Did not receive the src app id correctly 489 | return isActionAllowed(action), actionPolicy 490 | } 491 | 492 | // 如果spiffeID为空,表示主调证书空,则执行默认策略,如果accessControlList默认动作为deny,则表示不允许访问目标appid 493 | if spiffeID == nil { 494 | // Could not retrieve spiffe id or it is invalid. Apply global default action 495 | return isActionAllowed(action), actionPolicy 496 | } 497 | 498 | // 再构建形成appID||namespace, 作为key,来校验accessControlList下policy策略是否存在该key 499 | // 如果存在,则说明有针对该key的ACL权限访问控制限制 500 | key := getKeyForAppID(srcAppID, spiffeID.Namespace) 501 | appPolicy, found := accessControlList.PolicySpec[key] 502 | 503 | // 没有发现,则执行默认的访问动作,如果accessControlList的defaultAction为deny,则不允许该请求访问appid 504 | if !found { 505 | // no policies found for this src app id. Apply global default action 506 | return isActionAllowed(action), actionPolicy 507 | } 508 | 509 | // 匹配主调的信任域和ACL的访问信任域,不相同,则执行默认的动作策略 510 | if appPolicy.TrustDomain != spiffeID.TrustDomain { 511 | return isActionAllowed(action), actionPolicy 512 | } 513 | 514 | // 匹配主调的namespace与ACL控制的namespace,不相同则执行默认的动作策略 515 | if appPolicy.Namespace != spiffeID.Namespace { 516 | return isActionAllowed(action), actionPolicy 517 | } 518 | 519 | // 如果前面通过,则在进行appPolicy级别的ACL权限控制访问 520 | if appPolicy.DefaultAction != "" { 521 | // appPolicy默认策略动作为DefaultAction 522 | action = appPolicy.DefaultAction 523 | // 动作策略是appPolicy级别 524 | actionPolicy = ActionPolicyApp 525 | } 526 | 527 | // 添加路由前缀"/", 并获取prefix, postfix 528 | // “/v1/users/” prefix=/v1 postfix=/users 529 | if !strings.HasPrefix(inputOperation, "/") { 530 | inputOperation = "/" + inputOperation 531 | } 532 | inputOperationPrefix, inputOperationPostfix := getOperationPrefixAndPostfix(inputOp eration) 533 | 534 | // 如果为http请求,则把路由改为小写 535 | if appProtocol == HTTPProtocol { 536 | inputOperationPrefix = strings.ToLower(inputOperationPrefix) 537 | inputOperationPostfix = strings.ToLower(inputOperationPostfix) 538 | } 539 | 540 | // 进行appPolicy的路由前缀匹配,如果找到,则在进行动作和请求方法匹配 541 | operationPolicy, found := appPolicy.AppOperationActions[inputOperationPrefix] 542 | if found { 543 | // 路由匹配, 如果后缀路由以/*开头,则需要把/*替换为"",然后再进行后缀的前缀匹配 544 | // 如果匹配失败,则返回appPolicy级别的默认策略 545 | if strings.Contains(operationPolicy.OperationPostFix, "/*") { 546 | if !strings.HasPrefix(inputOperationPostfix, strings.ReplaceAll(operationPo licy.OperationPostFix, "/*", "")) { 547 | return isActionAllowed(action), actionPolicy 548 | } 549 | } else { 550 | // r如果不是以/*后缀结尾,则直接比较后缀路由,如果不相同则直接返回appPolicy级别的动作策略 551 | if operationPolicy.OperationPostFix != inputOperationPostfix { 552 | return isActionAllowed(action), actionPolicy 553 | } 554 | } 555 | 556 | // 就如果协议为http,则校验访问的http 访问,比如get,post等,返回该方法是否允许访问 557 | if appProtocol == HTTPProtocol { 558 | if httpVerb != common.HTTPExtension_NONE { 559 | verbAction, found := operationPolicy.VerbAction[httpVerb.String()] 560 | if found { 561 | // 发现了该访问,再把该方法的执行策略赋值给action 562 | action = verbAction 563 | } else { 564 | // 如果不存在,则校验是否具有*,则针对所有方法的访问权限 565 | verbAction, found = operationPolicy.VerbAction["*"] 566 | if found { 567 | // The verb matched the wildcard "*" 568 | action = verbAction 569 | } 570 | } 571 | } else { 572 | // 否则执行appPolicy的默认动作策略 573 | action = appPolicy.DefaultAction 574 | } 575 | } else if appProtocol == GRPCProtocol { 576 | // 如果协议为grpc,则只需要赋值该appPolicy下的action动作策略,不需要进行请求方法的匹配,比如GET、POST等 577 | action = operationPolicy.OperationAction 578 | } 579 | } 580 | 581 | // 否则执行默认策略 582 | return isActionAllowed(action), actionPolicy 583 | } 584 | ``` 585 | 586 | 587 | ## 总结 588 | 589 | 通过上面的分析,我们可以知道整个dapr configuration配置,是如何加载的,如何解析,以及configuration支持httpPipeline、metric、tracing、mtls、accessControl五种能力,并针对spiffeID标准来实现微服务之前访问的ACL访问权限细粒度控制,并针对主调与被调RPC访问的权限控制梳理。正是通过accessControl实现RPC内部访问的保护,防止内部服务被攻击。 590 | -------------------------------------------------------------------------------- /pkg/health.md: -------------------------------------------------------------------------------- 1 | 健康检查在dapr中以一个独立的服务模块存在,只需要进行监控包引入和 初始化,则就可以启动服务的健康检查。这个health包提供了server端和client端。 2 | 3 | ## 健康检查服务端 4 | 5 | 业务POD、或者dapr内部服务需要使用dapr health健康检查服务包,则需要创建健康检查goroutine服务,这里分三步: 6 | 1. 通过NewServer API,创建健康检查服务实例; 7 | 2. 通过Run启动健康检查端口服务,并对外提供**/healthz**健康检查路由服务; 8 | 3. 通过Ready和NotReady两个API,来设置引入该health包的服务健康状态; 9 | 10 | ```golang 11 | // Server 表示健康检查服务接口,启动健康检查服务和设置服务健康状态 12 | type Server interface { 13 | Run(context.Context, int) error 14 | Ready() 15 | NotReady() 16 | } 17 | 18 | // server 表示Server接口的一个实现类 19 | type server struct { 20 | ready bool 21 | log logger.Logger 22 | } 23 | 24 | // NewServer 创建一个健康检查实例server 25 | // 26 | // 健康检查的日志输出与引入该health包的日志保持一致 27 | func NewServer(log logger.Logger) Server { 28 | return &server{ 29 | log: log, 30 | } 31 | } 32 | 33 | // Ready 设置服务的健康检查状态为健康状态 34 | func (s *server) Ready() { 35 | s.ready = true 36 | } 37 | 38 | // NotReady 设置服务的健康检查状态为不健康状态 39 | func (s *server) NotReady() { 40 | s.ready = false 41 | } 42 | 43 | // Run 启动健康检查fasthttp端口服务, 并通过上一节的signals包,对context进行上下游整个链路控制 44 | // 45 | // 当context接收到进程退出信号时,则上下文ctx会通过Done来捕获退出信号,然后再优雅地关闭健康检查服务 46 | func (s *server) Run(ctx context.Context, port int) error { 47 | // 创建健康检查服务,并提供/healthz路由服务,来告诉主调,被掉是否健康 48 | router := http.NewServeMux() 49 | router.Handle("/healthz", s.healthz()) 50 | 51 | srv := &http.Server{ 52 | Addr: fmt.Sprintf(":%d", port), 53 | Handler: router, 54 | } 55 | 56 | doneCh := make(chan struct{}) 57 | 58 | // 启动goroutine, 当上游context关闭(进程退出), 则健康检查服务通过该信号优雅退出 59 | // 60 | // 另一个角度,当健康检查服务异常退出时,则可以通过close channel来优雅关闭goroutine 61 | go func() { 62 | select { 63 | case <-ctx.Done(): 64 | s.log.Info("Healthz server is shutting down") 65 | shutdownCtx, cancel := context.WithTimeout( 66 | context.Background(), 67 | time.Second*5, 68 | ) 69 | defer cancel() 70 | srv.Shutdown(shutdownCtx) // nolint: errcheck 71 | case <-doneCh: 72 | } 73 | }() 74 | 75 | s.log.Infof("Healthz server is listening on %s", srv.Addr) 76 | err := srv.ListenAndServe() 77 | if err != http.ErrServerClosed { 78 | s.log.Errorf("Healthz server error: %s", err) 79 | } 80 | close(doneCh) 81 | return err 82 | } 83 | 84 | // healthz 是健康检查服务的路由/healthz API,响应给主服务,告知服务健康状态 85 | // 86 | // 这种一般都是通过goroutine实现,当ready为true表示,主服务当前状态为健康,否则不健康,不能提供正常的服务 87 | // 健康时通过http 200返回,不健康时通过http 503返回 88 | func (s *server) healthz() http.Handler { 89 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 90 | var status int 91 | if s.ready { 92 | status = http.StatusOK 93 | } else { 94 | status = http.StatusServiceUnavailable 95 | } 96 | w.WriteHeader(status) 97 | }) 98 | } 99 | ``` 100 | 101 | ### 健康检查客户端 102 | 103 | 健康检查客户端, 也是主服务通过goroutine来实时获取服务健康状态, 一个服务是否健康,需要设置一些条件。 104 | 105 | 比如,健康检查探测周期,连续探测失败次数,等待健康检查服务启动的初始化时间, 以及请求健康检查服务的超时时间等 106 | 107 | ```golang 108 | const ( 109 | // 默认参数值设置,也就是上面说的四个参数 110 | // 再加上健康检查http成功状态码, 默认为200 111 | // 112 | // 如果默认参数,那么一个服务不健康的话,需要2*5=10s,也就是10s的健康探测都是失败状态,则为不健康状态 113 | initialDelay = time.Second * 1 114 | failureThreshold = 2 115 | requestTimeout = time.Second * 2 116 | interval = time.Second * 5 117 | successStatusCode = 200 118 | ) 119 | 120 | // Option 健康检查客户端函数参数赋值方式 121 | type Option func(o *healthCheckOptions) 122 | 123 | // 健康检查客户端4个关注的参数 124 | type healthCheckOptions struct { 125 | initialDelay time.Duration 126 | requestTimeout time.Duration 127 | failureThreshold int 128 | interval time.Duration 129 | successStatusCode int 130 | } 131 | 132 | // StartEndpointHealthCheck 客户端对客户端的健康检查参数赋值后,再启动周期性探测主服务的健康状态 133 | // 134 | // 并把主服务的健康状态通过返回值singalChan channel来实时传递给主服务 135 | func StartEndpointHealthCheck(endpointAddress string, opts ...Option) chan bool { 136 | // 初始化健康检查客户端参数和赋值 137 | options := &healthCheckOptions{} 138 | applyDefaults(options) 139 | 140 | for _, o := range opts { 141 | o(options) 142 | } 143 | 144 | // 设置实时返回给主服务的健康状态,true表示健康,false表示不健康 145 | signalChan := make(chan bool, 1) 146 | 147 | // 启动健康检查客户端goroutine,并周期性对主服务健康进行探测 148 | go func(ch chan<- bool, endpointAddress string, options *healthCheckOptions) { 149 | // 初始化等待健康检查端口服务启动完成 150 | ticker := time.NewTicker(options.interval) 151 | failureCount := 0 152 | time.Sleep(options.initialDelay) 153 | 154 | // 创建http client并周期性的发起健康探测, 路由: /healthz 155 | client := &fasthttp.Client{ 156 | MaxConnsPerHost: 5, // Limit Keep-Alive connections 157 | ReadTimeout: options.requestTimeout, 158 | MaxIdemponentCallAttempts: 1, 159 | } 160 | 161 | req := fasthttp.AcquireRequest() 162 | req.SetRequestURI(endpointAddress) 163 | req.Header.SetMethod(fasthttp.MethodGet) 164 | defer fasthttp.ReleaseRequest(req) 165 | 166 | // 周期性地执行健康探测任务 167 | for range ticker.C { 168 | resp := fasthttp.AcquireResponse() 169 | // 发起http client请求 170 | err := client.DoTimeout(req, resp, options.requestTimeout) 171 | // 如果请求报错,或者返回的http状态码不是自定义的状态码,则进入连续错误统计 172 | if err != nil || resp.StatusCode() != options.successStatusCode { 173 | failureCount++ 174 | // 当连续探测失败次数,达到了设置的失败阈值,则返回给主服务当前状态不健康 175 | if failureCount == options.failureThreshold { 176 | ch <- false 177 | } 178 | } else { 179 | // 否则表示状态健康,且重置failureCount为0,失败必须是连续的,才会发生统计 180 | ch <- true 181 | failureCount = 0 182 | } 183 | fasthttp.ReleaseResponse(resp) 184 | } 185 | }(signalChan, endpointAddress, options) 186 | // 返回signalChan给主调方,来告知当前服务的健康状态 187 | return signalChan 188 | } 189 | ``` 190 | 191 | ## 健康检查参数 192 | 193 | 目前参数也就是针对前面说的健康检查服务客户端的5个参数设置, 分别是 194 | 1. FailureThreshold, 连续失败阈值; 195 | 2. InitialDelay, 等待健康检查服务就绪 196 | 3. Interval, 健康检查探测周期 197 | 4. RequestTimeout,http请求健康检查服务的超时时间 198 | 5. SuccessStatusCode,设置健康检查服务响应成功的响应状态码 199 | 200 | ## 总结 201 | 202 | 通过这个heath健康检查包,就可以启动健康检查服务框架,以及使用健康检查客户端,开箱即用,然后再和自己主服务结合,来实现服务的健康检查。同时注意,使用了signals.Context包上下文,则可以在服务的整个链路和所有协程中,都用该上下文来监听主服务的退出信号做一些收尾处理 203 | -------------------------------------------------------------------------------- /pkg/http01.md: -------------------------------------------------------------------------------- 1 | apis、client、http、metrics、proto、diagnostics以及testing包还没有进行源码阅读 2 | 3 | 本文讲解dapr runtime http包, 该包有两个用途:1. 指定的dapr runtime接收http请求;2. dapr runtime通过nameresolution名字服务,来查找目标dapr runtime服务地址,并判断目标dapr runtime是在本地,还是远端,以及第三方软件服务,比如components-contrib下的pubsub/binding等等。 4 | 5 | 1. 接收外部http路由请求; 6 | 2. 根据传入的appid以及namespace,通过components-contrib下的nameresolution名字服务,来获取目标地址; 7 | 3. 根据第二步骤,如果传入目标的appid和namespace,与当前接收http请求的appid和namespace相同,则表示就是该业务POD所在的业务微服务处理请求,也就是本地处理http请求 8 | 4. 根据第二步骤,如果目标appid和namespace,和接收这个请求处理的dapr runtime所在appid和namespace不匹配,则需要借助与nameresolution名字服务获取目标dapr runtime所在地址,并通过grpc进行rpc调用。 9 | 5. 经过第三步骤或者第四步骤,那么最终都会外部http请求,都会通过本地的dapr runtime访问本地的业务微服务 10 | 11 | 接下来就针对http api构建,以及dapr runtime http server服务的启动, 最终再通过一个http请求贯穿整个请求、转发处理流程 12 | 13 | ## http api路由 14 | 15 | ```golang 16 | // API 接口用于构建http路由,以及设置dapr runtime http server是否准备就绪; 17 | // 同时设置与dapr runtime与其绑定的业务微服务通信方式:grpc或者http, 也就是pod内的容器之间的通信 18 | // DirectMessaging用于转发http请求到目标dapr runtime被调端 19 | // actor稍后再看 20 | type API interface { 21 | APIEndpoints() []Endpoint 22 | MarkStatusAsReady() 23 | SetAppChannel(appChannel channel.AppChannel) 24 | SetDirectMessaging(directMessaging messaging.DirectMessaging) 25 | SetActorRuntime(actor actors.Actors) 26 | } 27 | 28 | // http api作为API接口的一个具体实现 29 | type api struct { 30 | // endpoints 表示http server路由列表 31 | endpoints []Endpoint 32 | // directmessaging 表示接收dapr runtime的主调方,直接service invocation rpc调用目标dapr runtime,并最终调用业务微服务 33 | directMessaging messaging.DirectMessaging 34 | // appChannel 用于设置本业务pod中的dapr runtime访问其中的microservice, 方式http或者grpc 35 | appChannel channel.AppChannel 36 | // components组件列表, 后面再看::TODO 37 | components []components_v1alpha1.Component 38 | // stateStores state存储map 39 | stateStores map[string]state.Store 40 | // secretStores secret存储map 41 | secretStores map[string]secretstores.SecretStore 42 | // secretScope 43 | secretsConfiguration map[string]config.SecretsScope 44 | /json api 45 | json jsoniter.API 46 | // actor Actors接口的实现 47 | actor actors.Actors 48 | // pubsub适配器 49 | pubsubAdapter runtime_pubsub.Adapter 50 | // 绑定 51 | sendToOutputBindingFn func(name string, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) 52 | // appid 53 | id string 54 | extendedMetadata sync.Map 55 | readyStatus bool 56 | tracingSpec config.TracingSpec 57 | } 58 | 59 | // 创建一个API的api实例对象,并根据传入的参数进行初始化 60 | func NewAPI( 61 | appID string, 62 | appChannel channel.AppChannel, 63 | directMessaging messaging.DirectMessaging, 64 | components []components_v1alpha1.Component, 65 | stateStores map[string]state.Store, 66 | secretStores map[string]secretstores.SecretStore, 67 | secretsConfiguration map[string]config.SecretsScope, 68 | pubsubAdapter runtime_pubsub.Adapter, 69 | actor actors.Actors, 70 | sendToOutputBindingFn func(name string, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error), 71 | tracingSpec config.TracingSpec) API { 72 | // 初始化api 73 | api := &api{ 74 | appChannel: appChannel, 75 | directMessaging: directMessaging, 76 | stateStores: stateStores, 77 | secretStores: secretStores, 78 | secretsConfiguration: secretsConfiguration, 79 | json: jsoniter.ConfigFastest, 80 | actor: actor, 81 | pubsubAdapter: pubsubAdapter, 82 | sendToOutputBindingFn: sendToOutputBindingFn, 83 | id: appID, 84 | tracingSpec: tracingSpec, 85 | } 86 | api.components = components 87 | // 构建http server路由列表, 这些类型路由列表构建成了所有对外提供的dapr runtime http server服务 88 | // state 存储服务路由列表 89 | api.endpoints = append(api.endpoints, api.constructStateEndpoints()...) 90 | // secret 路由列表 91 | api.endpoints = append(api.endpoints, api.constructSecretEndpoints()...) 92 | // pubsub 发布订阅路由列表 93 | api.endpoints = append(api.endpoints, api.constructPubSubEndpoints()...) 94 | // actor Actor路由列表 95 | api.endpoints = append(api.endpoints, api.constructActorEndpoints()...) 96 | // directMessaging service invocation服务调用路由列表 97 | api.endpoints = append(api.endpoints, api.constructDirectMessagingEndpoints()...) 98 | // metadata 获取元数据路由列表 99 | api.endpoints = append(api.endpoints, api.constructMetadataEndpoints()...) 100 | // bindings 把事件传输给outputbinding路由列表 101 | api.endpoints = append(api.endpoints, api.constructBindingsEndpoints()...) 102 | // healthz 健康检查路由列表 103 | api.endpoints = append(api.endpoints, api.constructHealthzEndpoints()...) 104 | // 105 | // 以上八个路由类型列表,构成成了整个dapr runtime http server对外提供的所有服务,接下来就是针对这些路由背后的handler request处理请求进行源码阅读和分析 106 | return api 107 | } 108 | 109 | // APIEndpoints 返回dapr runtime http server路由列表 110 | func (a *api) APIEndpoints() []Endpoint { 111 | return a.endpoints 112 | } 113 | 114 | // MarkStatusAsReady 标记http server准备就绪,可以对外提供服务 115 | func (a *api) MarkStatusAsReady() { 116 | a.readyStatus = true 117 | } 118 | 119 | // 当前dapr runtime提供的http server都是v1.0版本,所以当前所有的外部http请求路由都是以 120 | // 121 | // /v1.0/xxx开头 122 | // 123 | // constructStateEndpoints 构建state路由列表 124 | // 主要是对请求数据的CRUD,包括:批量新增、和事务提交操作 125 | unc (a *api) constructStateEndpoints() []Endpoint { 126 | return []Endpoint{ 127 | { 128 | // /v1.0/state/{storeName}/{key} GET handler:onGetState 129 | // 作用: 130 | // 根据storeName和key,获取存储在dapr runtime中的storename与state type的映射关系,并发生第三方服务请求(components-contrib) 131 | Methods: []string{fasthttp.MethodGet}, 132 | Route: "state/{storeName}/{key}", 133 | Version: apiVersionV1, 134 | Handler: a.onGetState, 135 | }, 136 | { 137 | // /v1.0/state/{storeName} POST,PUT onPostState 138 | // 作用 139 | // 根据storeName找到component配置实例,并通过.spec.metdata找到配置,然后通过访问第三方state服务,来存储 140 | // key和value。比如:redis的键值对存储 141 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 142 | Route: "state/{storeName}", 143 | Version: apiVersionV1, 144 | Handler: a.onPostState, 145 | }, 146 | { 147 | // /v1.0/state/{storeName}/{key} DELETE onDeleteState 148 | // 作用: 149 | // 根据storeName找到state实例,并与state第三方服务通信,并删除state key数据 150 | Methods: []string{fasthttp.MethodDelete}, 151 | Route: "state/{storeName}/{key}", 152 | Version: apiVersionV1, 153 | Handler: a.onDeleteState, 154 | }, 155 | { 156 | // /v1.0/state/{storename}/bulk POST, PUT, onBulkGetState 157 | // 作用: 158 | // 根据storeName找到第三方state软件服务,并进行批量存储操作 159 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 160 | Route: "state/{storeName}/bulk", 161 | Version: apiVersionV1, 162 | Handler: a.onBulkGetState, 163 | }, 164 | { 165 | // /v1.0/state/{storeName}/trasaction POST,PUT onPostStateTransaction 166 | // 作用: 167 | // 根据storeName找到第三方软件服务实例,然后再针对事务的一致性选择,来存储数据 168 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 169 | Route: "state/{storeName}/transaction", 170 | Version: apiVersionV1, 171 | Handler: a.onPostStateTransaction, 172 | }, 173 | } 174 | } 175 | 176 | // constructSecretEndpoints secrets路由列表,对secret密钥进行操作. 177 | // 相当于请求访问所需要的资源所需要的token或者用户名、密码等 178 | // 179 | // 它包括单个获取和批量获取secrets 180 | // 181 | // 其他说明: 182 | // 我们在dapr configuration一文中,详细介绍了.spec.secrets.scopes={storeName, defaultAccess, allowedSecrets, deniedSecrets} 183 | func (a *api) constructSecretEndpoints() []Endpoint { 184 | return []Endpoint{ 185 | { 186 | // /v1.0/secrets/{secretStoreName}/bulk GET onBulkGetSecret 187 | // 作用 188 | // 通过secretStoreName找到配置实例的存储对象,然后获取这个存储对象下的secrets 189 | // 然后再经过configuration对象的.spec.secrets.scopes下的storeName进行允许或者拒绝的secrets action匹配 190 | // 匹配成功的secrets可以返回 191 | // 192 | // 最终这个返回对象就是包含了secret key,以及对应value map,因为一个key,信息可能会存在多个。 193 | // 比如:key=account(业务名), value={username: seachen, passwd: 123456} 194 | Methods: []string{fasthttp.MethodGet}, 195 | Route: "secrets/{secretStoreName}/bulk", 196 | Version: apiVersionV1, 197 | Handler: a.onBulkGetSecret, 198 | }, 199 | { 200 | // /v1.0/secrets/{secretStoreName}/{key} GET onGetSecret 201 | // 作用: 202 | // 通过secretStoreName名称找到配置实例的存储对象, 然后再获取这个存储对象下的指定key所对应的secret信息 203 | Methods: []string{fasthttp.MethodGet}, 204 | Route: "secrets/{secretStoreName}/{key}", 205 | Version: apiVersionV1, 206 | Handler: a.onGetSecret, 207 | }, 208 | } 209 | } 210 | 211 | // constructPubSubEndpoints pubsub消息发布路由 212 | func (a *api) constructPubSubEndpoints() []Endpoint { 213 | return []Endpoint{ 214 | { 215 | // /v1.0/publish/{pubsubname}/{topic:*} POST,PUT onPublish 216 | // 作用: 217 | // 根据传入的pubsubname,找到components下的pubsub实例名称,并对topic进行消息发布 218 | // 而具体的onPublish中,针对pubsub适配器接口的实现,其实就是dapr runtime实现的Publish和GetPubsub 219 | // 220 | // 注意这里一个比较有意思的点:{topic:*} 221 | // 它是针对topic值的转义。目前支持:空格和反斜杠: ' '和'/' 222 | // 因为topic命名可能是={unknown%20state%20store, stateStore%2F1} 223 | // 转义出来的结果={unknown state store, stateStore/1} 224 | // 具体是如何实现的,后面再看 225 | // 注意pubsub消息传递使用的是标准的cloudEvent 226 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 227 | Route: "publish/{pubsubname}/{topic:*}", 228 | Version: apiVersionV1, 229 | Handler: a.onPublish, 230 | }, 231 | } 232 | } 233 | 234 | // constructBindingsEndpoints 构建output binding路由 235 | func (a *api) constructBindingsEndpoints() []Endpoint { 236 | return []Endpoint{ 237 | { 238 | // /v1.0/bindings/{name} POST, PUT onOutputBindingMessage 239 | // 作用: 240 | // 使用name找到output binding对应的实例,然后把事件数据推送给第三方软件服务 241 | // 针对outputbinding和inputbinding的说明: 242 | // 1. binding是外部资源; 243 | // 2. outputbinding:允许microservice通过outputbinding访问外部资源,也就是bindings 244 | // 3. inputbinding:允许microservice通过inputbinding接收外部资源。 245 | // 所以input和output都是针对microservice的。是接收绑定的外部资源,还是把资源绑定发布给外部资源 246 | // 247 | // 所以这里的outputbinding是指microservice调用外部资源 248 | // inputbinding,允许inputbinding接收外部资源 249 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 250 | Route: "bindings/{name}", 251 | Version: apiVersionV1, 252 | Handler: a.onOutputBindingMessage, 253 | }, 254 | } 255 | } 256 | 257 | // constructDirectMessagingEndpoints 是direct messaging(service invocation) microservice rpc直接调用 258 | func (a *api) constructDirectMessagingEndpoints() []Endpoint { 259 | return []Endpoint{ 260 | { 261 | // /v1.0/invoke/{id}/method/{method:*} 允许所有的方法* onDirectMessage 262 | // 作用: 263 | // 根据id和nameresolution找到目标dapr runtime地址,然后再判断目标服务是在本地,还是在远端 264 | // {method:*} 与前面的{topic:*}是一样的处理方式,表示method方法可以是任意字符串,包括'/'和' ' 265 | Methods: []string{router.MethodWild}, 266 | Route: "invoke/{id}/method/{method:*}", 267 | Version: apiVersionV1, 268 | Handler: a.onDirectMessage, 269 | }, 270 | } 271 | } 272 | 273 | // constructActorEndpoints 用于构建actors的访问和在线实时任务调度处理 274 | func (a *api) constructActorEndpoints() []Endpoint { 275 | return []Endpoint{ 276 | { 277 | // /v1.0/actors/{actorType}/{actorId}/state POST,PUT onActorStateTransaction 278 | // 作用: 279 | // 根据actorType和actorId,找到dapr runtime下actorId的实例,并把批量的状态数据存储到该state中 280 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 281 | Route: "actors/{actorType}/{actorId}/state", 282 | Version: apiVersionV1, 283 | Handler: a.onActorStateTransaction, 284 | }, 285 | { 286 | // /v1.0/actors/{actorType}/{actorId}/method/{method} * onDirectActorMessage 287 | // 作用: 288 | // 根据actorType和actorId找到dapr runtime的actorId实例,并转发给microservice的method处理 289 | // DirectActorFMessage和DirectMessage两个不同之处,在于actorId获取的请求是串行执行,而rpc直接调用可以支持并发 290 | Methods: []string{fasthttp.MethodGet, fasthttp.MethodPost, fasthttp.MethodDelete, fasthttp.MethodPut}, 291 | Route: "actors/{actorType}/{actorId}/method/{method}", 292 | Version: apiVersionV1, 293 | Handler: a.onDirectActorMessage, 294 | }, 295 | { 296 | // /v1.0/actors/{actorType}/{actorId}/state/{key} GET onGetActorState 297 | // 作用 298 | // 根据actorType和actorId找到dapr runtime的actorId,并获取actor存储的key对应的值 299 | Methods: []string{fasthttp.MethodGet}, 300 | Route: "actors/{actorType}/{actorId}/state/{key}", 301 | Version: apiVersionV1, 302 | Handler: a.onGetActorState, 303 | }, 304 | { 305 | // /v1.0/actors/{actorType}/{actorId}/reminders/{name} POST, PUT, onCreateActorReminder 306 | // 作用: 307 | // 根据actorType和actorId,找到dapr runtime下的actorId对象, 然后根据reminder name创建数据 308 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 309 | Route: "actors/{actorType}/{actorId}/reminders/{name}", 310 | Version: apiVersionV1, 311 | Handler: a.onCreateActorReminder, 312 | }, 313 | { 314 | // /v1.0/actors/{actorType}/{actorId}/timers/{name} POST,PUT onCreateActorTimer 315 | // 作用: 316 | // 根据actorType和actorId,找到dapr runtime下的actorId对象,然后再提交timer name创建数据 317 | // 318 | // timer和reminder不同点,在于前者随着actor的销毁而销毁;而后者是持久化存储的,不再执行时,需要用户主动删除timer 319 | Methods: []string{fasthttp.MethodPost, fasthttp.MethodPut}, 320 | Route: "actors/{actorType}/{actorId}/timers/{name}", 321 | Version: apiVersionV1, 322 | Handler: a.onCreateActorTimer, 323 | }, 324 | { 325 | // 删除reminder动作 326 | Methods: []string{fasthttp.MethodDelete}, 327 | Route: "actors/{actorType}/{actorId}/reminders/{name}", 328 | Version: apiVersionV1, 329 | Handler: a.onDeleteActorReminder, 330 | }, 331 | { 332 | // 删除timer动作 333 | Methods: []string{fasthttp.MethodDelete}, 334 | Route: "actors/{actorType}/{actorId}/timers/{name}", 335 | Version: apiVersionV1, 336 | Handler: a.onDeleteActorTimer, 337 | }, 338 | { 339 | // timers是不需要持久化的,业务pod销毁则timer销毁;而reminder则是需要持久化,需要读取的和重新分配的 340 | // 341 | // 这里详细说下为什么有onGetActorReminder,而没有onGetActorTimer 342 | // 即 343 | // /v1.0/actors/{actorType}/{actorId}/reminders/{name}存在的理由 344 | // 和 345 | // /v1.0/actors/{actorType}/{actorId}/timers/{name}不存在的理由 346 | // 347 | // 如果microservice01 运行reminder01, 如果该服务销毁,则reminder01会被dapr-system命名空间下的dapr-placement调度到其他microservice中,再接着执行。所以外界资源需要GetReminder API的,来获取reminder来继承原来的actor。actorType和actorId不变; 348 | // 而microservice01 运行timer01,它会随着服务的销毁而销毁,再也不会被调度,所以一旦运行,接下来的操作只有删除销毁掉。不需要外界干预和继承 349 | // 这个站不站得住脚,后面再看::TODO 350 | Methods: []string{fasthttp.MethodGet}, 351 | Route: "actors/{actorType}/{actorId}/reminders/{name}", 352 | Version: apiVersionV1, 353 | Handler: a.onGetActorReminder, 354 | }, 355 | } 356 | } 357 | 358 | // constructMetadataEndpoints 用于获取dapr runtime http内部的metdata元数据相关信息 359 | // 包括:appid, 当前dapr runtime正在运行的actor梳理、已经注册的components列表,以及扩展元数据 360 | func (a *api) constructMetadataEndpoints() []Endpoint { 361 | return []Endpoint{ 362 | { 363 | // /v1.0/metdata GET 364 | //返回dapr runtime http server所有的元数据 365 | Methods: []string{fasthttp.MethodGet}, 366 | Route: "metadata", 367 | Version: apiVersionV1, 368 | Handler: a.onGetMetadata, 369 | }, 370 | { 371 | // /v1.0/metadata/{key} PUT 372 | // 存储外部传入的键值对数据。一般是存储同一个pod中的microservice数据,因为dapr runtime也是业务微服务的一个运行时,减轻业务负担。存储到扩展元数据中 373 | Methods: []string{fasthttp.MethodPut}, 374 | Route: "metadata/{key}", 375 | Version: apiVersionV1, 376 | Handler: a.onPutMetadata, 377 | }, 378 | } 379 | } 380 | 381 | // constructHealthzEndpoints 健康检查路由列表服务 382 | func (a *api) constructHealthzEndpoints() []Endpoint { 383 | return []Endpoint{ 384 | { 385 | // /v1.0/healthz GET onGetHealthz 386 | // 作用 387 | // dapr runtime http server的健康检查。 388 | Methods: []string{fasthttp.MethodGet}, 389 | Route: "healthz", 390 | Version: apiVersionV1, 391 | Handler: a.onGetHealthz, 392 | }, 393 | } 394 | } 395 | ``` 396 | 397 | ## http api handler 398 | 399 | 针对http api路由列表对应的各个request handler处理函数。 400 | 401 | ### outputbinding handler 402 | 403 | ```golang 404 | // onOutputBindingMessage microservice调用外部资源, 这个主要业务逻辑都在daprRuntime的sendTooutputbinding方法中 405 | // 406 | // 其他操作都是针对error以及获取请求数据、序列化和反序列化,以及获取trace等 407 | func (a *api) onOutputBindingMessage(reqCtx *fasthttp.RequestCtx) { 408 | // 路由:/v1.0/bindings/{name} 409 | // 比如:curl -X POST -H http://localhost:3500/v1.0/bindings/myevent -d '{ "data": { "message": "Hi!" }, "operation": "create" }' 410 | // 获取restful风格路由的name值以及获取request body数据 411 | name := reqCtx.UserValue(nameParam).(string) 412 | body := reqCtx.PostBody() 413 | 414 | // 把request body反序列化为OutputBindingRequest实例对象 415 | var req OutputBindingRequest 416 | err := a.json.Unmarshal(body, &req) 417 | if err != nil { 418 | msg := NewErrorResponse("ERR_MALFORMED_REQUEST", fmt.Sprintf(messages.ErrMalformedRequest, err)) 419 | respondWithError(reqCtx, fasthttp.StatusBadRequest, msg) 420 | log.Debug(msg) 421 | return 422 | } 423 | 424 | b, err := a.json.Marshal(req.Data) 425 | if err != nil { 426 | msg := NewErrorResponse("ERR_MALFORMED_REQUEST_DATA", fmt.Sprintf(messages.ErrMalformedRequestData, err)) 427 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 428 | log.Debug(msg) 429 | return 430 | } 431 | 432 | // 从requestcontext上下文尝试获取trace span,如果能获取到则需要通过outputbinding的metdata元数据 433 | // 传递给binding的外部资源, 元数据key:traceparent, value为:trace相关信息 434 | if span := diag_utils.SpanFromContext(reqCtx); span != nil { 435 | sc := span.SpanContext() 436 | if req.Metadata == nil { 437 | req.Metadata = map[string]string{} 438 | } 439 | req.Metadata[traceparentHeader] = diag.SpanContextToW3CString(sc) 440 | if sc.Tracestate != nil { 441 | req.Metadata[tracestateHeader] = diag.TraceStateToW3CString(sc) 442 | } 443 | } 444 | 445 | // sendToOutputBindingFn 为daprRuntime中的sendToOutputBinding方法 446 | // 并发起OutputBinding的Invoke操作 447 | // 448 | // 我们看到所有和第三方components-contrib服务的通信,都是通过daprRuntime处理的。 449 | resp, err := a.sendToOutputBindingFn(name, &bindings.InvokeRequest{ 450 | Metadata: req.Metadata, 451 | Data: b, 452 | Operation: bindings.OperationKind(req.Operation), 453 | }) 454 | if err != nil { 455 | msg := NewErrorResponse("ERR_INVOKE_OUTPUT_BINDING", fmt.Sprintf(messages.ErrInvokeOutputBinding, name, err)) 456 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 457 | log.Debug(msg) 458 | return 459 | } 460 | if resp == nil { 461 | respondEmpty(reqCtx) 462 | } else { 463 | respondWithJSON(reqCtx, fasthttp.StatusOK, resp.Data) 464 | } 465 | } 466 | 467 | // onBulkGetState 获取指定component下state storename传入的批量key,然后获取该存储下这些批量key下对应的所有数据列表 468 | // 469 | // 需要注意的一个问题是,可能state类别的所有具体存储组件,有些并不支持批量操作,这个就需要一个个上报 470 | // 不支持批量操作,如果单独一个个上报,则耗时太长,这里就需要前面文章提到的concurrency包 471 | // 它用来控制goroutine的并发量,来平衡退而求次的批量操作。 472 | func (a *api) onBulkGetState(reqCtx *fasthttp.RequestCtx) { 473 | // /v1.0/state/{storeName}/bulk 474 | // curl -X POST -H "Content-Type: application/json" -d '{"keys":["key1", "key2"]}' http://localhost:3500/v1.0/state/statestore/bulk 475 | // 476 | // 从请求路由获取storeName和api存储的stateStores对应的第三方存储对象 477 | store, storeName, err := a.getStateStoreWithRequestValidation(reqCtx) 478 | // 如果stateStores长度为0,或者storeName没有发现,都报错 479 | if err != nil { 480 | log.Debug(err) 481 | return 482 | } 483 | 484 | // 请求数据反序列化为BulkGetRequest实例对象 485 | /* 486 | type BulkGetRequest struct { 487 | Metadata map[string]string `json:"metadata"` 488 | Keys []string `json:"keys"` 489 | // 如果storeName映射的第三方存储服务不支持批量操作,则需要设置单个操作的最大并发量, 使得可以快速响应请求 490 | Parallelism int `json:"parallelism"` 491 | } 492 | */ 493 | var req BulkGetRequest 494 | err = a.json.Unmarshal(reqCtx.PostBody(), &req) 495 | if err != nil { 496 | msg := NewErrorResponse("ERR_MALFORMED_REQUEST", fmt.Sprintf(messages.ErrMalformedRequest, err)) 497 | respondWithError(reqCtx, fasthttp.StatusBadRequest, msg) 498 | log.Debug(msg) 499 | return 500 | } 501 | 502 | // 这里针对url的参数对参数key去'metdata.' 503 | // 如:metdata.name=seachen&value=tencent 504 | // 则处理后的结果为{name: seachen, value: tencent} 505 | metadata := getMetadataFromRequest(reqCtx) 506 | 507 | // 如果传入的keys列表为空,则直接返回响应空数据 508 | bulkResp := make([]BulkGetResponse, len(req.Keys)) 509 | if len(req.Keys) == 0 { 510 | b, _ := a.json.Marshal(bulkResp) 511 | respondWithJSON(reqCtx, fasthttp.StatusOK, b) 512 | return 513 | } 514 | 515 | // 请求参数转换,在上文中聊过的components/state中state_config.go文件中,针对key的前缀修改 516 | // 目前支持的前缀,是通过components组件statestore中.spec.metadata列表,获取keyPrefix值 517 | // keyPrefix值,有none、appid、storename和自定义的key前缀 518 | // 而state_loader.GetModifiedStateKey方法就是来修改前缀的。 519 | reqs := make([]state.GetRequest, len(req.Keys)) 520 | for i, k := range req.Keys { 521 | r := state.GetRequest{ 522 | Key: state_loader.GetModifiedStateKey(k, storeName, a.id), 523 | Metadata: req.Metadata, 524 | } 525 | reqs[i] = r 526 | } 527 | // 批量获取指定存储的key列表所对应的value列表 528 | // 返回的第一个参数bulkGet表示是否支持批量操作。 529 | bulkGet, responses, err := store.BulkGet(reqs) 530 | 531 | // 如果支持, 则对response数据列表进行数据校验 532 | if bulkGet { 533 | // 如果批量操作失败,则直接响应返回错误 534 | if err != nil { 535 | msg := NewErrorResponse("ERR_MALFORMED_REQUEST", fmt.Sprintf(messages.ErrMalformedRequest, err)) 536 | respondWithError(reqCtx, fasthttp.StatusBadRequest, msg) 537 | log.Debug(msg) 538 | return 539 | } 540 | 541 | // 否则,遍历response列表,并获取最初用户传入的key 542 | // 并校验response的error是否为空,并赋值数据给bulkResp列表 543 | for i := 0; i < len(responses) && i < len(req.Keys); i++ { 544 | bulkResp[i].Key = state_loader.GetOriginalStateKey(responses[i].Key) 545 | if responses[i].Error != "" { 546 | log.Debugf("bulk get: error getting key %s: %s", bulkResp[i].Key, responses[i].Error) 547 | bulkResp[i].Error = responses[i].Error 548 | } else { 549 | bulkResp[i].Data = jsoniter.RawMessage(responses[i].Data) 550 | bulkResp[i].ETag = responses[i].ETag 551 | } 552 | } 553 | } else { 554 | // 如果该存储不支持BulkGet操作,则通过concurrency并发度控制goroutine并发量 555 | // 批量执行获取单个key的API操作 556 | // 557 | // 注意如果用户没有传入parallelism,则默认并发量为100 558 | limiter := concurrency.NewLimiter(req.Parallelism) 559 | 560 | // 获取单个key所存储的值,并进行response数据转换 561 | for i, k := range req.Keys { 562 | bulkResp[i].Key = k 563 | 564 | // 基本上可以批量操作相同 565 | fn := func(param interface{}) { 566 | r := param.(*BulkGetResponse) 567 | gr := &state.GetRequest{ 568 | Key: state_loader.GetModifiedStateKey(r.Key, storeName, a.id), 569 | Metadata: metadata, 570 | } 571 | 572 | resp, err := store.Get(gr) 573 | if err != nil { 574 | log.Debugf("bulk get: error getting key %s: %s", r.Key, err) 575 | r.Error = err.Error() 576 | } else if resp != nil { 577 | r.Data = jsoniter.RawMessage(resp.Data) 578 | r.ETag = resp.ETag 579 | } 580 | } 581 | 582 | limiter.Execute(fn, &bulkResp[i]) 583 | } 584 | limiter.Wait() 585 | } 586 | 587 | // 最终把批量响应数据返回给主调方 588 | b, _ := a.json.Marshal(bulkResp) 589 | respondWithJSON(reqCtx, fasthttp.StatusOK, b) 590 | } 591 | // 以上需要注意的是,如果storeName对应的第三方存储服务支持事务提交和查询操作,则可以直接采用Multi方法进行操作; 592 | // 如果不支持,则采用退而求次的BulkGet方法,如果第三方存储服务的BulkGet实现为空,则bulkGet返回一定是false,这样 593 | // 最终再采用one by one的操作来退而求次的实现dapr runtime http server 的BulkGet方法。 594 | 595 | // /v1.0/state/{storeName}/{key} 596 | // onGetState 通过 597 | func (a *api) onGetState(reqCtx *fasthttp.RequestCtx) { 598 | // 同上onBulkGetState, 获取路由上的storeName,以及对应的第三方存储实例 599 | store, storeName, err := a.getStateStoreWithRequestValidation(reqCtx) 600 | if err != nil { 601 | log.Debug(err) 602 | return 603 | } 604 | 605 | // 把url请求参数中的'metdata.'前缀去掉,并把参数key和value构建成map 606 | metadata := getMetadataFromRequest(reqCtx) 607 | 608 | // 获取路由上的{key}映射的值 609 | key := reqCtx.UserValue(stateKeyParam).(string) 610 | // 查找url请求参数中,是否含有consistency策略: strong和eventual两种策略 611 | // dapr runtime这里并没有对一致性策略值做校验,而是直接丢给第三方存储服务校验该consistency参数值 612 | consistency := string(reqCtx.QueryArgs().Peek(consistencyParam)) 613 | // 创建访问第三方state存储服务的请求实例 614 | req := state.GetRequest{ 615 | Key: state_loader.GetModifiedStateKey(key, storeName, a.id), 616 | Options: state.GetStateOption{ 617 | Consistency: consistency, 618 | }, 619 | Metadata: metadata, 620 | } 621 | 622 | // 和前面onBulkGetState的bulkGet为false时one by one的操作 623 | resp, err := store.Get(&req) 624 | if err != nil { 625 | storeName := a.getStateStoreName(reqCtx) 626 | msg := NewErrorResponse("ERR_STATE_GET", fmt.Sprintf(messages.ErrStateGet, key, storeName, err.Error())) 627 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 628 | log.Debug(msg) 629 | return 630 | } 631 | // 返回响应数据给主调方 632 | if resp == nil || resp.Data == nil { 633 | respondEmpty(reqCtx) 634 | return 635 | } 636 | respondWithETaggedJSON(reqCtx, fasthttp.StatusOK, resp.Data, resp.ETag) 637 | } 638 | 639 | // /v1.0/secrets/{secretStoreName}/{key} 640 | // onGetSecret 获取指定secretStoreName实例存储下的key(可以是业务名)对应的相关密钥信息,包括用户名、密码、token等等 641 | func (a *api) onGetSecret(reqCtx *fasthttp.RequestCtx) { 642 | // 与onGetStateStore类似,获取secreteStoreName名称对应的secret存储实例 643 | store, secretStoreName, err := a.getSecretStoreWithRequestValidation(reqCtx) 644 | if err != nil { 645 | log.Debug(err) 646 | return 647 | } 648 | 649 | // 请求url参数key带有'metadata.'去掉 650 | // 如metdata.username为username,构建成元数据map存储 651 | metadata := getMetadataFromRequest(reqCtx) 652 | 653 | // 从restful url上获取secret存储实例的key 654 | key := reqCtx.UserValue(secretNameParam).(string) 655 | 656 | // 并校验该secretStoreName实例下的seret key是否允许访问 657 | // 比如key为daprsecret、redissecret等等 658 | // 659 | // 如果该secret实例下的key不允许被访问,则直接返回403错误码 660 | if !a.isSecretAllowed(secretStoreName, key) { 661 | msg := NewErrorResponse("ERR_PERMISSION_DENIED", fmt.Sprintf(messages.ErrPermissionDenied, key, secretStoreName)) 662 | respondWithError(reqCtx, fasthttp.StatusForbidden, msg) 663 | return 664 | } 665 | 666 | // 通过key和metadata,获取secret实例下的指定key的密钥数据 667 | req := secretstores.GetSecretRequest{ 668 | Name: key, 669 | Metadata: metadata, 670 | } 671 | resp, err := store.GetSecret(req) 672 | if err != nil { 673 | msg := NewErrorResponse("ERR_SECRET_GET", 674 | fmt.Sprintf(messages.ErrSecretGet, req.Name, secretStoreName, err.Error())) 675 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 676 | log.Debug(msg) 677 | return 678 | } 679 | 680 | // 返回响应数据 681 | if resp.Data == nil { 682 | respondEmpty(reqCtx) 683 | return 684 | } 685 | 686 | respBytes, _ := a.json.Marshal(resp.Data) 687 | respondWithJSON(reqCtx, fasthttp.StatusOK, respBytes) 688 | } 689 | 690 | // /v1.0/secrets/{secretStoreName}/bulk 691 | // onBulkGetSecret 批量获取seretStoreName名称关联的secret存储实例 692 | func (a *api) onBulkGetSecret(reqCtx *fasthttp.RequestCtx) { 693 | // 同上onGetSecret, 获取secretStoreName名称关联的secret实例, 如果该secretStoreName不存在,则返回错误 694 | store, secretStoreName, err := a.getSecretStoreWithRequestValidation(reqCtx) 695 | if err != nil { 696 | log.Debug(err) 697 | return 698 | } 699 | 700 | // 清除url携带的带有metdata.的前缀 701 | metadata := getMetadataFromRequest(reqCtx) 702 | 703 | // 批量获取secret, 比如获取secretStoreName实例下的所有密钥数据,如:daprsecret, redissecret, mysqlsecret等 704 | req := secretstores.BulkGetSecretRequest{ 705 | Metadata: metadata, 706 | } 707 | 708 | // 如果获取失败,则返回500错误码 709 | resp, err := store.BulkGetSecret(req) 710 | if err != nil { 711 | msg := NewErrorResponse("ERR_SECRET_GET", 712 | fmt.Sprintf(messages.ErrBulkSecretGet, secretStoreName, err.Error())) 713 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 714 | log.Debug(msg) 715 | return 716 | } 717 | 718 | if resp.Data == nil { 719 | respondEmpty(reqCtx) 720 | return 721 | } 722 | 723 | // 对返回的secret存储的密钥数据列表,遍历判断每个secret密钥数据,是否允许返回给上游 724 | // 这个是在configuration一文中有详细说明 725 | // 比如: 726 | // {redissecret: {"auth": "xxxx", "address": "xxx"}}等等 727 | filteredSecrets := map[string]map[string]string{} 728 | for key, v := range resp.Data { 729 | if a.isSecretAllowed(secretStoreName, key) { 730 | filteredSecrets[key] = v 731 | } else { 732 | // 如果key被deny,则落日志 733 | log.Debugf(messages.ErrPermissionDenied, key, secretStoreName) 734 | } 735 | } 736 | 737 | // 把secretStoreName名称对应的secret实例下的所有allow的secret密钥数据,返回给主调方 738 | respBytes, _ := a.json.Marshal(filteredSecrets) 739 | respondWithJSON(reqCtx, fasthttp.StatusOK, respBytes) 740 | } 741 | 742 | // /v1.0/state/{storeName} 743 | // onPostState 把一个或者多个数据存储到storeName名称对应的实例中 744 | func (a *api) onPostState(reqCtx *fasthttp.RequestCtx) { 745 | // 同上,获取storeName,以及校验当前dapr runtime http server是否支持storeName实例 746 | store, storeName, err := a.getStateStoreWithRequestValidation(reqCtx) 747 | if err != nil { 748 | log.Debug(err) 749 | return 750 | } 751 | 752 | // 解析state存储数据所需要的键值对,以及metdata和操作策略 753 | // 目前存储数据的选项,包括:数据一致性和并发策略。 754 | // 数据一致性,包括:最终一致性,和强一致性; 755 | // 并发:first-write, last-write 756 | // first-write: 并发情况下,只有第一个能写入成功,实现机制CAS 757 | // last-write: 无需考虑并发,请求来了就写入 758 | reqs := []state.SetRequest{} 759 | err = a.json.Unmarshal(reqCtx.PostBody(), &reqs) 760 | if err != nil { 761 | msg := NewErrorResponse("ERR_MALFORMED_REQUEST", err.Error()) 762 | respondWithError(reqCtx, fasthttp.StatusBadRequest, msg) 763 | log.Debug(msg) 764 | return 765 | } 766 | 767 | // 针对每个key-value键值对存储,进行keyPrefix的修改 768 | for i, r := range reqs { 769 | reqs[i].Key, err = state_loader.GetModifiedStateKey(r.Key, storeName, a.id) 770 | if err != nil { 771 | msg := NewErrorResponse("ERR_MALFORMED_REQUEST", err.Error()) 772 | respondWithError(reqCtx, fasthttp.StatusBadRequest, msg) 773 | log.Debug(err) 774 | return 775 | } 776 | } 777 | 778 | // 发起第三方服务调用,存储批量的kv键值对,并带有元数据和一致性、并发策略选项 779 | err = store.BulkSet(reqs) 780 | if err != nil { 781 | storeName := a.getStateStoreName(reqCtx) 782 | 783 | statusCode, errMsg, resp := a.stateErrorResponse(err, "ERR_STATE_SAVE") 784 | resp.Message = fmt.Sprintf(messages.ErrStateSave, storeName, errMsg) 785 | 786 | respondWithError(reqCtx, statusCode, resp) 787 | log.Debug(resp.Message) 788 | return 789 | } 790 | 791 | respondEmpty(reqCtx) 792 | } 793 | 794 | // /v1.0/invoke/{id}/method/{method:*} 795 | // onDirectMessage 接收外部请求,包括来自其他dapr runtime或者microservice的请求,进行rpc直接调用 796 | // 注意,这个内部的实现,我们在messaging一文中详细说明了。 797 | func (a *api) onDirectMessage(reqCtx *fasthttp.RequestCtx) { 798 | // 获取路由中的id值和method值,这样就可以知道appid和appid对应的microservice中handler 799 | targetID := reqCtx.UserValue(idParam).(string) 800 | verb := strings.ToUpper(string(reqCtx.Method())) 801 | invokeMethodName := reqCtx.UserValue(methodParam).(string) 802 | 803 | // 如果该appid所在的microservice没有提供对外的服务,则直接报错 804 | if a.directMessaging == nil { 805 | msg := NewErrorResponse("ERR_DIRECT_INVOKE", messages.ErrDirectInvokeNotReady) 806 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 807 | return 808 | } 809 | 810 | // 构建service invocation的请求参数实例 811 | req := invokev1.NewInvokeMethodRequest(invokeMethodName).WithHTTPExtension(verb, reqCtx.QueryArgs().String()) 812 | req.WithRawData(reqCtx.Request.Body(), string(reqCtx.Request.Header.ContentType())) 813 | // Save headers to internal metadata 814 | req.WithFastHTTPHeaders(&reqCtx.Request.Header) 815 | 816 | // directmessaging通过Invoke内部的nameresolution名字服务,发现appid和namespace的目标地址 817 | // 再校验appid是否为本dapr runtime所在的microservice。如果是直接本地调用; 818 | // 否则发起本dapr runtime访问目标dapr runtime的grpc调用 819 | resp, err := a.directMessaging.Invoke(reqCtx, targetID, req) 820 | if err != nil { 821 | statusCode := fasthttp.StatusInternalServerError 822 | if status.Code(err) == codes.PermissionDenied { 823 | statusCode = invokev1.HTTPStatusFromCode(codes.PermissionDenied) 824 | } 825 | msg := NewErrorResponse("ERR_DIRECT_INVOKE", fmt.Sprintf(messages.ErrDirectInvoke, targetID, err)) 826 | respondWithError(reqCtx, statusCode, msg) 827 | return 828 | } 829 | 830 | // 获取返回的响应数据,并设置返回数据到主调方 831 | invokev1.InternalMetadataToHTTPHeader(reqCtx, resp.Headers(), reqCtx.Response.Header.Set) 832 | contentType, body := resp.RawData() 833 | reqCtx.Response.Header.SetContentType(contentType) 834 | 835 | // Construct response 836 | statusCode := int(resp.Status().Code) 837 | if !resp.IsHTTPResponse() { 838 | statusCode = invokev1.HTTPStatusFromCode(codes.Code(statusCode)) 839 | if statusCode != fasthttp.StatusOK { 840 | if body, err = invokev1.ProtobufToJSON(resp.Status()); err != nil { 841 | msg := NewErrorResponse("ERR_MALFORMED_RESPONSE", err.Error()) 842 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 843 | return 844 | } 845 | } 846 | } 847 | respond(reqCtx, statusCode, body) 848 | } 849 | 850 | /* 851 | 对于actor的处理,和前面其他资源处理是相同的,获取url参数,并根据传入参数获取对应的实例,然后根据这个实例提供的方法进行数据访问和操作,真正的业务逻辑都在实例提供的方法中,而dapr runtime http server onXXX handler接收外部请求,都只是获取请求数据,找到对应的实例对象,然后获取或者操作数据后,封装响应数据给主调方。没有特殊逻辑 852 | 853 | 注意一点:单纯地对actor查询操作,包括事务批量查询,以及单个查询actor的状态数据,则需要调用IsActorHosted方法 854 | 该方法用于校验actorType和actorID在dapr runtime actor实例的actorTable中是否存在,不存在表示当前系统中不存在该actor。可能已经销毁了 855 | */ 856 | 857 | // /v1.0/metdata 858 | // onGetMetadata 获取dapr runtime http server存储有关业务元数据相关信息 859 | // 包括appid、活跃actor数量、业务可以使用已注册使用的组件列表,以及外部传入的metdata元数据 860 | // 861 | // 外部传入的metdata,是通过onPutMetadata路由handler处理的,kv全部转换为string:string 862 | func (a *api) onGetMetadata(reqCtx *fasthttp.RequestCtx) { 863 | temp := make(map[interface{}]interface{}) 864 | 865 | // extendedMetadata 遍历元数据,并写入到temp作为响应数据的一部分 866 | // 这部分是用户自己的传入数据. 服务自身不提供任何元数据 867 | a.extendedMetadata.Range(func(key, value interface{}) bool { 868 | temp[key] = value 869 | return true 870 | }) 871 | 872 | // 获取当前actor的actorId列表长度 873 | activeActorsCount := []actors.ActiveActorsCount{} 874 | if a.actor != nil { 875 | activeActorsCount = a.actor.GetActiveActorsCount(reqCtx) 876 | } 877 | 878 | // 获取dapr runtime已经注册的所有components,也就是可以当前microservice可以使用的数据 879 | registeredComponents := []registeredComponent{} 880 | 881 | for _, comp := range a.components { 882 | registeredComp := registeredComponent{ 883 | Name: comp.Name, 884 | Version: comp.Spec.Version, 885 | Type: comp.Spec.Type, 886 | } 887 | registeredComponents = append(registeredComponents, registeredComp) 888 | } 889 | 890 | // 构建响应数据,并序列化后返回给主调 891 | mtd := metadata{ 892 | ID: a.id, 893 | ActiveActorsCount: activeActorsCount, 894 | Extended: temp, 895 | RegisteredComponents: registeredComponents, 896 | } 897 | 898 | mtdBytes, err := a.json.Marshal(mtd) 899 | if err != nil { 900 | msg := NewErrorResponse("ERR_METADATA_GET", fmt.Sprintf(messages.ErrMetadataGet, err)) 901 | respondWithError(reqCtx, fasthttp.StatusInternalServerError, msg) 902 | log.Debug(msg) 903 | } else { 904 | respondWithJSON(reqCtx, fasthttp.StatusOK, mtdBytes) 905 | } 906 | } 907 | 908 | // /v1.0/metadata/{key} 909 | // onPutMetadata 获取路由中的key值,并取出请求body作为value,并存储到dapr runtime http server的extendmetadata内存中,随着microservice的销毁,这个数据也就不在了,一般提供给dapr runtime本地所在的microservice使用. 910 | // 911 | // 注意一点:request body的大小在microservice要有控制,不然内存可能会爆 912 | func (a *api) onPutMetadata(reqCtx *fasthttp.RequestCtx) { 913 | key := fmt.Sprintf("%v", reqCtx.UserValue("key")) 914 | body := reqCtx.PostBody() 915 | a.extendedMetadata.Store(key, string(body)) 916 | respondEmpty(reqCtx) 917 | } 918 | ``` 919 | 920 | ## 总结 921 | 922 | 本文为dapr runtime http server上篇,主要讲述了http server的路由构建过程、以及state/actor/secret/pubsub/direct_message/metadata/binding/health接收外部请求,并处理输入和输出,转发请求给第三方服务,包括components-contrib对应的第三方软件服务,或者microservices业务服务本身。转发本身没有什么业务逻辑,主要逻辑都在转发给的第三方服务。 923 | -------------------------------------------------------------------------------- /pkg/http02.md: -------------------------------------------------------------------------------- 1 | dapr runtime http server服务下篇,主要接着api提供的剩余路由做一个介绍,然后再整体讲解http server的构建过程 2 | 3 | ```golang 4 | // onPublish 主要是通过pubsub实例,来发布topic数据。 5 | func (a *api) onPublish(reqCtx *fasthttp.RequestCtx) 6 | ...... 7 | thepubsub := a.pubsubAdapter.GetPubSub(pubsubName) 8 | ...... 9 | // 获取traceID,并把传入参数构建成cloudevent实例对象 10 | // 11 | // CloudEvents 是以通用格式描述事件数据的规范,以提供跨服务、平台和系统的互操作性。 12 | span := diag_utils.SpanFromContext(reqCtx) 13 | corID := diag.SpanContextToW3CString(span.SpanContext()) 14 | envelope, err := runtime_pubsub.NewCloudEvent(&runtime_pubsub.CloudEvent{ 15 | ID: a.id, 16 | Topic: topic, 17 | DataContentType: contentType, 18 | Data: body, 19 | TraceID: corID, 20 | Pubsub: pubsubName, 21 | }) 22 | // 获取pubsub的features列表,并查找在metadata中是否存在ttlInSeconds字段 23 | // 如果存在,则校验pubsub的features中是否关注该ttl, 这个表示cloud event对象在整个链路中的生存时间 24 | features := thepubsub.Features() 25 | pubsub.ApplyMetadata(envelope, features, metadata) 26 | b, err := a.json.Marshal(envelope) 27 | ...... 28 | // 发布封装好的cloudevent实例对象到publishrequest中。 29 | req := pubsub.PublishRequest{ 30 | PubsubName: pubsubName, 31 | Topic: topic, 32 | Data: b, 33 | Metadata: metadata, 34 | } 35 | err = a.pubsubAdapter.Publish(&req) 36 | ...... 37 | } 38 | 39 | // GetStatusCodeFromMetadata 校验metdata元数据中是否存在http.status_code字段,如果存在则错误码值给主调方 40 | // 目前这个方法还没有被使用过 41 | func GetStatusCodeFromMetadata(metadata map[string]string) int { 42 | code := metadata[http.HTTPStatusCode] 43 | if code != "" { 44 | statusCode, err := strconv.Atoi(code) 45 | if err == nil { 46 | return statusCode 47 | } 48 | } 49 | return fasthttp.StatusOK 50 | } 51 | 52 | // onGetHealthz 路由健康检查,检查http server是否就绪 53 | func (a *api) onGetHealthz(reqCtx *fasthttp.RequestCtx) { 54 | if !a.readyStatus { 55 | // 返回健康检查没有就绪 56 | } 57 | // 否则,健康检查返回成功 58 | respondEmpty(reqCtx) 59 | } 60 | 61 | // onPostStateTransaction 62 | func (a *api) onPostStateTransaction(reqCtx *fasthttp.RequestCtx) { 63 | ...... 64 | // 请求数据中的key,进行keyPrefix前缀补充,并重新构建新的批量操作请求列表 65 | // 66 | // 并进行state事务提交, 这里需要注意,并不是所有components-contrib state具体组件接口的实现,都实现来Multi 67 | // API,而是有个默认的空state对象实例,继承关系,如果state具体组件没有实现该方法,则 68 | // 这个操作就是默认空操作 69 | err := transactionalStore.Multi(&state.TransactionalStateRequest{ 70 | Operations: operations, 71 | Metadata: req.Metadata, 72 | }) 73 | respondEmpty(reqCtx) 74 | } 75 | 76 | // SetAppChannel 设置本dapr runtime访问本microservice的方式,包括:http和grpc 77 | func (a *api) SetAppChannel(appChannel channel.AppChannel) { 78 | a.appChannel = appChannel 79 | } 80 | 81 | // SetDirectMessaging 设置service invocation直接调用实例对象,也就是内部业务微服务rpc方式 82 | func (a *api) SetDirectMessaging(directMessaging messaging.DirectMessaging) { 83 | a.directMessaging = directMessaging 84 | } 85 | 86 | // SetActorRuntime 设置操作actor的实例对象 87 | func (a *api) SetActorRuntime(actor actors.Actors) { 88 | a.actor = actor 89 | } 90 | ``` 91 | 92 | ## http server初始化和启动过程 93 | 94 | http server对象初始化,通过NewServer创建Server实例对象,并通过StartNonBlocking方法来启动http端口服务, 启动端口服务时,还需要一些启动的配置,包括:是否开启metric、tracing、profiling等,以及是否需要进行http pipeline拦截器,对请求进行拦截处理。同时提供端口地址, appid等 95 | ```golang 96 | type server struct { 97 | config ServerConfig 98 | tracingSpec config.TracingSpec 99 | metricSpec config.MetricSpec 100 | pipeline http_middleware.Pipeline 101 | api API 102 | } 103 | 104 | // NewServer 创建一个http server对象实例 105 | func NewServer(api API, config ServerConfig, tracingSpec config.TracingSpec, metricSpec config.MetricSpec, pipeline http_middleware.Pipeline) Server { 106 | return &server{ 107 | api: api, 108 | config: config, 109 | tracingSpec: tracingSpec, 110 | metricSpec: metricSpec, 111 | pipeline: pipeline, 112 | } 113 | } 114 | 115 | // StartNonBlocking 启动非阻塞的http server服务 116 | func (s *server) StartNonBlocking() { 117 | // 构建拦截器链表,从外到里,逐个执行请求包处理 118 | // 处理流程:tracing->metric->apiauthentication->cors->components->router handler 119 | // 其中, apiauthentication是对http header中token进行校验,如果与环境变量:DAPR_API_TOKEN值相同,则通过 120 | // 否则拒绝, 响应无效token 401错误码 121 | handler := 122 | useAPIAuthentication( 123 | // 跨域校验 124 | s.useCors( 125 | // components执行,也就是httpPipeline,在configuration设置 126 | s.useComponents( 127 | // 路由列表,也就是上篇中说到的Endpoints, 这背后就是对应的各个onXXXX handler 128 | s.useRouter()))) 129 | 130 | // metrics和tracing, 分别是由metricspec中的Enabled和tracingSpec.SamplingRate开关控制 131 | handler = s.useMetrics(handler) 132 | handler = s.useTracing(handler) 133 | 134 | customServer := &fasthttp.Server{ 135 | Handler: handler, 136 | MaxRequestBodySize: s.config.MaxRequestBodySize * 1024 * 1024, 137 | } 138 | 139 | // 启动http server 140 | go func() { 141 | log.Fatal(customServer.ListenAndServe(fmt.Sprintf(":%v", s.config.Port))) 142 | }() 143 | 144 | // 是否开启http server性能监控 145 | if s.config.EnableProfiling { 146 | go func() { 147 | log.Infof("starting profiling server on port %v", s.config.ProfilePort) 148 | log.Fatal(fasthttp.ListenAndServe(fmt.Sprintf(":%v", s.config.ProfilePort), pprofhandler.PprofHandler)) 149 | }() 150 | } 151 | } 152 | 153 | // unescapeRequestParametersHandler 主要解决路由过程中一些命名问题 154 | // 比如:/v1.0/publish/{topic:*}, 有可能topic为"order%21shop",带有转义符号,去转义变为"order/shop"。这样在写入requestCtx时,需要对其值进行去转义。同样还有一个"order%20shop"去转义为"order shop" 155 | func (s *server) unescapeRequestParametersHandler(next fasthttp.RequestHandler) fasthttp.RequestHandler { 156 | return func(ctx *fasthttp.RequestCtx) { 157 | parseError := false 158 | // 去转义方法 159 | unescapeRequestParameters := func(parameter []byte, value interface{}) { 160 | switch value.(type) { 161 | case string: 162 | if !parseError { 163 | parameterValue := fmt.Sprintf("%v", value) 164 | // 去转义 165 | parameterUnescapedValue, err := url.QueryUnescape(parameterValue) 166 | if err == nil { 167 | ctx.SetUserValueBytes(parameter, parameterUnescapedValue) 168 | } else { 169 | parseError = true 170 | errorMessage := fmt.Sprintf("Failed to unescape request parameter %s with value %v. Error: %s", parameter, value, err.Error()) 171 | log.Debug(errorMessage) 172 | ctx.Error(errorMessage, fasthttp.StatusBadRequest) 173 | } 174 | } 175 | } 176 | } 177 | // 对requestCtx中的所有key-value的value值进行去转义 178 | ctx.VisitUserValues(unescapeRequestParameters) 179 | 180 | if !parseError { 181 | next(ctx) 182 | } 183 | } 184 | } 185 | 186 | // getRouter, 对Enpoints列表的所有路由进行处理,校验路由是否与"/{.*}"匹配 187 | // 如果符合,则把该unescapeRequestParametersHandler方法设置为request handler上游 188 | func (s *server) getRouter(endpoints []Endpoint) *routing.Router { 189 | router := routing.New() 190 | parameterFinder, _ := regexp.Compile("/{.*}") 191 | for _, e := range endpoints { 192 | path := fmt.Sprintf("/%s/%s", e.Version, e.Route) 193 | for _, m := range e.Methods { 194 | // 如果该路由命中了正则表达式,则需要对http请求url做去转义处理 195 | pathIncludesParameters := parameterFinder.MatchString(path) 196 | if pathIncludesParameters { 197 | router.Handle(m, path, s.unescapeRequestParametersHandler(e.Handler)) 198 | } else { 199 | router.Handle(m, path, e.Handler) 200 | } 201 | } 202 | } 203 | return router 204 | } 205 | ``` 206 | 207 | 通过上下篇,我们就可以完整地了解dapr runtime http server的服务创建、拦截器链表、路由以及路由对应的handler处理过程。最终通过dapr runtime http server服务处理并转发给本地或者远端的microservices、或者转发给components-contrib组件库中初始化的第三方软件服务client真正处理外部请求。 208 | -------------------------------------------------------------------------------- /pkg/injector.md: -------------------------------------------------------------------------------- 1 | 本文主要讲述有关dapr runtime sidecar如何注入到业务pod中去的。并简单分析相关源码的注入过程。 2 | 3 | 本文由两部分构成 4 | 1. k8s准入控制器介绍,以及提及MutatingAdmissionWebhooks控制器; 5 | 2. 简单介绍dapr-system命名空间下**dapr-sidecar-injector**对业务pod的dapr runtime sidecar的注入源码分析 6 | 7 | ## k8s准入控制器 8 | 9 | 参考:[新手指南之 Kubernetes 准入控制器](https://zhuanlan.zhihu.com/p/106206871)一文 10 | 11 | 简单说, 如下图所示: 12 | 13 | ![API请求处理链路](https://wework.qpic.cn/wwpic/213401_NRyg5RxmRGW3nmB_1612145543/0) 14 | 15 | k8s准入控制器可以拦截API请求,然后更改API请求对象,验证API对象或者拒绝该请求。 16 | 17 | 上面这幅图当yaml资源提交到k8s api server后,会经过Authentication/Authrization(认证/授权), Mutation准入控制器(MutatingAdmissionWebhooks: Webhook01, Webhook02 ...), ObjectSchemeValidation(scheme各个对象验证。如上一节CRDs各个对象的资源注册), Validation准入控制器(验证API请求参数是否符合要求),最后持久化到ETCD集群中 18 | 19 | 其中Mutation准入控制器,可以修改API请求参数。 那么在dapr中则使用该MutatingAdmissionWebhooks准入控制器,来实现对Deployment资源写入到ETCD集群中时,去动态添加dapr runtime sidecar启动所需要的image和其他相关参数。来实现业务pod启动时,又多一个dapr runtime sidecar容器。 20 | 21 | 在dapr-system命名空间下,会有一个dapr-sidecar-injector POD,它会接收所有资源创建时,执行到MutatingAdmissionWebhooks后,发送webhook请求,也就是下面准入控制器中的service: **dapr-sidecar-injector**和路由:**/mutate**请求, 去访问dapr-system命名空间下的dapr-sidecar-injector POD来实现注入过程。 22 | 23 | ```yaml 24 | apiVersion: admissionregistration.k8s.io/v1 25 | kind: MutatingWebhookConfiguration 26 | metadata: 27 | name: dapr-sidecar-injector 28 | labels: 29 | app: dapr-sidecar-injector 30 | webhooks: 31 | - name: sidecar-injector.dapr.io 32 | clientConfig: 33 | service: 34 | namespace: {{ .Release.Namespace }} 35 | name: dapr-sidecar-injector 36 | path: "/mutate" 37 | caBundle: {{ if $existingSecret }}{{ index $existingSecret.data "ca.crt" }}{{ else }}{{ b64enc$ca.Cert }}{{ end }} 38 | rules: 39 | - apiGroups: 40 | - "" 41 | apiVersions: 42 | - v1 43 | resources: 44 | - pods 45 | operations: 46 | - CREATE 47 | failurePolicy: {{ .Values.webhookFailurePolicy}} 48 | sideEffects: None 49 | admissionReviewVersions: ["v1", "v1beta1"] 50 | ``` 51 | 52 | ## dapr injector注入POD服务 53 | 54 | dapr injector主要是通过dapr-system命名空间下的dapr-sidecar-injector POD来处理Mutation准入控制器发送过来的service/mutate请求。那么本dapr injector服务就是dapr-sidecar-injector POD服务,它用来接收mutate路由请求,来实现dapr runtime sidecar的注入过程。 55 | 56 | ```golang 57 | // NewInjector 根据配置返回injector实例对象 58 | func NewInjector(authUID string, config Config, daprClient scheme.Interface, kubeClient *kubernetes.Clientset) Injector { 59 | // dapr sidecar injector提供service http 4000端口服务 60 | mux := http.NewServeMux() 61 | 62 | i := &injector{ 63 | // config表示dapr runtime sidecar启动需要的配置 64 | // 65 | // 如:image,镜像拉取策略、dapr runtime sidecar启动的http/https证书路径, 命名空间 66 | config: config, 67 | // 接收http 4000端口请求数据,并进行反序列化 68 | deserializer: serializer.NewCodecFactory( 69 | runtime.NewScheme(), 70 | ).UniversalDeserializer(), 71 | server: &http.Server{ 72 | Addr: fmt.Sprintf(":%d", port), 73 | Handler: mux, 74 | }, 75 | // 通过.kube/config获取kubeclient对象 76 | kubeClient: kubeClient, 77 | // 获取dapr client对象 78 | daprClient: daprClient, 79 | // 是指service account所对应的objectmeta.uid 80 | // 也就是所关注的dapr injector的uid过滤唯一指标 81 | authUID: authUID, 82 | } 83 | 84 | // 处理k8s准入控制器Webhook:/mutate过来的请求 85 | mux.HandleFunc("/mutate", i.handleRequest) 86 | return i 87 | } 88 | 89 | // Run 启动dapr-sidecar-injector POD,提供/mutate API http 4000端口服务 90 | func (i *injector) Run(ctx context.Context) { 91 | doneCh := make(chan struct{}) 92 | 93 | go func() { 94 | select { 95 | case <-ctx.Done(): 96 | log.Info("Sidecar injector is shutting down") 97 | shutdownCtx, cancel := context.WithTimeout( 98 | context.Background(), 99 | time.Second*5, 100 | ) 101 | defer cancel() 102 | i.server.Shutdown(shutdownCtx) // nolint: errcheck 103 | case <-doneCh: 104 | } 105 | }() 106 | 107 | log.Infof("Sidecar injector is listening on %s, patching Dapr-enabled pods", i.serv er.Addr) 108 | err := i.server.ListenAndServeTLS(i.config.TLSCertFile, i.config.TLSKeyFile) 109 | if err != http.ErrServerClosed { 110 | log.Errorf("Sidecar injector error: %s", err) 111 | } 112 | close(doneCh) 113 | } 114 | 115 | // handleRequest 处理/mutate请求 116 | func (i *injector) handleRequest(w http.ResponseWriter, r *http.Request) { 117 | var body []byte 118 | if r.Body != nil { 119 | if data, err := ioutil.ReadAll(r.Body); err == nil { 120 | body = data 121 | } 122 | } 123 | // 通过deserializer对象,解析http request body数据 124 | // 转换成AdmissionReview协议目标数据 125 | /* 126 | type AdmissionReview struct { 127 | metav1.TypeMeta `json:",inline"` 128 | Request *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"` 129 | Response *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"` 130 | } 131 | */ 132 | var admissionResponse *v1.AdmissionResponse 133 | var patchOps []PatchOperation 134 | var err error 135 | 136 | ar := v1.AdmissionReview{} 137 | _, gvk, err := i.deserializer.Decode(body, nil, &ar) 138 | // 校验发送过来的请求数据,校验service account的uid与dapr injector的service account的uid是否相同 139 | // 这个是唯一用来dapr injector准入控制器的过滤指标 140 | if ar.Request.UserInfo.UID != i.authUID { 141 | err = errors.Wrapf(err, "unauthorized request") 142 | log.Error(err) 143 | // 必须是pod类型时才会做mutation修改 144 | } else if ar.Request.Kind.Kind != "Pod" { 145 | err = errors.Wrapf(err, "invalid kind for review: %s", ar.Kind) 146 | log.Error(err) 147 | } else { 148 | // 获取需要修改的资源对象,也就是打补丁,把dapr runtime sidecar运行所需要的资源全部写入到pod yaml文件中。构建成完整的pod yaml。 149 | patchOps, err = i.getPodPatchOperations(&ar, i.config.Namespace, i.config.S idecarImage, i.config.SidecarImagePullPolicy, i.kubeClient, i.daprClient) 150 | } 151 | // 如果不需要进行pod请求资源修改,则直接返回响应数据 152 | if len(patchOps) == 0 { 153 | admissionResponse = &v1.AdmissionResponse{ 154 | Allowed: true, 155 | } 156 | } else { 157 | // 否则,序列化patchOps为json流 158 | var patchBytes []byte 159 | patchBytes, err = json.Marshal(patchOps) 160 | if err != nil { 161 | admissionResponse = toAdmissionResponse(err) 162 | } else { 163 | // 序列化json流,并标识序列化的方法PathTypeJSONPatch JSONPatch 164 | // 并构建admissionResponse响应数据 165 | admissionResponse = &v1.AdmissionResponse{ 166 | Allowed: true, 167 | Patch: patchBytes, 168 | PatchType: func() *v1.PatchType { 169 | pt := v1.PatchTypeJSONPatch 170 | return &pt 171 | }(), 172 | } 173 | } 174 | } 175 | 176 | // 构建响应数据协议:v1.AdmissionReview{},并填充数据 177 | // 设置AdmissionReview的Response,以及metav1.TypeMeta, 和service account的UID 178 | admissionReview := v1.AdmissionReview{} 179 | if admissionResponse != nil { 180 | admissionReview.Response = admissionResponse 181 | if ar.Request != nil { 182 | admissionReview.Response.UID = ar.Request.UID 183 | admissionReview.SetGroupVersionKind(*gvk) 184 | } 185 | } 186 | 187 | log.Infof("ready to write response ...") 188 | // 最后通过json序列化为respBytes,并通过w.Write写入到http响应包并返回给上游 189 | respBytes, err := json.Marshal(admissionReview) 190 | ...... 191 | w.Header().Set("Content-Type", "application/json") 192 | if _, err := w.Write(respBytes); err != nil { 193 | log.Error(err) 194 | } else { 195 | monitoring.RecordSuccessfulSidecarInjectionCount(diagAppID) 196 | } 197 | } 198 | 199 | // getPodPatchOperations 获取PathchOperation列表. 这个也就是dapr runtime sidecar所需要的操作Path列表 200 | func (i *injector) getPodPatchOperations(ar *v1.AdmissionReview, 201 | namespace, image, imagePullPolicy string, kubeClient *kubernetes.Clientset, daprClient scheme.Interface) ([]PatchOperation, error) { 202 | // 把AdmissionReview中的Object.Raw值,通过json反序列化为v1.Pod 203 | req := ar.Request 204 | var pod corev1.Pod 205 | if err := json.Unmarshal(req.Object.Raw, &pod); err != nil { 206 | errors.Wrap(err, "could not unmarshal raw object") 207 | return nil, err 208 | } 209 | 210 | // 校验pod资源中的annotations的dapr.io/enabled变量值是否开启 211 | // 或者 212 | // 校验pod资源中的spec.containers, 判断name是否存在容器名daprd的变量值 213 | // 214 | // 通过上面两个annotations和spec.containers列表中的name,来判断是否需要开启dapr runtime sidecar 215 | if !isResourceDaprEnabled(pod.Annotations) || podContainsSidecarContainer(&pod) { 216 | return nil, nil 217 | } 218 | 219 | // 通过pod中的annotations,获取参数名为dapr.io/app-id的参数值 220 | // 如果不存在,则直接获取pod的名称,作为dapr runtime sidecar的appid的参数 221 | // 获取业务pod启动时指定的--app-id 222 | id := getAppID(pod) 223 | // 验证id是否符合k8s的命名规范 224 | err := validation.ValidateKubernetesAppID(id) 225 | if err != nil { 226 | return nil, err 227 | } 228 | // 标识访问dapr-system命名空间下的dapr-placement pod服务,它直接通过k8s service dns集群内解析获取POD IP地址 229 | // 比如:dapr-system命名空间下的placement POD服务,最终的DNS为 230 | // DNS: dapr-placement-server.dapr-system.svc.cluster.local 端口:50005 231 | // DNS: dapr-sentry.dapr-system.svc.cluster.local 端口:80 232 | // DNS: dapr-api.dapr-system.cluster.local 端口:80 233 | // placement server提供actor服务 234 | placementAddress := fmt.Sprintf("%s:50005", getKubernetesDNS(placementService, namespace)) 235 | // sentry server证书提供者 236 | sentryAddress := fmt.Sprintf("%s:80", getKubernetesDNS(sentryService, namespace)) 237 | // dapr api提供外部服务 238 | apiSrvAddress := fmt.Sprintf("%s:80", getKubernetesDNS(apiAddress, namespace)) 239 | 240 | var trustAnchors string 241 | var certChain string 242 | var certKey string 243 | var identity string 244 | 245 | mtlsEnabled := mTLSEnabled(daprClient) 246 | if mtlsEnabled { 247 | trustAnchors, certChain, certKey = getTrustAnchorsAndCertChain(kubeClient, namespace) 248 | identity = fmt.Sprintf("%s:%s", req.Namespace, pod.Spec.ServiceAccountName) 249 | } 250 | 251 | // service account挂载地址:/var/run/secrets/kubernetes.io/serviceaccount 252 | tokenMount := getTokenVolumeMount(pod) 253 | // 构建daprd container资源配置对象 254 | sidecarContainer, err := getSidecarContainer(pod.Annotations, id, image, imagePullPolicy, req.Namespace, apiSrvAddress, placementAddress, tokenMount, trustAnchors, certCh ain, certKey, sentryAddress, mtlsEnabled, identity) 255 | if err != nil { 256 | return nil, err 257 | } 258 | // 校验传入的http请求中的POD的spec.containers容器列表参数中是否含有其他容器镜像 259 | // 如果不存在,则表示该业务pod的sidecar只有daprd和业务本身 260 | // 否则,表示还有其他镜像,比如:日志镜像、CL5镜像等 261 | // 262 | // path表示pod yaml文件中资源位置,比如/spec/containers/- 263 | // value表示资源位置的资源值,比如:/spec/containers/env的值 264 | /* 265 | DAPR_HTTP_PORT: 3500 266 | DAPR_GRPC_PORT: 50001 267 | */ 268 | patchOps := []PatchOperation{} 269 | envPatchOps := []PatchOperation{} 270 | var path string 271 | var value interface{} 272 | if len(pod.Spec.Containers) == 0 { 273 | path = containersPath 274 | value = []corev1.Container{*sidecarContainer} 275 | } else { 276 | // 如果除了daprd runtime sidecar镜像外,还存在其他镜像,则需要把所有的镜像都注入两个环境变量 277 | // 这两个环境变量分别是: dapr runtime http 3500和dapr runtime grpc 50001 278 | // 这业务POD中的每个container都应该通过dapr runtime sidecar访问外部 279 | envPatchOps = addDaprEnvVarsToContainers(pod.Spec.Containers) 280 | path = "/spec/containers/-" 281 | value = sidecarContainer 282 | } 283 | 284 | // 首先把daprd runtime sidecar容器注入业务POD中 285 | patchOps = append( 286 | patchOps, 287 | PatchOperation{ 288 | Op: "add", 289 | Path: path, 290 | Value: value, 291 | }, 292 | ) 293 | // 然后把该业务POD中的其他镜像,包括业务本身container等镜像,添加与daprd runtime sidecar相关的服务环境变量 294 | // 分别是http 3500和grpc 50001 295 | patchOps = append(patchOps, envPatchOps...) 296 | 297 | return patchOps, nil 298 | } 299 | 300 | // addDaprEnvVarsToContainers 把业务pod中的spec.containers遍历,分别添加上访问dapr runtime sidecar的环境变量 301 | // DAPR_HTTP_PORT http 3500和DAPR_GRPC_PORT grpc 50001 302 | // 注意:如果.specs.containers已经存在container引用该环境变量,则不修改和覆盖 303 | func addDaprEnvVarsToContainers(containers []corev1.Container) []PatchOperation { 304 | portEnv := []corev1.EnvVar{ 305 | { 306 | Name: userContainerDaprHTTPPortName, 307 | Value: strconv.Itoa(sidecarHTTPPort), 308 | }, 309 | { 310 | Name: userContainerDaprGRPCPortName, 311 | Value: strconv.Itoa(sidecarAPIGRPCPort), 312 | }, 313 | } 314 | envPatchOps := []PatchOperation{} 315 | for i, container := range containers { 316 | path := fmt.Sprintf("%s/%d/env", containersPath, i) 317 | patchOps := getEnvPatchOperations(container.Env, portEnv, path) 318 | envPatchOps = append(envPatchOps, patchOps...) 319 | } 320 | return envPatchOps 321 | } 322 | ``` 323 | 324 | 最终业务POD的.spec.containers会构成建成: 325 | 1. 增加容器daprd runtime sidecar到业务POD中 326 | 2. 为该业务POD中的所有其他container增加,访问该POD中的dapr runtime sidecar容器所需要的两个环境变量,分别是DAPR_HTTP_PORT:3500和DAPR_GRPC_PORT:50001 327 | 328 | 第二点通过k8s准入控制器修改数据后的响应数据有关podpatches .spec.containers的修改,如下数据协议格式所示: 329 | 330 | ```json 331 | [{ 332 | "op": "add", 333 | "path": "/spec/containers/-", 334 | "value": "json(sidecarContainer)" 335 | }, 336 | { 337 | "op": "add", 338 | "path":"/spec/containers/env", 339 | "value": [ 340 | { 341 | "name": "DAPR_HTTP_PORT", 342 | "value": "3500" 343 | }, 344 | { 345 | "name": "DAPR_GRPC_PORT", 346 | "value": "50001" 347 | } 348 | ] 349 | }] 350 | ``` 351 | 352 | ### 注入dapr runtime sidecar容器 353 | 354 | dapr runtime sidecar注入到业务POD中,主要包括业务POD自身容器的环境变量的注入过程(dapr http和grpc,保证所有业务容器都可以通过dapr runtime sidecar访问外部服务),以及dapr runtime sidecar的注入过程;其中后者的注入包括了对业务服务治理方面的参数设置。 355 | 356 | ```golang 357 | // getSidecarContainer 通过参数构建dapr runtime sidecar容器参数对象 358 | func getSidecarContainer(annotations map[string]string, id, daprSidecarImage, imagePullPolicy, namespace, controlPlaneAddress, placementServiceAddress string, tokenVolumeMount *corev1.VolumeMount, trustAnchors, certChain, certKey, sentryAddress string, mtlsEnabled bool, identity string) (*corev1.Container, error) { 359 | // 通过pod中的annotations获取一些参数值: 360 | // 1. dapr.io/app-port业务服务端口 361 | // 2. dapr.io/metrics-port 362 | // 3. dapr.io/app-max-concurrency业务POD的最大处理并发量 363 | // 4. dapr.io/app-ssl SSL校验 364 | // 5. dapr.io/http-max-request-size 每个请求最大处理的数据量size 365 | appPort, err := getAppPort(annotations) 366 | appPortStr := "" 367 | if appPort > 0 { 368 | appPortStr = fmt.Sprintf("%v", appPort) 369 | } 370 | metricsPort := getMetricsPort(annotations) 371 | maxConcurrency, err := getMaxConcurrency(annotations) 372 | sslEnabled := appSSLEnabled(annotations) 373 | requestBodySize, err := getMaxRequestBodySize(annotations) 374 | 375 | // 获取dapr runtime sidecar镜像拉取策略={Always, Never和IfNotPresent} 376 | pullPolicy := getPullPolicy(imagePullPolicy) 377 | // 获取dapr runtime sidecar容器的健康探针: http 3500端口服务,router: /v1.0/healthz 378 | httpHandler := getProbeHTTPHandler(sidecarHTTPPort, apiVersionV1, sidecarHealthzPath) 379 | allowPrivilegeEscalation := false 380 | 381 | // 构建dapr runtime sidecar所需要的容器资源配置yaml 382 | c := &corev1.Container{ 383 | Name: sidecarContainerName, // 容器名:daprd 384 | Image: daprSidecarImage, // 镜像地址 385 | ImagePullPolicy: pullPolicy, // 镜像拉取策略 386 | SecurityContext: &corev1.SecurityContext{ 387 | AllowPrivilegeEscalation: &allowPrivilegeEscalation, 388 | }, 389 | // 业务POD对外暴露的端口服务,也即是dapr runtime sidecar暴露的dapr service 390 | // 包括:http 3500、grpc 50001, 内部grpc 50002和metrics 9090 391 | Ports: []corev1.ContainerPort{ 392 | { 393 | ContainerPort: int32(sidecarHTTPPort), 394 | Name: sidecarHTTPPortName, 395 | }, 396 | { 397 | ContainerPort: int32(sidecarAPIGRPCPort), 398 | Name: sidecarGRPCPortName, 399 | }, 400 | { 401 | ContainerPort: int32(sidecarInternalGRPCPort), 402 | Name: sidecarInternalGRPCPortName, 403 | }, 404 | { 405 | ContainerPort: int32(metricsPort), 406 | Name: sidecarMetricsPortName, 407 | }, 408 | }, 409 | // daprd runtime sidecar容器服务启动的命令:/darpd 410 | Command: []string{"/daprd"}, 411 | // daprd 运行需要的环境变量 412 | Env: []corev1.EnvVar{ 413 | { 414 | Name: utils.HostIPEnvVar, 415 | ValueFrom: &corev1.EnvVarSource{ 416 | FieldRef: &corev1.ObjectFieldSelector{ 417 | FieldPath: "status.podIP", 418 | }, 419 | }, 420 | }, 421 | ... 422 | }, 423 | // daprd runtime sidecar容器运行所需要的参数 424 | Args: []string{ 425 | "--mode", "kubernetes", // 运行模式k8s 426 | "--dapr-http-port", fmt.Sprintf("%v", sidecarHTTPPort), // http 3500 427 | "--dapr-grpc-port", fmt.Sprintf("%v", sidecarAPIGRPCPort), // grpc 50001 428 | "--dapr-internal-grpc-port", fmt.Sprintf("%v", sidecarInternalGRPCPort), // grpc 50002 429 | "--app-port", appPortStr, // 业务提供的服务端口 430 | "--app-id", id, // 业务ID 431 | "--control-plane-address", controlPlaneAddress, // dapr runtime sidecar API Server 432 | "--app-protocol", getProtocol(annotations), // dapr.io/app-protocol应用服务端口协议 433 | "--placement-host-address", placementServiceAddress, // dapr placement服务地址列表 434 | "--config", getConfig(annotations), // dapr.io/config配置 435 | "--log-level", getLogLevel(annotations), // dapr.io/log-level日志级别 436 | "--app-max-concurrency", fmt.Sprintf("%v", maxConcurrency), // 业务POD最大并发量 437 | "--sentry-address", sentryAddress, // 证书提供者服务 438 | "--metrics-port", fmt.Sprintf("%v", metricsPort), // metrics服务 439 | "--dapr-http-max-request-size", fmt.Sprintf("%v", requestBodySize), // 业务POD请求最大处理数据量 440 | }, 441 | // 就绪探针,当业务POD启动时,判断该服务是否可以正常提供服务的校验 442 | // 443 | // 这里的就绪探针和存活探针都是使用的3500 /v1.0/healthz路由服务作为入口 444 | ReadinessProbe: &corev1.Probe{ 445 | Handler: httpHandler, 446 | // annotations中的dapr.io/sidecar-readiness-probe-delay-seconds参数 447 | // 初始化默认值:3s 448 | InitialDelaySeconds: getInt32AnnotationOrDefault(annotations, daprReadinessProbeDelayKey, defaultHealthzProbeDelaySeconds), 449 | // annotations中的dapr.io/sidecar-readiness-probe-timeout-seconds参数 450 | // 超时默认值:3s 451 | TimeoutSeconds: getInt32AnnotationOrDefault(annotations, daprReadinessProbeTimeoutKey, defaultHealthzProbeTimeoutSeconds), 452 | // annotations中的dapr.io/sidecar-readiness-probe-period-seconds参数 453 | // 探针周期默认值:6s 454 | PeriodSeconds: getInt32AnnotationOrDefault(annotations, daprReadinessProbePeriodKey, defaultHealthzProbePeriodSeconds), 455 | // annotations中的dapr.io/sidecar-readiness-probe-threshold参数 456 | // 默认值: 3,表示如果3次探测都失败,表示该业务POD不可用 457 | FailureThreshold: getInt32AnnotationOrDefault(annotations, daprReadinessProbeThresholdKey, defaultHealthzProbeThreshold), 458 | }, 459 | // 存活探针,也就是常规的健康检查 460 | LivenessProbe: &corev1.Probe{ 461 | Handler: httpHandler, 462 | // dapr.io/sidecar-liveness-probe-delay-seconds 463 | // 初始化默认值:3s 464 | InitialDelaySeconds: getInt32AnnotationOrDefault(annotations, daprLivenessProbeDelayKey, defaultHealthzProbeDelaySeconds), 465 | // dapr.io/sidecar-liveness-probe-timeout-seconds 466 | // 超时默认值:3s 467 | TimeoutSeconds: getInt32AnnotationOrDefault(annotations, daprLivenessProbeTimeoutKey, defaultHealthzProbeTimeoutSeconds), 468 | // dapr.io/sidecar-liveness-probe-period-seconds 469 | // 探测周期默认值:6s 470 | PeriodSeconds: getInt32AnnotationOrDefault(annotations, daprLivenessProbePeriodKey, defaultHealthzProbePeriodSeconds), 471 | // dapr.io/sidecar-liveness-probe-threshold 472 | // 默认值:3,最大健康检查次数3,如果3次连续失败,则表示服务不可用 473 | FailureThreshold: getInt32AnnotationOrDefault(annotations, daprLivenessProbeThresholdKey, defaultHealthzProbeThreshold), 474 | }, 475 | } 476 | 477 | // 如果service account有指定,则传入到container中 478 | if tokenVolumeMount != nil { 479 | c.VolumeMounts = []corev1.VolumeMount{ 480 | *tokenVolumeMount, 481 | } 482 | } 483 | 484 | // 业务日志上报json是否开启:dapr.io/log-as-json 485 | if logAsJSONEnabled(annotations) { 486 | c.Args = append(c.Args, "--log-as-json") 487 | } 488 | 489 | // 是否开启性能采样 490 | if profilingEnabled(annotations) { 491 | c.Args = append(c.Args, "--enable-profiling") 492 | } 493 | 494 | // 证书mtls获取 495 | if mtlsEnabled && trustAnchors != "" { 496 | c.Args = append(c.Args, "--enable-mtls") 497 | c.Env = append(c.Env, corev1.EnvVar{ 498 | Name: certs.TrustAnchorsEnvVar, 499 | Value: trustAnchors, 500 | }, 501 | corev1.EnvVar{ 502 | Name: certs.CertChainEnvVar, 503 | Value: certChain, 504 | }, 505 | corev1.EnvVar{ 506 | Name: certs.CertKeyEnvVar, 507 | Value: certKey, 508 | }, 509 | corev1.EnvVar{ 510 | Name: "SENTRY_LOCAL_IDENTITY", 511 | Value: identity, 512 | }) 513 | } 514 | 515 | // ssl证书 516 | if sslEnabled { 517 | c.Args = append(c.Args, "--app-ssl") 518 | } 519 | 520 | // dapr.io/api-token-secret获取secret 521 | secret := getAPITokenSecret(annotations) 522 | if secret != "" { 523 | c.Env = append(c.Env, corev1.EnvVar{ 524 | Name: auth.APITokenEnvVar, 525 | ValueFrom: &corev1.EnvVarSource{ 526 | SecretKeyRef: &corev1.SecretKeySelector{ 527 | Key: "token", 528 | LocalObjectReference: corev1.LocalObjectReference{ 529 | Name: secret, 530 | }, 531 | }, 532 | }, 533 | }) 534 | } 535 | 536 | // dapr.io/app-token-secret获取app secret 537 | appSecret := GetAppTokenSecret(annotations) 538 | if appSecret != "" { 539 | c.Env = append(c.Env, corev1.EnvVar{ 540 | Name: auth.AppAPITokenEnvVar, 541 | ValueFrom: &corev1.EnvVarSource{ 542 | SecretKeyRef: &corev1.SecretKeySelector{ 543 | Key: "token", 544 | LocalObjectReference: corev1.LocalObjectReference{ 545 | Name: appSecret, 546 | }, 547 | }, 548 | }, 549 | }) 550 | } 551 | // 获取业务POD对资源的要求, 资源类型:cpu和内存 包括:limit和request 552 | // dapr.io/sidecar-cpu-limit 553 | // dapr.io/sidecar-memory-limit 554 | // dapr.io/sidecar-cpu-request 555 | // dapr.io/sidecar-memory-request 556 | resources, err := getResourceRequirements(annotations) 557 | if err != nil { 558 | log.Warnf("couldn't set container resource requirements: %s. using defaults", e rr) 559 | } 560 | // 并写入到container的resources 561 | if resources != nil { 562 | c.Resources = *resources 563 | } 564 | return c, nil 565 | } 566 | ``` 567 | 568 | 通过前面dapr injector整个注入源码分析,知道了MutatingAdmissionWebhooks webhook请求返回的PatchPod列表,来增加pod的其他资源注入或者修改,使得业务POD部署运行符合资源预期。 569 | 570 | ## 总结 571 | 572 | 通过对dapr injector源码分析,我们可以了解到dapr-system命名空间下的dapr-injector POD是通过注入k8s准入控制器,在提交资源对象到etcd集群之前,进行MutatingAdmissionWebhooks webhook请求,访问dapr-injector服务的http 4000端口, 路由为/mutate, 修改请求的资源对象。该过程包括dapr runtime sidecar容器的注入,以及其他容器参数访问dapr runtime sidecar的grpc和http端口服务。目的就是,所有业务POD中的所有容器都通过该dapr runtime sidecar容器暴露给外部访问。 573 | 574 | 而dapr runtime sidecar的注入到业务POD中的整个过程,包含了非常多的有关业务POD的参数控制, 包括: 575 | 1. 证书提供; 576 | 2. 业务服务流控; 577 | 3. 资源配额设置; 578 | 4. 业务服务性能开启; 579 | 5. 业务日志级别设置和json化; 580 | 6. 就绪和探活包检测及各个参数设置; 581 | 7. placement server和sentry server的访问服务端口配置; 582 | 8. 业务POD一次接收请求的最大包; 583 | 9. 指标上报的参数配置metrics; 584 | 10. 设置业务服务提供的端口和端口协议; 585 | 586 | 以上基本上把业务服务所关注的整个服务治理相关参数全部配置好了。减少业务开发人员针对这一块的开发工作。 587 | -------------------------------------------------------------------------------- /pkg/messaging.md: -------------------------------------------------------------------------------- 1 | messaging包用于dapr runtime之间的RPC服务调用, 它通过[nameresolution](https://github.com/dapr/components-contrib/tree/master/nameresolution)名字服务来实现服务发现,并进行grpc服务调用,然后请求到达被调dapr runtime后,再通过http/grpc来访问本地的应用端口服务,实现microservice rpc服务调用。 2 | 3 | 接下来就对messaging包实现服务之间的调用源码分析 4 | 5 | ## rpc请求pb协议 6 | 7 | service invocation服务调用PB协议定义如下: 8 | 9 | > github.com/dapr/dapr/dapr/proto/common/v1 10 | > github.com/dapr/dapr/dapr/proto/internals/v1 11 | 12 | ```pb 13 | // InvokeRequest 服务调用请求的数据 14 | message InvokeRequest { 15 | // 请求目标方法, 类似于路由 16 | string method = 1; 17 | 18 | // 任意类数据,这里是指数据流,里面含有数据流的数据类型 19 | google.protobuf.Any data = 2; 20 | 21 | // 数据类型的协议类型:application/json application/grpc+xxx 22 | string content_type = 3; 23 | 24 | // http扩展字段,表示http请求方法GET/POST/..., 以及queryString 25 | HTTPExtension http_extension = 4; 26 | } 27 | 28 | // InvokeResponse 服务调用响应结果 29 | message InvokeResponse { 30 | // 响应数据流,也包括数据类型 31 | google.protobuf.Any data = 1; 32 | 33 | // 数据类型的协议类型: application/json application/grpc+xxx 34 | string content_type = 2; 35 | } 36 | ``` 37 | 38 | 下面为RPC方法调用接口定义: 39 | 40 | ```pb 41 | // ServiceInvocation daprd runtime服务RPC调用接口 42 | // 两个方法: 43 | // 1. CallActor方法:dapr actor之间的RPC,它有一套自己独立的服务发现和服务注册,并通过dapr-system命名空间下的dapr-placement服务提供支持 44 | // 2. CallLocal方法:是指dapr runtime之间服务直接调用,它通过contrib-components中的nameresolution来实现服务发现。目前这个CallLocal只是说明本地调用,如果改为Call更加好一点 45 | service ServiceInvocation { 46 | // CallActor actor之间的rpc调用 47 | rpc CallActor (InternalInvokeRequest) returns (InternalInvokeResponse) {} 48 | 49 | // service invocation 服务RPC之间的调用 50 | rpc CallLocal (InternalInvokeRequest) returns (InternalInvokeResponse) {} 51 | } 52 | 53 | // Actor 包括actor_type和actor_id,代表一个actor实例对象 54 | message Actor { 55 | // Required. The type of actor. 56 | string actor_type = 1; 57 | 58 | // Required. The ID of actor type (actor_type) 59 | string actor_id = 2; 60 | } 61 | 62 | // InternalInvokeRequest 封装一个rpc请求,并携带版本信息、metadata和可能存在的actor信息 63 | message InternalInvokeRequest { 64 | // Required. The version of Dapr runtime API. 65 | APIVersion ver = 1; 66 | 67 | // Required. metadata holds caller's HTTP headers or gRPC metadata. 68 | map metadata = 2; 69 | 70 | // Required. message including caller's invocation request. 71 | common.v1.InvokeRequest message = 3; 72 | 73 | // Actor type and id. This field is used only for 74 | // actor service invocation. 75 | Actor actor = 4; 76 | } 77 | 78 | // InternalInvokeResponse 封装一个rpc响应信息,并协议响应码和错误信息,并协议headers、trailers 79 | message InternalInvokeResponse { 80 | // Required. HTTP/gRPC status. 81 | Status status = 1; 82 | 83 | // Required. The app callback response headers. 84 | map headers = 2; 85 | 86 | // App callback response trailers. 87 | // This will be used only for gRPC app callback 88 | map trailers = 3; 89 | 90 | // Callee's invocation response message. 91 | common.v1.InvokeResponse message = 4; 92 | } 93 | 94 | // ListStringValue metadata中的key值列表 95 | message ListStringValue { 96 | // The array of string. 97 | repeated string values = 1; 98 | } 99 | ``` 100 | 101 | 在`github.com/dapr/dapr/pkg/messaging/v1`下的代码都是针对RPC请求的参数,以及RPC响应参数的设置和获取,以及数据协议转换等, 比较简单 102 | 103 | ## rpc服务直接调用 104 | 105 | ```golang 106 | // messageClientConnection 函数类型用于创建一个grpc connection。这个函数实现类,则会实现ServiceInvocation接口 107 | type messageClientConnection func(address, id string, namespace string, skipTLS, recrea teIfExists, enableSSL bool) (*grpc.ClientConn, error) 108 | 109 | // DirectMessaging RPC调用封装接口,对daprd runtime提供RPC服务调用 110 | type DirectMessaging interface { 111 | Invoke(ctx context.Context, targetAppID string, req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error) 112 | } 113 | 114 | // directMessaging daprd runtime之间的直接调用 115 | type directMessaging struct { 116 | // appChannel 指同一个pod中的daprd runtime和microservice业务容器直接通信 117 | appChannel channel.AppChannel 118 | // 主调daprd runtime与被调daprd runtime建立grpc connection连接 119 | connectionCreatorFn messageClientConnection 120 | // 主调appid 121 | appID string 122 | // 模式:k8s或者standalone 123 | mode modes.DaprMode 124 | // daprd runtime之间的grpc port 125 | grpcPort int 126 | // daprd runtime所在pod的命名空间 127 | namespace string 128 | // 被调daprd runtime的服务发现,就是靠着contrib-components中的nameresolution名字服务来实现 129 | resolver nr.Resolver 130 | // 分布式跟踪配置 131 | tracingSpec config.TracingSpec 132 | // daprd runtime所在的服务地址 133 | hostAddress string 134 | // daprd runtime所在的服务主机名称 135 | hostName string 136 | // 最大请求数据包,单位:MB 137 | maxRequestBodySize int 138 | } 139 | 140 | // nameresolution名字服务发现的数据返回对象 141 | type remoteApp struct { 142 | id string 143 | namespace string 144 | address string 145 | } 146 | 147 | // NewDirectMessaging 创建一个direct message接口对象 148 | func NewDirectMessaging( 149 | appID, namespace string, 150 | port int, mode modes.DaprMode, 151 | appChannel channel.AppChannel, 152 | clientConnFn messageClientConnection, 153 | resolver nr.Resolver, 154 | tracingSpec config.TracingSpec, maxRequestBodySize int) DirectMessaging { 155 | hAddr, _ := utils.GetHostAddress() 156 | hName, _ := os.Hostname() 157 | return &directMessaging{ 158 | appChannel: appChannel, 159 | connectionCreatorFn: clientConnFn, 160 | appID: appID, 161 | mode: mode, 162 | grpcPort: port, 163 | namespace: namespace, 164 | resolver: resolver, 165 | tracingSpec: tracingSpec, 166 | hostAddress: hAddr, 167 | hostName: hName, 168 | maxRequestBodySize: maxRequestBodySize, 169 | } 170 | } 171 | 172 | // Invoke 用于daprd runtime发起一个rpc直接调用,并返回rpc请求的响应数据. 173 | func (d *directMessaging) Invoke(ctx context.Context, targetAppID string, req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error) { 174 | // 通过参数targetAppId,被调appid,通过服务发现来找到远端daprd runtime的地址信息 175 | app, err := d.getRemoteApp(targetAppID) 176 | if err != nil { 177 | return nil, err 178 | } 179 | 180 | // 并校验被调daprd runtime目标服务的appid和命名空间,是否和本daprd runtime一致 181 | // 如果一致,则表示这个调用为本地调用,直接通过appChannel访问本POD中的业务microservice容器服务 182 | if app.id == d.appID && app.namespace == d.namespace { 183 | return d.invokeLocal(ctx, req) 184 | } 185 | // 否则,发起远程调用,则首先通过daprd runtime访问被调的daprd runtime服务 186 | return d.invokeWithRetry(ctx, retry.DefaultLinearRetryCount, retry.DefaultLinearBackoffInterval, app, d.invokeRemote, req) 187 | } 188 | 189 | // requestAppIDAndNamespace 解析目标appid,获取目标服务的appid和namespace 190 | func (d *directMessaging) requestAppIDAndNamespace(targetAppID string) (string, string, error) { 191 | items := strings.Split(targetAppID, ".") 192 | if len(items) == 1 { 193 | // 如果目标appid不携带namespace,则表示目标服务与主调服务在同一个命名空间 194 | return targetAppID, d.namespace, nil 195 | } else if len(items) == 2 { 196 | return items[0], items[1], nil 197 | } else { 198 | return "", "", errors.Errorf("invalid app id %s", targetAppID) 199 | } 200 | } 201 | 202 | // invokeWithRetry 发起daprd runtime远程调用,是daprd runtime之间的RPC服务调用 203 | func (d *directMessaging) invokeWithRetry( 204 | ctx context.Context, 205 | numRetries int, 206 | backoffInterval time.Duration, 207 | app remoteApp, 208 | fn func(ctx context.Context, appID, namespace, appAddress string, req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error), 209 | req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error) { 210 | // 重试和退避策略,fn是指invokeRemote 211 | for i := 0; i < numRetries; i++ { 212 | // fn是指invokeRemote方法调用,它用于创建grpc client connection,并通过 213 | // 上面pb协议定义的ServiceInvocation接口CallLocal进行rpc服务调用 214 | resp, err := fn(ctx, app.id, app.namespace, app.address, req) 215 | // 如果rpc调用成功,则直接返回 216 | if err == nil { 217 | return resp, nil 218 | } 219 | // 否则执行退避策略和重试策略 220 | time.Sleep(backoffInterval) 221 | 222 | code := status.Code(err) 223 | // 如果返回的错误,不可达或者没有授权访问,则直接试图发起grpc client连接建立 224 | // 如果失败,直接返回失败错误 225 | // 否则如果为其他错误,则继续重试 226 | if code == codes.Unavailable || code == codes.Unauthenticated { 227 | _, connerr := d.connectionCreatorFn(app.address, app.id, app.namespace, false, true, false) 228 | if connerr != nil { 229 | return nil, connerr 230 | } 231 | continue 232 | } 233 | return resp, err 234 | } 235 | return nil, errors.Errorf("failed to invoke target %s after %v retries", app.id, numRetries) 236 | } 237 | 238 | // invokeLocal 表示是业务POD中的daprd runtime访问本地的microservice 239 | // 也就是一个pod中的两个容器之间的服务调用:daprd runtime调用本地业务服务 240 | func (d *directMessaging) invokeLocal(ctx context.Context, req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error) { 241 | if d.appChannel == nil { 242 | return nil, errors.New("cannot invoke local endpoint: app channel not initialized") 243 | } 244 | 245 | // 通过directMessaging提供的appChannel进行http或者grpc本地服务调用 246 | return d.appChannel.InvokeMethod(ctx, req) 247 | } 248 | 249 | // invokeRemote 一次daprd runtime服务与远端的daprd runtime之间的RPC直接调用 250 | func (d *directMessaging) invokeRemote(ctx context.Context, appID, namespace, appAddress string, req *invokev1.InvokeMethodRequest) (*invokev1.InvokeMethodResponse, error) { 251 | // connectionCreatorFn函数创建与远端daprd runtime之间的grpc client connection 252 | // 并返回这个连接,这个connectionCreatorFn返回的connection实现了ServiceInvocation接口 253 | // 254 | // 目前这个connectionCreatorFn是指grpc中的manager的GetGRPCConnection方法 255 | conn, err := d.connectionCreatorFn(appAddress, appID, namespace, false, false, false) 256 | if err != nil { 257 | return nil, err 258 | } 259 | 260 | // 参数填充,包括tracing、appid和真正用于请求业务服务microservice的请求参数 261 | span := diag_utils.SpanFromContext(ctx) 262 | ctx = diag.SpanContextToGRPCMetadata(ctx, span.SpanContext()) 263 | 264 | d.addForwardedHeadersToMetadata(req) 265 | d.addDestinationAppIDHeaderToMetadata(appID, req) 266 | 267 | clientV1 := internalv1pb.NewServiceInvocationClient(conn) 268 | 269 | var opts []grpc.CallOption 270 | opts = append(opts, grpc.MaxCallRecvMsgSize(d.maxRequestBodySize*1024*1024), grpc.MaxCallSendMsgSize(d.maxRequestBodySize*1024*1024)) 271 | 272 | // 最终发起grpc client 远程调用,访问主调 273 | resp, err := clientV1.CallLocal(ctx, req.Proto(), opts...) 274 | if err != nil { 275 | return nil, err 276 | } 277 | 278 | // 返回响应信息 279 | return invokev1.InternalInvokeResponse(resp) 280 | } 281 | 282 | // addDestinationAppIDHeaderToMetadata 请求参数InvokeMethodRequest的metadata填充目标appid参数 283 | func (d *directMessaging) addDestinationAppIDHeaderToMetadata(appID string, req *invokev1.InvokeMethodRequest) { 284 | req.Metadata()[invokev1.DestinationIDHeader] = &internalv1pb.ListStringValue{ 285 | Values: []string{appID}, 286 | } 287 | } 288 | 289 | // addForwardedHeadersToMetadata 请求参数InvokeMethodRequest填充主调的地址和hostname等信息到metdata中 290 | func (d *directMessaging) addForwardedHeadersToMetadata(req *invokev1.InvokeMethodRequest) { 291 | metadata := req.Metadata() 292 | 293 | var forwardedHeaderValue string 294 | 295 | if d.hostAddress != "" { 296 | // Add X-Forwarded-For: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For 297 | metadata[fasthttp.HeaderXForwardedFor] = &internalv1pb.ListStringValue{ 298 | Values: []string{d.hostAddress}, 299 | } 300 | 301 | forwardedHeaderValue += "for=" + d.hostAddress + ";by=" + d.hostAddress + ";" 302 | } 303 | 304 | if d.hostName != "" { 305 | // Add X-Forwarded-Host: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host 306 | metadata[fasthttp.HeaderXForwardedHost] = &internalv1pb.ListStringValue{ 307 | Values: []string{d.hostName}, 308 | } 309 | 310 | forwardedHeaderValue += "host=" + d.hostName 311 | } 312 | 313 | // Add Forwarded header: https://tools.ietf.org/html/rfc7239 314 | metadata[fasthttp.HeaderForwarded] = &internalv1pb.ListStringValue{ 315 | Values: []string{forwardedHeaderValue}, 316 | } 317 | } 318 | 319 | // getRemoteApp 通过目标appid和nameresolution,进行服务发现并返回远端daprd runtime的地址信息 320 | func (d *directMessaging) getRemoteApp(appID string) (remoteApp, error) { 321 | // 通过目标appid获取appid和appid所在的namespace 322 | id, namespace, err := d.requestAppIDAndNamespace(appID) 323 | if err != nil { 324 | return remoteApp{}, err 325 | } 326 | 327 | // 并把目标appid和所在的namespace,以及daprd runtime作为输入参数 328 | // 通过nameresolution名字服务找到daprd runtime目标服务地址 329 | request := nr.ResolveRequest{ID: id, Namespace: namespace, Port: d.grpcPort} 330 | address, err := d.resolver.ResolveID(request) 331 | if err != nil { 332 | return remoteApp{}, err 333 | } 334 | 335 | // 并发响应结果返回,包括目标appid,namespace和address 336 | return remoteApp{ 337 | namespace: namespace, 338 | id: id, 339 | address: address, 340 | }, nil 341 | } 342 | ``` 343 | 344 | 通过messaging包,我们就可以实现集群内部微服务之间的RPC直接调用,最关键的地方在于nameresolution名字服务, 目前在contrib-components中的nameresolution支持两种名字服务,一种为k8s, 另一个种为mdns,其他暂时不支持。 345 | 346 | 1. k8s的nameresolution:{appId}-dapr.{namespace}.svc.cluster.local:{grpcPort} 347 | 2. mdns基于局域网的服务发现 348 | 349 | 另一方面directMessaging对象中的行为Invoke方法,调用的上游在`github.com/dapr/dapr/pkg/grpc`和`github.com/dapr/dapr/pkg/http`中。 350 | 351 | 针对grpc和http两种调用: 352 | 1. daprd runtime grpc对外提供的服务方法API为:**InvokeService**; 353 | 2. daprd runtime http对外提供的服务方法API为: **onDirectMessage**, 也就是路由为:`invoke/{id}/method/{method:*}`中 354 | 355 | 这样外部用户或者内部业务微服务,就可以通过网关或者自身的业务POD内部的daprd runtime容器,就可以在daprd runtime服务中找到目标业务POD服务,并进行rpc直接调用,并返回响应数据给主调方 356 | -------------------------------------------------------------------------------- /pkg/middleware.md: -------------------------------------------------------------------------------- 1 | 目前还剩下apis、grpc、channel、client、http、metrics、proto、components、diagnostics和middleware,以及testing包还没有进行源码阅读 2 | 3 | 本文结合configuration章节,讲解middleware包。middleware包主要用于configuration yaml全局配置的httpPipline的注入过程,当外部请求经过dapr runtime http服务时,则通过链表把请求串行给各个handler处理,最后再交给转发给本daprd runtime所在的业务服务microservice处理。 4 | 5 | 接下来就通过`github.com/dapr/dapr/pkg/http`和`github.com/dapr/dapr/cmd/daprd`,以及configuration配置文件中的httpPipeline来处理 6 | 7 | ## middleware中间件 8 | 9 | middleware是指daprd runtime http接收外部请求时,通过拦截器链表处理请求。 10 | 11 | ```golang 12 | // http Middleware中间件,链表结构函数类型 13 | type Middleware func(h fasthttp.RequestHandler) fasthttp.RequestHandler 14 | 15 | // HTTPPipeline http pipeline 拦截器列表 16 | type Pipeline struct { 17 | Handlers []Middleware 18 | } 19 | 20 | // BuildHTTPPipeline 无用方法, 可去掉 21 | // 因为configuration配置中的httpPipeline需要使用到daprd runtime运行时,无法独立完成 22 | func BuildHTTPPipeline(spec config.PipelineSpec) (Pipeline, error) { 23 | return Pipeline{}, nil 24 | } 25 | 26 | // Apply 把Pipeline中的拦截器列表链接成链表,索引为0的拦截器在链表首位。传入的handler在链表尾部 27 | func (p Pipeline) Apply(handler fasthttp.RequestHandler) fasthttp.RequestHandler { 28 | for i := len(p.Handlers) - 1; i >= 0; i-- { 29 | handler = p.Handlers[i](handler) 30 | } 31 | return handler 32 | } 33 | ``` 34 | 35 | ## daprd components middleware加载 36 | 37 | > github.com/dapr/dapr/cmd/daprd 38 | 39 | 在daprd runtime初始化启动过程中,首先会加载所有的components组件,这样业务服务在yaml配置中通过dapr-system命名空间下的dapr-operator服务动态加载引入时,就不用daprd runtime重启。这里就有了components有关middleware的加载过程, 也就是http的拦截器加载过程 40 | 41 | 目前contrib-components第三方组件库支持的middleware类型,可以参考:[middleware](https://github.com/dapr/components-contrib/tree/master/middleware), 拦截器有[bearer](https://github.com/dapr/components-contrib/tree/master/middleware/http/bearer), [nethttpadaptor](https://github.com/dapr/components-contrib/tree/master/middleware/http/nethttpadaptor), [oauth2](https://github.com/dapr/components-contrib/tree/master/middleware/http/oauth2), [oauth2clientcredentials](https://github.com/dapr/components-contrib/tree/master/middleware/http/oauth2clientcredentials), [opa](https://github.com/dapr/components-contrib/tree/master/middleware/http/opa), [ratelimit](https://github.com/dapr/components-contrib/tree/master/middleware/http/ratelimit), 这些拦截器在http请求的处理作用各个功能和作用,后续再聊 42 | 43 | 以上contrib-components第三方组件库并不是全部middleware,还有一些内置初始化的组件,如下所示: 44 | 45 | ```golang 46 | // 初始化加载所有的middleware到内存中,使得业务服务加载任何middleware配置,都可以直接找到该具体组件实例 47 | runtime.WithHTTPMiddleware( 48 | // 这里就直接内置了一种middleware拦截器——uppercase 49 | // Middleware拦截器函数类型一个实例就是把请求包体数据改为小写 50 | /* 51 | // createFullName 则表示uppercase拦截器的唯一实例名称,也可以用来表示uppercase的路径,用于configuration配置httpPipeline表示type 52 | func createFullName(name string) string { 53 | return strings.ToLower("middleware.http." + name) 54 | } 55 | */ 56 | // 全部通过pkg/components/middleware中的各个组件类型,分别进行初始化和注册过程,使得component配置加载时,可以根据type找到 57 | // 各个middleware拦截器实例的RequestHanlder 58 | http_middleware_loader.New("uppercase", func(metadata middleware.Metadata) http_middleware.Middleware { 59 | return func(h fasthttp.RequestHandler) fasthttp.RequestHandler { 60 | return func(ctx *fasthttp.RequestCtx) { 61 | body := string(ctx.PostBody()) 62 | ctx.Request.SetBody([]byte(strings.ToUpper(body))) 63 | h(ctx) 64 | } 65 | } 66 | }), 67 | // 然后分别通过pkg/components/middleware初始化加载oatuh2, oauth2clientcredentials, ratelimit 68 | // bearer,opa 69 | http_middleware_loader.New("oauth2", func(metadata middleware.Metadata) htt p_middleware.Middleware { 70 | handler, _ := oauth2.NewOAuth2Middleware().GetHandler(metadata) 71 | return handler 72 | }), 73 | http_middleware_loader.New("oauth2clientcredentials", func(metadata middlew are.Metadata) http_middleware.Middleware { 74 | handler, _ := oauth2clientcredentials.NewOAuth2ClientCredentialsMiddlew are(log).GetHandler(metadata) 75 | return handler 76 | }), 77 | http_middleware_loader.New("ratelimit", func(metadata middleware.Metadata) http_middleware.Middleware { 78 | handler, _ := ratelimit.NewRateLimitMiddleware(log).GetHandler(metadata ) 79 | return handler 80 | }), 81 | http_middleware_loader.New("bearer", func(metadata middleware.Metadata) htt p_middleware.Middleware { 82 | handler, _ := bearer.NewBearerMiddleware(log).GetHandler(metadata) 83 | return handler 84 | }), 85 | http_middleware_loader.New("opa", func(metadata middleware.Metadata) http_m iddleware.Middleware { 86 | handler, _ := opa.NewMiddleware(log).GetHandler(metadata) 87 | return handler 88 | }), 89 | ), 90 | ``` 91 | 92 | 如configuration配置文件如下所示: 93 | ```yaml 94 | apiVersion: dapr.io/v1alpha1 95 | kind: Configuration 96 | metadata: 97 | name: pipeline 98 | spec: 99 | tracing: 100 | samplingRate: "1" 101 | httpPipeline: 102 | handlers: 103 | - type: middleware.http.uppercase 104 | name: uppercase 105 | ``` 106 | 107 | 上面这个configuration yaml配置文件通过pkg/config配置中的k8s或者standalone方式进行配置加载,然后存储到daprd runtime的globalConfig中,最终通过`pkg/runtime/runtime.go`文件进行configuration的middleware拦截器初始化。 108 | 109 | 注意只有在configuration配置文件中写明了加载httpPipeline,才会在业务服务中使用到该middleware拦截器,下面针对**middleware.http.uppercase**进行源码阅读 110 | 111 | ```golang 112 | // initRuntime 初始化daprd runtime 113 | func (a *DaprRuntime) initRuntime(opts *runtimeOpts) error{ 114 | ...... 115 | pipeline, err := a.buildHTTPPipeline() 116 | ...... 117 | } 118 | 119 | // buildHTTPPipeline 通过遍历configuration配置中的httpPipeline列表,来初始化RequestHandler到pipeline的列表中 120 | func (a *DaprRuntime) buildHTTPPipeline() (http_middleware.Pipeline, error) { 121 | var handlers []http_middleware.Middleware 122 | 123 | if a.globalConfig != nil { 124 | // 如果globalConfig不为空, 则遍历configuration配置对象中.spec.httpPipeline.handlers列表 125 | for i := 0; i < len(a.globalConfig.Spec.HTTPPipelineSpec.Handlers); i++ { 126 | // 配置中的每个handler都是由{type, name, version,selector[{field, value}]}构成 127 | middlewareSpec := a.globalConfig.Spec.HTTPPipelineSpec.Handlers[i] 128 | // 前面daprd main初始化加载全部的components的middleware拦截器时,则可以通过middle.http.upppercase 129 | // 找到component实例对象 130 | // 如果是oauth2,则需要加载CRDs component组件实例名称为oauth2的组件,用于与第三方软件服务建立连接 131 | component := a.getComponent(middlewareSpec.Type, middlewareSpec.Name) 132 | if component == nil { 133 | return http_middleware.Pipeline{}, errors.Errorf("couldn't find middlew are component with name %s and type %s/%s", 134 | middlewareSpec.Name, 135 | middlewareSpec.Type, 136 | middlewareSpec.Version) 137 | } 138 | // 通过前面daprd main初始化component注册的middle.http.uppercase找到RequestHandler,并返回handler 139 | handler, err := a.httpMiddlewareRegistry.Create(middlewareSpec.Type, middle wareSpec.Version, 140 | middleware.Metadata{Properties: a.convertMetadataItemsToProperties(comp onent.Spec.Metadata)}) 141 | if err != nil { 142 | return http_middleware.Pipeline{}, err 143 | } 144 | log.Infof("enabled %s/%s http middleware", middlewareSpec.Type, middlewareS pec.Version) 145 | // 最终存储到handlers列表中 146 | handlers = append(handlers, handler) 147 | } 148 | } 149 | // 把handlers存储pipeline,并返回,至此configuration中的middleware加载、注册以及查找过程就全部弄清楚了 150 | return http_middleware.Pipeline{Handlers: handlers}, nil 151 | } 152 | ``` 153 | 154 | 上面buildHTTPPipeline方法,获取了业务服务关注的httpPipeline的middleware实例列表,但是这里还没有形成链表结构, 接下来讲述链表的建立过程,以及链表RequestHandler的队头和队尾插入过程 155 | 156 | **注意: 目前configuration配置文件中的httpPipeline中的selector数据还没有开始启用** 157 | 158 | ## http pipeline的链表建立 159 | 160 | > github.com/dapr/dapr/pkg/http 161 | 162 | 下面讲述daprd runtime对外提供http服务的启动过程中,httpPipeline加载过程。前面在daprd初始化,以及pkg/runtime/runtime.go中讲述了middleware的加载,httpPipeline配置加载,最终形成pipeline中的handlers列表过程 163 | 164 | 接下来,我们具体看daprd runtime http服务的拦截器构建成链表过程 165 | 166 | ```golang 167 | func (s *server) StartNonBlocking() { 168 | // handler嵌套handler,本身比较晦涩, 169 | // 简明就是外层套里层,外层先执行, 那么handler封装下来最后形成一个完整的链表如下所示: 170 | // 171 | // useTracing->useMetrics->useAPIAuthentication->useCors->useComponents->useRouter 172 | // 而useComponents则是对configuration指定的httpPipeline handlers列表组件构建成正在的链表 173 | // 174 | // 在components中的middleware列表,索引为0的在useCors后面开头, 175 | // 而索引最大的len(handlers)在useRouter前面 176 | // 最终就形成了一个对外提供服务的拦截器处理请求链表 177 | // 178 | // middleware.Metadata作为component通用参数,当作闭包在pkg/runtime/runtime.go加载业务服务配置的httpPipeline实例时,通过component oauth2、ratelimit等实现类来传入.spec.metadata数据, 赋值给handler的内部对象初始化,使得可以根据metadata传入的参数动态处理http请求 179 | handler := 180 | useAPIAuthentication( 181 | s.useCors( 182 | s.useComponents( 183 | s.useRouter()))) 184 | 185 | handler = s.useMetrics(handler) 186 | handler = s.useTracing(handler) 187 | 188 | customServer := &fasthttp.Server{ 189 | Handler: handler, 190 | MaxRequestBodySize: s.config.MaxRequestBodySize * 1024 * 1024, 191 | } 192 | 193 | go func() { 194 | log.Fatal(customServer.ListenAndServe(fmt.Sprintf(":%v", s.config.Port))) 195 | }() 196 | ...... 197 | } 198 | ``` 199 | 200 | 通过上面的分析,我们可以完整的看到middleware包整个链路的组件middleware/http加载、component业务配置的引入和加载,以及daprd runtime提供http服务对所有拦截器的建立过程。 201 | -------------------------------------------------------------------------------- /pkg/operator.md: -------------------------------------------------------------------------------- 1 | k8s operator是CoreOS团队(现已被RetHat收购)提出的,主要用于对k8s CRDs资源变化的监听,并根据配置资源的预期,来决策、调度和部署资源。k8s支持的资源比较有限,比如Deployment、Service、Ingress等等,但是它并不能支持各类资源的组合一件部署、以及k8s除了内置资源的其他资源定义,并不能识别. 当k8s api server监听到etcd资源发生变更后,就会给到各个k8s controllers进行相关的资源处理。 2 | 3 | dapr operator的主要用途是,针对k8s CRDs(custom resource definitions 自定义资源), 创建DaprController控制器,去监听资源变化, 进行资源部署,包括是否需要进行dapr service部署。所有的CRDs基本都是通过资源的spec的anntotions注释部分,携带特征参数资源。如:**dapr.io/enabled**, **dapr.io/app-id**, **dapr.io/metrics-port**等. 4 | 5 | 其中所有的CRDs的CustomController,k8s都只对其暴露了一个回调实现API——**Reconcile**. 这个就是各个k8s controllers,包括内置和自定义实现的controller。比如:内置DeploymentController和自定义实现的DaprController等。并针对自己感兴趣的资源进行调度和相关资源创建、变更等等。以达到服务运行的预期 6 | 7 | dapr operator其实是一个dapr系统服务,它存在于dapr-system命名空间下的dapr-operator pod服务中,并注册和订阅自己所关注的资源对象,以及为业务pod创建dapr service服务,并暴露一系列端口服务给外部使用。同时还对所有业务pod提供获取CRDs资源对象服务。这个是通过grpc 3500端口服务提供。 8 | 9 | 10 | ```golang 11 | // https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/reconcile#Reconciler 12 | // 13 | // Reconciler通过创建,更新或删除Kubernetes对象或通过更改集群外部系统(例如cloudproviders,github等)来为特定资源实现Kubernetes API。 14 | // 15 | // 当k8s api server通过watch etcd发生资源变化后,则会通过各个controller订阅的资源,调用各个Controller的Reconcile API,进行资源的创建和变更。 16 | type Reconciler interface { 17 | Reconcile(context.Context, Request) (Result, error) 18 | } 19 | ``` 20 | 21 | ## dapr operator 22 | 23 | dapr operator主要有两个作用: 24 | - Reconcile API用于dapr controller接收API Server发送过来的资源变更请求, 进行资源调度和部署; 25 | - 通过k8s api server的client,获取dapr configuration配置、Components组件列表(components-contrib)和订阅者列表。并通过grpc提供:**GetConfiguration**, **ListComponents**, **ListSubscriptions**和**ComponentUpdate**四个API服务。 26 | 27 | 针对第二点,主要是把提交的配置资源数据同步给dapr runtime。 28 | 29 | ```golang 30 | // DaprHandler 用于处理Dapr CRDs的生命周期,也就是k8s operator CRDs 31 | type DaprHandler struct { 32 | // Manager 用于初始化依赖,如:缓存、k8s api server client和其他依赖, 并通过该Manager创建DaprController 33 | mgr ctrl.Manager 34 | 35 | // k8s api server client通过k8s api server获取etcd存储的数据。 36 | client.Client 37 | // Scheme 是对所有API资源对象进行序列化和反序列化 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // NewDaprHandler 创建DaprHandler对象 42 | func NewDaprHandler(mgr ctrl.Manager) *DaprHandler { 43 | return &DaprHandler{ 44 | // Manager用于初始化依赖和启动DaprController 45 | mgr: mgr, 46 | 47 | // 通过Manager获取k8s api server client 48 | Client: mgr.GetClient(), 49 | // 通过Manager获取Scheme,来对资源对象进行序列化和反序列化 50 | Scheme: mgr.GetScheme(), 51 | } 52 | } 53 | 54 | // DaprHandler Init初始化Manager 55 | func (h *DaprHandler) Init() error { 56 | // 注册设置Service的字段索引匹配规则,来找到Service的Deployment实例名称Name 57 | if err := h.mgr.GetFieldIndexer().IndexField( 58 | context.TODO(), 59 | &corev1.Service{}, daprServiceOwnerField, func(rawObj client.Object) []string { 60 | svc := rawObj.(*corev1.Service) 61 | owner := meta_v1.GetControllerOf(svc) 62 | if owner == nil || owner.APIVersion != appsv1.SchemeGroupVersion.String() | | owner.Kind != "Deployment" { 63 | return nil 64 | } 65 | return []string{owner.Name} 66 | }); err != nil { 67 | return err 68 | } 69 | 70 | // 构建Manager所需要的Builder,以及定义DaprController关注的对象Deployment和定义Service 71 | // 并通过Builder构建DaprController,订阅所关注的对象 72 | return ctrl.NewControllerManagedBy(h.mgr). 73 | For(&appsv1.Deployment{}). 74 | Owns(&corev1.Service{}). 75 | Complete(h) 76 | } 77 | 78 | /* 79 | apiVersion: v1 80 | kind: Service 81 | metadata: 82 | ... 83 | ownerReferences: 84 | - apiVersion: apps/v1 85 | controller: true 86 | blockOwnerDeletion: true 87 | kind: Deployment 88 | name: dapr-appid 89 | uid: d9607e19-f88f-11e6-a518-42010a800195 90 | ... 91 | */ 92 | 93 | // daprServiceName 用于返回注入dapr POD的实例名称appid-dapr 94 | func (h *DaprHandler) daprServiceName(appID string) string { 95 | return fmt.Sprintf("%s-dapr", appID) 96 | } 97 | 98 | // Reconcile 用于接收订阅的资源配置,并做相关资源部署和调度动作 99 | func (h *DaprHandler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, er ror) { 100 | var deployment appsv1.Deployment 101 | expectedService := false 102 | // 通过Namespace/Name,获取Deployment资源, 如果不存在,则判断是否deployment删除了? 103 | if err := h.Get(ctx, req.NamespacedName, &deployment); err != nil { 104 | if apierrors.IsNotFound(err) { 105 | log.Debugf("deployment has be deleted, %s", req.NamespacedName) 106 | } else { 107 | log.Errorf("unable to get deployment, %s, err: %s", req.NamespacedName, err ) 108 | return ctrl.Result{}, err 109 | } 110 | } else { 111 | // 如果存在,但是通过DeletionTimestamp参数值,判断deployment是否正在删除中 112 | if deployment.DeletionTimestamp != nil { 113 | log.Debugf("deployment is being deleted, %s", req.NamespacedName) 114 | return ctrl.Result{}, nil 115 | } 116 | // 如果Deployment已存在,通过Deployment资源配置对象中,获取annotations注释配置中dapr.io/enabled, 117 | // 判断该值是否为true。如果为true,表示需要该业务pod需要启用dapr runtime container。 118 | expectedService = h.isAnnotatedForDapr(&deployment) 119 | } 120 | 121 | // 如果dapr.io/enabled=true, 则表示需要创建service,也就是deployment的service。这个service是指访问dapr container的服务。 122 | if expectedService { 123 | if err := h.ensureDaprServicePresent(ctx, req.Namespace, &deployment); err != n il { 124 | return ctrl.Result{Requeue: true}, err 125 | } 126 | } else { 127 | // 如果deployment的dapr service已存在,则需要删除service 128 | if err := h.ensureDaprServiceAbsent(ctx, req.NamespacedName); err != nil { 129 | return ctrl.Result{Requeue: true}, err 130 | } 131 | } 132 | return ctrl.Result{}, nil 133 | } 134 | 135 | // ensureDaprServicePresent 用于创建dapr service 136 | // 137 | // 因为业务pod只暴露dapr service,不会暴露业务pod的container。 138 | func (h *DaprHandler) ensureDaprServicePresent(ctx context.Context, namespace string, deployment *appsv1.Deployment) error { 139 | // 通过deployment的annotations中的dapr.io/app-id 140 | // 并校验appid是否合法. 141 | // 因为这个appid将会用于dapr runtime的dapr service name 142 | appID := h.getAppID(deployment) 143 | // 校验appid是否合法 144 | err := validation.ValidateKubernetesAppID(appID) 145 | if err != nil { 146 | return err 147 | } 148 | 149 | // 通过service的namespace和name(appid-dapr),获取dapr service 150 | mayDaprService := types.NamespacedName{ 151 | Namespace: namespace, 152 | Name: h.daprServiceName(appID), 153 | } 154 | var daprSvc corev1.Service 155 | // 这个就是通过k8s api service client获取dapr service 156 | if err := h.Get(ctx, mayDaprService, &daprSvc); err != nil { 157 | // 如果dapr service不存在,则需要为deployment创建dapr service 158 | if apierrors.IsNotFound(err) { 159 | log.Debugf("no service for deployment found, deployment: %s/%s", namespace, deployment.Name) 160 | // 创建dapr service 161 | return h.createDaprService(ctx, mayDaprService, deployment) 162 | } 163 | log.Errorf("unable to get service, %s, err: %s", mayDaprService, err) 164 | return err 165 | } 166 | return nil 167 | } 168 | 169 | // createDaprService 为业务deployment创建dapr service. 170 | func (h *DaprHandler) createDaprService(ctx context.Context, expectedService types.NamespacedName, deployment *appsv1.Deployment) error { 171 | // 通过deployment的annotations dapr.io/app-id获取appid 172 | appID := h.getAppID(deployment) 173 | // 通过deployment的annotations dapr.io/metrics-port获取指标上报服务端口 174 | metricsPort := h.getMetricsPort(deployment) 175 | 176 | // 构建一个dapr service所需要的结构对象。 177 | // {metadata, spec, status} 178 | service := &corev1.Service{ 179 | // metadata: {name, namespace, label, annotations} 180 | ObjectMeta: meta_v1.ObjectMeta{ 181 | Name: expectedService.Name, 182 | Namespace: expectedService.Namespace, 183 | Labels: map[string]string{daprEnabledAnnotationKey: "true"}, 184 | Annotations: map[string]string{ 185 | "prometheus.io/scrape": "true", 186 | "prometheus.io/port": strconv.Itoa(metricsPort), 187 | "prometheus.io/path": "/", 188 | appIDAnnotationKey: appID, 189 | }, 190 | }, 191 | // spec: {selector, clusterIP, ports} 192 | // 其中:selector: 表示对业务pod中deployment的name选择,也就是对deployment的选择。 193 | // ports 提供多端口服务,有80端口。 194 | Spec: corev1.ServiceSpec{ 195 | // service对deployment的选择,在Reconcile API会动态新增OwnerReference的.metadata.controller 196 | // 也就是service通过deployment的name来选择后端资源 197 | Selector: deployment.Spec.Selector.MatchLabels, 198 | ClusterIP: clusterIPNone, 199 | Ports: []corev1.ServicePort{ 200 | // dapr-http port: 80 target 3500 201 | { 202 | Protocol: corev1.ProtocolTCP, 203 | Port: 80, 204 | TargetPort: intstr.FromInt(daprSidecarHTTPPort), 205 | Name: daprSidecarHTTPPortName, 206 | }, 207 | // dapr-grpc port: 50001: target: 50001 208 | { 209 | Protocol: corev1.ProtocolTCP, 210 | Port: int32(daprSidecarAPIGRPCPort), 211 | TargetPort: intstr.FromInt(daprSidecarAPIGRPCPort), 212 | Name: daprSidecarAPIGRPCPortName, 213 | }, 214 | // dapr-internal port: 50002 target: 50002 215 | { 216 | Protocol: corev1.ProtocolTCP, 217 | Port: int32(daprSidecarInternalGRPCPort), 218 | TargetPort: intstr.FromInt(daprSidecarInternalGRPCPort), 219 | Name: daprSidecarInternalGRPCPortName, 220 | }, 221 | // dapr-metrics port: 9090 target: 9090 222 | { 223 | Protocol: corev1.ProtocolTCP, 224 | Port: int32(metricsPort), 225 | TargetPort: intstr.FromInt(metricsPort), 226 | Name: daprSidecarMetricsPortName, 227 | }, 228 | }, 229 | }, 230 | } 231 | // 设置控制关系,实际上是给pod添加了.metadata.ownerReferences字段 232 | // 233 | // 设置deployment与pod之间的生命周期关联 234 | if err := ctrl.SetControllerReference(deployment, service, h.Scheme); err != nil { 235 | return err 236 | } 237 | // 通过k8s api server client创建service服务对象 238 | if err := h.Create(ctx, service); err != nil { 239 | log.Errorf("unable to create Dapr service for deployment, service: %s, err: %s" , expectedService, err) 240 | return err 241 | } 242 | log.Debugf("created service: %s", expectedService) 243 | monitoring.RecordServiceCreatedCount(appID) 244 | return nil 245 | } 246 | 247 | // ensureDaprServiceAbsent 获取deploymentKey映射的services是否已经存在 248 | // 如果已经存在,则删除services 249 | func (h *DaprHandler) ensureDaprServiceAbsent(ctx context.Context, deploymentKey types.NamespacedName) error { 250 | var services corev1.ServiceList 251 | // 通过namespace和name,k8s api server client获取services列表 252 | if err := h.List(ctx, &services, 253 | client.InNamespace(deploymentKey.Namespace), 254 | client.MatchingFields{daprServiceOwnerField: deploymentKey.Name}); err != nil { log.Errorf("unable to list services, err: %s", err) 255 | return err 256 | } 257 | // 遍历每个service,并删除所有service 258 | for i := range services.Items { 259 | svc := services.Items[i] 260 | log.Debugf("deleting service: %s/%s", svc.Namespace, svc.Name) 261 | if err := h.Delete(ctx, &svc, client.PropagationPolicy(meta_v1.DeletePropagationBackground)); client.IgnoreNotFound(err) != nil { 262 | log.Errorf("unable to delete svc: %s/%s, err: %s", svc.Namespace, svc.Name, err) 263 | } else { 264 | log.Debugf("deleted service: %s/%s", svc.Namespace, svc.Name) 265 | appID := svc.Annotations[appIDAnnotationKey] 266 | monitoring.RecordServiceDeletedCount(appID) 267 | } 268 | } 269 | return nil 270 | } 271 | ``` 272 | 273 | 因为是CRDs自定义资源,需要注册到Scheme中,用于序列化和反序列化API对象. dapr operator需要识别三种CRDs,分别是:component, configuration和subscription 274 | 275 | ### CRDs:component 276 | 277 | ```golang 278 | // 注册components到scheme,包括两种:component和component list 279 | func addKnownTypes(scheme *runtime.Scheme) error { 280 | scheme.AddKnownTypes( 281 | // groupVersion: dapr.io/v1alpha1 282 | SchemeGroupVersion, 283 | &Component{}, 284 | &ComponentList{}, 285 | ) 286 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 287 | return nil 288 | } 289 | 290 | // Component的构成={kind, apiversion, metadata, spec, auth, scopes} 291 | // CRDs Component描述来Dapr component类型 292 | type Component struct { 293 | metav1.TypeMeta `json:",inline"` 294 | // +optional 295 | metav1.ObjectMeta `json:"metadata,omitempty"` 296 | // +optional 297 | Spec ComponentSpec `json:"spec,omitempty"` 298 | // +optional 299 | Auth `json:"auth,omitempty"` 300 | // +optional 301 | Scopes []string `json:"scopes,omitempty"` 302 | } 303 | 304 | // ComponentSpec={type, version, ignoreErrors, metadata, initTimout} 305 | type ComponentSpec struct { 306 | Type string `json:"type"` 307 | // +optional 308 | Version string `json:"version"` 309 | // +optional 310 | IgnoreErrors bool `json:"ignoreErrors"` 311 | Metadata []MetadataItem `json:"metadata"` 312 | // +optional 313 | InitTimeout string `json:"initTimeout"` 314 | } 315 | 316 | // MetadataItem={name, value, secretKeyRef} 317 | type MetadataItem struct { 318 | Name string `json:"name"` 319 | // +optional 320 | Value DynamicValue `json:"value,omitempty"` 321 | // +optional 322 | SecretKeyRef SecretKeyRef `json:"secretKeyRef,omitempty"` 323 | } 324 | 325 | // SecretKeyRef={name, key} 326 | type SecretKeyRef struct { 327 | Name string `json:"name"` 328 | Key string `json:"key"` 329 | } 330 | ``` 331 | 332 | 333 | ```yaml 334 | ----------------component.yaml----------------- 335 | apiVersion: dapr.io/v1alpha1 336 | kind: Component 337 | metadata: 338 | name: sample-topic 339 | spec: 340 | type: bindings.kafka 341 | metadata: 342 | # Kafka broker connection setting 343 | - name: brokers 344 | value: localhost:9092 345 | # consumer configuration: topic and consumer group 346 | - name: topics 347 | value: sample 348 | - name: consumerGroup 349 | value: group1 350 | # publisher configuration: topic 351 | - name: publishTopic 352 | value: sample 353 | - name: authRequired 354 | value: "false" 355 | ``` 356 | 357 | ### CRDs: configuration 358 | 359 | ```golang 360 | // Configuration={kind, apiversion, metadata, spec} 361 | // 362 | // Configuration描述来configuration资源协议 363 | type Configuration struct { 364 | metav1.TypeMeta `json:",inline"` 365 | // +optional 366 | metav1.ObjectMeta `json:"metadata,omitempty"` 367 | // +optional 368 | Spec ConfigurationSpec `json:"spec,omitempty"` 369 | } 370 | 371 | // ConfigurationSpec={httpPipeline, tracing, metric, mtls, secrets, accessControl} 372 | type ConfigurationSpec struct { 373 | // +optional 374 | HTTPPipelineSpec PipelineSpec `json:"httpPipeline,omitempty"` 375 | // +optional 376 | TracingSpec TracingSpec `json:"tracing,omitempty"` 377 | // +kubebuilder:default={enabled:true} 378 | MetricSpec MetricSpec `json:"metric,omitempty"` 379 | // +optional 380 | MTLSSpec MTLSSpec `json:"mtls,omitempty"` 381 | // +optional 382 | Secrets SecretsSpec `json:"secrets,omitempty"` 383 | // +optional 384 | AccessControlSpec AccessControlSpec `json:"accessControl,omitempty"` 385 | } 386 | 387 | // SecretsSpec is the spec for secrets configuration 388 | type SecretsSpec struct { 389 | Scopes []SecretsScope `json:"scopes"` 390 | } 391 | 392 | // SecretsScope defines the scope for secrets 393 | type SecretsScope struct { 394 | // +optional 395 | DefaultAccess string `json:"defaultAccess,omitempty"` 396 | StoreName string `json:"storeName"` 397 | // +optional 398 | AllowedSecrets []string `json:"allowedSecrets,omitempty"` 399 | // +optional 400 | DeniedSecrets []string `json:"deniedSecrets,omitempty"` 401 | } 402 | ``` 403 | 404 | ```yaml 405 | apiVersion: dapr.io/v1alpha1 406 | kind: Configuration 407 | metadata: 408 | name: daprConfig 409 | spec: 410 | tracing: 411 | samplingRate: "1" 412 | ``` 413 | 414 | ### CRDs: Subscription 415 | 416 | ```yaml 417 | apiVersion: dapr.io/v1alpha1 418 | kind: Subscription 419 | metadata: 420 | name: myevent-subscription 421 | spec: 422 | topic: deathStarStatus 423 | route: /dsstatus 424 | pubsubname: pubsub 425 | scopes: 426 | - app1 427 | - app2 428 | ``` 429 | 430 | ```golang 431 | // subscription={kind, apiversion, metadata, spec, scopes} 432 | // Subscription描述来订阅pub/sub事件 433 | type Subscription struct { 434 | metav1.TypeMeta `json:",inline"` 435 | // +optional 436 | metav1.ObjectMeta `json:"metadata,omitempty"` 437 | Spec SubscriptionSpec `json:"spec,omitempty"` 438 | // +optional 439 | Scopes []string `json:"scopes,omitempty"` 440 | } 441 | 442 | // SubscriptionSpeci 443 | type SubscriptionSpec struct { 444 | Topic string `json:"topic"` 445 | Route string `json:"route"` 446 | Pubsubname string `json:"pubsubname"` 447 | } 448 | 449 | type SubscriptionList struct { 450 | metav1.TypeMeta `json:",inline"` 451 | metav1.ListMeta `json:"metadata"` 452 | 453 | Items []Subscription `json:"items"` 454 | } 455 | ``` 456 | 457 | ## dapr operator server 458 | 459 | dapr operator提供两个作用: 460 | 1. 提供DaprController服务,用于向k8s api server订阅自己关心的资源;比如:component、subscription、或者configuration; 以及针对annotations中的自定义参数进行资源分配和资源部署; 比如前面说的业务pod的dapr service 461 | 2. 获取三类资源数据,包括:component、subscribe和configuration等 462 | 463 | 那么这部分主要是讲解第二点:components, subscription和configuration的资源获取。 464 | 465 | 在dapr runtime启动时,会获取所有注册到ETCD的CRDs相关资源,并对业务POD中的daprd runtime进行全局相关参数设置(http拦截器、分布式跟踪、metrics等)或者初始化与第三方软件服务的初始化连接(components-contrib)等等; 466 | 467 | ```golang 468 | // NewAPIServer返回一个api server,这个最终就是通过k8s api server client去获取daprd runtime所关注的各个类型资源 469 | func NewAPIServer(client client.Client) Server { 470 | // client 就是指k8s api server client 471 | return &apiServer{ 472 | Client: client, 473 | updateChan: make(chan *componentsapi.Component, 1), 474 | } 475 | } 476 | 477 | // Run 用于启动dapr runtime api server服务 478 | // 479 | // 该服务提供grpc 6500的端口服务, 这个是daprd runtime内部端口服务。 480 | // daprd runtime初始化启动时,会通过该grpc端口服务获取来自k8s etcd存储的相关组件、订阅和配置数据 481 | func (a *apiServer) Run(certChain *dapr_credentials.CertChain) { 482 | lis, err := net.Listen("tcp", fmt.Sprintf(":%v", serverPort)) 483 | if err != nil { 484 | log.Fatal("error starting tcp listener: %s", err) 485 | } 486 | 487 | opts, err := dapr_credentials.GetServerOptions(certChain) 488 | if err != nil { 489 | log.Fatal("error creating gRPC options: %s", err) 490 | } 491 | s := grpc.NewServer(opts...) 492 | operatorv1pb.RegisterOperatorServer(s, a) 493 | 494 | log.Info("starting gRPC server") 495 | if err := s.Serve(lis); err != nil { 496 | log.Fatalf("gRPC server error: %v", err) 497 | } 498 | } 499 | 500 | // GetConfiguration 通过传入资源的namespace和name,从k8s api server获取configuration(dapr runtime的全局配置) 501 | func (a *apiServer) GetConfiguration(ctx context.Context, in *operatorv1pb.GetConfigurationRequest) (*operatorv1pb.GetConfigurationResponse, error) { 502 | key := types.NamespacedName{Namespace: in.Namespace, Name: in.Name} 503 | var config configurationapi.Configuration 504 | // 通过k8s api server client 获取业务pod自己的配置configuration,或者全局configuration 505 | if err := a.Client.Get(ctx, key, &config); err != nil { 506 | return nil, errors.Wrap(err, "error getting configuration") 507 | } 508 | b, err := json.Marshal(&config) 509 | if err != nil { 510 | return nil, errors.Wrap(err, "error marshalling configuration") 511 | } 512 | // 返回给dapr runtime 513 | return &operatorv1pb.GetConfigurationResponse{ 514 | Configuration: b, 515 | }, nil 516 | } 517 | 518 | // ListComponents 获取components列表, 这个是获取整个集群中的所有dapr components资源配置 519 | func (a *apiServer) ListComponents(ctx context.Context, in *emptypb.Empty) (*operatorv1pb.ListComponentResponse, error) { 520 | var components componentsapi.ComponentList 521 | // 从k8s api server client获取所有的components组件列表 522 | // 523 | // 注意:这里是获取k8s集群内所有业务配置的使用components集合资源,也就是说 524 | // 如果pod A业务使用的redis,那这个redis也会在暂时在另一个pod B业务中会进行初始化和动态更新 525 | // 虽然这个pod B业务暂时不会使用该redis,并不代表未来不会使用,所以一个业务使用某个component,集群内的所有业务pod中的daprd runtime container都会加载该component。进行与第三方软件服务的连接初始化动作 526 | if err := a.Client.List(ctx, &components); err != nil { 527 | return nil, errors.Wrap(err, "error getting components") 528 | } 529 | // 把从k8s api server获取的components列表数据,转换成目标协议数据列表,并返回给主调方dapr runtime 530 | resp := &operatorv1pb.ListComponentResponse{ 531 | Components: [][]byte{}, 532 | } 533 | for i := range components.Items { 534 | c := components.Items[i] // Make a copy since we will refer to this as a reference in this loop. 535 | b, err := json.Marshal(&c) 536 | if err != nil { 537 | log.Warnf("error marshalling component: %s", err) 538 | continue 539 | } 540 | resp.Components = append(resp.Components, b) 541 | } 542 | return resp, nil 543 | } 544 | 545 | // ListSubscriptions 获取subscriptions列表 546 | func (a *apiServer) ListSubscriptions(ctx context.Context, in *emptypb.Empty) (*operatorv1pb.ListSubscriptionsResponse, error) { 547 | var subs subscriptionsapi.SubscriptionList 548 | // 根据subscription资源类型,从k8s api server中获取SubscriptionList资源列表 549 | if err := a.Client.List(ctx, &subs); err != nil { 550 | return nil, errors.Wrap(err, "error getting subscriptions") 551 | } 552 | // 并把获取到的SubscriptionList资源数据,转换成目标协议数据Subscriptions,并返回给上游daprd runtime 553 | resp := &operatorv1pb.ListSubscriptionsResponse{ 554 | Subscriptions: [][]byte{}, 555 | } 556 | for i := range subs.Items { 557 | s := subs.Items[i] // Make a copy since we will refer to this as a reference in this loop. 558 | b, err := json.Marshal(&s) 559 | if err != nil { 560 | log.Warnf("error marshalling subscription: %s", err) 561 | continue 562 | } 563 | resp.Subscriptions = append(resp.Subscriptions, b) 564 | } 565 | return resp, nil 566 | } 567 | 568 | // ComponentUpdate component更新 569 | // 570 | // 也就是说components-contrib第三方软件服务列表中的某些参数或者配置发生变更 571 | // 或者,业务pod服务需要对第三方软件服务商进行切换,这时需要进行component资源对象变更,并更新到所有的 572 | // daprd runtime 573 | func (a *apiServer) ComponentUpdate(in *emptypb.Empty, srv operatorv1pb.Operator_ComponentUpdateServer) error { 574 | log.Info("sidecar connected for component updates") 575 | 576 | // 通过k8s Operator捕获关心的资源变更通知事件={新增,更新} 577 | // 578 | // NewOperator会通过componentInfomer增加关注的资源事件,并异步通过updateChan来异步通知本dapr runtime container,进行资源更新 579 | for c := range a.updateChan { 580 | go func(c *componentsapi.Component) { 581 | // 把component资源对象转换成目标协议数据,并通过grpc stream推送给上游dapr runtime server 582 | b, err := json.Marshal(&c) 583 | if err != nil { 584 | log.Warnf("error serializing component %s (%s): %s", c.GetName(), c.Spe 585 | c.Type, err) 586 | return 587 | } 588 | err = srv.Send(&operatorv1pb.ComponentUpdateEvent{ 589 | Component: b, 590 | }) 591 | if err != nil { 592 | log.Warnf("error updating sidecar with component %s (%s): %s", c.GetNam 593 | e(), c.Spec.Type, err) 594 | return 595 | } 596 | log.Infof("updated sidecar with component %s (%s)", c.GetName(), c.Spec.Typ 597 | e) 598 | }(c) 599 | } 600 | return nil 601 | } 602 | ``` 603 | 604 | 通过上面k8s dapr operator server提供的components、subscriptions和configuration三类CRDs资源配置,当业务pod的dapr runtime container进行初始化时,会通过k8s dapr operator提供的3500 grpc服务来获取这三类CRDs资源对象,当有component发生变更时,也会进行整个k8s使用了dapr注入的业务pod进行component更新。 605 | 606 | 小结,这前面k8s operator两部分讲述了k8s DaprController注册和订阅关心的资源对象,并进行业务pod的dapr service部署和删除工作;以及为dapr runtime主服务在初始化阶段提供components、subscriptions和configuration资源对象的获取,并对这些资源对象进行初始化工作。当k8s componentInfomer收到component资源对象的变更时(新增、更新),就会通知业务pod的dapr runtime,并进行初始化或者重连动作。 607 | 608 | 609 | ### dapr operator server 610 | 611 | 初始化和启动dapr operator服务,并为dapr runtime提供grpc服务,获取三类CRDs资源对象。 612 | 613 | ```golang 614 | // NewOperator 在dapr-system空间下创建dapr-operator pod服务实例 615 | func NewOperator(config, certChainPath string, enableLeaderElection bool) Operator { 616 | // 通过~/.kube/config.yaml配置文件初始化与k8s api server的连接,并获得manager 617 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 618 | Scheme: scheme, 619 | MetricsBindAddress: "0", 620 | LeaderElection: enableLeaderElection, 621 | LeaderElectionID: "operator.dapr.io", 622 | }) 623 | if err != nil { 624 | log.Fatal("unable to start manager") 625 | } 626 | // 通过manager获取k8s api server的client以及注册CRDs的scheme对象 627 | daprHandler := handlers.NewDaprHandler(mgr) 628 | // 把k8s operator所有关注的资源对象,包括Deployment、Service等资源对象生命周期联系起来。统一管理 629 | if err := daprHandler.Init(); err != nil { 630 | log.Fatalf("unable to initialize handler, err: %s", err) 631 | } 632 | 633 | // 构建k8s operator 对象,并在dapr-system命名空间下为所有业务pod的dapr runtime container提供grpc服务,用于获取业务pod注册各类CRDs资源对象。包括subscriptions、components和configuration资源对象 634 | o := &operator{ 635 | daprHandler: daprHandler, 636 | mgr: mgr, 637 | client: mgr.GetClient(), 638 | configName: config, 639 | certChainPath: certChainPath, 640 | } 641 | o.apiServer = api.NewAPIServer(o.client) 642 | // 在把会发生动态变更(新增、更新)的component资源对象,进行订阅并通知给各个业务dapr runtime运行时 643 | // 这里是通过syncComponent的updateChan异步方式来给到ComponentUpdate API最终通过grpc stream推送给 644 | // 各个业务pod的dapr runtime container服务 645 | // 646 | // 增加订阅通知事件 647 | if componentInfomer, err := mgr.GetCache().GetInformer(context.TODO(), &componentsapi.Component{}); err != nil { 648 | log.Fatalf("unable to get setup components informer, err: %s", err) 649 | } else { 650 | componentInfomer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 651 | AddFunc: o.syncComponent, 652 | UpdateFunc: func(_, newObj interface{}) { 653 | o.syncComponent(newObj) 654 | }, 655 | }) 656 | } 657 | return o 658 | } 659 | 660 | // prepareConfig 用于获取dapr-system命名空间的k8s dapr operator服务配置 661 | func (o *operator) prepareConfig() { 662 | var err error 663 | o.config, err = LoadConfiguration(o.configName, o.client) 664 | if err != nil { 665 | log.Fatalf("unable to load configuration, config: %s, err: %s", o.configName, e rr) 666 | } 667 | o.config.Credentials = credentials.NewTLSCredentials(o.certChainPath) 668 | } 669 | 670 | // syncComponent 通过componentInfomer订阅component资源事件,来推送给所有的业务pod dapr runtime 671 | func (o *operator) syncComponent(obj interface{}) { 672 | c, ok := obj.(*componentsapi.Component) 673 | if ok { 674 | log.Debugf("observed component to be synced, %s/%s", c.Namespace, c.Name) 675 | o.apiServer.OnComponentUpdated(c) 676 | } 677 | } 678 | 679 | // Run 在dapr-system命名空间下启动dapr-operator pod服务 680 | // 681 | // 启动k8s dapr operator和对所有业务pod提供的grpc 3500端口服务(获取subscription、component和configuration CRDs资源对象) 682 | func (o *operator) Run(ctx context.Context) { 683 | defer runtimeutil.HandleCrash() 684 | ctx, cancel := context.WithCancel(ctx) 685 | defer cancel() 686 | o.ctx = ctx 687 | go func() { 688 | <-ctx.Done() 689 | log.Infof("Dapr Operator is shutting down") 690 | }() 691 | log.Infof("Dapr Operator is started") 692 | 693 | go func() { 694 | // 启动k8s dapr operator,也就是DaprController控制器,订阅所关注的资源对象 695 | if err := o.mgr.Start(ctx); err != nil { 696 | if err != nil { 697 | log.Fatalf("failed to start controller manager, err: %s", err) 698 | } 699 | } 700 | }() 701 | if !o.mgr.GetCache().WaitForCacheSync(ctx) { 702 | log.Fatalf("failed to wait for cache sync") 703 | } 704 | o.prepareConfig() 705 | 706 | var certChain *credentials.CertChain 707 | if o.config.MTLSEnabled { 708 | log.Info("mTLS enabled, getting tls certificates") 709 | // try to load certs from disk, if not yet there, start a watch on the local fi lesystem 710 | chain, err := credentials.LoadFromDisk(o.config.Credentials.RootCertPath(), o.c onfig.Credentials.CertPath(), o.config.Credentials.KeyPath()) 711 | if err != nil { 712 | fsevent := make(chan struct{}) 713 | 714 | go func() { 715 | log.Infof("starting watch for certs on filesystem: %s", o.config.Creden tials.Path()) 716 | err = fswatcher.Watch(ctx, o.config.Credentials.Path(), fsevent) 717 | if err != nil { 718 | log.Fatal("error starting watch on filesystem: %s", err) 719 | } 720 | }() 721 | 722 | <-fsevent 723 | log.Info("certificates detected") 724 | 725 | chain, err = credentials.LoadFromDisk(o.config.Credentials.RootCertPath(), o.config.Credentials.CertPath(), o.config.Credentials.KeyPath()) 726 | if err != nil { 727 | log.Fatal("failed to load cert chain from disk: %s", err) 728 | } 729 | } 730 | certChain = chain 731 | log.Info("tls certificates loaded successfully") 732 | } 733 | 734 | go func() { 735 | // 启动dapr-system命名空间下的k8s dapr operator服务的健康检查 736 | healthzServer := health.NewServer(log) 737 | healthzServer.Ready() 738 | 739 | err := healthzServer.Run(ctx, healthzPort) 740 | if err != nil { 741 | log.Fatalf("failed to start healthz server: %s", err) 742 | } 743 | }() 744 | 745 | // 启动所有业务pod的dapr runtime sidecar提供的grpc 3500端口服务,去获取三类CRDs资源或者主动推送component资源 746 | o.apiServer.Run(certChain) 747 | } 748 | ``` 749 | 750 | 小结:通过上面k8s dapr operator实例的创建,并通过dapr-system命名空间下dapr-operator服务的启动,会创建dapr operator服务,以及对所有业务pod提供grpc 3500端口服务,用于获取CRDs资源对象。并根据这些CRDs资源对象,进行dapr runtime的接收数据处理(拦截器、metrics、分布式跟踪)。以及第三方软件服务components-contrib的初始化连接操作。 751 | 752 | 753 | ### k8s operator client 754 | 755 | k8s operator client并不是dapr-system下dapr-operator pod服务提供的端口服务,而是通过这个operator/client包,可以建立与dapr-operator系统服务的grpc client服务连接。这样业务pod的dapr runtime sidecar就可以获取grpc 3500端口服务的client,并获取configuration、subscription和component三类CRDs资源对象 756 | 757 | ```golang 758 | // GetOperatorClient 通过address和serviceName,建立并获取与grpc 3500端口服务的通信client 759 | // 这样业务pod中的dapr runtime sidecar就可以与k8s dapr operator进行通信,获取CRDs资源 760 | func GetOperatorClient(address, serverName string, certChain *dapr_credentials.CertChain) (operatorv1pb.OperatorClient, *grpc.ClientConn, error) { 761 | unaryClientInterceptor := grpc_retry.UnaryClientInterceptor() 762 | 763 | if diag.DefaultGRPCMonitoring.IsEnabled() { 764 | unaryClientInterceptor = grpc_middleware.ChainUnaryClient( 765 | unaryClientInterceptor, 766 | diag.DefaultGRPCMonitoring.UnaryClientInterceptor(), 767 | ) 768 | } 769 | 770 | opts := []grpc.DialOption{grpc.WithUnaryInterceptor(unaryClientInterceptor)} 771 | 772 | if certChain != nil { 773 | cp := x509.NewCertPool() 774 | ok := cp.AppendCertsFromPEM(certChain.RootCA) 775 | if !ok { 776 | return nil, nil, errors.New("failed to append PEM root cert to x509 CertPoo l") 777 | } 778 | 779 | config, err := dapr_credentials.TLSConfigFromCertAndKey(certChain.Cert, certCha in.Key, serverName, cp) 780 | if err != nil { 781 | return nil, nil, errors.Wrap(err, "failed to create tls config from cert an d key") 782 | } 783 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(config))) 784 | } else { 785 | opts = append(opts, grpc.WithInsecure()) 786 | } 787 | 788 | conn, err := grpc.Dial(address, opts...) 789 | if err != nil { 790 | return nil, nil, err 791 | } 792 | return operatorv1pb.NewOperatorClient(conn), conn, nil 793 | } 794 | ``` 795 | 796 | 797 | ### 总结 798 | 799 | 通过k8s dapr operator创建DaprController控制器注册和订阅所关心的资源对象,Deployment, Service和配置中的annotations dapr自定义字段值,去动态地为业务pod创建dapr service,所以业务pod的主container并没有直接通过自身的端口服务用service暴露出去,而是暴露的业务pod中dapr runtime sidecar中的各个端口服务作为service,供外部使用; 800 | 801 | 这个业务pod中的dapr service提供了http 80端口服务、grpc 50001端口服务, grpc 50002内部端口服务以及metrics的9090端口服务 802 | 803 | 同时dap-system命名空间下的这个dapr-operator系统服务,也对所有业务pod提供了获取CRDs三类资源(components,subscriptions和configuration资源)的服务,使得业务pod中的dapr runtime sidecar在初始化时进行消息订阅、拦截器、metrics指标上报、证书建立等相关连接初始化的动作。并且在业务pod启用或者变更component资源时,进行初始化或者重连与第三方软件服务的通信动作。这样后续其他业务pod可以直接复用这些系统已有的资源服务。 804 | -------------------------------------------------------------------------------- /pkg/sentry01.md: -------------------------------------------------------------------------------- 1 | sentry服务的作用和源码分析 2 | 3 | 可以参考三篇通俗易懂的文章: 4 | 5 | 1. [数字签名是什么?](https://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html) 6 | 2. [HTTPS 精读之 TLS 证书校验](https://zhuanlan.zhihu.com/p/30655259) 7 | 3. [证书链-Digital Certificates](https://www.jianshu.com/p/46e48bc517d0) 8 | 9 | CA:Certificate Authority(证书签发机构), 用来签发公钥 10 | 公钥:public key 11 | 私钥:private key 12 | 13 | ### CA机构 14 | **需要CA机构的理由**: 15 | 16 | 比如:鲍勃有一把公钥和私钥,苏姗写信给鲍勃。公钥是公开,大家都可以拿到的;私钥只能鲍勃有,且不能泄漏 17 | 18 | **正常情况下**:苏珊把写信的内容通过公钥进行加密,鲍勃接收到苏珊加密的信件后,用私钥解密,就得到了信的可见内容; 19 | 20 | **异常情况下**: 21 | 1. 如果第三方窃取了鲍勃的私钥,则可以打开任何人写给鲍勃的信件。(这个需要鲍勃自己防窃取,暂时找不到合适的方式); 22 | 2. 如果第三方把苏珊的公钥给换成第三方自己的公钥,则第三方可以解析苏珊写给鲍勃的任何信件;(这个就可以通过CA机构解决); 23 | 24 | 针对第二种异常情况,需要一个证明该公钥是鲍勃身份的权威机构,如果第三方偷换了苏珊手上的公钥,则苏珊写信之前找这个权威机构查下自己手上的公钥是不是鲍勃的,如果不是,则表示该公钥被篡改了。而这个权威机构就是CA机构。它用来为想要使用证书的单位签署和颁布公钥证书。这样任何公钥都是安全的,且不会被偷换了。 25 | 26 | 那么在dapr-system命名空间下的dapr sentry服务,就是为所有想要CSR(certificate signing request, 证书签名请求), 获取数字证书,也即是证书公钥文件。这样经过认证的公钥,都是在CA机构进行备案且能查找公钥的企业或者个人的真实身份。且是权威的。 27 | 28 | 因为dapr sentry服务作为CA机构,它主要用于k8s集群内部的dapr runtime sidecar grpc的http2请求认证,内部认证且不会和外部有访问。所以自己内部建立一个CA机构就OK了。 29 | 30 | ### 私钥和公钥 31 | 32 | 公钥是指CERTIFICATE,该证书内容通过解码后,可以获取**协议版本号**, **签名算法**(sha256对数据进行哈希,然后与CA机构私钥进行RSA加密), **证书有效期范围**, **主体**(地址,机构名等), **公钥**等信息 33 | 34 | 公钥示例如下: 35 | ```shell 36 | -----BEGIN CERTIFICATE----- 37 | MIIDgzCCAmugAwIBAgIEckecVjANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJD 38 | TjESMBAGA1UECBMJZ3Vhbmdkb25nMREwDwYDVQQHEwhzaGVuemhlbjEQMA4GA1UE 39 | ChMHdGVuY2VudDEQMA4GA1UECxMHdGVuY2VudDEYMBYGA1UEAxMPc3NvLnRlbmNl 40 | bnQuY29tMB4XDTE1MTEzMDAyMDcxNloXDTE2MDIyODAyMDcxNlowcjELMAkGA1UE 41 | BhMCQ04xEjAQBgNVBAgTCWd1YW5nZG9uZzERMA8GA1UEBxMIc2hlbnpoZW4xEDAO 42 | BgNVBAoTB3RlbmNlbnQxEDAOBgNVBAsTB3RlbmNlbnQxGDAWBgNVBAMTD3Nzby50 43 | ZW5jZW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJvvN6AU 44 | eq01dFk+47xRF8OBIyKCxQUPvaGRJJKbh1F58jmyLSUfbN/HG99F+KbKxSr8zvkF 45 | FvJ5gRjqJjEX+Ed6KUBYIYm/OXOu6ylMGBdJElNQSgTJ88iwWeKVn1Ohq5MBVD6q 46 | mUz67R5XU8yaO/IIwAn2e0E0FLdJOr8m4T5BFMWXGT2hZbKLaIbK2QUrhwI34qVg 47 | 4wsFR2n6zaQBWLcyihBhYrTMzcionvFL7riH+NZflrTB55BH1b1S5y0jnpumvRHY 48 | fwOGGzJ7oKbUF1R39piBCfOk3x1I25uBMFmslpMhKFnRmwM+tz5BORjSQ0Vtx4X+ 49 | cX5GJswMslaJ0UMCAwEAAaMhMB8wHQYDVR0OBBYEFN1yU7xGDyNQB+9+BUofztoO 50 | bXMHMA0GCSqGSIb3DQEBCwUAA4IBAQBFq5Rjkz9+83J8XKwXfMqmRG9rqliBs26H 51 | /O6ONyHuHYuJ00MxAnMUOjXJFpPoGdItN/qTm3xHHYYEtFfO0TyJyJyviRixAdby 52 | foUvIWCBbVhbsbttkus+Aseg+nej8oaIM8Win6sxhmvaQMvQSHgaINgIJuxi9/AN 53 | 6mtrFWsQ59DtGLlV9YYPzXRyMmJQYsNkDdWr/RlFozuHPa7CTlTjk8iCaGa6nOQK 54 | eZhxvuhZG3iCoeuHVYAymJboZyTzSpNQnECh/KcUy7q15/aZvHd4UNq8gjWrYQVv 55 | CCU1V6S4KwyQXIr9kEiiE1UvNGB/KLJOxza4+lAhsN/3KpzYojhF 56 | -----END CERTIFICATE----- 57 | ``` 58 | 59 | 上面的公钥经过下面的代码解析后,公钥里面的数据如下所示: 60 | 61 | ```golang 62 | package main 63 | 64 | import ( 65 | "crypto/x509" 66 | "encoding/pem" 67 | "errors" 68 | "fmt" 69 | "io/ioutil" 70 | ) 71 | 72 | const ( 73 | Certificate = "CERTIFICATE" 74 | ECPrivateKey = "EC PRIVATE KEY" 75 | RSAPrivateKey = "RSA PRIVATE KEY" 76 | ) 77 | 78 | func decodeCertificatePEM(crtb []byte) (*x509.Certificate, []byte, error) { 79 | block, crtb := pem.Decode(crtb) 80 | if block == nil { 81 | return nil, crtb, errors.New("invalid PEM certificate") 82 | } 83 | if block.Type != Certificate { 84 | return nil, nil, nil 85 | } 86 | c, err := x509.ParseCertificate(block.Bytes) 87 | return c, crtb, err 88 | } 89 | 90 | func main() { 91 | var ( 92 | bts []byte 93 | cert *x509.Certificate 94 | err error 95 | ) 96 | bts, _ = ioutil.ReadFile("ca.pem") 97 | cert, bts, err = decodeCertificatePEM(bts) 98 | if err != nil { 99 | panic(err) 100 | } 101 | fmt.Println(*cert) 102 | fmt.Println(string(bts)) 103 | } 104 | ``` 105 | 106 | 公钥解析后的数据如下: 107 | 108 | ```shell 109 | Certificate: 110 | Data: 111 | Version: 3 (0x2) 112 | Serial Number: 1917295702 (0x72479c56) 113 | Signature Algorithm: sha256WithRSAEncryption 114 | Issuer: C=CN, ST=guangdong, L=shenzhen, O=tencent, OU=tencent, CN=sso.tencent.com 115 | Validity 116 | Not Before: Nov 30 02:07:16 2015 GMT 117 | Not After : Feb 28 02:07:16 2016 GMT 118 | Subject: C=CN, ST=guangdong, L=shenzhen, O=tencent, OU=tencent, CN=sso.tencent.com 119 | Subject Public Key Info: 120 | Public Key Algorithm: rsaEncryption 121 | Public-Key: (2048 bit) 122 | Modulus: 123 | 00:9b:ef:37:a0:14:7a:ad:35:74:59:3e:e3:bc:51: 124 | 17:c3:81:23:22:82:c5:05:0f:bd:a1:91:24:92:9b: 125 | 87:51:79:f2:39:b2:2d:25:1f:6c:df:c7:1b:df:45: 126 | f8:a6:ca:c5:2a:fc:ce:f9:05:16:f2:79:81:18:ea: 127 | 26:31:17:f8:47:7a:29:40:58:21:89:bf:39:73:ae: 128 | eb:29:4c:18:17:49:12:53:50:4a:04:c9:f3:c8:b0: 129 | 59:e2:95:9f:53:a1:ab:93:01:54:3e:aa:99:4c:fa: 130 | ed:1e:57:53:cc:9a:3b:f2:08:c0:09:f6:7b:41:34: 131 | 14:b7:49:3a:bf:26:e1:3e:41:14:c5:97:19:3d:a1: 132 | 65:b2:8b:68:86:ca:d9:05:2b:87:02:37:e2:a5:60: 133 | e3:0b:05:47:69:fa:cd:a4:01:58:b7:32:8a:10:61: 134 | 62:b4:cc:cd:c8:a8:9e:f1:4b:ee:b8:87:f8:d6:5f: 135 | 96:b4:c1:e7:90:47:d5:bd:52:e7:2d:23:9e:9b:a6: 136 | bd:11:d8:7f:03:86:1b:32:7b:a0:a6:d4:17:54:77: 137 | f6:98:81:09:f3:a4:df:1d:48:db:9b:81:30:59:ac: 138 | 96:93:21:28:59:d1:9b:03:3e:b7:3e:41:39:18:d2: 139 | 43:45:6d:c7:85:fe:71:7e:46:26:cc:0c:b2:56:89: 140 | d1:43 141 | Exponent: 65537 (0x10001) 142 | X509v3 extensions: 143 | X509v3 Subject Key Identifier: 144 | DD:72:53:BC:46:0F:23:50:07:EF:7E:05:4A:1F:CE:DA:0E:6D:73:07 145 | Signature Algorithm: sha256WithRSAEncryption 146 | 45:ab:94:63:93:3f:7e:f3:72:7c:5c:ac:17:7c:ca:a6:44:6f: 147 | 6b:aa:58:81:b3:6e:87:fc:ee:8e:37:21:ee:1d:8b:89:d3:43: 148 | 31:02:73:14:3a:35:c9:16:93:e8:19:d2:2d:37:fa:93:9b:7c: 149 | 47:1d:86:04:b4:57:ce:d1:3c:89:c8:9c:af:89:18:b1:01:d6: 150 | f2:7e:85:2f:21:60:81:6d:58:5b:b1:bb:6d:92:eb:3e:02:c7: 151 | a0:fa:77:a3:f2:86:88:33:c5:a2:9f:ab:31:86:6b:da:40:cb: 152 | d0:48:78:1a:20:d8:08:26:ec:62:f7:f0:0d:ea:6b:6b:15:6b: 153 | 10:e7:d0:ed:18:b9:55:f5:86:0f:cd:74:72:32:62:50:62:c3: 154 | 64:0d:d5:ab:fd:19:45:a3:3b:87:3d:ae:c2:4e:54:e3:93:c8: 155 | 82:68:66:ba:9c:e4:0a:79:98:71:be:e8:59:1b:78:82:a1:eb: 156 | 87:55:80:32:98:96:e8:67:24:f3:4a:93:50:9c:40:a1:fc:a7: 157 | 14:cb:ba:b5:e7:f6:99:bc:77:78:50:da:bc:82:35:ab:61:05: 158 | 6f:08:25:35:57:a4:b8:2b:0c:90:5c:8a:fd:90:48:a2:13:55: 159 | 2f:34:60:7f:28:b2:4e:c7:36:b8:fa:50:21:b0:df:f7:2a:9c: 160 | d8:a2:38:45 161 | ``` 162 | 163 | 我们可以看到sso.tencent.com机构颁发的这个公钥,实际上已经过期了,表示这个证书已经失效。 164 | 165 | ## 证书协议 166 | 167 | ### x509标准 168 | 169 | [X.509是密码学里公钥证书的格式标准](https://zh.wikipedia.org/wiki/X.509) 170 | 171 | 证书组成结构标准用ASN.1(一种标准的语言)来进行描述. X.509 v3 数字证书结构如下: 172 | 173 | ```x509v3 174 | 证书 175 | 版本号 176 | 序列号 177 | 签名算法 178 | 颁发者 179 | 证书有效期 180 | 此日期前无效 181 | 此日期后无效 182 | 主题 183 | 主题公钥信息 184 | 公钥算法 185 | 主题公钥 186 | 颁发者唯一身份信息(可选项) 187 | 主题唯一身份信息(可选项) 188 | 扩展信息(可选项) 189 | ... 190 | 证书签名算法 191 | 数字签名 192 | ``` 193 | 194 | 将上面x509 v3版本协议数据,通过ASN1序列化为比特流,也就是序列化后的字符串数据。最后再在数据上标识该字符串数据的数据类型, 比如:CERTIFICATE、RSA PRIVATE KEY、EC PRIVATE KEY或者PRIVATE KEY等等类型 195 | 196 | 最终的PEM协议文件内容,标准格式如下所示: 197 | 198 | ```shell 199 | 1. CERTIFICATE类型 200 | -----BEGIN CERTIFICATE----- 201 | ...... 202 | -----END CERTIFICATE----- 203 | 204 | 2. RSA PRIVATE KEY类型 205 | -----BEGIN RSA PRIVATE KEY----- 206 | ...... 207 | -----END RSA PRIVATE KEY----- 208 | 209 | 3. EC PRIVATE KEY类型 210 | -----BEGIN EC PRIVATE KEY----- 211 | ..... 212 | -----END EC PRIVATE KEY----- 213 | 214 | 4. PRIVATE KEY类型 215 | -----BEGIN PRIVATE KEY----- 216 | ...... 217 | -----END PRIVATE KEY----- 218 | ``` 219 | 220 | 我们可以看到最终的协议证书文件的标准格式,如下: 221 | 222 | ```shell 223 | -----BEGIN TYPE----- 224 | ...... 225 | -----END TYPE----- 226 | ``` 227 | 228 | 那么我们在创建或者解析证书时,就是按照这个流程,进行封装或者解析。 229 | 230 | 231 | ## CA根证书和Issuer证书的解析 232 | 233 | dapr sentry内容非常多,但是具体也就做了一件事情,就是dapr runtime sidecar服务需要使用grpc服务的,为它颁布公钥和证书链. 234 | 235 | 服务提供者:dapr sentry 236 | 237 | 请求协议:grpc 238 | 239 | 默认端口:50001 240 | 241 | 路由:通过**/dapr.proto.sentry.v1.CA/SignCertificate**路由提供CSR请求服务 242 | 243 | 目的:颁布证书公钥文件 244 | 245 | ### CA机构 246 | 247 | 因为dapr sentry是一个内部集群的私有CA机构,只为内部的grpc提供证书公钥文件签署服务,首先需要创建一个CA机构,它主要做以下几件事情: 248 | 249 | 1. 为自己创建根公钥和私钥证书; 250 | 2. 再创建一个颁布者issuer公钥和私钥证书; 251 | 3. 最后再为通过grpc 50001端口请求的CSR,来颁布公钥证书文件。 252 | 253 | 再dapr sentry服务中,存在一个CA接口,它的定义如下所示: 254 | 255 | ```golang 256 | type CertificateAuthority interface { 257 | // 加载或者创建根级公钥和私钥证书 258 | LoadOrStoreTrustBundle() error 259 | // 获取CA证书Bundle 260 | GetCACertBundle() TrustRootBundler 261 | // 为CSR请求,签署公钥证书文件 262 | SignCSR(csrPem []byte, subject string, identity *identity.Bundle, ttl time.Duration, isCA bool) (*SignedCertificate, error) 263 | // 验证CSR请求是否合法 264 | ValidateCSR(csr *x509.CertificateRequest) error 265 | } 266 | ``` 267 | 268 | dapr sentry服务提供的默认CA机构,是自己内部私有的。但是该架构也预留了通过component-contrib公共接口提供的第三方CA机构服务。接下来,先讲解私有CA机构的实现 269 | 270 | ```golang 271 | // defaultCA dapr sentry服务提供的私有CA机构,它作为为CSR签署公钥证书文件 272 | type defaultCA struct { 273 | // bundle 是指CA根证书和中间证书机构链 274 | bundle *trustRootBundle 275 | // dapr sentry服务启动需要的参数,包括根证书路径、中间证书路径, 服务端口、证书有效期等 276 | config config.SentryConfig 277 | issuerLock *sync.RWMutex 278 | } 279 | 280 | // NewCertificateAuthority 创建一个CA对象,目前只支持defaultCA,也就是自身实现的私有CA机构 281 | func NewCertificateAuthority(config config.SentryConfig) (CertificateAuthority, error) { 282 | // Load future external CAs from components-contrib. 283 | switch config.CAStore { 284 | default: 285 | return &defaultCA{ 286 | config: config, 287 | issuerLock: &sync.RWMutex{}, 288 | }, nil 289 | } 290 | } 291 | 292 | // LoadOrStoreTrustBundle 根据dapr sentry加载证书或者创建CA机构证书 293 | func (c *defaultCA) LoadOrStoreTrustBundle() error { 294 | // 加载或者创建CA机构和中间机构的各个证书 295 | bundle, err := c.validateAndBuildTrustBundle() 296 | if err != nil { 297 | return err 298 | } 299 | 300 | c.bundle = bundle 301 | return nil 302 | } 303 | 304 | // validateAndBuildTrustBundle 验证CA机构证书或者创建CA机构证书 305 | func (c *defaultCA) validateAndBuildTrustBundle() (*trustRootBundle, error) { 306 | var issuerCreds *certs.Credentials 307 | var rootCertBytes []byte 308 | var issuerCertBytes []byte 309 | 310 | // 校验k8s或者hosted来判断是否应该创建TrustBundle 311 | // 也就是CA根证书和Issuer证书 312 | if !shouldCreateCerts(c.config) { 313 | // 进来,表示证书已经存在, 则从defaultCA对象的SentryConfig配置中获取路径 314 | // 并进行证书解析,构建成trustRootBundle证书链 315 | 316 | // 检测根证书路径是否存在,不存在. 317 | // 一般情况下,如果dapr sentry跑在k8s中,且dapr-trust-bundle secret对象已存在,这个RootCertPath路径可能不存在 318 | // 如果是运行在host上,走入到这个逻辑,RootCertPath是一定存在的。 319 | // 320 | // detectCertificates在30s内,每秒检测一次,查看RootCertPath根证书文件是否存在 321 | // 30s内如果检测都不存在,则返回异常 322 | err := detectCertificates(c.config.RootCertPath) 323 | if err != nil { 324 | return nil, err 325 | } 326 | 327 | // 从磁盘上读取三个证书路径: 328 | // 1. CA根证书文件数据;(公钥) 329 | // 2. 证书颁布者证书文件数据;(公钥) 330 | // 3. 证书颁布者密钥文件数据; 331 | // 这三个数据存储在证书链中, 协议格式:{RootCA: 根证书公钥, Cert: 证书颁布者公钥, Key: 证书颁布者私钥} 332 | certChain, err := credentials.LoadFromDisk(c.config.RootCertPath, c.config.IssuerCertPath, c.config.IssuerKeyPath) 333 | if err != nil { 334 | return nil, errors.Wrap(err, "error loading cert chain from disk") 335 | } 336 | 337 | // 根据证书颁布者的公钥证书和私钥数据流,解析成Credentials={PrivateKey, x509.Certificate} 338 | issuerCreds, err = certs.PEMCredentialsFromFiles(certChain.Cert, certChain.Key) 339 | if err != nil { 340 | return nil, errors.Wrap(err, "error reading PEM credentials") 341 | } 342 | 343 | // 把解析到的CA根证书和证书颁布者的公钥传入,并返回 344 | rootCertBytes = certChain.RootCA 345 | issuerCertBytes = certChain.Cert 346 | } else { 347 | // 否则,需要dapr sentry服务在初始化阶段去创建CA根证书、证书颁布者的公钥证书和私钥 348 | log.Info("root and issuer certs not found: generating self signed CA") 349 | var err error 350 | issuerCreds, rootCertBytes, issuerCertBytes, err = c.generateRootAndIssuerCerts() 351 | if err != nil { 352 | return nil, errors.Wrap(err, "error generating trust root bundle") 353 | } 354 | 355 | log.Info("self signed certs generated and persisted successfully") 356 | } 357 | 358 | // load trust anchors 359 | trustAnchors, err := certs.CertPoolFromPEM(rootCertBytes) 360 | if err != nil { 361 | return nil, errors.Wrap(err, "error parsing cert pool for trust anchors") 362 | } 363 | 364 | return &trustRootBundle{ 365 | issuerCreds: issuerCreds, 366 | trustAnchors: trustAnchors, 367 | trustDomain: c.config.TrustDomain, 368 | rootCertPem: rootCertBytes, 369 | issuerCertPem: issuerCertBytes, 370 | }, nil 371 | } 372 | 373 | // shouldCreateCerts 校验证书是否存在 374 | func shouldCreateCerts(conf config.SentryConfig) bool { 375 | // 检查如果dapr sentry是以k8s方式启动运行,则校验k8s secret是否存在dapr-trust-bundle实例 376 | // 比如: 377 | /* 378 | root:~$ kubectl describe secret dapr-trust-bundle -n dapr-system 379 | Name: dapr-trust-bundle 380 | Namespace: dapr-system 381 | Labels: 382 | Annotations: 383 | 384 | Type: Opaque 385 | 386 | Data 387 | ==== 388 | ca.crt: 704 bytes 389 | issuer.crt: 676 bytes 390 | issuer.key: 227 bytes 391 | */ 392 | exists, err := certs.CredentialsExist(conf) 393 | if err != nil { 394 | log.Errorf("error chcecking if credetials exist: %s", err) 395 | } 396 | // 如果存在表示不用再创建根证书和issuer证书 397 | if exists { 398 | return false 399 | } 400 | 401 | // 否则,校验CA根证书和issuer证书路径是否存在 402 | // 403 | // 如果不存在,则返回true,表示需要创建证书 404 | if _, err = os.Stat(conf.RootCertPath); os.IsNotExist(err) { 405 | return true 406 | } 407 | b, err := ioutil.ReadFile(conf.IssuerCertPath) 408 | if err != nil { 409 | return true 410 | } 411 | return len(b) == 0 412 | } 413 | 414 | // PEMCredentialsFromFiles 根据公钥和私钥文件数据流,构建成Credentials={PrivateKey, x509.Certificate}对象 415 | func PEMCredentialsFromFiles(certPem, keyPem []byte) (*Credentials, error) { 416 | // 先解析私钥数据流, 也就是解析下面这个x509 v3标准协议格式 417 | /* 418 | -----BEGIN TYPE----- 419 | ...... 420 | -----END TYPE----- 421 | */ 422 | // 这里我们只解析所有证书文件中的第一个x509 v3标准协议数据 423 | // 并返回自定义的PrivateKey={Type, 加密数据} 424 | pk, err := DecodePEMKey(keyPem) 425 | if err != nil { 426 | return nil, err 427 | } 428 | 429 | // 再解析公钥数据流, 解码成x509.Certificate列表 430 | crts, err := DecodePEMCertificates(certPem) 431 | if err != nil { 432 | return nil, err 433 | } 434 | 435 | // 如果公钥文件数据流中没有有效证书,则直接返回 436 | if len(crts) == 0 { 437 | return nil, errors.New("no certificates found") 438 | } 439 | 440 | // 校验私钥和公钥是否匹配 441 | // 匹配方式:私钥信息中的公钥是否和公钥信息相同 442 | match := matchCertificateAndKey(pk, crts[0]) 443 | if !match { 444 | return nil, errors.New("error validating credentials: public and private key pa ir do not match") 445 | } 446 | 447 | // 如果匹配, 则表示传入的私钥和公钥证书是一对 448 | // 并返回自定义证书的私钥和公钥数据流中的第一个公钥证书 449 | creds := &Credentials{ 450 | PrivateKey: pk, 451 | Certificate: crts[0], 452 | } 453 | 454 | return creds, nil 455 | } 456 | 457 | // DecodePEMKey 解析私钥数据流,反序列化为PrivateKey={Type: 私钥类型, key: 表示具体协议对象} 458 | // 459 | // 目前支持两种私钥加密算法格式: 460 | /* 461 | RSA:由 RSA 公司发明,是一个支持变长密钥的公共密钥算法,需要加密的文件块的长度也是可变的; 462 | ECC(Elliptic Curves Cryptography):椭圆曲线密码编码学。 463 | 464 | ECC相比RSA优势: 465 | 抗攻击性强 466 | CPU 占用少 467 | 内容使用少 468 | 网络消耗低 469 | 加密速度快 470 | */ 471 | func DecodePEMKey(key []byte) (*PrivateKey, error) { 472 | // 把密钥数据流解析成pem.Block={Type, Headers, Bytes}; 473 | // 比如:TYPE: RSA PRIVATE KEY, Headers: nil, Bytes则是加密后的密钥, 也就是BEGIN和END之间的数据 474 | // 下文我们可以看看具体的解析 475 | // 因为一个证书文件中,可能存在多个BEGIN和END对,所以第二个参数是解析一对BEGIN和END后,剩余的BEGIN和END还没有解析 476 | block, _ := pem.Decode(key) 477 | if block == nil { 478 | return nil, errors.New("key is not PEM encoded") 479 | } 480 | // 我们这里只支持两种加密方式,且默认为ECC加密方式 481 | // ECC和RSA 482 | switch block.Type { 483 | case ECPrivateKey: 484 | // 如果加密的Type是EC PRIVATE KEY, 则通过x509包中的EC密钥解析方法获取ecdsa.PrivateKey对象 485 | // 注意,每个密钥中都带有公钥信息, 但是公钥信息不带密钥信息 486 | k, err := x509.ParseECPrivateKey(block.Bytes) 487 | if err != nil { 488 | return nil, err 489 | } 490 | // 返回自定义的PrivateKey对象 491 | return &PrivateKey{Type: ECPrivateKey, Key: k}, nil 492 | case RSAPrivateKey: 493 | // 如果加密的Type是RSA PRIVATE KEY,则通过x509包中的RSA密钥解析方法获取rsa.PrivateKey对象 494 | // 注意,这里的RSA反序列化方式为PCKS1 495 | k, err := x509.ParsePKCS1PrivateKey(block.Bytes) 496 | if err != nil { 497 | return nil, err 498 | } 499 | // 返回自定义的PrivateKey对象 500 | return &PrivateKey{Type: RSAPrivateKey, Key: k}, nil 501 | default: 502 | return nil, errors.Errorf("unsupported block type %s", block.Type) 503 | } 504 | } 505 | 506 | // DecodePEMCertificates 根据传入的数据流解析公钥证书列表 507 | func DecodePEMCertificates(crtb []byte) ([]*x509.Certificate, error) { 508 | certs := []*x509.Certificate{} 509 | // 因为一个证书文件中可能存在多个x509 v3标准协议数据 510 | // 所以这里需要一个个解析, 并存储到x509.Ceritificate证书列表中 511 | for len(crtb) > 0 { 512 | var err error 513 | var cert *x509.Certificate 514 | 515 | // 根据传入尚未解析的证书数据流,进行x509 v3标准协议数据解析 516 | // 并返回一个证书和剩余未解析的数据流 517 | cert, crtb, err = decodeCertificatePEM(crtb) 518 | if err != nil { 519 | return nil, err 520 | } 521 | // 如果cert不为空,表示解析到了一个x509 v3证书 522 | if cert != nil { 523 | // it's a cert, add to pool 524 | certs = append(certs, cert) 525 | } 526 | } 527 | return certs, nil 528 | } 529 | 530 | // decodeCertificatePEM 最终也是采用x509 v3标准协议进行解码 531 | func decodeCertificatePEM(crtb []byte) (*x509.Certificate, []byte, error) { 532 | // 解码x509 v3标准协议 533 | block, crtb := pem.Decode(crtb) 534 | if block == nil { 535 | return nil, crtb, errors.New("invalid PEM certificate") 536 | } 537 | // 如果x509 v3标准协议数据的类型不是CERTIFICATE类型, 则表示该协议不是公钥证书,直接返回全部nil 538 | if block.Type != Certificate { 539 | return nil, nil, nil 540 | } 541 | // 然后通过asn1解析公钥数据,并反序列化为x509.Certificate协议数据 542 | c, err := x509.ParseCertificate(block.Bytes) 543 | return c, crtb, err 544 | } 545 | 546 | // matchCertificateAndKey 对私钥和公钥进行匹配,看看是不是一对 547 | func matchCertificateAndKey(pk *PrivateKey, cert *x509.Certificate) bool { 548 | // 首先对私钥的加密类型进行断言 549 | switch pk.Type { 550 | case ECPrivateKey: 551 | // 如果加密类型为EC PRIVATE KEY, 则直接断言为ecdsa.PrivateKey 552 | key := pk.Key.(*ecdsa.PrivateKey) 553 | pub, ok := cert.PublicKey.(*ecdsa.PublicKey) 554 | // 并比较公钥信息是否相同 555 | return ok && pub.X.Cmp(key.X) == 0 && pub.Y.Cmp(key.Y) == 0 556 | case RSAPrivateKey: 557 | // 如果加密类型为RSA PRIVATE KEY,则直接断言为rsa.PublicKey 558 | key := pk.Key.(*rsa.PrivateKey) 559 | pub, ok := cert.PublicKey.(*rsa.PublicKey) 560 | // 并比较公钥信息是否相同 561 | return ok && pub.N.Cmp(key.N) == 0 && pub.E == key.E 562 | default: 563 | return false 564 | } 565 | } 566 | ``` 567 | 568 | ## CA根证书和Issuer证书的创建 569 | 570 | 在前面了解到,如果传入的CA根证书和Issuer证书文件都不存在,或者k8s dapr-system命名空间下dapr-trust-bundle secret不存在,则需要创建CA根证书和Issuer证书,包括公钥和私钥,并存储到secret资源方式k8s dapr-system命名空间下,或者存储到指定的证书文件中。 571 | 572 | ```golang 573 | // generateRootAndIssuerCerts 创建CA根证书和Issuer证书 574 | /* 575 | 这里分三个步骤: 576 | 1. 创建CA根证书的私钥和公钥; 577 | 2. 创建Issuer颁布者的私钥和公钥. 578 | 3. 把创建好的CA根证书和Issuer颁布者的私钥和公钥写入到SentryConfig或者k8s dapr-system命名空间下类型为token,名为dapr-trust-bundle的资源对象中存储. 579 | */ 580 | func (c *defaultCA) generateRootAndIssuerCerts() (*certs.Credentials, []byte, []byte, error) { 581 | // 1. 创建CA根证书的私钥和公钥 582 | // 目前dapr对加密密钥的方式都是采用EC PRIVATE KEY, 创建随机密钥 583 | rootKey, err := certs.GenerateECPrivateKey() 584 | if err != nil { 585 | return nil, nil, nil, err 586 | } 587 | // 然后根据Certificate Signed Request证书签名请求,创建一个x509 v3的证书请求x509.Certifiate 588 | rootCsr, err := csr.GenerateRootCertCSR(caOrg, caCommonName, &rootKey.PublicKey, selfSignedRootCertLifetime) 589 | if err != nil { 590 | return nil, nil, nil, err 591 | } 592 | 593 | // 根据CSR证书签名请求,创建经过base64和asn1序列化的公钥数据流 594 | // 595 | // 这里注意后面四个参数:template, parent *Certificate, pub, priv interface{} 596 | // 对于前面两个:如果template与parent相同,表示自签名请求;否则,是对template作为公钥对CSR进行签名 597 | // 对于后面两个:public表示被签发的公钥(CSR请求的公钥);priv表示受签发的私钥(表示是哪个给你签) 598 | rootCertBytes, err := x509.CreateCertificate(rand.Reader, rootCsr, rootCsr, &rootKey.PublicKey, rootKey) 599 | if err != nil { 600 | return nil, nil, nil, err 601 | } 602 | 603 | // 把加密后的密钥数据流和加密类型,编码成x509 v3标准协议数据. 604 | /* 605 | -----BEGIN TYPE----- 606 | ...... 607 | -----END TYPE----- 608 | */ 609 | rootCertPem := pem.EncodeToMemory(&pem.Block{Type: certs.Certificate, Bytes: rootCertBytes}) 610 | 611 | // 把前面通过CSR请求获得的公钥数据流,解码成x509.Certificate协议数据对象 612 | rootCert, err := x509.ParseCertificate(rootCertBytes) 613 | if err != nil { 614 | return nil, nil, nil, err 615 | } 616 | 617 | // 2. 创建Issuer颁布者的公钥和私钥 618 | issuerKey, err := certs.GenerateECPrivateKey() 619 | if err != nil { 620 | return nil, nil, nil, err 621 | } 622 | 623 | // 根据CSR请求,创建一个x509 v3的证书请求x509.Certifiate 624 | issuerCsr, err := csr.GenerateIssuerCertCSR(caCommonName, &issuerKey.PublicKey, selfSignedRootCertLifetime) 625 | if err != nil { 626 | return nil, nil, nil, err 627 | } 628 | 629 | // 然后根据x509 v3的证书请求,创建经过base64和asn1序列化的公钥数据流 630 | // 631 | // 这里再注意后面四个参数: 632 | // 前面两个:分别是issuerCsr和rootCert。这次是对Issuer证书颁布者的CSR进行签名, 后者表示根证书 633 | // 后面两个:分别是Issuer公钥和CA根证书。前者是被签署的公钥,后者是帮CSR签署公钥证书的私钥 634 | issuerCertBytes, err := x509.CreateCertificate(rand.Reader, issuerCsr, rootCert, &issuerKey.PublicKey, rootKey) 635 | if err != nil { 636 | return nil, nil, nil, err 637 | } 638 | 639 | // 把签发的公钥数据流,构建成x509 v3标准协议数据流 640 | issuerCertPem := pem.EncodeToMemory(&pem.Block{Type: certs.Certificate, Bytes: issuerCertBytes}) 641 | 642 | // 把未加密的私钥通过EC PRIVATE KEY方式加密成数据流 643 | encodedKey, err := x509.MarshalECPrivateKey(issuerKey) 644 | if err != nil { 645 | return nil, nil, nil, err 646 | } 647 | // 然后最后把加密后的数据流构建成x509 v3标准协议数据 648 | issuerKeyPem := pem.EncodeToMemory(&pem.Block{Type: certs.ECPrivateKey, Bytes: encodedKey}) 649 | 650 | // 并把公钥的数据流,解码为x509.Certificate对象 651 | issuerCert, err := x509.ParseCertificate(issuerCertBytes) 652 | if err != nil { 653 | return nil, nil, nil, err 654 | } 655 | 656 | // 3. 把CA根证书和Issuer的公钥和私钥写入到k8s token或者文件中 657 | err = certs.StoreCredentials(c.config, rootCertPem, issuerCertPem, issuerKeyPem) 658 | if err != nil { 659 | return nil, nil, nil, err 660 | } 661 | 662 | // 最后,我们就得到了Issuer证书颁布者的私钥证书和公钥证书 663 | // 以及封装成x509 v3的CA根公钥和Issuer证书颁布者的公钥 664 | return &certs.Credentials{ 665 | PrivateKey: &certs.PrivateKey{ 666 | Type: certs.ECPrivateKey, 667 | Key: issuerKey, 668 | }, 669 | Certificate: issuerCert, 670 | }, rootCertPem, issuerCertPem, nil 671 | } 672 | 673 | // 我们可以从创建CA根证书和Issuer证书颁布者的证书和私钥,可以看到创建完成后,会进行证书的存储操作 674 | // 675 | // 两种存储情况:1. 以k8s seret资源的方式存储到k8s dapr-system命名空间下; 676 | // 2. 存储到SentryConfig配置指定的证书路径下。 677 | func StoreCredentials(conf config.SentryConfig, rootCertPem, issuerCertPem, issuerKeyPem []byte) error { 678 | // 判断当前服务是否运行k8s环境中 679 | if config.IsKubernetesHosted() { 680 | return storeKubernetes(rootCertPem, issuerCertPem, issuerKeyPem) 681 | } 682 | // 否则就是运行在host上 683 | return storeSelfhosted(rootCertPem, issuerCertPem, issuerKeyPem, conf.RootCertPath, conf.IssuerCertPath, conf.IssuerKeyPath) 684 | } 685 | 686 | // storeKubernetes 把CA根证书、Issuer证书颁布者的公钥和私钥,以secret资源的方式存储到k8s中 687 | func storeKubernetes(rootCertPem, issuerCertPem, issuerCertKey []byte) error { 688 | // 从.kube/config加载配置,获取k8s client 689 | kubeClient, err := kubernetes.GetClient() 690 | if err != nil { 691 | return err 692 | } 693 | 694 | // 构建成secret资源对象,并通过k8s api server client写入到etcd集群中 695 | // 这个secret前面已经了解过了。 696 | // NAMESPACE=dapr-system; Name=dapr-trust-bundle 697 | namespace := getNamespace() 698 | secret := &v1.Secret{ 699 | Data: map[string][]byte{ 700 | credentials.RootCertFilename: rootCertPem, 701 | credentials.IssuerCertFilename: issuerCertPem, 702 | credentials.IssuerKeyFilename: issuerCertKey, 703 | }, 704 | ObjectMeta: metav1.ObjectMeta{ 705 | Name: KubeScrtName, 706 | Namespace: namespace, 707 | }, 708 | Type: v1.SecretTypeOpaque, 709 | } 710 | 711 | // 通过k8s api server client写入到etcd集群中 712 | _, err = kubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) 713 | if err != nil { 714 | return errors.Wrap(err, "failed saving secret to kubernetes") 715 | } 716 | return nil 717 | } 718 | 719 | func getNamespace() string { 720 | // 这个Namespace为dapr-system 721 | namespace := os.Getenv("NAMESPACE") 722 | if namespace == "" { 723 | // 默认命名空间为default。 724 | namespace = defaultSecretNamespace 725 | } 726 | return namespace 727 | } 728 | 729 | // storeSelfhosted 把各个证书写入到SentryConfig配置指定的文件中 730 | func storeSelfhosted(rootCertPem, issuerCertPem, issuerKeyPem []byte, rootCertPath, issuerCertPath, issuerKeyPath string) error { 731 | err := ioutil.WriteFile(rootCertPath, rootCertPem, 0644) 732 | if err != nil { 733 | return errors.Wrapf(err, "failed saving file to %s", rootCertPath) 734 | } 735 | 736 | err = ioutil.WriteFile(issuerCertPath, issuerCertPem, 0644) 737 | if err != nil { 738 | return errors.Wrapf(err, "failed saving file to %s", issuerCertPath) 739 | } 740 | 741 | err = ioutil.WriteFile(issuerKeyPath, issuerKeyPem, 0644) 742 | if err != nil { 743 | return errors.Wrapf(err, "failed saving file to %s", issuerKeyPath) 744 | } 745 | return nil 746 | } 747 | ``` 748 | 749 | 通过CA根证书的公钥和私钥的创建,以及Issuer证书颁布者的公钥和私钥的创建过程,我们可以了解整个证书的创建过程,以后再也不会对这些概念既熟悉又陌生了。 750 | 751 | ## 介绍x509协议的解析 752 | 753 | x509 v3标准协议的解析过程,是针对下面的结构解析出pem.Block结构 754 | 755 | ```shell 756 | -----BEGIN TYPE----- 757 | ...... 758 | -----END TYPE----- 759 | ``` 760 | 761 | 那么所有证书文件中的数据流,都是先解析该结构,然后再针对证书的内容进行解码。 762 | 763 | 比如: 764 | 1. 公钥通过ASN.1进行解析; 765 | 2. 私钥则通过RSA或者ECC加密方式进行解析. 766 | 767 | 针对x509 v3标准的解码过程,encoding/pem.Decode的解码方法,获取到pem.Block和剩余数据流. 768 | 769 | ```golang 770 | var pemStart = []byte("\n-----BEGIN ") 771 | var pemEnd = []byte("\n-----END ") 772 | var pemEndOfLine = []byte("-----") 773 | 774 | // Decode 是encoding/pem包的Decode方法,用于解析x509 v3标准协议 775 | func Decode(data []byte) (p *Block, rest []byte) { 776 | rest = data 777 | // 校验数据流是否是以-----BEGIN 开头 778 | // 如果是,则剔除这个头,目的是只获取有效数据,包括类型和类型数据 779 | if bytes.HasPrefix(data, pemStart[1:]) { 780 | rest = rest[len(pemStart)-1 : len(data)] 781 | // 如果不是,则寻找以\n-----BEGIN 开头的第一条数据流, 并剔除前面的无效数据 782 | } else if i := bytes.Index(data, pemStart); i >= 0 { 783 | rest = rest[i+len(pemStart) : len(data)] 784 | // 否则,表示数据流中没有符合x509 v3标准的协议数据, 直接返回 785 | } else { 786 | return nil, data 787 | } 788 | 789 | // 获取TYPE------行数据 790 | typeLine, rest := getLine(rest) 791 | // 校验typeLine是否以------结尾,如果不是,表示不符合x509 v3标准 792 | if !bytes.HasSuffix(typeLine, pemEndOfLine) { 793 | return decodeError(data, rest) 794 | } 795 | // 所以第一行typeLine剔除-----后,剩下的就只有类型值了: 796 | // 比如:-----BEGIN EC PRIVATE KEY----- 797 | // 则解析后的typeLine值为EC PRIVATE KEY 798 | typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] 799 | 800 | // 然后再解析密钥类型数据 801 | p = &Block{ 802 | Headers: make(map[string]string), 803 | Type: string(typeLine), 804 | } 805 | ...... 806 | // 最后获取的第一个x509 v3标准协议数据后,通过base64解析数据后,把有效数据存储到pem.Block的Bytes数据流中 807 | // 并返回pem.Block和rest,分别表示第一个x509 v3标准协议数据对象和剩余数据流 808 | base64Data := removeSpacesAndTabs(rest[:endIndex]) 809 | p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) 810 | n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) 811 | if err != nil { 812 | return decodeError(data, rest) 813 | } 814 | p.Bytes = p.Bytes[:n] 815 | ...... 816 | return 817 | } 818 | ``` 819 | 820 | 821 | ## 总结 822 | 823 | 通过本文,我们应该掌握了下面知识点: 824 | 1. 对通信的数据加解密,包括:公钥、私钥和CA根证书,有了比较清楚的理解; 825 | 2. 对证书和私钥的签发过程,包括x509 v3标准协议,及其编解码、ASN.1、以及密钥加密算法的加解密(EC PRIVATE KEY和RSA PRIVATE KEY),有了清楚的理解; 826 | 3. 对dapr sentry服务的CA机构自签署、Issuer证书颁布者的公钥和私钥的签署,有了清楚的理解和掌握; 827 | 4. 对dapr sentry服务的CA根证书、Issuer证书和私钥的存储,包括k8s secret资源存储和host方式文件存储,也有了很好的账号 828 | 829 | 其中重点在于第一点和第二点 830 | 831 | 1. [加密算法RSA与ECC对比,以及Android、java中使用](https://blog.csdn.net/Jocerly/article/details/83339826) 832 | -------------------------------------------------------------------------------- /pkg/sentry02.md: -------------------------------------------------------------------------------- 1 | 本文继续进行dapr sentry源码阅读,本次是对dapr sentry服务启动,以及为外部请求签发证书的过程 2 | 3 | ```golang 4 | dapr sentry提供grpc 50001端口服务 5 | 路由:/dapr.proto.sentry.v1.CA/SignCertificate 6 | ``` 7 | 8 | ## dapr sentry服务启动 9 | 10 | 如果dapr sentry服务部署在k8s中,则该服务默认在dapr-system命名空间下,且该服务deployment的名称为dapr-sentry, 下面这个CAServer接口,用于启动grpc 50001端口服务,且带有一个关闭服务的API 11 | 12 | ```golang 13 | type CAServer interface { 14 | Run(port int, trustBundle ca.TrustRootBundler) error 15 | Shutdown() 16 | } 17 | ``` 18 | 19 | 在dapr sentry源码中,server为CAServer的默认实现。如下所示: 20 | 21 | ```golang 22 | // server为CAServer接口的实现,并提供SignedCertificate服务,用于给请求的CSR签署证书 23 | type server struct { 24 | certificate *tls.Certificate 25 | certAuth ca.CertificateAuthority 26 | srv *grpc.Server 27 | validator identity.Validator 28 | } 29 | 30 | // NewCAServer创建一个CAServer的实例对象 31 | // 参数CertificateAuthority, 表示传入defaultCA对象,用于获取或者创建CA根证书,以及Issuer证书颁布者的证书和密钥 32 | // 以及为CSR签署证书和验证CSR请求的合法性 33 | func NewCAServer(ca ca.CertificateAuthority, validator identity.Validator) CAServer { 34 | return &server{ 35 | certAuth: ca, 36 | validator: validator, 37 | } 38 | } 39 | 40 | // Run 启动dapr sentry服务,对外CSR(Certificate Signed Request)提供签署证书的服务 41 | // TrustRootBunder接口,用于获取CA根证书和Issuer证书颁布者的证书和私钥 42 | func (s *server) Run(port int, trustBundler ca.TrustRootBundler) error { 43 | // dapr sentry服务的提供的grpc默认端口是50001 44 | addr := fmt.Sprintf(":%v", port) 45 | lis, err := net.Listen("tcp", addr) 46 | if err != nil { 47 | return errors.Wrapf(err, "could not listen on %s", addr) 48 | } 49 | 50 | tlsOpt := s.tlsServerOption(trustBundler) 51 | s.srv = grpc.NewServer(tlsOpt) 52 | sentryv1pb.RegisterCAServer(s.srv, s) 53 | 54 | if err := s.srv.Serve(lis); err != nil { 55 | return errors.Wrap(err, "grpc serve error") 56 | } 57 | return nil 58 | } 59 | 60 | // TrustRootBundler 在上文中我们已经通过defaultCA实例,创建好CA根证书和Issuer证书颁布者的公钥和私钥 61 | // 并把这些数据存储到trustRootBundle对象中,该对象实现了TrustRootBundler接口 62 | type TrustRootBundler interface { 63 | // 获取Issuer证书颁布者的公钥封装好的x509 v3标准协议数据包 64 | GetIssuerCertPem() []byte 65 | // 获取CA根证书的公钥封装好的x509 v3标准协议数据包 66 | GetRootCertPem() []byte 67 | // 获取Issuer证书颁布者的自己公钥证书的有效期 68 | GetIssuerCertExpiry() time.Time 69 | // 获取CA根证书的公钥列表,构建成证书池 70 | GetTrustAnchors() *x509.CertPool 71 | // 获取dapr sentry服务的可信域,也就是来源 72 | GetTrustDomain() string 73 | } 74 | 75 | // tlsServerOption 根据传入的TrustRootBundle构建一个创建grpc服务且提供tls证书的的参数 grpc.ServerOption 76 | func (s *server) tlsServerOption(trustBundler ca.TrustRootBundler) grpc.ServerOption { 77 | // 通过传入的TrustRootBundle,获取CA根证书的x509.CertPool公钥池 78 | cp := trustBundler.GetTrustAnchors() 79 | 80 | // nolint:gosec 81 | // 创建tls配置实例, 用于为grpc添加tls安全证书 82 | config := &tls.Config{ 83 | // CA根证书公钥池 84 | ClientCAs: cp, 85 | // 要求客户端访问dapr sentry grpc服务,必须带证书且验证证书 86 | ClientAuth: tls.RequireAndVerifyClientCert, 87 | // GetCertificate 这个地方不太理解 88 | // 看代码好像是,获取grpc服务需要的TLS证书 89 | GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { 90 | // 如果dapr sentry初始化时,其certificate成员为nil,或者证书过期 91 | // 92 | // 注意这里的证书过期,是指马上过期。范围15分钟内就过期,则需要动态生成一个新的证书 93 | if s.certificate == nil || needsRefresh(s.certificate, serverCertExpiryBuffer) { 94 | cert, err := s.getServerCertificate() 95 | if err != nil { 96 | monitoring.ServerCertIssueFailed("server_cert") 97 | log.Error(err) 98 | return nil, errors.Wrap(err, "failed to get TLS server certificate" ) 99 | } 100 | s.certificate = cert 101 | } 102 | return s.certificate, nil 103 | }, 104 | } 105 | // 最后把TLS证书作为grpc的ServerOptions参数,则grpc服务启动则使用了TLS安全证书,且任何客户端访问dapr sentry都必须带上证书进行校验。否则拒绝建立连接 106 | return grpc.Creds(credentials.NewTLS(config)) 107 | } 108 | 109 | // getServerCertificate 为dapr sentry提供grpc服务生成一个TLS证书 110 | func (s *server) getServerCertificate() (*tls.Certificate, error) { 111 | // 生成一个x509 v3标准协议数据的公钥证书和私钥, 且该公钥还只是一个无效,没有经过任何CA机构认证的公钥 112 | // 相当于是一个CSR请求公钥证书, 而私钥不需要认证 113 | csrPem, pkPem, err := csr.GenerateCSR("", false) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | // 获取Issuer证书颁布者的过期时间 119 | now := time.Now().UTC() 120 | issuerExp := s.certAuth.GetCACertBundle().GetIssuerCertExpiry() 121 | // 表示为CSR请求签署的证书有效期 122 | // 这里注意:Issuer证书颁布者自身证书的有效期为1年,那么该Issuer证书颁布者为其他CSR签署的证书有效期肯定是小于1年 123 | serverCertTTL := issuerExp.Sub(now) 124 | 125 | // 需要使用CA机构对刚刚,自己创建的CSR证书,进行签署公钥和认证 126 | resp, err := s.certAuth.SignCSR(csrPem, s.certAuth.GetCACertBundle().GetTrustDomain(), nil, serverCertTTL, false) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | // 把CA机构为CSR证书签署好的x509 v3标准协议数据流,也就是公钥证书数据流 132 | // 以及CA根证书和Issuer证书颁布者公钥,全部写入到certPem, 并返回给dapr sentry服务,作为自身的公钥列表。 133 | certPem := resp.CertPEM 134 | certPem = append(certPem, s.certAuth.GetCACertBundle().GetIssuerCertPem()...) 135 | certPem = append(certPem, s.certAuth.GetCACertBundle().GetRootCertPem()...) 136 | 137 | // 最后再把签发好的公钥证书和私钥,构建成TLS证书,并返回给grpc的ServerOptions参数中 138 | cert, err := tls.X509KeyPair(certPem, pkPem) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | return &cert, nil 144 | } 145 | 146 | // GenerateCSR 根据传入参数创建一个CSR请求,申请公钥和私钥,并返回x509 v3标准协议数据的证书和私钥 147 | // 这个公钥证书其实是一个CERTIFICATE REQUEST类型的x509 v3标准协议数据,它没有经过CA认证。 148 | // 所以其实是一个CSR证书, 需要使用TrustRootBundle的SignCSR API对CSR公钥请求进行签署 149 | func GenerateCSR(org string, pkcs8 bool) ([]byte, []byte, error) { 150 | // 生成一个随机的私钥, 密钥生成的方式EC Private Key 151 | key, err := certs.GenerateECPrivateKey() 152 | if err != nil { 153 | return nil, nil, errors.Wrap(err, "unable to generate private keys") 154 | } 155 | 156 | // 根据传入参数,也就是生成公钥所需要的CSR请求 157 | templ, err := genCSRTemplate(org) 158 | if err != nil { 159 | return nil, nil, errors.Wrap(err, "error generating csr template") 160 | } 161 | 162 | // 根据CSR(Certificate Signed Request)请求,来生成一个公钥CSR请求证书. 163 | csrBytes, err := x509.CreateCertificateRequest(rand.Reader, templ, crypto.PrivateKey(key)) 164 | if err != nil { 165 | return nil, nil, errors.Wrap(err, "failed to create CSR") 166 | } 167 | 168 | // 然后把CSR请求公钥证书和私钥证书,分别构建成x509 v3标准协议的数据流 169 | crtPem, keyPem, err := encode(true, csrBytes, key, pkcs8) 170 | return crtPem, keyPem, err 171 | } 172 | 173 | // genCSRTemplate 构建成一个CSR签署证书的请求, org表示证书的subject。也就是哪个机构帮忙签署的 174 | func genCSRTemplate(org string) (*x509.CertificateRequest, error) { 175 | return &x509.CertificateRequest{ 176 | Subject: pkix.Name{ 177 | Organization: []string{org}, 178 | }, 179 | }, nil 180 | } 181 | 182 | // encode 把公钥和私钥证书,分别编码成x509 v3标准协议数据流 183 | func encode(csr bool, csrOrCert []byte, privKey *ecdsa.PrivateKey, pkcs8 bool) ([]byte, []byte, error) { 184 | // CERTIFICATE 表示公钥类型为CERTIFICATE 185 | // CERTIFICATE REQUEST 表示公钥类型为CSR类型 186 | // encodeMsgCert: CERTIFICATE 187 | encodeMsg := encodeMsgCert 188 | if csr { 189 | // encodeMsgCSR: CERTIFICATE REQUEST 190 | encodeMsg = encodeMsgCSR 191 | } 192 | // 把公钥数据流编码为x509 v3标准协议数据流 193 | csrOrCertPem := pem.EncodeToMemory(&pem.Block{Type: encodeMsg, Bytes: csrOrCert}) 194 | 195 | var encodedKey, privPem []byte 196 | var err error 197 | 198 | // PKCS#8 表示需要把私钥通过PKCS#8方式,进行编码, 然后再构建成x509 v3标准协议,编码类型PRIVATE KEY 199 | // 因为PKCS#8特定的编码方式,会进行case查找,它可以对rsa.PrivateKey(RSA PRIVATE KEY), ecdsa.PrivateKey(ECC PRIVATE KEY),ed25519.PrivateKey等密钥进行判断,然后再根据各自加密方式进行解码 200 | if pkcs8 { 201 | if encodedKey, err = x509.MarshalPKCS8PrivateKey(privKey); err != nil { 202 | return nil, nil, err 203 | } 204 | // 那么这里x509 v3标准协议数据中的编码类型:PRIVATE KEY 205 | privPem = pem.EncodeToMemory(&pem.Block{Type: blockTypePrivateKey, Bytes: encodedKey}) 206 | } else { 207 | // 如果不是PKCS#8封装EC编码处理,则直接使用EC编码方式,进行编码获取数据流 208 | encodedKey, err = x509.MarshalECPrivateKey(privKey) 209 | if err != nil { 210 | return nil, nil, err 211 | } 212 | // 然后再构建成x509 v3标准协议数据,编码协议类型:EC PRIVATE KEY 213 | privPem = pem.EncodeToMemory(&pem.Block{Type: blockTypeECPrivateKey, Bytes: encodedKey}) 214 | } 215 | // 最后把创建好的x509 v3标准协议数据,包括公钥证书和私钥都返回 216 | return csrOrCertPem, privPem, nil 217 | } 218 | 219 | // SignCSR,也就是CA机构为编码好的x509 v3协议数据流CSR请求,签署和颁发公钥证书 220 | func (c *defaultCA) SignCSR(csrPem []byte, subject string, identity *identity.Bundle, ttl time.Duration, isCA bool) (*SignedCertificate, error) { 221 | c.issuerLock.RLock() 222 | defer c.issuerLock.RUnlock() 223 | 224 | // 校验如果Issuer证书颁布者的有效期过短,则使用dapr sentry的SentryConfig中的证书有效期 225 | certLifetime := ttl 226 | if certLifetime.Seconds() < 0 { 227 | certLifetime = c.config.WorkloadCertTTL 228 | } 229 | 230 | // 并且允许一定的证书冗余有效期 231 | certLifetime += c.config.AllowedClockSkew 232 | 233 | // Issuer证书颁布者为CSR请求,签署和颁布证书 234 | signingCert := c.bundle.issuerCreds.Certificate 235 | signingKey := c.bundle.issuerCreds.PrivateKey 236 | 237 | // 解析使用x509 v3标准协议封装好的CSR请求,转换成x509 CertificateRequest协议数据 238 | cert, err := certs.ParsePemCSR(csrPem) 239 | if err != nil { 240 | return nil, errors.Wrap(err, "error parsing csr pem") 241 | } 242 | 243 | // 使用Issuer证书颁布者,为CSR请求签署证书 244 | crtb, err := csr.GenerateCSRCertificate(cert, subject, identity, signingCert, cert.PublicKey, signingKey.Key, certLifetime, isCA) 245 | if err != nil { 246 | return nil, errors.Wrap(err, "error signing csr") 247 | } 248 | 249 | // 把上一步获取的公钥,解析成x509 Certificate证书 250 | csrCert, err := x509.ParseCertificate(crtb) 251 | if err != nil { 252 | return nil, errors.Wrap(err, "error parsing cert") 253 | } 254 | 255 | // 然后再把Issuer证书颁布者签署的公钥数据流,构建成x509 v3标准协议数据流 256 | certPem := pem.EncodeToMemory(&pem.Block{ 257 | Type: certs.Certificate, 258 | Bytes: crtb, 259 | }) 260 | 261 | // 把签署和构建好的证书和x509 v3标准协议数据流返回给上游 262 | return &SignedCertificate{ 263 | Certificate: csrCert, 264 | CertPEM: certPem, 265 | }, nil 266 | } 267 | ``` 268 | 269 | 从dapr sentry grpc 50001端口服务创建过程中,我们可以看到主要是有两个步骤: 270 | 1. 为grpc 50001端口服务添加TLS安全证书; 271 | 2. grpc服务注册,主要为第三方CSR请求,提供SignCertificate API服务,签署和颁布公钥证书 272 | 273 | 前面的源码分析,主要是是针对dapr sentry grpc 50001端口服务,创建TLS证书,包括几个步骤: 274 | 1. 创建一个随机的EC Private Key加密算法私钥; 275 | 2. 构建一个CSR请求参数,并创建CertificateRequest请求; 276 | 3. 再根据2得到的CertificateRequest请求,编码成x509 v3标准协议数据; 277 | 4. 再根据x509 v3标准协议封装好的CSR请求,让Issuer证书颁布者签署和颁发公钥证书; 278 | 5. 最后再把Issuer证书颁布者颁布的公钥证书和自己生成的随机私钥,构建成TLS证书并返回给grpc server作为TLS参数 279 | 280 | 这里面有个GenerateCSRCertificate方法没有源码分析,这个放到grpc server接收外部请求,并通过SignCertificate API处理时,进行详细分析,因为这个里面提及里一个[SPIFEE标准](https://github.com/spiffe/spiffe), 它用于微服务内部调用的安全性,防止内部服务之间的攻击。 281 | 282 | SPIFEE标准在dapr runtime中使用,形如:**spiffe://trustDomain/ns/namespace/appID** 283 | 284 | ## dapr sentry服务签发证书 285 | 286 | 在dapr-system命名空间下的dapr sentry服务,为dapr runtime所有需要使用grpc协议的服务,提供CA公钥证书认证, 下面主要是对dapr sentry提供grpc 50001端口服务的SignCertificate API服务,进行详细分析 287 | 288 | ```golang 289 | // SignCertificate 对所有dapr runtime容器提供CA证书签署和颁发 290 | func (s *server) SignCertificate(ctx context.Context, req *sentryv1pb.SignCertificateRequest) (*sentryv1pb.SignCertificateResponse, error) { 291 | // 根据请求,获取CSR CertificateRequest请求数据流 292 | csrPem := req.GetCertificateSigningRequest() 293 | 294 | // 并把x509 v3标准协议数据封装的CertificateRequest,解析为CSR CertificateRequest实例对象 295 | csr, err := certs.ParsePemCSR(csrPem) 296 | if err != nil { 297 | err = errors.Wrap(err, "cannot parse certificate signing request pem") 298 | return nil, err 299 | } 300 | 301 | // 并通过TrustRootBundle来验证CertificateRequest请求参数的合法性 302 | // 目前主要是验证Subject 303 | err = s.certAuth.ValidateCSR(csr) 304 | if err != nil { 305 | err = errors.Wrap(err, "error validating csr") 306 | return nil, err 307 | } 308 | 309 | // 然后再验证id、token和namespace的合法性和正确性 310 | err = s.validator.Validate(req.GetId(), req.GetToken(), req.GetNamespace()) 311 | if err != nil { 312 | err = errors.Wrap(err, "error validating requester identity") 313 | return nil, err 314 | } 315 | 316 | // 把CSR请求中的参数,构建成bundle对象, 相当于一个CSR请求签署公钥的身份证 317 | identity := identity.NewBundle(csr.Subject.CommonName, req.GetNamespace(), req.GetTrustDomain()) 318 | // 为CSR请求签署和颁发公钥证书 319 | signed, err := s.certAuth.SignCSR(csrPem, csr.Subject.CommonName, identity, -1, false) 320 | if err != nil { 321 | err = errors.Wrap(err, "error signing csr") 322 | return nil, err 323 | } 324 | 325 | // 把为CSR请求颁发的公钥证书、以及CA根证书和Issuer证书颁布者的公钥证书,构建成一个证书列表数据流 326 | certPem := signed.CertPEM 327 | issuerCert := s.certAuth.GetCACertBundle().GetIssuerCertPem() 328 | rootCert := s.certAuth.GetCACertBundle().GetRootCertPem() 329 | 330 | certPem = append(certPem, issuerCert...) 331 | certPem = append(certPem, rootCert...) 332 | 333 | if len(certPem) == 0 { 334 | err = errors.New("insufficient data in certificate signing request, no certs signed") 335 | return nil, err 336 | } 337 | 338 | // 校验时间是否是合法的 339 | expiry := timestamppb.New(signed.Certificate.NotAfter) 340 | if err = expiry.CheckValid(); err != nil { 341 | return nil, errors.Wrap(err, "could not validate certificate validity") 342 | } 343 | 344 | // 返回公钥证书列表数据流和证书链,以及证书的有效期 345 | resp := &sentryv1pb.SignCertificateResponse{ 346 | WorkloadCertificate: certPem, 347 | TrustChainCertificates: [][]byte{issuerCert, rootCert}, 348 | ValidUntil: expiry, 349 | } 350 | 351 | return 352 | } 353 | 354 | // GenerateCSRCertificate 为x509 CertificateRequest请求签署和颁布一个公钥证书 355 | func GenerateCSRCertificate(csr *x509.CertificateRequest, subject string, identityBundle *identity.Bundle, signingCert *x509.Certificate, publicKey interface{}, signingKey crypto.PrivateKey, 356 | ttl time.Duration, isCA bool) ([]byte, error) { 357 | // 创建一个x509.Certificate实例对象, 并填充数据到证书中 358 | cert, err := generateBaseCert(ttl, publicKey) 359 | if err != nil { 360 | return nil, errors.Wrap(err, "error generating csr certificate") 361 | } 362 | if isCA { 363 | cert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageCRLSign 364 | } else { 365 | cert.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment 366 | cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth) 367 | } 368 | 369 | if subject == "cluster.local" { 370 | cert.Subject = pkix.Name{ 371 | CommonName: subject, 372 | } 373 | cert.DNSNames = []string{subject} 374 | } 375 | 376 | cert.Issuer = signingCert.Issuer 377 | cert.IsCA = isCA 378 | cert.IPAddresses = csr.IPAddresses 379 | cert.Extensions = csr.Extensions 380 | cert.BasicConstraintsValid = true 381 | cert.SignatureAlgorithm = csr.SignatureAlgorithm 382 | 383 | // 重点:如果是外部CSR请求,则需要对请求的服务进行保护,防止dapr runtime之间的恶意调用 384 | // 这里使用SPIFFE标准,相当于k8s service account。限定业务之间的保护调用。并把这个参数添加到证书的扩展字段中 385 | // 386 | // 证书中的扩展字段:X509v3 extensions 387 | if identityBundle != nil { 388 | // 创建一个spiffe id,ID协议结构: 389 | // spiffe://trustDomain/ns/namespace/appID 390 | // 如:spiffe://dc1/ns/default/demo 391 | // 392 | // 在k8s中 393 | // spiffe://cluster.local/ns/default/sa/default 394 | spiffeID, err := identity.CreateSPIFFEID(identityBundle.TrustDomain, identityBundle.Namespace, identityBundle.ID) 395 | if err != nil { 396 | return nil, errors.Wrap(err, "error generating spiffe id") 397 | } 398 | 399 | // 然后通过x509 v3标准协议中的扩展字段,进行SPIFFE标准数据填充 400 | // 但是具体如何使用,目前我还不太清楚,后面清楚了,我再补充进去 401 | // ::TODO 402 | rv := []asn1.RawValue{ 403 | { 404 | Bytes: []byte(spiffeID), 405 | Class: asn1.ClassContextSpecific, 406 | Tag: asn1.TagOID, 407 | }, 408 | { 409 | // 比如dns: dapr-placement-server.dapr-system.svc.cluster.local 410 | Bytes: []byte(fmt.Sprintf("%s.%s.svc.cluster.local", subject,identityBundle.Namespace)), 411 | Class: asn1.ClassContextSpecific, 412 | Tag: 2, 413 | }, 414 | } 415 | 416 | b, err := asn1.Marshal(rv) 417 | if err != nil { 418 | return nil, errors.Wrap(err, "failed to marshal asn1 raw value for spiffe id") 419 | } 420 | 421 | cert.ExtraExtensions = append(cert.ExtraExtensions, pkix.Extension{ 422 | Id: oidSubjectAlternativeName, 423 | Value: b, 424 | Critical: true, // According to x509 and SPIFFE specs, a SubjAltName extension must be critical if subject name and DNS are not present. 425 | }) 426 | } 427 | 428 | // 把x509.Certificate编码为加密数据流 429 | return x509.CreateCertificate(rand.Reader, cert, signingCert, publicKey, signingKey) 430 | } 431 | ``` 432 | 433 | 通过对外部grpc client访问dapr sentry服务,可以理解CSR请求证书签署和颁布公钥证书(数字签名)的整个过程,并且进行数字签名的过程中,还在证书带上了SPIFFE标准,用于保护内部微服务之间,也就是dapr runtime容器服务之间的访问保护,进行身份校验。 434 | 435 | 目前还不知道具体是如何在访问过程中进行证书扩展字段SPIFFE标准校验的, 后面理解后再补充**::TODO** 436 | 437 | ## dapr sentry grpc服务的封装 438 | 439 | 要把dapr sentry服务CA根证书和Issuer证书颁布者的证书和私钥创建过程,以及为dapr sentry grpc服务添加TLS证书的创建过程联系起来,则需要一个封装过程,这个是用sentry.CertificateAuthority接口来实现的。其默认实现类是sentry类 440 | 441 | ```golang 442 | // CertificateAuthority 接口表示dapr sentry服务的运行和重启 443 | type CertificateAuthority interface { 444 | Run(context.Context, config.SentryConfig, chan bool) 445 | Restart(ctx context.Context, conf config.SentryConfig) 446 | } 447 | 448 | // sentry表示CertificateAuthority接口的实现之一 449 | type sentry struct { 450 | server server.CAServer 451 | reloading bool 452 | } 453 | 454 | // NewSentryCA 创建一个CertificateAuthority接口实例sentry对象 455 | func NewSentryCA() CertificateAuthority { 456 | return &sentry{} 457 | } 458 | 459 | // Run 启动dapr sentry服务,对于k8s 则为dapr-system命名空间下的dapr-sentry POD. 460 | func (s *sentry) Run(ctx context.Context, conf config.SentryConfig, readyCh chan bool) { 461 | // 创建CA接口实例,也就是defaultCA实例对象 462 | certAuth, err := ca.NewCertificateAuthority(conf) 463 | if err != nil { 464 | log.Fatalf("error getting certificate authority: %s", err) 465 | } 466 | log.Info("certificate authority loaded") 467 | 468 | // 加载或者创建TrustRootBundle对象,包括CA根证书、Issuer证书颁布者的证书和私钥 469 | err = certAuth.LoadOrStoreTrustBundle() 470 | if err != nil { 471 | log.Fatalf("error loading trust root bundle: %s", err) 472 | } 473 | log.Infof("trust root bundle loaded. issuer cert expiry: %s", certAuth.GetCACertBundle().GetIssuerCertExpiry().String()) 474 | monitoring.IssuerCertExpiry(certAuth.GetCACertBundle().GetIssuerCertExpiry()) 475 | 476 | // Create 创建Validator,用于校验CSR请求的身份identity。包括:ID、Namespace和token 477 | v, err := createValidator() 478 | if err != nil { 479 | log.Fatalf("error creating validator: %s", err) 480 | } 481 | log.Info("validator created") 482 | 483 | // 创建grpc 50001端口服务,并通过CA机构创建证书,并最后填充到grpc ServerOptions的TLS参数中 484 | s.server = server.NewCAServer(certAuth, v) 485 | 486 | go func() { 487 | <-ctx.Done() 488 | log.Info("sentry certificate authority is shutting down") 489 | s.server.Shutdown() // nolint: errcheck 490 | }() 491 | if readyCh != nil { 492 | readyCh <- true 493 | s.reloading = false 494 | } 495 | 496 | log.Infof("sentry certificate authority is running, protecting ya'll") 497 | err = s.server.Run(conf.Port, certAuth.GetCACertBundle()) 498 | if err != nil { 499 | log.Fatalf("error starting gRPC server: %s", err) 500 | } 501 | } 502 | 503 | // createValidator 获取dapr k8s包创建的Validator验证实例, 用于校验identity的身份,包括ID、token和命名空间 504 | // 如果是host,则不用任何校验 505 | func createValidator() (identity.Validator, error) { 506 | if config.IsKubernetesHosted() { 507 | // we're in Kubernetes, create client and init a new serviceaccount token valid ator 508 | kubeClient, err := k8s.GetClient() 509 | if err != nil { 510 | return nil, errors.Wrap(err, "failed to create kubernetes client") 511 | } 512 | return kubernetes.NewValidator(kubeClient), nil 513 | } 514 | return selfhosted.NewValidator(), nil 515 | } 516 | 517 | // Restart 停止grpc server服务,并重启端口服务 518 | func (s *sentry) Restart(ctx context.Context, conf config.SentryConfig) { 519 | if s.reloading { 520 | return 521 | } 522 | s.reloading = true 523 | 524 | s.server.Shutdown() 525 | go s.Run(ctx, conf, nil) 526 | } 527 | ``` 528 | 529 | ## 总结 530 | 531 | 通过下篇dapr sentry服务的整个创建过程,以及为自身grpc服务提供TLS证书,和处理外部grpc client的CSR签署和颁布数字证书请求, 使得我们可以基本上了解CA机构和Issuer证书颁布者,以及如何为服务签署数字证书。 532 | -------------------------------------------------------------------------------- /pkg/validation_scopes_signals.md: -------------------------------------------------------------------------------- 1 | 2 | 本文对dapr的validation、modes、scopes、version、signals和cors六个包进行源码分析 3 | 4 | ## modes 5 | 6 | 目前dapr的部署支持两种模式,一种是k8s;另一种是standalone(host)。 7 | 8 | ## validation 9 | 10 | 该dapr validation库主要是针对业务POD的app-id设置,进行命名规范来符合k8s资源定义名称的设置。 11 | 12 | 因为在业务POD中设置dapr.io/app-id时,k8s只会对资源的实例名称进行名称校验,所以annotations是不会校验的,这时候就需要在Daprcontroller订阅到该资源时,去annotations中的数据进行k8s命名规范校验,然后构建生成service dapr app-id或者其他需要以app-id来创建资源的命名规范,包括component、configuration等等CRDs。 13 | 14 | 那么命名规范只需要保持与k8s的命名规范规则一样就OK了。所以这里需要借助k8s的命名规范,把相关参数值校验方法拿来即用就行。 15 | 16 | [k8s validation](https://github.com/kubernetes/apimachinery/blob/master/pkg/util/validation/validation.go) 17 | 18 | 主要采用正则表达式对appid进行校验,表达式: **^[a-z0-9]([-a-z0-9]*[a-z0-9])?$**, 校验规则的方法:**isDNS1123Label** 19 | 20 | 然后再通过一个封装方法,来达到对appid进行规则校验的目的: 21 | 22 | ```golang 23 | // ValidateKubernetesAppID 对appid进行k8s资源命名规则校验 24 | func ValidateKubernetesAppID(appID string) error { 25 | // appID 不能为空 26 | if appID == "" { 27 | return errors.New("value for the dapr.io/app-id annotation is empty") 28 | } 29 | // 正则表达式匹配appid, 是否符合k8s规范 30 | r := isDNS1123Label(appID) 31 | if len(r) == 0 { 32 | return nil 33 | } 34 | s := fmt.Sprintf("invalid app id(input: %s): %s", appID, strings.Join(r, ",")) 35 | return errors.New(s) 36 | } 37 | ``` 38 | 39 | ## version 40 | 41 | 返回version和commit。 42 | 43 | 1. version,表示当前代码库所在的版本号,当没有发布1.0.0的版本之前,全部都是edge; 44 | 2. commit。表示CI时构建服务时,所用的版本commitid。 45 | 46 | ## scopes 47 | 48 | 这个dapr scopes是对pubsub的发布和订阅功能进行范围控制定义。 49 | 50 | 因为可能一个资源配置文件CRDs,可以对整个dapr集群的所有appid进行资源使用配置,如下所示: 51 | 52 | ```yaml 53 | apiVersion: dapr.io/v1alpha1 54 | kind: Component 55 | metadata: 56 | name: pubsub 57 | namespace: default 58 | spec: 59 | type: pubsub.redis 60 | version: v1 61 | metadata: 62 | - name: redisHost 63 | value: "localhost:6379" 64 | - name: redisPassword 65 | value: "" 66 | - name: publishingScopes 67 | value: "app1=topic1;app2=topic2,topic3;app3=" 68 | - name: subscriptionScopes 69 | value: "app2=;app3=topic1" 70 | ``` 71 | 72 | The table below shows which applications are allowed to publish into the topics: 73 | 74 | | | topic1 | topic2 | topic3 | 75 | |------|--------|--------|--------| 76 | | app1 | X | | | 77 | | app2 | | X | X | 78 | | app3 | | | | 79 | 80 | The table below shows which applications are allowed to subscribe to the topics: 81 | 82 | | | topic1 | topic2 | topic3 | 83 | |------|--------|--------|--------| 84 | | app1 | X | X | X | 85 | | app2 | | | | 86 | | app3 | X | | | 87 | 88 | 上面这个配置是3个服务,三个topic。,我们可以看到app1可以通过配置publishingScopes,来达到发布topic消息的能力;然后app1也可以通过subscriptionScopes来达到订阅topic的能力。 89 | 90 | 注意,如果不进行显示设置,则默认为全部发布和订阅topic。如:app1的订阅,以及app2的不订阅能力 91 | 92 | 接下来主要对这个CRDs资源文件进行解析, 来校验指定appid是否具有订阅和发布topic的能力 93 | 94 | 95 | ```golang 96 | const ( 97 | // dapr的pubsub发布和订阅能力,归属于component CRDs资源配置中 98 | // SubscriptionScopes表示资源订阅范围: topic和appid列表 99 | SubscriptionScopes = "subscriptionScopes" 100 | // PublishingScopes 表示资源发布的范围:topic和appid列表 101 | PublishingScopes = "publishingScopes" 102 | // AllowedTopics 表示资源允许的topic列表 103 | AllowedTopics = "allowedTopics" 104 | // 解析topic与appid的资源分隔符种类 105 | appsSeperator = ";" 106 | appSeperator = "=" 107 | topicSeperator = "," 108 | ) 109 | 110 | // GetScopedTopics 根据scope key值,返回指定appID订阅的topic列表 111 | func GetScopedTopics(scope, appID string, metadata map[string]string) []string { 112 | topics := []string{} 113 | 114 | // 校验metadata中是否存在scope范围, 如果不存在,则表示没有设置过scope,意味着没有topic和appid使用scope能力 115 | // 116 | // 否则,表示scope存在,则继续查找appID订阅的topic列表 117 | if val, ok := metadata[scope]; ok && val != "" { 118 | // val如果为空,表示全部appid,则是返回全部的topic,还是返回空topic呢? 119 | // 这是个需要考虑的问题,后面实验下 ::TODO 120 | // 121 | // 获取到的appXXX=topic01, topic02, xxx, topicN; appXXY= 122 | // 首先通过;分割成列表,然后遍历找到appID 123 | apps := strings.Split(val, appsSeperator) 124 | for _, a := range apps { 125 | // 再给每个appid=topic01,topic02配置项进行解析 126 | // 通过'='分割,分割成appid与topic01, topic02... 127 | appTopics := strings.Split(a, appSeperator) 128 | if len(appTopics) == 0 { 129 | continue 130 | } 131 | 132 | // 然后在匹配appID与遍历检索到的appID是否相同 133 | app := appTopics[0] 134 | if app != appID { 135 | continue 136 | } 137 | 138 | // 最后再把topic01, topic02..., topic0N进行','分割,并返回列表给topic 139 | topics = strings.Split(appTopics[1], topicSeperator) 140 | break 141 | } 142 | } 143 | return topics 144 | } 145 | 146 | // GetAllowedTopics 用来校验允许进行发布和订阅的topic范围列表。 147 | func GetAllowedTopics(metadata map[string]string) []string { 148 | topics := []string{} 149 | 150 | if val, ok := metadata[AllowedTopics]; ok && val != "" { 151 | topics = strings.Split(val, topicSeperator) 152 | } 153 | return topics 154 | } 155 | ``` 156 | 157 | GetScopedTopics与GetAllowedTopics的关系说明: 158 | 159 | 对于任意一个发布和订阅的实例名称pubsub.name。从两个功能角度考虑: 160 | 1. publish。如果一个业务POD,其appid为appid1, 发布消息到指定的一个topic或者多个topic,则首先检测该topic是否在AllowedTopics;然后再检测该topic是否在PublishingScopes,配置了appid1=topic1, topicXXX..., 如果配置了,则表示可以进行publish; 161 | 2. subscribe。同上,只是对AllowdTopics检查完后,再对SubscriptionScopes的appid和topic进行校验,如果配置了,则表示可以进行subscribe 162 | 163 | 也就是说,AllowedTopics表示第一次拦截,ScopedTopics表示第二次拦截。当pubsub.name、appid和topic列表都通过后,则可以做相应的发布和订阅消息操作 164 | 165 | **疑问**: 166 | 167 | ```yaml 168 | type: pubsub.redis 169 | version: v1 170 | metadata: 171 | - name: publishingScopes 172 | value: "" 173 | ``` 174 | 175 | 下面这三个appid如果填写publishingScopes, 怎么填写呢? 176 | 177 | | | topic1 | topic2 | topic3 | 178 | |------|--------|--------|--------| 179 | | app1 | | | | 180 | | app2 | | | | 181 | | app3 | | | | 182 | 183 | ## signals 184 | 185 | ```golang 186 | // Context 用于接收系统信号,包括Ctrl+C中断进程等,并做一些context的后处理,包括尚未完成的业务逻辑、关闭监听端口等 187 | // 然后再一次监听Ctrl+C中断进程信息后,再通过log fatal exit退出进程。可以拿来复用 188 | // 189 | // 因为这个进程中断与context绑定起来了。使得进程退出时可以做一些后处理。使得进程可以正常退出,尽量减少对业务的影响 190 | func Context() context.Context { 191 | ctx, cancel := context.WithCancel(context.Background()) 192 | sigCh := make(chan os.Signal) 193 | signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) 194 | go func() { 195 | sig := <-sigCh 196 | log.Infof(`Received signal "%s"; beginning shutdown`, sig) 197 | cancel() 198 | sig = <-sigCh 199 | log.Fatalf( 200 | `Received signal "%s" during shutdown; exiting immediately`, 201 | sig, 202 | ) 203 | }() 204 | return ctx 205 | } 206 | ``` 207 | 208 | 通过这种方式,可以减少流量的损失,以及对业务的损害值得我们学习 209 | 210 | ## cors 211 | 212 | DefaultAllowedOrigins = "*" 213 | 214 | 这个cors包目前只有这行代码,表示默认支持所有跨域 215 | 216 | 217 | ## 总结 218 | 219 | 在上面这几个dapr包,对我最有收获的就是signals包,它与context联合来处理服务推出时,留出时间对业务处理,包括:未处理完的业务逻辑、关闭监听端口不再对外提供连接。以尽量减少对业务的负面影响 220 | --------------------------------------------------------------------------------