-
Notifications
You must be signed in to change notification settings - Fork 100
Expand file tree
/
Copy pathload_balancer_spec.go
More file actions
1733 lines (1502 loc) · 63.8 KB
/
load_balancer_spec.go
File metadata and controls
1733 lines (1502 loc) · 63.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2017 Oracle and/or its affiliates. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package oci
import (
"encoding/json"
"fmt"
"net"
"net/http"
"slices"
"strconv"
"strings"
"github.com/oracle/oci-go-sdk/v65/loadbalancer"
"go.uber.org/zap"
"golang.org/x/exp/maps"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
apiservice "k8s.io/kubernetes/pkg/api/v1/service"
"k8s.io/utils/pointer"
"github.com/oracle/oci-cloud-controller-manager/pkg/cloudprovider/providers/oci/config"
"github.com/oracle/oci-cloud-controller-manager/pkg/oci/client"
"github.com/oracle/oci-cloud-controller-manager/pkg/util"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/pkg/errors"
helper "k8s.io/cloud-provider/service/helpers"
net2 "k8s.io/utils/net"
)
const (
LB = "lb"
NLB = "nlb"
NSG = "NSG"
LBHealthCheckIntervalMin = 1000
LBHealthCheckIntervalMax = 1800000
NLBHealthCheckIntervalMin = 10000
NLBHealthCheckIntervalMax = 1800000
IPv4 = string(client.GenericIPv4)
IPv6 = string(client.GenericIPv6)
IPv4AndIPv6 = string("IPv4_AND_IPv6")
)
const (
defaultLoadBalancerSourceRangesIPv4 = "0.0.0.0/0"
defaultLoadBalancerSourceRangesIPv6 = "::/0"
)
const ProtocolTypeMixed = "TCP_AND_UDP"
const (
// ServiceAnnotationLoadBalancerInternal is a service annotation for
// specifying that a load balancer should be internal.
ServiceAnnotationLoadBalancerInternal = "service.beta.kubernetes.io/oci-load-balancer-internal"
// ServiceAnnotationLoadBalancerShape is a Service annotation for
// specifying the Shape of a load balancer. The shape is a template that
// determines the load balancer's total pre-provisioned maximum capacity
// (bandwidth) for ingress plus egress traffic. Available shapes include
// "100Mbps", "400Mbps", "8000Mbps", and "flexible". When using
// "flexible" ,it is required to also supply
// ServiceAnnotationLoadBalancerShapeFlexMin and
// ServiceAnnotationLoadBalancerShapeFlexMax.
ServiceAnnotationLoadBalancerShape = "service.beta.kubernetes.io/oci-load-balancer-shape"
// ServiceAnnotationLoadBalancerShapeFlexMin is a Service annotation for
// specifying the minimum bandwidth in Mbps if the LB shape is flex.
ServiceAnnotationLoadBalancerShapeFlexMin = "service.beta.kubernetes.io/oci-load-balancer-shape-flex-min"
// ServiceAnnotationLoadBalancerShapeFlexMax is a Service annotation for
// specifying the maximum bandwidth in Mbps if the shape is flex.
ServiceAnnotationLoadBalancerShapeFlexMax = "service.beta.kubernetes.io/oci-load-balancer-shape-flex-max"
// ServiceAnnotationLoadBalancerSubnet1 is a Service annotation for
// specifying the first subnet of a load balancer.
ServiceAnnotationLoadBalancerSubnet1 = "service.beta.kubernetes.io/oci-load-balancer-subnet1"
// ServiceAnnotationLoadBalancerSubnet2 is a Service annotation for
// specifying the second subnet of a load balancer.
ServiceAnnotationLoadBalancerSubnet2 = "service.beta.kubernetes.io/oci-load-balancer-subnet2"
// ServiceAnnotationLoadBalancerSSLPorts is a Service annotation for
// specifying the ports to enable SSL termination on the corresponding load
// balancer listener.
ServiceAnnotationLoadBalancerSSLPorts = "service.beta.kubernetes.io/oci-load-balancer-ssl-ports"
// ServiceAnnotationLoadBalancerTLSSecret is a Service annotation for
// specifying the TLS secret to install on the load balancer listeners which
// have SSL enabled.
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
ServiceAnnotationLoadBalancerTLSSecret = "service.beta.kubernetes.io/oci-load-balancer-tls-secret"
// ServiceAnnotationLoadBalancerTLSBackendSetSecret is a Service annotation for
// specifying the generic secret to install on the load balancer listeners which
// have SSL enabled.
// See: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls
ServiceAnnotationLoadBalancerTLSBackendSetSecret = "service.beta.kubernetes.io/oci-load-balancer-tls-backendset-secret"
// ServiceAnnotationLoadBalancerConnectionIdleTimeout is the annotation used
// on the service to specify the idle connection timeout.
ServiceAnnotationLoadBalancerConnectionIdleTimeout = "service.beta.kubernetes.io/oci-load-balancer-connection-idle-timeout"
// ServiceAnnotationLoadBalancerConnectionProxyProtocolVersion is the annotation used
// on the service to specify the proxy protocol version.
ServiceAnnotationLoadBalancerConnectionProxyProtocolVersion = "service.beta.kubernetes.io/oci-load-balancer-connection-proxy-protocol-version"
// ServiceAnnotationLoadBalancerSecurityListManagementMode is a Service annotation for
// specifying the security list management mode ("All", "Frontend", "None") that configures how security lists are managed by the CCM
ServiceAnnotationLoadBalancerSecurityListManagementMode = "service.beta.kubernetes.io/oci-load-balancer-security-list-management-mode"
// ServiceAnnotationLoadBalancerHealthCheckRetries is the annotation used
// on the service to specify the number of retries to attempt before a backend server is considered "unhealthy".
ServiceAnnotationLoadBalancerHealthCheckRetries = "service.beta.kubernetes.io/oci-load-balancer-health-check-retries"
// ServiceAnnotationLoadBalancerHealthCheckInterval is a Service annotation for
// specifying the interval between health checks, in milliseconds.
ServiceAnnotationLoadBalancerHealthCheckInterval = "service.beta.kubernetes.io/oci-load-balancer-health-check-interval"
// ServiceAnnotationLoadBalancerHealthCheckTimeout is a Service annotation for
// specifying the maximum time, in milliseconds, to wait for a reply to a health check. A health check is successful only if a reply
// returns within this timeout period.
ServiceAnnotationLoadBalancerHealthCheckTimeout = "service.beta.kubernetes.io/oci-load-balancer-health-check-timeout"
// ServiceAnnotationLoadBalancerBEProtocol is a Service annotation for specifying the
// load balancer listener backend protocol ("TCP", "HTTP").
// See: https://docs.cloud.oracle.com/iaas/Content/Balance/Concepts/balanceoverview.htm#concepts
ServiceAnnotationLoadBalancerBEProtocol = "service.beta.kubernetes.io/oci-load-balancer-backend-protocol"
// ServiceAnnotationLoadBalancerNetworkSecurityGroups is a service annotation for
// specifying Network security group Ids for the Loadbalancer
ServiceAnnotationLoadBalancerNetworkSecurityGroups = "oci.oraclecloud.com/oci-network-security-groups"
// ServiceAnnotationLoadBalancerPolicy is a service annotation for specifying
// loadbalancer traffic policy("ROUND_ROBIN", "LEAST_CONNECTION", "IP_HASH")
ServiceAnnotationLoadBalancerPolicy = "oci.oraclecloud.com/loadbalancer-policy"
// ServiceAnnotationLoadBalancerInitialDefinedTagsOverride is a service annotation for specifying
// defined tags on the LB
ServiceAnnotationLoadBalancerInitialDefinedTagsOverride = "oci.oraclecloud.com/initial-defined-tags-override"
// ServiceAnnotationLoadBalancerInitialFreeformTagsOverride is a service annotation for specifying
// freeform tags on the LB
ServiceAnnotationLoadBalancerInitialFreeformTagsOverride = "oci.oraclecloud.com/initial-freeform-tags-override"
// ServiceAnnotationLoadBalancerType is a service annotation for specifying lb type
ServiceAnnotationLoadBalancerType = "oci.oraclecloud.com/load-balancer-type"
// ServiceAnnotationLoadBalancerNodeFilter is a service annotation to select specific nodes as your backend in the LB
// based on label selector.
ServiceAnnotationLoadBalancerNodeFilter = "oci.oraclecloud.com/node-label-selector"
// ServiceAnnotationServiceAccountName is a service annotation to select Service Account to be used to
// exchange for Workload Identity Token which can then be used for LB/NLB Client to communicate to OCI LB/NLB API.
ServiceAnnotationServiceAccountName = "oci.oraclecloud.com/workload-service-account"
// ServiceAnnotationLoadBalancerSecurityRuleManagementMode is a Service annotation for
// specifying the security rule management mode ("SL-All", "SL-Frontend", "NSG", "None") that configures how security lists are managed by the CCM
ServiceAnnotationLoadBalancerSecurityRuleManagementMode = "oci.oraclecloud.com/security-rule-management-mode"
// ServiceAnnotationBackendSecurityRuleManagement is a service annotation to denote management of backend Network Security Group(s)
// ingress / egress security rules for a given kubernetes service could be either LB or NLB
ServiceAnnotationBackendSecurityRuleManagement = "oci.oraclecloud.com/oci-backend-network-security-group"
// ServiceAnnotationLoadbalancerListenerSSLConfig is a service annotation allows you to set the cipher suite on the listener
ServiceAnnotationLoadbalancerListenerSSLConfig = "oci.oraclecloud.com/oci-load-balancer-listener-ssl-config"
// ServiceAnnotationLoadbalancerBackendSetSSLConfig is a service annotation allows you to set the cipher suite on the backendSet
ServiceAnnotationLoadbalancerBackendSetSSLConfig = "oci.oraclecloud.com/oci-load-balancer-backendset-ssl-config"
// ServiceAnnotationIngressIpMode is a service annotation allows you to set the ".status.loadBalancer.ingress.ipMode" for a Service
// with type set to LoadBalancer.
// https://kubernetes.io/docs/concepts/services-networking/service/#load-balancer-ip-mode:~:text=Specifying%20IPMode%20of%20load%20balancer%20status
ServiceAnnotationIngressIpMode = "oci.oraclecloud.com/ingress-ip-mode"
// ServiceAnnotationRuleSets allows the user to specify rule sets of actions applied to traffic at a load balancer listener
// https://docs.oracle.com/en-us/iaas/Content/Balance/Tasks/managingrulesets.htm
// Expected format is a JSON blob containing a JSON object literal with keys being rule names and values being a JSON
// representation of a valid Rule object. https://docs.oracle.com/en-us/iaas/api/#/en/loadbalancer/20170115/datatypes/Rule
ServiceAnnotationRuleSets = "oci.oraclecloud.com/oci-load-balancer-rule-sets"
)
// NLB specific annotations
const (
// ServiceAnnotationNetworkLoadBalancerInternal is a service annotation for
// specifying that a network load balancer should be internal
ServiceAnnotationNetworkLoadBalancerInternal = "oci-network-load-balancer.oraclecloud.com/internal"
// ServiceAnnotationNetworkLoadBalancerSubnet is a Service annotation for
// specifying the first subnet of a network load balancer
ServiceAnnotationNetworkLoadBalancerSubnet = "oci-network-load-balancer.oraclecloud.com/subnet"
// ServiceAnnotationNetworkLoadBalancerNetworkSecurityGroups is a Service annotation for
// specifying network security group id's for the network load balancer
ServiceAnnotationNetworkLoadBalancerNetworkSecurityGroups = "oci-network-load-balancer.oraclecloud.com/oci-network-security-groups"
// ServiceAnnotationNetworkLoadBalancerHealthCheckRetries is the annotation used
// The number of retries to attempt before a backend server is considered "unhealthy".
ServiceAnnotationNetworkLoadBalancerHealthCheckRetries = "oci-network-load-balancer.oraclecloud.com/health-check-retries"
// ServiceAnnotationNetworkLoadBalancerHealthCheckInterval is a Service annotation for
// The interval between health checks requests, in milliseconds.
ServiceAnnotationNetworkLoadBalancerHealthCheckInterval = "oci-network-load-balancer.oraclecloud.com/health-check-interval"
// ServiceAnnotationNetworkLoadBalancerHealthCheckTimeout is a Service annotation for
// The maximum time, in milliseconds, to wait for a reply to a health check. A health check is successful only if a reply returns within this timeout period.
ServiceAnnotationNetworkLoadBalancerHealthCheckTimeout = "oci-network-load-balancer.oraclecloud.com/health-check-timeout"
// ServiceAnnotationNetworkLoadBalancerBackendPolicy is a Service annotation for
// The network load balancer policy for the backend set.
ServiceAnnotationNetworkLoadBalancerBackendPolicy = "oci-network-load-balancer.oraclecloud.com/backend-policy"
// ServiceAnnotationNetworkLoadBalancerSecurityListManagementMode is a Service annotation for
// specifying the security list management mode ("All", "Frontend", "None") that configures how security lists are managed by the CCM
ServiceAnnotationNetworkLoadBalancerSecurityListManagementMode = "oci-network-load-balancer.oraclecloud.com/security-list-management-mode"
// ServiceAnnotationNetworkLoadBalancerDefinedTags is a service annotation for specifying
// defined tags on the nlb
// DEPRECATED
ServiceAnnotationNetworkLoadBalancerDefinedTags = "oci-network-load-balancer.oraclecloud.com/defined-tags"
// ServiceAnnotationNetworkLoadBalancerFreeformTags is a service annotation for specifying
// freeform tags on the nlb
// DEPRECATED
ServiceAnnotationNetworkLoadBalancerFreeformTags = "oci-network-load-balancer.oraclecloud.com/freeform-tags"
// ServiceAnnotationNetworkLoadBalancerInitialDefinedTagsOverride is a service annotation for specifying
// defined tags on the nlb
ServiceAnnotationNetworkLoadBalancerInitialDefinedTagsOverride = "oci-network-load-balancer.oraclecloud.com/initial-defined-tags-override"
// ServiceAnnotationNetworkLoadBalancerInitialFreeformTagsOverride is a service annotation for specifying
// freeform tags on the nlb
ServiceAnnotationNetworkLoadBalancerInitialFreeformTagsOverride = "oci-network-load-balancer.oraclecloud.com/initial-freeform-tags-override"
// ServiceAnnotationNetworkLoadBalancerNodeFilter is a service annotation to select specific nodes as your backend in the NLB
// based on label selector.
ServiceAnnotationNetworkLoadBalancerNodeFilter = "oci-network-load-balancer.oraclecloud.com/node-label-selector"
// ServiceAnnotationNetworkLoadBalancerIsPreserveSource is a service annotation to enable/disable preserving source information
// on the NLB traffic. Default value when no annotation is given is to enable this for NLBs with externalTrafficPolicy=Local.
ServiceAnnotationNetworkLoadBalancerIsPreserveSource = "oci-network-load-balancer.oraclecloud.com/is-preserve-source"
// ServiceAnnotationNetworkLoadBalancerIsPpv2Enabled is a service annotation to enable/disable PPv2 feature for the listeners of this NLB.
ServiceAnnotationNetworkLoadBalancerIsPpv2Enabled = "oci-network-load-balancer.oraclecloud.com/is-ppv2-enabled"
// ServiceAnnotationNetworkLoadBalancerExternalIpOnly is a service a boolean annotation to skip private ip when assigning to ingress resource for NLB service
ServiceAnnotationNetworkLoadBalancerExternalIpOnly = "oci-network-load-balancer.oraclecloud.com/external-ip-only"
// ServiceAnnotationNetworkLoadBalancerAssignedPrivateIpV4 is s service annotation to provision Network LoadBalancer with an assigned
// IPv4 address from the subnet https://docs.oracle.com/en-us/iaas/api/#/en/networkloadbalancer/20200501/datatypes/CreateNetworkLoadBalancerDetails
ServiceAnnotationNetworkLoadBalancerAssignedPrivateIpV4 = "oci-network-load-balancer.oraclecloud.com/assigned-private-ipv4"
// ServiceAnnotationNetworkLoadBalancerAssignedIpV6 is s service annotation to provision Network LoadBalancer with an assigned
// IPv6 address from the subnet https://docs.oracle.com/en-us/iaas/api/#/en/networkloadbalancer/20200501/datatypes/CreateNetworkLoadBalancerDetails
ServiceAnnotationNetworkLoadBalancerAssignedIpV6 = "oci-network-load-balancer.oraclecloud.com/assigned-ipv6"
)
// Virtual Node Annotations
const (
// PrivateIPOCIDAnnotation is the privateIP OCID of the Container Instance running a virtual pod
// set by the virtual node
PrivateIPOCIDAnnotation = "oci.oraclecloud.com/pod.info.private_ip_ocid"
)
const (
ProtocolGrpc = "GRPC"
DefaultCipherSuiteForGRPC = "oci-default-http2-ssl-cipher-suite-v1"
)
// certificateData is a structure containing the data about a K8S secret required
// to store SSL information required for BackendSets and Listeners
type certificateData struct {
Name string
CACert []byte
PublicCert []byte
PrivateKey []byte
Passphrase []byte
}
type sslSecretReader interface {
readSSLSecret(ns, name string) (sslSecret *certificateData, err error)
}
type noopSSLSecretReader struct{}
func (ssr noopSSLSecretReader) readSSLSecret(ns, name string) (sslSecret *certificateData, err error) {
return nil, nil
}
// SSLConfig is a description of a SSL certificate.
type SSLConfig struct {
Ports sets.Int
ListenerSSLSecretName string
ListenerSSLSecretNamespace string
BackendSetSSLSecretName string
BackendSetSSLSecretNamespace string
sslSecretReader
}
type ManagedNetworkSecurityGroup struct {
nsgRuleManagementMode string
frontendNsgId string
backendNsgId []string
}
func requiresCertificate(svc *v1.Service) bool {
if getLoadBalancerType(svc) == NLB {
return false
}
_, ok := svc.Annotations[ServiceAnnotationLoadBalancerSSLPorts]
return ok
}
func requiresNsgManagement(svc *v1.Service) bool {
manageNSG := strings.ToLower(svc.Annotations[ServiceAnnotationLoadBalancerSecurityRuleManagementMode])
if manageNSG == "nsg" {
return true
}
return false
}
// NewSSLConfig constructs a new SSLConfig.
func NewSSLConfig(secretListenerString string, secretBackendSetString string, service *v1.Service, ports []int, ssr sslSecretReader) *SSLConfig {
if ssr == nil {
ssr = noopSSLSecretReader{}
}
listenerSecretName, listenerSecretNamespace := getSecretParts(secretListenerString, service)
backendSecretName, backendSecretNamespace := getSecretParts(secretBackendSetString, service)
return &SSLConfig{
Ports: sets.NewInt(ports...),
ListenerSSLSecretName: listenerSecretName,
ListenerSSLSecretNamespace: listenerSecretNamespace,
BackendSetSSLSecretName: backendSecretName,
BackendSetSSLSecretNamespace: backendSecretNamespace,
sslSecretReader: ssr,
}
}
// LBSpec holds the data required to build a OCI load balancer from a
// kubernetes service.
type LBSpec struct {
Type string
Name string
Shape string
FlexMin *int
FlexMax *int
Subnets []string
Internal bool
Listeners map[string]client.GenericListener
BackendSets map[string]client.GenericBackendSetDetails
LoadBalancerIP string
IsPreserveSource *bool
Ports map[string]portSpec
SourceCIDRs []string
SSLConfig *SSLConfig
securityListManager securityListManager
ManagedNetworkSecurityGroup *ManagedNetworkSecurityGroup
NetworkSecurityGroupIds []string
IpVersions *IpVersions
FreeformTags map[string]string
DefinedTags map[string]map[string]interface{}
SystemTags map[string]map[string]interface{}
ingressIpMode *v1.LoadBalancerIPMode
Compartment string
RuleSets map[string]loadbalancer.RuleSetDetails
AssignedPrivateIpv4 *string
AssignedIpv6 *string
service *v1.Service
nodes []*v1.Node
}
// NewLBSpec creates a LB Spec from a Kubernetes service and a slice of nodes.
func NewLBSpec(logger *zap.SugaredLogger, svc *v1.Service, provisionedNodes []*v1.Node, subnets []string,
sslConfig *SSLConfig, secListFactory securityListManagerFactory, versions *IpVersions, initialLBTags *config.InitialTags,
existingLB *client.GenericLoadBalancer, clusterCompartment string) (*LBSpec, error) {
if err := validateService(svc); err != nil {
return nil, errors.Wrap(err, "invalid service")
}
lbType := getLoadBalancerType(svc)
ipVersions := &IpVersions{
IpFamilyPolicy: versions.IpFamilyPolicy,
IpFamilies: versions.IpFamilies,
LbEndpointIpVersion: versions.LbEndpointIpVersion,
ListenerBackendIpVersion: versions.ListenerBackendIpVersion,
}
internal, err := isInternalLB(svc)
if err != nil {
return nil, err
}
shape, flexShapeMinMbps, flexShapeMaxMbps, err := getLBShape(svc, existingLB)
if err != nil {
return nil, err
}
sourceCIDRs, err := getLoadBalancerSourceRanges(svc)
if err != nil {
return nil, err
}
ruleSets, err := getRuleSets(svc)
if err != nil {
return nil, err
}
listeners, err := getListeners(svc, sslConfig, convertOciIpVersionsToOciIpFamilies(versions.ListenerBackendIpVersion))
if err != nil {
return nil, err
}
isPreserveSource, err := getPreserveSource(logger, svc)
if err != nil {
return nil, err
}
backendSets, err := getBackendSets(logger, svc, provisionedNodes, sslConfig, isPreserveSource, convertOciIpVersionsToOciIpFamilies(versions.ListenerBackendIpVersion))
if err != nil {
return nil, err
}
ports, err := getPorts(svc, convertOciIpVersionsToOciIpFamilies(versions.ListenerBackendIpVersion))
if err != nil {
return nil, err
}
networkSecurityGroupIds, err := getNetworkSecurityGroupIds(svc)
if err != nil {
return nil, err
}
loadbalancerIP, err := getLoadBalancerIP(svc)
if err != nil {
return nil, err
}
lbTags, err := getLoadBalancerTags(svc, initialLBTags)
if err != nil {
return nil, err
}
// merge lbtags with common tags if present
if enableOkeSystemTags && util.IsCommonTagPresent(initialLBTags) {
lbTags = util.MergeTagConfig(lbTags, initialLBTags.Common)
}
ruleManagementMode, managedNsg, err := getRuleManagementMode(svc)
if err != nil {
return nil, err
}
backendNsgOcids, err := getManagedBackendNSG(svc)
if err != nil {
return nil, err
}
if managedNsg != nil && ruleManagementMode == RuleManagementModeNsg && len(backendNsgOcids) != 0 {
managedNsg.backendNsgId = backendNsgOcids
}
ingressIpMode, err := getIngressIpMode(svc)
if err != nil {
return nil, err
}
compartment := getLoadBalancerCompartment(svc, clusterCompartment)
assignedPrivateIpv4, assignedIpv6, err := getAssignedPrivateIP(logger, svc)
if err != nil {
return nil, err
}
return &LBSpec{
Type: lbType,
Name: GetLoadBalancerName(svc),
Shape: shape,
FlexMin: flexShapeMinMbps,
FlexMax: flexShapeMaxMbps,
Internal: internal,
Subnets: subnets,
Listeners: listeners,
BackendSets: backendSets,
LoadBalancerIP: loadbalancerIP,
IsPreserveSource: &isPreserveSource,
Ports: ports,
SSLConfig: sslConfig,
SourceCIDRs: sourceCIDRs,
NetworkSecurityGroupIds: networkSecurityGroupIds,
ManagedNetworkSecurityGroup: managedNsg,
service: svc,
nodes: provisionedNodes,
securityListManager: secListFactory(ruleManagementMode),
IpVersions: ipVersions,
FreeformTags: lbTags.FreeformTags,
DefinedTags: lbTags.DefinedTags,
SystemTags: getResourceTrackingSystemTagsFromConfig(logger, initialLBTags),
ingressIpMode: ingressIpMode,
Compartment: compartment,
RuleSets: ruleSets,
AssignedPrivateIpv4: assignedPrivateIpv4,
AssignedIpv6: assignedIpv6,
}, nil
}
func getLoadBalancerCompartment(svc *v1.Service, clusterCompartment string) (compartment string) {
compartment = clusterCompartment
if value, exist := svc.Annotations[util.CompartmentIDAnnotation]; exist {
compartment = value
}
return
}
func getSecurityListManagementMode(svc *v1.Service) (string, error) {
lbType := getLoadBalancerType(svc)
logger := *zap.L().Sugar()
knownSecListModes := map[string]struct{}{
ManagementModeAll: struct{}{},
ManagementModeNone: struct{}{},
ManagementModeFrontend: struct{}{},
}
annotationExists := false
var annotationValue string
switch lbType {
case NLB:
{
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationNetworkLoadBalancerSecurityListManagementMode]
if !annotationExists {
return ManagementModeNone, nil
}
if _, ok := knownSecListModes[annotationValue]; !ok {
return "", fmt.Errorf("invalid value: %s provided for annotation: %s", annotationValue, ServiceAnnotationNetworkLoadBalancerSecurityListManagementMode)
}
return svc.Annotations[ServiceAnnotationNetworkLoadBalancerSecurityListManagementMode], nil
}
default:
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationLoadBalancerSecurityListManagementMode]
if !annotationExists {
return ManagementModeAll, nil
}
if _, ok := knownSecListModes[annotationValue]; !ok {
logger.Infof("invalid value: %s provided for annotation: %s; using default All", annotationValue, ServiceAnnotationLoadBalancerSecurityListManagementMode)
return ManagementModeAll, nil
}
return svc.Annotations[ServiceAnnotationLoadBalancerSecurityListManagementMode], nil
}
}
func getRuleManagementMode(svc *v1.Service) (string, *ManagedNetworkSecurityGroup, error) {
knownRuleManagementModes := map[string]struct{}{
RuleManagementModeSlAll: struct{}{},
RuleManagementModeSlFrontend: struct{}{},
RuleManagementModeNsg: struct{}{},
ManagementModeNone: struct{}{},
}
nsg := ManagedNetworkSecurityGroup{
nsgRuleManagementMode: ManagementModeNone,
frontendNsgId: "",
backendNsgId: []string{},
}
annotationExists := false
var annotationValue string
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationLoadBalancerSecurityRuleManagementMode]
if !annotationExists {
secListMode, err := getSecurityListManagementMode(svc)
return secListMode, &nsg, err
}
if strings.ToLower(annotationValue) == strings.ToLower(RuleManagementModeSlAll) {
return ManagementModeAll, &nsg, nil
}
if strings.ToLower(annotationValue) == strings.ToLower(RuleManagementModeSlFrontend) {
return ManagementModeFrontend, &nsg, nil
}
if strings.ToLower(annotationValue) == strings.ToLower(RuleManagementModeNsg) {
nsg = ManagedNetworkSecurityGroup{
nsgRuleManagementMode: RuleManagementModeNsg,
frontendNsgId: "",
backendNsgId: []string{},
}
return RuleManagementModeNsg, &nsg, nil
}
if _, ok := knownRuleManagementModes[annotationValue]; !ok {
return ManagementModeNone, &nsg, fmt.Errorf("invalid value: %s provided for annotation: %s", annotationValue, ServiceAnnotationLoadBalancerSecurityRuleManagementMode)
}
return ManagementModeNone, &nsg, nil
}
func getManagedBackendNSG(svc *v1.Service) ([]string, error) {
backendNsgList := make([]string, 0)
var networkSecurityGroupIds string
var nsgAnnotationString string
var ok bool
networkSecurityGroupIds, ok = svc.Annotations[ServiceAnnotationBackendSecurityRuleManagement]
nsgAnnotationString = ServiceAnnotationBackendSecurityRuleManagement
if !ok || networkSecurityGroupIds == "" {
return backendNsgList, nil
}
numOfNsgIds := 0
for _, nsgOCID := range RemoveDuplicatesFromList(strings.Split(strings.ReplaceAll(networkSecurityGroupIds, " ", ""), ",")) {
numOfNsgIds++
if nsgOCID != "" {
backendNsgList = append(backendNsgList, nsgOCID)
continue
}
return nil, fmt.Errorf("invalid NetworkSecurityGroups OCID: [%s] provided for annotation: %s", networkSecurityGroupIds, nsgAnnotationString)
}
return backendNsgList, nil
}
// Certificates builds a map of required SSL certificates.
func (s *LBSpec) Certificates() (map[string]client.GenericCertificate, error) {
certs := make(map[string]client.GenericCertificate)
if s.SSLConfig == nil {
return certs, nil
}
if s.SSLConfig.ListenerSSLSecretName != "" {
cert, err := s.SSLConfig.readSSLSecret(s.SSLConfig.ListenerSSLSecretNamespace, s.SSLConfig.ListenerSSLSecretName)
if err != nil {
return nil, errors.Wrap(err, "reading SSL Listener Secret")
}
certs[s.SSLConfig.ListenerSSLSecretName] = client.GenericCertificate{
CertificateName: &s.SSLConfig.ListenerSSLSecretName,
CaCertificate: common.String(string(cert.CACert)),
PublicCertificate: common.String(string(cert.PublicCert)),
PrivateKey: common.String(string(cert.PrivateKey)),
Passphrase: common.String(string(cert.Passphrase)),
}
}
if s.SSLConfig.BackendSetSSLSecretName != "" {
cert, err := s.SSLConfig.readSSLSecret(s.SSLConfig.BackendSetSSLSecretNamespace, s.SSLConfig.BackendSetSSLSecretName)
if err != nil {
return nil, errors.Wrap(err, "reading SSL Backend Secret")
}
certs[s.SSLConfig.BackendSetSSLSecretName] = client.GenericCertificate{
CertificateName: &s.SSLConfig.BackendSetSSLSecretName,
CaCertificate: common.String(string(cert.CACert)),
PublicCertificate: common.String(string(cert.PublicCert)),
PrivateKey: common.String(string(cert.PrivateKey)),
Passphrase: common.String(string(cert.Passphrase)),
}
}
return certs, nil
}
// TODO(apryde): aggregate errors using an error list.
func validateService(svc *v1.Service) error {
secListMgmtMode, err := getSecurityListManagementMode(svc)
if err != nil {
return err
}
lbType := getLoadBalancerType(svc)
if err := validateProtocols(svc.Spec.Ports, lbType, secListMgmtMode); err != nil {
return err
}
if svc.Spec.SessionAffinity != v1.ServiceAffinityNone {
return errors.New("OCI only supports SessionAffinity \"None\" currently")
}
return nil
}
func getPreserveSource(logger *zap.SugaredLogger, svc *v1.Service) (bool, error) {
if svc.Annotations[ServiceAnnotationLoadBalancerType] != NLB {
return false, nil
}
// fail the request if externalTrafficPolicy is set to Cluster and is-preserve-source annotation is set
if svc.Spec.ExternalTrafficPolicy == v1.ServiceExternalTrafficPolicyTypeCluster {
_, ok := svc.Annotations[ServiceAnnotationNetworkLoadBalancerIsPreserveSource]
if ok {
logger.Errorf("error : externalTrafficPolicy is set to Cluster and the %s annotation is set", ServiceAnnotationNetworkLoadBalancerIsPreserveSource)
return false, fmt.Errorf("%s annotation cannot be set when externalTrafficPolicy is set to Cluster", ServiceAnnotationNetworkLoadBalancerIsPreserveSource)
}
}
enablePreservation, err := getPreserveSourceAnnotation(logger, svc)
if err != nil {
return false, err
}
if svc.Spec.ExternalTrafficPolicy == v1.ServiceExternalTrafficPolicyTypeLocal && enablePreservation {
return true, nil
}
return false, nil
}
func getPreserveSourceAnnotation(logger *zap.SugaredLogger, svc *v1.Service) (bool, error) {
if annotationString, ok := svc.Annotations[ServiceAnnotationNetworkLoadBalancerIsPreserveSource]; ok {
enable, err := strconv.ParseBool(annotationString)
if err != nil {
logger.Errorf("failed to to parse %s annotation value - %s", ServiceAnnotationNetworkLoadBalancerIsPreserveSource, annotationString)
return false, fmt.Errorf("failed to to parse %s annotation value - %s", ServiceAnnotationNetworkLoadBalancerIsPreserveSource, annotationString)
}
return enable, nil
}
// default behavior is to enable source destination preservation
return true, nil
}
func getLoadBalancerSourceRanges(service *v1.Service) ([]string, error) {
sourceRanges, err := apiservice.GetLoadBalancerSourceRanges(service)
if err != nil {
return []string{}, err
}
requireIPv6 := contains(getIpFamilies(service), IPv6)
sourceCIDRs := make([]string, 0, len(sourceRanges))
for _, sourceRange := range sourceRanges {
sourceCIDRs = append(sourceCIDRs, sourceRange.String())
}
if len(sourceCIDRs) > 1 || (len(sourceCIDRs) == 1 && sourceCIDRs[0] != defaultLoadBalancerSourceRangesIPv4) {
// User provided Loadbalancer source ranges, don't add any
return sourceCIDRs, nil
}
if requireIPv6 {
if !isServiceDualStack(service) {
if len(sourceCIDRs) == 1 && sourceCIDRs[0] == defaultLoadBalancerSourceRangesIPv4 {
sourceCIDRs = removeAtPosition(sourceCIDRs, 0)
}
}
sourceCIDRs = append(sourceCIDRs, defaultLoadBalancerSourceRangesIPv6)
}
return sourceCIDRs, nil
}
func getBackendSetName(protocol string, port int) string {
return fmt.Sprintf("%s-%d", protocol, port)
}
func getPorts(svc *v1.Service, listenerBackendIpVersion []string) (map[string]portSpec, error) {
ports := make(map[string]portSpec)
for backendSetName, servicePort := range getBackendSetNamePortMap(svc) {
healthChecker, err := getHealthChecker(svc)
if err != nil {
return nil, err
}
if strings.Contains(backendSetName, IPv6) && contains(listenerBackendIpVersion, IPv6) {
ports[backendSetName] = portSpec{
BackendPort: int(servicePort.NodePort),
ListenerPort: int(servicePort.Port),
HealthCheckerPort: *healthChecker.Port,
}
} else if !strings.Contains(backendSetName, IPv6) && contains(listenerBackendIpVersion, IPv4) {
ports[backendSetName] = portSpec{
BackendPort: int(servicePort.NodePort),
ListenerPort: int(servicePort.Port),
HealthCheckerPort: *healthChecker.Port,
}
}
}
return ports, nil
}
func getBackends(logger *zap.SugaredLogger, provisionedNodes []*v1.Node, nodePort int32) ([]client.GenericBackend, []client.GenericBackend) {
IPv4Backends := make([]client.GenericBackend, 0)
IPv6Backends := make([]client.GenericBackend, 0)
// Prepare provisioned nodes backends
for _, node := range provisionedNodes {
nodeAddressString := NodeInternalIP(node)
nodeAddressStringV4 := common.String(nodeAddressString.V4)
nodeAddressStringV6 := common.String(nodeAddressString.V6)
if *nodeAddressStringV6 == "" {
// Since Internal IP is optional for IPv6 populate external IP of node if present
externalNodeAddressString := NodeExternalIp(node)
nodeAddressStringV6 = common.String(externalNodeAddressString.V6)
}
if *nodeAddressStringV4 == "" && *nodeAddressStringV6 == "" {
logger.Warnf("Node %q has an empty IP address'", node.Name)
continue
}
instanceID, err := MapProviderIDToResourceID(node.Spec.ProviderID)
if err != nil {
logger.Warnf("Node %q has an empty ProviderID.", node.Name)
continue
}
genericBackend := client.GenericBackend{
Port: common.Int(int(nodePort)),
Weight: common.Int(1),
}
if net2.IsIPv6String(*nodeAddressStringV6) {
// IPv6 IP
genericBackend.IpAddress = nodeAddressStringV6
genericBackend.TargetId = nil
IPv6Backends = append(IPv6Backends, genericBackend)
}
if net2.IsIPv4String(*nodeAddressStringV4) {
// IPv4 IP
genericBackend.IpAddress = nodeAddressStringV4
genericBackend.TargetId = &instanceID
IPv4Backends = append(IPv4Backends, genericBackend)
}
}
return IPv4Backends, IPv6Backends
}
func getBackendSets(logger *zap.SugaredLogger, svc *v1.Service, provisionedNodes []*v1.Node, sslCfg *SSLConfig, isPreserveSource bool, listenerBackendIpVersion []string) (map[string]client.GenericBackendSetDetails, error) {
backendSets := make(map[string]client.GenericBackendSetDetails)
loadbalancerPolicy, err := getLoadBalancerPolicy(svc)
if err != nil {
return nil, err
}
for backendSetName, servicePort := range getBackendSetNamePortMap(svc) {
var secretName string
var sslConfiguration *client.GenericSslConfigurationDetails
if sslCfg != nil && len(sslCfg.BackendSetSSLSecretName) != 0 && getLoadBalancerType(svc) == LB {
secretName = sslCfg.BackendSetSSLSecretName
backendSetSSLConfig, _ := svc.Annotations[ServiceAnnotationLoadbalancerBackendSetSSLConfig]
sslConfiguration, err = getSSLConfiguration(sslCfg, secretName, int(servicePort.Port), backendSetSSLConfig)
if err != nil {
return nil, err
}
}
healthChecker, err := getHealthChecker(svc)
if err != nil {
return nil, err
}
backendsIPv4, backendsIPv6 := getBackends(logger, provisionedNodes, servicePort.NodePort)
genericBackendSetDetails := client.GenericBackendSetDetails{
Name: common.String(backendSetName),
Policy: &loadbalancerPolicy,
HealthChecker: healthChecker,
IsPreserveSource: &isPreserveSource,
SslConfiguration: sslConfiguration,
}
if strings.Contains(backendSetName, IPv6) && contains(listenerBackendIpVersion, IPv6) {
genericBackendSetDetails.IpVersion = GenericIpVersion(client.GenericIPv6)
genericBackendSetDetails.Backends = backendsIPv6
backendSets[backendSetName] = genericBackendSetDetails
} else if !strings.Contains(backendSetName, IPv6) && contains(listenerBackendIpVersion, IPv4) {
genericBackendSetDetails.IpVersion = GenericIpVersion(client.GenericIPv4)
genericBackendSetDetails.Backends = backendsIPv4
backendSets[backendSetName] = genericBackendSetDetails
}
}
return backendSets, nil
}
func getHealthChecker(svc *v1.Service) (*client.GenericHealthChecker, error) {
retries, err := getHealthCheckRetries(svc)
if err != nil {
return nil, err
}
intervalInMillis, err := getHealthCheckInterval(svc)
if err != nil {
return nil, err
}
timeoutInMillis, err := getHealthCheckTimeout(svc)
if err != nil {
return nil, err
}
// Default healthcheck protocol is set to HTTP
isForcePlainText := false
// HTTP healthcheck for HTTPS backends
_, ok := svc.Annotations[ServiceAnnotationLoadBalancerTLSBackendSetSecret]
if ok {
isForcePlainText = true
}
checkPath, checkPort := helper.GetServiceHealthCheckPathPort(svc)
if checkPath != "" {
return &client.GenericHealthChecker{
Protocol: lbNodesHealthCheckProto,
IsForcePlainText: common.Bool(isForcePlainText),
UrlPath: &checkPath,
Port: common.Int(int(checkPort)),
Retries: &retries,
IntervalInMillis: &intervalInMillis,
TimeoutInMillis: &timeoutInMillis,
ReturnCode: common.Int(http.StatusOK),
}, nil
}
return &client.GenericHealthChecker{
Protocol: lbNodesHealthCheckProto,
IsForcePlainText: common.Bool(isForcePlainText),
UrlPath: common.String(lbNodesHealthCheckPath),
Port: common.Int(lbNodesHealthCheckPort),
Retries: &retries,
IntervalInMillis: &intervalInMillis,
TimeoutInMillis: &timeoutInMillis,
ReturnCode: common.Int(http.StatusOK),
}, nil
}
func getHealthCheckRetries(svc *v1.Service) (int, error) {
lbType := getLoadBalancerType(svc)
var retries = 3
annotationValue := ""
annotationExists := false
annotationString := ""
switch lbType {
case NLB:
{
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationNetworkLoadBalancerHealthCheckRetries]
annotationString = ServiceAnnotationNetworkLoadBalancerHealthCheckRetries
}
default:
{
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationLoadBalancerHealthCheckRetries]
annotationString = ServiceAnnotationLoadBalancerHealthCheckRetries
}
}
if annotationExists {
rInt, err := strconv.Atoi(annotationValue)
if err != nil {
return -1, fmt.Errorf("invalid value: %s provided for annotation: %s", annotationValue, annotationString)
}
retries = rInt
}
return retries, nil
}
func getHealthCheckInterval(svc *v1.Service) (int, error) {
lbType := getLoadBalancerType(svc)
var intervalInMillis = 10000
annotationValue := ""
annotationExists := false
annotationString := ""
HealthCheckIntervalMin := LBHealthCheckIntervalMin
HealthCheckIntervalMax := LBHealthCheckIntervalMax
switch lbType {
case NLB:
{
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationNetworkLoadBalancerHealthCheckInterval]
annotationString = ServiceAnnotationNetworkLoadBalancerHealthCheckInterval
HealthCheckIntervalMin = NLBHealthCheckIntervalMin
HealthCheckIntervalMax = NLBHealthCheckIntervalMax
}
default:
{
annotationValue, annotationExists = svc.Annotations[ServiceAnnotationLoadBalancerHealthCheckInterval]
annotationString = ServiceAnnotationLoadBalancerHealthCheckInterval
}
}
if annotationExists {
iInt, err := strconv.Atoi(annotationValue)
if err != nil {
return -1, fmt.Errorf("invalid value: %s provided for annotation: %s", annotationValue, annotationString)
}
intervalInMillis = iInt
if intervalInMillis < HealthCheckIntervalMin || intervalInMillis > HealthCheckIntervalMax {
return -1, fmt.Errorf("invalid value for health check interval, should be between %v and %v", HealthCheckIntervalMin, HealthCheckIntervalMax)
}
}
return intervalInMillis, nil
}
func getHealthCheckTimeout(svc *v1.Service) (int, error) {
lbType := getLoadBalancerType(svc)
var timeoutInMillis = 3000
annotationValue := ""
annotationExists := false
annotationString := ""
switch lbType {
case NLB:
{