3.7 KiB
title | subtitle | author | theme |
---|---|---|---|
Host a DevOps exam using NixOS | \footnotesize Deploy a Bastion host and a KVM virtual machine per student in order for them to deploy a website which will be auto-validated. | Yvan SRAKA, Ryan LAHFA | default |
Host a DevOps exam using NixOS
-
DevOps students have to test their skills in a real-world exam, what’s better than scripting it using NixOps in order to reuse it?
-
They will be provided with their own user account on the Bastion and access to a jump user account, WireGuard will be auto-configured on the host to enable access to their KVM host directly.
-
The main point is controlling the KVM guests “declaratively” and giving choice to the students to choose their guest OS: Debian or NixOS.
-
We will see how we can easily generate Nix expression from a scripting language, e.g. Python, and feed it to a NixOps deployment.
Disclaimer: all this is incomplete and experimental!
This was tried during a real exam this summer, but it's very alpha-quality and had to get a lot of last-minute fixes due to external issues (e.g. disks dying…)
Git repository
The full code can be found here\footnote{https://git.newtype.fr/yvan/devops-exam-model}, it's still a bit messy...
\nix
folder contains files on which will focus this talk:
- \color{red}
student-setup.nix
\color{black} is generated by \color{red}generate-setup.py
\color{black} (that rely on \color{red}nix-expr.py
\color{black}) from students data. - \color{red}
deploy.sh
\color{black} push on our setup \color{red}exam.nix
\color{black}, \color{red}student.nix
\color{black}, \color{red}kvm-guests.nix
\color{black} and \color{red}student-setup.nix
\color{black}
deploy.sh
##!/usr/bin/env bash
echo "[+] Regenerating of the setup"
python3 generate_setup.py
echo "[+] Sending Nix files"
rsync --inplace --temp-dir=/tmp -avPz *.nix \
yvan@bastion:/etc/nixos/
echo "[+] Rebuilding of the exam machine"
ssh -t yvan@bastion "sudo nixos-rebuild switch"
generate_setup.py
(subset of)
def students(csv_filename):
with open(csv_filename, 'r', newline='') as csvfile:
ereader = csv.reader(csvfile, delimiter=';')
next(ereader) # exhaust header.
for index, student in enumerate(ereader):
name, surname, email, username = student
wg_pubkey, wg_privkey = wireguard_parameters()
keys = read_keys(username)
yield {
"surname": surname,
"name": name,
"email": email,
"username": username,
"keys": keys,
"wireguardPublicKey": wg_pubkey,
"wireguardPrivateKey": wg_privkey,
}
student-setup.nix
(generated)
ltorvalds = {
email = "torvalds@linux-foundation.org";
guestOperatingSystem = "debian";
index = 42;
keys = [
"ssh-ed25519 ... torvalds@linux-foundation.org"
];
name = "Linus";
surname = "Torvalds";
username = "ltorvalds";
wireguardPrivateKey = "0ECE2Js+RkxQVTyJ9BvZB0DjpEGnWMy1X5cI8R2RdHA=";
wireguardPublicKey = "3o9Dhmrrql/5PZEhi5kS+Fob1m8rN70SXzDGy48bMR0=";
};
rstallman = { ... }
student.nix
(subset of)
mkGuest = name: student: {
memory = "1G";
netDevice = "tap${toString student.index}";
vncDisplay = "localhost:${toString student.index}";
operatingSystem = student.guestOperatingSystem;
};
services.kvmGuests = {
enable = true;
guests = mapAttrs mkGuest cfg.students;
};
# Create users for each student + management/jump accounts.
users.users = (mapAttrs mkBastionUser cfg.students) // ({
jump = mkJumpUser cfg.students;
admin = adminUser;
});