github.com/micro/go-micro/v2@v2.9.1/runtime/kubernetes/service.go (about) 1 package kubernetes 2 3 import ( 4 "encoding/json" 5 "strings" 6 "time" 7 8 "github.com/micro/go-micro/v2/logger" 9 "github.com/micro/go-micro/v2/runtime" 10 "github.com/micro/go-micro/v2/util/kubernetes/api" 11 "github.com/micro/go-micro/v2/util/kubernetes/client" 12 ) 13 14 type service struct { 15 // service to manage 16 *runtime.Service 17 // Kubernetes service 18 kservice *client.Service 19 // Kubernetes deployment 20 kdeploy *client.Deployment 21 } 22 23 func parseError(err error) *api.Status { 24 status := new(api.Status) 25 json.Unmarshal([]byte(err.Error()), &status) 26 return status 27 } 28 29 func newService(s *runtime.Service, c runtime.CreateOptions) *service { 30 // use pre-formatted name/version 31 name := client.Format(s.Name) 32 version := client.Format(s.Version) 33 34 kservice := client.NewService(name, version, c.Type, c.Namespace) 35 kdeploy := client.NewDeployment(name, version, c.Type, c.Namespace) 36 37 // ensure the metadata is set 38 if kdeploy.Spec.Template.Metadata.Annotations == nil { 39 kdeploy.Spec.Template.Metadata.Annotations = make(map[string]string) 40 } 41 42 // create if non existent 43 if s.Metadata == nil { 44 s.Metadata = make(map[string]string) 45 } 46 47 // add the service metadata to the k8s labels, do this first so we 48 // don't override any labels used by the runtime, e.g. name 49 for k, v := range s.Metadata { 50 kdeploy.Metadata.Annotations[k] = v 51 } 52 53 // attach our values to the deployment; name, version, source 54 kdeploy.Metadata.Annotations["name"] = s.Name 55 kdeploy.Metadata.Annotations["version"] = s.Version 56 kdeploy.Metadata.Annotations["source"] = s.Source 57 58 // associate owner:group to be later augmented 59 kdeploy.Metadata.Annotations["owner"] = "micro" 60 kdeploy.Metadata.Annotations["group"] = "micro" 61 62 // update the deployment is a custom source is provided 63 if len(c.Image) > 0 { 64 for i := range kdeploy.Spec.Template.PodSpec.Containers { 65 kdeploy.Spec.Template.PodSpec.Containers[i].Image = c.Image 66 kdeploy.Spec.Template.PodSpec.Containers[i].Command = []string{} 67 kdeploy.Spec.Template.PodSpec.Containers[i].Args = []string{} 68 } 69 } 70 71 // define the environment values used by the container 72 env := make([]client.EnvVar, 0, len(c.Env)) 73 for _, evar := range c.Env { 74 evarPair := strings.Split(evar, "=") 75 env = append(env, client.EnvVar{Name: evarPair[0], Value: evarPair[1]}) 76 } 77 78 // if environment has been supplied update deployment default environment 79 if len(env) > 0 { 80 kdeploy.Spec.Template.PodSpec.Containers[0].Env = append(kdeploy.Spec.Template.PodSpec.Containers[0].Env, env...) 81 } 82 83 // set the command if specified 84 if len(c.Command) > 0 { 85 kdeploy.Spec.Template.PodSpec.Containers[0].Command = c.Command 86 } 87 88 if len(c.Args) > 0 { 89 kdeploy.Spec.Template.PodSpec.Containers[0].Args = c.Args 90 } 91 92 return &service{ 93 Service: s, 94 kservice: kservice, 95 kdeploy: kdeploy, 96 } 97 } 98 99 func deploymentResource(d *client.Deployment) *client.Resource { 100 return &client.Resource{ 101 Name: d.Metadata.Name, 102 Kind: "deployment", 103 Value: d, 104 } 105 } 106 107 func serviceResource(s *client.Service) *client.Resource { 108 return &client.Resource{ 109 Name: s.Metadata.Name, 110 Kind: "service", 111 Value: s, 112 } 113 } 114 115 // Start starts the Kubernetes service. It creates new kubernetes deployment and service API objects 116 func (s *service) Start(k client.Client, opts ...client.CreateOption) error { 117 // create deployment first; if we fail, we dont create service 118 if err := k.Create(deploymentResource(s.kdeploy), opts...); err != nil { 119 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 120 logger.Debugf("Runtime failed to create deployment: %v", err) 121 } 122 s.Status("error", err) 123 v := parseError(err) 124 if v.Reason == "AlreadyExists" { 125 return runtime.ErrAlreadyExists 126 } 127 return err 128 } 129 // create service now that the deployment has been created 130 if err := k.Create(serviceResource(s.kservice), opts...); err != nil { 131 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 132 logger.Debugf("Runtime failed to create service: %v", err) 133 } 134 s.Status("error", err) 135 v := parseError(err) 136 if v.Reason == "AlreadyExists" { 137 return runtime.ErrAlreadyExists 138 } 139 return err 140 } 141 142 s.Status("started", nil) 143 144 return nil 145 } 146 147 func (s *service) Stop(k client.Client, opts ...client.DeleteOption) error { 148 // first attempt to delete service 149 if err := k.Delete(serviceResource(s.kservice), opts...); err != nil { 150 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 151 logger.Debugf("Runtime failed to delete service: %v", err) 152 } 153 s.Status("error", err) 154 return err 155 } 156 // delete deployment once the service has been deleted 157 if err := k.Delete(deploymentResource(s.kdeploy), opts...); err != nil { 158 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 159 logger.Debugf("Runtime failed to delete deployment: %v", err) 160 } 161 s.Status("error", err) 162 return err 163 } 164 165 s.Status("stopped", nil) 166 167 return nil 168 } 169 170 func (s *service) Update(k client.Client, opts ...client.UpdateOption) error { 171 if err := k.Update(deploymentResource(s.kdeploy), opts...); err != nil { 172 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 173 logger.Debugf("Runtime failed to update deployment: %v", err) 174 } 175 s.Status("error", err) 176 return err 177 } 178 if err := k.Update(serviceResource(s.kservice), opts...); err != nil { 179 if logger.V(logger.DebugLevel, logger.DefaultLogger) { 180 logger.Debugf("Runtime failed to update service: %v", err) 181 } 182 return err 183 } 184 185 return nil 186 } 187 188 func (s *service) Status(status string, err error) { 189 s.Metadata["lastStatusUpdate"] = time.Now().Format(time.RFC3339) 190 if err == nil { 191 s.Metadata["status"] = status 192 delete(s.Metadata, "error") 193 return 194 } 195 s.Metadata["status"] = "error" 196 s.Metadata["error"] = err.Error() 197 }