software engineer. former new yorker — current chicagoan

ARM64 Varnish Docker Image and k8s Configuration

@zackkitzmiller 2020-11-10 04:04

ARM64 Varnish

In the RaspberryPI Kubernetes deployment experiment (that’s serving this page), I wanted to use Varnish for a simple static asset cache. I’m not sure why I decided to go down this path, because this side isn’t exactly high traffic, but ¯\_(ツ)_/¯. After about 30 seconds of looking around I couldn’t find a pre-built ARM64 docker image for Varnish… so I built one.

Building the Image

This was about as much as creating a image repository on DockerHub, cloning this and running:

docker buildx build --platform linux/arm64 . -t zackkitzmiller/varnish-arm:alpine --push

You can get it here.

Edit: Websocket for Elixir support coming soon.

Edit 2: Websocket support:

vcl 4.0;

import directors;
import std;


backend z19rpw {
  .host = "z19rpw-service";
  .port = "80";
}

sub vcl_recv {
  if (req.http.upgrade ~ "(?i)websocket") {
    return (pipe);
  }

  # normalize the port for testing/staging
  set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");
  std.log(req.http.Host);

  # pass on the real IP address here as X-Forwarded-For
  if (req.restarts == 0) {
      if (req.http.X-Forwarded-For) {
          set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
      } else {
      set req.http.X-Forwarded-For = client.ip;
      }
  }

  if (req.http.Host ~ "z19r.pw") {
    std.log("setting backend to z19rpw");
    set req.backend_hint = z19rpw;
  }

  if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
  }

  if (req.url == "/") {
    std.log("attempting to cache home");
    unset req.http.Cookie;
    return (hash);
  }

  if (req.url ~ "\.(png|jpg|jpeg|css|js)") {
    std.log("processing for z19r assets");
    unset req.http.Cookie;
    return (hash);
  }

  std.log("not an asset request");
  return (pass);
}

sub vcl_deliver {
  if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }
  return (deliver);
}

sub vcl_backend_response {
  if (bereq.url ~ "\.(png|jpg|jpeg|css|js)" || bereq.url == "/") {
    unset beresp.http.set-cookie;
  }

  if (bereq.url ~ "^/$") {
    # Software pages are purged explicitly, so cache them for 48h
    set beresp.http.Cache-Control = "max-age=3600";
    set beresp.ttl = 1h;
  }

  return (deliver);
}

sub vcl_pipe {
  if (req.http.upgrade) {
    set bereq.http.upgrade = req.http.upgrade;
    set bereq.http.connection = req.http.connection;
  }

  return (pipe);
}

Handling Concurrent Connections in Elixir

@zackkitzmiller 2020-10-31 04:04

There’s 6k concurrent visitors hitting z19r.com right now as I type this.

Just a few RPis are powering it. Here’s the story.

TODO: Fix draft button?

Mnesia Configuration Helper

@zackkitzmiller 2020-10-04 04:04

Important:

If you need to use the Mnesia library for cluster syncing make sure that you change the backing store for Pow here.

defmodule Z19rpwWeb.Credentials do
  @moduledoc "Authentication helper functions"

  alias Z19rpw.Users.User
  alias Phoenix.LiveView.Socket
  alias Pow.Store.CredentialsCache

  @doc """
  Retrieves the currently-logged-in user from the Pow credentials cache.
  """
  @spec get_user(
          socket :: Socket.t(),
          session :: map(),
          config :: keyword()
        ) :: %User{} | nil

  def get_user(socket, session, config \\ [otp_app: :z19rpw])

  def get_user(socket, %{"z19rpw_auth" => signed_token}, config) do
    conn = struct!(Plug.Conn, secret_key_base: socket.endpoint.config(:secret_key_base))
    salt = Atom.to_string(Pow.Plug.Session)

    with {:ok, token} <- Pow.Plug.verify_token(conn, salt, signed_token, config),
         {user, _metadata} <-
           CredentialsCache.get([backend: Pow.Store.Backend.MnesiaCache], token) do
      user
    else
      _any -> nil
    end
  end

  def get_user(_, _, _), do: nil
end