2019-11-15 16:41:40 +03:00
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
2020-01-18 05:13:24 +03:00
|
|
|
"encoding/json"
|
2020-08-14 13:47:28 +03:00
|
|
|
"fmt"
|
2019-11-15 16:41:40 +03:00
|
|
|
"strings"
|
2020-06-08 12:47:25 +03:00
|
|
|
"time"
|
2019-11-15 16:41:40 +03:00
|
|
|
|
2020-07-27 15:22:00 +03:00
|
|
|
"github.com/micro/go-micro/v3/logger"
|
|
|
|
"github.com/micro/go-micro/v3/runtime"
|
|
|
|
"github.com/micro/go-micro/v3/util/kubernetes/api"
|
|
|
|
"github.com/micro/go-micro/v3/util/kubernetes/client"
|
2019-11-15 16:41:40 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
type service struct {
|
|
|
|
// service to manage
|
|
|
|
*runtime.Service
|
|
|
|
// Kubernetes service
|
|
|
|
kservice *client.Service
|
|
|
|
// Kubernetes deployment
|
|
|
|
kdeploy *client.Deployment
|
|
|
|
}
|
|
|
|
|
2020-01-18 05:13:24 +03:00
|
|
|
func parseError(err error) *api.Status {
|
|
|
|
status := new(api.Status)
|
|
|
|
json.Unmarshal([]byte(err.Error()), &status)
|
|
|
|
return status
|
|
|
|
}
|
|
|
|
|
2019-11-15 16:41:40 +03:00
|
|
|
func newService(s *runtime.Service, c runtime.CreateOptions) *service {
|
2019-11-25 19:31:14 +03:00
|
|
|
// use pre-formatted name/version
|
|
|
|
name := client.Format(s.Name)
|
|
|
|
version := client.Format(s.Version)
|
2019-11-15 16:41:40 +03:00
|
|
|
|
2020-04-23 15:53:42 +03:00
|
|
|
kservice := client.NewService(name, version, c.Type, c.Namespace)
|
|
|
|
kdeploy := client.NewDeployment(name, version, c.Type, c.Namespace)
|
2019-11-25 19:31:14 +03:00
|
|
|
|
2020-02-28 18:07:55 +03:00
|
|
|
// ensure the metadata is set
|
|
|
|
if kdeploy.Spec.Template.Metadata.Annotations == nil {
|
|
|
|
kdeploy.Spec.Template.Metadata.Annotations = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
2020-03-15 00:18:41 +03:00
|
|
|
// create if non existent
|
|
|
|
if s.Metadata == nil {
|
|
|
|
s.Metadata = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
2020-02-28 18:07:55 +03:00
|
|
|
// add the service metadata to the k8s labels, do this first so we
|
|
|
|
// don't override any labels used by the runtime, e.g. name
|
|
|
|
for k, v := range s.Metadata {
|
|
|
|
kdeploy.Metadata.Annotations[k] = v
|
2020-02-06 12:17:10 +03:00
|
|
|
}
|
|
|
|
|
2019-11-25 19:31:14 +03:00
|
|
|
// attach our values to the deployment; name, version, source
|
|
|
|
kdeploy.Metadata.Annotations["name"] = s.Name
|
|
|
|
kdeploy.Metadata.Annotations["version"] = s.Version
|
|
|
|
kdeploy.Metadata.Annotations["source"] = s.Source
|
|
|
|
|
|
|
|
// associate owner:group to be later augmented
|
|
|
|
kdeploy.Metadata.Annotations["owner"] = "micro"
|
|
|
|
kdeploy.Metadata.Annotations["group"] = "micro"
|
|
|
|
|
2020-02-28 18:07:55 +03:00
|
|
|
// update the deployment is a custom source is provided
|
2020-03-13 21:39:59 +03:00
|
|
|
if len(c.Image) > 0 {
|
2020-02-28 18:07:55 +03:00
|
|
|
for i := range kdeploy.Spec.Template.PodSpec.Containers {
|
2020-03-13 21:39:59 +03:00
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[i].Image = c.Image
|
2020-02-28 18:07:55 +03:00
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[i].Command = []string{}
|
2020-03-13 21:39:59 +03:00
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[i].Args = []string{}
|
2020-02-28 18:07:55 +03:00
|
|
|
}
|
2019-11-26 20:33:41 +03:00
|
|
|
}
|
2019-11-26 16:49:52 +03:00
|
|
|
|
2019-11-25 19:31:14 +03:00
|
|
|
// define the environment values used by the container
|
2019-11-15 16:41:40 +03:00
|
|
|
env := make([]client.EnvVar, 0, len(c.Env))
|
|
|
|
for _, evar := range c.Env {
|
|
|
|
evarPair := strings.Split(evar, "=")
|
|
|
|
env = append(env, client.EnvVar{Name: evarPair[0], Value: evarPair[1]})
|
|
|
|
}
|
|
|
|
|
2020-07-29 15:41:50 +03:00
|
|
|
// if secrets were provided, pass them to the service
|
|
|
|
for key := range c.Secrets {
|
2020-07-10 18:25:46 +03:00
|
|
|
env = append(env, client.EnvVar{
|
2020-07-29 15:41:50 +03:00
|
|
|
Name: key,
|
2020-07-10 18:25:46 +03:00
|
|
|
ValueFrom: &client.EnvVarSource{
|
|
|
|
SecretKeyRef: &client.SecretKeySelector{
|
2020-07-29 15:41:50 +03:00
|
|
|
Name: credentialsName(s),
|
|
|
|
Key: key,
|
2020-07-10 18:25:46 +03:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-11-21 20:31:13 +03:00
|
|
|
// if environment has been supplied update deployment default environment
|
2019-11-15 16:41:40 +03:00
|
|
|
if len(env) > 0 {
|
2019-11-21 20:31:13 +03:00
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[0].Env = append(kdeploy.Spec.Template.PodSpec.Containers[0].Env, env...)
|
2019-11-15 16:41:40 +03:00
|
|
|
}
|
|
|
|
|
2020-03-13 21:39:59 +03:00
|
|
|
// set the command if specified
|
|
|
|
if len(c.Command) > 0 {
|
2019-11-25 19:31:14 +03:00
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[0].Command = c.Command
|
2019-11-15 16:41:40 +03:00
|
|
|
}
|
|
|
|
|
2020-03-13 21:39:59 +03:00
|
|
|
if len(c.Args) > 0 {
|
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[0].Args = c.Args
|
|
|
|
}
|
|
|
|
|
2020-08-14 13:47:28 +03:00
|
|
|
// apply resource limits
|
|
|
|
if c.Resources != nil {
|
|
|
|
resLimits := &client.ResourceLimits{}
|
|
|
|
if c.Resources.CPU > 0 {
|
|
|
|
resLimits.CPU = fmt.Sprintf("%vm", c.Resources.CPU)
|
|
|
|
}
|
|
|
|
if c.Resources.Mem > 0 {
|
|
|
|
resLimits.Memory = fmt.Sprintf("%vMi", c.Resources.Mem)
|
|
|
|
}
|
|
|
|
if c.Resources.Disk > 0 {
|
|
|
|
resLimits.EphemeralStorage = fmt.Sprintf("%vMi", c.Resources.Disk)
|
|
|
|
}
|
|
|
|
|
|
|
|
kdeploy.Spec.Template.PodSpec.Containers[0].Resources = &client.ResourceRequirements{Limits: resLimits}
|
|
|
|
}
|
|
|
|
|
2019-11-15 16:41:40 +03:00
|
|
|
return &service{
|
|
|
|
Service: s,
|
|
|
|
kservice: kservice,
|
|
|
|
kdeploy: kdeploy,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func deploymentResource(d *client.Deployment) *client.Resource {
|
|
|
|
return &client.Resource{
|
|
|
|
Name: d.Metadata.Name,
|
|
|
|
Kind: "deployment",
|
|
|
|
Value: d,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func serviceResource(s *client.Service) *client.Resource {
|
|
|
|
return &client.Resource{
|
|
|
|
Name: s.Metadata.Name,
|
|
|
|
Kind: "service",
|
|
|
|
Value: s,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start starts the Kubernetes service. It creates new kubernetes deployment and service API objects
|
2020-04-23 15:53:42 +03:00
|
|
|
func (s *service) Start(k client.Client, opts ...client.CreateOption) error {
|
2019-11-15 16:41:40 +03:00
|
|
|
// create deployment first; if we fail, we dont create service
|
2020-04-23 15:53:42 +03:00
|
|
|
if err := k.Create(deploymentResource(s.kdeploy), opts...); err != nil {
|
2020-03-11 20:55:39 +03:00
|
|
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
|
|
logger.Debugf("Runtime failed to create deployment: %v", err)
|
|
|
|
}
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("error", err)
|
2020-01-18 05:13:24 +03:00
|
|
|
v := parseError(err)
|
|
|
|
if v.Reason == "AlreadyExists" {
|
|
|
|
return runtime.ErrAlreadyExists
|
|
|
|
}
|
2019-11-15 16:41:40 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// create service now that the deployment has been created
|
2020-04-23 15:53:42 +03:00
|
|
|
if err := k.Create(serviceResource(s.kservice), opts...); err != nil {
|
2020-03-11 20:55:39 +03:00
|
|
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
|
|
logger.Debugf("Runtime failed to create service: %v", err)
|
|
|
|
}
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("error", err)
|
2020-01-18 05:13:24 +03:00
|
|
|
v := parseError(err)
|
|
|
|
if v.Reason == "AlreadyExists" {
|
|
|
|
return runtime.ErrAlreadyExists
|
|
|
|
}
|
2019-11-15 16:41:40 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("started", nil)
|
|
|
|
|
2019-11-15 16:41:40 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-23 15:53:42 +03:00
|
|
|
func (s *service) Stop(k client.Client, opts ...client.DeleteOption) error {
|
2019-11-15 16:41:40 +03:00
|
|
|
// first attempt to delete service
|
2020-04-23 15:53:42 +03:00
|
|
|
if err := k.Delete(serviceResource(s.kservice), opts...); err != nil {
|
2020-03-11 20:55:39 +03:00
|
|
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
|
|
logger.Debugf("Runtime failed to delete service: %v", err)
|
|
|
|
}
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("error", err)
|
2019-11-15 16:41:40 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// delete deployment once the service has been deleted
|
2020-04-23 15:53:42 +03:00
|
|
|
if err := k.Delete(deploymentResource(s.kdeploy), opts...); err != nil {
|
2020-03-11 20:55:39 +03:00
|
|
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
|
|
logger.Debugf("Runtime failed to delete deployment: %v", err)
|
|
|
|
}
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("error", err)
|
2019-11-15 16:41:40 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("stopped", nil)
|
|
|
|
|
2019-11-15 16:41:40 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-04-23 15:53:42 +03:00
|
|
|
func (s *service) Update(k client.Client, opts ...client.UpdateOption) error {
|
|
|
|
if err := k.Update(deploymentResource(s.kdeploy), opts...); err != nil {
|
2020-03-11 20:55:39 +03:00
|
|
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
|
|
logger.Debugf("Runtime failed to update deployment: %v", err)
|
|
|
|
}
|
2020-01-11 00:54:28 +03:00
|
|
|
s.Status("error", err)
|
2019-11-15 16:41:40 +03:00
|
|
|
return err
|
|
|
|
}
|
2020-04-23 15:53:42 +03:00
|
|
|
if err := k.Update(serviceResource(s.kservice), opts...); err != nil {
|
2020-03-11 20:55:39 +03:00
|
|
|
if logger.V(logger.DebugLevel, logger.DefaultLogger) {
|
|
|
|
logger.Debugf("Runtime failed to update service: %v", err)
|
|
|
|
}
|
2019-11-15 16:41:40 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-01-11 00:54:28 +03:00
|
|
|
|
|
|
|
func (s *service) Status(status string, err error) {
|
2020-06-08 12:47:25 +03:00
|
|
|
s.Metadata["lastStatusUpdate"] = time.Now().Format(time.RFC3339)
|
2020-01-11 00:54:28 +03:00
|
|
|
if err == nil {
|
|
|
|
s.Metadata["status"] = status
|
2020-06-08 12:47:25 +03:00
|
|
|
delete(s.Metadata, "error")
|
2020-01-11 00:54:28 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
s.Metadata["status"] = "error"
|
|
|
|
s.Metadata["error"] = err.Error()
|
|
|
|
}
|