Adding AppProtocol to Service and Endpoints Ports

This commit is contained in:
Rob Scott 2020-02-18 17:30:57 -08:00
parent 4e79344501
commit 6a33727632
No known key found for this signature in database
GPG Key ID: 05B37CFC2CDE8B85
29 changed files with 1598 additions and 986 deletions

View File

@ -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": {

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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")
}

View File

@ -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

View File

@ -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
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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

View File

@ -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,
})
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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
}

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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;
}

View File

@ -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"`
}

View File

@ -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 {

View File

@ -78,7 +78,8 @@
{
"name": "37",
"port": 1546792211,
"protocol": "\u003eŽ燹憍峕?狱³-Ǐ忄*"
"protocol": "\u003eŽ燹憍峕?狱³-Ǐ忄*",
"appProtocol": "38"
}
]
}

View File

@ -55,6 +55,7 @@ subsets:
resourceVersion: "35"
uid: Ă凗蓏Ŋ蛊ĉy
ports:
- name: "37"
- appProtocol: "38"
name: "37"
port: 1546792211
protocol: '>Ž燹憍峕?狱³-Ǐ忄*'

View File

@ -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"
}
]
}

View File

@ -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"