Jiří Meitner | IT služby Linux | Kladno | Česko

Docker without iptables with NFTables install Matomo Analytics

Docker a nftables: jak na instalace Matomo Analytics

Moderní linuxový firewall nftables si získal srdce mnoha administrátorů. Postupně nahrazuje zastaralý a těžkopádný iptables. Přestože je nftables s námi už řadu let – psalo se o něm v seriálech na Rootu od Petra Krčmáře a objevoval se i na LinuxDays – stále ne všechno software se bez iptables obejde. Typickým příkladem je Docker. Pokud do Googlu zadáte dotaz docker nftables, nenajdete jasnou a praktickou odpověď. V tomto článku vám proto ukážu, jak si trvale vygenerovat pravidla pro vaši Docker aplikaci.

Pro tyto účely použiju Matomo – moderní OpenSource náhradu Google Analytics. Pravidla se naučíme uložit do samostatného souboru a includovat k našim existujícím nftables pravidlům. Následně zakážeme dockeru vytvářet iptables pravidla. Až budete instalovat další aplikaci, budete už umět si ty pravidla odchytit a použít.

Hlavní výhody moderního firewallu nftables ve zkratce:

  1. Sjednocení: Nahrazuje a sjednocuje iptables, ip6tables, arptables a ebtables do jediného konzistentního frameworku.
  2. Moderní a flexibilní syntaxe: Umožňuje psát přehlednější a efektivnější pravidla, včetně pokročilých datových struktur (sady, mapy, proměnné).
  3. Atomické operace: Změny pravidel se aplikují okamžitě a konzistentně, bez rizika dočasného nezabezpečeného stavu.
  4. Vyšší výkon a efektivita: Optimalizováno pro moderní linuxová jádra, což vede k nižší systémové zátěži a lepšímu využití prostředků.
  5. Snadnější správa komplexních pravidel: Díky pokročilým funkcím je správa rozsáhlých a složitějších firewallových konfigurací přehlednější a méně náchylná k chybám.

Co se stane, když používáte iptables s dockerem a restartujete nftables

Je možné používat iptables i nftables současně, protože dnešní iptables ve skutečnosti běží nad nftables pomocí kompatibilitní vrstvy (iptables-nft), která rozumí původním iptables příkazům.

Na serveru, kde neběží jen Docker, ale řešíte i celkovou bezpečnost, není dobré míchat nftables a iptables. Pokud totiž dojde k restartu nftables (například při ručním doplnění pravidla nebo po restartu přes systemd), Docker v tu chvíli přestane fungovat. Jeho pravidla totiž zmizí a znovu se zavedou teprve po restartu služby docker.service. Důvod je jednoduchý – při každém restartu nftables se nejprve kompletně vymažou všechna existující pravidla.

#!/usr/sbin/nft -f

flush rulesetCode language: JavaScript (javascript)

Problém není ani tak v kompatibilitní vrstvě iptables-nft, ale spíše v pohodlnosti administrátorů. Ti si totiž zvykli, že Docker při startu automaticky nastaví potřebná pravidla ve firewallu a vše tak zdánlivě funguje bez jejich zásahu.

Pravidla, která Docker nastaví při svém startu, nemusí po delší době běhu serveru fungovat správně. Pokud dojde k restartu nftables, kontejnery si často přestanou mezi sebou rozumět a aplikace v nich přestanou fungovat.

Jak lze situaci s dockerem a nftables řešit?

Docker při svém startu vytváří iptables pravidla (pokud mu to výslovně nezakážeme v /etc/docker/daemon.json) a zavádí je do systému. Součástí tohoto procesu je i vytváření bridge rozhraní, která dostávají náhodné názvy a při každém dalším spuštění se mohou jmenovat jinak. Řešením je používat pevně určené názvy rozhraní, například pomocí docker-compose.yml. Tím zajistíme, že Docker vygeneruje konzistentní pravidla, která můžeme znovu použít i po restartu serveru, aniž bychom se museli spoléhat na jejich nové generování.

Iptables pravidla, která Docker vygeneruje, lze díky kompatibilní vrstvě iptables-nft zobrazit jako nativní nftables pravidla a uložit je do samostatného souboru. Tento soubor pak můžeme jednoduše includovat do naší hlavní konfigurace nftables. Díky tomu máme jistotu, že i po restartu nftables se firewall nastaví přesně podle námi schválené a uložené konfigurace.

Krok za krokem, jak probíhá instalace Matomo do dockeru s nftables

  1. Nachystáte si reverzní proxy. Doporučuji Caddy.
  2. Dočasně povolíte iptables v `/etc/docker/daemon.json` (`“iptables“: true`).
  3. Vytvoříte `docker-compose.yml` pro Matomo se stabilním jménem bridge (např. `br-matomo`).
  4. Nainstalujte Matomo (např. spuštěním docker-compose a následným webovým průvodcem instalace)
  5. Zastavíte ostatní kontejnery a necháte jen `matomo` a `matomo-db`.
  6. Do `/etc/nftables.conf` přidáte `include „/etc/nftables.d/*.conf“`, vytvoříte adresář `/etc/nftables.d` a prázdný soubor `/etc/nftables.d/3_docker_matomo.conf`.
  7. Vyresetujete nftables pravidla (`nft flush ruleset`) – pozor, tím na chvíli vypnete firewall.
  8. Restartujete Docker.
  9. Přesměrujete aktuální pravidla (která vytvořil Docker při restartu) do souboru:
    `nft list ruleset > /etc/nftables.d/3_docker_matomo.conf`.
    Doporučeno projít a případně pročistit.
  10. Restartujete nftables (`systemctl restart nftables`).
  11. Ověříte `nft list ruleset`, že máte jak své původní pravidla, tak i nově přidaná pravidla pro Matomo.
  12. V `/etc/docker/daemon.json` zakážete iptables (`“iptables“: false`) a restartujete Docker (`systemctl restart docker`).
  13. Restartujete server a ověříte, že vše funguje.

Reverzní proxy (Caddy) a proč nevázat kontejnery na 0.0.0.0

Pro produkční nasazení Matoma (a obecně webových aplikací v Dockeru) je vhodné dát před aplikaci reverzní proxy. Doporučuji Caddy: automaticky řeší HTTPS (Let’s Encrypt), HTTP/2/3 a bezpečnostní hlavičky. Zároveň je dobrá praxe nevázat kontejnery na všechna rozhraní (0.0.0.0), ale na konkrétní IP – konfigurace je předvídatelnější a bezpečnější.

Většina ukázek docker-compose mapuje jen porty:
ports:
– „8080:80“

Tím Docker naslouchá na všech rozhraních (0.0.0.0). Já preferuji vázat službu na konkrétní IP (např. 127.0.0.2):
ports:
– target: 80
published: 8080
protocol: tcp
mode: host
host_ip: „127.0.0.2“

Výhody:

  • služba není „otevřená všude“, ale jen na zvolené IP,
  • reverzní proxy ví přesně, kam směrovat (žádné překvapení po restartu),
  • firewall/nftables pravidla se píší a udržují lépe (deterministická adresa i porty).
  • je možné používat různé aplikace používající stejné porty na různých IP adresách

Důležité: IP adresa a port v Caddyfile musí odpovídat tomu, co máte v docker-compose. Pokud v compose běží Matomo na 127.0.0.2:8080, stejnou adresu použijte v reverse_proxy.

Poznámky:

  • DNS A/AAAA záznamy směřujte na veřejnou IP serveru, Caddy si certifikát obstará sám. Nepoužívejte subdoménu se jménem matomo, stats, analytics, piwik a pod. Nepoužívejte CNAME záznam. Používejte subdoménu, na webu, na kterém měříte, ale z jejího názvu by nemělo nic nasvědčovat tomu, že slouží k měření.
  • Pokud používáte IPv6, můžete analogicky vázat i na vyhrazenou ::1/lo adresu (nebo jinou lokální v6), případně v Caddy reverse_proxy použít http://[::1]:8080.
  • Kdo nechce bind na 127.0.0.2, může zvolit jinou lokální adresu (např. 127.0.0.10); důležité je mít shodu compose ↔ Caddy ↔ nftables pravidla.
  • Adresu si před použitím opingujte, ať víte, že tam fakt je. Přes ip a s nemusí být vidět, a přesto lze tyto adresy použít.

/etc/caddy/Caddyfile

Příklad konfigurace webserveru Caddy:

# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.
{
	servers {
		protocols h3 h2 h2c h1
		trusted_proxies static private_ranges
		log_credentials
	}
}

(logging) {
	log {
		level DEBUG
		output file /var/log/caddy/access.log {
			roll_size 1gb
			roll_keep 1
			roll_keep_for 720h
		}
	}
}

(canonical_domain) {
	@canonicalPath {
		not query
	}
	rewrite @canonicalPath {http.request.uri.path}
	header {
		Link "<https://{args[0]}{http.request.uri.path}>; rel=\"canonical\""
	}
}

matomo.example.com {
	encode zstd gzip
	# Backend běží na HTTP/80 → vynucený HTTP transport
	reverse_proxy /* http://127.0.0.2:8080 {
		transport http {
		}
	}
	import logging
	header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
	header Content-Security-Policy "upgrade-insecure-requests;"
	header Referrer-Policy "no-referrer-when-downgrade"
	header Access-Control-Allow-Origin "https://{host}"
	import canonical_domain "{host}"
}

m1.example1.com m1.example2.cz {
	encode zstd gzip
	# Backend běží na HTTP/80 → vynucený HTTP transport
	reverse_proxy /* http://127.0.0.2:8080 {
		transport http {
		}
	}
	import logging
	header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
	header Content-Security-Policy "upgrade-insecure-requests;"
	header Referrer-Policy "no-referrer-when-downgrade"
	header Access-Control-Allow-Origin "https://{host}"
	import canonical_domain "{host}"
	@js path /matomo.js /m.js
	handle @js {
		uri replace /m.js /matomo.js
		reverse_proxy 127.0.0.2:8080
	}

	@hit path /matomo.php /t
	handle @hit {
		uri replace /t /matomo.php
		reverse_proxy 127.0.0.2:8080
	}

	# cokoliv jiného nevracej (zabrání UI přes tracker host)
	respond 404
}
Code language: PHP (php)

/etc/docker/daemon.json

Slouží pro konfiguraci Dockeru, například v něm nastavujeme, zda má použít iptables pro generování pravidel. Dočasně povolíme iptables => vygenerujeme pravidla => zakážeme. Takto vypadá soubor na jednom z mých serverů:

{
  "experimental": true,
  "iptables": false,
  "ip6tables": false,
  "dns": ["1.1.1.1"],
  "dns-search": []
}
Code language: JSON / JSON with Comments (json)

~/docker/matomo/docker-compose.yml

Soubor docker-compose.yml zajišťuje, aby se Matomo při startu spustilo správně. Podle potřeby si v něm upravte například hesla a další parametry.

services:
  db:
    image: mariadb:11
    container_name: matomo-db
    command: >-
      --max-allowed-packet=64M
      --innodb-log-file-size=512M
      --skip-name-resolve
    restart: unless-stopped
    volumes:
      - db_data:/var/lib/mysql
      - ./my.cnf:/etc/mysql/conf.d/my.cnf:ro
    tmpfs:
      - /tmp:size=1g,noexec,nosuid,nodev
    ulimits:
      nofile:
        soft: 1048576
        hard: 1048576
    environment:
      MYSQL_ROOT_PASSWORD: KrutoPrisneHeslo123
      MYSQL_DATABASE: matomo
      MYSQL_USER: matomo
      MYSQL_PASSWORD: NejkrutesjiZnejkrutejsich357
    networks: [net]
    dns:
      - 1.1.1.1
      - 8.8.8.8

  matomo:
    image: matomo:latest
    container_name: matomo
    restart: unless-stopped
    ports:
      - target: 80
        published: 8080
        protocol: tcp
        mode: host
        host_ip: "127.0.0.2"
    environment:
      MATOMO_DATABASE_HOST: db
      MATOMO_DATABASE_ADAPTER: mysql
      MATOMO_DATABASE_TABLES_PREFIX: matomo_
      MATOMO_DATABASE_USERNAME: matomo
      MATOMO_DATABASE_PASSWORD: NejkrutesjiZnejkrutejsich357
      MATOMO_DATABASE_DBNAME: matomo
    volumes:
      - matomo_data:/var/www/html
    networks: [net]
    dns:
      - 1.1.1.1
      - 8.8.8.8

volumes:
  db_data:
    name: matomo_db_data
  matomo_data:
    name: matomo_matomo_data

networks:
  net:
    driver: bridge
    driver_opts:
      com.docker.network.bridge.name: br-matomo   # stálý název
    ipam:
      driver: default
      config:
        - subnet: 172.30.0.0/16
          gateway: 172.30.0.1

Code language: YAML (yaml)

/etc/nftables.d/3_docker_matomo.conf

Do tohoto souboru uložíte nftables pravidla pro Matomo. Pokud použijete můj docker-compose.yml, měla by fungovat beze změny.

table ip nat {
	chain DOCKER {
		iifname "docker0" counter  return
		iifname "br-matomo" counter  return
		ip daddr 127.0.0.2 iifname != "br-matomo" tcp dport 8080 counter  dnat to 172.30.0.3:80
	}

	chain PREROUTING {
		type nat hook prerouting priority dstnat; policy accept;
		fib daddr type local counter  jump DOCKER
	}

	chain OUTPUT {
		type nat hook output priority dstnat; policy accept;
		ip daddr != 127.0.0.0/8 fib daddr type local counter  jump DOCKER
	}

	chain POSTROUTING {
		type nat hook postrouting priority srcnat; policy accept;
		ip saddr 172.17.0.0/16 oifname != "docker0" counter  masquerade
		ip saddr 172.30.0.0/16 oifname != "br-matomo" counter  masquerade
	}
}
table ip filter {
	chain DOCKER {
		ip daddr 172.30.0.3 iifname != "br-matomo" oifname "br-matomo" tcp dport 80 counter  accept
		iifname != "br-matomo" oifname "br-matomo" counter  drop
		iifname != "docker0" oifname "docker0" counter  drop
	}

	chain DOCKER-FORWARD {
		counter  jump DOCKER-CT
		counter  jump DOCKER-ISOLATION-STAGE-1
		counter  jump DOCKER-BRIDGE
		iifname "br-matomo" counter  accept
		iifname "docker0" counter  accept
	}

	chain DOCKER-BRIDGE {
		oifname "br-matomo" counter  jump DOCKER
		oifname "docker0" counter  jump DOCKER
	}

	chain DOCKER-CT {
		oifname "br-matomo" ct state related,established counter  accept
		oifname "docker0" ct state related,established counter  accept
	}

	chain DOCKER-ISOLATION-STAGE-1 {
		iifname "br-matomo" oifname != "br-matomo" counter  jump DOCKER-ISOLATION-STAGE-2
		iifname "docker0" oifname != "docker0" counter  jump DOCKER-ISOLATION-STAGE-2
	}

	chain DOCKER-ISOLATION-STAGE-2 {
		oifname "docker0" counter  drop
		oifname "br-matomo" counter  drop
	}

	chain FORWARD {
		type filter hook forward priority filter; policy accept;
		counter  jump DOCKER-USER
		counter  jump DOCKER-FORWARD
	}

	chain DOCKER-USER {
	}
}
table ip raw {
	chain PREROUTING {
		type filter hook prerouting priority raw; policy accept;
		ip daddr 172.30.0.2 iifname != "br-matomo" counter  drop
		ip daddr 172.30.0.3 iifname != "br-matomo" counter  drop
		ip daddr 127.0.0.2 iifname != "lo" tcp dport 8080 counter  drop
	}
}

Code language: JavaScript (javascript)

Líbil se vám článek? Sdílejte ho dál.
Potřebujete Matomo na svůj server? Ozvěte se.
Hledáte správce, který se vám bude starat o servery? Klidně mě kontaktujte.

Summary
Docker a nftables: instalace Matomo Analytics
Article Name
Docker a nftables: instalace Matomo Analytics
Description
Podrobný návod, jak provozovat Docker s nftables místo iptables. Ukázka instalace Matomo Analytics, konfigurace firewallu a řešení typických problémů.
Author
Publisher Name
ITisLove.cz
Publisher Logo

Comments

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

This site uses Akismet to reduce spam. Learn how your comment data is processed.