Skip to content

Commit

Permalink
feat: Create ClusterIP Service for clients access to cluster (#44)
Browse files Browse the repository at this point in the history
in order to access cluster from clients using one IP we decided to use
ClusterIP Service
  • Loading branch information
sircthulhu authored Mar 19, 2024
1 parent 921377a commit a6eb964
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 1 deletion.
53 changes: 53 additions & 0 deletions internal/controller/etcdcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ func (r *EtcdClusterReconciler) ensureClusterObjects(
if err := r.ensureClusterStatefulSet(ctx, cluster); err != nil {
return err
}
// 3. create or update ClusterIP Service
if err := r.ensureClusterClientService(ctx, cluster); err != nil {
return err
}

return nil
}
Expand Down Expand Up @@ -196,6 +200,51 @@ func (r *EtcdClusterReconciler) ensureClusterService(ctx context.Context, cluste
return nil
}

func (r *EtcdClusterReconciler) ensureClusterClientService(ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster) error {
svc := &corev1.Service{}
err := r.Get(ctx, client.ObjectKey{
Namespace: cluster.Namespace,
Name: r.getClientServiceName(cluster),
}, svc)
// Service exists, skip creation
if err == nil {
return nil
}
if !errors.IsNotFound(err) {
return fmt.Errorf("cannot get cluster client service: %w", err)
}

svc = &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: r.getClientServiceName(cluster),
Namespace: cluster.Namespace,
Labels: map[string]string{
"app.kubernetes.io/name": "etcd",
"app.kubernetes.io/instance": cluster.Name,
"app.kubernetes.io/managed-by": "etcd-operator",
},
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{Name: "client", TargetPort: intstr.FromInt32(2379), Port: 2379, Protocol: corev1.ProtocolTCP},
},
Type: corev1.ServiceTypeClusterIP,
Selector: map[string]string{
"app.kubernetes.io/name": "etcd",
"app.kubernetes.io/instance": cluster.Name,
"app.kubernetes.io/managed-by": "etcd-operator",
},
},
}
if err = ctrl.SetControllerReference(cluster, svc, r.Scheme); err != nil {
return fmt.Errorf("cannot set controller reference: %w", err)
}
if err = r.Create(ctx, svc); err != nil {
return fmt.Errorf("cannot create cluster client service: %w", err)
}
return nil
}

// ensureClusterStateConfigMap creates or updates cluster state configmap.
func (r *EtcdClusterReconciler) ensureClusterStateConfigMap(
ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster, isClusterInitialized bool) error {
Expand Down Expand Up @@ -421,6 +470,10 @@ func (r *EtcdClusterReconciler) getClusterStateConfigMapName(cluster *etcdaenixi
return cluster.Name + "-cluster-state"
}

func (r *EtcdClusterReconciler) getClientServiceName(cluster *etcdaenixiov1alpha1.EtcdCluster) string {
return cluster.Name + "-client"
}

// updateClusterState patches status condition in cluster using merge by Type
func (r *EtcdClusterReconciler) updateClusterState(cluster *etcdaenixiov1alpha1.EtcdCluster, state metav1.Condition) {
if initIdx := slices.IndexFunc(cluster.Status.Conditions, func(condition metav1.Condition) bool {
Expand Down
11 changes: 10 additions & 1 deletion internal/controller/etcdcluster_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,21 @@ var _ = Describe("EtcdCluster Controller", func() {
// check that Service is created
svc := &v1.Service{}
err = k8sClient.Get(ctx, typeNamespacedName, svc)
Expect(err).NotTo(HaveOccurred(), "cluster headless Service state should exist")
Expect(err).NotTo(HaveOccurred(), "cluster headless Service should exist")
Expect(svc.Spec.ClusterIP).To(Equal("None"), "cluster Service should be headless")
// check that StatefulSet is created
sts := &appsv1.StatefulSet{}
err = k8sClient.Get(ctx, typeNamespacedName, sts)
Expect(err).NotTo(HaveOccurred(), "cluster statefulset should exist")
// check that Service is created
svc = &v1.Service{}
clientSvcName := types.NamespacedName{
Namespace: typeNamespacedName.Namespace,
Name: controllerReconciler.getClientServiceName(etcdcluster),
}
err = k8sClient.Get(ctx, clientSvcName, svc)
Expect(err).NotTo(HaveOccurred(), "cluster client Service should exist")
Expect(svc.Spec.ClusterIP).NotTo(Equal("None"), "cluster client Service should NOT be headless")
})

It("should successfully reconcile the resource twice and mark as ready", func() {
Expand Down

0 comments on commit a6eb964

Please sign in to comment.