Skip to content

Commit 8b8f175

Browse files
authored
Merge pull request #3125 from anubisg1/master
Pi-Hole Local DNS Support - rebases #2321
2 parents 74bbb48 + cc2c387 commit 8b8f175

File tree

8 files changed

+1255
-2
lines changed

8 files changed

+1255
-2
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Do *not* upgrade to these versions if you use external-dns
3535

3636
---
3737

38-
ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` and nodes in various cloud providers:
38+
ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchronized with Ingresses and Services of `type=LoadBalancer` and nodes in various DNS providers:
3939
* [Google Cloud DNS](https://cloud.google.com/dns/docs/)
4040
* [AWS Route 53](https://aws.amazon.com/route53/)
4141
* [AWS Cloud Map](https://docs.aws.amazon.com/cloud-map/)
@@ -69,6 +69,7 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
6969
* [TencentCloud PrivateDNS](https://cloud.tencent.com/product/privatedns)
7070
* [TencentCloud DNSPod](https://cloud.tencent.com/product/cns)
7171
* [Plural](https://www.plural.sh/)
72+
* [Pi-hole](https://pi-hole.net/)
7273

7374
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
7475

@@ -130,6 +131,7 @@ The following table clarifies the current status of the providers according to t
130131
| IBMCloud | Alpha | @hughhuangzh |
131132
| TencentCloud | Alpha | @Hyzhou |
132133
| Plural | Alpha | @michaeljguarino |
134+
| Pi-hole | Alpha | @tinyzimmer |
133135

134136
## Kubernetes version compatibility
135137

@@ -201,6 +203,7 @@ The following tutorials are provided:
201203
* [Nodes as source](docs/tutorials/nodes.md)
202204
* [TencentCloud](docs/tutorials/tencentcloud.md)
203205
* [Plural](docs/tutorials/plural.md)
206+
* [Pi-hole](docs/tutorials/pihole.md)
204207

205208
### Running Locally
206209

docs/tutorials/pihole.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Setting up ExternalDNS for Pi-hole
2+
3+
This tutorial describes how to setup ExternalDNS to sync records with Pi-hole's Custom DNS.
4+
Pi-hole has an internal list it checks last when resolving requests. This list can contain any number of arbitrary A or CNAME records.
5+
There is a pseudo-API exposed that ExternalDNS is able to use to manage these records.
6+
7+
## Deploy ExternalDNS
8+
9+
You can skip to the [manifest](#externaldns-manifest) if authentication is disabled on your Pi-hole instance or you don't want to use secrets.
10+
11+
If your Pi-hole server's admin dashboard is protected by a password, you'll likely want to create a secret first containing its value.
12+
This is optional since you _do_ retain the option to pass it as a flag with `--pihole-password`.
13+
14+
You can create the secret with:
15+
16+
```bash
17+
kubectl create secret generic pihole-password \
18+
--from-literal EXTERNAL_DNS_PIHOLE_PASSWORD=supersecret
19+
```
20+
21+
Replacing **"supersecret"** with the actual password to your Pi-hole server.
22+
23+
### ExternalDNS Manifest
24+
25+
Apply the following manifest to deploy ExternalDNS, editing values for your environment accordingly.
26+
Be sure to change the namespace in the `ClusterRoleBinding` if you are using a namespace other than **default**.
27+
28+
```yaml
29+
---
30+
apiVersion: v1
31+
kind: ServiceAccount
32+
metadata:
33+
name: external-dns
34+
---
35+
apiVersion: rbac.authorization.k8s.io/v1
36+
kind: ClusterRole
37+
metadata:
38+
name: external-dns
39+
rules:
40+
- apiGroups: [""]
41+
resources: ["services","endpoints","pods"]
42+
verbs: ["get","watch","list"]
43+
- apiGroups: ["extensions","networking.k8s.io"]
44+
resources: ["ingresses"]
45+
verbs: ["get","watch","list"]
46+
- apiGroups: [""]
47+
resources: ["nodes"]
48+
verbs: ["list","watch"]
49+
---
50+
apiVersion: rbac.authorization.k8s.io/v1
51+
kind: ClusterRoleBinding
52+
metadata:
53+
name: external-dns-viewer
54+
roleRef:
55+
apiGroup: rbac.authorization.k8s.io
56+
kind: ClusterRole
57+
name: external-dns
58+
subjects:
59+
- kind: ServiceAccount
60+
name: external-dns
61+
namespace: default
62+
---
63+
apiVersion: apps/v1
64+
kind: Deployment
65+
metadata:
66+
name: external-dns
67+
spec:
68+
strategy:
69+
type: Recreate
70+
selector:
71+
matchLabels:
72+
app: external-dns
73+
template:
74+
metadata:
75+
labels:
76+
app: external-dns
77+
spec:
78+
serviceAccountName: external-dns
79+
containers:
80+
- name: external-dns
81+
image: k8s.gcr.io/external-dns/external-dns:latest
82+
# If authentication is disabled and/or you didn't create
83+
# a secret, you can remove this block.
84+
envFrom:
85+
- secretRef:
86+
# Change this if you gave the secret a different name
87+
name: pihole-password
88+
args:
89+
- --source=service
90+
- --source=ingress
91+
# Pihole only supports A/CNAME records so there is no mechanism to track ownership.
92+
# You don't need to set this flag, but if you leave it unset, you will receive warning
93+
# logs when ExternalDNS attempts to create TXT records.
94+
- --registry=noop
95+
# IMPORTANT: If you have records that you manage manually in Pi-hole, set
96+
# the policy to upsert-only so they do not get deleted.
97+
- --policy=upsert-only
98+
- --provider=pihole
99+
# Change this to the actual address of your Pi-hole web server
100+
- --pihole-server=http://pihole-web.pihole.svc.cluster.local
101+
securityContext:
102+
fsGroup: 65534 # For ExternalDNS to be able to read Kubernetes token files
103+
```
104+
105+
### Arguments
106+
107+
- `--pihole-server (env: EXTERNAL_DNS_PIHOLE_SERVER)` - The address of the Pi-hole web server
108+
- `--pihole-password (env: EXTERNAL_DNS_PIHOLE_PASSWORD)` - The password to the Pi-hole web server (if enabled)
109+
- `--pihole-tls-skip-verify (env: EXTERNAL_DNS_PIHOLE_TLS_SKIP_VERIFY)` - Skip verification of any TLS certificates served by the Pi-hole web server.
110+
111+
## Verify ExternalDNS Works
112+
113+
### Ingress Example
114+
115+
Create an Ingress resource. ExternalDNS will use the hostname specified in the Ingress object.
116+
117+
```yaml
118+
apiVersion: networking.k8s.io/v1
119+
kind: Ingress
120+
metadata:
121+
name: foo
122+
spec:
123+
ingressClassName: nginx
124+
rules:
125+
- host: foo.bar.com
126+
http:
127+
paths:
128+
- path: /
129+
pathType: Prefix
130+
backend:
131+
service:
132+
name: foo
133+
port:
134+
number: 80
135+
```
136+
137+
### Service Example
138+
139+
The below sample application can be used to verify Services work.
140+
For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.
141+
142+
```yaml
143+
---
144+
apiVersion: v1
145+
kind: Service
146+
metadata:
147+
name: nginx
148+
annotations:
149+
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.homelab.com
150+
spec:
151+
type: LoadBalancer
152+
ports:
153+
- port: 80
154+
name: http
155+
targetPort: 80
156+
selector:
157+
app: nginx
158+
---
159+
apiVersion: apps/v1
160+
kind: Deployment
161+
metadata:
162+
name: nginx
163+
spec:
164+
selector:
165+
matchLabels:
166+
app: nginx
167+
template:
168+
metadata:
169+
labels:
170+
app: nginx
171+
spec:
172+
containers:
173+
- image: nginx
174+
name: nginx
175+
ports:
176+
- containerPort: 80
177+
name: http
178+
```
179+
180+
You can then query your Pi-hole to see if the record was created.
181+
182+
_Change `@192.168.100.2` to the actual address of your DNS server_
183+
184+
```bash
185+
$ dig +short @192.168.100.2 nginx.external-dns-test.homelab.com
186+
187+
192.168.100.129
188+
```

main.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import (
6060
"sigs.k8s.io/external-dns/provider/oci"
6161
"sigs.k8s.io/external-dns/provider/ovh"
6262
"sigs.k8s.io/external-dns/provider/pdns"
63+
"sigs.k8s.io/external-dns/provider/pihole"
6364
"sigs.k8s.io/external-dns/provider/plural"
6465
"sigs.k8s.io/external-dns/provider/rcode0"
6566
"sigs.k8s.io/external-dns/provider/rdns"
@@ -335,6 +336,16 @@ func main() {
335336
p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
336337
case "gandi":
337338
p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
339+
case "pihole":
340+
p, err = pihole.NewPiholeProvider(
341+
pihole.PiholeConfig{
342+
Server: cfg.PiholeServer,
343+
Password: cfg.PiholePassword,
344+
TLSInsecureSkipVerify: cfg.PiholeTLSInsecureSkipVerify,
345+
DomainFilter: domainFilter,
346+
DryRun: cfg.DryRun,
347+
},
348+
)
338349
case "ibmcloud":
339350
p, err = ibmcloud.NewIBMCloudProvider(cfg.IBMCloudConfigFile, domainFilter, zoneIDFilter, endpointsSource, cfg.IBMCloudProxied, cfg.DryRun)
340351
case "safedns":

pkg/apis/externaldns/types.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ type Config struct {
192192
IBMCloudConfigFile string
193193
TencentCloudConfigFile string
194194
TencentCloudZoneType string
195+
PiholeServer string
196+
PiholePassword string `secure:"yes"`
197+
PiholeTLSInsecureSkipVerify bool
195198
PluralCluster string
196199
PluralProvider string
197200
}
@@ -331,6 +334,9 @@ var defaultConfig = &Config{
331334
IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json",
332335
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json",
333336
TencentCloudZoneType: "",
337+
PiholeServer: "",
338+
PiholePassword: "",
339+
PiholeTLSInsecureSkipVerify: false,
334340
PluralCluster: "",
335341
PluralProvider: "",
336342
}
@@ -422,7 +428,7 @@ func (cfg *Config) ParseFlags(args []string) error {
422428
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
423429

424430
// Flags related to providers
425-
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns, tencentcloud)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns", "tencentcloud", "plural")
431+
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns, tencentcloud)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns", "tencentcloud", "pihole", "plural")
426432
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
427433
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
428434
app.Flag("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default(defaultConfig.RegexDomainFilter.String()).RegexpVar(&cfg.RegexDomainFilter)
@@ -536,6 +542,11 @@ func (cfg *Config) ParseFlags(args []string) error {
536542
app.Flag("transip-account", "When using the TransIP provider, specify the account name (required when --provider=transip)").Default(defaultConfig.TransIPAccountName).StringVar(&cfg.TransIPAccountName)
537543
app.Flag("transip-keyfile", "When using the TransIP provider, specify the path to the private key file (required when --provider=transip)").Default(defaultConfig.TransIPPrivateKeyFile).StringVar(&cfg.TransIPPrivateKeyFile)
538544

545+
// Flags related to Pihole provider
546+
app.Flag("pihole-server", "When using the Pihole provider, the base URL of the Pihole web server (required when --provider=pihole)").Default(defaultConfig.PiholeServer).StringVar(&cfg.PiholeServer)
547+
app.Flag("pihole-password", "When using the Pihole provider, the password to the server if it is protected").Default(defaultConfig.PiholePassword).StringVar(&cfg.PiholePassword)
548+
app.Flag("pihole-tls-skip-verify", "When using the Pihole provider, disable verification of any TLS certificates").BoolVar(&cfg.PiholeTLSInsecureSkipVerify)
549+
539550
// Flags related to the Plural provider
540551
app.Flag("plural-cluster", "When using the plural provider, specify the cluster name you're running with").Default(defaultConfig.PluralCluster).StringVar(&cfg.PluralCluster)
541552
app.Flag("plural-provider", "When using the plural provider, specify the provider name you're running with").Default(defaultConfig.PluralProvider).StringVar(&cfg.PluralProvider)

0 commit comments

Comments
 (0)