Educational material go brr
This commit is contained in:
parent
757019b7d8
commit
55b3e74aa2
24 changed files with 380 additions and 0 deletions
3
.dockerignore
Normal file
3
.dockerignore
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/*.tf*
|
||||||
|
/.git
|
||||||
|
/.terraform
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/.idea
|
||||||
|
/.terraform
|
||||||
|
/terraform.tfstate*
|
||||||
|
/.terraform.tfstate*
|
42
.terraform.lock.hcl
Normal file
42
.terraform.lock.hcl
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# This file is maintained automatically by "tofu init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/hashicorp/random" {
|
||||||
|
version = "3.6.2"
|
||||||
|
constraints = "~> 3.3"
|
||||||
|
hashes = [
|
||||||
|
"h1:PXvoOj9gj+Or+9k0tQWCQJKxnsVO0GqnQwVahgwRrsU=",
|
||||||
|
"zh:1f27612f7099441526d8af59f5b4bdcc35f46915df5d243043d7337ea5a3e38a",
|
||||||
|
"zh:2a58e66502825db8b4b96116c04bd0323bca1cf1f5752bdd8f9c26feb84d3b1e",
|
||||||
|
"zh:4f0a4fa479e29de0c3c90146fd58799c097f7a55401cb00560dd4e9b1e6fad9d",
|
||||||
|
"zh:9c93c0fe6ef685513734527e0c8078636b2cc07591427502a7260f4744b1af1d",
|
||||||
|
"zh:a466ff5219beb77fb3b18a3d7e7fe30e7edd4d95c8e5c87f4f4e3fe3eeb8c2d7",
|
||||||
|
"zh:ab33e6176d0c757ddb31e40e01a941e6918ad10f7a786c8e8e4f35e5cff81c96",
|
||||||
|
"zh:b6eabf377a1c12cb3f9ddd97aacdd5b49c1646dc959074124f81d40fcd216d7e",
|
||||||
|
"zh:ccec5d03d0d1c0f354be299cdd6a417b2700f1a6781df36bcce77246b2f57e50",
|
||||||
|
"zh:d2a7945eeb691fdd2b1474da76ddc2d1655e2aedbb14b57f06d4f5123d47adf9",
|
||||||
|
"zh:ed62351f4ad9d1469c6798b77dee5f63b18b29c473620a0046ba3d4f111b621d",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.opentofu.org/kreuzwerker/docker" {
|
||||||
|
version = "3.0.2"
|
||||||
|
constraints = "~> 3.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:cT2ccWOtlfKYBUE60/v2/4Q6Stk1KYTNnhxSck+VPlU=",
|
||||||
|
"zh:15b0a2b2b563d8d40f62f83057d91acb02cd0096f207488d8b4298a59203d64f",
|
||||||
|
"zh:23d919de139f7cd5ebfd2ff1b94e6d9913f0977fcfc2ca02e1573be53e269f95",
|
||||||
|
"zh:38081b3fe317c7e9555b2aaad325ad3fa516a886d2dfa8605ae6a809c1072138",
|
||||||
|
"zh:4a9c5065b178082f79ad8160243369c185214d874ff5048556d48d3edd03c4da",
|
||||||
|
"zh:5438ef6afe057945f28bce43d76c4401254073de01a774760169ac1058830ac2",
|
||||||
|
"zh:60b7fadc287166e5c9873dfe53a7976d98244979e0ab66428ea0dea1ebf33e06",
|
||||||
|
"zh:61c5ec1cb94e4c4a4fb1e4a24576d5f39a955f09afb17dab982de62b70a9bdd1",
|
||||||
|
"zh:a38fe9016ace5f911ab00c88e64b156ebbbbfb72a51a44da3c13d442cd214710",
|
||||||
|
"zh:c2c4d2b1fd9ebb291c57f524b3bf9d0994ff3e815c0cd9c9bcb87166dc687005",
|
||||||
|
"zh:d567bb8ce483ab2cf0602e07eae57027a1a53994aba470fa76095912a505533d",
|
||||||
|
"zh:e83bf05ab6a19dd8c43547ce9a8a511f8c331a124d11ac64687c764ab9d5a792",
|
||||||
|
"zh:e90c934b5cd65516fbcc454c89a150bfa726e7cf1fe749790c7480bbeb19d387",
|
||||||
|
"zh:f05f167d2eaf913045d8e7b88c13757e3cf595dd5cd333057fdafc7c4b7fed62",
|
||||||
|
"zh:fcc9c1cea5ce85e8bcb593862e699a881bd36dffd29e2e367f82d15368659c3d",
|
||||||
|
]
|
||||||
|
}
|
39
README.md
39
README.md
|
@ -0,0 +1,39 @@
|
||||||
|
Example Dockerised, Terraformed application deployment onto docker on bare metal
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
|
We're gonna run Quassel on bare metal in the most low-effort and high-reward enterprisey way possible.
|
||||||
|
Why? Because we can.
|
||||||
|
|
||||||
|
## Pre-requisites
|
||||||
|
We're gonna need some tools:
|
||||||
|
|
||||||
|
* OpenTofu, the replacement for Terraform since Hashicorp changed the licence terms: https://opentofu.org/docs/intro/install/.
|
||||||
|
* As Tofu is a drop in replacement for Terraform, you can use Terraform if you prefer, and I'll refer to them interchangeably.
|
||||||
|
|
||||||
|
## What have we got going on in here?
|
||||||
|
The main players:
|
||||||
|
* database.tf - Configures our Quassel instances Postgres
|
||||||
|
* quassel.tf - Configures our Quassel instance itself
|
||||||
|
|
||||||
|
The bit-actors:
|
||||||
|
* docker.tf - Configures the Docker Provider for Tofu
|
||||||
|
* inputs.tf - Where inputs to this deployment are defined and may have defaults set
|
||||||
|
* outputs.tf - Where outputs from this deployment are defined, which will be output when we run `tofu apply` or `tofu outputs`
|
||||||
|
* terraform.tf - Telling Terraform/ToFu what packages we need to run this deployment
|
||||||
|
* .terraform.lock.hcl - Lock file for Terraform/ToFu, this is here to ensure that we're all using the same versions of the same packages
|
||||||
|
* terraform.tfvars - Where we may set the variables defined in inputs.tf
|
||||||
|
|
||||||
|
## What happens when we run this?
|
||||||
|
When we run `tofu apply`:
|
||||||
|
* Tofu will create a Docker network for our Quassel instance
|
||||||
|
* Tofu will create a Postgres instance for our Quassel instance
|
||||||
|
* Tofu will compile our Quassel instance image from the Dockerfile in the quassel directory
|
||||||
|
* Tofu will create a Quassel instance using that image
|
||||||
|
* Tofu will output the IP address of the Quassel instance and postgres instance
|
||||||
|
|
||||||
|
## How do we run this?
|
||||||
|
Did you give `tofu apply` a go yet?
|
||||||
|
|
||||||
|
## What we're gonna end up with:
|
||||||
|
|
||||||
|
`DIAGRAM GOES HERE`
|
62
database.tf
Normal file
62
database.tf
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
# Find our latest postgres 16 image
|
||||||
|
data "docker_registry_image" "postgres_quassel" {
|
||||||
|
name = "postgres:16"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate a random password for our database
|
||||||
|
resource "random_password" "quassel_db_password" {
|
||||||
|
length = 32
|
||||||
|
special = false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a volume for our database data to live in
|
||||||
|
resource "docker_volume" "quassel_db" {
|
||||||
|
name = "${var.docker_prefix}-quassel-db"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create our database service
|
||||||
|
resource "docker_service" "quassel_db" {
|
||||||
|
name = "${var.docker_prefix}-quassel-db"
|
||||||
|
task_spec {
|
||||||
|
container_spec {
|
||||||
|
# We've got our image from the registry...
|
||||||
|
image = "${data.docker_registry_image.postgres_quassel.name}@${data.docker_registry_image.postgres_quassel.sha256_digest}"
|
||||||
|
# And we're going to set some environment variables
|
||||||
|
env = {
|
||||||
|
POSTGRES_USER = local.pg_username
|
||||||
|
POSTGRES_DB = local.pg_database
|
||||||
|
POSTGRES_PASSWORD = local.pg_password
|
||||||
|
}
|
||||||
|
# We're going to define a nice healthcheck that will check that postgres is alive and well
|
||||||
|
healthcheck {
|
||||||
|
# Effectively this is running 'pg_isready -d postgres -U postgres' on the commandline inside the container and if it returns 0, the container is healthy, anything else is failure
|
||||||
|
test = ["CMD-SHELL", "pg_isready", "-d", local.pg_database, "-U", local.pg_username]
|
||||||
|
interval = "5s"
|
||||||
|
start_period = "15s"
|
||||||
|
}
|
||||||
|
# And we're going to mount our data volume to the container so that the data persists between restarts
|
||||||
|
mounts {
|
||||||
|
target = "/var/lib/postgresql/data"
|
||||||
|
type = "volume"
|
||||||
|
source = docker_volume.quassel_db.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# And attach our network so that the quassel service can talk to the database
|
||||||
|
networks_advanced {
|
||||||
|
name = docker_network.quassel.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# And we're going to wait for it to be up and running before we move on
|
||||||
|
converge_config {
|
||||||
|
delay = "5s" # Wait 5 seconds between checks
|
||||||
|
timeout = "2m" # Give up after 2 minutes
|
||||||
|
}
|
||||||
|
endpoint_spec {
|
||||||
|
ports {
|
||||||
|
target_port = local.pg_port_internal
|
||||||
|
published_port = local.pg_port_external
|
||||||
|
protocol = "tcp"
|
||||||
|
publish_mode = "ingress"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
docker.tf
Normal file
3
docker.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
provider "docker" {
|
||||||
|
host = "ssh://california.ti"
|
||||||
|
}
|
7
inputs.tf
Normal file
7
inputs.tf
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
variable "docker_prefix" {
|
||||||
|
description = "Prefix for all docker resources.. We're not alone on this box, so we best avoid collisions"
|
||||||
|
}
|
||||||
|
variable "tz" {
|
||||||
|
description = "Timezone for the server"
|
||||||
|
default = "Europe/Amsterdam"
|
||||||
|
}
|
15
locals.tf
Normal file
15
locals.tf
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
locals {
|
||||||
|
# Lets just take a moment to make some ease of life variables
|
||||||
|
|
||||||
|
# Quassel
|
||||||
|
quassel_port = random_integer.quassel_port.result # Lets just put the port in a variable so we can use it in multiple places
|
||||||
|
|
||||||
|
# database
|
||||||
|
pg_username = "postgres"
|
||||||
|
pg_database = "postgres"
|
||||||
|
pg_password = random_password.quassel_db_password.result
|
||||||
|
pg_hostname = docker_service.quassel_db.name
|
||||||
|
# We're going to use the quassel port + 1 for the postgres port because why not.. But this is only for external access, inside the internal network it will still be 5432!
|
||||||
|
pg_port_internal = 5432
|
||||||
|
pg_port_external = local.quassel_port + 1
|
||||||
|
}
|
15
outputs.tf
Normal file
15
outputs.tf
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
output "quassel" {
|
||||||
|
value = {
|
||||||
|
hostname = docker_service.quassel_db.name
|
||||||
|
port = local.quassel_port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output "postgres" {
|
||||||
|
value = {
|
||||||
|
hostname = docker_service.quassel_db.name
|
||||||
|
port = local.pg_port_external
|
||||||
|
username = local.pg_username
|
||||||
|
password = nonsensitive(local.pg_password)
|
||||||
|
database = local.pg_database
|
||||||
|
}
|
||||||
|
}
|
71
quassel.tf
Normal file
71
quassel.tf
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
# Pick a random port to use for our uplink port.
|
||||||
|
resource "random_integer" "quassel_port" {
|
||||||
|
max = 65535
|
||||||
|
min = 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build our latest quassel docker image.
|
||||||
|
resource "docker_image" "quassel" {
|
||||||
|
name = "${var.docker_prefix}-quassel"
|
||||||
|
build {
|
||||||
|
context = "${path.module}/quassel"
|
||||||
|
}
|
||||||
|
triggers = {
|
||||||
|
dir_sha1 = sha1(join("", [for f in fileset(path.module, "quassel/*") : filesha1(f)]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a network for our quassel service and postgres service to communicate upon
|
||||||
|
resource "docker_network" "quassel" {
|
||||||
|
name = "${var.docker_prefix}-quassel"
|
||||||
|
driver = "overlay" # We're using overlay networking because its fuckin' rad.
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create our Quassel docker service.
|
||||||
|
resource "docker_service" "quassel" {
|
||||||
|
name = "${var.docker_prefix}-quassel"
|
||||||
|
|
||||||
|
# We need the database to be present for this container to work, so we can explicitly tell TF about it here
|
||||||
|
depends_on = [docker_service.quassel_db]
|
||||||
|
|
||||||
|
# We're going to define the task specification
|
||||||
|
task_spec {
|
||||||
|
# Which contains a container specification
|
||||||
|
container_spec {
|
||||||
|
# Which has a docker image set
|
||||||
|
#image = "${data.docker_registry_image.quassel.name}@${data.docker_registry_image.quassel.sha256_digest}"
|
||||||
|
image = docker_image.quassel.name
|
||||||
|
env = {
|
||||||
|
# And a bunch of environment variables as per the upstream documentation.
|
||||||
|
PUID = 1000
|
||||||
|
PGID = 1000
|
||||||
|
TZ = var.tz
|
||||||
|
RUN_OPTS = "--config-from-environment"
|
||||||
|
DB_BACKEND = "PostgreSQL"
|
||||||
|
DB_PGSQL_USERNAME = local.pg_username
|
||||||
|
DB_PGSQL_PASSWORD = local.pg_password
|
||||||
|
DB_PGSQL_HOSTNAME = local.pg_hostname
|
||||||
|
DB_PGSQL_PORT = local.pg_port_internal
|
||||||
|
AUTH_AUTHENTICATOR = "Database"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Attach our task to the network we created earlier
|
||||||
|
networks_advanced {
|
||||||
|
name = docker_network.quassel.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Setting a converge config means that we will wait for the service to be up and running (and reporting it is healthy) before moving on.
|
||||||
|
converge_config {
|
||||||
|
delay = "5s" # Wait 5 seconds between checks
|
||||||
|
timeout = "2m" # Give up after 2 minutes
|
||||||
|
}
|
||||||
|
endpoint_spec {
|
||||||
|
# Configure our service to listen on a random port on the ingress network (which means any node in the swarm will redirect the traffic to (an instance of) this service)
|
||||||
|
ports {
|
||||||
|
target_port = 4242 # default quassel port on the container
|
||||||
|
published_port = local.quassel_port # Use the random port we generated earlier
|
||||||
|
protocol = "tcp"
|
||||||
|
publish_mode = "ingress" # Its that fwicked cool sweet awesome overlay network again, but this time ingress from the outside of the cluster
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
quassel/Dockerfile
Normal file
82
quassel/Dockerfile
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.18 as build-stage
|
||||||
|
|
||||||
|
# build time arguements
|
||||||
|
ARG CXXFLAGS="\
|
||||||
|
-D_FORTIFY_SOURCE=2 \
|
||||||
|
-Wp,-D_GLIBCXX_ASSERTIONS \
|
||||||
|
-fstack-protector-strong \
|
||||||
|
-fPIE -pie -Wl,-z,noexecstack \
|
||||||
|
-Wl,-z,relro -Wl,-z,now"
|
||||||
|
ARG QUASSEL_RELEASE
|
||||||
|
# install build packages
|
||||||
|
RUN \
|
||||||
|
apk add --no-cache \
|
||||||
|
boost-dev \
|
||||||
|
build-base \
|
||||||
|
cmake \
|
||||||
|
dbus-dev \
|
||||||
|
icu-dev \
|
||||||
|
openssl-dev \
|
||||||
|
openldap-dev \
|
||||||
|
qt5-qtbase-dev \
|
||||||
|
qt5-qtscript-dev \
|
||||||
|
qt5-qtbase-postgresql \
|
||||||
|
qt5-qtbase-sqlite \
|
||||||
|
qca-dev \
|
||||||
|
zlib-dev
|
||||||
|
|
||||||
|
# fetch source
|
||||||
|
RUN \
|
||||||
|
mkdir -p \
|
||||||
|
/tmp/quassel-src/build && \
|
||||||
|
if [ -z ${QUASSEL_RELEASE+x} ]; then \
|
||||||
|
QUASSEL_RELEASE=$(curl -sX GET "https://api.github.com/repos/quassel/quassel/releases/latest" \
|
||||||
|
| jq -r .tag_name); \
|
||||||
|
fi && \
|
||||||
|
curl -o \
|
||||||
|
/tmp/quassel.tar.gz -L \
|
||||||
|
"https://github.com/quassel/quassel/archive/${QUASSEL_RELEASE}.tar.gz" && \
|
||||||
|
tar xf \
|
||||||
|
/tmp/quassel.tar.gz -C \
|
||||||
|
/tmp/quassel-src --strip-components=1
|
||||||
|
|
||||||
|
# build package
|
||||||
|
RUN \
|
||||||
|
cd /tmp/quassel-src/build && \
|
||||||
|
cmake \
|
||||||
|
-DCMAKE_BUILD_TYPE="Release" \
|
||||||
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
-DWANT_CORE=ON \
|
||||||
|
-DWANT_MONO=OFF \
|
||||||
|
-DWANT_QTCLIENT=OFF \
|
||||||
|
-DWITH_KDE=OFF \
|
||||||
|
/tmp/quassel-src && \
|
||||||
|
make -j2 && \
|
||||||
|
make DESTDIR=/build/quassel install
|
||||||
|
|
||||||
|
FROM ghcr.io/linuxserver/baseimage-alpine:3.18
|
||||||
|
|
||||||
|
# set environment variables
|
||||||
|
ENV HOME /config
|
||||||
|
|
||||||
|
# install runtime packages
|
||||||
|
RUN \
|
||||||
|
apk add --no-cache \
|
||||||
|
icu-libs \
|
||||||
|
openssl \
|
||||||
|
qt5-qtbase \
|
||||||
|
qt5-qtbase-postgresql \
|
||||||
|
qt5-qtbase-sqlite \
|
||||||
|
qt5-qtscript \
|
||||||
|
libqca \
|
||||||
|
openldap
|
||||||
|
|
||||||
|
# copy artifacts build stage
|
||||||
|
COPY --from=build-stage /build/quassel/usr/ /usr/
|
||||||
|
|
||||||
|
# add local files
|
||||||
|
COPY root/ /
|
||||||
|
|
||||||
|
# ports and volumes
|
||||||
|
VOLUME /config
|
||||||
|
EXPOSE 4242
|
13
quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/run
Executable file
13
quassel/root/etc/s6-overlay/s6-rc.d/init-quassel-config/run
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
# generate key
|
||||||
|
if [[ ! -f /config/quasselCert.pem ]]; then
|
||||||
|
openssl req -x509 -nodes -days 3650 \
|
||||||
|
-newkey rsa:4096 -keyout /config/quasselCert.pem -out /config/quasselCert.pem \
|
||||||
|
-subj "/CN=Quassel-core"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# permissions
|
||||||
|
lsiown -R abc:abc \
|
||||||
|
/config
|
|
@ -0,0 +1 @@
|
||||||
|
oneshot
|
|
@ -0,0 +1 @@
|
||||||
|
/etc/s6-overlay/s6-rc.d/init-quassel-config/run
|
|
@ -0,0 +1 @@
|
||||||
|
3
|
7
quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/run
Executable file
7
quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/run
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/with-contenv bash
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
exec \
|
||||||
|
s6-notifyoncheck -d -n 300 -w 1000 -c "nc -z localhost 4242" \
|
||||||
|
s6-setuidgid abc quasselcore \
|
||||||
|
--configdir /config ${RUN_OPTS}
|
1
quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/type
Normal file
1
quassel/root/etc/s6-overlay/s6-rc.d/svc-quassel/type
Normal file
|
@ -0,0 +1 @@
|
||||||
|
longrun
|
12
terraform.tf
Normal file
12
terraform.tf
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "~>3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
terraform.tfvars
Normal file
1
terraform.tfvars
Normal file
|
@ -0,0 +1 @@
|
||||||
|
docker_prefix = "example-deployable"
|
Loading…
Reference in a new issue