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

WIP: https & http/2 support (both h2c and full h2) #27

Merged
merged 9 commits into from
May 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
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