Skip to content

Commit

Permalink
update readme and add goreleaser support
Browse files Browse the repository at this point in the history
  • Loading branch information
tzneal committed Nov 30, 2021
1 parent 5c68b82 commit 66dc9dd
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 27 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.idea
.envrc
supplant
dist/
30 changes: 30 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
before:
hooks:
- go mod tidy
- go generate ./...
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
Binary file added .images/supplant-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
158 changes: 158 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# 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


![supplant Diagram](./.images/supplant-diagram.png)

## 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](https://github.com/telepresenceio/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.
```bash
# 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.

```yml
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.

```bash
$ ./supplant config clean test.yml
```

The test.yml now looks like this:
```yaml
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:

```bash
$ 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.

```bash
$ 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:
```bash
$ 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.
```bash
$ 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.
```bash
$ 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/
```
17 changes: 17 additions & 0 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/tzneal/supplant/model"
"gopkg.in/yaml.v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
)
Expand All @@ -33,7 +34,12 @@ easy construction of the configuration.`,
log.Fatalf("error listing services: %s", err)
}
cfg := model.Config{}
includeAll,_ := cmd.Flags().GetBool("all")
for _, svc := range svcList.Items {
// skip kube-system services by default
if skipByDefault(svc) && !includeAll {
continue
}
cfg.Supplant = append(cfg.Supplant, model.MapSupplantService(svc))
cfg.External = append(cfg.External, model.MapExternalService(svc))
}
Expand All @@ -42,6 +48,16 @@ easy construction of the configuration.`,
},
}

func skipByDefault(svc v1.Service) bool {
if svc.Namespace =="kube-system" {
return true
}
if svc.Namespace == "default" && svc.Name == "kubernetes" {
return true
}
return false
}

func writeConfig(cfg model.Config, outputFile string) {
fo, err := os.Create(outputFile)
if err != nil {
Expand All @@ -56,4 +72,5 @@ func writeConfig(cfg model.Config, outputFile string) {

func init() {
configCmd.AddCommand(createCmd)
createCmd.Flags().BoolP("all","A",false,"If true, include items in the kube-system namespace")
}
8 changes: 2 additions & 6 deletions cmd/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,8 @@ func printInfo(format string, a ...interface{}) {
}

func printError(format string, a ...interface{}) {
color.Red("ERROR ")
fmt.Printf(format, a...)
fmt.Println()
color.Red("ERROR " + format, a...)
}
func printWarn(format string, a ...interface{}) {
color.Yellow("WARN ")
fmt.Printf(format, a...)
fmt.Println()
color.Yellow("WARN " + format, a...)
}
Loading

0 comments on commit 66dc9dd

Please sign in to comment.