From 920b5bb15d396a5bf050595409fef2507b19ec85 Mon Sep 17 00:00:00 2001 From: Timo Reimann Date: Tue, 7 Mar 2017 13:09:11 +0100 Subject: [PATCH] Support cluster-external Kubernetes client. (#1159) Detect whether in-cluster or cluster-external Kubernetes client should be used based on the KUBERNETES_SERVICE_{HOST,PORT} environment variables. Adds bearer token and CA certificate file path parameters. --- docs/toml.md | 39 +++++++++++++++++++++++++++---- provider/k8s/client.go | 53 ++++++++++++++++++++++++++++-------------- provider/kubernetes.go | 20 ++++++++++++---- server.go | 5 ++-- traefik.sample.toml | 39 +++++++++++++++++++++++++++---- 5 files changed, 122 insertions(+), 34 deletions(-) diff --git a/docs/toml.md b/docs/toml.md index fabbebc50..31ee4b409 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -1042,17 +1042,46 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration: # Kubernetes server endpoint # -# When deployed as a replication controller in Kubernetes, -# Traefik will use env variable KUBERNETES_SERVICE_HOST -# and KUBERNETES_SERVICE_PORT_HTTPS as endpoint +# When deployed as a replication controller in Kubernetes, Traefik will use +# the environment variables KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT +# to construct the endpoint. # Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token # and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # -# Optional +# The endpoint may be given to override the environment variable values. +# +# When the environment variables are not found, Traefik will try to connect to +# the Kubernetes API server with an external-cluster client. In this case, the +# endpoint is required. Specifically, it may be set to the URL used by +# `kubectl proxy` to connect to a Kubernetes cluster from localhost. +# +# Optional for in-cluster configuration, required otherwise +# Default: empty # # endpoint = "http://localhost:8080" -# namespaces = ["default","production"] + +# Bearer token used for the Kubernetes client configuration. # +# Optional +# Default: empty +# +# token = "my token" + +# Path to the certificate authority file used for the Kubernetes client +# configuration. +# +# Optional +# Default: empty +# +# certAuthFilePath = "/my/ca.crt" + +# Array of namespaces to watch. +# +# Optional +# Default: ["default"]. +# +# namespaces = ["default", "production"] + # See: http://kubernetes.io/docs/user-guide/labels/#list-and-watch-filtering # labelselector = "A and not B" # diff --git a/provider/k8s/client.go b/provider/k8s/client.go index d4806d870..36a6a26f1 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -1,6 +1,9 @@ package k8s import ( + "errors" + "fmt" + "io/ioutil" "time" "k8s.io/client-go/1.5/kubernetes" @@ -39,32 +42,48 @@ type clientImpl struct { clientset *kubernetes.Clientset } -// NewInClusterClient returns a new Kubernetes client that expect to run inside the cluster -func NewInClusterClient() (Client, error) { +// NewInClusterClient returns a new Kubernetes client that is expected to run +// inside the cluster. +func NewInClusterClient(endpoint string) (Client, error) { config, err := rest.InClusterConfig() if err != nil { - return nil, err - } - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create in-cluster configuration: %s", err) } - return &clientImpl{ - clientset: clientset, - }, nil + if endpoint != "" { + config.Host = endpoint + } + + return createClientFromConfig(config) } -// NewInClusterClientWithEndpoint is the same as NewInClusterClient but uses the provided endpoint URL -func NewInClusterClientWithEndpoint(endpoint string) (Client, error) { - config, err := rest.InClusterConfig() - if err != nil { - return nil, err +// NewExternalClusterClient returns a new Kubernetes client that may run outside +// of the cluster. +// The endpoint parameter must not be empty. +func NewExternalClusterClient(endpoint, token, caFilePath string) (Client, error) { + if endpoint == "" { + return nil, errors.New("endpoint missing for external cluster client") } - config.Host = endpoint + config := &rest.Config{ + Host: endpoint, + BearerToken: token, + } - clientset, err := kubernetes.NewForConfig(config) + if caFilePath != "" { + caData, err := ioutil.ReadFile(caFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read CA file %s: %s", caFilePath, err) + } + + config.TLSClientConfig = rest.TLSClientConfig{CAData: caData} + } + + return createClientFromConfig(config) +} + +func createClientFromConfig(c *rest.Config) (Client, error) { + clientset, err := kubernetes.NewForConfig(c) if err != nil { return nil, err } diff --git a/provider/kubernetes.go b/provider/kubernetes.go index cb6327d70..da93a55ab 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -1,6 +1,8 @@ package provider import ( + "fmt" + "os" "reflect" "strconv" "strings" @@ -30,7 +32,9 @@ const ( // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Kubernetes server endpoint"` + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)"` + Token string `description:"Kubernetes bearer token (not needed for in-cluster client)"` + CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)"` DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` Namespaces k8s.Namespaces `description:"Kubernetes namespaces"` LabelSelector string `description:"Kubernetes api label selector to use"` @@ -38,12 +42,18 @@ type Kubernetes struct { } func (provider *Kubernetes) newK8sClient() (k8s.Client, error) { + withEndpoint := "" if provider.Endpoint != "" { - log.Infof("Creating in cluster Kubernetes client with endpoint %v", provider.Endpoint) - return k8s.NewInClusterClientWithEndpoint(provider.Endpoint) + withEndpoint = fmt.Sprintf(" with endpoint %v", provider.Endpoint) } - log.Info("Creating in cluster Kubernetes client") - return k8s.NewInClusterClient() + + if os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "" { + log.Infof("Creating in-cluster Kubernetes client%s\n", withEndpoint) + return k8s.NewInClusterClient(provider.Endpoint) + } + + log.Infof("Creating cluster-external Kubernetes client%s\n", withEndpoint) + return k8s.NewExternalClusterClient(provider.Endpoint, provider.Token, provider.CertAuthFilePath) } // Provide allows the provider to provide configurations to traefik diff --git a/server.go b/server.go index 457093eb2..a919c6251 100644 --- a/server.go +++ b/server.go @@ -385,13 +385,14 @@ func (server *Server) configureProviders() { func (server *Server) startProviders() { // start providers for _, provider := range server.providers { + providerType := reflect.TypeOf(provider) jsonConf, _ := json.Marshal(provider) - log.Infof("Starting provider %v %s", reflect.TypeOf(provider), jsonConf) + log.Infof("Starting provider %v %s", providerType, jsonConf) currentProvider := provider safe.Go(func() { err := currentProvider.Provide(server.configurationChan, server.routinesPool, server.globalConfiguration.Constraints) if err != nil { - log.Errorf("Error starting provider %s", err) + log.Errorf("Error starting provider %v: %s", providerType, err) } }) } diff --git a/traefik.sample.toml b/traefik.sample.toml index 15d1d97d3..9d7e2cd4c 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -665,17 +665,46 @@ # Kubernetes server endpoint # -# When deployed as a replication controller in Kubernetes, -# Traefik will use env variable KUBERNETES_SERVICE_HOST -# and KUBERNETES_SERVICE_PORT_HTTPS as endpoint +# When deployed as a replication controller in Kubernetes, Traefik will use +# the environment variables KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT +# to construct the endpoint. # Secure token will be found in /var/run/secrets/kubernetes.io/serviceaccount/token # and SSL CA cert in /var/run/secrets/kubernetes.io/serviceaccount/ca.crt # +# The endpoint may be given to override the environment variable values. +# +# When the environment variables are not found, Traefik will try to connect to +# the Kubernetes API server with an external-cluster client. In this case, the +# endpoint is required. Specifically, it may be set to the URL used by +# `kubectl proxy` to connect to a Kubernetes cluster from localhost. +# +# Optional for in-cluster configuration, required otherwise +# Default: empty +# +# endpoint = "http://127.0.0.1:8001" + +# Bearer token used for the Kubernetes client configuration. +# # Optional +# Default: empty +# +# token = "my token" + +# Path to the certificate authority file used for the Kubernetes client +# configuration. +# +# Optional +# Default: empty +# +# certAuthFilePath = "/my/ca.crt" + +# Array of namespaces to watch. +# +# Optional +# Default: ["default"]. # -# endpoint = "http://localhost:8080" # namespaces = ["default"] -# + # See: http://kubernetes.io/docs/user-guide/labels/#list-and-watch-filtering # labelselector = "A and not B"