Skip to content
This repository has been archived by the owner on Jan 27, 2021. It is now read-only.

Commit

Permalink
WIP: https & http/2 support (both h2c and full h2) (#27)
Browse files Browse the repository at this point in the history
* change metrics we collect-- no changes to how they're used... yet

* update zeroscaler algorithm for scale-to-zero decision

* copy httputil.ReverseProxy code from Go 1.12

* add packages for implementing advanced proxies

* corresponding vendor updates

* make metrics-collecting proxy sidecar use dyanmic l4/l7 proxy

* make activator use dyanmic l4/l7 proxy

* upload coverage reports

* update example to use http/1.x, http/2, https, and grpc
  • Loading branch information
krancour authored May 21, 2019
1 parent 6b69328 commit 4f8deee
Show file tree
Hide file tree
Showing 57 changed files with 10,244 additions and 313 deletions.
3 changes: 3 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
- run:
name: Run Unit Tests
command: make test-unit
- run:
name: Upload Coverage Report
command: bash <(curl -s https://codecov.io/bash)
build:
<<: *base-docker-job
steps:
Expand Down
2 changes: 2 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
run:
concurrency: 1
deadline: 2m
skip-files:
- pkg/net/http/httputil/reverseproxy*

linters:
disable-all: true
Expand Down
35 changes: 34 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions cmd/activator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ func runActivator(ctx context.Context) {
glog.Fatalf("Error building kubernetes clientset: %s", err)
}

activator, err := deployments.NewActivator(client)
if err != nil {
glog.Fatalf("Error retrieving activator configuration: %s", err)
glog.Fatalf("Error initializing activator: %s", err)
}

// Run the activator
deployments.NewActivator(client).Run(ctx)
activator.Run(ctx)
}
57 changes: 44 additions & 13 deletions example/hello-osiris.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,31 @@ metadata:
spec:
type: LoadBalancer
ports:
- name: hello
port: 80
targetPort: 4000
- name: goodbye
port: 5000
targetPort: 5000
- name: http
port: 8080
targetPort: 8080
- name: h2c
port: 8081
targetPort: 8081
- name: grpc
port: 8082
targetPort: 8082
- name: https
port: 4430
targetPort: 4430
selector:
app: hello-osiris
---
apiVersion: v1
kind: Secret
metadata:
name: hello-osiris-cert
labels:
app: hello-osiris
data:
server.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQyRENDQXNBQ0NRQy9HeC92dExTTlZEQU5CZ2txaGtpRzl3MEJBUXNGQURDQnJURUxNQWtHQTFVRUJoTUMKVlZNeEV6QVJCZ05WQkFnTUNsZGhjMmhwYm1kMGIyNHhFREFPQmdOVkJBY01CMUpsWkcxdmJtUXhFakFRQmdOVgpCQW9NQ1VSbGFYTWdUR0ZpY3pFVU1CSUdBMVVFQ3d3TFJXNW5hVzVsWlhKcGJtY3hJVEFmQmdOVkJBTU1HR2hsCmJHeHZMVzl6YVhKcGN5NWtaV2x6YkdGaWN5NXBiekVxTUNnR0NTcUdTSWIzRFFFSkFSWWJhMlZ1ZEM1eVlXNWoKYjNWeWRFQnRhV055YjNOdlpuUXVZMjl0TUI0WERURTVNREl5TXpBd01UazBNVm9YRFRJd01ESXlNekF3TVRrMApNVm93Z2EweEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBYWVhOb2FXNW5kRzl1TVJBd0RnWURWUVFICkRBZFNaV1J0YjI1a01SSXdFQVlEVlFRS0RBbEVaV2x6SUV4aFluTXhGREFTQmdOVkJBc01DMFZ1WjJsdVpXVnkKYVc1bk1TRXdId1lEVlFRRERCaG9aV3hzYnkxdmMybHlhWE11WkdWcGMyeGhZbk11YVc4eEtqQW9CZ2txaGtpRwo5dzBCQ1FFV0cydGxiblF1Y21GdVkyOTFjblJBYldsamNtOXpiMlowTG1OdmJUQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFMZEJZTFlwT2NaSUdEWWs2Qzl5T1hreE8wcUZQbVkzNGVWanBrc0oKZjZqaUVEUDFWZlBoWXV6TnRwMUY0ZWhlR1h3WEU0cmt4ZjQ5bExtcmU4L3g4NUh6RHNKK1NNdnlaZ09XZjZMcgpoNE53aVBKcmNjcDhGTVlXMmtJenJiVWZFS0wxYUZ1VCtkRXc3NkgxRlhPUmsvS0Y0V3JMYXhkRlBPbDhLMWVPCm1NazFtSkU3NTNYZzVYd2FVVUVHZ2tGbUZkZHJhQ2N3Y1U0QmtnbXRObTdFTExJQ2Nnb3MzNHVmR21ndmN2ZkwKSWhuenZxNmxCNDM4a0hyaG16OG11WFVwYjhQa1k2NmtxRGpxTk53YlBsLzJrMjYvVTg1RUVlazI0YnowMzlBZApsUnpKUTFndUhacmxKOTBQckl0aFJzM3NNZmdzWGtIaGZDR0J5dlVXYWZubUZPY0NBd0VBQVRBTkJna3Foa2lHCjl3MEJBUXNGQUFPQ0FRRUFzckltVURVK0E2YWNHVGloK3N5c2lpQWVWYWtsL0FNcytvWXJIa1NPK2NrOXVNcFUKMUw3dUtrNDZBSGZ0dkplbXJqaFBObHJpSmZOSEh5bEZ0YlpkTjRqM2RmL3p1L1ExbVRMa2dLUXJWZkl6ZFF2eApVUlEyOXB2ZWFBdFJyL0x6VXZINllWRE5lTk9wWXk3ZEJJT1ZqcGpjZFJ5amRHZE1xejBLbEhvUGlnbEdiUEFWCldzdXBmbWI3Nzd1Q3ZtUGRHc1lwb2wvTE9jOU44ZUE0VUdPNk9sWmtWU0NGOTJjSk9oaCtyd1c0cktTOXZFTTcKRzJmaXZVVDZJWCtFamdGQzB0ZytLMkNSSjRoTElnNTFSc0lmdEllNk01MFBpOCsvc3d6andYV2ZuQmpkUWkyegpudC9XYmR3THA2Q2pMR01UdVNrZmVGam00Z2QzM2cyMmRSY09RZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
server.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdDBGZ3RpazV4a2dZTmlUb0wzSTVlVEU3U29VK1pqZmg1V09tU3dsL3FPSVFNL1ZWCjgrRmk3TTIyblVYaDZGNFpmQmNUaXVURi9qMlV1YXQ3ei9IemtmTU93bjVJeS9KbUE1Wi9vdXVIZzNDSThtdHgKeW53VXhoYmFRak90dFI4UW92Vm9XNVA1MFREdm9mVVZjNUdUOG9YaGFzdHJGMFU4Nlh3clY0Nll5VFdZa1R2bgpkZURsZkJwUlFRYUNRV1lWMTJ0b0p6QnhUZ0dTQ2EwMmJzUXNzZ0p5Q2l6Zmk1OGFhQzl5OThzaUdmTytycVVICmpmeVFldUdiUHlhNWRTbHZ3K1JqcnFTb09PbzAzQnMrWC9hVGJyOVR6a1FSNlRiaHZQVGYwQjJWSE1sRFdDNGQKbXVVbjNRK3NpMkZHemV3eCtDeGVRZUY4SVlISzlSWnArZVlVNXdJREFRQUJBb0lCQUdVS2lDK0lSWkc5V0pRcAovMWVCekl5MUIzTU1TcDZEdTJzR2FiOC82b0tNdXRCYk9sd3c3cUdRdjFxeUdHQk4yaEZnaStidVF2anVyVjArClh4TUYzZjJnSFloQnB4UEVnRmtFRnpZV1ZXNjBrdDNQUGp1ZDlMcFFDV0d0S3Q4TjFOZDFKbWd3Qy9NNjN6WFcKYzFCNGVUR2tmZWlyWmsyN1lGMkFtRWs3bDZTQWw4SXVSbHNvUnpSVjBjTGxDQ1ZqeGZrWFFaTko3d0tnQzk2RAovSXppTVhzZ2h3MEd5NFg2L1FadjZnSGFIdHN0d1lITkFyeUF4eExYMVNQOTJhc1BEdWFXYUFMbEJzdGRSc2RtCkpIdU43dWtuMGFHZEMyeVAwQWlDS2prM1NjUnpVdHhyMlBqb3d4WGYwd3BDYUFYN2xVenk2RWpId29VTmg1VEcKenlhRTRoa0NnWUVBN2o2T21lSzdqN1E4SlMwR2NtTDVxbmMzdkRkUkdSdFlhclk2UzR1Qko2UmVnMkJGSFZ4MApPU2lrV1E2Z2dTTkJvMnEwR3pvcVpYZ3FSdGVtYjh1WUhUR25ad2NlRGd4dVhMcXg2cHRKWTlvajJJcDNXQXFaCjdWei95WGl6aWpIWmJZTW5IQmUyODFEMmFUQzlJRXpwdGg2NmlTZ2VYa1pWOFhNL1BCQjk1ZVVDZ1lFQXhPbXcKM04wY2J6QkxPeUN5Zkdhc2phRnAxNml3djFMYVB2b3ZSaG9ubDhkczVmVy9GdC9kMTY1YVorOCtnTU1rN1kxTwpFdkxmVjFkZXVUS0Y0VDV4dC90ZTVYcU5ocXJRbWt5RmZUY0tFem8rODZnUnpxTWRqSXh1eDhjN3FRWjg3Y2xxCitSNTBUbzZraGt1YXlpc1hWT05CV2VxekFSWk9QWmh2L2xSaUl0c0NnWUVBb0xZZ1dkeGg2OW1JTFFmSGJvZ24KcFA5UTRLMXNEb1NzeXlkc0FhUDBsdnBCSzF4WW95ckgxL3I3aW52Y2QrQ0JtYXdVSEwzSzliSHV5dVVVQ0J3TgoyN3V3RWtieDFrWTZlR0VVUFk5TkhZZDhZTWxmSWt2Y2RBc2xIUkpJQXJRSDJPRDlFKzFIWTdFODE4Nmg5ZFVNClh1Y3hxKzRkTmprNkptczR2OXJjSXFVQ2dZQUJrTDRJTTNYTGFIM2duWFR0eWo4cTdSS1RWVkw2WW1VN3hPOWwKUmtYMFRmQ09yM0p5Y3hzbllNcDFNeEN6STFvQ3pYSEdjc25WdnVzUTI5YjJvSEYwL2ZtV0ozQkNsczhMdXZvQQpzZFJSck0vZFRnTytPY3U5VjB4MktCNVFUSzNua2dkWXJhWk5EWk0vUWhDYjlOVzlwZ1RaK3lTcktJczhzQjZMCnpnM3Rxd0tCZ1FDTzhrNkVZRVVoZW5DMWNldSs0ejdEWmZrL01CTWlJci9ob1NySllaSmVOWldBSDJOd2p5M0cKUlhYWTZzdVRZRFRXVUJYWTZZMDl2STdOQzhmRk11ZmhyM28zaThMMVNWMlNCQ0VyMlV4T3RHWnN4TEVMMnhUQwpCbFIrMEF2MnUzSFBKRTBiV3ptVGh3U1RlQ2h0Z3pZQ2tIUlZlNlJMZVhET0w3SkFnQWNyM1E9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
---
apiVersion: apps/v1
kind: Deployment
metadata:
Expand All @@ -41,15 +57,30 @@ spec:
spec:
containers:
- name: hello-osiris
image: tariq181290/hello-osiris:0.3.0
image: krancour/hello-osiris:v0.1.0
args:
- --https-cert
- /hello-osiris/cert/server.crt
- --https-key
- /hello-osiris/cert/server.key
ports:
- containerPort: 4000
- containerPort: 5000
- containerPort: 8080
- containerPort: 8081
- containerPort: 8082
- containerPort: 4430
volumeMounts:
- name: cert
mountPath: /hello-osiris/cert
readOnly: true
livenessProbe:
httpGet:
path: /
port: 4000
path: /healthz
port: 8080
readinessProbe:
httpGet:
path: /
port: 4000
path: /healthz
port: 8080
volumes:
- name: cert
secret:
secretName: hello-osiris-cert
49 changes: 33 additions & 16 deletions pkg/deployments/activator/activator.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/deislabs/osiris/pkg/healthz"
k8s "github.com/deislabs/osiris/pkg/kubernetes"
"github.com/deislabs/osiris/pkg/net/tcp"
"github.com/golang/glog"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -30,13 +31,13 @@ type activator struct {
indicesLock sync.RWMutex
deploymentActivations map[string]*deploymentActivation
deploymentActivationsLock sync.Mutex
srv *http.Server
dynamicProxyListenAddrStr string
dynamicProxy tcp.DynamicProxy
httpClient *http.Client
}

func NewActivator(kubeClient kubernetes.Interface) Activator {
func NewActivator(kubeClient kubernetes.Interface) (Activator, error) {
const port = 5000
mux := http.NewServeMux()
a := &activator{
kubeClient: kubeClient,
servicesInformer: k8s.ServicesIndexInformer(
Expand All @@ -51,18 +52,30 @@ func NewActivator(kubeClient kubernetes.Interface) Activator {
nil,
nil,
),
services: map[string]*corev1.Service{},
nodeAddresses: map[string]struct{}{},
srv: &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
},
appsByHost: map[string]*app{},
deploymentActivations: map[string]*deploymentActivation{},
dynamicProxyListenAddrStr: fmt.Sprintf(":%d", port),
services: map[string]*corev1.Service{},
nodeAddresses: map[string]struct{}{},
appsByHost: map[string]*app{},
deploymentActivations: map[string]*deploymentActivation{},
httpClient: &http.Client{
Timeout: time.Minute * 1,
},
}
var err error
a.dynamicProxy, err = tcp.NewDynamicProxy(
a.dynamicProxyListenAddrStr,
func(r *http.Request) (string, int, error) {
return a.activateAndWait(r.Host)
},
nil,
func(serverName string) (string, int, error) {
return a.activateAndWait(fmt.Sprintf("%s:tls", serverName))
},
nil,
)
if err != nil {
return nil, err
}
a.servicesInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: a.syncService,
UpdateFunc: func(_, newObj interface{}) {
Expand All @@ -77,8 +90,7 @@ func NewActivator(kubeClient kubernetes.Interface) Activator {
},
DeleteFunc: a.syncDeletedNode,
})
mux.HandleFunc("/", a.handleRequest)
return a
return a, nil
}

func (a *activator) Run(ctx context.Context) {
Expand All @@ -97,10 +109,15 @@ func (a *activator) Run(ctx context.Context) {
cancel()
}()
go func() {
if err := a.runServer(ctx); err != nil {
glog.Errorf("Server error: %s", err)
cancel()
glog.Infof(
"Activator server is listening on %s, proxying all deactivated, "+
"Osiris-enabled applications",
a.dynamicProxyListenAddrStr,
)
if err := a.dynamicProxy.ListenAndServe(ctx); err != nil {
glog.Errorf("Error listening and serving: %s", err)
}
cancel()
}()
healthz.RunServer(ctx, 5001)
cancel()
Expand Down
15 changes: 5 additions & 10 deletions pkg/deployments/activator/app.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package activator

import (
"net/http/httputil"
"net/url"
)

type app struct {
namespace string
serviceName string
deploymentName string
targetURL *url.URL
proxyRequestHandler *httputil.ReverseProxy
namespace string
serviceName string
deploymentName string
targetHost string
targetPort int
}
72 changes: 51 additions & 21 deletions pkg/deployments/activator/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ package activator

import (
"fmt"
"net/http/httputil"
"net/url"
"regexp"

"github.com/golang/glog"
)

// nolint: lll
Expand Down Expand Up @@ -59,25 +55,46 @@ func (a *activator) updateIndex() {
}
}
}
// Determine the "default" TLS port. When a TLS-secured request arrives at
// the activator, the TLS SNI header won't indicate a port. After
// activation is complete, the activator needs to forward the request to
// the service (which is now backed by application endpoints). It's
// important to know which service port to forward the request to.
var tlsDefaultPort string
if tlsDefaultPort, ok =
svc.Annotations["osiris.deislabs.io/tlsPort"]; !ok {
// If not specified, try to infer it.
// If there's only one port, that's it.
if len(svc.Spec.Ports) == 1 {
tlsDefaultPort = fmt.Sprintf("%d", svc.Spec.Ports[0].Port)
} else {
// Look for a port named "https". If found, that's it. While we're
// looping also look to see if the servie exposes port 443. If no port
// is named "https", we'll assume 443 (if exposed) is the default
// port.
var foundPort443 bool
for _, port := range svc.Spec.Ports {
if port.Name == "https" {
tlsDefaultPort = fmt.Sprintf("%d", port.Port)
break
}
if port.Port == 443 {
foundPort443 = true
}
}
if tlsDefaultPort == "" && foundPort443 {
ingressDefaultPort = "443"
}
}
}
// For every port...
for _, port := range svc.Spec.Ports {
targetURL, err :=
url.Parse(fmt.Sprintf("http://%s:%d", svc.Spec.ClusterIP, port.Port))
if err != nil {
glog.Errorf(
"Error parsing target URL for service %s in namespace %s: %s",
svc.Name,
svc.Namespace,
err,
)
continue
}
app := &app{
namespace: svc.Namespace,
serviceName: svc.Name,
deploymentName: deploymentName,
targetURL: targetURL,
proxyRequestHandler: httputil.NewSingleHostReverseProxy(targetURL),
namespace: svc.Namespace,
serviceName: svc.Name,
deploymentName: deploymentName,
targetHost: svc.Spec.ClusterIP,
targetPort: int(port.Port),
}
// If the port is 80, also index by hostname/IP sans port number...
if port.Port == 80 {
Expand Down Expand Up @@ -108,6 +125,19 @@ func (a *activator) updateIndex() {
}
}
}
if fmt.Sprintf("%d", port.Port) == tlsDefaultPort {
// Now index by hostname:tls. Note that there's no point in indexing
// by IP:tls because SNI server name will never be an IP.
// kube-dns name
appsByHost[fmt.Sprintf("%s:tls", svcDNSName)] = app
// Honor all annotations of the form
// ^osiris\.deislabs\.io/loadBalancerHostname(?:-\d+)?$
for k, v := range svc.Annotations {
if loadBalancerHostnameAnnotationRegex.MatchString(k) {
appsByHost[fmt.Sprintf("%s:tls", v)] = app
}
}
}
// Now index by hostname/IP:port...
// kube-dns name
appsByHost[fmt.Sprintf("%s:%d", svcDNSName, port.Port)] = app
Expand All @@ -119,7 +149,7 @@ func (a *activator) updateIndex() {
appsByHost[fmt.Sprintf("%s:%d", loadBalancerIngress.IP, port.Port)] = app // nolint: lll
}
}
// Node honame/IP:node-port
// Node hostname/IP:node-port
if port.NodePort != 0 {
for nodeAddress := range a.nodeAddresses {
appsByHost[fmt.Sprintf("%s:%d", nodeAddress, port.NodePort)] = app
Expand Down
Loading

0 comments on commit 4f8deee

Please sign in to comment.