Add websocket example

This commit is contained in:
Nick Sardo 2017-06-09 12:29:07 -07:00
parent dbb12afbb9
commit 46a9943eb6
5 changed files with 249 additions and 0 deletions

View file

@ -0,0 +1,5 @@
FROM alpine:3.5
COPY wsserver /wsserver
CMD ["/wsserver"]

View file

@ -0,0 +1,109 @@
# Simple Websocket Example
Any websocket server will suffice; however, for the purpose of demonstration, we'll use the gorilla/websocket package in a Go process.
### Build
```shell
➜ CGO_ENABLED=0 go build -o wsserver
```
### Containerize
```shell
➜ docker build -t nicksardo/websocketexample .
Sending build context to Docker daemon 6.134 MB
Step 1 : FROM alpine:3.5
---> 4a415e366388
Step 2 : COPY wsserver /wsserver
---> 8002887d752d
Removing intermediate container 7772a3e76155
Step 3 : CMD /wsserver
---> Running in 27c8ff226267
---> eecd0574e5d1
Removing intermediate container 27c8ff226267
Successfully built eecd0574e5d1
➜ docker push nicksardo/websocketexample:latest
...
```
### Deploy
Either update the image in the `Deployment` to your newly created image or continue using `nicksardo/websocketexample.`
```shell
➜ vi deployment.yaml
# Change image to your own
```
```shell
➜ kubectl create -f deployment.yaml
deployment "ws-example" created
service "ws-example-svc" created
ingress "ws-example-ing" created
```
### Test
Retrieve the ingress external IP:
```shell
➜ kubectl get ing/ws-example-ing
NAME HOSTS ADDRESS PORTS AGE
ws-example-ing * xxx.xxx.xxx.xxx 80 3m
```
Wait for the loadbalancer to be created and functioning. When you receive a successful response, you can proceed.
```
➜ curl http://xxx.xxx.xxx.xxx
Websocket example. Connect to /ws%
```
The binary we deployed does not have any html/javascript to demonstrate thwe websocket, so we'll use websocket.org's client.
Visit http://www.websocket.org/echo.html. It's important to use `HTTP` instead of `HTTPS` since we assembled an `HTTP` load balancer. Browsers may prevent `HTTP` websocket connections as a security feature.
Set the `Location` to
```
ws://xxx.xxx.xxx.xxx/ws
```
Click 'Connect' and you should see messages received from server:
![Log screenshot](http://i.imgur.com/hlwwa0G.png)
### Change backend timeout
At this point, the websocket connection will be destroyed by the HTTP(S) Load Balancer after 30 seconds, which is the default timeout. Note: this timeout is not an idle timeout - it's a timeout on the connection lifetime.
Currently, the GCE ingress controller does not provide a way to set this timeout via Ingress specification. You'll need to change this value either through the GCP Cloud Console or through gcloud CLI.
```shell
➜ kubectl describe ingress/ws-example-ing
Name: ws-example-ing
Namespace: default
Address: xxxxxxxxxxxx
Default backend: ws-example-svc:80 (10.48.10.12:8080,10.48.5.14:8080,10.48.7.11:8080)
Rules:
Host Path Backends
---- ---- --------
* * ws-example-svc:80 (10.48.10.12:8080,10.48.5.14:8080,10.48.7.11:8080)
Annotations:
target-proxy: k8s-tp-default-ws-example-ing--52aa8ae8221ffa9c
url-map: k8s-um-default-ws-example-ing--52aa8ae8221ffa9c
backends: {"k8s-be-31127--52aa8ae8221ffa9c":"HEALTHY"}
forwarding-rule: k8s-fw-default-ws-example-ing--52aa8ae8221ffa9c
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
12m 12m 1 loadbalancer-controller Normal ADD default/ws-example-ing
11m 11m 1 loadbalancer-controller Normal CREATE ip: xxxxxxxxxxxx
11m 9m 5 loadbalancer-controller Normal Service default backend set to ws-example-svc:31127
```
Retrieve the name of the backend service from within the annotation section.
Update the timeout field for every backend that needs a higher timeout.
```shell
➜ export BACKEND=k8s-be-31127--52aa8ae8221ffa9c
➜ gcloud compute backend-services update $BACKEND --global --timeout=86400 # seconds
Updated [https://www.googleapis.com/compute/v1/projects/xxxxxxxxx/global/backendServices/k8s-be-31127--52aa8ae8221ffa9c].
```
Wait up to twenty minutes for this change to propagate.

View file

@ -0,0 +1,47 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ws-example
spec:
replicas: 3
template:
metadata:
labels:
app: wseg
spec:
containers:
- name: websocketexample
image: nicksardo/websocketexample
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
env:
- name: podname
valueFrom:
fieldRef:
fieldPath: metadata.name
---
apiVersion: v1
kind: Service
metadata:
name: ws-example-svc
labels:
app: wseg
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
protocol: TCP
selector:
app: wseg
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ws-example-ing
spec:
backend:
serviceName: ws-example-svc
servicePort: 80

View file

@ -0,0 +1,81 @@
package main
import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/gorilla/websocket"
)
var podName string
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // Ignore http origin
},
}
func init() {
podName = os.Getenv("podname")
}
func ws(w http.ResponseWriter, r *http.Request) {
log.Println("Received request", r.RemoteAddr)
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("failed to upgrade:", err)
return
}
defer c.Close()
s := fmt.Sprintf("Connected to %v", podName)
c.WriteMessage(websocket.TextMessage, []byte(s))
handleWSConn(c)
}
func handleWSConn(c *websocket.Conn) {
stop := make(chan struct{})
go func() {
for {
time.Sleep(5 * time.Second)
select {
case <-stop:
return
default:
}
s := fmt.Sprintf("%s reports time: %v", podName, time.Now().String())
c.WriteMessage(websocket.TextMessage, []byte(s))
}
}()
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("Error while reading:", err)
break
}
if err = c.WriteMessage(mt, message); err != nil {
log.Println("Error while writing:", err)
break
}
}
close(stop)
}
func root(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Write([]byte(`Websocket example. Connect to /ws`))
}
func main() {
log.Println("Starting")
http.HandleFunc("/ws", ws)
http.HandleFunc("/", root)
log.Fatal(http.ListenAndServe(":8080", nil))
}

View file

@ -26,6 +26,7 @@ Table of Contents
* [What GCE resources are shared between Ingresses?](#what-gce-resources-are-shared-between-ingresses) * [What GCE resources are shared between Ingresses?](#what-gce-resources-are-shared-between-ingresses)
* [How do I debug a controller spin loop?](#host-do-i-debug-a-controller-spinloop) * [How do I debug a controller spin loop?](#host-do-i-debug-a-controller-spinloop)
* [Creating an Internal Load Balancer without existing ingress](#creating-an-internal-load-balancer-without-existing-ingress) * [Creating an Internal Load Balancer without existing ingress](#creating-an-internal-load-balancer-without-existing-ingress)
* [Can I use websockets?](#can-i-use-websockets)
## How do I deploy an Ingress controller? ## How do I deploy an Ingress controller?
@ -380,3 +381,9 @@ kubectl get nodes
gcloud compute instance-groups unmanaged add-instances $GROUPNAME --zone {ZONE} --instances=A,B,C... gcloud compute instance-groups unmanaged add-instances $GROUPNAME --zone {ZONE} --instances=A,B,C...
``` ```
You can now follow the GCP Console wizard for creating an internal load balancer and point to the `k8s-ig--{UID}` instance group. You can now follow the GCP Console wizard for creating an internal load balancer and point to the `k8s-ig--{UID}` instance group.
## Can I use websockets?
Yes!
The GCP HTTP(S) Load Balancer supports websockets. You do not need to change your http server or Kubernetes deployment. You will need to manually configure the created Backend Service's `timeout` setting. This value is the interpreted as the max connection duration. The default value of 30 seconds is probably too small for you. You can increase it to the supported maximum: 86400 (a day) through the GCP Console or the gcloud CLI.
View the [example](/controllers/gce/examples/websocket/).