Merge pull request #341 from errm/k8s-namespacing
Adds option to namespace k8s ingresses
This commit is contained in:
commit
8a39ee65cd
6 changed files with 449 additions and 3 deletions
1
cmd.go
1
cmd.go
|
@ -172,6 +172,7 @@ func init() {
|
||||||
|
|
||||||
traefikCmd.PersistentFlags().BoolVar(&arguments.kubernetes, "kubernetes", false, "Enable Kubernetes backend")
|
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")
|
traefikCmd.PersistentFlags().StringVar(&arguments.Kubernetes.Endpoint, "kubernetes.endpoint", "127.0.0.1:8080", "Kubernetes server endpoint")
|
||||||
|
traefikCmd.PersistentFlags().StringSliceVar(&arguments.Kubernetes.Namespaces, "kubernetes.namespaces", []string{}, "Kubernetes namespaces")
|
||||||
|
|
||||||
_ = 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"))
|
||||||
|
|
|
@ -648,6 +648,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# endpoint = "http://localhost:8080"
|
# endpoint = "http://localhost:8080"
|
||||||
|
# namespaces = ["default","production"]
|
||||||
```
|
```
|
||||||
|
|
||||||
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).
|
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).
|
||||||
|
|
|
@ -49,7 +49,7 @@ func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIngresses returns all services in the cluster
|
// GetIngresses returns all ingresses in the cluster
|
||||||
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
|
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
|
||||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ const (
|
||||||
type Kubernetes struct {
|
type Kubernetes struct {
|
||||||
BaseProvider `mapstructure:",squash"`
|
BaseProvider `mapstructure:",squash"`
|
||||||
Endpoint string
|
Endpoint string
|
||||||
|
Namespaces []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||||
|
@ -123,7 +124,15 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||||
|
|
||||||
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
|
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
|
||||||
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
||||||
|
if len(provider.Namespaces) == 0 {
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
|
for _, n := range provider.Namespaces {
|
||||||
|
if ingress.ObjectMeta.Namespace == n {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error retrieving ingresses: %+v", err)
|
log.Errorf("Error retrieving ingresses: %+v", err)
|
||||||
|
|
|
@ -167,6 +167,434 @@ func TestLoadIngresses(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadNamespacedIngresses(t *testing.T) {
|
||||||
|
ingresses := []k8s.Ingress{
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "awesome",
|
||||||
|
},
|
||||||
|
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(443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service2",
|
||||||
|
ServicePort: k8s.FromInt(802),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "not-awesome",
|
||||||
|
},
|
||||||
|
Spec: k8s.IngressSpec{
|
||||||
|
Rules: []k8s.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "baz",
|
||||||
|
IngressRuleValue: k8s.IngressRuleValue{
|
||||||
|
HTTP: &k8s.HTTPIngressRuleValue{
|
||||||
|
Paths: []k8s.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/baz",
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: k8s.FromInt(801),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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{
|
||||||
|
{
|
||||||
|
Port: 802,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service3",
|
||||||
|
UID: "3",
|
||||||
|
},
|
||||||
|
Spec: k8s.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.3",
|
||||||
|
Ports: []k8s.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
watchChan := make(chan interface{})
|
||||||
|
client := clientMock{
|
||||||
|
ingresses: ingresses,
|
||||||
|
services: services,
|
||||||
|
watchChan: watchChan,
|
||||||
|
}
|
||||||
|
provider := Kubernetes{
|
||||||
|
Namespaces: []string{"awesome"},
|
||||||
|
}
|
||||||
|
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: "https://10.0.0.3:443",
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actualJSON, _ := json.Marshal(actual)
|
||||||
|
expectedJSON, _ := json.Marshal(expected)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadMultipleNamespacedIngresses(t *testing.T) {
|
||||||
|
ingresses := []k8s.Ingress{
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "awesome",
|
||||||
|
},
|
||||||
|
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(443),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service2",
|
||||||
|
ServicePort: k8s.FromInt(802),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "somewhat-awesome",
|
||||||
|
},
|
||||||
|
Spec: k8s.IngressSpec{
|
||||||
|
Rules: []k8s.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "awesome",
|
||||||
|
IngressRuleValue: k8s.IngressRuleValue{
|
||||||
|
HTTP: &k8s.HTTPIngressRuleValue{
|
||||||
|
Paths: []k8s.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/quix",
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: k8s.FromInt(801),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Namespace: "not-awesome",
|
||||||
|
},
|
||||||
|
Spec: k8s.IngressSpec{
|
||||||
|
Rules: []k8s.IngressRule{
|
||||||
|
{
|
||||||
|
Host: "baz",
|
||||||
|
IngressRuleValue: k8s.IngressRuleValue{
|
||||||
|
HTTP: &k8s.HTTPIngressRuleValue{
|
||||||
|
Paths: []k8s.HTTPIngressPath{
|
||||||
|
{
|
||||||
|
Path: "/baz",
|
||||||
|
Backend: k8s.IngressBackend{
|
||||||
|
ServiceName: "service1",
|
||||||
|
ServicePort: k8s.FromInt(801),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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{
|
||||||
|
{
|
||||||
|
Port: 802,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: k8s.ObjectMeta{
|
||||||
|
Name: "service3",
|
||||||
|
UID: "3",
|
||||||
|
},
|
||||||
|
Spec: k8s.ServiceSpec{
|
||||||
|
ClusterIP: "10.0.0.3",
|
||||||
|
Ports: []k8s.ServicePort{
|
||||||
|
{
|
||||||
|
Name: "http",
|
||||||
|
Port: 443,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
watchChan := make(chan interface{})
|
||||||
|
client := clientMock{
|
||||||
|
ingresses: ingresses,
|
||||||
|
services: services,
|
||||||
|
watchChan: watchChan,
|
||||||
|
}
|
||||||
|
provider := Kubernetes{
|
||||||
|
Namespaces: []string{"awesome", "somewhat-awesome"},
|
||||||
|
}
|
||||||
|
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: "https://10.0.0.3:443",
|
||||||
|
Weight: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CircuitBreaker: nil,
|
||||||
|
LoadBalancer: nil,
|
||||||
|
},
|
||||||
|
"awesome/quix": {
|
||||||
|
Servers: map[string]types.Server{
|
||||||
|
"1": {
|
||||||
|
URL: "http://10.0.0.1:801",
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"awesome/quix": {
|
||||||
|
Backend: "awesome/quix",
|
||||||
|
Routes: map[string]types.Route{
|
||||||
|
"/quix": {
|
||||||
|
Rule: "PathPrefixStrip:/quix",
|
||||||
|
},
|
||||||
|
"awesome": {
|
||||||
|
Rule: "Host:awesome",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actualJSON, _ := json.Marshal(actual)
|
||||||
|
expectedJSON, _ := json.Marshal(expected)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("expected %+v, got %+v", string(expectedJSON), string(actualJSON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type clientMock struct {
|
type clientMock struct {
|
||||||
ingresses []k8s.Ingress
|
ingresses []k8s.Ingress
|
||||||
services []k8s.Service
|
services []k8s.Service
|
||||||
|
@ -174,7 +602,13 @@ type clientMock struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) {
|
func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) {
|
||||||
return c.ingresses, nil
|
var ingresses []k8s.Ingress
|
||||||
|
for _, ingress := range c.ingresses {
|
||||||
|
if predicate(ingress) {
|
||||||
|
ingresses = append(ingresses, ingress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ingresses, nil
|
||||||
}
|
}
|
||||||
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
|
||||||
return c.watchChan, make(chan error), nil
|
return c.watchChan, make(chan error), nil
|
||||||
|
|
|
@ -343,6 +343,7 @@
|
||||||
# Optional
|
# Optional
|
||||||
#
|
#
|
||||||
# endpoint = "http://localhost:8080"
|
# endpoint = "http://localhost:8080"
|
||||||
|
# namespaces = ["default"]
|
||||||
|
|
||||||
################################################################
|
################################################################
|
||||||
# Consul KV configuration backend
|
# Consul KV configuration backend
|
||||||
|
|
Loading…
Reference in a new issue