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 = "*"
|
||||
python-dotenv = "*"
|
||||
dnspython = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
||||
|
||||
35
README.md
35
README.md
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
@@ -13,22 +13,25 @@ The names of the environment variables is the same as in the config file. Enviro
|
||||
### Environment variables
|
||||
```bash
|
||||
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 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 REFRESH_INTERVAL=5 \
|
||||
-e ONE_SHOT=True \
|
||||
-e NAMESERVER=10.100.0.11 \
|
||||
-e TSIG_NAME=dck1 \
|
||||
-e TSIG_KEY=SyYXDCJ4kIs3qhvI= \
|
||||
merijntjetak/docker-bind/ddns:latest
|
||||
merijntjetak/docker-nsupdate-ddns:latest
|
||||
```
|
||||
|
||||
### Config file
|
||||
```bash
|
||||
cat <<EOF >configfile
|
||||
DOCKER_SOCKET=/var/run/docker.sock
|
||||
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
|
||||
REFRESH_INTERVAL=5
|
||||
ONE_SHOT=True
|
||||
@@ -38,15 +41,17 @@ TSIG_KEY=SyYXDCJ4kIs3qhvI=
|
||||
EOF
|
||||
|
||||
docker run -d \
|
||||
-v `pwd`/configfile:/configfile
|
||||
merijntjetak/docker-bind-ddns:latest /configfile
|
||||
-v `pwd`/configfile:/configfile \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
merijntjetak/docker-nsupdate-ddns:latest /configfile
|
||||
|
||||
```
|
||||
|
||||
### 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.
|
||||
- `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
|
||||
- `REFRESH_INTERVAL` - Interval between updates
|
||||
- `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
|
||||
### Requirements
|
||||
|
||||
- Eventual redunancy (Bind9 zone transfers to secondary)
|
||||
- Support for multiple individual Docker servers
|
||||
- IPv6 support
|
||||
- Detect hostname in decreasing order of priority:
|
||||
- [x] Eventual redundancy (Bind9 zone transfers to secondary)
|
||||
- [x] Support for multiple individual Docker servers
|
||||
- [ ]IPv6 support
|
||||
- [x]Detect hostname in decreasing order of priority:
|
||||
- label
|
||||
- Container name
|
||||
- Forward-only
|
||||
- Clean up DNS (DNS is stateful but the script isn't, so there might be a mismatch)
|
||||
- [x]Forward-only
|
||||
- [ ]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
|
||||
|
||||
### Alternatives
|
||||
@@ -98,3 +102,4 @@ docker run -d \
|
||||
CoreDNS didn't fit the requirements, because zone transfers out of a CoreDNS server do not
|
||||
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
|
||||
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
||||
DEFAULT_NETWORK=10.100.0.192/26
|
||||
|
||||
@@ -11,8 +11,8 @@ def get_container_name(container):
|
||||
"""
|
||||
x = container.attrs['Name'][1:]
|
||||
|
||||
if config['hostname_label'] in container.attrs['Config']['Labels']:
|
||||
x = container.attrs['Config']['Labels'][config['hostname_label']]
|
||||
if config['HOSTNAME_LABEL'] in container.attrs['Config']['Labels']:
|
||||
x = container.attrs['Config']['Labels'][config['HOSTNAME_LABEL']]
|
||||
|
||||
x = x.replace("_", "-") # Be compliant with RFC1035
|
||||
return x
|
||||
@@ -28,11 +28,12 @@ def get_container_ip(container):
|
||||
x = container.attrs['NetworkSettings']['IPAddress']
|
||||
|
||||
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']
|
||||
|
||||
if config['default_network'] in container.attrs['NetworkSettings']['Networks']:
|
||||
x = container.attrs['NetworkSettings']['Networks'][config['default_network']]
|
||||
if config['DEFAULT_NETWORK'] in container.attrs['NetworkSettings']['Networks']:
|
||||
x = container.attrs['NetworkSettings']['Networks'][config['DEFAULT_NETWORK']]
|
||||
|
||||
return x
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ config = {}
|
||||
|
||||
|
||||
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():
|
||||
print("Adding record for " + hostname + "(" + ip + ")")
|
||||
|
||||
@@ -18,18 +19,19 @@ def add_records(records):
|
||||
if isinstance(address, ipaddress.IPv6Address):
|
||||
rrtype = "AAAA"
|
||||
|
||||
update = dns.update.Update(config['domain'], keyring=keyring)
|
||||
update = dns.update.Update(config['DOMAIN'], keyring=keyring)
|
||||
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):
|
||||
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():
|
||||
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)
|
||||
dns.query.tcp(update, config['nameserver'], timeout=2)
|
||||
dns.query.tcp(update, config['NAMESERVER'], timeout=2)
|
||||
|
||||
|
||||
def init(_config):
|
||||
|
||||
35
main.py
35
main.py
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import stat
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
from dotenv import dotenv_values
|
||||
import time
|
||||
|
||||
from lib import *
|
||||
@@ -12,12 +13,23 @@ ipam4_old = {}
|
||||
|
||||
|
||||
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:
|
||||
loop()
|
||||
time.sleep(config['refresh_interval'])
|
||||
time.sleep(config['REFRESH_INTERVAL'])
|
||||
|
||||
loop()
|
||||
|
||||
@@ -39,16 +51,13 @@ def loop():
|
||||
|
||||
def get_config():
|
||||
config_file = sys.argv[1] if len(sys.argv) >= 2 else 'config'
|
||||
load_dotenv(config_file)
|
||||
|
||||
config['domain'] = os.environ.get("DOMAIN")
|
||||
config['hostname_label'] = os.environ.get("HOSTNAME_LABEL")
|
||||
config['default_network'] = os.environ.get("DEFAULT_NETWORK")
|
||||
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")
|
||||
config['tsig_name'] = os.environ.get("TSIG_NAME")
|
||||
config['tsig_key'] = os.environ.get("TSIG_KEY")
|
||||
x = {
|
||||
**dotenv_values(os.path.join(os.getcwd(), config_file)),
|
||||
**os.environ
|
||||
}
|
||||
|
||||
return x
|
||||
|
||||
|
||||
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