Merge pull request #186 from containous/add-kubernetes-ingress-support
Add kubernetes Ingress backend
This commit is contained in:
commit
6b76f64b48
24 changed files with 1713 additions and 219 deletions
8
cmd.go
8
cmd.go
|
@ -51,6 +51,7 @@ var arguments = struct {
|
||||||
etcd bool
|
etcd bool
|
||||||
etcdTLS bool
|
etcdTLS bool
|
||||||
boltdb bool
|
boltdb bool
|
||||||
|
kubernetes bool
|
||||||
}{
|
}{
|
||||||
GlobalConfiguration{
|
GlobalConfiguration{
|
||||||
EntryPoints: make(EntryPoints),
|
EntryPoints: make(EntryPoints),
|
||||||
|
@ -72,7 +73,8 @@ var arguments = struct {
|
||||||
TLS: &provider.KvTLS{},
|
TLS: &provider.KvTLS{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Boltdb: &provider.BoltDb{},
|
Boltdb: &provider.BoltDb{},
|
||||||
|
Kubernetes: &provider.Kubernetes{},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
@ -86,6 +88,7 @@ var arguments = struct {
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -167,6 +170,9 @@ func init() {
|
||||||
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
|
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Endpoint, "boltdb.endpoint", "127.0.0.1:4001", "Boltdb server endpoint")
|
||||||
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
|
traefikCmd.PersistentFlags().StringVar(&arguments.Boltdb.Prefix, "boltdb.prefix", "/traefik", "Prefix used for KV store")
|
||||||
|
|
||||||
|
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
|
||||||
|
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
|
||||||
|
|
||||||
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
_ = viper.BindPFlag("configFile", traefikCmd.PersistentFlags().Lookup("configFile"))
|
||||||
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
_ = viper.BindPFlag("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||||
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
_ = viper.BindPFlag("logLevel", traefikCmd.PersistentFlags().Lookup("logLevel"))
|
||||||
|
|
|
@ -37,6 +37,7 @@ type GlobalConfiguration struct {
|
||||||
Etcd *provider.Etcd
|
Etcd *provider.Etcd
|
||||||
Zookeeper *provider.Zookepper
|
Zookeeper *provider.Zookepper
|
||||||
Boltdb *provider.BoltDb
|
Boltdb *provider.BoltDb
|
||||||
|
Kubernetes *provider.Kubernetes
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultEntryPoints holds default entry points
|
// DefaultEntryPoints holds default entry points
|
||||||
|
@ -209,7 +210,11 @@ func LoadConfiguration() *GlobalConfiguration {
|
||||||
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
|
viper.AddConfigPath("$HOME/.traefik/") // call multiple times to add many search paths
|
||||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
if len(viper.ConfigFileUsed()) > 0 {
|
||||||
|
fmtlog.Printf("Error reading configuration file: %s", err)
|
||||||
|
} else {
|
||||||
|
fmtlog.Printf("No configuration file found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(arguments.EntryPoints) > 0 {
|
if len(arguments.EntryPoints) > 0 {
|
||||||
|
@ -254,6 +259,9 @@ func LoadConfiguration() *GlobalConfiguration {
|
||||||
if arguments.boltdb {
|
if arguments.boltdb {
|
||||||
viper.Set("boltdb", arguments.Boltdb)
|
viper.Set("boltdb", arguments.Boltdb)
|
||||||
}
|
}
|
||||||
|
if arguments.kubernetes {
|
||||||
|
viper.Set("kubernetes", arguments.Kubernetes)
|
||||||
|
}
|
||||||
if err := unmarshal(&configuration); err != nil {
|
if err := unmarshal(&configuration); err != nil {
|
||||||
|
|
||||||
fmtlog.Fatalf("Error reading file: %s", err)
|
fmtlog.Fatalf("Error reading file: %s", err)
|
||||||
|
|
31
docs/toml.md
31
docs/toml.md
|
@ -614,6 +614,37 @@ Labels can be used on containers to override default behaviour:
|
||||||
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`.
|
||||||
* `traefik.domain=traefik.localhost`: override the default domain
|
* `traefik.domain=traefik.localhost`: override the default domain
|
||||||
|
|
||||||
|
|
||||||
|
## Kubernetes Ingress backend
|
||||||
|
|
||||||
|
|
||||||
|
Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
################################################################
|
||||||
|
# Kubernetes Ingress configuration backend
|
||||||
|
################################################################
|
||||||
|
# Enable Kubernetes Ingress configuration backend
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
[kubernetes]
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# endpoint = "http://localhost:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find here an example [ingress](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.ingress.yaml) and [replication controller](https://raw.githubusercontent.com/containous/traefik/master/examples/k8s.rc.yaml).
|
||||||
|
|
||||||
## Consul backend
|
## Consul backend
|
||||||
|
|
||||||
Træfɪk can be configured to use Consul as a backend configuration:
|
Træfɪk can be configured to use Consul as a backend configuration:
|
||||||
|
|
17
examples/compose-k8s.yaml
Normal file
17
examples/compose-k8s.yaml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# etcd:
|
||||||
|
# image: gcr.io/google_containers/etcd:2.2.1
|
||||||
|
# net: host
|
||||||
|
# command: ['/usr/local/bin/etcd', '--addr=127.0.0.1:4001', '--bind-addr=0.0.0.0:4001', '--data-dir=/var/etcd/data']
|
||||||
|
|
||||||
|
kubelet:
|
||||||
|
image: gcr.io/google_containers/hyperkube-amd64:v1.2.2
|
||||||
|
privileged: true
|
||||||
|
pid: host
|
||||||
|
net : host
|
||||||
|
volumes:
|
||||||
|
- /:/rootfs:ro
|
||||||
|
- /sys:/sys:ro
|
||||||
|
- /var/lib/docker/:/var/lib/docker:rw
|
||||||
|
- /var/lib/kubelet/:/var/lib/kubelet:rw
|
||||||
|
- /var/run:/var/run:rw
|
||||||
|
command: ['/hyperkube', 'kubelet', '--containerized', '--hostname-override=127.0.0.1', '--address=0.0.0.0', '--api-servers=http://localhost:8080', '--config=/etc/kubernetes/manifests', '--allow-privileged=true', '--v=2']
|
93
examples/k8s.ingress.yaml
Normal file
93
examples/k8s.ingress.yaml
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
# 3 Services for the 3 endpoints of the Ingress
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: service1
|
||||||
|
labels:
|
||||||
|
app: whoami
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
nodePort: 30283
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: whoami
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: service2
|
||||||
|
labels:
|
||||||
|
app: whoami
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
nodePort: 30284
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: whoami
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: service3
|
||||||
|
labels:
|
||||||
|
app: whoami
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
nodePort: 30285
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: whoami
|
||||||
|
---
|
||||||
|
# A single RC matching all Services
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ReplicationController
|
||||||
|
metadata:
|
||||||
|
name: whoami
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: whoami
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: whoami
|
||||||
|
image: emilevauge/whoami
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
---
|
||||||
|
# An Ingress with 2 hosts and 3 endpoints
|
||||||
|
apiVersion: extensions/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: whoami-ingress
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: foo.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
backend:
|
||||||
|
serviceName: service1
|
||||||
|
servicePort: 80
|
||||||
|
- host: bar.localhost
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- backend:
|
||||||
|
serviceName: service2
|
||||||
|
servicePort: 80
|
||||||
|
- backend:
|
||||||
|
serviceName: service3
|
||||||
|
servicePort: 80
|
10
examples/k8s.namespace.sh
Executable file
10
examples/k8s.namespace.sh
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
kubectl create -f - << EOF
|
||||||
|
kind: Namespace
|
||||||
|
apiVersion: v1
|
||||||
|
metadata:
|
||||||
|
name: kube-system
|
||||||
|
labels:
|
||||||
|
name: kube-system
|
||||||
|
EOF
|
31
examples/k8s.rc.yaml
Normal file
31
examples/k8s.rc.yaml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ReplicationController
|
||||||
|
metadata:
|
||||||
|
name: traefik-ingress-controller
|
||||||
|
labels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
k8s-app: traefik-ingress-lb
|
||||||
|
name: traefik-ingress-lb
|
||||||
|
spec:
|
||||||
|
terminationGracePeriodSeconds: 60
|
||||||
|
containers:
|
||||||
|
- image: containous/traefik
|
||||||
|
name: traefik-ingress-lb
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
hostPort: 80
|
||||||
|
- containerPort: 443
|
||||||
|
hostPort: 443
|
||||||
|
- containerPort: 8080
|
||||||
|
args:
|
||||||
|
- --web
|
||||||
|
- --kubernetes
|
||||||
|
- --logLevel=DEBUG
|
28
glide.lock
generated
28
glide.lock
generated
|
@ -1,5 +1,5 @@
|
||||||
hash: fffa87220825895f7e3a6ceed3b13ecbf6bc934ab072fc9be3d00e3eef411ecb
|
hash: 6fe539ee86a9dc90a67b60f42b027c72359bed0ca22e7a94355ad80f37a32d68
|
||||||
updated: 2016-04-13T14:05:41.300658168+02:00
|
updated: 2016-04-18T21:31:13.195184921+02:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/alecthomas/template
|
- name: github.com/alecthomas/template
|
||||||
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||||
|
@ -8,7 +8,7 @@ imports:
|
||||||
- name: github.com/boltdb/bolt
|
- name: github.com/boltdb/bolt
|
||||||
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||||
- name: github.com/BurntSushi/toml
|
- name: github.com/BurntSushi/toml
|
||||||
version: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
|
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
|
||||||
- name: github.com/BurntSushi/ty
|
- name: github.com/BurntSushi/ty
|
||||||
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||||
subpackages:
|
subpackages:
|
||||||
|
@ -29,6 +29,7 @@ imports:
|
||||||
- memmetrics
|
- memmetrics
|
||||||
- roundrobin
|
- roundrobin
|
||||||
- utils
|
- utils
|
||||||
|
- connlimit
|
||||||
- stream
|
- stream
|
||||||
- name: github.com/coreos/go-etcd
|
- name: github.com/coreos/go-etcd
|
||||||
version: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
version: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
||||||
|
@ -42,7 +43,6 @@ imports:
|
||||||
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||||
subpackages:
|
subpackages:
|
||||||
- reference
|
- reference
|
||||||
- digest
|
|
||||||
- name: github.com/docker/docker
|
- name: github.com/docker/docker
|
||||||
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||||
subpackages:
|
subpackages:
|
||||||
|
@ -94,13 +94,11 @@ imports:
|
||||||
- client/transport
|
- client/transport
|
||||||
- client/transport/cancellable
|
- client/transport/cancellable
|
||||||
- types/network
|
- types/network
|
||||||
- types/reference
|
|
||||||
- types/registry
|
- types/registry
|
||||||
- types/time
|
- types/time
|
||||||
- types/versions
|
|
||||||
- types/blkiodev
|
- types/blkiodev
|
||||||
- name: github.com/docker/go-connections
|
- name: github.com/docker/go-connections
|
||||||
version: f549a9393d05688dff0992ef3efd8bbe6c628aeb
|
version: 5b7154ba2efe13ff86ae8830a9e7cb120b080d6e
|
||||||
subpackages:
|
subpackages:
|
||||||
- nat
|
- nat
|
||||||
- sockets
|
- sockets
|
||||||
|
@ -148,7 +146,7 @@ imports:
|
||||||
subpackages:
|
subpackages:
|
||||||
- api
|
- api
|
||||||
- name: github.com/hashicorp/hcl
|
- name: github.com/hashicorp/hcl
|
||||||
version: 2604f3bda7e8960c1be1063709e7d7f0765048d0
|
version: 27a57f2605e04995c111273c263d51cee60d9bc4
|
||||||
subpackages:
|
subpackages:
|
||||||
- hcl/ast
|
- hcl/ast
|
||||||
- hcl/parser
|
- hcl/parser
|
||||||
|
@ -177,13 +175,17 @@ imports:
|
||||||
- name: github.com/Microsoft/go-winio
|
- name: github.com/Microsoft/go-winio
|
||||||
version: 862b6557927a5c5c81e411c12aa6de7e566cbb7a
|
version: 862b6557927a5c5c81e411c12aa6de7e566cbb7a
|
||||||
- name: github.com/miekg/dns
|
- name: github.com/miekg/dns
|
||||||
version: dd83d5cbcfd986f334b2747feeb907e281318fdf
|
version: a5cc44dc6b2eee8eddfd6581e1c6bb753ff0d176
|
||||||
- name: github.com/mitchellh/mapstructure
|
- name: github.com/mitchellh/mapstructure
|
||||||
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
version: d2dd0262208475919e1a362f675cfc0e7c10e905
|
||||||
|
- name: github.com/moul/http2curl
|
||||||
|
version: 1812aee76a1ce98d604a44200c6a23c689b17a89
|
||||||
- name: github.com/opencontainers/runc
|
- name: github.com/opencontainers/runc
|
||||||
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||||
subpackages:
|
subpackages:
|
||||||
- libcontainer/user
|
- libcontainer/user
|
||||||
|
- name: github.com/parnurzeal/gorequest
|
||||||
|
version: 91b42fce877cc6af96c45818665a4c615cc5f4ee
|
||||||
- name: github.com/pmezard/go-difflib
|
- name: github.com/pmezard/go-difflib
|
||||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||||
subpackages:
|
subpackages:
|
||||||
|
@ -203,7 +205,7 @@ imports:
|
||||||
- name: github.com/spf13/jwalterweatherman
|
- name: github.com/spf13/jwalterweatherman
|
||||||
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
version: 33c24e77fb80341fe7130ee7c594256ff08ccc46
|
||||||
- name: github.com/spf13/pflag
|
- name: github.com/spf13/pflag
|
||||||
version: 1f296710f879815ad9e6d39d947c828c3e4b4c3d
|
version: 8f6a28b0916586e7f22fe931ae2fcfc380b1c0e6
|
||||||
- name: github.com/spf13/viper
|
- name: github.com/spf13/viper
|
||||||
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||||
- name: github.com/streamrail/concurrent-map
|
- name: github.com/streamrail/concurrent-map
|
||||||
|
@ -220,7 +222,7 @@ imports:
|
||||||
- name: github.com/unrolled/render
|
- name: github.com/unrolled/render
|
||||||
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||||
- name: github.com/vdemeester/docker-events
|
- name: github.com/vdemeester/docker-events
|
||||||
version: 6ea3f28df37f29a47498bc8b32b36ad8491dbd37
|
version: 1ecaca5890ef1ffd266fcbfdbe43073ef105704b
|
||||||
- name: github.com/vdemeester/libkermit
|
- name: github.com/vdemeester/libkermit
|
||||||
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||||
- name: github.com/vdemeester/shakers
|
- name: github.com/vdemeester/shakers
|
||||||
|
@ -243,11 +245,11 @@ imports:
|
||||||
- name: github.com/wendal/errors
|
- name: github.com/wendal/errors
|
||||||
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
version: f66c77a7882b399795a8987ebf87ef64a427417e
|
||||||
- name: github.com/xenolf/lego
|
- name: github.com/xenolf/lego
|
||||||
version: 23e88185c255e95a106835d80e76e5a3a66d7c54
|
version: 684400fe76a813e78d87803a62bc04d977c501d2
|
||||||
subpackages:
|
subpackages:
|
||||||
- acme
|
- acme
|
||||||
- name: golang.org/x/crypto
|
- name: golang.org/x/crypto
|
||||||
version: d68c3ecb62c850b645dc072a8d78006286bf81ca
|
version: 1777f3ba8c1fed80fcaec3317e3aaa4f627764d2
|
||||||
subpackages:
|
subpackages:
|
||||||
- ocsp
|
- ocsp
|
||||||
- name: golang.org/x/net
|
- name: golang.org/x/net
|
||||||
|
|
358
glide.yaml
358
glide.yaml
|
@ -1,177 +1,185 @@
|
||||||
package: main
|
package: main
|
||||||
import:
|
import:
|
||||||
- package: github.com/coreos/go-etcd
|
- package: github.com/coreos/go-etcd
|
||||||
ref: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
version: cc90c7b091275e606ad0ca7102a23fb2072f3f5e
|
||||||
subpackages:
|
subpackages:
|
||||||
- etcd
|
- etcd
|
||||||
- package: github.com/mailgun/log
|
- package: github.com/mailgun/log
|
||||||
ref: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
version: 44874009257d4d47ba9806f1b7f72a32a015e4d8
|
||||||
- package: github.com/containous/oxy
|
- package: github.com/containous/oxy
|
||||||
ref: 021f82bd8260ba15f5862a9fe62018437720dff5
|
version: 021f82bd8260ba15f5862a9fe62018437720dff5
|
||||||
subpackages:
|
subpackages:
|
||||||
- cbreaker
|
- cbreaker
|
||||||
- forward
|
- forward
|
||||||
- memmetrics
|
- memmetrics
|
||||||
- roundrobin
|
- roundrobin
|
||||||
- utils
|
- utils
|
||||||
- package: github.com/hashicorp/consul
|
- package: github.com/hashicorp/consul
|
||||||
ref: de080672fee9e6104572eeea89eccdca135bb918
|
version: de080672fee9e6104572eeea89eccdca135bb918
|
||||||
subpackages:
|
subpackages:
|
||||||
- api
|
- api
|
||||||
- package: github.com/samuel/go-zookeeper
|
- package: github.com/samuel/go-zookeeper
|
||||||
ref: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
version: fa6674abf3f4580b946a01bf7a1ce4ba8766205b
|
||||||
subpackages:
|
subpackages:
|
||||||
- zk
|
- zk
|
||||||
- package: github.com/docker/libtrust
|
- package: github.com/docker/libtrust
|
||||||
ref: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
version: 9cbd2a1374f46905c68a4eb3694a130610adc62a
|
||||||
- package: github.com/go-check/check
|
- package: github.com/go-check/check
|
||||||
ref: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
version: 11d3bc7aa68e238947792f30573146a3231fc0f1
|
||||||
- package: golang.org/x/net
|
- package: golang.org/x/net
|
||||||
ref: d9558e5c97f85372afee28cf2b6059d7d3818919
|
version: d9558e5c97f85372afee28cf2b6059d7d3818919
|
||||||
subpackages:
|
subpackages:
|
||||||
- context
|
- context
|
||||||
- package: github.com/gorilla/handlers
|
- package: github.com/gorilla/handlers
|
||||||
ref: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
version: 40694b40f4a928c062f56849989d3e9cd0570e5f
|
||||||
- package: github.com/docker/libkv
|
- package: github.com/docker/libkv
|
||||||
ref: 3732f7ff1b56057c3158f10bceb1e79133025373
|
version: 3732f7ff1b56057c3158f10bceb1e79133025373
|
||||||
- package: github.com/alecthomas/template
|
- package: github.com/alecthomas/template
|
||||||
ref: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
version: b867cc6ab45cece8143cfcc6fc9c77cf3f2c23c0
|
||||||
- package: github.com/vdemeester/shakers
|
- package: github.com/vdemeester/shakers
|
||||||
ref: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
version: 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3
|
||||||
- package: github.com/alecthomas/units
|
- package: github.com/alecthomas/units
|
||||||
ref: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
version: 6b4e7dc5e3143b85ea77909c72caf89416fc2915
|
||||||
- package: github.com/gambol99/go-marathon
|
- package: github.com/gambol99/go-marathon
|
||||||
ref: ade11d1dc2884ee1f387078fc28509559b6235d1
|
version: ade11d1dc2884ee1f387078fc28509559b6235d1
|
||||||
- package: github.com/vulcand/predicate
|
- package: github.com/vulcand/predicate
|
||||||
ref: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
version: cb0bff91a7ab7cf7571e661ff883fc997bc554a3
|
||||||
- package: github.com/thoas/stats
|
- package: github.com/thoas/stats
|
||||||
ref: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
version: 54ed61c2b47e263ae2f01b86837b0c4bd1da28e8
|
||||||
- package: github.com/Sirupsen/logrus
|
- package: github.com/Sirupsen/logrus
|
||||||
ref: 418b41d23a1bf978c06faea5313ba194650ac088
|
version: 418b41d23a1bf978c06faea5313ba194650ac088
|
||||||
- package: github.com/unrolled/render
|
- package: github.com/unrolled/render
|
||||||
ref: 26b4e3aac686940fe29521545afad9966ddfc80c
|
version: 26b4e3aac686940fe29521545afad9966ddfc80c
|
||||||
- package: github.com/flynn/go-shlex
|
- package: github.com/flynn/go-shlex
|
||||||
ref: 3f9db97f856818214da2e1057f8ad84803971cff
|
version: 3f9db97f856818214da2e1057f8ad84803971cff
|
||||||
- package: github.com/boltdb/bolt
|
- package: github.com/boltdb/bolt
|
||||||
ref: 51f99c862475898df9773747d3accd05a7ca33c1
|
version: 51f99c862475898df9773747d3accd05a7ca33c1
|
||||||
- package: gopkg.in/mgo.v2
|
- package: gopkg.in/mgo.v2
|
||||||
ref: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
version: 22287bab4379e1fbf6002fb4eb769888f3fb224c
|
||||||
subpackages:
|
subpackages:
|
||||||
- bson
|
- bson
|
||||||
- package: github.com/docker/docker
|
- package: github.com/docker/docker
|
||||||
ref: f39987afe8d611407887b3094c03d6ba6a766a67
|
version: f39987afe8d611407887b3094c03d6ba6a766a67
|
||||||
subpackages:
|
subpackages:
|
||||||
- autogen
|
- autogen
|
||||||
- api
|
- api
|
||||||
- cliconfig
|
- cliconfig
|
||||||
- daemon/network
|
- daemon/network
|
||||||
- graph/tags
|
- graph/tags
|
||||||
- image
|
- image
|
||||||
- opts
|
- opts
|
||||||
- pkg/archive
|
- pkg/archive
|
||||||
- pkg/fileutils
|
- pkg/fileutils
|
||||||
- pkg/homedir
|
- pkg/homedir
|
||||||
- pkg/httputils
|
- pkg/httputils
|
||||||
- pkg/ioutils
|
- pkg/ioutils
|
||||||
- pkg/jsonmessage
|
- pkg/jsonmessage
|
||||||
- pkg/mflag
|
- pkg/mflag
|
||||||
- pkg/nat
|
- pkg/nat
|
||||||
- pkg/parsers
|
- pkg/parsers
|
||||||
- pkg/pools
|
- pkg/pools
|
||||||
- pkg/promise
|
- pkg/promise
|
||||||
- pkg/random
|
- pkg/random
|
||||||
- pkg/stdcopy
|
- pkg/stdcopy
|
||||||
- pkg/stringid
|
- pkg/stringid
|
||||||
- pkg/symlink
|
- pkg/symlink
|
||||||
- pkg/system
|
- pkg/system
|
||||||
- pkg/tarsum
|
- pkg/tarsum
|
||||||
- pkg/term
|
- pkg/term
|
||||||
- pkg/timeutils
|
- pkg/timeutils
|
||||||
- pkg/tlsconfig
|
- pkg/tlsconfig
|
||||||
- pkg/ulimit
|
- pkg/ulimit
|
||||||
- pkg/units
|
- pkg/units
|
||||||
- pkg/urlutil
|
- pkg/urlutil
|
||||||
- pkg/useragent
|
- pkg/useragent
|
||||||
- pkg/version
|
- pkg/version
|
||||||
- registry
|
- registry
|
||||||
- runconfig
|
- runconfig
|
||||||
- utils
|
- utils
|
||||||
- volume
|
- volume
|
||||||
- package: github.com/mailgun/timetools
|
- package: github.com/mailgun/timetools
|
||||||
ref: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
version: fd192d755b00c968d312d23f521eb0cdc6f66bd0
|
||||||
- package: github.com/codegangsta/negroni
|
- package: github.com/codegangsta/negroni
|
||||||
ref: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
version: c7477ad8e330bef55bf1ebe300cf8aa67c492d1b
|
||||||
- package: gopkg.in/yaml.v2
|
- package: gopkg.in/yaml.v2
|
||||||
ref: 7ad95dd0798a40da1ccdff6dff35fd177b5edf40
|
version: 7ad95dd0798a40da1ccdff6dff35fd177b5edf
|
||||||
- package: github.com/opencontainers/runc
|
- package: github.com/opencontainers/runc
|
||||||
ref: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
version: 4ab132458fc3e9dbeea624153e0331952dc4c8d5
|
||||||
subpackages:
|
subpackages:
|
||||||
- libcontainer/user
|
- libcontainer/user
|
||||||
- package: github.com/gorilla/mux
|
- package: github.com/gorilla/mux
|
||||||
ref: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
version: f15e0c49460fd49eebe2bcc8486b05d1bef68d3a
|
||||||
- package: github.com/BurntSushi/ty
|
- package: github.com/BurntSushi/ty
|
||||||
ref: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
version: 6add9cd6ad42d389d6ead1dde60b4ad71e46fd74
|
||||||
- package: github.com/elazarl/go-bindata-assetfs
|
- package: github.com/elazarl/go-bindata-assetfs
|
||||||
ref: d5cac425555ca5cf00694df246e04f05e6a55150
|
version: d5cac425555ca5cf00694df246e04f05e6a55150
|
||||||
- package: github.com/BurntSushi/toml
|
- package: github.com/BurntSushi/toml
|
||||||
ref: bd2bdf7f18f849530ef7a1c29a4290217cab32a1
|
version: bbd5bb678321a0d6e58f1099321dfa73391c1b6f
|
||||||
- package: gopkg.in/alecthomas/kingpin.v2
|
- package: gopkg.in/alecthomas/kingpin.v2
|
||||||
ref: 639879d6110b1b0409410c7b737ef0bb18325038
|
version: 639879d6110b1b0409410c7b737ef0bb18325038
|
||||||
- package: github.com/cenkalti/backoff
|
- package: github.com/cenkalti/backoff
|
||||||
ref: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
version: 4dc77674aceaabba2c7e3da25d4c823edfb73f99
|
||||||
- package: gopkg.in/fsnotify.v1
|
- package: gopkg.in/fsnotify.v1
|
||||||
ref: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
version: 96c060f6a6b7e0d6f75fddd10efeaca3e5d1bcb0
|
||||||
- package: github.com/mailgun/manners
|
- package: github.com/mailgun/manners
|
||||||
ref: fada45142db3f93097ca917da107aa3fad0ffcb5
|
version: fada45142db3f93097ca917da107aa3fad0ffcb5
|
||||||
- package: github.com/gorilla/context
|
- package: github.com/gorilla/context
|
||||||
ref: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
version: 215affda49addc4c8ef7e2534915df2c8c35c6cd
|
||||||
- package: github.com/codahale/hdrhistogram
|
- package: github.com/codahale/hdrhistogram
|
||||||
ref: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
version: 954f16e8b9ef0e5d5189456aa4c1202758e04f17
|
||||||
- package: github.com/gorilla/websocket
|
- package: github.com/gorilla/websocket
|
||||||
- package: github.com/donovanhide/eventsource
|
- package: github.com/donovanhide/eventsource
|
||||||
ref: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
version: d8a3071799b98cacd30b6da92f536050ccfe6da4
|
||||||
- package: github.com/golang/glog
|
- package: github.com/golang/glog
|
||||||
ref: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
version: fca8c8854093a154ff1eb580aae10276ad6b1b5f
|
||||||
- package: github.com/spf13/cast
|
- package: github.com/spf13/cast
|
||||||
ref: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
version: ee7b3e0353166ab1f3a605294ac8cd2b77953778
|
||||||
- package: github.com/mitchellh/mapstructure
|
- package: github.com/mitchellh/mapstructure
|
||||||
- package: github.com/spf13/jwalterweatherman
|
- package: github.com/spf13/jwalterweatherman
|
||||||
- package: github.com/spf13/pflag
|
- package: github.com/spf13/pflag
|
||||||
- package: github.com/wendal/errors
|
- package: github.com/wendal/errors
|
||||||
- package: github.com/hashicorp/hcl
|
- package: github.com/hashicorp/hcl
|
||||||
- package: github.com/kr/pretty
|
- package: github.com/kr/pretty
|
||||||
- package: github.com/magiconair/properties
|
- package: github.com/magiconair/properties
|
||||||
- package: github.com/kr/text
|
- package: github.com/kr/text
|
||||||
- package: github.com/spf13/viper
|
- package: github.com/spf13/viper
|
||||||
ref: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
version: a212099cbe6fbe8d07476bfda8d2d39b6ff8f325
|
||||||
- package: github.com/spf13/cobra
|
- package: github.com/spf13/cobra
|
||||||
subpackages:
|
subpackages:
|
||||||
- /cobra
|
- cobra
|
||||||
- package: github.com/google/go-querystring/query
|
- package: github.com/google/go-querystring
|
||||||
- package: github.com/vulcand/vulcand/plugin/rewrite
|
subpackages:
|
||||||
- package: github.com/stretchr/testify/mock
|
- query
|
||||||
- package: github.com/xenolf/lego
|
- package: github.com/vulcand/vulcand
|
||||||
- package: github.com/vdemeester/libkermit
|
subpackages:
|
||||||
ref: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
- plugin/rewrite
|
||||||
- package: github.com/docker/libcompose
|
- package: github.com/stretchr/testify
|
||||||
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
subpackages:
|
||||||
- package: github.com/docker/distribution
|
- mock
|
||||||
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
- package: github.com/xenolf/lego
|
||||||
subpackages:
|
- package: github.com/vdemeester/libkermit
|
||||||
- reference
|
version: 7e4e689a6fa9281e0fb9b7b9c297e22d5342a5ec
|
||||||
- package: github.com/docker/engine-api
|
- package: github.com/docker/libcompose
|
||||||
subpackages:
|
version: e290a513ba909ca3afefd5cd611f3a3fe56f6a3a
|
||||||
- client
|
- package: github.com/docker/distribution
|
||||||
- types
|
version: ff6f38ccb69afa96214c7ee955359465d1fc767a
|
||||||
- types/container
|
subpackages:
|
||||||
- types/filters
|
- reference
|
||||||
- types/strslice
|
- package: github.com/docker/engine-api
|
||||||
- package: github.com/vdemeester/docker-events
|
version: 8924d6900370b4c7e7984be5adc61f50a80d7537
|
||||||
- package: github.com/docker/go-connections
|
subpackages:
|
||||||
subpackages:
|
- client
|
||||||
- nat
|
- types
|
||||||
- sockets
|
- types/container
|
||||||
- tlsconfig
|
- types/filters
|
||||||
- package: github.com/docker/go-units
|
- types/strslice
|
||||||
- package: github.com/mailgun/multibuf
|
- package: github.com/vdemeester/docker-events
|
||||||
- package: github.com/streamrail/concurrent-map
|
- package: github.com/docker/go-connections
|
||||||
|
subpackages:
|
||||||
|
- nat
|
||||||
|
- sockets
|
||||||
|
- tlsconfig
|
||||||
|
- package: github.com/docker/go-units
|
||||||
|
- package: github.com/mailgun/multibuf
|
||||||
|
- package: github.com/streamrail/concurrent-map
|
||||||
|
- package: github.com/parnurzeal/gorequest
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
|
|
||||||
|
"bytes"
|
||||||
checker "github.com/vdemeester/shakers"
|
checker "github.com/vdemeester/shakers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -16,25 +17,45 @@ type SimpleSuite struct{ BaseSuite }
|
||||||
|
|
||||||
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
|
func (s *SimpleSuite) TestNoOrInexistentConfigShouldFail(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary)
|
cmd := exec.Command(traefikBinary)
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
c.Assert(err, checker.NotNil)
|
var b bytes.Buffer
|
||||||
c.Assert(string(output), checker.Contains, "Error reading file: open : no such file or directory")
|
cmd.Stdout = &b
|
||||||
|
cmd.Stderr = &b
|
||||||
|
|
||||||
|
cmd.Start()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
output := b.Bytes()
|
||||||
|
|
||||||
|
c.Assert(string(output), checker.Contains, "No configuration file found")
|
||||||
|
cmd.Process.Kill()
|
||||||
|
|
||||||
nonExistentFile := "non/existent/file.toml"
|
nonExistentFile := "non/existent/file.toml"
|
||||||
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
|
cmd = exec.Command(traefikBinary, "--configFile="+nonExistentFile)
|
||||||
output, err = cmd.CombinedOutput()
|
|
||||||
|
|
||||||
c.Assert(err, checker.NotNil)
|
cmd.Stdout = &b
|
||||||
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading file: open %s: no such file or directory", nonExistentFile))
|
cmd.Stderr = &b
|
||||||
|
|
||||||
|
cmd.Start()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
output = b.Bytes()
|
||||||
|
|
||||||
|
c.Assert(string(output), checker.Contains, fmt.Sprintf("Error reading configuration file: open %s: no such file or directory", nonExistentFile))
|
||||||
|
cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
func (s *SimpleSuite) TestInvalidConfigShouldFail(c *check.C) {
|
||||||
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
|
cmd := exec.Command(traefikBinary, "--configFile=fixtures/invalid_configuration.toml")
|
||||||
output, err := cmd.CombinedOutput()
|
|
||||||
|
|
||||||
c.Assert(err, checker.NotNil)
|
var b bytes.Buffer
|
||||||
c.Assert(string(output), checker.Contains, "Error reading file: While parsing config: Near line 1")
|
cmd.Stdout = &b
|
||||||
|
cmd.Stderr = &b
|
||||||
|
|
||||||
|
cmd.Start()
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
output := b.Bytes()
|
||||||
|
|
||||||
|
c.Assert(string(output), checker.Contains, "While parsing config: Near line 0 (last key parsed ''): Bare keys cannot contain '{'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
func (s *SimpleSuite) TestSimpleDefaultConfig(c *check.C) {
|
||||||
|
|
|
@ -743,11 +743,11 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
`"frontend-Host-test-docker-localhost"`: {
|
"frontend-Host-test-docker-localhost": {
|
||||||
Backend: "backend-test",
|
Backend: "backend-test",
|
||||||
EntryPoints: []string{},
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test-docker-localhost"`: {
|
"route-frontend-Host-test-docker-localhost": {
|
||||||
Rule: "Host:test.docker.localhost",
|
Rule: "Host:test.docker.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -815,20 +815,20 @@ func TestDockerLoadDockerConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedFrontends: map[string]*types.Frontend{
|
expectedFrontends: map[string]*types.Frontend{
|
||||||
`"frontend-Host-test1-docker-localhost"`: {
|
"frontend-Host-test1-docker-localhost": {
|
||||||
Backend: "backend-foobar",
|
Backend: "backend-foobar",
|
||||||
EntryPoints: []string{"http", "https"},
|
EntryPoints: []string{"http", "https"},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test1-docker-localhost"`: {
|
"route-frontend-Host-test1-docker-localhost": {
|
||||||
Rule: "Host:test1.docker.localhost",
|
Rule: "Host:test1.docker.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
`"frontend-Host-test2-docker-localhost"`: {
|
"frontend-Host-test2-docker-localhost": {
|
||||||
Backend: "backend-foobar",
|
Backend: "backend-foobar",
|
||||||
EntryPoints: []string{},
|
EntryPoints: []string{},
|
||||||
Routes: map[string]types.Route{
|
Routes: map[string]types.Route{
|
||||||
`"route-frontend-Host-test2-docker-localhost"`: {
|
"route-frontend-Host-test2-docker-localhost": {
|
||||||
Rule: "Host:test2.docker.localhost",
|
Rule: "Host:test2.docker.localhost",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
284
provider/k8s/client.go
Normal file
284
provider/k8s/client.go
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
package k8s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/containous/traefik/safe"
|
||||||
|
"github.com/parnurzeal/gorequest"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// APIEndpoint defines the base path for kubernetes API resources.
|
||||||
|
APIEndpoint = "/api/v1"
|
||||||
|
extentionsEndpoint = "/apis/extensions/v1beta1"
|
||||||
|
defaultIngress = "/ingresses"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client is a client for the Kubernetes master.
|
||||||
|
type Client interface {
|
||||||
|
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
|
||||||
|
GetServices(predicate func(Service) bool) ([]Service, error)
|
||||||
|
WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientImpl 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 &clientImpl{
|
||||||
|
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
|
||||||
|
token: token,
|
||||||
|
caCert: caCert,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIngresses returns all services in the cluster
|
||||||
|
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
|
||||||
|
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||||
|
|
||||||
|
body, err := c.do(c.request(getURL))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: GET %q : %v", getURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ingresses in the cluster
|
||||||
|
func (c *clientImpl) WatchIngresses(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||||
|
return c.watch(getURL, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServices returns all services in the cluster
|
||||||
|
func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) {
|
||||||
|
getURL := c.endpointURL + APIEndpoint + "/services"
|
||||||
|
|
||||||
|
body, err := c.do(c.request(getURL))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: GET %q : %v", getURL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchServices returns all services in the cluster
|
||||||
|
func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
getURL := c.endpointURL + APIEndpoint + "/services"
|
||||||
|
return c.watch(getURL, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchEvents returns events in the cluster
|
||||||
|
func (c *clientImpl) WatchEvents(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
getURL := c.endpointURL + APIEndpoint + "/events"
|
||||||
|
return c.watch(getURL, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchPods returns pods in the cluster
|
||||||
|
func (c *clientImpl) WatchPods(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
getURL := c.endpointURL + APIEndpoint + "/pods"
|
||||||
|
return c.watch(getURL, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchReplicationControllers returns ReplicationControllers in the cluster
|
||||||
|
func (c *clientImpl) WatchReplicationControllers(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
getURL := c.endpointURL + APIEndpoint + "/replicationcontrollers"
|
||||||
|
return c.watch(getURL, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchAll returns events in the cluster
|
||||||
|
func (c *clientImpl) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
watchCh := make(chan interface{})
|
||||||
|
errCh := make(chan error)
|
||||||
|
|
||||||
|
stopIngresses := make(chan bool)
|
||||||
|
chanIngresses, chanIngressesErr, err := c.WatchIngresses(stopIngresses)
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||||
|
}
|
||||||
|
stopServices := make(chan bool)
|
||||||
|
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||||
|
}
|
||||||
|
stopPods := make(chan bool)
|
||||||
|
chanPods, chanPodsErr, err := c.WatchPods(stopPods)
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||||
|
}
|
||||||
|
stopReplicationControllers := make(chan bool)
|
||||||
|
chanReplicationControllers, chanReplicationControllersErr, err := c.WatchReplicationControllers(stopReplicationControllers)
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create watch %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
defer close(watchCh)
|
||||||
|
defer close(errCh)
|
||||||
|
defer close(stopIngresses)
|
||||||
|
defer close(stopServices)
|
||||||
|
defer close(stopPods)
|
||||||
|
defer close(stopReplicationControllers)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopCh:
|
||||||
|
stopIngresses <- true
|
||||||
|
stopServices <- true
|
||||||
|
stopPods <- true
|
||||||
|
stopReplicationControllers <- true
|
||||||
|
break
|
||||||
|
case err := <-chanIngressesErr:
|
||||||
|
errCh <- err
|
||||||
|
case err := <-chanServicesErr:
|
||||||
|
errCh <- err
|
||||||
|
case err := <-chanPodsErr:
|
||||||
|
errCh <- err
|
||||||
|
case err := <-chanReplicationControllersErr:
|
||||||
|
errCh <- err
|
||||||
|
case event := <-chanIngresses:
|
||||||
|
watchCh <- event
|
||||||
|
case event := <-chanServices:
|
||||||
|
watchCh <- event
|
||||||
|
case event := <-chanPods:
|
||||||
|
watchCh <- event
|
||||||
|
case event := <-chanReplicationControllers:
|
||||||
|
watchCh <- event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return watchCh, errCh, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) {
|
||||||
|
res, body, errs := request.EndBytes()
|
||||||
|
if errs != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body))
|
||||||
|
}
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) request(url string) *gorequest.SuperAgent {
|
||||||
|
// Make request to Kubernetes API
|
||||||
|
request := gorequest.New().Get(url)
|
||||||
|
if len(c.token) > 0 {
|
||||||
|
request.Header["Authorization"] = "Bearer " + c.token
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
pool.AppendCertsFromPEM(c.caCert)
|
||||||
|
c.tls = &tls.Config{RootCAs: pool}
|
||||||
|
}
|
||||||
|
return request.TLSClientConfig(c.tls)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenericObject generic object
|
||||||
|
type GenericObject struct {
|
||||||
|
TypeMeta `json:",inline"`
|
||||||
|
ListMeta `json:"metadata,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clientImpl) watch(url string, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
watchCh := make(chan interface{})
|
||||||
|
errCh := make(chan error)
|
||||||
|
|
||||||
|
// get version
|
||||||
|
body, err := c.do(c.request(url))
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var generic GenericObject
|
||||||
|
if err := json.Unmarshal(body, &generic); err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", url, err)
|
||||||
|
}
|
||||||
|
resourceVersion := generic.ResourceVersion
|
||||||
|
|
||||||
|
url = url + "?watch&resourceVersion=" + resourceVersion
|
||||||
|
// Make request to Kubernetes API
|
||||||
|
request := c.request(url)
|
||||||
|
request.Transport.Dial = func(network, addr string) (net.Conn, error) {
|
||||||
|
conn, err := net.Dial(network, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// No timeout for long-polling request
|
||||||
|
conn.SetDeadline(time.Now())
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
req, err := request.TLSClientConfig(c.tls).MakeRequest()
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to create request: GET %q : %v", url, err)
|
||||||
|
}
|
||||||
|
res, err := request.Client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return watchCh, errCh, fmt.Errorf("failed to make request: GET %q: %v", url, 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 eventList interface{}
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
|
||||||
|
if !shouldStop.Get().(bool) {
|
||||||
|
errCh <- fmt.Errorf("failed to decode watch event: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
watchCh <- eventList
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return watchCh, errCh, nil
|
||||||
|
}
|
151
provider/k8s/ingress.go
Normal file
151
provider/k8s/ingress.go
Normal 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"`
|
||||||
|
}
|
326
provider/k8s/service.go
Normal file
326
provider/k8s/service.go
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromInt creates an IntOrString object with an int32 value. It is
|
||||||
|
// your responsibility not to call this method with a value greater
|
||||||
|
// than int32.
|
||||||
|
// TODO: convert to (val int32)
|
||||||
|
func FromInt(val int) IntOrString {
|
||||||
|
return IntOrString{Type: Int, IntVal: int32(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString creates an IntOrString object with a string value.
|
||||||
|
func FromString(val string) IntOrString {
|
||||||
|
return IntOrString{Type: String, StrVal: val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
198
provider/kubernetes.go
Normal file
198
provider/kubernetes.go
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Error retrieving ingresses: %+v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
templateObjects := types.Configuration{
|
||||||
|
map[string]*types.Backend{},
|
||||||
|
map[string]*types.Frontend{},
|
||||||
|
}
|
||||||
|
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: "PathPrefixStrip:" + 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 {
|
||||||
|
var protocol string
|
||||||
|
for _, port := range service.Spec.Ports {
|
||||||
|
if port.Port == pa.Backend.ServicePort.IntValue() {
|
||||||
|
protocol = port.Name
|
||||||
|
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) 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
|
||||||
|
}
|
187
provider/kubernetes_test.go
Normal file
187
provider/kubernetes_test.go
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containous/traefik/provider/k8s"
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoadIngresses(t *testing.T) {
|
||||||
|
ingresses := []k8s.Ingress{{
|
||||||
|
Spec: k8s.IngressSpec{
|
||||||
|
Rules: []k8s.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "foo",
|
||||||
|
IngressRuleValue: k8s.IngressRuleValue{
|
||||||
|
HTTP: &k8s.HTTPIngressRuleValue{
|
||||||
|
Paths: []k8s.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/bar",
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: k8s.FromInt(801),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Host: "bar",
|
||||||
|
IngressRuleValue: k8s.IngressRuleValue{
|
||||||
|
HTTP: &k8s.HTTPIngressRuleValue{
|
||||||
|
Paths: []k8s.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service3",
|
||||||
|
ServicePort: k8s.FromInt(803),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service2",
|
||||||
|
ServicePort: k8s.FromInt(802),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
services := []k8s.Service{
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service1",
|
||||||
|
UID: "1",
|
||||||
|
},
|
||||||
|
Spec: k8s.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.1",
|
||||||
|
Ports: []k8s.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 801,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service2",
|
||||||
|
UID: "2",
|
||||||
|
},
|
||||||
|
Spec: k8s.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.2",
|
||||||
|
Ports: []k8s.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 802,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service3",
|
||||||
|
UID: "3",
|
||||||
|
},
|
||||||
|
Spec: k8s.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.3",
|
||||||
|
Ports: []k8s.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 803,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
watchChan := make(chan interface{})
|
||||||
|
client := clientMock{
|
||||||
|
ingresses: ingresses,
|
||||||
|
services: services,
|
||||||
|
watchChan: watchChan,
|
||||||
|
}
|
||||||
|
provider := Kubernetes{}
|
||||||
|
actual, err := provider.loadIngresses(client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &types.Configuration{
|
||||||
|
Backends: map[string]*types.Backend{
|
||||||
|
"foo/bar": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"1": {
|
||||||
|
URL: "http://10.0.0.1:801",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"2": {
|
||||||
|
URL: "http://10.0.0.2:802",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
URL: "http://10.0.0.3:803",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frontends: map[string]*types.Frontend{
|
||||||
|
"foo/bar": {
|
||||||
|
Backend: "foo/bar",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"/bar": {
|
||||||
|
Rule: "PathPrefixStrip:/bar",
|
||||||
|
},
|
||||||
|
"foo": {
|
||||||
|
Rule: "Host:foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
Backend: "bar",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"bar": {
|
||||||
|
Rule: "Host:bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual.Backends, expected.Backends) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", expected.Backends, actual.Backends)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual.Frontends, expected.Frontends) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientMock struct {
|
||||||
|
ingresses []k8s.Ingress
|
||||||
|
services []k8s.Service
|
||||||
|
watchChan chan interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) {
|
||||||
|
return c.ingresses, nil
|
||||||
|
}
|
||||||
|
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
return c.watchChan, make(chan error), nil
|
||||||
|
}
|
||||||
|
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) {
|
||||||
|
return c.services, nil
|
||||||
|
}
|
||||||
|
func (c clientMock) WatchAll(stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
|
return c.watchChan, make(chan error), nil
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containous/traefik/safe"
|
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
@ -81,7 +80,7 @@ func TestKvList(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
keys: []string{"foo", "/baz/"},
|
keys: []string{"foo", "/baz/"},
|
||||||
expected: []string{"foo/baz/biz", "foo/baz/1", "foo/baz/2"},
|
expected: []string{"foo/baz/1", "foo/baz/2"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,9 +256,9 @@ func TestKvWatchTree(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
configChan := make(chan types.ConfigMessage)
|
configChan := make(chan types.ConfigMessage)
|
||||||
safe.Go(func() {
|
go func() {
|
||||||
provider.watchKv(configChan, "prefix", make(chan bool, 1))
|
provider.watchKv(configChan, "prefix", make(chan bool, 1))
|
||||||
})
|
}()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case c1 := <-returnedChans:
|
case c1 := <-returnedChans:
|
||||||
|
@ -339,7 +338,7 @@ func (s *Mock) List(prefix string) ([]*store.KVPair, error) {
|
||||||
}
|
}
|
||||||
kv := []*store.KVPair{}
|
kv := []*store.KVPair{}
|
||||||
for _, kvPair := range s.KVPairs {
|
for _, kvPair := range s.KVPairs {
|
||||||
if strings.HasPrefix(kvPair.Key, prefix) {
|
if strings.HasPrefix(kvPair.Key, prefix) && !strings.ContainsAny(strings.TrimPrefix(kvPair.Key, prefix), "/") {
|
||||||
kv = append(kv, kvPair)
|
kv = append(kv, kvPair)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -365,3 +364,86 @@ func (s *Mock) AtomicDelete(key string, previous *store.KVPair) (bool, error) {
|
||||||
func (s *Mock) Close() {
|
func (s *Mock) Close() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestKVLoadConfig(t *testing.T) {
|
||||||
|
provider := &Kv{
|
||||||
|
Prefix: "traefik",
|
||||||
|
kvclient: &Mock{
|
||||||
|
KVPairs: []*store.KVPair{
|
||||||
|
{
|
||||||
|
Key: "traefik/frontends/frontend.with.dot",
|
||||||
|
Value: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/frontends/frontend.with.dot/backend",
|
||||||
|
Value: []byte("backend.with.dot.too"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/frontends/frontend.with.dot/routes",
|
||||||
|
Value: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/frontends/frontend.with.dot/routes/route.with.dot",
|
||||||
|
Value: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/frontends/frontend.with.dot/routes/route.with.dot/rule",
|
||||||
|
Value: []byte("Host:test.localhost"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/backends/backend.with.dot.too",
|
||||||
|
Value: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/backends/backend.with.dot.too/servers",
|
||||||
|
Value: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot",
|
||||||
|
Value: []byte(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/url",
|
||||||
|
Value: []byte("http://172.17.0.2:80"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "traefik/backends/backend.with.dot.too/servers/server.with.dot/weight",
|
||||||
|
Value: []byte("1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual := provider.loadConfig()
|
||||||
|
expected := &types.Configuration{
|
||||||
|
Backends: map[string]*types.Backend{
|
||||||
|
"backend.with.dot.too": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"server.with.dot": {
|
||||||
|
URL: "http://172.17.0.2:80",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Frontends: map[string]*types.Frontend{
|
||||||
|
"frontend.with.dot": {
|
||||||
|
Backend: "backend.with.dot.too",
|
||||||
|
PassHostHeader: false,
|
||||||
|
EntryPoints: []string{},
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"route.with.dot": {
|
||||||
|
Rule: "Host:test.localhost",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual.Backends, expected.Backends) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", expected.Backends, actual.Backends)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual.Frontends, expected.Frontends) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ func TestConfigurationErrors(t *testing.T) {
|
||||||
Filename: templateInvalidTOMLFile.Name(),
|
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{
|
funcMap: template.FuncMap{
|
||||||
"Foo": func() string {
|
"Foo": func() string {
|
||||||
return "bar"
|
return "bar"
|
||||||
|
|
|
@ -22,4 +22,4 @@ if [ -z "$DATE" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build binaries
|
# Build binaries
|
||||||
CGO_ENABLED=0 GOGC=off go build $FLAGS -ldflags "-X main.Version=$VERSION -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik .
|
CGO_ENABLED=0 GOGC=off go build $FLAGS -ldflags "-s -w -X main.Version=$VERSION -X main.BuildDate=$DATE" -a -installsuffix nocgo -o dist/traefik .
|
||||||
|
|
|
@ -236,6 +236,9 @@ func (server *Server) configureProviders() {
|
||||||
if server.globalConfiguration.Boltdb != nil {
|
if server.globalConfiguration.Boltdb != nil {
|
||||||
server.providers = append(server.providers, server.globalConfiguration.Boltdb)
|
server.providers = append(server.providers, server.globalConfiguration.Boltdb)
|
||||||
}
|
}
|
||||||
|
if server.globalConfiguration.Kubernetes != nil {
|
||||||
|
server.providers = append(server.providers, server.globalConfiguration.Kubernetes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) startProviders() {
|
func (server *Server) startProviders() {
|
||||||
|
|
16
templates/kubernetes.tmpl
Normal file
16
templates/kubernetes.tmpl
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[backends]{{range $backendName, $backend := .Backends}}
|
||||||
|
{{range $serverName, $server := $backend.Servers}}
|
||||||
|
[backends."{{$backendName}}".servers."{{$serverName}}"]
|
||||||
|
url = "{{$server.URL}}"
|
||||||
|
weight = {{$server.Weight}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
[frontends]{{range $frontendName, $frontend := .Frontends}}
|
||||||
|
[frontends."{{$frontendName}}"]
|
||||||
|
backend = "{{$frontend.Backend}}"
|
||||||
|
{{range $routeName, $route := $frontend.Routes}}
|
||||||
|
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||||
|
rule = "{{$route.Rule}}"
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
|
@ -1,19 +1,19 @@
|
||||||
{{$frontends := List .Prefix "/frontends/" }}
|
{{$frontends := List .Prefix "/frontends/" }}
|
||||||
{{$backends := List .Prefix "/backends/"}}
|
{{$backends := List .Prefix "/backends/"}}
|
||||||
|
|
||||||
{{range $backends}}
|
[backends]{{range $backends}}
|
||||||
{{$backend := .}}
|
{{$backend := .}}
|
||||||
{{$servers := List $backend "/servers/" }}
|
{{$servers := List $backend "/servers/" }}
|
||||||
|
|
||||||
{{$circuitBreaker := Get "" . "/circuitbreaker/" "expression"}}
|
{{$circuitBreaker := Get "" . "/circuitbreaker/" "expression"}}
|
||||||
{{with $circuitBreaker}}
|
{{with $circuitBreaker}}
|
||||||
[backends.{{Last $backend}}.circuitBreaker]
|
[backends."{{Last $backend}}".circuitBreaker]
|
||||||
expression = "{{$circuitBreaker}}"
|
expression = "{{$circuitBreaker}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{$loadBalancer := Get "" . "/loadbalancer/" "method"}}
|
{{$loadBalancer := Get "" . "/loadbalancer/" "method"}}
|
||||||
{{with $loadBalancer}}
|
{{with $loadBalancer}}
|
||||||
[backends.{{Last $backend}}.loadBalancer]
|
[backends."{{Last $backend}}".loadBalancer]
|
||||||
method = "{{$loadBalancer}}"
|
method = "{{$loadBalancer}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -21,14 +21,14 @@
|
||||||
{{$maxConnExtractorFunc := Get "" . "/maxconn/" "extractorfunc"}}
|
{{$maxConnExtractorFunc := Get "" . "/maxconn/" "extractorfunc"}}
|
||||||
{{with $maxConnAmt}}
|
{{with $maxConnAmt}}
|
||||||
{{with $maxConnExtractorFunc}}
|
{{with $maxConnExtractorFunc}}
|
||||||
[backends.{{Last $backend}}.maxConn]
|
[backends."{{Last $backend}}".maxConn]
|
||||||
amount = {{$maxConnAmt}}
|
amount = {{$maxConnAmt}}
|
||||||
extractorFunc = "{{$maxConnExtractorFunc}}"
|
extractorFunc = "{{$maxConnExtractorFunc}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{range $servers}}
|
{{range $servers}}
|
||||||
[backends.{{Last $backend}}.servers.{{Last .}}]
|
[backends."{{Last $backend}}".servers."{{Last .}}"]
|
||||||
url = "{{Get "" . "/url"}}"
|
url = "{{Get "" . "/url"}}"
|
||||||
weight = {{Get "" . "/weight"}}
|
weight = {{Get "" . "/weight"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -323,6 +323,26 @@
|
||||||
# [marathon.TLS]
|
# [marathon.TLS]
|
||||||
# InsecureSkipVerify = true
|
# InsecureSkipVerify = true
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# Kubernetes Ingress configuration backend
|
||||||
|
################################################################
|
||||||
|
# Enable Kubernetes Ingress configuration backend
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
#
|
||||||
|
# [kubernetes]
|
||||||
|
|
||||||
|
# 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
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
# endpoint = "http://localhost:8080"
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Consul KV configuration backend
|
# Consul KV configuration backend
|
||||||
|
|
|
@ -40,10 +40,10 @@
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/containous/traefik/blob/master/docs/index.md" target="_blank">Documentation</a>
|
<a href="https://docs.traefik.io" target="_blank">Documentation</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="http://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>
|
<a href="https://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue