This commit is contained in:
dev 2019-04-16 16:19:51 -04:00
commit 835b4c018c
27 changed files with 962 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.DS_Store
bin
vendor

3
Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM scratch
COPY ./bin/ingress-controller .
ENTRYPOINT ["/ingress-controller"]

6
Makefile Normal file
View File

@ -0,0 +1,6 @@
build:
@mkdir -p bin
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ./bin/ingress-controller ./cmd/caddy
publish-docker:
@echo 'TODO :- not implemented'

132
cmd/caddy/main.go Normal file
View File

@ -0,0 +1,132 @@
package main
import (
"os"
"time"
"bitbucket.org/lightcodelabs/ingress/internal/controller"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog"
)
const (
// High enough QPS to fit all expected use cases. QPS=0 is not set here, because
// client code is overriding it.
defaultQPS = 1e6
// High enough Burst to fit all expected use cases. Burst=0 is not set here, because
// client code is overriding it.
defaultBurst = 1e6
)
func main() {
klog.InitFlags(nil)
// get the namespace to monitor ingress resources for
namespace := os.Getenv("KUBERNETES_NAMESPACE")
if len(namespace) == 0 {
namespace = v1.NamespaceAll
klog.Warning("KUBERNETES_NAMESPACE is unset, will monitor ingresses in all namespaces.")
}
// TODO :- implement
// parse any flags required to configure the caddy ingress controller
// cfg, err := parseFlags()
// if err != nil {
// klog.Fatal(err)
// }
// get client to access the kubernetes service api
kubeClient, err := createApiserverClient()
if err != nil {
msg := `
Error while initiating a connection to the Kubernetes API server.
This could mean the cluster is misconfigured (e.g. it has invalid API server certificates or Service Accounts configuration)
`
klog.Fatalf(msg, err)
}
var resource = "ingresses"
restClient := kubeClient.ExtensionsV1beta1().RESTClient()
// start ingress controller
c := controller.NewCaddyController(namespace, kubeClient, resource, restClient)
// TODO :-
// create http server to expose controller health metrics
klog.Info("Starting the caddy ingress controller")
// start the ingress controller
stopCh := make(chan struct{}, 1)
defer close(stopCh)
go c.Run(stopCh)
select {}
}
// createApiserverClient creates a new Kubernetes REST client. We assume the
// controller runs inside Kubernetes and use the in-cluster config.
func createApiserverClient() (*kubernetes.Clientset, error) {
cfg, err := clientcmd.BuildConfigFromFlags("", "")
if err != nil {
return nil, err
}
cfg.QPS = defaultQPS
cfg.Burst = defaultBurst
cfg.ContentType = "application/vnd.kubernetes.protobuf"
klog.Infof("Creating API client for %s", cfg.Host)
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
// The client may fail to connect to the API server in the first request
defaultRetry := wait.Backoff{
Steps: 10,
Duration: 1 * time.Second,
Factor: 1.5,
Jitter: 0.1,
}
klog.V(2).Info("Trying to discover Kubernetes version")
var v *version.Info
var retries int
var lastErr error
err = wait.ExponentialBackoff(defaultRetry, func() (bool, error) {
v, err = client.Discovery().ServerVersion()
if err == nil {
return true, nil
}
lastErr = err
klog.V(2).Infof("Unexpected error discovering Kubernetes version (attempt %v): %v", retries, err)
retries++
return false, nil
})
// err is returned in case of timeout in the exponential backoff (ErrWaitTimeout)
if err != nil {
return nil, lastErr
}
// this should not happen, warn the user
if retries > 0 {
klog.Warningf("Initial connection to the Kubernetes API server was retried %d times.", retries)
}
msg := "Running in Kubernetes cluster version v%v.%v (%v) - git (%v) commit %v - platform %v"
klog.Infof(msg, v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)
return client, nil
}

31
go.mod Normal file
View File

@ -0,0 +1,31 @@
module bitbucket.org/lightcodelabs/ingress
go 1.12
require (
github.com/gogo/protobuf v1.2.1 // indirect
github.com/golang/protobuf v1.3.1 // indirect
github.com/google/gofuzz v1.0.0 // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/json-iterator/go v1.1.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/openshift/api v3.9.0+incompatible // indirect
github.com/sirupsen/logrus v1.4.1 // indirect
github.com/spf13/pflag v1.0.3
github.com/stakater/IngressMonitorController v1.0.62
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a // indirect
golang.org/x/net v0.0.0-20190415100556-4a65cf94b679 // indirect
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
k8s.io/klog v0.3.0
k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
)

123
go.sum Normal file
View File

@ -0,0 +1,123 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE=
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs=
github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stakater/IngressMonitorController v1.0.62 h1:KLC79Qo8YNEcNehPSEDrZ1vEtqPux1fnNnXC4m43Lhk=
github.com/stakater/IngressMonitorController v1.0.62/go.mod h1:LXpqENu96n+xgxMr1GGSRKydCrVaszM21Y8PKTb+JK8=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190415100556-4a65cf94b679 h1:tzVWzOrXxwAwdSCMrf+mbNrZFxwS0+HLP4m2qxtfdhk=
golang.org/x/net v0.0.0-20190415100556-4a65cf94b679/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo=
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/api v0.0.0-20190415132514-c2f1300cac21 h1:XGJCFakX0XXD6V2frIsafsr96sDU1q6Gcvm2JaFQVyM=
k8s.io/api v0.0.0-20190415132514-c2f1300cac21/go.mod h1:5HMaKNcWJji8AGOBjOZxmFDwRutMItn4MrrXZcFANNo=
k8s.io/api v0.0.0-20190417212520-7b2abe144378 h1:5MbuLXn4OcNwi6vnf6OqDuMvZQQFMG5Bl97Ji6U+v/I=
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA=
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.0.0-20190415132420-07d458fe0356 h1:IneIG23feOS594nIIsJVRUHK50ALz/g0/Co/3iML7RI=
k8s.io/apimachinery v0.0.0-20190415132420-07d458fe0356/go.mod h1:jLjiXl596L+wVGdx8dRx6sSNnabzqnI2+nAJqqqyesQ=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ=
k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.0.0-20190306015804-8e90cee79f82/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7 h1:8r+l4bNWjRlsFYlQJnKJ2p7s1YQPj4XyXiJVqDHRx7c=
k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View File

@ -0,0 +1,24 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: example
labels:
app: example
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
containers:
- name: httpecho
image: hashicorp/http-echo
args:
- "-listen=:8080"
- "-text=hello world"
ports:
- containerPort: 8080

View File

@ -0,0 +1,13 @@
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example
spec:
rules:
- host: hello-world.xyz
http:
paths:
- path: /*
backend:
serviceName: example
servicePort: 8080

View File

@ -0,0 +1,12 @@
kind: Service
apiVersion: v1
metadata:
name: example
spec:
type: NodePort
selector:
app: example
ports:
- protocol: TCP
port: 80
targetPort: 8080

View File

@ -0,0 +1,97 @@
package controller
import (
"fmt"
"k8s.io/api/extensions/v1beta1"
)
func (c *CaddyController) onResourceAdded(obj interface{}) {
c.syncQueue.Add(ResourceAddedAction{
resource: obj,
})
}
func (c *CaddyController) onResourceUpdated(old interface{}, new interface{}) {
c.syncQueue.Add(ResourceUpdatedAction{
resource: new,
oldResource: old,
})
}
func (c *CaddyController) onResourceDeleted(obj interface{}) {
c.syncQueue.Add(ResourceDeletedAction{
resource: obj,
})
}
func (c *CaddyController) onSyncStatus(obj interface{}) {
c.syncQueue.Add(SyncStatusAction{})
}
// Action is an interface for ingress actions
type Action interface {
handle(c *CaddyController) error
}
// ResourceAddedAction provides an implementation of the action interface
type ResourceAddedAction struct {
resource interface{}
}
// ResourceUpdatedAction provides an implementation of the action interface
type ResourceUpdatedAction struct {
resource interface{}
oldResource interface{}
}
// ResourceDeletedAction provides an implementation of the action interface
type ResourceDeletedAction struct {
resource interface{}
}
func (r ResourceAddedAction) handle(c *CaddyController) error {
// configure caddy to handle this resource
ing, ok := r.resource.(*v1beta1.Ingress)
if !ok {
return fmt.Errorf("ResourceAddedAction: incoming resource is not of type ingress")
}
// 1. Parse ingress resource and convert to obj to configure caddy with
// 2. Get current caddy config for rollback purposes
// 3. Update internal caddy config
// 4. Get ingress controller publish address
// 5. call syncIngress for this specific resource
// 6. Add this ingress to resource store
c.resourceStore.AddIngress(ing)
// ~~~~
// when updating caddy config the ingress controller should bypass kube-proxy and get the ip address of
// the pod that the deployment we are proxying to is running on so that we can proxy to that ip address port.
// this is good for session affinity and increases performance (since we don't have to hit dns).
// example getting an ingress
// ingClient := c.kubeClient.ExtensionsV1beta1().Ingresses(c.namespace) // get a client to update the ingress
// ingClient.UpdateStatus(ing) // pass an ingress with the status.address field updated
// ~~~
return nil
}
func (r ResourceUpdatedAction) handle(c *CaddyController) error {
// find the caddy config related to the oldResource and update it
fmt.Printf("\nUpdated resource:\n +%v\n\nOld resource: \n %+v\n", r.resource, r.oldResource)
return nil
}
func (r ResourceDeletedAction) handle(c *CaddyController) error {
// delete all resources from caddy config that are associated with this resource
// reload caddy config
fmt.Printf("\nDeleted resource:\n +%v\n", r.resource)
return nil
}

View File

@ -0,0 +1,160 @@
package controller
import (
"fmt"
"os"
"time"
"bitbucket.org/lightcodelabs/ingress/internal/store"
"k8s.io/api/extensions/v1beta1"
"k8s.io/apimachinery/pkg/fields"
run "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog"
)
// ResourceMap are resources from where changes are going to be detected
var ResourceMap = map[string]run.Object{
"ingresses": &v1beta1.Ingress{},
}
// how often we should attempt to keep ingress resource's source address in sync
const syncInterval = time.Second * 60
// CaddyController represents an caddy ingress controller.
type CaddyController struct {
resourceStore *store.Store
kubeClient *kubernetes.Clientset
namespace string
indexer cache.Indexer
syncQueue workqueue.RateLimitingInterface
statusQueue workqueue.RateLimitingInterface // statusQueue performs ingress status updates every 60 seconds but inserts the work into the sync queue
informer cache.Controller
}
// NewCaddyController returns an instance of the caddy ingress controller.
func NewCaddyController(namespace string, kubeClient *kubernetes.Clientset, resource string, restClient rest.Interface) *CaddyController {
controller := &CaddyController{
kubeClient: kubeClient,
namespace: namespace,
syncQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
statusQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
}
ingressListWatcher := cache.NewListWatchFromClient(restClient, resource, namespace, fields.Everything())
indexer, informer := cache.NewIndexerInformer(ingressListWatcher, ResourceMap[resource], 0, cache.ResourceEventHandlerFuncs{
AddFunc: controller.onResourceAdded,
UpdateFunc: controller.onResourceUpdated,
DeleteFunc: controller.onResourceDeleted,
}, cache.Indexers{})
controller.indexer = indexer
controller.informer = informer
controller.resourceStore = store.NewStore(controller.kubeClient)
// =======
// TODO :- get info of the current pod, we'll need the ip address so we can forward requests to this ingress
// controller
// podInfo, err := k8s.GetPodDetails(kubeClient)
// if err != nil {
// klog.Fatalf("Unexpected error obtaining pod information: %v", err)
// }
// =======
// TODO :- attempt to do initial sync with ingresses here
return controller
}
// Shutdown stops the caddy controller.
func (c *CaddyController) Shutdown() error {
// TODO :- implement a graceful shutdown for the ingress controller and caddy server
// shutdown statusQueue
// shutdown syncQueue
// shutdownCaddy
return nil
}
// handleErrs reports errors received from queue actions.
func (c *CaddyController) handleErr(err error, action interface{}) {
klog.Error(err)
}
// Run method starts the ingress controller.
func (c *CaddyController) Run(stopCh chan struct{}) {
klog.Info("starting caddy ingress controller")
// TODO :- start an instance of caddy server
defer runtime.HandleCrash()
defer c.syncQueue.ShutDown()
// start the ingress informer where we listen to new / updated ingress resources
go c.informer.Run(stopCh)
// wait for all involved caches to be synced, before processing items from the queue is started
if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
runtime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
return
}
// start processing events for syncing ingress resources
go wait.Until(c.runWorker, time.Second, stopCh)
// start ingress status syncher
go wait.Until(c.dispatchSync, syncInterval, stopCh)
<-stopCh
klog.Info("stopping ingress controller")
exitCode := 0
err := c.Shutdown()
if err != nil {
klog.Errorf("could not shutdown ingress controller properly")
exitCode = 1
}
os.Exit(exitCode)
}
// TODO :- copy this for the status updater for ingress controllers
// every 60 seconds attempt to update the statusIP for ingresses
// add into the syncqueue
// process items in the event queue
func (c *CaddyController) runWorker() {
for c.processNextItem() {
}
}
// if there is an ingress item in the event queue process it
func (c *CaddyController) processNextItem() bool {
// Wait until there is a new item in the working queue
action, quit := c.syncQueue.Get()
if quit {
return false
}
// Tell the queue that we are done with processing this key. This unblocks the key for other workers
// This allows safe parallel processing because two ingresses with the same key are never processed in
// parallel.
defer c.syncQueue.Done(action)
// Invoke the method containing the business logic
err := action.(Action).handle(c)
if err != nil {
c.handleErr(err, action)
}
return true
}

View File

@ -0,0 +1,28 @@
package controller
import (
"fmt"
"k8s.io/api/extensions/v1beta1"
)
// dispatchSync is run every syncInterval duration to sync ingress source address fields.
func (c *CaddyController) dispatchSync() {
c.syncQueue.Add(SyncStatusAction{})
}
// SyncStatusAction provides an implementation of the action interface
type SyncStatusAction struct {
}
func (r SyncStatusAction) handle(c *CaddyController) error {
c.syncStatus(c.resourceStore.Ingresses)
return nil
}
// syncStatus ensures that the ingress source address points to this ingress controller's IP address.
func (c *CaddyController) syncStatus(ings []*v1beta1.Ingress) {
// TODO :- update source address to ingress controller published address
fmt.Println("Handle Synching Ingress Source Address")
}

View File

@ -0,0 +1,3 @@
package ingress
// handle getting ingress information here

49
internal/store/store.go Normal file
View File

@ -0,0 +1,49 @@
package store
import (
"k8s.io/api/extensions/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/klog"
)
// Store represents a collection of ingresses and secrets that we are monitoring.
type Store struct {
Ingresses []*v1beta1.Ingress
Secrets []interface{} // TODO :- should we store the secrets in the ingress object?
}
// NewStore returns a new store that keeps track of ingresses and secrets. It will attempt to get
// all current ingresses before returning.
func NewStore(kubeClient *kubernetes.Clientset) *Store {
ingresses, err := kubeClient.ExtensionsV1beta1().Ingresses("").List(v1.ListOptions{})
if err != nil {
klog.Errorf("could not get existing ingresses in cluster")
return &Store{}
}
s := &Store{
Ingresses: make([]*v1beta1.Ingress, len(ingresses.Items)-1),
}
for _, i := range ingresses.Items {
s.Ingresses = append(s.Ingresses, &i)
}
return s
}
// AddIngress adds an ingress to the store
func (s *Store) AddIngress(ing *v1beta1.Ingress) {
isUniq := true
for _, i := range s.Ingresses {
if i.GetUID() == ing.GetUID() {
isUniq = false
}
}
if isUniq {
s.Ingresses = append(s.Ingresses, ing)
}
}

View File

@ -0,0 +1,27 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: caddyingresscontroller-role
rules:
- apiGroups:
- ""
- "extensions"
resources:
- ingresses
- routes
verbs:
- list
- get
- update
- patch
- watch
- delete
- apiGroups:
- ""
resources:
- services
- pods
verbs:
- list
- get
- watch

View File

@ -0,0 +1,12 @@
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: caddyingresscontroller-role-binding
roleRef:
kind: ClusterRole
name: caddyingresscontroller-role
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: caddyingresscontroller
namespace: default

View File

@ -0,0 +1,34 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: caddyingresscontroller
labels:
app: caddyIngressController
chart: "caddyingresscontroller-v0.1.0"
release: "release-name"
heritage: "Tiller"
version: v0.1.0
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
app: caddyIngressController
release: "release-name"
template:
metadata:
labels:
app: caddyIngressController
chart: "caddyingresscontroller-v0.1.0"
release: "release-name"
heritage: "Tiller"
version: v0.1.0
spec:
containers:
- env:
image: "caddy/ingresscontroller"
imagePullPolicy: IfNotPresent
name: caddyingresscontroller
serviceAccountName: caddyingresscontroller

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: caddyingresscontroller
labels:
app: caddyIngressController
chart: "caddyingresscontroller-v0.1.0"
release: "release-name"
heritage: "Tiller"
version: v0.1.0

View File

@ -0,0 +1,4 @@
apiVersion: v1
description: A helm chart for the Caddy Kubernetes ingress controller
name: caddyingresscontroller
version: v0.1.0

View File

@ -0,0 +1,29 @@
{{- if and ( .Values.caddyingresscontroller.rbac.create ) (eq .Values.caddyingresscontroller.watchNamespace "") }}
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: {{ .Values.name }}-role
rules:
- apiGroups:
- ""
- "extensions"
resources:
- ingresses
- routes
verbs:
- list
- get
- update
- patch
- watch
- delete
- apiGroups:
- ""
resources:
- services
- pods
verbs:
- list
- get
- watch
{{- end }}

View File

@ -0,0 +1,14 @@
{{- if and ( .Values.caddyingresscontroller.rbac.create ) (eq .Values.caddyingresscontroller.watchNamespace "") }}
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: {{ .Values.name }}-role-binding
roleRef:
kind: ClusterRole
name: {{ .Values.name }}-role
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: {{ .Values.serviceAccountName }}
namespace: {{ .Release.Namespace }}
{{- end }}

View File

@ -0,0 +1,40 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: {{ .Values.name }}
labels:
app: {{ .Values.name }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name | quote }}
heritage: {{ .Release.Service | quote }}
{{- if .Values.caddyingresscontroller.deployment.labels }}
{{ toYaml .Values.caddyingresscontroller.deployment.labels | indent 4 }}
{{- end }}
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
app: {{ .Values.name }}
release: {{ .Release.Name | quote }}
template:
metadata:
labels:
app: {{ .Values.name }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name | quote }}
heritage: {{ .Release.Service | quote }}
{{- if .Values.caddyingresscontroller.deployment.labels }}
{{ toYaml .Values.caddyingresscontroller.deployment.labels | indent 8 }}
{{- end }}
spec:
containers:
- env:
{{- if .Values.caddyingresscontroller.watchNamespace }}
- name: KUBERNETES_NAMESPACE
value: {{ .Values.caddyingresscontroller.watchNamespace | quote }}
{{- end }}
image: "{{ .Values.caddyingresscontroller.image.name }}:{{ .Values.caddyingresscontroller.image.tag }}"
imagePullPolicy: {{ .Values.caddyingresscontroller.image.pullPolicy }}
name: {{ .Values.name }}
serviceAccountName: {{ .Values.serviceAccountName }}

View File

@ -0,0 +1,30 @@
{{- if and ( .Values.caddyingresscontroller.rbac.create ) (.Values.caddyingresscontroller.watchNamespace) }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ .Values.name }}-role
namespace: {{ .Values.caddyingresscontroller.watchNamespace | quote }}
rules:
- apiGroups:
- ""
- "extensions"
resources:
- ingresses
- routes
verbs:
- list
- get
- update
- patch
- watch
- delete
- apiGroups:
- ""
resources:
- services
- pods
verbs:
- list
- get
- watch
{{- end }}

View File

@ -0,0 +1,15 @@
{{- if and ( .Values.caddyingresscontroller.rbac.create ) (.Values.caddyingresscontroller.watchNamespace) }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ .Values.name }}-role-binding
namespace: {{ .Values.caddyingresscontroller.watchNamespace | quote }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ .Values.name }}-role
subjects:
- kind: ServiceAccount
name: {{ .Values.serviceAccountName }}
namespace: {{ .Release.Namespace | quote }}
{{- end }}

View File

@ -0,0 +1,17 @@
{{- if .Values.caddyingresscontroller.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: {{ .Values.name }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: {{ .Release.Name | quote }}
heritage: {{ .Release.Service | quote }}
{{- if .Values.caddyingresscontroller.serviceAccount.labels }}
{{ toYaml .Values.caddyingresscontroller.serviceAccount.labels | indent 4 }}
{{- end }}
{{- if .Values.caddyingresscontroller.matchLabels }}
{{ toYaml .Values.caddyingresscontroller.matchLabels | indent 4 }}
{{- end }}
name: {{ .Values.serviceAccountName }}
{{- end }}

View File

@ -0,0 +1,34 @@
# Default values for caddyingresscontroller.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
kubernetes:
host: https://kubernetes.default
caddyingresscontroller:
tolerations: {}
watchNamespace: ""
deployment:
labels:
version: "v0.1.0"
config:
labels:
version: "v0.1.0"
rbac:
create: true
# Service account config for the agent pods
serviceAccount:
# Specifies whether a ServiceAccount should be created
create: true
labels:
version: "v0.1.0"
# The name of the ServiceAccount to use.
# If not set and create is true, a name is generated using the fullname template
name: caddyIngressController
image:
name: caddy/ingresscontroller
tag: "v0.1.0"
pullPolicy: IfNotPresent
name: "caddyingresscontroller"
serviceAccountName: "caddyingresscontroller"

12
skaffold.yaml Normal file
View File

@ -0,0 +1,12 @@
apiVersion: skaffold/v1beta8
kind: Config
build:
artifacts:
- image: caddy/ingresscontroller
deploy:
kubectl:
manifests:
- kubernetes/generated/clusterrole.yaml
- kubernetes/generated/clusterrolebinding.yaml
- kubernetes/generated/deployment.yaml
- kubernetes/generated/serviceaccount.yaml