Add a primary (write) Service routing to pod-0 #7
@@ -105,10 +105,18 @@ type BindClusterSpec struct {
|
||||
// +optional
|
||||
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
|
||||
|
||||
// Service controls how the cluster is exposed.
|
||||
// Service controls how the cluster is exposed for reads (all pods).
|
||||
// +optional
|
||||
Service ClusterServiceSpec `json:"service,omitempty"`
|
||||
|
||||
// PrimaryService, when set, creates an additional Service routing only to the
|
||||
// primary pod (ordinal 0) — the write endpoint for RFC2136/nsupdate, since
|
||||
// secondaries reject updates. Reads still use Service (all pods). Use
|
||||
// ClusterIP for in-cluster writers (e.g. external-dns) or LoadBalancer for
|
||||
// external writers.
|
||||
// +optional
|
||||
PrimaryService *ClusterServiceSpec `json:"primaryService,omitempty"`
|
||||
|
||||
// NodeSelector for the BIND pods.
|
||||
// +optional
|
||||
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
|
||||
|
||||
@@ -301,6 +301,11 @@ func (in *BindClusterSpec) DeepCopyInto(out *BindClusterSpec) {
|
||||
}
|
||||
in.Resources.DeepCopyInto(&out.Resources)
|
||||
in.Service.DeepCopyInto(&out.Service)
|
||||
if in.PrimaryService != nil {
|
||||
in, out := &in.PrimaryService, &out.PrimaryService
|
||||
*out = new(ClusterServiceSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.NodeSelector != nil {
|
||||
in, out := &in.NodeSelector, &out.NodeSelector
|
||||
*out = make(map[string]string, len(*in))
|
||||
|
||||
@@ -1013,6 +1013,41 @@ spec:
|
||||
type: string
|
||||
description: NodeSelector for the BIND pods.
|
||||
type: object
|
||||
primaryService:
|
||||
description: |-
|
||||
PrimaryService, when set, creates an additional Service routing only to the
|
||||
primary pod (ordinal 0) — the write endpoint for RFC2136/nsupdate, since
|
||||
secondaries reject updates. Reads still use Service (all pods). Use
|
||||
ClusterIP for in-cluster writers (e.g. external-dns) or LoadBalancer for
|
||||
external writers.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Annotations added to the client-facing Service (e.g.
|
||||
PureLB/MetalLB hints).
|
||||
type: object
|
||||
externalTrafficPolicy:
|
||||
description: |-
|
||||
ExternalTrafficPolicy for a LoadBalancer/NodePort Service. Local preserves
|
||||
client source IPs (required for source-IP ACLs on the DNS servers) but
|
||||
only routes to nodes running a pod. Defaults to Cluster.
|
||||
enum:
|
||||
- Cluster
|
||||
- Local
|
||||
type: string
|
||||
loadBalancerIP:
|
||||
description: LoadBalancerIP requests a specific address when Type
|
||||
is LoadBalancer.
|
||||
type: string
|
||||
type:
|
||||
description: Type of the client-facing Service. Defaults to ClusterIP.
|
||||
enum:
|
||||
- ClusterIP
|
||||
- LoadBalancer
|
||||
- NodePort
|
||||
type: string
|
||||
type: object
|
||||
recursion:
|
||||
description: |-
|
||||
Recursion overrides the default per-mode recursion setting. When nil,
|
||||
@@ -1086,7 +1121,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
service:
|
||||
description: Service controls how the cluster is exposed.
|
||||
description: Service controls how the cluster is exposed for reads
|
||||
(all pods).
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
|
||||
+37
-1
@@ -1318,6 +1318,41 @@ spec:
|
||||
type: string
|
||||
description: NodeSelector for the BIND pods.
|
||||
type: object
|
||||
primaryService:
|
||||
description: |-
|
||||
PrimaryService, when set, creates an additional Service routing only to the
|
||||
primary pod (ordinal 0) — the write endpoint for RFC2136/nsupdate, since
|
||||
secondaries reject updates. Reads still use Service (all pods). Use
|
||||
ClusterIP for in-cluster writers (e.g. external-dns) or LoadBalancer for
|
||||
external writers.
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: Annotations added to the client-facing Service (e.g.
|
||||
PureLB/MetalLB hints).
|
||||
type: object
|
||||
externalTrafficPolicy:
|
||||
description: |-
|
||||
ExternalTrafficPolicy for a LoadBalancer/NodePort Service. Local preserves
|
||||
client source IPs (required for source-IP ACLs on the DNS servers) but
|
||||
only routes to nodes running a pod. Defaults to Cluster.
|
||||
enum:
|
||||
- Cluster
|
||||
- Local
|
||||
type: string
|
||||
loadBalancerIP:
|
||||
description: LoadBalancerIP requests a specific address when Type
|
||||
is LoadBalancer.
|
||||
type: string
|
||||
type:
|
||||
description: Type of the client-facing Service. Defaults to ClusterIP.
|
||||
enum:
|
||||
- ClusterIP
|
||||
- LoadBalancer
|
||||
- NodePort
|
||||
type: string
|
||||
type: object
|
||||
recursion:
|
||||
description: |-
|
||||
Recursion overrides the default per-mode recursion setting. When nil,
|
||||
@@ -1391,7 +1426,8 @@ spec:
|
||||
type: object
|
||||
type: object
|
||||
service:
|
||||
description: Service controls how the cluster is exposed.
|
||||
description: Service controls how the cluster is exposed for reads
|
||||
(all pods).
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
|
||||
@@ -277,7 +277,52 @@ func (r *BindClusterReconciler) reconcileServices(ctx context.Context, c *bindv1
|
||||
if svcType == corev1.ServiceTypeLoadBalancer || svcType == corev1.ServiceTypeNodePort {
|
||||
client.Spec.ExternalTrafficPolicy = c.Spec.Service.ExternalTrafficPolicy
|
||||
}
|
||||
return r.upsertService(ctx, c, client)
|
||||
if err := r.upsertService(ctx, c, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Primary (write) Service: routes only to pod-0. Created when configured,
|
||||
// deleted when removed.
|
||||
return r.reconcilePrimaryService(ctx, c, dnsPorts)
|
||||
}
|
||||
|
||||
func (r *BindClusterReconciler) reconcilePrimaryService(ctx context.Context, c *bindv1alpha1.BindCluster, dnsPorts []corev1.ServicePort) error {
|
||||
name := primaryServiceName(c.Name)
|
||||
if c.Spec.PrimaryService == nil {
|
||||
var existing corev1.Service
|
||||
err := r.Get(ctx, types.NamespacedName{Namespace: c.Namespace, Name: name}, &existing)
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.IgnoreNotFound(r.Delete(ctx, &existing))
|
||||
}
|
||||
|
||||
ps := c.Spec.PrimaryService
|
||||
psType := ps.Type
|
||||
if psType == "" {
|
||||
psType = corev1.ServiceTypeClusterIP
|
||||
}
|
||||
svc := &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: c.Namespace,
|
||||
Labels: commonLabels(c.Name),
|
||||
Annotations: ps.Annotations,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: psType,
|
||||
Selector: primaryPodSelector(c.Name),
|
||||
Ports: dnsPorts,
|
||||
LoadBalancerIP: ps.LoadBalancerIP,
|
||||
},
|
||||
}
|
||||
if psType == corev1.ServiceTypeLoadBalancer || psType == corev1.ServiceTypeNodePort {
|
||||
svc.Spec.ExternalTrafficPolicy = ps.ExternalTrafficPolicy
|
||||
}
|
||||
return r.upsertService(ctx, c, svc)
|
||||
}
|
||||
|
||||
func (r *BindClusterReconciler) reconcileStatefulSet(ctx context.Context, c *bindv1alpha1.BindCluster) (*appsv1.StatefulSet, error) {
|
||||
|
||||
@@ -29,11 +29,20 @@ const (
|
||||
)
|
||||
|
||||
func headlessServiceName(cluster string) string { return cluster + "-headless" }
|
||||
func clientServiceName(cluster string) string { return cluster }
|
||||
func primaryPodName(cluster string) string { return cluster + "-0" }
|
||||
func configMapName(cluster string) string { return cluster + "-config" }
|
||||
func keysSecretName(cluster string) string { return cluster + "-keys" }
|
||||
func rndcSecretName(cluster string) string { return cluster + "-rndc" }
|
||||
func primaryServiceName(cluster string) string { return cluster + "-primary" }
|
||||
|
||||
// primaryPodSelector selects only the primary pod (ordinal 0) via the stable
|
||||
// StatefulSet pod-name label, for the write Service.
|
||||
func primaryPodSelector(cluster string) map[string]string {
|
||||
s := commonLabels(cluster)
|
||||
s["statefulset.kubernetes.io/pod-name"] = primaryPodName(cluster)
|
||||
return s
|
||||
}
|
||||
func clientServiceName(cluster string) string { return cluster }
|
||||
func primaryPodName(cluster string) string { return cluster + "-0" }
|
||||
func configMapName(cluster string) string { return cluster + "-config" }
|
||||
func keysSecretName(cluster string) string { return cluster + "-keys" }
|
||||
func rndcSecretName(cluster string) string { return cluster + "-rndc" }
|
||||
|
||||
// primaryAddress is the in-cluster DNS name of the primary pod (ordinal 0).
|
||||
func primaryAddress(cluster, namespace string) string {
|
||||
|
||||
Reference in New Issue
Block a user