2016-02-08 20:57:32 +00:00
|
|
|
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"
|
2016-04-25 14:56:06 +00:00
|
|
|
"io"
|
2016-02-08 20:57:32 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2016-05-18 16:30:42 +00:00
|
|
|
"strconv"
|
2016-04-25 14:56:06 +00:00
|
|
|
"strings"
|
2016-02-08 20:57:32 +00:00
|
|
|
"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 {
|
2016-05-10 11:43:24 +00:00
|
|
|
BaseProvider `mapstructure:",squash"`
|
|
|
|
Endpoint string
|
|
|
|
disablePassHostHeaders bool
|
|
|
|
Namespaces []string
|
2016-02-08 20:57:32 +00:00
|
|
|
}
|
|
|
|
|
2016-04-20 11:26:51 +00:00
|
|
|
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
2016-02-08 20:57:32 +00:00
|
|
|
var token string
|
|
|
|
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
|
|
|
|
if err == nil {
|
|
|
|
token = string(tokenBytes)
|
|
|
|
log.Debugf("Kubernetes token: %s", token)
|
|
|
|
} else {
|
2016-04-25 14:56:06 +00:00
|
|
|
log.Errorf("Kubernetes load token error: %s", err)
|
2016-02-08 20:57:32 +00:00
|
|
|
}
|
|
|
|
caCert, err := ioutil.ReadFile(serviceAccountCACert)
|
|
|
|
if err == nil {
|
|
|
|
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
|
|
|
|
} else {
|
2016-04-25 14:56:06 +00:00
|
|
|
log.Errorf("Kubernetes load token error: %s", err)
|
2016-02-08 20:57:32 +00:00
|
|
|
}
|
|
|
|
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)
|
2016-04-19 17:23:08 +00:00
|
|
|
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()
|
2016-02-08 20:57:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-04-25 14:56:06 +00:00
|
|
|
backOff := backoff.NewExponentialBackOff()
|
2016-02-08 20:57:32 +00:00
|
|
|
|
|
|
|
pool.Go(func(stop chan bool) {
|
|
|
|
stopWatch := make(chan bool)
|
2016-04-25 14:56:06 +00:00
|
|
|
defer close(stopWatch)
|
2016-02-08 20:57:32 +00:00
|
|
|
operation := func() error {
|
|
|
|
select {
|
|
|
|
case <-stop:
|
|
|
|
return nil
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
for {
|
2016-04-25 14:56:06 +00:00
|
|
|
eventsChan, errEventsChan, err := k8sClient.WatchAll(stopWatch)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error watching kubernetes events: %v", err)
|
2016-02-08 20:57:32 +00:00
|
|
|
return err
|
2016-04-25 14:56:06 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
2016-04-19 17:23:08 +00:00
|
|
|
return err
|
2016-04-25 14:56:06 +00:00
|
|
|
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),
|
|
|
|
}
|
2016-02-08 20:57:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
notify := func(err error, time time.Duration) {
|
|
|
|
log.Errorf("Kubernetes connection error %+v, retrying in %s", err, time)
|
|
|
|
}
|
2016-04-25 14:56:06 +00:00
|
|
|
err := backoff.RetryNotify(operation, backOff, notify)
|
2016-02-08 20:57:32 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Cannot connect to Kubernetes server %+v", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2016-04-25 14:56:06 +00:00
|
|
|
templateObjects, err := provider.loadIngresses(k8sClient)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
configurationChan <- types.ConfigMessage{
|
|
|
|
ProviderName: "kubernetes",
|
|
|
|
Configuration: provider.loadConfig(*templateObjects),
|
|
|
|
}
|
|
|
|
|
2016-02-08 20:57:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-20 11:26:51 +00:00
|
|
|
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
|
2016-04-19 17:23:08 +00:00
|
|
|
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
2016-04-28 00:23:55 +00:00
|
|
|
if len(provider.Namespaces) == 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
for _, n := range provider.Namespaces {
|
|
|
|
if ingress.ObjectMeta.Namespace == n {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2016-04-19 17:23:08 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error retrieving ingresses: %+v", err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
templateObjects := types.Configuration{
|
|
|
|
map[string]*types.Backend{},
|
|
|
|
map[string]*types.Frontend{},
|
|
|
|
}
|
2016-05-10 11:43:24 +00:00
|
|
|
PassHostHeader := provider.getPassHostHeader()
|
2016-04-19 17:23:08 +00:00
|
|
|
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{
|
2016-05-10 11:43:24 +00:00
|
|
|
Backend: r.Host + pa.Path,
|
|
|
|
PassHostHeader: PassHostHeader,
|
|
|
|
Routes: make(map[string]types.Route),
|
2016-04-19 17:23:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
2016-05-17 13:22:37 +00:00
|
|
|
ruleType := i.Annotations["traefik.frontend.rule.type"]
|
2016-05-17 10:50:06 +00:00
|
|
|
|
|
|
|
switch strings.ToLower(ruleType) {
|
|
|
|
case "pathprefixstrip":
|
|
|
|
ruleType = "PathPrefixStrip"
|
|
|
|
case "pathstrip":
|
|
|
|
ruleType = "PathStrip"
|
|
|
|
case "path":
|
|
|
|
ruleType = "Path"
|
|
|
|
case "pathprefix":
|
|
|
|
ruleType = "PathPrefix"
|
|
|
|
default:
|
2016-05-17 13:54:40 +00:00
|
|
|
log.Warnf("Unknown RuleType `%s`, falling back to `PathPrefix", ruleType)
|
2016-05-17 10:50:06 +00:00
|
|
|
ruleType = "PathPrefix"
|
|
|
|
}
|
|
|
|
|
|
|
|
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
|
|
|
|
Rule: ruleType + ":" + pa.Path,
|
2016-04-19 17:23:08 +00:00
|
|
|
}
|
|
|
|
}
|
2016-04-25 14:56:06 +00:00
|
|
|
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
|
2016-05-18 15:46:19 +00:00
|
|
|
return service.ObjectMeta.Namespace == i.ObjectMeta.Namespace && service.Name == pa.Backend.ServiceName
|
2016-04-19 17:23:08 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error retrieving services: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
2016-04-25 14:56:06 +00:00
|
|
|
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)
|
|
|
|
}
|
2016-04-19 17:23:08 +00:00
|
|
|
for _, service := range services {
|
2016-04-26 20:26:25 +00:00
|
|
|
protocol := "http"
|
2016-04-19 17:23:08 +00:00
|
|
|
for _, port := range service.Spec.Ports {
|
2016-05-18 16:30:42 +00:00
|
|
|
if equalPorts(port, pa.Backend.ServicePort) {
|
2016-04-26 20:26:25 +00:00
|
|
|
if port.Port == 443 {
|
|
|
|
protocol = "https"
|
|
|
|
}
|
2016-04-20 11:26:51 +00:00
|
|
|
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
2016-05-18 16:30:42 +00:00
|
|
|
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port),
|
2016-04-20 11:26:51 +00:00
|
|
|
Weight: 1,
|
|
|
|
}
|
2016-04-19 17:23:08 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return &templateObjects, nil
|
|
|
|
}
|
|
|
|
|
2016-05-18 16:30:42 +00:00
|
|
|
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool {
|
|
|
|
if servicePort.Port == ingressPort.IntValue() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if servicePort.Name != "" && servicePort.Name == ingressPort.String() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-05-10 11:43:24 +00:00
|
|
|
func (provider *Kubernetes) getPassHostHeader() bool {
|
|
|
|
if provider.disablePassHostHeaders {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-02-08 20:57:32 +00:00
|
|
|
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
|
|
|
|
}
|