Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revision -1 Salad cloud virtual kubelete provider #7

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