diff --git a/examples/websocket/README.md b/examples/websocket/README.md new file mode 100644 index 0000000000..337f568880 --- /dev/null +++ b/examples/websocket/README.md @@ -0,0 +1,34 @@ +# WebSocket support + +To load balance a WebSocket application with NGINX Ingress controllers, you need to add the **nginx.org/websocket-services** annotation to your Ingress resource definition. The annotation specifies which services are websocket services. The annotation syntax is as follows: +``` +nginx.org/websocket-services: "service1[,service2,...]" +``` + +In the following example we load balance three applications, one of which is using WebSocket: +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: cafe-ingress + annotations: + nginx.org/websocket-services: "ws-svc" +spec: + rules: + - host: cafe.example.com + http: + paths: + - path: /tea + backend: + serviceName: tea-svc + servicePort: 80 + - path: /coffee + backend: + serviceName: coffee-svc + servicePort: 80 + - path: /ws + backend: + serviceName: ws-svc + servicePort: 8008 +``` +*ws-svc* is a service for the WebSocket application. The service becomes available at the `/ws` path. Note how we used the **nginx.org/websocket-services** annotation. diff --git a/nginx-controller/nginx/configurator.go b/nginx-controller/nginx/configurator.go index 4c45791967..fdbab6a726 100644 --- a/nginx-controller/nginx/configurator.go +++ b/nginx-controller/nginx/configurator.go @@ -2,6 +2,7 @@ package nginx import ( "fmt" + "strings" "sync" "github.com/golang/glog" @@ -78,6 +79,8 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri upstreams := make(map[string]Upstream) + wsServices := getWebsocketServices(ingEx) + if ingEx.Ingress.Spec.Backend != nil { name := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName) upstream := cnf.createUpstream(ingEx, name, ingEx.Ingress.Spec.Backend, ingEx.Ingress.Namespace) @@ -116,7 +119,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri upstreams[upsName] = upstream } - loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg) + loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg, wsServices[path.Backend.ServiceName]) locations = append(locations, loc) if loc.Path == "/" { @@ -126,7 +129,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri if rootLocation == false && ingEx.Ingress.Spec.Backend != nil { upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName) - loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg) + loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName]) locations = append(locations, loc) } @@ -147,7 +150,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName) - loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg) + loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName]) locations = append(locations, loc) server.Locations = locations @@ -172,13 +175,26 @@ func (cnf *Configurator) createConfig(ingEx *IngressEx) Config { return ingCfg } -func createLocation(path string, upstream Upstream, cfg *Config) Location { +func getWebsocketServices(ingEx *IngressEx) map[string]bool { + wsServices := make(map[string]bool) + + if services, exists := ingEx.Ingress.Annotations["nginx.org/websocket-services"]; exists { + for _, svc := range strings.Split(services, ",") { + wsServices[svc] = true + } + } + + return wsServices +} + +func createLocation(path string, upstream Upstream, cfg *Config, websocket bool) Location { loc := Location{ Path: path, Upstream: upstream, ProxyConnectTimeout: cfg.ProxyConnectTimeout, ProxyReadTimeout: cfg.ProxyReadTimeout, ClientMaxBodySize: cfg.ClientMaxBodySize, + Websocket: websocket, } return loc diff --git a/nginx-controller/nginx/ingress.tmpl b/nginx-controller/nginx/ingress.tmpl index a16ff8f6e8..09b18a0380 100644 --- a/nginx-controller/nginx/ingress.tmpl +++ b/nginx-controller/nginx/ingress.tmpl @@ -25,6 +25,11 @@ server { {{range $location := $server.Locations}} location {{$location.Path}} { + proxy_http_version 1.1; + {{if $location.Websocket}} + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + {{end}} proxy_connect_timeout {{$location.ProxyConnectTimeout}}; proxy_read_timeout {{$location.ProxyReadTimeout}}; client_max_body_size {{$location.ClientMaxBodySize}}; diff --git a/nginx-controller/nginx/nginx.conf.tmpl b/nginx-controller/nginx/nginx.conf.tmpl index 97ef4c9889..c5803b323d 100644 --- a/nginx-controller/nginx/nginx.conf.tmpl +++ b/nginx-controller/nginx/nginx.conf.tmpl @@ -31,5 +31,10 @@ http { server_names_hash_max_size {{.ServerNamesHashMaxSize}}; {{if .ServerNamesHashBucketSize}}server_names_hash_bucket_size {{.ServerNamesHashBucketSize}};{{end}} + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + include /etc/nginx/conf.d/*.conf; } diff --git a/nginx-controller/nginx/nginx.go b/nginx-controller/nginx/nginx.go index 12429d0fc3..1846a9eb03 100644 --- a/nginx-controller/nginx/nginx.go +++ b/nginx-controller/nginx/nginx.go @@ -52,6 +52,7 @@ type Location struct { ProxyConnectTimeout string ProxyReadTimeout string ClientMaxBodySize string + Websocket bool } // NginxMainConfig describe the main NGINX configuration file diff --git a/nginx-plus-controller/nginx/configurator.go b/nginx-plus-controller/nginx/configurator.go index d941f5cbb7..e328139737 100644 --- a/nginx-plus-controller/nginx/configurator.go +++ b/nginx-plus-controller/nginx/configurator.go @@ -2,6 +2,7 @@ package nginx import ( "fmt" + "strings" "sync" "time" @@ -81,6 +82,8 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri upstreams := make(map[string]Upstream) + wsServices := getWebsocketServices(ingEx) + if ingEx.Ingress.Spec.Backend != nil { name := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName) upstream := cnf.createUpstream(name) @@ -121,7 +124,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri upstreams[upsName] = upstream } - loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg) + loc := createLocation(pathOrDefault(path.Path), upstreams[upsName], &ingCfg, wsServices[path.Backend.ServiceName]) locations = append(locations, loc) if loc.Path == "/" { @@ -131,7 +134,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri if rootLocation == false && ingEx.Ingress.Spec.Backend != nil { upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName) - loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg) + loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName]) locations = append(locations, loc) } @@ -156,7 +159,7 @@ func (cnf *Configurator) generateNginxCfg(ingEx *IngressEx, pems map[string]stri upsName := getNameForUpstream(ingEx.Ingress, emptyHost, ingEx.Ingress.Spec.Backend.ServiceName) - loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg) + loc := createLocation(pathOrDefault("/"), upstreams[upsName], &ingCfg, wsServices[ingEx.Ingress.Spec.Backend.ServiceName]) locations = append(locations, loc) server.Locations = locations @@ -181,13 +184,26 @@ func (cnf *Configurator) createConfig(ingEx *IngressEx) Config { return ingCfg } -func createLocation(path string, upstream Upstream, cfg *Config) Location { +func getWebsocketServices(ingEx *IngressEx) map[string]bool { + wsServices := make(map[string]bool) + + if services, exists := ingEx.Ingress.Annotations["nginx.org/websocket-services"]; exists { + for _, svc := range strings.Split(services, ",") { + wsServices[svc] = true + } + } + + return wsServices +} + +func createLocation(path string, upstream Upstream, cfg *Config, websocket bool) Location { loc := Location{ Path: path, Upstream: upstream, ProxyConnectTimeout: cfg.ProxyConnectTimeout, ProxyReadTimeout: cfg.ProxyReadTimeout, ClientMaxBodySize: cfg.ClientMaxBodySize, + Websocket: websocket, } return loc diff --git a/nginx-plus-controller/nginx/ingress.tmpl b/nginx-plus-controller/nginx/ingress.tmpl index 03412cf914..a071d8b6a5 100644 --- a/nginx-plus-controller/nginx/ingress.tmpl +++ b/nginx-plus-controller/nginx/ingress.tmpl @@ -27,6 +27,11 @@ server { {{range $location := $server.Locations}} location {{$location.Path}} { + proxy_http_version 1.1; + {{if $location.Websocket}} + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + {{end}} proxy_connect_timeout {{$location.ProxyConnectTimeout}}; proxy_read_timeout {{$location.ProxyReadTimeout}}; client_max_body_size {{$location.ClientMaxBodySize}}; diff --git a/nginx-plus-controller/nginx/nginx.conf.tmpl b/nginx-plus-controller/nginx/nginx.conf.tmpl index 71d60a6c12..92a87fbe7c 100644 --- a/nginx-plus-controller/nginx/nginx.conf.tmpl +++ b/nginx-plus-controller/nginx/nginx.conf.tmpl @@ -31,5 +31,10 @@ http { server_names_hash_max_size {{.ServerNamesHashMaxSize}}; {{if .ServerNamesHashBucketSize}}server_names_hash_bucket_size {{.ServerNamesHashBucketSize}};{{end}} + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + include /etc/nginx/conf.d/*.conf; } diff --git a/nginx-plus-controller/nginx/nginx.go b/nginx-plus-controller/nginx/nginx.go index edfee8fa6e..48a902fb87 100644 --- a/nginx-plus-controller/nginx/nginx.go +++ b/nginx-plus-controller/nginx/nginx.go @@ -73,6 +73,7 @@ type Location struct { ProxyConnectTimeout string ProxyReadTimeout string ClientMaxBodySize string + Websocket bool } // NginxMainConfig describe the main NGINX configuration file