traefik/provider/kubernetes.go

220 lines
6.2 KiB
Go
Raw Normal View History

package provider
import (
log "github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"io"
"io/ioutil"
"os"
"strings"
"text/template"
"time"
)
const (
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
// Kubernetes holds configurations of the Kubernetes provider.
type Kubernetes struct {
BaseProvider `mapstructure:",squash"`
Endpoint string
disablePassHostHeaders bool
Namespaces []string
}
func (provider *Kubernetes) createClient() (k8s.Client, error) {
var token string
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
if err == nil {
token = string(tokenBytes)
log.Debugf("Kubernetes token: %s", token)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
caCert, err := ioutil.ReadFile(serviceAccountCACert)
if err == nil {
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST")
kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
if len(kubernetesPort) > 0 && len(kubernetesHost) > 0 {
provider.Endpoint = "https://" + kubernetesHost + ":" + kubernetesPort
}
log.Debugf("Kubernetes endpoint: %s", provider.Endpoint)
return k8s.NewClient(provider.Endpoint, caCert, token)
}
// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool) error {
k8sClient, err := provider.createClient()
if err != nil {
return err
}
backOff := backoff.NewExponentialBackOff()
pool.Go(func(stop chan bool) {
stopWatch := make(chan bool)
defer close(stopWatch)
operation := func() error {
select {
case <-stop:
return nil
default:
}
for {
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
if err != nil {
log.Errorf("Error watching kubernetes events: %v", err)
return err
}
Watch:
for {
select {
case <-stop:
stopWatch <- true
return nil
case err := <-errEventsChan:
if strings.Contains(err.Error(), io.EOF.Error()) {
// edge case, kubernetes long-polling disconnection
break Watch
}
return err
case event := <-eventsChan:
log.Debugf("Received event from kubenetes %+v", event)
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
}
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
}
}
}
}
notify := func(err error, time time.Duration) {
log.Errorf("Kubernetes connection error %+v, retrying in %s", err, time)
}
err := backoff.RetryNotify(operation, backOff, notify)
if err != nil {
log.Fatalf("Cannot connect to Kubernetes server %+v", err)
}
})
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
}
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
return nil
}
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
if len(provider.Namespaces) == 0 {
return true
}
for _, n := range provider.Namespaces {
if ingress.ObjectMeta.Namespace == n {
return true
}
}
return false
})
if err != nil {
log.Errorf("Error retrieving ingresses: %+v", err)
return nil, err
}
templateObjects := types.Configuration{
map[string]*types.Backend{},
map[string]*types.Frontend{},
}
PassHostHeader := provider.getPassHostHeader()
for _, i := range ingresses {
for _, r := range i.Spec.Rules {
for _, pa := range r.HTTP.Paths {
if _, exists := templateObjects.Backends[r.Host+pa.Path]; !exists {
templateObjects.Backends[r.Host+pa.Path] = &types.Backend{
Servers: make(map[string]types.Server),
}
}
if _, exists := templateObjects.Frontends[r.Host+pa.Path]; !exists {
templateObjects.Frontends[r.Host+pa.Path] = &types.Frontend{
Backend: r.Host + pa.Path,
PassHostHeader: PassHostHeader,
Routes: make(map[string]types.Route),
}
}
if _, exists := templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host]; !exists {
templateObjects.Frontends[r.Host+pa.Path].Routes[r.Host] = types.Route{
Rule: "Host:" + r.Host,
}
}
if len(pa.Path) > 0 {
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
Rule: "PathPrefix:" + pa.Path,
}
}
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
return service.Name == pa.Backend.ServiceName
})
if err != nil {
log.Errorf("Error retrieving services: %v", err)
continue
}
if len(services) == 0 {
// no backends found, delete frontend...
delete(templateObjects.Frontends, r.Host+pa.Path)
log.Errorf("Error retrieving services %s", pa.Backend.ServiceName)
}
for _, service := range services {
protocol := "http"
for _, port := range service.Spec.Ports {
if port.Port == pa.Backend.ServicePort.IntValue() {
if port.Port == 443 {
protocol = "https"
}
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
Weight: 1,
}
break
}
}
}
}
}
}
return &templateObjects, nil
}
func (provider *Kubernetes) getPassHostHeader() bool {
if provider.disablePassHostHeaders {
return false
}
return true
}
func (provider *Kubernetes) loadConfig(templateObjects types.Configuration) *types.Configuration {
var FuncMap = template.FuncMap{}
configuration, err := provider.getConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects)
if err != nil {
log.Error(err)
}
return configuration
}