Here are some useful guides and resources for working with leng. Contributions welcome!
Why Leng
Reasons you would want to use Leng include:
- Ad-blocking at the DNS level: this compliments misses browser adblockers (they use a different approach to block ads), and is especially useful in devices where ad-blockers are hard to install (like smart TVs, or non-browser apps).
- Blocking tracking at the DNS level: vendors, especially your device's manufacturers, will often track you outside of websites (where browser ad-blockers are powerless). When using the right blocklists, leng will block this tracking for all devices that use it as their DNS provider.
- DNS Server for self-hosted infra: by specifying your records on a config file, leng is a very easily maintanable custom DNS server deployment.
- DNS Privacy and Security: many devices use the most basic DNS implementation, DNS over UDP. This is a bad idea because it is less private and less secure (you can read here to understand why). Leng can serve as a secure proxy so that even if your devices speak to it via UDP, it speaks to the rest of the internet via the more secure alternatives (like DoH or DoT).
- It's small and fast
- There are few open-source DNS servers with the above features: my motivation for forking grimd and creating leng was the need for a server that provided blocklists (like Blocky) as well as decent custom DNS records support (like CoreDNS, grimd was almost there).
For more on leveraging leng for DNS privacy, see DNS Privacy.
Alternatives Comparison
Leng overlaps with a few other solutions in terms of providing DNS sinkholing for advertisements, as well as custom DNS. This pages aims to clarify how leng compares to these other solutions.
TLDR: Leng is suitable for a simple DNS server that serves custom records and blocks ads. It is designed to be small and easily scriptable (like Blocky), whereas Adguard, PiHole, etc are more comprehensive solutions that include many more features but are not stateless, and are likely to have a larger fingerprint.
This is by all means not a comprehensive list. Note I have not tried every single of these alternatives, so some information might be outdated or plain wrong - if so please submit a PR to correct it if you find it so.
Trait | Leng | Blocky | Adguard | PiHole | CoreDNS |
---|---|---|---|---|---|
Blocklist-basd blocking (remote fetch) | ✅ | ✅ | ✅ | ❌ ish | ❌ |
Custom DNS records support | ✅ | ❌ ish (only rewrites) | ❌ ish | ✅ ish via dnsmasq (no templating) | ✅ |
RAM footprint | 50MB with traffic + DoH | 150MB | 512MB | 250MB but depends heavily on plugins | |
Ease of use | Config file | Config file | Config file + Web UI | Web UI | Config file |
Parental controls | Through parental blocklists | Through parental blocklists | ✅ | ✅ | ❌ |
DNS-over-HTTPS server | ✅ | ✅ | ✅ | ✅ | ❌ |
DNS-over-HTTPS upstream proxy | ✅ | ✅ | ✅ | ✅ | ✅ |
Stateless (all config as files) | ✅ | ✅ | ❌ | ❌ | ✅ |
Running rootless | ✅ | ✅ | ✅ | ❌ | ✅ |
Prometheus metrics API | ✅ | ✅ | ❌ see PR | ❌ but exporter exists | ✅ via plugin |
Per device config | ❌ | ✅ via client groups | ✅ | ✅ | ✅ via plugins |
DHCP Server (Assigns IPs to devices) | ❌ | ❌ | ✅ | ✅ | ❌ |
Fancy Web UI | ❌ | ❌ | ✅ | ✅ | ❌ |
DNS (overview)
Leng works by proxying your DNS requests to an upstream DNS server, and returning a useless response when the request is for a blocked domain.
Blocked domains are those that appear on a blocklist (downloaded at startup). You can see which blocklists are enabled by default and how to change them in Configuration.
Additionally, you can also configure custom responses for specific domains, indepenently of the blocklists. See more in Custom DNS.
sequenceDiagram User --> Leng: Online Blocklists -->> Leng: Download lists Note over Online Blocklists,Leng: At startup User->> +Leng: A google.com Leng ->> Upstream DNS: A gogle.com Upstream DNS ->> Leng: google.com IN A 234.213.532.12 Leng ->> -User: google.com IN A 234.213.532.12 User ->> +Leng: A adservice.google.com Leng ->> -User: adservice.google.com IN A 0.0.0.0
DNS-over-HTTP(S), aka DoH
Leng supports DNS-over-HTTPS as per RFC-8484, although it is disabled by default.
Custom DNS records will be served over DoH the same as normal DNS requests.
You can specify your key files yourself to have leng serve HTTPS traffic, or you can let leng serve HTTP traffic and have a proxy manage the HTTPS certificates.
Specifying Key files (HTTP)
[DnsOverHttpServer]
enabled = true
bind = "0.0.0.0:80"
timeoutMs = 5000
[DnsOverHttpServer.TLS]
enabled = true
certPath = ""
keyPath = ""
# if empty, system CAs will be used
caPath = ""
Not specifying key files (TLS disabled, HTTP traffic from leng)
[DnsOverHttpServer]
enabled = true
bind = "0.0.0.0:80"
timeoutMs = 5000
⚠ It is not recommended to use HTTP without TLS at all directly. Your queries will be un-encrypted, so they won't be much different than normal UDP queries.
You can use DoH in most browsers.
Custom DNS Records
You can make leng return records of your choosing (which will take precedence over upstream DNS records) by setting customdnsrecords
in the Configuration.
Custom DNS records are represented as Resource Record strings. Class defaults to IN and TTL defaults to 3600. Full zone file syntax is supported.
customdnsrecords = [
"example.com. 3600 IN A 10.10.0.1",
"example.cname.com. IN CNAME wikipedia.org",
]
CNAME Following
Leng implements following CNAME records as specified in RFC-1034 section 3.6.2, where it returns all necessary CNAME and A records to fully resolve the query (as opposed to just returning a synthetic A record, which is known as CNAME flattening).
⚠ This is the behaviour of most if not all DNS servers - Leng is only special in this in that it has to deal with cuustom DNS records, the resolvers it proxies, and blocklists. You should not need to change its default behaviour, but this page aims to leave it well-documented.
dig
request example
$> dig first.example
; <<>> DiG 9.18.19 <<>> first.example
;; QUESTION SECTION:
;first.example. IN A
;; ANSWER SECTION:
first.example. 300 IN CNAME second.example.
second.example. 300 IN CNAME third.example.
third.example. 300 IN A 139.201.133.245
Behaviour
The resolving for the downstream CNAME records is done with the same question type as the original question. That is, if you ask AAAA some-cname.com
, the following CNAME queries will be AAAA
questions too.
Custom records
If you have set up your own custom records, those can also be part of the CNAME chain.
This makes it easy to alias custom records to external domains:
customdnsrecords = [
"login.vpn IN CNAME this.very.long.other.domain.login.login.my-company.xyz"
]
Querying login.vpn
will also return the A record corresponding to this.very.long.other.domain.login.login.my-company.xyz
.
Blocking
If any of the domains involved in the CNAME-following is part of a blocklist (that is, it would get blocked if it corresponded to an A
response, rather than CNAME
) then the entire request blocked (unless the domain is part of the custom DNS defined in the config)
For the example where we have
first.example IN CNAME second.example
second.example IN CNAME third.example
third.example IN A 10.0.0.0
if any of first.example
, second.example
or third.example
appear in a blocklist, the request for first.example
will fail.
Configuration
CNAME-following is enabled by default, but you can disabled with the following:
# leng.toml
followCnameDepth = 0
Blocking DNS
There are many blocklist resources online, and by default leng is configured to use some of the more popular ones from around the internet for blocking ads and malware domains. Some services exist that will allow you to regularly get blocklist updates automatically from feeds.
Blocklists
https://github.com/StevenBlack/hosts/
DNS Privacy
Leng can enhance your DNS Privacy in several ways
As your DoH provider
DNS-over-HTTPS allows encrypted, hard-to-block DNS. You can set up DNS-over-HTTPS for most major browsers (see how here).
See how to set it up for leng at DNS-over-HTTP.
As a DoH proxy
DoH is great, but most devices use DNS-over-UDP by default, and some can't even be configured otherwise.
If you have your own private secure network, you can stop attackers from learning what websites you visit by using leng as a secure proxy:
graph TD subgraph Secure Network U("🧘 User") --> |"🔓 Insecure\nDNS-over-UDP"|L[Leng] end L --> |"🔒 Secure DoH"| Up[Upstream DNS] A("👿 Attacker") ---> |Cannot see contents\nof DNS requests | Up
This way you allow 'insecure' DNS, but only inside your network, and your requests are private to external attackers.
No configuration is required for this: leng will always try to resolve domains by DoH via cloudflare before falling back to other methods. You can choose the upstream DoH resolver in the Configuration.
Note that this method is only as secure as your network is! Ideally set up as many devices as possible to use DoH directly
Preserving privacy against a single upstream
If you do not trust upstream providers with your privacy, ideally you should not send all your requests to any one of them. Because of the authoritative nature of DNS, asking some upstream cannot be avoided, but the best you can do is use a fully recursive resolver like unbound. You can still use non-recursive DNS proxies (leng, blocky, or CoreDNS) and their features by using unbound as your upstream, and letting unbound resolve your queries.
graph LR you(("You")) --> leng(leng) --> unbound(unbound) -.-> u1["upstream A"] & u2["upstream B"] & u3["upstream C"]
If leng.toml is not found the default configuration will be used. If it is found, fields that are set will act as overrides.
Quick Start
If you are happy to use Cloudflare as your upstream DNS provider and just want to generally block tracking and advertising, the following minimal config should be enough.
If you want to tweak more settings, keep scrolling down!
# address to bind to for the DNS server
bind = "0.0.0.0:53"
# address to bind to for the API server
api = "127.0.0.1:8080"
# manual custom dns entries - comments for reference
customdnsrecords = [
# "example.mywebsite.tld IN A 10.0.0.1",
]
[Metrics]
enabled = false
[Blocking]
# manual whitelist entries - comments for reference
whitelist = [
# "getsentry.com",
]
Default configuration
# log configuration
# format: comma separated list of options, where options is one of
# file:<filename>@<loglevel>
# stderr>@<loglevel>
# syslog@<loglevel>
# loglevel: 0 = errors and important operations, 1 = dns queries, 2 = debug
# e.g. logconfig = "file:leng.log@2,syslog@1,stderr@2"
logconfig = "stderr@2"
# apidebug enables the debug mode of the http api library
apidebug = false
# address to bind to for the DNS server
bind = "0.0.0.0:53"
# address to bind to for the API server
api = "127.0.0.1:8080"
# concurrency interval for lookups in miliseconds
interval = 200
# question cache capacity, 0 for infinite but not recommended (this is used for storing logs)
questioncachecap = 5000
# manual custom dns entries - comments for reference
customdnsrecords = [
# "example.mywebsite.tld IN A 10.0.0.1",
# "example.other.tld IN CNAME wikipedia.org"
]
[Blocking]
# response to blocked queries with a NXDOMAIN
nxdomain = false
# ipv4 address to forward blocked queries to
nullroute = "0.0.0.0"
# ipv6 address to forward blocked queries to
nullroutev6 = "0:0:0:0:0:0:0:0"
# manual blocklist entries
blocklist = []
# list of sources to pull blocklists from, stores them in ./sources
sources = [
"https://mirror1.malwaredomains.com/files/justdomains",
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
"https://sysctl.org/cameleon/hosts",
"https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
"https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
"https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt"
]
# list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list)
sourcedirs = ["./sources"]
sourcesStore = "./sources"
# manual whitelist entries - comments for reference
whitelist = [
# "getsentry.com",
# "www.getsentry.com"
]
[Upstream]
# Dns over HTTPS provider to use.
DoH = "https://cloudflare-dns.com/dns-query"
# nameservers to forward queries to
nameservers = ["1.1.1.1:53", "1.0.0.1:53"]
# query timeout for dns lookups in seconds
timeout_s = 5
# cache entry lifespan in seconds
expire = 600
# cache capacity, 0 for infinite
maxcount = 0
# Prometheus metrics
[Metrics]
enabled = false
path = "/metrics"
# see https://cottand.github.io/leng/Prometheus-Metrics.html
highCardinalityEnabled = false
histogramsEnabled = false
resetPeriodMinutes = 60
[DnsOverHttpServer]
enabled = false
bind = "0.0.0.0:80"
timeoutMs = 5000
# TLS config is not required for DoH if you have some proxy (ie, caddy, nginx, traefik...) manage HTTPS for you
[DnsOverHttpServer.TLS]
enabled = false
certPath = ""
keyPath = ""
# if empty, system CAs will be used
caPath = ""
The most up-to-date version can be found on config.go
Prometheus metrics
The HTTP API has a /metrics
endpoint that exposes Go runtime metrics as well as things including:
- downstream DNS requests, broken down by type
- upstream DNS requests
- upstream DNS-over-HTTPS success rate
- downstream DNS-over-HTTPS success rate
No grafana dashboards exist for leng yet. If you make one, please make a PR!
High cardinality metrics
Tags can be added to some metrics (upstream_request
, request_total
) so that
they include information such as the name of the DNS request (ie, example.com.
)
or the IP of host making the request.
If leng is left to run for a few hours (and you have enough traffic),
the cardinality of these metrics will grow, to the point
the
size of the /metrics
response will grow to be so big the metrics stop being updated.
While resetting the counters periodically can help
(and you can tweak that with the config Metrics.resetPeriodMinutes
)
you might still see issues depending on your traffic.
You can
read this SO post
to learn more.
High cardinality metrics can also compromise your privacy by exposing in the metrics endpoint what domains clients are querying as well as their IPs.
For these reasons, high cardinality metrics are disabled by default. You can enable them with the following config:
[Metrics]
enabled = true
path = "/metrics"
highCardinalityEnabled = true
Histogram metrics
Histogram metrics are not unbounded and usually will not be as high-cardinality as the metrics discussed above, but you should still expect them to have some impact on leng's the memory footprint.
You can enable them with:
[Metrics]
enabled = true
path = "/metrics"
histogramsEnabled = true
Signals (config reload)
Leng will listen for SIGUSR1
signals in order to reload the config file at run-time
and apply the new config.
Currently, the only field of the config file that supports reloading is
customdnsrecords
, which specifies custom DNS records served.
Please make an issue if you have a use-case where you would
like some other config fields to also be able to be reloaded.
In the meantime, for all fields, it is safe to simply change the config file while leng is running, and restart it.
Note on Nomad deployments
Nomad is able to send signals rather than restarting a task when a template (like the config file) changes.
It is not recommended to use this approach with leng, because there are instances in which Nomad can try to send a signal even if the task is not running (see hashicorp/nomad#5459, which was unresolved at the time of writing).
Instead, set the template's
change_mode = "restart"
and rely on Nomad restarting the task. Due to leng's fast startup time and tiny image size, it should take seconds even when redownloading the image.In order to still mitigate this downtime, rolling/canary deployments can be used so there is always an instance of leng up to serve traffic.
Docker
Leng is also distributed as a Docker image. You can find published images here. The image is small (v1.3.1 is under 13MB).
Supported architectures are linux AMD64, ARM64, ARMv6, ARMv7.
If you think leng ought to support more OSs or architectures, please make an issue.
Running
With the default configuration:
docker run -d \
-p 53:53/udp \
-p 53:53/tcp \
-p 8080:8080/tcp \
ghcr.io/cottand/leng
With a specific leng.toml
:
docker run -d \
-p 53:53/udp \
-p 53:53/tcp \
-p 8080:8080/tcp \
-v leng.toml:/leng.toml \
ghcr.io/cottand/leng \
-config /leng.toml
See Configuration for the full config.
Deploying on Debian
Installing leng
Installing leng is the easiest when you simply download a release from the releases page. Go ahead and copy the link for leng_linux_x64 and run the following in your terminal.
mkdir ~/grim
cd ~/grim
wget <leng release>
This will download the binary to ~/grim
which will be leng's working directory. First, lets setup file permissions for leng, by running the following.
chmod a+x ./leng_linux_x64
Setup is pretty much complete, the only thing left to do is run leng and let it generate the default configuration and download the blocklists, but lets set it up as a systemd service so it automatically restarts and updates when starting.
Setting up the service
Create the leng service by running the following,
nano /etc/systemd/system/leng.service
Now paste in the code for the service below,
[Unit]
Description=leng dns proxy
Documentation=https://github.com/looterz/leng
After=network.target
[Service]
User=root
WorkingDirectory=/root/grim
LimitNOFILE=4096
PIDFile=/var/run/leng/leng.pid
ExecStart=/root/grim/leng_linux_x64 -update
Restart=always
StartLimitInterval=30
[Install]
WantedBy=multi-user.target
Save, and now you can start, stop, restart and run status commands on the leng service like follows
systemctl start leng # start
systemctl enable leng # start on boot
The only thing left to do is setup your clients to use your leng dns server.
Security
Now that leng is setup on your droplet, it's recommended to secure the installation from non-whitelisted clients.
Securing on Linux
The recommended way to harden access to the leng server is to only allow connections from clients you trust, mainly because public dns servers are hit by penetration testers and hackers regularly to scout for vulnerabilities.
Installing Requirements
Let's grab ufw to allow for easy editing of iptables.
apt-get install ufw -y
Firewall Setup
Now let's whitelist our dns clients IP address or range, and block access from everywhere else by default using ufw.
ufw deny 53
ufw allow from <ip or range> to any port 53
ufw reload
Now only the client(s) you whitelisted can access the dns server.
⚠ For Docker deployments, keep in mind
ufw
will not stop outside connections to your containers if you bind their ports. See the Docker docs about the issue.
Nix
Leng is also packaged as a Nix flake.
Running
You can simply run nix run github:cottand/leng
to run latest master
.
Installing in NixOS via a Module
The leng flake also exports a NixOS module for easy deployment on NixOS machines.
Please refer to Configuration for the options you can use under services.leng.configuration. = ...
.
In your flake
{
inputs = {
# pinned version for safety
leng.url = "github:cottand/leng/v1.6.0";
leng.nixpkgs.follows = "nixpkgs";
};
outputs = { self, leng, ... }: {
# Use in your outputs
nixosConfigurations."this-is-a-server-innit" = nixpkgs.lib.nixosSystem {
modules = [
./configuration.nix
leng.nixosModules.default # <- import leng module
{
services.leng = { # <-- now you can use services.leng!
enable = true;
configuration = {
api = "127.0.0.1:8080";
metrics.enabled = true;
blocking.sourcesStore = "/var/lib/leng-sources";
};
};
}
];
};
};
}
Legacy Nix
Add the following inside your configuration.nix:
{pkgs, lib, ... }: {
imports = [
# import leng module
(builtins.getFlake "github:cottand/leng/v1.6.0").nixosModules.default
];
# now you can use services.leng!
services.leng = {
enable = true;
configuration = {
api = "127.0.0.1:8080";
metrics.enabled = true;
blocking.sourcesStore = "/var/lib/leng-sources";
};
};
}
Using leng for local DNS
If you want to use leng to resolve the DNS queries of the machine you are installing it on, you should also add it to the local nameservers:
networking.nameservers = [ "127.0.0.1" ];
Developing
The flake's development shell simply includes Go 1.21+ and a fish shell. You can enter it with nix develop
.
Deploying on Nomad
Example Job file
You can deploy leng on Nomad. The following job file should serve as a starting point. It includes
- ports bound to a host network
YOUR_VPN
- services for metrics and DNS, including DoH
It is strongly recommended that
- you do not expose your DNS ports to the outer internet (as you will make
yourself vulnerable to DNS amplification and DoS attacks). I recommend
you use Nomad's
host_network
feature to select what interface to bind the ports to. - you bind the
dns
port to 53 in order to make it reachable. Other ports can be reached through your preferred method of service discovery.
Drop down for Job file
job "dns" {
group "leng-dns" {
network {
mode = "bridge"
port "dns" {
static = 53
}
port "metrics" {}
port "http_doh" {}
}
update {
canary = 1
min_healthy_time = "30s"
healthy_deadline = "5m"
auto_revert = true
auto_promote = true
}
service {
name = "dns-metrics"
provider = "nomad"
port = "metrics"
tags = ["metrics"]
}
service {
name = "dns"
provider = "nomad"
port = "dns"
}
service {
name = "doh"
provider = "nomad"
port = "http_doh"
}
task "leng-dns" {
driver = "docker"
config {
image = "ghcr.io/cottand/leng:sha-6b2e265"
args = [
"--config", "${NOMAD_ALLOC_DIR}/config.toml",
"--update",
]
ports = ["dns", "metrics"]
}
resources {
cpu = 80
memory = 80
}
template {
destination = "${NOMAD_ALLOC_DIR}/config.toml"
change_mode = "restart"
data = <<EOF
logconfig = "stderr@1"
# address to bind to for the DNS server
bind = "0.0.0.0:{{ env "NOMAD_PORT_dns" }}"
# address to bind to for the API server
api = "0.0.0.0:{{ env "NOMAD_PORT_metrics" }}"
# concurrency interval for lookups in miliseconds
interval = 200
metrics.enabled = true
[Upstream]
nameservers = ["1.1.1.1:53", "1.0.0.1:53"]
DoH = "https://cloudflare-dns.com/dns-query"
[DnsOverHttpServer]
enabled = true
bind = "0.0.0.0:{{ env "NOMAD_PORT_http_doh" }}"
timeoutMs = 5000
[DnsOverHttpServer.TLS]
enabled = false
EOF
}
}
}
}
Service Discovery
Leng can be used as a sort of Consul replacement to implement
DNS-based service discovery - meaning you can address your
Nomad services by DNS. See the following templated config file
(you would use it inside the template above, as a replacemenet for the
provided config.toml
file).
Example diagram for discovering a grafana
service (SRV record omitted):
graph TD J("🐳 Nomad Job (grafana)\n at 10.0.0.3") -->|" registers `grafana` service "| N[Nomad] N -->|renders template| L["⚡ Leng \n `grafana.nomad. IN A 10.10.0.3` "] U("You (or some container)") -->|queries DNS| L L -->|reaches| J
Templated TOML config for leng
- creates A record per Nomad client
- creates A record per service
- creates SRV record per service pointing to (port, client)
logconfig = "stderr@1"
# address to bind to for the DNS server
bind = "0.0.0.0:{{ env "NOMAD_PORT_dns" }}"
# address to bind to for the API server
api = "0.0.0.0:{{ env "NOMAD_PORT_metrics" }}"
metrics.enabled = true
customdnsrecords = [
# example for your hardcoded service:
{{ range $i, $s := nomadService "my-service" }}
"myservice.mytld 3600 IN A {{ .Address }}",
{{ end }}
## start - generation for every registered nomad service" ##
{{ $rr_a := sprig_list -}}
{{- $rr_srv := sprig_list -}}
{{- $base_domain := ".nomad" -}} {{- /* Change this field for a diferent tld! */ -}}
{{- $ttl := 3600 -}} {{- /* Change this field for a diferent ttl! */ -}}
{{- /* Iterate over all of the registered Nomad services */ -}}
{{- range nomadServices -}}
{{ $service := . }}
{{- /* Iterate over all of the instances of a services */ -}}
{{- range nomadService $service.Name -}}
{{ $svc := . }}
{{- /* Generate a uniq label for IP */ -}}
{{- $node := $svc.Address | md5sum | sprig_trunc 8 }}
{{- /* Record A & SRV RRs */ -}}
{{- $rr_a = sprig_append $rr_a (sprig_list $svc.Name $svc.Address) -}}
{{- $rr_a = sprig_append $rr_a (sprig_list $node $svc.Address) -}}
{{- $rr_srv = sprig_append $rr_srv (sprig_list $svc.Name $svc.Port $node) -}}
{{- end -}}
{{- end -}}
{{- /* Iterate over lists and print everything */ -}}
{{- /* Only the latest record will get returned - see https://github.com/looterz/grimd/issues/114 */ -}}
{{ range $rr_srv -}}
"{{ printf "%-45s %s %s %d %d %6d %s" (sprig_nospace (sprig_cat (index . 0) $base_domain ".srv")) "IN" "SRV" 0 0 (index . 1) (sprig_nospace (sprig_cat (index . 2) $base_domain )) }}",
{{ end -}}
{{- range $rr_a | sprig_uniq -}}
"{{ printf "%-45s %4d %s %4s %s" (sprig_nospace (sprig_cat (index . 0) $base_domain)) $ttl "IN" "A" (sprig_last . ) }}",
{{ end -}}
Templating works very well with leng because of its fast startup and small Docker image. When Nomad restarts the task because of a change in the template, leng will be back up in seconds or less.
Proxy discovery with DNS-based routing
You can tweak this further to direct DNS to your ingress proxy, rather than directly to each container.
For example, if you are using traefik, you could:
- Add services to traefik by default (with defaultRule setting) for example:
# in traefik.toml
providers.nomad.defaultRule = "Host(`{{"{{ .Name }}"}}.traefik`)"`)
- Add a DNS A record pointing to traefik for each service:
customdnsrecords = [
{{- $ttl := 3600 -}} {{- /* Change this field for a diferent ttl! */ -}}
{{- $traefik_ip := "10.10.4.1" -}} {{- /* Change this field to the IP of your traefik! */ -}}
{{- range nomadServices -}}
{{ $service := . }}
{{- /* Iterate over all of the instances of a services */ -}}
{{- range nomadService $service.Name -}}
{{- /* A records to traefik IP: */ -}}
"{{ printf "%-45s %4d %s %4s %s" (sprig_nospace (sprig_cat .Name ".traefik")) $ttl "IN" "A" $traefik_ip }}",
{{ end }}
{{ end }}
]
The end result: if you visit grafana.traefik
, you will get directed
to the instance where traefik is running, and traefik
will proxy your request to the actual instance where the grafana
service is running!
Example diagram for discovering a grafana
service:
graph TD J("🐳 Nomad Job (grafana)\n at 10.0.0.3") -->|" registers `grafana` service "| N[Nomad] U("You (or some container)") -->|queries DNS| L T -->|discovers services from| N T("🛣 Traefik Job at 10.0.0.1") -->|proxies for| J N -->|renders template| L["⚡ Leng \n `grafana.traefik. IN A 10.10.0.1` "] L -->|reaches| T
Acknowledgments
The templating are modified versions of this gist by m1keil.
Some parameters may require your attention, such as path and executable name.
systemd
Create /etc/systemd/services/leng.service
and paste in the following.
[Unit]
Description=leng dns proxy
Documentation=https://github.com/looterz/leng
After=network.target
[Service]
User=root
WorkingDirectory=/root/grim
LimitNOFILE=4096
PIDFile=/var/run/leng/leng.pid
ExecStart=/root/grim/leng_linux_x64 -update
Restart=always
StartLimitInterval=30
[Install]
WantedBy=multi-user.target
init.d
Create /etc/init.d/leng
and paste in the following.
#!/bin/bash
# leng daemon
# chkconfig: 345 20 80
# description: leng daemon
# processname: leng
DAEMON_PATH="/root/grim"
DAEMON=leng
DAEMONOPTS="-update"
NAME=leng
DESC="https://github.com/looterz/leng"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
case "$1" in
start)
printf "%-50s" "Starting $NAME..."
cd $DAEMON_PATH
PID=`$DAEMON $DAEMONOPTS > /dev/null 2>&1 & echo $!`
#echo "Saving PID" $PID " to " $PIDFILE
if [ -z $PID ]; then
printf "%s\n" "Fail"
else
echo $PID > $PIDFILE
printf "%s\n" "Ok"
fi
;;
status)
printf "%-50s" "Checking $NAME..."
if [ -f $PIDFILE ]; then
PID=`cat $PIDFILE`
if [ -z "`ps axf | grep ${PID} | grep -v grep`" ]; then
printf "%s\n" "Process dead but pidfile exists"
else
echo "Running"
fi
else
printf "%s\n" "Service not running"
fi
;;
stop)
printf "%-50s" "Stopping $NAME"
PID=`cat $PIDFILE`
cd $DAEMON_PATH
if [ -f $PIDFILE ]; then
kill -HUP $PID
printf "%s\n" "Ok"
rm -f $PIDFILE
else
printf "%s\n" "pidfile not found"
fi
;;
restart)
$0 stop
$0 start
;;
*)
echo "Usage: $0 {status|start|stop|restart}"
exit 1
esac
rc.d
Create /etc/rc.d/leng
and paste in the following.
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: leng
# REQUIRE: NETWORKING SYSLOG
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf to enable leng:
#
#leng_enable="YES"
. /etc/rc.subr
name="leng"
rcvar="leng_enable"
load_rc_config $name
: ${leng_user:="root"}
: ${leng_enable:="NO"}
: ${leng_directory:="/root/grim"}
command="${leng_directory}/leng -update"
pidfile="${leng_directory}/${name}.pid"
start_cmd="export USER=${leng_user}; export HOME=${leng_directory}; /usr/sbin/daemon -f -u ${leng_user} -p ${pidfile} $command"
#stop_cmd="kill $(cat $pidfile)"
stop_cmd="${name}_stop"
leng_stop() {
if [ ! -f $pidfile ]; then
echo "leng PID File not found. Maybe leng is not running?"
else
kill $(cat $pidfile)
fi
}
run_rc_command "$1"
or
#
# OpenBSD
#
daemon="<path_to_daemon>"
. /etc/rc.d/rc.subr
rc_bg=YES
rc_reload=NO
rc_cmd $1