Introduction
One of the changes in the latest Kubernetes version, 1.30 (Uwubnetes) is the transition from SPDY to websocket for some of the kubectl
commands. Most of the updates get most of the attention, but this is no less important.
Understanding SPDY:
Google created SPDY to improve HTTP by speeding up webpage load times and enhancing security with a session layer. It allowed multiple data streams to be sent simultaneously, prioritized request values, and optimized communication by compressing headers. However, SPDY was deprecated in 2016 and replaced by HTTP/2, which is more advanced. Therefore, for technologies like Kubernetes that require better and standards communication methods, SPDY is no longer relevant.
Why WebSockets?
WebSockets use a bidirectional communication protocol over a single TCP connection, widely supported in web servers, proxies, and network gateways. This increases compatibility and communication performance for kubectl
commands. Here are some points about WebSockets:
- WebSockets allow for full-duplex communication, so it's very efficient to send and receive data. It requires no multiple connections, that would require handshakes.
- This technology reduces overhead and allows for high-speed data exchanges, which is especially interesting for Kubernetes-related tasks.
- WebSockets are protocol-independent so they may be used for communication over any full-duplex single-connection protocol, which will reduce latency and help in real-time command execution.
- WebSockets keep a persistent connection and thus use resources more efficiently and cut the number of TCP connections used, in turn reducing the amount of network overhead.
- The use of WebSockets fix issues that older protocols like SPDY had, and it's more compatible with modern web technologies.
The migration from SPDY to WebSockets in Kubernetes makes it much easier to communicate control messages, which will allow for better cluster management and stability.
Kep 4006
The SPDY protocol was deprecated in 2016. In 2023, a proposal was opened in the Kubernetes community to change the protocol for commands like port-forward, attach, exec, and portforward that use upgraded connections.
Link to proposal Link to issue
Implementation Overview:
WebSocket support in Kubernetes and kubectl starts with an HTTP handshake that upgrades the connection to a WebSocket, like as the SPDY. The WebSocket framing method provides improved technique for data framing for streams such as stdin, stdout, and stderr through framing with a channel identifier. This is an extension of the technique once used by SPDY through binary data framing.
As a fallback mechanism in case WebSocket support is not available on the server, a fallback to SPDY is utilized. The initial performance assessments demonstrated that WebSocket is as efficient as, or even a little better than, SPDY for standard kubectl operations, and continuing performance testing is intended to better performance across multiple use cases. There are many implications from this transition, including greater network compatibility in settings where strong proxy and firewall policies are in place, and better integration with external tools.
API server changes:
The API server now supports the WebSocket protocol by replacing SPDY with explicit headers to enable bidirectional streams. Additionally, the StreamTranslatorProxy
has been added to help create WebSocket connections from clients to internal SPDY connections in Kubernetes.
The management of WebSocket frames is critical, especially for the occasional use of the ping/pong frames to keep the WebSocket connection alive. This feature was first introduced in an alpha version that one could use through the feature gate for port forwarding Websockets within the API server and a corresponding flag in kubectl
.
Client (kubectl
) Changes:
On the client side, it added an upgrade to WebSocket for port forwarding, along with signing, response processing, and data serialization and deserialization over the WebSocket. This gives the user the ability to enable WebSocket port forwarding through environment variables. To make this transition easy, kubectl has also introduced mechanisms to handle errors and reliability, with error handling during its alpha and beta phases, along with a fallback to SPDY in case the WebSocket connection fails to ensure that service continuity is not lost.
Websockets in kubectl port-forward
with Kind
Setup Env
Before creating my environment, Docker, Kind, and Go have to be installed on the machine. All of these are needed for developing and deploying our Kubernetes cluster on our local machines. Let's set up the environment with Kubernetes 1.30.
Let's start by setting up the necessary environment variables and dependencies:
# Set Go environment variables
export GOPATH=$HOME/go
export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
# Create a directory for Kubernetes source
mkdir -p $GOPATH/src/k8s.io
cd $GOPATH/src/k8s.io
# Clone the Kubernetes repository
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
Install Dependencies on Mac M1
For those uses Mac with an M1 chip:
brew install bash
brew install gnu-tar
Building the kubernetes and kubectl 1.30 image
To build the images and kubectl, we need to have Go 1.22 or later. Then, execute some commands:
# Checkout to the Kubernetes 1.30 release
git checkout release-1.30
# Build a node image using Kind
kind build node-image
# Build the kubectl in this version
make kubectl KUBE_BUILD_PLATFORMS=darwin/arm64
# kubectl artifact output path : $GOPATH/kubernetes/_output/dockerized/bin/darwin/arm64/kubectl
chmod +x $GOPATH/kubernetes/_output/dockerized/bin/darwin/arm64/kubectl
Kind Cluster Configs
First, set up the kind cluster with 1.30 images by creating a config.yaml
file with featureGates PortForwardWebsockets
enabled:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
"PortForwardWebsockets": true
nodes:
- role: control-plane
image: kindest/node:latest
- role: worker
image: kindest/node:latest
- role: worker
image: kindest/node:latest
Create the cluster
When you have your configuration file set up you can add the cluster with the following command:
# Change default kubeconfig path to have only with kind test cluster
export KUBECONFIG=~/.kube/kind-kind
kind create cluster --name kind-1-30 --config config.yaml
Deploy a sample app
You can test your new WebSocket capability by deploying a simple application and using the cluster to connect by port forwarding:
git clone https://github.com/digitalocean/kubernetes-sample-apps.git
cd kubernetes-sample-apps
kubectl apply -k bookinfo-example/kustomize
Forward the Port Using WebSockets
Enable the WebSocket port forwarding :
export KUBECTL_PORT_FORWARD_WEBSOCKETS="true"
$GOPATH/kubernetes/_output/dockerized/bin/darwin/arm64/kubectl -v6 port-forward svc/productpage -n bookinfo 9080:9080
Now, open http://localhost:9080/
in your browser. You should get responses from the echoserver.
Verifying WebSocket Port Forwarding
To confirm if WebSocket port forwarding is active, review the kubectl logs:
This is the log with SDPY (default):
I0422 17:32:50.963059 11299 round_trippers.go:466] curl -v -XPOST -H "X-Stream-Protocol-Version: portforward.k8s.io" -H "User-Agent: kubectl/v1.31.0 (darwin/arm64) kubernetes/cae35db" 'https://192.168.68.109:6443/api/v1/namespaces/bookinfo/pods/productpage-v1-64c658687d-5bhsg/portforward'
I0422 17:32:50.965663 11299 round_trippers.go:510] HTTP Trace: Dial to tcp:192.168.68.109:6443 succeed
I0422 17:32:50.978422 11299 round_trippers.go:553] POST https://192.168.68.109:6443/api/v1/namespaces/bookinfo/pods/productpage-v1-64c658687d-5bhsg/portforward 101 Switching Protocols in 15 milliseconds
I0422 17:32:50.978440 11299 round_trippers.go:570] HTTP Statistics: DNSLookup 0 ms Dial 2 ms TLSHandshake 0 ms Duration 15 ms
I0422 17:32:50.978444 11299 round_trippers.go:577] Response Headers:
I0422 17:32:50.978448 11299 round_trippers.go:580] Upgrade: SPDY/3.1
I0422 17:32:50.978450 11299 round_trippers.go:580] X-Stream-Protocol-Version: portforward.k8s.io
I0422 17:32:50.978453 11299 round_trippers.go:580] Connection: Upgrade
This is the logs with Websocket enabled:
17:29:21.504179 7561 tunneling_dialer.go:75] Before WebSocket Upgrade Connection...
I0422 17:29:21.504194 7561 round_trippers.go:466] curl -v -XGET -H "Sec-Websocket-Protocol: SPDY/3.1+portforward.k8s.io" -H "User-Agent: kubectl/v1.31.0 (darwin/arm64) kubernetes/cae35db" 'https://192.168.68.109:6443/api/v1/namespaces/bookinfo/pods/productpage-v1-64c658687d-5bhsg/portforward'
I0422 17:29:21.506351 7561 round_trippers.go:510] HTTP Trace: Dial to tcp:192.168.68.109:6443 succeed
I0422 17:29:21.519936 7561 round_trippers.go:553] GET https://192.168.68.109:6443/api/v1/namespaces/bookinfo/pods/productpage-v1-64c658687d-5bhsg/portforward 101 Switching Protocols in 15 milliseconds
I0422 17:29:21.519954 7561 round_trippers.go:570] HTTP Statistics: DNSLookup 0 ms Dial 2 ms TLSHandshake 4 ms ServerProcessing 8 ms Duration 15 ms
I0422 17:29:21.519958 7561 round_trippers.go:577] Response Headers:
I0422 17:29:21.519962 7561 round_trippers.go:580] Sec-Websocket-Accept: +AqmlgtoGPP/Rlfw6oAZMCN34SY=
I0422 17:29:21.519964 7561 round_trippers.go:580] Sec-Websocket-Protocol: SPDY/3.1+portforward.k8s.io
I0422 17:29:21.519966 7561 round_trippers.go:580] Upgrade: websocket
I0422 17:29:21.519968 7561 round_trippers.go:580] Connection: Upgrade
I0422 17:29:21.519973 7561 tunneling_dialer.go:85] negotiated protocol: portforward.k8s.io
The logs show that the protocol upgrade to SPDY via WebSocket was successful in Kubernetes 1.30
KEP Roadmap Port Forward over Websockets
Alpha Release (Kubernetes 1.30):
WebSocket support added. Feature enabled via the KUBECTL_PORT_FORWARD_WEBSOCKETS=true
env var on the client side and PortForwardWebsockets
alpha feature flag should be enabled on the API Server,.
Focus in the alpha phase would be testing from all perspectives, user feedback, and updating developer and user documentation for this alpha feature.
Beta Release(Target 1.31):
More extensive testing for performance fine-tuning, user feedback, and updated documentation, the documentation will be updated regularly with the changes during the beta phase improvements.
User community feedback will be the basis in making the feature stable and usable.
Stable Release (Target 1.32):
Ensure WebSocket support transitions from beta to stable with alpha feature flags removed and publish a deprecation notice to officially mark the transition away from SPDY-based port forwarding.
KEP Roadmap RemoteCommand over websockets (exec, cp, attach)
Beta release (Kubernetes 1.30):
WebSocket for RemoteCommand
(exec, cp, attach) will be on by default. The feature gates to the client and server are expected to ensure that the SPDY replacement has as little impact on the user as possible.
The biggest emphasis here is on making the changes transparent to the user and, for those who are wary of this new implementation, will always have the choice to disable it.
Path to Stability:
Post 1.30 and leading up to the 1.32 release, the focus will be on refinement in WebSocket functionality, all aimed at reaching zero known critical bugs.
Conclusion
And thats all, folks. This hopefully explains the important protocol communication change in Kubernetes. I've had too many issues due to SPDY and lack of compatibility with new load balancers, proxies, etc., which was so complicated that I found myself at work. Now I see this migration from SPDY to WebSockets, so I'm a bit excited.
Looking forward, I'll let you know if there are any updates about this KEP
Also, maybe there'll be errors or misinformation in this post; I'm just human after all. Feel free to let me know if you find anything off on social media or post a comment here at the blog. Thanks!