Skip to content

Distribute cache invalidation requests to a fleet of varnish instances.

License

Notifications You must be signed in to change notification settings

emgag/varnish-towncrier

Repository files navigation

varnish-towncrier

build release Go Report Card License

varnish-towncrier is designed to distribute cache invalidation requests to a fleet of varnish instances. The agent daemon is listening for PURGE and BAN requests on a Redis Pub/Sub channel and forwards incoming cache invalidation requests to its local varnish instance. It's the successor of varnish-cache-reaper, which is also used to fan out invalidation requests to multiple varnish instances, though its host list is static while with varnish-towncrier, each varnish instance registers itself automatically.

It supports PURGE and BAN requests as well as surrogate keys (cache tags) using the xkey module, formerly known as Hashtwo.

Requirements

  • Redis service, accessible from both the applications issuing invalidation requests as well as the varnish instances running the agent.
  • Varnish, obviously. Although as it doesn't use any specific varnish APIs and uses plain HTTP, it can probably be configured for other proxies as well.
  • VCL has to be modified to support purging, banning and distinguishing the two different xkey purging methods supported by varnish-towncrier. See VCL example below.
  • Go >=1.19 for building.

Agent

Configuration

The agent configuration is done using either a YAML file ( see varnish-towncrier.yml.dist), default location is /etc/varnish-towncrier.yml or through environment variables.

redis section:

  • uri (VT_REDIS_URI): redis host to connect to. Use redis:// for unencrypted, rediss:// for an encrypted connection.
  • password (VT_REDIS_PASSWORD): provide password if the connection needs to be authenticated.
  • subscribe (VT_REDIS_SUBSCRIBE): list of pubsub channels the agent will subscribe to. When used within an environment variable, a space separated string is used to list multiple values.

endpoint section:

  • uri (VT_ENDPOINT_URI): the HTTP endpoint of the varnish instance.
  • xkeyheader (VT_ENDPOINT_XKEYHEADER): The header used to supply list of keys to purge using xkey.purge().
  • softxkeyheader (VT_ENDPOINT_SOFTXKEYHEADER): The header used to supply list of keys to purge using * xkey.softpurge()*.
  • banheader (VT_ENDPOINT_BANHEADER): The header used to supply the expression for ban().
  • banurlheader (VT_ENDPOINT_BANURLHEADER): The header used to supply the pattern for an URL ban.

Example (default values):

redis:
  uri: redis://127.0.0.1:6379
  password: thepasswordifneeeded
  subscribe:
    - varnish.purge
endpoint:
  uri: http://127.0.0.1:8080/
  xkeyheader: x-xkey
  softxkeyheader: x-xkey-soft
  banheader: x-ban-expression
  banurlheader: x-ban-url  

Usage

Distribute cache invalidation requests to a fleet of varnish instances.

Usage:
  varnish-towncrier [command]

Available Commands:
  ban         Issue ban request to all registered instances
  help        Help about any command
  listen      Listen for incoming invalidation requests
  purge       Issue purge request to all registered instances
  version     Print the version number of varnish-towncrier
  xkey        Invalidate selected surrogate keys on all registered instances

Flags:
  -c, --config string   config file (default is /etc/varnish-towncrier.yml)
  -h, --help            help for varnish-towncrier

Use "varnish-towncrier [command] --help" for more information about a command.

Example:

$ varnish-towncrier -c varnish-towncrier.yml listen
2017/12/14 01:09:14 Connecting to redis...
2017/12/14 01:09:14 Connected to redis://127.0.0.1:6379
2017/12/14 01:09:14 subscribe: varnish.purge (1)
[...]

Docker

varnish-towncrier is packaged for docker with the image

ghcr.io/emgag/varnish-towncrier

and can be configured either by copying a config file to the root or by supplying environment variables.

Example using baked in config file:

FROM ghcr.io/emgag/varnish-towncrier
COPY varnish-towncrier.yml /varnish-towncrier.yml

Run

docker run emgag/varnish-towncrier:latest listen

Kubernetes

varnish-towncrier can be run alongside a varnish container ( like ghcr.io/emgag/varnish) in a pod to handle cache resets, e.g.


---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: varnish-deployment
spec:
  selector:
    matchLabels:
      app: varnish
  replicas: 2
  template:
    metadata:
      labels:
        app: varnish
    spec:
      containers:
      - name: varnish
        image: ghcr.io/emgag/varnish:7.2.0
        ports:
        - containerPort: 80
        env:
        - name: VARNISH_STORAGE
          value: "malloc,512m"
        volumeMounts:
        - name: config-volume
          mountPath: /etc/varnish          
      - name: varnish-towncrier
        image: ghcr.io/emgag/varnish-towncrier:latest
        args:
        - listen
        env:
        - name: VT_REDIS_URI
          value: redis://redis-service
        - name: VT_ENDPOINT_URI
          value: http://127.0.0.1:80/
      volumes:
      - name: config-volume
        configMap:
          name: varnish-config
---
apiVersion: v1
kind: Service
metadata:
  name: varnish-service
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
    protocol: TCP
  selector:
    app: varnish

Invalidation request API

Invalidation requests can be sent by publishing to a Redis Pub/Sub channel.

Message format

The publish message payload consists of a JSON object with following properties:

  • command: string. Required. Either ban, ban.url, purge, xkey or xkey.soft.
  • host: string. Optional. The Host header used in the PURGE/BAN request to varnish. If omitted, the host is derived from the local endpoint's URL.
  • value: string[]. Required. Meaning depends on the command. ban: List of ban() expressions, ban.url: List of regular expressions matching the path portion of the URL to be banned, purge: The path portion of the URL to be purged, xkey and xkey.soft: List of keys to (soft-)purge.

Example:

{
  "command": "xkey",
  "host": "www.example.org",
  "value": [
    "still",
    "flying"
  ]
}

Using varnish-towncrier:

$ varnish-towncrier -c config.yml xkey --host www.example.org still flying

Using redis-cli:

$ redis-cli
127.0.0.1:6379> publish varnish.purge '{"command": "xkey", "host": "www.example.org", "value": ["still", "flying"]}'

Using PHP & Predis:

$client = new Predis\Client([
    'scheme' => 'tcp',
    'host'   => '127.0.0.1',
    'port'   => '6379'
]);

$message = json_encode([
    'command' => 'xkey',
    'host'    => 'www.example.org',
    'value'   => ['still', 'flying']
]);

$client->publish('varnish.purge', $message);

Using PHP, Predis & varnish-towncrier-php:

$client = new Predis\Client([
    'scheme' => 'tcp',
    'host'   => '127.0.0.1',
    'port'   => '6379'
]);

$vt = new VarnishTowncrier($client);
$vt->xkey('example.org', ['still', 'flying']);

VCL example

Varnish 6.x / 7.x

Varnish documentation or varnish 6.0 or varnish 7.1 or varnish 7.2

[...]
# use xkey module
import xkey;

# purgers acl
# - who is allowed to issue PURGE and BAN requests
# 
acl purgers {
    "127.0.0.1";
}

sub vcl_recv {
    [...]
    if (req.method == "PURGE") {
        if (!client.ip ~ purgers) {
            return(synth(405,"Method not allowed"));
        }
        
        if(req.http.x-xkey) {
            set req.http.n-gone = xkey.purge(req.http.x-xkey);
            return (synth(200, "Got " + req.http.x-xkey + ", invalidated " + req.http.n-gone + " objects"));
        }
        
        if(req.http.x-xkey-soft) {
            set req.http.n-gone = xkey.softpurge(req.http.x-xkey-soft);
            return (synth(200, "Got " + req.http.x-xkey-soft + ", invalidated " + req.http.n-gone + " objects"));
        }
        
        return (purge);
    }

    if (req.method == "BAN") {
        if (!client.ip ~ purgers) {
            return(synth(405,"Method not allowed"));
        }
        
        if (req.http.x-ban-expression) {
            ban(req.http.x-ban-expression);
            return(synth(200, "Banned expression"));
        
        } else if (req.http.x-ban-url) {
            ban(
                "obj.http.x-host == " + req.http.host + " && " +
                "obj.http.x-url ~ " + req.http.x-ban-url
            );
            
            return(synth(200, "Banned URL"));
        }
        
        return(synth(400, "No bans"));        
    }

    [...]

    return(hash);
}

sub vcl_backend_response {
    [...]

    # be friendly to ban lurker
    set beresp.http.x-url = bereq.url;
    set beresp.http.x-host = bereq.http.host;

    [...]
}

sub vcl_deliver {
    [...]
    
    # remove some variables we used before
    unset resp.http.x-url;
    unset resp.http.x-host;
    unset resp.http.xkey;
    
    [...]
}

Build

On Linux:

$ git clone github.com/emgag/varnish-towncrier 
$ cd varnish-towncrier
$ make 

License

varnish-towncrier is licensed under the MIT License.