Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better namespace experience with Kubernetes #991

Merged
merged 6 commits into from
May 9, 2018

Conversation

silvin-lubecki
Copy link
Contributor

- What I did
I redesigned a better UX for namespaces with Kubernetes:

  • the default namespace defined in the ~/.kube/config is now used with all stack commands
  • a NAMESPACE column is added for docker stack ls command on Kubernetes orchestrator only
  • a --all-namespaces flag is added for docker stack ls command on Kubernetes orchestrator only

- How I did it

  1. For the default namespace, I modified the kubernetes/config.go:NewKubernetesConfig function to return a lower level config object, which can resolve an optional namespace defined in the kubernetes configuration file.
  2. For the NAMESPACE column, I modified the formatter/stack.go:NewStackFormat function, which is now almost no-op. We now have two default format, a Kubernetes one with a NAMESPACE column, and a Swarm one without it, so I let the caller choosing the appropriate default table format. Buy the way now these two default format are exported. A closer look will be appreciated 😄
  3. For the --all-namespaces flag, I patched the Stack client to handle the all-namespaces option on List command. All stack clients are namespaced, but with Kubernetes, setting the namespace to empty string means all namespaces.

- How to verify it

# Use Kubernetes by default for every following commands
$ export DOCKER_ORCHESTRATOR=kubernetes

# Deploy a stack on default namespace
$ docker stack deploy mystack -c docker-compose.yml
...
 
# List the stacks in the default namespace, it will show a new NAMESPACE column
$ docker stack ls
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
mystack             3                   Kubernetes          default

# Swarm hasn't any concept of namespace, so no NAMESPACE column
$ DOCKER_ORCHESTRATOR=swarm docker stack ls
NAME                SERVICES            ORCHESTRATOR

# Create a namespace on Kubernetes:
$ kubectl create namespace mynamespace
namespace "mynamespace" created

# Deploy a stack on that namespace
$ docker stack deploy mystack --namespace mynamespace -c docker-compose.yml
...

# List all stacks in all namespaces using --all-namespace flag
$ docker stack ls --all-namespaces
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
mystack             3                   Kubernetes          default
mystack             1                   Kubernetes          mynamespace

# Edit your ~/.kube/config file to add a different default namespace to your current context
$ cat ~/.kube/config
...
contexts:
- context:
    cluster: docker-for-desktop-cluster
    namespace: mynamespace
    user: docker-for-desktop
  name: docker-for-desktop
...

# List again the stacks in the default namespace
$ docker stack ls
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
mystack             3                   Kubernetes          mynamespace

- Description for the changelog

  • Fix using namespace defined in ~/.kube/config for stack commands
  • Add a NAMESPACE column for docker stack ls command
  • Add a --all-namespaces flag for docker stack ls command

- A picture of a cute animal (not mandatory but encouraged)

image

⚠️ Depends on #973 ⚠️

@silvin-lubecki silvin-lubecki changed the title Better namespace experience with Kubernetes [WIP] Better namespace experience with Kubernetes Apr 9, 2018
@thaJeztah
Copy link
Member

@silvin-lubecki looks like there's a linting issue

cli/command/stack/kubernetes/client.go:80:47:warning: exported method Stacks returns unexported type kubernetes.stackClient, which can be annoying to use (golint)
Exited with code 1

@silvin-lubecki
Copy link
Contributor Author

Yes, I'm on it, thanks @thaJeztah!

@silvin-lubecki silvin-lubecki changed the title [WIP] Better namespace experience with Kubernetes Better namespace experience with Kubernetes Apr 10, 2018
@silvin-lubecki
Copy link
Contributor Author

PTAL

@mat007
Copy link
Member

mat007 commented Apr 30, 2018

Rebased!

@vieux
Copy link
Contributor

vieux commented Apr 30, 2018

@silvin-lubecki what's the easiest way for me to test this ?

@mat007
Copy link
Member

mat007 commented May 2, 2018

@vieux silvin is on holiday, I'm carrying his PRs meanwhile.
The most simple means to install Kubernetes would be either through Docker for Desktop (Mac and Windows) or probably UCP (linux), after which following the «How to verify it» steps should work.
In Docker for Desktop there is only a checkbox to toggle in order to enable and install Kubernetes, I'm afraid I don't know how to proceed for UCP and linux, maybe @vdemeester has some experience with this?

Copy link
Collaborator

@vdemeester vdemeester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🐸

@vdemeester
Copy link
Collaborator

On non docker-for-desktop, you need to have orchestrator: "kubernetes" and experimental: "enabled" in the client configuration ($HOME/.docker/config.json) — and a kube configuration pointing to a live kubernetes cluster.


configNamespace, _, err := clientConfig.Namespace()
if err != nil {
return nil, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this code is executed (possibly showing an error) even if the namespace is overridden through opts.Namespace. Retrieving the namespace from the config should probably be done conditional (i.e., if there's no --namespace option set)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll invert the condition below, but the code in here is likely going to change when I address your next comment about --namespace.

return nil, err
}
cli.kubeNamespace = configNamespace
if opts.Namespace != "default" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • What if opts.Namespace is an empty string? (also; I'm not a big fan of magic words, such as "default" for default values)
  • What if I run docker stack ls --namespace=default; will the specified ("default") namespace be ignored? (perhaps kubernetes.NewOptions() should use flags.Changed() to detect if the flag was set (even if it's with the default value
    // NewOptions returns an Options initialized with command line flags
    func NewOptions(flags *flag.FlagSet) Options {
    var opts Options
    if namespace, err := flags.GetString("namespace"); err == nil {
    opts.Namespace = namespace
    }
    if kubeConfig, err := flags.GetString("kubeconfig"); err == nil {
    opts.Config = kubeConfig
    }
    return opts
    }
    )

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running a complete set of (manual) tests with both kubectl get stacks and docker stack ls reveals a few discrepancies. I'll sort that out.

"k8s.io/client-go/tools/clientcmd"
)

// NewKubernetesConfig resolves the path to the desired Kubernetes configuration file, depending
// environment variable and command line flag.
func NewKubernetesConfig(configFlag string) (*restclient.Config, error) {
func NewKubernetesConfig(configFlag string) clientcmd.ClientConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not new, but the configFlag variable is a bit confusing (i.e., it's not the flag itself, but a custom path, which happens to be passed through a flag). Perhaps renamed this to (e.g.) configPath

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, changing this.

"k8s.io/client-go/tools/clientcmd"
)

// NewKubernetesConfig resolves the path to the desired Kubernetes configuration file, depending
// environment variable and command line flag.
func NewKubernetesConfig(configFlag string) (*restclient.Config, error) {
func NewKubernetesConfig(configFlag string) clientcmd.ClientConfig {
kubeConfig := configFlag
if kubeConfig == "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exact same code is also in cli.WrapCli() (and looks to be redundant). I suggest extracting this to a function, e.g.

func ConfigPath(kubeconfigPath string) string {
	if kubeconfigPath != "" {
		return kubeconfigPath
	}
	if config := os.Getenv("KUBECONFIG"); config != "" {
		return config
	}
	return filepath.Join(homedir.Get(), ".kube/config")
}

Is a non-existing path reason for an error?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well actually WrapCli calls NewKubernetesConfig right after the duplicated code so this looks like the code was moved/factorized and not removed…
Fixed!

}

// NewStackFormat returns a format for use with a stack Context
func NewStackFormat(source string) Format {
switch source {
case TableFormatKey:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change in behaviour; before this change:

docker stack ls --format=table
NAME                SERVICES
foobar              1

After this change:

docker stack ls --format=table

# (no output)

Perhaps remove this function, and in swarm/kubernetes do;

if len(format) == 0 || format == formatter.TableFormatKey {
	format = formatter.KubernetesStackTableFormat
}

stackCtx := formatter.Context{
	Output: dockerCli.Out(),
	Format: formatter.Format(format),
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, fixing this!

@@ -17,7 +17,7 @@ func RunList(dockerCli *KubeCli, opts options.List) error {
}
format := opts.Format
if len(format) == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment above

@@ -25,7 +25,7 @@ func RunList(dockerCli command.Cli, opts options.List) error {
}
format := opts.Format
if len(format) == 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comment above

@@ -31,5 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {

flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "", "Pretty-print stacks using a Go template")
flags.BoolVarP(&opts.AllNamespaces, "all-namespaces", "", false, "List stacks among all Kubernetes namespaces")
flags.SetAnnotation("all-namespaces", "kubernetes", nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be gated by API-version as well? (possibly not because it was experimental so far)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it was experimental and also targeting only Kubernetes and UCP. The behavior toward the Docker daemon is supposed to not have been changed.

@@ -31,5 +31,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {

flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "", "Pretty-print stacks using a Go template")
flags.BoolVarP(&opts.AllNamespaces, "all-namespaces", "", false, "List stacks among all Kubernetes namespaces")
Copy link
Member

@thaJeztah thaJeztah May 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we considered

  • using --namespace=all (e.g.) to show all namespaces (which would be consistent with --orchestrator=all), also allowing listing stack in a single namespace --namespace=<something>
  • showing all namespaces by default, but use a --filter namespace=foo to limit the output

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe what drives the current design is to mimic what kubectl does in order to not confuse Kubernetes users.

@thaJeztah
Copy link
Member

Oh, this also needs a documentation update to document the .Namespace placeholder for --format

@mat007
Copy link
Member

mat007 commented May 9, 2018

For the record, here is the manual test suite I ran against kubectl, obviously this is highly dependent on the services deployed beforehand…

+ kubectl.exe config set-context docker-for-desktop --namespace=tata
Context "docker-for-desktop" modified.
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace
NAME      NAMESPACE
dtc       tata
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=default
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=
NAME      NAMESPACE
dtc       tata
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=tata
NAME      NAMESPACE
dtc       tata
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=bla
NAME      NAMESPACE
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --all-namespaces
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
dtc           tata
dtc           tutu
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --all-namespaces --namespace=bla
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
dtc           tata
dtc           tutu
+ kubectl.exe config set-context docker-for-desktop --namespace=
Context "docker-for-desktop" modified.
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=default
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=tata
NAME      NAMESPACE
dtc       tata
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --namespace=bla
NAME      NAMESPACE
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --all-namespaces
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
dtc           tata
dtc           tutu
+ kubectl get stacks --output=custom-columns=NAME:.metadata.name,NAMESPACE:.metadata.namespace --all-namespaces --namespace=bla
NAME          NAMESPACE
blaaaaaa      default
blaaaaaaaaa   default
dtcccccc      default
dtc           tata
dtc           tutu

@mat007
Copy link
Member

mat007 commented May 9, 2018

And here is the matching docker stack ls test suite output (after I fixed the issues with --namespace):

+ kubectl.exe config set-context docker-for-desktop --namespace=tata
Context "docker-for-desktop" modified.
+ ./build/docker.exe stack ls
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
dtc                 5                   Kubernetes          tata
+ ./build/docker.exe stack ls --namespace=default
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtcccccc            5                   Kubernetes          default
+ ./build/docker.exe stack ls --namespace=
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
dtc                 5                   Kubernetes          tata
+ ./build/docker.exe stack ls --namespace=tata
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
dtc                 5                   Kubernetes          tata
+ ./build/docker.exe stack ls --namespace=bla
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
+ ./build/docker.exe stack ls --all-namespaces
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtc                 5                   Kubernetes          tata
dtc                 5                   Kubernetes          tutu
dtcccccc            5                   Kubernetes          default
+ ./build/docker.exe stack ls --all-namespaces --namespace=bla
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtc                 5                   Kubernetes          tata
dtc                 5                   Kubernetes          tutu
dtcccccc            5                   Kubernetes          default
+ kubectl.exe config set-context docker-for-desktop --namespace=
Context "docker-for-desktop" modified.
+ ./build/docker.exe stack ls
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtcccccc            5                   Kubernetes          default
+ ./build/docker.exe stack ls --namespace=default
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtcccccc            5                   Kubernetes          default
+ ./build/docker.exe stack ls --namespace=
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtcccccc            5                   Kubernetes          default
+ ./build/docker.exe stack ls --namespace=tata
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
dtc                 5                   Kubernetes          tata
+ ./build/docker.exe stack ls --namespace=bla
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
+ ./build/docker.exe stack ls --all-namespaces
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtc                 5                   Kubernetes          tata
dtc                 5                   Kubernetes          tutu
dtcccccc            5                   Kubernetes          default
+ ./build/docker.exe stack ls --all-namespaces --namespace=bla
NAME                SERVICES            ORCHESTRATOR        NAMESPACE
blaaaaaa            1                   Kubernetes          default
blaaaaaaaaa         1                   Kubernetes          default
dtc                 5                   Kubernetes          tata
dtc                 5                   Kubernetes          tutu
dtcccccc            5                   Kubernetes          default

@mat007
Copy link
Member

mat007 commented May 9, 2018

@thaJeztah I believe I have addressed all your comments. I added small commits so it remains easy for you to check the changes, but afterwards if you would rather have me collapse them or merge them with the bigger commits from @silvin-lubecki I'll do it.

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but if you can combine some of the commits, that'd be good

@mat007
Copy link
Member

mat007 commented May 9, 2018

Done, thanks for your help!

@thaJeztah thaJeztah merged commit 552ee50 into docker:master May 9, 2018
@GordonTheTurtle GordonTheTurtle added this to the 18.06.0 milestone May 9, 2018
@silvin-lubecki silvin-lubecki deleted the kubernetes-namespace branch May 18, 2018 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants