From 84928e275159ae614be1d0fc578c95d72340ef5a Mon Sep 17 00:00:00 2001 From: Adam Reese Date: Thu, 15 Dec 2016 11:21:40 -0800 Subject: [PATCH] feat(*): allow tiller to be installed in an alternate namespace Adds `--tiller-namespace` flag and `TILLER_NAMESPACE` envvar to use tiller in an alternate namespace. closes #1418 --- cmd/helm/helm.go | 46 +++++++++++++++++++-------- cmd/helm/init.go | 5 ++- cmd/helm/installer/install.go | 7 +++- cmd/helm/installer/install_test.go | 4 +++ cmd/tiller/tiller.go | 24 ++++++++++++-- docs/install.md | 4 +++ pkg/tiller/environment/environment.go | 4 +-- 7 files changed, 72 insertions(+), 22 deletions(-) diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index da49834c5..912d5f721 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "log" "os" + "path/filepath" "strings" "github.com/spf13/cobra" @@ -40,12 +41,14 @@ const ( localRepoIndexFilePath = "index.yaml" homeEnvVar = "HELM_HOME" hostEnvVar = "HELM_HOST" + tillerNamespaceEnvVar = "TILLER_NAMESPACE" ) var ( - helmHome string - tillerHost string - kubeContext string + helmHome string + tillerHost string + tillerNamespace string + kubeContext string ) // flagDebug is a signal that the user wants additional output. @@ -68,9 +71,10 @@ Common actions from this point include: - helm list: list releases of charts Environment: - $HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm - $HELM_HOST set an alternative Tiller host. The format is host:port - $KUBECONFIG set an alternate Kubernetes configuration file (default "~/.kube/config") + $HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm + $HELM_HOST set an alternative Tiller host. The format is host:port + $TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-namespace") + $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") ` func newRootCmd(out io.Writer) *cobra.Command { @@ -83,16 +87,12 @@ func newRootCmd(out io.Writer) *cobra.Command { teardown() }, } - home := os.Getenv(homeEnvVar) - if home == "" { - home = "$HOME/.helm" - } - thost := os.Getenv(hostEnvVar) p := cmd.PersistentFlags() - p.StringVar(&helmHome, "home", home, "location of your Helm config. Overrides $HELM_HOME") - p.StringVar(&tillerHost, "host", thost, "address of tiller. Overrides $HELM_HOST") + p.StringVar(&helmHome, "home", defaultHelmHome(), "location of your Helm config. Overrides $HELM_HOME") + p.StringVar(&tillerHost, "host", defaultHelmHost(), "address of tiller. Overrides $HELM_HOST") p.StringVar(&kubeContext, "kube-context", "", "name of the kubeconfig context to use") p.BoolVar(&flagDebug, "debug", false, "enable verbose output") + p.StringVar(&tillerNamespace, "tiller-namespace", defaultTillerNamespace(), "namespace of tiller") // Tell gRPC not to log to console. grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags)) @@ -146,7 +146,7 @@ func main() { func setupConnection(c *cobra.Command, args []string) error { if tillerHost == "" { - tunnel, err := newTillerPortForwarder(environment.TillerNamespace, kubeContext) + tunnel, err := newTillerPortForwarder(tillerNamespace, kubeContext) if err != nil { return err } @@ -194,10 +194,28 @@ func prettyError(err error) error { return errors.New(grpc.ErrorDesc(err)) } +func defaultHelmHome() string { + if home := os.Getenv(homeEnvVar); home != "" { + return home + } + return filepath.Join(os.Getenv("HOME"), ".helm") +} + func homePath() string { return os.ExpandEnv(helmHome) } +func defaultHelmHost() string { + return os.Getenv(hostEnvVar) +} + +func defaultTillerNamespace() string { + if ns := os.Getenv(tillerNamespaceEnvVar); ns != "" { + return ns + } + return environment.DefaultTillerNamespace +} + // getKubeClient is a convenience method for creating kubernetes config and client // for a given kubeconfig context func getKubeClient(context string) (*restclient.Config, *internalclientset.Clientset, error) { diff --git a/cmd/helm/init.go b/cmd/helm/init.go index c7a9b24c8..11f140067 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -29,7 +29,6 @@ import ( "k8s.io/helm/cmd/helm/helmpath" "k8s.io/helm/cmd/helm/installer" "k8s.io/helm/pkg/repo" - "k8s.io/helm/pkg/tiller/environment" ) const initDesc = ` @@ -75,8 +74,7 @@ type initCmd struct { func newInitCmd(out io.Writer) *cobra.Command { i := &initCmd{ - out: out, - namespace: environment.TillerNamespace, + out: out, } cmd := &cobra.Command{ @@ -87,6 +85,7 @@ func newInitCmd(out io.Writer) *cobra.Command { if len(args) != 0 { return errors.New("This command does not accept arguments") } + i.namespace = tillerNamespace i.home = helmpath.Home(homePath()) return i.run() }, diff --git a/cmd/helm/installer/install.go b/cmd/helm/installer/install.go index 1cccb8605..09f3f6ea5 100644 --- a/cmd/helm/installer/install.go +++ b/cmd/helm/installer/install.go @@ -88,7 +88,12 @@ func generateDeployment(namespace, image string) *extensions.Deployment { Name: "tiller", Image: image, ImagePullPolicy: "IfNotPresent", - Ports: []api.ContainerPort{{ContainerPort: 44134, Name: "tiller"}}, + Ports: []api.ContainerPort{ + {ContainerPort: 44134, Name: "tiller"}, + }, + Env: []api.EnvVar{ + {Name: "TILLER_NAMESPACE", Value: namespace}, + }, LivenessProbe: &api.Probe{ Handler: api.Handler{ HTTPGet: &api.HTTPGetAction{ diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index 7a43660ac..dc9114074 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -57,6 +57,10 @@ func TestDeploymentManifest(t *testing.T) { if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect { t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got) } + + if got := dep.Spec.Template.Spec.Containers[0].Env[0].Value; got != api.NamespaceDefault { + t.Errorf("%s: expected namespace %q, got %q", tt.name, api.NamespaceDefault, got) + } } } diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index 4009c2a24..79c31bc82 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -18,10 +18,12 @@ package main // import "k8s.io/helm/cmd/tiller" import ( "fmt" + "io/ioutil" "log" "net" "net/http" "os" + "strings" "github.com/spf13/cobra" @@ -70,9 +72,11 @@ var rootCommand = &cobra.Command{ Run: start, } -func main() { +func init() { log.SetFlags(log.Flags() | log.Lshortfile) +} +func main() { p := rootCommand.PersistentFlags() p.StringVarP(&grpcAddr, "listen", "l", ":44134", "address:port to listen on") p.StringVar(&store, "storage", storageConfigMap, "storage driver to use. One of 'configmap' or 'memory'") @@ -90,7 +94,7 @@ func start(c *cobra.Command, args []string) { case storageMemory: env.Releases = storage.Init(driver.NewMemory()) case storageConfigMap: - env.Releases = storage.Init(driver.NewConfigMaps(clientset.Core().ConfigMaps(environment.TillerNamespace))) + env.Releases = storage.Init(driver.NewConfigMaps(clientset.Core().ConfigMaps(namespace()))) } lstn, err := net.Listen("tcp", grpcAddr) @@ -132,3 +136,19 @@ func start(c *cobra.Command, args []string) { fmt.Fprintf(os.Stderr, "Probes server died: %s\n", err) } } + +// namespace returns the namespace of tiller +func namespace() string { + if ns := os.Getenv("POD_NAMESPACE"); ns != "" { + return ns + } + + // Fall back to the namespace associated with the service account token, if available + if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { + if ns := strings.TrimSpace(string(data)); len(ns) > 0 { + return ns + } + } + + return environment.DefaultTillerNamespace +} diff --git a/docs/install.md b/docs/install.md index df7295e24..677ffeb79 100644 --- a/docs/install.md +++ b/docs/install.md @@ -110,12 +110,16 @@ You can explicitly tell `helm init` to... - Install the canary build with the `--canary-image` flag - Install a particular image (version) with `--tiller-image` - Install to a particular cluster with `--kube-context` +- Install into a particular namespace with `--tiller-namespace` Once Tiller is installed, running `helm version` should show you both the client and server version. (If it shows only the client version, `helm` cannot yet connect to the server. Use `kubectl` to see if any `tiller` pods are running.) +If Helm will look for Tiller in the `kube-system` namespace unless +`--tiller-namespace` or `TILLER_NAMESPACE` is set. + ### Installing Tiller Canary Builds Canary images are built from the `master` branch. They may not be diff --git a/pkg/tiller/environment/environment.go b/pkg/tiller/environment/environment.go index 1b2aa4e6d..1bd5bb96f 100644 --- a/pkg/tiller/environment/environment.go +++ b/pkg/tiller/environment/environment.go @@ -33,8 +33,8 @@ import ( "k8s.io/helm/pkg/storage/driver" ) -// TillerNamespace is the namespace tiller is running in. -const TillerNamespace = "kube-system" +// DefaultTillerNamespace is the default namespace for tiller. +const DefaultTillerNamespace = "kube-system" // GoTplEngine is the name of the Go template engine, as registered in the EngineYard. const GoTplEngine = "gotpl"