feat(registry): 开启 manifest 删除,GC sidecar 在 gate 只读窗口内回收 blob

This commit is contained in:
Lemon-miaow committed 2026-09-24 22:46:37 +08:00
1 parent c49336ba21
commit 7f772bccbb
9 files changed
+806 -22

No files matched your search

+47 -2
View File
@@ -2,6 +2,7 @@ package platform
import (
"fmt"
"strings"
"testing"
appsv1 "k8s.io/api/apps/v1"
@@ -462,8 +463,8 @@ func TestRegistry_DeploymentServicePVC(t *testing.T) {
pvc := registryPVC(p)
ps, c := namedContainer(t, dep, registryName)
if len(ps.Containers) != 2 {
t.Fatalf("registry pod containers = %d, want registry + gate", len(ps.Containers))
if len(ps.Containers) != 3 {
t.Fatalf("registry pod containers = %d, want registry + gate + gc", len(ps.Containers))
}
if c.Image != defaultRegistryImage {
t.Errorf("registry image = %q, want default %q", c.Image, defaultRegistryImage)
@@ -476,6 +477,14 @@ func TestRegistry_DeploymentServicePVC(t *testing.T) {
if len(c.Ports) != 0 {
t.Errorf("registry container ports = %+v, want none (the gate owns the port)", c.Ports)
}
// Deletion on, and the blob descriptor cache off: a cached descriptor for a
// blob the GC removed would let the next push skip uploading it.
if v := envValue(c.Env, "REGISTRY_STORAGE_DELETE_ENABLED"); v != "true" {
t.Errorf("REGISTRY_STORAGE_DELETE_ENABLED = %q, want true", v)
}
if v := envValue(c.Env, "REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR"); v == "inmemory" || v == "redis" || v == "" {
t.Errorf("REGISTRY_STORAGE_CACHE_BLOBDESCRIPTOR = %q, want the cache disabled", v)
}
// The registry's limits are deliberately NOT the control-plane template's: audit
// #46 caught the registry OOM-killed mid-upload at 256Mi on a real 475MB-layer push.
if mem := c.Resources.Limits[corev1.ResourceMemory]; mem.Value() < 2*1024*1024*1024 {
@@ -495,11 +504,47 @@ func TestRegistry_DeploymentServicePVC(t *testing.T) {
fmt.Sprintf("--listen=:%d", p.RegistryPort),
fmt.Sprintf("--upstream=http://127.0.0.1:%d", p.RegistryPort+1),
"--auth-dir=" + registryAuthMountPath,
// The GC handshake has no authentication: loopback only.
fmt.Sprintf("--maint-listen=127.0.0.1:%d", p.RegistryPort+2),
"--maint-dir=" + registryMaintMountPath,
} {
if !contains(gate.Args, want) {
t.Errorf("gate args = %v, want %s", gate.Args, want)
}
}
// The GC sidecar: the registry image on the same data volume, hardened like
// the rest, pointed at the gate's maintenance port, never --delete-untagged
// (digest-pinned servers may boot an untagged manifest).
_, gc := namedContainer(t, dep, registryGCName)
if gc.Image != p.RegistryImage {
t.Errorf("gc image = %q, want the registry image %q", gc.Image, p.RegistryImage)
}
if v := envValue(gc.Env, "FELIS_GC_MAINT_PORT"); v != fmt.Sprint(p.RegistryPort+2) {
t.Errorf("FELIS_GC_MAINT_PORT = %q, want %d", v, p.RegistryPort+2)
}
script := strings.Join(gc.Command, " ")
if !strings.Contains(script, "garbage-collect") || strings.Contains(script, "delete-untagged") {
t.Errorf("gc command must run garbage-collect without --delete-untagged: %s", script)
}
if !strings.Contains(script, "/readonly?lease=") || !strings.Contains(script, "/readwrite") {
t.Errorf("gc command must take and hand back the gate's read-only window: %s", script)
}
gcData := false
for _, m := range gc.VolumeMounts {
if m.Name == registryAuthVolume {
t.Error("the gc sidecar must not mount the write tokens")
}
if m.Name == registryVolume && m.MountPath == registryDataPath {
gcData = true
}
}
if !gcData {
t.Errorf("gc sidecar must mount the registry data at %s, mounts=%v", registryDataPath, gc.VolumeMounts)
}
if gc.SecurityContext == nil || gc.SecurityContext.ReadOnlyRootFilesystem == nil || !*gc.SecurityContext.ReadOnlyRootFilesystem {
t.Error("gc sidecar must run with a read-only root filesystem")
}
// The node-side pull path: exactly one container port, mirrored by a LOOPBACK
// hostPort. Node containerd cannot dial the Service VIP, so its registries.yaml
// mirror rewrites the Service name onto 127.0.0.1:<port>; nothing else may be