Scriptlets/application images?

Hello,

Again I’m not sure if this belongs here.

There’s this degree of automation that I want to achieve and I can probably achieve it creating cloud-init scripts and put some shenanigans here and there but I don’t want to reinvent the wheel.

Is there like a repository of app images (wordpress, fancy app, you name it) out there where all you have to do is fill in some variables with the credentials you want and launch it on your LXD server?

Well, any tips welcome on automating deployments are welcome.

I have been using Packer and the LXD plugin for this to pre-bake Buildkite CI images.

Here is a template:

packer {
  required_plugins {
    lxd = {
      version = ">=1.0.0"
      source  = "github.com/hashicorp/lxd"
    }
  }
}

variable "buildkite_token" {
  description = "buildkite agent token"
  sensitive   = true
}

variable "ci_ssh_key" {
  description = "path to ssh private key for our builder user; obtain this from bitwarden"
}

locals {
  # Our bot https://github.com/greymatter-ci
  ci_email               = "ci@greymatter.io"
  ci_user                = "greymatter-ci"
  git_script             = "/tmp/git-script.sh"
  buildkite_download_url = "https://github.com/buildkite/agent/releases/download/v3.31.0/buildkite-agent-linux-amd64-3.31.0.tar.gz"
  bazelisk_url           = "https://github.com/bazelbuild/bazelisk/releases/download/v1.9.0/bazelisk-linux-amd64"
  ts                     = formatdate("YYYYMMDDhhmmss", timestamp())
}

source "lxd" "arch" {
  image = "images:archlinux/cloud"
}

source "lxd" "ubuntu_2104" {
  image = "images:ubuntu/21.04/cloud"
}

source "lxd" "ubuntu_2004" {
  image = "images:ubuntu/20.04/cloud"
}

build {
  source "lxd.arch" {
    name           = "archlinux-ci"
    output_image   = "archlinux-ci"
    container_name = "archlinux-ci-${local.ts}"
  }

  source "lxd.ubuntu_2104" {
    name           = "ubuntu-2104-ci"
    output_image   = "ubuntu-2104-ci"
    container_name = "ubuntu-2104-ci-${local.ts}"
  }

  source "lxd.ubuntu_2004" {
    name           = "ubuntu-2004-ci"
    output_image   = "ubuntu-2004-ci"
    container_name = "ubuntu-2004-ci-${local.ts}"
  }

  # Arch Linux packages
  provisioner "shell" {
    only    = ["lxd.archlinux-ci"]
    scripts = ["packages.archlinux.sh"]
  }

  # Ubuntu 20.04 packages
  provisioner "shell" {
    only    = ["lxd.ubuntu-2004-ci"]
    scripts = ["packages.ubuntu_2004.sh"]
  }

  # Ubuntu 21.04 packages
  provisioner "shell" {
    only    = ["lxd.ubuntu-2104-ci"]
    scripts = ["packages.ubuntu_2104.sh"]
  }

  provisioner "file" {
    source      = "./git-script.sh"
    destination = "${local.git_script}"
  }

  # Create builder user, install bazelisk, chown the git config script
  provisioner "shell" {
    inline = [
      "wget -q ${local.bazelisk_url} -O /usr/local/bin/bazel && chmod +x /usr/local/bin/bazel",
      "groupadd -g 2000 builder",
      "useradd -s /bin/bash --uid 2000 --gid 2000 -m builder",
      "chmod +x ${local.git_script}",
      "chown builder:builder ${local.git_script}",
    ]
  }


  provisioner "shell" {
    environment_vars = [
      "BUILDKITE_DOWNLOAD_URL=${local.buildkite_download_url}",
      "BUILDKITE_TOKEN=${var.buildkite_token}",
      "CONTAINER_IMAGE=${source.name}",
    ]
    script = "install_buildkite.sh"
  }

  provisioner "shell-local" {
    # The target of exec must be constructed like the source's "container_name"
    inline = [
      "lxc exec ${source.name}-${local.ts} --env HOME=/home/builder --cwd /home/builder --user 2000 --group 2000 -- mkdir /home/builder/.ssh",
      "lxc exec ${source.name}-${local.ts} --env HOME=/home/builder --cwd /home/builder --user 2000 --group 2000 -- ${local.git_script}",
    ]
  }

  provisioner "file" {
    source      = "${var.ci_ssh_key}"
    destination = "/home/builder/.ssh/id_ed25519"
  }

  provisioner "shell" {
    inline = [
      "chown builder:builder /home/builder/.ssh/id_ed25519",
      "chmod 0600 /home/builder/.ssh/id_ed25519",
    ]
  }
}

You’ll notice that the packer config references scripts and variables defined elsewhere. It’s very flexible. Here is the directory contents where this builder.pkr.hcl template lives.

builder.pkr.hcl               # the HCL file shown above
git-script.sh
image-cleanup.sh
install_buildkite.sh          # some scripts work on arch AND ubuntu, so they're shared
packages.archlinux.sh
packages.ubuntu_2004.sh       # we have a few distro-specific scripts
packages.ubuntu_2104.sh
variables.pkr.hcl             # has secrets! don't check it in
variables.pkr.hcl.example     # copy example instead

This builds 3 images. After they are done, you will see them in lxc image list.

Packer leaves behind “intermediate” images. They don’t seem to be needed, and since they keep multiplying, I feel like they’re something besides the base images canonical hosts. Get rid of them by deleting images w/o aliases. Only do this if you’re sure you don’t need those blank images! That’s the case for my infra.

#!/bin/bash
set -e
# Packer leaves intermdiate images behind. They can be identified as the images w/o aliases. See `lxc image list`.
for fp in $(lxc image list --format json | jq -r '.[] | select(.aliases | length == 0) | .fingerprint')
do
    lxc image delete $fp
done


1 Like

Thank you for sharing! Wasn’t aware of the existence of Packer (unsurprisingly)

It has a learning curve, but it’s worth it. You can use it to build many kinds of images: Docker, LXD, virtual machine images, etc.