Fixed config loading, added Dockerfile
This commit is contained in:
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
**
|
||||||
|
|
||||||
|
!Pipfile
|
||||||
|
!main.py
|
||||||
|
!lib/
|
||||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
FROM python:alpine AS base
|
||||||
|
|
||||||
|
ENV LANG C.UTF-8
|
||||||
|
ENV LC_ALL C.UTF-8
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
|
ENV PYTHONFAULTHANDLER 1
|
||||||
|
|
||||||
|
FROM base AS python-build
|
||||||
|
RUN pip install pipenv
|
||||||
|
|
||||||
|
COPY Pipfile .
|
||||||
|
RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy --python /usr/local/bin/python
|
||||||
|
|
||||||
|
FROM base AS runtime
|
||||||
|
|
||||||
|
# Copy virtual env from python-deps stage
|
||||||
|
COPY --from=python-build /.venv /.venv
|
||||||
|
ENV PATH="/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . /app/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/main.py"]
|
||||||
3
Pipfile
3
Pipfile
@@ -9,6 +9,3 @@ verify_ssl = true
|
|||||||
docker = "*"
|
docker = "*"
|
||||||
python-dotenv = "*"
|
python-dotenv = "*"
|
||||||
dnspython = "*"
|
dnspython = "*"
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.6"
|
|
||||||
|
|||||||
35
README.md
35
README.md
@@ -1,6 +1,6 @@
|
|||||||
# docker-nsupdate-ddns
|
# docker-nsupdate-ddns
|
||||||
|
|
||||||
This script pushes container/ip information from a Docker instance to a DNS server via the DNS update mechanism desribed in RFC 2136 (nsupdate). nsupdate is implemented by ISC Bind9.
|
This script pushes container/ip information from local Docker instance to a DNS server via the DNS update mechanism desribed in RFC 2136 (nsupdate). nsupdate is implemented by ISC Bind9.
|
||||||
|
|
||||||
Every REFRESH_INTERVAL seconds, it queries all the Docker containers on the local host, finds their IP and pushes it with the name to the DNS server.
|
Every REFRESH_INTERVAL seconds, it queries all the Docker containers on the local host, finds their IP and pushes it with the name to the DNS server.
|
||||||
|
|
||||||
@@ -13,22 +13,25 @@ The names of the environment variables is the same as in the config file. Enviro
|
|||||||
### Environment variables
|
### Environment variables
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker run -d \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e DOCKER_SOCKET=/var/run/docker.sock \
|
||||||
-e DOMAIN=int.mtak.nl \
|
-e DOMAIN=int.mtak.nl \
|
||||||
-e HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname \
|
-e HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname \
|
||||||
-e DEFAULT_NETWORK=10.100.0.192/26 \
|
-e DEFAULT_NETWORK=10.100.0.192/26 \
|
||||||
-e REFRESH_INTERVAL=5 \
|
-e REFRESH_INTERVAL=5 \
|
||||||
-e ONE_SHOT=True \
|
-e ONE_SHOT=True \
|
||||||
-e NAMESERVER=10.100.0.11 \
|
-e NAMESERVER=10.100.0.11 \
|
||||||
-e TSIG_NAME=dck1 \
|
-e TSIG_NAME=dck1 \
|
||||||
-e TSIG_KEY=SyYXDCJ4kIs3qhvI= \
|
-e TSIG_KEY=SyYXDCJ4kIs3qhvI= \
|
||||||
merijntjetak/docker-bind/ddns:latest
|
merijntjetak/docker-nsupdate-ddns:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
### Config file
|
### Config file
|
||||||
```bash
|
```bash
|
||||||
cat <<EOF >configfile
|
cat <<EOF >configfile
|
||||||
|
DOCKER_SOCKET=/var/run/docker.sock
|
||||||
DOMAIN=int.mtak.nl
|
DOMAIN=int.mtak.nl
|
||||||
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
HOSTNAME_LABEL=nl.mtak.docker-nsupdate-ddns.hostname
|
||||||
DEFAULT_NETWORK=10.100.0.192/26
|
DEFAULT_NETWORK=10.100.0.192/26
|
||||||
REFRESH_INTERVAL=5
|
REFRESH_INTERVAL=5
|
||||||
ONE_SHOT=True
|
ONE_SHOT=True
|
||||||
@@ -38,15 +41,17 @@ TSIG_KEY=SyYXDCJ4kIs3qhvI=
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
docker run -d \
|
docker run -d \
|
||||||
-v `pwd`/configfile:/configfile
|
-v `pwd`/configfile:/configfile \
|
||||||
merijntjetak/docker-bind-ddns:latest /configfile
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
merijntjetak/docker-nsupdate-ddns:latest /configfile
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration
|
### Configuration
|
||||||
|
|
||||||
|
- `DOCKER_SOCKET` - Sets the location of the Docker socket
|
||||||
- `DOMAIN` - Sets the domain in which the records are created. Needs to match the Bind zone.
|
- `DOMAIN` - Sets the domain in which the records are created. Needs to match the Bind zone.
|
||||||
- `HOSTNAME_LABEL` - Docker label to override the default record name with. Use with `docker --label=nl.mtak.docker-bind-ddns.hostname=nginx` to get `nginx.int.mtak.nl`
|
- `HOSTNAME_LABEL` - Docker label to override the default record name with. Use with `docker --label=nl.mtak.docker-nsupdate-ddns.hostname=nginx` to get `nginx.int.mtak.nl`
|
||||||
- `DEFAULT_NETWORK` - Preferred network to find IP for, in case there are multiple networks
|
- `DEFAULT_NETWORK` - Preferred network to find IP for, in case there are multiple networks
|
||||||
- `REFRESH_INTERVAL` - Interval between updates
|
- `REFRESH_INTERVAL` - Interval between updates
|
||||||
- `ONE_SHOT` - Set to True for the script to update once and immediately quit (nice for debugging)
|
- `ONE_SHOT` - Set to True for the script to update once and immediately quit (nice for debugging)
|
||||||
@@ -79,18 +84,17 @@ docker run -d \
|
|||||||
## Design
|
## Design
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- Eventual redunancy (Bind9 zone transfers to secondary)
|
- [x] Eventual redundancy (Bind9 zone transfers to secondary)
|
||||||
- Support for multiple individual Docker servers
|
- [x] Support for multiple individual Docker servers
|
||||||
- IPv6 support
|
- [ ]IPv6 support
|
||||||
- Detect hostname in decreasing order of priority:
|
- [x]Detect hostname in decreasing order of priority:
|
||||||
- label
|
- label
|
||||||
- Container name
|
- Container name
|
||||||
- Forward-only
|
- [x]Forward-only
|
||||||
- Clean up DNS (DNS is stateful but the script isn't, so there might be a mismatch)
|
- [ ]Clean up DNS (DNS is stateful but the script isn't, so there might be a mismatch)
|
||||||
|
|
||||||
### Todo
|
### Nice to have
|
||||||
|
|
||||||
- Make event-driven with [Ahab](https://github.com/instacart/ahab)
|
|
||||||
- Add tests
|
- Add tests
|
||||||
|
|
||||||
### Alternatives
|
### Alternatives
|
||||||
@@ -98,3 +102,4 @@ docker run -d \
|
|||||||
CoreDNS didn't fit the requirements, because zone transfers out of a CoreDNS server do not
|
CoreDNS didn't fit the requirements, because zone transfers out of a CoreDNS server do not
|
||||||
include the records from the coredns-dockerdiscovery plugin.
|
include the records from the coredns-dockerdiscovery plugin.
|
||||||
|
|
||||||
|
K3s and k8s would probably do this, but incur significant complexity over a standalone Docker instance.
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
DOCKER_SOCKET=/var/run/docker.sock
|
||||||
DOMAIN=int.mtak.nl
|
DOMAIN=int.mtak.nl
|
||||||
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
||||||
DEFAULT_NETWORK=10.100.0.192/26
|
DEFAULT_NETWORK=10.100.0.192/26
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ def get_container_name(container):
|
|||||||
"""
|
"""
|
||||||
x = container.attrs['Name'][1:]
|
x = container.attrs['Name'][1:]
|
||||||
|
|
||||||
if config['hostname_label'] in container.attrs['Config']['Labels']:
|
if config['HOSTNAME_LABEL'] in container.attrs['Config']['Labels']:
|
||||||
x = container.attrs['Config']['Labels'][config['hostname_label']]
|
x = container.attrs['Config']['Labels'][config['HOSTNAME_LABEL']]
|
||||||
|
|
||||||
x = x.replace("_", "-") # Be compliant with RFC1035
|
x = x.replace("_", "-") # Be compliant with RFC1035
|
||||||
return x
|
return x
|
||||||
@@ -28,11 +28,12 @@ def get_container_ip(container):
|
|||||||
x = container.attrs['NetworkSettings']['IPAddress']
|
x = container.attrs['NetworkSettings']['IPAddress']
|
||||||
|
|
||||||
if next(iter(container.attrs['NetworkSettings']['Networks'])):
|
if next(iter(container.attrs['NetworkSettings']['Networks'])):
|
||||||
network_name = next(iter(container.attrs['NetworkSettings']['Networks']))
|
network_name = next(
|
||||||
|
iter(container.attrs['NetworkSettings']['Networks']))
|
||||||
x = container.attrs['NetworkSettings']['Networks'][network_name]['IPAddress']
|
x = container.attrs['NetworkSettings']['Networks'][network_name]['IPAddress']
|
||||||
|
|
||||||
if config['default_network'] in container.attrs['NetworkSettings']['Networks']:
|
if config['DEFAULT_NETWORK'] in container.attrs['NetworkSettings']['Networks']:
|
||||||
x = container.attrs['NetworkSettings']['Networks'][config['default_network']]
|
x = container.attrs['NetworkSettings']['Networks'][config['DEFAULT_NETWORK']]
|
||||||
|
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ config = {}
|
|||||||
|
|
||||||
|
|
||||||
def add_records(records):
|
def add_records(records):
|
||||||
keyring = dns.tsigkeyring.from_text({config['tsig_name']: config['tsig_key']})
|
keyring = dns.tsigkeyring.from_text(
|
||||||
|
{config['TSIG_NAME']: config['TSIG_KEY']})
|
||||||
for hostname, ip in records.items():
|
for hostname, ip in records.items():
|
||||||
print("Adding record for " + hostname + "(" + ip + ")")
|
print("Adding record for " + hostname + "(" + ip + ")")
|
||||||
|
|
||||||
@@ -18,18 +19,19 @@ def add_records(records):
|
|||||||
if isinstance(address, ipaddress.IPv6Address):
|
if isinstance(address, ipaddress.IPv6Address):
|
||||||
rrtype = "AAAA"
|
rrtype = "AAAA"
|
||||||
|
|
||||||
update = dns.update.Update(config['domain'], keyring=keyring)
|
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
||||||
update.add(hostname, 60, rrtype, ip)
|
update.add(hostname, 60, rrtype, ip)
|
||||||
dns.query.tcp(update, config['nameserver'], timeout=2)
|
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
||||||
|
|
||||||
|
|
||||||
def delete_records(records):
|
def delete_records(records):
|
||||||
keyring = dns.tsigkeyring.from_text({config['tsig_name']: config['tsig_key']})
|
keyring = dns.tsigkeyring.from_text(
|
||||||
|
{config['TSIG_NAME']: config['TSIG_KEY']})
|
||||||
for hostname, ip in records.items():
|
for hostname, ip in records.items():
|
||||||
print("Deleting record for " + hostname + "(" + ip + ")")
|
print("Deleting record for " + hostname + "(" + ip + ")")
|
||||||
update = dns.update.Update(config['domain'], keyring=keyring)
|
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
||||||
update.delete(hostname)
|
update.delete(hostname)
|
||||||
dns.query.tcp(update, config['nameserver'], timeout=2)
|
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
||||||
|
|
||||||
|
|
||||||
def init(_config):
|
def init(_config):
|
||||||
|
|||||||
35
main.py
35
main.py
@@ -1,8 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import stat
|
||||||
import sys
|
import sys
|
||||||
from dotenv import load_dotenv
|
from dotenv import dotenv_values
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from lib import *
|
from lib import *
|
||||||
@@ -12,12 +13,23 @@ ipam4_old = {}
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
get_config()
|
global config
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
if not config['one_shot']:
|
try:
|
||||||
|
stat.S_ISSOCK(os.stat(config['DOCKER_SOCKET']).st_mode)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print(
|
||||||
|
"Docker socket " +
|
||||||
|
config['DOCKER_SOCKET'] +
|
||||||
|
" not found, exiting...")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not config['ONE_SHOT']:
|
||||||
while True:
|
while True:
|
||||||
loop()
|
loop()
|
||||||
time.sleep(config['refresh_interval'])
|
time.sleep(config['REFRESH_INTERVAL'])
|
||||||
|
|
||||||
loop()
|
loop()
|
||||||
|
|
||||||
@@ -39,16 +51,13 @@ def loop():
|
|||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
config_file = sys.argv[1] if len(sys.argv) >= 2 else 'config'
|
config_file = sys.argv[1] if len(sys.argv) >= 2 else 'config'
|
||||||
load_dotenv(config_file)
|
|
||||||
|
|
||||||
config['domain'] = os.environ.get("DOMAIN")
|
x = {
|
||||||
config['hostname_label'] = os.environ.get("HOSTNAME_LABEL")
|
**dotenv_values(os.path.join(os.getcwd(), config_file)),
|
||||||
config['default_network'] = os.environ.get("DEFAULT_NETWORK")
|
**os.environ
|
||||||
config['refresh_interval'] = int(os.environ.get("REFRESH_INTERVAL"))
|
}
|
||||||
config['one_shot'] = os.environ.get("ONE_SHOT").lower() in ['true', 'yes']
|
|
||||||
config['nameserver'] = os.environ.get("NAMESERVER")
|
return x
|
||||||
config['tsig_name'] = os.environ.get("TSIG_NAME")
|
|
||||||
config['tsig_key'] = os.environ.get("TSIG_KEY")
|
|
||||||
|
|
||||||
|
|
||||||
def determine_additions(ipam, ipam_old):
|
def determine_additions(ipam, ipam_old):
|
||||||
|
|||||||
42
make.sh
Executable file
42
make.sh
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
VERSION="1.0"
|
||||||
|
NAMESPACE='merijntjetak'
|
||||||
|
IMAGENAME='docker-nsupdate-ddns'
|
||||||
|
DOCKER_REGISTRY_HOST=''
|
||||||
|
DOCKER_REGISTRY_PORT=''
|
||||||
|
|
||||||
|
# Generate docker URI
|
||||||
|
if [[ "x$DOCKER_REGISTRY_HOST" != "x" && "x$DOCKER_REGISTRY_PORT" != "x" ]]; then
|
||||||
|
DOCKER_REGISTRY_AUTHORITY="${DOCKER_REGISTRY_HOST}:${DOCKER_REGISTRY_PORT}/"
|
||||||
|
else
|
||||||
|
DOCKER_REGISTRY_AUTHORITY=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$1" == "build" ]]; then
|
||||||
|
docker build -t ${NAMESPACE}/${IMAGENAME} -t ${NAMESPACE}/${IMAGENAME}:${VERSION} .
|
||||||
|
|
||||||
|
elif [[ "$1" == "run" ]]; then
|
||||||
|
docker run -ti --rm \
|
||||||
|
-v '/var/run/docker.sock:/var/run/docker.sock' \
|
||||||
|
-v `pwd`/config:/config \
|
||||||
|
${NAMESPACE}/${IMAGENAME}:${VERSION} \
|
||||||
|
/config
|
||||||
|
|
||||||
|
elif [[ "$1" == "publish" ]]; then
|
||||||
|
docker tag ${PUBLISHER}/${IMAGENAME}:${VERSION} ${DOCKER_REGISTRY_AUTHORITY}${NAMESPACE}/${IMAGENAME}:$VERSION
|
||||||
|
docker push ${DOCKER_REGISTRY_AUTHORITY}${NAMESPACE}/${IMAGENAME}:$VERSION
|
||||||
|
|
||||||
|
else
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 action
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
build - Build docker image
|
||||||
|
run - Run docker image
|
||||||
|
publish - Publish docker image
|
||||||
|
|
||||||
|
Change the parameters in the run section to adjust for local testing variables.
|
||||||
|
EOF
|
||||||
|
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user