-
-
Notifications
You must be signed in to change notification settings - Fork 88
/
service.go
173 lines (156 loc) · 6.41 KB
/
service.go
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
package kubectl
import (
"context"
"strings"
"github.com/gruntwork-io/go-commons/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"github.com/gruntwork-io/kubergrunt/logging"
)
const (
lbTypeAnnotationKey = "service.beta.kubernetes.io/aws-load-balancer-type"
lbTargetAnnotationKey = "service.beta.kubernetes.io/aws-load-balancer-nlb-target-type"
lbTypeAnnotationNLB = "nlb"
lbTypeAnnotationExternal = "external"
lbTargetAnnotationIP = "ip"
lbTargetAnnotationNLBIP = "nlb-ip"
lbTargetAnnotationInstance = "instance"
)
// GetAllServices queries Kubernetes for information on all deployed Service resources in the current cluster that the
// provided client can access.
func GetAllServices(clientset *kubernetes.Clientset) ([]corev1.Service, error) {
// We use the empty string for the namespace to indicate all namespaces
namespace := ""
servicesApi := clientset.CoreV1().Services(namespace)
services := []corev1.Service{}
params := metav1.ListOptions{}
for {
resp, err := servicesApi.List(context.Background(), params)
if err != nil {
return nil, errors.WithStackTrace(err)
}
for _, service := range resp.Items {
services = append(services, service)
}
if resp.Continue == "" {
break
}
params.Continue = resp.Continue
}
return services, nil
}
// GetAWSLoadBalancers will query Kubernetes for all services, filter for LoadBalancer services, and then parse out the
// following information:
// - Type of LB (NLB or Classic LB)
// - Instance target or IP target
// TODO: support ALBs with Ingress as well
func GetAWSLoadBalancers(kubectlOptions *KubectlOptions) ([]AWSLoadBalancer, error) {
logger := logging.GetProjectLogger()
logger.Infof("Getting all LoadBalancers from services in kubernetes")
client, err := GetKubernetesClientFromOptions(kubectlOptions)
if err != nil {
return nil, errors.WithStackTrace(err)
}
services, err := GetAllServices(client)
if err != nil {
return nil, errors.WithStackTrace(err)
}
loadBalancerServices := filterLoadBalancerServices(services)
logger.Infof("Found %d LoadBalancer services of %d services in kubernetes.", len(loadBalancerServices), len(services))
lbs := []AWSLoadBalancer{}
for _, service := range loadBalancerServices {
lbName, err := GetLoadBalancerNameFromService(service)
if err != nil {
return nil, errors.WithStackTrace(err)
}
lbType, lbTargetType, err := GetLoadBalancerTypeFromService(service)
if err != nil {
return nil, err
}
lbs = append(
lbs,
AWSLoadBalancer{
Name: lbName,
Type: lbType,
TargetType: lbTargetType,
},
)
}
logger.Infof("Successfully extracted AWS Load Balancers")
return lbs, nil
}
// filterLoadBalancerServices will return services that are of type LoadBalancer from the provided list of services.
func filterLoadBalancerServices(services []corev1.Service) []corev1.Service {
out := []corev1.Service{}
for _, service := range services {
if service.Spec.Type == corev1.ServiceTypeLoadBalancer {
out = append(out, service)
}
}
return out
}
// GetLoadBalancerNameFromService will return the name of the LoadBalancer given a Kubernetes service object
func GetLoadBalancerNameFromService(service corev1.Service) (string, error) {
loadbalancerInfo := service.Status.LoadBalancer.Ingress
if len(loadbalancerInfo) == 0 {
return "", NewLoadBalancerNotReadyError(service.Name)
}
loadbalancerHostname := loadbalancerInfo[0].Hostname
// TODO: When expanding to GCP, update this logic
return getAWSLoadBalancerNameFromHostname(loadbalancerHostname)
}
// getAWSLoadBalancerNameFromHostname will return the AWS LoadBalancer name given the assigned hostname. For ELB (both
// v1 and v2), the subdomain will be one of NAME-TIME or internal-NAME-TIME. Note that we need to use strings.Join here
// to account for LB names that contain '-'.
func getAWSLoadBalancerNameFromHostname(hostname string) (string, error) {
loadbalancerHostnameSubDomain := strings.Split(hostname, ".")[0]
loadbalancerHostnameSubDomainParts := strings.Split(loadbalancerHostnameSubDomain, "-")
numParts := len(loadbalancerHostnameSubDomainParts)
if numParts < 2 {
return "", NewLoadBalancerNameFormatError(hostname)
} else if loadbalancerHostnameSubDomainParts[0] == "internal" {
return strings.Join(loadbalancerHostnameSubDomainParts[1:numParts-1], "-"), nil
} else {
return strings.Join(loadbalancerHostnameSubDomainParts[:numParts-1], "-"), nil
}
}
// GetLoadBalancerTypeFromService will return the ELB type and target type of the given LoadBalancer Service. This uses
// the following heuristic:
// - A LoadBalancer Service with no type annotations will default to Classic Load Balancer (from the in-tree
// controller).
// - If service.beta.kubernetes.io/aws-load-balancer-type is set to nlb or external, then the ELB will be NLB. (When
// external, we assume the LB controller handles it)
// - For LB services handled by the LB controller, also check for
// service.beta.kubernetes.io/aws-load-balancer-nlb-target-type which determines the target type. Otherwise, it is
// always instance target type.
func GetLoadBalancerTypeFromService(service corev1.Service) (ELBType, ELBTargetType, error) {
annotations := service.ObjectMeta.Annotations
lbTypeString, hasLBTypeAnnotation := annotations[lbTypeAnnotationKey]
if !hasLBTypeAnnotation {
// No annotation base case
return CLB, InstanceTarget, nil
}
if lbTypeString == lbTypeAnnotationNLB {
// in-tree controller based NLB provisioning only supports instance targets
return NLB, InstanceTarget, nil
} else if lbTypeString != lbTypeAnnotationExternal {
// Unsupported load balancer type
return UnknownELB, UnknownELBTarget, errors.WithStackTrace(UnknownAWSLoadBalancerTypeErr{typeKey: lbTypeAnnotationKey, typeStr: lbTypeString})
}
// lbTypeString is external at this point, which means we are using the AWS LB controller. This means we need to
// take into account the target type.
lbTargetTypeString, hasLBTargetAnnotation := annotations[lbTargetAnnotationKey]
if !hasLBTargetAnnotation {
// Default is instance target type
return NLB, InstanceTarget, nil
}
switch lbTargetTypeString {
case lbTargetAnnotationInstance:
return NLB, InstanceTarget, nil
case lbTargetAnnotationIP, lbTargetAnnotationNLBIP:
return NLB, IPTarget, nil
default:
return NLB, UnknownELBTarget, errors.WithStackTrace(UnknownAWSLoadBalancerTypeErr{typeKey: lbTargetAnnotationKey, typeStr: lbTargetTypeString})
}
}