supplant
Overview
supplant
is a tool used for improve the development experience with Kubernetes. The concept is to start with a
working cluster with all of the deployed services in your application and then to supplant or
replace a service by replacing the K8s service by a new service without a selector and creating an endpoint
that points to your local machine. The end result is that from within the cluster, the service now points to a port
on your machine outside the cluster. To allow the service are developing that exists outside the cluster
to reach any dependent services inside the cluster, those services are exposed individually via port forwarding
Why?
- Pushing new images to test a change works, but the code/test cycle is very slow
- It's convenient to run all of your existing tooling including running the service under a debugger on your machine
- Telepresence is another more seamless approach at doing this, but
it has to use a bit of networking magic to make it happen and I've had a few reliability issues with it.
Production Use
Please don't run this against a production cluster. It attempts to replace and un-replace
Sample Usage
We'll launch and expose two deployments via services that listen on port 80 and 81 respectively.
# launch the first
$ kubectl create deployment hello-1 --image=k8s.gcr.io/echoserver:1.4
$ kubectl expose deployment hello-1 --port 80 --target-port 8080
# launch the second
$ kubectl create deployment hello-2 --image=k8s.gcr.io/echoserver:1.4
$ kubectl expose deployment hello-2 --port 81 --target-port 8080
# generate our config file
$ supplant config create test.yml
The generated test.yml will now look something like this, where each of the two services is listed under a supplant
and an external
section
within the YAML file.
supplant:
- name: hello-1
namespace: default
enabled: false
ports:
- protocol: TCP
port: 80
listenport: 8080
- name: hello-2
namespace: default
enabled: false
ports:
- protocol: TCP
port: 81
listenport: 8080
external:
- name: hello-1
namespace: default
enabled: false
ports:
- protocol: TCP
targetport: 8080
localport: 0
- name: hello-2
namespace: default
enabled: false
ports:
- protocol: TCP
targetport: 8080
localport: 0
We want to replace the hello-1
service, but have our replacement be able to access the hello-2
service. So we enable
those two, and then clean our config file which removes the disabled services which are the services we will not be replacing
or providing port forwarding to.
$ ./supplant config clean test.yml
The test.yml now looks like this:
supplant:
- name: hello-1
namespace: default
enabled: true
ports:
- protocol: TCP
port: 80
listenport: 8080
external:
- name: hello-2
namespace: default
enabled: true
ports:
- protocol: TCP
targetport: 8080
localport: 0
We can now run supplant
on this configuration file:
$ supplant test.yml
=> connecting to K8s
=> K8s version: v1.21.1
=> updating service hello-1
- 192.168.88.128:8080 is now the endpoint for hello-1:80
=> forwarding for hello-2
- 127.0.0.1:38989 points to remote hello-2:8080
forwarding ports, hit Ctrl+C to exit
It lets us know that from within our cluster, anything trying to reach the hello-1 service will connect to 192.168.88.128:8080. supplant
has also
forwarded our local port 38989 to the hello-2 service at hello-2:8080
. We can verify tat we have replaced the hello-1 service by trying to reach
it from the hello-2 pod which fails as we haven't started anything listening on port 8080 yet.
$ kubectl exec -it deployment/hello-2 -- curl hello-1:80
curl: (7) Failed to connect to hello-1 port 80: Connection refused
command terminated with exit code 7
If we start a web server locally on port 8080, the connection will then work. In a separate shell we start a web server:
$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...
And then retry the connection to the hello-1 service, which now hits our Python web server.
$ kubectl exec -it deployment/hello-2 -- curl hello-1:80
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
...
Lastly, we can verify that the port forward works locally as we can reach the hello-2 service. This allows our local
service to access any resources inside the cluster that it needs to.
$ curl 127.0.0.1:38989
CLIENT VALUES:
client_address=127.0.0.1
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://127.0.0.1:8080/