Target upstream ISC bind9 image
Uses internetsystemsconsortium/bind9 as the default base image instead of a self-hosted one, verified against internetsystemsconsortium/bind9:9.20 (runs as root; named/rndc/nsupdate at /usr/sbin,/usr/sbin,/usr/bin). - project operator config at /etc/bind-operator instead of overmounting the image's /etc/bind (keeps bind.keys / base config intact) - reference named/rndc/nsupdate by absolute path (exec PATH may exclude /usr/sbin) - centralise filesystem + binary paths in internal/bind/consts.go - default spec.image to internetsystemsconsortium/bind9:9.20
This commit is contained in:
@@ -90,8 +90,10 @@ on pull requests; pushing a `v*` tag builds and pushes
|
|||||||
## Notes & caveats
|
## Notes & caveats
|
||||||
|
|
||||||
- The BIND container image (`spec.image`, default
|
- The BIND container image (`spec.image`, default
|
||||||
`git.unkin.net/unkin/bind9:latest`) must ship `named`, `rndc` and `nsupdate`,
|
`internetsystemsconsortium/bind9:9.20`) must ship `named`, `rndc` and
|
||||||
read `/run/named/named.conf`, and honour the operator's `/etc/bind` layout.
|
`nsupdate`. The operator projects its config at `/etc/bind-operator` (leaving
|
||||||
|
the image's own `/etc/bind`, including `bind.keys`, intact) and runs
|
||||||
|
`named -g -c /run/named/named.conf`.
|
||||||
- Dynamic updates authenticate with `nsupdate -y`; the TSIG secret is passed on
|
- Dynamic updates authenticate with `nsupdate -y`; the TSIG secret is passed on
|
||||||
the argv of an exec'd process inside the pod.
|
the argv of an exec'd process inside the pod.
|
||||||
- RPZ IP-trigger encodings (`ip`, `client-ip`, `nsip`) are emitted verbatim;
|
- RPZ IP-trigger encodings (`ip`, `client-ip`, `nsip`) are emitted verbatim;
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ type BindClusterSpec struct {
|
|||||||
// +optional
|
// +optional
|
||||||
Replicas int32 `json:"replicas,omitempty"`
|
Replicas int32 `json:"replicas,omitempty"`
|
||||||
|
|
||||||
// Image is the BIND9 container image.
|
// Image is the BIND9 container image. Must ship named, rndc and nsupdate.
|
||||||
// +kubebuilder:default="git.unkin.net/unkin/bind9:latest"
|
// +kubebuilder:default="internetsystemsconsortium/bind9:9.20"
|
||||||
// +optional
|
// +optional
|
||||||
Image string `json:"image,omitempty"`
|
Image string `json:"image,omitempty"`
|
||||||
|
|
||||||
|
|||||||
@@ -994,8 +994,9 @@ spec:
|
|||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
image:
|
image:
|
||||||
default: git.unkin.net/unkin/bind9:latest
|
default: internetsystemsconsortium/bind9:9.20
|
||||||
description: Image is the BIND9 container image.
|
description: Image is the BIND9 container image. Must ship named,
|
||||||
|
rndc and nsupdate.
|
||||||
type: string
|
type: string
|
||||||
imagePullPolicy:
|
imagePullPolicy:
|
||||||
description: ImagePullPolicy for the BIND container.
|
description: ImagePullPolicy for the BIND container.
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package bind
|
||||||
|
|
||||||
|
// Filesystem layout and binary locations inside a BIND pod. The operator mounts
|
||||||
|
// its rendered config at ConfigDir (a path distinct from the image's own
|
||||||
|
// /etc/bind, so the base image's bind.keys trust anchors remain available for
|
||||||
|
// dnssec-validation).
|
||||||
|
const (
|
||||||
|
// ContainerName is the BIND container name within each pod.
|
||||||
|
ContainerName = "bind"
|
||||||
|
|
||||||
|
// ConfigDir is where the operator projects named.conf, keys and the
|
||||||
|
// entrypoint. Kept separate from the image's /etc/bind.
|
||||||
|
ConfigDir = "/etc/bind-operator"
|
||||||
|
|
||||||
|
// DataDir is BIND's writable working directory (backed by the PVC): zone
|
||||||
|
// databases and journals.
|
||||||
|
DataDir = "/var/lib/named"
|
||||||
|
|
||||||
|
// RunDir holds the ordinal-selected named.conf (writable emptyDir).
|
||||||
|
RunDir = "/run/named"
|
||||||
|
|
||||||
|
// Binary locations in the ISC BIND9 image (Debian/Ubuntu layout).
|
||||||
|
NamedBin = "/usr/sbin/named"
|
||||||
|
RndcBin = "/usr/sbin/rndc"
|
||||||
|
NsupdateBin = "/usr/bin/nsupdate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config file paths derived from ConfigDir.
|
||||||
|
const (
|
||||||
|
NamedConfRun = RunDir + "/named.conf"
|
||||||
|
NamedConfPrimary = ConfigDir + "/named.conf.primary"
|
||||||
|
NamedConfSecondary = ConfigDir + "/named.conf.secondary"
|
||||||
|
EntrypointPath = ConfigDir + "/entrypoint.sh"
|
||||||
|
KeysConfPath = ConfigDir + "/keys.conf"
|
||||||
|
RndcKeyPath = ConfigDir + "/rndc.key"
|
||||||
|
RndcConfPath = ConfigDir + "/rndc.conf"
|
||||||
|
)
|
||||||
@@ -14,9 +14,6 @@ import (
|
|||||||
"k8s.io/client-go/tools/remotecommand"
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerName is the BIND container name within each pod.
|
|
||||||
const ContainerName = "bind"
|
|
||||||
|
|
||||||
// Executor runs commands inside BIND pods via the exec subresource.
|
// Executor runs commands inside BIND pods via the exec subresource.
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
config *rest.Config
|
config *rest.Config
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (e *Executor) NSUpdate(ctx context.Context, namespace, pod, zone string, cr
|
|||||||
}
|
}
|
||||||
b.WriteString("send\n")
|
b.WriteString("send\n")
|
||||||
|
|
||||||
cmd := []string{"nsupdate", "-y", fmt.Sprintf("%s:%s:%s", creds.Algorithm, creds.Name, creds.Secret)}
|
cmd := []string{NsupdateBin, "-y", fmt.Sprintf("%s:%s:%s", creds.Algorithm, creds.Name, creds.Secret)}
|
||||||
if out, err := e.Exec(ctx, namespace, pod, cmd, b.String()); err != nil {
|
if out, err := e.Exec(ctx, namespace, pod, cmd, b.String()); err != nil {
|
||||||
return fmt.Errorf("nsupdate zone %s: %w (out: %s)", zone, err, out)
|
return fmt.Errorf("nsupdate zone %s: %w (out: %s)", zone, err, out)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,6 @@ type RenderInput struct {
|
|||||||
PrimaryAddress string
|
PrimaryAddress string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DataDir is where BIND keeps zone databases and journals (backed by the PVC).
|
|
||||||
const DataDir = "/var/lib/named"
|
|
||||||
|
|
||||||
// RenderNamedConf returns the primary and secondary named.conf contents for a
|
// RenderNamedConf returns the primary and secondary named.conf contents for a
|
||||||
// cluster. Both variants are shipped in the ConfigMap; the entrypoint selects
|
// cluster. Both variants are shipped in the ConfigMap; the entrypoint selects
|
||||||
// one based on the pod ordinal.
|
// one based on the pod ordinal.
|
||||||
@@ -35,7 +32,8 @@ func render(in RenderInput, isPrimary bool) string {
|
|||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
|
|
||||||
b.WriteString("// Managed by bind-operator. Do not edit.\n")
|
b.WriteString("// Managed by bind-operator. Do not edit.\n")
|
||||||
b.WriteString(`include "/etc/bind/keys/keys.conf";` + "\n\n")
|
b.WriteString(fmt.Sprintf("include \"%s\";\n", RndcKeyPath))
|
||||||
|
b.WriteString(fmt.Sprintf("include \"%s\";\n\n", KeysConfPath))
|
||||||
|
|
||||||
// Named ACLs (global scope).
|
// Named ACLs (global scope).
|
||||||
acls := append([]bindv1alpha1.BindACL(nil), in.ACLs...)
|
acls := append([]bindv1alpha1.BindACL(nil), in.ACLs...)
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RndcConfPath is the operator-managed rndc client config mounted in each pod.
|
|
||||||
const RndcConfPath = "/etc/bind/rndc.conf"
|
|
||||||
|
|
||||||
// Rndc runs `rndc <args...>` on a pod and returns its output.
|
// Rndc runs `rndc <args...>` on a pod and returns its output.
|
||||||
func (e *Executor) Rndc(ctx context.Context, namespace, pod string, args ...string) (string, error) {
|
func (e *Executor) Rndc(ctx context.Context, namespace, pod string, args ...string) (string, error) {
|
||||||
base := []string{"rndc", "-c", RndcConfPath}
|
base := []string{RndcBin, "-c", RndcConfPath}
|
||||||
return e.Exec(ctx, namespace, pod, append(base, args...), "")
|
return e.Exec(ctx, namespace, pod, append(base, args...), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (r *BindClusterReconciler) reconcileRNDCSecret(ctx context.Context, c *bind
|
|||||||
return genErr
|
return genErr
|
||||||
}
|
}
|
||||||
keyClause := bind.KeyClause("rndc-key", "hmac-sha256", secret)
|
keyClause := bind.KeyClause("rndc-key", "hmac-sha256", secret)
|
||||||
rndcConf := fmt.Sprintf("include \"/etc/bind/rndc.key\";\noptions {\n default-key \"rndc-key\";\n default-server 127.0.0.1;\n default-port 953;\n};\n")
|
rndcConf := fmt.Sprintf("include \"%s\";\noptions {\n default-key \"rndc-key\";\n default-server 127.0.0.1;\n default-port 953;\n};\n", bind.RndcKeyPath)
|
||||||
s := &corev1.Secret{
|
s := &corev1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: c.Namespace, Labels: commonLabels(c.Name)},
|
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: c.Namespace, Labels: commonLabels(c.Name)},
|
||||||
Data: map[string][]byte{
|
Data: map[string][]byte{
|
||||||
@@ -259,7 +259,7 @@ func (r *BindClusterReconciler) reconcileStatefulSet(ctx context.Context, c *bin
|
|||||||
replicas := c.Spec.Replicas
|
replicas := c.Spec.Replicas
|
||||||
image := c.Spec.Image
|
image := c.Spec.Image
|
||||||
if image == "" {
|
if image == "" {
|
||||||
image = "git.unkin.net/unkin/bind9:latest"
|
image = defaultBindImage
|
||||||
}
|
}
|
||||||
storageSize := c.Spec.StorageSize
|
storageSize := c.Spec.StorageSize
|
||||||
if storageSize == "" {
|
if storageSize == "" {
|
||||||
@@ -295,15 +295,15 @@ func (r *BindClusterReconciler) reconcileStatefulSet(ctx context.Context, c *bin
|
|||||||
Name: bind.ContainerName,
|
Name: bind.ContainerName,
|
||||||
Image: image,
|
Image: image,
|
||||||
ImagePullPolicy: c.Spec.ImagePullPolicy,
|
ImagePullPolicy: c.Spec.ImagePullPolicy,
|
||||||
Command: []string{"/bin/sh", "/etc/bind/entrypoint.sh"},
|
Command: []string{"/bin/sh", bind.EntrypointPath},
|
||||||
Ports: []corev1.ContainerPort{
|
Ports: []corev1.ContainerPort{
|
||||||
{Name: "dns-udp", ContainerPort: 53, Protocol: corev1.ProtocolUDP},
|
{Name: "dns-udp", ContainerPort: 53, Protocol: corev1.ProtocolUDP},
|
||||||
{Name: "dns-tcp", ContainerPort: 53, Protocol: corev1.ProtocolTCP},
|
{Name: "dns-tcp", ContainerPort: 53, Protocol: corev1.ProtocolTCP},
|
||||||
},
|
},
|
||||||
Resources: c.Spec.Resources,
|
Resources: c.Spec.Resources,
|
||||||
VolumeMounts: []corev1.VolumeMount{
|
VolumeMounts: []corev1.VolumeMount{
|
||||||
{Name: "bind-etc", MountPath: "/etc/bind", ReadOnly: true},
|
{Name: "bind-etc", MountPath: bind.ConfigDir, ReadOnly: true},
|
||||||
{Name: "run", MountPath: "/run/named"},
|
{Name: "run", MountPath: bind.RunDir},
|
||||||
{Name: "data", MountPath: bind.DataDir},
|
{Name: "data", MountPath: bind.DataDir},
|
||||||
},
|
},
|
||||||
ReadinessProbe: &corev1.Probe{
|
ReadinessProbe: &corev1.Probe{
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ const (
|
|||||||
clusterLabel = "bind.unkin.net/cluster"
|
clusterLabel = "bind.unkin.net/cluster"
|
||||||
|
|
||||||
finalizer = "bind.unkin.net/finalizer"
|
finalizer = "bind.unkin.net/finalizer"
|
||||||
|
|
||||||
|
// defaultBindImage is used when BindCluster.spec.image is empty.
|
||||||
|
defaultBindImage = "internetsystemsconsortium/bind9:9.20"
|
||||||
)
|
)
|
||||||
|
|
||||||
func headlessServiceName(cluster string) string { return cluster + "-headless" }
|
func headlessServiceName(cluster string) string { return cluster + "-headless" }
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
|
||||||
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
|
bindv1alpha1 "git.unkin.net/unkin/bind-operator/api/v1alpha1"
|
||||||
|
"git.unkin.net/unkin/bind-operator/internal/bind"
|
||||||
)
|
)
|
||||||
|
|
||||||
func intstrFromInt(i int) intstr.IntOrString { return intstr.FromInt(i) }
|
func intstrFromInt(i int) intstr.IntOrString { return intstr.FromInt(i) }
|
||||||
@@ -26,17 +28,17 @@ func podReady(pod *corev1.Pod) bool {
|
|||||||
// entrypointScript selects the primary or secondary named.conf based on the
|
// entrypointScript selects the primary or secondary named.conf based on the
|
||||||
// pod's StatefulSet ordinal and launches named in the foreground.
|
// pod's StatefulSet ordinal and launches named in the foreground.
|
||||||
func entrypointScript() string {
|
func entrypointScript() string {
|
||||||
return `#!/bin/sh
|
return fmt.Sprintf(`#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
ORD="${HOSTNAME##*-}"
|
ORD="${HOSTNAME##*-}"
|
||||||
if [ "$ORD" = "0" ]; then
|
if [ "$ORD" = "0" ]; then
|
||||||
cp /etc/bind/named.conf.primary /run/named/named.conf
|
cp %[1]s %[3]s
|
||||||
else
|
else
|
||||||
cp /etc/bind/named.conf.secondary /run/named/named.conf
|
cp %[2]s %[3]s
|
||||||
fi
|
fi
|
||||||
mkdir -p /var/lib/named/zones /var/lib/named/catalog
|
mkdir -p %[4]s/zones %[4]s/catalog
|
||||||
exec named -g -c /run/named/named.conf
|
exec %[5]s -g -c %[3]s
|
||||||
`
|
`, bind.NamedConfPrimary, bind.NamedConfSecondary, bind.NamedConfRun, bind.DataDir, bind.NamedBin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BindClusterReconciler) upsertService(ctx context.Context, c *bindv1alpha1.BindCluster, desired *corev1.Service) error {
|
func (r *BindClusterReconciler) upsertService(ctx context.Context, c *bindv1alpha1.BindCluster, desired *corev1.Service) error {
|
||||||
|
|||||||
Reference in New Issue
Block a user