This vmod offers flexible load-balancing features, with notably local reconfiguration and smart retry behavior.
vcl 4.1;
import udo;
backend be1 { .host = "1.1.1.1"; }
backend be2 { .host = "2.2.2.2"; }
backend be3 { .host = "3.3.3.3"; }
backend be4 { .host = "4.4.4.4"; }
# create a weighted random director
sub vcl_init {
new udo_dir = udo.director();
udo_dir.set_type(random);
udo_dir.add_backend(be1, weight = 1);
udo_dir.add_backend(be2, weight = 1);
udo_dir.add_backend(be3, weight = 1);
udo_dir.add_backend(be4, weight = 4);
}
sub vcl_backend_fetch {
set bereq.backend = udo_dir.backend();
# for videos, use consistent hashing
if (bereq.url ~ "^/video/") {
udo_dir.set_type(hash);
}
}
sub vcl_backend_response {
# if the response isn't a 200, retry, udo_dir will pick a different
# backend, even with consistent hashing enabled
if (beresp.status != 200) {
return (retry);
}
}
An important facet of udo
is that each task get its own cache of the state,
allowing the VCL writer to change load-balancing policy or to blacklist some
backends easily on a per-request fashion.
OBJECT udo.director()
Creates a new director. must be called in vcl_init
.
Returns
An empty director object, configure for consistent hashing.
Method VOID .add_backend(BACKEND backend, REAL weight = 1, [BLOB hash])
Introduce be
into the director, with an optional weight
and hash
. If no
hash is given, the VCL name of the backend is used.
Note that this call must be made from vcl_init
and will fail the
initialization of the VCL if a backend with the same hash
already exists in
the director.
Arguments
BACKEND backend
- the backend to add to the directorREAL weight
- a number dictating how much of the traffic should be pushed
to the new backendBLOB hash
- override the hash of the backend (based on its VCL name). Must
be 32 bytes long.Returns
Nothing
VOID .set_type(ENUM {hash, fallback, random})
Changes the type of the director:
If used in vcl_init
, the change is global and all requests will inherit this
property. If called from client (e.g. vcl_recv
) or backend (e.g.
vcl_backend_fetch
) context, the type is changed just within this context.
Arguments
ENUM {hash, fallback, random}
- a policy keywordReturns
Nothing
VOID .set_hash(BLOB hash)
Override the object hash, within a client or backend context. It’s useful in two
notable cases:
- to select a backend on a subset of the URL, for example in the video case
where you need to send all requests pertaining to one stream to the same
backend
- to use .dump()
before vcl_hash
has been run
Arguments
BLOB hash
- The object hash
to use for the current taskReturns
Nothing
STRING .dump(ENUM {list, json} fmt = list)
Output some of the director’s internal information.
json
will produce a fairly complete (and large) string:
{
"hash": "0x77d833ebbeae1ef5fcc14eb2fe94cf23a7d12bf5d582926e199b68808c6788b0",
"type": "hash",
"backends": [
{
"name": "s2",
"used": false,
"score": 5.733361,
"weight": 1.000000,
"hash": "0xad328846aa18b32a335816374511cac163c704b8c57999e51da9f908290a7a4"
},
...
]
}
While list
is a simple comma-separated list:
s2, s1, s3, s4
Arguments
ENUM {list, json} fmt
- the output format to use, defaults to listReturns
The current request-local state of the director in the selected format.
BACKEND .backend()
Return a backend, according to the current type set.
Arguments
None
Returns
A healthy, non-used backend.
VOID .exhaust_backend(BACKEND be)
Backends are automatically used and avoided later while in the same task, but
this function forcefully marks be
as “used” so that it won’t be returned
again while in the same VCL task, without needing to actually use it first.
Arguments
BACKEND be
- The backend to avoid.Returns
Nothing
VOID .subscribe(STRING tag)
Subscribe to an ActiveDNS dns_group. This director will start to generate dynamic backends based on DNS results and dns_group configuration. For more information on how to create and configure a dns_group, see the ActiveDNS VMOD documentation.
Depending on the DNS environment and configuration of the dns_group, dual-address backends may be created. The unique backend hash is therefore generated from the combination of IPv4 address, IPv6 address, and port. Any duplicate DNS results will be ignored.
If a dynamic backend is created with port number 443, and no backend hint has
been set for the dns_group, ssl is implicitly enabled for created backends.
However, if a backend hint has been set for the dns_group, .ssl = 1;
must be
set in the backend definition for ssl to be enabled.
When creating a backend from an SRV record, the SRV record weight is used as the weight of that backend. The weight influences the hash distribution in the same way as for static backends. Currently, the SRV record priority is ignored.
The name of each backend has the following format:
udo.DIRNAME.(sa[4,6]:IP:PORT)[.(sa6:IP:PORT)]
Arguments
STRING tag
- Tag string.Example
import udo;
import activedns;
sub vcl_init {
new group = activedns.dns_group();
group.set_host("example.com");
new udo_dir = udo.director();
udo_dir.subscribe(group.get_tag());
}
Given three SRV records for “example.com”, each pointing to an A and/or an AAAA record, the resulting backend names may look like this:
udo.udo_dir.(sa4:1.1.1.1:5566)
udo.udo_dir.(sa4:2.2.2.2:7777).(sa6:::2:7777)
udo.udo_dir.(sa6:::3:8080)
See the documentation for vmod_activedns for additional examples on how to configure a dns_group.
$Method STRING .get_identifier()
Returns a random alphanumeric identifier string.
The identifier should be placed in a header when self-routing a request, and becomes temporarily associated with the backend Varnish is fetching from. When an identifier header is received, it should be passed to the directors .self_identify()
method, which will attempt to self-identify the director. When the director has successfully self-identified, this function returns a static string indicating that the identification is complete.
$Method BOOL .self_identify(STRING identifier)
Take an identifier string and attempt to self-identify the director, returns true if successful. The director keeps track of identifiers that have been retrieved with .get_identifier()
, so if the identifier passed to this method is recognized, this director must have sent a request to itself. The backend that the identifier is associated with becomes the identity of the director.
$Method BOOL .self_is_next([INT lookahead])
Returns true if this node is next in line for this request. The next in line for each request is determined by ordering the backends according to the director type (usually hash
), and checking whether the directors identity matches the next healthy and unused backend in the list.
If lookahead
is supplied, more than one backend is checked for an identity match. A lookahead value of two will match the director identity against the two next healthy and unused backends.
Minimal self-routing example
sub vcl_recv {
if (udo_dir.self_identify(req.http.identifier)) {
return (synth(242, "Self-Identified"));
}
}
sub vcl_backend_fetch {
if (!udo_dir.self_is_next()) {
set bereq.http.identifier = udo_dir.get_identifier();
set bereq.backend = udo_dir.backend();
}
}
sub vcl_backend_response {
if (beresp.status == 242 && beresp.reason == "Self-Identified") {
return (retry);
}
}
The above example is intended to show the bare minimum VCL required implement self-routing.
This VMOD ships also with a VCL called cluster.vcl
, which uses this VMOD to implement self routing in a more complete way compared to the example above.
Please note that new versions of this file might alter the behavior slightly, if such changes can improve the overall design and performance of the solutions it provides.
If you use the VCL or the new self routing functions in this VMOD, please pay attention to change logs and release announcements until this notice has been removed.
The VCL file can be included at the top of your VCL to enable self-routing:
include "cluster.vcl";
cluster.vcl
provides a self-routing director called cluster
, all the logic required to self-route within the cluster, and options to modify the caching behavior.
Available options
token
: Required. Used to indicate that a request is coming a cluster node.primaries
: Optional, default “1”. Determines the number of nodes that can go to origin for a given object.replicas
: Optional. If set, determines the number of long duration replicas that should be stored across the cluster.long_ttl
: Optional. TTL set for long duration replicas.long_grace
: Optional. Grace set for long duration replicas.long_keep
: Optional. Keep set for long duration replicas.long_stores
: Optional. Sets the MSE stores for long duration replicas.short_ttl
: Optional. TTL set for short duration objects.short_grace
: Optional. Grace set for short duration objects.short_keep
: Optional. Keep set for short duration objects.short_stores
: Optional. Sets the MSE stores for short duration objects.Cluster configuration example
import activedns;
include "cluster.vcl";
backend origin { .host = "origin.node"; }
sub vcl_init {
new cluster_group = activedns.dns_group();
cluster_group.set_host("varnish.nodes:6081");
cluster.subscribe(cluster_group.get_tag());
cluster_opts.set("token", "secret");
cluster_opts.set("replicas", "2");
cluster_opts.set("short_stores", "none");
cluster_opts.set("long_stores", "ssd");
}
sub vcl_backend_fetch {
set bereq.backend = origin;
}
This configuration will self route requests among nodes resolved from varnish.nodes, and the primary node for each request will fetch from origin. When caching an object in the cluster, two nodes will store the object on SSD, while the rest of the nodes store the object in memory. TTL, grace, and keep are not modified.