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  }