AdguardTeam / AdGuardHome

Network-wide ads & trackers blocking DNS server
https://adguard.com/adguard-home.html
GNU General Public License v3.0
24.9k stars 1.79k forks source link

Docker Service deployment (Stack) #515

Open prologic opened 5 years ago

prologic commented 5 years ago

I'd like to see support for running, updating and managing AdGuardHome via docker and more specifically a Docker Swarm set-up using docker stack deploy (//Docker Swarm mode is far far far simpler to use and operationalize than k8s!//).

Somewhat related is #448

I'm happy to contribute to this but it would also require #377 to be resolved with up-to-date upstream images built in some kind of CI. Ideally using Docker Hub's own Automated Build would be nice. #514 would help a lot with this.

A (//not yet tested//) adguardhome.yml (//Docker Stackfile//): would look like this:

version: "3.4"

services:
  adguardhome:
    image: adguardteam/adguardhome:latest
    ports:
      - "53:53/udp"
    networks:
      - bridge
    volumes:
      - adguardhomedata:/data
    deploy:
      replicas: 1

networks:
  bridge:
    external: true

volumes:
  adguardhomedata:
    driver: local
prologic commented 5 years ago

Your upgrade process would then look like this:

$ docker service update -d --image adguardteam/adguardhome:latest adguardhome_adguardhome
prologic commented 5 years ago

I now have this running at my home network in my Docker Swarm mode cluster.

Here is the config:

version: "3.4"

services:
  adguardhome:
    image: r.mydomain.com/myuser/adguardhome
    command: -c /data/config.yml
    volumes:
      - adguardhomedata:/data
    networks:
      - bridge
      - traefik
    ports:
      - "53:53/udp"
    deploy:
      placement:
        constraints:
          - "node.hostname == my-master-node"
      labels:
        - "traefik.enable=true"
        - "traefik.port=3000"
        - "traefik.backend=adguardhome"
        - "traefik.docker.network=traefik"
        - "traefik.frontend.rule=Host:adguardhome.mydomain.com"
      replicas: 1

networks:
  bridge:
    external: true
  traefik:
    external: true

volumes:
  adguardhomedata:
    driver: local

However I wasn't able to use Docker Configs for this because the AdGuardHome daemon insists on rewriting the config file? It seems the last-updated timestamp of each filter source is stored in teh config file. This is pretty bad design :/ Can we rethink this? Mutable data like this should well umm go to somewhere mutable like the /data volume?

ameshkov commented 5 years ago

Mutable data like this should well umm go to somewhere mutable like the /data volume?

Yep, that makes sense indeed.

ameshkov commented 5 years ago

It seems the last-updated timestamp of each filter source is stored in teh config file

Resolved in v0.93 btw.

wmandra commented 3 years ago

I have successfully managed to get AdGuardHome running replicated on a docker swarm cluster.

Caveats:

First, create macvlan config on each node that will run AdGuardHome: Node 1: docker network create --config-only --subnet 192.168.1.0/24 --gateway 192.168.1.1 -o parent=eth0 --ip-range 192.168.1.53/32 dns-config

Node 2: docker network create --config-only --subnet 192.168.1.0/24 --gateway 192.168.1.1 -o parent=eth0 --ip-range 192.168.1.153/32 dns-config

In the above, 192.168.1.53 and 192.168.1.153 are the IP Addresses that AdGuardHome will be available on.

On a manager node create a swarm scoped macvlan network: docker network create -d macvlan --scope swarm --attachable --config-from dns-config dns

Add a label to each node in the swarm that will run an instance of AdGuardHome:

docker node update --label-add dns=true node01
docker node update --label-add dns=true node02

Create shared data volumes for each instance. In my case I have a gluster volume shared across all nodes and mounted to /mnt/data:

mkdir /mnt/data/adguard/ns1/conf
mkdir /mnt/data/adguard/ns1/work
mkdir /mnt/data/adguard/ns2/conf
mkdir /mnt/data/adguard/ns2/work

If you want to have more than 2 replicas running, just create additional directories as ns3, ns4...

Docker compose file (adguard.yml):

version: '3.8'

services:
  ns:
    image: adguard/adguardhome:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "853:853/tcp"
      - "3000:3000/tcp"
    volumes:
      - type: bind
        source: "/mnt/data/adguard/ns{{.Task.Slot}}/conf"
        target: /opt/adguardhome/conf
      - type: bind
        source: "/mnt/data/adguard/ns{{.Task.Slot}}/work"
        target: /opt/adguardhome/work
    networks:
      - dns
    cap_add:
      - NET_ADMIN
    dns:
      - 9.9.9.10
      - 149.112.112.10
    hostname: "ns{{.Task.Slot}}"
    deploy:
      mode: replicated
      replicas: 2
      placement:
        max_replicas_per_node: 1
        constraints: [node.labels.dns == true]
      labels:
        - docker.group=dns

networks:
  dns:
    external: true

Important bits in the above yaml file:

Deploy the stack: docker stack deploy adguard -c ./adguard.yml

In my deployment I left the "bind_port" in the AdGuardHome.yaml configuration file set to 80, then enabled encryption and set the HTTPS port to 3000. I don't use DNS-over-HTTPS internally just 53/udp so this makes the admin portal available on 3000 with proper certificate included in the config.

Note, for the container instance to be able to communicate with outside work via macvlan network the parent interface of the host must be in promiscuous mode ip link set eth0 promisc on and the default iptables forward rule must be changed from DROP to ACCEPT iptables -P FORWARD ACCEPT.

Finally, if the host needs to use the container for its dns you will also have to create a macvlan shim interface on the host and add a static route:

ip link add macvlan-shim link eth0 type macvlan mode bridge
ip addr add {MAC_VLAN_IP}/32 dev macvlan-shim
ip link set macvlan-shim up
ip route add X.X.X.X/32 dev macvlan-shim

This can easily be added to a script and run on boot via a simple systemd unit file.

Hope this helps.