← Back to list

3000+ Clusters Part 4: How not to DDoS your internal registry

Deploying containers in environments with limited internet access or in big scale can be challenging. Talos Linux simplifies this with a…

Daniel Hansen in JYSK Tech · 2025-09-01 13:04 · 4 claps · 5.7 min read
#talos-linux #kubernetes
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud 🔓 · Open Source

3000+ Clusters Part 4: How not to DDoS your internal registry

Deploying containers in environments with limited internet access or in big scale can be challenging. Talos Linux simplifies this with a local image cache that stores container images on the node itself. By avoiding repeated downloads from external registries, Talos image cache reduces deployment times, conserves bandwidth, and ensures a smoother, more predictable workflow.

In the recent months we have been using time on daily operations and things like upgrading clusters. In that process we identified some critical issues with having Talos placed at the edge, where resources might be limited or uptime is crucial. Besides that, we also figured out that having 3000 clusters pulling from the same registry at once may be too much and will result in a DDoS of the registry.

Here we can see what happens with the availability of the registry, when 3000 clusters pulls at the same time.

That made us look into if we could eliminate the process of downloading images when deploying a cluster — This is where Talos Image Cache comes in.

How does image caching works

In an earlier article we wrote about how the image creation works with Packer. We now incorporate the cache creation into that process. When creating the Packer image, you will need to prepare the cache to be included in the VM image, as it then works a built-in part of Talos disk layout.

When the VM boots first time and starts in maintenance mode it will not be aware of the cache. This is because it has to be enabled in the machineconfig. To enable the cache you will need to patch the machineconfig with this:

machine:
  features:
    imageCache:
      localEnabled: true

After patching the machineconfig file you must bootstrap the VM with talosctl bootstrap, which will get the config applied. In that process it will create a new registryd service in Talos, that will serve as a local image registry and is tied to the image cache partition.

Creating the cache

For creating the cache you will need dependencies like Talosctl Docker in the environment used for creating the image. To see which images Talos needs you can list them by running the command below.

talosctl images default
ghcr.io/siderolabs/flannel:v0.26.5
registry.k8s.io/coredns/coredns:v1.12.0
gcr.io/etcd-development/etcd:v3.5.19
registry.k8s.io/kube-apiserver:v1.32.3
registry.k8s.io/kube-controller-manager:v1.32.3
registry.k8s.io/kube-scheduler:v1.32.3
registry.k8s.io/kube-proxy:v1.32.3
ghcr.io/siderolabs/kubelet:v1.32.3
ghcr.io/siderolabs/installer:v1.9.5
registry.k8s.io/pause:3.10

Those images need to be parsed to a file, which will be used as a reference list by docker.

talosctl images default > images.txt

If you need additional images like Flux Cilium ExternalDNS you can create a new file e.g. extra-images.txt — which contains those images like the example below. Make sure to include the both tag-based and digit-based reference to each image needed.

ghcr.io/external-secrets/external-secrets:v0.8.5
ghcr.io/external-secrets/external-secrets@sha256:7fa397bec94e05d4de27d8c6b892933b592e64b7a439f1abc985bf3ca26b97b2
ghcr.io/fluxcd/helm-controller:v1.1.0
ghcr.io/fluxcd/helm-controller@sha256:4c75ca6c24ceb1f1bd7e935d9287a93e4f925c512f206763ec5a47de3ef3ff48
ghcr.io/fluxcd/kustomize-controller:v1.4.0
ghcr.io/fluxcd/kustomize-controller@sha256:e3b0cf847e9cdf47b19af0fbcfe22786b80b598e0caeea8b6d2a5f9c26a48a24
ghcr.io/fluxcd/source-controller:v1.4.1
ghcr.io/fluxcd/source-controller@sha256:3c5f0f022f990ffc0daf00e5b199548fc0fa6e7119e972318f0267081a332963
quay.io/cilium/cilium-envoy@sha256:0287b36f70cfbdf54f894160082f4f94d1ee1fb10389f3a95baa6c8e448586ed
quay.io/cilium/cilium@sha256:d55ec38938854133e06739b1af237932b9c4dd4e75e9b7b2ca3acc72540a44bf
quay.io/cilium/operator-generic@sha256:c55a7cbe19fe0b6b28903a085334edb586a3201add9db56d2122c8485f7a51c5

You will then need to add the list into images.txt .

cat extra-images.txt >> images.txt

You can now prepare the cache.

cat images.txt | talosctl images cache-create --image-cache-path ./image-cache.oci --images=-

It is now possible to create the docker image either as an ISO or disk-image — here we will be creating a disk-image. Depending on the amount of images you need, it can be nessesary to extend the disk size for the image. The disk size of the cache image can be changed by adding following option to the command below --image-disk-size=3GB .

mkdir -p _out/
docker run --rm -t -v $PWD/_out:/secureboot:ro -v $PWD/_out:/out -v $PWD/image-cache.oci:/image-cache.oci:ro -v /dev:/dev --privileged ghcr.io/siderolabs/imager:v1.9.5 metal --image-cache /image-cache.oci

There is now a ZST compressed disk-image located in _out/ which contains the whole Talos installation and cached images. It can now be used a the base for the Packer build, which wont be done here.

Activate caching

When the Packer build is done and Proxmox template created. Clone the VM and boot it up. After boot and it goes into maintenance mode, you will need to patch the VM with config for enabling the cache — if you don´t enable it, Talos will automatically remove the image cache partition.

The configuration you need to parse to Talos is as below.

machine:
  features:
    imageCache:
      localEnabled: true

When the config is parsed to the controlplane config and bootstrap is done. We can verify if the cache is working.

First, we can check if the image cache partition is created. Look for the partition with label IMAGECACHE .

talosctl get discoveredvolumes --talosconfig talosconfig
NODE            NAMESPACE   TYPE               ID      VERSION   TYPE        SIZE     DISCOVERED   LABEL        PARTITIONLABEL
192.168.1.79   runtime     DiscoveredVolume   loop0   1         disk        684 kB   squashfs
192.168.1.79   runtime     DiscoveredVolume   loop1   1         disk        77 MB    squashfs
192.168.1.79   runtime     DiscoveredVolume   vda     1         disk        22 GB    gpt
192.168.1.79   runtime     DiscoveredVolume   vda1    2         partition   105 MB   xfs          STATE        STATE
192.168.1.79   runtime     DiscoveredVolume   vda2    1         partition   1.0 MB                             BIOS
192.168.1.79   runtime     DiscoveredVolume   vda3    1         partition   1.0 GB   xfs          BOOT         BOOT
192.168.1.79   runtime     DiscoveredVolume   vda4    1         partition   1.0 MB   talosmeta                 META
192.168.1.79   runtime     DiscoveredVolume   vda5    1         partition   2.8 GB   ext4         IMAGECACHE   IMAGECACHE
192.168.1.79   runtime     DiscoveredVolume   vda6    2         partition   16 GB    xfs          EPHEMERAL    EPHEMERAL
192.168.1.79   runtime     DiscoveredVolume   vdb     1         disk        11 GB

After verifing the parition is created, let´s list the services and see if registryd is running.

talosctl get services --talosconfig talosconfig
NODE           NAMESPACE   TYPE      ID           VERSION   RUNNING   HEALTHY   HEALTH UNKNOWN
192.168.1.79   runtime     Service   apid         2         true      true      false
192.168.1.79   runtime     Service   auditd       2         true      true      false
192.168.1.79   runtime     Service   containerd   2         true      true      false
192.168.1.79   runtime     Service   cri          2         true      true      false
192.168.1.79   runtime     Service   dashboard    1         true      false     true
192.168.1.79   runtime     Service   etcd         2         true      true      false
192.168.1.79   runtime     Service   kubelet      2         true      true      false
192.168.1.79   runtime     Service   machined     2         true      true      false
192.168.1.79   runtime     Service   registryd    2         true      true      false
192.168.1.79   runtime     Service   syslogd      2         true      true      false
192.168.1.79   runtime     Service   trustd       2         true      true      false
192.168.1.79   runtime     Service   udevd        2         true      true      false

So everything looks to be running, but let´s just check that Talos actually is pulling from the local registryd .

talosctl logs -f registryd --talosconfig talosconfig
192.168.1.79: 2025-09-01T11:47:13.826Z INFO image request {"method": "GET", "url": "/v2/kube-proxy/blobs/sha256:a1ae78fd2f9d8fc345928378dc947c7f1e95f01c1a552781827071867a95d09c?ns=registry.k8s.io", "remote_addr": "127.0.0.1:41478", "name": "kube-proxy", "digest": "sha256:a1ae78fd2f9d8fc345928378dc947c7f1e95f01c1a552781827071867a95d09c", "is_blob": true, "registry": "registry.k8s.io"}
192.168.1.79: 2025-09-01T11:47:13.839Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/manifests/sha256:ffbc360bd071f093138a026d23e35b8b759e153badaae351a931a238d3c80987?ns=ghcr.io", "remote_addr": "127.0.0.1:41496", "name": "siderolabs/flannel", "digest": "sha256:ffbc360bd071f093138a026d23e35b8b759e153badaae351a931a238d3c80987", "is_blob": false, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:13.858Z INFO image request {"method": "GET", "url": "/v2/kube-proxy/blobs/sha256:4157173e30dd7c3fb793c3d8b3c7c36957097c55541ffede2871e11d3864d2e4?ns=registry.k8s.io", "remote_addr": "127.0.0.1:41478", "name": "kube-proxy", "digest": "sha256:4157173e30dd7c3fb793c3d8b3c7c36957097c55541ffede2871e11d3864d2e4", "is_blob": true, "registry": "registry.k8s.io"}
192.168.1.79: 2025-09-01T11:47:13.867Z INFO image request {"method": "GET", "url": "/v2/kube-proxy/blobs/sha256:b3e9d7d2007338bdeb8a4a6b1bcfaa85be948fe1a259aced775691903c0d8c99?ns=registry.k8s.io", "remote_addr": "127.0.0.1:41512", "name": "kube-proxy", "digest": "sha256:b3e9d7d2007338bdeb8a4a6b1bcfaa85be948fe1a259aced775691903c0d8c99", "is_blob": true, "registry": "registry.k8s.io"}
192.168.1.79: 2025-09-01T11:47:13.879Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:aed85bb568e158c63c120be3dc0f2894b3ae56d057c516f21e467778f1f362b3?ns=ghcr.io", "remote_addr": "127.0.0.1:41496", "name": "siderolabs/flannel", "digest": "sha256:aed85bb568e158c63c120be3dc0f2894b3ae56d057c516f21e467778f1f362b3", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:13.936Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:afe4beee6ae0524e783966cf4d6625d85bff2a485f8f770cf6ce4919c4f430fa?ns=ghcr.io", "remote_addr": "127.0.0.1:41496", "name": "siderolabs/flannel", "digest": "sha256:afe4beee6ae0524e783966cf4d6625d85bff2a485f8f770cf6ce4919c4f430fa", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:13.981Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:d62bb7eb03b5936dc5a5665fd5a6ede7eab4a6bd0ed965be8c6c3c21e1e53931?ns=ghcr.io", "remote_addr": "127.0.0.1:41522", "name": "siderolabs/flannel", "digest": "sha256:d62bb7eb03b5936dc5a5665fd5a6ede7eab4a6bd0ed965be8c6c3c21e1e53931", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.000Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:bac69ade4f604293095657fde91c3bac6b5563ea2aa7f5f5c33ae1be64de180b?ns=ghcr.io", "remote_addr": "127.0.0.1:41538", "name": "siderolabs/flannel", "digest": "sha256:bac69ade4f604293095657fde91c3bac6b5563ea2aa7f5f5c33ae1be64de180b", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.130Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:7105b7cfd4a2b27f2597c6ed14792241915524daef063c4aac782873ba704d8e?ns=ghcr.io", "remote_addr": "127.0.0.1:41496", "name": "siderolabs/flannel", "digest": "sha256:7105b7cfd4a2b27f2597c6ed14792241915524daef063c4aac782873ba704d8e", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.141Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:211abe45b3872c3bcbcf72be3fdecf2ece3feebb62f246b02efcb62f6abdd5b2?ns=ghcr.io", "remote_addr": "127.0.0.1:41496", "name": "siderolabs/flannel", "digest": "sha256:211abe45b3872c3bcbcf72be3fdecf2ece3feebb62f246b02efcb62f6abdd5b2", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.169Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:9b3b3a025c7289260e7f8aae6fbf35cbf03d3ac1083e797d3d330aeba7dbe483?ns=ghcr.io", "remote_addr": "127.0.0.1:41522", "name": "siderolabs/flannel", "digest": "sha256:9b3b3a025c7289260e7f8aae6fbf35cbf03d3ac1083e797d3d330aeba7dbe483", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.179Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:b7418df0b7850e1076ff1c0782755cb3ccbf72787d3a24e2dd3e6b5b827316ca?ns=ghcr.io", "remote_addr": "127.0.0.1:41522", "name": "siderolabs/flannel", "digest": "sha256:b7418df0b7850e1076ff1c0782755cb3ccbf72787d3a24e2dd3e6b5b827316ca", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.201Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:af7ae5dd5a38bfc9278937ccc3c7badad2d230db4dcde52ed51b7642cdd58464?ns=ghcr.io", "remote_addr": "127.0.0.1:41522", "name": "siderolabs/flannel", "digest": "sha256:af7ae5dd5a38bfc9278937ccc3c7badad2d230db4dcde52ed51b7642cdd58464", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.266Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1?ns=ghcr.io", "remote_addr": "127.0.0.1:41522", "name": "siderolabs/flannel", "digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1", "is_blob": true, "registry": "ghcr.io"}
192.168.1.79: 2025-09-01T11:47:14.274Z INFO image request {"method": "GET", "url": "/v2/siderolabs/flannel/blobs/sha256:ca3adac18af57418e1221de048ef762eea12685706c0861d0db6b5e7e7c8d077?ns=ghcr.io", "remote_addr": "127.0.0.1:41522", "name": "siderolabs/flannel", "digest": "sha256:ca3adac18af57418e1221de048ef762eea12685706c0861d0db6b5e7e7c8d077", "is_blob": true, "registry": "ghcr.io"}

Here is the important part remote_addr . That should be set to 127.0.0.1 as that is the local registryd service.

Conclusion

So, if you are running Talos in an air-gapped environment, where resources like bandwidth is very limited or you deploy to a large scale, image cache might a worth looking into. It is both easy to configure, optimize deployment time and wont disturb your local registry, as it will not need external sources for pulling images.


메타데이터
post_id
b230fc596159
slug
3000-clusters-part-4-how-not-to-ddos-your-internal-registry-b230fc596159
url
https://jysk.tech/3000-clusters-part-4-how-not-to-ddos-your-internal-registry-b230fc596159
canonical_url
https://jysk.tech/3000-clusters-part-4-how-not-to-ddos-your-internal-registry-b230fc596159
author_url
https://medium.com/@daniel.hansen15
status
ok
fetched_at
2026-06-10 09:45:17