Skip to content

Commit 87a653f

Browse files
awesomenixCopilot
andcommitted
Use containerd GC for image-fetcher cleanup
Mark unpacked pulled layers as GC-eligible during VHD image preloading and trigger a single synchronous containerd GC after the preload batch. This avoids scanning all images per pull while preserving fetch-only image blobs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7a4da12 commit 87a653f

File tree

2 files changed

+56
-29
lines changed

2 files changed

+56
-29
lines changed

image-fetcher/main.go

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import (
55
"fmt"
66
"os"
77
"runtime"
8+
"time"
89

910
containerd "github.com/containerd/containerd/v2/client"
11+
"github.com/containerd/containerd/v2/core/images"
12+
"github.com/containerd/containerd/v2/core/leases"
1013
"github.com/containerd/containerd/v2/pkg/namespaces"
1114
"github.com/containerd/platforms"
1215
)
@@ -23,6 +26,7 @@ const (
2326
func main() {
2427
if len(os.Args) < 2 {
2528
fmt.Fprintf(os.Stderr, "Usage: %s <image-ref> [image-ref...]\n", os.Args[0])
29+
fmt.Fprintf(os.Stderr, " %s --gc\n", os.Args[0])
2630
fmt.Fprintf(os.Stderr, "Example: %s mcr.microsoft.com/oss/kubernetes/pause:3.9\n", os.Args[0])
2731
os.Exit(1)
2832
}
@@ -45,6 +49,15 @@ func main() {
4549

4650
ctx := namespaces.WithNamespace(context.Background(), ns)
4751

52+
if len(os.Args) == 2 && os.Args[1] == "--gc" {
53+
if err := triggerGarbageCollection(ctx, client); err != nil {
54+
fmt.Fprintf(os.Stderr, "Failed to trigger containerd GC: %v\n", err)
55+
os.Exit(1)
56+
}
57+
fmt.Println("Triggered containerd GC")
58+
return
59+
}
60+
4861
failed := 0
4962
for _, ref := range os.Args[1:] {
5063
if err := fetchImage(ctx, client, ref); err != nil {
@@ -68,7 +81,7 @@ func main() {
6881
// already-fetched content from the store and handles snapshotter resolution
6982
// internally (namespace label → platform default).
7083
func fetchImage(ctx context.Context, client *containerd.Client, ref string) error {
71-
fetchOnly := os.Getenv("IMAGE_FETCH_ONLY") == "true"
84+
//fetchOnly := os.Getenv("IMAGE_FETCH_ONLY") == "true"
7285

7386
fmt.Printf("Fetching %s ...\n", ref)
7487

@@ -79,42 +92,55 @@ func fetchImage(ctx context.Context, client *containerd.Client, ref string) erro
7992
}
8093
platformMatcher := platforms.OnlyStrict(p)
8194

82-
imageMeta, err := client.Fetch(ctx, ref,
95+
// imageMeta, err := client.Fetch(ctx, ref,
96+
// containerd.WithPlatformMatcher(platformMatcher),
97+
// )
98+
// if err != nil {
99+
// return fmt.Errorf("fetch failed: %w", err)
100+
// }
101+
102+
// if fetchOnly {
103+
// fmt.Printf("OK %s -> %s (fetched)\n", imageMeta.Name, imageMeta.Target.Digest)
104+
// return nil
105+
// }
106+
107+
// image := containerd.NewImage(client, imageMeta)
108+
109+
// size, err := image.Size(ctx)
110+
// if err != nil {
111+
// fmt.Fprintf(os.Stderr, "WARN %s: could not determine image size, skipping unpack: %v\n", ref, err)
112+
// fmt.Printf("OK %s -> %s (fetched)\n", imageMeta.Name, imageMeta.Target.Digest)
113+
// return nil
114+
// }
115+
116+
// if size < pullSizeThreshold {
117+
// We use pull here instead of use unpack because some runtimes (e.g. containerd-shim-runsc-v1),
118+
// require pull to trigger unpacking into the correct snapshotter based on the image's platform.
119+
pullOpts := []containerd.RemoteOpt{
83120
containerd.WithPlatformMatcher(platformMatcher),
84-
)
85-
if err != nil {
86-
return fmt.Errorf("fetch failed: %w", err)
121+
containerd.WithPullUnpack,
122+
containerd.WithChildLabelMap(images.ChildGCLabelsFilterLayers),
87123
}
88124

89-
if fetchOnly {
90-
fmt.Printf("OK %s -> %s (fetched)\n", imageMeta.Name, imageMeta.Target.Digest)
91-
return nil
125+
imageMeta, err := client.Pull(ctx, ref, pullOpts...)
126+
if err != nil {
127+
return fmt.Errorf("pull failed: %w", err)
92128
}
129+
fmt.Printf("OK %s (pulled)\n", imageMeta.Name)
130+
// } else {
131+
// fmt.Printf("OK %s -> %s (fetched, %s)\n", imageMeta.Name, imageMeta.Target.Digest, formatSize(size))
132+
// }
93133

94-
image := containerd.NewImage(client, imageMeta)
134+
return nil
135+
}
95136

96-
size, err := image.Size(ctx)
137+
func triggerGarbageCollection(ctx context.Context, client *containerd.Client) error {
138+
ls := client.LeasesService()
139+
l, err := ls.Create(ctx, leases.WithRandomID(), leases.WithExpiration(time.Hour))
97140
if err != nil {
98-
fmt.Fprintf(os.Stderr, "WARN %s: could not determine image size, skipping unpack: %v\n", ref, err)
99-
fmt.Printf("OK %s -> %s (fetched)\n", imageMeta.Name, imageMeta.Target.Digest)
100-
return nil
101-
}
102-
103-
if size < pullSizeThreshold {
104-
// We use pull here instead of use unpack because some runtimes (e.g. containerd-shim-runsc-v1),
105-
// require pull to trigger unpacking into the correct snapshotter based on the image's platform.
106-
if _, err := client.Pull(ctx, ref,
107-
containerd.WithPlatformMatcher(platformMatcher),
108-
containerd.WithPullUnpack,
109-
); err != nil {
110-
return fmt.Errorf("pull failed: %w", err)
111-
}
112-
fmt.Printf("OK %s -> %s (pulled, %s)\n", imageMeta.Name, imageMeta.Target.Digest, formatSize(size))
113-
} else {
114-
fmt.Printf("OK %s -> %s (fetched, %s)\n", imageMeta.Name, imageMeta.Target.Digest, formatSize(size))
141+
return err
115142
}
116-
117-
return nil
143+
return ls.Delete(ctx, l, leases.SynchronousDelete)
118144
}
119145

120146
func formatSize(bytes int64) string {

vhdbuilder/packer/install-dependencies.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ while IFS= read -r imageToBePulled; do
699699
done <<< "$ContainerImages"
700700
echo "Waiting for container image pulls to finish. PID: ${image_pids[@]}"
701701
wait ${image_pids[@]}
702+
/opt/azure/containers/image-fetcher --gc
702703
capture_benchmark "${SCRIPT_NAME}_caching_container_images"
703704

704705
retagAKSNodeCAWatcher() {

0 commit comments

Comments
 (0)