forked from grey/example-deployable-app
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