From faa86d72909a475e9ae3bd3d1bc67c4e778800c1 Mon Sep 17 00:00:00 2001 From: Robert Grandl Date: Fri, 3 Nov 2023 08:58:00 -0700 Subject: [PATCH] Add yaml config file with configurable k8s knobs --- internal/impl/babysitter.go | 18 +- internal/impl/config.go | 180 ++++++++-------- internal/impl/deploy.go | 84 +++----- internal/impl/kube.go | 367 +++++++++++++++++---------------- internal/impl/kube.pb.go | 59 ++++-- internal/impl/kube.proto | 1 + internal/impl/observability.go | 29 ++- internal/proto/env.go | 40 ---- 8 files changed, 362 insertions(+), 416 deletions(-) delete mode 100644 internal/proto/env.go diff --git a/internal/impl/babysitter.go b/internal/impl/babysitter.go index 9c33c24..e896b4e 100644 --- a/internal/impl/babysitter.go +++ b/internal/impl/babysitter.go @@ -194,7 +194,10 @@ func (b *babysitter) watchPods(ctx context.Context, component string) error { b.mu.Unlock() // Watch the pods running the requested component. - rs := replicaSetName(component, b.app) + rs, ok := b.cfg.Groups[component] + if !ok { + return fmt.Errorf("unable to determine group name for component %s", component) + } name := deploymentName(b.app.Name, rs, b.cfg.DeploymentId) opts := metav1.ListOptions{LabelSelector: fmt.Sprintf("serviceweaver/name=%s", name)} watcher, err := b.clientset.CoreV1().Pods(b.cfg.Namespace).Watch(ctx, opts) @@ -323,16 +326,3 @@ func serveHTTP(ctx context.Context, lis net.Listener, handler http.Handler) erro return server.Shutdown(ctx) } } - -// replicaSetName returns the name of the replica set that hosts a given -// component. -func replicaSetName(component string, app *protos.AppConfig) string { - for _, group := range app.Colocate { - for _, c := range group.Components { - if c == component { - return group.Components[0] - } - } - } - return component -} diff --git a/internal/impl/config.go b/internal/impl/config.go index a2cb222..6181a89 100644 --- a/internal/impl/config.go +++ b/internal/impl/config.go @@ -14,10 +14,17 @@ package impl -// kubeConfig contains configuration for a Service Weaver application deployed -// with `weaver kube deploy`. The contents of a kubeConfig are parsed from the -// [kube] section of a weaver.toml file. +import ( + autoscalingv2 "k8s.io/api/autoscaling/v2" + corev1 "k8s.io/api/core/v1" +) + +// kubeConfig contains the kubernetes configuration for a Service Weaver +// application deployed with `weaver kube deploy`. type kubeConfig struct { + // Path to the app config file. + AppConfig string + // Image is the name of the container image hosting the Service Weaver // application. // @@ -51,70 +58,90 @@ type kubeConfig struct { // account for your namespace. // // [1] https://kubernetes.io/docs/concepts/security/service-accounts/ - ServiceAccount string `toml:"service_account"` + ServiceAccount string // If true, application listeners will use the underlying nodes' network. // This behavior is generally discouraged, but it may be useful when running // the application in a minikube environment, where using the underlying // nodes' network may make it easier to access the listeners directly from // the host machine. - UseHostNetwork bool `toml:"use_host_network"` + UseHostNetwork bool - // Options for the application listeners, keyed by listener name. - // If a listener isn't specified in the map, default options will be used. - Listeners map[string]*listenerConfig + // Options for the application listeners. If a listener isn't specified in the + // map, default options will be used. + Listeners []listenerSpec - // Observability controls how the deployer will export observability information - // such as logs, metrics and traces, keyed by service. If no options are - // specified, the deployer will launch corresponding services for exporting logs, - // metrics and traces automatically. + // Resource requirements needed to run the pods. Note that the resources should + // satisfy the format specified in [1]. + // + // [1] https://pkg.go.dev/k8s.io/api/core/v1#ResourceRequirements. + // + // TODO(rgrandl): we might want to allow the user to specify the resource + // requirements differently for each group of collocated components. + ResourceSpec *corev1.ResourceRequirements + + // Specs on how to scale the pods. Note that the scaling specs should satisfy + // the format specified in [1]. // - // The key must be one of the following strings: - // "prometheus_service" - to export metrics to Prometheus [1] - // "jaeger_service" - to export traces to Jaeger [2] - // "loki_service" - to export logs to Grafana Loki [3] - // "grafana_service" - to visualize/manipulate observability information [4] + // [1] https://pkg.go.dev/k8s.io/kubernetes/pkg/apis/autoscaling#HorizontalPodAutoscalerSpec. + // + // TODO(rgrandl): we might want to allow the user to specify the scaling spec + // differently for each group of collocated components. + ScalingSpec *autoscalingv2.HorizontalPodAutoscalerSpec + + // Volumes that should be provided to all the running components. + Volumes []volume + + // Options for probes to check the readiness/liveness/startup of the pods. + // Note that the scaling specs should satisfy the format specified in [1]. + // + // [1] https://pkg.go.dev/k8s.io/api/core/v1#Probe. + ProbeSpec probes + + // Groups contains kubernetes configuration for groups of collocated components. + // Note that some knobs if specified for a group will override the corresponding + // knob set for all the groups (e.g., ScalingSpec, ResourceSpec); for knobs like + // Volumes, each group will contain the sum of the volumes specified for all + // groups and the ones set for the group. + Groups []group + + // Observability controls how the deployer will export observability information + // such as logs, metrics and traces. If no options are specified, the deployer + //will launch corresponding services for exporting logs, metrics and traces automatically. // // Possible values for each service: - // 1) do not specify a value at all; leave it empty + // 1) do not specify a value at all // this is the default behavior; kube deployer will automatically create the // observability service for you. // // 2) "none" // kube deployer will not export the corresponding observability information to - // any service. E.g., prometheus_service = "none" means that the user will not + // any service. E.g., `prometheusService: none` means that the user will not // be able to see any metrics at all. This can be useful for testing or // benchmarking the performance of your application. // - // 3) "your_observability_service_name" + // 3) `your observability service name` // if you already have a running service to collect metrics, traces or logs, // then you can simply specify the service name, and your application will // automatically export the corresponding information to your service. E.g., - // jaeger_service = "jaeger-all-in-one" will enable your running Jaeger + // `jaegerService: jaeger-all-in-one` will enable your running Jaeger // "service/jaeger-all-in-one" to capture all the app traces. // // [1] - https://prometheus.io/ // [2] - https://www.jaegertracing.io/ // [3] - https://grafana.com/oss/loki/ // [4] - https://grafana.com/ - Observability map[string]string - - // Resources needed to run the pods. Note that the resources should satisfy - // the format specified in [1]. - // - // [1] https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#example-MustParse. - Resources resourceRequirements - - // Options for probes to check the readiness/liveness of the pods. - LivenessProbeOpts *probeOptions `toml:"liveness_probe"` - ReadinessProbeOpts *probeOptions `toml:"readiness_probe"` + Observability observability } -// listenerConfig stores configuration options for a listener. -type listenerConfig struct { +// listenerSpec stores configuration options for a listener. +type listenerSpec struct { + // Listener name. + Name string + // If specified, the listener service will have the name set to this value. // Otherwise, we will generate a unique name for each app version. - ServiceName string `toml:"service_name"` + ServiceName string // Is the listener public, i.e., should it receive ingress traffic // from the public internet. If false, the listener is configured only @@ -127,67 +154,38 @@ type listenerConfig struct { Port int32 } -// resourceRequirements stores the resource requirements configuration for running pods. -type resourceRequirements struct { - // Describes the minimum amount of CPU required to run the pod. - RequestsCPU string `toml:"requests_cpu"` - // Describes the minimum amount of memory required to run the pod. - RequestsMem string `toml:"requests_mem"` - // Describes the maximum amount of CPU allowed to run the pod. - LimitsCPU string `toml:"limits_cpu"` - // Describes the maximum amount of memory allowed to run the pod. - LimitsMem string `toml:"limits_mem"` -} - -// probeOptions stores the probes [1] configuration for the pods. These options -// mirror the Kubernetes probe options available in [2]. -// -// [1] https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ -// [2] https://github.com/kubernetes/api/blob/v0.28.3/core/v1/types.go#L2277 +// Encapsulates probe specs as defined by the user in the kubernetes config. // -// TODO(rgrandl): There are a few more knobs available in the kubernetes probe -// definition. We can enable more knobs if really needed. -type probeOptions struct { - // How often to perform the probe. - PeriodSecs int32 `toml:"period_secs"` - // Number of seconds after which the probe times out. - TimeoutSecs int32 `toml:"timeout_secs"` - // Minimum consecutive successes for the probe to be considered successful after having failed. - SuccessThreshold int32 `toml:"success_threshold"` - // Minimum consecutive failures for the probe to be considered failed after having succeeded. - FailureThreshold int32 `toml:"failure_threshold"` - - // Probe behavior. Note that only one of the following should be set by the user. - - // The probe action is taken by executing commands. - Exec *execAction - // The probe action is taken by executing HTTP GET requests. - Http *httpAction - // The probe action is taken by executing TCP requests. - Tcp *tcpAction +// Note that we create this struct, so we can group the probe specs under a single +// section in the yaml config file. +type probes struct { + ReadinessProbe *corev1.Probe // Periodic probe of container service readiness. + LivenessProbe *corev1.Probe // Periodic probe of container liveness. + StartupProbe *corev1.Probe // Indicates that the pod has successfully initialized. } -// execAction describes the probe action when using a list of commands. It mirrors -// Kubernetes ExecAction [1]. -// -// [1] https://github.com/kubernetes/api/blob/v0.28.3/core/v1/types.go#L2265 -type execAction struct { - Cmd []string // List of commands to execute inside the container. +// group contains kubernetes configuration for a group of colocated components. +type group struct { + Name string // name of the group + Components []string // list of components in the group + Volumes []volume // list of volumes to mount + ResourceSpec *corev1.ResourceRequirements + ScalingSpec *autoscalingv2.HorizontalPodAutoscalerSpec + listeners []listener // hosted listeners, populated by the kube deployer. } -// httpAction describes the probe action when using HTTP. It mirrors Kubernetes -// HTTPGetAction [1]. -// -// [1] https://github.com/kubernetes/api/blob/v0.28.3/core/v1/types.go#L2208 -type httpAction struct { - Path string // Path to access on the HTTP server. - Port int32 // Port number to access on the container. +// volume contains kubernetes information to configure a kubernetes volume. +type volume struct { + Name string + VolumeSource *corev1.VolumeSource + VolumeMount *corev1.VolumeMount } -// tcpAction describes the probe action when using TCP. It mirrors Kubernetes -// TCPSocketAction [1]. -// -// [1] https://github.com/kubernetes/api/blob/v0.28.3/core/v1/types.go#L2241 -type tcpAction struct { - Port int32 // Port number to access on the container. +// observability contains information on how metrics, traces, logs and dashboards +// should be exported. +type observability struct { + JaegerService string + PrometheusService string + LokiService string + GrafanaService string } diff --git a/internal/impl/deploy.go b/internal/impl/deploy.go index b5bdcbf..1486611 100644 --- a/internal/impl/deploy.go +++ b/internal/impl/deploy.go @@ -26,22 +26,38 @@ import ( "github.com/ServiceWeaver/weaver/runtime/codegen" "github.com/ServiceWeaver/weaver/runtime/version" "github.com/google/uuid" + "sigs.k8s.io/yaml" ) // Deploy generates a Kubernetes YAML file and corresponding Docker image to -// deploy the Service Weaver application specified by the provided weaver.toml +// deploy the Service Weaver application specified by the provided kube.yaml // config file. -func Deploy(ctx context.Context, configFilename string) error { +func Deploy(ctx context.Context, configFile string) error { // Read the config file. - contents, err := os.ReadFile(configFilename) + contents, err := os.ReadFile(configFile) if err != nil { - return fmt.Errorf("read config file %q: %w", configFilename, err) + return fmt.Errorf("read deployment config file %q: %w", configFile, err) + } + + // Parse and validate the deployment config. + config := &kubeConfig{} + if err := yaml.Unmarshal(contents, config); err != nil { + return fmt.Errorf("parse deployment config file %q: %w", configFile, err) + } + + if config.AppConfig == "" { + return fmt.Errorf("app config file not specified") + } + // Read the app config file. + contents, err = os.ReadFile(config.AppConfig) + if err != nil { + return fmt.Errorf("read app config file %q: %w", config.AppConfig, err) } // Parse and validate the app config. - app, err := runtime.ParseConfig(configFilename, string(contents), codegen.ComponentConfigValidator) + app, err := runtime.ParseConfig(config.AppConfig, string(contents), codegen.ComponentConfigValidator) if err != nil { - return fmt.Errorf("parse config file %q: %w", configFilename, err) + return fmt.Errorf("parse app config file %q: %w", config.AppConfig, err) } if _, err := os.Stat(app.Binary); errors.Is(err, os.ErrNotExist) { return fmt.Errorf("binary %q doesn't exist", app.Binary) @@ -50,11 +66,6 @@ func Deploy(ctx context.Context, configFilename string) error { return err } - // Parse and validate the [kube] section of the app config. - config := &kubeConfig{} - if err := runtime.ParseConfigSection("kube", "github.com/ServiceWeaver/weaver/kube", app.Sections, config); err != nil { - return fmt.Errorf("parse [kube] section of config: %w", err) - } if config.Repo == "" { fmt.Fprintln(os.Stderr, "No container repo specified in the config file. The container image will only be accessible locally. See `weaver kube deploy --help` for details.") } @@ -65,14 +76,6 @@ func Deploy(ctx context.Context, configFilename string) error { config.ServiceAccount = "default" } - // Validate the probe options. - if err := checkProbeOptions(config.LivenessProbeOpts); err != nil { - return fmt.Errorf("invalid liveness probe spec: %w", err) - } - if err := checkProbeOptions(config.ReadinessProbeOpts); err != nil { - return fmt.Errorf("invalid readiness probe spec: %w", err) - } - binListeners, err := bin.ReadListeners(app.Binary) if err != nil { return fmt.Errorf("cannot read listeners from binary %s: %w", app.Binary, err) @@ -83,9 +86,9 @@ func Deploy(ctx context.Context, configFilename string) error { allListeners[l] = struct{}{} } } - for lis := range config.Listeners { - if _, ok := allListeners[lis]; !ok { - return fmt.Errorf("listener %s specified in the config not found in the binary", lis) + for _, lis := range config.Listeners { + if _, ok := allListeners[lis.Name]; !ok { + return fmt.Errorf("listener %s specified in the config not found in the binary", lis.Name) } } @@ -99,7 +102,7 @@ func Deploy(ctx context.Context, configFilename string) error { } // Generate the kube deployment information. - return generateYAMLs(configFilename, app, config, depId, image) + return generateYAMLs(app, config, depId, image) } // checkVersionCompatibility checks that the `weaver kube` binary is compatible @@ -115,7 +118,7 @@ func checkVersionCompatibility(appBinary string) error { } // Try to relativize the binary, defaulting to the absolute path if there - // are any errors.. + // are any errors. relativize := func(bin string) string { cwd, err := os.Getwd() if err != nil { @@ -147,36 +150,3 @@ func checkVersionCompatibility(appBinary string) error { } return nil } - -// checkProbeOptions validates the configuration options for the probes. -func checkProbeOptions(opts *probeOptions) error { - if opts == nil { - return nil - } - // Check that exactly one of the probe handlers is set. - phSet := 0 - if opts.Http != nil { - phSet++ - } - if opts.Tcp != nil { - phSet++ - } - if opts.Exec != nil { - phSet++ - } - if phSet != 1 { - return fmt.Errorf("exactly one probe handler should be specified; %d provided", phSet) - } - - // Validate the handlers. - if opts.Http != nil && (opts.Http.Port < 1 || opts.Http.Port > 65535) { - return fmt.Errorf("http handler: invalid port %d", opts.Http.Port) - } - if opts.Tcp != nil && (opts.Tcp.Port < 1 || opts.Tcp.Port > 65535) { - return fmt.Errorf("tcp handler: invalid port %d", opts.Tcp.Port) - } - if opts.Exec != nil && len(opts.Exec.Cmd) == 0 { - return fmt.Errorf("exec handler: no commands specified") - } - return nil -} diff --git a/internal/impl/kube.go b/internal/impl/kube.go index 11c60ce..2653727 100644 --- a/internal/impl/kube.go +++ b/internal/impl/kube.go @@ -34,7 +34,6 @@ import ( _ "k8s.io/api/autoscaling/v2beta2" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -58,21 +57,14 @@ var externalPort int32 = 20000 // Note that this is different from a Kubernetes Deployment. A deployed Service // Weaver application consists of many Kubernetes Deployments. type deployment struct { - deploymentId string // globally unique deployment id - image string // Docker image URI - traceServiceURL string // where traces are exported to, if not empty - config *kubeConfig // [kube] config from weaver.toml + deploymentId string // globally unique deployment id + image string // Docker image URI + traceServiceURL string // where traces are exported to, if not empty + config *kubeConfig app *protos.AppConfig // parsed weaver.toml groups []group // groups } -// group contains information about a possibly replicated group of components. -type group struct { - name string // group name - components []string // hosted components - listeners []listener // hosted listeners -} - // listener contains information about a listener. type listener struct { name string // listener name @@ -81,27 +73,6 @@ type listener struct { public bool // is the listener publicly accessible } -// shortenComponent shortens the given component name to be of the format -// -. (Recall that the full component name is of the format -// //...//.) -func shortenComponent(component string) string { - parts := strings.Split(component, "/") - switch len(parts) { - case 0: // should never happen - panic(fmt.Errorf("invalid component name: %s", component)) - case 1: - return parts[0] - default: - return fmt.Sprintf("%s-%s", parts[len(parts)-2], parts[len(parts)-1]) - } -} - -func deploymentName(app, component, deploymentId string) string { - hash := hash8([]string{app, component, deploymentId}) - shortened := strings.ToLower(shortenComponent(component)) - return fmt.Sprintf("%s-%s-%s", shortened, deploymentId[:8], hash) -} - // buildDeployment generates a Kubernetes Deployment for a group. // // TODO(rgrandl): test to see if it works with an app where a component foo is @@ -109,13 +80,13 @@ func deploymentName(app, component, deploymentId string) string { // calls foo. func buildDeployment(d deployment, g group) (*appsv1.Deployment, error) { // Create labels. - name := deploymentName(d.app.Name, g.name, d.deploymentId) + name := deploymentName(d.app.Name, g.Name, d.deploymentId) podLabels := map[string]string{ "serviceweaver/name": name, "serviceweaver/app": d.app.Name, "serviceweaver/version": d.deploymentId[:8], } - if d.config.Observability[metricsConfigKey] != disabled { + if d.config.Observability.PrometheusService != disabled { podLabels["metrics"] = d.app.Name // Needed by Prometheus to scrape the metrics. } @@ -132,7 +103,7 @@ func buildDeployment(d deployment, g group) (*appsv1.Deployment, error) { } // Create Deployment. - return &appsv1.Deployment{ + dep := &appsv1.Deployment{ TypeMeta: metav1.TypeMeta{ APIVersion: "apps/v1", Kind: "Deployment", @@ -145,7 +116,7 @@ func buildDeployment(d deployment, g group) (*appsv1.Deployment, error) { "serviceweaver/version": d.deploymentId[:8], }, Annotations: map[string]string{ - "description": fmt.Sprintf("This Deployment hosts components %v.", strings.Join(g.components, ", ")), + "description": fmt.Sprintf("This Deployment hosts components %v.", strings.Join(g.Components, ", ")), }, }, Spec: appsv1.DeploymentSpec{ @@ -159,7 +130,7 @@ func buildDeployment(d deployment, g group) (*appsv1.Deployment, error) { Labels: podLabels, Namespace: d.config.Namespace, Annotations: map[string]string{ - "description": fmt.Sprintf("This Pod hosts components %v.", strings.Join(g.components, ", ")), + "description": fmt.Sprintf("This Pod hosts components %v.", strings.Join(g.Components, ", ")), }, }, Spec: corev1.PodSpec{ @@ -182,7 +153,30 @@ func buildDeployment(d deployment, g group) (*appsv1.Deployment, error) { }, }, }, - }, nil + } + + // Add volume sources if any volume specified for the application. + for _, v := range d.config.Volumes { + if err := validateVolumeSpec(v); err != nil { + return nil, err + } + dep.Spec.Template.Spec.Volumes = append(dep.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: v.Name, + VolumeSource: *v.VolumeSource, + }) + } + + // Add volume sources if any volume specified for the group. + for _, v := range g.Volumes { + if err := validateVolumeSpec(v); err != nil { + return nil, err + } + dep.Spec.Template.Spec.Volumes = append(dep.Spec.Template.Spec.Volumes, corev1.Volume{ + Name: v.Name, + VolumeSource: *v.VolumeSource, + }) + } + return dep, nil } // buildListenerService generates a kubernetes service for a listener. @@ -216,7 +210,7 @@ func buildListenerService(d deployment, g group, lis listener) (*corev1.Service, Spec: corev1.ServiceSpec{ Type: corev1.ServiceType(serviceType), Selector: map[string]string{ - "serviceweaver/name": deploymentName(d.app.Name, g.name, d.deploymentId), + "serviceweaver/name": deploymentName(d.app.Name, g.Name, d.deploymentId), }, Ports: []corev1.ServicePort{ { @@ -232,29 +226,18 @@ func buildListenerService(d deployment, g group, lis listener) (*corev1.Service, // buildAutoscaler generates a Kubernetes HorizontalPodAutoscaler for a group. func buildAutoscaler(d deployment, g group) (*autoscalingv2.HorizontalPodAutoscaler, error) { // Per deployment name that is app version specific. - name := deploymentName(d.app.Name, g.name, d.deploymentId) - return &autoscalingv2.HorizontalPodAutoscaler{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "autoscaling/v2", - Kind: "HorizontalPodAutoscaler", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: d.config.Namespace, - Labels: map[string]string{ - "serviceweaver/app": d.app.Name, - "serviceweaver/version": d.deploymentId[:8], - }, - Annotations: map[string]string{ - "description": fmt.Sprintf("This HorizontalPodAutoscaler scales the %q Deployment.", name), - }, - }, - Spec: autoscalingv2.HorizontalPodAutoscalerSpec{ - ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{ - APIVersion: "apps/v1", - Kind: "Deployment", - Name: name, - }, + name := deploymentName(d.app.Name, g.Name, d.deploymentId) + + var spec autoscalingv2.HorizontalPodAutoscalerSpec + + // Override the autoscaling spec if the user provides any spec. The scaling + // spec set for the group takes priority. + if g.ScalingSpec != nil { // Scaling spec specified for the group + spec = *g.ScalingSpec + } else if d.config.ScalingSpec != nil { // Scaling spec specified for the app + spec = *d.config.ScalingSpec + } else { // No scaling spec specified, compute default spec. + spec = autoscalingv2.HorizontalPodAutoscalerSpec{ MinReplicas: ptrOf(int32(1)), MaxReplicas: 10, Metrics: []autoscalingv2.MetricSpec{ @@ -271,7 +254,32 @@ func buildAutoscaler(d deployment, g group) (*autoscalingv2.HorizontalPodAutosca }, }, }, + } + } + + spec.ScaleTargetRef = autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: name, + } + + return &autoscalingv2.HorizontalPodAutoscaler{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "autoscaling/v2", + Kind: "HorizontalPodAutoscaler", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: d.config.Namespace, + Labels: map[string]string{ + "serviceweaver/app": d.app.Name, + "serviceweaver/version": d.deploymentId[:8], + }, + Annotations: map[string]string{ + "description": fmt.Sprintf("This HorizontalPodAutoscaler scales the %q Deployment.", name), + }, }, + Spec: spec, }, nil } @@ -285,7 +293,7 @@ func buildContainer(d deployment, g group) (corev1.Container, error) { ContainerPort: l.port, }) } - if d.config.Observability[metricsConfigKey] != disabled { + if d.config.Observability.PrometheusService != disabled { // Expose the metrics port from the container, so it can be // discoverable for scraping by Prometheus. // @@ -298,18 +306,11 @@ func buildContainer(d deployment, g group) (corev1.Container, error) { }) } - // Gather the set of resources. - resources, err := computeResourceRequirements(d.config.Resources) - if err != nil { - return corev1.Container{}, err - } - c := corev1.Container{ Name: appContainerName, Image: d.image, ImagePullPolicy: corev1.PullIfNotPresent, - Args: append([]string{"babysitter", "/weaver/weaver.toml", "/weaver/config.textpb"}, g.components...), - Resources: resources, + Args: append([]string{"babysitter", "/weaver/weaver.toml", "/weaver/config.textpb"}, g.Components...), Ports: ports, VolumeMounts: []corev1.VolumeMount{ { @@ -330,46 +331,45 @@ func buildContainer(d deployment, g group) (corev1.Container, error) { Stdin: true, } - createProbeFn := func(opts *probeOptions) *corev1.Probe { - probe := &corev1.Probe{} - if opts.TimeoutSecs > 0 { - probe.TimeoutSeconds = opts.TimeoutSecs - } - if opts.PeriodSecs > 0 { - probe.PeriodSeconds = opts.PeriodSecs - } - if opts.SuccessThreshold > 0 { - probe.SuccessThreshold = opts.SuccessThreshold + // Add volume mounts if any volume specified for the application. + for _, v := range d.config.Volumes { + if err := validateVolumeSpec(v); err != nil { + return c, err } - if opts.FailureThreshold > 0 { - probe.FailureThreshold = opts.FailureThreshold - } - if opts.Tcp != nil { - probe.TCPSocket = &corev1.TCPSocketAction{Port: intstr.IntOrString{IntVal: opts.Tcp.Port}} - } - if opts.Http != nil { - probe.HTTPGet = &corev1.HTTPGetAction{Port: intstr.IntOrString{IntVal: opts.Http.Port}} - if opts.Http.Path != "" { - // If no path specified, the HTTPGetAction will do health checks on "/". - probe.HTTPGet.Path = opts.Http.Path - } - } - if opts.Exec != nil { - // Command is optional for an ExecAction. However, it's confusing why that's - // the case, especially that this is the only parameter to configure for an - // ExecAction. - probe.Exec = &corev1.ExecAction{Command: opts.Exec.Cmd} + vm := v.VolumeMount + vm.Name = v.Name // Name should match the name of the volume source. + c.VolumeMounts = append(c.VolumeMounts, *vm) + } + + // Add volume mounts if any volume specified for the group. + for _, v := range g.Volumes { + if err := validateVolumeSpec(v); err != nil { + return c, err } - return probe + vm := v.VolumeMount + vm.Name = v.Name // Name should match the name of the volume source. + c.VolumeMounts = append(c.VolumeMounts, *vm) + } + + // Add custom resource requirements if any. The resource spec set for the group + // takes priority. + if g.ResourceSpec != nil { + c.Resources = *g.ResourceSpec + } else if d.config.ResourceSpec != nil { + c.Resources = *d.config.ResourceSpec } // Add probes if any. - if d.config.LivenessProbeOpts != nil { - c.LivenessProbe = createProbeFn(d.config.LivenessProbeOpts) + if d.config.ProbeSpec.ReadinessProbe != nil { + c.ReadinessProbe = d.config.ProbeSpec.ReadinessProbe } - if d.config.ReadinessProbeOpts != nil { - c.LivenessProbe = createProbeFn(d.config.ReadinessProbeOpts) + if d.config.ProbeSpec.LivenessProbe != nil { + c.LivenessProbe = d.config.ProbeSpec.LivenessProbe } + if d.config.ProbeSpec.StartupProbe != nil { + c.StartupProbe = d.config.ProbeSpec.StartupProbe + } + return c, nil } @@ -400,7 +400,7 @@ func buildContainer(d deployment, g group) (corev1.Container, error) { // // - If observability services are enabled (e.g., Prometheus, Jaeger), a // Kubernetes Deployment and/or a Service for each observability service. -func generateYAMLs(configFilename string, app *protos.AppConfig, cfg *kubeConfig, depId, image string) error { +func generateYAMLs(app *protos.AppConfig, cfg *kubeConfig, depId, image string) error { fmt.Fprintf(os.Stderr, greenText(), "\nGenerating kube deployment info ...") // Form deployment. @@ -424,7 +424,7 @@ func generateYAMLs(configFilename string, app *protos.AppConfig, cfg *kubeConfig } // Generate configuration ConfigMap. - if err := generateConfigMap(&b, configFilename, d); err != nil { + if err := generateConfigMap(&b, cfg.AppConfig, d); err != nil { return fmt.Errorf("unable to generate configuration ConfigMap: %w", err) } @@ -513,7 +513,7 @@ func header(d deployment, filename string) (string, error) { // Compute groups. groups := make([][]string, len(d.groups)) for i, g := range d.groups { - groups[i] = g.components + groups[i] = g.Components } // Compute listeners. @@ -612,18 +612,24 @@ func generateConfigMap(w io.Writer, configFilename string, d deployment) error { return err } - // Form config.textpb. + // Form config.textpb and mapping from component names to their group names. listeners := map[string]int32{} + groups := map[string]string{} for _, g := range d.groups { for _, lis := range g.listeners { listeners[lis.name] = lis.port } + for _, c := range g.Components { + groups[c] = g.Name + } } + babysitterConfig := &BabysitterConfig{ Namespace: d.config.Namespace, DeploymentId: d.deploymentId, TraceServiceUrl: d.traceServiceURL, Listeners: listeners, + Groups: groups, } configTextpb, err := prototext.MarshalOptions{Multiline: true}.Marshal(babysitterConfig) if err != nil { @@ -669,7 +675,7 @@ func generateCoreYAMLs(w io.Writer, d deployment) error { if err != nil { return fmt.Errorf("unable to create kube listener service for %s: %w", lis.name, err) } - if err := marshalResource(w, service, fmt.Sprintf("Listener Service for group %s", g.name)); err != nil { + if err := marshalResource(w, service, fmt.Sprintf("Listener Service for group %s", g.Name)); err != nil { return err } fmt.Fprintf(os.Stderr, "Generated kube listener service for listener %v\n", lis.name) @@ -678,22 +684,22 @@ func generateCoreYAMLs(w io.Writer, d deployment) error { // Build a Deployment for the group. deployment, err := buildDeployment(d, g) if err != nil { - return fmt.Errorf("unable to create kube deployment for group %s: %w", g.name, err) + return fmt.Errorf("unable to create kube deployment for group %s: %w", g.Name, err) } - if err := marshalResource(w, deployment, fmt.Sprintf("Deployment for group %s", g.name)); err != nil { + if err := marshalResource(w, deployment, fmt.Sprintf("Deployment for group %s", g.Name)); err != nil { return err } - fmt.Fprintf(os.Stderr, "Generated kube deployment for group %v\n", g.name) + fmt.Fprintf(os.Stderr, "Generated kube deployment for group %v\n", g.Name) // Build autoscaler HorizontalPodAutoscaler for the Deployment. autoscaler, err := buildAutoscaler(d, g) if err != nil { - return fmt.Errorf("unable to create kube autoscaler for group %s: %w", g.name, err) + return fmt.Errorf("unable to create kube autoscaler for group %s: %w", g.Name, err) } - if err := marshalResource(w, autoscaler, fmt.Sprintf("Autoscaler for group %s", g.name)); err != nil { + if err := marshalResource(w, autoscaler, fmt.Sprintf("Autoscaler for group %s", g.Name)); err != nil { return err } - fmt.Fprintf(os.Stderr, "Generated kube autoscaler for group %v\n", g.name) + fmt.Fprintf(os.Stderr, "Generated kube autoscaler for group %v\n", g.Name) } return nil } @@ -707,8 +713,8 @@ func newDeployment(app *protos.AppConfig, cfg *kubeConfig, depId, image string) } // Map every component to its group, or nil if it's in a group by itself. - groups := map[string]*protos.ComponentGroup{} - for _, group := range app.Colocate { + groups := map[string]group{} + for _, group := range cfg.Groups { for _, component := range group.Components { groups[component] = group } @@ -718,32 +724,45 @@ func newDeployment(app *protos.AppConfig, cfg *kubeConfig, depId, image string) groupsByName := map[string]group{} for component, listeners := range components { // We use the first component in a group as the name of the group. - name := component - if group, ok := groups[component]; ok { - name = group.Components[0] + var gname string + cgroup, ok := groups[component] + if ok { + gname = cgroup.Name + } else { + gname = component } // Append the component and listeners to the group. - g, ok := groupsByName[name] + g, ok := groupsByName[gname] if !ok { - g = group{name: name} + g = group{ + Name: gname, + Volumes: cgroup.Volumes, + ResourceSpec: cgroup.ResourceSpec, + ScalingSpec: cgroup.ScalingSpec, + } } - g.components = append(g.components, component) + g.Components = append(g.Components, component) for _, name := range listeners { - g.listeners = append(g.listeners, newListener(depId, cfg, name)) + lis, err := newListener(depId, cfg, name) + if err != nil { + return deployment{}, err + } + g.listeners = append(g.listeners, lis) } - groupsByName[name] = g + + groupsByName[gname] = g } // Sort groups by name to ensure stable YAML. sorted := maps.Values(groupsByName) sort.Slice(sorted, func(i, j int) bool { - return sorted[i].name < sorted[j].name + return sorted[i].Name < sorted[j].Name }) // Compute the URL of the export traces service. var traceServiceURL string - switch jservice := cfg.Observability[tracesConfigKey]; { + switch jservice := cfg.Observability.JaegerService; { case jservice == auto: // Point to the service launched by the kube deployer. traceServiceURL = fmt.Sprintf("http://%s:%d/api/traces", name{app.Name, jaegerAppName}.DNSLabel(), defaultJaegerCollectorPort) @@ -766,7 +785,7 @@ func newDeployment(app *protos.AppConfig, cfg *kubeConfig, depId, image string) } // newListener returns a new listener. -func newListener(depId string, config *kubeConfig, name string) listener { +func newListener(depId string, config *kubeConfig, name string) (listener, error) { lis := listener{ name: name, serviceName: fmt.Sprintf("%s-%s", name, depId[:8]), @@ -775,60 +794,22 @@ func newListener(depId string, config *kubeConfig, name string) listener { } externalPort++ - opts, ok := config.Listeners[name] - if ok && opts.ServiceName != "" { - lis.serviceName = opts.ServiceName - } - if ok && opts.Public { - lis.public = opts.Public - } - if ok && opts.Port != 0 { - lis.port = opts.Port - } - return lis -} - -// computeResourceRequirements computes resource requirements. -func computeResourceRequirements(req resourceRequirements) (corev1.ResourceRequirements, error) { - requests := corev1.ResourceList{} - limits := corev1.ResourceList{} - - // Compute the resource requests. - if req.RequestsMem != "" { - reqsMem, err := resource.ParseQuantity(req.RequestsMem) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("unable to parse requests_mem: %w", err) + for _, l := range config.Listeners { + if name != l.Name { + continue } - requests[corev1.ResourceMemory] = reqsMem - } - if req.RequestsCPU != "" { - reqsCPU, err := resource.ParseQuantity(req.RequestsCPU) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("unable to parse requests_cpu: %w", err) + if l.ServiceName != "" { + lis.serviceName = l.ServiceName } - requests[corev1.ResourceCPU] = reqsCPU - } - - // Compute the resource limits. - if req.LimitsMem != "" { - limitsMem, err := resource.ParseQuantity(req.LimitsMem) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("unable to parse limits_mem: %w", err) + if l.Public { + lis.public = l.Public } - limits[corev1.ResourceMemory] = limitsMem - } - if req.LimitsCPU != "" { - limitsCPU, err := resource.ParseQuantity(req.LimitsCPU) - if err != nil { - return corev1.ResourceRequirements{}, fmt.Errorf("unable to parse limits_cpu: %w", err) + if l.Port != 0 { + lis.port = l.Port } - limits[corev1.ResourceCPU] = limitsCPU + return lis, nil } - - return corev1.ResourceRequirements{ - Requests: requests, - Limits: limits, - }, nil + return lis, nil } // readComponentsAndListeners returns a map from every component to its @@ -855,3 +836,33 @@ func readComponentsAndListeners(binary string) (map[string][]string, error) { return components, nil } + +// shortenComponent shortens the given component name to be of the format +// -. (Recall that the full component name is of the format +// //...//.) +func shortenComponent(component string) string { + parts := strings.Split(component, "/") + switch len(parts) { + case 0: // should never happen + panic(fmt.Errorf("invalid component name: %s", component)) + case 1: + return parts[0] + default: + return fmt.Sprintf("%s-%s", parts[len(parts)-2], parts[len(parts)-1]) + } +} + +func deploymentName(app, component, deploymentId string) string { + hash := hash8([]string{app, component, deploymentId}) + shortened := strings.ToLower(shortenComponent(component)) + return fmt.Sprintf("%s-%s-%s", shortened, deploymentId[:8], hash) +} + +func validateVolumeSpec(v volume) error { + if v.Name == "" || v.VolumeSource == nil || v.VolumeMount == nil { + return fmt.Errorf("invalid volume spec; need valid name, "+ + "source and mount specs; got name: %s, VolumeSource: %v, VolumeMount: %v", + v.Name, v.VolumeSource, v.VolumeMount) + } + return nil +} diff --git a/internal/impl/kube.pb.go b/internal/impl/kube.pb.go index 7f1d39b..bd739cc 100644 --- a/internal/impl/kube.pb.go +++ b/internal/impl/kube.pb.go @@ -40,10 +40,11 @@ type BabysitterConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` // Kubernetes namespace - DeploymentId string `protobuf:"bytes,2,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` // globally unique deployment id - TraceServiceUrl string `protobuf:"bytes,3,opt,name=trace_service_url,json=traceServiceUrl,proto3" json:"trace_service_url,omitempty"` // if not empty, where to send traces - Listeners map[string]int32 `protobuf:"bytes,4,rep,name=listeners,proto3" json:"listeners,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` // a map from listener name to port + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` // Kubernetes namespace + DeploymentId string `protobuf:"bytes,2,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` // globally unique deployment id + TraceServiceUrl string `protobuf:"bytes,3,opt,name=trace_service_url,json=traceServiceUrl,proto3" json:"trace_service_url,omitempty"` // if not empty, where to send traces + Listeners map[string]int32 `protobuf:"bytes,4,rep,name=listeners,proto3" json:"listeners,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` // a map from listener name to port + Groups map[string]string `protobuf:"bytes,5,rep,name=groups,proto3" json:"groups,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // a map from component name to group name } func (x *BabysitterConfig) Reset() { @@ -106,12 +107,19 @@ func (x *BabysitterConfig) GetListeners() map[string]int32 { return nil } +func (x *BabysitterConfig) GetGroups() map[string]string { + if x != nil { + return x.Groups + } + return nil +} + var File_internal_impl_kube_proto protoreflect.FileDescriptor var file_internal_impl_kube_proto_rawDesc = []byte{ 0x0a, 0x18, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x6d, 0x70, 0x6c, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x69, 0x6d, 0x70, 0x6c, - 0x22, 0x84, 0x02, 0x0a, 0x10, 0x42, 0x61, 0x62, 0x79, 0x73, 0x69, 0x74, 0x74, 0x65, 0x72, 0x43, + 0x22, 0xfb, 0x02, 0x0a, 0x10, 0x42, 0x61, 0x62, 0x79, 0x73, 0x69, 0x74, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, @@ -123,15 +131,22 @@ var file_internal_impl_kube_proto_rawDesc = []byte{ 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x69, 0x6d, 0x70, 0x6c, 0x2e, 0x42, 0x61, 0x62, 0x79, 0x73, 0x69, 0x74, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, - 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x1a, 0x3c, 0x0a, 0x0e, 0x4c, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x57, 0x65, 0x61, - 0x76, 0x65, 0x72, 0x2f, 0x77, 0x65, 0x61, 0x76, 0x65, 0x72, 0x2d, 0x6b, 0x75, 0x62, 0x65, 0x2f, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x69, 0x6d, 0x70, 0x6c, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x3a, 0x0a, 0x06, 0x67, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x69, 0x6d, 0x70, 0x6c, + 0x2e, 0x42, 0x61, 0x62, 0x79, 0x73, 0x69, 0x74, 0x74, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x73, 0x1a, 0x3c, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, + 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x1a, 0x39, 0x0a, 0x0b, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x34, + 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x57, 0x65, 0x61, 0x76, 0x65, 0x72, 0x2f, 0x77, 0x65, 0x61, 0x76, 0x65, + 0x72, 0x2d, 0x6b, 0x75, 0x62, 0x65, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x69, 0x6d, 0x70, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -146,18 +161,20 @@ func file_internal_impl_kube_proto_rawDescGZIP() []byte { return file_internal_impl_kube_proto_rawDescData } -var file_internal_impl_kube_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_internal_impl_kube_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_internal_impl_kube_proto_goTypes = []interface{}{ (*BabysitterConfig)(nil), // 0: impl.BabysitterConfig nil, // 1: impl.BabysitterConfig.ListenersEntry + nil, // 2: impl.BabysitterConfig.GroupsEntry } var file_internal_impl_kube_proto_depIdxs = []int32{ 1, // 0: impl.BabysitterConfig.listeners:type_name -> impl.BabysitterConfig.ListenersEntry - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 2, // 1: impl.BabysitterConfig.groups:type_name -> impl.BabysitterConfig.GroupsEntry + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_internal_impl_kube_proto_init() } @@ -185,7 +202,7 @@ func file_internal_impl_kube_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_internal_impl_kube_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/impl/kube.proto b/internal/impl/kube.proto index 3015b35..3906fee 100644 --- a/internal/impl/kube.proto +++ b/internal/impl/kube.proto @@ -24,4 +24,5 @@ message BabysitterConfig { string deployment_id = 2; // globally unique deployment id string trace_service_url = 3; // if not empty, where to send traces map listeners = 4; // a map from listener name to port + map groups = 5; // a map from component name to group name } diff --git a/internal/impl/observability.go b/internal/impl/observability.go index 70d975f..bcdef6f 100644 --- a/internal/impl/observability.go +++ b/internal/impl/observability.go @@ -202,7 +202,7 @@ func generateObservabilityYAMLs(w io.Writer, appName string, cfg *kubeConfig) er // [1] https://helm.sh/ func generateConfigsToExportTraces(w io.Writer, appName string, cfg *kubeConfig) error { // The user disabled exporting the traces, don't generate anything. - if cfg.Observability[tracesConfigKey] != auto { + if cfg.Observability.JaegerService != auto { return nil } @@ -338,7 +338,7 @@ func generateConfigsToExportTraces(w io.Writer, appName string, cfg *kubeConfig) // [1] https://helm.sh/ func generateConfigsToExportMetrics(w io.Writer, appName string, cfg *kubeConfig) error { // The user disabled exporting the metrics, don't generate anything. - if cfg.Observability[metricsConfigKey] == disabled { + if cfg.Observability.PrometheusService == disabled { return nil } @@ -349,7 +349,7 @@ func generateConfigsToExportMetrics(w io.Writer, appName string, cfg *kubeConfig // Generate the Prometheus kubernetes deployment info iff the kube deployer // should automatically start the Prometheus service. - if cfg.Observability[metricsConfigKey] != auto { + if cfg.Observability.PrometheusService != auto { return nil } @@ -591,7 +591,7 @@ func generatePrometheusServiceConfigs(w io.Writer, appName string, cfg *kubeConf // [1] https://helm.sh/ func generateConfigsToExportLogs(w io.Writer, appName string, cfg *kubeConfig) error { // The user disabled exporting the logs, don't generate anything. - if cfg.Observability[logsConfigKey] == disabled { + if cfg.Observability.LokiService == disabled { return nil } @@ -605,7 +605,7 @@ func generateConfigsToExportLogs(w io.Writer, appName string, cfg *kubeConfig) e // Generate the Loki/Promtail kubernetes deployment configs iff the kube deployer // should deploy the Loki/Promtail. - if cfg.Observability[logsConfigKey] != auto { + if cfg.Observability.LokiService != auto { return nil } @@ -704,8 +704,7 @@ func generatePromtailConfigs(w io.Writer, appName string, cfg *kubeConfig) error promName := name{appName, "promtail"}.DNSLabel() var lokiURL string - lservice := cfg.Observability[logsConfigKey] - switch { + switch lservice := cfg.Observability.LokiService; { case lservice == auto: // lokiURL should point to the Loki service generated by Kube. lokiURL = name{appName, "loki"}.DNSLabel() @@ -1058,7 +1057,7 @@ func generatePromtailAgentConfigs(w io.Writer, appName string, cfg *kubeConfig) // [1] https://helm.sh/ func generateConfigsToExportToGrafana(w io.Writer, appName string, cfg *kubeConfig) error { // The user disabled Grafana, don't generate anything. - if cfg.Observability[grafanaConfigKey] == disabled { + if cfg.Observability.GrafanaService == disabled { return nil } @@ -1069,7 +1068,7 @@ func generateConfigsToExportToGrafana(w io.Writer, appName string, cfg *kubeConf // Generate the Grafana kubernetes deployment info iff the kube deployer should // deploy the Grafana service. - if cfg.Observability[grafanaConfigKey] != auto { + if cfg.Observability.GrafanaService != auto { return nil } @@ -1099,8 +1098,8 @@ datasources: // Set up the Jaeger data source (if any). var jaegerURL string - jservice := cfg.Observability[tracesConfigKey] - switch { + + switch jservice := cfg.Observability.JaegerService; { case jservice == auto: // jaegerURL should point to the Jaeger service generated by the Kube deployer. jaegerURL = fmt.Sprintf("http://%s:%d", name{appName, jaegerAppName}.DNSLabel(), defaultJaegerUIPort) @@ -1121,8 +1120,8 @@ datasources: // Set up the Prometheus data source (if any). var prometheusURL string - pservice := cfg.Observability[metricsConfigKey] - switch { + + switch pservice := cfg.Observability.PrometheusService; { case pservice == auto: // prometheusURL should point to the Prometheus service generated by the Kube deployer. prometheusURL = fmt.Sprintf("http://%s", name{appName, "prometheus"}.DNSLabel()) @@ -1145,8 +1144,8 @@ datasources: // Set up the Loki data source (if any). var lokiURL string - lservice := cfg.Observability[logsConfigKey] - switch { + + switch lservice := cfg.Observability.LokiService; { case lservice == auto: // lokiURL should point to the Loki service generated by the Kube deployer. lokiURL = fmt.Sprintf("http://%s:%d", name{appName, "loki"}.DNSLabel(), defaultLokiPort) diff --git a/internal/proto/env.go b/internal/proto/env.go deleted file mode 100644 index 89d5aa6..0000000 --- a/internal/proto/env.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package proto - -import ( - "encoding/base64" - - "google.golang.org/protobuf/proto" -) - -// ToEnv converts msg to a string suitable for storing in the process's -// OS environment (i.e., os.Environ()). -func ToEnv(msg proto.Message) (string, error) { - data, err := proto.Marshal(msg) - if err != nil { - return "", err - } - return base64.StdEncoding.EncodeToString(data), nil -} - -// FromEnv fills msg from a string created by ToEnv. -func FromEnv(in string, msg proto.Message) error { - data, err := base64.StdEncoding.DecodeString(in) - if err != nil { - return nil - } - return proto.Unmarshal(data, msg) -}