Skip to content

Commit 709ee1b

Browse files
authored
Do not set status.ip when proxy protocol is set (#5)
1 parent 5648242 commit 709ee1b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+5800
-17
lines changed

.env.sample

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CIVO_API_KEY=

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
kubeconfig
2+
.env
3+
./e2e/kubeconfig

README.md

+33
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,39 @@ This controller is the Kubernetes cloud controller manager implementation for Ci
66

77
How do I run the `civo-cloud-controller-manager` [here!](https://github.com/civo/civo-cloud-controller-manager/blob/master/doc/getting-started.md)
88

9+
## Load Balancers
10+
11+
The CCM will listen for Services with a type `LoadBalancer` set and provision an external load balancer within the Civo platform.
12+
13+
Once the Load Balancer is provisioned, a public DNS entry will be added mapping to the id of the load balancer. e.g. 92f8162c-c23c-4019-b6a0-2c18b8363f50.lb.civo.com.
14+
15+
Read More: https://www.civo.com/learn/managing-external-load-balancers-on-civo
16+
17+
### Load Balancer Customisation
18+
19+
| Annotation | Description | Example Values |
20+
|------------|-------------|----------------|
21+
| kubernetes.civo.com/firewall-id | If provided, an existing Firewall will be used. | 03093EF6-31E6-48B1-AB1D-152AC3A8C90A |
22+
| kubernetes.civo.com/loadbalancer-enable-proxy-protocol | If set, a proxy-protocol header will be sent via the load balancer. <br /><br />NB: This requires support from the Service End Points within the cluster. | send-proxy<br />send-proxy-v2 |
23+
| kubernetes.civo.com/loadbalancer-algorithm | Custom the algorithm the external load balancer uses | round_robin<br />least_connections |
24+
25+
### Load Balancer Status Annotations
26+
27+
| Annotation | Description | Sample Value |
28+
| ------------------------------------- | ------------------------------------------------------ | ------------------------------------ |
29+
| kubernetes.civo.com/cluster-id | The ID of the cluster the load balancer is assigned to | 05CE1CA2-067F-42F0-9BAA-17A6A800EFBB |
30+
| kubernetes.civo.com/loadbalancer-id | The ID of the Load Balancer within Civo. | 92F8162C-C23C-4019-B6A0-2C18B8363F50 |
31+
| kubernetes.civo.com/loadbalancer-name | The name of the Load Balancer | Lb-test |
32+
33+
34+
35+
### Proxy Protocol
36+
37+
When the proxy protocol annotation (`kubernetes.civo.com/loadbalancer-enable-proxy-protocol`) is set, the IP of the LoadBalancer is not set, only the Hostname. This will mean that all traffic local to the cluster to the LoadBalancer end point is now sent via the LoadBalancer. This allows services like CertManager to work correctly.
38+
39+
This option is currently a workaround for the issue https://github.com/kubernetes/ingress-nginx/issues/3996, should be removed or refactored after the Kubernetes [KEP-1860]
40+
41+
942
## Contributing
1043

1144
Bug reports and pull requests are welcome on GitHub at https://github.com/civo/civo-cloud-controller-manager

cloud-controller-manager/civo/loadbalancer.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,7 @@ func (l *loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName strin
111111
return nil, err
112112
}
113113

114-
return &v1.LoadBalancerStatus{
115-
Ingress: []v1.LoadBalancerIngress{
116-
{
117-
IP: updatedlb.PublicIP,
118-
},
119-
},
120-
}, nil
114+
return lbStatusFor(updatedlb), nil
121115
}
122116

123117
err = createLoadBalancer(ctx, clusterName, service, nodes, l.client.civoClient, l.client.kclient)
@@ -136,13 +130,20 @@ func (l *loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName strin
136130
return nil, fmt.Errorf("loadbalancer is not yet available, current state: %s", civolb.State)
137131
}
138132

139-
return &v1.LoadBalancerStatus{
140-
Ingress: []v1.LoadBalancerIngress{
141-
{
142-
IP: civolb.PublicIP,
143-
},
144-
},
145-
}, nil
133+
return lbStatusFor(civolb), nil
134+
}
135+
136+
func lbStatusFor(civolb *civogo.LoadBalancer) *v1.LoadBalancerStatus {
137+
status := &v1.LoadBalancerStatus{
138+
Ingress: make([]v1.LoadBalancerIngress, 1),
139+
}
140+
141+
if civolb.EnableProxyProtocol == "" {
142+
status.Ingress[0].IP = civolb.PublicIP
143+
}
144+
status.Ingress[0].Hostname = fmt.Sprintf("%s.lb.civo.com", civolb.ID)
145+
146+
return status
146147
}
147148

148149
// UpdateLoadBalancer updates hosts under the specified load balancer.

cloud-controller-manager/civo/loadbalancer_test.go

+76-3
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ func TestEnsureLoadBalancer(t *testing.T) {
411411
nodes []*corev1.Node
412412
store []civogo.LoadBalancer
413413
cluster []civogo.KubernetesCluster
414+
setIP bool
414415
err error
415416
}{
416417
{
@@ -473,7 +474,72 @@ func TestEnsureLoadBalancer(t *testing.T) {
473474
},
474475
},
475476
},
476-
err: nil,
477+
setIP: true,
478+
err: nil,
479+
},
480+
{
481+
name: "should not set ip for proxy protocol",
482+
service: &corev1.Service{
483+
ObjectMeta: metav1.ObjectMeta{
484+
Name: "test",
485+
Namespace: corev1.NamespaceDefault,
486+
Annotations: map[string]string{
487+
annotationCivoClusterID: "a32fe5eb-1922-43e8-81bc-7f83b4011334",
488+
annotationCivoLoadBalancerEnableProxyProtocol: "send-proxy",
489+
},
490+
},
491+
Spec: corev1.ServiceSpec{
492+
Type: corev1.ServiceTypeLoadBalancer,
493+
Ports: []corev1.ServicePort{
494+
{
495+
Name: "http",
496+
Protocol: corev1.ProtocolTCP,
497+
Port: 80,
498+
NodePort: 30000,
499+
},
500+
},
501+
},
502+
},
503+
nodes: []*corev1.Node{
504+
{
505+
ObjectMeta: metav1.ObjectMeta{
506+
Name: "node1",
507+
},
508+
Status: corev1.NodeStatus{
509+
Addresses: []corev1.NodeAddress{
510+
{
511+
Address: "192.168.1.1",
512+
},
513+
},
514+
},
515+
},
516+
{
517+
ObjectMeta: metav1.ObjectMeta{
518+
Name: "node2",
519+
},
520+
Status: corev1.NodeStatus{
521+
Addresses: []corev1.NodeAddress{
522+
{
523+
Address: "192.168.1.2",
524+
},
525+
},
526+
},
527+
},
528+
},
529+
cluster: []civogo.KubernetesCluster{
530+
{
531+
ID: "a32fe5eb-1922-43e8-81bc-7f83b4011334",
532+
Name: "test",
533+
Instances: []civogo.KubernetesInstance{
534+
{
535+
ID: "11bd4686-5dbf-4e35-b703-75f2864bd6b9",
536+
Hostname: "node1",
537+
},
538+
},
539+
},
540+
},
541+
setIP: false,
542+
err: nil,
477543
},
478544
{
479545
name: "should update an existing load balancer",
@@ -549,7 +615,8 @@ func TestEnsureLoadBalancer(t *testing.T) {
549615
},
550616
},
551617
},
552-
err: nil,
618+
setIP: true,
619+
err: nil,
553620
},
554621
}
555622

@@ -576,7 +643,13 @@ func TestEnsureLoadBalancer(t *testing.T) {
576643
lbStatus, err := lb.EnsureLoadBalancer(context.Background(), test.cluster[0].Name, test.service, test.nodes)
577644
g.Expect(err).To(BeNil())
578645

579-
g.Expect(lbStatus.Ingress[0].IP).NotTo(BeEmpty())
646+
if test.setIP {
647+
g.Expect(lbStatus.Ingress[0].IP).NotTo(BeEmpty())
648+
} else {
649+
g.Expect(lbStatus.Ingress[0].IP).To(BeEmpty())
650+
}
651+
652+
g.Expect(lbStatus.Ingress[0].Hostname).NotTo(BeEmpty())
580653

581654
svc, err := lb.client.kclient.CoreV1().Services(test.service.Namespace).Get(context.Background(), test.service.Name, metav1.GetOptions{})
582655
if err != nil {

e2e/Readme.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# e2e Tests
2+
3+
e2e tests for the Civo Cloud Controller Manager.
4+
5+
These tests can be run:
6+
7+
```bash
8+
CIVO_API_KEY=.... go test --timeout 30 -v ./e2e/...
9+
```
10+
11+
or the CIVO_API_KEY can be set in the `.env` file in the root of the project
12+
13+
## PreRequisites
14+
15+
A civo.com account is needed, and you'll need to get your API key from the web front end.
16+
17+
## Tests
18+
19+
The general flow for each test is as follows:
20+
21+
1. Provision a new cluster
22+
2. Wait for the cluster to be provisioned
23+
2. Scale down pre-deployed CCM in the cluster
24+
4. Run the local copy of CCM
25+
5. Run e2e Tests
26+
6. Delete the cluster
27+
28+
### Node
29+
30+
> WIP
31+
32+
### Loadbalancers
33+
34+
> WIP

e2e/loadbalacner_test.go

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
8+
. "github.com/onsi/gomega"
9+
"sigs.k8s.io/controller-runtime/pkg/client"
10+
11+
appsv1 "k8s.io/api/apps/v1"
12+
corev1 "k8s.io/api/core/v1"
13+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14+
"k8s.io/apimachinery/pkg/util/intstr"
15+
)
16+
17+
func TestLoadbalacnerBasic(t *testing.T) {
18+
19+
g := NewGomegaWithT(t)
20+
21+
mirrorDeploy, err := deployMirrorPods(e2eTest.tenantClient)
22+
g.Expect(err).ShouldNot(HaveOccurred())
23+
24+
lbls := map[string]string{"app": "mirror-pod"}
25+
// Create a service of type: LoadBalacner
26+
svc := &corev1.Service{
27+
ObjectMeta: metav1.ObjectMeta{
28+
Name: "echo-pods",
29+
Namespace: "default",
30+
},
31+
Spec: corev1.ServiceSpec{
32+
Ports: []corev1.ServicePort{
33+
{Name: "http", Protocol: "TCP", Port: 80, TargetPort: intstr.FromInt(8080)},
34+
{Name: "https", Protocol: "TCP", Port: 443, TargetPort: intstr.FromInt(8443)},
35+
},
36+
Selector: lbls,
37+
Type: "LoadBalancer",
38+
},
39+
}
40+
41+
fmt.Println("Creating Service")
42+
err = e2eTest.tenantClient.Create(context.TODO(), svc)
43+
g.Expect(err).ShouldNot(HaveOccurred())
44+
45+
g.Eventually(func() string {
46+
err = e2eTest.tenantClient.Get(context.TODO(), client.ObjectKeyFromObject(svc), svc)
47+
if len(svc.Status.LoadBalancer.Ingress) == 0 {
48+
return ""
49+
}
50+
return svc.Status.LoadBalancer.Ingress[0].IP
51+
}, "2m", "5s").ShouldNot(BeEmpty())
52+
53+
// Cleanup
54+
err = cleanUp(mirrorDeploy, svc)
55+
g.Expect(err).ShouldNot(HaveOccurred())
56+
57+
g.Eventually(func() error {
58+
return e2eTest.tenantClient.Get(context.TODO(), client.ObjectKeyFromObject(svc), svc)
59+
}, "2m", "5s").ShouldNot(BeNil())
60+
}
61+
62+
func TestLoadbalacnerProxy(t *testing.T) {
63+
g := NewGomegaWithT(t)
64+
65+
_, err := deployMirrorPods(e2eTest.tenantClient)
66+
g.Expect(err).ShouldNot(HaveOccurred())
67+
68+
lbls := map[string]string{"app": "mirror-pod"}
69+
// Create a service of type: LoadBalacner
70+
svc := &corev1.Service{
71+
ObjectMeta: metav1.ObjectMeta{
72+
Name: "echo-pods",
73+
Namespace: "default",
74+
Annotations: map[string]string{
75+
"kubernetes.civo.com/loadbalancer-enable-proxy-protocol": "send-proxy",
76+
},
77+
},
78+
Spec: corev1.ServiceSpec{
79+
Ports: []corev1.ServicePort{
80+
{Name: "http", Protocol: "TCP", Port: 80, TargetPort: intstr.FromInt(8081)},
81+
{Name: "https", Protocol: "TCP", Port: 443, TargetPort: intstr.FromInt(8444)},
82+
},
83+
Selector: lbls,
84+
Type: "LoadBalancer",
85+
},
86+
}
87+
88+
fmt.Println("Creating Service")
89+
err = e2eTest.tenantClient.Create(context.TODO(), svc)
90+
g.Expect(err).ShouldNot(HaveOccurred())
91+
92+
g.Eventually(func() string {
93+
err = e2eTest.tenantClient.Get(context.TODO(), client.ObjectKeyFromObject(svc), svc)
94+
if len(svc.Status.LoadBalancer.Ingress) == 0 {
95+
return ""
96+
}
97+
return svc.Status.LoadBalancer.Ingress[0].IP
98+
}, "2m", "5s").ShouldNot(BeEmpty())
99+
100+
/*
101+
// Cleanup
102+
err = cleanUp(mirrorDeploy, svc)
103+
g.Expect(err).ShouldNot(HaveOccurred())
104+
105+
g.Eventually(func() error {
106+
return e2eTest.tenantClient.Get(context.TODO(), client.ObjectKeyFromObject(svc), svc)
107+
}, "2m", "5s").ShouldNot(BeNil())
108+
*/
109+
}
110+
111+
func cleanUp(mirrorDeploy *appsv1.Deployment, svc *corev1.Service) error {
112+
err := e2eTest.tenantClient.Delete(context.TODO(), svc)
113+
if err != nil {
114+
return err
115+
}
116+
117+
return e2eTest.tenantClient.Delete(context.TODO(), mirrorDeploy)
118+
}
119+
120+
func deployMirrorPods(c client.Client) (*appsv1.Deployment, error) {
121+
lbls := map[string]string{"app": "mirror-pod"}
122+
replicas := int32(2)
123+
mirrorDeploy := &appsv1.Deployment{
124+
ObjectMeta: metav1.ObjectMeta{
125+
Name: "echo-pods",
126+
Namespace: "default",
127+
},
128+
Spec: appsv1.DeploymentSpec{
129+
Selector: &metav1.LabelSelector{
130+
MatchLabels: lbls,
131+
},
132+
Replicas: &replicas,
133+
Template: corev1.PodTemplateSpec{
134+
ObjectMeta: metav1.ObjectMeta{
135+
Labels: lbls,
136+
Annotations: map[string]string{
137+
"danm.k8s.io/interfaces": "[{\"tenantNetwork\":\"tenant-vxlan\", \"ip\":\"dynamic\"}]",
138+
},
139+
},
140+
Spec: corev1.PodSpec{
141+
Containers: []corev1.Container{
142+
{
143+
Name: "mirror-pod",
144+
Image: "dmajrekar/nginx-echo:latest",
145+
ImagePullPolicy: corev1.PullIfNotPresent,
146+
Ports: []corev1.ContainerPort{
147+
{Protocol: "TCP", ContainerPort: 8080},
148+
{Protocol: "TCP", ContainerPort: 8081},
149+
{Protocol: "TCP", ContainerPort: 8443},
150+
{Protocol: "TCP", ContainerPort: 8444},
151+
},
152+
},
153+
},
154+
},
155+
},
156+
},
157+
}
158+
159+
fmt.Println("Creating mirror deployment")
160+
err := c.Create(context.TODO(), mirrorDeploy)
161+
return mirrorDeploy, err
162+
163+
}

0 commit comments

Comments
 (0)