Skip to content

Commit

Permalink
Add yaml config file with configurable k8s knobs (#87)
Browse files Browse the repository at this point in the history
Right now if the user wants to run `weaver kube deploy` it has to
specify various kubernetes knobs in weaver.toml. However, more knobs we
would like the user to control, harder is to make them work using
weaver.toml. We've been trying to expose some knobs (e.g., resource
requirements, probes) in toml. To do that, we had to write wrapper
datastructures that mimic the corresponding kubernetes types. This is
not only ugly, but it's hard to capture all the knobs and makes the code
unnecessarily complicated.

With this PR, the user should be able to write a YAML config where it
can specify deployment information for kubernetes using native go k8s
types. We let the user configure the k8s knobs that are the most
commonly used:
1) resource requirements (cpu/mem requests/limits)
2) scaling spec (min/max replicas, target utilization threshold,
   resource to monitor, etc for the horizontal pod autoscaler)
3) probes (liveness, readiness, startup)
4) volumes (any kind of volume like pvc, config maps, secrets, etc.)

For each of these knobs we let the user specify native go k8s types. The
user can copy paste these values directly from their existing yaml
files, or can type it by themselves.

The kube deployer takes a single YAML config that contains a reference
to the TOML app config. The TOML app config can contain only the name of
the binary.

The YAML config also allows the user to specify groups of collocated
components and to fine tune various configs for each group (e.g.,
resource requirements, scaling spec, volumes).

The simplest YAML config to run `weaver kube` deployer looks like:

``` kube.yaml
appConfig: weaver.toml
repo: docker.io/your_docker_hub_username

listeners:
  - name: foo
    public: true
```

where the weaver.toml looks like

``` weaver.toml
[serviceweaver]
binary = "./collatz"
```

To add a set of colocated components you can specify a group as follows:

``` kube.yaml
appConfig: weaver.toml
repo: docker.io/your_docker_hub_username

listeners:
  - name: foo
    public: true

groups:
  - name: mygroup
    components:
      - component1
      - component2
```

To specify resource requirements that apply to all the pods in the app,
you can add a k8s resource spec as follows:

``` kube.yaml
appConfig: weaver.toml
repo: docker.io/your_docker_hub_username

listeners:
  - name: foo
    public: true

resourceSpec:
   requests:
     memory: "64Mi"
     cpu: "250m"
   limits:
     memory: "128Mi"
     cpu: "500m"

groups:
  - name: mygroup
    components:
      - component1
      - component2
```

If you want to have different resource requirements for the components
in `mygroup` you can set the resourceSpec for `mygroup` as follows:

``` kube.yaml
appConfig: weaver.toml
repo: docker.io/your_docker_hub_username

listeners:
  - name: foo
    public: true

resourceSpec:
   requests:
     memory: "64Mi"
     cpu: "250m"
   limits:
     memory: "128Mi"
     cpu: "500m"

groups:
  - name: mygroup
    components:
      - component1
      - component2

    resourceSpec:
      requests:
        memory: "72Mi"
        cpu: "290m"
      limits:
        memory: "189Mi"
        cpu: "700m"
```

We can do the same thing with scaling spec. E.g., scaling spec:

``` kube.yaml
appConfig: weaver.toml
repo: docker.io/your_docker_hub_username

listeners:
  - name: foo
    public: true

scalingSpec:
  minReplicas: 1
  maxReplicas: 3
  metrics:
   - type: Resource
     resource:
       name: cpu
       target:
         type: Utilization
         averageUtilization: 80
```

To add volumes (per app or per group or both), you have to specify the
name of the volume, the volume source and the volume mount (both volume
source and volume mount are go k8s types). E.g., to add a secret
`my-secret` to all the pods, you can do:

``` kube.yaml
appConfig: weaver.toml
repo: docker.io/your_docker_hub_username

listeners:
  - name: foo
    public: true

volumes:
  - name: robert-secret
    volumeSource:
      secret:
        secretName: my-secret
    volumeMount:
      mountPath: /etc/secret
```

Fixed Michaels' comments
  • Loading branch information
rgrandl authored Nov 6, 2023
1 parent d79c670 commit e6d3dc9
Show file tree
Hide file tree
Showing 11 changed files with 386 additions and 440 deletions.
23 changes: 17 additions & 6 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
name: Integration Test

env:
CONFIG_FILE: deploy.toml
APP_CONFIG_FILE: app.toml
KUBE_CONFIG_FILE: deploy.yaml
REPO_URL: 192.168.50.2:5000
WAIT_TIMEOUT: "5m"

Expand Down Expand Up @@ -67,17 +68,27 @@ jobs:
CONFIG=$(cat << EOF
[serviceweaver]
binary = "./examples/echo/echo"
EOF
)
echo "$CONFIG" > ${{ env.APP_CONFIG_FILE }}
[kube]
repo = "${{ env.REPO_URL }}"
listeners.echo = {public = true}
- name: Generate kubernetes config file
run: |
CONFIG=$(cat << EOF
appConfig: "${{ env.APP_CONFIG_FILE }}"
repo: "${{ env.REPO_URL }}"
listeners:
- name: echo
public: true
EOF
)
echo "$CONFIG" > ${{ env.CONFIG_FILE }}
echo "$CONFIG" > ${{ env.KUBE_CONFIG_FILE }}
- name: Build the docker image and push
run: |
WEAVER_YAML=$(./cmd/weaver-kube/weaver-kube deploy ${{ env.CONFIG_FILE }})
WEAVER_YAML=$(./cmd/weaver-kube/weaver-kube deploy ${{ env.KUBE_CONFIG_FILE }})
echo "Generated YAML file:" $WEAVER_YAML
echo "WEAVER_YAML=$WEAVER_YAML" >> $GITHUB_ENV
Expand Down
2 changes: 1 addition & 1 deletion examples/echo/echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type echoer struct {
weaver.Implements[Echoer]
}

func (e echoer) Echo(ctx context.Context, s string) (string, error) {
func (e echoer) Echo(_ context.Context, s string) (string, error) {
stringLength.Put(float64(len(s))) // Update the stringLength metric.
return s, nil
}
2 changes: 1 addition & 1 deletion examples/echo/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type server struct {
lis weaver.Listener `weaver:"echo"`
}

func serve(ctx context.Context, s *server) error {
func serve(_ context.Context, s *server) error {
// Setup the HTTP handler.
var mux http.ServeMux
mux.Handle("/", weaver.InstrumentHandlerFunc("echo",
Expand Down
18 changes: 4 additions & 14 deletions internal/impl/babysitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,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)
Expand Down Expand Up @@ -279,16 +282,3 @@ func (b *babysitter) readMetrics() []*metrics.MetricSnapshot {
}
return append(ms, m...)
}

// 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
}
140 changes: 65 additions & 75 deletions internal/impl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -51,35 +58,56 @@ 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
// config, default options will be used.
Listeners []listenerSpec

// 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.
ResourceSpec *corev1.ResourceRequirements

// Resources needed to run the pods. Note that the resources should satisfy
// Specs on how to scale the pods. Note that the scaling specs should satisfy
// the format specified in [1].
//
// [1] https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#example-MustParse.
Resources resourceRequirements
// [1] https://pkg.go.dev/k8s.io/kubernetes/pkg/apis/autoscaling#HorizontalPodAutoscalerSpec.
ScalingSpec *autoscalingv2.HorizontalPodAutoscalerSpec

// Options for probes to check the readiness/liveness of the pods.
LivenessProbeOpts *probeOptions `toml:"liveness_probe"`
ReadinessProbeOpts *probeOptions `toml:"readiness_probe"`
// Volumes that should be provided to all the running components.
StorageSpec volumeSpecs

// 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
}

// 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
Expand All @@ -92,67 +120,29 @@ 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
StorageSpec volumeSpecs // list of volumes and volume mounts
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.
}

// 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.
// volumeSpecs encapsulates volumes and volume mounts specs as defined by the
// user in the kubernetes config.
type volumeSpecs struct {
Volumes []corev1.Volume
VolumeMounts []corev1.VolumeMount
}
Loading

0 comments on commit e6d3dc9

Please sign in to comment.