Natively Uncloud doesn’t do L4 forwarding. This is an open issue as we are figuring out how to actually do this. But it turns out you can already do this with the current code - well almost.

Buckle up, because there is quite some stuff you may need:

  1. A patched Uncloud, see this PR for that.
  2. A custom Caddy build, with the L4 plugin.
  3. A DNS server, I use atomdns of course.

Caddy

With the following Dockerfile you create a custom caddy image that we will use.

FROM caddy:2.11.2-builder AS builder
RUN xcaddy build \
    --with github.com/mholt/caddy-l4

FROM caddy:2.11.2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

The compose.yaml is remarkably short, but we also need a Caddyfile, both are listed here, the Caddyfile first, this tells caddy to forward the DNS traffic to the internal address of the DNS server.

{
        layer4 {
                udp/:53 {
                        route {
                                proxy udp/atomdns.internal:53
                        }
                }
                tcp/:53 {
                        route {
                                proxy tcp/atomdns.internal:53
                        }
                }
        }
}

And the compose.yaml:

services:
  caddy:
    image: registry.science.ru.nl/cncz/sys/image/caddy:latest
    command: caddy run -c /config/Caddyfile
    environment:
      CADDY_ADMIN: unix//run/caddy/admin.sock
    volumes:
      - /var/lib/uncloud/caddy:/data
      - /var/lib/uncloud/caddy:/config
      - /run/uncloud/caddy:/run/caddy
    x-ports:
      - 80:80@host
      - 443:443@host
      - 443:443/udp@host
      - enp1s0:53:53@host
      - enp1s0:53:53/udp@host
    x-caddy: Caddyfile
    deploy:
      mode: global

Where I want to highlight enp1s0:53:53@host and enp1s0:53:53/udp@host which is what the pull requests adds. As the interface name is shared on all machines, the caddy service can still be globally deployed. If I were to bind on all interfaces (the default for Uncloud) I trample on uncloudd own use of the port 53.

If you are in the directory where you saved the above files you can just run uc deploy -y and have caddy running.

atomdns

We use this Dockerfile to build atomdns ourselves.

ARG BUILD_IMAGE=golang:latest
ARG BASE=alpine:latest

FROM --platform=$BUILDPLATFORM ${BUILD_IMAGE} AS build
WORKDIR /tmp
RUN git clone --depth 1 https://codeberg.org/miekg/dns
WORKDIR /tmp/dns/cmd/atomdns
RUN go mod download
RUN CGO_ENABLED=0 go build

FROM ${BASE}
COPY --from=build /tmp/dns/cmd/atomdns/atomdns /atomdns
WORKDIR /
EXPOSE 53 53/udp 443
ENTRYPOINT ["/atomdns"]

The compose file a longer, and includes the Conffile for atomdns to use:

# This compose file configures a atomdns that can be used as the cluster-dns' server.
services:
  atomdns:
    image: registry.science.ru.nl/cncz/sys/image/atomdns:latest
    pull_policy: always
    configs:
      - source: atomdns_config
        target: /etc/Conffile
        mode: 0444
    command:
      - /etc/Conffile
    x-ports:
      - ctrl.u.science.ru.nl:443/https

    deploy:
      replicas: 1

    volumes:
      - atomdns_data:/var/lib/atomdns

volumes:
  atomdns_data:
    labels:
      nl.ru.science.backup: "kopia"

configs:
  atomdns_config:
    content: |
      {
          log
          root /var/lib/atomdns
          dns {
              addr [::]:53
          }
      }

      u.science.ru.nl {
          uncloud u.science.ru.nl.db {
              addr [::]:443
          }
          dbsqlite u.science.ru.nl.db
      }

      # add example.org for debugging purposes
      example.org {
        whoami
        log
      }

Also deploy that. Note because the database (sqlite) isn’t shared, this can only run on a single machine. Also the example.org zone is there to help in debugging.

When running the above you can send DNS queries to your cluster and they will get answers

% uc ls
NAME            MODE         REPLICAS   IMAGE                                                  ENDPOINTS
atomdns         replicated   1          registry.science.ru.nl/cncz/sys/image/atomdns:latest   https://ctrl.u.science.ru.nl → :443
caddy           global       2          registry.science.ru.nl/cncz/sys/image/caddy:latest     (custom Caddy config)
debug           replicated   1          registry.science.ru.nl/cncz/sys/image/debug:latest

And dig @uncloud1.vm.science.ru.nl a whoami.example.org returns an answer.