Adds option to namespace k8s ingresses
If the flag kubernetes.namespaces is set... Then we only select ingresses from that/those namespace(s) This allows multiple instances of traefik to independently load balance for each namespace. This could be for logical or security reasons. Addresses #336
This commit is contained in:
parent
d1b0bece47
commit
301a463aeb
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().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("graceTimeOut", traefikCmd.PersistentFlags().Lookup("graceTimeOut"))
|
||||
|
|
|
@ -648,6 +648,7 @@ Træfɪk can be configured to use Kubernetes Ingress as a backend configuration:
|
|||
# Optional
|
||||
#
|
||||
# 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).
|
||||
|
|
|
@ -49,7 +49,7 @@ func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
|
|||
}, 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) {
|
||||
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ const (
|
|||
type Kubernetes struct {
|
||||
BaseProvider `mapstructure:",squash"`
|
||||
Endpoint string
|
||||
Namespaces []string
|
||||
}
|
||||
|
||||
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) {
|
||||
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
||||
return true
|
||||
if len(provider.Namespaces) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, n := range provider.Namespaces {
|
||||
if ingress.ObjectMeta.Namespace == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
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 {
|
||||
ingresses []k8s.Ingress
|
||||
services []k8s.Service
|
||||
|
@ -174,7 +602,13 @@ type clientMock struct {
|
|||
}
|
||||
|
||||
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) {
|
||||
return c.watchChan, make(chan error), nil
|
||||
|
|
|
@ -343,6 +343,7 @@
|
|||
# Optional
|
||||
#
|
||||
# endpoint = "http://localhost:8080"
|
||||
# namespaces = ["default"]
|
||||
|
||||
################################################################
|
||||
# Consul KV configuration backend
|
||||
|
|
Loading…
Reference in a new issue