Skip to content

Commit

Permalink
Add initial Salad cloud virtual kubelet provider (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucklypriyansh-2 authored Sep 25, 2023
1 parent 235669b commit 25c1569
Show file tree
Hide file tree
Showing 12 changed files with 848 additions and 72 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,34 @@
[![License](https://img.shields.io/github/license/SaladTechnologies/virtual-kubelet-saladcloud)](./LICENSE) [![CI Workflow](https://github.com/SaladTechnologies/virtual-kubelet-saladcloud/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/SaladTechnologies/virtual-kubelet-saladcloud/actions/workflows/ci.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/SaladTechnologies/virtual-kubelet-saladcloud)](https://goreportcard.com/report/github.com/SaladTechnologies/virtual-kubelet-saladcloud)

Salad's Virtual Kubelet (VK) provider for SaladCloud enables running Kubernetes (K8s) pods as container group deployments.

To Setup the project
1. Clone the repo command
```bash
git clone
```
2. Install the dependencies
```bash
go mod download
```
3. Build the project
```bash
go build
```
4. Run the project
```bash
go run main.go --nodename {valid_node_name} --projectName {projectName} --organizationName {organizationName} --api-key {api-key} --kubeconfig {kubeconfig}
```

## Prerequisites
You should have valid configuration required to run the project

go to portal.salad.io and create a project and get the api-key and organization name
set the kubeconfig to the valid kubeconfig file


1. Valid ApiKey and OrganizationName
2. Valid Kubeconfig
3. Valid NodeName
4. Valid ProjectName

169 changes: 139 additions & 30 deletions cmd/virtual-kubelet/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,75 +2,184 @@ package main

import (
"context"
"errors"
"fmt"
"math/rand"
"os"
"os/signal"
"path/filepath"

"github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/models"
"github.com/SaladTechnologies/virtual-kubelet-saladcloud/internal/provider"
"github.com/mitchellh/go-homedir"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/virtual-kubelet/virtual-kubelet/errdefs"
"github.com/virtual-kubelet/virtual-kubelet/log"
logruslogger "github.com/virtual-kubelet/virtual-kubelet/log/logrus"
"github.com/virtual-kubelet/virtual-kubelet/node"
"github.com/virtual-kubelet/virtual-kubelet/node/nodeutil"
v1 "k8s.io/api/core/v1"
)

var (
binaryFilename = filepath.Base(os.Args[0])
description = fmt.Sprintf("%s implements a node on a Kubernetes cluster using Workload API to run pods.", binaryFilename)
inputs = defaultInputs()
letters = []rune("0123456789abcdefghijklmnopqrstuvwxyz")
taintEffectMapping = map[string]v1.TaintEffect{
"NoSchedule": v1.TaintEffectNoSchedule,
"NoExecute": v1.TaintEffectNoExecute,
"PreferNoSchedule": v1.TaintEffectPreferNoSchedule,
}
)

func defaultInputs() models.InputVars {
home, err := homedir.Dir()
kubeConfig := os.Getenv("KUBECONFIG")
if err == nil && home != "" {
kubeConfig = filepath.Join(home, ".kube", "config")
}

return models.InputVars{
NodeName: "saladcloud-edge-provider",
KubeConfig: kubeConfig,
LogLevel: "info",
OrganizationName: "",
TaintKey: "virtual-kubelet.io/provider",
TaintEffect: "NoSchedule",
TaintValue: "saladCloud",
ProjectName: "",
ApiKey: "",
}
}

func main() {
log.Info("SaladCloud Virtual Kubelet Provider")
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

name := fmt.Sprintf("saladcloud-%s", randSeq(8))
node, err := nodeutil.NewNode(name, func(pc nodeutil.ProviderConfig) (nodeutil.Provider, node.NodeProvider, error) {
p, err := provider.NewSaladCloudProvider(context.Background())
if err != nil {
return nil, nil, err
}
if err := virtualKubeletCommand.ExecuteContext(ctx); err != nil && !errors.Is(err, context.Canceled) {
logrus.WithError(err).Error("Failed to execute virtual kubelet command")
os.Exit(1)
}
}

func initCommandFlags() {
virtualKubeletCommand.Flags().StringVar(&inputs.NodeName, "nodename", inputs.NodeName, "Kubernetes node name")
virtualKubeletCommand.Flags().StringVar(&inputs.KubeConfig, "kube-config", inputs.KubeConfig, "Kubeconfig file")
virtualKubeletCommand.Flags().StringVar(&inputs.OrganizationName, "organizationName", inputs.OrganizationName, "Organization name for Salad Client")
virtualKubeletCommand.Flags().BoolVar(&inputs.DisableTaint, "Disable taint flag", inputs.DisableTaint, "Disable the tainted effect")
virtualKubeletCommand.Flags().StringVar(&inputs.ApiKey, "api-key", inputs.ApiKey, "API key for the Salad Client")
virtualKubeletCommand.Flags().StringVar(&inputs.ProjectName, "projectName", inputs.ProjectName, "Project name for Salad Client")

markFlagRequired("organizationName")
markFlagRequired("projectName")
}

func markFlagRequired(flagName string) {
if err := virtualKubeletCommand.MarkFlagRequired(flagName); err != nil {
logrus.WithError(err).Errorf("Failed to mark %s as required", flagName)
os.Exit(1)
}
}

func runNode(ctx context.Context) error {
logrus.Infof("Running node with name prefix: %s", inputs.NodeName)
inputs.NodeName = fmt.Sprintf("%s-%s", inputs.NodeName, randSeq(3))

return p, nil, nil
}, withClient)
node, err := nodeutil.NewNode(inputs.NodeName, newSaladCloudProvider, withClient, withTaint)
if err != nil {
log.Fatal(err)
logrus.WithError(err).Error("Failed to create new node")
return err
}

go func() {
if err := node.Run(context.Background()); err != nil {
log.Fatal(err)
if err := node.Run(ctx); err != nil {
logrus.WithError(err).Error("Node runtime error")
}
}()

err = node.WaitReady(context.Background(), 0)
if err != nil {
log.Fatal(err)
if err = node.WaitReady(ctx, 0); err != nil {
logrus.WithError(err).Error("Node readiness error")
return err
}

<-node.Done()
err = node.Err()
if err = node.Err(); err != nil {
logrus.WithError(err).Error("Node finished with error")
return err
}
return nil
}

func newSaladCloudProvider(pc nodeutil.ProviderConfig) (nodeutil.Provider, node.NodeProvider, error) {
p, err := provider.NewSaladCloudProvider(context.Background(), inputs)
if err != nil {
log.Fatal(err)
logrus.WithError(err).Error("Failed to create SaladCloud provider")
return nil, nil, err
}
p.ConfigureNode(context.Background(), pc.Node)
return p, nil, nil
}

func withClient(cfg *nodeutil.NodeConfig) error {
var conf string
home, _ := homedir.Dir()
if home != "" {
conf = filepath.Join(home, ".kube", "config")
} else {
conf = "config"
func withTaint(cfg *nodeutil.NodeConfig) error {
if inputs.DisableTaint {
return nil
}

client, err := nodeutil.ClientsetFromEnv(conf)
if err != nil {
taintEffect, validEffect := taintEffectMapping[inputs.TaintEffect]
if !validEffect {
err := errdefs.InvalidInputf("Taint effect %q is not supported", inputs.TaintEffect)
logrus.WithError(err).Error("Invalid taint effect provided")
return err
}

return nodeutil.WithClient(client)(cfg)
cfg.NodeSpec.Spec.Taints = append(cfg.NodeSpec.Spec.Taints, v1.Taint{
Key: inputs.TaintKey,
Value: inputs.TaintValue,
Effect: taintEffect,
})

return nil
}

var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyz")
func withClient(cfg *nodeutil.NodeConfig) error {
client, err := nodeutil.ClientsetFromEnv(inputs.KubeConfig)
if err != nil {
logrus.WithError(err).Error("Failed to retrieve clientset from environment")
return err
}
cfg.Client = client
return nil
}

// https://stackoverflow.com/a/22892986/2709066
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

var virtualKubeletCommand = &cobra.Command{
Use: binaryFilename,
Short: description,
Long: description,
Run: func(cmd *cobra.Command, args []string) {
logger := logrus.StandardLogger()
if logLevel, err := logrus.ParseLevel(inputs.LogLevel); err == nil {
logger.SetLevel(logLevel)
} else {
logrus.WithError(err).Error("Failed to parse log level, defaulting to INFO")
}

ctx := log.WithLogger(cmd.Context(), logruslogger.FromLogrus(logrus.NewEntry(logger)))
if err := runNode(ctx); err != nil {
logrus.WithError(err).Fatal("Node failed to run")
}
},
}

func init() {
initCommandFlags()
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
module github.com/SaladTechnologies/virtual-kubelet-saladcloud

go 1.21.0
go 1.20

require (
github.com/lucklypriyansh-2/salad-client v0.0.0-20230902193233-7c5a02b4c6d7
github.com/prometheus/client_model v0.4.0
github.com/sirupsen/logrus v1.9.3
github.com/virtual-kubelet/virtual-kubelet v1.10.0
Expand Down Expand Up @@ -55,7 +56,7 @@ require (
github.com/prometheus/client_golang v1.16.0 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.9 // indirect
Expand Down
Loading

0 comments on commit 25c1569

Please sign in to comment.