mirror of
https://github.com/onepanelio/onepanel.git
synced 2025-10-04 21:32:39 +08:00
Compare commits
12 Commits
v0.13.0-rc
...
v0.13.1-rc
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0e1e48dfc8 | ||
![]() |
dd0f1f7705 | ||
![]() |
03f8f47664 | ||
![]() |
c85496d216 | ||
![]() |
5f6415548d | ||
![]() |
c641c17a8c | ||
![]() |
83a2543b13 | ||
![]() |
e8dae0f2e9 | ||
![]() |
b85bf4d688 | ||
![]() |
7fe0ab2654 | ||
![]() |
dfa6eb2fe6 | ||
![]() |
cc2c51ace5 |
@@ -2,7 +2,6 @@ package migration
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
v1 "github.com/onepanelio/core/pkg"
|
|
||||||
uid2 "github.com/onepanelio/core/pkg/util/uid"
|
uid2 "github.com/onepanelio/core/pkg/util/uid"
|
||||||
"github.com/pressly/goose"
|
"github.com/pressly/goose"
|
||||||
)
|
)
|
||||||
@@ -103,13 +102,7 @@ func Up20200929153931(tx *sql.Tx) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
workspaceTemplate := &v1.WorkspaceTemplate{
|
if _, err := client.UpdateWorkspaceTemplateManifest(namespace.Name, uid, jupyterWorkspaceTemplate3); err != nil {
|
||||||
UID: uid,
|
|
||||||
Name: jupyterLabTemplateName,
|
|
||||||
Manifest: jupyterWorkspaceTemplate3,
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := client.UpdateWorkspaceTemplate(namespace.Name, workspaceTemplate); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,14 +137,9 @@ func Down20200929153931(tx *sql.Tx) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
workspaceTemplate := &v1.WorkspaceTemplate{
|
|
||||||
UID: uid,
|
|
||||||
Name: jupyterLabTemplateName,
|
|
||||||
Manifest: jupyterWorkspaceTemplate2,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, namespace := range namespaces {
|
for _, namespace := range namespaces {
|
||||||
if _, err := client.UpdateWorkspaceTemplate(namespace.Name, workspaceTemplate); err != nil {
|
if _, err := client.UpdateWorkspaceTemplateManifest(namespace.Name, uid, jupyterWorkspaceTemplate2); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -197,70 +196,21 @@ func injectArtifactRepositoryConfig(artifact *wfv1.Artifact, namespaceConfig *Na
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// injectContainerResourceQuotas adds resource requests and limits if they exist
|
// injectHostPortToContainer adds a hostPort to the template container, if a nodeSelector is present.
|
||||||
// Code grabs the resource request information from the nodeSelector, compared against running nodes.
|
// Kubernetes will ensure that multiple containers with the same hostPort do not share the same node.
|
||||||
// If the running node is not present, no resource information is retrieved.
|
func (c *Client) injectHostPortToContainer(template *wfv1.Template) error {
|
||||||
func (c *Client) injectContainerResourceQuotas(wf *wfv1.Workflow, template *wfv1.Template, systemConfig SystemConfig) error {
|
|
||||||
if template.NodeSelector == nil {
|
if template.NodeSelector == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
supportedNodePoolLabels := []string{"beta.kubernetes.io/instance-type", "node.kubernetes.io/instance-type"}
|
ports := []corev1.ContainerPort{
|
||||||
nodePoolLabel := ""
|
{Name: "node-capturer", HostPort: 80, ContainerPort: 80},
|
||||||
var value string
|
|
||||||
for k, v := range template.NodeSelector {
|
|
||||||
for _, supportedNodePoolLabel := range supportedNodePoolLabels {
|
|
||||||
if k == supportedNodePoolLabel {
|
|
||||||
nodePoolLabel = k
|
|
||||||
value = v
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if value == "" {
|
if template.Container != nil {
|
||||||
return nil
|
template.Container.Ports = ports
|
||||||
}
|
}
|
||||||
if strings.Contains(value, "{{workflow.") {
|
if template.Script != nil {
|
||||||
parts := strings.Split(strings.Replace(value, "}}", "", -1), ".")
|
template.Script.Container.Ports = ports
|
||||||
paramName := parts[len(parts)-1]
|
|
||||||
for _, param := range wf.Spec.Arguments.Parameters {
|
|
||||||
if param.Name == paramName && param.Value != nil {
|
|
||||||
value = *param.Value
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runningNodes, err := c.Interface.CoreV1().Nodes().List(ListOptions{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, node := range runningNodes.Items {
|
|
||||||
if node.Labels[nodePoolLabel] == value {
|
|
||||||
cpu, memory, gpu, gpuManufacturer := CalculateResourceRequirements(node, nodePoolLabel, value)
|
|
||||||
if cpu != "" && memory != "" {
|
|
||||||
resourceList := corev1.ResourceRequirements{
|
|
||||||
Limits: nil,
|
|
||||||
Requests: map[corev1.ResourceName]resource.Quantity{
|
|
||||||
corev1.ResourceCPU: resource.MustParse(cpu),
|
|
||||||
corev1.ResourceMemory: resource.MustParse(memory),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if gpu > 0 {
|
|
||||||
stringGpu := strconv.FormatInt(gpu, 10)
|
|
||||||
resourceList.Limits = make(map[corev1.ResourceName]resource.Quantity)
|
|
||||||
resourceList.Limits[corev1.ResourceName(gpuManufacturer)] = resource.MustParse(stringGpu)
|
|
||||||
}
|
|
||||||
if template.Container != nil {
|
|
||||||
template.Container.Resources = resourceList
|
|
||||||
}
|
|
||||||
if template.Script != nil {
|
|
||||||
template.Script.Container.Resources = resourceList
|
|
||||||
}
|
|
||||||
//process only one node
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -332,7 +282,7 @@ func (c *Client) injectAutomatedFields(namespace string, wf *wfv1.Workflow, opts
|
|||||||
Name: "sys-dshm",
|
Name: "sys-dshm",
|
||||||
MountPath: "/dev/shm",
|
MountPath: "/dev/shm",
|
||||||
})
|
})
|
||||||
err = c.injectContainerResourceQuotas(wf, template, systemConfig)
|
err = c.injectHostPortToContainer(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -340,7 +290,7 @@ func (c *Client) injectAutomatedFields(namespace string, wf *wfv1.Workflow, opts
|
|||||||
}
|
}
|
||||||
|
|
||||||
if template.Script != nil {
|
if template.Script != nil {
|
||||||
err = c.injectContainerResourceQuotas(wf, template, systemConfig)
|
err = c.injectHostPortToContainer(template)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
125
pkg/workspace.go
125
pkg/workspace.go
@@ -15,8 +15,6 @@ import (
|
|||||||
"github.com/onepanelio/core/pkg/util/request"
|
"github.com/onepanelio/core/pkg/util/request"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -287,26 +285,7 @@ func (c *Client) addResourceRequestsAndLimitsToWorkspaceTemplate(t wfv1.Template
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("unable to type check statefulset manifest")
|
return nil, errors.New("unable to type check statefulset manifest")
|
||||||
}
|
}
|
||||||
//Get node selected
|
extraContainer := generateExtraContainerWithHostPortToSequesterNode()
|
||||||
labelKey := "sys-node-pool-label"
|
|
||||||
labelKeyVal := ""
|
|
||||||
for _, parameter := range argoTemplate.Spec.Arguments.Parameters {
|
|
||||||
if parameter.Name == labelKey {
|
|
||||||
labelKeyVal = *parameter.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodePoolKey := "sys-node-pool"
|
|
||||||
nodePoolVal := ""
|
|
||||||
for _, parameter := range workspace.Parameters {
|
|
||||||
if parameter.Name == nodePoolKey {
|
|
||||||
nodePoolVal = *parameter.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extraContainer, err := generateExtraContainerWithResources(c, labelKeyVal, nodePoolVal)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if extraContainer != nil {
|
if extraContainer != nil {
|
||||||
containers, ok := templateSpec["containers"].([]interface{})
|
containers, ok := templateSpec["containers"].([]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -322,93 +301,25 @@ func (c *Client) addResourceRequestsAndLimitsToWorkspaceTemplate(t wfv1.Template
|
|||||||
return resultManifest, nil
|
return resultManifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateExtraContainerWithResources will add an extra container to a workspace.
|
// generateExtraContainerWithHostPortToSequesterNode will add an extra container to a workspace.
|
||||||
// The extra container will have the calculated resource request for the node selected by the workspace.
|
// The extra container have a hostPort set. Kubernetes will ensure the hostPort does not get conflict
|
||||||
|
// between containers, scheduling a new node as needed.
|
||||||
// The container will sleep once started, and generally consume negligible resources.
|
// The container will sleep once started, and generally consume negligible resources.
|
||||||
//
|
func generateExtraContainerWithHostPortToSequesterNode() map[string]interface{} {
|
||||||
// The node that was selected has to be already running, in order to get the resource request correct.
|
extraContainer := map[string]interface{}{
|
||||||
func generateExtraContainerWithResources(c *Client, labelKeyVal string, nodePoolVal string) (map[string]interface{}, error) {
|
"image": "alpine:latest",
|
||||||
runningNodes, err := c.Interface.CoreV1().Nodes().List(ListOptions{})
|
"name": "node-capturer",
|
||||||
if err != nil {
|
"command": []interface{}{"/bin/sh"},
|
||||||
return nil, err
|
"args": []interface{}{"-c", "while :; do sleep 2073600; done"},
|
||||||
|
"ports": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"name": "node-capturer",
|
||||||
|
"hostPort": 80,
|
||||||
|
"containerPort": 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, node := range runningNodes.Items {
|
return extraContainer
|
||||||
cpu, memory, gpu, gpuManufacturer := CalculateResourceRequirements(node, labelKeyVal, nodePoolVal)
|
|
||||||
if cpu != "" && memory != "" {
|
|
||||||
extraContainer := map[string]interface{}{
|
|
||||||
"image": "alpine:latest",
|
|
||||||
"name": "resource-requester",
|
|
||||||
"command": []interface{}{"/bin/sh"},
|
|
||||||
"args": []interface{}{"-c", "while :; do sleep 2073600; done"},
|
|
||||||
"resources": map[string]interface{}{
|
|
||||||
"requests": map[string]interface{}{
|
|
||||||
"cpu": cpu,
|
|
||||||
"memory": memory,
|
|
||||||
},
|
|
||||||
"limits": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if gpu > 0 {
|
|
||||||
res, ok := extraContainer["resources"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to type check extraContainer")
|
|
||||||
}
|
|
||||||
reqs, ok := res["requests"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to type check extraContainer")
|
|
||||||
}
|
|
||||||
reqs[gpuManufacturer] = gpu
|
|
||||||
|
|
||||||
limits, ok := res["limits"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to type check extraContainer")
|
|
||||||
}
|
|
||||||
limits[gpuManufacturer] = gpu
|
|
||||||
|
|
||||||
}
|
|
||||||
//process only one node
|
|
||||||
return extraContainer, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalculateResourceRequirements will take the passed in Node and try to figure out the
|
|
||||||
// allocatable capacity. Once the capacity is discovered, function figures out how to request 90%.
|
|
||||||
// - We use 90% because manual calculation is error prone and not necessarily reliable.
|
|
||||||
// Params:
|
|
||||||
// k8sInstanceTypeLabel - Such as 'beta.kubernetes.io/instance-type'
|
|
||||||
// nodeSelectorValue - Server/VM Type Name, Such as "Standard_NC6", on Azure.
|
|
||||||
func CalculateResourceRequirements(node corev1.Node, k8sInstanceTypeLabel string, nodeSelectorValue string) (string, string, int64, string) {
|
|
||||||
var cpu string
|
|
||||||
var memory string
|
|
||||||
var gpu int64
|
|
||||||
gpuManufacturer := ""
|
|
||||||
if node.Labels[k8sInstanceTypeLabel] == nodeSelectorValue {
|
|
||||||
cpuInt := node.Status.Allocatable.Cpu().MilliValue()
|
|
||||||
cpu = strconv.FormatFloat(float64(cpuInt)*.9, 'f', 0, 64) + "m"
|
|
||||||
memoryInt := node.Status.Allocatable.Memory().MilliValue()
|
|
||||||
kiBase := 1024.0
|
|
||||||
ninetyPerc := float64(memoryInt) * .9
|
|
||||||
toKi := ninetyPerc / kiBase / kiBase
|
|
||||||
memory = strconv.FormatFloat(toKi, 'f', 0, 64) + "Ki"
|
|
||||||
//Check for Nvidia
|
|
||||||
gpuQuantity := node.Status.Allocatable["nvidia.com/gpu"]
|
|
||||||
if gpuQuantity.IsZero() == false {
|
|
||||||
gpu = gpuQuantity.Value()
|
|
||||||
gpuManufacturer = "nvidia.com/gpu"
|
|
||||||
}
|
|
||||||
|
|
||||||
//Check for AMD
|
|
||||||
//Source: https://github.com/RadeonOpenCompute/k8s-device-plugin/blob/master/example/pod/alexnet-gpu.yaml
|
|
||||||
gpuQuantity = node.Status.Allocatable["amd.com/gpu"]
|
|
||||||
if gpuQuantity.IsZero() == false {
|
|
||||||
gpu = gpuQuantity.Value()
|
|
||||||
gpuManufacturer = "amd.com/gpu"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpu, memory, gpu, gpuManufacturer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// startWorkspace starts a workspace and related resources. It assumes a DB record already exists
|
// startWorkspace starts a workspace and related resources. It assumes a DB record already exists
|
||||||
|
@@ -1158,6 +1158,19 @@ func (c *Client) UpdateWorkspaceTemplate(namespace string, workspaceTemplate *Wo
|
|||||||
return workspaceTemplate, nil
|
return workspaceTemplate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateWorkspaceTemplateManifest updates a workspace template by creating a new version where the only difference is the manifest
|
||||||
|
func (c *Client) UpdateWorkspaceTemplateManifest(namespace, uid string, manifest string) (*WorkspaceTemplate, error) {
|
||||||
|
existingTemplate, err := c.GetWorkspaceTemplate(namespace, uid, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTemplate.UID = uid
|
||||||
|
existingTemplate.Manifest = manifest
|
||||||
|
|
||||||
|
return c.UpdateWorkspaceTemplate(namespace, existingTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
// ListWorkspaceTemplates returns a list of workspace templates that are not archived, sorted by most recent created first
|
// ListWorkspaceTemplates returns a list of workspace templates that are not archived, sorted by most recent created first
|
||||||
func (c *Client) ListWorkspaceTemplates(namespace string, request *request.Request) (workspaceTemplates []*WorkspaceTemplate, err error) {
|
func (c *Client) ListWorkspaceTemplates(namespace string, request *request.Request) (workspaceTemplates []*WorkspaceTemplate, err error) {
|
||||||
sb := c.workspaceTemplatesSelectBuilder(namespace).
|
sb := c.workspaceTemplatesSelectBuilder(namespace).
|
||||||
|
@@ -363,7 +363,7 @@ func (s *WorkspaceServer) RetryLastWorkspaceAction(ctx context.Context, req *api
|
|||||||
func (s *WorkspaceServer) GetWorkspaceStatisticsForNamespace(ctx context.Context, req *api.GetWorkspaceStatisticsForNamespaceRequest) (*api.GetWorkspaceStatisticsForNamespaceResponse, error) {
|
func (s *WorkspaceServer) GetWorkspaceStatisticsForNamespace(ctx context.Context, req *api.GetWorkspaceStatisticsForNamespaceRequest) (*api.GetWorkspaceStatisticsForNamespaceResponse, error) {
|
||||||
client := getClient(ctx)
|
client := getClient(ctx)
|
||||||
|
|
||||||
allowed, err := auth.IsAuthorized(client, req.Namespace, "list", "argoproj.io", "workspaces", "")
|
allowed, err := auth.IsAuthorized(client, req.Namespace, "list", "onepanel.io", "workspaces", "")
|
||||||
if err != nil || !allowed {
|
if err != nil || !allowed {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user