Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions image-builder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
./logs/
52 changes: 52 additions & 0 deletions image-builder/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
FROM ubuntu:24.04

ARG GOSU_VERSION=1.19

ENV GOSU_URL="https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64" \
HOME=/home/openstack \
PATH=/home/openstack/bin:/home/openstack/.local/bin:/home/openstack/image-builder/images/capi/.local/bin:$PATH \
IMAGE_BUILDER="https://github.com/kubernetes-sigs/image-builder"

RUN mkdir -p /home/openstack/bin

RUN apt update && apt upgrade -y && \
apt install -y vim git curl unzip findutils diffutils less \
python3 python-is-python3 python3-pip jq build-essential \
ca-certificates gnupg \
python3-openstackclient python3-magnumclient \
python3-octaviaclient python3-designate && \
apt clean && rm -rf /var/lib/apt/lists/* && \
# gosu install start
curl -fsSL $GOSU_URL -o /usr/local/bin/gosu && \
curl -fsSL $GOSU_URL.asc -o /tmp/gosu.asc && \
export GNUPGHOME="$(mktemp -d)" && \
# @tianon's, maintainer of gosu, public key, in case you are wondering
export KEY=B42F6819007F00F88E364FD4036A9C25BF357DD4 && \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys $KEY && \
gpg --batch --verify /tmp/gosu.asc /usr/local/bin/gosu && \
gpgconf --kill all && \
rm -rf "$GNUPGHOME" /tmp/gosu.asc && \
chmod +x /usr/local/bin/gosu && \
# gosu install end
useradd openstack && \
chown -R openstack /home/openstack

USER openstack

# Begin image-builder
WORKDIR /home/openstack
RUN git clone $IMAGE_BUILDER && \
cd image-builder/images/capi/ && \
make deps-openstack

# $HOME/.local/bin is the ansible install path
# $HOME/image-builder/images/capi/.local/bin is the packer install path
ENV PATH=$HOME/.local/bin:$HOME/image-builder/images/capi/.local/bin:$PATH
# End image-builder

WORKDIR /home/openstack/image-builder/images/capi

COPY --chmod=+x entrypoint.sh /

# Start container
ENTRYPOINT ["/entrypoint.sh"]
65 changes: 65 additions & 0 deletions image-builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# How to image-builder

[Image builder](https://github.com/kubernetes-sigs/image-builder) is a K8s special interest group project that creates "ClusterAPI compatible" images that something like Openstack Magnum can readily use to create cluster nodes. The project makes use of [Packer](https://developer.hashicorp.com/packer), a Hashicorp project that can be used to make machine images for a variety of platforms using a single configuration, and [Ansible](https://docs.ansible.com)--an "IT automation engine" used to configure and run tasks on hosts.

We make use of it on Jetstream2 cloud to be capable of producing CAPI/K8s ready images that incorporate the most recent vulnerability patches and/or mitigations. It can also be used to further customize images as appropriate. For example, this work will also ensure that the `nsf-common` package is installed, allowing for Network File System mounts.

## Unidata Quickstart

Commands run on the `openstack-jetstream2` machine

1) `cd usg-devops/image-builder`
2) Run `image-builder.sh`
3) If successful, set `kube_version` property on resulting image:
`openstack image set --property kube_version=v${VERSION} $IMAGE_NAME`
4) Edits vars in `create_cluster_template.sh` and run

# WIP

## Prerequisites

You will need:

- `docker` and `docker-compose`
- A valid `openrc.sh` file with credentials for Jetstream2
- The `openstack` CLI
- An SSH key-pair; public key available on Jetstream2

### Creating security groups to allow SSH traffic

The host that image-builder runs on needs to be able to SSH into the server that is created to build the image. To this end, we'll create two security groups:

1) `image-builder`: Opens port 22 (SSH) to any openstack servers with the `image-builder-client` group; will be specified in `var_file.json`
2) `image-builder-client`: A "dummy" security group attached to the server that image-builder runs on; doesn't open any ports

Create these security groups, and the necessary rule, as follows:

```bash
openstack security group create --description "Allow machines with this SG to SSH into machines with the image-builder SG" image-builder-client
openstack security group create --description "Open port 22 (SSH) to any openstack servers with the 'image-builder-client' SG" image-builder
openstack security group rule create --protocol tcp --dst-port 22 --remote-group image-builder-client image-builder
```

Now add the `image-builder-client` to the Jetstream2 host where image-builder is running:

```bash
openstack server add security group <client-name> image-builder-client
```

>[!NOTE]
>At Unidata, we've already done these steps and attached the `image-builder-client` security group to the `openstack-jetstream2` machine.

### Create an SSH keypair

Image-builder uses `ansible` and thus `ssh` to configure our "source instance" that will be snapshot into the resulting image. On your image-builder client machine you must create an SSH keypair in the usual/preferred manner using `ssh-keygen`.

Then, use the `openstack` CLI to upload the public key to Jetstream2. We will refer to this keypair resource in one of our `packer` configuration files.

`openstack keypair create --public-key /path/to/public/key.pub`

>[!IMPORTANT]
>The `openstack` application credentials (i.e. `openrc.sh` or `clouds.yaml`) used to run the above command must be the same that are supplied to the image-builder workflow; `openstack` is not aware of keypairs created by other users.

## Running the workflow

The workflow has been containerized. Thus, there is no need to install or build any additional dependencies other than docker and docker-compose. This workflow is intended to be ran on a Jetstream2 machine
34 changes: 34 additions & 0 deletions image-builder/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
set -euo pipefail

USERNAME="openstack"
USER_ID=${OPENSTACK_USER_ID:-1001}
GROUP_ID=${OPENSTACK_GROUP_ID:-1001}

case "$USER_ID" in
(''|*[!0-9]*)
echo "ERROR: OPENSTACK_USER_ID must be numeric, got '$USER_ID'" >&2;
exit 1;;
esac
case "$GROUP_ID" in
(''|*[!0-9]*)
echo "ERROR: OPENSTACK_GROUP_ID must be numeric, got '$GROUP_ID'" >&2;
exit 1;;
esac
if [ "$USER_ID" -eq 0 ] || [ "$GROUP_ID" -eq 0 ]; then
echo "ERROR: OPENSTACK_USER_ID and OPENSTACK_GROUP_ID must be non-root" >&2
exit 1
fi

if ! getent group "$USERNAME" >/dev/null; then
groupadd -r "$USERNAME" -g "$GROUP_ID"
fi

if ! id -u "$USERNAME" >/dev/null 2>&1; then
useradd -u "$USER_ID" -g "$USERNAME" -s /bin/bash -c "Openstack user" "$USERNAME" 2>/dev/null
fi

HOME_DIR=$(getent passwd "$USERNAME" | cut -d: -f6)
chown -R "$USER_ID:$GROUP_ID" "$HOME_DIR"

exec gosu "$USERNAME" "$@"
96 changes: 96 additions & 0 deletions image-builder/image-builder.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
usage () {
cat <<USAGE
Usage: export or set environment variables when running
Usage: Only OPENRC_PATH, SOURCE_IMAGE, and NETWORK are necessary
Usage: See source for all available options
OPENRC_PATH=/path/to/openrc.sh \\
SOURCE_IMAGE=UUID \\
NETWORK=UUID \\
$0
USAGE
}

TAG=${TAG:-latest}

# SOURCE_IMAGE=$(openstack image show Featured-Minimal-Ubuntu22 -f value -c id)
# NETWORK=${NETWORK:-$(openstack network show auto_allocated_network -f value -c id)}

if [[ -z "$SOURCE_IMAGE" ]]; then
echo "!!! ERROR: Must provide a SOURCE_IMAGE"
usage
exit 1
fi
if [[ -z "$NETWORK" ]]; then
echo "!!! ERROR: Must provide a NETWORK"
usage
exit 1
fi

FLAVOR=${FLAVOR:-m3.quad}

IMAGE_NAME_BASE=${IMAGE_NAME_BASE:-unidata-ubuntu-magnum}
TIMESTAMP=$(date +%Y%m%d_%H%M)
IMAGE_NAME_SUFFIX=${IMAGE_NAME_SUFFIX:-$TIMESTAMP}
# export necessary for container to inherit with a `docker run -e IMAGE_NAME`
export IMAGE_NAME=${IMAGE_NAME:-$IMAGE_NAME_BASE-$IMAGE_NAME_SUFFIX}

# If wanting to base off of something that isn't a featured ubuntu image, give ourselves a backdoor
SSH_USERNAME=${SSH_USERNAME:-ubuntu}

# The keypair name to use, as recognized by JS2/openstack
SSH_KEYPAIR_NAME=${SSH_KEYPAIR_NAME:-packer}

# Will be mounted via docker, so we need the full path
KEY_FILE=${KEY_FILE:-~/.ssh/id_ed25519_packer}
KEY_FILE=$(realpath $KEY_FILE)

# Ditto, but error out if one is not provided
if [[ -z "$OPENRC_PATH" ]]; then
echo "!!! ERROR: Must provide an OPENRC_PATH !!!"
usage
exit 1
fi
OPENRC_PATH=$(realpath $OPENRC_FILE)

NODE_CUSTOM_ROLES_POST=${NODE_CUSTOM_ROLES_POST:-unidata-profile}

# Setup directories
LOG_DIR=$(pwd)/logs/$IMAGE_NAME
ROLES_DIR=$(pwd)/roles/$NODE_CUSTOM_ROLES_POST
mkdir -p $LOG_DIR

# Construct a packer var_file.json
VAR_FILE=$(cat <<VAR_FILE
{
"source_image": "$SOURCE_IMAGE",
"networks": "$NETWORK",
"flavor": "$FLAVOR",
"floating_ip_network": "",
"use_floating_ip": "false",
"image_name": "$IMAGE_NAME",
"image_visibility": "private",
"image_disk_format": "",
"use_blockstorage_volume": "false",
"volume_size": "",
"volume_type": "",
"security_groups": "image-builder",
"ssh_username": "$SSH_USERNAME",
"ssh_keypair_name": "$SSH_KEYPAIR_NAME",
"ssh_private_key_file": "~/.ssh/id_ed25519_packer",
"node_custom_roles_post": "$NODE_CUSTOM_ROLES_POST"
}
VAR_FILE
)
echo $VAR_FILE > $LOG_DIR/var_file.json

docker run -t \
--name $IMAGE_NAME \
-e IMAGE_NAME \
-e PACKER_LOG=1 \
-e PACKER_LOG_PATH=/image-builder-log/packer_debug.log \
-e PACKER_VAR_FILES=/image-builder-log/var_file.json \
--env-file $OPENRC_PATH \
-v $LOG_DIR:/image-builder-log \
-v $ROLES_DIR:/home/openstack/image-builder/images/capi/ansible/roles/$NODE_CUSTOM_ROLES_POST \
-v $KEY_FILE:/home/openstack/.ssh/id_ed25519_packer \
unidata/image-builder:$TAG make build-openstack-ubuntu-2204
4 changes: 4 additions & 0 deletions image-builder/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
python-openstackclient==8.0.0
python-magnumclient==4.8.1
python-octaviaclient==3.11.0
python-designateclient==6.3.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
blacklist act_pedit
3 changes: 3 additions & 0 deletions image-builder/roles/unidata-profile/files/dirtyfrag.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
blacklist cifs
27 changes: 27 additions & 0 deletions image-builder/roles/unidata-profile/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
- name: Restrict ptrace_scope
ansible.posix.sysctl:
name: "kernel.yama.ptrace_scope"
value: 2
sysctl_file: /etc/sysctl.d/10-ptrace.conf
- name: Unpack modprobe config files
ansible.builtin.unarchive:
src: ../files/security.tar
dest: /etc/modprobe.d
list_files: true

- name: Check if nfs-common is needed
raw: which mount.nfs
register: need_nfs
failed_when: false
changed_when: false
# This command should always run, even in check mode
check_mode: false
tags:
- facts
- name: Install nfs-common
raw:
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y nfs-common
become: true
when:
- need_nfs.rc != 0
Loading