-
Notifications
You must be signed in to change notification settings - Fork 343
/
endpointslices.go
156 lines (137 loc) · 5.88 KB
/
endpointslices.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
package kubernetes
import (
"github.com/zalando/skipper/dataclients/kubernetes/definitions"
)
// There are [1..N] Kubernetes endpointslices created for a single Kubernetes service.
// Kubernetes endpointslices of a given service can have duplicates with different states.
// Therefore Kubernetes endpointslices need to be de-duplicated before usage.
// The business object skipperEndpointSlice is a de-duplicated endpoint list that concats all endpointslices of a given service into one slice of skipperEndpointSlice.
type skipperEndpointSlice struct {
Meta *definitions.Metadata
Endpoints []*skipperEndpoint
Ports []*endpointSlicePort
}
// Conditions have to be evaluated before creation
type skipperEndpoint struct {
Address string
Zone string
}
func (eps *skipperEndpointSlice) getPort(protocol, pName string, pValue int) int {
var port int
for _, p := range eps.Ports {
if protocol != "" && p.Protocol != protocol {
continue
}
// https://pkg.go.dev/k8s.io/api/core/v1#ServicePort
// Optional if only one ServicePort is defined on this service.
// Therefore empty name match is fine.
if p.Name == pName {
port = p.Port
break
}
if pValue != 0 && p.Port == pValue {
port = pValue
break
}
}
return port
}
func (eps *skipperEndpointSlice) targetsByServicePort(protocol, scheme string, servicePort *servicePort) []string {
var port int
if servicePort.Name != "" {
port = eps.getPort(protocol, servicePort.Name, servicePort.Port)
} else if servicePort.TargetPort != nil {
var ok bool
port, ok = servicePort.TargetPort.Number()
if !ok {
port = eps.getPort(protocol, servicePort.Name, servicePort.Port)
}
} else {
port = eps.getPort(protocol, servicePort.Name, servicePort.Port)
}
result := make([]string, 0, len(eps.Endpoints))
for _, ep := range eps.Endpoints {
result = append(result, formatEndpointString(ep.Address, scheme, port))
}
return result
}
func (eps *skipperEndpointSlice) targetsByServiceTarget(protocol, scheme string, serviceTarget *definitions.BackendPort) []string {
pName, _ := serviceTarget.Value.(string)
pValue, _ := serviceTarget.Value.(int)
port := eps.getPort(protocol, pName, pValue)
result := make([]string, 0, len(eps.Endpoints))
for _, ep := range eps.Endpoints {
result = append(result, formatEndpointString(ep.Address, scheme, port))
}
return result
}
func (eps *skipperEndpointSlice) targets(protocol, scheme string) []string {
result := make([]string, 0, len(eps.Endpoints))
var port int
for _, p := range eps.Ports {
if p.Protocol == protocol {
port = p.Port
break
}
}
for _, ep := range eps.Endpoints {
result = append(result, formatEndpointString(ep.Address, scheme, port))
}
return result
}
type endpointSliceList struct {
Meta *definitions.Metadata
Items []*endpointSlice `json:"items"`
}
// see https://kubernetes.io/docs/reference/kubernetes-api/service-resources/endpoint-slice-v1/#EndpointSlice
type endpointSlice struct {
Meta *definitions.Metadata `json:"metadata"`
AddressType string `json:"addressType"` // "IPv4"
Endpoints []*EndpointSliceEndpoints `json:"endpoints"`
Ports []*endpointSlicePort `json:"ports"` // contains all ports like 9999/9911
}
// ToResourceID returns the same string for a group endpointlisces created for the same svc
func (eps *endpointSlice) ToResourceID() definitions.ResourceID {
svcName := eps.Meta.Labels["kubernetes.io/service-name"]
namespace := eps.Meta.Namespace
return newResourceID(namespace, svcName)
}
// EndpointSliceEndpoints is the single endpoint definition
type EndpointSliceEndpoints struct {
// Addresses [1..100] of the same AddressType, see also https://github.com/kubernetes/kubernetes/issues/106267
// Basically it always has only one in our case and likely makes no sense to use more than one.
// Pick first or one at random are possible, but skipper will pick the first.
// If you need something else please create an issue https://github.com/zalando/skipper/issues/new/choose
Addresses []string `json:"addresses"` // [ "10.2.13.9" ]
// Conditions are used for deciding to drop out of load balancer or fade into the load balancer.
Conditions *endpointsliceCondition `json:"conditions"`
// Zone is used for zone aware traffic routing, please see also
// https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/#constraints
// https://kubernetes.io/docs/concepts/services-networking/topology-aware-routing/#safeguards
// Zone aware routing will be available if https://github.com/zalando/skipper/issues/1446 is closed.
Zone string `json:"zone"` // "eu-central-1c"
}
type endpointsliceCondition struct {
Ready *bool `json:"ready"` // ready endpoint -> put into endpoints unless terminating
Serving *bool `json:"serving"` // serving endpoint
Terminating *bool `json:"terminating"` // termiating pod -> drop out of endpoints
}
type endpointSlicePort struct {
Name string `json:"name"` // "http"
Port int `json:"port"` // 8080
Protocol string `json:"protocol"` // "TCP"
// AppProtocol is not used, but would make it possible to optimize H2C and websocket connections
AppProtocol string `json:"appProtocol"` // "kubernetes.io/h2c", "kubernetes.io/ws", "kubernetes.io/wss"
}
func (ep *EndpointSliceEndpoints) isTerminating() bool {
// see also https://github.com/kubernetes/kubernetes/blob/91aca10d5984313c1c5858979d4946ff9446615f/pkg/proxy/endpointslicecache.go#L137C39-L139
return ep.Conditions != nil && ep.Conditions.Terminating != nil && *ep.Conditions.Terminating
}
func (ep *EndpointSliceEndpoints) isReady() bool {
if ep.isTerminating() {
return false
}
// defaults to ready, see also https://github.com/kubernetes/kubernetes/blob/91aca10d5984313c1c5858979d4946ff9446615f/pkg/proxy/endpointslicecache.go#L137C39-L139
// we ignore serving because of https://github.com/zalando/skipper/issues/2684
return ep.Conditions.Ready == nil || *ep.Conditions.Ready
}