From 0b43df5ee45ac60339efd9017e8460df70719169 Mon Sep 17 00:00:00 2001 From: Vaastav Anand Date: Fri, 26 Jan 2024 17:14:47 +0100 Subject: [PATCH 1/7] Add basic framework for Kubernetes --- plugins/kubernetes/kubepod/deploy.go | 24 ++++++++++++++ plugins/kubernetes/kubepod/ir.go | 42 ++++++++++++++++++++++++ plugins/kubernetes/kubeservice/wiring.go | 1 + 3 files changed, 67 insertions(+) create mode 100644 plugins/kubernetes/kubepod/deploy.go create mode 100644 plugins/kubernetes/kubepod/ir.go create mode 100644 plugins/kubernetes/kubeservice/wiring.go diff --git a/plugins/kubernetes/kubepod/deploy.go b/plugins/kubernetes/kubepod/deploy.go new file mode 100644 index 00000000..202dbeae --- /dev/null +++ b/plugins/kubernetes/kubepod/deploy.go @@ -0,0 +1,24 @@ +package kubepod + +import ( + "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" + "github.com/blueprint-uservices/blueprint/plugins/docker" +) + +// A Kubernetes pod deployer. It generates the pod config files on the local filesystem. +type kubePod interface { + ir.ArtifactGenerator +} + +// A workspace used when deploying a set of containers as a Kubernetes Pod +// +// Implements docker.ContainerWorkspace defined in docker/ir.go +// +// This workspace generates Pod files at the root of the output directory. +type kubePodWorkspace struct { + ir.VisitTrackerImpl + + info docker.ContainerWorkspaceInfo + + ImageDirs map[string]string +} diff --git a/plugins/kubernetes/kubepod/ir.go b/plugins/kubernetes/kubepod/ir.go new file mode 100644 index 00000000..6d29d79b --- /dev/null +++ b/plugins/kubernetes/kubepod/ir.go @@ -0,0 +1,42 @@ +package kubepod + +import ( + "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" + "github.com/blueprint-uservices/blueprint/plugins/docker" +) + +// An IRNode representing a Kubernetes pod, which is simply a collection of container instances. +type Pod struct { + kubePod + PodName string + Nodes []ir.IRNode + Edges []ir.IRNode +} + +// Implements IRNode +func (node *Pod) Name() string { + return node.PodName +} + +// Implements IRNode +func (node *Pod) String() string { + return ir.PrettyPrintNamespace(node.PodName, "KubernetesPod", node.Edges, node.Nodes) +} + +// Implements [wiring.NamespaceHandler] +func (pod *Pod) Accepts(nodeType any) bool { + _, isDockerContainerNode := nodeType.(docker.Container) + return isDockerContainerNode +} + +// Implements [wiring.NamespaceHandler] +func (pod *Pod) AddEdge(name string, edge ir.IRNode) error { + pod.Edges = append(pod.Edges, edge) + return nil +} + +// Implements [wiring.NamespaceHandler] +func (pod *Pod) AddNode(name string, node ir.IRNode) error { + pod.Nodes = append(pod.Nodes, node) + return nil +} diff --git a/plugins/kubernetes/kubeservice/wiring.go b/plugins/kubernetes/kubeservice/wiring.go new file mode 100644 index 00000000..00180b88 --- /dev/null +++ b/plugins/kubernetes/kubeservice/wiring.go @@ -0,0 +1 @@ +package kubeservice From 060a6be47094a4cec60d3926c02e78a1a1ef8604 Mon Sep 17 00:00:00 2001 From: Vaastav Anand Date: Mon, 29 Jan 2024 18:26:24 +0100 Subject: [PATCH 2/7] Add kube pod deployment template --- plugins/kubernetes/kubepod/deploy.go | 105 ++++++++++- .../kubepod/deploygen/deployfile.go | 177 ++++++++++++++++++ plugins/kubernetes/kubepod/ir.go | 14 +- plugins/kubernetes/kubetemplate/template.go | 62 ++++++ 4 files changed, 349 insertions(+), 9 deletions(-) create mode 100644 plugins/kubernetes/kubepod/deploygen/deployfile.go create mode 100644 plugins/kubernetes/kubetemplate/template.go diff --git a/plugins/kubernetes/kubepod/deploy.go b/plugins/kubernetes/kubepod/deploy.go index 202dbeae..0541e660 100644 --- a/plugins/kubernetes/kubepod/deploy.go +++ b/plugins/kubernetes/kubepod/deploy.go @@ -1,12 +1,21 @@ package kubepod import ( + "fmt" + "path/filepath" + + "github.com/blueprint-uservices/blueprint/blueprint/pkg/blueprint" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/blueprint/ioutil" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/coreplugins/address" "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" "github.com/blueprint-uservices/blueprint/plugins/docker" + "golang.org/x/exp/slog" + + "github.com/blueprint-uservices/blueprint/plugins/kubernetes/kubepod/deploygen" ) // A Kubernetes pod deployer. It generates the pod config files on the local filesystem. -type kubePod interface { +type kubePodDeployment interface { ir.ArtifactGenerator } @@ -15,10 +24,102 @@ type kubePod interface { // Implements docker.ContainerWorkspace defined in docker/ir.go // // This workspace generates Pod files at the root of the output directory. -type kubePodWorkspace struct { +type kubeDeploymentWorkspace struct { ir.VisitTrackerImpl info docker.ContainerWorkspaceInfo ImageDirs map[string]string + + F *deploygen.KubeDeploymentFile +} + +// Implements ir.ArtifactGenerator +func (node *PodDeployment) GenerateArtifacts(dir string) error { + slog.Info(fmt.Sprintf("Generating container instances for Kubernetes Pod %s in %s", node.Name(), dir)) + workspace := NewKubePodWorkspace(node.Name(), dir) + return node.generateArtifacts(workspace) +} + +func (node *PodDeployment) generateArtifacts(workspace *kubeDeploymentWorkspace) error { + // Add all locally-built container images + for _, n := range ir.Filter[docker.ProvidesContainerImage](node.Nodes) { + if err := n.AddContainerArtifacts(workspace); err != nil { + return err + } + } + + // Add all pre-built container instances + for _, n := range ir.Filter[docker.ProvidesContainerInstance](node.Nodes) { + if err := n.AddContainerInstance(workspace); err != nil { + return err + } + } + + // Build the Kubernetes pod config files + if err := workspace.Finish(); err != nil { + return err + } + + // Reset any port assignments for externally-visible servers + address.ResetPorts(node.Edges) + return nil +} + +func NewKubePodWorkspace(name string, dir string) *kubeDeploymentWorkspace { + return &kubeDeploymentWorkspace{ + info: docker.ContainerWorkspaceInfo{ + Path: filepath.Clean(dir), + Target: "kubedeployment", + }, + ImageDirs: make(map[string]string), + F: deploygen.NewKubeDeploymentFile(name, dir, name+"-deployment.yaml"), + } +} + +// Implements docker.ContainerWorkspace +func (p *kubeDeploymentWorkspace) Info() docker.ContainerWorkspaceInfo { + return p.info +} + +// Implements docker.ContainerWorkspace +func (p *kubeDeploymentWorkspace) CreateImageDir(imageName string) (string, error) { + // Only alphanumeric and underscores are allowed in a proc name + imageName = ir.CleanName(imageName) + imageDir, err := ioutil.CreateNodeDir(p.info.Path, imageName) + p.ImageDirs[imageName] = imageDir + return imageDir, err } + +// Implements docker.ContainerWorkspace +func (p *kubeDeploymentWorkspace) DeclarePrebuiltInstance(instanceName string, image string, args ...ir.IRNode) error { + if err := address.CheckPorts(args); err != nil { + return blueprint.Errorf("unable to add docker instance %v due to %v", instanceName, err.Error()) + } + + return p.F.AddImageInstance(instanceName, image, args...) +} + +// Implements docker.ContainerWorkspace +func (p *kubeDeploymentWorkspace) DeclareLocalImage(instanceName string, imageDir string, args ...ir.IRNode) error { + // Docker containers should assign all internal server ports (typically using address.AssignPorts) before adding an instance + if err := address.CheckPorts(args); err != nil { + return blueprint.Errorf("unable to add docker instance %v due to %v", instanceName, err.Error()) + } + // For now set image to instanceName + image := instanceName + return p.F.AddImageInstance(instanceName, image, args...) +} + +// Implements docker.ContainerWorkspace +func (p *kubeDeploymentWorkspace) SetEnvironmentVariable(instanceName string, key string, val string) error { + return p.F.AddEnvVar(instanceName, key, val) +} + +// Generates the pod config file +func (p *kubeDeploymentWorkspace) Finish() error { + return p.F.Generate() +} + +func (p *kubeDeploymentWorkspace) ImplementsBuildContext() {} +func (p *kubeDeploymentWorkspace) ImplementsContainerWorkspace() {} diff --git a/plugins/kubernetes/kubepod/deploygen/deployfile.go b/plugins/kubernetes/kubepod/deploygen/deployfile.go new file mode 100644 index 00000000..f6a13cbc --- /dev/null +++ b/plugins/kubernetes/kubepod/deploygen/deployfile.go @@ -0,0 +1,177 @@ +package deploygen + +import ( + "fmt" + "path/filepath" + + "github.com/blueprint-uservices/blueprint/blueprint/pkg/blueprint" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/coreplugins/address" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" + "github.com/blueprint-uservices/blueprint/plugins/kubernetes/kubetemplate" + "github.com/blueprint-uservices/blueprint/plugins/linux" + "golang.org/x/exp/slog" +) + +type KubeDeploymentFile struct { + WorkspaceName string + WorkspaceDir string + FileName string + FilePath string + Instances map[string]instance + localServers map[string]*address.BindConfig + localDials map[string]*address.DialConfig +} + +type instance struct { + InstanceName string + Image string + Ports map[string]uint16 + Config map[string]string + Passthrough map[string]struct{} +} + +func NewKubeDeploymentFile(workspaceName string, workspaceDir string, filename string) *KubeDeploymentFile { + return &KubeDeploymentFile{ + WorkspaceName: workspaceName, + WorkspaceDir: workspaceDir, + FileName: filename, + FilePath: filepath.Join(workspaceDir, filename), + Instances: make(map[string]instance), + } +} + +func (k *KubeDeploymentFile) Generate() error { + k.ResolveLocalDials() + slog.Info(fmt.Sprintf("Generating %v/%v", k.WorkspaceName, k.FileName)) + return kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesTemplate, k, k.FilePath) +} + +func (k *KubeDeploymentFile) AddImageInstance(instanceName string, image string, args ...ir.IRNode) error { + return k.addInstance(instanceName, image, args...) +} + +func (k *KubeDeploymentFile) AddEnvVar(instanceName string, key string, val string) error { + instanceName = ir.CleanName(instanceName) + if i, exists := k.Instances[instanceName]; !exists { + return blueprint.Errorf("container instance with name %v not found", instanceName) + } else { + i.Config[key] = val + k.Instances[instanceName] = i + } + return nil +} + +func (k *KubeDeploymentFile) addInstance(instanceName string, image string, args ...ir.IRNode) error { + instanceName = ir.CleanName(instanceName) + if _, exists := k.Instances[instanceName]; exists { + return blueprint.Errorf("re-declaration of container instance %v of image %v", instanceName, image) + } + instance := instance{ + InstanceName: instanceName, + Image: image, + Ports: make(map[string]uint16), + Config: make(map[string]string), + } + for _, node := range args { + varname := linux.EnvVar(node.Name()) + + if bind, isBindConf := node.(*address.BindConfig); isBindConf { + if bind.Port == 0 { + return blueprint.Errorf("cannot add container instance %v due to unbound server port %v", instanceName, bind.Name()) + } + instance.Ports[requiredEnvVar(node)] = bind.Port + } + + if conf, isConfig := node.(ir.IRConfig); isConfig { + if conf.HasValue() { + instance.Config[varname] = conf.Value() + continue + } else if conf.Optional() { + instance.Passthrough[varname] = struct{}{} + continue + } + } + instance.Config[varname] = requiredEnvVar(node) + } + + k.Instances[instanceName] = instance + + k.checkForAddrs(args) + + return nil +} + +func (d *KubeDeploymentFile) checkForAddrs(nodes []ir.IRNode) { + for _, node := range nodes { + switch c := node.(type) { + case *address.BindConfig: + d.localServers[c.AddressName] = c + case *address.DialConfig: + d.localDials[c.AddressName] = c + } + } +} + +func (d *KubeDeploymentFile) ResolveLocalDials() error { + for name, bind := range d.localServers { + dial, hasLocalDial := d.localDials[name] + if !hasLocalDial { + continue + } + + // Update the configured value for any instance that uses this dial addr + // to point it directly towards the local server + dialVarname := linux.EnvVar(dial.Name()) + for _, instance := range d.Instances { + if _, hasConfig := instance.Config[dialVarname]; hasConfig { + instance.Config[dialVarname] = bind.Value() + } + } + } + return nil +} + +func requiredEnvVar(node ir.IRNode) string { + return fmt.Sprintf("${%v?%v must be set by the calling environment}", linux.EnvVar(node.Name()), node.Name()) +} + +var kubernetesTemplate = ` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{.Name}} + labels: + blueprint.service: {{.Name}} +spec: + replicas: {{.NumReplicas}} + selector: + matchLabels: + blueprint.service: {{.Name}} + template: + metadata: + name: {{.Name}} + labels: + blueprint.service: {{.Name}} + spec: + containers: + {{range $_, $decl := .Instances}} + - name: {{.InstanceName}} + image: {{.Image}} + {{- if .Config}} + env: + {{- range $name, $value := .Config}} + - name: {{$name}} + value: "{{$value}}" + {{- end}} + {{- end}} + {{- if .Ports}} + ports: + {{- range $external, $internal := .Ports}} + - containerPort: {{$internal}} + hostIP: 0.0.0.0 + hostPort: {{$external}} + {{- end}} + {{-end}} + restartPolicy: Always + hostname: {{.InstanceName}} +` diff --git a/plugins/kubernetes/kubepod/ir.go b/plugins/kubernetes/kubepod/ir.go index 6d29d79b..a8934b27 100644 --- a/plugins/kubernetes/kubepod/ir.go +++ b/plugins/kubernetes/kubepod/ir.go @@ -6,37 +6,37 @@ import ( ) // An IRNode representing a Kubernetes pod, which is simply a collection of container instances. -type Pod struct { - kubePod +type PodDeployment struct { + kubePodDeployment PodName string Nodes []ir.IRNode Edges []ir.IRNode } // Implements IRNode -func (node *Pod) Name() string { +func (node *PodDeployment) Name() string { return node.PodName } // Implements IRNode -func (node *Pod) String() string { +func (node *PodDeployment) String() string { return ir.PrettyPrintNamespace(node.PodName, "KubernetesPod", node.Edges, node.Nodes) } // Implements [wiring.NamespaceHandler] -func (pod *Pod) Accepts(nodeType any) bool { +func (pod *PodDeployment) Accepts(nodeType any) bool { _, isDockerContainerNode := nodeType.(docker.Container) return isDockerContainerNode } // Implements [wiring.NamespaceHandler] -func (pod *Pod) AddEdge(name string, edge ir.IRNode) error { +func (pod *PodDeployment) AddEdge(name string, edge ir.IRNode) error { pod.Edges = append(pod.Edges, edge) return nil } // Implements [wiring.NamespaceHandler] -func (pod *Pod) AddNode(name string, node ir.IRNode) error { +func (pod *PodDeployment) AddNode(name string, node ir.IRNode) error { pod.Nodes = append(pod.Nodes, node) return nil } diff --git a/plugins/kubernetes/kubetemplate/template.go b/plugins/kubernetes/kubetemplate/template.go new file mode 100644 index 00000000..79c68138 --- /dev/null +++ b/plugins/kubernetes/kubetemplate/template.go @@ -0,0 +1,62 @@ +package kubetemplate + +import ( + "bytes" + "html/template" + "os" + "strings" + + "github.com/blueprint-uservices/blueprint/plugins/linux" +) + +func ExecuteTemplate(name string, body string, args any) (string, error) { + return newTemplateExecutor(args).exec(name, body, args) +} + +func ExecuteTemplateToFile(name string, body string, args any, filename string) error { + f, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, 0755) + if err != nil { + return err + } + defer f.Close() + + code, err := ExecuteTemplate(name, body, args) + if err != nil { + return err + } + _, err = f.WriteString(code) + return err +} + +type templateExecutor struct { + Funcs template.FuncMap +} + +func newTemplateExecutor(args any) *templateExecutor { + e := &templateExecutor{ + Funcs: template.FuncMap{}, + } + + e.Funcs["EnvVarName"] = e.EnvVarName + e.Funcs["Title"] = e.TitleCase + + return e +} + +func (e *templateExecutor) exec(name string, body string, args any) (string, error) { + t, err := template.New(name).Funcs(e.Funcs).Parse(body) + if err != nil { + return "", err + } + buf := &bytes.Buffer{} + err = t.Execute(buf, args) + return buf.String(), err +} + +func (e *templateExecutor) EnvVarName(name string) (string, error) { + return linux.EnvVar(name), nil +} + +func (e *templateExecutor) TitleCase(arg string) (string, error) { + return strings.Title(arg), nil +} From 0a33a1b51b164f8d86de400564a32864a5d28829 Mon Sep 17 00:00:00 2001 From: Vaastav Anand Date: Mon, 29 Jan 2024 18:46:26 +0100 Subject: [PATCH 3/7] Add generation of service file --- plugins/kubernetes/kubepod/deploy.go | 2 +- .../kubepod/deploygen/deployfile.go | 50 +++++++++++++------ plugins/kubernetes/kubeservice/wiring.go | 1 - 3 files changed, 37 insertions(+), 16 deletions(-) delete mode 100644 plugins/kubernetes/kubeservice/wiring.go diff --git a/plugins/kubernetes/kubepod/deploy.go b/plugins/kubernetes/kubepod/deploy.go index 0541e660..8a67924b 100644 --- a/plugins/kubernetes/kubepod/deploy.go +++ b/plugins/kubernetes/kubepod/deploy.go @@ -73,7 +73,7 @@ func NewKubePodWorkspace(name string, dir string) *kubeDeploymentWorkspace { Target: "kubedeployment", }, ImageDirs: make(map[string]string), - F: deploygen.NewKubeDeploymentFile(name, dir, name+"-deployment.yaml"), + F: deploygen.NewKubeDeploymentFile(name, dir, name+"-deployment.yaml", name+"-service.yaml"), } } diff --git a/plugins/kubernetes/kubepod/deploygen/deployfile.go b/plugins/kubernetes/kubepod/deploygen/deployfile.go index f6a13cbc..e4ec1a3b 100644 --- a/plugins/kubernetes/kubepod/deploygen/deployfile.go +++ b/plugins/kubernetes/kubepod/deploygen/deployfile.go @@ -13,13 +13,14 @@ import ( ) type KubeDeploymentFile struct { - WorkspaceName string - WorkspaceDir string - FileName string - FilePath string - Instances map[string]instance - localServers map[string]*address.BindConfig - localDials map[string]*address.DialConfig + WorkspaceName string + WorkspaceDir string + FileName string + ServiceFilename string + FilePath string + Instances map[string]instance + localServers map[string]*address.BindConfig + localDials map[string]*address.DialConfig } type instance struct { @@ -30,20 +31,26 @@ type instance struct { Passthrough map[string]struct{} } -func NewKubeDeploymentFile(workspaceName string, workspaceDir string, filename string) *KubeDeploymentFile { +func NewKubeDeploymentFile(workspaceName string, workspaceDir string, filename string, serviceFilename string) *KubeDeploymentFile { return &KubeDeploymentFile{ - WorkspaceName: workspaceName, - WorkspaceDir: workspaceDir, - FileName: filename, - FilePath: filepath.Join(workspaceDir, filename), - Instances: make(map[string]instance), + WorkspaceName: workspaceName, + WorkspaceDir: workspaceDir, + FileName: filename, + ServiceFilename: serviceFilename, + FilePath: filepath.Join(workspaceDir, filename), + Instances: make(map[string]instance), } } func (k *KubeDeploymentFile) Generate() error { k.ResolveLocalDials() slog.Info(fmt.Sprintf("Generating %v/%v", k.WorkspaceName, k.FileName)) - return kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesTemplate, k, k.FilePath) + err := kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesTemplate, k, k.FilePath) + if err != nil { + return err + } + serviceFilePath := filepath.Join(k.WorkspaceDir, k.ServiceFilename) + return kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesServiceTemplate, k, serviceFilePath) } func (k *KubeDeploymentFile) AddImageInstance(instanceName string, image string, args ...ir.IRNode) error { @@ -175,3 +182,18 @@ spec: restartPolicy: Always hostname: {{.InstanceName}} ` + +var kubernetesServiceTemplate = ` +apiVersion: v1 +kind: Service +metadata: + name: {{.Name}}-service +spec: + selector: + blueprint.service: {{.Name}} + ports: + {{- range $external, $internal := .Ports}} + - port: {{$internal}} + targetPort: {{$external}} + {{- end}} +` diff --git a/plugins/kubernetes/kubeservice/wiring.go b/plugins/kubernetes/kubeservice/wiring.go deleted file mode 100644 index 00180b88..00000000 --- a/plugins/kubernetes/kubeservice/wiring.go +++ /dev/null @@ -1 +0,0 @@ -package kubeservice From 35cb19e5d3de0b3cb11399e47d781a3da7475fb5 Mon Sep 17 00:00:00 2001 From: Vaastav Anand Date: Tue, 30 Jan 2024 11:25:07 +0100 Subject: [PATCH 4/7] Add wiring funcs for Kubernetes --- plugins/kubernetes/ir.go | 20 ++++++ plugins/kubernetes/kubepod/wiring.go | 63 ++++++++++++++++++ plugins/kubernetes/wiring.go | 95 ++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 plugins/kubernetes/ir.go create mode 100644 plugins/kubernetes/kubepod/wiring.go create mode 100644 plugins/kubernetes/wiring.go diff --git a/plugins/kubernetes/ir.go b/plugins/kubernetes/ir.go new file mode 100644 index 00000000..a53aa3aa --- /dev/null +++ b/plugins/kubernetes/ir.go @@ -0,0 +1,20 @@ +package kubernetes + +import "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" + +// An IRNode representing a Kubernetes applicaiton deployment which is a collection of Kubernetes Pod + Service Deployment instances. +type Application struct { + AppName string + Nodes []ir.IRNode + Edges []ir.IRNode +} + +// Implements IRNode +func (n *Application) Name() string { + return n.AppName +} + +// Implements IRNode +func (n *Application) String() string { + return ir.PrettyPrintNamespace(n.AppName, "KubeApp", n.Edges, n.Nodes) +} diff --git a/plugins/kubernetes/kubepod/wiring.go b/plugins/kubernetes/kubepod/wiring.go new file mode 100644 index 00000000..94e86aee --- /dev/null +++ b/plugins/kubernetes/kubepod/wiring.go @@ -0,0 +1,63 @@ +// Package kubepod is a plugin for instantiating multiple container instances in a single Kubernetes pod deployment. +// +// # Wiring Spec Usage +// +// To use the kubepod plugin in your wiring spec, you can declare a Kubernetes Pod Deployment, giving it a name and specifying which container instances to include +// +// kubepod.NewKubePod(spec, "my_pod", "my_container_1", "my_container_2") +// +// You can add containers to existing pods: +// +// kubepod.AddContainerToPod(spec, "my_pod", "my_container_3") +// +// To deploy an application-level service in a Kubernetes Pod, make sure you first deploy the service to a process (with the [goproc] plugin) and to a container image (with the [linuxcontainer] plugin) +// +// # Artifacts Generated +// +// During compilation, the plugin generates a `podName-deployment.yaml` file that instantiates the pod as a Kubernetes deployment and a `podName-service.yaml` file that converts the deployed pod into a Kubernetes service. +// +// # Running Artifacts +// +// You need to have a working kubernetes cluster and `kubectl` installed. +// To deploy the pods to the cluster, use the following commands: +// +// kubectl apply -f podName-deployment.yaml +// kubectl apply -f podName-service.yaml +// +// [linuxcontainer]: https://github.com/Blueprint-uServices/blueprint/tree/main/plugins/linuxcontainer +// [goproc]: https://github.com/Blueprint-uServices/blueprint/tree/main/plugins/goproc +package kubepod + +import ( + "github.com/blueprint-uservices/blueprint/blueprint/pkg/coreplugins/namespaceutil" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/wiring" +) + +// [AddContainerToPod] can be used by wiring specs to add more containers to a pod +func AddContainerToPod(spec wiring.WiringSpec, podName string, containerName string) { + namespaceutil.AddNodeTo[PodDeployment](spec, podName, containerName) +} + +// [NewKubePod] can be used by wiring specs to create a Kubernetes Pod that instantiates a single Kubernetes Pod consisting of multiple containers. +// +// Further containers can be added to the Pod by calling [AddContainerToPod]. +// +// During compilation, generates the deployment.yaml and service.yaml files for the pod. +// +// Returns podName +func NewKubePod(spec wiring.WiringSpec, podName string, containers ...string) string { + + // If any children were provided in this call, add them to the pod via a property + for _, containerName := range containers { + AddContainerToPod(spec, podName, containerName) + } + + spec.Define(podName, &PodDeployment{}, func(ns wiring.Namespace) (ir.IRNode, error) { + pod := &PodDeployment{PodName: podName} + _, err := namespaceutil.InstantiateNamespace(ns, pod) + return pod, err + }) + + return podName +} diff --git a/plugins/kubernetes/wiring.go b/plugins/kubernetes/wiring.go new file mode 100644 index 00000000..f7ff5b82 --- /dev/null +++ b/plugins/kubernetes/wiring.go @@ -0,0 +1,95 @@ +// Package kubernetes is a plugin for instantiating multiple container instances in a Kubernetes cluster. +// +// # Wiring Spec Usage +// +// To use the kubernetes plugin in your wiring spec, you can declare a Kuberenetes application, giving it a name and specifying which containers to include. Each container will be deployed in a separate pod in the Kubernetes cluster. +// +// kubernetes.NewApplication(spec, "my_app", "my_container_1", "my_container_2") +// +// You can add more containers to an existing application: +// +// kubernetes.AddContainerToDeployment(spec, "my_app", "my_container_3") +// +// You can also deploy multiple containers in a single pod: +// +// kubernetes.AddPodToApplication(spec, "my_app", "my_container_4", "my_container_5") +// +// # Artifacts Generated +// +// During compilation, the plugin generates deployment.yaml and service.yaml files for each Pod. +// +// # Running Artifacts +// +// You need to have a working kubernetes cluster and `kubectl` installed. +// To deploy the pods to the cluster, use the following commands: +// +// kubectl apply -f podName-deployment.yaml +// kubectl apply -f podName-service.yaml +// +package kubernetes + +import ( + "github.com/blueprint-uservices/blueprint/blueprint/pkg/coreplugins/namespaceutil" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" + "github.com/blueprint-uservices/blueprint/blueprint/pkg/wiring" + "github.com/blueprint-uservices/blueprint/plugins/kubernetes/kubepod" +) + +// [AddContainerToApplication] can be used by wiring specs to add more containers to a Kubernetes application +func AddContainerToApplication(spec wiring.WiringSpec, appName string, containerName string) { + AddPodToApplication(spec, appName, containerName) +} + +// [AddPodToApplication] can be used by wiring specs to bundle multiple containers in a single Kubernetes Pod and add that pod to an application +func AddPodToApplication(spec wiring.WiringSpec, appName string, containers ...string) { + podName := kubepod.NewKubePod(spec, containers[0], containers...) + namespaceutil.AddNodeTo[Application](spec, appName, podName) +} + +// [NewApplication] can be used by wiring specs to create a Kubernetes Application that instantiates a number of kubernetes pod deployments as services. For each provided container, a new pod deployment is created with that container added to the pod. +// +// Further pod deployments for containers can be generated by calling [AddContainerToApplication]. +// +// If one wishes to bundle multiple containers into a single pod, then that can be done by calling [AddPodToApplication]. Note that the containers provided to that must not have already been added to the application before. +// +// During compilation, generates the various configuration files for generating pod deployments and services. +// +// Returns appName +func NewApplication(spec wiring.WiringSpec, appName string, containers ...string) string { + + // If any children were provided in this call, add them to the app via a property + for _, containerName := range containers { + AddContainerToApplication(spec, appName, containerName) + } + + spec.Define(appName, &Application{}, func(ns wiring.Namespace) (ir.IRNode, error) { + application := &Application{AppName: appName} + _, err := namespaceutil.InstantiateNamespace(ns, &applicationNamespace{application}) + return application, err + }) + + return appName +} + +// A [wiring.NamespaceHandler] used to build kubernetes deployments +type applicationNamespace struct { + *Application +} + +// Implements [wiring.NamespaceHandler] +func (application *Application) Accepts(nodeType any) bool { + _, isPodDeploymentNode := nodeType.(kubepod.PodDeployment) + return isPodDeploymentNode +} + +// Implements [wiring.NamespaceHandler] +func (application *Application) AddEdge(name string, edge ir.IRNode) error { + application.Edges = append(application.Edges, edge) + return nil +} + +// Implements [wiring.NamespaceHandler] +func (application *Application) AddNode(name string, node ir.IRNode) error { + application.Nodes = append(application.Nodes, node) + return nil +} From d086a8b5bc627a398581fea025a648973dbf66d8 Mon Sep 17 00:00:00 2001 From: Vaastav Anand Date: Tue, 30 Jan 2024 15:21:46 +0100 Subject: [PATCH 5/7] Implement GenerateArtifacts interface --- plugins/kubernetes/ir.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/kubernetes/ir.go b/plugins/kubernetes/ir.go index a53aa3aa..13329ca1 100644 --- a/plugins/kubernetes/ir.go +++ b/plugins/kubernetes/ir.go @@ -18,3 +18,15 @@ func (n *Application) Name() string { func (n *Application) String() string { return ir.PrettyPrintNamespace(n.AppName, "KubeApp", n.Edges, n.Nodes) } + +// Implements ir.ArtifactGenerator +func (n *Application) GenerateArtifacts(dir string) error { + nodes := ir.Filter[ir.ArtifactGenerator](n.Nodes) + for _, node := range nodes { + err := node.GenerateArtifacts(dir) + if err != nil { + return err + } + } + return nil +} From 5217351b6ff595c316e2689ef630bb01253b96b3 Mon Sep 17 00:00:00 2001 From: Jonathan Mace Date: Fri, 2 Feb 2024 12:13:22 -0800 Subject: [PATCH 6/7] Update port logic for kubernetees --- .../dockercompose/dockergen/dockercompose.go | 2 - plugins/kubernetes/kubepod/deploy.go | 119 +++++++++++++++-- .../kubepod/deploygen/deployfile.go | 124 ++++++------------ 3 files changed, 145 insertions(+), 100 deletions(-) diff --git a/plugins/dockercompose/dockergen/dockercompose.go b/plugins/dockercompose/dockergen/dockercompose.go index eea73b79..898f408b 100644 --- a/plugins/dockercompose/dockergen/dockercompose.go +++ b/plugins/dockercompose/dockergen/dockercompose.go @@ -31,7 +31,6 @@ type instance struct { Ports map[string]uint16 // Map from bindconfig name to internal port Expose map[uint16]struct{} // Ports exposed with expose directive Config map[string]string // Map from environment variable name to value - Passthrough map[string]struct{} // Environment variables that just get passed through to the container } func NewDockerComposeFile(workspaceName, workspaceDir, fileName string) *DockerComposeFile { @@ -140,7 +139,6 @@ func (d *DockerComposeFile) addInstance(instanceName string, image string, conta Expose: make(map[uint16]struct{}), Ports: make(map[string]uint16), Config: make(map[string]string), - Passthrough: make(map[string]struct{}), } d.Instances[instanceName] = &instance return nil diff --git a/plugins/kubernetes/kubepod/deploy.go b/plugins/kubernetes/kubepod/deploy.go index 8a67924b..f53ebe9b 100644 --- a/plugins/kubernetes/kubepod/deploy.go +++ b/plugins/kubernetes/kubepod/deploy.go @@ -3,6 +3,7 @@ package kubepod import ( "fmt" "path/filepath" + "reflect" "github.com/blueprint-uservices/blueprint/blueprint/pkg/blueprint" "github.com/blueprint-uservices/blueprint/blueprint/pkg/blueprint/ioutil" @@ -29,7 +30,8 @@ type kubeDeploymentWorkspace struct { info docker.ContainerWorkspaceInfo - ImageDirs map[string]string + ImageDirs map[string]string + InstanceArgs map[string][]ir.IRNode // argnodes for each instance added to the workspace F *deploygen.KubeDeploymentFile } @@ -60,9 +62,6 @@ func (node *PodDeployment) generateArtifacts(workspace *kubeDeploymentWorkspace) if err := workspace.Finish(); err != nil { return err } - - // Reset any port assignments for externally-visible servers - address.ResetPorts(node.Edges) return nil } @@ -93,22 +92,16 @@ func (p *kubeDeploymentWorkspace) CreateImageDir(imageName string) (string, erro // Implements docker.ContainerWorkspace func (p *kubeDeploymentWorkspace) DeclarePrebuiltInstance(instanceName string, image string, args ...ir.IRNode) error { - if err := address.CheckPorts(args); err != nil { - return blueprint.Errorf("unable to add docker instance %v due to %v", instanceName, err.Error()) - } - - return p.F.AddImageInstance(instanceName, image, args...) + p.InstanceArgs[instanceName] = args + return p.F.AddImageInstance(instanceName, image) } // Implements docker.ContainerWorkspace func (p *kubeDeploymentWorkspace) DeclareLocalImage(instanceName string, imageDir string, args ...ir.IRNode) error { - // Docker containers should assign all internal server ports (typically using address.AssignPorts) before adding an instance - if err := address.CheckPorts(args); err != nil { - return blueprint.Errorf("unable to add docker instance %v due to %v", instanceName, err.Error()) - } + p.InstanceArgs[instanceName] = args // For now set image to instanceName image := instanceName - return p.F.AddImageInstance(instanceName, image, args...) + return p.F.AddImageInstance(instanceName, image) } // Implements docker.ContainerWorkspace @@ -118,8 +111,106 @@ func (p *kubeDeploymentWorkspace) SetEnvironmentVariable(instanceName string, ke // Generates the pod config file func (p *kubeDeploymentWorkspace) Finish() error { + // We didn't set any arguments or environment variables while accumulating instances. Do so now. + if err := p.processArgNodes(); err != nil { + return err + } + return p.F.Generate() } +func asMap[T any](s []*T) map[*T]struct{} { + m := make(map[*T]struct{}) + for _, v := range s { + m[v] = struct{}{} + } + return m +} + +// Goes through each container's arg nodes, determining which need to be passed to the container +// as environment variables. +// +// Has special handling for addresses; containers that bind a server will have ports assigned, +// and containers that dial to a server within this namespace will have the dial address set. +// +// We don't pick external-facing ports for any addresses; these will be set by the caller or user. +func (p *kubeDeploymentWorkspace) processArgNodes() error { + + // (1) Assign ports to containers + // Servers like backends will already be pre-bound to specific ports. Other servers + // like gRPC ones will need a port assigned. + // The networking address space in a pod is shared between containers, so port assignments + // must be unique across all containers in the pod. + var allBinds []*address.BindConfig + var assignedBinds map[*address.BindConfig]struct{} + var localAddresses map[string]string + { + localAddresses = make(map[string]string) + for _, instanceArgs := range p.InstanceArgs { + allBinds = append(allBinds, ir.Filter[*address.BindConfig](instanceArgs)...) + } + + _, assigned, err := address.AssignPorts(allBinds) + if err != nil { + return err + } + assignedBinds = asMap(assigned) + + localAddresses = make(map[string]string) + for _, bind := range allBinds { + localAddresses[bind.AddressName] = fmt.Sprintf("localhost:%v", bind.Port) + } + } + + // (2) Set environment variables for containers + // (3) Expose container ports externally + for instanceName, instanceArgs := range p.InstanceArgs { + binds, dials, remaining := address.Split(instanceArgs) + + // Handle the instanceArgs that are regular config args and not address related + for _, arg := range remaining { + switch node := arg.(type) { + case ir.IRConfig: + if node.HasValue() { + // Ignore if the value is already set, because it implies it's hard-coded + // inside the container image + } else { + // TODO: if Kubernetes supports pass-through environment variables, then + // implement this + return blueprint.Errorf("kubernetes doesn't support runtime environment variable passthrough for %v", node.Name()) + } + default: + return blueprint.Errorf("container instance %v can only accept IRConfig nodes as arguments, but found %v of type %v", instanceName, arg, reflect.TypeOf(arg)) + } + } + + // If we assigned ports for this container, then set the environment variables for them + for _, bind := range binds { + if _, isAssigned := assignedBinds[bind]; isAssigned { + p.F.AddEnvVar(instanceName, bind.Name(), fmt.Sprintf("0.0.0.0:%v", bind.Port)) + } + } + + // Expose all ports for this container + for _, bind := range binds { + p.F.ExposePort(instanceName, bind.AddressName, bind.Port) + } + + // If a dial is local, then it just calls localhost:port. If it's not local + // then ?????????????????? + for _, dial := range dials { + if addr, isLocalDial := localAddresses[dial.AddressName]; isLocalDial { + p.F.AddEnvVar(instanceName, dial.Name(), addr) + } else { + // TODO: do we pass through an environment variable? how do we know the name to dial for + // services that are outside this service? maybe just use the + // service_a.grpc.dial_addr name itself as the service lookup name??? + } + } + } + + return nil +} + func (p *kubeDeploymentWorkspace) ImplementsBuildContext() {} func (p *kubeDeploymentWorkspace) ImplementsContainerWorkspace() {} diff --git a/plugins/kubernetes/kubepod/deploygen/deployfile.go b/plugins/kubernetes/kubepod/deploygen/deployfile.go index e4ec1a3b..c9478ee1 100644 --- a/plugins/kubernetes/kubepod/deploygen/deployfile.go +++ b/plugins/kubernetes/kubepod/deploygen/deployfile.go @@ -5,7 +5,6 @@ import ( "path/filepath" "github.com/blueprint-uservices/blueprint/blueprint/pkg/blueprint" - "github.com/blueprint-uservices/blueprint/blueprint/pkg/coreplugins/address" "github.com/blueprint-uservices/blueprint/blueprint/pkg/ir" "github.com/blueprint-uservices/blueprint/plugins/kubernetes/kubetemplate" "github.com/blueprint-uservices/blueprint/plugins/linux" @@ -18,9 +17,7 @@ type KubeDeploymentFile struct { FileName string ServiceFilename string FilePath string - Instances map[string]instance - localServers map[string]*address.BindConfig - localDials map[string]*address.DialConfig + Instances map[string]*instance } type instance struct { @@ -28,7 +25,6 @@ type instance struct { Image string Ports map[string]uint16 Config map[string]string - Passthrough map[string]struct{} } func NewKubeDeploymentFile(workspaceName string, workspaceDir string, filename string, serviceFilename string) *KubeDeploymentFile { @@ -38,12 +34,11 @@ func NewKubeDeploymentFile(workspaceName string, workspaceDir string, filename s FileName: filename, ServiceFilename: serviceFilename, FilePath: filepath.Join(workspaceDir, filename), - Instances: make(map[string]instance), + Instances: make(map[string]*instance), } } func (k *KubeDeploymentFile) Generate() error { - k.ResolveLocalDials() slog.Info(fmt.Sprintf("Generating %v/%v", k.WorkspaceName, k.FileName)) err := kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesTemplate, k, k.FilePath) if err != nil { @@ -53,95 +48,55 @@ func (k *KubeDeploymentFile) Generate() error { return kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesServiceTemplate, k, serviceFilePath) } -func (k *KubeDeploymentFile) AddImageInstance(instanceName string, image string, args ...ir.IRNode) error { - return k.addInstance(instanceName, image, args...) +func (k *KubeDeploymentFile) AddImageInstance(instanceName string, image string) error { + return k.addInstance(instanceName, image) } -func (k *KubeDeploymentFile) AddEnvVar(instanceName string, key string, val string) error { +func (k *KubeDeploymentFile) getInstance(instanceName string) (*instance, error) { instanceName = ir.CleanName(instanceName) - if i, exists := k.Instances[instanceName]; !exists { - return blueprint.Errorf("container instance with name %v not found", instanceName) + if i, exists := k.Instances[instanceName]; exists { + return i, nil } else { - i.Config[key] = val - k.Instances[instanceName] = i + return nil, blueprint.Errorf("container instance with name %v not found", instanceName) + } +} + +func (k *KubeDeploymentFile) AddEnvVar(instanceName string, key string, val string) error { + key = linux.EnvVar(key) + instance, err := k.getInstance(instanceName) + if err != nil { + return err + } else { + instance.Config[key] = val + return nil } - return nil } -func (k *KubeDeploymentFile) addInstance(instanceName string, image string, args ...ir.IRNode) error { +func (k *KubeDeploymentFile) ExposePort(instanceName string, portName string, port uint16) error { + instance, err := k.getInstance(instanceName) + if err != nil { + return err + } else { + instance.Ports[portName] = port + return nil + } +} + +func (k *KubeDeploymentFile) addInstance(instanceName string, image string) error { instanceName = ir.CleanName(instanceName) if _, exists := k.Instances[instanceName]; exists { return blueprint.Errorf("re-declaration of container instance %v of image %v", instanceName, image) } - instance := instance{ + instance := &instance{ InstanceName: instanceName, Image: image, Ports: make(map[string]uint16), Config: make(map[string]string), } - for _, node := range args { - varname := linux.EnvVar(node.Name()) - - if bind, isBindConf := node.(*address.BindConfig); isBindConf { - if bind.Port == 0 { - return blueprint.Errorf("cannot add container instance %v due to unbound server port %v", instanceName, bind.Name()) - } - instance.Ports[requiredEnvVar(node)] = bind.Port - } - - if conf, isConfig := node.(ir.IRConfig); isConfig { - if conf.HasValue() { - instance.Config[varname] = conf.Value() - continue - } else if conf.Optional() { - instance.Passthrough[varname] = struct{}{} - continue - } - } - instance.Config[varname] = requiredEnvVar(node) - } - k.Instances[instanceName] = instance - - k.checkForAddrs(args) - - return nil -} - -func (d *KubeDeploymentFile) checkForAddrs(nodes []ir.IRNode) { - for _, node := range nodes { - switch c := node.(type) { - case *address.BindConfig: - d.localServers[c.AddressName] = c - case *address.DialConfig: - d.localDials[c.AddressName] = c - } - } -} - -func (d *KubeDeploymentFile) ResolveLocalDials() error { - for name, bind := range d.localServers { - dial, hasLocalDial := d.localDials[name] - if !hasLocalDial { - continue - } - - // Update the configured value for any instance that uses this dial addr - // to point it directly towards the local server - dialVarname := linux.EnvVar(dial.Name()) - for _, instance := range d.Instances { - if _, hasConfig := instance.Config[dialVarname]; hasConfig { - instance.Config[dialVarname] = bind.Value() - } - } - } return nil } -func requiredEnvVar(node ir.IRNode) string { - return fmt.Sprintf("${%v?%v must be set by the calling environment}", linux.EnvVar(node.Name()), node.Name()) -} - var kubernetesTemplate = ` apiVersion: apps/v1 kind: Deployment @@ -173,10 +128,8 @@ spec: {{- end}} {{- if .Ports}} ports: - {{- range $external, $internal := .Ports}} - - containerPort: {{$internal}} - hostIP: 0.0.0.0 - hostPort: {{$external}} + {{- range $name, $port := .Ports}} + - containerPort: {{$port}} {{- end}} {{-end}} restartPolicy: Always @@ -187,13 +140,16 @@ var kubernetesServiceTemplate = ` apiVersion: v1 kind: Service metadata: - name: {{.Name}}-service + name: {{.Name}} spec: selector: blueprint.service: {{.Name}} ports: - {{- range $external, $internal := .Ports}} - - port: {{$internal}} - targetPort: {{$external}} + {{range $_, $decl := .Instances}} + {{- range $name, $port := .Ports}} + - name: {{$name}} + port: {{$port}} + targetPort: {{$port}} + {{- end}} {{- end}} ` From be6301b4d2a5c14827604208ab7f2c157ee3e2ef Mon Sep 17 00:00:00 2001 From: Vaastav Anand Date: Wed, 28 Feb 2024 18:39:08 +0100 Subject: [PATCH 7/7] Fix minor bugs in kube yaml file generation process --- examples/leaf/wiring/main.go | 1 + plugins/kubernetes/kubepod/deploy.go | 6 ++++-- .../kubernetes/kubepod/deploygen/deployfile.go | 16 +++++++++++----- plugins/linuxcontainer/deploy_docker.go | 1 + 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/examples/leaf/wiring/main.go b/examples/leaf/wiring/main.go index 7485d380..4eaf84c3 100644 --- a/examples/leaf/wiring/main.go +++ b/examples/leaf/wiring/main.go @@ -34,5 +34,6 @@ func main() { specs.Xtrace_Logger, specs.OT_Logger, specs.Govector, + specs.Kubernetes, ) } diff --git a/plugins/kubernetes/kubepod/deploy.go b/plugins/kubernetes/kubepod/deploy.go index f53ebe9b..2114768b 100644 --- a/plugins/kubernetes/kubepod/deploy.go +++ b/plugins/kubernetes/kubepod/deploy.go @@ -71,8 +71,9 @@ func NewKubePodWorkspace(name string, dir string) *kubeDeploymentWorkspace { Path: filepath.Clean(dir), Target: "kubedeployment", }, - ImageDirs: make(map[string]string), - F: deploygen.NewKubeDeploymentFile(name, dir, name+"-deployment.yaml", name+"-service.yaml"), + ImageDirs: make(map[string]string), + InstanceArgs: make(map[string][]ir.IRNode), + F: deploygen.NewKubeDeploymentFile(name, dir, name+"-deployment.yaml", name+"-service.yaml"), } } @@ -98,6 +99,7 @@ func (p *kubeDeploymentWorkspace) DeclarePrebuiltInstance(instanceName string, i // Implements docker.ContainerWorkspace func (p *kubeDeploymentWorkspace) DeclareLocalImage(instanceName string, imageDir string, args ...ir.IRNode) error { + slog.Info("Inside DeclareLocalImage") p.InstanceArgs[instanceName] = args // For now set image to instanceName image := instanceName diff --git a/plugins/kubernetes/kubepod/deploygen/deployfile.go b/plugins/kubernetes/kubepod/deploygen/deployfile.go index c9478ee1..c6dfcfb4 100644 --- a/plugins/kubernetes/kubepod/deploygen/deployfile.go +++ b/plugins/kubernetes/kubepod/deploygen/deployfile.go @@ -12,11 +12,12 @@ import ( ) type KubeDeploymentFile struct { - WorkspaceName string + Name string WorkspaceDir string FileName string ServiceFilename string FilePath string + NumReplicas int64 Instances map[string]*instance } @@ -29,22 +30,26 @@ type instance struct { func NewKubeDeploymentFile(workspaceName string, workspaceDir string, filename string, serviceFilename string) *KubeDeploymentFile { return &KubeDeploymentFile{ - WorkspaceName: workspaceName, + Name: workspaceName, WorkspaceDir: workspaceDir, FileName: filename, ServiceFilename: serviceFilename, FilePath: filepath.Join(workspaceDir, filename), Instances: make(map[string]*instance), + // For now NumReplicas is fixed. + NumReplicas: 1, } } func (k *KubeDeploymentFile) Generate() error { - slog.Info(fmt.Sprintf("Generating %v/%v", k.WorkspaceName, k.FileName)) + slog.Info(fmt.Sprintf("Generating %v/%v", k.Name, k.FileName)) err := kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesTemplate, k, k.FilePath) if err != nil { return err } + slog.Info("NUmber of instances: ", "num", len(k.Instances)) serviceFilePath := filepath.Join(k.WorkspaceDir, k.ServiceFilename) + slog.Info(fmt.Sprintf("Generating %v/%v", k.Name, k.ServiceFilename)) return kubetemplate.ExecuteTemplateToFile("kubedeployment", kubernetesServiceTemplate, k, serviceFilePath) } @@ -131,9 +136,10 @@ spec: {{- range $name, $port := .Ports}} - containerPort: {{$port}} {{- end}} - {{-end}} + {{- end}} + {{- end}} restartPolicy: Always - hostname: {{.InstanceName}} + hostname: {{.Name}} ` var kubernetesServiceTemplate = ` diff --git a/plugins/linuxcontainer/deploy_docker.go b/plugins/linuxcontainer/deploy_docker.go index aebfde27..484ce923 100644 --- a/plugins/linuxcontainer/deploy_docker.go +++ b/plugins/linuxcontainer/deploy_docker.go @@ -79,6 +79,7 @@ func (node *Container) AddContainerArtifacts(target docker.ContainerWorkspace) e func (node *Container) AddContainerInstance(target docker.ContainerWorkspace) error { // The instance only needs to be added to the output directory once if target.Visited(node.InstanceName + ".instance") { + slog.Info(fmt.Sprintf("%v already declared as a container", node.InstanceName)) return nil }