PowerDNS的部署与使用

PowerDNS简介

PowerDNS成立于上世纪90年代后期,是开源DNS的主要供应商

目前主要产品有Authoritative Server、Recursor和Dnsdist产品,目前是完全开源的

PowerDNS也有商业化的产品支持,其用户和客户包括了全球领先的电信服务供应商、大型集成商及财富500强软件公司,在斯堪的纳维亚、德国和荷兰,PowerDNS是域名服务器软件的第一大供应商。

与bind不同,powerDNS的权威和递归服务是分开的服务

作为权威域名服务器

安装

#这里以mysql作为后端存储服务,所以这里安装的是pdns-backend-mysql
yum install pdns pdns-backend-mysql

#也可以指定版本安装,例如,要安装4.6版本的
yum install -y epel-release yum-plugin-priorities &&
curl -o /etc/yum.repos.d/powerdns-auth-46.repo https://repo.powerdns.com/repo-files/el-auth-46.repo &&
yum install pdns -y

可以通过 yum list pdns-backend* | grep 4.6 来查看可用的4.6版本的后端所有存储驱动

安装存储数据库

这里以mysql作为例子,为了方便,这里使用docker-compose镜像部署

version: '3'
services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 111111
      #MYSQL_ALLOW_EMPTY_PASSWORD: yes
      MYSQL_DATABASE: powerdns
      MYSQL_USER: powerdns
      MYSQL_PASSWORD: powerdns
    volumes:
    - ./db:/var/lib/mysql
    - ./etc/my.cnf:/etc/my.cnf
    - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime
    ports:
    - 3306:3306
    command:
      #为了能远程登录root,所以修改了验证模块,生产中建不开启
      - --default-authentication-plugin=mysql_native_password

docker-compose.yaml文件同目录下的新建etc目录,用来存放mysql配置文件

配置文件内容

vim ./etc/my.cnf

[mysqld]
port        = 3306
#socket      = /tmp/mysql.sock
datadir = /var/lib/mysql/var
skip-external-locking
key_buffer_size = 32M
max_allowed_packet = 1M
table_open_cache = 128
sort_buffer_size = 768K
net_buffer_length = 8K
read_buffer_size = 768K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M
thread_cache_size = 16
tmp_table_size = 32M
performance_schema_max_table_instances = 1000

explicit_defaults_for_timestamp = true
#skip-networking
max_connections = 500
max_connect_errors = 100
open_files_limit = 65535

log-bin=mysql-bin
binlog_format=mixed
server-id   = 1
expire_logs_days = 10
early-plugin-load = ""

default_storage_engine = InnoDB
innodb_file_per_table = 1
innodb_data_home_dir = /var/lib/mysql/var
innodb_data_file_path = ibdata1:10M:autoextend
innodb_log_group_home_dir = /var/lib/mysql/var
innodb_buffer_pool_size = 128M
innodb_log_file_size = 32M
innodb_log_buffer_size = 8M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip_name_resolve=on

部署容器

docker-compose up -d

新建相关表

mysql -u powerdns -ppowerdns
use powerdns

CREATE TABLE domains (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255) NOT NULL,
  master                VARCHAR(128) DEFAULT NULL,
  last_check            INT DEFAULT NULL,
  type                  VARCHAR(6) NOT NULL,
  notified_serial       INT DEFAULT NULL,
  account               VARCHAR(40) DEFAULT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB;
 
CREATE UNIQUE INDEX name_index ON domains(name);
 
CREATE TABLE records (
  id                    BIGINT AUTO_INCREMENT,
  domain_id             INT DEFAULT NULL,
  name                  VARCHAR(255) DEFAULT NULL,
  type                  VARCHAR(10) DEFAULT NULL,
  content               BLOB(64000) DEFAULT NULL,
  ttl                   INT DEFAULT NULL,
  prio                  INT DEFAULT NULL,
  change_date           INT DEFAULT NULL,
  disabled              TINYINT(1) DEFAULT 0,
  ordername            VARCHAR(255) BINARY DEFAULT NULL,
  auth                  TINYINT(1) DEFAULT 1,
  PRIMARY KEY (id)
) Engine=InnoDB;
 
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername);
 
CREATE TABLE supermasters (
  ip                    VARCHAR(64) NOT NULL,
  nameserver            VARCHAR(255) NOT NULL,
  account               VARCHAR(40) NOT NULL,
  PRIMARY KEY (ip, nameserver)
) Engine=InnoDB;
 
CREATE TABLE comments (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  name                  VARCHAR(255) NOT NULL,
  type                  VARCHAR(10) NOT NULL,
  modified_at           INT NOT NULL,
  account               VARCHAR(40) NOT NULL,
  comment               BLOB(64000) NOT NULL,
  PRIMARY KEY (id)
) Engine=InnoDB;
 
CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
 
CREATE TABLE domainmetadata (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  kind                  VARCHAR(32),
  content               TEXT,
  PRIMARY KEY (id)
) Engine=InnoDB;
 
CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind);
 
CREATE TABLE cryptokeys (
  id                    INT AUTO_INCREMENT,
  domain_id             INT NOT NULL,
  flags                 INT NOT NULL,
  active                BOOL,
  content               TEXT,
  PRIMARY KEY(id)
) Engine=InnoDB;
 
CREATE INDEX domainidindex ON cryptokeys(domain_id);
 
CREATE TABLE tsigkeys (
  id                    INT AUTO_INCREMENT,
  name                  VARCHAR(255),
  algorithm             VARCHAR(50),
  secret                VARCHAR(255),
  PRIMARY KEY (id)
) Engine=InnoDB;
 
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
 
flush privileges;

pdns配置

vim /etc/pdns/pdns.conf

#设置数据库相关的
launch=gmysql
gmysql-host=10.0.0.10
gmysql-port=3306
gmysql-dbname=powerdns
#管理数据库的相关用户和密码
gmysql-user=powerdns
gmysql-password=powerdns
#访问权限
allow-axfr-ips=0.0.0.0/0,::1
allow-dnsupdate-from=0.0.0.0/0,::1
allow-notify-from=0.0.0.0/0,::/0
allow-unsigned-notify=yes
also-notify=10.0.0.10
#daemon 启动
daemon=yes
disable-axfr=no
guardian=no
#是否master
master=yes
#是否slave
slave=no
#启动权限
setgid=pdns
setuid=pdns
#打印日志
log-dns-details=yes
log-dns-queries=no
loglevel=6
log-timestamp=yes
logging-facility=0
#开启api
api=yes
api-key=dnsadmin1syn
#启动webserver 监控
webserver=yes
webserver-address=0.0.0.0
webserver-allow-from=0.0.0.0/0
webserver-port=8081
#监听的地址端口
local-address=0.0.0.0
local-port=54
#记录query 日志
query-logging=yes

启服务

systemctl start pnds

测试

通过api来访问

#需要带上配置文件中设置的api-key
curl -v -H 'X-API-Key: dnsadmin1syn' http://10.0.0.10:8081/api/v1/servers
* About to connect() to 10.0.0.10 port 8081 (#0)
*   Trying 10.0.0.10...
* Connected to 10.0.0.10 (10.0.0.10) port 8081 (#0)
> GET /api/v1/servers HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.0.0.10:8081
> Accept: */*
> X-API-Key: dnsadmin1syn
> 
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Connection: close
< Content-Length: 250
< Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'
< Content-Type: application/json
< Server: PowerDNS/4.1.14
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Permitted-Cross-Domain-Policies: none
< X-Xss-Protection: 1; mode=block
< 
* Closing connection 0
[{"config_url": "/api/v1/servers/localhost/config{/config_setting}", "daemon_type": "authoritative", "id": "localhost", "type": "Server", "url": "/api/v1/servers/localhost", "version": "4.1.14", "zones_url": "/api/v1/servers/localhost/zones{/zone}"}]

部署powerdns-admin

用来通过web界面,来管理pdns

通过docker来部署

#这里的key就是pdns配置文件中设置的api-key
docker run -it -d \
    -e SECRET_KEY='dnsadmin1syn' \
    -v pda-data:/data \
    -p 9191:80 \
    ngoduykhanh/powerdns-admin:latest

然后直接访问http://[IP]:9191

然后注册用户,第一个注册的用户默认为管理员

进去之后,需要添加pdns的服务

连接成功的会在左边菜单栏显示PDNS,没配置成功的是无法显示的

新建域名。创建一条记录

然后测试是否生效

dig www.abc.com @10.0.0.10 -p 54

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7 <<>> www.abc.com @10.0.0.10 -p 54
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 38613
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;www.abc.com.			IN	A

;; ANSWER SECTION:
www.abc.com.		60	IN	A	1.1.1.1

;; Query time: 0 msec
;; SERVER: 10.0.0.10#54(10.0.0.10)
;; WHEN: 一 4月 24 12:10:54 CST 2023
;; MSG SIZE  rcvd: 56

已经可以正常解析

但是发现无法递归解析

这里以解析百度为例

dig www.baidu.com @10.0.0.10 -p 54

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7 <<>> www.baidu.com @10.0.0.10 -p 54
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 29256
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1680
;; QUESTION SECTION:
;www.baidu.com.			IN	A

;; Query time: 2 msec
;; SERVER: 10.0.0.10#54(10.0.0.10)
;; WHEN: 一 4月 24 12:12:02 CST 2023
;; MSG SIZE  rcvd: 42

可以看到是被拒绝的,这是因为,pdns服务是不提供递归和转发的,如果需要有递归和转发的功能,就需要启 pdns-recursor 服务

部署pdns-recursor递归服务

安装

yum install -y pdns-recursor

修改配置文件

vim /etc/pdns-recursor/recursor.conf
#允许访问的地址
allow-from=0.0.0.0/0
#开启日志
disable-syslog=yes
#关闭dnssec 转发有用
dnssec=off
#forward-zones是只转发,不递归 少用
#forward-zones=hexug.com=127.0.0.1:54
#forward-zones-recurse是找不到就递归,找的到就转发,用逗号分隔  这里是将pdns中定义的域名,转发到pdns服务的54端口上
forward-zones-recurse=hexug.com=127.0.0.1:54,abc.com=127.0.0.1:54,.=114.114.114.114
#绑定在所有端口商
local-address=0.0.0.0
local-port=53
log-common-errors=yes
security-poll-suffix=
setgid=pdns-recursor
setuid=pdns-recursor

启服务

systemctl restart pdns-recursor

注意,这里可能会报错,因为一般本地会有服务在占用53端口,需要先kill掉

再解析测试,这个时候解析的端口就不再是54端口,而是53端口

解析本地定义的域名

dig www.abc.com @10.0.0.10 -p 53

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7 <<>> www.abc.com @10.0.0.10 -p 53
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 49462
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.abc.com.			IN	A

;; ANSWER SECTION:
www.abc.com.		52	IN	A	1.1.1.1

;; Query time: 0 msec
;; SERVER: 10.0.0.10#53(10.0.0.10)
;; WHEN: 一 4月 24 12:21:10 CST 2023
;; MSG SIZE  rcvd: 56

解析其他互联网域名

dig www.baidu.com @10.0.0.10 -p 53

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7 <<>> www.baidu.com @10.0.0.10 -p 53
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52182
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.baidu.com.			IN	A

;; ANSWER SECTION:
www.baidu.com.		125	IN	CNAME	www.a.shifen.com.
www.a.shifen.com.	125	IN	A	14.119.104.254
www.a.shifen.com.	125	IN	A	14.119.104.189

;; Query time: 28 msec
;; SERVER: 10.0.0.10#53(10.0.0.10)
;; WHEN: 一 4月 24 12:22:03 CST 2023
;; MSG SIZE  rcvd: 101

就可以正常解析

WEB&API

前文提到递归服务器并没有图形化的配置界面,但仍然提供了web服务及api服务,可以通过向配置文件中添加以下内容来完成web服务的开启:

修改配置文件 /etc/pdns-recursor/recursor.conf

添加下面这段,开启web服务

webserver=yes
webserver-allow-from=0.0.0.0/0
webserver-address=0.0.0.0
webserver-port=8082

#开启webserver,允许所有ip访问,web服务监听IP为所有接口IP,web服务器监听端口号8082

web开启后,则可通过 http://<服务器IP>:8082 访问web服务,可看到当下服务器的工作状态:

创建 /var/zones 文件夹,用于放置配置文件,并将所有者授权给pdns-recursor:

mkdir /var/zones
chown pdns-recursor:pdns-recursor /var/zones

修改递归服务器配置文件 /etc/pdns-recursor/recursor.conf ,加入以下内容

api-key=abcd.1234
api-config-dir=/var/zones/
include-dir=/var/zones
#加入api-key,api配置文件位置/var/zones,将/var/zones文件夹包含进配置文件
#api部分就是使用web的端口,就是上面的定义的8082

修改完成后,需要对服务进行重启。

使用以下命令进行验证(若有(23) Failed writing body报错,请先 yum -y install jq

curl -v -H 'X-API-Key: abcd.1234' http://127.0.0.1:8082/api/v1/servers/localhost | jq .

返回以下结果,则表示API启用成功:

<---此处省略若干字符--->
{
  "config_url": "/api/v1/servers/localhost/config{/config_setting}",
  "daemon_type": "recursor",
  "id": "localhost",
  "type": "Server",
  "url": "/api/v1/servers/localhost",
  "version": "4.6.0",
  "zones_url": "/api/v1/servers/localhost/zones{/zone}"
}

此时我们添加 http://sina.cn 的解析,到 10.0.0.10:54 ,并且允许其递归:

数据部分

{
    "kind": "Forwarded",
    "name": "sina.com.",
    "servers": ["10.0.0.10:54"],
    #转发的同时是否允许递归
    "recursion_desired": true,
    "type": "zone"
}

添加完成后,到递归服务器的 /var/zones 文件夹下会发现一个 zone-sina.cn..conf 的文件,查看内容,显示如下:

cat /var/zones/zone-sina.cn..conf 
# Generated by pdns-recursor REST API, DO NOT EDIT
forward-zones-recurse+=sina.com.=10.0.0.10:54

再次尝试将 http://qq.com 的解析递归只 10.1.1.1 ,并且不允许递归:

body部分

{
    "kind": "Forwarded",
    "name": "qq.com.",
    "servers": ["10.1.1.1:53"],
    "recursion_desired": false,
    "type": "zone"
}

虽然显示的是422,并且返回的消息是无法找到域名

{
    "error": "Could not find domain 'qq.com.'"
}

但是,查看 /var/zones 目录下,还是多了一个 zone-qq.com..conf 文件,说明创建已经成功,文件内容如下:

cat /var/zones/zone-qq.com..conf
# Generated by pdns-recursor REST API, DO NOT EDIT
forward-zones+=qq.com.=10.1.1.1:53

但是解析的时候还是解析出了公网的,说明,其实没有生效,而且重启pdns-recursor会报错

就是没有定义forward-zones字段

所以,需要在配置文件中添加上

vim /etc/pdns-recursor/recursor.conf

forward-zones=

不用加任何值,只要有这个字段就行

然后再添加

会显示已存在,这个时候,只需要重启服务就好了

然后再添加一个别的,正确添加的返回结果如下

将以上内容转换成Python代码,如下:

import requests , json

def set_forwarded_zone(recursor,domainname,dns,dnsport=53,boolean = False):
    url = 'http://%s:8082/api/v1/servers/localhost/zones'%recursor
    headers = {'X-API-Key':'abcd.1234'}
    data = {
        "kind":"Forwarded",
        "name":domainname+'.',
        "servers":["%s:%s"%(dns,dnsport)],
        "recursion_desired":boolean,
        "type":"zone"
    }
    data = json.dumps(data)
    req = requests.post(url=url,headers=headers,data=data)
    print(req.text)

添加 http://sina.cn 的解析,到 10.5.22.11:53 ,并且允许其递归:

set_forwarded_zone('10.0.0.10','sina.cn','10.5.22.11',53,True)

执行后返回结果:

{"id": "sina.cn.", "kind": "Forwarded", "name": "sina.cn.", "records": [], "recursion_desired": true, "servers": ["10.5.22.11:53"], "url": "/api/v1/servers/localhost/zones/sina.cn."}

http://qq.com的解析递归到10.1.1.1,并且不允许递归,执行:

set_forwarded_zone('10.0.0.10','qq.com','10.1.1.1',53)

执行后返回结果:

{"id": "qq.com.", "kind": "Forwarded", "name": "qq.com.", "records": [], "recursion_desired": false, "servers": ["10.1.1.1:53"], "url": "/api/v1/servers/localhost/zones/qq.com."}

以上配置生效后,预期效果:10.5.22.11和10.1.1.1并非是http://sina.cn和http://qq.com的权威服务器,由于http://sina.cn允许递归,理论上可以得到解析结果,而http://qq.com由于不允许递归,则无法解析。

进行验证:

dig +short @10.0.0.10 sina.cn
dig +short @10.0.0.10 qq.com

结果都无解析结果返回,推测,可能是由于10.5.22.11和10.1.1.1都不是dns服务器导致,在此,将转发目标都指向一台dns服务器10.0.0.10:54,再次尝试。

修改 zone-sina.cn..conf和zone-qq.com..conf文件,重启递归服务再次尝试:

dig +short @10.0.0.10 sina.com
66.102.251.24
dig +short @10.0.0.10 qq.com

所以,递归转发到目标服务器,目标服务器必须是一台DNS,允许转发的选项才会生效。

接口部分

都是需要加上一个Header

X-API-Key: xxxx #就是pdns-recursor的配置文件中配置的api-key

创建转发域名

POST /api/v1/servers/localhost/zones

#data部分
{
    "kind": "Forwarded",
    "name": "qq.com.",
    "servers": ["10.0.0.10:54"],
    "recursion_desired": false,
    "type": "zone"
}

获取所有域名配置

GET /api/v1/servers/localhost/zones

获取指定域名的配置

GET /api/v1/servers/localhost/zones/<zone-name>

#例如
GET http://10.0.0.10:8082/api/v1/servers/localhost/zones/qq.com

删除指定域名

DELETE /api/v1/servers/localhost/zones/<zone-name>

DNS分配器(Dnsdist)

dnsdist 是一个DNS的负载均衡器。

它本身并不提供解析服务,而是将DNS流量路由到指定服务器,为用户提供最佳解析性能,同时也可实现应用流量分摊。

安装服务

dnsdist安装非常简单,以CentOS/RH为例,以下命令即可完成安装:

yum install -y epel-release
yum install -y dnsdist

在前台运行

安装dnsdist后,开始试验的最快方法是在前台启动它:

dnsdist -l 127.0.0.1:5300 9.9.9.9 2620:fe::fe 2620:fe::9

这将使 dnsdist 侦听 IP 地址 127.0.0.1、端口 5300 并将所有查询转发到列出的三个 IP 地址,并采用合理的平衡策略。

简单配置

这里只对dnsdist的使用作简单介绍。

新建/etc/dnsdist/dnsdist.conf文件,写入以下内容:

注意:配置文件的语法是lua的语法,所以注释是 --

--设置本地监听地址为0.0.0.0的5300端口
setLocal('0.0.0.0:5300')

--允许列表内的IP来进行解析
setACL({'192.0.2.0/28', '10.194.0.0/16'}) 

--新增服务器114.114.114.114,qps限制为10
newServer({address="114.114.114.114", qps=10})

--新增服务器10.5.253.14,qps限制为10
newServer({address="10.5.253.14", qps=10})

--新增服务器10.5.253.15:5300,并使用本机的10.210.11.18与其进行通信
newServer({address="10.5.253.15:5300", source="10.210.11.18"})

newServer({address="2001:db8::4", name="dns1", qps=10})
newServer({address="[2001:db8::3]:5300", qps=10})

--设置服务负载方式为轮询
setServerPolicy(roundrobin)

注意,请确认newServer中的服务器均为可用的dns服务器,为能够更加直观看到效果,建议它们解析到的某一个域名为不同的IP地址。

以上配置加入后,可通过命令设置自启并立即启动dnsdist,更多的配置请参考配置指南:官方文档

#前台手动启动
dnsdist -C dnsdist.conf --local=0.0.0.0:5300
Marking downstream 114.114.114.114:53 as 'up'
Marking downstream 10.5.253.14:53 as 'up'
Marking downstream 10.5.253.15:5300 as 'up'
Marking downstream [2001:db8::4]:53 as 'up'
Marking downstream [2001:db8::3]:5300 as 'up'
Listening on 0.0.0.0:5300
>

#systemd管理启动
systemctl enable dnsdist --now

启动后,可直接在本机重复使用以下命令进行测试:

dig +short @10.210.11.18 -p 5300 s.......t.com.cn  +nocookie

dnsdist具有强大功能,支持Lua脚本

请注意,dnsdist 将我们放在上面的提示中,我们可以在其中获得一些统计信息:

> showServers()
#   Address                   State     Qps    Qlim Ord Wt    Queries   Drops Drate   Lat Pools
0   114.114.114.114:53           up     0.0       1   1  1          1       0   0.0   0.0
1   10.5.253.14:53               up     0.0       1   1  1          0       0   0.0   0.0
2   [2001:db8::3]:5300           up     0.0      10   1  1          0       0   0.0   0.0
3   [2001:db8::4]:53             up     0.0      10   1  1          0       0   0.0   0.0
4   10.5.253.15:5300             up     0.0       0   1  1          0       0   0.0   0.0

showServers() 通常是您登录控制台时首先使用的命令之一。

在这里我们也可以看到我们的配置。配置了 5 个下游服务器,其中前 4 个有 QPS 限制(分别为每秒 1、1、10 和 10 次查询)

最后一个服务器没有限制,我们可以轻松测试:

for a in {0..1000}; do dig powerdns.com @127.0.0.1 -p 5300 +noall +nocookie > /dev/null; done

> showServers()
#   Address                   State     Qps    Qlim Ord Wt    Queries   Drops Drate   Lat Pools
0   114.114.114.114:53           up     1.0       1   1  1          7       0   0.0   1.6
1   10.5.253.14:53               up     1.0       1   1  1          6       0   0.0   0.6
2   [2001:db8::3]:5300           up    10.3      10   1  1         64       0   0.0   2.4
3   [2001:db8::4]:53             up    10.3      10   1  1         63       0   0.0   2.4
4   10.5.253.15:5300             up   125.8       0   1  1        671       0   0.0   0.4
All                                   145.0                       811       0

请注意,前 4 台服务器都被限制在接近其配置的 QPS,而我们的最后一台服务器占用了大部分流量。没有查询被删除,所有服务器都保持运行。

更改服务器设置

服务器来自 showServers() 编号, getServer() 用于获取此Server对象以对其进行操作。

要强制关闭服务器,请尝试Server:setDown()

> getServer(0):setDown()
> showServers()
#   Address                   State     Qps    Qlim Ord Wt    Queries   Drops Drate   Lat Pools
0   114.114.114.114:53           up     0.0       1   1  1          8       0   0.0   0.0
...

DOWN全部大写表示它被强制关闭。
小写down意味着 dnsdist 本身已经断定服务器已关闭。
同样,Server:setUp() 强制服务器启动,并将 Server:setAuto() 其返回到默认的可用性探测。

要更改服务器的 QPS,请使用 Server:setQPS()

> getServer(0):setQPS(1000)

对解析记录自定义

查看所有自定义规则

> showRules()
#   Name                             Matches Rule                                                     Action
0                                          2 qname in www.aaa.com.                                    spoof in 2001:db8::1 
1                                          4 (Src: ::/0)                                              to pool ipv6root
2                                          0 (Src: 0.0.0.0/0)                                         to pool ipv6root

或者带有uuid的

> showRules({showUUIDs=true})
#   Name                           UUID                                   Cr. Order   Matches Rule                                                     Action
0                                  d60df1ea-0c1c-4b22-b2b8-9a5c24c8e19c           2         2 qname in www.aaa.com.                                    spoof in 2001:db8::1 
1                                  19c8012e-4850-4217-8d2c-d1fc211dd51c           0         4 (Src: ::/0)                                              to pool ipv6root
2                                  b1a7525f-db5f-4593-ad77-9615e94a01b7           1         0 (Src: 0.0.0.0/0)                                         to pool ipv6root

添加解析规则

> addAction({"www.bbb.com."}, SpoofAction("1.2.3.4"))

查看

> showRules({showUUIDs=true})
#   Name                           UUID                                   Cr. Order   Matches Rule                                                     Action
0                                  d60df1ea-0c1c-4b22-b2b8-9a5c24c8e19c           2         2 qname in www.aaa.com.                                    spoof in 2001:db8::1 
1                                  19c8012e-4850-4217-8d2c-d1fc211dd51c           0         4 (Src: ::/0)                                              to pool ipv6root
2                                  b1a7525f-db5f-4593-ad77-9615e94a01b7           1         0 (Src: 0.0.0.0/0)                                         to pool ipv6root
3                                  1324d473-1b76-46b6-884f-bc59ef6c2c99           3         0 qname in www.bbb.com.                                    spoof in 1.2.3.4

发现是在最下面,因为上面有两条规则是兜底的

1                                  19c8012e-4850-4217-8d2c-d1fc211dd51c           0         4 (Src: ::/0)                                              to pool ipv6root
2                                  b1a7525f-db5f-4593-ad77-9615e94a01b7           1         0 (Src: 0.0.0.0/0)                                         to pool ipv6root

包揽了所有的解析,如果解析规则在这两条下面,就永远也无法匹配到,所有需要移到最上面去

> mvRuleToTop()
> showRules({showUUIDs=true})
#   Name                           UUID                                   Cr. Order   Matches Rule                                                     Action
0                                  1324d473-1b76-46b6-884f-bc59ef6c2c99           3         0 qname in www.bbb.com.                                    spoof in 1.2.3.4 
1                                  d60df1ea-0c1c-4b22-b2b8-9a5c24c8e19c           2         2 qname in www.aaa.com.                                    spoof in 2001:db8::1 
2                                  19c8012e-4850-4217-8d2c-d1fc211dd51c           0         4 (Src: ::/0)                                              to pool ipv6root
3                                  b1a7525f-db5f-4593-ad77-9615e94a01b7           1         0 (Src: 0.0.0.0/0)                                         to pool ipv6root

此时再解析 www.bbb.com 就会是我们设置的ip

$ dig www.bbb.com @192.168.140.71

; <<>> DiG 9.18.18-0ubuntu0.22.04.2-Ubuntu <<>> www.bbb.com @192.168.140.71
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20696
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.bbb.com.			IN	A

;; ANSWER SECTION:
www.bbb.com.		60	IN	A	1.2.3.4

;; Query time: 0 msec
;; SERVER: 192.168.140.71#53(192.168.140.71) (UDP)
;; WHEN: Wed Jul 24 17:02:59 CST 2024
;; MSG SIZE  rcvd: 56

删除规则

> rmRule("1324d473-1b76-46b6-884f-bc59ef6c2c99")

就删除了uuid=1324d473-1b76-46b6-884f-bc59ef6c2c99的规则

限制访问

默认情况下,dnsdist 侦听127.0.0.1(不是::1!)端口 53。

要侦听不同的地址,请使用 -l 命令行选项(对于在前台进行测试很有用),或在配置文件中使用 setLocal()and :addLocal()

setLocal('192.0.2.53')      -- 监听192.0.2.53, port 53
addLocal('[::1]:5300')      -- 同时也监听再 ::1, port 5300

在处理数据包之前,它们必须通过 ACL,这有助于默认为RFC 1918私有 IP 空间。这使我们无法轻易成为开放的 DNS 解析器。

将网络范围添加到ACL是使用 setACL()addACL() 函数完成的:

setACL({'192.0.2.0/28', '2001:db8:1::/56'}) -- 设置acl 仅允许使用解析器的网段
addACL('2001:db8:2::/56')                   -- 添加网段 扩展acl  允许这个网段的使用

dnsdist 旨在几乎立即(重新)启动。

但是为了防止更改配置时停机,控制台可用于实时配置。

在控制台上发出delta()将打印自启动以来对配置所做的更改:

> delta()
-- Wed Feb 22 2017 11:31:44 CET
addLocal('127.0.0.1:5301', false)
-- Wed Feb 22 2017 12:03:48 CET
addACL('192.0.2.1/8')
-- Wed Feb 22 2017 12:05:51 CET
addACL('2001:db8::1')

使用 dnsdist 控制台

dnsdist 可以通过加密的 tcp 连接公开命令行控制台,以控制它、调试 DNS 问题和检索统计信息。

控制台可以启用controlSocket():

controlSocket('0.0.0.0:5199')

不建议在未启用加密的情况下启用控制台。请注意,加密需要在启用 libsodium 支持的情况下构建 dnsdist。

一旦你有一个启用了 libsodium 的 dnsdist,启用加密的第一步是生成一个密钥 makeKey()

./dnsdist -l 127.0.0.1:5300
[..]
> makeKey()
setKey("ENCODED KEY")

然后将生成的 setKey() 行添加到您的 dnsdist 配置文件,以及 controlSocket()

controlSocket('0.0.0.0:5199')    -- 在本机的所有IP的5199端口上侦听客户端连接
setKey("ENCODED KEY")            -- 控制台的key

现在您可以运行 dnsdist -c 来连接到控制台。

这会使dnsdist读取其配置文件并使用 controlSocket()setKey()语句来建立与服务器的连接。

如果要通过网络连接,请使用相同的两条语句创建一个配置文件并运行 dnsdist -C /path/to/configfile -c

或者,可以在客户端命令行上指定地址和密钥:

dnsdist -k "ENCODED KEY" -c 192.0.2.53:5199

注意:这会将密钥泄露到您的 shell 的历史记录中,因此不推荐这样做。

从 1.3.0 开始,dnsdist 支持使用 ACL 限制哪些客户端可以连接到控制台:

controlSocket('192.0.2.53:5199')
setConsoleACL('192.0.2.0/24')

默认值为“127.0.0.1”,将控制台的使用限制为本地用户。

请确保在使用 addConsoleACL()setConsoleACL() 允许从远程客户端连接之前启用加密。

即使控制台仅限于本地用户使用,仍强烈建议使用加密,以防止未经授权的本地用户连接到控制台。

范例配置文件

dnsdist.conf 文件

-- 设置监听在ipv4和ipv6的5199端口上
controlSocket("192.168.1.71")
controlSocket("[2001:1:2:2::71]")

-- 允许192.168.1.0/24网络和所有ipv6地址的客户端能够远程访问到dnsdist的console
setConsoleACL({"192.168.1.0/24","::/0"})
setKey("MxxxxxxxxxxxxxxxxxxxxxNWNb+Se2eXU5+Bb74=")

-- 设置dns服务的监听地址
addLocal("[2001:1:2:2::71]:53")
addLocal("192.168.1.71:53")
-- 设置允许访问的客户端ip
setACL({"0.0.0.0/0","::/0"})

-- 导入其他lua配置
package.path = package.path .. ";/etc/dnsdist/?.lua"
require "custom"

其他lua配置

custom.lua

-- 创建缓存
ipcache = newPacketCache(50000000, { 86400, 0, 60, 120, false })


-- 设置规则 当匹配到什么地址的时候,请求发往哪个服务器池
addAction(AndRule({makeRule("::/0"),}), PoolAction("pool1"))
addAction(AndRule({makeRule("0.0.0.0/0"),}), PoolAction("pool1"))


-- 设置提供服务的服务器池 这些服务器是后端真实提供解析的服务器
newServer({name="rec-1",address="[2001:1:2:3::100]:53",sockets=16,weight=1,useClientSubnet=true,order=1, pool={"pool1",},})
newServer({name="rec-2",address="[2001:1:2:3::200]:53",sockets=16,weight=1,useClientSubnet=true,order=1, pool={"pool1",},})

getPool("pool1"):setCache(ipcache)

setUDPMultipleMessagesVectorSize(1024)
setRingBuffersSize(50000, 200)
setWHashedPertubation(1234)
setServerPolicy(whashed)
setECSSourcePrefixV6(32)
setSecurityPollSuffix("")

服务器分发策略

使用 setServerPolicy(<POLICY>) 来设置

默认自带的策略

  • leastOutstanding 默认策略,按照查询最少的来匹配
    简单说就是每次都分配查询次数最少的服务器
    • 按照查询最少的来分配
    • 遇到查询一样少的,按照设置的顺序来匹配
    • 在同一条设置的时候,选择具有最低测量延迟的那个(超过该服务器回答的最后 128 个查询的平均值)
  • firstAvailable
    匹配第一个 未超过 其 QPS 限制的可用服务器,按递增的“顺序”排序。
    如果所有服务器都超过其 QPS 限制,则根据 leastOutstanding 策略选择服务器。
    目前这是唯一使用 QPS 限制的策略。
  • wrandom
    随机分配
    也可以根据权重来分配,权重是在newServer()中,有个参数 weight 来配置权重,不配置此参数,则默认权重为1
  • whashed
    whashed是一个类似的加权策略,但将具有相同哈希值的问题分配给相同的服务器,从而实现更好的缓存集中度(“粘性查询”)。
    当前的散列算法是基于查询的 qname。
    可以使用 setWHashedPertubation(value) 来明确哈希的对象,使不同的实例能得到相同的hash
  • chashed
    chashed是一致的散列分布策略。具有相同哈希值的相同问题将分发到相同的服务器。
    但与whashed策略不同的是,这种分布会随着时间的推移保持一致。
    添加或删除服务器只会重新映射一小部分查询。
  • roundrobin
    不加区别地将每个查询发送到下一个启动的服务器。如果所有服务器都关闭,该策略仍会默认选择一台服务器。
    设置 setRoundRobinFailOnNoServer()true 将更改此行为。

还可以通过LUA自定义策略,这里不做介绍,可以参考 https://dnsdist.org/guides/serverselection.html#lua-server-policies

其他ui管理pdns

处理上面用容器部署的powerdns-admin容器服务

还有基于LNMP的powerdnsadmin服务,这里的唯一麻烦的点就是需要部署LNMP

可以借助 一键LNMP脚本 或者 根据另一篇博客来自己搭建LNMP服务

需要注意的是PHP版本一定要大于8.1

下载包 https://www.poweradmin.org/

解压到nginx的html文件中,然后访问 http://[IP][:port]/install 就可以安装

posted @ 2023-04-24 12:31  厚礼蝎  阅读(3638)  评论(0编辑  收藏  举报