Skip to content

Commit b6fdc23

Browse files
authored
Enhance: support custom health checks for AlibabaCloud-NLB (openkruise#147)
Signed-off-by: ChrisLiu <[email protected]>
1 parent cafaab3 commit b6fdc23

File tree

4 files changed

+669
-44
lines changed

4 files changed

+669
-44
lines changed

cloudprovider/alibabacloud/nlb.go

+209-18
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package alibabacloud
1818

1919
import (
2020
"context"
21+
"fmt"
2122
gamekruiseiov1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1"
2223
"github.com/openkruise/kruise-game/cloudprovider"
2324
cperrors "github.com/openkruise/kruise-game/cloudprovider/errors"
@@ -30,6 +31,7 @@ import (
3031
"k8s.io/apimachinery/pkg/types"
3132
"k8s.io/apimachinery/pkg/util/intstr"
3233
log "k8s.io/klog/v2"
34+
"regexp"
3335
"sigs.k8s.io/controller-runtime/pkg/client"
3436
"strconv"
3537
"strings"
@@ -39,6 +41,30 @@ import (
3941
const (
4042
NlbNetwork = "AlibabaCloud-NLB"
4143
AliasNLB = "NLB-Network"
44+
45+
// annotations provided by AlibabaCloud Cloud Controller Manager
46+
LBHealthCheckFlagAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-flag"
47+
LBHealthCheckTypeAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-type"
48+
LBHealthCheckConnectPortAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-connect-port"
49+
LBHealthCheckConnectTimeoutAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-connect-timeout"
50+
LBHealthyThresholdAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-healthy-threshold"
51+
LBUnhealthyThresholdAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-unhealthy-threshold"
52+
LBHealthCheckIntervalAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-interval"
53+
LBHealthCheckUriAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-uri"
54+
LBHealthCheckDomainAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-domain"
55+
LBHealthCheckMethodAnnotationKey = "service.beta.kubernetes.io/alibaba-cloud-loadbalancer-health-check-method"
56+
57+
// ConfigNames defined by OKG
58+
LBHealthCheckFlagConfigName = "LBHealthCheckFlag"
59+
LBHealthCheckTypeConfigName = "LBHealthCheckType"
60+
LBHealthCheckConnectPortConfigName = "LBHealthCheckConnectPort"
61+
LBHealthCheckConnectTimeoutConfigName = "LBHealthCheckConnectTimeout"
62+
LBHealthCheckIntervalConfigName = "LBHealthCheckInterval"
63+
LBHealthCheckUriConfigName = "LBHealthCheckUri"
64+
LBHealthCheckDomainConfigName = "LBHealthCheckDomain"
65+
LBHealthCheckMethodConfigName = "LBHealthCheckMethod"
66+
LBHealthyThresholdConfigName = "LBHealthyThreshold"
67+
LBUnhealthyThresholdConfigName = "LBUnhealthyThreshold"
4268
)
4369

4470
type NlbPlugin struct {
@@ -50,10 +76,20 @@ type NlbPlugin struct {
5076
}
5177

5278
type nlbConfig struct {
53-
lbIds []string
54-
targetPorts []int
55-
protocols []corev1.Protocol
56-
isFixed bool
79+
lbIds []string
80+
targetPorts []int
81+
protocols []corev1.Protocol
82+
isFixed bool
83+
lBHealthCheckFlag string
84+
lBHealthCheckType string
85+
lBHealthCheckConnectPort string
86+
lBHealthCheckConnectTimeout string
87+
lBHealthCheckInterval string
88+
lBHealthCheckUri string
89+
lBHealthCheckDomain string
90+
lBHealthCheckMethod string
91+
lBHealthyThreshold string
92+
lBUnhealthyThreshold string
5793
}
5894

5995
func (n *NlbPlugin) Name() string {
@@ -91,7 +127,10 @@ func (n *NlbPlugin) OnPodUpdated(c client.Client, pod *corev1.Pod, ctx context.C
91127

92128
networkStatus, _ := networkManager.GetNetworkStatus()
93129
networkConfig := networkManager.GetNetworkConfig()
94-
sc := parseNlbConfig(networkConfig)
130+
sc, err := parseNlbConfig(networkConfig)
131+
if err != nil {
132+
return pod, cperrors.NewPluginError(cperrors.ParameterError, err.Error())
133+
}
95134
if networkStatus == nil {
96135
pod, err := networkManager.UpdateNetworkStatus(gamekruiseiov1alpha1.NetworkStatus{
97136
CurrentNetworkState: gamekruiseiov1alpha1.NetworkNotReady,
@@ -101,7 +140,7 @@ func (n *NlbPlugin) OnPodUpdated(c client.Client, pod *corev1.Pod, ctx context.C
101140

102141
// get svc
103142
svc := &corev1.Service{}
104-
err := c.Get(ctx, types.NamespacedName{
143+
err = c.Get(ctx, types.NamespacedName{
105144
Name: pod.GetName(),
106145
Namespace: pod.GetNamespace(),
107146
}, svc)
@@ -258,15 +297,31 @@ func (n *NlbPlugin) consSvc(nc *nlbConfig, pod *corev1.Pod, c client.Client, ctx
258297

259298
loadBalancerClass := "alibabacloud.com/nlb"
260299

300+
svcAnnotations := map[string]string{
301+
SlbListenerOverrideKey: "true",
302+
SlbIdAnnotationKey: lbId,
303+
SlbConfigHashKey: util.GetHash(nc),
304+
LBHealthCheckFlagAnnotationKey: nc.lBHealthCheckFlag,
305+
}
306+
if nc.lBHealthCheckFlag == "on" {
307+
svcAnnotations[LBHealthCheckTypeAnnotationKey] = nc.lBHealthCheckType
308+
svcAnnotations[LBHealthCheckConnectPortAnnotationKey] = nc.lBHealthCheckConnectPort
309+
svcAnnotations[LBHealthCheckConnectTimeoutAnnotationKey] = nc.lBHealthCheckConnectTimeout
310+
svcAnnotations[LBHealthCheckIntervalAnnotationKey] = nc.lBHealthCheckInterval
311+
svcAnnotations[LBHealthyThresholdAnnotationKey] = nc.lBHealthyThreshold
312+
svcAnnotations[LBUnhealthyThresholdAnnotationKey] = nc.lBUnhealthyThreshold
313+
if nc.lBHealthCheckType == "http" {
314+
svcAnnotations[LBHealthCheckDomainAnnotationKey] = nc.lBHealthCheckDomain
315+
svcAnnotations[LBHealthCheckUriAnnotationKey] = nc.lBHealthCheckUri
316+
svcAnnotations[LBHealthCheckMethodAnnotationKey] = nc.lBHealthCheckMethod
317+
}
318+
}
319+
261320
svc := &corev1.Service{
262321
ObjectMeta: metav1.ObjectMeta{
263-
Name: pod.GetName(),
264-
Namespace: pod.GetNamespace(),
265-
Annotations: map[string]string{
266-
SlbListenerOverrideKey: "true",
267-
SlbIdAnnotationKey: lbId,
268-
SlbConfigHashKey: util.GetHash(nc),
269-
},
322+
Name: pod.GetName(),
323+
Namespace: pod.GetNamespace(),
324+
Annotations: svcAnnotations,
270325
OwnerReferences: getSvcOwnerReference(c, ctx, pod, nc.isFixed),
271326
},
272327
Spec: corev1.ServiceSpec{
@@ -348,11 +403,21 @@ func (n *NlbPlugin) deAllocate(nsName string) {
348403
log.Infof("pod %s deallocate nlb %s ports %v", nsName, lbId, ports)
349404
}
350405

351-
func parseNlbConfig(conf []gamekruiseiov1alpha1.NetworkConfParams) *nlbConfig {
406+
func parseNlbConfig(conf []gamekruiseiov1alpha1.NetworkConfParams) (*nlbConfig, error) {
352407
var lbIds []string
353408
ports := make([]int, 0)
354409
protocols := make([]corev1.Protocol, 0)
355410
isFixed := false
411+
lBHealthCheckFlag := "on"
412+
lBHealthCheckType := "tcp"
413+
lBHealthCheckConnectPort := "0"
414+
lBHealthCheckConnectTimeout := "5"
415+
lBHealthCheckInterval := "10"
416+
lBUnhealthyThreshold := "2"
417+
lBHealthyThreshold := "2"
418+
lBHealthCheckUri := ""
419+
lBHealthCheckDomain := ""
420+
lBHealthCheckMethod := ""
356421
for _, c := range conf {
357422
switch c.Name {
358423
case NlbIdsConfigName:
@@ -381,12 +446,138 @@ func parseNlbConfig(conf []gamekruiseiov1alpha1.NetworkConfParams) *nlbConfig {
381446
continue
382447
}
383448
isFixed = v
449+
case LBHealthCheckFlagConfigName:
450+
flag := strings.ToLower(c.Value)
451+
if flag != "on" && flag != "off" {
452+
return nil, fmt.Errorf("invalid lb health check flag value: %s", c.Value)
453+
}
454+
lBHealthCheckFlag = flag
455+
case LBHealthCheckTypeConfigName:
456+
checkType := strings.ToLower(c.Value)
457+
if checkType != "tcp" && checkType != "http" {
458+
return nil, fmt.Errorf("invalid lb health check type: %s", c.Value)
459+
}
460+
lBHealthCheckType = checkType
461+
case LBHealthCheckConnectPortConfigName:
462+
portInt, err := strconv.Atoi(c.Value)
463+
if err != nil {
464+
return nil, fmt.Errorf("invalid lb health check connect port: %s", c.Value)
465+
}
466+
if portInt < 0 || portInt > 65535 {
467+
return nil, fmt.Errorf("invalid lb health check connect port: %d", portInt)
468+
}
469+
lBHealthCheckConnectPort = c.Value
470+
case LBHealthCheckConnectTimeoutConfigName:
471+
timeoutInt, err := strconv.Atoi(c.Value)
472+
if err != nil {
473+
return nil, fmt.Errorf("invalid lb health check connect timeout: %s", c.Value)
474+
}
475+
if timeoutInt < 1 || timeoutInt > 300 {
476+
return nil, fmt.Errorf("invalid lb health check connect timeout: %d", timeoutInt)
477+
}
478+
lBHealthCheckConnectTimeout = c.Value
479+
case LBHealthCheckIntervalConfigName:
480+
intervalInt, err := strconv.Atoi(c.Value)
481+
if err != nil {
482+
return nil, fmt.Errorf("invalid lb health check interval: %s", c.Value)
483+
}
484+
if intervalInt < 1 || intervalInt > 50 {
485+
return nil, fmt.Errorf("invalid lb health check interval: %d", intervalInt)
486+
}
487+
lBHealthCheckInterval = c.Value
488+
case LBHealthyThresholdConfigName:
489+
thresholdInt, err := strconv.Atoi(c.Value)
490+
if err != nil {
491+
return nil, fmt.Errorf("invalid lb healthy threshold: %s", c.Value)
492+
}
493+
if thresholdInt < 2 || thresholdInt > 10 {
494+
return nil, fmt.Errorf("invalid lb healthy threshold: %d", thresholdInt)
495+
}
496+
lBHealthyThreshold = c.Value
497+
case LBUnhealthyThresholdConfigName:
498+
thresholdInt, err := strconv.Atoi(c.Value)
499+
if err != nil {
500+
return nil, fmt.Errorf("invalid lb unhealthy threshold: %s", c.Value)
501+
}
502+
if thresholdInt < 2 || thresholdInt > 10 {
503+
return nil, fmt.Errorf("invalid lb unhealthy threshold: %d", thresholdInt)
504+
}
505+
lBUnhealthyThreshold = c.Value
506+
case LBHealthCheckUriConfigName:
507+
if validateUri(c.Value) != nil {
508+
return nil, fmt.Errorf("invalid lb health check uri: %s", c.Value)
509+
}
510+
lBHealthCheckUri = c.Value
511+
case LBHealthCheckDomainConfigName:
512+
if validateDomain(c.Value) != nil {
513+
return nil, fmt.Errorf("invalid lb health check domain: %s", c.Value)
514+
}
515+
lBHealthCheckDomain = c.Value
516+
case LBHealthCheckMethodConfigName:
517+
method := strings.ToLower(c.Value)
518+
if method != "get" && method != "head" {
519+
return nil, fmt.Errorf("invalid lb health check method: %s", c.Value)
520+
}
521+
lBHealthCheckMethod = method
384522
}
385523
}
386524
return &nlbConfig{
387-
lbIds: lbIds,
388-
protocols: protocols,
389-
targetPorts: ports,
390-
isFixed: isFixed,
525+
lbIds: lbIds,
526+
protocols: protocols,
527+
targetPorts: ports,
528+
isFixed: isFixed,
529+
lBHealthCheckFlag: lBHealthCheckFlag,
530+
lBHealthCheckType: lBHealthCheckType,
531+
lBHealthCheckConnectPort: lBHealthCheckConnectPort,
532+
lBHealthCheckConnectTimeout: lBHealthCheckConnectTimeout,
533+
lBHealthCheckInterval: lBHealthCheckInterval,
534+
lBHealthCheckUri: lBHealthCheckUri,
535+
lBHealthCheckDomain: lBHealthCheckDomain,
536+
lBHealthCheckMethod: lBHealthCheckMethod,
537+
lBHealthyThreshold: lBHealthyThreshold,
538+
lBUnhealthyThreshold: lBUnhealthyThreshold,
539+
}, nil
540+
}
541+
542+
func validateDomain(domain string) error {
543+
if len(domain) < 1 || len(domain) > 80 {
544+
return fmt.Errorf("the domain length must be between 1 and 80 characters")
545+
}
546+
547+
// Regular expression matches lowercase letters, numbers, dashes and periods
548+
domainRegex := regexp.MustCompile(`^[a-z0-9-.]+$`)
549+
if !domainRegex.MatchString(domain) {
550+
return fmt.Errorf("the domain must only contain lowercase letters, numbers, hyphens, and periods")
551+
}
552+
553+
// make sure the domain name does not start or end with a dash or period
554+
if domain[0] == '-' || domain[0] == '.' || domain[len(domain)-1] == '-' || domain[len(domain)-1] == '.' {
555+
return fmt.Errorf("the domain must not start or end with a hyphen or period")
556+
}
557+
558+
// make sure the domain name does not contain consecutive dots or dashes
559+
if regexp.MustCompile(`(--|\.\.)`).MatchString(domain) {
560+
return fmt.Errorf("the domain must not contain consecutive hyphens or periods")
561+
}
562+
563+
return nil
564+
}
565+
566+
func validateUri(uri string) error {
567+
if len(uri) < 1 || len(uri) > 80 {
568+
return fmt.Errorf("string length must be between 1 and 80 characters")
569+
}
570+
571+
regexPattern := `^/[0-9a-zA-Z.!$%&'*+/=?^_` + "`" + `{|}~-]*$`
572+
matched, err := regexp.MatchString(regexPattern, uri)
573+
574+
if err != nil {
575+
return fmt.Errorf("regex error: %v", err)
576+
}
577+
578+
if !matched {
579+
return fmt.Errorf("string does not match the required pattern")
391580
}
581+
582+
return nil
392583
}

0 commit comments

Comments
 (0)