Files
my-vault/01_Projects/Infrastructure/VPS/DNS.md
T

649 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
### Powerdns
ns1
hk2.chans.xyz
with docker compose:
好的,按你要的思路来:**仍然用“官方 schema 自动建表”的 Compose 方案**PostgreSQL 16 + PowerDNS Authoritative),**另外提供一个“手动恢复数据”的脚本**,支持恢复纯 SQL dump 和 `pg_dump -Fc` 自定义格式 dump。
---
# 目录结构(建议)
```
pdns/
├─ docker-compose.yml
├─ auth/
│ └─ pdns.conf
├─ db-init/
│ └─ 01-pdns-schema.sql # 官方 gpgsql schema(见下文获取方式)
├─ backup/
│ └─ pdns.dump # 你的备份文件(.sql 或 -Fc)
└─ scripts/
└─ restore-pdns.sh # 手动恢复脚本
```
## 1) 获取 PowerDNS 官方 PostgreSQL schema
> **重要**:请下载与你将使用的 `powerdns/pdns-auth-49` **相同小版本**的 schema(例如 4.9.x 对应的 tag),保存为 `db-init/01-pdns-schema.sql`。
> (官方路径通常在 `modules/gpgsqlbackend/schema.pgsql.sql` 对应版本的 tag 下。)
---
## 2) `docker-compose.yml`(保持“官方 schema 建表”的方案)
```yaml
version: "3.9"
services:
db:
image: postgres:16
container_name: pdns-db
environment:
POSTGRES_DB: powerdns
POSTGRES_USER: powerdns
POSTGRES_PASSWORD: ${PDNS_DB_PASSWORD:-changeme_db}
volumes:
- dbdata:/var/lib/postgresql/data
- ./db-init:/docker-entrypoint-initdb.d:ro # 首启时自动导入 schema
- ./backup:/backup:ro # 供手动恢复脚本读取 dump
healthcheck:
test: ["CMD-SHELL", "pg_isready -U powerdns -d powerdns"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
networks: [dnsnet]
auth:
# 固定到你要用的 4.9.x 小版本,例如 4.9.4
image: powerdns/pdns-auth-49:4.9.4
container_name: pdns-auth
depends_on:
db:
condition: service_healthy
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081" # REST API / 内置 webserver
volumes:
- ./auth/pdns.conf:/etc/powerdns/pdns.conf:ro
environment:
PDNS_API_KEY: ${PDNS_API_KEY:-changeme_api}
healthcheck:
test: ["CMD", "sh", "-c",
"wget -qO- --header='X-API-Key: ${PDNS_API_KEY:-changeme_api}' http://127.0.0.1:8081/api/v1/servers/localhost | grep -q 'version'"]
interval: 10s
timeout: 5s
retries: 12
restart: unless-stopped
networks: [dnsnet]
networks:
dnsnet:
driver: bridge
volumes:
dbdata: {}
```
## 3) `auth/pdns.conf`PostgreSQL 后端示例)
```ini
local-address=0.0.0.0
local-port=53
launch=gpgsql
gpgsql-host=pdns-db
gpgsql-port=5432
gpgsql-dbname=powerdns
gpgsql-user=powerdns
gpgsql-password=changeme_db
api=yes
api-key=changeme_api
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=127.0.0.1,10.0.0.0/8,192.168.0.0/16
version-string=anonymous
disable-syslog=yes
loglevel=4
```
## 4) 启动
```bash
docker compose up -d
# 等 db 健康检查 OK 后,auth 会启动并可通过 8081 API 访问
```
---
# 手动恢复脚本(支持 .sql 与 -Fc)
`scripts/restore-pdns.sh`:默认**安全模式**是恢复到一个**新数据库**(避免与你现有 schema 冲突),完成后你只需把 `pdns.conf``gpgsql-dbname` 改成新库名并 `docker compose restart auth` 即可。也提供 `--inplace` 选项可“原地覆盖”(会清空原库 `public` 模式)——谨慎使用。
```bash
#!/usr/bin/env bash
set -euo pipefail
# 用法:
# scripts/restore-pdns.sh /absolute/or/relative/path/to/backup/pdns.dump
# 可选: --inplace # 原地覆盖到 powerdns 库(会清空 public schema
#
# 说明:
# - 支持两类 dump:
# 1) 纯 SQL
# 2) pg_dump -Fc 自定义格式
# - 默认行为: 恢复到新库 powerdns_restore_YYYYmmddHHMMSS
# - 容器/数据库参数需与 docker-compose.yml 一致
DB_SVC="db" # Compose 中的服务名
DB_NAME="powerdns"
DB_USER="powerdns"
INPLACE=0
if [[ "${1:-}" == "--inplace" ]]; then
INPLACE=1
shift
fi
if [[ $# -lt 1 ]]; then
echo "Usage: $0 [--inplace] <path-to-dump>"
exit 1
fi
DUMP_PATH="$1"
if [[ ! -f "$DUMP_PATH" ]]; then
echo "Dump file not found: $DUMP_PATH"
exit 1
fi
# 统一让容器内能看到该文件(compose 已挂载 ./backup -> /backup:ro
# 若传入的不是 ./backup 下的文件,临时 cp 进去容器使用
IN_CONTAINER_DUMP=""
if [[ "$DUMP_PATH" == ./backup/* || "$DUMP_PATH" == backup/* ]]; then
# 剥离前缀,映射到 /backup
BN="${DUMP_PATH##*/}"
IN_CONTAINER_DUMP="/backup/${BN}"
else
# 复制到容器临时路径
BN="$(basename "$DUMP_PATH")"
echo "Copying dump into container..."
docker compose cp "$DUMP_PATH" "${DB_SVC}:/tmp/${BN}"
IN_CONTAINER_DUMP="/tmp/${BN}"
fi
# 检测 dump 类型(在容器内使用 'file'
FILE_OUT="$(docker compose exec -T ${DB_SVC} sh -lc "file -b ${IN_CONTAINER_DUMP} || true")"
echo "Detected: ${FILE_OUT}"
IS_FC=0
if echo "$FILE_OUT" | grep -qi 'PostgreSQL custom database dump'; then
IS_FC=1
fi
if [[ $INPLACE -eq 1 ]]; then
echo ">>> INPLACE mode: dropping and recreating schema 'public' in ${DB_NAME}"
docker compose exec -T ${DB_SVC} psql -U "${DB_USER}" -d "${DB_NAME}" -v ON_ERROR_STOP=1 <<'SQL'
DO $$
BEGIN
EXECUTE 'DROP SCHEMA IF EXISTS public CASCADE';
EXECUTE 'CREATE SCHEMA public AUTHORIZATION powerdns';
EXECUTE 'GRANT ALL ON SCHEMA public TO powerdns';
END$$;
SQL
if [[ $IS_FC -eq 1 ]]; then
echo ">>> Restoring (-Fc) into ${DB_NAME} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"pg_restore --clean --if-exists --no-owner -U ${DB_USER} -d ${DB_NAME} ${IN_CONTAINER_DUMP}"
else
echo ">>> Restoring (SQL) into ${DB_NAME} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"psql -v ON_ERROR_STOP=1 -U ${DB_USER} -d ${DB_NAME} -f ${IN_CONTAINER_DUMP}"
fi
echo ">>> Done. INPLACE restore finished for DB: ${DB_NAME}"
else
NEWDB="powerdns_restore_$(date +%Y%m%d%H%M%S)"
echo ">>> Creating new database: ${NEWDB}"
docker compose exec -T ${DB_SVC} sh -lc \
"psql -U ${DB_USER} -d postgres -v ON_ERROR_STOP=1 -c 'CREATE DATABASE ${NEWDB} OWNER ${DB_USER};'"
if [[ $IS_FC -eq 1 ]]; then
echo ">>> Restoring (-Fc) into ${NEWDB} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"pg_restore --clean --if-exists --no-owner -U ${DB_USER} -d ${NEWDB} ${IN_CONTAINER_DUMP}"
else
echo ">>> Restoring (SQL) into ${NEWDB} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"psql -v ON_ERROR_STOP=1 -U ${DB_USER} -d ${NEWDB} -f ${IN_CONTAINER_DUMP}"
fi
echo ">>> Done. Now edit ./auth/pdns.conf:"
echo " gpgsql-dbname=${NEWDB}"
echo ">>> Then restart PDNS:"
echo " docker compose restart auth"
fi
```
给执行权限:
```bash
chmod +x scripts/restore-pdns.sh
```
### 使用示例
1. **默认安全模式(恢复到新库,不影响当前运行):**
```bash
./scripts/restore-pdns.sh ./backup/pdns.dump
# 按提示把 auth/pdns.conf 的 gpgsql-dbname 改为脚本输出的新库名
docker compose restart auth
```
2. **原地覆盖(会清空当前库的 public schema):**
```bash
./scripts/restore-pdns.sh --inplace ./backup/pdns.dump
```
---
## 小结 / 注意点
- **官方 schema 自动建表**:通过 `db-init/01-pdns-schema.sql` 在首次初始化时创建所需表结构,之后你随时可以用上面的脚本**手动恢复**。
- **避免冲突**:若你的 dump 是“包含 schema 的完整备份”,默认脚本会恢复到**新库**,避免与已存在的官方 schema 冲突;你也可以用 `--inplace` 覆盖恢复。
- **版本匹配**:尽量让 dump 的来源 PostgreSQL 版本 ≤ 目标版本(例如从 13/14/15 恢复到 16 一般可行);PowerDNS 版本与 schema 文件要匹配同一小版本。
- **安全回滚**:新库恢复方式便于回滚——只需把 `pdns.conf` 切回旧库名并 `restart auth`
需要我把「下载 schema 的命令」按你的目标 `pdns-auth` 版本具体写死(例如 4.9.4)吗?我可以直接给出一条 `curl` 命令放到 README 里。
## 4.9.8:
#### db init
```
curl -L \
https://raw.githubusercontent.com/PowerDNS/pdns/master/modules/gpgsqlbackend/schema.pgsql.sql \
-o db-init/01-pdns-schema.sql
```
```
docker compose exec -T db \
pg_restore --clean --if-exists --no-owner \
-U pdns -d pdns /backup/pdns.dump
```
```
docker compose exec -e PGPASSWORD=windyboy2006 -T db \
pg_restore --jobs=4 --clean --if-exists --no-owner --no-acl \
-U pdns -d pdns /backup/pdns.dump
```
```
docker compose exec -T db psql -U pdns -d pdns -c "SELECT count(*) FROM domains;"
```
db-init/02-pda.sql
```sql
-- 创建 PowerDNS-Admin 的数据库与用户(与 PDNS 库隔离)
CREATE USER pdnsadmin WITH PASSWORD 'windyboy2006';
CREATE DATABASE pdnsadmin OWNER pdnsadmin ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE pdnsadmin TO pdnsadmin;
```
```shell
docker compose exec -T db psql -U ${PDNS_DB_USER:-pdns} -d postgres -v ON_ERROR_STOP=1 \
-c "DO \$\$BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='pdnsadmin') THEN CREATE ROLE pdnsadmin LOGIN PASSWORD 'changeme_pdapass'; END IF; END\$\$;"
docker compose exec -T db psql -U ${PDNS_DB_USER:-pdns} -d postgres -v ON_ERROR_STOP=1 \
-c "CREATE DATABASE pdnsadmin OWNER pdnsadmin;" || true
```
```shell
docker compose exec -T db psql \
-U "${PDNS_DB_USER:-powerdns}" -d postgres -v ON_ERROR_STOP=1 \
-c "ALTER ROLE \"${PDNSADMIN_DB_USER:-pdns}\" WITH PASSWORD '${PDNSADMIN_DB_PASSWORD}';"
```
```
pda:
image: powerdnsadmin/pda-legacy:latest
container_name: powerdns-admin
depends_on:
db:
condition: service_healthy
auth:
condition: service_started
ports:
- "${PDA_HTTP_PORT:-9191}:80"
environment:
SECRET_KEY: ${PDA_SECRET_KEY:-changeme_pda_secret}
SQLALCHEMY_DATABASE_URI: >-
postgresql://${PDNSADMIN_DB_USER:-pdnsadmin}:${PDNSADMIN_DB_PASSWORD:-changeme_pdapass}@db:5432/${PDNSADMIN_DB:-pdnsadmin}
restart: unless-stopped
networks: [dnsnet]
```
```
docker compose exec -T db psql -U pdns -d postgres -v ON_ERROR_STOP=1
-c "ALTER ROLE pdnsadmin WITH PASSWORD 'windyboy2006';"
```
```
docker compose exec -e PGPASSWORD="$PDNSADMIN_DB_PASSWORD" -T db \
psql -U "${PDNSADMIN_DB_USER:-pdnsadmin}" -d "${PDNSADMIN_DB:-pdnsadmin}" \
-c "select current_user, current_database();"
```
```shell
docker compose exec auth pdnsutil list-zone chans.xyz
```
```
docker compose exec auth pdnsutil add-record chans.xyz hk2 A 154.36.174.161
```
```
docker compose exec auth pdnsutil delete-rrset example.test www AAAA
```
# PowerDNS Auth 变更 IP — 简版操作手册(按本次实际操作)
> 场景回顾:主节点(Docker**154.36.174.161**)更换/确认公网 IP;二级节点 **us1.wsvc.info** 为二进制安装(非 Docker)。前端用 **Nginx Proxy Manager (NPM)** 代理 **PowerDNS-Admin (PDA)** 与 **PDNS API**。
---
## 1) 现状与目标
- **主节点(Docker**`pdns-auth-49:4.9.8` + `postgres:16` + `powerdnsadmin/pda-legacy`
- **从节点(us1**:二进制 `pdns-server`,数据库 `domains` 使用老式字段 `type/master`
- **NPM 单独 compose**,与 PDNS/PDA 通过 **共享外部网络 `npm_proxy`** 互通。
- 对外 **只开放 53/tcp, 53/udp**PDA 与 PDNS API 仅在容器网络内,由 NPM 反代并做 IP 白名单。
---
## 2) 主节点(Docker)操作
### 2.1 网络与 Compose
```bash
docker network create npm_proxy || true
```
**pdns compose 关键点:**
- `auth`
- `ports`: 只保留 `53:53/udp``53:53/tcp`
- `expose`: `8081`API 仅在容器网络可见)。
- `networks`: 加入 `default` + `npm_proxy`
- **挂载目录**`./auth:/etc/powerdns:ro`(避免 pdns.conf 丢失)。
- 健康检查可用 `curl``curl -fsS -H 'X-API-Key: ${PDNS_API_KEY}' http://127.0.0.1:${PDNS_API_PORT}/api/v1/servers/localhost`
- `pda`
- **移除** `9191:80` 的对外发布;仅 `expose: 80`,加入 `npm_proxy` 网络。
### 2.2 `auth/pdns.conf` 关键参数
```ini
api=yes
api-key=<你的长随机值>
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=127.0.0.1,::1,172.16.0.0/12
launch=gpgsql
# gpgsql-* 与 Postgres 账户一致
```
> **提示**:若本机 `curl http://localhost:8081` 空回应,多半命中 IPv6 `::1`;用 `curl -4` 或把 `::1` 加入 `webserver-allow-from`。
### 2.3 通过 NPM 反代
- NPM `app` 服务加入 `npm_proxy`;发布 `80/443`
- 新建 Proxy Host
- **PDA** → `pda:80`(绑定域名,Access List 白名单)。
- **PDNS API** → `auth:8081`(同上)。
> 在 NPM 容器内自检:
```bash
docker compose exec app curl -sI http://pda:80/ | head
docker compose exec app curl -sI http://auth:8081/api/v1/servers/localhost | head
```
### 2.4 记录修改(本次实际)
- **ns1.wsvc.info A** 改为 `154.36.174.161`
```bash
curl -s -X PATCH -H "X-API-Key: $PDNS_API_KEY" -H 'Content-Type: application/json' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/wsvc.info. \
-d '{"rrsets":[{"name":"ns1.wsvc.info.","type":"A","changetype":"REPLACE","ttl":86400,"records":[{"content":"154.36.174.161","disabled":false}]}]}'
```
- **hk2.chans.xyz A** 移除 `.101`,保留 `.161`
```bash
curl -s -X PATCH -H "X-API-Key: $PDNS_API_KEY" -H 'Content-Type: application/json' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/chans.xyz. \
-d '{"rrsets":[{"name":"hk2.chans.xyz.","type":"A","changetype":"REPLACE","ttl":3600,"records":[{"content":"154.36.174.161","disabled":false}]}]}'
```
- 验证:`dig @127.0.0.1 ns1.wsvc.info A +short``dig @127.0.0.1 hk2.chans.xyz A +short`
---
## 3) us1(二进制从节点)操作(本次实际)
> us1 使用二进制 `pdns-server`,数据库 `domains` 为旧 schema`type/master`)。本次已验证以下两种方式皆可;**推荐优先使用 pdnsutil 命令**。
### 3.1 推荐:用 `pdnsutil` 指向新主并拉取
确保允许从区:
```
# /etc/powerdns/pdns.conf
secondary=yes # 旧版本为 slave=yes
```
把各区改为 Secondary,并设置新的主(**154.36.174.161**),然后触发 AXFR
```bash
sudo pdnsutil set-kind wsvc.info secondary
sudo pdnsutil set-kind chans.xyz secondary
sudo pdnsutil set-kind windy.me secondary
# 你的 pdnsutil 支持:change-secondary-zone-primary
sudo pdnsutil change-secondary-zone-primary wsvc.info 154.36.174.161
sudo pdnsutil change-secondary-zone-primary chans.xyz 154.36.174.161
sudo pdnsutil change-secondary-zone-primary windy.me 154.36.174.161
# 立即拉取(若无此命令可重启 pdns 替代)
sudo pdnsutil retrieve-secondary wsvc.info
sudo pdnsutil retrieve-secondary chans.xyz
sudo pdnsutil retrieve-secondary windy.me
```
日志与连通性:
```bash
journalctl -u pdns -e | egrep -i 'SOA|AXFR|IXFR|NOTIFY'
dig @154.36.174.161 wsvc.info SOA +tcp +time=2 +tries=1
```
> 失败多为 **TCP/53 未通** 或主节点未监听 TCP/53。
### 3.2 备选 A:没有该子命令时,用“重建从区”
```bash
sudo pdnsutil delete-zone wsvc.info && sudo pdnsutil create-secondary-zone wsvc.info 154.36.174.161
sudo pdnsutil delete-zone chans.xyz && sudo pdnsutil create-secondary-zone chans.xyz 154.36.174.161
sudo pdnsutil delete-zone windy.me && sudo pdnsutil create-secondary-zone windy.me 154.36.174.161
```
### 3.3 备选 B:直接改数据库后补拉取(你本次已用)
```sql
-- 在 us1 上
UPDATE domains SET type='SLAVE', master='154.36.174.161'
WHERE name IN ('wsvc.info','chans.xyz','windy.me');
```
然后:
```bash
sudo pdnsutil retrieve-secondary wsvc.info || sudo systemctl restart pdns
sudo pdnsutil retrieve-secondary chans.xyz || sudo systemctl restart pdns
sudo pdnsutil retrieve-secondary windy.me || sudo systemctl restart pdns
```
### 3.4 主节点授权(在 161 的容器上执行)
```bash
US1_IP=<us1 公网 IP>
docker compose exec auth pdnsutil set-meta wsvc.info ALLOW-AXFR-FROM $US1_IP
docker compose exec auth pdnsutil set-meta chans.xyz ALLOW-AXFR-FROM $US1_IP
docker compose exec auth pdnsutil set-meta windy.me ALLOW-AXFR-FROM $US1_IP
# 可选:
docker compose exec auth pdnsutil set-meta wsvc.info ALSO-NOTIFY $US1_IP
```
---
## 4) 常见问题(按本次排障)
- **PDA 看不到 zone**PDA 不读数据库,需连 PDNS API。PDA 服务器配置:`API URL=http://auth:8081``API Key``pdns.conf` 一致、`Server ID=localhost`。从 `pda` 容器内 `curl http://auth:8081/...` 验证。
- **`Empty reply from server`**`curl` 命中 IPv6 `::1`;改 `curl -4` 或在 `webserver-allow-from``::1`
- **`auth` Unhealthy**:多数是 API Key 不一致或健康检查命令在镜像中不可用;改用 `curl` 并确保 key 一致。
- **`Received NOTIFY ... not a primary (Refused)`**:从节点仍为 `MASTER`;将其改为 `SLAVE` 并正确设置 `master`
---
## 5) 验收清单(简版)
- 主节点:`db` healthy、`auth` API 可返回版本、PDA 通过 NPM 可访问。
- us1`domains``type=SLAVE``master=154.36.174.161``retrieve-secondary` 成功;日志有 AXFR/IXFR 记录。
- 主节点对 `US1_IP` 设置了 `ALLOW-AXFR-FROM`(必要时 `ALSO-NOTIFY`)。
- 关键记录变更已生效(`dig` 结果正确,考虑 TTL 缓存)。
```
export PGHOST=127.0.0.1
export PGPORT=5432
export PGUSER=pdns
export PGPASSWORD='windyboy2006'
export PGDATABASE=pdns
```
```
pg_dump --no-owner --no-acl --format=p --file=~/pdns_backup_$(date +%F).sql \
--single-transaction
```
```
docker compose exec -T db psql -U pdns -c "DROP DATABASE IF EXISTS pdns;"
docker compose exec -T db psql -U pdns -c "CREATE DATABASE pdns OWNER pdns;"
```
```
docker compose exec -T db psql -U pdns -d template1 -c "DROP DATABASE IF EXISTS pdns;"
docker compose exec -T db psql -U pdns -d template1 -c "CREATE DATABASE pdns OWNER pdns;"
```
```
docker compose exec -T db psql -U pdns -d pdns < backup/pdns_backup_2025-11-03.sql
```
```
docker compose exec -T db psql -U pdns -d pdns -c "UPDATE domains SET type='MASTER';"
```
```
docker compose exec -T db psql -U pdns -d pdns -c "DELETE FROM domainmetadata WHERE kind='AXFR-MASTER-TSIG';"
```
```
docker compose exec -T db psql -U pdns -d pdns -c "TRUNCATE TABLE supermasters;"
```
```
docker compose exec auth pdnsutil list-all-zones
docker compose exec auth pdnsutil check-all-zones
docker compose exec auth pdnsutil list-zone windy.me
```
```
docker compose exec -e PGPASSWORD=windyboy2006 backup psql -h db -U pdns -d pdns -c "\l"
```