Create a DERP server in the K8s cluster

This article was last updated on: July 24, 2024 am

Preface

The purpose of this article is to build Tailscale’s DERP server within a K8s cluster.

Background Knowledge

Tailscale

Tailscale allows you to easily manage access to private resources (essentially a VPN tool), quickly SSH into devices on your network, and work securely from anywhere in the world.

Create a secure WireGuard mesh network between your devices, virtual machines and servers - even if they are separated by a firewall or subnet.

DERP

Tailscale runs a DERP relay server to help connect your nodes. In addition to using the DERP servers provided by tailscale, you can also run your own servers.

Tailscale runs DERP relay servers distributed around the world, using your Tailscale node point-to-point as a side channel during NAT traversal and as a backup in case NAT traversal fails and a direct connection cannot be established.

Tailscale runs DERP servers in many locations. As of September 2022, this list includes:

  • Australia (Sydney)
  • Brazil (São Paulo)
  • Canada (Toronto)
  • Dubai (Dubai)
  • France (Paris)
  • Germany (Frankfurt)
  • Hong Kong (Hong Kong)
  • India (Bangalore)
  • Japan (Tokyo)
  • Netherlands (Amsterdam)
  • Poland (Warsaw)
  • Singapore (Singapore)
  • South Africa (Johannesburg)
  • Spain (Madrid)
  • United Kingdom (London)
  • United States (Chicago, Dallas, Denver, Honolulu, Los Angeles, Miami, New York City, San Francisco, and Seattle)

Tailscale clients automatically select the nearest low-latency relay. In order to provide low latency connectivity, Tailscale is continuously expanding and adding more DERP servers as needed.

In order to achieve Low Latency and Stability, DERP servers need to be built.

Steps

Convert any minimal docker-compose configuration to K8s configuration according to the last reference document (you can use the tool: kompose to convert), the converted configuration is as follows:

📝Notes:

To facilitate the configuration of domains as Env, StatefulSets is used here.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Service
metadata:
name: derper-tok
labels:
io.kompose.service: derper-tok
spec:
ports:
- port: 443
name: https
- port: 80
name: http
- protocol: UDP
port: 3478
name: stun
clusterIP: None
selector:
io.kompose.service: derper-tok
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
apiVersion: apps/v1
kind: StatefulSet
metadata:
annotations:
kompose.cmd: kompose convert -f docker-compose.yml
kompose.version: 1.26.1 (a9d05d509)
labels:
io.kompose.service: derper-tok
name: derper-tok
namespace: tailscale
spec:
replicas: 3
selector:
matchLabels:
io.kompose.service: derper-tok
serviceName: derper-tok
template:
metadata:
annotations:
kompose.cmd: kompose convert -f docker-compose.yml
kompose.version: 1.26.1 (a9d05d509)
labels:
io.kompose.service: derper-tok
spec:
containers:
- env:
- name: MY_POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: DOMAIN
value: example.com
- name: DERP_ADDR
value: :443
- name: DERP_CERT_DIR
value: /app/certs
- name: DERP_CERT_MODE
value: letsencrypt
- name: DERP_DOMAIN
value: $(MY_POD_NAME).$(DOMAIN)
- name: DERP_HTTP_PORT
value: "80"
- name: DERP_STUN
value: "true"
- name: DERP_VERIFY_CLIENTS
value: "true"
image: fredliang/derper:latest
imagePullPolicy: Always
name: derper-tok
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: true
volumeMounts:
- mountPath: /var/run/tailscale/tailscaled.sock
name: tailscale-socket
hostNetwork: true
volumes:
- hostPath:
path: /run/tailscale/tailscaled.sock
type: Socket
name: tailscale-socket
updateStrategy:
rollingUpdate:
partition: 0
type: RollingUpdate

The details are as follows:

  • Why use StatefulSets, instead of Deployment or DaemonSet, mainly because my own expectations are as follows:
    • I want to be able to use a domain name like derper-tok-{1..3}.example.com, so that if I use StatefulSets, derper-tok-1 is the POD Name, which is easy to configure.
    • If you use Deployment or DaemonSet, the Pod name is random and the domain name needs to be configured one by one.
  • The K8s service here is purely because it is needed to create StatefulSets, and is not actually used
  • The domain name is combined by MY_POD_NAME DOMAIN DERP_DOMAIN based on the POD name
  • DERP_CERT_MODE Now the new version of DERP supports let’s encrypt to apply certificate automatically, which is much more convenient than before.
  • DERP_VERIFY_CLIENTS: true Ensure that only you can use your own DERP server, need to use with tailscale
  • fredliang/derper:latest The image is used directly
  • securityContext needs to ensure that it has NET_ADMIN capability, and privileged: true is best added to ensure greater privileges.
  • hostNetwork: true uses the host network directly, i.e.: port 443, 3478 to listen directly to the K8s Node port, simple and brutal. If there is a port conflict you need to adjust the port, or don’t use this mode.
  • volumeMounts and volumes: Here I installed the tailscale socket on the K8s Node as /run/tailscale/tailscaled.sock, and mounted it to the DERP container /var/run/tailscale/ tailscaled.sock, and with DERP_VERIFY_CLIENTS: true, the DERP server will automatically verify the client and ensure security.

That’s it, kubectl apply and you’re done.

🎉🎉🎉

Summary

This article is rather pure and illustrates a scenario: installing a DERP server in K8s. There is not much context, so you can learn about it yourself if you are interested.

There may be time for an article on installing tailscale in K8s later.

After installation, configure the ACL on the tailscale console, add the new DERP domains to derpMap and you are done.
Finally, you can verify it with: tailscale netcheck.

Reference documentation