Skip to content

Better namespace experience with Kubernetes - #991

Merged
thaJeztah merged 6 commits into
docker:masterfrom
silvin-lubecki:kubernetes-namespace
May 9, 2018
Merged

Better namespace experience with Kubernetes#991
thaJeztah merged 6 commits into
docker:masterfrom
silvin-lubecki:kubernetes-namespace

Conversation

@silvin-lubecki

Copy link
Copy Markdown
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-lubeckisilvin-lubecki changed the title Better namespace experience with Kubernetes[WIP] Better namespace experience with KubernetesApr 9, 2018
@thaJeztah

Copy link
Copy Markdown
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
Copy Markdown
ContributorAuthor

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

@silvin-lubeckisilvin-lubecki changed the title [WIP] Better namespace experience with KubernetesBetter namespace experience with KubernetesApr 10, 2018
@silvin-lubecki

Copy link
Copy Markdown
ContributorAuthor

PTAL

@mat007

Copy link
Copy Markdown
Member

Rebased!

@vieux

Copy link
Copy Markdown
Contributor

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

@mat007

Copy link
Copy Markdown
Member

@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?

@vdemeestervdemeester left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM 🐸

@vdemeester

Copy link
Copy Markdown
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.

Comment threadcli/command/stack/kubernetes/cli.go Outdated

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

Copy link
Copy Markdown
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
Copy Markdown
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.

Comment threadcli/command/stack/kubernetes/cli.go Outdated
return nil, err
}
cli.kubeNamespace = configNamespace
if opts.Namespace != "default" {

Copy link
Copy Markdown
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
    funcNewOptions(flags*flag.FlagSet) Options {
    varoptsOptions
    ifnamespace, err:=flags.GetString("namespace"); err==nil {
    opts.Namespace=namespace
    }
    ifkubeConfig, err:=flags.GetString("kubeconfig"); err==nil {
    opts.Config=kubeConfig
    }
    returnopts
    }
    )

Copy link
Copy Markdown
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.

Comment threadkubernetes/config.go Outdated
// 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
Copy Markdown
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
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, changing this.

Comment threadkubernetes/config.go
func NewKubernetesConfig(configFlag string) (*restclient.Config, error) {
func NewKubernetesConfig(configFlag string) clientcmd.ClientConfig {
kubeConfig := configFlag
if kubeConfig == "" {

Copy link
Copy Markdown
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.

funcConfigPath(kubeconfigPathstring) string {
ifkubeconfigPath!="" {
returnkubeconfigPath
}
ifconfig:=os.Getenv("KUBECONFIG"); config!="" {
returnconfig
}
returnfilepath.Join(homedir.Get(), ".kube/config")
}

Is a non-existing path reason for an error?

Copy link
Copy Markdown
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
Copy Markdown
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;

iflen(format) ==0||format==formatter.TableFormatKey {
format=formatter.KubernetesStackTableFormat
}
stackCtx:= formatter.Context{
Output: dockerCli.Out(),
Format: formatter.Format(format),
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Indeed, fixing this!

Comment threadcli/command/stack/kubernetes/list.go Outdated
@@ -17,7 +17,7 @@ func RunList(dockerCli *KubeCli, opts options.List) error {
}
format := opts.Format
if len(format) == 0 {

Copy link
Copy Markdown
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

Comment threadcli/command/stack/swarm/list.go Outdated
@@ -25,7 +25,7 @@ func RunList(dockerCli command.Cli, opts options.List) error {
}
format := opts.Format
if len(format) == 0 {

Copy link
Copy Markdown
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

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
Copy Markdown
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
Copy Markdown
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.


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")

@thaJeztahthaJeztahMay 8, 2018

Copy link
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
Member

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

@mat007

mat007 commented May 9, 2018

Copy link
Copy Markdown
Member

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

mat007 commented May 9, 2018

Copy link
Copy Markdown
Member

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
Copy Markdown
Member

@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.

@thaJeztahthaJeztah left a comment

Copy link
Copy Markdown
Member

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

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
…elongs
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
…rnetes orchestrator
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
…rator, to list all stacks in all namespaces.
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
…alues
Signed-off-by: Mathieu Champlon <mathieu.champlon@docker.com>
@mat007
mat007force-pushed the kubernetes-namespace branch from a82c9f1 to 2af66beCompareMay 9, 2018 15:41
@mat007

Copy link
Copy Markdown
Member

Done, thanks for your help!

@thaJeztah
thaJeztah merged commit 552ee50 into docker:masterMay 9, 2018
@GordonTheTurtleGordonTheTurtle 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 freeto 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

@silvin-lubecki@thaJeztah@mat007@vieux@vdemeester@wsong@GordonTheTurtle