2016-02-08 23:57:32 +03:00
package provider
import (
2016-05-18 16:45:48 +03:00
"fmt"
2016-02-08 23:57:32 +03:00
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 17:56:06 +03:00
"io"
2016-02-08 23:57:32 +03:00
"io/ioutil"
"os"
2016-05-18 19:30:42 +03:00
"strconv"
2016-04-25 17:56:06 +03:00
"strings"
2016-02-08 23:57:32 +03:00
"text/template"
"time"
)
const (
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
)
2016-05-03 17:52:14 +03:00
// Namespaces holds kubernetes namespaces
type Namespaces [ ] string
2016-05-18 16:45:48 +03:00
//Set adds strings elem into the the parser
//it splits str on , and ;
func ( ns * Namespaces ) Set ( str string ) error {
fargs := func ( c rune ) bool {
return c == ',' || c == ';'
}
// get function
slice := strings . FieldsFunc ( str , fargs )
* ns = append ( * ns , slice ... )
return nil
}
//Get []string
func ( ns * Namespaces ) Get ( ) interface { } { return Namespaces ( * ns ) }
//String return slice in a string
func ( ns * Namespaces ) String ( ) string { return fmt . Sprintf ( "%v" , * ns ) }
//SetValue sets []string into the parser
func ( ns * Namespaces ) SetValue ( val interface { } ) {
* ns = Namespaces ( val . ( Namespaces ) )
}
2016-02-08 23:57:32 +03:00
// Kubernetes holds configurations of the Kubernetes provider.
type Kubernetes struct {
2016-05-24 18:31:50 +03:00
BaseProvider
2016-05-03 17:52:14 +03:00
Endpoint string ` description:"Kubernetes server endpoint" `
DisablePassHostHeaders bool ` description:"Kubernetes disable PassHost Headers" `
Namespaces Namespaces ` description:"Kubernetes namespaces" `
2016-02-08 23:57:32 +03:00
}
2016-04-20 14:26:51 +03:00
func ( provider * Kubernetes ) createClient ( ) ( k8s . Client , error ) {
2016-02-08 23:57:32 +03: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 17:56:06 +03:00
log . Errorf ( "Kubernetes load token error: %s" , err )
2016-02-08 23:57:32 +03:00
}
caCert , err := ioutil . ReadFile ( serviceAccountCACert )
if err == nil {
log . Debugf ( "Kubernetes CA cert: %s" , serviceAccountCACert )
} else {
2016-04-25 17:56:06 +03:00
log . Errorf ( "Kubernetes load token error: %s" , err )
2016-02-08 23:57:32 +03: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 20:23:08 +03: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 23:57:32 +03:00
if err != nil {
return err
}
2016-04-25 17:56:06 +03:00
backOff := backoff . NewExponentialBackOff ( )
2016-02-08 23:57:32 +03:00
pool . Go ( func ( stop chan bool ) {
operation := func ( ) error {
for {
2016-05-19 21:09:01 +03:00
stopWatch := make ( chan bool , 5 )
defer close ( stopWatch )
2016-04-25 17:56:06 +03:00
eventsChan , errEventsChan , err := k8sClient . WatchAll ( stopWatch )
if err != nil {
log . Errorf ( "Error watching kubernetes events: %v" , err )
2016-05-19 21:09:01 +03:00
timer := time . NewTimer ( 1 * time . Second )
select {
case <- timer . C :
return err
case <- stop :
return nil
}
2016-04-25 17:56:06 +03:00
}
Watch :
for {
select {
case <- stop :
stopWatch <- true
return nil
2016-05-19 21:09:01 +03:00
case err , ok := <- errEventsChan :
stopWatch <- true
if ok && strings . Contains ( err . Error ( ) , io . EOF . Error ( ) ) {
2016-04-25 17:56:06 +03:00
// edge case, kubernetes long-polling disconnection
break Watch
}
2016-04-19 20:23:08 +03:00
return err
2016-04-25 17:56:06 +03:00
case event := <- eventsChan :
2016-05-19 21:09:01 +03:00
log . Debugf ( "Received event from kubernetes %+v" , event )
2016-04-25 17:56:06 +03:00
templateObjects , err := provider . loadIngresses ( k8sClient )
if err != nil {
return err
}
configurationChan <- types . ConfigMessage {
ProviderName : "kubernetes" ,
Configuration : provider . loadConfig ( * templateObjects ) ,
}
2016-02-08 23:57:32 +03:00
}
}
}
}
notify := func ( err error , time time . Duration ) {
log . Errorf ( "Kubernetes connection error %+v, retrying in %s" , err , time )
}
2016-04-25 17:56:06 +03:00
err := backoff . RetryNotify ( operation , backOff , notify )
2016-02-08 23:57:32 +03:00
if err != nil {
log . Fatalf ( "Cannot connect to Kubernetes server %+v" , err )
}
} )
2016-04-25 17:56:06 +03:00
templateObjects , err := provider . loadIngresses ( k8sClient )
if err != nil {
return err
}
configurationChan <- types . ConfigMessage {
ProviderName : "kubernetes" ,
Configuration : provider . loadConfig ( * templateObjects ) ,
}
2016-02-08 23:57:32 +03:00
return nil
}
2016-04-20 14:26:51 +03:00
func ( provider * Kubernetes ) loadIngresses ( k8sClient k8s . Client ) ( * types . Configuration , error ) {
2016-04-19 20:23:08 +03:00
ingresses , err := k8sClient . GetIngresses ( func ( ingress k8s . Ingress ) bool {
2016-04-28 03:23:55 +03: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 20:23:08 +03: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 14:43:24 +03:00
PassHostHeader := provider . getPassHostHeader ( )
2016-04-19 20:23:08 +03: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 14:43:24 +03:00
Backend : r . Host + pa . Path ,
PassHostHeader : PassHostHeader ,
Routes : make ( map [ string ] types . Route ) ,
2016-04-19 20:23:08 +03:00
}
}
2016-05-25 15:16:19 +03:00
if len ( r . Host ) > 0 {
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 ,
}
2016-04-19 20:23:08 +03:00
}
}
if len ( pa . Path ) > 0 {
2016-05-17 16:22:37 +03:00
ruleType := i . Annotations [ "traefik.frontend.rule.type" ]
2016-05-17 13:50:06 +03: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 16:54:40 +03:00
log . Warnf ( "Unknown RuleType `%s`, falling back to `PathPrefix" , ruleType )
2016-05-17 13:50:06 +03:00
ruleType = "PathPrefix"
}
templateObjects . Frontends [ r . Host + pa . Path ] . Routes [ pa . Path ] = types . Route {
Rule : ruleType + ":" + pa . Path ,
2016-04-19 20:23:08 +03:00
}
}
2016-05-26 02:53:51 +03:00
service , err := k8sClient . GetService ( pa . Backend . ServiceName , i . ObjectMeta . Namespace )
2016-04-19 20:23:08 +03:00
if err != nil {
2016-05-19 21:09:01 +03:00
log . Warnf ( "Error retrieving services: %v" , err )
2016-04-25 17:56:06 +03:00
delete ( templateObjects . Frontends , r . Host + pa . Path )
2016-05-19 21:09:01 +03:00
log . Warnf ( "Error retrieving services %s" , pa . Backend . ServiceName )
2016-05-26 02:53:51 +03:00
continue
2016-04-25 17:56:06 +03:00
}
2016-05-26 02:53:51 +03:00
protocol := "http"
for _ , port := range service . Spec . Ports {
if equalPorts ( port , pa . Backend . ServicePort ) {
if port . Port == 443 {
protocol = "https"
}
endpoints , err := k8sClient . GetEndpoints ( service . ObjectMeta . Name , service . ObjectMeta . Namespace )
if err != nil {
log . Errorf ( "Error retrieving endpoints: %v" , err )
continue
}
if len ( endpoints . Subsets ) == 0 {
log . Warnf ( "Endpoints not found for %s/%s, falling back to Service ClusterIP" , service . ObjectMeta . Namespace , service . ObjectMeta . Name )
templateObjects . Backends [ r . Host + pa . Path ] . Servers [ string ( service . UID ) ] = types . Server {
URL : protocol + "://" + service . Spec . ClusterIP + ":" + strconv . Itoa ( port . Port ) ,
Weight : 1 ,
2016-05-20 19:34:57 +03:00
}
2016-05-26 02:53:51 +03:00
} else {
for _ , subset := range endpoints . Subsets {
for _ , address := range subset . Addresses {
url := protocol + "://" + address . IP + ":" + strconv . Itoa ( endpointPortNumber ( port , subset . Ports ) )
templateObjects . Backends [ r . Host + pa . Path ] . Servers [ url ] = types . Server {
URL : url ,
Weight : 1 ,
2016-05-20 19:34:57 +03:00
}
}
2016-04-20 14:26:51 +03:00
}
2016-04-19 20:23:08 +03:00
}
2016-05-26 02:53:51 +03:00
break
2016-04-19 20:23:08 +03:00
}
}
}
}
}
return & templateObjects , nil
}
2016-05-20 19:34:57 +03:00
func endpointPortNumber ( servicePort k8s . ServicePort , endpointPorts [ ] k8s . EndpointPort ) int {
if len ( endpointPorts ) > 0 {
//name is optional if there is only one port
port := endpointPorts [ 0 ]
for _ , endpointPort := range endpointPorts {
if servicePort . Name == endpointPort . Name {
port = endpointPort
}
}
return int ( port . Port )
}
return servicePort . Port
}
2016-05-18 19:30:42 +03: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 14:43:24 +03:00
func ( provider * Kubernetes ) getPassHostHeader ( ) bool {
2016-05-03 17:52:14 +03:00
if provider . DisablePassHostHeaders {
2016-05-10 14:43:24 +03:00
return false
}
return true
}
2016-02-08 23:57:32 +03: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
}