From 6fe7093c7bd432f5810e06fdb04a6d17c0b9e952 Mon Sep 17 00:00:00 2001 From: tafkam Date: Sun, 29 Mar 2020 19:01:11 +0200 Subject: [PATCH] Supermaster support, multiple slaves on the same mysql server (#32) * better supermaster handling, support for multiple slaves on the same db server * minor style fix * requested changes * replaced cut with awk * manifests * newlines * clarification for axfr --- examples/kubernetes/README.MD | 39 ++++++ examples/kubernetes/admin-dashboard.yaml | 122 ++++++++++++++++++ examples/kubernetes/kustomization.yaml | 6 + examples/kubernetes/mariadb.yaml | 126 +++++++++++++++++++ examples/kubernetes/master-daemonset.yaml | 144 ++++++++++++++++++++++ examples/kubernetes/slave-daemonset.yaml | 96 +++++++++++++++ pdns/docker-entrypoint.sh | 44 +++++-- 7 files changed, 565 insertions(+), 12 deletions(-) create mode 100644 examples/kubernetes/README.MD create mode 100644 examples/kubernetes/admin-dashboard.yaml create mode 100644 examples/kubernetes/kustomization.yaml create mode 100644 examples/kubernetes/mariadb.yaml create mode 100644 examples/kubernetes/master-daemonset.yaml create mode 100644 examples/kubernetes/slave-daemonset.yaml diff --git a/examples/kubernetes/README.MD b/examples/kubernetes/README.MD new file mode 100644 index 0000000..1e53b7a --- /dev/null +++ b/examples/kubernetes/README.MD @@ -0,0 +1,39 @@ +# PDNS example for Kubernetes + +## Files + - master-daemonset.yaml : Daemonset for PDNS supermaster + - slave-daemonset.yaml : Daemonset for PDNS slaves + - admin-dashboard.yaml : Deployment for PDNS-Admin Web Dashboard + - mariadb.yaml : Example Mysql Deployment + +## Example setup + +This example deploys a supermaster and two slaves on the host network, so pdns can be reached from external networks. Access to the admin-dashboard has to be configured separately with ingress. The admin-dashboard uses a kubernetes clusterip service to use the supermaster-api. Supermaster, slaves and dashboard use the same MariaDB example deployment with different databases (not recommended for production environments). +For signed AXFR you have to manually deploy TSIG Keys to you supermaster and slaves (https://doc.powerdns.com/authoritative/tsig.html). + +## Requirements + +### Node Labels +The Daemonsets use node-role labels as nodeSelector: + + kubectl label node node1 node-role.kubernetes.io/pdns-master=true + kubectl label node node2 node-role.kubernetes.io/pdns-slave=true + kubectl label node node3 node-role.kubernetes.io/pdns-slave=true + +Any other node labels will also work. + +### Service Names +Service names in the pdns namespace **must not** start with 'pdns' or they will break the pdns.conf environment templating. + +### Pod-CIDR +Replace "10.244.0.0/16" in the manifests with your cluster's pod-cidr. + +### Node IPs +Replace IPs and hostnames in the daemonset environments with your own node IPs and domains. + +Used in this example: +| component | host | ip | +|--|--|--| +| supermaster | ns1.example.com | 10.0.0.1 | +| slave1 | ns2.example.com | 10.0.0.2 | +| slave2 | ns3.example.com | 10.0.0.3 | diff --git a/examples/kubernetes/admin-dashboard.yaml b/examples/kubernetes/admin-dashboard.yaml new file mode 100644 index 0000000..d1142fd --- /dev/null +++ b/examples/kubernetes/admin-dashboard.yaml @@ -0,0 +1,122 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: pdns-admin + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: admin + name: pdns-admin +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: pdns-admin + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: admin + template: + metadata: + labels: + app.kubernetes.io/name: pdns-admin + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: admin + spec: + hostAliases: + - ip: "127.0.0.1" + hostnames: + - "pdns-admin-uwsgi" + containers: + - name: pdns-admin-static + image: pschiffe/pdns-admin-static:ngoduykhanh + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 + protocol: TCP + resources: + limits: + cpu: 300m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + - name: pdns-admin-uwsgi + image: pschiffe/pdns-admin-uwsgi:ngoduykhanh + imagePullPolicy: IfNotPresent + env: + - name: PDNS_ADMIN_SQLA_DB_HOST + value: "'mariadb-pdns'" + - name: PDNS_ADMIN_SQLA_DB_PORT + value: "'3306'" + - name: PDNS_ADMIN_SQLA_DB_USER + value: "'root'" + - name: PDNS_ADMIN_SQLA_DB_PASSWORD + valueFrom: + secretKeyRef: + name: pdns-admin-secret + key: quoted_mysql_password + - name: PDNS_ADMIN_SQLA_DB_NAME + value: "'pdnsadmin'" + - name: PDNS_API_URL + value: "http://master-api-pdns:8081/" + - name: PDNS_VERSION + value: "4.2.1" + - name: PDNS_API_KEY + valueFrom: + secretKeyRef: + name: master-pdns-secret + key: apikey + volumeMounts: + - name: uploads + mountPath: /opt/powerdns-admin/upload/ + resources: + limits: + cpu: 300m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + volumes: + - name: uploads + persistentVolumeClaim: + claimName: pdns-admin-pvc +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: pdns-admin-pvc + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: admin + name: pdns-admin-pvc +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: pdns-admin-svc + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: admin + name: admin-dashboard-pdns +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 80 + selector: + app.kubernetes.io/name: pdns-admin +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/name: pdns-admin-secret + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: admin + name: pdns-admin-secret +data: + quoted_mysql_password: J3Jvb3Qn diff --git a/examples/kubernetes/kustomization.yaml b/examples/kubernetes/kustomization.yaml new file mode 100644 index 0000000..4876267 --- /dev/null +++ b/examples/kubernetes/kustomization.yaml @@ -0,0 +1,6 @@ +namespace: default +resources: +- mariadb.yaml +- master-daemonset.yaml +- slave-daemonset.yaml +- admin-dashboard.yaml diff --git a/examples/kubernetes/mariadb.yaml b/examples/kubernetes/mariadb.yaml new file mode 100644 index 0000000..a5ef67d --- /dev/null +++ b/examples/kubernetes/mariadb.yaml @@ -0,0 +1,126 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: mariadb-pdns + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + name: mariadb-pdns +spec: + podManagementPolicy: OrderedReady + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/name: mariadb-pdns + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + serviceName: mariadb-pdns + template: + metadata: + labels: + app.kubernetes.io/name: mariadb-pdns + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + spec: + containers: + - name: mariadb-pdns + image: mariadb:10.4 + imagePullPolicy: Always + env: + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mariadb-pdns-secret + key: password + volumeMounts: + - name: mariadb-pdns-storage + mountPath: /var/lib/mysql + - name: mariadb-pdns-config + mountPath: /etc/mysql/conf.d + ports: + - containerPort: 3306 + resources: + limits: + cpu: 300m + memory: 256Mi + requests: + cpu: 50m + memory: 128Mi + volumes: + - name: mariadb-pdns-storage + persistentVolumeClaim: + claimName: mariadb-pdns-pvc + - name: mariadb-pdns-config + configMap: + name: mariadb-pdns-config + items: + - key: mariadb-override.cnf + path: mariadb-override.cnf +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: mariadb-pdns-svc + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + name: mariadb-pdns +spec: + ports: + - name: tcp-mysql + port: 3306 + protocol: TCP + targetPort: 3306 + selector: + app.kubernetes.io/name: mariadb-pdns + sessionAffinity: None + type: ClusterIP +--- +apiVersion: v1 +kind: ConfigMap +metadata: + labels: + app.kubernetes.io/name: mariadb-pdns-config + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + name: mariadb-pdns-config +data: + mariadb-override.cnf: | + [mysqld] + collation-server = utf8mb4_unicode_ci + init-connect='SET NAMES utf8mb4' + character-set-server = utf8mb4 + innodb_buffer_pool_size = 134217728 + innodb_log_file_size = 134217728 + innodb-log-files-in-group = 3 + binlog_format = ROW +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + labels: + app.kubernetes.io/name: mariadb-pdns-pvc + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + name: mariadb-pdns-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/name: mariadb-pdns-secret + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: mariadb + name: mariadb-pdns-secret +data: + username: cm9vdA== + password: cm9vdA== diff --git a/examples/kubernetes/master-daemonset.yaml b/examples/kubernetes/master-daemonset.yaml new file mode 100644 index 0000000..5072ce7 --- /dev/null +++ b/examples/kubernetes/master-daemonset.yaml @@ -0,0 +1,144 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app.kubernetes.io/name: pdns-master + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: master + name: pdns-master +spec: + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/name: pdns-master + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: master + template: + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: pdns-master + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: master + spec: + nodeSelector: + node-role.kubernetes.io/pdns-master: "true" + containers: + - image: pschiffe/pdns-mysql:alpine + imagePullPolicy: IfNotPresent + name: pdns-master + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: PDNS_gmysql_host + value: "mariadb-pdns" + - name: PDNS_gmysql_port + value: "3306" + - name: PDNS_gmysql_user + valueFrom: + secretKeyRef: + name: mariadb-pdns-secret + key: username + - name: PDNS_gmysql_password + valueFrom: + secretKeyRef: + name: mariadb-pdns-secret + key: password + - name: PDNS_gmysql_dbname + value: "master_" + - name: PDNS_version_string + value: "anonymous" + - name: PDNS_master + value: "yes" + - name: PDNS_api + value: "yes" + - name: PDNS_api_key + valueFrom: + secretKeyRef: + name: master-pdns-secret + key: apikey + - name: PDNS_webserver + value: "yes" + - name: PDNS_webserver_address + value: "0.0.0.0" + - name: PDNS_webserver_allow_from + value: "127.0.0.1/32 10.244.0.0/16" + - name: PDNS_webserver_password + valueFrom: + secretKeyRef: + name: master-pdns-secret + key: webserver + - name: PDNS_default_ttl + value: "1500" + - name: PDNS_soa_minimum_ttl + value: "1200" + - name: PDNS_default_soa_name + value: "ns1.example.com" + - name: PDNS_default_soa_mail + value: "dnsmaster.example.com" + - name: PDNS_allow_axfr_ips + value: "10.0.0.2 10.0.0.3" + - name: PDNS_only_notify + value: "10.0.0.2 10.0.0.3" + - name: PDNS_dnsupdate + value: "yes" + - name: PDNS_allow_dnsupdate_from + value: "10.0.0.1/32 127.0.0.1/32 10.244.0.0/16" + resources: + limits: + cpu: 300m + memory: 256Mi + requests: + cpu: 50m + memory: 128Mi + ports: + - name: dns-udp + containerPort: 53 + protocol: UDP + hostPort: 53 + - name: dns-tcp + containerPort: 53 + protocol: TCP + hostPort: 53 + - containerPort: 8081 + protocol: TCP + dnsPolicy: ClusterFirstWithHostNet + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + updateStrategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + app.kubernetes.io/name: master-pdns-secret + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: master + name: master-pdns-secret +data: + apikey: MTIzNDU2Nzg5MA== + webserver: MDk4NzY1NDMyMQ== +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: master-api-pdns + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: master + name: master-api-pdns +spec: + type: ClusterIP + ports: + - port: 8081 + targetPort: 8081 + selector: + app.kubernetes.io/name: pdns-master diff --git a/examples/kubernetes/slave-daemonset.yaml b/examples/kubernetes/slave-daemonset.yaml new file mode 100644 index 0000000..79b46dd --- /dev/null +++ b/examples/kubernetes/slave-daemonset.yaml @@ -0,0 +1,96 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app.kubernetes.io/name: pdns-slave + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: slave + name: pdns-slave +spec: + revisionHistoryLimit: 10 + selector: + matchLabels: + app.kubernetes.io/name: pdns-slave + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: slave + template: + metadata: + creationTimestamp: null + labels: + app.kubernetes.io/name: pdns-slave + app.kubernetes.io/part-of: pdns + app.kubernetes.io/component: slave + spec: + nodeSelector: + node-role.kubernetes.io/pdns-slave: "true" + containers: + - image: pschiffe/pdns-mysql:alpine + imagePullPolicy: IfNotPresent + name: pdns-slave + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: PDNS_gmysql_host + value: "mariadb-pdns" + - name: PDNS_gmysql_port + value: "3306" + - name: PDNS_gmysql_user + valueFrom: + secretKeyRef: + name: mariadb-pdns-secret + key: username + - name: PDNS_gmysql_password + valueFrom: + secretKeyRef: + name: mariadb-pdns-secret + key: password + - name: PDNS_gmysql_dbname + value: "slave_" + - name: PDNS_version_string + value: "anonymous" + - name: PDNS_disable_axfr + value: "yes" + - name: PDNS_slave + value: "yes" + - name: PDNS_superslave + value: "yes" + - name: PDNS_allow_unsigned_supermaster + value: "no" + - name: PDNS_allow_notify_from + value: "10.0.0.1" + - name: SUPERMASTER_HOSTS + value: "ns1.example.com" + - name: SUPERMASTER_IPS + value: "10.0.0.1" + ports: + - name: dns-udp + containerPort: 53 + protocol: UDP + hostPort: 53 + - name: dns-tcp + containerPort: 53 + protocol: TCP + hostPort: 53 + resources: + limits: + cpu: 300m + memory: 256Mi + requests: + cpu: 50m + memory: 128Mi + securityContext: + privileged: false + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirstWithHostNet + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 + updateStrategy: + rollingUpdate: + maxUnavailable: 1 + type: RollingUpdate diff --git a/pdns/docker-entrypoint.sh b/pdns/docker-entrypoint.sh index 1c0104d..8e84933 100755 --- a/pdns/docker-entrypoint.sh +++ b/pdns/docker-entrypoint.sh @@ -12,17 +12,21 @@ fi : "${PDNS_gmysql_password:=${MYSQL_ENV_MYSQL_PASSWORD:-powerdns}}" : "${PDNS_gmysql_dbname:=${MYSQL_ENV_MYSQL_DATABASE:-powerdns}}" -export PDNS_gmysql_host PDNS_gmysql_port PDNS_gmysql_user PDNS_gmysql_password PDNS_gmysql_dbname +# use first part of node name as database name suffix +if [ "${NODE_NAME:-}" ]; then + NODE_NAME=$(echo ${NODE_NAME} | sed -e 's/\..*//' -e 's/-//') + PDNS_gmysql_dbname="${PDNS_gmysql_dbname}${NODE_NAME}" +fi -# Create config file from template -envtpl < /pdns.conf.tpl > /etc/pdns/pdns.conf + +export PDNS_gmysql_host PDNS_gmysql_port PDNS_gmysql_user PDNS_gmysql_password PDNS_gmysql_dbname # Initialize DB if needed MYSQL_COMMAND="mysql -h ${PDNS_gmysql_host} -P ${PDNS_gmysql_port} -u ${PDNS_gmysql_user} -p${PDNS_gmysql_password}" until $MYSQL_COMMAND -e ';' ; do >&2 echo 'MySQL is unavailable - sleeping' - sleep 1 + sleep 3 done $MYSQL_COMMAND -e "CREATE DATABASE IF NOT EXISTS ${PDNS_gmysql_dbname}" @@ -33,14 +37,30 @@ if [ "$MYSQL_NUM_TABLE" -eq 0 ]; then $MYSQL_COMMAND -D "$PDNS_gmysql_dbname" < /usr/share/doc/pdns/schema.mysql.sql fi -# Configure supermasters if needed -if [ "${SUPERMASTER_IPS:-}" ]; then - $MYSQL_COMMAND -D "$PDNS_gmysql_dbname" -e "TRUNCATE supermasters;" - MYSQL_INSERT_SUPERMASTERS='' - for i in $SUPERMASTER_IPS; do - MYSQL_INSERT_SUPERMASTERS="${MYSQL_INSERT_SUPERMASTERS} INSERT INTO supermasters VALUES('${i}', '$(hostname -f)', 'admin');" - done - $MYSQL_COMMAND -D "$PDNS_gmysql_dbname" -e "$MYSQL_INSERT_SUPERMASTERS" +if [ "${PDNS_superslave:-no}" == "yes" ]; then + # Configure supermasters if needed + if [ "${SUPERMASTER_IPS:-}" ]; then + $MYSQL_COMMAND -D "$PDNS_gmysql_dbname" -e "TRUNCATE supermasters;" + MYSQL_INSERT_SUPERMASTERS='' + if [ "${SUPERMASTER_COUNT:-0}" == "0" ]; then + SUPERMASTER_COUNT=10 + fi + i=1; while [ $i -le ${SUPERMASTER_COUNT} ]; do + SUPERMASTER_HOST=$(echo ${SUPERMASTER_HOSTS:-} | awk -v col="$i" '{ print $col }') + SUPERMASTER_IP=$(echo ${SUPERMASTER_IPS} | awk -v col="$i" '{ print $col }') + if [ -z "${SUPERMASTER_HOST:-}" ]; then + SUPERMASTER_HOST=$(hostname -f) + fi + if [ "${SUPERMASTER_IP:-}" ]; then + MYSQL_INSERT_SUPERMASTERS="${MYSQL_INSERT_SUPERMASTERS} INSERT INTO supermasters VALUES('${SUPERMASTER_IP}', '${SUPERMASTER_HOST}', 'admin');" + fi + i=$(( i + 1 )) + done + $MYSQL_COMMAND -D "$PDNS_gmysql_dbname" -e "$MYSQL_INSERT_SUPERMASTERS" + fi fi +# Create config file from template +envtpl < /pdns.conf.tpl > /etc/pdns/pdns.conf + exec "$@"