This is a template deployment to self-host a debian mirror. Good for home labs, enterprises and everything in-between.
This project follows the guidance outlined in Declarative Management of Kubernetes Objects Using Kustomize and uses bases and overlays to define deployments. Deployment-specific configurations should not be included in the bases, and overlays should be generated with patches to the base to cater to specific deployments. Below are the files in the project structure and their purpose:
debmirror
.We will be creating an overlay for this specific deployment. In this overlay we can add deployment specific resources such as secrets or patching base resources. If you are unfamiliar with patching in a kustomize configuration using overlays, please review patching documentation maintained by kubernetes.
First we need to create an overlay. To do that simply make a directory like so:
mkdir -p overlays/local
Replace local
with any meaningful directory name for your deployment.
The debmirror tool used in the base container requires persistent storage; as much as 100 gigs if mirroring source and binaries. In this example I will be patching the hostPath volume type defined in the nginx deployment and the debmirror job. HostPath is the default one defined in the base as a placeholder but is only okay to use if you're on a single-node kubernetes deployment. For multi-node an nfs, ceph or other network based storage should be used.
For this example I will be using a cephfs solution without a persistent volume claim but consult kubernetes documentation on Persistent Volumes and the lifecycle of Persistent Volume Claims for best-practices guidance. Since I'm using cephfs I will need to define a secret with the key used to authenticate with ceph storage area network:
cat > overlays/local/ceph-secret.yaml << EOF
apiVersion: v1
stringData:
key: [INSERT YOUR CEPHFS KEY]
kind: Secret
metadata:
name: ceph-secret
EOF
Now let's create the nginx deployment storage patch:
cat > overlays/local/nginx-patch.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: debmirror
component: http
name: debmirror-nginx
spec:
template:
spec:
containers:
- name: debian-mirror-nginx
volumeMounts:
- name: data-patched
mountPath: /usr/share/nginx/html
- name: nginx-conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: mirror-list
configMap:
name: mirror-list
- name: nginx-conf
configMap:
name: nginx-conf
- name: data-patched
cephfs:
monitors:
- 192.168.1.70:6789
- 192.168.1.71:6789
- 192.168.1.72:6789
user: kube
secretRef:
name: ceph-secret
path: /kube/debmirror/data
EOF
Next we'll modify the debmirror job to point to the same persistent storage. Note that instead of
[ROOT PERSISTENCE DIR]/data
we reference [ROOT PERSISTENCE DIR]
without the /data
subdirectory. That's because
the data directory is all that's needed to serve on the http service and the other directories are used by debmirror
only for keeping track of the state of the mirror.
Now is also a good time to modify the CronJob's schedule if desired.
cat > overlays/local/debmirror-job-patch.yaml << EOF
apiVersion: batch/v1
kind: CronJob
metadata:
name: debmirror-cron
spec:
schedule: "0 3 * * 3"
jobTemplate:
spec:
template:
spec:
containers:
- name: debmirror-cron
volumeMounts:
- name: data-patched
mountPath: /var/spool/apt-mirror
- name: mirror-list-patched
mountPath: /etc/apt/mirror.list
subPath: mirror.list
volumes:
- name: mirror-list-patched
configMap:
name: mirror-list
- name: data-patched
cephfs:
monitors:
- 192.168.1.70:6789
- 192.168.1.71:6789
- 192.168.1.72:6789
user: kube
secretRef:
name: ceph-secret
path: /kube/debmirror # DO NOT USE /data SUBDIRECTORY HERE!
EOF
We will be replacing configmap resources defined in the base. This is only required if you want your hostname to be
something besides localhost
and/or if you want to pull updates from mirrors other than debian.org.
First make the configuration directory:
mkdir -p overlays/local/conf
Next if it's desired to change the resolving hostname, create a default.conf
with the desired hostname in
nginx configuration native syntax.
cat > overlays/local/conf/default.conf << EOF
server {
listen 80;
listen [::]:80;
server_name debmirror.local;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
autoindex on;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
EOF
Next create a patch for the ingress to replace the hostname with the desired hostname:
cat > overlays/local/ingress-patch.yaml << EOF
- op: replace
path: /spec/rules/0/host
value: debmirror.local
EOF
Finally if it's desired to modify the mirror list, create a mirror.list
replacement. I live close to Indiana so I
changed mine to the Purdue debian mirror. I also added armhf
and arm64
architectures to the mirror so I could update
my standard Debian deployments as well as my raspberry pi based deployments.
cat > overlays/local/conf/mirror.list << EOF
set base_path /var/spool/apt-mirror
set mirror_path $base_path/data
set skel_path $base_path/skel
set var_path $base_path/var
set postmirror_script $var_path/postmirror.sh
set defaultarch amd64
set run_postmirror 0
set nthreads 6
set limit_rate 100m
set _tilde 0
set unlink 1
set use_proxy off
set http_proxy 127.0.0.1:3128
set proxy_user user
set proxy_password passwordx
deb http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware
deb [arch=armhf] http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware
deb [arch=arm64] http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware
deb-src http://plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware
deb http://security.debian.org/debian-security stable-security main contrib non-free-firmware
deb [arch=armhf] http://security.debian.org/debian-security stable-security main contrib non-free-firmware
deb [arch=arm64] http://security.debian.org/debian-security stable-security main contrib non-free-firmware
deb-src http://security.debian.org/debian-security stable-security main contrib non-free-firmware
deb http://plug-mirror.rcac.purdue.edu/debian stable-updates main
deb [arch=armhf] http://plug-mirror.rcac.purdue.edu/debian stable-updates main
deb [arch=arm64] http://plug-mirror.rcac.purdue.edu/debian stable-updates main
deb-src http://plug-mirror.rcac.purdue.edu/debian stable-updates main contrib non-free-firmware
deb http://plug-mirror.rcac.purdue.edu/debian stable-backports main
deb [arch=armhf] http://plug-mirror.rcac.purdue.edu/debian stable-backports main
deb [arch=arm64] http://plug-mirror.rcac.purdue.edu/debian stable-backports main
deb-src http://plug-mirror.rcac.purdue.edu/debian stable-backports main contrib non-free-firmware
deb [arch=armhf] http://plug-mirror.rcac.purdue.edu/raspbian stable main contrib non-free firmware rpi
EOF
One can also change the namespace deployed simply by modifying the namespace.yaml resource and/or modifying the kustomization.yaml. Changing the deployed namespace is not covered in this tutorial, but if desired, refer to the kustomization documentation maintained by kubernetes for guidance on making these or other additional changes in the overlay.
All that's left is to tie together all of the above with a kustomization.yaml
file and deploy it. Below is the basic
template with optional line items highlighted with comments:
cat > overlays/local/kustomization.yaml << EOF
namespace: debmirror
resources:
- ../../base
- ceph-secret.yaml
configMapGenerator:
- name: mirror-list # Only necessary if you updated mirror.list
behavior: replace
files:
- conf/mirror.list
- name: nginx-conf # Only necessary if you overrode the default hostname localhost
behavior: replace
files:
- conf/default.conf
patches:
- target:
kind: Ingress
path: ingress-patch.yaml # Only necessary if you overrode the default hostname localhost
patchesStrategicMerge:
- debmirror-job-patch.yaml
- nginx-patch.yaml
EOF
Now simply apply the overlay. The below command will also execute a watch on kubectl get all -n debmirror
so you
can monitor the deployment:
kubectl apply -k overlays/local && \
watch kubectl get all -n debmirror
The output should resemble the following:
Every 2.0s: kubectl get all -n debmirror my-workstation: Fri Jan 5 14:17:23 2024
NAME READY STATUS RESTARTS AGE
pod/debmirror-nginx-85ff7b64fc-jpcts 1/1 Running 0 46s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/debmirror-service ClusterIP 10.152.183.199 <none> 80/TCP 47s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/debmirror-nginx 1/1 1 1 47s
NAME DESIRED CURRENT READY AGE
replicaset.apps/debmirror-nginx-85ff7b64fc 1 1 1 47s
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE
cronjob.batch/debmirror-cron 0 3 * * 3 False 0 <none> 47s
Initiate a synchronization job manually with the following command:
kubectl create job -n debmirror --from=cronjob/debmirror-cron debmirror-manual-job
You can then monitor the job by watching the logs of the generated pod:
# Run kubectl get pods to get the name of you pod
kubectl logs -f -n debmirror pods/debmirror-manual-job-86lbn
You should see output similar to the below:
Downloading 318 index files using 6 threads...
Begin time: Fri Jan 5 19:27:21 2024
[6]... [5]... [4]... [3]... [2]... [1]... [0]...
End time: Fri Jan 5 19:27:34 2024
Processing translation indexes: [TTTTTTTTTTTTT]
...
When done, be sure to cleanup this resource with kubectl delete job/debmirror-manual-job -n debmirror
.
The initial sync will take a long time and download 100's of GB's depending on your mirror list configuration. For me since I was downloading x86-64, armhf and arm64, it was 380+ GB's. Follow on mirror runs will need far less. After your initial sync job wait for the next scheduled sync job and you should see a much smaller size of download logged. Here's logs from my first and most recent sync for comparison:
356.0 GiB will be downloaded into archive.
Downloading 304327 archive files using 6 threads...
...
2.6 GiB will be downloaded into archive.
Downloading 287 archive files using 6 threads...
...
Notice the 2.6 GB vs several hundred. That shows your mirror is working and updating as expected. You can also
inspect the completions with kubectl get jobs -n debmirror
:
$> kubectl get jobs -n debmirror
NAME COMPLETIONS DURATION AGE
20231228-apt-mirror-job 1/1 4h18m 8d
apt-mirror-cron-28404480 1/1 8m44s 2d11h
You can use a browser or curl
to inspect the file structure:
$> curl debmirror.local
<html>
<head><title>Index of /</title></head>
<body>
<h1>Index of /</h1><hr><pre><a href="https://github.com/nuvious/debmirror-kubernetes/blob/main/./">../</a>
<a href="https://github.com/nuvious/debmirror-kubernetes/blob/main/plug-mirror.rcac.purdue.edu/">plug-mirror.rcac.purdue.edu/</a> 28-Dec-2023 17:14 -
<a href="https://github.com/nuvious/debmirror-kubernetes/blob/main/security.debian.org/">security.debian.org/</a> 28-Dec-2023 21:03 -
</pre><hr></body>
</html>
$>
sources.list
After your mirror is synced you should be able to point your package repository to the new mirror. Here's an example
/etc/apt/sources.list
I have from my debian template VM I used in my cluster:
deb http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware
deb-src http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable main contrib non-free-firmware
deb http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable-updates main contrib non-free-firmware
deb-src http://debmirror..local/plug-mirror.rcac.purdue.edu/debian stable-updates main contrib non-free-firmware
Your specific configuration will vary based on the mirrors you use, the distros/architectures you select and the
components you select. Also you can modify the paths in your nginx service and/or ingress as desired if you're only
mirroring from a single mirror to exclude the hostname directory paths (ex make
deb http://debmirror..local/plug-mirror.rcac.purdue.edu/debian
just
deb http://debmirror..local/debian
).
After updating, your sources.list
you should be able to run apt update
and see your server pull from your new local
repository:
nuvious@debiantemplate:~$ sudo apt update
Hit:1 http://debmirror.local/plug-mirror.rcac.purdue.edu/debian stable InRelease
Get:2 http://debmirror.local/plug-mirror.rcac.purdue.edu/debian stable-updates InRelease [52.1 kB]
Fetched 52.1 kB in 0s (128 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
All packages are up to date.
nuvious@debiantemplate:~$
All contributions are welcome and MUST conform to the Declarative Management of Kubernetes Objects Using Kustomize documentation.
The base image of this project will also persist as the one I maintain under nuvious/apt-mirror-docker
. If you would
like to contribute to the image used in this deployment please check out
nuvious/apt-mirror-docker github repository and file issues/make
contributions there.