Skip to content

Commit

Permalink
Support automatically port forwarding to flux instances in a Kubernet…
Browse files Browse the repository at this point in the history
…es cluster.

Simplify accessing flux instances in a Kubernetes cluster by automatically creating
port forwards, just like Helm does.

This is based on the port forward implementations from kubectl and Helm:

* https://github.com/kubernetes/helm/blob/master/pkg/kube/tunnel.go
* https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/cmd/portforward.go

The flow is:

* If `--url` is set, then fluxctl will connect directly to the specified URL.
* If `--token` is set, then fluxctl will connect to Weave cloud with the token.
* Otherwise, fluxctl will look for a pod in `--k8s-forward-namespace` (`default` by
  default) with the `name in (flux, fluxd, weave-flux-agent)` labels.
* If the number of pods that match the labels in the namespace are not exactly 1, it will
  return an error.
* It will find an empty port (by first binding to port 0 and then closing the port) and
  start a port forward on that port that forwards to the flux pod on port 3030.
* It will set the flux url to `http://127.0.0.1:$port/api/flux`.
* The port forward goroutine will get cleaned up automatically when fluxctl terminates.

Use-cases:

* Easier use of standalone flux instances.
* Discourage exposing flux apis to the internet (hopefully nobody does this).
* More easily manage large numbers of flux instances.
  • Loading branch information
Justin Barrick committed Jul 12, 2018
1 parent 891f2a0 commit 14c070a
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 53 deletions.
53 changes: 52 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ required = ["k8s.io/code-generator/cmd/client-gen"]
[[constraint]]
name = "github.com/google/go-cmp"
version = "0.2.0"

[[constraint]]
name = "github.com/justinbarrick/go-k8s-portforward"
version = "v1.0.0"
39 changes: 37 additions & 2 deletions cmd/fluxctl/root_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ import (
"github.com/weaveworks/flux/api"
transport "github.com/weaveworks/flux/http"
"github.com/weaveworks/flux/http/client"
"github.com/justinbarrick/go-k8s-portforward"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type rootOpts struct {
URL string
Token string
Namespace string
API api.Server
}

Expand All @@ -37,6 +41,7 @@ Workflow:

const (
envVariableURL = "FLUX_URL"
envVariableNamespace = "FLUX_FORWARD_NAMESPACE"
envVariableToken = "FLUX_SERVICE_TOKEN"
envVariableCloudToken = "WEAVE_CLOUD_TOKEN"
)
Expand All @@ -49,7 +54,10 @@ func (opts *rootOpts) Command() *cobra.Command {
SilenceErrors: true,
PersistentPreRunE: opts.PersistentPreRunE,
}
cmd.PersistentFlags().StringVarP(&opts.URL, "url", "u", "https://cloud.weave.works/api/flux",

cmd.PersistentFlags().StringVarP(&opts.Namespace, "k8s-forward-namespace", "f", "default",
fmt.Sprintf("Namespace that flux is in for creating a port forward; you can also set the environment variable %s", envVariableNamespace))
cmd.PersistentFlags().StringVarP(&opts.URL, "url", "u", "",
fmt.Sprintf("Base URL of the flux service; you can also set the environment variable %s", envVariableURL))
cmd.PersistentFlags().StringVarP(&opts.Token, "token", "t", "",
fmt.Sprintf("Weave Cloud service token; you can also set the environment variable %s or %s", envVariableCloudToken, envVariableToken))
Expand All @@ -74,11 +82,38 @@ func (opts *rootOpts) Command() *cobra.Command {
}

func (opts *rootOpts) PersistentPreRunE(cmd *cobra.Command, _ []string) error {
opts.Namespace = getFromEnvIfNotSet(cmd.Flags(), "k8s-forward-namespace", opts.Namespace, envVariableNamespace)
opts.Token = getFromEnvIfNotSet(cmd.Flags(), "token", opts.Token, envVariableToken, envVariableCloudToken)
opts.URL = getFromEnvIfNotSet(cmd.Flags(), "url", opts.URL, envVariableURL)

if opts.Token != "" {
opts.URL = "https://cloud.weave.works/api/flux"
} else if opts.URL == "" {
portforwarder, err := portforward.NewPortForwarder(opts.Namespace, metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: "name",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"flux", "fluxd", "weave-flux-agent"},
},
},
}, 3030)
if err != nil {
return errors.Wrap(err, "initializing port forwarder")
}

err = portforwarder.Start()
if err != nil {
return errors.Wrap(err, "creating port forward")
}

opts.URL = fmt.Sprintf("http://127.0.0.1:%d/api/flux", portforwarder.ListenPort)
}

if _, err := url.Parse(opts.URL); err != nil {
return errors.Wrapf(err, "parsing URL")
}
opts.Token = getFromEnvIfNotSet(cmd.Flags(), "token", opts.Token, envVariableToken, envVariableCloudToken)

opts.API = client.New(http.DefaultClient, transport.NewAPIRouter(), opts.URL, client.Token(opts.Token))
return nil
}
Expand Down
20 changes: 0 additions & 20 deletions deploy/flux-nodeport.yaml

This file was deleted.

42 changes: 12 additions & 30 deletions site/using.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,28 @@ You need to tell `fluxctl` where to find the Flux API and to make
the pod accessible to the command-line client `fluxctl`, you
will need to expose the API outside the cluster.

A simple way to do this is to piggy-back on `kubectl port-forward`,
assuming you can access the Kubernetes API:
By default, fluxctl will attempt to forward to your Flux instance in the `default`
namespace. You can also specify the namespace that your Flux instance lives in with the
`-f` or `--k8s-forward-namespace` flag:

```sh
fluxpod=$(kubectl get pod -l name=flux -o name | awk -F / '{ print $2; }')
kubectl port-forward "$fluxpod" 10080:3030 &
export FLUX_URL="http://localhost:10080/api/flux"
```

Exporting `FLUX_URL` is enough for `fluxctl` to know how to contact
the daemon. You could alternatively supply the `--url` argument
each time.

## Flux API service

Now you can easily query the Flux API:

```sh
fluxctl list-controllers --all-namespaces
fluxctl -f default list-controllers
```

### Local endpoint
This setting can also be set in an environment variable (`FLUX_FORWARD_NAMESPACE`).

**Beware**: this exposes the Flux API, unauthenticated, over an
insecure channel. Do not do this _unless_ you are operating Flux
entirely locally; and arguably, only to try it out.
If you are not able to use the port forward to connect, you can connect by URL with
`--url` or `FLUX_URL`:

If you are running Flux locally, e.g., in minikube, you can use a
service with a
[`NodePort`](http://kubernetes.io/docs/user-guide/services/#type-nodeport).
```
fluxctl --url http://127.0.0.1:3030/ list-controllers
```

An example manifest is given in
[flux-nodeport.yaml](../../deploy/flux-nodeport.yaml).
## Flux API service

Then you can access the API on the `NodePort`, by retrieving the port
number (this example assumes you are using minikube):
Now you can easily query the Flux API:

```sh
fluxport=$(kubectl get svc flux --template '{{ index .spec.ports 0 "nodePort" }}')
export FLUX_URL="http://$(minikube ip):$fluxport/api/flux"
fluxctl list-controllers --all-namespaces
```

Expand Down

0 comments on commit 14c070a

Please sign in to comment.