Skip to content

Commit cb7468b

Browse files
committed
kubelet imagepuller: PullImage() - accept TrackAuthConfigs directly
The image puller's PullImage() method should be just a dumb pull without any further logic. Make it accept everything it needs to pull an image and defer any other magic to the image manager.
1 parent 09284d9 commit cb7468b

File tree

13 files changed

+193
-150
lines changed

13 files changed

+193
-150
lines changed

pkg/credentialprovider/plugin/plugins.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,12 @@ func NewExternalCredentialProviderDockerKeyring(podNamespace, podName, podUID, s
8787
return keyring
8888
}
8989

90-
func (k *externalCredentialProviderKeyring) Lookup(image string) ([]credentialprovider.AuthConfig, bool) {
90+
func (k *externalCredentialProviderKeyring) Lookup(image string) ([]credentialprovider.TrackedAuthConfig, bool) {
9191
keyring := &credentialprovider.BasicDockerKeyring{}
9292

9393
for _, p := range k.providers {
94-
keyring.Add(p.Provide(image))
94+
// TODO: modify the credentialprovider.CredentialSource to contain the SA/pod information
95+
keyring.Add(nil, p.Provide(image))
9596
}
9697

9798
return keyring.Lookup(image)

pkg/kubelet/container/runtime.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"k8s.io/client-go/util/flowcontrol"
3434
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
3535
"k8s.io/klog/v2"
36+
"k8s.io/kubernetes/pkg/credentialprovider"
3637
"k8s.io/kubernetes/pkg/volume"
3738
)
3839

@@ -151,8 +152,11 @@ type StreamingRuntime interface {
151152
// ImageService interfaces allows to work with image service.
152153
type ImageService interface {
153154
// PullImage pulls an image from the network to local storage using the supplied
154-
// secrets if necessary. It returns a reference (digest or ID) to the pulled image.
155-
PullImage(ctx context.Context, image ImageSpec, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig, serviceAccountName string) (string, error)
155+
// secrets if necessary.
156+
// It returns a reference (digest or ID) to the pulled image and the credentials
157+
// that were used to pull the image. If the returned credentials are nil, the
158+
// pull was anonymous.
159+
PullImage(ctx context.Context, image ImageSpec, credentials []credentialprovider.TrackedAuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, *credentialprovider.TrackedAuthConfig, error)
156160
// GetImageRef gets the reference (digest or ID) of the image which has already been in
157161
// the local storage. It returns ("", nil) if the image isn't in the local storage.
158162
GetImageRef(ctx context.Context, image ImageSpec) (string, error)

pkg/kubelet/container/testing/fake_runtime.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/types"
2929
"k8s.io/client-go/util/flowcontrol"
3030
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
31+
"k8s.io/kubernetes/pkg/credentialprovider"
3132
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
3233
"k8s.io/kubernetes/pkg/volume"
3334
)
@@ -308,7 +309,7 @@ func (f *FakeRuntime) GetContainerLogs(_ context.Context, pod *v1.Pod, container
308309
return f.Err
309310
}
310311

311-
func (f *FakeRuntime) PullImage(ctx context.Context, image kubecontainer.ImageSpec, pullSecrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig, serviceAccountName string) (string, error) {
312+
func (f *FakeRuntime) PullImage(ctx context.Context, image kubecontainer.ImageSpec, creds []credentialprovider.TrackedAuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, *credentialprovider.TrackedAuthConfig, error) {
312313
f.Lock()
313314
f.CalledFunctions = append(f.CalledFunctions, "PullImage")
314315
if f.Err == nil {
@@ -319,9 +320,15 @@ func (f *FakeRuntime) PullImage(ctx context.Context, image kubecontainer.ImageSp
319320
f.ImageList = append(f.ImageList, i)
320321
}
321322

323+
// if credentials were supplied for the pull at least return the first in the list
324+
var retCreds *credentialprovider.TrackedAuthConfig = nil
325+
if len(creds) > 0 {
326+
retCreds = &creds[0]
327+
}
328+
322329
if !f.BlockImagePulls {
323330
f.Unlock()
324-
return image.Image, f.Err
331+
return image.Image, retCreds, f.Err
325332
}
326333

327334
retErr := f.Err
@@ -334,7 +341,8 @@ func (f *FakeRuntime) PullImage(ctx context.Context, image kubecontainer.ImageSp
334341
case <-ctx.Done():
335342
case <-f.imagePullTokenBucket:
336343
}
337-
return image.Image, retErr
344+
345+
return image.Image, retCreds, retErr
338346
}
339347

340348
// UnblockImagePulls unblocks a certain number of image pulls, if BlockImagePulls is true.

pkg/kubelet/container/testing/runtime_mock.go

Lines changed: 31 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/kubelet/images/helpers.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import (
2020
"context"
2121
"fmt"
2222

23-
v1 "k8s.io/api/core/v1"
2423
"k8s.io/client-go/util/flowcontrol"
2524
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
25+
"k8s.io/kubernetes/pkg/credentialprovider"
2626
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
2727
)
2828

@@ -44,9 +44,9 @@ type throttledImageService struct {
4444
limiter flowcontrol.RateLimiter
4545
}
4646

47-
func (ts throttledImageService) PullImage(ctx context.Context, image kubecontainer.ImageSpec, secrets []v1.Secret, podSandboxConfig *runtimeapi.PodSandboxConfig, serviceAccountName string) (string, error) {
47+
func (ts throttledImageService) PullImage(ctx context.Context, image kubecontainer.ImageSpec, credentials []credentialprovider.TrackedAuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (string, *credentialprovider.TrackedAuthConfig, error) {
4848
if ts.limiter.TryAccept() {
49-
return ts.ImageService.PullImage(ctx, image, secrets, podSandboxConfig, serviceAccountName)
49+
return ts.ImageService.PullImage(ctx, image, credentials, podSandboxConfig)
5050
}
51-
return "", fmt.Errorf("pull QPS exceeded")
51+
return "", nil, fmt.Errorf("pull QPS exceeded")
5252
}

pkg/kubelet/images/image_manager.go

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@ import (
2525

2626
v1 "k8s.io/api/core/v1"
2727
"k8s.io/apimachinery/pkg/types"
28+
utilfeature "k8s.io/apiserver/pkg/util/feature"
2829
"k8s.io/client-go/tools/record"
2930
"k8s.io/client-go/util/flowcontrol"
3031
"k8s.io/klog/v2"
3132

3233
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
3334
crierrors "k8s.io/cri-api/pkg/errors"
35+
"k8s.io/kubernetes/pkg/credentialprovider"
36+
credentialproviderplugin "k8s.io/kubernetes/pkg/credentialprovider/plugin"
37+
credentialprovidersecrets "k8s.io/kubernetes/pkg/credentialprovider/secrets"
38+
"k8s.io/kubernetes/pkg/features"
3439
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
3540
"k8s.io/kubernetes/pkg/kubelet/events"
3641
"k8s.io/kubernetes/pkg/kubelet/metrics"
@@ -50,15 +55,16 @@ type imageManager struct {
5055
prevPullErrMsg sync.Map
5156

5257
// It will check the presence of the image, and report the 'image pulling', image pulled' events correspondingly.
53-
puller imagePuller
58+
puller imagePuller
59+
nodeKeyring credentialprovider.DockerKeyring
5460

5561
podPullingTimeRecorder ImagePodPullingTimeRecorder
5662
}
5763

5864
var _ ImageManager = &imageManager{}
5965

6066
// NewImageManager instantiates a new ImageManager object.
61-
func NewImageManager(recorder record.EventRecorder, imageService kubecontainer.ImageService, imageBackOff *flowcontrol.Backoff, serialized bool, maxParallelImagePulls *int32, qps float32, burst int, podPullingTimeRecorder ImagePodPullingTimeRecorder) ImageManager {
67+
func NewImageManager(recorder record.EventRecorder, nodeKeyring credentialprovider.DockerKeyring, imageService kubecontainer.ImageService, imageBackOff *flowcontrol.Backoff, serialized bool, maxParallelImagePulls *int32, qps float32, burst int, podPullingTimeRecorder ImagePodPullingTimeRecorder) ImageManager {
6268
imageService = throttleImagePulling(imageService, qps, burst)
6369

6470
var puller imagePuller
@@ -70,6 +76,7 @@ func NewImageManager(recorder record.EventRecorder, imageService kubecontainer.I
7076
return &imageManager{
7177
recorder: recorder,
7278
imageService: imageService,
79+
nodeKeyring: nodeKeyring,
7380
backOff: imageBackOff,
7481
puller: puller,
7582
podPullingTimeRecorder: podPullingTimeRecorder,
@@ -153,7 +160,39 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, objRef *v1.ObjectR
153160
return imageRef, msg, nil
154161
}
155162

156-
backOffKey := fmt.Sprintf("%s_%s", pod.UID, imgRef)
163+
img := spec.Image
164+
repoToPull, _, _, err := parsers.ParseImageName(img)
165+
if err != nil {
166+
return "", err.Error(), err
167+
}
168+
169+
// construct the dynamic keyring using the providers we have in the kubelet
170+
var podName, podNamespace, podUID string
171+
if utilfeature.DefaultFeatureGate.Enabled(features.KubeletServiceAccountTokenForCredentialProviders) {
172+
sandboxMetadata := podSandboxConfig.GetMetadata()
173+
174+
podName = sandboxMetadata.Name
175+
podNamespace = sandboxMetadata.Namespace
176+
podUID = sandboxMetadata.Uid
177+
}
178+
179+
externalCredentialProviderKeyring := credentialproviderplugin.NewExternalCredentialProviderDockerKeyring(
180+
podNamespace,
181+
podName,
182+
podUID,
183+
pod.Spec.ServiceAccountName)
184+
185+
keyring, err := credentialprovidersecrets.MakeDockerKeyring(pullSecrets, credentialprovider.UnionDockerKeyring{m.nodeKeyring, externalCredentialProviderKeyring})
186+
if err != nil {
187+
return "", err.Error(), err
188+
}
189+
190+
pullCredentials, _ := keyring.Lookup(repoToPull)
191+
return m.pullImage(ctx, logPrefix, objRef, pod.UID, imgRef, spec, pullCredentials, podSandboxConfig)
192+
}
193+
194+
func (m *imageManager) pullImage(ctx context.Context, logPrefix string, objRef *v1.ObjectReference, podUID types.UID, imgRef string, imgSpec kubecontainer.ImageSpec, pullCredentials []credentialprovider.TrackedAuthConfig, podSandboxConfig *runtimeapi.PodSandboxConfig) (imageRef, message string, err error) {
195+
backOffKey := fmt.Sprintf("%s_%s", podUID, imgRef)
157196
if m.backOff.IsInBackOffSinceUpdate(backOffKey, m.backOff.Clock.Now()) {
158197
msg := fmt.Sprintf("Back-off pulling image %q", imgRef)
159198
m.logIt(objRef, v1.EventTypeNormal, events.BackOffPullImage, logPrefix, msg, klog.Info)
@@ -171,16 +210,16 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, objRef *v1.ObjectR
171210
// Ensure that the map cannot grow indefinitely.
172211
m.prevPullErrMsg.Delete(backOffKey)
173212

174-
m.podPullingTimeRecorder.RecordImageStartedPulling(pod.UID)
213+
m.podPullingTimeRecorder.RecordImageStartedPulling(podUID)
175214
m.logIt(objRef, v1.EventTypeNormal, events.PullingImage, logPrefix, fmt.Sprintf("Pulling image %q", imgRef), klog.Info)
176215
startTime := time.Now()
216+
177217
pullChan := make(chan pullResult)
178-
m.puller.pullImage(ctx, spec, pullSecrets, pullChan, podSandboxConfig, pod.Spec.ServiceAccountName)
218+
m.puller.pullImage(ctx, imgSpec, pullCredentials, pullChan, podSandboxConfig)
179219
imagePullResult := <-pullChan
180220
if imagePullResult.err != nil {
181221
m.logIt(objRef, v1.EventTypeWarning, events.FailedToPullImage, logPrefix, fmt.Sprintf("Failed to pull image %q: %v", imgRef, imagePullResult.err), klog.Warning)
182222
m.backOff.Next(backOffKey, m.backOff.Clock.Now())
183-
184223
msg, err := evalCRIPullErr(imgRef, imagePullResult.err)
185224

186225
// Store the actual pull error for providing that information during
@@ -189,12 +228,13 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, objRef *v1.ObjectR
189228

190229
return "", msg, err
191230
}
192-
m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID)
231+
m.podPullingTimeRecorder.RecordImageFinishedPulling(podUID)
193232
imagePullDuration := time.Since(startTime).Truncate(time.Millisecond)
194233
m.logIt(objRef, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting). Image size: %v bytes.",
195234
imgRef, imagePullResult.pullDuration.Truncate(time.Millisecond), imagePullDuration, imagePullResult.imageSize), klog.Info)
196235
metrics.ImagePullDuration.WithLabelValues(metrics.GetImageSizeBucket(imagePullResult.imageSize)).Observe(imagePullDuration.Seconds())
197236
m.backOff.GC()
237+
198238
return imagePullResult.imageRef, "", nil
199239
}
200240

pkg/kubelet/images/image_manager_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
featuregatetesting "k8s.io/component-base/featuregate/testing"
3636
crierrors "k8s.io/cri-api/pkg/errors"
3737
"k8s.io/kubernetes/pkg/controller/testutil"
38+
"k8s.io/kubernetes/pkg/credentialprovider"
3839
"k8s.io/kubernetes/pkg/features"
3940
. "k8s.io/kubernetes/pkg/kubelet/container"
4041
ctest "k8s.io/kubernetes/pkg/kubelet/container/testing"
@@ -380,7 +381,7 @@ func pullerTestEnv(t *testing.T, c pullerTestCase, serialized bool, maxParallelI
380381

381382
fakePodPullingTimeRecorder = &mockPodPullingTimeRecorder{}
382383

383-
puller = NewImageManager(fakeRecorder, fakeRuntime, backOff, serialized, maxParallelImagePulls, c.qps, c.burst, fakePodPullingTimeRecorder)
384+
puller = NewImageManager(fakeRecorder, &credentialprovider.BasicDockerKeyring{}, fakeRuntime, backOff, serialized, maxParallelImagePulls, c.qps, c.burst, fakePodPullingTimeRecorder)
384385
return
385386
}
386387

0 commit comments

Comments
 (0)