https://blog.mattsbit.co.uk/2024/02/22/gitlab-ephemeral-environments-for-pull-requests/ Another blog from another guy called Matt Occasional blog posts from a random systems engineer home about posts Gitlab Ephemeral Environments for Pull Requests 22 Feb, 2024 * Read in about 13 min * (2627 Words) Background I maintain a handful of open sources projects - most of which are of no interest to anyone. There are one or two, however, that have a small handful of users - and also a small number of contributors. Because of this, I spent a very inconsistent amount of time on each of these, occasionally fixing bugs and occasionally spending an hour or two each for a week to get a feature done. Since some of these projects do have users - these people have become a combination of things for me: * Stakeholders * Bug reporters * Code reviewers * Code contributors Problem I may often implement a change and wonder what others think - it's hard to ask someone I've nevber met, speak to via a one line comment every 2-6 months to ask them to spend valuable time on a code review to have input of a change. So I wanted to have per-PR deployment environments, so I can create a PR and they can simply view the branch on a running instance. My requirements for this project were: * Integration with self-hosted Gitlab * Each PR would automatically create an environment without intervention * Each PR would automatically (and reliably) destroy itself on PR close/merge * Run on nomad * Be isolated from any other infrastructure (much of the infrastructure already is) * Avoid using Gitlab docker registry (since I back this up and ~1GB /PR would be incredibly expensive!) * Not require any changes in: + External DNS (since this is bind and a manual change) + External load balancers (again a manual change in HAProxy) * Provide a tidy solution - it should use Terraform to deploy - but the Terraform shouldn't clutter the real repository. * Any authentication should be created as dynamically as possible, avoiding lots of credentials hard coded in pipeline secrets. Setting up nomad Since I've started a long journey to migrating from a variety of other tools, such as kubernetes (running via rancher), docker swarm+portainer etc. to nomad, I have a Terraform module for setting up nomad. I start by creating a new virtual machine: module "zante" { source = "terraform-registry.internal.domain/cosh-servers-zante__dockstudios/libvirt-virtual-machine/libvirt" version = ">= 1.1.0, < 2.0.0" name = "zante" ip_address = "10.2.1.13" ip_gateway = "10.2.1.1" ip_prefix_length = 24 nameservers = local.dns_servers memory = 3 * 1024 disk_size = 20 * 1024 base_image_path = local.base_image_path lvm_volume_group = local.volume_groups["ssd-raid-storage"] hypervisor_hostname = local.hypervisor_hostname hypervisor_username = local.hypervisor_username docker_ssh_key = local.ssh_key ssh_key = local.ssh_key domain_name = local.domain_name http_proxy = local.http_proxy https_proxy = local.http_proxy no_proxy = local.http_no_proxy NO_PROXY = local.http_no_proxy # Connect to isolated network network_bridge = local.network_bridges["gitlab-pr-isolated-network"] # Install docker, which will be used for configuring nomad install_docker = true create_host_records = false # Create directories for docker data (though this probably won't be needed) # and nomad data directories create_directories = [ "/docker-data", "/nomad", "/nomad/config", "/nomad/config/server-certs", "/nomad/data" ] } Once the machine is created, nomad and traefik can be setup and configured on it: module "server" { source = "terraform-registry.internal.domain/gitlab-env-nomad__dockstudios/nomad/nomad//modules/server" version = ">= 1.1.0, < 2.0.0" hostname = "zante" domain_name = "internal.domain" docker_username = "docker-connect" nomad_version = "1.6.3" consul_version = "1.17.0" http_proxy = var.http_proxy aws_endpoint = var.aws_endpoint aws_profile = var.aws_profile container_data_directory = "/docker-data" primary_network_interface = "ens3" } module "traefik" { source = "terraform-registry.internal.domain/gitlab-env-nomad__dockstudios/nomad/nomad//modules/traefik" version = ">= 1.0.0, < 2.0.0" cpu = 64 memory = 128 # A wildcard SSL cert for *.gitlab-pr.internal.domain will be created # and a CNAME of *.gitlab-pr.internal.domain will point to the nomad server. # Traefik will be configured with a default service rule of Host(`{{ .Name }}.gitlab-pr.dockstudios.co.uk`) base_service_domain = "gitlab-pr.dockstudios.co.uk" nomad_fqdn = "zante.internal.domain" domain_name = "internal.domain" service_names = ["*.gitlab-pr"] } Application deployments Now that we have a nomad instance running, we can look at how the application will be deployed. Either: * We can have generic terraform to deploy any application and pass through a lot of variables in the Gitlab CI pipeline + This will lead to lots of sensitive information being stored in CI and lots of pass through variables in Gitlab CI. + Changes would require: Common terraform updating, the Gitlab CI config in the OSS repo AND adding any values manually into the Gitlab * Dedicated terraform for the project: + Although this can use a common module, this will add overhead when adding this deployment pipeline to new projects. * Store the terraform in the repo + This will likely cause confusion to anyone viewing the repo, since the Terraform will be quite specific to the CI pipeline. I chose to go with a dedicate repo per application. For the basis of this test, I'll bne creating this for Terrareg. The basic Terraform for deploying a nomad service will look like: resource "nomad_job" "terrareg" { jobspec = <