From 7000a02188b4a8b1abe8c708747f1aab00571471 Mon Sep 17 00:00:00 2001 From: Marius Ensrud Date: Tue, 6 Jun 2023 13:16:12 +0200 Subject: [PATCH] powerdns --- charts/powerdns/0.1.11/.helmignore | 23 +++ charts/powerdns/0.1.11/Chart.yaml | 6 + charts/powerdns/0.1.11/README.md | 148 ++++++++++++++++++ charts/powerdns/0.1.11/templates/NOTES.txt | 35 +++++ charts/powerdns/0.1.11/templates/_helpers.tpl | 45 ++++++ charts/powerdns/0.1.11/templates/_metallb.tpl | 3 + .../0.1.11/templates/admin-deployment.yaml | 67 ++++++++ .../0.1.11/templates/admin-service.yaml | 19 +++ .../0.1.11/templates/db-deployment.yaml | 65 ++++++++ .../powerdns/0.1.11/templates/db-service.yaml | 19 +++ charts/powerdns/0.1.11/templates/ingress.yaml | 28 ++++ .../0.1.11/templates/mariadb-pvc.yaml | 15 ++ .../templates/powerdns-admin-secret.yaml | 18 +++ .../templates/powerdns-api-service.yaml | 17 ++ .../0.1.11/templates/powerdns-deployment.yaml | 103 ++++++++++++ .../templates/powerdns-dns-service-tcp.yaml | 28 ++++ .../templates/powerdns-dns-service-udp.yaml | 26 +++ .../0.1.11/templates/powerdns-secret.yaml | 17 ++ charts/powerdns/0.1.11/values.yaml | 121 ++++++++++++++ charts/powerdns/powerdns-0.1.11.tgz | Bin 0 -> 7385 bytes 20 files changed, 803 insertions(+) create mode 100644 charts/powerdns/0.1.11/.helmignore create mode 100644 charts/powerdns/0.1.11/Chart.yaml create mode 100644 charts/powerdns/0.1.11/README.md create mode 100644 charts/powerdns/0.1.11/templates/NOTES.txt create mode 100644 charts/powerdns/0.1.11/templates/_helpers.tpl create mode 100644 charts/powerdns/0.1.11/templates/_metallb.tpl create mode 100644 charts/powerdns/0.1.11/templates/admin-deployment.yaml create mode 100644 charts/powerdns/0.1.11/templates/admin-service.yaml create mode 100644 charts/powerdns/0.1.11/templates/db-deployment.yaml create mode 100644 charts/powerdns/0.1.11/templates/db-service.yaml create mode 100644 charts/powerdns/0.1.11/templates/ingress.yaml create mode 100644 charts/powerdns/0.1.11/templates/mariadb-pvc.yaml create mode 100644 charts/powerdns/0.1.11/templates/powerdns-admin-secret.yaml create mode 100644 charts/powerdns/0.1.11/templates/powerdns-api-service.yaml create mode 100644 charts/powerdns/0.1.11/templates/powerdns-deployment.yaml create mode 100644 charts/powerdns/0.1.11/templates/powerdns-dns-service-tcp.yaml create mode 100644 charts/powerdns/0.1.11/templates/powerdns-dns-service-udp.yaml create mode 100644 charts/powerdns/0.1.11/templates/powerdns-secret.yaml create mode 100644 charts/powerdns/0.1.11/values.yaml create mode 100644 charts/powerdns/powerdns-0.1.11.tgz diff --git a/charts/powerdns/0.1.11/.helmignore b/charts/powerdns/0.1.11/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/powerdns/0.1.11/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/powerdns/0.1.11/Chart.yaml b/charts/powerdns/0.1.11/Chart.yaml new file mode 100644 index 0000000..39f8b7d --- /dev/null +++ b/charts/powerdns/0.1.11/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +appVersion: 4.3.4 +description: A Helm chart for Kubernetes +name: powerdns +type: application +version: 0.1.11 diff --git a/charts/powerdns/0.1.11/README.md b/charts/powerdns/0.1.11/README.md new file mode 100644 index 0000000..cc59a70 --- /dev/null +++ b/charts/powerdns/0.1.11/README.md @@ -0,0 +1,148 @@ +# Powerdns +PowerDNS with Mariadb and PowerDNS-Admin Helm to easy PowerDNS deploy on Kubernetes + +[PowerDNS](https://www.powerdns.com/) is an open source DNS Authoritative Server (answer questions about domains it knows about, but will not go out on the net to resolve queries about other domains) software. + +## TL;DR; + +Supported for Helm v3 + +```console +helm repo add aecharts https://raw.githubusercontent.com/aescanero/helm-charts/master/ +helm repo update +helm install aecharts/powerdns +``` + +## Introduction + +This chart bootstraps a [pschiffe/docker-pdns](https://github.com/pschiffe/docker-pdns) deployment on a [Kubernetes](http://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +It also packages: +- [aescanero/docker-powerdns-admin-alpine](https://github.com/aescanero/docker-powerdns-admin-alpine) based in [ngoduykhanh/PowerDNS-Admin](https://github.com/ngoduykhanh/PowerDNS-Admin) which provide a dashboard for PowerDNS management. +- [mariadb](https://www.mariadb.org) which is required for bootstrapping a MariaDB deployment for the database requirements of PowerDNS and PowerDNS-Admin applications. + +## Prerequisites + +- Kubernetes 1.8+ with Beta APIs enabled +- PV provisioner support in the underlying infrastructure (Optional) + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```console +helm repo add aecharts https://raw.githubusercontent.com/aescanero/helm-charts/master/ +helm repo update +helm install my-release aecharts/powerdns +``` + +The command deploys PowerDNS on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-release` deployment: + +```console +$ helm delete my-release +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Configuration + +The following table lists the configurable parameters of the PowerDNS chart and their default values. + +| Parameter | Description | Default | +|-----------------------------------|--------------------------------------------|---------------------------------------------------------| +| `powerdns.enabled` | Deploy the DNS Server packaged with Helm | `true` | +| `powerdns.service.dns.tcp.enabled`| Enable DNS (TCP) Service | `false` | +| `powerdns.service.dns.tcp.port` | Port of the DNS (TCP) Service | `53` | +| `powerdns.service.dns.tcp.type` | Class of the Kubernetes DNS (TCP) Service | `LoadBalancer` | +| `powerdns.service.dns.tcp.loadBalancerIP` | Statically assign LoadBalancerIP (UDP) | `` | +| `powerdns.service.dns.tcp.annotations` | Annotations for service (TCP) | `` | +| `powerdns.service.dns.udp.enabled`| Enable DNS (UDP) Service | `true` | +| `powerdns.service.dns.udp.port` | Port of the DNS (UDP) Service | `53` | +| `powerdns.service.dns.udp.type` | Class of the Kubernetes DNS (UDP) Service | `LoadBalancer` | +| `powerdns.service.dns.udp.loadBalancerIP` | Statically assign LoadBalancerIP (TCP) | `` | +| `powerdns.service.dns.udp.annotations` | Annotations for service (UDP) | `` | +| `powerdns.service.api.type` | Class of the Kubernetes PowerDNSAPI Service| `ClusterIP` | +| `powerdns.service.api.port` | Port of the DNS Service | `53` | +| `powerdns.image.repository` | PowerDNS image name | `pschiffe/pdns-mysql` | +| `powerdns.image.tag` | PowerDNS image tag | `alpine` | +| `powerdns.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `powerdns.domain` | Automatically create a domain | `external.local` | +| `powerdns.master` | Deploy PowerDNS as master | `yes` | +| `powerdns.api` | Enable API for Management (need webserver) | `yes` | +| `powerdns.webserver` | Enable web server to publish API | `yes` | +| `powerdns.webserver_address` | IP where the web server is published | `0.0.0.0` | +| `powerdns.webserver_allow_from` | Allow access to web server only from | `0.0.0.0/0` | +| `powerdns.version_string` | Version to designate the DNS Server | `anonymous` | +| `powerdns.default_ttl` | time-to-live of the DNS resources | `1500` | +| `powerdns.soa_minimum_ttl` | Minimal time-to-live of SOA | `1200` | +| `powerdns.default_soa_name` | Name to designate the zone | `ns1.external.local` | +| `powerdns.mysql_host` | Host of the external database | `127.0.0.1` | +| `powerdns.mysql_database` | Name of the external database | `powerdns` | +| `powerdns.mysql_user` | User of the external database | `powerdns` | +| `powerdns.mysql_rootpass` | Password of the root user of external BD | `nil` | +| `powerdns.mysql_pass` | Password of the user | `nil` | +| `powerdns.innodb_read_committed` | Set powerdns option gmysql-innodb-read-commited | `no` | +| `powerdns.resources` | CPU/Memory resource requests/limits | Memory: `512Mi`, CPU: `300m` | +| `mariadb.enabled` | Deploy the Database packaged with Helm | `true` | +| `mariadb.image.repository` | MariaDB image name | `mariadb` | +| `mariadb.image.tag` | MariaDB image tag | `latest` | +| `mariadb.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `mariadb.mysql_rootpass` | Password of the root user of internal BD | `nil` | +| `mariadb.mysql_pass` | Password of the user | `nil` | +| `mariadb.persistence.enabled` | Enable persistence using PVC | `true` | +| `mariadb.persistence.storageClass`| PVC Storage Class for MariaDB volume | `nil` | +| `mariadb.persistence.accessMode` | PVC Access Mode for MariaDB volume | `ReadWriteOnce` | +| `mariadb.persistence.size` | PVC Storage Request for MariaDB volume | `1Gi` | +| `mariadb.resources` | CPU/Memory resource requests/limits | Memory: `512Mi`, CPU: `300m` | +| `mariadb.args` | mysqld arguments | `--bind-address=127.0.0.1 --innodb_use_native_aio=0 --innodb_flush_method=fsync` | +| `powerdnsadmin.enabled` | Deploy the Dashboard packaged with Helm | `true` | +| `powerdnsadmin.service.type` | Class of Kubernetes PowerDNS-Admin Service | `LoadBalancer` | +| `powerdnsadmin.service.port` | Port of the PowerDNS-Admin Service | `9191` | +| `powerdnsadmin.image.repository` | PowerDNS-Admin image name | `aescanero/powerdns-admin` | +| `powerdnsadmin.image.tag` | PowerDNS-Admin image tag | `latest` | +| `powerdnsadmin.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `powerdnsadmin.proto` | Protocol of PowerDNS-Admin Service | `http` | +| `powerdnsadmin.powerdns_host` | Where is PowerDNS Service | `127.0.0.1` | +| `powerdnsadmin.powerdns_port` | Port of the PowerDNS API Service | `8081` | +| `powerdnsadmin.mysql_host` | Host of the external database | `127.0.0.1` | +| `powerdnsadmin.mysql_database` | Name of the external database | `powerdns` | +| `powerdnsadmin.mysql_user` | User of the external database | `powerdns` | +| `powerdnsadmin.mysql_pass` | Password of the user | `nil` | +| `powerdnsadmin.resources` | CPU/Memory resource requests/limits | Memory: `512Mi`, CPU: `300m` | +| `powerdnsadmin.ingress.enabled` | Deploy the Dashboard with Ingress | `false` | +| `powerdnsadmin.ingress.class` | Class of Ingress | `traefik` | +| `powerdnsadmin.ingress.hostname` | Hostname without domain part | `powerdns-admin` | +| `powerdnsadmin.ingress.path` | Path within the url structure | `/` | + +The above parameters map to the env variables defined in each container. For more information please refer to each image documentation. + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +helm repo add aecharts https://raw.githubusercontent.com/aescanero/helm-charts/master/ +helm repo update +helm install powerdns-release --set domain=disasterproject.com aecharts/powerdns +``` + +The above command sets the domain managed by PowerDNS to `disasterproject.com`. + +Alternatively, a YAML file that specifies the values for the above parameters can be provided while installing the chart. For example, + +```console +helm repo add aecharts https://raw.githubusercontent.com/aescanero/helm-charts/master/ +helm repo update +helm install powerdns-release -f values.yaml aecharts/powerdns +``` + +## Persistence + +The [mariadb](https://www.mariadb.org) image stores the Database at `/var/lib/mysql` path of the container. + +Persistent Volume Claims are used to keep the data across deployments. + diff --git a/charts/powerdns/0.1.11/templates/NOTES.txt b/charts/powerdns/0.1.11/templates/NOTES.txt new file mode 100644 index 0000000..3b9d1b4 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/NOTES.txt @@ -0,0 +1,35 @@ +Access to the DNS Server and Dashboard by running these commands: +{{- if .Values.admin.ingress.enabled }} + http://{{ .Values.admin.ingress.hostname }}.{{ .Values.powerdns.domain }}{{ .Values.admin.ingress.path }} +{{- end }} +{{- if eq .Values.service.dns.tcp.type "NodePort" }} + export NODE_PORT=$(kubectl {{- if .Release.Namespace }} -n {{.Release.Namespace}} {{ end -}} get -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "powerdns.fullname" . }})-service-dns-tcp + export NODE_IP=$(kubectl {{- if .Release.Namespace }} -n {{.Release.Namespace}} {{ end -}} get nodes -o jsonpath="{.items[0].status.addresses[0].address}") + echo Point your DNS client to $NODE_IP:$NODE_PORT +{{- end }} +{{- if eq .Values.admin.service.type "NodePort" }} + export NODE_PORT=$(kubectl {{- if .Release.Namespace }} -n {{.Release.Namespace}} {{ end -}} get -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "powerdns.fullname" . }})-service-admin + export NODE_IP=$(kubectl {{- if .Release.Namespace }} -n {{.Release.Namespace}} {{ end -}} get nodes -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT + echo Dashboard access at http://$SERVICE_IP2:{{ .Values.admin.service.port }} +{{- end }} + +{{- if eq .Values.service.dns.tcp.type "LoadBalancer" }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl {{ if .Release.Namespace }} -n {{.Release.Namespace}} {{ end }} get svc -w {{ template "powerdns.fullname" . }}' + export SERVICE_IP1=$(kubectl {{ if .Release.Namespace }} -n {{.Release.Namespace}} {{ end }} get svc {{ template "powerdns.fullname" . }}-service-dns-tcp -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + export SERVICE_IP2=$(kubectl {{ if .Release.Namespace }} -n {{.Release.Namespace}} {{ end }} get svc {{ template "powerdns.fullname" . }}-service-admin -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo Point your DNS client to $SERVICE_IP1:{{ .Values.service.dns.tcp.port }} + echo Dashboard access at http://$SERVICE_IP2:{{ .Values.admin.service.port }} +{{- end }} + +{{- if contains "ClusterIP" .Values.service.dns.tcp.type }} + export POD_NAME=$(kubectl {{- if .Release.Namespace }} -n {{.Release.Namespace}} {{ end -}} get pods -l "app={{ template "powerdns.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Point your DNS client to 127.0.0.1:53" + kubectl port-forward $POD_NAME 53:53 +{{- end }} +{{- if contains "ClusterIP" .Values.admin.service.type }} + export POD_NAME=$(kubectl {{- if .Release.Namespace }} -n {{.Release.Namespace}} {{ end -}} get pods -l "app={{ template "powerdns.name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use the dashboard" + kubectl port-forward $POD_NAME 8080:80 +{{- end }} diff --git a/charts/powerdns/0.1.11/templates/_helpers.tpl b/charts/powerdns/0.1.11/templates/_helpers.tpl new file mode 100644 index 0000000..2bc49d4 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "powerdns.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "powerdns.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "powerdns.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "powerdns.labels" -}} +app.kubernetes.io/name: {{ include "powerdns.name" . }} +helm.sh/chart: {{ include "powerdns.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} diff --git a/charts/powerdns/0.1.11/templates/_metallb.tpl b/charts/powerdns/0.1.11/templates/_metallb.tpl new file mode 100644 index 0000000..7824697 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/_metallb.tpl @@ -0,0 +1,3 @@ +{{- define "metallb.pool.annotation" -}} +{{ printf "metallb.universe.tf/address-pool: %s" .Values.metallb.address_pool }} +{{- end -}} diff --git a/charts/powerdns/0.1.11/templates/admin-deployment.yaml b/charts/powerdns/0.1.11/templates/admin-deployment.yaml new file mode 100644 index 0000000..2154e61 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/admin-deployment.yaml @@ -0,0 +1,67 @@ +{{- if .Values.admin.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "powerdns.fullname" . }}-admin" + labels: + powerdns.com/role: admin +{{ include "powerdns.labels" . | indent 4 }} +spec: + replicas: {{ default .Values.replicaCount 1 }} + selector: + matchLabels: +{{ include "powerdns.labels" . | indent 6 }} + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: admin + template: + metadata: + labels: +{{ include "powerdns.labels" . | indent 8 }} + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: admin + spec: + containers: + - name: {{ .Chart.Name }}-powerdnsadmin + image: "{{ .Values.admin.image.repository }}:{{ .Values.admin.image.tag }}" + imagePullPolicy: {{ .Values.admin.image.pullPolicy }} + livenessProbe: + exec: + command: ["/bin/sh", "-c", "nc -vz 127.0.0.1 9191 2>/dev/null"] + initialDelaySeconds: 80 + resources: +{{ toYaml .Values.admin.resources | indent 12 }} + env: + - name: PDNS_API_KEY + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: PDNS_APIKEY + - name: PDNSADMIN_SECRET_KEY + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: PDNSADMIN_SECRET + - name: PDNS_PROTO + value: {{ .Values.admin.proto | quote }} + - name: PDNS_HOST + value: {{ template "powerdns.fullname" . }}-service-api + - name: PDNS_PORT + value: {{ .Values.admin.powerdns_port | quote }} + - name: PDNSADMIN_SQLA_DB_HOST + value: {{ template "powerdns.fullname" . }}-service-db + - name: PDNSADMIN_SQLA_DB_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: MYSQL_PASS + - name: PDNSADMIN_SQLA_DB_NAME + value: {{ .Values.admin.mysql_database | quote }} + - name: PDNSADMIN_SQLA_DB_USER + value: {{ .Values.admin.mysql_user | quote }} + ports: + - containerPort: 9191 + name: pdns-admin-http + protocol: TCP +{{- end }} diff --git a/charts/powerdns/0.1.11/templates/admin-service.yaml b/charts/powerdns/0.1.11/templates/admin-service.yaml new file mode 100644 index 0000000..9a99dd1 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/admin-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.admin.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "powerdns.fullname" . }}-service-admin + labels: +{{ include "powerdns.labels" . | indent 4 }} +spec: + type: {{ .Values.admin.service.type }} + ports: + - port: {{ .Values.admin.service.port }} + targetPort: pdns-admin-http + protocol: TCP + name: pdns-admin-http + selector: + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: admin +{{- end }} diff --git a/charts/powerdns/0.1.11/templates/db-deployment.yaml b/charts/powerdns/0.1.11/templates/db-deployment.yaml new file mode 100644 index 0000000..5bc30d3 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/db-deployment.yaml @@ -0,0 +1,65 @@ +{{- if .Values.mariadb.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "powerdns.fullname" . }}-db" + labels: + powerdns.com/role: db +{{ include "powerdns.labels" . | indent 4 }} +spec: + replicas: {{ coalesce .Values.mariadb.replicaCount 1 }} + selector: + matchLabels: +{{ include "powerdns.labels" . | indent 6 }} + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: db + template: + metadata: + labels: +{{ include "powerdns.labels" . | indent 8 }} + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: db + spec: + containers: + - name: mariadb + image: "{{ .Values.mariadb.image.repository }}:{{ .Values.mariadb.image.tag }}" + imagePullPolicy: {{ .Values.mariadb.image.pullPolicy }} + resources: +{{ toYaml .Values.mariadb.resources | indent 12 }} +{{- if .Values.mariadb.args }} + args: +{{- range .Values.mariadb.args }} + - {{ . | quote }} +{{- end }} +{{- end }} + env: + - name: MYSQL_INITDB_SKIP_TZINFO + value: "1" + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "powerdns.fullname" . }}-secret + key: MYSQL_PASS + - name: MYSQL_DATABASE + value: {{ .Values.powerdns.mysql_database | quote }} + - name: MYSQL_USER + value: {{ .Values.powerdns.mysql_user | quote }} + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "powerdns.fullname" . }}-secret + key: MYSQL_PASS +{{- if .Values.mariadb.persistence.enabled }} + volumeMounts: + - name: data + mountPath: /var/lib/mysql +{{- end }} +{{- if .Values.mariadb.persistence.enabled }} + volumes: + - name: data + persistentVolumeClaim: + claimName: "pvc-{{ template "powerdns.fullname" . }}-mariadb" +{{- end }} +{{ end -}} diff --git a/charts/powerdns/0.1.11/templates/db-service.yaml b/charts/powerdns/0.1.11/templates/db-service.yaml new file mode 100644 index 0000000..11cc46a --- /dev/null +++ b/charts/powerdns/0.1.11/templates/db-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.mariadb.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "powerdns.fullname" . }}-service-db + labels: +{{ include "powerdns.labels" . | indent 4 }} +spec: + type: ClusterIP + ports: + - port: 3306 + name: db + protocol: TCP + targetPort: 3306 + selector: + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: db +{{ end -}} diff --git a/charts/powerdns/0.1.11/templates/ingress.yaml b/charts/powerdns/0.1.11/templates/ingress.yaml new file mode 100644 index 0000000..489a968 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/ingress.yaml @@ -0,0 +1,28 @@ +{{- if .Values.admin.ingress.enabled -}} +{{- $fullName := include "powerdns.fullname" . -}} +{{- $svcPort := .Values.admin.service.port -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $fullName }} +{{- if eq .Values.admin.ingress.class "traefik" }} + annotations: + kubernetes.io/ingress.class: traefik + traefik.ingress.kubernetes.io/frontend-entry-points: http,https + traefik.ingress.kubernetes.io/redirect-entry-point: https + traefik.ingress.kubernetes.io/redirect-permanent: "true" +{{- end }} +{{- if eq .Values.admin.ingress.class "nginx" }} + annotations: + kubernetes.io/ingress.class: nginx +{{- end }} +spec: + rules: + - host: {{ .Values.admin.ingress.hostname }}.{{ .Values.powerdns.domain }} + http: + paths: + - path: {{ .Values.admin.ingress.path | quote }} + backend: + serviceName: {{ $fullName }}-service-admin + servicePort: {{ $svcPort }} +{{- end }} diff --git a/charts/powerdns/0.1.11/templates/mariadb-pvc.yaml b/charts/powerdns/0.1.11/templates/mariadb-pvc.yaml new file mode 100644 index 0000000..109b18a --- /dev/null +++ b/charts/powerdns/0.1.11/templates/mariadb-pvc.yaml @@ -0,0 +1,15 @@ +{{ if .Values.mariadb.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: pvc-{{ template "powerdns.fullname" . }}-mariadb +spec: + accessModes: + - {{ .Values.mariadb.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.mariadb.persistence.size }} +{{ if .Values.mariadb.persistence.storageClass }} + storageClassName: {{ .Values.mariadb.persistence.storageClass }} +{{ end }} +{{ end }} diff --git a/charts/powerdns/0.1.11/templates/powerdns-admin-secret.yaml b/charts/powerdns/0.1.11/templates/powerdns-admin-secret.yaml new file mode 100644 index 0000000..4bdc02c --- /dev/null +++ b/charts/powerdns/0.1.11/templates/powerdns-admin-secret.yaml @@ -0,0 +1,18 @@ +{{ if .Values.admin.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "powerdns.fullname" . }}-admin-secret + labels: +{{ include "powerdns.labels" . | indent 4}} + release: {{ .Release.Name }} + chart: {{ template "powerdns.chart" . }} +type: Opaque +data: + PDNS_APIKEY: {{ required "Missing value: apikey" .Values.apikey | b64enc }} + MYSQL_PASS: {{ required "Missing value: mariadb.mysql_pass" .Values.mariadb.mysql_pass | b64enc }} + {{ if .Values.mariadb.mysql_rootpass }} + MYSQL_ROOTPASS: {{ .Values.mariadb.mysql_rootpass | b64enc }} + {{ end }} + PDNSADMIN_SECRET: {{ required "Missing value: .admin.secret" .Values.admin.secret | b64enc }} +{{ end }} diff --git a/charts/powerdns/0.1.11/templates/powerdns-api-service.yaml b/charts/powerdns/0.1.11/templates/powerdns-api-service.yaml new file mode 100644 index 0000000..fc2f72c --- /dev/null +++ b/charts/powerdns/0.1.11/templates/powerdns-api-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "powerdns.fullname" . }}-service-api + labels: +{{ include "powerdns.labels" . | indent 4 }} +spec: + type: {{ .Values.service.api.type }} + ports: + - port: {{ .Values.service.api.port }} + targetPort: api + protocol: TCP + name: api + selector: + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: api diff --git a/charts/powerdns/0.1.11/templates/powerdns-deployment.yaml b/charts/powerdns/0.1.11/templates/powerdns-deployment.yaml new file mode 100644 index 0000000..f4e0e6b --- /dev/null +++ b/charts/powerdns/0.1.11/templates/powerdns-deployment.yaml @@ -0,0 +1,103 @@ +{{- $db_host := coalesce .Values.powerdns.mysql_host (printf "%s-service-db" (include "powerdns.fullname" . )) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ template "powerdns.fullname" . }}" + labels: +{{ include "powerdns.labels" . | indent 4 }} + powerdns.com/role: api +spec: + replicas: {{ default .Values.replicaCount 1 }} + selector: + matchLabels: +{{ include "powerdns.labels" . | indent 6 }} + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: api + template: + metadata: + labels: +{{ include "powerdns.labels" . | indent 8 }} + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: api + spec: + containers: + - name: powerdns + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + livenessProbe: + exec: + command: ["/bin/sh", "-c", "pdnsutil list-zone {{ .Values.domain }}"] + readinessProbe: + exec: + command: ["/bin/sh", "-c", "nc -vz {{ $db_host }} 3306"] + initialDelaySeconds: 20 + lifecycle: + postStart: + exec: + command: + - "/bin/sh" + - "-c" + - "a=0;while [ $a -lt 200 ];do sleep 1;a=$[a+1];echo 'stage: '$a;if nc -vz {{ $db_host }} 3306;then (! pdnsutil list-zone {{ .Values.powerdns.domain }}) && pdnsutil create-zone {{ .Values.powerdns.domain }};echo 'End Stage';a=200;fi;done" + resources: +{{ toYaml .Values.powerdns.resources | indent 12 }} + env: + - name: PDNS_api_key + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: PDNS_APIKEY + - name: PDNS_master + value: {{ .Values.powerdns.master | quote}} + - name: PDNS_api + value: {{ .Values.powerdns.api | quote }} + - name: PDNS_webserver + value: {{ .Values.powerdns.webserver | quote }} + - name: PDNS_webserver_address + value: {{ .Values.powerdns.webserver_address | quote }} + - name: PDNS_webserver_allow_from + value: {{ .Values.powerdns.webserver_allow_from | quote }} + - name: PDNS_webserver_password + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: PDNS_APIKEY + - name: PDNS_version_string + value: {{ .Values.powerdns.version_string | quote }} + - name: PDNS_default_ttl + value: {{ .Values.powerdns.default_ttl | quote }} + - name: PDNS_soa_minimum_ttl + value: {{ .Values.powerdns.soa_minimum_ttl | quote }} + - name: PDNS_default_soa_name + value: "ns1.{{ .Values.powerdns.domain }}" + - name: PDNS_default_soa_mail + value: "hostmaster.{{ .Values.powerdns.domain }}" + - name: PDNS_gmysql_innodb_read_committed + value: {{ .Values.powerdns.innodb_read_committed | quote }} + - name: MYSQL_ENV_MYSQL_HOST + value: {{ quote $db_host }} + - name: MYSQL_ENV_MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: MYSQL_PASS + - name: MYSQL_ENV_MYSQL_DATABASE + value: {{ .Values.powerdns.mysql_database | quote }} + - name: MYSQL_ENV_MYSQL_USER + value: {{ .Values.powerdns.mysql_user | quote }} + - name: MYSQL_ENV_MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: "{{ template "powerdns.fullname" . }}-secret" + key: MYSQL_PASS + ports: + - containerPort: 53 + name: dns-udp + protocol: UDP + - containerPort: 53 + name: dns-tcp + protocol: TCP + - containerPort: 8081 + name: api + protocol: TCP diff --git a/charts/powerdns/0.1.11/templates/powerdns-dns-service-tcp.yaml b/charts/powerdns/0.1.11/templates/powerdns-dns-service-tcp.yaml new file mode 100644 index 0000000..92d012e --- /dev/null +++ b/charts/powerdns/0.1.11/templates/powerdns-dns-service-tcp.yaml @@ -0,0 +1,28 @@ +{{- if .Values.service.dns.tcp.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "powerdns.fullname" . }}-service-dns-tcp + labels: +{{ include "powerdns.labels" . | indent 4 }} +{{- with .Values.service.dns.tcp.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +{{ template "metallb.address.pool" . | indent 4}} +spec: + type: {{ .Values.service.dns.tcp.type }} + {{- if .Values.service.dns.tcp.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.dns.tcp.loadBalancerIP }} + {{- end }} + ports: + - port: {{ .Values.service.dns.tcp.port }} + targetPort: dns-tcp + protocol: TCP + name: dns-tcp + selector: + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: api +{{- end }} + diff --git a/charts/powerdns/0.1.11/templates/powerdns-dns-service-udp.yaml b/charts/powerdns/0.1.11/templates/powerdns-dns-service-udp.yaml new file mode 100644 index 0000000..73ad339 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/powerdns-dns-service-udp.yaml @@ -0,0 +1,26 @@ +{{- if .Values.service.dns.udp.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "powerdns.fullname" . }}-service-dns-udp + labels: +{{ include "powerdns.labels" . | indent 4 }} +{{- with .Values.service.dns.udp.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: + type: {{ .Values.service.dns.udp.type }} + {{- if .Values.service.dns.udp.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.dns.udp.loadBalancerIP }} + {{- end }} + ports: + - port: {{ .Values.service.dns.udp.port }} + targetPort: dns-udp + protocol: UDP + name: dns-udp + selector: + app.kubernetes.io/name: {{ include "powerdns.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + powerdns.com/role: api +{{- end }} diff --git a/charts/powerdns/0.1.11/templates/powerdns-secret.yaml b/charts/powerdns/0.1.11/templates/powerdns-secret.yaml new file mode 100644 index 0000000..3b2f5b9 --- /dev/null +++ b/charts/powerdns/0.1.11/templates/powerdns-secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "powerdns.fullname" . }}-secret + labels: +{{ include "powerdns.labels" . | indent 4}} + release: {{ .Release.Name }} + chart: {{ template "powerdns.chart" . }} +type: Opaque +data: + PDNS_APIKEY: {{ required "Missing variable: apikey" .Values.apikey | b64enc }} + MYSQL_PASS: {{ required "Missing variable: mariadb.mysql_pass" .Values.mariadb.mysql_pass | b64enc }} + {{ if .Values.mariadb.mysql_rootpass }} + MYSQL_ROOTPASS: {{ .Values.mariadb.mysql_rootpass | b64enc }} + {{ end }} + PDNSADMIN_SECRET: {{ required "Missing variable: powerdnsadmin.secret" .Values.admin.secret | b64enc }} + diff --git a/charts/powerdns/0.1.11/values.yaml b/charts/powerdns/0.1.11/values.yaml new file mode 100644 index 0000000..f7ec4c8 --- /dev/null +++ b/charts/powerdns/0.1.11/values.yaml @@ -0,0 +1,121 @@ +#Author: aescanero@disasterproject.com +#Website: www.disasterproject.com +#Version: 0.3 + +enabled: true +service: + dns: + tcp: + enabled: false + type: LoadBalancer + port: 53 + annotations: + udp: + enabled: true + type: LoadBalancer + port: 53 + annotations: + api: + type: ClusterIP + port: 8081 +image: + repository: pschiffe/pdns-mysql + tag: alpine + pullPolicy: IfNotPresent +domain: external.local +master: "yes" +api: "yes" +version_string: "anonymous" +default_ttl: "1500" +soa_minimum_ttl: "1200" +default_soa_name: "ns1.external.local" +apikey: foobarbaz + +metallb: + address_pool: default + +powerdns: + enabled: true + image: + repository: pschiffe/pdns-mysql + tag: alpine + pullPolicy: IfNotPresent + domain: external.local + master: "yes" + api: "yes" + webserver: "yes" + webserver_address: "0.0.0.0" + webserver_allow_from: "0.0.0.0/0" + version_string: "anonymous" + default_ttl: "1500" + soa_minimum_ttl: "1200" + default_soa_name: "ns1.external.local" + mysql_host: "127.0.0.1" + mysql_database: "powerdns" + mysql_user: "powerdns" + innodb_read_committed: "no" + requests: + memory: "512Mi" + cpu: "300m" + limits: + memory: "512Mi" + cpu: "300m" + +mariadb: + enabled: true + image: + repository: mariadb + tag: latest + pullPolicy: IfNotPresent + persistence: + enabled: false + accessMode: 'ReadWriteOnce' + size: '1Gi' + args: + - --bind-address=0.0.0.0 + - --innodb_use_native_aio=0 + - --innodb_flush_method=fsync + requests: + memory: "512Mi" + cpu: "300m" + limits: + memory: "512Mi" + cpu: "300m" + mysql_pass: changeme + +admin: + enabled: true + service: + type: LoadBalancer + port: 9191 + image: + repository: aescanero/powerdns-admin + tag: latest + pullPolicy: IfNotPresent + proto: "http" + powerdns_host: "127.0.0.1" + powerdns_port: "8081" + mysql_host: powerdns-db + mysql_database: "powerdns" + mysql_user: "powerdns" + ingress: + enabled: "false" + class: "traefik" + path: "/" + hostname: "powerdns-admin" + requests: + memory: "512Mi" + cpu: "300m" + limits: + memory: "512Mi" + cpu: "300m" + secret: changeme + +nameOverride: "" +fullnameOverride: "" + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/charts/powerdns/powerdns-0.1.11.tgz b/charts/powerdns/powerdns-0.1.11.tgz new file mode 100644 index 0000000000000000000000000000000000000000..10c39934cdcad5ff92737f547be48b36421d8036 GIT binary patch literal 7385 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 Lh;R_90KxzODs7wB literal 0 HcmV?d00001