Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ It also optionally collects each pull attempt's duration and result.
This image pull secret should be usable for all images fetched by the given instance.
If provided, it must be of type `kubernetes.io/dockerconfigjson` and exist in the same namespace.
- `--collect-metrics`: if the image pull metrics should be collected.
- `--use-kubelet-image-credential-integration=MODE`: enables kubelet [credential provider](https://kubernetes.io/blog/2022/12/22/kubelet-credential-providers/) plugin integration.
Plugin credentials fetched dynamically and tried for the images configured in the `CredentialProviderConfig` before pull secrets.
Currently only supports mode `GKE`, which uses `/etc/srv/kubernetes/cri_auth_config.yaml` and `/home/kubernetes/bin` mounted from the host.

Example:

Expand Down
26 changes: 15 additions & 11 deletions cmd/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,23 @@ It talks to Container Runtime Interface API to pull images in parallel, with ret
return err
}
imageList = append(imageList, args...)
return internal.Run(logger, criSocket, dockerConfigJSONPath, timing, metricsEndpoint, imageList...)
return internal.Run(logger, criSocket, dockerConfigJSONPath, imageCredentialProviderConfig, imageCredentialProviderBinDir, timing, metricsEndpoint, imageList...)
},
}

var (
criSocket string
dockerConfigJSONPath string
imageListFile string
metricsEndpoint string
imageListTimeout = time.Minute
initialPullAttemptTimeout = 30 * time.Second
maxPullAttemptTimeout = 5 * time.Minute
overallTimeout = 20 * time.Minute
initialPullAttemptDelay = time.Second
maxPullAttemptDelay = 10 * time.Minute
criSocket string
dockerConfigJSONPath string
imageListFile string
metricsEndpoint string
imageCredentialProviderConfig string
imageCredentialProviderBinDir string
imageListTimeout = time.Minute
initialPullAttemptTimeout = 30 * time.Second
maxPullAttemptTimeout = 5 * time.Minute
overallTimeout = 20 * time.Minute
initialPullAttemptDelay = time.Second
maxPullAttemptDelay = 10 * time.Minute
)

func init() {
Expand All @@ -58,6 +60,8 @@ func init() {
fetchCmd.Flags().StringVar(&dockerConfigJSONPath, "docker-config", "", "Path to docker config json file.")
fetchCmd.Flags().StringVar(&imageListFile, "image-list-file", "", "Path to text file containing images to pull (one per line).")
fetchCmd.Flags().StringVar(&metricsEndpoint, "metrics-endpoint", "", "A host:port to submit image pull metrics to.")
fetchCmd.Flags().StringVar(&imageCredentialProviderConfig, "image-credential-provider-config", "", "Path to credential provider plugin config file.")
fetchCmd.Flags().StringVar(&imageCredentialProviderBinDir, "image-credential-provider-bin-dir", "", "Path to credential provider plugin binary directory.")

fetchCmd.Flags().DurationVar(&imageListTimeout, "image-list-timeout", imageListTimeout, "Timeout for image list calls (for debugging).")
fetchCmd.Flags().DurationVar(&initialPullAttemptTimeout, "initial-pull-attempt-timeout", initialPullAttemptTimeout, "Timeout for initial image pull call. Each subsequent attempt doubles it until max.")
Expand Down
22 changes: 22 additions & 0 deletions deploy/deployment.yaml.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ spec:
{{ if .CollectMetrics }}
- "--metrics-endpoint={{ .Name }}-metrics:8443"
{{ end }}
{{ if eq .UseKubeletImageCredentialIntegration "GKE" }}
- "--image-credential-provider-config=/tmp/credential-provider/cri_auth_config.yaml"
- "--image-credential-provider-bin-dir=/tmp/credential-provider-bin"
{{ end }}
env:
- name: NODE_NAME
valueFrom:
Expand All @@ -189,6 +193,14 @@ spec:
name: pull-secret
readOnly: true
{{ end }}
{{ if eq .UseKubeletImageCredentialIntegration "GKE" }}
- mountPath: /tmp/credential-provider
name: credential-provider-config
readOnly: true
- mountPath: /tmp/credential-provider-bin
name: credential-provider-bin
readOnly: true
{{ end }}
securityContext:
readOnlyRootFilesystem: true
{{ if .NeedsPrivileged }}
Expand Down Expand Up @@ -225,3 +237,13 @@ spec:
secret:
secretName: {{ .Secret }}
{{ end }}
{{ if eq .UseKubeletImageCredentialIntegration "GKE" }}
- name: credential-provider-config
hostPath:
path: /etc/srv/kubernetes
type: Directory
- name: credential-provider-bin
hostPath:
path: /home/kubernetes/bin
type: Directory
{{ end }}
46 changes: 25 additions & 21 deletions deploy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import (
)

type settings struct {
Name string
Namespace string
Image string
Version string
Secret string
IsCRIO bool
NeedsPrivileged bool
CollectMetrics bool
Name string
Namespace string
Image string
Version string
Secret string
IsCRIO bool
NeedsPrivileged bool
CollectMetrics bool
UseKubeletImageCredentialIntegration string
}

const (
Expand All @@ -35,11 +36,12 @@ const imageRepo = "quay.io/stackrox-io/image-prefetcher"
var deploymentTemplate string

var (
version string
namespace string
k8sFlavor k8sFlavorType
secret string
collectMetrics bool
version string
namespace string
k8sFlavor k8sFlavorType
secret string
collectMetrics bool
useKubeletImageCredentialIntegration string
)

func init() {
Expand All @@ -48,6 +50,7 @@ func init() {
flag.TextVar(&k8sFlavor, "k8s-flavor", flavor(vanillaFlavor), fmt.Sprintf("Kubernetes flavor. Accepted values: %s", strings.Join(allFlavors, ",")))
flag.StringVar(&secret, "secret", "", "Kubernetes image pull Secret to use when pulling.")
flag.BoolVar(&collectMetrics, "collect-metrics", false, "Whether to collect and expose image pull metrics.")
flag.StringVar(&useKubeletImageCredentialIntegration, "use-kubelet-image-credential-integration", "", "Enable kubelet image credential provider plugin integration. Accepted values: GKE")
}

// processVersion processes the version string and returns the appropriate format.
Expand Down Expand Up @@ -78,14 +81,15 @@ func main() {
isOcp := k8sFlavor == ocpFlavor

s := settings{
Name: name,
Namespace: namespace,
Image: imageRepo,
Version: processVersion(version),
Secret: secret,
IsCRIO: isOcp,
NeedsPrivileged: isOcp,
CollectMetrics: collectMetrics,
Name: name,
Namespace: namespace,
Image: imageRepo,
Version: processVersion(version),
Secret: secret,
IsCRIO: isOcp,
NeedsPrivileged: isOcp,
CollectMetrics: collectMetrics,
UseKubeletImageCredentialIntegration: useKubeletImageCredentialIntegration,
}
tmpl := template.Must(template.New("deployment").Parse(deploymentTemplate))
if err := tmpl.Execute(os.Stdout, s); err != nil {
Expand Down
17 changes: 15 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,38 @@ require (
k8s.io/client-go v0.35.3
k8s.io/cri-api v0.35.3
k8s.io/klog/v2 v2.140.0
k8s.io/kubelet v0.35.3
k8s.io/kubernetes v1.35.3
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.49.0 // indirect
Expand All @@ -48,6 +60,7 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/component-base v0.35.3 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
Expand Down
30 changes: 26 additions & 4 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions internal/credentialprovider/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
// Therefore we have copy of the functionality necessary to use pull secrets the same way as kubernetes does.
// The files we copied do not change often upstream, but ideally we should check for changes every kubernetes release
// and update the permalink above to reflect the latest sync point.
//
// The file plugin.go contains a simplified plugin-based credential provider.
// See https://github.com/kubernetes/kubernetes/blob/master/pkg/credentialprovider/plugin/plugin.go
package credentialprovider
Loading
Loading