Merge branch 'upstream' into nginx/extauth_headers

This commit is contained in:
rsafronov 2017-03-24 20:25:18 -04:00
commit 6d07d32003
63 changed files with 1075 additions and 218 deletions

1
controllers/gce/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
glbc

View file

@ -18,7 +18,7 @@ __A reminder on Services__: A Kubernetes Service defines a set of pods and a mea
### L7 Load balancing on Kubernetes
To achive L7 loadbalancing through Kubernetes, we employ a resource called `Ingress`. The Ingress is consumed by this loadbalancer controller, which creates the following GCE resource graph:
To achieve L7 loadbalancing through Kubernetes, we employ a resource called `Ingress`. The Ingress is consumed by this loadbalancer controller, which creates the following GCE resource graph:
[Global Forwarding Rule](https://cloud.google.com/compute/docs/load-balancing/http/global-forwarding-rules) -> [TargetHttpProxy](https://cloud.google.com/compute/docs/load-balancing/http/target-proxies) -> [Url Map](https://cloud.google.com/compute/docs/load-balancing/http/url-map) -> [Backend Service](https://cloud.google.com/compute/docs/load-balancing/http/backend-service) -> [Instance Group](https://cloud.google.com/compute/docs/instance-groups/)
@ -649,12 +649,12 @@ If you hit that it means the controller isn't even starting. Re-check your input
A default GKE/GCE cluster needs at least 1 firewall rule for GLBC to function. The Ingress controller should create this for you automatically. You can also create it thus:
```console
$ gcloud compute firewall-rules create allow-130-211-0-0-22 \
--source-ranges 130.211.0.0/22 \
--source-ranges 130.211.0.0/22,35.191.0.0/16 \
--target-tags $TAG \
--allow tcp:$NODE_PORT
```
Where `130.211.0.0/22` is the source range of the GCE L7, `$NODE_PORT` is the node port your Service is exposed on, i.e:
Where `130.211.0.0/22` and `35.191.0.0/16` are the source ranges of the GCE L7, `$NODE_PORT` is the node port your Service is exposed on, i.e:
```console
$ kubectl get -o jsonpath="{.spec.ports[0].nodePort}" services ${SERVICE_NAME}
```

View file

@ -119,7 +119,7 @@ func NewBackendPool(
func(i interface{}) (string, error) {
bs := i.(*compute.BackendService)
if !namer.NameBelongsToCluster(bs.Name) {
return "", fmt.Errorf("Unrecognized name %v", bs.Name)
return "", fmt.Errorf("unrecognized name %v", bs.Name)
}
port, err := namer.BePort(bs.Name)
if err != nil {

View file

@ -47,7 +47,7 @@ func (f *FakeBackendServices) GetBackendService(name string) (*compute.BackendSe
f.calls = append(f.calls, utils.Get)
obj, exists, err := f.backendServices.GetByKey(name)
if !exists {
return nil, fmt.Errorf("Backend service %v not found", name)
return nil, fmt.Errorf("backend service %v not found", name)
}
if err != nil {
return nil, err
@ -57,7 +57,7 @@ func (f *FakeBackendServices) GetBackendService(name string) (*compute.BackendSe
if name == svc.Name {
return svc, nil
}
return nil, fmt.Errorf("Backend service %v not found", name)
return nil, fmt.Errorf("backend service %v not found", name)
}
// CreateBackendService fakes backend service creation.
@ -77,7 +77,7 @@ func (f *FakeBackendServices) DeleteBackendService(name string) error {
f.calls = append(f.calls, utils.Delete)
svc, exists, err := f.backendServices.GetByKey(name)
if !exists {
return fmt.Errorf("Backend service %v not found", name)
return fmt.Errorf("backend service %v not found", name)
}
if err != nil {
return err

View file

@ -290,7 +290,7 @@ func (lbc *LoadBalancerController) storesSynced() bool {
func (lbc *LoadBalancerController) sync(key string) (err error) {
if !lbc.hasSynced() {
time.Sleep(storeSyncPollPeriod)
return fmt.Errorf("Waiting for stores to sync")
return fmt.Errorf("waiting for stores to sync")
}
glog.V(3).Infof("Syncing %v", key)
@ -326,7 +326,7 @@ func (lbc *LoadBalancerController) sync(key string) (err error) {
defer func() {
if deferErr := lbc.CloudClusterManager.GC(lbNames, nodePorts); deferErr != nil {
err = fmt.Errorf("Error during sync %v, error during GC %v", err, deferErr)
err = fmt.Errorf("error during sync %v, error during GC %v", err, deferErr)
}
glog.V(3).Infof("Finished syncing %v", key)
}()
@ -343,7 +343,7 @@ func (lbc *LoadBalancerController) sync(key string) (err error) {
if ingExists {
lbc.recorder.Eventf(obj.(*extensions.Ingress), api.EventTypeWarning, eventMsg, err.Error())
} else {
err = fmt.Errorf("%v Error: %v", eventMsg, err)
err = fmt.Errorf("%v, error: %v", eventMsg, err)
}
syncError = err
}

View file

@ -65,11 +65,11 @@ func (t *apiServerTLSLoader) load(ing *extensions.Ingress) (*loadbalancers.TLSCe
}
cert, ok := secret.Data[api.TLSCertKey]
if !ok {
return nil, fmt.Errorf("Secret %v has no private key", secretName)
return nil, fmt.Errorf("secret %v has no private key", secretName)
}
key, ok := secret.Data[api.TLSPrivateKeyKey]
if !ok {
return nil, fmt.Errorf("Secret %v has no cert", secretName)
return nil, fmt.Errorf("secret %v has no cert", secretName)
}
certs := &loadbalancers.TLSCerts{Key: string(key), Cert: string(cert)}
if err := t.validate(certs); err != nil {
@ -95,5 +95,5 @@ func (f *fakeTLSSecretLoader) load(ing *extensions.Ingress) (*loadbalancers.TLSC
return cert, nil
}
}
return nil, fmt.Errorf("Couldn't find secret for ingress %v", ing.Name)
return nil, fmt.Errorf("couldn't find secret for ingress %v", ing.Name)
}

View file

@ -216,24 +216,35 @@ func (s *StoreToIngressLister) List() (ing extensions.IngressList, err error) {
// GetServiceIngress gets all the Ingress' that have rules pointing to a service.
// Note that this ignores services without the right nodePorts.
func (s *StoreToIngressLister) GetServiceIngress(svc *api.Service) (ings []extensions.Ingress, err error) {
IngressLoop:
for _, m := range s.Store.List() {
ing := *m.(*extensions.Ingress)
if ing.Namespace != svc.Namespace {
continue
}
for _, rules := range ing.Spec.Rules {
if rules.IngressRuleValue.HTTP == nil {
// Check service of default backend
if ing.Spec.Backend != nil && ing.Spec.Backend.ServiceName == svc.Name {
ings = append(ings, ing)
continue
}
for _, p := range rules.IngressRuleValue.HTTP.Paths {
// Check the target service for each path rule
for _, rule := range ing.Spec.Rules {
if rule.IngressRuleValue.HTTP == nil {
continue
}
for _, p := range rule.IngressRuleValue.HTTP.Paths {
if p.Backend.ServiceName == svc.Name {
ings = append(ings, ing)
// Skip the rest of the rules to avoid duplicate ingresses in list
continue IngressLoop
}
}
}
}
if len(ings) == 0 {
err = fmt.Errorf("No ingress for service %v", svc.Name)
err = fmt.Errorf("no ingress for service %v", svc.Name)
}
return
}
@ -315,7 +326,7 @@ func (t *GCETranslator) toGCEBackend(be *extensions.IngressBackend, ns string) (
backend, err := t.CloudClusterManager.backendPool.Get(int64(port))
if err != nil {
return nil, fmt.Errorf(
"No GCE backend exists for port %v, kube backend %+v", port, be)
"no GCE backend exists for port %v, kube backend %+v", port, be)
}
return backend, nil
}
@ -332,7 +343,7 @@ func (t *GCETranslator) getServiceNodePort(be extensions.IngressBackend, namespa
})
if !exists {
return invalidPort, errorNodePortNotFound{be, fmt.Errorf(
"Service %v/%v not found in store", namespace, be.ServiceName)}
"service %v/%v not found in store", namespace, be.ServiceName)}
}
if err != nil {
return invalidPort, errorNodePortNotFound{be, err}
@ -356,7 +367,7 @@ func (t *GCETranslator) getServiceNodePort(be extensions.IngressBackend, namespa
return nodePort, nil
}
return invalidPort, errorNodePortNotFound{be, fmt.Errorf(
"Could not find matching nodeport from service.")}
"could not find matching nodeport from service")}
}
// toNodePorts converts a pathlist to a flat list of nodeports.
@ -374,7 +385,7 @@ func (t *GCETranslator) toNodePorts(ings *extensions.IngressList) []int64 {
}
for _, rule := range ing.Spec.Rules {
if rule.HTTP == nil {
glog.Errorf("Ignoring non http Ingress rule.")
glog.Errorf("ignoring non http Ingress rule")
continue
}
for _, path := range rule.HTTP.Paths {
@ -411,7 +422,7 @@ func (t *GCETranslator) GetZoneForNode(name string) (string, error) {
return getZone(n), nil
}
}
return "", fmt.Errorf("Node not found %v", name)
return "", fmt.Errorf("node not found %v", name)
}
// ListZones returns a list of zones this Kubernetes cluster spans.

View file

@ -35,7 +35,7 @@ func (f *fakeFirewallRules) GetFirewall(name string) (*compute.Firewall, error)
return rule, nil
}
}
return nil, fmt.Errorf("Firewall rule %v not found.", name)
return nil, fmt.Errorf("firewall rule %v not found", name)
}
func (f *fakeFirewallRules) CreateFirewall(name, msgTag string, srcRange netset.IPNet, ports []int64, hosts []string) error {
@ -66,7 +66,7 @@ func (f *fakeFirewallRules) DeleteFirewall(name string) error {
firewalls = append(firewalls, rule)
}
if !exists {
return fmt.Errorf("Failed to find health check %v", name)
return fmt.Errorf("failed to find health check %v", name)
}
f.fw = firewalls
return nil
@ -95,7 +95,7 @@ func (f *fakeFirewallRules) UpdateFirewall(name, msgTag string, srcRange netset.
if exists {
return nil
}
return fmt.Errorf("Update failed for rule %v, srcRange %v ports %v, rule not found", name, srcRange, ports)
return fmt.Errorf("update failed for rule %v, srcRange %v ports %v, rule not found", name, srcRange, ports)
}
// NewFakeFirewallRules creates a fake for firewall rules.

View file

@ -26,25 +26,25 @@ import (
"k8s.io/kubernetes/pkg/util/sets"
)
// Src range from which the GCE L7 performs health checks.
const l7SrcRange = "130.211.0.0/22"
// Src ranges from which the GCE L7 performs health checks.
var l7SrcRanges = []string{"130.211.0.0/22", "35.191.0.0/16"}
// FirewallRules manages firewall rules.
type FirewallRules struct {
cloud Firewall
namer *utils.Namer
srcRange netset.IPNet
srcRanges netset.IPNet
}
// NewFirewallPool creates a new firewall rule manager.
// cloud: the cloud object implementing Firewall.
// namer: cluster namer.
func NewFirewallPool(cloud Firewall, namer *utils.Namer) SingleFirewallPool {
srcNetSet, err := netset.ParseIPNets(l7SrcRange)
srcNetSet, err := netset.ParseIPNets(l7SrcRanges...)
if err != nil {
glog.Fatalf("Could not parse L7 src range %v for firewall rule: %v", l7SrcRange, err)
glog.Fatalf("Could not parse L7 src ranges %v for firewall rule: %v", l7SrcRanges, err)
}
return &FirewallRules{cloud: cloud, namer: namer, srcRange: srcNetSet}
return &FirewallRules{cloud: cloud, namer: namer, srcRanges: srcNetSet}
}
// Sync sync firewall rules with the cloud.
@ -60,7 +60,7 @@ func (fr *FirewallRules) Sync(nodePorts []int64, nodeNames []string) error {
rule, _ := fr.cloud.GetFirewall(name)
if rule == nil {
glog.Infof("Creating global l7 firewall rule %v", name)
return fr.cloud.CreateFirewall(suffix, "GCE L7 firewall rule", fr.srcRange, nodePorts, nodeNames)
return fr.cloud.CreateFirewall(suffix, "GCE L7 firewall rule", fr.srcRanges, nodePorts, nodeNames)
}
requiredPorts := sets.NewString()
@ -77,7 +77,7 @@ func (fr *FirewallRules) Sync(nodePorts []int64, nodeNames []string) error {
return nil
}
glog.V(3).Infof("Firewall rule %v already exists, updating nodeports %v", name, nodePorts)
return fr.cloud.UpdateFirewall(suffix, "GCE L7 firewall rule", fr.srcRange, nodePorts, nodeNames)
return fr.cloud.UpdateFirewall(suffix, "GCE L7 firewall rule", fr.srcRanges, nodePorts, nodeNames)
}
// Shutdown shuts down this firewall rules manager.

View file

@ -60,7 +60,7 @@ func (f *FakeHealthChecks) GetHttpHealthCheck(name string) (*compute.HttpHealthC
return h, nil
}
}
return nil, fmt.Errorf("Health check %v not found.", name)
return nil, fmt.Errorf("health check %v not found", name)
}
// DeleteHttpHealthCheck fakes out deleting a http health check.
@ -75,7 +75,7 @@ func (f *FakeHealthChecks) DeleteHttpHealthCheck(name string) error {
healthChecks = append(healthChecks, h)
}
if !exists {
return fmt.Errorf("Failed to find health check %v", name)
return fmt.Errorf("failed to find health check %v", name)
}
f.hc = healthChecks
return nil
@ -94,7 +94,7 @@ func (f *FakeHealthChecks) UpdateHttpHealthCheck(hc *compute.HttpHealthCheck) er
}
}
if !found {
return fmt.Errorf("Cannot update a non-existent health check %v", hc.Name)
return fmt.Errorf("cannot update a non-existent health check %v", hc.Name)
}
f.hc = healthChecks
return nil

View file

@ -75,7 +75,7 @@ func (f *FakeInstanceGroups) GetInstanceGroup(name, zone string) (*compute.Insta
}
}
// TODO: Return googleapi 404 error
return nil, fmt.Errorf("Instance group %v not found", name)
return nil, fmt.Errorf("instance group %v not found", name)
}
// CreateInstanceGroup fakes instance group creation.
@ -97,7 +97,7 @@ func (f *FakeInstanceGroups) DeleteInstanceGroup(name, zone string) error {
newGroups = append(newGroups, ig)
}
if !found {
return fmt.Errorf("Instance Group %v not found", name)
return fmt.Errorf("instance group %v not found", name)
}
f.instanceGroups = newGroups
return nil

View file

@ -109,7 +109,7 @@ func (f *FakeLoadBalancers) GetGlobalForwardingRule(name string) (*compute.Forwa
return f.Fw[i], nil
}
}
return nil, fmt.Errorf("Forwarding rule %v not found", name)
return nil, fmt.Errorf("forwarding rule %v not found", name)
}
// CreateGlobalForwardingRule fakes forwarding rule creation.
@ -176,7 +176,7 @@ func (f *FakeLoadBalancers) GetUrlMap(name string) (*compute.UrlMap, error) {
return f.Um[i], nil
}
}
return nil, fmt.Errorf("Url Map %v not found", name)
return nil, fmt.Errorf("url map %v not found", name)
}
// CreateUrlMap fakes url-map creation.
@ -226,7 +226,7 @@ func (f *FakeLoadBalancers) GetTargetHttpProxy(name string) (*compute.TargetHttp
return f.Tp[i], nil
}
}
return nil, fmt.Errorf("Targetproxy %v not found", name)
return nil, fmt.Errorf("target http proxy %v not found", name)
}
// CreateTargetHttpProxy fakes creating a target http proxy.
@ -275,7 +275,7 @@ func (f *FakeLoadBalancers) GetTargetHttpsProxy(name string) (*compute.TargetHtt
return f.Tps[i], nil
}
}
return nil, fmt.Errorf("Targetproxy %v not found", name)
return nil, fmt.Errorf("target https proxy %v not found", name)
}
// CreateTargetHttpsProxy fakes creating a target http proxy.
@ -326,7 +326,7 @@ func (f *FakeLoadBalancers) SetSslCertificateForTargetHttpsProxy(proxy *compute.
}
}
if !found {
return fmt.Errorf("Failed to find proxy %v", proxy.Name)
return fmt.Errorf("failed to find proxy %v", proxy.Name)
}
return nil
}
@ -415,7 +415,7 @@ func (f *FakeLoadBalancers) GetGlobalStaticIP(name string) (*compute.Address, er
return f.IP[i], nil
}
}
return nil, fmt.Errorf("Static IP %v not found", name)
return nil, fmt.Errorf("static IP %v not found", name)
}
// DeleteGlobalStaticIP fakes out static IP deletion.
@ -441,7 +441,7 @@ func (f *FakeLoadBalancers) GetSslCertificate(name string) (*compute.SslCertific
return f.Certs[i], nil
}
}
return nil, fmt.Errorf("Cert %v not found", name)
return nil, fmt.Errorf("cert %v not found", name)
}
// CreateSslCertificate fakes out certificate creation.

View file

@ -106,7 +106,7 @@ func (l *L7s) Get(name string) (*L7, error) {
name = l.namer.LBName(name)
lb, exists := l.snapshotter.Get(name)
if !exists {
return nil, fmt.Errorf("Loadbalancer %v not in pool", name)
return nil, fmt.Errorf("loadbalancer %v not in pool", name)
}
return lb.(*L7), nil
}
@ -292,7 +292,7 @@ type L7 struct {
func (l *L7) checkUrlMap(backend *compute.BackendService) (err error) {
if l.glbcDefaultBackend == nil {
return fmt.Errorf("Cannot create urlmap without default backend.")
return fmt.Errorf("cannot create urlmap without default backend")
}
urlMapName := l.namer.Truncate(fmt.Sprintf("%v-%v", urlMapPrefix, l.Name))
urlMap, _ := l.cloud.GetUrlMap(urlMapName)
@ -313,7 +313,7 @@ func (l *L7) checkUrlMap(backend *compute.BackendService) (err error) {
func (l *L7) checkProxy() (err error) {
if l.um == nil {
return fmt.Errorf("Cannot create proxy without urlmap.")
return fmt.Errorf("cannot create proxy without urlmap")
}
proxyName := l.namer.Truncate(fmt.Sprintf("%v-%v", targetProxyPrefix, l.Name))
proxy, _ := l.cloud.GetTargetHttpProxy(proxyName)
@ -362,7 +362,7 @@ func (l *L7) checkSSLCert() (err error) {
return err
}
if cert == nil {
return fmt.Errorf("Cannot find existing sslCertificate %v for %v", certName, l.Name)
return fmt.Errorf("cannot find existing sslCertificate %v for %v", certName, l.Name)
}
glog.Infof("Using existing sslCertificate %v for %v", certName, l.Name)
@ -429,7 +429,7 @@ func (l *L7) checkHttpsProxy() (err error) {
return nil
}
if l.um == nil {
return fmt.Errorf("No UrlMap for %v, will not create HTTPS proxy.", l.Name)
return fmt.Errorf("no UrlMap for %v, will not create HTTPS proxy", l.Name)
}
proxyName := l.namer.Truncate(fmt.Sprintf("%v-%v", targetHTTPSProxyPrefix, l.Name))
proxy, _ := l.cloud.GetTargetHttpsProxy(proxyName)
@ -535,7 +535,7 @@ func (l *L7) getEffectiveIP() (string, bool) {
func (l *L7) checkHttpForwardingRule() (err error) {
if l.tp == nil {
return fmt.Errorf("Cannot create forwarding rule without proxy.")
return fmt.Errorf("cannot create forwarding rule without proxy")
}
name := l.namer.Truncate(fmt.Sprintf("%v-%v", forwardingRulePrefix, l.Name))
address, _ := l.getEffectiveIP()
@ -565,7 +565,7 @@ func (l *L7) checkHttpsForwardingRule() (err error) {
// checkStaticIP reserves a static IP allocated to the Forwarding Rule.
func (l *L7) checkStaticIP() (err error) {
if l.fw == nil || l.fw.IPAddress == "" {
return fmt.Errorf("Will not create static IP without a forwarding rule.")
return fmt.Errorf("will not create static IP without a forwarding rule")
}
// Don't manage staticIPs if the user has specified an IP.
if address, manageStaticIP := l.getEffectiveIP(); !manageStaticIP {
@ -704,7 +704,7 @@ func getNameForPathMatcher(hostRule string) string {
// pathmatcher of the host.
func (l *L7) UpdateUrlMap(ingressRules utils.GCEURLMap) error {
if l.um == nil {
return fmt.Errorf("Cannot add url without an urlmap.")
return fmt.Errorf("cannot add url without an urlmap")
}
glog.V(3).Infof("Updating urlmap for l7 %v", l.Name)

View file

@ -316,7 +316,7 @@ func useDefaultOrLookupVault(cfgVault *storage.ConfigMapVault, cm_key, default_n
// 2. No such key in config map - found=false, err=nil
// 3. Apiserver flake - found=false, err!=nil
// It is not safe to proceed in 3.
return "", fmt.Errorf("Failed to retrieve %v: %v, returning empty name", cm_key, err)
return "", fmt.Errorf("failed to retrieve %v: %v, returning empty name", cm_key, err)
} else if !found {
// Not found but safe to proceed.
return "", nil

View file

@ -95,12 +95,12 @@ func (c *ConfigMapVault) Put(key, val string) error {
glog.Infof("Configmap %v will be updated with %v = %v", cfgMapKey, key, val)
}
if err := c.ConfigMapStore.Update(apiObj); err != nil {
return fmt.Errorf("Failed to update %v: %v", cfgMapKey, err)
return fmt.Errorf("failed to update %v: %v", cfgMapKey, err)
}
} else {
apiObj.Data = map[string]string{key: val}
if err := c.ConfigMapStore.Add(apiObj); err != nil {
return fmt.Errorf("Failed to add %v: %v", cfgMapKey, err)
return fmt.Errorf("failed to add %v: %v", cfgMapKey, err)
}
}
glog.Infof("Successfully stored key %v = %v in config map %v", key, val, cfgMapKey)
@ -176,7 +176,7 @@ func (a *APIServerConfigMapStore) Delete(obj interface{}) error {
func (a *APIServerConfigMapStore) GetByKey(key string) (item interface{}, exists bool, err error) {
nsName := strings.Split(key, "/")
if len(nsName) != 2 {
return nil, false, fmt.Errorf("Failed to get key %v, unexpecte format, expecting ns/name", key)
return nil, false, fmt.Errorf("failed to get key %v, unexpecte format, expecting ns/name", key)
}
ns, name := nsName[0], nsName[1]
cfg, err := a.client.Core().ConfigMaps(ns).Get(name)

View file

@ -223,11 +223,11 @@ func (n *Namer) BePort(beName string) (string, error) {
}
match := r.FindStringSubmatch(beName)
if len(match) < 2 {
return "", fmt.Errorf("Unable to lookup port for %v", beName)
return "", fmt.Errorf("unable to lookup port for %v", beName)
}
_, err = strconv.Atoi(match[1])
if err != nil {
return "", fmt.Errorf("Unexpected regex match: %v", beName)
return "", fmt.Errorf("unexpected regex match: %v", beName)
}
return match[1], nil
}

View file

@ -1,5 +1,74 @@
Changelog
### 0.9-beta.3
**Image:** `gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3`
*New Features:*
- Custom log formats using `log-format-upstream` directive in the configuration configmap.
- Force redirect to SSL using the annotation `ingress.kubernetes.io/force-ssl-redirect`
- Prometheus metric for VTS status module (transparent, just enable vts stats)
- Improved external authentication adding `ingress.kubernetes.io/auth-signin` annotation. Please check this [example](https://github.com/kubernetes/ingress/tree/master/examples/external-auth/nginx)
*Breaking changes:*
- `ssl-dh-param` configuration in configmap is now the name of a secret that contains the Diffie-Hellman key
*Changes:*
- [X] [#433](https://github.com/kubernetes/ingress/pull/433) close over the ingress variable or the last assignment will be used
- [X] [#424](https://github.com/kubernetes/ingress/pull/424) Manually sync secrets from certificate authentication annotations
- [X] [#423](https://github.com/kubernetes/ingress/pull/423) Scrap json metrics from nginx vts module when enabled
- [X] [#418](https://github.com/kubernetes/ingress/pull/418) Only update Ingress status for the configured class
- [X] [#415](https://github.com/kubernetes/ingress/pull/415) Improve external authentication docs
- [X] [#410](https://github.com/kubernetes/ingress/pull/410) Add support for "signin url"
- [X] [#409](https://github.com/kubernetes/ingress/pull/409) Allow custom http2 header sizes
- [X] [#408](https://github.com/kubernetes/ingress/pull/408) Review docs
- [X] [#406](https://github.com/kubernetes/ingress/pull/406) Add debug info and fix spelling
- [X] [#402](https://github.com/kubernetes/ingress/pull/402) allow specifying custom dh param
- [X] [#397](https://github.com/kubernetes/ingress/pull/397) Fix external auth
- [X] [#394](https://github.com/kubernetes/ingress/pull/394) Update README.md
- [X] [#392](https://github.com/kubernetes/ingress/pull/392) Fix http2 header size
- [X] [#391](https://github.com/kubernetes/ingress/pull/391) remove tmp nginx-diff files
- [X] [#390](https://github.com/kubernetes/ingress/pull/390) Fix RateLimit comment
- [X] [#385](https://github.com/kubernetes/ingress/pull/385) add Copyright
- [X] [#382](https://github.com/kubernetes/ingress/pull/382) Ingress Fake Certificate generation
- [X] [#380](https://github.com/kubernetes/ingress/pull/380) Fix custom log format
- [X] [#373](https://github.com/kubernetes/ingress/pull/373) Cleanup
- [X] [#371](https://github.com/kubernetes/ingress/pull/371) add configuration to disable listening on ipv6
- [X] [#370](https://github.com/kubernetes/ingress/pull/270) Add documentation for ingress.kubernetes.io/force-ssl-redirect
- [X] [#369](https://github.com/kubernetes/ingress/pull/369) Minor text fix for "ApiServer"
- [X] [#367](https://github.com/kubernetes/ingress/pull/367) BuildLogFormatUpstream was always using the default log-format
- [X] [#366](https://github.com/kubernetes/ingress/pull/366) add_judgment
- [X] [#365](https://github.com/kubernetes/ingress/pull/365) add ForceSSLRedirect ingress annotation
- [X] [#364](https://github.com/kubernetes/ingress/pull/364) Fix error caused by increasing proxy_buffer_size (#363)
- [X] [#362](https://github.com/kubernetes/ingress/pull/362) Fix ingress class
- [X] [#360](https://github.com/kubernetes/ingress/pull/360) add example of 'run multiple nginx ingress controllers as a deployment'
- [X] [#358](https://github.com/kubernetes/ingress/pull/358) Checks if the TLS secret contains a valid keypair structure
- [X] [#356](https://github.com/kubernetes/ingress/pull/356) Disable listen only on ipv6 and fix proxy_protocol
- [X] [#354](https://github.com/kubernetes/ingress/pull/354) add judgment
- [X] [#352](https://github.com/kubernetes/ingress/pull/352) Add ability to customize upstream and stream log format
- [X] [#351](https://github.com/kubernetes/ingress/pull/351) Enable custom election id for status sync.
- [X] [#347](https://github.com/kubernetes/ingress/pull/347) Fix client source IP address
- [X] [#345](https://github.com/kubernetes/ingress/pull/345) Fix lint error
- [X] [#344](https://github.com/kubernetes/ingress/pull/344) Refactoring of TCP and UDP services
- [X] [#343](https://github.com/kubernetes/ingress/pull/343) Fix node lister when --watch-namespace is used
- [X] [#341](https://github.com/kubernetes/ingress/pull/341) Do not run coverage check in the default target.
- [X] [#340](https://github.com/kubernetes/ingress/pull/340) Add support for specify proxy cookie path/domain
- [X] [#337](https://github.com/kubernetes/ingress/pull/337) Fix for formatting error introduced in #304
- [X] [#335](https://github.com/kubernetes/ingress/pull/335) Fix for vet complaints:
- [X] [#332](https://github.com/kubernetes/ingress/pull/332) Add annotation to customize nginx configuration
- [X] [#331](https://github.com/kubernetes/ingress/pull/331) Correct spelling mistake
- [X] [#328](https://github.com/kubernetes/ingress/pull/328) fix misspell "affinity" in main.go
- [X] [#326](https://github.com/kubernetes/ingress/pull/326) add nginx daemonset example
- [X] [#311](https://github.com/kubernetes/ingress/pull/311) Sort stream service ports to avoid extra reloads
- [X] [#307](https://github.com/kubernetes/ingress/pull/307) Add docs for body-size annotation
- [X] [#306](https://github.com/kubernetes/ingress/pull/306) modify nginx readme
- [X] [#304](https://github.com/kubernetes/ingress/pull/304) change 'buildSSPassthrouthUpstreams' to 'buildSSLPassthroughUpstreams'
### 0.9-beta.2
**Image:** `gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2`

View file

@ -3,7 +3,7 @@ all: push
BUILDTAGS=
# Use the 0.0 tag for testing, it shouldn't clobber any release builds
RELEASE?=0.9.0-beta.2
RELEASE?=0.9.0-beta.3
PREFIX?=gcr.io/google_containers/nginx-ingress-controller
GOOS?=linux
DOCKER?=gcloud docker --
@ -21,7 +21,7 @@ build: clean
-ldflags "-s -w -X ${PKG}/pkg/version.RELEASE=${RELEASE} -X ${PKG}/pkg/version.COMMIT=${COMMIT} -X ${PKG}/pkg/version.REPO=${REPO_INFO}" \
-o rootfs/nginx-ingress-controller ${PKG}/pkg/cmd/controller
container:
container: build
$(DOCKER) build --pull -t $(PREFIX):$(RELEASE) rootfs
push: container
@ -50,4 +50,4 @@ vet:
@go vet $(shell go list ${PKG}/... | grep -v vendor)
clean:
rm -f nginx-ingress-controller
rm -f rootfs/nginx-ingress-controller

View file

@ -40,6 +40,7 @@ The following annotations are supported:
|Name |type|
|---------------------------|------|
|[ingress.kubernetes.io/add-base-url](#rewrite)|true or false|
|[ingress.kubernetes.io/app-root](#rewrite)|string|
|[ingress.kubernetes.io/affinity](#session-affinity)|true or false|
|[ingress.kubernetes.io/auth-realm](#authentication)|string|
|[ingress.kubernetes.io/auth-secret](#authentication)|string|
@ -174,7 +175,9 @@ Set the annotation `ingress.kubernetes.io/rewrite-target` to the path expected b
If the application contains relative links it is possible to add an additional annotation `ingress.kubernetes.io/add-base-url` that will prepend a [`base` tag](https://developer.mozilla.org/en/docs/Web/HTML/Element/base) in the header of the returned HTML from the backend.
Please check the [rewrite](examples/rewrite/README.md) example.
If the Application Root is exposed in a different path and needs to be redirected, the annotation `ingress.kubernetes.io/app-root` might be used.
Please check the [rewrite](/examples/rewrite/README.md) example.
### Rate limiting

View file

@ -441,7 +441,7 @@ func (n NGINXController) Check(_ *http.Request) error {
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("Ingress controller is not healthy")
return fmt.Errorf("ingress controller is not healthy")
}
return nil
}

View file

@ -47,7 +47,7 @@ const (
gzipTypes = "application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component"
logFormatUpstream = `%v - [$proxy_add_x_forwarded_for] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status"`
logFormatUpstream = `%v - [$proxy_add_x_forwarded_for] - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status`
logFormatStream = `[$time_local] $protocol [$ssl_preread_server_name] [$stream_upstream] $status $bytes_sent $bytes_received $session_time`

View file

@ -138,7 +138,7 @@ func (bit BoolToFloat64) UnmarshalJSON(data []byte) error {
} else if asString == "0" || asString == "false" {
bit = 0
} else {
return fmt.Errorf(fmt.Sprintf("Boolean unmarshal error: invalid input %s", asString))
return fmt.Errorf(fmt.Sprintf("boolean unmarshal error: invalid input %s", asString))
}
return nil
}

View file

@ -203,10 +203,16 @@ func buildLocation(input interface{}) string {
path := location.Path
if len(location.Redirect.Target) > 0 && location.Redirect.Target != path {
if path == "/" {
if path == slash {
return fmt.Sprintf("~* %s", path)
}
return fmt.Sprintf("~* ^%s", path)
// baseuri regex will parse basename from the given location
baseuri := `(?<baseuri>.*)`
if !strings.HasSuffix(path, slash) {
// Not treat the slash after "location path" as a part of baseuri
baseuri = fmt.Sprintf(`\/?%s`, baseuri)
}
return fmt.Sprintf(`~* ^%s%s`, path, baseuri)
}
return path
@ -294,13 +300,10 @@ func buildProxyPass(b interface{}, loc interface{}) string {
if len(location.Redirect.Target) > 0 {
abu := ""
if location.Redirect.AddBaseURL {
bPath := location.Redirect.Target
if !strings.HasSuffix(bPath, slash) {
bPath = fmt.Sprintf("%s/", bPath)
}
abu = fmt.Sprintf(`subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name%v">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name%v">' r;
// path has a slash suffix, so that it can be connected with baseuri directly
bPath := fmt.Sprintf("%s%s", path, "$baseuri")
abu = fmt.Sprintf(`subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host%v">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host%v">' r;
`, bPath, bPath)
}

View file

@ -47,33 +47,43 @@ var (
rewrite /(.*) /jenkins/$1 break;
proxy_pass http://upstream-name;
`, false},
"redirect /something to /": {"/something", "/", "~* ^/something", `
"redirect /something to /": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
rewrite /something/(.*) /$1 break;
rewrite /something / break;
proxy_pass http://upstream-name;
`, false},
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", "~* ^/something-complex", `
"redirect /end-with-slash/ to /not-root": {"/end-with-slash/", "/not-root", "~* ^/end-with-slash/(?<baseuri>.*)", `
rewrite /end-with-slash/(.*) /not-root/$1 break;
proxy_pass http://upstream-name;
`, false},
"redirect /something-complex to /not-root": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
rewrite /something-complex/(.*) /not-root/$1 break;
proxy_pass http://upstream-name;
`, false},
"redirect / to /jenkins and rewrite": {"/", "/jenkins", "~* /", `
rewrite /(.*) /jenkins/$1 break;
proxy_pass http://upstream-name;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name/jenkins/">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name/jenkins/">' r;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/$baseuri">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/$baseuri">' r;
`, true},
"redirect /something to / and rewrite": {"/something", "/", "~* ^/something", `
"redirect /something to / and rewrite": {"/something", "/", `~* ^/something\/?(?<baseuri>.*)`, `
rewrite /something/(.*) /$1 break;
rewrite /something / break;
proxy_pass http://upstream-name;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name/">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name/">' r;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something/$baseuri">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something/$baseuri">' r;
`, true},
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", "~* ^/something-complex", `
"redirect /end-with-slash/ to /not-root and rewrite": {"/end-with-slash/", "/not-root", `~* ^/end-with-slash/(?<baseuri>.*)`, `
rewrite /end-with-slash/(.*) /not-root/$1 break;
proxy_pass http://upstream-name;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/end-with-slash/$baseuri">' r;
`, true},
"redirect /something-complex to /not-root and rewrite": {"/something-complex", "/not-root", `~* ^/something-complex\/?(?<baseuri>.*)`, `
rewrite /something-complex/(.*) /not-root/$1 break;
proxy_pass http://upstream-name;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$server_name/not-root/">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$server_name/not-root/">' r;
subs_filter '<head(.*)>' '<head$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
subs_filter '<HEAD(.*)>' '<HEAD$1><base href="$scheme://$http_host/something-complex/$baseuri">' r;
`, true},
}
)

View file

@ -16,10 +16,8 @@ FROM gcr.io/google_containers/nginx-slim:0.14
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
diffutils \
ssl-cert \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& make-ssl-cert generate-default-snakeoil --force-overwrite
&& rm -rf /var/lib/apt/lists/*
COPY . /

View file

@ -237,6 +237,18 @@ http {
ssl_verify_depth {{ $location.CertificateAuth.ValidationDepth }};
{{ end }}
{{ if (or $location.Redirect.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect)) }}
# enforce ssl on server side
if ($pass_access_scheme = http) {
return 301 https://$host$request_uri;
}
{{ end }}
{{ if not (empty $location.Redirect.AppRoot)}}
if ($uri = /) {
return 302 {{ $location.Redirect.AppRoot }};
}
{{ end }}
{{ if not (empty $authPath) }}
location = {{ $authPath }} {
internal;
@ -282,12 +294,7 @@ http {
error_page 401 = {{ $location.ExternalAuth.SigninURL }};
{{ end }}
{{ if (or $location.Redirect.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Redirect.SSLRedirect)) }}
# enforce ssl on server side
if ($pass_access_scheme = http) {
return 301 https://$host$request_uri;
}
{{ end }}
{{/* if the location contains a rate limit annotation, create one */}}
{{ $limits := buildRateLimit $location }}
{{ range $limit := $limits }}
@ -493,7 +500,7 @@ stream {
# TCP services
{{ range $i, $tcpServer := .TCPBackends }}
upstream {{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }} {
upstream tcp-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }} {
{{ range $j, $endpoint := $tcpServer.Endpoints }}
server {{ $endpoint.Address }}:{{ $endpoint.Port }};
{{ end }}
@ -501,22 +508,22 @@ stream {
server {
listen {{ $tcpServer.Port }};
proxy_pass {{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }};
proxy_pass tcp-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }};
}
{{ end }}
# UDP services
{{ range $i, $udpServer := .UDPBackends }}
upstream {{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }} {
upstream udp-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }} {
{{ range $j, $endpoint := $udpServer.Endpoints }}
server {{ $endpoint.Address }}:{{ $endpoint.Port }};
{{ end }}
}
server {
listen {{ $udpServer.Port }};
listen {{ $udpServer.Port }} udp;
proxy_responses 1;
proxy_pass {{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }};
proxy_pass udp-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }};
}
{{ end }}
}

View file

@ -28,6 +28,7 @@ const (
addBaseURL = "ingress.kubernetes.io/add-base-url"
sslRedirect = "ingress.kubernetes.io/ssl-redirect"
forceSSLRedirect = "ingress.kubernetes.io/force-ssl-redirect"
appRoot = "ingress.kubernetes.io/app-root"
)
// Redirect describes the per location redirect config
@ -41,6 +42,8 @@ type Redirect struct {
SSLRedirect bool `json:"sslRedirect"`
// ForceSSLRedirect indicates if the location section is accessible SSL only
ForceSSLRedirect bool `json:"forceSSLRedirect"`
// AppRoot defines the Application Root that the Controller must redirect if it's not in '/' context
AppRoot string `json:"appRoot"`
}
type rewrite struct {
@ -65,10 +68,12 @@ func (a rewrite) Parse(ing *extensions.Ingress) (interface{}, error) {
fSslRe = a.backendResolver.GetDefaultBackend().ForceSSLRedirect
}
abu, _ := parser.GetBoolAnnotation(addBaseURL, ing)
ar, _ := parser.GetStringAnnotation(appRoot, ing)
return &Redirect{
Target: rt,
AddBaseURL: abu,
SSLRedirect: sslRe,
ForceSSLRedirect: fSslRe,
AppRoot: ar,
}, nil
}

View file

@ -158,3 +158,20 @@ func TestForceSSLRedirect(t *testing.T) {
t.Errorf("Expected true but returned false")
}
}
func TestAppRoot(t *testing.T) {
ing := buildIngress()
data := map[string]string{}
data[appRoot] = "/app1"
ing.SetAnnotations(data)
i, _ := NewParser(mockBackend{true}).Parse(ing)
redirect, ok := i.(*Redirect)
if !ok {
t.Errorf("expected a App Context")
}
if redirect.AppRoot != "/app1" {
t.Errorf("Unexpected value got in AppRoot")
}
}

View file

@ -17,8 +17,11 @@ limitations under the License.
package controller
import (
"fmt"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/ingress/core/pkg/ingress/annotations/auth"
@ -47,11 +50,13 @@ type extractorConfig interface {
}
type annotationExtractor struct {
secretResolver resolver.Secret
annotations map[string]parser.IngressAnnotation
}
func newAnnotationExtractor(cfg extractorConfig) annotationExtractor {
return annotationExtractor{
cfg,
map[string]parser.IngressAnnotation{
"BasicDigestAuth": auth.NewParser(auth.AuthDirectory, cfg),
"ExternalAuth": authreq.NewParser(),
@ -104,6 +109,7 @@ const (
healthCheck = "HealthCheck"
sslPassthrough = "SSLPassthrough"
sessionAffinity = "SessionAffinity"
certificateAuth = "CertificateAuth"
)
func (e *annotationExtractor) SecureUpstream(ing *extensions.Ingress) bool {
@ -125,3 +131,17 @@ func (e *annotationExtractor) SessionAffinity(ing *extensions.Ingress) *sessiona
val, _ := e.annotations[sessionAffinity].Parse(ing)
return val.(*sessionaffinity.AffinityConfig)
}
func (e *annotationExtractor) ContainsCertificateAuth(ing *extensions.Ingress) bool {
val, _ := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
return val != ""
}
func (e *annotationExtractor) CertificateAuthSecret(ing *extensions.Ingress) (*api.Secret, error) {
val, _ := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
if val == "" {
return nil, fmt.Errorf("ingress rule %v/%v does not contains the auth-tls-secret annotation", ing.Namespace, ing.Name)
}
return e.secretResolver.GetSecret(val)
}

View file

@ -18,6 +18,7 @@ package controller
import (
"fmt"
"reflect"
"strings"
"time"
@ -68,13 +69,18 @@ func (ic *GenericController) syncSecret(k interface{}) error {
}
// create certificates and add or update the item in the store
_, exists = ic.sslCertTracker.Get(key)
cur, exists := ic.sslCertTracker.Get(key)
if exists {
glog.V(3).Infof("updating secret %v/%v in the store ", sec.Namespace, sec.Name)
s := cur.(*ingress.SSLCert)
if reflect.DeepEqual(s, cert) {
// no need to update
return nil
}
glog.Infof("updating secret %v/%v in the local store", sec.Namespace, sec.Name)
ic.sslCertTracker.Update(key, cert)
return nil
}
glog.V(3).Infof("adding secret %v/%v to the store ", sec.Namespace, sec.Name)
glog.Infof("adding secret %v/%v to the local store", sec.Namespace, sec.Name)
ic.sslCertTracker.Add(key, cert)
return nil
}
@ -84,10 +90,10 @@ func (ic *GenericController) syncSecret(k interface{}) error {
func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
secretInterface, exists, err := ic.secrLister.Store.GetByKey(secretName)
if err != nil {
return nil, fmt.Errorf("error retriveing secret %v: %v", secretName, err)
return nil, fmt.Errorf("error retrieving secret %v: %v", secretName, err)
}
if !exists {
return nil, fmt.Errorf("secret named %v does not exists", secretName)
return nil, fmt.Errorf("secret named %v does not exist", secretName)
}
secret := secretInterface.(*api.Secret)
@ -100,13 +106,13 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
var s *ingress.SSLCert
if okcert && okkey {
glog.V(3).Infof("Found certificate and private key, configuring %v as a TLS Secret", secretName)
glog.Infof("found certificate and private key, configuring %v as a TLS Secret", secretName)
s, err = ssl.AddOrUpdateCertAndKey(nsSecName, cert, key, ca)
} else if ca != nil {
glog.V(3).Infof("Found only ca.crt, configuring %v as an Certificate Authentication secret", secretName)
glog.Infof("found only ca.crt, configuring %v as an Certificate Authentication secret", secretName)
s, err = ssl.AddCertAuth(nsSecName, ca)
} else {
return nil, fmt.Errorf("No keypair or CA cert could be found in %v", secretName)
return nil, fmt.Errorf("ko keypair or CA cert could be found in %v", secretName)
}
if err != nil {
@ -122,10 +128,14 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
func (ic *GenericController) secrReferenced(name, namespace string) bool {
for _, ingIf := range ic.ingLister.Store.List() {
ing := ingIf.(*extensions.Ingress)
str, err := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
if err == nil && str == fmt.Sprintf("%v/%v", namespace, name) {
if ic.annotations.ContainsCertificateAuth(ing) {
str, _ := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
if str == fmt.Sprintf("%v/%v", namespace, name) {
return true
}
}
if ing.Namespace != namespace {
continue
}

View file

@ -111,7 +111,7 @@ type GenericController struct {
// Configuration contains all the settings required by an Ingress controller
type Configuration struct {
Client *clientset.Clientset
Client clientset.Interface
ResyncPeriod time.Duration
DefaultService string
@ -170,6 +170,12 @@ func newIngressController(config *Configuration) *GenericController {
}
ic.recorder.Eventf(addIng, api.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
ic.syncQueue.Enqueue(obj)
if ic.annotations.ContainsCertificateAuth(addIng) {
s, err := ic.annotations.CertificateAuthSecret(addIng)
if err == nil {
ic.syncSecret(fmt.Sprintf("%v/%v", s.Namespace, s.Name))
}
}
},
DeleteFunc: func(obj interface{}) {
delIng := obj.(*extensions.Ingress)
@ -209,6 +215,13 @@ func newIngressController(config *Configuration) *GenericController {
}()
}
}
if ic.annotations.ContainsCertificateAuth(upIng) {
s, err := ic.annotations.CertificateAuthSecret(upIng)
if err == nil {
ic.syncSecret(fmt.Sprintf("%v/%v", s.Namespace, s.Name))
}
}
ic.syncQueue.Enqueue(cur)
}
},
@ -280,11 +293,11 @@ func newIngressController(config *Configuration) *GenericController {
&api.Endpoints{}, ic.cfg.ResyncPeriod, eventHandler)
ic.secrLister.Store, ic.secrController = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "secrets", ic.cfg.Namespace, fields.Everything()),
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "secrets", api.NamespaceAll, fields.Everything()),
&api.Secret{}, ic.cfg.ResyncPeriod, secrEventHandler)
ic.mapLister.Store, ic.mapController = cache.NewInformer(
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "configmaps", ic.cfg.Namespace, fields.Everything()),
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "configmaps", api.NamespaceAll, fields.Everything()),
&api.ConfigMap{}, ic.cfg.ResyncPeriod, mapEventHandler)
ic.svcLister.Indexer, ic.svcController = cache.NewIndexerInformer(
@ -551,7 +564,7 @@ func (ic *GenericController) getDefaultUpstream() *ingress.Backend {
}
if !svcExists {
glog.Warningf("service %v does not exists", svcKey)
glog.Warningf("service %v does not exist", svcKey)
upstream.Endpoints = append(upstream.Endpoints, newDefaultServer())
return upstream
}
@ -693,7 +706,7 @@ func (ic GenericController) GetAuthCertificate(secretName string) (*resolver.Aut
bc, exists := ic.sslCertTracker.Get(secretName)
if !exists {
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exists", secretName)
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exist", secretName)
}
cert := bc.(*ingress.SSLCert)
return &resolver.AuthSSLCert{
@ -792,7 +805,7 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
}
if !svcExists {
err = fmt.Errorf("service %v does not exists", svcKey)
err = fmt.Errorf("service %v does not exist", svcKey)
return upstreams, err
}
@ -947,6 +960,12 @@ func (ic *GenericController) createServers(data []interface{},
}
}
if tlsSecretName == "" {
glog.Warningf("ingress rule %v/%v for host %v does not contains a matching tls host", ing.Namespace, ing.Name, host)
glog.V(2).Infof("%v", ing.Spec.TLS)
continue
}
key := fmt.Sprintf("%v/%v", ing.Namespace, tlsSecretName)
bc, exists := ic.sslCertTracker.Get(key)
if exists {
@ -954,7 +973,11 @@ func (ic *GenericController) createServers(data []interface{},
if isHostValid(host, cert) {
servers[host].SSLCertificate = cert.PemFileName
servers[host].SSLPemChecksum = cert.PemSHA
} else {
glog.Warningf("ssl certificate %v does not contains a common name for host %v", key, host)
}
} else {
glog.Warningf("ssl certificate \"%v\" does not exist in local store", key)
}
}
}
@ -978,6 +1001,11 @@ func (ic *GenericController) getEndpoints(
upsServers := []ingress.Endpoint{}
// avoid duplicated upstream servers when the service
// contains multiple port definitions sharing the same
// targetport.
adus := make(map[string]bool, 0)
for _, ss := range ep.Subsets {
for _, epPort := range ss.Ports {
@ -1018,6 +1046,10 @@ func (ic *GenericController) getEndpoints(
}
for _, epAddress := range ss.Addresses {
ep := fmt.Sprintf("%v:%v", epAddress.IP, targetPort)
if _, exists := adus[ep]; exists {
continue
}
ups := ingress.Endpoint{
Address: epAddress.IP,
Port: fmt.Sprintf("%v", targetPort),
@ -1025,6 +1057,7 @@ func (ic *GenericController) getEndpoints(
FailTimeout: hz.FailTimeout,
}
upsServers = append(upsServers, ups)
adus[ep] = true
}
}
}

View file

@ -128,10 +128,12 @@ func NewIngressController(backend ingress.Controller) *GenericController {
glog.Infof("service %v validated as source of Ingress status", *publishSvc)
}
if *configMap != "" {
_, _, err = k8s.ParseNameNS(*configMap)
if *watchNamespace != "" {
_, err = k8s.IsValidNamespace(kubeClient, *watchNamespace)
if err != nil {
glog.Fatalf("configmap error: %v", err)
glog.Fatalf("no watchNamespace with name %v found: %v", *watchNamespace, err)
}
}

View file

@ -32,13 +32,13 @@ import (
)
// checkSvcForUpdate verifies if one of the running pods for a service contains
// named port. If the annotation in the service does not exists or is not equals
// named port. If the annotation in the service does not exist or is not equals
// to the port mapping obtained from the pod the service must be updated to reflect
// the current state
func (ic *GenericController) checkSvcForUpdate(svc *api.Service) error {
// get the pods associated with the service
// TODO: switch this to a watch
pods, err := ic.cfg.Client.Pods(svc.Namespace).List(api.ListOptions{
pods, err := ic.cfg.Client.Core().Pods(svc.Namespace).List(api.ListOptions{
LabelSelector: labels.Set(svc.Spec.Selector).AsSelector(),
})
@ -82,7 +82,7 @@ func (ic *GenericController) checkSvcForUpdate(svc *api.Service) error {
if len(namedPorts) > 0 && !reflect.DeepEqual(curNamedPort, namedPorts) {
data, _ := json.Marshal(namedPorts)
newSvc, err := ic.cfg.Client.Services(svc.Namespace).Get(svc.Name)
newSvc, err := ic.cfg.Client.Core().Services(svc.Namespace).Get(svc.Name)
if err != nil {
return fmt.Errorf("error getting service %v/%v: %v", svc.Namespace, svc.Name, err)
}
@ -93,7 +93,7 @@ func (ic *GenericController) checkSvcForUpdate(svc *api.Service) error {
newSvc.ObjectMeta.Annotations[service.NamedPortAnnotation] = string(data)
glog.Infof("updating service %v with new named port mappings", svc.Name)
_, err = ic.cfg.Client.Services(svc.Namespace).Update(newSvc)
_, err = ic.cfg.Client.Core().Services(svc.Namespace).Update(newSvc)
if err != nil {
return fmt.Errorf("error syncing service %v/%v: %v", svc.Namespace, svc.Name, err)
}

View file

@ -0,0 +1,191 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controller
import (
"reflect"
"testing"
"k8s.io/ingress/core/pkg/ingress/annotations/service"
"k8s.io/kubernetes/pkg/api"
testclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/util/intstr"
)
func buildSimpleClientSet() *testclient.Clientset {
return testclient.NewSimpleClientset(
&api.PodList{Items: []api.Pod{
{
ObjectMeta: api.ObjectMeta{
Name: "foo1",
Namespace: api.NamespaceDefault,
Labels: map[string]string{
"lable_sig": "foo_pod",
},
},
Spec: api.PodSpec{
NodeName: "foo_node_1",
Containers: []api.Container{
{
Ports: []api.ContainerPort{
{
Name: "foo1_named_port_c1",
Protocol: api.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
{
ObjectMeta: api.ObjectMeta{
Name: "foo1",
Namespace: api.NamespaceSystem,
Labels: map[string]string{
"lable_sig": "foo_pod",
},
},
},
}},
&api.ServiceList{Items: []api.Service{
{
ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceDefault,
Name: "named_port_test_service",
},
},
}},
)
}
func buildGenericController() *GenericController {
return &GenericController{
cfg: &Configuration{
Client: buildSimpleClientSet(),
},
}
}
func buildService() *api.Service {
return &api.Service{
ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceSystem,
Name: "named_port_test_service",
},
Spec: api.ServiceSpec{
ClusterIP: "10.10.10.10",
},
}
}
func TestCheckSvcForUpdate(t *testing.T) {
foos := []struct {
n string
ns string
sps []api.ServicePort
sl map[string]string
er string
}{
{
"pods_have_not_been_found_in_this_namespace",
api.NamespaceSystem,
[]api.ServicePort{
{Name: "foo_port_1", Port: 8080, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("foo1_named_port_c1")},
{Name: "foo_port_2", Port: 8181, Protocol: api.ProtocolTCP, TargetPort: intstr.FromInt(81)},
{Name: "foo_port_3", Port: 8282, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("")},
},
map[string]string{
"lable_sig": "foo_pod",
},
"",
},
{
"ports_have_not_been_found_in_this_pod",
api.NamespaceDefault,
[]api.ServicePort{
{Name: "foo_port_1", Port: 8080, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("foo1_named_port_cXX")},
{Name: "foo_port_2", Port: 8181, Protocol: api.ProtocolTCP, TargetPort: intstr.FromInt(81)},
{Name: "foo_port_3", Port: 8282, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("")},
},
map[string]string{
"lable_sig": "foo_pod",
},
"",
},
{
"ports_fixed",
api.NamespaceDefault,
[]api.ServicePort{
{Name: "foo_port_1", Port: 8080, Protocol: api.ProtocolTCP, TargetPort: intstr.FromInt(80)},
{Name: "foo_port_2", Port: 8181, Protocol: api.ProtocolTCP, TargetPort: intstr.FromInt(81)},
{Name: "foo_port_3", Port: 8282, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("")},
},
map[string]string{
"lable_sig": "foo_pod",
},
"",
},
{
"nil_selector",
api.NamespaceDefault,
[]api.ServicePort{
{Name: "foo_port_1", Port: 8080, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("foo1_named_port_c1")},
{Name: "foo_port_2", Port: 8181, Protocol: api.ProtocolTCP, TargetPort: intstr.FromInt(81)},
{Name: "foo_port_3", Port: 8282, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("")},
},
nil,
"{\"foo1_named_port_c1\":\"80\"}",
},
{
"normal_update",
api.NamespaceDefault,
[]api.ServicePort{
{Name: "foo_port_1", Port: 8080, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("foo1_named_port_c1")},
{Name: "foo_port_2", Port: 8181, Protocol: api.ProtocolTCP, TargetPort: intstr.FromInt(81)},
{Name: "foo_port_3", Port: 8282, Protocol: api.ProtocolTCP, TargetPort: intstr.FromString("")},
},
map[string]string{
"lable_sig": "foo_pod",
},
"{\"foo1_named_port_c1\":\"80\"}",
},
}
for _, foo := range foos {
t.Run(foo.n, func(t *testing.T) {
gc := buildGenericController()
s := buildService()
s.SetNamespace(foo.ns)
s.Spec.Ports = foo.sps
s.Spec.Selector = foo.sl
err := gc.checkSvcForUpdate(s)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
rs, _ := gc.cfg.Client.Core().Services(api.NamespaceDefault).Get("named_port_test_service")
rr := rs.ObjectMeta.Annotations[service.NamedPortAnnotation]
if !reflect.DeepEqual(rr, foo.er) {
t.Errorf("Returned %s, but expected %s for %s", rr, foo.er, foo.n)
}
})
}
}

View file

@ -46,7 +46,7 @@ func isHostValid(host string, cert *ingress.SSLCert) bool {
return false
}
for _, cn := range cert.CN {
if matchHostnames(cn, host) {
if matchHostnames(cn, strings.ToLower(host)) {
return true
}
}

View file

@ -6,6 +6,8 @@ import "net"
// The reason of this requirements is the annotations are generic. If some implementation do not supports
// one or more annotations it just can provides defaults
type Backend struct {
// AppRoot contains the AppRoot for apps that doesn't exposes its content in the 'root' context
AppRoot string `json:"app-root"`
// enables which HTTP codes should be passed for processing with the error_page directive
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors

View file

@ -252,7 +252,7 @@ func (s *statusSync) updateStatus(newIPs []api.LoadBalancerIngress) {
continue
}
go func(wg *sync.WaitGroup) {
go func(wg *sync.WaitGroup, ing *extensions.Ingress) {
defer wg.Done()
ingClient := s.Client.Extensions().Ingresses(ing.Namespace)
currIng, err := ingClient.Get(ing.Name)
@ -274,7 +274,7 @@ func (s *statusSync) updateStatus(newIPs []api.LoadBalancerIngress) {
if err != nil {
glog.Warningf("error updating ingress rule: %v", err)
}
}(&wg)
}(&wg, ing)
}
wg.Wait()

View file

@ -34,6 +34,30 @@ func IsValidService(kubeClient clientset.Interface, name string) (*api.Service,
return kubeClient.Core().Services(ns).Get(name)
}
// isValidConfigMap check if exists a configmap with the specified name
func IsValidConfigMap(kubeClient clientset.Interface, fullName string) (*api.ConfigMap, error) {
ns, name, err := ParseNameNS(fullName)
if err != nil {
return nil, err
}
configMap, err := kubeClient.Core().ConfigMaps(ns).Get(name)
if err != nil {
return nil, fmt.Errorf("configmap not found: %v", err)
}
return configMap, nil
}
// isValidNamespace chck if exists a namespace with the specified name
func IsValidNamespace(kubeClient clientset.Interface, name string) (*api.Namespace, error) {
return kubeClient.Core().Namespaces().Get(name)
}
// IsValidSecret checks if exists a secret with the specified name
func IsValidSecret(kubeClient clientset.Interface, name string) (*api.Secret, error) {
ns, name, err := ParseNameNS(name)

View file

@ -85,6 +85,64 @@ func TestIsValidService(t *testing.T) {
}
}
func TestIsValidNamespace(t *testing.T) {
fk := testclient.NewSimpleClientset(&api.Namespace{
ObjectMeta: api.ObjectMeta{
Name: "default",
},
})
_, err := IsValidNamespace(fk, "empty")
if err == nil {
t.Errorf("expected error but return nill")
}
ns, err := IsValidNamespace(fk, "default")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if ns == nil {
t.Errorf("expected a configmap but returned nil")
}
}
func TestIsValidConfigMap(t *testing.T) {
fk := testclient.NewSimpleClientset(&api.ConfigMap{
ObjectMeta: api.ObjectMeta{
Namespace: api.NamespaceDefault,
Name: "demo",
},
})
_, err := IsValidConfigMap(fk, "")
if err == nil {
t.Errorf("expected error but return nill")
}
s, err := IsValidConfigMap(fk, "default/demo")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if s == nil {
t.Errorf("expected a configmap but returned nil")
}
fk = testclient.NewSimpleClientset()
s, err = IsValidConfigMap(fk, "default/demo")
if err == nil {
t.Errorf("expected an error but returned nil")
}
if s != nil {
t.Errorf("unexpected Configmap returned: %v", s)
}
}
func TestIsValidSecret(t *testing.T) {
fk := testclient.NewSimpleClientset(&api.Secret{
ObjectMeta: api.ObjectMeta{

View file

@ -43,10 +43,10 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
tempPemFile, err := ioutil.TempFile(ingress.DefaultSSLDirectory, pemName)
glog.V(3).Infof("Creating temp file %v for Keypair: %v", tempPemFile.Name(), pemName)
if err != nil {
return nil, fmt.Errorf("could not create temp pem file %v: %v", pemFileName, err)
}
glog.V(3).Infof("Creating temp file %v for Keypair: %v", tempPemFile.Name(), pemName)
_, err = tempPemFile.Write(cert)
if err != nil {
@ -75,13 +75,13 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("No valid PEM formatted block found")
return nil, fmt.Errorf("no valid PEM formatted block found")
}
// If the file does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
if pemBlock.Type != "CERTIFICATE" {
_ = os.Remove(tempPemFile.Name())
return nil, fmt.Errorf("Certificate %v contains invalid data, and must be created with 'kubectl create secret tls'", name)
return nil, fmt.Errorf("certificate %v contains invalid data, and must be created with 'kubectl create secret tls'", name)
}
pemCert, err := x509.ParseCertificate(pemBlock.Bytes)
@ -115,7 +115,7 @@ func AddOrUpdateCertAndKey(name string, cert, key, ca []byte) (*ingress.SSLCert,
caFile, err := os.OpenFile(pemFileName, os.O_RDWR|os.O_APPEND, 0600)
if err != nil {
return nil, fmt.Errorf("Could not open file %v for writing additional CA chains: %v", pemFileName, err)
return nil, fmt.Errorf("could not open file %v for writing additional CA chains: %v", pemFileName, err)
}
defer caFile.Close()
@ -150,11 +150,11 @@ func AddCertAuth(name string, ca []byte) (*ingress.SSLCert, error) {
pemCABlock, _ := pem.Decode(ca)
if pemCABlock == nil {
return nil, fmt.Errorf("No valid PEM formatted block found")
return nil, fmt.Errorf("no valid PEM formatted block found")
}
// If the first certificate does not start with 'BEGIN CERTIFICATE' it's invalid and must not be used.
if pemCABlock.Type != "CERTIFICATE" {
return nil, fmt.Errorf("CA File %v contains invalid data, and must be created only with PEM formated certificates", name)
return nil, fmt.Errorf("CA file %v contains invalid data, and must be created only with PEM formated certificates", name)
}
_, err := x509.ParseCertificate(pemCABlock.Bytes)
@ -206,13 +206,13 @@ func AddOrUpdateDHParam(name string, dh []byte) (string, error) {
pemBlock, _ := pem.Decode(pemCerts)
if pemBlock == nil {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("No valid PEM formatted block found")
return "", fmt.Errorf("no valid PEM formatted block found")
}
// If the file does not start with 'BEGIN DH PARAMETERS' it's invalid and must not be used.
if pemBlock.Type != "DH PARAMETERS" {
_ = os.Remove(tempPemFile.Name())
return "", fmt.Errorf("Certificate %v contains invalid data", name)
return "", fmt.Errorf("certificate %v contains invalid data", name)
}
err = os.Rename(tempPemFile.Name(), pemFileName)

View file

@ -23,7 +23,7 @@ Table of Contents
The Kubernetes Service is an abstraction over endpoints (pod-ip:port pairings).
The Ingress is an abstraction over Services. This doesn't mean all Ingress
controller must route *through* a Service, but rather, that routing, security
and auth configuration is represented in the Ingerss resource per Service, and
and auth configuration is represented in the Ingress resource per Service, and
not per pod. As long as this configuration is respected, a given Ingress
controller is free to route to the DNS name of a Service, the VIP, a NodePort,
or directly to the Service's endpoints.

View file

@ -72,7 +72,7 @@ In addition to this pipeline:
Service
* Each port on the Backend Service has a matching port on the Instance Group
* Each port on the Backend Service is exposed through a firewall-rule open
to the GCE LB IP range (`130.211.0.0/22`)
to the GCE LB IP ranges (`130.211.0.0/22` and `35.191.0.0/16`)
## The Ingress controller events complain about quota, how do I increase it?

View file

@ -255,7 +255,7 @@ spec:
spec:
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: ingress-nginx
imagePullPolicy: Always
ports:

View file

@ -28,8 +28,8 @@ Re-encrypty | terminate, apply routing rules, re-encrypt | nginx | Advanced
Name | Description | Platform | Complexity Level
-----| ----------- | ---------- | ----------------
Daemonset | run multiple controllers in a daemonset | nginx | Intermediate
Deployment | run multiple controllers as a deployment | nginx | Intermediate
Daemonset | run multiple controllers in a daemonset | nginx/haproxy | Intermediate
Deployment | run multiple controllers as a deployment | nginx/haproxy | Intermediate
Multi-zone | bridge different zones in a single cluster | gce | Intermediate
Static-ip | a single ingress gets a single static ip | * | Intermediate
Geo-routing | route to geographically closest endpoint | nginx | Advanced

View file

@ -19,7 +19,7 @@ spec:
# hostNetwork: true
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:

View file

@ -16,7 +16,7 @@ spec:
spec:
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-lb
imagePullPolicy: Always
readinessProbe:

View file

@ -19,7 +19,7 @@ spec:
# hostNetwork: true
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:

View file

@ -16,7 +16,7 @@ spec:
spec:
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-lb
imagePullPolicy: Always
readinessProbe:

View file

@ -0,0 +1,59 @@
# Deploying the Nginx Ingress controller
This example aims to demonstrate the deployment of an nginx ingress controller and
use a ConfigMap to enable nginx vts module and export metrics for prometheus.
## Default Backend
The default backend is a Service capable of handling all url paths and hosts the
nginx controller doesn't understand. This most basic implementation just returns
a 404 page:
```console
$ kubectl apply -f default-backend.yaml
deployment "default-http-backend" created
service "default-http-backend" created
$ kubectl -n kube-system get po
NAME READY STATUS RESTARTS AGE
default-http-backend-2657704409-qgwdd 1/1 Running 0 28s
```
## Custom configuration
```console
$ cat nginx-vts-metrics-conf.yaml
apiVersion: v1
data:
enable-vts-status: "true"
kind: ConfigMap
metadata:
name: nginx-vts-metrics-conf
namespace: kube-system
```
```console
$ kubectl create -f nginx-vts-metrics-conf.yaml
```
## Controller
You can deploy the controller as follows:
```console
$ kubectl apply -f nginx-ingress-controller.yaml
deployment "nginx-ingress-controller" created
$ kubectl -n kube-system get po
NAME READY STATUS RESTARTS AGE
default-http-backend-2657704409-qgwdd 1/1 Running 0 2m
nginx-ingress-controller-873061567-4n3k2 1/1 Running 0 42s
```
## Result
Check wether to open the vts status:
```console
$ kubectl exec nginx-ingress-controller-873061567-4n3k2 -n kube-system cat /etc/nginx/nginx.conf|grep vhost_traffic_status_display
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
```

View file

@ -0,0 +1,51 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
k8s-app: default-http-backend
namespace: kube-system
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.0
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
namespace: kube-system
labels:
k8s-app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
k8s-app: default-http-backend

View file

@ -0,0 +1,56 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-ingress-controller
labels:
k8s-app: nginx-ingress-controller
namespace: kube-system
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: nginx-ingress-controller
spec:
# hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration
# however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host
# that said, since hostPort is broken on CNI (https://github.com/kubernetes/kubernetes/issues/31307) we have to use hostNetwork where CNI is used
# like with kubeadm
# hostNetwork: true
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 1
ports:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-vts-metrics-conf

View file

@ -0,0 +1,7 @@
apiVersion: v1
data:
enable-vts-status: "true"
kind: ConfigMap
metadata:
name: nginx-vts-metrics-conf
namespace: kube-system

View file

@ -10,10 +10,13 @@ spec:
metadata:
labels:
name: nginx-ingress-lb
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-lb
readinessProbe:
httpGet:

View file

@ -71,7 +71,7 @@ spec:
hostNetwork: true
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:

View file

@ -11,6 +11,9 @@ spec:
metadata:
labels:
k8s-app: nginx-ingress-controller
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
# hostNetwork makes it possible to use ipv6 and to preserve the source IP correctly regardless of docker configuration
# however, it is not a hard dependency of the nginx-ingress-controller itself and it may cause issues if port 10254 already is taken on the host
@ -19,7 +22,7 @@ spec:
# hostNetwork: true
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:

View file

@ -1,66 +0,0 @@
Create an Ingress rule with a rewrite annotation:
```
$ echo "
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/rewrite-target: /
name: rewrite
namespace: default
spec:
rules:
- host: rewrite.bar.com
http:
paths:
- backend:
serviceName: echoheaders
servicePort: 80
path: /something
" | kubectl create -f -
```
Check the rewrite is working
```
$ curl -v http://172.17.4.99/something -H 'Host: rewrite.bar.com'
* Trying 172.17.4.99...
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
> GET /something HTTP/1.1
> Host: rewrite.bar.com
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.11.0
< Date: Tue, 31 May 2016 16:07:31 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
<
CLIENT VALUES:
client_address=10.2.56.9
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://rewrite.bar.com:8080/
SERVER VALUES:
server_version=nginx: 1.9.11 - lua: 10001
HEADERS RECEIVED:
accept=*/*
connection=close
host=rewrite.bar.com
user-agent=curl/7.43.0
x-forwarded-for=10.2.56.1
x-forwarded-host=rewrite.bar.com
x-forwarded-port=80
x-forwarded-proto=http
x-real-ip=10.2.56.1
BODY:
* Connection #0 to host 172.17.4.99 left intact
-no body in request-
```

View file

@ -0,0 +1,127 @@
# Rewrite
This example demonstrates how to use the Rewrite annotations
## Prerequisites
You will need to make sure you Ingress targets exactly one Ingress
controller by specifying the [ingress.class annotation](/examples/PREREQUISITES.md#ingress-class),
and that you have an ingress controller [running](/examples/deployment) in your cluster.
## Deployment
Rewriting can be controlled using the following annotations:
|Name|Description|Values|
| --- | --- | --- |
|ingress.kubernetes.io/rewrite-target|Target URI where the traffic must be redirected|string|
|ingress.kubernetes.io/add-base-url|indicates if is required to add a base tag in the head of the responses from the upstream servers|bool|
|ingress.kubernetes.io/ssl-redirect|Indicates if the location section is accessible SSL only (defaults to True when Ingress contains a Certificate)|bool|
|ingress.kubernetes.io/force-ssl-redirect|Forces the redirection to HTTPS even if the Ingress is not TLS Enabled|bool|
|ingress.kubernetes.io/app-root|Defines the Application Root that the Controller must redirect if it's not in '/' context|string|
## Validation
### Rewrite Target
Create an Ingress rule with a rewrite annotation:
```
$ echo "
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/rewrite-target: /
name: rewrite
namespace: default
spec:
rules:
- host: rewrite.bar.com
http:
paths:
- backend:
serviceName: echoheaders
servicePort: 80
path: /something
" | kubectl create -f -
```
Check the rewrite is working
```
$ curl -v http://172.17.4.99/something -H 'Host: rewrite.bar.com'
* Trying 172.17.4.99...
* Connected to 172.17.4.99 (172.17.4.99) port 80 (#0)
> GET /something HTTP/1.1
> Host: rewrite.bar.com
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.11.0
< Date: Tue, 31 May 2016 16:07:31 GMT
< Content-Type: text/plain
< Transfer-Encoding: chunked
< Connection: keep-alive
<
CLIENT VALUES:
client_address=10.2.56.9
command=GET
real path=/
query=nil
request_version=1.1
request_uri=http://rewrite.bar.com:8080/
SERVER VALUES:
server_version=nginx: 1.9.11 - lua: 10001
HEADERS RECEIVED:
accept=*/*
connection=close
host=rewrite.bar.com
user-agent=curl/7.43.0
x-forwarded-for=10.2.56.1
x-forwarded-host=rewrite.bar.com
x-forwarded-port=80
x-forwarded-proto=http
x-real-ip=10.2.56.1
BODY:
* Connection #0 to host 172.17.4.99 left intact
-no body in request-
```
### App Root
Create an Ingress rule with a app-root annotation:
```
$ echo "
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
ingress.kubernetes.io/app-root: /app1
name: approot
namespace: default
spec:
rules:
- host: approot.bar.com
http:
paths:
- backend:
serviceName: echoheaders
servicePort: 80
path: /
" | kubectl create -f -
```
Check the rewrite is working
```
$ curl -I -k http://approot.bar.com/
HTTP/1.1 302 Moved Temporarily
Server: nginx/1.11.10
Date: Mon, 13 Mar 2017 14:57:15 GMT
Content-Type: text/html
Content-Length: 162
Location: http://stickyingress.example.com/app1
Connection: keep-alive
```

View file

@ -0,0 +1,65 @@
# Deploying multi Haproxy Ingress Controllers
This example aims to demonstrate the Deployment of multi haproxy ingress controllers.
## Prerequisites
This ingress controller doesn't yet have support for
[ingress classes](/examples/PREREQUISITES.md#ingress-class). You MUST turn
down any existing ingress controllers before running HAProxy Ingress controller or
they will fight for Ingresses. This includes any cloudprovider controller.
This document has also the following prerequisites:
* Create a [TLS secret](/examples/PREREQUISITES.md#tls-certificates) named `tls-secret` to be used as default TLS certificate
Creating the TLS secret:
```console
$ openssl req \
-x509 -newkey rsa:2048 -nodes -days 365 \
-keyout tls.key -out tls.crt -subj '/CN=localhost'
$ kubectl create secret tls tls-secret --cert=tls.crt --key=tls.key
$ rm -v tls.crt tls.key
```
## Default Backend
The default backend is a service of handling all url paths and hosts the haproxy controller doesn't understand. Deploy the default-http-backend as follow:
```console
$ kubectl create -f default-backend.yaml
deployment "default-http-backend" created
service "default-http-backend" created
$ kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default-http-backend 192.168.3.4 <none> 80/TCP 30m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
default-http-backend-q5sb6 1/1 Running 0 30m
```
## Ingress Deployment
Deploy the Deployment of multi controllers as follows:
```console
$ kubectl apply -f haproxy-ingress-deployment.yaml
deployment "haproxy-ingress" created
```
Check if the controller was successfully deployed:
```console
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
default-http-backend 1 1 1 1 30m
haproxy-ingress 2 2 2 2 45s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
default-http-backend-q5sb6 1/1 Running 0 35m
haproxy-ingress-1779899633-k045t 1/1 Running 0 1m
haproxy-ingress-1779899633-mhthv 1/1 Running 0 1m
```

View file

@ -0,0 +1,49 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
k8s-app: default-http-backend
spec:
replicas: 1
template:
metadata:
labels:
k8s-app: default-http-backend
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissable as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: gcr.io/google_containers/defaultbackend:1.0
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi
---
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
labels:
k8s-app: default-http-backend
spec:
ports:
- port: 80
targetPort: 8080
selector:
k8s-app: default-http-backend

View file

@ -0,0 +1,39 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
run: haproxy-ingress
name: haproxy-ingress
spec:
replicas: 2
selector:
matchLabels:
run: haproxy-ingress
template:
metadata:
labels:
run: haproxy-ingress
spec:
containers:
- name: haproxy-ingress
image: quay.io/jcmoraisjr/haproxy-ingress
imagePullPolicy: IfNotPresent
args:
- --default-backend-service=default/default-http-backend
- --default-ssl-certificate=default/tls-secret
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
- name: stat
containerPort: 1936
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace

View file

@ -14,7 +14,7 @@ spec:
spec:
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:

View file

@ -18,7 +18,7 @@ spec:
# hostNetwork: true
terminationGracePeriodSeconds: 60
containers:
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
- image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.3
name: nginx-ingress-controller
readinessProbe:
httpGet:

View file

@ -234,7 +234,7 @@ func getDeployer() (deployer, error) {
case "bash":
return bash{}, nil
default:
return nil, fmt.Errorf("Unknown deployment strategy %q", *deployment)
return nil, fmt.Errorf("unknown deployment strategy %q", *deployment)
}
}