Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

support http2 protocol (#1523) #2306

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions pkg/apis/internal.acorn.io/v1/appspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ func (in Build) BaseBuild() Build {
type Protocol string

var (
ProtocolTCP = Protocol("tcp")
ProtocolUDP = Protocol("udp")
ProtocolHTTP = Protocol("http")
ProtocolTCP = Protocol("tcp")
ProtocolUDP = Protocol("udp")
ProtocolHTTP = Protocol("http")
ProtocolHTTP2 = Protocol("http2")
)

type PublishProtocol string
Expand All @@ -74,6 +75,7 @@ var (
PublishProtocolUDP = PublishProtocol("udp")
PublishProtocolHTTP = PublishProtocol("http")
PublishProtocolHTTPS = PublishProtocol("https")
PublishProtocolHTTP2 = PublishProtocol("http2")
)

type PortBindings []PortBinding
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/internal.acorn.io/v1/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func validProto(p string) (Protocol, bool) {
case ProtocolUDP:
fallthrough
case ProtocolHTTP:
fallthrough
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: all of these cases can be put on the same line:

switch ret {
    case ProtocolTCP, ProtocolUDP, ProtocolHTTP, ProtocolHTTP2, "":
        return ret, true
}

case ProtocolHTTP2:
return ret, true
case "":
return ret, true
Expand Down
6 changes: 3 additions & 3 deletions pkg/appdefinition/app.acorn
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,15 @@

PortSingle: (int > 0 && int < 65536) || PortRegexp
Port: (int > 0 && int < 65536) || PortRegexp || PortSpec
PortRegexp: string =~ `^([a-z][-a-z0-9.]+:)?([0-9]+:)?([a-z][-a-z0-9]+:)?([0-9]+)(/(tcp|udp|http))?$`
PortRegexp: string =~ `^([a-z][-a-z0-9.]+:)?([0-9]+:)?([a-z][-a-z0-9]+:)?([0-9]+)(/(tcp|udp|http|http2))?$`

PortSpec: {
publish?: bool
dev?: bool
hostname?: string
port?: int
targetPort?: int
protocol?: enum("tcp", "udp", "http")
protocol?: enum("tcp", "udp", "http", "http2")
}

Metrics: {
Expand Down Expand Up @@ -385,7 +385,7 @@
hostname?: string
targetPort?: int
targetServiceName: DNSName
protocol?: enum("tcp", "udp", "http")
protocol?: enum("tcp", "udp", "http", "http2")
} || string || int

Router: {
Expand Down
66 changes: 51 additions & 15 deletions pkg/controller/service/service.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package service

import (
"fmt"

"github.com/acorn-io/baaah/pkg/router"
v1 "github.com/acorn-io/runtime/pkg/apis/internal.acorn.io/v1"
"github.com/acorn-io/runtime/pkg/ports"
"github.com/acorn-io/runtime/pkg/publish"
"github.com/acorn-io/runtime/pkg/services"
)
Expand All @@ -13,25 +16,58 @@ func RenderServices(req router.Request, resp router.Response) error {
// reset, should get repopulated
svcInstance.Status.Endpoints = nil

objs, _, err := services.ToK8sService(req, svcInstance)
if err != nil {
return err
}
resp.Objects(objs...)
svcInstance.Status.HasService = len(objs) > 0
var svcList []*v1.ServiceInstance
http2Ports := ports.ByProtocol(svcInstance.Spec.Ports, true, v1.ProtocolHTTP2)
httpPorts := ports.ByProtocol(svcInstance.Spec.Ports, true, v1.ProtocolHTTP)
// If we have both types of ports we need to create a separate service with the annotation
if len(http2Ports) > 0 && len(httpPorts) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll put some explanation on this, since we paired up to work on this yesterday and I still feel like this is a good and simple solution that can be a little confusing.

The background is that we have to put a traefik annotation on the K8s Service for http2 support (h2c). With that service we'd break the traffic for "normal" http traefik to other ports listed in that service though.
E.g. if someone uses http2 for the app server, but metrics are served via http.
That means, if we have both http/http2 mixed in a single ServiceInstance, we have to split it up to generate two K8s services.

However, all the code further down the stream is specific to a single service and even re-uses the ServiceInstance name in a lot of places.
So we need a "virtual" ServiceInstance with a different name, holding only the http2 ports to be able to leave lots of code untouched.

The DeepCopies are necessary to leave the original serviceInstance object untouched during that process and we only have to populated actively modified status fields on it.

svcCopy := svcInstance.DeepCopy()
svcCopy.Spec.Ports = ports.ByProtocol(svcInstance.Spec.Ports, false, v1.ProtocolHTTP2) // filter out http2 ports
svcList = append(svcList, svcCopy)

objs, err = publish.ServiceLoadBalancer(req, svcInstance)
if err != nil {
return err
svcCopyHttp2 := svcInstance.DeepCopy()
svcCopyHttp2.Spec.Ports = http2Ports
svcCopyHttp2.Name = fmt.Sprintf("%s-%s", svcInstance.Name, v1.ProtocolHTTP2)
if svcCopyHttp2.Spec.Annotations == nil {
svcCopyHttp2.Spec.Annotations = map[string]string{}
}
svcCopyHttp2.Spec.Annotations["traefik.ingress.kubernetes.io/service.serversscheme"] = "h2c"
svcList = append(svcList, svcCopyHttp2)
} else if len(http2Ports) > 0 {
svcCopyHttp2 := svcInstance.DeepCopy()
if svcCopyHttp2.Spec.Annotations == nil {
svcCopyHttp2.Spec.Annotations = map[string]string{}
}
svcCopyHttp2.Spec.Annotations["traefik.ingress.kubernetes.io/service.serversscheme"] = "h2c"
svcList = append(svcList, svcCopyHttp2)
} else {
svcList = []*v1.ServiceInstance{svcInstance}
}
resp.Objects(objs...)
svcInstance.Status.HasService = svcInstance.Status.HasService || len(objs) > 0

objs, err = publish.Ingress(req, svcInstance)
if err != nil {
return err
for _, svc := range svcList {
objs, _, err := services.ToK8sService(req, svc)
if err != nil {
return err
}
resp.Objects(objs...)
svcInstance.Status.HasService = svcInstance.Status.HasService || len(objs) > 0

objs, err = publish.ServiceLoadBalancer(req, svc)
if err != nil {
return err
}
resp.Objects(objs...)
svcInstance.Status.HasService = svcInstance.Status.HasService || len(objs) > 0

objs, err = publish.Ingress(req, svc)
if err != nil {
return err
}
resp.Objects(objs...)

// Copy all modifications made by the above publish.Ingress call
svcInstance.Status.Endpoints = append(svcInstance.Status.Endpoints, svc.Status.Endpoints...)
}
resp.Objects(objs...)

resp.Objects(svcInstance)
return nil
Expand Down
8 changes: 6 additions & 2 deletions pkg/ports/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,18 @@ func IsLinked(app *v1.AppInstance, name string) bool {
return false
}

func ByProtocol(ports []v1.PortDef, protocols ...v1.Protocol) (result []v1.PortDef) {
func ByProtocol(ports []v1.PortDef, include bool, protocols ...v1.Protocol) (result []v1.PortDef) {
for _, port := range ports {
matched := false
for _, proto := range protocols {
if port.Complete().Protocol == proto {
result = append(result, port)
matched = true
break
}
}
if (matched && include) || (!matched && !include) {
result = append(result, port)
}
}
return result
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/publish/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func Ingress(req router.Request, svc *v1.ServiceInstance) (result []kclient.Obje
return nil, err
}

bindings := ports.ApplyBindings(svc.Spec.PublishMode, svc.Spec.Publish, ports.ByProtocol(svc.Spec.Ports, v1.ProtocolHTTP))
bindings := ports.ApplyBindings(svc.Spec.PublishMode, svc.Spec.Publish, ports.ByProtocol(svc.Spec.Ports, true, v1.ProtocolHTTP, v1.ProtocolHTTP2))

if len(bindings) == 0 {
return nil, nil
Expand Down
2 changes: 1 addition & 1 deletion pkg/publish/servicelb.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func ServiceLoadBalancer(req router.Request, svc *v1.ServiceInstance) (result []
}

bindings := ports.ApplyBindings(svc.Spec.PublishMode, svc.Spec.Publish,
ports.ByProtocol(svc.Spec.Ports, v1.ProtocolTCP, v1.ProtocolUDP))
ports.ByProtocol(svc.Spec.Ports, true, v1.ProtocolTCP, v1.ProtocolUDP))

if len(bindings) == 0 {
return nil, nil
Expand Down