Bootstrap an Air-Gapped Cluster
Introduction
This guide outlines the steps to bootstrap a Cozystack cluster in an air-gapped environment.
Air-gapped installation means that the cluster has no direct access to the Internet. All necessary resources, such as images and metadata, must be available on the private network.
Configuring Talos Nodes
For installing with Talm, it’s enough to make all mentioned changes once in ./templates/_helpers.tpl and then build the actual node configuration files with talm template.
For installing with talosctl, the changes should be made in patch.yaml and patch-controlplane.yaml.
1. Configure NTP Servers
Accurate time synchronization is critical for the cluster. In your Talos machine configuration, set local NTP servers that are accessible inside your private network:
machine:
time:
servers:
# example values
- 192.168.0.4
- 10.10.0.5
Ensure that these NTP servers are reachable from the first Talos node.
2. Configure Container Registry Mirrors
Since the cluster cannot access public container registries, it needs to use their local mirrors. Creating such mirrors is out of the scope of this guide.
Update your machine configuration in the following way, providing the IP addresses and ports of your local mirrors for each registry:
machine:
registries:
mirrors:
docker.io:
endpoints:
- http://10.0.0.1:8082
ghcr.io:
endpoints:
- http://10.0.0.1:8083
gcr.io:
endpoints:
- http://10.0.0.1:8084
registry.k8s.io:
endpoints:
- http://10.0.0.1:8085
quay.io:
endpoints:
- http://10.0.0.1:8086
cr.fluentbit.io:
endpoints:
- http://10.0.0.1:8087
docker-registry3.mariadb.com:
endpoints:
- http://10.0.0.1:8088
config:
"10.0.0.1:8082":
tls:
insecureSkipVerify: true
auth:
username: myuser
password: mypass
Of course, the values for config.[0].auth.* are given as examples, and you need to use real credentials.
Make sure your local registry proxies mirror all required images for Talos and Kubernetes components.
3. Add CA Certificate
To use a private Certificate Authority, you need to add its certificate to the nodes.
# talm: nodes=["10.10.10.10"], endpoints=["10.10.10.10"], templates=["templates/controlplane.yaml"]
# THIS FILE IS AUTOGENERATED. PREFER TEMPLATE EDITS OVER MANUAL ONES.
machine:
# ...
# ...
discovery:
enabled: false
etcd:
advertisedSubnets:
- 10.4.100.10/24
allowSchedulingOnControlPlanes: true
---
apiVersion: v1alpha1
kind: TrustedRootsConfig
name: my-enterprise-ca
certificates: |
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
4. Apply Changes
After you have made the changes above, you can apply the configuration and bootstrap a cluster:
Using Talm
Rebuild the node configuration files and apply them to each node:
talm template -e <ip> -n <ip> -t templates/controlplane.yaml -i > nodes/node1.yaml
talm apply -f nodes/node1.yaml
# repeat for each node
Finally, bootstrap the cluster as usual:
talm bootstrap -f nodes/node1.yaml
Read the Talm configuration guide to learn more.
Using talosctl
Apply the configuration to each node:
talosctl apply -f controlplane.yaml -n <ip> -e <ip> -i
Finally, bootstrap the cluster using one of the nodes:
talosctl bootstrap -n <ip> -e <ip>
Read the
talosctl configuration guide to learn more.
5. Configure Container Registry Mirrors for Tenant Kubernetes
Tenant Kubernetes clusters in Cozystack use Kamaji for the control plane. The control plane components run as pods on the management cluster nodes, so they automatically use the registry mirrors configured in step 2 for Talos.
However, tenant worker nodes run as separate virtual machines with their own containerd instance. These worker nodes need a separate registry mirror configuration.
To perform this configuration, you first need to deploy a Cozystack cluster of (or upgrade your cluster to) version v0.32.0 or later. Check your current cluster version with:
kubectl get deploy -n cozy-system cozystack -oyaml | grep installer
Option A: Configure via platform package
The platform package can automatically generate the patch-containerd secret
from the registries section in the platform values.
Add the registries section to your cozystack-platform.yaml:
apiVersion: cozystack.io/v1alpha1
kind: Package
metadata:
name: cozystack.cozystack-platform
spec:
variant: isp-full
components:
platform:
values:
# ... your existing publishing, networking, etc. ...
registries:
mirrors:
docker.io:
endpoints:
- http://10.0.0.1:8082
ghcr.io:
endpoints:
- http://10.0.0.1:8083
gcr.io:
endpoints:
- http://10.0.0.1:8084
registry.k8s.io:
endpoints:
- http://10.0.0.1:8085
quay.io:
endpoints:
- http://10.0.0.1:8086
cr.fluentbit.io:
endpoints:
- http://10.0.0.1:8087
docker-registry3.mariadb.com:
endpoints:
- http://10.0.0.1:8088
config:
"10.0.0.1:8082":
tls:
insecureSkipVerify: true
auth:
username: myuser
password: mypass
Then apply it:
kubectl apply -f cozystack-platform.yaml
This will create a patch-containerd secret in the cozy-system namespace,
which is automatically copied to every tenant Kubernetes cluster.
Alternatively, patch an existing platform package
If the platform package is already deployed, you can add registry mirrors with a patch:
kubectl patch packages.cozystack.io cozystack.cozystack-platform --type=merge -p '{
"spec": {
"components": {
"platform": {
"values": {
"registries": {
"mirrors": {
"docker.io": {
"endpoints": ["http://10.0.0.1:8082"]
},
"ghcr.io": {
"endpoints": ["http://10.0.0.1:8083"]
},
"gcr.io": {
"endpoints": ["http://10.0.0.1:8084"]
},
"registry.k8s.io": {
"endpoints": ["http://10.0.0.1:8085"]
},
"quay.io": {
"endpoints": ["http://10.0.0.1:8086"]
},
"cr.fluentbit.io": {
"endpoints": ["http://10.0.0.1:8087"]
},
"docker-registry3.mariadb.com": {
"endpoints": ["http://10.0.0.1:8088"]
}
},
"config": {
"10.0.0.1:8082": {
"tls": {
"insecureSkipVerify": true
},
"auth": {
"username": "myuser",
"password": "mypass"
}
}
}
}
}
}
}
}
}'
Option B: Create the secret manually
Alternatively, create a
Kubernetes Secret named patch-containerd directly:
apiVersion: v1
kind: Secret
metadata:
name: patch-containerd
namespace: cozy-system
type: Opaque
stringData:
docker.io.toml: |
server = "https://registry-1.docker.io"
[host."http://10.0.0.1:8082"]
capabilities = ["pull", "resolve"]
skip_verify = true
ghcr.io.toml: |
server = "https://ghcr.io"
[host."http://10.0.0.1:8083"]
capabilities = ["pull", "resolve"]
skip_verify = true
gcr.io.toml: |
server = "https://gcr.io"
[host."http://10.0.0.1:8084"]
capabilities = ["pull", "resolve"]
skip_verify = true
registry.k8s.io.toml: |
server = "https://registry.k8s.io"
[host."http://10.0.0.1:8085"]
capabilities = ["pull", "resolve"]
skip_verify = true
quay.io.toml: |
server = "https://quay.io"
[host."http://10.0.0.1:8086"]
capabilities = ["pull", "resolve"]
skip_verify = true
cr.fluentbit.io.toml: |
server = "https://cr.fluentbit.io"
[host."http://10.0.0.1:8087"]
capabilities = ["pull", "resolve"]
skip_verify = true
docker-registry3.mariadb.com.toml: |
server = "https://docker-registry3.mariadb.com"
[host."http://10.0.0.1:8088"]
capabilities = ["pull", "resolve"]
skip_verify = true
If your registry mirrors require authentication, add a custom Authorization header
with Base64-encoded credentials:
server = "https://registry-1.docker.io"
[host."http://10.0.0.1:8082"]
capabilities = ["pull", "resolve"]
skip_verify = true
[host."http://10.0.0.1:8082".header]
Authorization = "Basic bXl1c2VyOm15cGFzcw=="
To generate the Base64-encoded value, run:
echo -n 'myuser:mypass' | base64
For dynamic or token-based authentication (e.g., Docker Hub), use Kubernetes image pull secrets instead of plaintext credentials.
How it works
The patch-containerd secret from the cozy-system namespace is automatically copied
to every tenant Kubernetes cluster namespace during deployment.
The secret data is mounted into worker node VMs as containerd registry configuration files
at /etc/containerd/certs.d/<registry>/hosts.toml.
Per-cluster configuration
It is possible to configure registry mirrors for a particular tenant Kubernetes cluster
instead of using the global patch-containerd secret:
- The tenant cluster must be deployed with a Kubernetes package version 0.23.1 or later, which is available since Cozystack 0.32.1.
- Before deploying the tenant cluster, create a Kubernetes Secret named
kubernetes-<cluster-name>-patch-containerdin the tenant cluster namespace, using the same format as the examples above.
patch-containerd secret and a per-cluster secret exist, the global secret takes precedence and the per-cluster secret is ignored. To use a per-cluster configuration, ensure that the global patch-containerd secret in the cozy-system namespace is not present.To learn more about registry configuration values, read the CRI Plugin configuration guide