Skip to content

Commit 92d9fcb

Browse files
Merge pull request #6 from civo/reserved-ip
Reserved ip
2 parents 709ee1b + ac7ec22 commit 92d9fcb

Some content is hidden

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

58 files changed

+2685
-1150
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Read More: https://www.civo.com/learn/managing-external-load-balancers-on-civo
2121
| kubernetes.civo.com/firewall-id | If provided, an existing Firewall will be used. | 03093EF6-31E6-48B1-AB1D-152AC3A8C90A |
2222
| 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 |
2323
| kubernetes.civo.com/loadbalancer-algorithm | Custom the algorithm the external load balancer uses | round_robin<br />least_connections |
24+
|kubernetes.civo.com/ipv4-address| If set, LoadBalancer will have the mentioned IP as the public IP. Please note: the reserved IP should be present in the account before claiming it. | 10.0.0.20<br/> my-reserved-ip |
2425

2526
### Load Balancer Status Annotations
2627

cloud-controller-manager/civo/cloud.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@ import (
99
)
1010

1111
const (
12+
// ProviderName is the name of the provider.
1213
ProviderName string = "civo"
1314
)
1415

1516
var (
16-
ApiURL string
17-
ApiKey string
18-
Region string
17+
// APIURL is the URL of the Civo API.
18+
APIURL string
19+
// APIKey is the API key for the Civo API.
20+
APIKey string
21+
// Region is the region of the Civo API.
22+
Region string
23+
// Namespace of the cluster
1924
Namespace string
25+
// ClusterID is the ID of the Civo cluster.
2026
ClusterID string
2127
)
2228

@@ -33,7 +39,7 @@ func init() {
3339
}
3440

3541
func newCloud() (cloudprovider.Interface, error) {
36-
client, err := civogo.NewClientWithURL(ApiKey, ApiURL, Region)
42+
client, err := civogo.NewClientWithURL(APIKey, APIURL, Region)
3743
if err != nil {
3844
return nil, err
3945
}

cloud-controller-manager/civo/loadbalancer.go

+107-61
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const (
3434

3535
// annotationCivoLoadBalancerAlgorithm is the annotation specifying the CivoLoadbalancer algorith.
3636
annotationCivoLoadBalancerAlgorithm = "kubernetes.civo.com/loadbalancer-algorithm"
37+
38+
// annotationCivoIPv4 is the annotation specifying the reserved IP.
39+
annotationCivoIPv4 = "kubernetes.civo.com/ipv4-address"
3740
)
3841

3942
type loadbalancer struct {
@@ -88,30 +91,15 @@ func (l *loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName strin
8891
return nil, err
8992
}
9093

91-
// CivLB has been found
94+
// CivoLB has been found
9295
if err == nil {
93-
lbuc := civogo.LoadBalancerUpdateConfig{
94-
ExternalTrafficPolicy: string(service.Spec.ExternalTrafficPolicy),
95-
Region: Region,
96-
}
97-
98-
if enableProxyProtocol := getEnableProxyProtocol(service); enableProxyProtocol != "" {
99-
lbuc.EnableProxyProtocol = enableProxyProtocol
100-
}
101-
if algorithm := getAlgorithm(service); algorithm != "" {
102-
lbuc.Algorithm = algorithm
103-
}
104-
if firewallID := getFirewallID(service); firewallID != "" {
105-
lbuc.FirewallID = firewallID
106-
}
107-
108-
updatedlb, err := l.client.civoClient.UpdateLoadBalancer(civolb.ID, &lbuc)
96+
ul, err := l.updateLBConfig(civolb, service, nodes)
10997
if err != nil {
11098
klog.Errorf("Unable to update loadbalancer, error: %v", err)
11199
return nil, err
112100
}
113101

114-
return lbStatusFor(updatedlb), nil
102+
return lbStatusFor(ul)
115103
}
116104

117105
err = createLoadBalancer(ctx, clusterName, service, nodes, l.client.civoClient, l.client.kclient)
@@ -125,41 +113,10 @@ func (l *loadbalancer) EnsureLoadBalancer(ctx context.Context, clusterName strin
125113
return nil, err
126114
}
127115

128-
if civolb.State != statusAvailable {
129-
klog.Errorf("Loadbalancer is not available, state: %s", civolb.State)
130-
return nil, fmt.Errorf("loadbalancer is not yet available, current state: %s", civolb.State)
131-
}
132-
133-
return lbStatusFor(civolb), nil
116+
return lbStatusFor(civolb)
134117
}
135118

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
147-
}
148-
149-
// UpdateLoadBalancer updates hosts under the specified load balancer.
150-
// Implementations must treat the *v1.Service and *v1.Node
151-
// parameters as read-only and not modify them.
152-
// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager
153-
func (l *loadbalancer) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error {
154-
civolb, err := getLoadBalancer(ctx, l.client.civoClient, l.client.kclient, clusterName, service)
155-
if err != nil {
156-
if strings.Contains(err.Error(), string(civogo.ZeroMatchesError)) || strings.Contains(err.Error(), string(civogo.DatabaseLoadBalancerNotFoundError)) {
157-
return nil
158-
}
159-
klog.Errorf("Unable to get loadbalancer, error: %v", err)
160-
return err
161-
}
162-
119+
func (l *loadbalancer) updateLBConfig(civolb *civogo.LoadBalancer, service *v1.Service, nodes []*v1.Node) (*civogo.LoadBalancer, error) {
163120
lbuc := civogo.LoadBalancerUpdateConfig{
164121
ExternalTrafficPolicy: string(service.Spec.ExternalTrafficPolicy),
165122
Region: Region,
@@ -189,7 +146,97 @@ func (l *loadbalancer) UpdateLoadBalancer(ctx context.Context, clusterName strin
189146
}
190147
lbuc.Backends = backends
191148

192-
ulb, err := l.client.civoClient.UpdateLoadBalancer(civolb.ID, &lbuc)
149+
if ip := getReservedIPFromAnnotation(service); ip != "" {
150+
rip, err := l.client.civoClient.FindIP(ip)
151+
if err != nil {
152+
klog.Errorf("Unable to find reserved IP, error: %v", err)
153+
return nil, err
154+
}
155+
156+
// this is so that we don't try to reassign the reserved IP to the loadbalancer
157+
if rip.AssignedTo.ID != civolb.ID {
158+
_, err = l.client.civoClient.AssignIP(rip.ID, civolb.ID, "loadbalancer")
159+
if err != nil {
160+
klog.Errorf("Unable to assign reserved IP, error: %v", err)
161+
return nil, err
162+
}
163+
}
164+
} else {
165+
ip, err := findIPWithLBID(l.client.civoClient, civolb.ID)
166+
if err != nil {
167+
klog.Errorf("Unable to find IP with loadbalancer ID, error: %v", err)
168+
return nil, err
169+
}
170+
171+
if ip != nil {
172+
_, err = l.client.civoClient.UnassignIP(ip.ID)
173+
if err != nil {
174+
klog.Errorf("Unable to unassign IP, error: %v", err)
175+
return nil, err
176+
}
177+
}
178+
}
179+
180+
updatedlb, err := l.client.civoClient.UpdateLoadBalancer(civolb.ID, &lbuc)
181+
if err != nil {
182+
klog.Errorf("Unable to update loadbalancer, error: %v", err)
183+
return nil, err
184+
}
185+
186+
return updatedlb, nil
187+
188+
}
189+
190+
// there's no direct way to find if the LB is using a reserved IP. This method lists all the reserved IPs in the account
191+
// and checks if the loadbalancer is using one of them.
192+
func findIPWithLBID(civo civogo.Clienter, lbID string) (*civogo.IP, error) {
193+
ips, err := civo.ListIPs()
194+
if err != nil {
195+
klog.Errorf("Unable to list IPs, error: %v", err)
196+
return nil, err
197+
}
198+
199+
for _, ip := range ips.Items {
200+
if ip.AssignedTo.ID == lbID {
201+
return &ip, nil
202+
}
203+
}
204+
return nil, nil
205+
}
206+
207+
func lbStatusFor(civolb *civogo.LoadBalancer) (*v1.LoadBalancerStatus, error) {
208+
status := &v1.LoadBalancerStatus{
209+
Ingress: make([]v1.LoadBalancerIngress, 1),
210+
}
211+
212+
if civolb.State != statusAvailable {
213+
klog.Errorf("Loadbalancer is not available, state: %s", civolb.State)
214+
return nil, fmt.Errorf("loadbalancer is not yet available, current state: %s", civolb.State)
215+
}
216+
217+
if civolb.EnableProxyProtocol == "" {
218+
status.Ingress[0].IP = civolb.PublicIP
219+
}
220+
status.Ingress[0].Hostname = fmt.Sprintf("%s.lb.civo.com", civolb.ID)
221+
222+
return status, nil
223+
}
224+
225+
// UpdateLoadBalancer updates hosts under the specified load balancer.
226+
// Implementations must treat the *v1.Service and *v1.Node
227+
// parameters as read-only and not modify them.
228+
// Parameter 'clusterName' is the name of the cluster as presented to kube-controller-manager
229+
func (l *loadbalancer) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error {
230+
civolb, err := getLoadBalancer(ctx, l.client.civoClient, l.client.kclient, clusterName, service)
231+
if err != nil {
232+
if strings.Contains(err.Error(), string(civogo.ZeroMatchesError)) || strings.Contains(err.Error(), string(civogo.DatabaseLoadBalancerNotFoundError)) {
233+
return nil
234+
}
235+
klog.Errorf("Unable to get loadbalancer, error: %v", err)
236+
return err
237+
}
238+
239+
ulb, err := l.updateLBConfig(civolb, service, nodes)
193240
if err != nil {
194241
klog.Errorf("Unable to update loadbalancer, error: %v", err)
195242
return err
@@ -350,20 +397,19 @@ func getEnableProxyProtocol(service *v1.Service) string {
350397

351398
// getAlgorithm returns the algorithm value from the service annotation.
352399
func getAlgorithm(service *v1.Service) string {
353-
algorithm, ok := service.Annotations[annotationCivoLoadBalancerAlgorithm]
354-
if !ok {
355-
return ""
356-
}
400+
algorithm, _ := service.Annotations[annotationCivoLoadBalancerAlgorithm]
357401

358402
return algorithm
359403
}
360404

405+
// getReservedIPFromAnnotation returns the reservedIP value from the service annotation.
406+
func getReservedIPFromAnnotation(service *v1.Service) string {
407+
ip, _ := service.Annotations[annotationCivoIPv4]
408+
return ip
409+
}
410+
361411
// getFirewallID returns the firewallID value from the service annotation.
362412
func getFirewallID(service *v1.Service) string {
363-
firewallID, ok := service.Annotations[annotationCivoFirewallID]
364-
if !ok {
365-
return ""
366-
}
367-
413+
firewallID, _ := service.Annotations[annotationCivoFirewallID]
368414
return firewallID
369415
}

cloud-controller-manager/cmd/civo-cloud-controller-manager/main.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ import (
1818

1919
func main() {
2020

21-
civo.ApiURL = os.Getenv("CIVO_API_URL")
22-
civo.ApiKey = os.Getenv("CIVO_API_KEY")
21+
civo.APIURL = os.Getenv("CIVO_API_URL")
22+
civo.APIKey = os.Getenv("CIVO_API_KEY")
2323
civo.Region = os.Getenv("CIVO_REGION")
2424
civo.ClusterID = os.Getenv("CIVO_CLUSTER_ID")
2525

26-
if civo.ApiURL == "" || civo.ApiKey == "" || civo.Region == "" || civo.ClusterID == "" {
26+
if civo.APIURL == "" || civo.APIKey == "" || civo.Region == "" || civo.ClusterID == "" {
2727
fmt.Println("CIVO_API_URL, CIVO_API_KEY, CIVO_REGION, CIVO_CLUSTER_ID environment variables must be set")
2828
os.Exit(1)
2929
}
3030

31-
klog.Infof("Starting ccm with CIVO_API_URL: %s, CIVO_REGION: %s, CIVO_CLUSTER_ID: %s", civo.ApiURL, civo.Region, civo.ClusterID)
31+
klog.Infof("Starting ccm with CIVO_API_URL: %s, CIVO_REGION: %s, CIVO_CLUSTER_ID: %s", civo.APIURL, civo.Region, civo.ClusterID)
3232

3333
opts, err := options.NewCloudControllerManagerOptions()
3434
if err != nil {

cloud-controller-manager/pkg/utils/instance.go

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func civoInstanceFromID(clusterID, instanceID string, c civogo.Clienter) (civogo
3131
return *instance, nil
3232
}
3333

34+
// CivoInstanceFromProviderID finds civo instance by clusterID and providerID
3435
func CivoInstanceFromProviderID(providerID, clusterID string, c civogo.Clienter) (civogo.Instance, error) {
3536
civoInstanceID, err := civoInstanceIDFromProviderID(providerID)
3637
if err != nil {
@@ -45,6 +46,7 @@ func CivoInstanceFromProviderID(providerID, clusterID string, c civogo.Clienter)
4546
return civoInstance, nil
4647
}
4748

49+
// CivoInstanceFromName finds civo instance by clusterID and name
4850
func CivoInstanceFromName(clusterID, instanceName string, c civogo.Clienter) (civogo.Instance, error) {
4951
instance, err := c.FindKubernetesClusterInstance(clusterID, instanceName)
5052
if err != nil {

0 commit comments

Comments
 (0)