mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Adding AppProtocol to Service and Endpoints Ports
This commit is contained in:
parent
4e79344501
commit
6a33727632
10
api/openapi-spec/swagger.json
generated
10
api/openapi-spec/swagger.json
generated
@ -5951,6 +5951,10 @@
|
||||
"io.k8s.api.core.v1.EndpointPort": {
|
||||
"description": "EndpointPort is a tuple that describes a single port.",
|
||||
"properties": {
|
||||
"appProtocol": {
|
||||
"description": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol. Field can be enabled with ServiceAppProtocol feature gate.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of this port. This must match the 'name' field in the corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port is defined.",
|
||||
"type": "string"
|
||||
@ -9847,6 +9851,10 @@
|
||||
"io.k8s.api.core.v1.ServicePort": {
|
||||
"description": "ServicePort contains information on service's port.",
|
||||
"properties": {
|
||||
"appProtocol": {
|
||||
"description": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol. Field can be enabled with ServiceAppProtocol feature gate.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. When considering the endpoints for a Service, this must match the 'name' field in the EndpointPort. Optional if only one ServicePort is defined on this service.",
|
||||
"type": "string"
|
||||
@ -10529,7 +10537,7 @@
|
||||
"description": "EndpointPort represents a Port used by an EndpointSlice",
|
||||
"properties": {
|
||||
"appProtocol": {
|
||||
"description": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names. Default is empty string.",
|
||||
"description": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
|
@ -3532,6 +3532,16 @@ type ServicePort struct {
|
||||
// The IP protocol for this port. Supports "TCP", "UDP", and "SCTP".
|
||||
Protocol Protocol
|
||||
|
||||
// The application protocol for this port.
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// Field can be enabled with ServiceAppProtocol feature gate.
|
||||
// +optional
|
||||
AppProtocol *string
|
||||
|
||||
// The port that will be exposed on the service.
|
||||
Port int32
|
||||
|
||||
@ -3672,6 +3682,16 @@ type EndpointPort struct {
|
||||
|
||||
// The IP protocol for this port.
|
||||
Protocol Protocol
|
||||
|
||||
// The application protocol for this port.
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// Field can be enabled with ServiceAppProtocol feature gate.
|
||||
// +optional
|
||||
AppProtocol *string
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
4
pkg/apis/core/v1/zz_generated.conversion.go
generated
4
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -3226,6 +3226,7 @@ func autoConvert_v1_EndpointPort_To_core_EndpointPort(in *v1.EndpointPort, out *
|
||||
out.Name = in.Name
|
||||
out.Port = in.Port
|
||||
out.Protocol = core.Protocol(in.Protocol)
|
||||
out.AppProtocol = (*string)(unsafe.Pointer(in.AppProtocol))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3238,6 +3239,7 @@ func autoConvert_core_EndpointPort_To_v1_EndpointPort(in *core.EndpointPort, out
|
||||
out.Name = in.Name
|
||||
out.Port = in.Port
|
||||
out.Protocol = v1.Protocol(in.Protocol)
|
||||
out.AppProtocol = (*string)(unsafe.Pointer(in.AppProtocol))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -7405,6 +7407,7 @@ func Convert_core_ServiceList_To_v1_ServiceList(in *core.ServiceList, out *v1.Se
|
||||
func autoConvert_v1_ServicePort_To_core_ServicePort(in *v1.ServicePort, out *core.ServicePort, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Protocol = core.Protocol(in.Protocol)
|
||||
out.AppProtocol = (*string)(unsafe.Pointer(in.AppProtocol))
|
||||
out.Port = in.Port
|
||||
out.TargetPort = in.TargetPort
|
||||
out.NodePort = in.NodePort
|
||||
@ -7419,6 +7422,7 @@ func Convert_v1_ServicePort_To_core_ServicePort(in *v1.ServicePort, out *core.Se
|
||||
func autoConvert_core_ServicePort_To_v1_ServicePort(in *core.ServicePort, out *v1.ServicePort, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Protocol = v1.Protocol(in.Protocol)
|
||||
out.AppProtocol = (*string)(unsafe.Pointer(in.AppProtocol))
|
||||
out.Port = in.Port
|
||||
out.TargetPort = in.TargetPort
|
||||
out.NodePort = in.NodePort
|
||||
|
@ -3908,7 +3908,7 @@ var supportedServiceType = sets.NewString(string(core.ServiceTypeClusterIP), str
|
||||
var supportedServiceIPFamily = sets.NewString(string(core.IPv4Protocol), string(core.IPv6Protocol))
|
||||
|
||||
// ValidateService tests if required fields/annotations of a Service are valid.
|
||||
func ValidateService(service *core.Service) field.ErrorList {
|
||||
func ValidateService(service *core.Service, allowAppProtocol bool) field.ErrorList {
|
||||
allErrs := ValidateObjectMeta(&service.ObjectMeta, true, ValidateServiceName, field.NewPath("metadata"))
|
||||
|
||||
specPath := field.NewPath("spec")
|
||||
@ -3953,7 +3953,7 @@ func ValidateService(service *core.Service) field.ErrorList {
|
||||
portsPath := specPath.Child("ports")
|
||||
for i := range service.Spec.Ports {
|
||||
portPath := portsPath.Index(i)
|
||||
allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, isHeadlessService, &allPortNames, portPath)...)
|
||||
allErrs = append(allErrs, validateServicePort(&service.Spec.Ports[i], len(service.Spec.Ports) > 1, isHeadlessService, allowAppProtocol, &allPortNames, portPath)...)
|
||||
}
|
||||
|
||||
if service.Spec.Selector != nil {
|
||||
@ -4125,7 +4125,7 @@ func ValidateService(service *core.Service) field.ErrorList {
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bool, allNames *sets.String, fldPath *field.Path) field.ErrorList {
|
||||
func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService, allowAppProtocol bool, allNames *sets.String, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if requireName && len(sp.Name) == 0 {
|
||||
@ -4151,6 +4151,16 @@ func validateServicePort(sp *core.ServicePort, requireName, isHeadlessService bo
|
||||
|
||||
allErrs = append(allErrs, ValidatePortNumOrName(sp.TargetPort, fldPath.Child("targetPort"))...)
|
||||
|
||||
if sp.AppProtocol != nil {
|
||||
if allowAppProtocol {
|
||||
for _, msg := range validation.IsQualifiedName(*sp.AppProtocol) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("appProtocol"), sp.AppProtocol, msg))
|
||||
}
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("appProtocol"), "This field can be enabled with the ServiceAppProtocol feature gate"))
|
||||
}
|
||||
}
|
||||
|
||||
// in the v1 API, targetPorts on headless services were tolerated.
|
||||
// once we have version-specific validation, we can reject this on newer API versions, but until then, we have to tolerate it for compatibility.
|
||||
//
|
||||
@ -4207,6 +4217,14 @@ func ValidateServiceExternalTrafficFieldsCombination(service *core.Service) fiel
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateServiceCreate validates Services as they are created.
|
||||
func ValidateServiceCreate(service *core.Service) field.ErrorList {
|
||||
// allow AppProtocol value if the feature gate is set.
|
||||
allowAppProtocol := utilfeature.DefaultFeatureGate.Enabled(features.ServiceAppProtocol)
|
||||
|
||||
return ValidateService(service, allowAppProtocol)
|
||||
}
|
||||
|
||||
// ValidateServiceUpdate tests if required fields in the service are set during an update
|
||||
func ValidateServiceUpdate(service, oldService *core.Service) field.ErrorList {
|
||||
allErrs := ValidateObjectMetaUpdate(&service.ObjectMeta, &oldService.ObjectMeta, field.NewPath("metadata"))
|
||||
@ -4226,8 +4244,19 @@ func ValidateServiceUpdate(service, oldService *core.Service) field.ErrorList {
|
||||
}
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, ValidateService(service)...)
|
||||
return allErrs
|
||||
// allow AppProtocol value if the feature gate is set or the field is
|
||||
// already set on the resource.
|
||||
allowAppProtocol := utilfeature.DefaultFeatureGate.Enabled(features.ServiceAppProtocol)
|
||||
if !allowAppProtocol {
|
||||
for _, port := range oldService.Spec.Ports {
|
||||
if port.AppProtocol != nil {
|
||||
allowAppProtocol = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return append(allErrs, ValidateService(service, allowAppProtocol)...)
|
||||
}
|
||||
|
||||
// ValidateServiceStatusUpdate tests if required fields in the Service are set when updating status.
|
||||
@ -5443,15 +5472,42 @@ func ValidateNamespaceFinalizeUpdate(newNamespace, oldNamespace *core.Namespace)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateEndpoints tests if required fields are set.
|
||||
func ValidateEndpoints(endpoints *core.Endpoints) field.ErrorList {
|
||||
// ValidateEndpoints validates Endpoints on create and update.
|
||||
func ValidateEndpoints(endpoints *core.Endpoints, allowAppProtocol bool) field.ErrorList {
|
||||
allErrs := ValidateObjectMeta(&endpoints.ObjectMeta, true, ValidateEndpointsName, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(endpoints.Annotations, field.NewPath("annotations"))...)
|
||||
allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, field.NewPath("subsets"))...)
|
||||
allErrs = append(allErrs, validateEndpointSubsets(endpoints.Subsets, allowAppProtocol, field.NewPath("subsets"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateEndpointSubsets(subsets []core.EndpointSubset, fldPath *field.Path) field.ErrorList {
|
||||
// ValidateEndpointsCreate validates Endpoints on create.
|
||||
func ValidateEndpointsCreate(endpoints *core.Endpoints) field.ErrorList {
|
||||
allowAppProtocol := utilfeature.DefaultFeatureGate.Enabled(features.ServiceAppProtocol)
|
||||
return ValidateEndpoints(endpoints, allowAppProtocol)
|
||||
}
|
||||
|
||||
// ValidateEndpointsUpdate validates Endpoints on update. NodeName changes are
|
||||
// allowed during update to accommodate the case where nodeIP or PodCIDR is
|
||||
// reused. An existing endpoint ip will have a different nodeName if this
|
||||
// happens.
|
||||
func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *core.Endpoints) field.ErrorList {
|
||||
allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata"))
|
||||
allowAppProtocol := utilfeature.DefaultFeatureGate.Enabled(features.ServiceAppProtocol)
|
||||
if !allowAppProtocol {
|
||||
for _, oldSubset := range oldEndpoints.Subsets {
|
||||
for _, port := range oldSubset.Ports {
|
||||
if port.AppProtocol != nil {
|
||||
allowAppProtocol = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allErrs = append(allErrs, ValidateEndpoints(newEndpoints, allowAppProtocol)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateEndpointSubsets(subsets []core.EndpointSubset, allowAppProtocol bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i := range subsets {
|
||||
ss := &subsets[i]
|
||||
@ -5469,7 +5525,7 @@ func validateEndpointSubsets(subsets []core.EndpointSubset, fldPath *field.Path)
|
||||
allErrs = append(allErrs, validateEndpointAddress(&ss.NotReadyAddresses[addr], idxPath.Child("notReadyAddresses").Index(addr))...)
|
||||
}
|
||||
for port := range ss.Ports {
|
||||
allErrs = append(allErrs, validateEndpointPort(&ss.Ports[port], len(ss.Ports) > 1, idxPath.Child("ports").Index(port))...)
|
||||
allErrs = append(allErrs, validateEndpointPort(&ss.Ports[port], len(ss.Ports) > 1, allowAppProtocol, idxPath.Child("ports").Index(port))...)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5520,7 +5576,7 @@ func validateNonSpecialIP(ipAddress string, fldPath *field.Path) field.ErrorList
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *field.Path) field.ErrorList {
|
||||
func validateEndpointPort(port *core.EndpointPort, requireName, allowAppProtocol bool, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if requireName && len(port.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
@ -5535,16 +5591,15 @@ func validateEndpointPort(port *core.EndpointPort, requireName bool, fldPath *fi
|
||||
} else if !supportedPortProtocols.Has(string(port.Protocol)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("protocol"), port.Protocol, supportedPortProtocols.List()))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidateEndpointsUpdate tests to make sure an endpoints update can be applied.
|
||||
// NodeName changes are allowed during update to accommodate the case where nodeIP or PodCIDR is reused.
|
||||
// An existing endpoint ip will have a different nodeName if this happens.
|
||||
func ValidateEndpointsUpdate(newEndpoints, oldEndpoints *core.Endpoints) field.ErrorList {
|
||||
allErrs := ValidateObjectMetaUpdate(&newEndpoints.ObjectMeta, &oldEndpoints.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, validateEndpointSubsets(newEndpoints.Subsets, field.NewPath("subsets"))...)
|
||||
allErrs = append(allErrs, ValidateEndpointsSpecificAnnotations(newEndpoints.Annotations, field.NewPath("annotations"))...)
|
||||
if port.AppProtocol != nil {
|
||||
if allowAppProtocol {
|
||||
for _, msg := range validation.IsQualifiedName(*port.AppProtocol) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("appProtocol"), port.AppProtocol, msg))
|
||||
}
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("appProtocol"), "This field can be enabled with the ServiceAppProtocol feature gate"))
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
|
@ -9387,14 +9387,15 @@ func TestValidatePodEphemeralContainersUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateService(t *testing.T) {
|
||||
func TestValidateServiceCreate(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SCTPSupport, true)()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceTopology, true)()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
tweakSvc func(svc *core.Service) // given a basic valid service, each test case can customize it
|
||||
numErrs int
|
||||
name string
|
||||
tweakSvc func(svc *core.Service) // given a basic valid service, each test case can customize it
|
||||
numErrs int
|
||||
appProtocolEnabled bool
|
||||
}{
|
||||
{
|
||||
name: "missing namespace",
|
||||
@ -10128,15 +10129,57 @@ func TestValidateService(t *testing.T) {
|
||||
},
|
||||
numErrs: 1,
|
||||
},
|
||||
{
|
||||
name: `valid appProtocol`,
|
||||
tweakSvc: func(s *core.Service) {
|
||||
s.Spec.Ports = []core.ServicePort{{
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
Protocol: "TCP",
|
||||
AppProtocol: utilpointer.StringPtr("HTTP"),
|
||||
}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numErrs: 0,
|
||||
},
|
||||
{
|
||||
name: `valid custom appProtocol`,
|
||||
tweakSvc: func(s *core.Service) {
|
||||
s.Spec.Ports = []core.ServicePort{{
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
Protocol: "TCP",
|
||||
AppProtocol: utilpointer.StringPtr("example.com/protocol"),
|
||||
}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numErrs: 0,
|
||||
},
|
||||
{
|
||||
name: `invalid appProtocol`,
|
||||
tweakSvc: func(s *core.Service) {
|
||||
s.Spec.Ports = []core.ServicePort{{
|
||||
Port: 12345,
|
||||
TargetPort: intstr.FromInt(12345),
|
||||
Protocol: "TCP",
|
||||
AppProtocol: utilpointer.StringPtr("example.com/protocol_with{invalid}[characters]"),
|
||||
}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numErrs: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
svc := makeValidService()
|
||||
tc.tweakSvc(&svc)
|
||||
errs := ValidateService(&svc)
|
||||
if len(errs) != tc.numErrs {
|
||||
t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAppProtocol, true)()
|
||||
svc := makeValidService()
|
||||
tc.tweakSvc(&svc)
|
||||
errs := ValidateServiceCreate(&svc)
|
||||
if len(errs) != tc.numErrs {
|
||||
t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -11736,9 +11779,10 @@ func TestValidateNodeUpdate(t *testing.T) {
|
||||
|
||||
func TestValidateServiceUpdate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them
|
||||
numErrs int
|
||||
name string
|
||||
tweakSvc func(oldSvc, newSvc *core.Service) // given basic valid services, each test case can customize them
|
||||
numErrs int
|
||||
appProtocolEnabled bool
|
||||
}{
|
||||
{
|
||||
name: "no change",
|
||||
@ -12182,16 +12226,54 @@ func TestValidateServiceUpdate(t *testing.T) {
|
||||
},
|
||||
numErrs: 1,
|
||||
},
|
||||
{
|
||||
name: "update with valid app protocol, field unset, gate disabled",
|
||||
tweakSvc: func(oldSvc, newSvc *core.Service) {
|
||||
oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP"}}
|
||||
newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP", AppProtocol: utilpointer.StringPtr("https")}}
|
||||
},
|
||||
numErrs: 1,
|
||||
},
|
||||
{
|
||||
name: "update to valid app protocol, field set, gate disabled",
|
||||
tweakSvc: func(oldSvc, newSvc *core.Service) {
|
||||
oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP", AppProtocol: utilpointer.StringPtr("http")}}
|
||||
newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP", AppProtocol: utilpointer.StringPtr("https")}}
|
||||
},
|
||||
numErrs: 0,
|
||||
},
|
||||
{
|
||||
name: "update to valid app protocol, gate enabled",
|
||||
tweakSvc: func(oldSvc, newSvc *core.Service) {
|
||||
oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP"}}
|
||||
newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP", AppProtocol: utilpointer.StringPtr("https")}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numErrs: 0,
|
||||
},
|
||||
{
|
||||
name: "update to invalid app protocol, gate enabled",
|
||||
tweakSvc: func(oldSvc, newSvc *core.Service) {
|
||||
oldSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP"}}
|
||||
newSvc.Spec.Ports = []core.ServicePort{{Name: "a", Port: 443, TargetPort: intstr.FromInt(3000), Protocol: "TCP", AppProtocol: utilpointer.StringPtr("~https")}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numErrs: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
oldSvc := makeValidService()
|
||||
newSvc := makeValidService()
|
||||
tc.tweakSvc(&oldSvc, &newSvc)
|
||||
errs := ValidateServiceUpdate(&newSvc, &oldSvc)
|
||||
if len(errs) != tc.numErrs {
|
||||
t.Errorf("Unexpected error list for case %q: %v", tc.name, errs.ToAggregate())
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAppProtocol, tc.appProtocolEnabled)()
|
||||
|
||||
oldSvc := makeValidService()
|
||||
newSvc := makeValidService()
|
||||
tc.tweakSvc(&oldSvc, &newSvc)
|
||||
errs := ValidateServiceUpdate(&newSvc, &oldSvc)
|
||||
if len(errs) != tc.numErrs {
|
||||
t.Errorf("Expected %d errors, got %d: %v", tc.numErrs, len(errs), errs.ToAggregate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -13478,53 +13560,82 @@ func TestValidateSSHAuthSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEndpoints(t *testing.T) {
|
||||
successCases := map[string]core.Endpoints{
|
||||
func TestValidateEndpointsCreate(t *testing.T) {
|
||||
successCases := map[string]struct {
|
||||
endpoints core.Endpoints
|
||||
appProtocolEnabled bool
|
||||
}{
|
||||
"simple endpoint": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
|
||||
Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
|
||||
},
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
|
||||
Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
|
||||
endpoints: core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}, {IP: "10.10.2.2"}},
|
||||
Ports: []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}, {Name: "b", Port: 309, Protocol: "TCP"}},
|
||||
},
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
|
||||
Ports: []core.EndpointPort{{Name: "a", Port: 93, Protocol: "TCP"}, {Name: "b", Port: 76, Protocol: "TCP"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"empty subsets": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
endpoints: core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
},
|
||||
},
|
||||
"no name required for singleton port": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
|
||||
Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
|
||||
endpoints: core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
|
||||
Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"valid appProtocol": {
|
||||
endpoints: core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
|
||||
Ports: []core.EndpointPort{{Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("HTTP")}},
|
||||
},
|
||||
},
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
},
|
||||
"empty ports": {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
|
||||
endpoints: core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.3.3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range successCases {
|
||||
if errs := ValidateEndpoints(&v); len(errs) != 0 {
|
||||
t.Errorf("Expected success for %s, got %v", k, errs)
|
||||
}
|
||||
for name, tc := range successCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAppProtocol, tc.appProtocolEnabled)()
|
||||
errs := ValidateEndpointsCreate(&tc.endpoints)
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("Expected no validation errors, got %v", errs)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
errorCases := map[string]struct {
|
||||
endpoints core.Endpoints
|
||||
errorType field.ErrorType
|
||||
errorDetail string
|
||||
endpoints core.Endpoints
|
||||
appProtocolEnabled bool
|
||||
errorType field.ErrorType
|
||||
errorDetail string
|
||||
}{
|
||||
"missing namespace": {
|
||||
endpoints: core.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: "mysvc"}},
|
||||
@ -13682,12 +13793,100 @@ func TestValidateEndpoints(t *testing.T) {
|
||||
errorType: "FieldValueInvalid",
|
||||
errorDetail: "link-local multicast",
|
||||
},
|
||||
"Invalid AppProtocol": {
|
||||
endpoints: core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace"},
|
||||
Subsets: []core.EndpointSubset{
|
||||
{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.10.1.1"}},
|
||||
Ports: []core.EndpointPort{{Name: "p", Port: 93, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("lots-of[invalid]-{chars}")}},
|
||||
},
|
||||
},
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
errorType: "FieldValueInvalid",
|
||||
errorDetail: "name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character",
|
||||
},
|
||||
}
|
||||
|
||||
for k, v := range errorCases {
|
||||
if errs := ValidateEndpoints(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
|
||||
t.Errorf("[%s] Expected error type %s with detail %q, got %v", k, v.errorType, v.errorDetail, errs)
|
||||
}
|
||||
t.Run(k, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAppProtocol, v.appProtocolEnabled)()
|
||||
if errs := ValidateEndpointsCreate(&v.endpoints); len(errs) == 0 || errs[0].Type != v.errorType || !strings.Contains(errs[0].Detail, v.errorDetail) {
|
||||
t.Errorf("Expected error type %s with detail %q, got %v", v.errorType, v.errorDetail, errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEndpointsUpdate(t *testing.T) {
|
||||
baseEndpoints := core.Endpoints{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "mysvc", Namespace: "namespace", ResourceVersion: "1234"},
|
||||
Subsets: []core.EndpointSubset{{
|
||||
Addresses: []core.EndpointAddress{{IP: "10.1.2.3"}},
|
||||
}},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
tweakOldEndpoints func(ep *core.Endpoints)
|
||||
tweakNewEndpoints func(ep *core.Endpoints)
|
||||
appProtocolEnabled bool
|
||||
numExpectedErrors int
|
||||
}{
|
||||
"update with valid app protocol, field unset, gate not enabled": {
|
||||
tweakOldEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
|
||||
},
|
||||
tweakNewEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("https")}}
|
||||
},
|
||||
numExpectedErrors: 1,
|
||||
},
|
||||
"update with valid app protocol, field set, gate not enabled": {
|
||||
tweakOldEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("http")}}
|
||||
},
|
||||
tweakNewEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("https")}}
|
||||
},
|
||||
numExpectedErrors: 0,
|
||||
},
|
||||
"update to valid app protocol, gate enabled": {
|
||||
tweakOldEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
|
||||
},
|
||||
tweakNewEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("https")}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numExpectedErrors: 0,
|
||||
},
|
||||
"update to invalid app protocol, gate enabled": {
|
||||
tweakOldEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP"}}
|
||||
},
|
||||
tweakNewEndpoints: func(ep *core.Endpoints) {
|
||||
ep.Subsets[0].Ports = []core.EndpointPort{{Name: "a", Port: 8675, Protocol: "TCP", AppProtocol: utilpointer.StringPtr("~https")}}
|
||||
},
|
||||
appProtocolEnabled: true,
|
||||
numExpectedErrors: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAppProtocol, tc.appProtocolEnabled)()
|
||||
oldEndpoints := baseEndpoints.DeepCopy()
|
||||
tc.tweakOldEndpoints(oldEndpoints)
|
||||
newEndpoints := baseEndpoints.DeepCopy()
|
||||
tc.tweakNewEndpoints(newEndpoints)
|
||||
|
||||
errs := ValidateEndpointsUpdate(newEndpoints, oldEndpoints)
|
||||
if len(errs) != tc.numExpectedErrors {
|
||||
t.Errorf("Expected %d validation errors, got %d: %v", tc.numExpectedErrors, len(errs), errs)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -14226,7 +14425,7 @@ func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
|
||||
updatedEndpoint := newNodeNameEndpoint("kubernetes-changed-nodename")
|
||||
// Check that NodeName can be changed during update, this is to accommodate the case where nodeIP or PodCIDR is reused.
|
||||
// The same ip will now have a different nodeName.
|
||||
errList := ValidateEndpoints(updatedEndpoint)
|
||||
errList := ValidateEndpoints(updatedEndpoint, false)
|
||||
errList = append(errList, ValidateEndpointsUpdate(updatedEndpoint, oldEndpoint)...)
|
||||
if len(errList) != 0 {
|
||||
t.Error("Endpoint should allow changing of Subset.Addresses.NodeName on update")
|
||||
@ -14236,7 +14435,7 @@ func TestEndpointAddressNodeNameUpdateRestrictions(t *testing.T) {
|
||||
func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
|
||||
// Check NodeName DNS validation
|
||||
endpoint := newNodeNameEndpoint("illegal*.nodename")
|
||||
errList := ValidateEndpoints(endpoint)
|
||||
errList := ValidateEndpoints(endpoint, false)
|
||||
if len(errList) == 0 {
|
||||
t.Error("Endpoint should reject invalid NodeName")
|
||||
}
|
||||
@ -14244,7 +14443,7 @@ func TestEndpointAddressNodeNameInvalidDNSSubdomain(t *testing.T) {
|
||||
|
||||
func TestEndpointAddressNodeNameCanBeAnIPAddress(t *testing.T) {
|
||||
endpoint := newNodeNameEndpoint("10.10.1.1")
|
||||
errList := ValidateEndpoints(endpoint)
|
||||
errList := ValidateEndpoints(endpoint, false)
|
||||
if len(errList) != 0 {
|
||||
t.Error("Endpoint should accept a NodeName that is an IP address")
|
||||
}
|
||||
|
18
pkg/apis/core/zz_generated.deepcopy.go
generated
18
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -1096,6 +1096,11 @@ func (in *EndpointAddress) DeepCopy() *EndpointAddress {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EndpointPort) DeepCopyInto(out *EndpointPort) {
|
||||
*out = *in
|
||||
if in.AppProtocol != nil {
|
||||
in, out := &in.AppProtocol, &out.AppProtocol
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -1129,7 +1134,9 @@ func (in *EndpointSubset) DeepCopyInto(out *EndpointSubset) {
|
||||
if in.Ports != nil {
|
||||
in, out := &in.Ports, &out.Ports
|
||||
*out = make([]EndpointPort, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -5107,6 +5114,11 @@ func (in *ServiceList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServicePort) DeepCopyInto(out *ServicePort) {
|
||||
*out = *in
|
||||
if in.AppProtocol != nil {
|
||||
in, out := &in.AppProtocol, &out.AppProtocol
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
out.TargetPort = in.TargetPort
|
||||
return
|
||||
}
|
||||
@ -5152,7 +5164,9 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
|
||||
if in.Ports != nil {
|
||||
in, out := &in.Ports, &out.Ports
|
||||
*out = make([]ServicePort, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Selector != nil {
|
||||
in, out := &in.Selector, &out.Selector
|
||||
|
@ -139,8 +139,8 @@ type EndpointPort struct {
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names.
|
||||
// Default is empty string.
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// +optional
|
||||
AppProtocol *string
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ go_test(
|
||||
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
|
||||
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
|
||||
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -444,17 +444,14 @@ func (e *EndpointController) syncService(key string) error {
|
||||
} else {
|
||||
for i := range service.Spec.Ports {
|
||||
servicePort := &service.Spec.Ports[i]
|
||||
|
||||
portName := servicePort.Name
|
||||
portProto := servicePort.Protocol
|
||||
portNum, err := podutil.FindPort(pod, servicePort)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("Failed to find port for service %s/%s: %v", service.Namespace, service.Name, err)
|
||||
continue
|
||||
}
|
||||
epp := endpointPortFromServicePort(servicePort, portNum)
|
||||
|
||||
var readyEps, notReadyEps int
|
||||
epp := &v1.EndpointPort{Name: portName, Port: int32(portNum), Protocol: portProto}
|
||||
subsets, readyEps, notReadyEps = addEndpointSubset(subsets, pod, epa, epp, tolerateUnreadyEndpoints)
|
||||
totalReadyEps = totalReadyEps + readyEps
|
||||
totalNotReadyEps = totalNotReadyEps + notReadyEps
|
||||
@ -608,3 +605,15 @@ func shouldPodBeInEndpoints(pod *v1.Pod) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func endpointPortFromServicePort(servicePort *v1.ServicePort, portNum int) *v1.EndpointPort {
|
||||
epp := &v1.EndpointPort{
|
||||
Name: servicePort.Name,
|
||||
Port: int32(portNum),
|
||||
Protocol: servicePort.Protocol,
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ServiceAppProtocol) {
|
||||
epp.AppProtocol = servicePort.AppProtocol
|
||||
}
|
||||
return epp
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
endpointutil "k8s.io/kubernetes/pkg/controller/util/endpoint"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
var alwaysReady = func() bool { return true }
|
||||
@ -1947,6 +1948,55 @@ func TestSyncEndpointsServiceNotFound(t *testing.T) {
|
||||
endpointsHandler.ValidateRequest(t, "/api/v1/namespaces/"+ns+"/endpoints/foo", "DELETE", nil)
|
||||
}
|
||||
|
||||
func TestEndpointPortFromServicePort(t *testing.T) {
|
||||
http := utilpointer.StringPtr("http")
|
||||
testCases := map[string]struct {
|
||||
featureGateEnabled bool
|
||||
serviceAppProtocol *string
|
||||
expectedEndpointsAppProtocol *string
|
||||
}{
|
||||
"feature gate disabled, empty app protocol": {
|
||||
featureGateEnabled: false,
|
||||
serviceAppProtocol: nil,
|
||||
expectedEndpointsAppProtocol: nil,
|
||||
},
|
||||
"feature gate disabled, http app protocol": {
|
||||
featureGateEnabled: false,
|
||||
serviceAppProtocol: http,
|
||||
expectedEndpointsAppProtocol: nil,
|
||||
},
|
||||
"feature gate enabled, empty app protocol": {
|
||||
featureGateEnabled: true,
|
||||
serviceAppProtocol: nil,
|
||||
expectedEndpointsAppProtocol: nil,
|
||||
},
|
||||
"feature gate enabled, http app protocol": {
|
||||
featureGateEnabled: true,
|
||||
serviceAppProtocol: http,
|
||||
expectedEndpointsAppProtocol: http,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ServiceAppProtocol, tc.featureGateEnabled)()
|
||||
|
||||
epp := endpointPortFromServicePort(&v1.ServicePort{Name: "test", AppProtocol: tc.serviceAppProtocol}, 80)
|
||||
|
||||
if epp.AppProtocol != tc.expectedEndpointsAppProtocol {
|
||||
t.Errorf("Expected Endpoints AppProtocol to be %s, got %s", stringVal(tc.expectedEndpointsAppProtocol), stringVal(epp.AppProtocol))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringVal(str *string) string {
|
||||
if str == nil {
|
||||
return "nil"
|
||||
}
|
||||
return *str
|
||||
}
|
||||
|
||||
func podChangedHelper(oldPod, newPod *v1.Pod, endpointChanged endpointutil.EndpointsMatch) bool {
|
||||
podChanged, _ := endpointutil.PodChanged(oldPod, newPod, endpointChanged)
|
||||
return podChanged
|
||||
|
@ -117,9 +117,10 @@ func getEndpointPorts(service *corev1.Service, pod *corev1.Pod) []discovery.Endp
|
||||
|
||||
i32PortNum := int32(portNum)
|
||||
endpointPorts = append(endpointPorts, discovery.EndpointPort{
|
||||
Name: &portName,
|
||||
Port: &i32PortNum,
|
||||
Protocol: &portProto,
|
||||
Name: &portName,
|
||||
Port: &i32PortNum,
|
||||
Protocol: &portProto,
|
||||
AppProtocol: servicePort.AppProtocol,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -353,6 +353,97 @@ func TestServiceControllerKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEndpointPorts(t *testing.T) {
|
||||
protoTCP := v1.ProtocolTCP
|
||||
|
||||
testCases := map[string]struct {
|
||||
service *v1.Service
|
||||
pod *v1.Pod
|
||||
expectedPorts []*discovery.EndpointPort
|
||||
}{
|
||||
"service with AppProtocol on one port": {
|
||||
service: &v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
Ports: []v1.ServicePort{{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
TargetPort: intstr.FromInt(80),
|
||||
Protocol: protoTCP,
|
||||
AppProtocol: utilpointer.StringPtr("example.com/custom-protocol"),
|
||||
}},
|
||||
},
|
||||
},
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Ports: []v1.ContainerPort{},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedPorts: []*discovery.EndpointPort{{
|
||||
Name: utilpointer.StringPtr("http"),
|
||||
Port: utilpointer.Int32Ptr(80),
|
||||
Protocol: &protoTCP,
|
||||
AppProtocol: utilpointer.StringPtr("example.com/custom-protocol"),
|
||||
}},
|
||||
},
|
||||
"service with named port and AppProtocol on one port": {
|
||||
service: &v1.Service{
|
||||
Spec: v1.ServiceSpec{
|
||||
Ports: []v1.ServicePort{{
|
||||
Name: "http",
|
||||
Port: 80,
|
||||
TargetPort: intstr.FromInt(80),
|
||||
Protocol: protoTCP,
|
||||
}, {
|
||||
Name: "https",
|
||||
Protocol: protoTCP,
|
||||
TargetPort: intstr.FromString("https"),
|
||||
AppProtocol: utilpointer.StringPtr("https"),
|
||||
}},
|
||||
},
|
||||
},
|
||||
pod: &v1.Pod{
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{
|
||||
Ports: []v1.ContainerPort{{
|
||||
Name: "https",
|
||||
ContainerPort: int32(443),
|
||||
Protocol: protoTCP,
|
||||
}},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedPorts: []*discovery.EndpointPort{{
|
||||
Name: utilpointer.StringPtr("http"),
|
||||
Port: utilpointer.Int32Ptr(80),
|
||||
Protocol: &protoTCP,
|
||||
}, {
|
||||
Name: utilpointer.StringPtr("https"),
|
||||
Port: utilpointer.Int32Ptr(443),
|
||||
Protocol: &protoTCP,
|
||||
AppProtocol: utilpointer.StringPtr("https"),
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actualPorts := getEndpointPorts(tc.service, tc.pod)
|
||||
|
||||
if len(actualPorts) != len(tc.expectedPorts) {
|
||||
t.Fatalf("Expected %d ports, got %d", len(tc.expectedPorts), len(actualPorts))
|
||||
}
|
||||
|
||||
for i, actualPort := range actualPorts {
|
||||
if !reflect.DeepEqual(&actualPort, tc.expectedPorts[i]) {
|
||||
t.Errorf("Expected port: %+v, got %+v", tc.expectedPorts[i], &actualPort)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test helpers
|
||||
|
||||
func newPod(n int, namespace string, ready bool, nPorts int) *v1.Pod {
|
||||
|
@ -534,6 +534,12 @@ const (
|
||||
// Enables topology aware service routing
|
||||
ServiceTopology featuregate.Feature = "ServiceTopology"
|
||||
|
||||
// owner: @robscott
|
||||
// alpha: v1.18
|
||||
//
|
||||
// Enables AppProtocol field for Services and Endpoints.
|
||||
ServiceAppProtocol featuregate.Feature = "ServiceAppProtocol"
|
||||
|
||||
// owner: @wojtek-t
|
||||
// alpha: v1.18
|
||||
//
|
||||
@ -623,6 +629,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
|
||||
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
||||
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
||||
ServiceAppProtocol: {Default: false, PreRelease: featuregate.Alpha},
|
||||
ImmutableEphemeralVolumes: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
|
@ -53,7 +53,7 @@ func (endpointsStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.
|
||||
|
||||
// Validate validates a new endpoints.
|
||||
func (endpointsStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
allErrs := validation.ValidateEndpoints(obj.(*api.Endpoints))
|
||||
allErrs := validation.ValidateEndpointsCreate(obj.(*api.Endpoints))
|
||||
allErrs = append(allErrs, validation.ValidateConditionalEndpoints(obj.(*api.Endpoints), nil)...)
|
||||
return allErrs
|
||||
}
|
||||
@ -71,8 +71,7 @@ func (endpointsStrategy) AllowCreateOnUpdate() bool {
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user.
|
||||
func (endpointsStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||
errorList := validation.ValidateEndpoints(obj.(*api.Endpoints))
|
||||
errorList = append(errorList, validation.ValidateEndpointsUpdate(obj.(*api.Endpoints), old.(*api.Endpoints))...)
|
||||
errorList := validation.ValidateEndpointsUpdate(obj.(*api.Endpoints), old.(*api.Endpoints))
|
||||
errorList = append(errorList, validation.ValidateConditionalEndpoints(obj.(*api.Endpoints), old.(*api.Endpoints))...)
|
||||
return errorList
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ func (svcStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
|
||||
// Validate validates a new service.
|
||||
func (svcStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
service := obj.(*api.Service)
|
||||
allErrs := validation.ValidateService(service)
|
||||
allErrs := validation.ValidateServiceCreate(service)
|
||||
allErrs = append(allErrs, validation.ValidateConditionalService(service, nil)...)
|
||||
return allErrs
|
||||
}
|
||||
|
1802
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
1802
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -1042,6 +1042,16 @@ message EndpointPort {
|
||||
// Default is TCP.
|
||||
// +optional
|
||||
optional string protocol = 3;
|
||||
|
||||
// The application protocol for this port.
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// Field can be enabled with ServiceAppProtocol feature gate.
|
||||
// +optional
|
||||
optional string appProtocol = 4;
|
||||
}
|
||||
|
||||
// EndpointSubset is a group of addresses with a common set of ports. The
|
||||
@ -4609,6 +4619,16 @@ message ServicePort {
|
||||
// +optional
|
||||
optional string protocol = 2;
|
||||
|
||||
// The application protocol for this port.
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// Field can be enabled with ServiceAppProtocol feature gate.
|
||||
// +optional
|
||||
optional string appProtocol = 6;
|
||||
|
||||
// The port that will be exposed by this service.
|
||||
optional int32 port = 3;
|
||||
|
||||
|
@ -3989,6 +3989,16 @@ type ServicePort struct {
|
||||
// +optional
|
||||
Protocol Protocol `json:"protocol,omitempty" protobuf:"bytes,2,opt,name=protocol,casttype=Protocol"`
|
||||
|
||||
// The application protocol for this port.
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// Field can be enabled with ServiceAppProtocol feature gate.
|
||||
// +optional
|
||||
AppProtocol *string `json:"appProtocol,omitempty" protobuf:"bytes,6,opt,name=appProtocol"`
|
||||
|
||||
// The port that will be exposed by this service.
|
||||
Port int32 `json:"port" protobuf:"varint,3,opt,name=port"`
|
||||
|
||||
@ -4204,6 +4214,16 @@ type EndpointPort struct {
|
||||
// Default is TCP.
|
||||
// +optional
|
||||
Protocol Protocol `json:"protocol,omitempty" protobuf:"bytes,3,opt,name=protocol,casttype=Protocol"`
|
||||
|
||||
// The application protocol for this port.
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// Field can be enabled with ServiceAppProtocol feature gate.
|
||||
// +optional
|
||||
AppProtocol *string `json:"appProtocol,omitempty" protobuf:"bytes,4,opt,name=appProtocol"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
@ -502,10 +502,11 @@ func (EndpointAddress) SwaggerDoc() map[string]string {
|
||||
}
|
||||
|
||||
var map_EndpointPort = map[string]string{
|
||||
"": "EndpointPort is a tuple that describes a single port.",
|
||||
"name": "The name of this port. This must match the 'name' field in the corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port is defined.",
|
||||
"port": "The port number of the endpoint.",
|
||||
"protocol": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.",
|
||||
"": "EndpointPort is a tuple that describes a single port.",
|
||||
"name": "The name of this port. This must match the 'name' field in the corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port is defined.",
|
||||
"port": "The port number of the endpoint.",
|
||||
"protocol": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.",
|
||||
"appProtocol": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol. Field can be enabled with ServiceAppProtocol feature gate.",
|
||||
}
|
||||
|
||||
func (EndpointPort) SwaggerDoc() map[string]string {
|
||||
@ -2169,12 +2170,13 @@ func (ServiceList) SwaggerDoc() map[string]string {
|
||||
}
|
||||
|
||||
var map_ServicePort = map[string]string{
|
||||
"": "ServicePort contains information on service's port.",
|
||||
"name": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. When considering the endpoints for a Service, this must match the 'name' field in the EndpointPort. Optional if only one ServicePort is defined on this service.",
|
||||
"protocol": "The IP protocol for this port. Supports \"TCP\", \"UDP\", and \"SCTP\". Default is TCP.",
|
||||
"port": "The port that will be exposed by this service.",
|
||||
"targetPort": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service",
|
||||
"nodePort": "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport",
|
||||
"": "ServicePort contains information on service's port.",
|
||||
"name": "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. When considering the endpoints for a Service, this must match the 'name' field in the EndpointPort. Optional if only one ServicePort is defined on this service.",
|
||||
"protocol": "The IP protocol for this port. Supports \"TCP\", \"UDP\", and \"SCTP\". Default is TCP.",
|
||||
"appProtocol": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol. Field can be enabled with ServiceAppProtocol feature gate.",
|
||||
"port": "The port that will be exposed by this service.",
|
||||
"targetPort": "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service",
|
||||
"nodePort": "The port on each node on which this service is exposed when type=NodePort or LoadBalancer. Usually assigned by the system. If specified, it will be allocated to the service if unused or else creation of the service will fail. Default is to auto-allocate a port if the ServiceType of this Service requires one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport",
|
||||
}
|
||||
|
||||
func (ServicePort) SwaggerDoc() map[string]string {
|
||||
|
@ -1096,6 +1096,11 @@ func (in *EndpointAddress) DeepCopy() *EndpointAddress {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *EndpointPort) DeepCopyInto(out *EndpointPort) {
|
||||
*out = *in
|
||||
if in.AppProtocol != nil {
|
||||
in, out := &in.AppProtocol, &out.AppProtocol
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -1129,7 +1134,9 @@ func (in *EndpointSubset) DeepCopyInto(out *EndpointSubset) {
|
||||
if in.Ports != nil {
|
||||
in, out := &in.Ports, &out.Ports
|
||||
*out = make([]EndpointPort, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -5122,6 +5129,11 @@ func (in *ServiceList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServicePort) DeepCopyInto(out *ServicePort) {
|
||||
*out = *in
|
||||
if in.AppProtocol != nil {
|
||||
in, out := &in.AppProtocol, &out.AppProtocol
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
out.TargetPort = in.TargetPort
|
||||
return
|
||||
}
|
||||
@ -5167,7 +5179,9 @@ func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) {
|
||||
if in.Ports != nil {
|
||||
in, out := &in.Ports, &out.Ports
|
||||
*out = make([]ServicePort, len(*in))
|
||||
copy(*out, *in)
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Selector != nil {
|
||||
in, out := &in.Selector, &out.Selector
|
||||
|
@ -107,8 +107,9 @@ message EndpointPort {
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names.
|
||||
// Default is empty string.
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// +optional
|
||||
optional string appProtocol = 4;
|
||||
}
|
||||
|
||||
|
@ -143,8 +143,9 @@ type EndpointPort struct {
|
||||
// This field follows standard Kubernetes label syntax.
|
||||
// Un-prefixed names are reserved for IANA standard service names (as per
|
||||
// RFC-6335 and http://www.iana.org/assignments/service-names).
|
||||
// Non-standard protocols should use prefixed names.
|
||||
// Default is empty string.
|
||||
// Non-standard protocols should use prefixed names such as
|
||||
// mycompany.com/my-custom-protocol.
|
||||
// +optional
|
||||
AppProtocol *string `json:"appProtocol,omitempty" protobuf:"bytes,4,name=appProtocol"`
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ var map_EndpointPort = map[string]string{
|
||||
"name": "The name of this port. All ports in an EndpointSlice must have a unique name. If the EndpointSlice is dervied from a Kubernetes service, this corresponds to the Service.ports[].name. Name must either be an empty string or pass DNS_LABEL validation: * must be no more than 63 characters long. * must consist of lower case alphanumeric characters or '-'. * must start and end with an alphanumeric character. Default is empty string.",
|
||||
"protocol": "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.",
|
||||
"port": "The port number of the endpoint. If this is not specified, ports are not restricted and must be interpreted in the context of the specific consumer.",
|
||||
"appProtocol": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names. Default is empty string.",
|
||||
"appProtocol": "The application protocol for this port. This field follows standard Kubernetes label syntax. Un-prefixed names are reserved for IANA standard service names (as per RFC-6335 and http://www.iana.org/assignments/service-names). Non-standard protocols should use prefixed names such as mycompany.com/my-custom-protocol.",
|
||||
}
|
||||
|
||||
func (EndpointPort) SwaggerDoc() map[string]string {
|
||||
|
@ -78,7 +78,8 @@
|
||||
{
|
||||
"name": "37",
|
||||
"port": 1546792211,
|
||||
"protocol": "\u003eŽ燹憍峕?狱³-Ǐ忄*"
|
||||
"protocol": "\u003eŽ燹憍峕?狱³-Ǐ忄*",
|
||||
"appProtocol": "38"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Binary file not shown.
@ -55,6 +55,7 @@ subsets:
|
||||
resourceVersion: "35"
|
||||
uid: Ă凗蓏Ŋ蛊ĉy
|
||||
ports:
|
||||
- name: "37"
|
||||
- appProtocol: "38"
|
||||
name: "37"
|
||||
port: 1546792211
|
||||
protocol: '>Ž燹憍峕?狱³-Ǐ忄*'
|
||||
|
@ -45,25 +45,26 @@
|
||||
{
|
||||
"name": "19",
|
||||
"protocol": "@Hr鯹)晿",
|
||||
"appProtocol": "20",
|
||||
"port": 202283346,
|
||||
"targetPort": "20",
|
||||
"targetPort": "21",
|
||||
"nodePort": -474380055
|
||||
}
|
||||
],
|
||||
"selector": {
|
||||
"21": "22"
|
||||
"22": "23"
|
||||
},
|
||||
"clusterIP": "23",
|
||||
"clusterIP": "24",
|
||||
"type": ".蘯6ċV夸",
|
||||
"externalIPs": [
|
||||
"24"
|
||||
"25"
|
||||
],
|
||||
"sessionAffinity": "ɑ",
|
||||
"loadBalancerIP": "25",
|
||||
"loadBalancerIP": "26",
|
||||
"loadBalancerSourceRanges": [
|
||||
"26"
|
||||
"27"
|
||||
],
|
||||
"externalName": "27",
|
||||
"externalName": "28",
|
||||
"externalTrafficPolicy": "ʤ脽ěĂ凗蓏Ŋ蛊ĉy緅縕\u003eŽ",
|
||||
"healthCheckNodePort": -1095807277,
|
||||
"publishNotReadyAddresses": true,
|
||||
@ -74,15 +75,15 @@
|
||||
},
|
||||
"ipFamily": "³-Ǐ忄*齧獚敆ȎțêɘIJ斬",
|
||||
"topologyKeys": [
|
||||
"28"
|
||||
"29"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"loadBalancer": {
|
||||
"ingress": [
|
||||
{
|
||||
"ip": "29",
|
||||
"hostname": "30"
|
||||
"ip": "30",
|
||||
"hostname": "31"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Binary file not shown.
@ -30,34 +30,35 @@ metadata:
|
||||
selfLink: "5"
|
||||
uid: "7"
|
||||
spec:
|
||||
clusterIP: "23"
|
||||
clusterIP: "24"
|
||||
externalIPs:
|
||||
- "24"
|
||||
externalName: "27"
|
||||
- "25"
|
||||
externalName: "28"
|
||||
externalTrafficPolicy: ʤ脽ěĂ凗蓏Ŋ蛊ĉy緅縕>Ž
|
||||
healthCheckNodePort: -1095807277
|
||||
ipFamily: ³-Ǐ忄*齧獚敆ȎțêɘIJ斬
|
||||
loadBalancerIP: "25"
|
||||
loadBalancerIP: "26"
|
||||
loadBalancerSourceRanges:
|
||||
- "26"
|
||||
- "27"
|
||||
ports:
|
||||
- name: "19"
|
||||
- appProtocol: "20"
|
||||
name: "19"
|
||||
nodePort: -474380055
|
||||
port: 202283346
|
||||
protocol: '@Hr鯹)晿'
|
||||
targetPort: "20"
|
||||
targetPort: "21"
|
||||
publishNotReadyAddresses: true
|
||||
selector:
|
||||
"21": "22"
|
||||
"22": "23"
|
||||
sessionAffinity: ɑ
|
||||
sessionAffinityConfig:
|
||||
clientIP:
|
||||
timeoutSeconds: -1973740160
|
||||
topologyKeys:
|
||||
- "28"
|
||||
- "29"
|
||||
type: .蘯6ċV夸
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- hostname: "30"
|
||||
ip: "29"
|
||||
- hostname: "31"
|
||||
ip: "30"
|
||||
|
Loading…
Reference in New Issue
Block a user