Skip to content

Commit 146515a

Browse files
committed
kubelet: pullmanager: write to pulled record if secret matches during query
1 parent 788b7ab commit 146515a

File tree

2 files changed

+98
-22
lines changed

2 files changed

+98
-22
lines changed

pkg/kubelet/images/pullmanager/image_pull_manager.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ import (
3434

3535
var _ ImagePullManager = &PullManager{}
3636

37+
// writeRecordWhileMatchingLimit is a limit at which we stop writing yet-uncached
38+
// records that we found when we were checking if an image pull must be attempted.
39+
// This is to prevent unbounded writes in cases of high namespace turnover.
40+
const writeRecordWhileMatchingLimit = 100
41+
3742
// PullManager is an implementation of the ImagePullManager. It
3843
// tracks images pulled by the kubelet by creating records about ongoing and
3944
// successful pulls.
@@ -256,16 +261,33 @@ func (f *PullManager) MustAttemptImagePull(image, imageRef string, podSecrets []
256261
for _, cachedSecret := range cachedCreds.KubernetesSecrets {
257262

258263
// we need to check hash len in case hashing failed while storing the record in the keyring
259-
if len(cachedSecret.CredentialHash) > 0 && podSecret.CredentialHash == cachedSecret.CredentialHash {
260-
// TODO: should we record the new secret in case it has different coordinates? If the secret rotates, we will pull unnecessarily otherwise
264+
hashesMatch := len(cachedSecret.CredentialHash) > 0 && podSecret.CredentialHash == cachedSecret.CredentialHash
265+
secretCoordinatesMatch := podSecret.UID == cachedSecret.UID &&
266+
podSecret.Namespace == cachedSecret.Namespace &&
267+
podSecret.Name == cachedSecret.Name
268+
269+
if hashesMatch {
270+
if !secretCoordinatesMatch && len(cachedCreds.KubernetesSecrets) < writeRecordWhileMatchingLimit {
271+
// While we're only matching at this point, we want to ensure this secret is considered valid in the future
272+
// and so we make an additional write to the cache.
273+
// writePulledRecord() is a noop in case the secret with the updated hash already appears in the cache.
274+
if err := f.writePulledRecordIfChanged(image, imageRef, &kubeletconfiginternal.ImagePullCredentials{KubernetesSecrets: []kubeletconfiginternal.ImagePullSecret{podSecret}}); err != nil {
275+
klog.ErrorS(err, "failed to write an image pulled record", "image", image, "imageRef", imageRef)
276+
}
277+
}
261278
return false
262279
}
263280

264-
if podSecret.UID == cachedSecret.UID &&
265-
podSecret.Namespace == cachedSecret.Namespace &&
266-
podSecret.Name == cachedSecret.Name {
267-
// TODO: should we record the new creds in this case so that we don't pull if these are present in a different secret?
268-
return false
281+
if secretCoordinatesMatch {
282+
if !hashesMatch && len(cachedCreds.KubernetesSecrets) < writeRecordWhileMatchingLimit {
283+
// While we're only matching at this point, we want to ensure the updated credentials are considered valid in the future
284+
// and so we make an additional write to the cache.
285+
// writePulledRecord() is a noop in case the hash got updated in the meantime.
286+
if err := f.writePulledRecordIfChanged(image, imageRef, &kubeletconfiginternal.ImagePullCredentials{KubernetesSecrets: []kubeletconfiginternal.ImagePullSecret{podSecret}}); err != nil {
287+
klog.ErrorS(err, "failed to write an image pulled record", "image", image, "imageRef", imageRef)
288+
}
289+
return false
290+
}
269291
}
270292
}
271293
}

pkg/kubelet/images/pullmanager/image_pull_manager_test.go

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,16 @@ func Test_pulledRecordMergeNewCreds(t *testing.T) {
199199

200200
func TestFileBasedImagePullManager_MustAttemptImagePull(t *testing.T) {
201201
tests := []struct {
202-
name string
203-
imagePullPolicy ImagePullPolicyEnforcer
204-
podSecrets []kubeletconfiginternal.ImagePullSecret
205-
image string
206-
imageRef string
207-
pulledFiles []string
208-
pullingFiles []string
209-
want bool
202+
name string
203+
imagePullPolicy ImagePullPolicyEnforcer
204+
podSecrets []kubeletconfiginternal.ImagePullSecret
205+
image string
206+
imageRef string
207+
pulledFiles []string
208+
pullingFiles []string
209+
expectedPullRecord *kubeletconfiginternal.ImagePulledRecord
210+
want bool
211+
expectedCacheWrite bool
210212
}{
211213
{
212214
name: "image exists and is recorded with pod's exact secret",
@@ -240,7 +242,18 @@ func TestFileBasedImagePullManager_MustAttemptImagePull(t *testing.T) {
240242
image: "docker.io/testing/test:latest",
241243
imageRef: "testimageref",
242244
pulledFiles: []string{"sha256-b3c0cc4278800b03a308ceb2611161430df571ca733122f0a40ac8b9792a9064"},
243-
want: false,
245+
expectedPullRecord: &kubeletconfiginternal.ImagePulledRecord{
246+
ImageRef: "testimageref",
247+
CredentialMapping: map[string]kubeletconfiginternal.ImagePullCredentials{
248+
"docker.io/testing/test": {
249+
KubernetesSecrets: []kubeletconfiginternal.ImagePullSecret{
250+
{UID: "testsecretuid", Namespace: "default", Name: "pull-secret", CredentialHash: "differenthash"},
251+
},
252+
},
253+
},
254+
},
255+
want: false,
256+
expectedCacheWrite: true,
244257
},
245258
{
246259
name: "image exists and is recorded with a different secret with a different UID",
@@ -279,7 +292,19 @@ func TestFileBasedImagePullManager_MustAttemptImagePull(t *testing.T) {
279292
image: "docker.io/testing/test:latest",
280293
imageRef: "testimageref",
281294
pulledFiles: []string{"sha256-b3c0cc4278800b03a308ceb2611161430df571ca733122f0a40ac8b9792a9064"},
282-
want: false,
295+
expectedPullRecord: &kubeletconfiginternal.ImagePulledRecord{
296+
ImageRef: "testimageref",
297+
CredentialMapping: map[string]kubeletconfiginternal.ImagePullCredentials{
298+
"docker.io/testing/test": {
299+
KubernetesSecrets: []kubeletconfiginternal.ImagePullSecret{
300+
{UID: "testsecretuid", Namespace: "default", Name: "pull-secret", CredentialHash: "testsecrethash"},
301+
{UID: "testsecretuid", Namespace: "differentns", Name: "pull-secret", CredentialHash: "testsecrethash"},
302+
},
303+
},
304+
},
305+
},
306+
want: false,
307+
expectedCacheWrite: true,
283308
},
284309
{
285310
name: "image exists but the pull is recorded with a different image name but with the exact same secret",
@@ -409,11 +434,13 @@ func TestFileBasedImagePullManager_MustAttemptImagePull(t *testing.T) {
409434
copyTestData(t, pullingDir, "pulling", tt.pullingFiles)
410435
copyTestData(t, pulledDir, "pulled", tt.pulledFiles)
411436

412-
fsRecordAccessor := &fsPullRecordsAccessor{
413-
pullingDir: pullingDir,
414-
pulledDir: pulledDir,
415-
encoder: encoder,
416-
decoder: decoder,
437+
fsRecordAccessor := &testWriteCountingFSPullRecordsAccessor{
438+
fsPullRecordsAccessor: fsPullRecordsAccessor{
439+
pullingDir: pullingDir,
440+
pulledDir: pulledDir,
441+
encoder: encoder,
442+
decoder: decoder,
443+
},
417444
}
418445

419446
f := &PullManager{
@@ -426,10 +453,37 @@ func TestFileBasedImagePullManager_MustAttemptImagePull(t *testing.T) {
426453
if got := f.MustAttemptImagePull(tt.image, tt.imageRef, tt.podSecrets); got != tt.want {
427454
t.Errorf("FileBasedImagePullManager.MustAttemptImagePull() = %v, want %v", got, tt.want)
428455
}
456+
457+
if tt.expectedCacheWrite != (fsRecordAccessor.imagePulledRecordsWrites != 0) {
458+
t.Errorf("expected zero cache writes, got: %v", fsRecordAccessor.imagePulledRecordsWrites)
459+
}
460+
461+
if tt.expectedPullRecord != nil {
462+
got, found, err := fsRecordAccessor.GetImagePulledRecord(tt.imageRef)
463+
if err != nil && !found {
464+
t.Fatalf("failed to get an expected ImagePulledRecord")
465+
}
466+
got.LastUpdatedTime = tt.expectedPullRecord.LastUpdatedTime
467+
468+
if !reflect.DeepEqual(got, tt.expectedPullRecord) {
469+
t.Errorf("expected ImagePulledRecord != got; diff: %s", cmp.Diff(tt.expectedPullRecord, got))
470+
}
471+
}
429472
})
430473
}
431474
}
432475

476+
type testWriteCountingFSPullRecordsAccessor struct {
477+
imagePulledRecordsWrites int
478+
479+
fsPullRecordsAccessor
480+
}
481+
482+
func (a *testWriteCountingFSPullRecordsAccessor) WriteImagePulledRecord(pulledRecord *kubeletconfiginternal.ImagePulledRecord) error {
483+
a.imagePulledRecordsWrites += 1
484+
return a.fsPullRecordsAccessor.WriteImagePulledRecord(pulledRecord)
485+
}
486+
433487
func TestFileBasedImagePullManager_RecordPullIntent(t *testing.T) {
434488
tests := []struct {
435489
name string

0 commit comments

Comments
 (0)