1
0
Fork 0

Add kubernetes Ingress backend

Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
emile 2016-02-08 21:57:32 +01:00 committed by Emile Vauge
parent 6f47434833
commit 720912e880
No known key found for this signature in database
GPG key ID: D808B4C167352E59
18 changed files with 1191 additions and 201 deletions

View file

@ -743,11 +743,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
},
expectedFrontends: map[string]*types.Frontend{
`"frontend-Host-test-docker-localhost"`: {
"frontend-Host-test-docker-localhost": {
Backend: "backend-test",
EntryPoints: []string{},
Routes: map[string]types.Route{
`"route-frontend-Host-test-docker-localhost"`: {
"route-frontend-Host-test-docker-localhost": {
Rule: "Host:test.docker.localhost",
},
},
@ -815,20 +815,20 @@ func TestDockerLoadDockerConfig(t *testing.T) {
},
},
expectedFrontends: map[string]*types.Frontend{
`"frontend-Host-test1-docker-localhost"`: {
"frontend-Host-test1-docker-localhost": {
Backend: "backend-foobar",
EntryPoints: []string{"http", "https"},
Routes: map[string]types.Route{
`"route-frontend-Host-test1-docker-localhost"`: {
"route-frontend-Host-test1-docker-localhost": {
Rule: "Host:test1.docker.localhost",
},
},
},
`"frontend-Host-test2-docker-localhost"`: {
"frontend-Host-test2-docker-localhost": {
Backend: "backend-foobar",
EntryPoints: []string{},
Routes: map[string]types.Route{
`"route-frontend-Host-test2-docker-localhost"`: {
"route-frontend-Host-test2-docker-localhost": {
Rule: "Host:test2.docker.localhost",
},
},

163
provider/k8s/client.go Normal file
View file

@ -0,0 +1,163 @@
package k8s
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"github.com/containous/traefik/safe"
"github.com/parnurzeal/gorequest"
"net/http"
"net/url"
"strings"
)
const (
// APIEndpoint defines the base path for kubernetes API resources.
APIEndpoint = "/api/v1"
defaultService = "/namespaces/default/services"
extentionsEndpoint = "/apis/extensions/v1beta1"
defaultIngress = "/ingresses"
)
// Client is a client for the Kubernetes master.
type Client struct {
endpointURL string
tls *tls.Config
token string
caCert []byte
}
// NewClient returns a new Kubernetes client.
// The provided host is an url (scheme://hostname[:port]) of a
// Kubernetes master without any path.
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master.
func NewClient(baseURL string, caCert []byte, token string) (*Client, error) {
validURL, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
}
return &Client{
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
token: token,
caCert: caCert,
}, nil
}
// GetIngresses returns all services in the cluster
func (c *Client) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
request := gorequest.New().Get(getURL)
if len(c.token) > 0 {
request.Header["Authorization"] = "Bearer " + c.token
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(c.caCert)
c.tls = &tls.Config{RootCAs: pool}
}
res, body, errs := request.TLSClientConfig(c.tls).EndBytes()
if errs != nil {
return nil, fmt.Errorf("failed to create request: GET %q : %v", getURL, errs)
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, getURL, string(body))
}
var ingressList IngressList
if err := json.Unmarshal(body, &ingressList); err != nil {
return nil, fmt.Errorf("failed to decode list of ingress resources: %v", err)
}
ingresses := ingressList.Items[:0]
for _, ingress := range ingressList.Items {
if predicate(ingress) {
ingresses = append(ingresses, ingress)
}
}
return ingresses, nil
}
// WatchIngresses returns all services in the cluster
func (c *Client) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{})
errCh := make(chan error)
getURL := c.endpointURL + extentionsEndpoint + defaultIngress + "?watch=true"
// Make request to Kubernetes API
request := gorequest.New().Get(getURL)
if len(c.token) > 0 {
request.Set("Authorization", "Bearer "+c.token)
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(c.caCert)
c.tls = &tls.Config{RootCAs: pool}
}
req, err := request.TLSClientConfig(c.tls).MakeRequest()
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", getURL, err)
}
request.Client.Transport = request.Transport
res, err := request.Client.Do(req)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to make request: GET %q: %v", getURL, err)
}
shouldStop := safe.New(false)
go func() {
select {
case <-stopCh:
shouldStop.Set(true)
res.Body.Close()
return
}
}()
go func() {
defer close(watchCh)
defer close(errCh)
for {
var ingressList interface{}
if err := json.NewDecoder(res.Body).Decode(&ingressList); err != nil {
if !shouldStop.Get().(bool) {
errCh <- fmt.Errorf("failed to decode list of ingress resources: %v", err)
}
return
}
watchCh <- ingressList
}
}()
return watchCh, errCh, nil
}
// GetServices returns all services in the cluster
func (c *Client) GetServices(predicate func(Service) bool) ([]Service, error) {
getURL := c.endpointURL + APIEndpoint + defaultService
// Make request to Kubernetes API
request := gorequest.New().Get(getURL)
if len(c.token) > 0 {
request.Header["Authorization"] = "Bearer " + c.token
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(c.caCert)
c.tls = &tls.Config{RootCAs: pool}
}
res, body, errs := request.TLSClientConfig(c.tls).EndBytes()
if errs != nil {
return nil, fmt.Errorf("failed to create request: GET %q : %v", getURL, errs)
}
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, getURL, string(body))
}
var serviceList ServiceList
if err := json.Unmarshal(body, &serviceList); err != nil {
return nil, fmt.Errorf("failed to decode list of services resources: %v", err)
}
services := serviceList.Items[:0]
for _, service := range serviceList.Items {
if predicate(service) {
services = append(services, service)
}
}
return services, nil
}

151
provider/k8s/ingress.go Normal file
View file

@ -0,0 +1,151 @@
package k8s
// Ingress is a collection of rules that allow inbound connections to reach the
// endpoints defined by a backend. An Ingress can be configured to give services
// externally-reachable urls, load balance traffic, terminate SSL, offer name
// based virtual hosting etc.
type Ingress struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ObjectMeta `json:"metadata,omitempty"`
// Spec is the desired state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Spec IngressSpec `json:"spec,omitempty"`
// Status is the current state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Status IngressStatus `json:"status,omitempty"`
}
// IngressList is a collection of Ingress.
type IngressList struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ListMeta `json:"metadata,omitempty"`
// Items is the list of Ingress.
Items []Ingress `json:"items"`
}
// IngressSpec describes the Ingress the user wishes to exist.
type IngressSpec struct {
// A default backend capable of servicing requests that don't match any
// rule. At least one of 'backend' or 'rules' must be specified. This field
// is optional to allow the loadbalancer controller or defaulting logic to
// specify a global default.
Backend *IngressBackend `json:"backend,omitempty"`
// TLS configuration. Currently the Ingress only supports a single TLS
// port, 443. If multiple members of this list specify different hosts, they
// will be multiplexed on the same port according to the hostname specified
// through the SNI TLS extension, if the ingress controller fulfilling the
// ingress supports SNI.
TLS []IngressTLS `json:"tls,omitempty"`
// A list of host rules used to configure the Ingress. If unspecified, or
// no rule matches, all traffic is sent to the default backend.
Rules []IngressRule `json:"rules,omitempty"`
// TODO: Add the ability to specify load-balancer IP through claims
}
// IngressTLS describes the transport layer security associated with an Ingress.
type IngressTLS struct {
// Hosts are a list of hosts included in the TLS certificate. The values in
// this list must match the name/s used in the tlsSecret. Defaults to the
// wildcard host setting for the loadbalancer controller fulfilling this
// Ingress, if left unspecified.
Hosts []string `json:"hosts,omitempty"`
// SecretName is the name of the secret used to terminate SSL traffic on 443.
// Field is left optional to allow SSL routing based on SNI hostname alone.
// If the SNI host in a listener conflicts with the "Host" header field used
// by an IngressRule, the SNI host is used for termination and value of the
// Host header is used for routing.
SecretName string `json:"secretName,omitempty"`
// TODO: Consider specifying different modes of termination, protocols etc.
}
// IngressStatus describe the current state of the Ingress.
type IngressStatus struct {
// LoadBalancer contains the current status of the load-balancer.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// IngressRule represents the rules mapping the paths under a specified host to
// the related backend services. Incoming requests are first evaluated for a host
// match, then routed to the backend associated with the matching IngressRuleValue.
type IngressRule struct {
// Host is the fully qualified domain name of a network host, as defined
// by RFC 3986. Note the following deviations from the "host" part of the
// URI as defined in the RFC:
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
// IP in the Spec of the parent Ingress.
// 2. The `:` delimiter is not respected because ports are not allowed.
// Currently the port of an Ingress is implicitly :80 for http and
// :443 for https.
// Both these may change in the future.
// Incoming requests are matched against the host before the IngressRuleValue.
// If the host is unspecified, the Ingress routes all traffic based on the
// specified IngressRuleValue.
Host string `json:"host,omitempty"`
// IngressRuleValue represents a rule to route requests for this IngressRule.
// If unspecified, the rule defaults to a http catch-all. Whether that sends
// just traffic matching the host to the default backend or all traffic to the
// default backend, is left to the controller fulfilling the Ingress. Http is
// currently the only supported IngressRuleValue.
IngressRuleValue `json:",inline,omitempty"`
}
// IngressRuleValue represents a rule to apply against incoming requests. If the
// rule is satisfied, the request is routed to the specified backend. Currently
// mixing different types of rules in a single Ingress is disallowed, so exactly
// one of the following must be set.
type IngressRuleValue struct {
//TODO:
// 1. Consider renaming this resource and the associated rules so they
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
// 2. Consider adding fields for ingress-type specific global options
// usable by a loadbalancer, like http keep-alive.
HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
}
// HTTPIngressRuleValue is a list of http selectors pointing to backends.
// In the example: http://<host>/<path>?<searchpart> -> backend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
// A collection of paths that map requests to backends.
Paths []HTTPIngressPath `json:"paths"`
// TODO: Consider adding fields for ingress-type specific global
// options usable by a loadbalancer, like http keep-alive.
}
// HTTPIngressPath associates a path regex with a backend. Incoming urls matching
// the path are forwarded to the backend.
type HTTPIngressPath struct {
// Path is a extended POSIX regex as defined by IEEE Std 1003.1,
// (i.e this follows the egrep/unix syntax, not the perl syntax)
// matched against the path of an incoming request. Currently it can
// contain characters disallowed from the conventional "path"
// part of a URL as defined by RFC 3986. Paths must begin with
// a '/'. If unspecified, the path defaults to a catch all sending
// traffic to the backend.
Path string `json:"path,omitempty"`
// Backend defines the referenced service endpoint to which the traffic
// will be forwarded to.
Backend IngressBackend `json:"backend"`
}
// IngressBackend describes all endpoints for a given service and port.
type IngressBackend struct {
// Specifies the name of the referenced service.
ServiceName string `json:"serviceName"`
// Specifies the port of the referenced service.
ServicePort IntOrString `json:"servicePort"`
}

313
provider/k8s/service.go Normal file
View file

@ -0,0 +1,313 @@
package k8s
import (
"encoding/json"
"strconv"
"time"
)
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
type TypeMeta struct {
// Kind is a string value representing the REST resource this object represents.
// Servers may infer this from the endpoint the client submits requests to.
// Cannot be updated.
// In CamelCase.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind,omitempty"`
// APIVersion defines the versioned schema of this representation of an object.
// Servers should convert recognized schemas to the latest internal value, and
// may reject unrecognized values.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources
APIVersion string `json:"apiVersion,omitempty"`
}
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
// Name is unique within a namespace. Name is required when creating resources, although
// some resources may allow a client to request the generation of an appropriate name
// automatically. Name is primarily intended for creation idempotence and configuration
// definition.
Name string `json:"name,omitempty"`
// GenerateName indicates that the name should be made unique by the server prior to persisting
// it. A non-empty value for the field indicates the name will be made unique (and the name
// returned to the client will be different than the name passed). The value of this field will
// be combined with a unique suffix on the server if the Name field has not been provided.
// The provided value must be valid within the rules for Name, and may be truncated by the length
// of the suffix required to make the value unique on the server.
//
// If this field is specified, and Name is not present, the server will NOT return a 409 if the
// generated name exists - instead, it will either return 201 Created or 500 with Reason
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
// should retry (optionally after the time indicated in the Retry-After header).
GenerateName string `json:"generateName,omitempty"`
// Namespace defines the space within which name must be unique. An empty namespace is
// equivalent to the "default" namespace, but "default" is the canonical representation.
// Not all objects are required to be scoped to a namespace - the value of this field for
// those objects will be empty.
Namespace string `json:"namespace,omitempty"`
// SelfLink is a URL representing this object.
SelfLink string `json:"selfLink,omitempty"`
// UID is the unique in time and space value for this object. It is typically generated by
// the server on successful creation of a resource and is not allowed to change on PUT
// operations.
UID UID `json:"uid,omitempty"`
// An opaque value that represents the version of this resource. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and values may only be valid for a particular
// resource or set of resources. Only servers will generate resource versions.
ResourceVersion string `json:"resourceVersion,omitempty"`
// A sequence number representing a specific generation of the desired state.
// Populated by the system. Read-only.
Generation int64 `json:"generation,omitempty"`
// CreationTimestamp is a timestamp representing the server time when this object was
// created. It is not guaranteed to be set in happens-before order across separate operations.
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
CreationTimestamp Time `json:"creationTimestamp,omitempty"`
// DeletionTimestamp is the time after which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource will be deleted (no longer visible from
// resource lists, and not reachable by name) after the time in this field. Once set, this
// value may not be unset or be set further into the future, although it may be shortened
// or the resource may be deleted prior to this time. For example, a user may request that
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
// will send a hard termination signal to the container.
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"`
// DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion
// was requested. Represents the most recent grace period, and may only be shortened once set.
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
// Labels are key value pairs that may be used to scope and select individual resources.
// Label keys are of the form:
// label-key ::= prefixed-name | name
// prefixed-name ::= prefix '/' name
// prefix ::= DNS_SUBDOMAIN
// name ::= DNS_LABEL
// The prefix is optional. If the prefix is not specified, the key is assumed to be private
// to the user. Other system components that wish to use labels must specify a prefix. The
// "kubernetes.io/" prefix is reserved for use by kubernetes components.
// TODO: replace map[string]string with labels.LabelSet type
Labels map[string]string `json:"labels,omitempty"`
// Annotations are unstructured key value data stored with a resource that may be set by
// external tooling. They are not queryable and should be preserved when modifying
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
// comments on Labels for details.
Annotations map[string]string `json:"annotations,omitempty"`
}
// UID is a type that holds unique ID values, including UUIDs. Because we
// don't ONLY use UUIDs, this is an alias to string. Being a type captures
// intent and helps make sure that UIDs and names do not get conflated.
type UID string
// Time is a wrapper around time.Time which supports correct
// marshaling to YAML and JSON. Wrappers are provided for many
// of the factory methods that the time package offers.
//
// +protobuf.options.marshal=false
// +protobuf.as=Timestamp
type Time struct {
time.Time `protobuf:"-"`
}
// Service is a named abstraction of software service (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the selector that determines which pods
// will answer requests sent through the proxy.
type Service struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// Spec defines the behavior of a service.
Spec ServiceSpec `json:"spec,omitempty"`
// Status represents the current status of a service.
Status ServiceStatus `json:"status,omitempty"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
Type ServiceType `json:"type,omitempty"`
// Required: The list of ports that are exposed by this service.
Ports []ServicePort `json:"ports"`
// This service will route traffic to pods having labels matching this selector. If empty or not present,
// the service is assumed to have endpoints set by an external process and Kubernetes will not modify
// those endpoints.
Selector map[string]string `json:"selector"`
// ClusterIP is usually assigned by the master. If specified by the user
// we will try to respect it or else fail the request. This field can
// not be changed by updates.
// Valid values are None, empty string (""), or a valid IP address
// None can be specified for headless services when proxying is not required
ClusterIP string `json:"clusterIP,omitempty"`
// ExternalIPs are used by external load balancers, or can be set by
// users to handle external traffic that arrives at a node.
ExternalIPs []string `json:"externalIPs,omitempty"`
// Only applies to Service Type: LoadBalancer
// LoadBalancer will get created with the IP specified in this field.
// This feature depends on whether the underlying cloud-provider supports specifying
// the loadBalancerIP when a load balancer is created.
// This field will be ignored if the cloud-provider does not support the feature.
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
// Required: Supports "ClientIP" and "None". Used to maintain session affinity.
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
}
// ServicePort service port
type ServicePort struct {
// Optional if only one ServicePort is defined on this service: The
// name of this port within the service. This must be a DNS_LABEL.
// All ports within a ServiceSpec must have unique names. This maps to
// the 'Name' field in EndpointPort objects.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP" and "UDP".
Protocol Protocol `json:"protocol"`
// The port that will be exposed on the service.
Port int `json:"port"`
// Optional: The target port on pods selected by this service. If this
// is a string, it will be looked up as a named port in the target
// Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
// This field is ignored for services with clusterIP=None, and should be
// omitted or set equal to the 'port' field.
TargetPort IntOrString `json:"targetPort"`
// The port on each node on which this service is exposed.
// Default is to auto-allocate a port if the ServiceType of this Service requires one.
NodePort int `json:"nodePort"`
}
// ServiceStatus represents the current status of a service
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty"`
}
// ServiceAffinity Session Affinity Type string
type ServiceAffinity string
// ServiceType Service Type string describes ingress methods for a service
type ServiceType string
// Protocol defines network protocols supported for things like container ports.
type Protocol string
// IntOrString is a type that can hold an int32 or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type IntOrString struct {
Type Type
IntVal int32
StrVal string
}
// String returns the string value, or the Itoa of the int value.
func (intstr *IntOrString) String() string {
if intstr.Type == String {
return intstr.StrVal
}
return strconv.Itoa(intstr.IntValue())
}
// IntValue returns the IntVal if type Int, or if
// it is a String, will attempt a conversion to int.
func (intstr *IntOrString) IntValue() int {
if intstr.Type == String {
i, _ := strconv.Atoi(intstr.StrVal)
return i
}
return int(intstr.IntVal)
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {
intstr.Type = String
return json.Unmarshal(value, &intstr.StrVal)
}
intstr.Type = Int
return json.Unmarshal(value, &intstr.IntVal)
}
// Type represents the stored type of IntOrString.
type Type int
const (
// Int int
Int Type = iota // The IntOrString holds an int.
//String string
String // The IntOrString holds a string.
)
// ServiceList holds a list of services.
type ServiceList struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
Items []Service `json:"items"`
}
// ListMeta describes metadata that synthetic resources must have, including lists and
// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.
type ListMeta struct {
// SelfLink is a URL representing this object.
// Populated by the system.
// Read-only.
SelfLink string `json:"selfLink,omitempty"`
// String that identifies the server's internal version of this object that
// can be used by clients to determine when objects have changed.
// Value must be treated as opaque by clients and passed unmodified back to the server.
// Populated by the system.
// Read-only.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency
ResourceVersion string `json:"resourceVersion,omitempty"`
}

164
provider/kubernetes.go Normal file
View file

@ -0,0 +1,164 @@
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/ioutil"
"os"
"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
}
// 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 {
var token string
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
if err == nil {
token = string(tokenBytes)
log.Debugf("Kubernetes token: %s", token)
} else {
log.Debugf("Kubernetes load token error: %s", err)
}
caCert, err := ioutil.ReadFile(serviceAccountCACert)
if err == nil {
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
} else {
log.Debugf("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)
k8sClient, err := k8s.NewClient(provider.Endpoint, caCert, token)
if err != nil {
return err
}
pool.Go(func(stop chan bool) {
stopWatch := make(chan bool)
operation := func() error {
select {
case <-stop:
return nil
default:
}
ingressesChan, errChan, err := k8sClient.WatchIngresses(func(ingress k8s.Ingress) bool {
return true
}, stopWatch)
if err != nil {
log.Errorf("Error retrieving ingresses: %v", err)
return err
}
for {
templateObjects := types.Configuration{
map[string]*types.Backend{},
map[string]*types.Frontend{},
}
select {
case <-stop:
stopWatch <- true
return nil
case err := <-errChan:
return err
case event := <-ingressesChan:
log.Debugf("Received event from kubenetes %+v", event)
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
return true
})
if err != nil {
log.Errorf("Error retrieving ingresses: %+v", err)
continue
}
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,
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: "Path:" + 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
}
for _, service := range services {
var protocol string
for _, port := range service.Spec.Ports {
if port.Port == pa.Backend.ServicePort.IntValue() {
protocol = port.Name
break
}
}
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
Weight: 1,
}
}
}
}
}
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.NewExponentialBackOff(), notify)
if err != nil {
log.Fatalf("Cannot connect to Kubernetes server %+v", err)
}
})
return nil
}
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
}

View file

@ -74,7 +74,7 @@ func TestConfigurationErrors(t *testing.T) {
Filename: templateInvalidTOMLFile.Name(),
},
},
expectedError: "Near line 1, key 'Hello': Near line 1: Expected key separator '=', but got '<' instead",
expectedError: "Near line 1 (last key parsed 'Hello'): Expected key separator '=', but got '<' instead",
funcMap: template.FuncMap{
"Foo": func() string {
return "bar"