From a2f4ef8a1d83ac42195ad9c10d95572b44609dcd Mon Sep 17 00:00:00 2001 From: spmfox Date: Mon, 27 May 2024 22:02:53 -0400 Subject: Initial commit of project --- .gitignore | 1 + README.md | 103 ++++++++++++++++++++++++++++++ collections/requirements.yml | 3 + deploy.yml | 109 ++++++++++++++++++++++++++++++++ docs/sample-environment.yml | 25 ++++++++ iso.yml | 42 ++++++++++++ templates/bootcblade-iso.sh.j2 | 15 +++++ templates/bootcblade-rebuild.service.j2 | 6 ++ templates/bootcblade-rebuild.timer.j2 | 10 +++ templates/bootcblade.config.toml.j2 | 5 ++ templates/bootcblade.containerfile.j2 | 24 +++++++ templates/centos-bootc-deploy.sh.j2 | 9 +++ update.yml | 15 +++++ 13 files changed, 367 insertions(+) create mode 100644 .gitignore create mode 100644 collections/requirements.yml create mode 100644 deploy.yml create mode 100644 docs/sample-environment.yml create mode 100644 iso.yml create mode 100644 templates/bootcblade-iso.sh.j2 create mode 100644 templates/bootcblade-rebuild.service.j2 create mode 100644 templates/bootcblade-rebuild.timer.j2 create mode 100644 templates/bootcblade.config.toml.j2 create mode 100644 templates/bootcblade.containerfile.j2 create mode 100644 templates/centos-bootc-deploy.sh.j2 create mode 100644 update.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b5fba1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +inventories diff --git a/README.md b/README.md index 56a6f0f..e10cf9d 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,106 @@ Ansible automation for deploying a KVM hypervisor using bootc on CentOS Stream. ![BootcBlade](docs/images/logo.png) + +This Ansible automation uses bootc to create "the perfect" KVM hypervisor with ZFS, NFS + Samba, Cockpit, and Sanoid + Syncoid. + +## Usage - deploy on top of existing system +1. Install a fresh CentOS Stream 9 to the desired host - use a minimal install to save disk space on the resulting deployed machine +2. Install ```podman``` on the host +3. Generate an SSH key +4. Create inventory using the example in the ```docs``` folder +5. Make sure you have passwordless sudo or root access to desired host using your new SSH key +6. Install needed Ansible collections ```ansible-galaxy install -r collections/requirements.yml``` +7. Run ```ansible-playbook deploy.yml -i inventories/``` + +## Usage - create ISO for automatic installation +1. You will need an existing machine with Podman and Ansible +2. The Ansible can be run locally or remotely - just be sure to have root or passwordless sudo working +3. Create inventory for a local or remote run using the example in the ```docs``` folder +4. Run ```ansible-playbook iso.yml -i inventories/``` +5. ISO will be created on the local or remote machine, ```/root/bootcblade-output/bootiso/install.iso``` + + +## Credentials +### Deploy +- The SSH key (for the user) is added for the root user and baked into the image generation - **the resulting BootcBlade image will include your SSH key** +- The user, ssh key, and password (if defined) are configured after deployment via Ansible + +### ISO +- The user, SSH key, and password (if defined) are baked into the ISO - **the resulting ISO will include your user, SSH key, and hashed password (if defined)** + + +## How It Works +### Deploy +1. A new or existing system must exist. This system should be as small as possible because its filesystem will persist in the resulting deployed machine +2. A "base" centos-bootc is used for the first deploy - the user SSH key is set for the root user now +3. Once the base is deployed, we use that to build and ```bootc switch``` into the final BootcBlade image +4. Ansible creates the user with (or without) the password and adds the SSH key + +### ISO +1. An existing system must exist to build the ISO on, no changes to this system are made +2. Running the Ansible will create the files necessary to generate the ISO - including the user with (or without) password and the SSH key +3. Resulting ISO is stored in the /root directory +4. Configuration files and all used container images are deleted + +## Updating +Updates happen in two ways. When ```deploy.yml``` is used, there is a systemd unit created (```bootcblade-rebuild.service```) that is created and will run once a week. +This service depends on ```/root/bootcblade.containerfile``` to exist, as the container is rebuilt. If the build is successful, then the new container is staged. +The default update service ```bootc-fetch-apply-updates.service``` is masked as well so it will not run. + +If ```deploy.yml``` is not used (perhaps the system was installed using an ISO created by ```iso.yml```), then there is no automated update process +and the default ```bootc-fetch-apply-updates.service``` is still enabled. If you want the automatic update method, then ```ansible-playbook deploy.yml --tags configure-bootcblade``` +will need to be run, either remotely or as localhost, and the required variables will need to be defined. + +## Troubleshooting +### ```/root/bootcblade.containerfile``` is gone: +You can use ```update.yml``` to recreate this, assuming you have the correct inventory. + +### BootcBlade will no longer build +It is possible that the upstream ```centos-bootc``` project will change something (the kernel perhaps) that makes ZFS building no longer possible. You can go to [https://quay.io/repository/centos-bootc/centos-bootc?tab=tags](https://quay.io/repository/centos-bootc/centos-bootc?tab=tags) and try specifing an older tag using ```centos_bootc_tag```. + +Another possibility is to just wait, ususally these repo related problems work themselves out and the image will build again within a week. + +## Variable Usage +This is a description of each variable, what it does, and a table to determine when it is needed. + +- ```create_user```: This user will be created during ```deploy.yml``` and ```iso.yml``` +- ```create_user_password```: This password will be used for the created user +- ```create_user_ssh_pub```: This is a SSH pubkey that will be added to the created user during ```deploy.yml``` and ```iso.yml```, also it is applied to the root user in ```deploy.yml``` +- ```create_user_shell```: This shell setting will be used for the created user only during ```deploy.yml``` +- ```centos_bootc_tag```: Override the tag for centos-bootc source image for ```deploy.yml```, ```iso.yml```, and ```update.yml``` +- ```bootc_acknowledge```: This setting is only effective when setting it to ```false```, newer versions of ```bootc``` require an acknowledgment during ```deploy.yml``` but older versions break +if this is defined - so this can override the default and remove that +- ```ansible_user``` - This is an Ansible variable, useful for connecting to the initial machine with a different user during ```deploy.yml``` +- ```ansible_connection``` - This is an Ansible variable, useful when running Ansible locally with ```iso.yml``` and ```update.yml``` + +### deploy.yml +| Variable | Used | Required | +| -------- | ---- | -------- | +| create_user | X | - | +| create_user_password | X | - | +| create_user_ssh_pub | X | X | +| create_user_shell | X | - | +| centos_bootc_tag | X | - | +| bootc_acknowledge | X | - | + +### iso.yml +| Variable | Used | Required | +| -------- | ---- | -------- | +| create_user | X | X| +| create_user_password | X | - | +| create_user_ssh_pub | X | X | +| create_user_shell | - | - | +| centos_bootc_tag | X | - | +| bootc_acknowledge | - | - | + +### update.yml +| Variable | Used | Required | +| -------- | ---- | -------- | +| create_user | - | - | +| create_user_password | - | - | +| create_user_ssh_pub | - | - | +| create_user_shell | - | - | +| centos_bootc_tag | X | - | +| bootc_acknowledge | - | - | + diff --git a/collections/requirements.yml b/collections/requirements.yml new file mode 100644 index 0000000..1c4fcf4 --- /dev/null +++ b/collections/requirements.yml @@ -0,0 +1,3 @@ +# ansible-galaxy install -r collections/requirements.yml +collections: + - name: ansible.posix diff --git a/deploy.yml b/deploy.yml new file mode 100644 index 0000000..f4ef4f9 --- /dev/null +++ b/deploy.yml @@ -0,0 +1,109 @@ +- hosts: all + become: true + gather_facts: false + vars: + ansible_ssh_common_args: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" + + tasks: + - name: Block for deploy-base-bootc + block: + - name: Create /root/centos-bootc-deploy.sh + ansible.builtin.template: + src: "centos-bootc-deploy.sh.j2" + dest: "/root/centos-bootc-deploy.sh" + + - name: Create root ssh authorized keys + ansible.posix.authorized_key: + user: "root" + key: "{{ create_user_ssh_pub }}" + state: present + + - name: Run bootc deployment for basic centos-bootc environment + ansible.builtin.shell: "bash /root/centos-bootc-deploy.sh" + + - name: Reboot into basic centos-bootc environment + ansible.builtin.reboot: + reboot_timeout: 1 + ignore_errors: true + tags: deploy-base-bootc + + - name: Block for deploy-bootcblade + block: + - name: Wait for connectivity to basic centos-bootc environment + ansible.builtin.wait_for_connection: + + - name: Create /root/BootcBlade.containerfile + ansible.builtin.template: + src: "bootcblade.containerfile.j2" + dest: "/root/bootcblade.containerfile" + + - name: Build BootcBlade container image + ansible.builtin.shell: "podman build -t localhost/bootcblade -f /root/bootcblade.containerfile" + + - name: Run bootc-switch into BootcBlade image + ansible.builtin.shell: "bootc switch --transport containers-storage localhost/bootcblade:latest" + + - name: Reboot into BootcBlade environment + ansible.builtin.reboot: + vars: + ansible_user: "root" + tags: deploy-bootcblade + + - name: Block for configure-bootcblade + block: + - name: Create user + ansible.builtin.user: + name: "{{ create_user }}" + groups: "wheel" + append: true + shell: "{{ create_user_shell if create_user_shell is defined else '/bin/bash' }}" + when: create_user is defined and create_user_password is not defined + + - name: Create user (with password) + ansible.builtin.user: + name: "{{ create_user }}" + groups: "wheel" + append: true + shell: "{{ create_user_shell if create_user_shell is defined else '/bin/bash' }}" + password: "{{ create_user_password | password_hash('sha512') }}" + when: create_user is defined and create_user_password is defined + + - name: Create user ssh authorized keys + ansible.posix.authorized_key: + user: "{{ create_user }}" + key: "{{ create_user_ssh_pub }}" + state: present + when: (create_user is defined) and (create_user_ssh_pub is defined) + + - name: Add bootcblade-rebuild.service and .timer files for automatic update + ansible.builtin.template: + src: "{{ item }}.j2" + dest: "/etc/systemd/system/{{ item }}" + loop: + - "bootcblade-rebuild.service" + - "bootcblade-rebuild.timer" + + - name: Enable and start bootcblade-rebuild services + ansible.builtin.systemd_service: + name: "{{ item.name }}" + state: "{{ item.state }}" + enabled: "{{ item.enabled }}" + daemon-reload: true + loop: + - { name: "bootcblade-rebuild.service", state: "stopped", enabled: false } + - { name: "bootcblade-rebuild.timer", state: "started", enabled: true } + + - name: Stop and disable (mask) bootc-fetch-apply-updates + ansible.builtin.systemd_service: + name: "{{ item }}" + state: "stopped" + enabled: false + masked: true + daemon-reload: true + loop: + - "bootc-fetch-apply-updates.timer" + - "bootc-fetch-apply-updates.service" + vars: + ansible_user: "root" + tags: configure-bootcblade + diff --git a/docs/sample-environment.yml b/docs/sample-environment.yml new file mode 100644 index 0000000..18e65b0 --- /dev/null +++ b/docs/sample-environment.yml @@ -0,0 +1,25 @@ +# See README.md to determine which variables are needed for which activity. + +all: + hosts: + remote-machine: + create_user_ssh_pub: "ssh-rsa " +# This is the bare minimum required - no users will be created, this key will be used for root only. + + remote-machine2: + ansible_user: "root" + create_user: "spmfox" + create_user_password: "password" + create_user_shell: "/usr/bin/fish" + create_user_ssh_pub: "ssh-rsa " +# This will create a user with a custom shell defined, and use the same SSH key for the user and root. User will also have a password. +# Root account is used for the initial connection during deployment on an existing machine. + + localhost: + ansible_connection: local + create_user: "spmfox" + create_user_ssh_pub: "ssh-rsa " + centos_bootc_tag: "stream9-1714747911" + bootc_acknowledge: false +# This is a local run only, useful for creating ISOs and perhaps running update.yml. The centos-bootc:tag can be specified here, using an earlier version. +# Because this version is before the need for an acknowledgement during bootc deployment, we can override and remove that from the command. diff --git a/iso.yml b/iso.yml new file mode 100644 index 0000000..52b49e7 --- /dev/null +++ b/iso.yml @@ -0,0 +1,42 @@ +- hosts: all + become: true + gather_facts: false + + tasks: + - name: Create /root/bootcblade-output folder + ansible.builtin.file: + path: "/root/bootcblade-output" + state: directory + tags: setup + + - name: Create files + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + loop: + - { src: "bootcblade.config.toml.j2", dest: "/root/bootcblade.config.toml" } + - { src: "bootcblade.containerfile.j2", dest: "/root/bootcblade.containerfile" } + - { src: "bootcblade-iso.sh.j2", dest: "/root/bootcblade-iso.sh" } + tags: setup + + - name: Build BootcBlade container image + ansible.builtin.shell: "podman build -t localhost/bootcblade -f /root/bootcblade.containerfile" + tags: build-container + + - name: Create BootcBlade ISO + ansible.builtin.shell: "bash /root/bootcblade-iso.sh" + tags: build-iso + + - name: Cleanup files + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - "/root/bootcblade.config.toml" + - "/root/bootcblade.containerfile" + - "/root/bootcblade-iso.sh" + tags: cleanup + + - name: Cleanup images + ansible.builtin.shell: "podman image rm localhost/bootcblade ; podman image rm quay.io/centos-bootc/bootc-image-builder ; podman image rm quay.io/centos-bootc/centos-bootc:{{ centos_bootc_tag if centos_bootc_tag is defined else 'stream9' }} ; podman image prune -f" + tags: cleanup diff --git a/templates/bootcblade-iso.sh.j2 b/templates/bootcblade-iso.sh.j2 new file mode 100644 index 0000000..0d276e9 --- /dev/null +++ b/templates/bootcblade-iso.sh.j2 @@ -0,0 +1,15 @@ +#!/bin/bash +podman run \ + --rm \ + -it \ + --privileged \ + --pull=newer \ + --security-opt label=type:unconfined_t \ + -v /root/bootcblade.config.toml:/config.toml \ + -v /root/bootcblade-output:/output \ + -v /var/lib/containers/storage:/var/lib/containers/storage \ + quay.io/centos-bootc/bootc-image-builder:latest \ + --type anaconda-iso \ + --rootfs xfs \ + --local \ + localhost/bootcblade:latest diff --git a/templates/bootcblade-rebuild.service.j2 b/templates/bootcblade-rebuild.service.j2 new file mode 100644 index 0000000..525978a --- /dev/null +++ b/templates/bootcblade-rebuild.service.j2 @@ -0,0 +1,6 @@ +[Unit] +Description=BootcBlade rebuild service + +[Service] +ExecStart=/usr/bin/podman build -t localhost/bootcblade -f /root/bootcblade.containerfile +ExecStartPost=/usr/bin/bash -c "/usr/bin/sleep 10 ; /usr/bin/bootc update && /usr/bin/podman image prune -f" diff --git a/templates/bootcblade-rebuild.timer.j2 b/templates/bootcblade-rebuild.timer.j2 new file mode 100644 index 0000000..cffd275 --- /dev/null +++ b/templates/bootcblade-rebuild.timer.j2 @@ -0,0 +1,10 @@ +[Unit] +Description=bootcblade-rebuild timer + +[Timer] +OnBootSec=60min +OnUnitActiveSec=1w + + +[Install] +WantedBy=timers.target diff --git a/templates/bootcblade.config.toml.j2 b/templates/bootcblade.config.toml.j2 new file mode 100644 index 0000000..2087b70 --- /dev/null +++ b/templates/bootcblade.config.toml.j2 @@ -0,0 +1,5 @@ +[[customizations.user]] +name = "{{ create_user }}" +{{ 'password = "' if create_user_password is defined else ''}}{{ create_user_password if create_user_password is defined else '' }}{{ '"' if create_user_password is defined else '' }} +key = "{{ create_user_ssh_pub }}" +groups = ["wheel"] diff --git a/templates/bootcblade.containerfile.j2 b/templates/bootcblade.containerfile.j2 new file mode 100644 index 0000000..7e5ade6 --- /dev/null +++ b/templates/bootcblade.containerfile.j2 @@ -0,0 +1,24 @@ +FROM quay.io/centos-bootc/centos-bootc:{{ centos_bootc_tag if centos_bootc_tag is defined else 'stream9' }} +RUN mkdir /var/roothome +RUN echo "%wheel ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/wheel-passwordless-sudo +RUN dnf -y install epel-release && \ + dnf -y install https://zfsonlinux.org/epel/zfs-release-2-3$(rpm --eval "%{dist}").noarch.rpm && \ + dnf -y install kernel-devel-$(ls /usr/lib/modules) && \ + dnf -y install zfs && \ + dkms build zfs/$(rpm -q --qf '%{VERSION}' zfs) -k $(ls /usr/lib/modules) && \ + dkms install zfs/$(rpm -q --qf '%{VERSION}' zfs) -k $(ls /usr/lib/modules) +RUN dnf -y install vim git podman fish ansible wget && \ + echo "qemu:x:107:107:qemu user:/:/sbin/nologin" >> /etc/passwd && \ + dnf -y install qemu-kvm libvirt virt-install virt-viewer && \ + dnf -y install cockpit cockpit-bridge cockpit-file-sharing cockpit-machines cockpit-pcp cockpit-podman cockpit-storaged cockpit-system +RUN git clone https://github.com/45drives/cockpit-zfs-manager.git /root/cockpit-zfs-manager && \ + cp -r /root/cockpit-zfs-manager/zfs /usr/share/cockpit && \ + rm -r /root/cockpit-zfs-manager +RUN git clone https://github.com/jimsalterjrs/sanoid.git /root/sanoid && \ + cd /root/sanoid && git checkout $(git tag | grep "^v" | tail -n 1) && cp sanoid syncoid findoid sleepymutex /usr/local/sbin && \ + mkdir /etc/sanoid && cp sanoid.defaults.conf /etc/sanoid && touch /etc/sanoid/sanoid.conf && cp sanoid.conf /etc/sanoid/sanoid.example.conf && \ + rm -r /root/sanoid +RUN dnf -y install perl-Data-Dumper perl-Getopt-Long lzop mbuffer mhash pv && \ + PERL_MM_USE_DEFAULT=1 cpan install Capture::Tiny && PERL_MM_USE_DEFAULT=1 cpan install Config::IniFiles +RUN dnf -y install firewalld && \ + systemctl enable firewalld diff --git a/templates/centos-bootc-deploy.sh.j2 b/templates/centos-bootc-deploy.sh.j2 new file mode 100644 index 0000000..2083295 --- /dev/null +++ b/templates/centos-bootc-deploy.sh.j2 @@ -0,0 +1,9 @@ +#!/bin/bash +podman run --rm --privileged \ + --pid=host --security-opt label=type:unconfined_t \ + --volume /dev:/dev \ + --volume /var/lib/containers:/var/lib/containers \ + --volume /:/target \ + --entrypoint bootc \ + quay.io/centos-bootc/centos-bootc:{{ centos_bootc_tag if centos_bootc_tag is defined else 'stream9' }} \ + install to-filesystem --skip-fetch-check --replace=alongside /target --root-ssh-authorized-keys /target/root/.ssh/authorized_keys {{ '' if bootc_acknowledge is false else '--acknowledge-destructive' }} diff --git a/update.yml b/update.yml new file mode 100644 index 0000000..9d23f7a --- /dev/null +++ b/update.yml @@ -0,0 +1,15 @@ +- hosts: all + become: true + gather_facts: false + + tasks: + - name: Create /root/BootcBlade.containerfile + ansible.builtin.template: + src: "bootcblade.containerfile.j2" + dest: "/root/bootcblade.containerfile" + + - name: Build BootcBlade container image + ansible.builtin.shell: "podman build -t localhost/bootcblade -f /root/bootcblade.containerfile" + + - name: Run bootc-update to apply newly built image + ansible.builtin.shell: "bootc update" -- cgit