/kind feature
Motivation
Nodes currently download file assets (nodeup, kubelet, containerd, CNI) and pull images from public mirrors and registries. Serving all of a cluster's assets from a single registry removes the dependency on public upstream sources, keeps downloads same-region when the registry is in the cluster's region, and enables clusters whose nodes cannot reach the public internet (the remaining external dependencies are the cloud's own endpoints: registry, metadata service, state store).
OCI registries are a natural fit for file assets because blob digests are the sha256 of the content: the hash kOps already embeds in the bootstrap script doubles as the download address (/v2/<repository>/blobs/sha256:<hash>). Nodes need no OCI client and no manifest parsing: the bootstrap script fetches the nodeup binary with curl, and nodeup fetches each remaining asset with a single HTTP GET after a short per-registry token exchange, with the existing hash validation verifying every download independently.
Target user experience, in increasing order of convenience:
# Step 1: any registry that allows anonymous pulls, on any cloud provider
spec:
assets:
fileRepository: oci://registry.example.com/assets
# Step 2: a pre-created private cloud registry, pulled with the instance identity
spec:
assets:
fileRepository: oci://myregistry.azurecr.io/assets
# Step 3: kOps creates and manages the registry; locations are defaulted
spec:
assets:
managed: true
Why no OCI client on nodes
Since kOps controls the push format, pulling an asset reduces to one authenticated GET of a well-known URL. A registry client (go-containerregistry, ORAS) would only be needed to resolve tags through manifests, and would add a large vendored dependency tree plus the per-cloud auth stacks to nodeup, a binary every node downloads at boot. The bootstrap script could not use a Go library either way, so it would still need the curl token flows. Instead, the per-cloud token exchanges are a few hundred lines of plain HTTP in Go, mirrored by short curl flows in the bootstrap script; in release clusters the script downloads exactly one artifact, the nodeup binary, and nodeup fetches the rest.
Plan
Each step delivers standalone user value; later steps build on the earlier ones.
1. oci:// fileRepository foundation (anonymous pulls, all cloud providers)
2. Private cloud registries (registry pre-created by the user)
Like state store buckets, the registry must already exist, and the instances must be granted pull access out of band. kOps pushes the assets and the nodes authenticate with their instance identity. The auth mode is inferred from the registry host (*.azurecr.io on Azure, *.pkg.dev on GCE, *.dkr.ecr.*.amazonaws.com on AWS, anonymous elsewhere), and each provider's registry is validated for use on its own cloud only.
3. kOps-managed registries: assets.managed: true
kOps creates the per-cluster registry resources in the cluster's cloud account, grants the instances pull access, pushes file and image assets during kops update cluster (ordered after the registry creation and after the full asset list is known; with the terraform target the push instead runs via kops get assets --copy after terraform apply), enumerates the resources in the deletion preview, and deletes them with the cluster.
4. Bonus: authenticated object-store downloads in the bootstrap script
Support downloading nodeup itself from private buckets with curl, giving a private fileRepository without an OCI registry. nodeup can already read these stores natively; the gap is only the bootstrap script's download of the nodeup binary.
Open questions
- ECR repositories in managed mode: ECR does not create repositories on direct push, and the feature produces many repository names: image remapping flattens image paths into single-component names (
coredns-coredns), while each file asset maps to its own repository path under the fileRepository prefix. The full name set is only known once the asset list is collected. Create the repositories from the collected list at push time, evaluate ECR repository creation templates (create-on-push), or restructure the file layout into a single repository (files are addressed by digest tag, so one repository could suffice for them)?
Out of scope
- Pruning: assets accumulate across upgrades and nothing is deleted until the cluster is. Storage is billed, not capped (ACR Basic includes 10 GiB), and years of upgrades cost little, so garbage collection is not worth the complexity for now.
- Private endpoints: ECR interface endpoints and Private Google Access work with these flows without kOps changes; ACR Basic does not support Private Link, and a SKU option can be added later.
- Authenticated third-party registries (e.g. Harbor with basic auth): nodes have no credential distribution story; only anonymous third-party registries are supported.
- Sovereign and China cloud partitions (
.azurecr.us/.cn, .amazonaws.com.cn): the host gates and token audiences cover the public partitions only.
/kind feature
Motivation
Nodes currently download file assets (nodeup, kubelet, containerd, CNI) and pull images from public mirrors and registries. Serving all of a cluster's assets from a single registry removes the dependency on public upstream sources, keeps downloads same-region when the registry is in the cluster's region, and enables clusters whose nodes cannot reach the public internet (the remaining external dependencies are the cloud's own endpoints: registry, metadata service, state store).
OCI registries are a natural fit for file assets because blob digests are the sha256 of the content: the hash kOps already embeds in the bootstrap script doubles as the download address (
/v2/<repository>/blobs/sha256:<hash>). Nodes need no OCI client and no manifest parsing: the bootstrap script fetches the nodeup binary with curl, and nodeup fetches each remaining asset with a single HTTP GET after a short per-registry token exchange, with the existing hash validation verifying every download independently.Target user experience, in increasing order of convenience:
Why no OCI client on nodes
Since kOps controls the push format, pulling an asset reduces to one authenticated GET of a well-known URL. A registry client (go-containerregistry, ORAS) would only be needed to resolve tags through manifests, and would add a large vendored dependency tree plus the per-cloud auth stacks to nodeup, a binary every node downloads at boot. The bootstrap script could not use a Go library either way, so it would still need the curl token flows. Instead, the per-cloud token exchanges are a few hundred lines of plain HTTP in Go, mirrored by short curl flows in the bootstrap script; in release clusters the script downloads exactly one artifact, the nodeup binary, and nodeup fetches the rest.
Plan
Each step delivers standalone user value; later steps build on the earlier ones.
1.
oci://fileRepository foundation (anonymous pulls, all cloud providers)kops get assets --copypushes each file asset as a single-layer image whose blob digest is the file's hash, tagged with that hash; pushing authenticates with the local docker credentials, so any registrydocker logincan reach works.WWW-Authenticatechallenge (tested against Docker Hub and ghcr.io).2. Private cloud registries (registry pre-created by the user)
Like state store buckets, the registry must already exist, and the instances must be granted pull access out of band. kOps pushes the assets and the nodes authenticate with their instance identity. The auth mode is inferred from the registry host (
*.azurecr.ioon Azure,*.pkg.devon GCE,*.dkr.ecr.*.amazonaws.comon AWS, anonymous elsewhere), and each provider's registry is validated for use on its own cloud only.containerRegistryand the existinggcp-credential-provider.GetAuthorizationTokencall with the vendored SigV4 signer, and the bootstrap script signs it withcurl --aws-sigv41. Kubelet image pulls already work today viacontainerRegistryand the existingecr-credential-provider.acr-credential-providerkubelet plugin from cloud-provider-azure.containerRegistryneeds a pre-pull with the instance identity on every cloud, not just Azure.3. kOps-managed registries:
assets.managed: truekOps creates the per-cluster registry resources in the cluster's cloud account, grants the instances pull access, pushes file and image assets during
kops update cluster(ordered after the registry creation and after the full asset list is known; with the terraform target the push instead runs viakops get assets --copyafterterraform apply), enumerates the resources in the deletion preview, and deletes them with the cluster.AcrPullrole assignments per VM scale set, and terraform support for the resources. Registry names are globally unique, so whenfileRepositoryandcontainerRegistryare unset they default to a name derived from the subscription and cluster name.4. Bonus: authenticated object-store downloads in the bootstrap script
Support downloading nodeup itself from private buckets with curl, giving a private
fileRepositorywithout an OCI registry. nodeup can already read these stores natively; the gap is only the bootstrap script's download of the nodeup binary.curl --aws-sigv4proved unreliable for S3 requests across the distro fleet in testing (unlike the ECR API call), and presigned URLs are ruled out because nodes boot long afterkops update cluster(SigV4 presigning caps at 7 days)Open questions
coredns-coredns), while each file asset maps to its own repository path under thefileRepositoryprefix. The full name set is only known once the asset list is collected. Create the repositories from the collected list at push time, evaluate ECR repository creation templates (create-on-push), or restructure the file layout into a single repository (files are addressed by digest tag, so one repository could suffice for them)?Out of scope
.azurecr.us/.cn,.amazonaws.com.cn): the host gates and token audiences cover the public partitions only.Footnotes
curl --aws-sigv4signs the ECR call correctly from curl 7.86.0 on: Debian 12, Ubuntu 24.04, Amazon Linux 2023, and Flatcar qualify. ↩