gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/runtime/kubernetes/kubernetes.go (about) 1 // Package kubernetes implements kubernetes micro runtime 2 package kubernetes 3 4 import ( 5 "fmt" 6 "sync" 7 "time" 8 9 "gitee.com/liuxuezhan/go-micro-v1.18.0/runtime" 10 "gitee.com/liuxuezhan/go-micro-v1.18.0/runtime/kubernetes/client" 11 "gitee.com/liuxuezhan/go-micro-v1.18.0/util/log" 12 ) 13 14 // action to take on runtime service 15 type action int 16 17 const ( 18 start action = iota 19 update 20 stop 21 ) 22 23 // task is queued into runtime queue 24 type task struct { 25 action action 26 service *service 27 } 28 29 type kubernetes struct { 30 sync.RWMutex 31 // options configure runtime 32 options runtime.Options 33 // indicates if we're running 34 running bool 35 // task queue for kubernetes services 36 queue chan *task 37 // used to stop the runtime 38 closed chan bool 39 // client is kubernetes client 40 client client.Kubernetes 41 } 42 43 // getService queries kubernetes for micro service 44 // NOTE: this function is not thread-safe 45 func (k *kubernetes) getService(labels map[string]string) ([]*runtime.Service, error) { 46 // get the service status 47 serviceList := new(client.ServiceList) 48 r := &client.Resource{ 49 Kind: "service", 50 Value: serviceList, 51 } 52 53 // get the service from k8s 54 if err := k.client.Get(r, labels); err != nil { 55 return nil, err 56 } 57 58 // get the deployment status 59 depList := new(client.DeploymentList) 60 d := &client.Resource{ 61 Kind: "deployment", 62 Value: depList, 63 } 64 65 // get the deployment from k8s 66 if err := k.client.Get(d, labels); err != nil { 67 return nil, err 68 } 69 70 // service map 71 svcMap := make(map[string]*runtime.Service) 72 73 // collect info from kubernetes service 74 for _, kservice := range serviceList.Items { 75 // name of the service 76 name := kservice.Metadata.Labels["name"] 77 // version of the service 78 version := kservice.Metadata.Labels["version"] 79 80 // save as service 81 svcMap[name+version] = &runtime.Service{ 82 Name: name, 83 Version: version, 84 Metadata: make(map[string]string), 85 } 86 87 // copy annotations metadata into service metadata 88 for k, v := range kservice.Metadata.Annotations { 89 svcMap[name+version].Metadata[k] = v 90 } 91 } 92 93 // collect additional info from kubernetes deployment 94 for _, kdep := range depList.Items { 95 // name of the service 96 name := kdep.Metadata.Labels["name"] 97 // versio of the service 98 version := kdep.Metadata.Labels["version"] 99 100 // access existing service map based on name + version 101 if svc, ok := svcMap[name+version]; ok { 102 // we're expecting our own service name in metadata 103 if _, ok := kdep.Metadata.Annotations["name"]; !ok { 104 continue 105 } 106 107 // set the service name, version and source 108 // based on existing annotations we stored 109 svc.Name = kdep.Metadata.Annotations["name"] 110 svc.Version = kdep.Metadata.Annotations["version"] 111 svc.Source = kdep.Metadata.Annotations["source"] 112 113 // delete from metadata 114 delete(kdep.Metadata.Annotations, "name") 115 delete(kdep.Metadata.Annotations, "version") 116 delete(kdep.Metadata.Annotations, "source") 117 118 // copy all annotations metadata into service metadata 119 for k, v := range kdep.Metadata.Annotations { 120 svc.Metadata[k] = v 121 } 122 123 // parse out deployment status and inject into service metadata 124 if len(kdep.Status.Conditions) > 0 { 125 status := kdep.Status.Conditions[0].Type 126 // pick the last known condition type and mark the service status with it 127 log.Debugf("Runtime setting %s service deployment status: %v", name, status) 128 svc.Metadata["status"] = status 129 } 130 131 // parse out deployment build 132 if build, ok := kdep.Spec.Template.Metadata.Annotations["build"]; ok { 133 buildTime, err := time.Parse(time.RFC3339, build) 134 if err != nil { 135 log.Debugf("Runtime failed parsing build time for %s: %v", name, err) 136 continue 137 } 138 svc.Metadata["build"] = fmt.Sprintf("%d", buildTime.Unix()) 139 continue 140 } 141 // if no build annotation is found, set it to current time 142 svc.Metadata["build"] = fmt.Sprintf("%d", time.Now().Unix()) 143 } 144 } 145 146 // collect all the services and return 147 services := make([]*runtime.Service, 0, len(serviceList.Items)) 148 149 for _, service := range svcMap { 150 services = append(services, service) 151 } 152 153 return services, nil 154 } 155 156 // run runs the runtime management loop 157 func (k *kubernetes) run(events <-chan runtime.Event) { 158 t := time.NewTicker(time.Second * 10) 159 defer t.Stop() 160 161 for { 162 select { 163 case <-t.C: 164 // TODO: figure out what to do here 165 // - do we even need the ticker for k8s services? 166 case task := <-k.queue: 167 // The task queue is used to take actions e.g (CRUD - R) 168 switch task.action { 169 case start: 170 log.Debugf("Runtime starting new service: %s", task.service.Name) 171 if err := task.service.Start(k.client); err != nil { 172 log.Debugf("Runtime failed to start service %s: %v", task.service.Name, err) 173 continue 174 } 175 case stop: 176 log.Debugf("Runtime stopping service: %s", task.service.Name) 177 if err := task.service.Stop(k.client); err != nil { 178 log.Debugf("Runtime failed to stop service %s: %v", task.service.Name, err) 179 continue 180 } 181 case update: 182 log.Debugf("Runtime updating service: %s", task.service.Name) 183 if err := task.service.Update(k.client); err != nil { 184 log.Debugf("Runtime failed to update service %s: %v", task.service.Name, err) 185 continue 186 } 187 default: 188 log.Debugf("Runtime received unknown action for service: %s", task.service.Name) 189 } 190 case event := <-events: 191 // NOTE: we only handle Update events for now 192 log.Debugf("Runtime received notification event: %v", event) 193 switch event.Type { 194 case runtime.Update: 195 // only process if there's an actual service 196 // we do not update all the things individually 197 if len(event.Service) == 0 { 198 continue 199 } 200 201 // format the name 202 name := client.Format(event.Service) 203 204 // set the default labels 205 labels := map[string]string{ 206 "micro": k.options.Type, 207 "name": name, 208 } 209 210 if len(event.Version) > 0 { 211 labels["version"] = event.Version 212 } 213 214 // get the deployment status 215 deployed := new(client.DeploymentList) 216 217 // get the existing service rather than creating a new one 218 err := k.client.Get(&client.Resource{ 219 Kind: "deployment", 220 Value: deployed, 221 }, labels) 222 223 if err != nil { 224 log.Debugf("Runtime update failed to get service %s: %v", event.Service, err) 225 continue 226 } 227 228 // technically we should not receive multiple versions but hey ho 229 for _, service := range deployed.Items { 230 // check the name matches 231 if service.Metadata.Name != name { 232 continue 233 } 234 235 // update build time annotation 236 if service.Spec.Template.Metadata.Annotations == nil { 237 service.Spec.Template.Metadata.Annotations = make(map[string]string) 238 239 } 240 241 // check the existing build timestamp 242 if build, ok := service.Spec.Template.Metadata.Annotations["build"]; ok { 243 buildTime, err := time.Parse(time.RFC3339, build) 244 if err == nil && !event.Timestamp.After(buildTime) { 245 continue 246 } 247 } 248 249 // update the build time 250 service.Spec.Template.Metadata.Annotations["build"] = event.Timestamp.Format(time.RFC3339) 251 252 log.Debugf("Runtime updating service: %s deployment: %s", event.Service, service.Metadata.Name) 253 if err := k.client.Update(deploymentResource(&service)); err != nil { 254 log.Debugf("Runtime failed to update service %s: %v", event.Service, err) 255 continue 256 } 257 } 258 } 259 case <-k.closed: 260 log.Debugf("Runtime stopped") 261 return 262 } 263 } 264 } 265 266 // Init initializes runtime options 267 func (k *kubernetes) Init(opts ...runtime.Option) error { 268 k.Lock() 269 defer k.Unlock() 270 271 for _, o := range opts { 272 o(&k.options) 273 } 274 275 return nil 276 } 277 278 // Creates a service 279 func (k *kubernetes) Create(s *runtime.Service, opts ...runtime.CreateOption) error { 280 k.Lock() 281 defer k.Unlock() 282 283 options := runtime.CreateOptions{ 284 Type: k.options.Type, 285 } 286 for _, o := range opts { 287 o(&options) 288 } 289 290 // quickly prevalidate the name and version 291 name := s.Name 292 if len(s.Version) > 0 { 293 name = name + "-" + s.Version 294 } 295 296 // format as we'll format in the deployment 297 name = client.Format(name) 298 299 // create new kubernetes micro service 300 service := newService(s, options) 301 302 log.Debugf("Runtime queueing service %s version %s for start action", service.Name, service.Version) 303 304 // push into start queue 305 k.queue <- &task{ 306 action: start, 307 service: service, 308 } 309 310 return nil 311 } 312 313 // Read returns all instances of given service 314 func (k *kubernetes) Read(opts ...runtime.ReadOption) ([]*runtime.Service, error) { 315 k.Lock() 316 defer k.Unlock() 317 318 // set the default labels 319 labels := map[string]string{ 320 "micro": k.options.Type, 321 } 322 323 var options runtime.ReadOptions 324 for _, o := range opts { 325 o(&options) 326 } 327 328 if len(options.Service) > 0 { 329 labels["name"] = client.Format(options.Service) 330 } 331 332 // add version to labels if a version has been supplied 333 if len(options.Version) > 0 { 334 labels["version"] = options.Version 335 } 336 337 if len(options.Type) > 0 { 338 labels["micro"] = options.Type 339 } 340 341 return k.getService(labels) 342 } 343 344 // List the managed services 345 func (k *kubernetes) List() ([]*runtime.Service, error) { 346 k.Lock() 347 defer k.Unlock() 348 349 labels := map[string]string{ 350 "micro": k.options.Type, 351 } 352 353 log.Debugf("Runtime listing all micro services") 354 355 return k.getService(labels) 356 } 357 358 // Update the service in place 359 func (k *kubernetes) Update(s *runtime.Service) error { 360 // create new kubernetes micro service 361 service := newService(s, runtime.CreateOptions{ 362 Type: k.options.Type, 363 }) 364 365 // update build time annotation 366 service.kdeploy.Spec.Template.Metadata.Annotations["build"] = time.Now().Format(time.RFC3339) 367 368 log.Debugf("Runtime queueing service %s for update action", service.Name) 369 370 // queue service for removal 371 k.queue <- &task{ 372 action: update, 373 service: service, 374 } 375 376 return nil 377 } 378 379 // Delete removes a service 380 func (k *kubernetes) Delete(s *runtime.Service) error { 381 k.Lock() 382 defer k.Unlock() 383 384 // create new kubernetes micro service 385 service := newService(s, runtime.CreateOptions{ 386 Type: k.options.Type, 387 }) 388 389 log.Debugf("Runtime queueing service %s for delete action", service.Name) 390 391 // queue service for removal 392 k.queue <- &task{ 393 action: stop, 394 service: service, 395 } 396 397 return nil 398 } 399 400 // Start starts the runtime 401 func (k *kubernetes) Start() error { 402 k.Lock() 403 defer k.Unlock() 404 405 // already running 406 if k.running { 407 return nil 408 } 409 410 // set running 411 k.running = true 412 k.closed = make(chan bool) 413 414 var events <-chan runtime.Event 415 if k.options.Notifier != nil { 416 var err error 417 events, err = k.options.Notifier.Notify() 418 if err != nil { 419 // TODO: should we bail here? 420 log.Debugf("Runtime failed to start update notifier") 421 } 422 } 423 424 go k.run(events) 425 426 return nil 427 } 428 429 // Stop shuts down the runtime 430 func (k *kubernetes) Stop() error { 431 k.Lock() 432 defer k.Unlock() 433 434 if !k.running { 435 return nil 436 } 437 438 select { 439 case <-k.closed: 440 return nil 441 default: 442 close(k.closed) 443 // set not running 444 k.running = false 445 // stop the notifier too 446 if k.options.Notifier != nil { 447 return k.options.Notifier.Close() 448 } 449 } 450 451 return nil 452 } 453 454 // String implements stringer interface 455 func (k *kubernetes) String() string { 456 return "kubernetes" 457 } 458 459 // NewRuntime creates new kubernetes runtime 460 func NewRuntime(opts ...runtime.Option) runtime.Runtime { 461 // get default options 462 options := runtime.Options{ 463 // Create labels with type "micro": "service" 464 Type: "service", 465 } 466 467 // apply requested options 468 for _, o := range opts { 469 o(&options) 470 } 471 472 // kubernetes client 473 client := client.NewClientInCluster() 474 475 return &kubernetes{ 476 options: options, 477 closed: make(chan bool), 478 queue: make(chan *task, 128), 479 client: client, 480 } 481 }