Architektura
Co se dozvíte v článku
Knot Resolver se od ostatních open-source implementací DNS resolverů liší převážně svou modularitou, a to v několika aspektech. Kompletní Knot Resolver je tvořen několika komponentami: kresd (samostatný resolvující démon), cache databáze s DNS záznamy a cache garbage collector (proces starající se o periodické čištění záznamů v cache). Resolvující démon je navíc jednovláknový proces, což znamená, že je velmi jednoduchý a přímočarý, pokud ale chceme plně využít zdroje procesoru, musí být spuštěno více jeho samostatných instancí. Jejich počet zpravidla odpovídá počtu procesorových jader.
Jednotlivé základní i doplňující funkce resolvujícího démona (kresd) jsou zajišťovány samostatnými moduly, které je možné připojovat a odpojovat a mít tak „štíhlý“ resolver přizpůsobený vlastním potřebám bez nadbytečných funkcí. Další neobvyklostí byla až dosud konfigurace, která se píše v plnohodnotném programovacím jazyce Lua. Společně s možností psaní vlastních modulů v C nebo Lua to přináší takřka neomezené možnosti, jak Knot Resolver a jeho funkce doplňovat nebo konfigurovat.
S tím, jakou má Knot Resolver architekturu, se váže hned několik problémů:
- Jednovláknové procesy neumožňují tak jednoduchou škálovatelnost na vícejádrových strojích. Musí se spustit více procesů, o které je pak třeba se jednotlivě starat. K tomu je zpravidla využíván systemd, který ale není možné nasadit v dockerových kontejnerech nebo některých linuxových distribucích (např. Turris OS a OpenWrt).
- Jednotlivé moduly je před použitím třeba manuálně načíst, jinak jejich funkce nebudou dostupné. U některých modulů je také potřeba zajistit správné pořadí jejich načtení.
- Konfigurace v jazyce Lua neumožňuje jakýmkoli způsobem ověřit její správnost před spuštěním v resolveru. V lepším případě se chyba projeví hned při spuštění, v horším případě může resolver běžet bez problému a chyba se projeví až při pokusu o aplikaci špatné konfigurace nebo spuštění chybného kódu.
V nové nadcházející verzi vše zmíněné zůstává, ale přibývá nová komponenta zvaná manažer (manager), která se snaží nevýhody tohoto přístupu zredukovat a usnadnit interakci uživatele s Knot Resolverem.
Manažer a řízení procesů
Manažer je nová komponenta napsaná v Pythonu, která spravuje zbylé části resolveru na základě konfigurace:
- Dává pokyn ke spuštění nebo zastavení ostatních procesů. K tomu používá supervisord, který funguje i v dockerovém kontejneru a linuxových distribucích nepodporujících systemd. Díky manažerovi je tedy mnohem snadnější spravovat procesy resolveru na systémech s absencí systemd.
- Používá deklarativní formu konfigurace ve formátu YAML, která je výrazně přehlednější než Lua. Navíc je možné ji validovat a odchytit tak chyby v konfiguraci ještě před spuštěním resolveru. Na pozadí se pak postará o správné načtení potřebných modulů v případě použití jejich funkcí.
- Poskytuje HTTP API, které umožňuje změnu konfigurace za běhu nebo načtení agregovaných metrik ze všech procesů kresd.
V Knot Resolveru existují dvě různé řídicí struktury, viz následující diagram. Sémanticky manažer řídí všechny zbylé části resolveru. Manažer však zároveň není kořenem procesní hierarchie, tím je supervisord, který je na vrcholu procesního stromu a řídí všechny procesy.
Procesní hierarchie lehce komplikuje proceduru spuštění resolveru. Je možné si toho všimnout i při náhledu do logů hned po spuštění.
Co se děje v při studeném startu:
- Spustí se manažer a přečte si deklarativní konfiguraci. Pokud je konfigurace validní, vygeneruje konfiguraci pro supervisord.
- Poté se spustí supervisord a dojde k nahrazení běžícího procesu manažera novým procesem supervisord.
- Supervisord načte svou konfiguraci a spustí novou instanci manažera. Nově spuštěný manažer běží jako podřízený proces pod supervisord, což je žádoucí stav.
- Manažer si znovu načte konfigurační soubor a na základě toho vytvoří konfiguraci pro zbylé procesy resolveru.
- Nakonec vydá manažer pokyn pro supervisord, aby spustil vyžadovaný počet procesů, které si následně načtou pro ně vytvořenou konfiguraci.
Díky tomu, že procesy řídí supervisord, je možné vyřešit selhání jednotlivých procesů resolveru bez zásahu uživatele. Vše mimo supervisord se při selhání automaticky restartuje. Pokud se ze selhání není možné zotavit automaticky, procesy se zastaví a nezanechají za sebou žádné smetí.
Zpracování konfigurace
Následující diagram detailněji popisuje průběh načtení konfiguračního souboru při studeném startu nebo při konfiguraci resolveru za běhu pomocí HTTP API. K jednodušší komunikaci s HTTP API je možné použít CLI nástroj kresctl
. Konfigurační soubor je jediný autoritativní zdroj konfigurace a na rozdíl od konfigurování prostřednictvím HTTP API je perzistentní napříč restarty resolveru nebo i celého stroje. Omezením konfigurace přes HTTP API je také to, že z ní není možné generovat konfiguraci pro supervisord, která se sestavuje pouze při studeném startu. HTTP API proto neumožňuje konfigurovat naprosto vše, co umí konfigurační soubor.
Po načtení ze souboru nebo přijetí přes HTTP API je konfigurace naparsována z řetězce YAML nebo JSON do jednotného slovníkového formátu. Následně proběhne validace dat oproti vzorovému schématu a jejich normalizace. Dojde například k přiřazení výchozích hodnot nebo přiřazení hodnot na základě kontextu v konfiguračních datech. Je to možné díky vrstvení vzorového schématu konfigurace, které mezi jednotlivými vrstvami umožňuje transformace na základě kontextu z vrstvy předchozí nebo získání nové hodnoty ze systému. Tím vzniknou nová konfigurační data, která jsou validována oproti vrstvě následující. Uživatel tedy pracuje s lehce odlišnou strukturou konfigurace než resolver interně po validaci.
Po úspěšné validaci dojde při studeném startu k vygenerování konfigurace pro supervisord, v ostatních případech mimo studený start dojde pouze k vytvoření konfigurace pro jednotlivé procesy resolveru.
Instalace
Kompletní dokumentaci k testovací verzi lze nalézt na knot.pages.nic.cz/knot-resolver. Jak nainstalovat testovací verzi pro vybrané linuxové distribuce je popsané v kapitole Getting Started.
Spuštění
Naše upstreamové balíčky pro jednotlivé linuxové distribuce jsou standardně dodávány s integrací pro systemd. V nové verzi již není nutné spouštět jednotlivé instance kresd (kresd@1, kresd@2, …), stačí pouze spustit manažera pomocí nové služby systemd.
$ systemctl start knot-resolver # stop, reload, restart
Počet instancí kresd, kterým má manažer zajistit spuštění, se určuje číselnou hodnotou v konfiguračním souboru pod novým názvem workers
. Hodnotu je také možné nastavit na auto
, kdy manažer nastaví počet workerů automaticky na základě počtu jader procesoru.
# /etc/knot-resolver/config.yaml workers: 4
Knot Resolver standardně využívá logování přes systemd, kam je možné se podívat pomocí služby systemd-journald, například při neúspěšném startu resolveru.
$ journalctl -eu knot-resolver
Konfigurace
Konfigurace je nově deklarativní ve formátu YAML nebo JSON (pro HTTP API). Je možné ji před použitím validovat a zachytit tak případné chyby ještě před spuštěním Knot Resolveru.
Validace
Validace konfigurace vždy probíhá automaticky při spuštění Knot Resolveru. V případě vadné konfigurace skončí jeho spuštění chybou.
Konfiguraci je možné validovat nezávisle na spuštění resolveru pomocí CLI nástroje kresctl
.
$ kresctl validate /etc/knot-resolver/config.yaml
Pokud validace selže, objeví se detailní chybová hláška s informacemi, kde a co je špatně. Stejný výpis se objeví v logu Knot Resolveru, pokud selže validace konfigurace při startu.
Configuration validation errors detected: [/network/listen[0]/port] value 65536 is higher than the maximum 65535 [/logging/level] degugg does not match any of the expected values (crit, ..., debug)
Dynamická změna konfigurace
Změnit konfiguraci resolveru za běhu je možné dvěma způsoby. Knot Resolver je schopný dynamicky změnit vlastní konfiguraci znovunačtením konfiguračního souboru nebo prostřednictvím HTTP API.
Po úpravě konfiguračního souboru stačí následně na službu zavolat reload.
$ systemctl reload knot-resolver
K zaslání nové konfigurace na HTTP API je opět možné využít CLI nástrojekresctl
nebo například standardní nástroj curl.
$ kresctl config set /workers 8
Při standardním použití sikresctl
sám zjistí konfiguraci HTTP API z konfiguračního souboru. Mimo operace s konfigurací je možné prostřednictvím HTTP API získat metriky resolveru nebo kompletní JSON schéma konfigurace. Vše je detailněji popsáno v dokumentační sekci management.
HTTP API je ve výchozím nastavení nakonfigurované, aby poslouchalo na unixovém socketu. API je také možné nakonfigurovat na jedno ze síťových rozhraní a port.
# /etc/knot-resolver/config.yaml
management:
interface: 127.0.0.1@5000
# nebo použijte místo rozhraní unix-socket
# unix-socket: /my/new/socket.sock
Obdobná změna konfigurace s pomocícurl
nástroje bude vypadat následovně.
$ curl -H 'Content-Type: application/json' \ -X PUT -d '8' http://127.0.0.1:5000/v1/config/workers
Rekonfigurace probíhá následujícím způsobem:
- Nová konfigurace získaná ze souboru nebo přes HTTP API je validována.
- Manažer zkontroluje, zda v nové konfiguraci nejsou nastavení, která by vyžadovala kompletní restart resolveru včetně manažera a vytvoření nové konfigurace supervisord. Pokud změny v konfiguraci nevyhovují, operace tímto končí chybovou hláškou a změnu konfigurace je potřeba provést pomocí restartu celého resolveru pomocí systemd.
- Manažer vytvoří novou konfiguraci pro jednotlivé procesy a s touto konfigurací spustí takzvaný proces „canary“, díky kterému se zjistí případné chyby.
- Pokud v procesu „canary“ vše funguje jak má, následuje spuštění vyžadovaného počtu procesů s novou konfigurací.
- Nově spuštěné procesy v daný moment nahradí původní již běžící a tím je rekonfigurace dokončena bez výpadku služby.
Příklady konfigurace
První věc, kterou budete pravděpodobně chtít nakonfigurovat, jsou síťová rozhraní pro poslech. Následující příklad instruuje resolver, aby přijímal standardní nezašifrované DNS dotazy na adresách přidělených na rozhraní localhost. Zabezpečené DNS lze nastavit pomocí protokolů DNS-over-TLS nebo DNS-over-HTTPS. Nastavení nestandardního portu pro protokol je možné explicitně určit pomocí parametru port
.
YAML také umožňuje vytvářet proměnné (&interfaces
) a obsah následně replikovat v dalších částech konfigurace (*interfaces
). Díky tomu není v příkladu třeba opakovaně vypisovat síťová rozhraní.
# /etc/knot-resolver/config.yaml network: listen: # Nezabezpečené DNS na portu 53 (default). - interface: &interfaces - 127.0.0.1 - "::1" # Pro IPv6 adresy a jiné řetězce začínající # dvojtečkou je potřeba použít uvozovky. # DNS-over-TLS na portu 853 (default). - interface: *interfaces kind: dot # DNS-over-HTTPS (port 443 je default). - interface: *interfaces kind: doh2 # Port je také možné nastavit explicitně. port: 5000
Dalším výrazným zlepšením nejen v konfiguraci, ale v chování resolveru obecně, je změna přístupu k vyhodnocování a aplikaci pravidel policy. Deklarativní přístup v konfiguraci zajišťuje, že je vždy vybráno pravidlo, které přicházejícímu dotazu odpovídá nejlépe. Starý přístup v předchozích verzích vybral první pravidlo, které odpovídalo dotazu. Bylo proto třeba dbát velké opatrnosti při určování pořadí pravidel, jinak se často nechtěným způsobem vzájemně „zastiňovala“.
U pravidel pro třídění uživatelů na základě zdrojové podsítě bude mít vždy přednost menší odpovídající podsíť před větší. To je možné vidět v následujícím příkladu. Jedním pravidlem jsou uživatelé ze všech podsítí odmítnuti a dalšími pravidly s menšími podsítěmi jsou povoleni.
# /etc/knot-resolver/config.yaml views: # Podsítě, které jsou povoleny. - subnets: [ 10.0.10.0/24, 127.0.0.1, "::1" ] answer: allow # Podsíť, pro kterou jsou přiřazena další pravidla # pomocí štítků (tags). - subnets: [ 192.168.1.0/24 ] tags: [ malware, localnames ] # Vše ostatní je odmítnuto. - subnets: [ 0.0.0.0/0, "::/0" ] answer: refused
Jednotlivá pravidla je možné mezi sebou kombinovat. K tomu slouží štítky (tags), které stačí přiřadit k pravidlům, které chceme zkombinovat. Kombinace různých pravidel se chovají výrazně předvídatelněji než dříve. Je tak možné filtrovat uživatele na základě podsítě, ze které dotaz přichází, a pomocí tagu přiřadit pro tuto podsíť další pravidla zpracování dotazu. Mohou to být například úpravy v lokálních DNS záznamech, blokovací seznamy a další.
# /etc/knot-resolver/config.yaml local-data: # Místo pro statické DNS záznamy. records: | www.google.com CNAME forcesafesearch.google.com rpz: # Malware seznam pro blokování. - file: /tmp/malware.rpz # Je použito pouze pro "malware" štítek (tag). # Například v kombinaci s podsítěmi ve "views". tags: [ malware ]
# /etc/knot-resolver/config.yaml local-data: # Statické páry domén a adres. addresses: a1.example.com: 2001:db8::1 a2.example.org: - 192.0.2.2 - 192.0.2.3 - 2001:db8::4 # Import souboru ve formát "/etc/hosts". addresses-files: - /etc/hosts
Deklarativní přístup dále umožnil pokrýt další části použití v oblasti přeposílání dotazů, kde je nově možné přeposlání na více míst i rámci jednoho dotazu. Cílem dotazu pak může být i autoritativní server. Přeposílání nyní nerozbije CNAME a resolver dotaz vyřeší správným způsobem.
# /etc/knot-resolver/config.yaml forward: # Veškeré DNS dotazy budou přeposlány na ODVR servery. - subtree: '.' servers: - address: - 2001:148f:fffe::1 - 193.17.47.1 # Komunikace je zabezpečena pomocí TLS. transport: tls hostname: odvr.nic.cz
# /etc/knot-resolver/config.yaml forward: # Přeposlání dotazu pro interní doménu. - subtree: internal.example.com servers: [ 10.0.0.53 ] options: # Cílem je interní autoritativní DNS server. authoritative: true dnssec: false
Vyzkoušejte a dejte vědět
Na závěr bych rád poděkoval všem, kteří si nový Knot Resolver vyzkoušeli nebo teprve vyzkoušejí. Více o nové verzi naleznete v dokumentaci. Případnou zpětnou vazbu nám můžete poskytnout nejlépe v GitLabu nebo zaslat na e-mailovou adresu projektu.
Pokud vše půjde podle plánu, na první oficiální vydání se můžete těšit začátkem příštího roku s označením verze 6.1.0.
(Původně vyšlo na blogu CZ.NIC, autorem obrázků je Aleš Mrázek.)