Unverified Commit fbf1ffd7 authored by atchernych's avatar atchernych Committed by GitHub
Browse files

feat: kubernetes overrides for the entrypoint and cmd (#1396)

parent 49b7e930
...@@ -1484,7 +1484,7 @@ Subdependencies: ...@@ -1484,7 +1484,7 @@ Subdependencies:
* `golang.org/x/net` * `golang.org/x/net`
* `golang.org/x/text` * `golang.org/x/text`
* `gopkg.in/inf.v0` * `gopkg.in/inf.v0`
* `gopkg.in/yaml.v2` * `github.com/goccy/go-yaml`
* `k8s.io/klog/v2` * `k8s.io/klog/v2`
* `k8s.io/utils` * `k8s.io/utils`
* `sigs.k8s.io/json` * `sigs.k8s.io/json`
...@@ -3179,7 +3179,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ...@@ -3179,7 +3179,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
``` ```
   
### gopkg.in/yaml.v2 ### github.com/goccy/go-yaml
   
License Identifier: Apache-2.0 License Identifier: Apache-2.0
License Text: License Text:
...@@ -4864,7 +4864,7 @@ Subdependencies: ...@@ -4864,7 +4864,7 @@ Subdependencies:
* `google.golang.org/genproto/googleapis/api` * `google.golang.org/genproto/googleapis/api`
* `google.golang.org/genproto/googleapis/rpc` * `google.golang.org/genproto/googleapis/rpc`
* `google.golang.org/protobuf` * `google.golang.org/protobuf`
* `gopkg.in/yaml.v2` * `github.com/goccy/go-yaml`
   
### github.com/dustin/go-humanize ### github.com/dustin/go-humanize
   
...@@ -8180,7 +8180,7 @@ Subdependencies: ...@@ -8180,7 +8180,7 @@ Subdependencies:
* `golang.org/x/text` * `golang.org/x/text`
* `google.golang.org/genproto/googleapis/rpc` * `google.golang.org/genproto/googleapis/rpc`
* `gopkg.in/inf.v0` * `gopkg.in/inf.v0`
* `gopkg.in/yaml.v2` * `github.com/goccy/go-yaml`
* `k8s.io/klog/v2` * `k8s.io/klog/v2`
* `k8s.io/utils` * `k8s.io/utils`
* `sigs.k8s.io/json` * `sigs.k8s.io/json`
...@@ -9056,7 +9056,7 @@ Subdependencies: ...@@ -9056,7 +9056,7 @@ Subdependencies:
* `google.golang.org/genproto/googleapis/api` * `google.golang.org/genproto/googleapis/api`
* `google.golang.org/protobuf` * `google.golang.org/protobuf`
* `gopkg.in/inf.v0` * `gopkg.in/inf.v0`
* `gopkg.in/yaml.v2` * `github.com/goccy/go-yaml`
* `gopkg.in/yaml.v3` * `gopkg.in/yaml.v3`
* `k8s.io/api` * `k8s.io/api`
* `k8s.io/klog/v2` * `k8s.io/klog/v2`
...@@ -22970,7 +22970,7 @@ Subdependencies: ...@@ -22970,7 +22970,7 @@ Subdependencies:
* `google.golang.org/protobuf` * `google.golang.org/protobuf`
* `gopkg.in/evanphx/json-patch.v4` * `gopkg.in/evanphx/json-patch.v4`
* `gopkg.in/inf.v0` * `gopkg.in/inf.v0`
* `gopkg.in/yaml.v2` * `github.com/goccy/go-yaml`
* `gopkg.in/yaml.v3` * `gopkg.in/yaml.v3`
* `k8s.io/component-base` * `k8s.io/component-base`
* `k8s.io/gengo/v2` * `k8s.io/gengo/v2`
...@@ -86,7 +86,7 @@ kubectl get storageclass ...@@ -86,7 +86,7 @@ kubectl get storageclass
1. Set the required environment variables: 1. Set the required environment variables:
```bash ```bash
export PROJECT_ROOT=($pwd) export PROJECT_ROOT=$(pwd)
export DOCKER_USERNAME=<your-docker-username> export DOCKER_USERNAME=<your-docker-username>
export DOCKER_PASSWORD=<your-docker-password> export DOCKER_PASSWORD=<your-docker-password>
export DOCKER_SERVER=<your-docker-server> export DOCKER_SERVER=<your-docker-server>
......
...@@ -62,4 +62,5 @@ type ExtraPodSpec struct { ...@@ -62,4 +62,5 @@ type ExtraPodSpec struct {
Containers []corev1.Container `json:"containers,omitempty"` Containers []corev1.Container `json:"containers,omitempty"`
ServiceAccountName string `json:"serviceAccountName,omitempty"` ServiceAccountName string `json:"serviceAccountName,omitempty"`
PriorityClassName string `json:"priorityClassName,omitempty"` PriorityClassName string `json:"priorityClassName,omitempty"`
MainContainer *corev1.Container `json:"mainContainer,omitempty"`
} }
...@@ -151,6 +151,11 @@ func (in *ExtraPodSpec) DeepCopyInto(out *ExtraPodSpec) { ...@@ -151,6 +151,11 @@ func (in *ExtraPodSpec) DeepCopyInto(out *ExtraPodSpec) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
if in.MainContainer != nil {
in, out := &in.MainContainer, &out.MainContainer
*out = new(v1.Container)
(*in).DeepCopyInto(*out)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraPodSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraPodSpec.
......
...@@ -11,6 +11,7 @@ require ( ...@@ -11,6 +11,7 @@ require (
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1
github.com/bsm/gomega v1.27.10 github.com/bsm/gomega v1.27.10
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589
github.com/goccy/go-yaml v1.18.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/google/go-containerregistry v0.20.5 github.com/google/go-containerregistry v0.20.5
github.com/huandu/xstrings v1.4.0 github.com/huandu/xstrings v1.4.0
...@@ -22,7 +23,6 @@ require ( ...@@ -22,7 +23,6 @@ require (
github.com/sergeymakinen/go-quote v1.1.0 github.com/sergeymakinen/go-quote v1.1.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
go.etcd.io/etcd/client/v3 v3.5.16 go.etcd.io/etcd/client/v3 v3.5.16
gopkg.in/yaml.v2 v2.4.0
istio.io/api v1.23.1 istio.io/api v1.23.1
istio.io/client-go v1.23.1 istio.io/client-go v1.23.1
k8s.io/api v0.32.3 k8s.io/api v0.32.3
......
...@@ -111,6 +111,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr ...@@ -111,6 +111,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
...@@ -310,8 +312,6 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP ...@@ -310,8 +312,6 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
......
...@@ -40,6 +40,7 @@ import ( ...@@ -40,6 +40,7 @@ import (
"github.com/apparentlymart/go-shquot/shquot" "github.com/apparentlymart/go-shquot/shquot"
"github.com/awslabs/amazon-ecr-credential-helper/ecr-login" "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper" "github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
"github.com/goccy/go-yaml"
"github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/google" "github.com/google/go-containerregistry/pkg/v1/google"
...@@ -49,7 +50,6 @@ import ( ...@@ -49,7 +50,6 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
"github.com/sergeymakinen/go-quote/unix" "github.com/sergeymakinen/go-quote/unix"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors" k8serrors "k8s.io/apimachinery/pkg/api/errors"
......
...@@ -1345,6 +1345,7 @@ func getDynamoComponentRepositoryNameAndDynamoComponentVersion(dynamoComponent * ...@@ -1345,6 +1345,7 @@ func getDynamoComponentRepositoryNameAndDynamoComponentVersion(dynamoComponent *
//nolint:gocyclo,nakedret //nolint:gocyclo,nakedret
func (r *DynamoComponentDeploymentReconciler) generatePodTemplateSpec(ctx context.Context, opt generateResourceOption) (podTemplateSpec *corev1.PodTemplateSpec, err error) { func (r *DynamoComponentDeploymentReconciler) generatePodTemplateSpec(ctx context.Context, opt generateResourceOption) (podTemplateSpec *corev1.PodTemplateSpec, err error) {
logs := log.FromContext(ctx)
podLabels := r.getKubeLabels(opt.dynamoComponentDeployment, opt.dynamoComponent) podLabels := r.getKubeLabels(opt.dynamoComponentDeployment, opt.dynamoComponent)
if opt.isStealingTrafficDebugModeEnabled { if opt.isStealingTrafficDebugModeEnabled {
podLabels[commonconsts.KubeLabelDynamoDeploymentTargetType] = DeploymentTargetTypeDebug podLabels[commonconsts.KubeLabelDynamoDeploymentTargetType] = DeploymentTargetTypeDebug
...@@ -1659,6 +1660,28 @@ func (r *DynamoComponentDeploymentReconciler) generatePodTemplateSpec(ctx contex ...@@ -1659,6 +1660,28 @@ func (r *DynamoComponentDeploymentReconciler) generatePodTemplateSpec(ctx contex
container.SecurityContext.RunAsUser = &[]int64{0}[0] container.SecurityContext.RunAsUser = &[]int64{0}[0]
} }
// For now only overwrite the command and args.
if opt.dynamoComponentDeployment.Spec.ExtraPodSpec != nil {
extraPodSpecMainContainer := opt.dynamoComponentDeployment.Spec.ExtraPodSpec.MainContainer
if extraPodSpecMainContainer != nil {
if len(extraPodSpecMainContainer.Command) > 0 {
logs.Info("Overriding container '" + container.Name + "' Command with: " + strings.Join(extraPodSpecMainContainer.Command, " "))
container.Command = extraPodSpecMainContainer.Command
}
if len(extraPodSpecMainContainer.Args) > 0 {
// Special case: if command is "sh -c", we must collapse args into a single string
if len(container.Command) == 2 && container.Command[0] == "sh" && container.Command[1] == "-c" {
joinedArgs := strings.Join(extraPodSpecMainContainer.Args, " ")
logs.Info("Special case detected for container '" + container.Name + "': Command is 'sh -c'; collapsing Args to: " + joinedArgs)
container.Args = []string{joinedArgs}
} else {
logs.Info("Overriding container '" + container.Name + "' Args with: " + strings.Join(extraPodSpecMainContainer.Args, " "))
container.Args = extraPodSpecMainContainer.Args
}
}
}
}
containers = append(containers, container) containers = append(containers, container)
debuggerImage := "python:3.12-slim" debuggerImage := "python:3.12-slim"
......
...@@ -42,7 +42,7 @@ import ( ...@@ -42,7 +42,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log"
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/archive" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/archive"
"gopkg.in/yaml.v2" "github.com/goccy/go-yaml"
) )
const ( const (
...@@ -83,6 +83,7 @@ type Config struct { ...@@ -83,6 +83,7 @@ type Config struct {
ApiEndpoints []string `yaml:"api_endpoints,omitempty"` ApiEndpoints []string `yaml:"api_endpoints,omitempty"`
Workers *int32 `yaml:"workers,omitempty"` Workers *int32 `yaml:"workers,omitempty"`
TotalGpus *int32 `yaml:"total_gpus,omitempty"` TotalGpus *int32 `yaml:"total_gpus,omitempty"`
ExtraPodSpec *common.ExtraPodSpec `yaml:"extraPodSpec,omitempty"`
} }
type ServiceConfig struct { type ServiceConfig struct {
...@@ -360,6 +361,7 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD ...@@ -360,6 +361,7 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
return nil, err return nil, err
} }
} }
deployment.Spec.Autoscaling = &v1alpha1.Autoscaling{ deployment.Spec.Autoscaling = &v1alpha1.Autoscaling{
Enabled: false, Enabled: false,
} }
...@@ -368,6 +370,12 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD ...@@ -368,6 +370,12 @@ func GenerateDynamoComponentsDeployments(ctx context.Context, parentDynamoGraphD
deployment.Spec.Autoscaling.MinReplicas = service.Config.Autoscaling.MinReplicas deployment.Spec.Autoscaling.MinReplicas = service.Config.Autoscaling.MinReplicas
deployment.Spec.Autoscaling.MaxReplicas = service.Config.Autoscaling.MaxReplicas deployment.Spec.Autoscaling.MaxReplicas = service.Config.Autoscaling.MaxReplicas
} }
// Override properties from the ExtraPodSpec (i.e. command and args) if provided.
if err := mergeExtraPodSpec(deployment, &service.Config); err != nil {
return nil, err
}
// override the component config with the component config that is in the parent deployment // override the component config with the component config that is in the parent deployment
if configOverride, ok := parentDynamoGraphDeployment.Spec.Services[service.Name]; ok { if configOverride, ok := parentDynamoGraphDeployment.Spec.Services[service.Name]; ok {
err := mergo.Merge(&deployment.Spec.DynamoComponentDeploymentSharedSpec, configOverride.DynamoComponentDeploymentSharedSpec, mergo.WithOverride) err := mergo.Merge(&deployment.Spec.DynamoComponentDeploymentSharedSpec, configOverride.DynamoComponentDeploymentSharedSpec, mergo.WithOverride)
...@@ -527,3 +535,22 @@ func mergeEnvs(common, specific []corev1.EnvVar) []corev1.EnvVar { ...@@ -527,3 +535,22 @@ func mergeEnvs(common, specific []corev1.EnvVar) []corev1.EnvVar {
} }
return merged return merged
} }
// mergeExtraPodSpec merges the ExtraPodSpec from service config into the deployment spec
func mergeExtraPodSpec(deployment *v1alpha1.DynamoComponentDeployment, serviceConfig *Config) error {
if serviceConfig.ExtraPodSpec != nil && serviceConfig.ExtraPodSpec.MainContainer != nil {
if deployment.Spec.DynamoComponentDeploymentSharedSpec.ExtraPodSpec == nil {
deployment.Spec.DynamoComponentDeploymentSharedSpec.ExtraPodSpec = new(common.ExtraPodSpec)
}
err := mergo.Merge(
deployment.Spec.DynamoComponentDeploymentSharedSpec.ExtraPodSpec,
serviceConfig.ExtraPodSpec,
mergo.WithOverride,
mergo.WithOverwriteWithEmptyValue,
)
if err != nil {
return err
}
}
return nil
}
...@@ -1402,6 +1402,69 @@ func TestGenerateDynamoComponentsDeployments(t *testing.T) { ...@@ -1402,6 +1402,69 @@ func TestGenerateDynamoComponentsDeployments(t *testing.T) {
}, },
wantErr: false, wantErr: false,
}, },
{
name: "Test GenerateDynamoComponentsDeployments with ExtraPodSpec.MainContainer Command and Args",
args: args{
parentDynamoGraphDeployment: &v1alpha1.DynamoGraphDeployment{
ObjectMeta: metav1.ObjectMeta{
Name: "test-dynamographdeployment",
Namespace: "default",
},
Spec: v1alpha1.DynamoGraphDeploymentSpec{
DynamoGraph: "dynamocomponent:ac4e234",
},
},
config: &DynamoGraphConfig{
DynamoTag: "dynamocomponent:MyServiceWithOverrides",
Services: []ServiceConfig{
{
Name: "service1",
Dependencies: []map[string]string{},
Config: Config{
ExtraPodSpec: &compounaiCommon.ExtraPodSpec{
MainContainer: &corev1.Container{
Command: []string{"sh", "-c"},
Args: []string{"echo hello world", "sleep 99999"},
},
},
},
},
},
},
ingressSpec: &v1alpha1.IngressSpec{},
},
want: map[string]*v1alpha1.DynamoComponentDeployment{
"service1": {
ObjectMeta: metav1.ObjectMeta{
Name: "test-dynamographdeployment-service1",
Namespace: "default",
Labels: map[string]string{
commonconsts.KubeLabelDynamoComponent: "service1",
},
},
Spec: v1alpha1.DynamoComponentDeploymentSpec{
DynamoComponent: "dynamocomponent:ac4e234",
DynamoTag: "dynamocomponent:MyServiceWithOverrides",
DynamoComponentDeploymentSharedSpec: v1alpha1.DynamoComponentDeploymentSharedSpec{
ServiceName: "service1",
Autoscaling: &v1alpha1.Autoscaling{
Enabled: false,
},
Labels: map[string]string{
commonconsts.KubeLabelDynamoComponent: "service1",
},
ExtraPodSpec: &compounaiCommon.ExtraPodSpec{
MainContainer: &corev1.Container{
Command: []string{"sh", "-c"},
Args: []string{"echo hello world", "sleep 99999"},
},
},
},
},
},
},
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
......
...@@ -22,6 +22,7 @@ import importlib.util ...@@ -22,6 +22,7 @@ import importlib.util
import inspect import inspect
import logging import logging
import os import os
import shlex
import shutil import shutil
import subprocess import subprocess
import sys import sys
...@@ -42,6 +43,7 @@ from dynamo.sdk.core.protocol.deployment import Service ...@@ -42,6 +43,7 @@ from dynamo.sdk.core.protocol.deployment import Service
from dynamo.sdk.core.protocol.interface import ( from dynamo.sdk.core.protocol.interface import (
DynamoConfig, DynamoConfig,
DynamoTransport, DynamoTransport,
KubernetesOverrides,
LinkedServices, LinkedServices,
ServiceInterface, ServiceInterface,
) )
...@@ -110,6 +112,7 @@ class ServiceConfig(BaseModel): ...@@ -110,6 +112,7 @@ class ServiceConfig(BaseModel):
dynamo: DynamoConfig = Field(default_factory=DynamoConfig) dynamo: DynamoConfig = Field(default_factory=DynamoConfig)
http_exposed: bool = False http_exposed: bool = False
api_endpoints: t.List[str] = Field(default_factory=list) api_endpoints: t.List[str] = Field(default_factory=list)
kubernetes_overrides: KubernetesOverrides | None = None
class ServiceInfo(BaseModel): class ServiceInfo(BaseModel):
...@@ -147,6 +150,7 @@ class ServiceInfo(BaseModel): ...@@ -147,6 +150,7 @@ class ServiceInfo(BaseModel):
dynamo=DynamoConfig(**service.config.dynamo.model_dump()), dynamo=DynamoConfig(**service.config.dynamo.model_dump()),
http_exposed=len(api_endpoints) > 0, http_exposed=len(api_endpoints) > 0,
api_endpoints=api_endpoints, api_endpoints=api_endpoints,
kubernetes_overrides=service.config.kubernetes_overrides,
) )
return cls( return cls(
...@@ -221,6 +225,33 @@ class ManifestInfo(BaseModel): ...@@ -221,6 +225,33 @@ class ManifestInfo(BaseModel):
"dynamo": service["config"]["dynamo"], "dynamo": service["config"]["dynamo"],
}, },
} }
# Add kubernetes_overrides if present.
if (
"kubernetes_overrides" in service["config"]
and service["config"]["kubernetes_overrides"]
):
# Map kubernetes_overrides fields to mainContainer if present.
kube_overrides = service["config"]["kubernetes_overrides"]
main_container = {}
entrypoint = kube_overrides.get("entrypoint")
if entrypoint:
if isinstance(entrypoint, str):
main_container["command"] = shlex.split(entrypoint)
else:
main_container["command"] = shlex.split(entrypoint[0])
cmd = kube_overrides.get("cmd")
if cmd:
if isinstance(cmd, str):
main_container["args"] = shlex.split(cmd)
else:
main_container["args"] = shlex.split(cmd[0])
if main_container:
service_dict["config"]["extraPodSpec"] = {
"mainContainer": main_container
}
# Add HTTP configuration if exposed # Add HTTP configuration if exposed
if service["config"]["http_exposed"]: if service["config"]["http_exposed"]:
...@@ -309,6 +340,7 @@ class Package: ...@@ -309,6 +340,7 @@ class Package:
build_ctx: str, build_ctx: str,
version: t.Optional[str] = None, version: t.Optional[str] = None,
) -> Package: ) -> Package:
"""Create a package from a build config."""
dyn_svc = cls.dynamo_service(build_config, build_ctx) dyn_svc = cls.dynamo_service(build_config, build_ctx)
# Get service name for package # Get service name for package
......
...@@ -21,7 +21,7 @@ from enum import Enum, auto ...@@ -21,7 +21,7 @@ from enum import Enum, auto
from typing import Any, Dict, Generic, List, Optional, Set, Tuple, Type, TypeVar from typing import Any, Dict, Generic, List, Optional, Set, Tuple, Type, TypeVar
from fastapi import FastAPI from fastapi import FastAPI
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field, field_validator
from .deployment import Env from .deployment import Env
...@@ -74,6 +74,24 @@ class ResourceConfig(BaseModel): ...@@ -74,6 +74,24 @@ class ResourceConfig(BaseModel):
gpu: str = Field(default="0") gpu: str = Field(default="0")
class KubernetesOverrides(BaseModel):
"""Class for kubernetes overrides from the sdk to limit to supported fields."""
model_config = ConfigDict(extra="forbid")
entrypoint: List[str] | None = None
cmd: List[str] | None = None
@field_validator("entrypoint", "cmd", mode="before")
@classmethod
def _coerce_str_to_list(cls, v):
if v is None or isinstance(v, list):
return v
if isinstance(v, str):
return [v]
raise TypeError("Must be str or list[str]")
class ServiceConfig(BaseModel): class ServiceConfig(BaseModel):
"""Base service configuration that can be extended by adapters""" """Base service configuration that can be extended by adapters"""
...@@ -83,6 +101,7 @@ class ServiceConfig(BaseModel): ...@@ -83,6 +101,7 @@ class ServiceConfig(BaseModel):
image: str | None = None image: str | None = None
envs: List[Env] | None = None envs: List[Env] | None = None
labels: Dict[str, str] | None = None labels: Dict[str, str] | None = None
kubernetes_overrides: KubernetesOverrides | None = None
class DynamoEndpointInterface(ABC): class DynamoEndpointInterface(ABC):
......
...@@ -95,12 +95,19 @@ This example can be deployed to a Kubernetes cluster using [Dynamo Cloud](../../ ...@@ -95,12 +95,19 @@ This example can be deployed to a Kubernetes cluster using [Dynamo Cloud](../../
You must have first followed the instructions in [deploy/cloud/helm/README.md](https://github.com/ai-dynamo/dynamo/blob/main/deploy/cloud/helm/README.md) to create your Dynamo cloud deployment. You must have first followed the instructions in [deploy/cloud/helm/README.md](https://github.com/ai-dynamo/dynamo/blob/main/deploy/cloud/helm/README.md) to create your Dynamo cloud deployment.
### Deployment Steps ### Deployment Steps For your Hello World graph.
For detailed deployment instructions, please refer to the [Operator Deployment Guide](../../docs/guides/dynamo_deploy/operator_deployment.md). The following are the specific commands for the hello world example: For detailed deployment instructions, please refer to the [Operator Deployment Guide](../../docs/guides/dynamo_deploy/operator_deployment.md). The following are the specific commands for the hello world example:
```bash ```bash
# Set your project root directory Make sure your dynamo cloud deploy.sh script from the prior step finished successfully and setup port forwaring in another window
per its suggestion.
kubectl port-forward svc/...-dynamo-api-store <local-port>:80 -n $NAMESPACE
# Set your dynamo root directory
cd <root dynamo folder>
export PROJECT_ROOT=$(pwd) export PROJECT_ROOT=$(pwd)
# Configure environment variables (see operator_deployment.md for details) # Configure environment variables (see operator_deployment.md for details)
......
...@@ -125,6 +125,11 @@ class Middle: ...@@ -125,6 +125,11 @@ class Middle:
@service( @service(
dynamo={"namespace": "inference"}, dynamo={"namespace": "inference"},
image=DYNAMO_IMAGE, image=DYNAMO_IMAGE,
# Example of kubernetes overrides if needed.
# kubernetes_overrides={
# "entrypoint": ["sh -c"],
# "cmd": ["echo hello from FrontEnd!"],
# },
) )
class Frontend: class Frontend:
"""A simple frontend HTTP API that forwards requests to the dynamo graph.""" """A simple frontend HTTP API that forwards requests to the dynamo graph."""
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment