diff --git a/deploy/cloud/operator/internal/consts/consts.go b/deploy/cloud/operator/internal/consts/consts.go index fdb2172339..725eb56b1f 100644 --- a/deploy/cloud/operator/internal/consts/consts.go +++ b/deploy/cloud/operator/internal/consts/consts.go @@ -30,6 +30,8 @@ const ( KubeAnnotationEnableGrove = "nvidia.com/enable-grove" + KubeAnnotationDisableImagePullSecretDiscovery = "nvidia.com/disable-image-pull-secret-discovery" + KubeLabelDynamoGraphDeploymentName = "nvidia.com/dynamo-graph-deployment-name" KubeLabelDynamoComponent = "nvidia.com/dynamo-component" KubeLabelDynamoNamespace = "nvidia.com/dynamo-namespace" diff --git a/deploy/cloud/operator/internal/dynamo/graph.go b/deploy/cloud/operator/internal/dynamo/graph.go index 3ffc500bc5..ccf0808bf3 100644 --- a/deploy/cloud/operator/internal/dynamo/graph.go +++ b/deploy/cloud/operator/internal/dynamo/graph.go @@ -765,8 +765,10 @@ func GenerateBasePodSpec( maps.Copy(container.Resources.Limits, overrideResources.Limits) } + shouldDisableImagePullSecret := component.Annotations[commonconsts.KubeAnnotationDisableImagePullSecretDiscovery] == commonconsts.KubeLabelValueTrue + imagePullSecrets := []corev1.LocalObjectReference{} - if secretsRetriever != nil && component.ExtraPodSpec != nil && component.ExtraPodSpec.MainContainer != nil && component.ExtraPodSpec.MainContainer.Image != "" { + if !shouldDisableImagePullSecret && secretsRetriever != nil && component.ExtraPodSpec != nil && component.ExtraPodSpec.MainContainer != nil && component.ExtraPodSpec.MainContainer.Image != "" { secretsName, err := secretsRetriever.GetSecrets(namespace, component.ExtraPodSpec.MainContainer.Image) if err == nil { for _, secretName := range secretsName { diff --git a/deploy/cloud/operator/internal/dynamo/graph_test.go b/deploy/cloud/operator/internal/dynamo/graph_test.go index 56b98c7b97..87c6b413a8 100644 --- a/deploy/cloud/operator/internal/dynamo/graph_test.go +++ b/deploy/cloud/operator/internal/dynamo/graph_test.go @@ -3141,6 +3141,20 @@ func (m *mockSecretsRetriever) GetSecrets(namespace, registry string) ([]string, return []string{}, nil } +// Mock SecretsRetriever that returns secrets for testing docker secrets functionality +type mockSecretsRetrieverWithSecrets struct{} + +func (m *mockSecretsRetrieverWithSecrets) RetrieveImagePullSecrets(ctx context.Context, deployment *v1alpha1.DynamoGraphDeployment) ([]corev1.LocalObjectReference, error) { + return []corev1.LocalObjectReference{ + {Name: "test-docker-secret"}, + }, nil +} + +func (m *mockSecretsRetrieverWithSecrets) GetSecrets(namespace, registry string) ([]string, error) { + // Return some mock secrets when called + return []string{"test-docker-secret"}, nil +} + func TestGeneratePodSpecForComponent_SGLang(t *testing.T) { secretsRetriever := &mockSecretsRetriever{} dynamoDeployment := &v1alpha1.DynamoGraphDeployment{ @@ -4484,6 +4498,138 @@ func TestGenerateBasePodSpec_PlannerServiceAccount(t *testing.T) { } } +func TestGenerateBasePodSpec_DisableImagePullSecretDiscovery(t *testing.T) { + tests := []struct { + name string + component *v1alpha1.DynamoComponentDeploymentOverridesSpec + secretsRetriever SecretsRetriever + expectedImagePullSecrets []corev1.LocalObjectReference + }{ + { + name: "disable docker secrets annotation set to true", + component: &v1alpha1.DynamoComponentDeploymentOverridesSpec{ + DynamoComponentDeploymentSharedSpec: v1alpha1.DynamoComponentDeploymentSharedSpec{ + ComponentType: commonconsts.ComponentTypeFrontend, + Annotations: map[string]string{ + commonconsts.KubeAnnotationDisableImagePullSecretDiscovery: commonconsts.KubeLabelValueTrue, + }, + ExtraPodSpec: &common.ExtraPodSpec{ + MainContainer: &corev1.Container{ + Image: "test-registry/test-image:latest", + }, + }, + }, + }, + secretsRetriever: &mockSecretsRetrieverWithSecrets{}, + expectedImagePullSecrets: nil, // Should be nil when disabled + }, + { + name: "disable docker secrets annotation set to false", + component: &v1alpha1.DynamoComponentDeploymentOverridesSpec{ + DynamoComponentDeploymentSharedSpec: v1alpha1.DynamoComponentDeploymentSharedSpec{ + ComponentType: commonconsts.ComponentTypeFrontend, + Annotations: map[string]string{ + commonconsts.KubeAnnotationDisableImagePullSecretDiscovery: commonconsts.KubeLabelValueFalse, + }, + ExtraPodSpec: &common.ExtraPodSpec{ + MainContainer: &corev1.Container{ + Image: "test-registry/test-image:latest", + }, + }, + }, + }, + secretsRetriever: &mockSecretsRetrieverWithSecrets{}, + expectedImagePullSecrets: []corev1.LocalObjectReference{ + {Name: "test-docker-secret"}, + }, // Should be present when enabled + }, + { + name: "disable docker secrets annotation not set (default behavior)", + component: &v1alpha1.DynamoComponentDeploymentOverridesSpec{ + DynamoComponentDeploymentSharedSpec: v1alpha1.DynamoComponentDeploymentSharedSpec{ + ComponentType: commonconsts.ComponentTypeFrontend, + ExtraPodSpec: &common.ExtraPodSpec{ + MainContainer: &corev1.Container{ + Image: "test-registry/test-image:latest", + }, + }, + }, + }, + secretsRetriever: &mockSecretsRetrieverWithSecrets{}, + expectedImagePullSecrets: []corev1.LocalObjectReference{ + {Name: "test-docker-secret"}, + }, // Should be present by default + }, + { + name: "disable docker secrets annotation set to invalid value", + component: &v1alpha1.DynamoComponentDeploymentOverridesSpec{ + DynamoComponentDeploymentSharedSpec: v1alpha1.DynamoComponentDeploymentSharedSpec{ + ComponentType: commonconsts.ComponentTypeFrontend, + Annotations: map[string]string{ + commonconsts.KubeAnnotationDisableImagePullSecretDiscovery: "invalid", + }, + ExtraPodSpec: &common.ExtraPodSpec{ + MainContainer: &corev1.Container{ + Image: "test-registry/test-image:latest", + }, + }, + }, + }, + secretsRetriever: &mockSecretsRetrieverWithSecrets{}, + expectedImagePullSecrets: []corev1.LocalObjectReference{ + {Name: "test-docker-secret"}, + }, // Should be present when annotation is not "true" + }, + { + name: "disable docker secrets but no secrets retriever", + component: &v1alpha1.DynamoComponentDeploymentOverridesSpec{ + DynamoComponentDeploymentSharedSpec: v1alpha1.DynamoComponentDeploymentSharedSpec{ + ComponentType: commonconsts.ComponentTypeFrontend, + Annotations: map[string]string{ + commonconsts.KubeAnnotationDisableImagePullSecretDiscovery: commonconsts.KubeLabelValueFalse, + }, + ExtraPodSpec: &common.ExtraPodSpec{ + MainContainer: &corev1.Container{ + Image: "test-registry/test-image:latest", + }, + }, + }, + }, + secretsRetriever: nil, + expectedImagePullSecrets: nil, // Should be nil when no retriever + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + controllerConfig := controller_common.Config{} + + podSpec, err := GenerateBasePodSpec( + tt.component, + BackendFrameworkNoop, + tt.secretsRetriever, + "test-deployment", + "default", + RoleMain, + 1, + controllerConfig, + commonconsts.MultinodeDeploymentTypeGrove, + "test-service", + ) + + if err != nil { + t.Errorf("GenerateBasePodSpec() error = %v", err) + return + } + + if !reflect.DeepEqual(podSpec.ImagePullSecrets, tt.expectedImagePullSecrets) { + t.Errorf("GenerateBasePodSpec() ImagePullSecrets = %v, want %v", + podSpec.ImagePullSecrets, tt.expectedImagePullSecrets) + } + }) + } +} + func TestGenerateBasePodSpec_Worker(t *testing.T) { secretsRetriever := &mockSecretsRetriever{} controllerConfig := controller_common.Config{} diff --git a/docs/guides/dynamo_deploy/create_deployment.md b/docs/guides/dynamo_deploy/create_deployment.md index a34865314c..99a446df78 100644 --- a/docs/guides/dynamo_deploy/create_deployment.md +++ b/docs/guides/dynamo_deploy/create_deployment.md @@ -130,4 +130,38 @@ If you are a Dynamo contributor the [dynamo run guide](../dynamo_run.md) for det ```yaml args: - --is-prefill-worker # For disaggregated prefill workers -``` \ No newline at end of file +``` + +### Image Pull Secret Configuration + +#### Automatic Discovery and Injection + +By default, the Dynamo operator automatically discovers and injects image pull secrets based on container registry host matching. The operator scans Docker config secrets within the same namespace and matches their registry hostnames to the container image URLs, automatically injecting the appropriate secrets into the pod's `imagePullSecrets`. + +**Disabling Automatic Discovery:** +To disable this behavior for a component and manually control image pull secrets: + +```yaml + YourWorker: + dynamoNamespace: your-namespace + componentType: worker + annotations: + nvidia.com/disable-image-pull-secret-discovery: "true" +``` + +When disabled, you can manually specify secrets as you would for a normal pod spec via: +```yaml + YourWorker: + dynamoNamespace: your-namespace + componentType: worker + annotations: + nvidia.com/disable-image-pull-secret-discovery: "true" + extraPodSpec: + imagePullSecrets: + - name: my-registry-secret + - name: another-secret + mainContainer: + image: your-image +``` + +This automatic discovery eliminates the need to manually configure image pull secrets for each deployment. \ No newline at end of file