K8S Deploy

Library for deploying Elixir web apps to Kubernetes. Used in combination with docker_build library.

It will build a docker image of your app, push it and then deploy it to K8S by creating a K8S Deployment, Service and Ingress for your app. It will also request a SSL cert for your app using Cert manager. This typically is used to obtain Letsencrypt certificates but can be used with other providers.

Prerequisites

Installation

Add to mix.exs:

def deps do
[
{:k8s_deploy, "~> 0.5.0", runtime: false, only: :dev}
]
end

Install and configure docker_build to build your docker image for you.

Basic Use

Add the following entry in mix.exs:

# mix.exs
def project do
[
...
k8s_deploy: k8s_deploy(),
...
]
end
defp k8s_deploy do
[
context: "my-k8s-cluster.com", # The kubectl context name in kubectl
image_pull_secrets: ["my-pull-secret"], # Unless a public docker image is used this must be set up before
cert_manager_cluster_issuer: "letsencrypt-cluster-prod", # This needs to be set up before.
host: "www.mysite.com" # HTTPS host
]
end

Deploy

To build a docker image and deploy:

mix k8s.deploy

For additional options run:

mix help k8s.deploy

Advanced usage

Additional configuration

The following additional config values are available:

Using a ConfigMap for environment variables

Instead of providing environment variables via the :env_vars key, you can provide a K8S ConfigMap in the deploy/k8s folder with the name configmap-prod.yaml. (If using a different environment change prod to match).

The name of the ConfigMap must match the :app_name key specified in the docker_build config, with the suffix -configmap. This will be referenced using envFrom in the Deployment.

For example:

---
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-configmap
data:
FOO: BAR
FOO2: BAR2

Running migrations

To run migrations set the :migrator config key to either a module name, e.g MyApp.Release which contains a migrate/0 function, or a mfa, e.g. {MyApp.Release, :migrate, []}. You can create the necessary code by following the recommendation in Phoenix.

A K8S Job will be created using the same docker image. It will execute the migrate function and run to completion before the deploy continutes. Any ConfigMap or vars in :env_vars will be available in to the Pod container that the job creates.

Deploying without an ingress

If you omit the host field, no ingress will be deployed (unless you have a custom template - see below). You might use this if another app deploys the ingress rules for this app.

Deploying to multiple contexts

You can also specify :context as a list. All K8S resources will then be deployed to each context in turn.

Using a custom Deployment, Service or Ingress template

If you need to customise the templates beyond what the configuration options provide, you can place your own template in your project in the location deploy/k8s/{resource}-{environment}.yaml. For example deployment-prod.yaml for a custom Deployment template.

These files can include EEx templating and accept the same variables as the default templates (see priv/templates), e.g. <%= @deployment_id> or <%= @docker_image %> in the Deployment template. N.B. The @deployment_id variable is an integer so it must be quoted in your template.

Secrets

Secrets can be encrypted at rest with SOPS.

Setup

  1. Install packages:
brew install sops age
  1. Create an age key pair outside the repo:
mkdir -p ~/.config/sops/age/
age-keygen -o ~/.config/sops/age/keys.txt
  1. Create the file .sops.yaml in the root of your repo with the public key output
creation_rules:
- path_regex: secrets/.*\.yaml$
age: <public key output by age-keygen>

N.B. SOPS picks up the file using the SOPS_AGE_KEY_FILE env var.

  1. Protect the file from access by non-root:
# Use root:root on linux
sudo chown root:wheel ~/.config/sops/age/keys.txt
sudo chmod 600 ~/.config/sops/age/keys.txt
  1. Create a K8S secrets file in /k8s/deploy/secret-prod.yaml like this. Don't commit yet.
kind: Secret
metadata:
name: myapp-secrets
type: Opaque
stringData:
SECRET1: secret1_value
SECRET2: secret2_value
  1. Encrypt the file initially
SOPS_AGE_KEY=$(sudo cat ~/.config/sops/age/keys.txt) sops -e -i deploy/k8s/secrets-prod.yaml

The file can now be committed.

Workflows

Edit the file to change or add a new secret
SOPS_AGE_KEY=$(sudo cat ~/.config/sops/age/keys.txt) sops deploy/k8s/secrets-prod.yaml
Deploy skipping secrets (doesn't need sudo)
mix k8s.deploy --skip-secrets
Deploy including secrets
sudo mix k8s.deploy

TODO