Remove GenericController and add tests
This commit is contained in:
parent
1701bfc334
commit
86f39d9deb
39 changed files with 1131 additions and 1325 deletions
64
cmd/nginx/flag_test.go
Normal file
64
cmd/nginx/flag_test.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resetForTesting clears all flag state and sets the usage function as directed.
|
||||||
|
// After calling resetForTesting, parse errors in flag handling will not
|
||||||
|
// exit the program.
|
||||||
|
// Extracted from https://github.com/golang/go/blob/master/src/flag/export_test.go
|
||||||
|
func resetForTesting(usage func()) {
|
||||||
|
flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
|
||||||
|
flag.Usage = usage
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMandatoryFlag(t *testing.T) {
|
||||||
|
_, _, err := parseFlags()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected and error about default backend service")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaults(t *testing.T) {
|
||||||
|
resetForTesting(func() { t.Fatal("bad parse") })
|
||||||
|
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
os.Args = []string{"cmd", "--default-backend-service", "namespace/test", "--http-port", "0", "--https-port", "0"}
|
||||||
|
|
||||||
|
showVersion, conf, err := parseFlags()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error parsing default flags: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if showVersion {
|
||||||
|
t.Fatal("expected false but true was returned for flag show-version")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf == nil {
|
||||||
|
t.Fatal("expected a configuration but nil returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupSSLProxy(t *testing.T) {
|
||||||
|
// TODO
|
||||||
|
}
|
191
cmd/nginx/flags.go
Normal file
191
cmd/nginx/flags.go
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/controller"
|
||||||
|
ngx_config "k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
|
ing_net "k8s.io/ingress-nginx/pkg/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defIngressClass = "nginx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseFlags() (bool, *controller.Configuration, error) {
|
||||||
|
var (
|
||||||
|
flags = pflag.NewFlagSet("", pflag.ExitOnError)
|
||||||
|
|
||||||
|
apiserverHost = flags.String("apiserver-host", "", "The address of the Kubernetes Apiserver "+
|
||||||
|
"to connect to in the format of protocol://address:port, e.g., "+
|
||||||
|
"http://localhost:8080. If not specified, the assumption is that the binary runs inside a "+
|
||||||
|
"Kubernetes cluster and local discovery is attempted.")
|
||||||
|
kubeConfigFile = flags.String("kubeconfig", "", "Path to kubeconfig file with authorization and master location information.")
|
||||||
|
|
||||||
|
defaultSvc = flags.String("default-backend-service", "",
|
||||||
|
`Service used to serve a 404 page for the default backend. Takes the form
|
||||||
|
namespace/name. The controller uses the first node port of this Service for
|
||||||
|
the default backend.`)
|
||||||
|
|
||||||
|
ingressClass = flags.String("ingress-class", "",
|
||||||
|
`Name of the ingress class to route through this controller.`)
|
||||||
|
|
||||||
|
configMap = flags.String("configmap", "",
|
||||||
|
`Name of the ConfigMap that contains the custom configuration to use`)
|
||||||
|
|
||||||
|
publishSvc = flags.String("publish-service", "",
|
||||||
|
`Service fronting the ingress controllers. Takes the form
|
||||||
|
namespace/name. The controller will set the endpoint records on the
|
||||||
|
ingress objects to reflect those on the service.`)
|
||||||
|
|
||||||
|
tcpConfigMapName = flags.String("tcp-services-configmap", "",
|
||||||
|
`Name of the ConfigMap that contains the definition of the TCP services to expose.
|
||||||
|
The key in the map indicates the external port to be used. The value is the name of the
|
||||||
|
service with the format namespace/serviceName and the port of the service could be a
|
||||||
|
number of the name of the port.
|
||||||
|
The ports 80 and 443 are not allowed as external ports. This ports are reserved for the backend`)
|
||||||
|
|
||||||
|
udpConfigMapName = flags.String("udp-services-configmap", "",
|
||||||
|
`Name of the ConfigMap that contains the definition of the UDP services to expose.
|
||||||
|
The key in the map indicates the external port to be used. The value is the name of the
|
||||||
|
service with the format namespace/serviceName and the port of the service could be a
|
||||||
|
number of the name of the port.`)
|
||||||
|
|
||||||
|
resyncPeriod = flags.Duration("sync-period", 600*time.Second,
|
||||||
|
`Relist and confirm cloud resources this often. Default is 10 minutes`)
|
||||||
|
|
||||||
|
watchNamespace = flags.String("watch-namespace", apiv1.NamespaceAll,
|
||||||
|
`Namespace to watch for Ingress. Default is to watch all namespaces`)
|
||||||
|
|
||||||
|
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
|
||||||
|
|
||||||
|
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret
|
||||||
|
that contains a SSL certificate to be used as default for a HTTPS catch-all server`)
|
||||||
|
|
||||||
|
defHealthzURL = flags.String("health-check-path", "/healthz", `Defines
|
||||||
|
the URL to be used as health check inside in the default server in NGINX.`)
|
||||||
|
|
||||||
|
updateStatus = flags.Bool("update-status", true, `Indicates if the
|
||||||
|
ingress controller should update the Ingress status IP/hostname. Default is true`)
|
||||||
|
|
||||||
|
electionID = flags.String("election-id", "ingress-controller-leader", `Election id to use for status update.`)
|
||||||
|
|
||||||
|
forceIsolation = flags.Bool("force-namespace-isolation", false,
|
||||||
|
`Force namespace isolation. This flag is required to avoid the reference of secrets or
|
||||||
|
configmaps located in a different namespace than the specified in the flag --watch-namespace.`)
|
||||||
|
|
||||||
|
disableNodeList = flags.Bool("disable-node-list", false,
|
||||||
|
`Disable querying nodes. If --force-namespace-isolation is true, this should also be set.`)
|
||||||
|
|
||||||
|
updateStatusOnShutdown = flags.Bool("update-status-on-shutdown", true, `Indicates if the
|
||||||
|
ingress controller should update the Ingress status IP/hostname when the controller
|
||||||
|
is being stopped. Default is true`)
|
||||||
|
|
||||||
|
sortBackends = flags.Bool("sort-backends", false,
|
||||||
|
`Defines if backends and it's endpoints should be sorted`)
|
||||||
|
|
||||||
|
useNodeInternalIP = flags.Bool("report-node-internal-ip-address", false,
|
||||||
|
`Defines if the nodes IP address to be returned in the ingress status should be the internal instead of the external IP address`)
|
||||||
|
|
||||||
|
showVersion = flags.Bool("version", false,
|
||||||
|
`Shows release information about the NGINX Ingress controller`)
|
||||||
|
|
||||||
|
enableSSLPassthrough = flags.Bool("enable-ssl-passthrough", false, `Enable SSL passthrough feature. Default is disabled`)
|
||||||
|
|
||||||
|
httpPort = flags.Int("http-port", 80, `Indicates the port to use for HTTP traffic`)
|
||||||
|
httpsPort = flags.Int("https-port", 443, `Indicates the port to use for HTTPS traffic`)
|
||||||
|
statusPort = flags.Int("status-port", 18080, `Indicates the TCP port to use for exposing the nginx status page`)
|
||||||
|
sslProxyPort = flags.Int("ssl-passtrough-proxy-port", 442, `Default port to use internally for SSL when SSL Passthgough is enabled`)
|
||||||
|
defServerPort = flags.Int("default-server-port", 8181, `Default port to use for exposing the default server (catch all)`)
|
||||||
|
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
|
||||||
|
)
|
||||||
|
|
||||||
|
flag.Set("logtostderr", "true")
|
||||||
|
|
||||||
|
flags.AddGoFlagSet(flag.CommandLine)
|
||||||
|
flags.Parse(os.Args)
|
||||||
|
flag.Set("logtostderr", "true")
|
||||||
|
|
||||||
|
// Workaround for this issue:
|
||||||
|
// https://github.com/kubernetes/kubernetes/issues/17162
|
||||||
|
flag.CommandLine.Parse([]string{})
|
||||||
|
|
||||||
|
if *showVersion {
|
||||||
|
return true, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if *defaultSvc == "" {
|
||||||
|
return false, nil, fmt.Errorf("Please specify --default-backend-service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ingressClass != "" {
|
||||||
|
glog.Infof("Watching for ingress class: %s", *ingressClass)
|
||||||
|
|
||||||
|
if *ingressClass != defIngressClass {
|
||||||
|
glog.Warningf("only Ingress with class \"%v\" will be processed by this ingress controller", *ingressClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check port collisions
|
||||||
|
if !ing_net.IsPortAvailable(*httpPort) {
|
||||||
|
return false, nil, fmt.Errorf("Port %v is already in use. Please check the flag --http-port", *httpPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ing_net.IsPortAvailable(*httpsPort) {
|
||||||
|
return false, nil, fmt.Errorf("Port %v is already in use. Please check the flag --https-port", *httpsPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ing_net.IsPortAvailable(*statusPort) {
|
||||||
|
return false, nil, fmt.Errorf("Port %v is already in use. Please check the flag --status-port", *statusPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ing_net.IsPortAvailable(*defServerPort) {
|
||||||
|
return false, nil, fmt.Errorf("Port %v is already in use. Please check the flag --default-server-port", *defServerPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *enableSSLPassthrough && !ing_net.IsPortAvailable(*sslProxyPort) {
|
||||||
|
return false, nil, fmt.Errorf("Port %v is already in use. Please check the flag --ssl-passtrough-proxy-port", *sslProxyPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &controller.Configuration{
|
||||||
|
APIServerHost: *apiserverHost,
|
||||||
|
KubeConfigFile: *kubeConfigFile,
|
||||||
|
UpdateStatus: *updateStatus,
|
||||||
|
ElectionID: *electionID,
|
||||||
|
EnableProfiling: *profiling,
|
||||||
|
EnableSSLPassthrough: *enableSSLPassthrough,
|
||||||
|
ResyncPeriod: *resyncPeriod,
|
||||||
|
DefaultService: *defaultSvc,
|
||||||
|
IngressClass: *ingressClass,
|
||||||
|
Namespace: *watchNamespace,
|
||||||
|
ConfigMapName: *configMap,
|
||||||
|
TCPConfigMapName: *tcpConfigMapName,
|
||||||
|
UDPConfigMapName: *udpConfigMapName,
|
||||||
|
DefaultSSLCertificate: *defSSLCertificate,
|
||||||
|
DefaultHealthzURL: *defHealthzURL,
|
||||||
|
PublishService: *publishSvc,
|
||||||
|
ForceNamespaceIsolation: *forceIsolation,
|
||||||
|
DisableNodeList: *disableNodeList,
|
||||||
|
UpdateStatusOnShutdown: *updateStatusOnShutdown,
|
||||||
|
SortBackends: *sortBackends,
|
||||||
|
UseNodeInternalIP: *useNodeInternalIP,
|
||||||
|
ListenPorts: &ngx_config.ListenPorts{
|
||||||
|
Default: *defServerPort,
|
||||||
|
Health: *healthzPort,
|
||||||
|
HTTP: *httpPort,
|
||||||
|
HTTPS: *httpsPort,
|
||||||
|
SSLProxy: *sslProxyPort,
|
||||||
|
Status: *statusPort,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, config, nil
|
||||||
|
}
|
|
@ -17,29 +17,125 @@ limitations under the License.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/controller"
|
proxyproto "github.com/armon/go-proxyproto"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/server/healthz"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/controller"
|
||||||
|
"k8s.io/ingress-nginx/pkg/k8s"
|
||||||
|
"k8s.io/ingress-nginx/pkg/net/ssl"
|
||||||
|
"k8s.io/ingress-nginx/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// start a new nginx controller
|
fmt.Println(version.String())
|
||||||
ngx := controller.NewNGINXController()
|
|
||||||
|
showVersion, conf, err := parseFlags()
|
||||||
|
if showVersion {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeClient, err := createApiserverClient(conf.APIServerHost, conf.KubeConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
handleFatalInitError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ns, name, err := k8s.ParseNameNS(conf.DefaultService)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("invalid format for service %v: %v", conf.DefaultService, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = kubeClient.Core().Services(ns).Get(name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "cannot get services in the namespace") {
|
||||||
|
glog.Fatalf("✖ It seems the cluster it is running with Authorization enabled (like RBAC) and there is no permissions for the ingress controller. Please check the configuration")
|
||||||
|
}
|
||||||
|
glog.Fatalf("no service with name %v found: %v", conf.DefaultService, err)
|
||||||
|
}
|
||||||
|
glog.Infof("validated %v as the default backend", conf.DefaultService)
|
||||||
|
|
||||||
|
if conf.PublishService != "" {
|
||||||
|
ns, name, err := k8s.ParseNameNS(conf.PublishService)
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("invalid service format: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc, err := kubeClient.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("unexpected error getting information about service %v: %v", conf.PublishService, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(svc.Status.LoadBalancer.Ingress) == 0 {
|
||||||
|
if len(svc.Spec.ExternalIPs) > 0 {
|
||||||
|
glog.Infof("service %v validated as assigned with externalIP", conf.PublishService)
|
||||||
|
} else {
|
||||||
|
// We could poll here, but we instead just exit and rely on k8s to restart us
|
||||||
|
glog.Fatalf("service %s does not (yet) have ingress points", conf.PublishService)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
glog.Infof("service %v validated as source of Ingress status", conf.PublishService)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.Namespace != "" {
|
||||||
|
_, err = kubeClient.CoreV1().Namespaces().Get(conf.Namespace, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("no watchNamespace with name %v found: %v", conf.Namespace, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.ResyncPeriod.Seconds() < 10 {
|
||||||
|
glog.Fatalf("resync period (%vs) is too low", conf.ResyncPeriod.Seconds())
|
||||||
|
}
|
||||||
|
|
||||||
|
// create directory that will contains the SSL Certificates
|
||||||
|
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Failed to mkdir SSL directory: %v", err)
|
||||||
|
}
|
||||||
|
// create the default SSL certificate (dummy)
|
||||||
|
sha, pem := createDefaultSSLCertificate()
|
||||||
|
conf.FakeCertificatePath = pem
|
||||||
|
conf.FakeCertificateSHA = sha
|
||||||
|
|
||||||
|
conf.Client = kubeClient
|
||||||
|
conf.DefaultIngressClass = defIngressClass
|
||||||
|
|
||||||
|
ngx := controller.NewNGINXController(conf)
|
||||||
|
|
||||||
|
if conf.EnableSSLPassthrough {
|
||||||
|
setupSSLProxy(conf.ListenPorts.HTTPS, conf.ListenPorts.SSLProxy, ngx)
|
||||||
|
}
|
||||||
|
|
||||||
go handleSigterm(ngx)
|
go handleSigterm(ngx)
|
||||||
// start the controller
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
go registerHandlers(conf.EnableProfiling, conf.ListenPorts.Health, ngx, mux)
|
||||||
|
|
||||||
ngx.Start()
|
ngx.Start()
|
||||||
// wait
|
|
||||||
glog.Infof("shutting down Ingress controller...")
|
|
||||||
for {
|
|
||||||
glog.Infof("Handled quit, awaiting pod deletion")
|
|
||||||
time.Sleep(30 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSigterm(ngx *controller.NGINXController) {
|
func handleSigterm(ngx *controller.NGINXController) {
|
||||||
|
@ -54,6 +150,176 @@ func handleSigterm(ngx *controller.NGINXController) {
|
||||||
exitCode = 1
|
exitCode = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glog.Infof("Handled quit, awaiting pod deletion")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
|
||||||
glog.Infof("Exiting with %v", exitCode)
|
glog.Infof("Exiting with %v", exitCode)
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupSSLProxy(sslPort, proxyPort int, n *controller.NGINXController) {
|
||||||
|
glog.Info("starting TLS proxy for SSL passthrough")
|
||||||
|
n.Proxy = &controller.TCPProxy{
|
||||||
|
Default: &controller.TCPServer{
|
||||||
|
Hostname: "localhost",
|
||||||
|
IP: "127.0.0.1",
|
||||||
|
Port: proxyPort,
|
||||||
|
ProxyProtocol: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", sslPort))
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyList := &proxyproto.Listener{Listener: listener}
|
||||||
|
|
||||||
|
// start goroutine that accepts tcp connections in port 443
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if n.IsProxyProtocolEnabled {
|
||||||
|
// we need to wrap the listener in order to decode
|
||||||
|
// proxy protocol before handling the connection
|
||||||
|
conn, err = proxyList.Accept()
|
||||||
|
} else {
|
||||||
|
conn, err = listener.Accept()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
glog.Warningf("unexpected error accepting tcp connection: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(3).Infof("remote address %s to local %s", conn.RemoteAddr(), conn.LocalAddr())
|
||||||
|
go n.Proxy.Handle(conn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// createApiserverClient creates new Kubernetes Apiserver client. When kubeconfig or apiserverHost param is empty
|
||||||
|
// the function assumes that it is running inside a Kubernetes cluster and attempts to
|
||||||
|
// discover the Apiserver. Otherwise, it connects to the Apiserver specified.
|
||||||
|
//
|
||||||
|
// apiserverHost param is in the format of protocol://address:port/pathPrefix, e.g.http://localhost:8001.
|
||||||
|
// kubeConfig location of kubeconfig file
|
||||||
|
func createApiserverClient(apiserverHost string, kubeConfig string) (*kubernetes.Clientset, error) {
|
||||||
|
cfg, err := buildConfigFromFlags(apiserverHost, kubeConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.QPS = defaultQPS
|
||||||
|
cfg.Burst = defaultBurst
|
||||||
|
cfg.ContentType = "application/vnd.kubernetes.protobuf"
|
||||||
|
|
||||||
|
glog.Infof("Creating API client for %s", cfg.Host)
|
||||||
|
|
||||||
|
client, err := kubernetes.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := client.Discovery().ServerVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("Running in Kubernetes Cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
|
||||||
|
v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
fakeCertificate = "default-fake-certificate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildConfigFromFlags builds REST config based on master URL and kubeconfig path.
|
||||||
|
// If both of them are empty then in cluster config is used.
|
||||||
|
func buildConfigFromFlags(masterURL, kubeconfigPath string) (*rest.Config, error) {
|
||||||
|
if kubeconfigPath == "" && masterURL == "" {
|
||||||
|
kubeconfig, err := rest.InClusterConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return kubeconfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||||
|
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
||||||
|
&clientcmd.ConfigOverrides{
|
||||||
|
ClusterInfo: clientcmdapi.Cluster{
|
||||||
|
Server: masterURL,
|
||||||
|
},
|
||||||
|
}).ClientConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles fatal init error that prevents server from doing any work. Prints verbose error
|
||||||
|
* message and quits the server.
|
||||||
|
*/
|
||||||
|
func handleFatalInitError(err error) {
|
||||||
|
glog.Fatalf("Error while initializing connection to Kubernetes apiserver. "+
|
||||||
|
"This most likely means that the cluster is misconfigured (e.g., it has "+
|
||||||
|
"invalid apiserver certificates or service accounts configuration). Reason: %s\n"+
|
||||||
|
"Refer to the troubleshooting guide for more information: "+
|
||||||
|
"https://github.com/kubernetes/ingress-nginx/blob/master/docs/troubleshooting.md", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerHandlers(enableProfiling bool, port int, ic *controller.NGINXController, mux *http.ServeMux) {
|
||||||
|
// expose health check endpoint (/healthz)
|
||||||
|
healthz.InstallHandler(mux,
|
||||||
|
healthz.PingHealthz,
|
||||||
|
ic,
|
||||||
|
)
|
||||||
|
|
||||||
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
|
|
||||||
|
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
b, _ := json.Marshal(version.String())
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if enableProfiling {
|
||||||
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||||
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: fmt.Sprintf(":%v", port),
|
||||||
|
Handler: mux,
|
||||||
|
}
|
||||||
|
glog.Fatal(server.ListenAndServe())
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDefaultSSLCertificate() (string, string) {
|
||||||
|
defCert, defKey := ssl.GetFakeSSLCert()
|
||||||
|
c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
|
||||||
|
if err != nil {
|
||||||
|
glog.Fatalf("Error generating self signed certificate: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.PemSHA, c.PemFileName
|
||||||
|
}
|
||||||
|
|
|
@ -14,28 +14,40 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package controller
|
package file
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestDiff(t *testing.T) {
|
func TestSHA1(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
a []byte
|
content []byte
|
||||||
b []byte
|
sha string
|
||||||
empty bool
|
|
||||||
}{
|
}{
|
||||||
{[]byte(""), []byte(""), true},
|
{[]byte(""), "da39a3ee5e6b4b0d3255bfef95601890afd80709"},
|
||||||
{[]byte("a"), []byte("a"), true},
|
{[]byte("hello world"), "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"},
|
||||||
{[]byte("a"), []byte("b"), false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
b, err := diff(test.a, test.b)
|
f, err := ioutil.TempFile("", "sha-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error returned: %v", err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if len(b) == 0 && !test.empty {
|
f.Write(test.content)
|
||||||
t.Fatalf("expected empty but returned %s", b)
|
f.Sync()
|
||||||
|
|
||||||
|
sha := SHA1(f.Name())
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
if sha != test.sha {
|
||||||
|
t.Fatalf("expected %v but returned %s", test.sha, sha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sha := SHA1("")
|
||||||
|
if sha != ""{
|
||||||
|
t.Fatalf("expected an empty sha but returned %s", sha)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -77,7 +77,7 @@ func TestIngressCorsConfig(t *testing.T) {
|
||||||
t.Errorf("expected a Config type")
|
t.Errorf("expected a Config type")
|
||||||
}
|
}
|
||||||
|
|
||||||
if nginxCors.CorsEnabled != true {
|
if !nginxCors.CorsEnabled {
|
||||||
t.Errorf("expected cors enabled but returned %v", nginxCors.CorsEnabled)
|
t.Errorf("expected cors enabled but returned %v", nginxCors.CorsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ import (
|
||||||
// syncSecret keeps in sync Secrets used by Ingress rules with the files on
|
// syncSecret keeps in sync Secrets used by Ingress rules with the files on
|
||||||
// disk to allow copy of the content of the secret to disk to be used
|
// disk to allow copy of the content of the secret to disk to be used
|
||||||
// by external processes.
|
// by external processes.
|
||||||
func (ic *GenericController) syncSecret(key string) {
|
func (ic *NGINXController) syncSecret(key string) {
|
||||||
glog.V(3).Infof("starting syncing of secret %v", key)
|
glog.V(3).Infof("starting syncing of secret %v", key)
|
||||||
|
|
||||||
cert, err := ic.getPemCertificate(key)
|
cert, err := ic.getPemCertificate(key)
|
||||||
|
@ -70,7 +70,7 @@ func (ic *GenericController) syncSecret(key string) {
|
||||||
|
|
||||||
// getPemCertificate receives a secret, and creates a ingress.SSLCert as return.
|
// getPemCertificate receives a secret, and creates a ingress.SSLCert as return.
|
||||||
// It parses the secret and verifies if it's a keypair, or a 'ca.crt' secret only.
|
// It parses the secret and verifies if it's a keypair, or a 'ca.crt' secret only.
|
||||||
func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
|
func (ic *NGINXController) getPemCertificate(secretName string) (*ingress.SSLCert, error) {
|
||||||
secret, err := ic.listers.Secret.GetByName(secretName)
|
secret, err := ic.listers.Secret.GetByName(secretName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error retrieving secret %v: %v", secretName, err)
|
return nil, fmt.Errorf("error retrieving secret %v: %v", secretName, err)
|
||||||
|
@ -127,7 +127,7 @@ func (ic *GenericController) getPemCertificate(secretName string) (*ingress.SSLC
|
||||||
// checkMissingSecrets verify if one or more ingress rules contains a reference
|
// checkMissingSecrets verify if one or more ingress rules contains a reference
|
||||||
// to a secret that is not present in the local secret store.
|
// to a secret that is not present in the local secret store.
|
||||||
// In this case we call syncSecret.
|
// In this case we call syncSecret.
|
||||||
func (ic *GenericController) checkMissingSecrets() {
|
func (ic *NGINXController) checkMissingSecrets() {
|
||||||
for _, obj := range ic.listers.Ingress.List() {
|
for _, obj := range ic.listers.Ingress.List() {
|
||||||
ing := obj.(*extensions.Ingress)
|
ing := obj.(*extensions.Ingress)
|
||||||
|
|
||||||
|
|
|
@ -103,8 +103,8 @@ func buildControllerForBackendSSL() cache_client.Controller {
|
||||||
return cache_client.New(cfg)
|
return cache_client.New(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildGenericControllerForBackendSSL() *GenericController {
|
func buildGenericControllerForBackendSSL() *NGINXController {
|
||||||
gc := &GenericController{
|
gc := &NGINXController{
|
||||||
syncRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.3, 1),
|
syncRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.3, 1),
|
||||||
cfg: &Configuration{
|
cfg: &Configuration{
|
||||||
Client: buildSimpleClientSetForBackendSSL(),
|
Client: buildSimpleClientSetForBackendSSL(),
|
||||||
|
|
68
pkg/ingress/controller/checker.go
Normal file
68
pkg/ingress/controller/checker.go
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/ncabatoff/process-exporter/proc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Name returns the healthcheck name
|
||||||
|
func (n NGINXController) Name() string {
|
||||||
|
return "Ingress Controller"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
||||||
|
func (n NGINXController) Check(_ *http.Request) error {
|
||||||
|
res, err := http.Get(fmt.Sprintf("http://0.0.0.0:%v%v", n.cfg.ListenPorts.Status, ngxHealthPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("ingress controller is not healthy")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the nginx master process is running
|
||||||
|
fs, err := proc.NewFS("/proc")
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("%v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := ioutil.ReadFile("/run/nginx.pid")
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("%v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = fs.NewProc(pid)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("%v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -24,7 +24,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -35,12 +34,7 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/conversion"
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
|
||||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
||||||
"k8s.io/client-go/tools/record"
|
|
||||||
"k8s.io/client-go/util/flowcontrol"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
|
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
|
@ -48,11 +42,10 @@ import (
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/healthcheck"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/proxy"
|
||||||
|
ngx_config "k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/status"
|
|
||||||
"k8s.io/ingress-nginx/pkg/k8s"
|
"k8s.io/ingress-nginx/pkg/k8s"
|
||||||
"k8s.io/ingress-nginx/pkg/net/ssl"
|
|
||||||
"k8s.io/ingress-nginx/pkg/task"
|
"k8s.io/ingress-nginx/pkg/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,63 +53,32 @@ const (
|
||||||
defUpstreamName = "upstream-default-backend"
|
defUpstreamName = "upstream-default-backend"
|
||||||
defServerName = "_"
|
defServerName = "_"
|
||||||
rootLocation = "/"
|
rootLocation = "/"
|
||||||
|
|
||||||
fakeCertificate = "default-fake-certificate"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// list of ports that cannot be used by TCP or UDP services
|
// list of ports that cannot be used by TCP or UDP services
|
||||||
reservedPorts = []string{"80", "443", "8181", "18080"}
|
reservedPorts = []string{"80", "443", "8181", "18080"}
|
||||||
|
|
||||||
fakeCertificatePath = ""
|
cloner *conversion.Cloner
|
||||||
fakeCertificateSHA = ""
|
|
||||||
|
|
||||||
cloner = conversion.NewCloner()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenericController holds the boilerplate code required to build an Ingress controlller.
|
func init() {
|
||||||
type GenericController struct {
|
cloner := conversion.NewCloner()
|
||||||
cfg *Configuration
|
cloner.RegisterDeepCopyFunc(ingress.GetGeneratedDeepCopyFuncs)
|
||||||
|
|
||||||
listers *ingress.StoreLister
|
|
||||||
cacheController *cacheController
|
|
||||||
|
|
||||||
annotations annotationExtractor
|
|
||||||
|
|
||||||
recorder record.EventRecorder
|
|
||||||
|
|
||||||
syncQueue *task.Queue
|
|
||||||
|
|
||||||
syncStatus status.Sync
|
|
||||||
|
|
||||||
// local store of SSL certificates
|
|
||||||
// (only certificates used in ingress)
|
|
||||||
sslCertTracker *sslCertTracker
|
|
||||||
|
|
||||||
syncRateLimiter flowcontrol.RateLimiter
|
|
||||||
|
|
||||||
// stopLock is used to enforce only a single call to Stop is active.
|
|
||||||
// Needed because we allow stopping through an http endpoint and
|
|
||||||
// allowing concurrent stoppers leads to stack traces.
|
|
||||||
stopLock *sync.Mutex
|
|
||||||
|
|
||||||
stopCh chan struct{}
|
|
||||||
|
|
||||||
// runningConfig contains the running configuration in the Backend
|
|
||||||
runningConfig *ingress.Configuration
|
|
||||||
|
|
||||||
forceReload int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuration contains all the settings required by an Ingress controller
|
// Configuration contains all the settings required by an Ingress controller
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
|
APIServerHost string
|
||||||
|
KubeConfigFile string
|
||||||
Client clientset.Interface
|
Client clientset.Interface
|
||||||
|
|
||||||
ResyncPeriod time.Duration
|
ResyncPeriod time.Duration
|
||||||
|
|
||||||
|
ConfigMapName string
|
||||||
DefaultService string
|
DefaultService string
|
||||||
IngressClass string
|
IngressClass string
|
||||||
Namespace string
|
Namespace string
|
||||||
ConfigMapName string
|
|
||||||
|
|
||||||
ForceNamespaceIsolation bool
|
ForceNamespaceIsolation bool
|
||||||
DisableNodeList bool
|
DisableNodeList bool
|
||||||
|
@ -125,14 +87,13 @@ type Configuration struct {
|
||||||
TCPConfigMapName string
|
TCPConfigMapName string
|
||||||
// optional
|
// optional
|
||||||
UDPConfigMapName string
|
UDPConfigMapName string
|
||||||
DefaultSSLCertificate string
|
|
||||||
DefaultHealthzURL string
|
DefaultHealthzURL string
|
||||||
DefaultIngressClass string
|
DefaultIngressClass string
|
||||||
|
DefaultSSLCertificate string
|
||||||
|
|
||||||
// optional
|
// optional
|
||||||
PublishService string
|
PublishService string
|
||||||
// Backend is the particular implementation to be used.
|
|
||||||
// (for instance NGINX)
|
|
||||||
Backend ingress.Controller
|
|
||||||
|
|
||||||
UpdateStatus bool
|
UpdateStatus bool
|
||||||
UseNodeInternalIP bool
|
UseNodeInternalIP bool
|
||||||
|
@ -140,74 +101,25 @@ type Configuration struct {
|
||||||
UpdateStatusOnShutdown bool
|
UpdateStatusOnShutdown bool
|
||||||
|
|
||||||
SortBackends bool
|
SortBackends bool
|
||||||
}
|
|
||||||
|
|
||||||
// newIngressController creates an Ingress controller
|
ListenPorts *ngx_config.ListenPorts
|
||||||
func newIngressController(config *Configuration) *GenericController {
|
|
||||||
|
|
||||||
eventBroadcaster := record.NewBroadcaster()
|
EnableSSLPassthrough bool
|
||||||
eventBroadcaster.StartLogging(glog.Infof)
|
|
||||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
|
|
||||||
Interface: config.Client.CoreV1().Events(config.Namespace),
|
|
||||||
})
|
|
||||||
|
|
||||||
ic := GenericController{
|
EnableProfiling bool
|
||||||
cfg: config,
|
|
||||||
stopLock: &sync.Mutex{},
|
|
||||||
stopCh: make(chan struct{}),
|
|
||||||
syncRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.3, 1),
|
|
||||||
recorder: eventBroadcaster.NewRecorder(scheme.Scheme, apiv1.EventSource{
|
|
||||||
Component: "ingress-controller",
|
|
||||||
}),
|
|
||||||
sslCertTracker: newSSLCertTracker(),
|
|
||||||
}
|
|
||||||
|
|
||||||
ic.syncQueue = task.NewTaskQueue(ic.syncIngress)
|
FakeCertificatePath string
|
||||||
|
FakeCertificateSHA string
|
||||||
ic.listers, ic.cacheController = ic.createListers(config.DisableNodeList)
|
|
||||||
|
|
||||||
if config.UpdateStatus {
|
|
||||||
ic.syncStatus = status.NewStatusSyncer(status.Config{
|
|
||||||
Client: config.Client,
|
|
||||||
PublishService: ic.cfg.PublishService,
|
|
||||||
IngressLister: ic.listers.Ingress,
|
|
||||||
ElectionID: config.ElectionID,
|
|
||||||
IngressClass: config.IngressClass,
|
|
||||||
DefaultIngressClass: config.DefaultIngressClass,
|
|
||||||
UpdateStatusOnShutdown: config.UpdateStatusOnShutdown,
|
|
||||||
CustomIngressStatus: ic.cfg.Backend.UpdateIngressStatus,
|
|
||||||
UseNodeInternalIP: ic.cfg.UseNodeInternalIP,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
|
|
||||||
}
|
|
||||||
ic.annotations = newAnnotationExtractor(ic)
|
|
||||||
|
|
||||||
ic.cfg.Backend.SetListers(ic.listers)
|
|
||||||
|
|
||||||
cloner.RegisterDeepCopyFunc(ingress.GetGeneratedDeepCopyFuncs)
|
|
||||||
|
|
||||||
return &ic
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info returns information about the backend
|
|
||||||
func (ic GenericController) Info() *ingress.BackendInfo {
|
|
||||||
return ic.cfg.Backend.Info()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IngressClass returns information about the backend
|
|
||||||
func (ic GenericController) IngressClass() string {
|
|
||||||
return ic.cfg.IngressClass
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultBackend returns the default backend
|
// GetDefaultBackend returns the default backend
|
||||||
func (ic GenericController) GetDefaultBackend() defaults.Backend {
|
func (n NGINXController) GetDefaultBackend() defaults.Backend {
|
||||||
return ic.cfg.Backend.BackendDefaults()
|
return n.backendDefaults
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublishService returns the configured service used to set ingress status
|
// GetPublishService returns the configured service used to set ingress status
|
||||||
func (ic GenericController) GetPublishService() *apiv1.Service {
|
func (n NGINXController) GetPublishService() *apiv1.Service {
|
||||||
s, err := ic.listers.Service.GetByName(ic.cfg.PublishService)
|
s, err := n.listers.Service.GetByName(n.cfg.PublishService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -215,42 +127,37 @@ func (ic GenericController) GetPublishService() *apiv1.Service {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecorder returns the event recorder
|
|
||||||
func (ic GenericController) GetRecorder() record.EventRecorder {
|
|
||||||
return ic.recorder
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSecret searches for a secret in the local secrets Store
|
// GetSecret searches for a secret in the local secrets Store
|
||||||
func (ic GenericController) GetSecret(name string) (*apiv1.Secret, error) {
|
func (n NGINXController) GetSecret(name string) (*apiv1.Secret, error) {
|
||||||
return ic.listers.Secret.GetByName(name)
|
return n.listers.Secret.GetByName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetService searches for a service in the local secrets Store
|
// GetService searches for a service in the local secrets Store
|
||||||
func (ic GenericController) GetService(name string) (*apiv1.Service, error) {
|
func (n NGINXController) GetService(name string) (*apiv1.Service, error) {
|
||||||
return ic.listers.Service.GetByName(name)
|
return n.listers.Service.GetByName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync collects all the pieces required to assemble the configuration file and
|
// sync collects all the pieces required to assemble the configuration file and
|
||||||
// then sends the content to the backend (OnUpdate) receiving the populated
|
// then sends the content to the backend (OnUpdate) receiving the populated
|
||||||
// template as response reloading the backend if is required.
|
// template as response reloading the backend if is required.
|
||||||
func (ic *GenericController) syncIngress(item interface{}) error {
|
func (n *NGINXController) syncIngress(item interface{}) error {
|
||||||
ic.syncRateLimiter.Accept()
|
n.syncRateLimiter.Accept()
|
||||||
|
|
||||||
if ic.syncQueue.IsShuttingDown() {
|
if n.syncQueue.IsShuttingDown() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if element, ok := item.(task.Element); ok {
|
if element, ok := item.(task.Element); ok {
|
||||||
if name, ok := element.Key.(string); ok {
|
if name, ok := element.Key.(string); ok {
|
||||||
if obj, exists, _ := ic.listers.Ingress.GetByKey(name); exists {
|
if obj, exists, _ := n.listers.Ingress.GetByKey(name); exists {
|
||||||
ing := obj.(*extensions.Ingress)
|
ing := obj.(*extensions.Ingress)
|
||||||
ic.readSecrets(ing)
|
n.readSecrets(ing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort ingress rules using the ResourceVersion field
|
// Sort ingress rules using the ResourceVersion field
|
||||||
ings := ic.listers.Ingress.List()
|
ings := n.listers.Ingress.List()
|
||||||
sort.SliceStable(ings, func(i, j int) bool {
|
sort.SliceStable(ings, func(i, j int) bool {
|
||||||
ir := ings[i].(*extensions.Ingress).ResourceVersion
|
ir := ings[i].(*extensions.Ingress).ResourceVersion
|
||||||
jr := ings[j].(*extensions.Ingress).ResourceVersion
|
jr := ings[j].(*extensions.Ingress).ResourceVersion
|
||||||
|
@ -261,14 +168,14 @@ func (ic *GenericController) syncIngress(item interface{}) error {
|
||||||
var ingresses []*extensions.Ingress
|
var ingresses []*extensions.Ingress
|
||||||
for _, ingIf := range ings {
|
for _, ingIf := range ings {
|
||||||
ing := ingIf.(*extensions.Ingress)
|
ing := ingIf.(*extensions.Ingress)
|
||||||
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
|
if !class.IsValid(ing, n.cfg.IngressClass, n.cfg.DefaultIngressClass) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ingresses = append(ingresses, ing)
|
ingresses = append(ingresses, ing)
|
||||||
}
|
}
|
||||||
|
|
||||||
upstreams, servers := ic.getBackendServers(ingresses)
|
upstreams, servers := n.getBackendServers(ingresses)
|
||||||
var passUpstreams []*ingress.SSLPassthroughBackend
|
var passUpstreams []*ingress.SSLPassthroughBackend
|
||||||
|
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
|
@ -294,19 +201,19 @@ func (ic *GenericController) syncIngress(item interface{}) error {
|
||||||
pcfg := ingress.Configuration{
|
pcfg := ingress.Configuration{
|
||||||
Backends: upstreams,
|
Backends: upstreams,
|
||||||
Servers: servers,
|
Servers: servers,
|
||||||
TCPEndpoints: ic.getStreamServices(ic.cfg.TCPConfigMapName, apiv1.ProtocolTCP),
|
TCPEndpoints: n.getStreamServices(n.cfg.TCPConfigMapName, apiv1.ProtocolTCP),
|
||||||
UDPEndpoints: ic.getStreamServices(ic.cfg.UDPConfigMapName, apiv1.ProtocolUDP),
|
UDPEndpoints: n.getStreamServices(n.cfg.UDPConfigMapName, apiv1.ProtocolUDP),
|
||||||
PassthroughBackends: passUpstreams,
|
PassthroughBackends: passUpstreams,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ic.isForceReload() && ic.runningConfig != nil && ic.runningConfig.Equal(&pcfg) {
|
if !n.isForceReload() && n.runningConfig != nil && n.runningConfig.Equal(&pcfg) {
|
||||||
glog.V(3).Infof("skipping backend reload (no changes detected)")
|
glog.V(3).Infof("skipping backend reload (no changes detected)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Infof("backend reload required")
|
glog.Infof("backend reload required")
|
||||||
|
|
||||||
err := ic.cfg.Backend.OnUpdate(pcfg)
|
err := n.OnUpdate(pcfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
incReloadErrorCount()
|
incReloadErrorCount()
|
||||||
glog.Errorf("unexpected failure restarting the backend: \n%v", err)
|
glog.Errorf("unexpected failure restarting the backend: \n%v", err)
|
||||||
|
@ -317,13 +224,13 @@ func (ic *GenericController) syncIngress(item interface{}) error {
|
||||||
incReloadCount()
|
incReloadCount()
|
||||||
setSSLExpireTime(servers)
|
setSSLExpireTime(servers)
|
||||||
|
|
||||||
ic.runningConfig = &pcfg
|
n.runningConfig = &pcfg
|
||||||
ic.SetForceReload(false)
|
n.SetForceReload(false)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *GenericController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service {
|
func (n *NGINXController) getStreamServices(configmapName string, proto apiv1.Protocol) []ingress.L4Service {
|
||||||
glog.V(3).Infof("obtaining information about stream services of type %v located in configmap %v", proto, configmapName)
|
glog.V(3).Infof("obtaining information about stream services of type %v located in configmap %v", proto, configmapName)
|
||||||
if configmapName == "" {
|
if configmapName == "" {
|
||||||
// no configmap configured
|
// no configmap configured
|
||||||
|
@ -336,7 +243,7 @@ func (ic *GenericController) getStreamServices(configmapName string, proto apiv1
|
||||||
return []ingress.L4Service{}
|
return []ingress.L4Service{}
|
||||||
}
|
}
|
||||||
|
|
||||||
configmap, err := ic.listers.ConfigMap.GetByName(configmapName)
|
configmap, err := n.listers.ConfigMap.GetByName(configmapName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("unexpected error reading configmap %v: %v", configmapName, err)
|
glog.Errorf("unexpected error reading configmap %v: %v", configmapName, err)
|
||||||
return []ingress.L4Service{}
|
return []ingress.L4Service{}
|
||||||
|
@ -386,7 +293,7 @@ func (ic *GenericController) getStreamServices(configmapName string, proto apiv1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
svcObj, svcExists, err := ic.listers.Service.GetByKey(nsName)
|
svcObj, svcExists, err := n.listers.Service.GetByKey(nsName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("error getting service %v: %v", nsName, err)
|
glog.Warningf("error getting service %v: %v", nsName, err)
|
||||||
continue
|
continue
|
||||||
|
@ -406,7 +313,7 @@ func (ic *GenericController) getStreamServices(configmapName string, proto apiv1
|
||||||
for _, sp := range svc.Spec.Ports {
|
for _, sp := range svc.Spec.Ports {
|
||||||
if sp.Name == svcPort {
|
if sp.Name == svcPort {
|
||||||
if sp.Protocol == proto {
|
if sp.Protocol == proto {
|
||||||
endps = ic.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{})
|
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -417,7 +324,7 @@ func (ic *GenericController) getStreamServices(configmapName string, proto apiv1
|
||||||
for _, sp := range svc.Spec.Ports {
|
for _, sp := range svc.Spec.Ports {
|
||||||
if sp.Port == int32(targetPort) {
|
if sp.Port == int32(targetPort) {
|
||||||
if sp.Protocol == proto {
|
if sp.Protocol == proto {
|
||||||
endps = ic.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{})
|
endps = n.getEndpoints(svc, &sp, proto, &healthcheck.Upstream{})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -450,29 +357,29 @@ func (ic *GenericController) getStreamServices(configmapName string, proto apiv1
|
||||||
// getDefaultUpstream returns an upstream associated with the
|
// getDefaultUpstream returns an upstream associated with the
|
||||||
// default backend service. In case of error retrieving information
|
// default backend service. In case of error retrieving information
|
||||||
// configure the upstream to return http code 503.
|
// configure the upstream to return http code 503.
|
||||||
func (ic *GenericController) getDefaultUpstream() *ingress.Backend {
|
func (n *NGINXController) getDefaultUpstream() *ingress.Backend {
|
||||||
upstream := &ingress.Backend{
|
upstream := &ingress.Backend{
|
||||||
Name: defUpstreamName,
|
Name: defUpstreamName,
|
||||||
}
|
}
|
||||||
svcKey := ic.cfg.DefaultService
|
svcKey := n.cfg.DefaultService
|
||||||
svcObj, svcExists, err := ic.listers.Service.GetByKey(svcKey)
|
svcObj, svcExists, err := n.listers.Service.GetByKey(svcKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("unexpected error searching the default backend %v: %v", ic.cfg.DefaultService, err)
|
glog.Warningf("unexpected error searching the default backend %v: %v", n.cfg.DefaultService, err)
|
||||||
upstream.Endpoints = append(upstream.Endpoints, ic.cfg.Backend.DefaultEndpoint())
|
upstream.Endpoints = append(upstream.Endpoints, n.DefaultEndpoint())
|
||||||
return upstream
|
return upstream
|
||||||
}
|
}
|
||||||
|
|
||||||
if !svcExists {
|
if !svcExists {
|
||||||
glog.Warningf("service %v does not exist", svcKey)
|
glog.Warningf("service %v does not exist", svcKey)
|
||||||
upstream.Endpoints = append(upstream.Endpoints, ic.cfg.Backend.DefaultEndpoint())
|
upstream.Endpoints = append(upstream.Endpoints, n.DefaultEndpoint())
|
||||||
return upstream
|
return upstream
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := svcObj.(*apiv1.Service)
|
svc := svcObj.(*apiv1.Service)
|
||||||
endps := ic.getEndpoints(svc, &svc.Spec.Ports[0], apiv1.ProtocolTCP, &healthcheck.Upstream{})
|
endps := n.getEndpoints(svc, &svc.Spec.Ports[0], apiv1.ProtocolTCP, &healthcheck.Upstream{})
|
||||||
if len(endps) == 0 {
|
if len(endps) == 0 {
|
||||||
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
||||||
endps = []ingress.Endpoint{ic.cfg.Backend.DefaultEndpoint()}
|
endps = []ingress.Endpoint{n.DefaultEndpoint()}
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream.Service = svc
|
upstream.Service = svc
|
||||||
|
@ -482,14 +389,14 @@ func (ic *GenericController) getDefaultUpstream() *ingress.Backend {
|
||||||
|
|
||||||
// getBackendServers returns a list of Upstream and Server to be used by the backend
|
// getBackendServers returns a list of Upstream and Server to be used by the backend
|
||||||
// An upstream can be used in multiple servers if the namespace, service name and port are the same
|
// An upstream can be used in multiple servers if the namespace, service name and port are the same
|
||||||
func (ic *GenericController) getBackendServers(ingresses []*extensions.Ingress) ([]*ingress.Backend, []*ingress.Server) {
|
func (n *NGINXController) getBackendServers(ingresses []*extensions.Ingress) ([]*ingress.Backend, []*ingress.Server) {
|
||||||
du := ic.getDefaultUpstream()
|
du := n.getDefaultUpstream()
|
||||||
upstreams := ic.createUpstreams(ingresses, du)
|
upstreams := n.createUpstreams(ingresses, du)
|
||||||
servers := ic.createServers(ingresses, upstreams, du)
|
servers := n.createServers(ingresses, upstreams, du)
|
||||||
|
|
||||||
for _, ing := range ingresses {
|
for _, ing := range ingresses {
|
||||||
affinity := ic.annotations.SessionAffinity(ing)
|
affinity := n.annotations.SessionAffinity(ing)
|
||||||
anns := ic.annotations.Extract(ing)
|
anns := n.annotations.Extract(ing)
|
||||||
|
|
||||||
for _, rule := range ing.Spec.Rules {
|
for _, rule := range ing.Spec.Rules {
|
||||||
host := rule.Host
|
host := rule.Host
|
||||||
|
@ -508,7 +415,7 @@ func (ic *GenericController) getBackendServers(ingresses []*extensions.Ingress)
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.CertificateAuth.CAFileName == "" {
|
if server.CertificateAuth.CAFileName == "" {
|
||||||
ca := ic.annotations.CertificateAuth(ing)
|
ca := n.annotations.CertificateAuth(ing)
|
||||||
if ca != nil {
|
if ca != nil {
|
||||||
server.CertificateAuth = *ca
|
server.CertificateAuth = *ca
|
||||||
// It is possible that no CAFileName is found in the secret
|
// It is possible that no CAFileName is found in the secret
|
||||||
|
@ -609,7 +516,7 @@ func (ic *GenericController) getBackendServers(ingresses []*extensions.Ingress)
|
||||||
// check if the location contains endpoints and a custom default backend
|
// check if the location contains endpoints and a custom default backend
|
||||||
if location.DefaultBackend != nil {
|
if location.DefaultBackend != nil {
|
||||||
sp := location.DefaultBackend.Spec.Ports[0]
|
sp := location.DefaultBackend.Spec.Ports[0]
|
||||||
endps := ic.getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, &healthcheck.Upstream{})
|
endps := n.getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, &healthcheck.Upstream{})
|
||||||
if len(endps) > 0 {
|
if len(endps) > 0 {
|
||||||
glog.V(3).Infof("using custom default backend in server %v location %v (service %v/%v)",
|
glog.V(3).Infof("using custom default backend in server %v location %v (service %v/%v)",
|
||||||
server.Hostname, location.Path, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
|
server.Hostname, location.Path, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
|
||||||
|
@ -656,7 +563,7 @@ func (ic *GenericController) getBackendServers(ingresses []*extensions.Ingress)
|
||||||
aUpstreams = append(aUpstreams, upstream)
|
aUpstreams = append(aUpstreams, upstream)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ic.cfg.SortBackends {
|
if n.cfg.SortBackends {
|
||||||
sort.SliceStable(aUpstreams, func(a, b int) bool {
|
sort.SliceStable(aUpstreams, func(a, b int) bool {
|
||||||
return aUpstreams[a].Name < aUpstreams[b].Name
|
return aUpstreams[a].Name < aUpstreams[b].Name
|
||||||
})
|
})
|
||||||
|
@ -678,17 +585,17 @@ func (ic *GenericController) getBackendServers(ingresses []*extensions.Ingress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAuthCertificate is used by the auth-tls annotations to get a cert from a secret
|
// GetAuthCertificate is used by the auth-tls annotations to get a cert from a secret
|
||||||
func (ic GenericController) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
|
func (n NGINXController) GetAuthCertificate(name string) (*resolver.AuthSSLCert, error) {
|
||||||
if _, exists := ic.sslCertTracker.Get(name); !exists {
|
if _, exists := n.sslCertTracker.Get(name); !exists {
|
||||||
ic.syncSecret(name)
|
n.syncSecret(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := ic.listers.Secret.GetByName(name)
|
_, err := n.listers.Secret.GetByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &resolver.AuthSSLCert{}, fmt.Errorf("unexpected error: %v", err)
|
return &resolver.AuthSSLCert{}, fmt.Errorf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bc, exists := ic.sslCertTracker.Get(name)
|
bc, exists := n.sslCertTracker.Get(name)
|
||||||
if !exists {
|
if !exists {
|
||||||
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exist", name)
|
return &resolver.AuthSSLCert{}, fmt.Errorf("secret %v does not exist", name)
|
||||||
}
|
}
|
||||||
|
@ -702,15 +609,15 @@ func (ic GenericController) GetAuthCertificate(name string) (*resolver.AuthSSLCe
|
||||||
|
|
||||||
// createUpstreams creates the NGINX upstreams for each service referenced in
|
// createUpstreams creates the NGINX upstreams for each service referenced in
|
||||||
// Ingress rules. The servers inside the upstream are endpoints.
|
// Ingress rules. The servers inside the upstream are endpoints.
|
||||||
func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ingress.Backend) map[string]*ingress.Backend {
|
func (n *NGINXController) createUpstreams(data []*extensions.Ingress, du *ingress.Backend) map[string]*ingress.Backend {
|
||||||
upstreams := make(map[string]*ingress.Backend)
|
upstreams := make(map[string]*ingress.Backend)
|
||||||
upstreams[defUpstreamName] = du
|
upstreams[defUpstreamName] = du
|
||||||
|
|
||||||
for _, ing := range data {
|
for _, ing := range data {
|
||||||
secUpstream := ic.annotations.SecureUpstream(ing)
|
secUpstream := n.annotations.SecureUpstream(ing)
|
||||||
hz := ic.annotations.HealthCheck(ing)
|
hz := n.annotations.HealthCheck(ing)
|
||||||
serviceUpstream := ic.annotations.ServiceUpstream(ing)
|
serviceUpstream := n.annotations.ServiceUpstream(ing)
|
||||||
upstreamHashBy := ic.annotations.UpstreamHashBy(ing)
|
upstreamHashBy := n.annotations.UpstreamHashBy(ing)
|
||||||
|
|
||||||
var defBackend string
|
var defBackend string
|
||||||
if ing.Spec.Backend != nil {
|
if ing.Spec.Backend != nil {
|
||||||
|
@ -726,7 +633,7 @@ func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ing
|
||||||
// Add the service cluster endpoint as the upstream instead of individual endpoints
|
// Add the service cluster endpoint as the upstream instead of individual endpoints
|
||||||
// if the serviceUpstream annotation is enabled
|
// if the serviceUpstream annotation is enabled
|
||||||
if serviceUpstream {
|
if serviceUpstream {
|
||||||
endpoint, err := ic.getServiceClusterEndpoint(svcKey, ing.Spec.Backend)
|
endpoint, err := n.getServiceClusterEndpoint(svcKey, ing.Spec.Backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Failed to get service cluster endpoint for service %s: %v", svcKey, err)
|
glog.Errorf("Failed to get service cluster endpoint for service %s: %v", svcKey, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -735,7 +642,7 @@ func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ing
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(upstreams[defBackend].Endpoints) == 0 {
|
if len(upstreams[defBackend].Endpoints) == 0 {
|
||||||
endps, err := ic.serviceEndpoints(svcKey, ing.Spec.Backend.ServicePort.String(), hz)
|
endps, err := n.serviceEndpoints(svcKey, ing.Spec.Backend.ServicePort.String(), hz)
|
||||||
upstreams[defBackend].Endpoints = append(upstreams[defBackend].Endpoints, endps...)
|
upstreams[defBackend].Endpoints = append(upstreams[defBackend].Endpoints, endps...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("error creating upstream %v: %v", defBackend, err)
|
glog.Warningf("error creating upstream %v: %v", defBackend, err)
|
||||||
|
@ -780,7 +687,7 @@ func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ing
|
||||||
// Add the service cluster endpoint as the upstream instead of individual endpoints
|
// Add the service cluster endpoint as the upstream instead of individual endpoints
|
||||||
// if the serviceUpstream annotation is enabled
|
// if the serviceUpstream annotation is enabled
|
||||||
if serviceUpstream {
|
if serviceUpstream {
|
||||||
endpoint, err := ic.getServiceClusterEndpoint(svcKey, &path.Backend)
|
endpoint, err := n.getServiceClusterEndpoint(svcKey, &path.Backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("failed to get service cluster endpoint for service %s: %v", svcKey, err)
|
glog.Errorf("failed to get service cluster endpoint for service %s: %v", svcKey, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -789,7 +696,7 @@ func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ing
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(upstreams[name].Endpoints) == 0 {
|
if len(upstreams[name].Endpoints) == 0 {
|
||||||
endp, err := ic.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
|
endp, err := n.serviceEndpoints(svcKey, path.Backend.ServicePort.String(), hz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("error obtaining service endpoints: %v", err)
|
glog.Warningf("error obtaining service endpoints: %v", err)
|
||||||
continue
|
continue
|
||||||
|
@ -797,7 +704,7 @@ func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ing
|
||||||
upstreams[name].Endpoints = endp
|
upstreams[name].Endpoints = endp
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := ic.listers.Service.GetByName(svcKey)
|
s, err := n.listers.Service.GetByName(svcKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("error obtaining service: %v", err)
|
glog.Warningf("error obtaining service: %v", err)
|
||||||
continue
|
continue
|
||||||
|
@ -811,8 +718,8 @@ func (ic *GenericController) createUpstreams(data []*extensions.Ingress, du *ing
|
||||||
return upstreams
|
return upstreams
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *GenericController) getServiceClusterEndpoint(svcKey string, backend *extensions.IngressBackend) (endpoint ingress.Endpoint, err error) {
|
func (n *NGINXController) getServiceClusterEndpoint(svcKey string, backend *extensions.IngressBackend) (endpoint ingress.Endpoint, err error) {
|
||||||
svcObj, svcExists, err := ic.listers.Service.GetByKey(svcKey)
|
svcObj, svcExists, err := n.listers.Service.GetByKey(svcKey)
|
||||||
|
|
||||||
if !svcExists {
|
if !svcExists {
|
||||||
return endpoint, fmt.Errorf("service %v does not exist", svcKey)
|
return endpoint, fmt.Errorf("service %v does not exist", svcKey)
|
||||||
|
@ -848,9 +755,9 @@ func (ic *GenericController) getServiceClusterEndpoint(svcKey string, backend *e
|
||||||
|
|
||||||
// serviceEndpoints returns the upstream servers (endpoints) associated
|
// serviceEndpoints returns the upstream servers (endpoints) associated
|
||||||
// to a service.
|
// to a service.
|
||||||
func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
func (n *NGINXController) serviceEndpoints(svcKey, backendPort string,
|
||||||
hz *healthcheck.Upstream) ([]ingress.Endpoint, error) {
|
hz *healthcheck.Upstream) ([]ingress.Endpoint, error) {
|
||||||
svc, err := ic.listers.Service.GetByName(svcKey)
|
svc, err := n.listers.Service.GetByName(svcKey)
|
||||||
|
|
||||||
var upstreams []ingress.Endpoint
|
var upstreams []ingress.Endpoint
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -864,12 +771,12 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
||||||
servicePort.TargetPort.String() == backendPort ||
|
servicePort.TargetPort.String() == backendPort ||
|
||||||
servicePort.Name == backendPort {
|
servicePort.Name == backendPort {
|
||||||
|
|
||||||
endps := ic.getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, hz)
|
endps := n.getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, hz)
|
||||||
if len(endps) == 0 {
|
if len(endps) == 0 {
|
||||||
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ic.cfg.SortBackends {
|
if n.cfg.SortBackends {
|
||||||
sort.SliceStable(endps, func(i, j int) bool {
|
sort.SliceStable(endps, func(i, j int) bool {
|
||||||
iName := endps[i].Address
|
iName := endps[i].Address
|
||||||
jName := endps[j].Address
|
jName := endps[j].Address
|
||||||
|
@ -898,7 +805,7 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
||||||
Port: int32(externalPort),
|
Port: int32(externalPort),
|
||||||
TargetPort: intstr.FromString(backendPort),
|
TargetPort: intstr.FromString(backendPort),
|
||||||
}
|
}
|
||||||
endps := ic.getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, hz)
|
endps := n.getEndpoints(svc, &servicePort, apiv1.ProtocolTCP, hz)
|
||||||
if len(endps) == 0 {
|
if len(endps) == 0 {
|
||||||
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
glog.Warningf("service %v does not have any active endpoints", svcKey)
|
||||||
return upstreams, nil
|
return upstreams, nil
|
||||||
|
@ -908,7 +815,7 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
||||||
return upstreams, nil
|
return upstreams, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ic.cfg.SortBackends {
|
if !n.cfg.SortBackends {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
for i := range upstreams {
|
for i := range upstreams {
|
||||||
j := rand.Intn(i + 1)
|
j := rand.Intn(i + 1)
|
||||||
|
@ -923,7 +830,7 @@ func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
|
||||||
// FDQN referenced by ingress rules and the common name field in the referenced
|
// FDQN referenced by ingress rules and the common name field in the referenced
|
||||||
// SSL certificates. Each server is configured with location / using a default
|
// SSL certificates. Each server is configured with location / using a default
|
||||||
// backend specified by the user or the one inside the ingress spec.
|
// backend specified by the user or the one inside the ingress spec.
|
||||||
func (ic *GenericController) createServers(data []*extensions.Ingress,
|
func (n *NGINXController) createServers(data []*extensions.Ingress,
|
||||||
upstreams map[string]*ingress.Backend,
|
upstreams map[string]*ingress.Backend,
|
||||||
du *ingress.Backend) map[string]*ingress.Server {
|
du *ingress.Backend) map[string]*ingress.Server {
|
||||||
|
|
||||||
|
@ -932,7 +839,7 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
// remove the alias to avoid conflicts.
|
// remove the alias to avoid conflicts.
|
||||||
aliases := make(map[string]string, len(data))
|
aliases := make(map[string]string, len(data))
|
||||||
|
|
||||||
bdef := ic.GetDefaultBackend()
|
bdef := n.GetDefaultBackend()
|
||||||
ngxProxy := proxy.Configuration{
|
ngxProxy := proxy.Configuration{
|
||||||
BodySize: bdef.ProxyBodySize,
|
BodySize: bdef.ProxyBodySize,
|
||||||
ConnectTimeout: bdef.ProxyConnectTimeout,
|
ConnectTimeout: bdef.ProxyConnectTimeout,
|
||||||
|
@ -946,12 +853,12 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
}
|
}
|
||||||
|
|
||||||
// generated on Start() with createDefaultSSLCertificate()
|
// generated on Start() with createDefaultSSLCertificate()
|
||||||
defaultPemFileName := fakeCertificatePath
|
defaultPemFileName := n.cfg.FakeCertificatePath
|
||||||
defaultPemSHA := fakeCertificateSHA
|
defaultPemSHA := n.cfg.FakeCertificateSHA
|
||||||
|
|
||||||
// Tries to fetch the default Certificate from nginx configuration.
|
// Tries to fetch the default Certificate from nginx configuration.
|
||||||
// If it does not exists, use the ones generated on Start()
|
// If it does not exists, use the ones generated on Start()
|
||||||
defaultCertificate, err := ic.getPemCertificate(ic.cfg.DefaultSSLCertificate)
|
defaultCertificate, err := n.getPemCertificate(n.cfg.DefaultSSLCertificate)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
defaultPemFileName = defaultCertificate.PemFileName
|
defaultPemFileName = defaultCertificate.PemFileName
|
||||||
defaultPemSHA = defaultCertificate.PemSHA
|
defaultPemSHA = defaultCertificate.PemSHA
|
||||||
|
@ -976,7 +883,7 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
for _, ing := range data {
|
for _, ing := range data {
|
||||||
|
|
||||||
// check if ssl passthrough is configured
|
// check if ssl passthrough is configured
|
||||||
sslpt := ic.annotations.SSLPassthrough(ing)
|
sslpt := n.annotations.SSLPassthrough(ing)
|
||||||
|
|
||||||
// default upstream server
|
// default upstream server
|
||||||
un := du.Name
|
un := du.Name
|
||||||
|
@ -1028,8 +935,8 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
// configure default location, alias, and SSL
|
// configure default location, alias, and SSL
|
||||||
for _, ing := range data {
|
for _, ing := range data {
|
||||||
// setup server-alias based on annotations
|
// setup server-alias based on annotations
|
||||||
aliasAnnotation := ic.annotations.Alias(ing)
|
aliasAnnotation := n.annotations.Alias(ing)
|
||||||
srvsnippet := ic.annotations.ServerSnippet(ing)
|
srvsnippet := n.annotations.ServerSnippet(ing)
|
||||||
|
|
||||||
for _, rule := range ing.Spec.Rules {
|
for _, rule := range ing.Spec.Rules {
|
||||||
host := rule.Host
|
host := rule.Host
|
||||||
|
@ -1095,7 +1002,7 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("%v/%v", ing.Namespace, tlsSecretName)
|
key := fmt.Sprintf("%v/%v", ing.Namespace, tlsSecretName)
|
||||||
bc, exists := ic.sslCertTracker.Get(key)
|
bc, exists := n.sslCertTracker.Get(key)
|
||||||
if !exists {
|
if !exists {
|
||||||
glog.Warningf("ssl certificate \"%v\" does not exist in local store", key)
|
glog.Warningf("ssl certificate \"%v\" does not exist in local store", key)
|
||||||
continue
|
continue
|
||||||
|
@ -1130,7 +1037,7 @@ func (ic *GenericController) createServers(data []*extensions.Ingress,
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEndpoints returns a list of <endpoint ip>:<port> for a given service/target port combination.
|
// getEndpoints returns a list of <endpoint ip>:<port> for a given service/target port combination.
|
||||||
func (ic *GenericController) getEndpoints(
|
func (n *NGINXController) getEndpoints(
|
||||||
s *apiv1.Service,
|
s *apiv1.Service,
|
||||||
servicePort *apiv1.ServicePort,
|
servicePort *apiv1.ServicePort,
|
||||||
proto apiv1.Protocol,
|
proto apiv1.Protocol,
|
||||||
|
@ -1171,7 +1078,7 @@ func (ic *GenericController) getEndpoints(
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(3).Infof("getting endpoints for service %v/%v and port %v", s.Namespace, s.Name, servicePort.String())
|
glog.V(3).Infof("getting endpoints for service %v/%v and port %v", s.Namespace, s.Name, servicePort.String())
|
||||||
ep, err := ic.listers.Endpoint.GetServiceEndpoints(s)
|
ep, err := n.listers.Endpoint.GetServiceEndpoints(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("unexpected error obtaining service endpoints: %v", err)
|
glog.Warningf("unexpected error obtaining service endpoints: %v", err)
|
||||||
return upsServers
|
return upsServers
|
||||||
|
@ -1221,99 +1128,32 @@ func (ic *GenericController) getEndpoints(
|
||||||
}
|
}
|
||||||
|
|
||||||
// readSecrets extracts information about secrets from an Ingress rule
|
// readSecrets extracts information about secrets from an Ingress rule
|
||||||
func (ic *GenericController) readSecrets(ing *extensions.Ingress) {
|
func (n *NGINXController) readSecrets(ing *extensions.Ingress) {
|
||||||
for _, tls := range ing.Spec.TLS {
|
for _, tls := range ing.Spec.TLS {
|
||||||
if tls.SecretName == "" {
|
if tls.SecretName == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
key := fmt.Sprintf("%v/%v", ing.Namespace, tls.SecretName)
|
key := fmt.Sprintf("%v/%v", ing.Namespace, tls.SecretName)
|
||||||
ic.syncSecret(key)
|
n.syncSecret(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
key, _ := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
|
key, _ := parser.GetStringAnnotation("ingress.kubernetes.io/auth-tls-secret", ing)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ic.syncSecret(key)
|
n.syncSecret(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the loadbalancer controller.
|
func (n *NGINXController) isForceReload() bool {
|
||||||
func (ic GenericController) Stop() error {
|
return atomic.LoadInt32(&n.forceReload) != 0
|
||||||
ic.stopLock.Lock()
|
|
||||||
defer ic.stopLock.Unlock()
|
|
||||||
|
|
||||||
// Only try draining the workqueue if we haven't already.
|
|
||||||
if !ic.syncQueue.IsShuttingDown() {
|
|
||||||
glog.Infof("shutting down controller queues")
|
|
||||||
close(ic.stopCh)
|
|
||||||
go ic.syncQueue.Shutdown()
|
|
||||||
if ic.syncStatus != nil {
|
|
||||||
ic.syncStatus.Shutdown()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("shutdown already in progress")
|
func (n *NGINXController) SetForceReload(shouldReload bool) {
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the Ingress controller.
|
|
||||||
func (ic *GenericController) Start() {
|
|
||||||
glog.Infof("starting Ingress controller")
|
|
||||||
|
|
||||||
ic.cacheController.Run(ic.stopCh)
|
|
||||||
|
|
||||||
createDefaultSSLCertificate()
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
// initial sync of secrets to avoid unnecessary reloads
|
|
||||||
glog.Info("running initial sync of secrets")
|
|
||||||
for _, obj := range ic.listers.Ingress.List() {
|
|
||||||
ing := obj.(*extensions.Ingress)
|
|
||||||
|
|
||||||
if !class.IsValid(ing, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
|
|
||||||
a, _ := parser.GetStringAnnotation(class.IngressKey, ing)
|
|
||||||
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", ing.Name, class.IngressKey, a)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ic.readSecrets(ing)
|
|
||||||
}
|
|
||||||
|
|
||||||
go ic.syncQueue.Run(time.Second, ic.stopCh)
|
|
||||||
|
|
||||||
if ic.syncStatus != nil {
|
|
||||||
go ic.syncStatus.Run(ic.stopCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
go wait.Until(ic.checkMissingSecrets, 30*time.Second, ic.stopCh)
|
|
||||||
|
|
||||||
// force initial sync
|
|
||||||
ic.syncQueue.Enqueue(&extensions.Ingress{})
|
|
||||||
|
|
||||||
<-ic.stopCh
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *GenericController) isForceReload() bool {
|
|
||||||
return atomic.LoadInt32(&ic.forceReload) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *GenericController) SetForceReload(shouldReload bool) {
|
|
||||||
if shouldReload {
|
if shouldReload {
|
||||||
atomic.StoreInt32(&ic.forceReload, 1)
|
atomic.StoreInt32(&n.forceReload, 1)
|
||||||
ic.syncQueue.Enqueue(&extensions.Ingress{})
|
n.syncQueue.Enqueue(&extensions.Ingress{})
|
||||||
} else {
|
} else {
|
||||||
atomic.StoreInt32(&ic.forceReload, 0)
|
atomic.StoreInt32(&n.forceReload, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDefaultSSLCertificate() {
|
|
||||||
defCert, defKey := ssl.GetFakeSSLCert()
|
|
||||||
c, err := ssl.AddOrUpdateCertAndKey(fakeCertificate, defCert, defKey, []byte{})
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("Error generating self signed certificate: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fakeCertificateSHA = c.PemSHA
|
|
||||||
fakeCertificatePath = c.PemFileName
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,335 +0,0 @@
|
||||||
package controller
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/pprof"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
|
||||||
"k8s.io/ingress-nginx/pkg/k8s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewIngressController returns a configured Ingress controller
|
|
||||||
func NewIngressController(backend ingress.Controller) *GenericController {
|
|
||||||
var (
|
|
||||||
flags = pflag.NewFlagSet("", pflag.ExitOnError)
|
|
||||||
|
|
||||||
apiserverHost = flags.String("apiserver-host", "", "The address of the Kubernetes Apiserver "+
|
|
||||||
"to connect to in the format of protocol://address:port, e.g., "+
|
|
||||||
"http://localhost:8080. If not specified, the assumption is that the binary runs inside a "+
|
|
||||||
"Kubernetes cluster and local discovery is attempted.")
|
|
||||||
kubeConfigFile = flags.String("kubeconfig", "", "Path to kubeconfig file with authorization and master location information.")
|
|
||||||
|
|
||||||
defaultSvc = flags.String("default-backend-service", "",
|
|
||||||
`Service used to serve a 404 page for the default backend. Takes the form
|
|
||||||
namespace/name. The controller uses the first node port of this Service for
|
|
||||||
the default backend.`)
|
|
||||||
|
|
||||||
ingressClass = flags.String("ingress-class", "",
|
|
||||||
`Name of the ingress class to route through this controller.`)
|
|
||||||
|
|
||||||
configMap = flags.String("configmap", "",
|
|
||||||
`Name of the ConfigMap that contains the custom configuration to use`)
|
|
||||||
|
|
||||||
publishSvc = flags.String("publish-service", "",
|
|
||||||
`Service fronting the ingress controllers. Takes the form
|
|
||||||
namespace/name. The controller will set the endpoint records on the
|
|
||||||
ingress objects to reflect those on the service.`)
|
|
||||||
|
|
||||||
tcpConfigMapName = flags.String("tcp-services-configmap", "",
|
|
||||||
`Name of the ConfigMap that contains the definition of the TCP services to expose.
|
|
||||||
The key in the map indicates the external port to be used. The value is the name of the
|
|
||||||
service with the format namespace/serviceName and the port of the service could be a
|
|
||||||
number of the name of the port.
|
|
||||||
The ports 80 and 443 are not allowed as external ports. This ports are reserved for the backend`)
|
|
||||||
|
|
||||||
udpConfigMapName = flags.String("udp-services-configmap", "",
|
|
||||||
`Name of the ConfigMap that contains the definition of the UDP services to expose.
|
|
||||||
The key in the map indicates the external port to be used. The value is the name of the
|
|
||||||
service with the format namespace/serviceName and the port of the service could be a
|
|
||||||
number of the name of the port.`)
|
|
||||||
|
|
||||||
resyncPeriod = flags.Duration("sync-period", 600*time.Second,
|
|
||||||
`Relist and confirm cloud resources this often. Default is 10 minutes`)
|
|
||||||
|
|
||||||
watchNamespace = flags.String("watch-namespace", apiv1.NamespaceAll,
|
|
||||||
`Namespace to watch for Ingress. Default is to watch all namespaces`)
|
|
||||||
|
|
||||||
healthzPort = flags.Int("healthz-port", 10254, "port for healthz endpoint.")
|
|
||||||
|
|
||||||
profiling = flags.Bool("profiling", true, `Enable profiling via web interface host:port/debug/pprof/`)
|
|
||||||
|
|
||||||
defSSLCertificate = flags.String("default-ssl-certificate", "", `Name of the secret
|
|
||||||
that contains a SSL certificate to be used as default for a HTTPS catch-all server`)
|
|
||||||
|
|
||||||
defHealthzURL = flags.String("health-check-path", "/healthz", `Defines
|
|
||||||
the URL to be used as health check inside in the default server in NGINX.`)
|
|
||||||
|
|
||||||
updateStatus = flags.Bool("update-status", true, `Indicates if the
|
|
||||||
ingress controller should update the Ingress status IP/hostname. Default is true`)
|
|
||||||
|
|
||||||
electionID = flags.String("election-id", "ingress-controller-leader", `Election id to use for status update.`)
|
|
||||||
|
|
||||||
forceIsolation = flags.Bool("force-namespace-isolation", false,
|
|
||||||
`Force namespace isolation. This flag is required to avoid the reference of secrets or
|
|
||||||
configmaps located in a different namespace than the specified in the flag --watch-namespace.`)
|
|
||||||
|
|
||||||
disableNodeList = flags.Bool("disable-node-list", false,
|
|
||||||
`Disable querying nodes. If --force-namespace-isolation is true, this should also be set.`)
|
|
||||||
|
|
||||||
updateStatusOnShutdown = flags.Bool("update-status-on-shutdown", true, `Indicates if the
|
|
||||||
ingress controller should update the Ingress status IP/hostname when the controller
|
|
||||||
is being stopped. Default is true`)
|
|
||||||
|
|
||||||
sortBackends = flags.Bool("sort-backends", false,
|
|
||||||
`Defines if backends and it's endpoints should be sorted`)
|
|
||||||
|
|
||||||
useNodeInternalIP = flags.Bool("report-node-internal-ip-address", false,
|
|
||||||
`Defines if the nodes IP address to be returned in the ingress status should be the internal instead of the external IP address`)
|
|
||||||
|
|
||||||
showVersion = flags.Bool("version", false,
|
|
||||||
`Shows release information about the NGINX Ingress controller`)
|
|
||||||
)
|
|
||||||
|
|
||||||
flags.AddGoFlagSet(flag.CommandLine)
|
|
||||||
backend.ConfigureFlags(flags)
|
|
||||||
flags.Parse(os.Args)
|
|
||||||
// Workaround for this issue:
|
|
||||||
// https://github.com/kubernetes/kubernetes/issues/17162
|
|
||||||
flag.CommandLine.Parse([]string{})
|
|
||||||
|
|
||||||
if *showVersion {
|
|
||||||
fmt.Println(backend.Info().String())
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
backend.OverrideFlags(flags)
|
|
||||||
|
|
||||||
flag.Set("logtostderr", "true")
|
|
||||||
|
|
||||||
glog.Info(backend.Info())
|
|
||||||
|
|
||||||
if *ingressClass != "" {
|
|
||||||
glog.Infof("Watching for ingress class: %s", *ingressClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *defaultSvc == "" {
|
|
||||||
glog.Fatalf("Please specify --default-backend-service")
|
|
||||||
}
|
|
||||||
|
|
||||||
kubeClient, err := createApiserverClient(*apiserverHost, *kubeConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
handleFatalInitError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ns, name, err := k8s.ParseNameNS(*defaultSvc)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("invalid format for service %v: %v", *defaultSvc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = kubeClient.Core().Services(ns).Get(name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "cannot get services in the namespace") {
|
|
||||||
glog.Fatalf("✖ It seems the cluster it is running with Authorization enabled (like RBAC) and there is no permissions for the ingress controller. Please check the configuration")
|
|
||||||
}
|
|
||||||
glog.Fatalf("no service with name %v found: %v", *defaultSvc, err)
|
|
||||||
}
|
|
||||||
glog.Infof("validated %v as the default backend", *defaultSvc)
|
|
||||||
|
|
||||||
if *publishSvc != "" {
|
|
||||||
ns, name, err := k8s.ParseNameNS(*publishSvc)
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("invalid service format: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
svc, err := kubeClient.CoreV1().Services(ns).Get(name, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("unexpected error getting information about service %v: %v", *publishSvc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(svc.Status.LoadBalancer.Ingress) == 0 {
|
|
||||||
if len(svc.Spec.ExternalIPs) > 0 {
|
|
||||||
glog.Infof("service %v validated as assigned with externalIP", *publishSvc)
|
|
||||||
} else {
|
|
||||||
// We could poll here, but we instead just exit and rely on k8s to restart us
|
|
||||||
glog.Fatalf("service %s does not (yet) have ingress points", *publishSvc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
glog.Infof("service %v validated as source of Ingress status", *publishSvc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if *watchNamespace != "" {
|
|
||||||
_, err = kubeClient.CoreV1().Namespaces().Get(*watchNamespace, metav1.GetOptions{})
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("no watchNamespace with name %v found: %v", *watchNamespace, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resyncPeriod.Seconds() < 10 {
|
|
||||||
glog.Fatalf("resync period (%vs) is too low", resyncPeriod.Seconds())
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(ingress.DefaultSSLDirectory, 0655)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Failed to mkdir SSL directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &Configuration{
|
|
||||||
UpdateStatus: *updateStatus,
|
|
||||||
ElectionID: *electionID,
|
|
||||||
Client: kubeClient,
|
|
||||||
ResyncPeriod: *resyncPeriod,
|
|
||||||
DefaultService: *defaultSvc,
|
|
||||||
IngressClass: *ingressClass,
|
|
||||||
DefaultIngressClass: backend.DefaultIngressClass(),
|
|
||||||
Namespace: *watchNamespace,
|
|
||||||
ConfigMapName: *configMap,
|
|
||||||
TCPConfigMapName: *tcpConfigMapName,
|
|
||||||
UDPConfigMapName: *udpConfigMapName,
|
|
||||||
DefaultSSLCertificate: *defSSLCertificate,
|
|
||||||
DefaultHealthzURL: *defHealthzURL,
|
|
||||||
PublishService: *publishSvc,
|
|
||||||
Backend: backend,
|
|
||||||
ForceNamespaceIsolation: *forceIsolation,
|
|
||||||
DisableNodeList: *disableNodeList,
|
|
||||||
UpdateStatusOnShutdown: *updateStatusOnShutdown,
|
|
||||||
SortBackends: *sortBackends,
|
|
||||||
UseNodeInternalIP: *useNodeInternalIP,
|
|
||||||
}
|
|
||||||
|
|
||||||
ic := newIngressController(config)
|
|
||||||
go registerHandlers(*profiling, *healthzPort, ic)
|
|
||||||
return ic
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerHandlers(enableProfiling bool, port int, ic *GenericController) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
// expose health check endpoint (/healthz)
|
|
||||||
healthz.InstallHandler(mux,
|
|
||||||
healthz.PingHealthz,
|
|
||||||
ic.cfg.Backend,
|
|
||||||
)
|
|
||||||
|
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
|
||||||
|
|
||||||
mux.HandleFunc("/build", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
b, _ := json.Marshal(ic.Info())
|
|
||||||
w.Write(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if enableProfiling {
|
|
||||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
||||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
||||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
||||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
||||||
}
|
|
||||||
|
|
||||||
server := &http.Server{
|
|
||||||
Addr: fmt.Sprintf(":%v", port),
|
|
||||||
Handler: mux,
|
|
||||||
}
|
|
||||||
glog.Fatal(server.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
// buildConfigFromFlags builds REST config based on master URL and kubeconfig path.
|
|
||||||
// If both of them are empty then in cluster config is used.
|
|
||||||
func buildConfigFromFlags(masterURL, kubeconfigPath string) (*rest.Config, error) {
|
|
||||||
if kubeconfigPath == "" && masterURL == "" {
|
|
||||||
kubeconfig, err := rest.InClusterConfig()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return kubeconfig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
|
||||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfigPath},
|
|
||||||
&clientcmd.ConfigOverrides{
|
|
||||||
ClusterInfo: clientcmdapi.Cluster{
|
|
||||||
Server: masterURL,
|
|
||||||
},
|
|
||||||
}).ClientConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
// createApiserverClient creates new Kubernetes Apiserver client. When kubeconfig or apiserverHost param is empty
|
|
||||||
// the function assumes that it is running inside a Kubernetes cluster and attempts to
|
|
||||||
// discover the Apiserver. Otherwise, it connects to the Apiserver specified.
|
|
||||||
//
|
|
||||||
// apiserverHost param is in the format of protocol://address:port/pathPrefix, e.g.http://localhost:8001.
|
|
||||||
// kubeConfig location of kubeconfig file
|
|
||||||
func createApiserverClient(apiserverHost string, kubeConfig string) (*kubernetes.Clientset, error) {
|
|
||||||
cfg, err := buildConfigFromFlags(apiserverHost, kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.QPS = defaultQPS
|
|
||||||
cfg.Burst = defaultBurst
|
|
||||||
cfg.ContentType = "application/vnd.kubernetes.protobuf"
|
|
||||||
|
|
||||||
glog.Infof("Creating API client for %s", cfg.Host)
|
|
||||||
|
|
||||||
client, err := kubernetes.NewForConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := client.Discovery().ServerVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("Running in Kubernetes Cluster version v%v.%v (%v) - git (%v) commit %v - platform %v",
|
|
||||||
v.Major, v.Minor, v.GitVersion, v.GitTreeState, v.GitCommit, v.Platform)
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles fatal init error that prevents server from doing any work. Prints verbose error
|
|
||||||
* message and quits the server.
|
|
||||||
*/
|
|
||||||
func handleFatalInitError(err error) {
|
|
||||||
glog.Fatalf("Error while initializing connection to Kubernetes apiserver. "+
|
|
||||||
"This most likely means that the cluster is misconfigured (e.g., it has "+
|
|
||||||
"invalid apiserver certificates or service accounts configuration). Reason: %s\n"+
|
|
||||||
"Refer to the troubleshooting guide for more information: "+
|
|
||||||
"https://github.com/kubernetes/ingress-nginx/blob/master/docs/troubleshooting.md", err)
|
|
||||||
}
|
|
|
@ -64,20 +64,21 @@ func (c *cacheController) Run(stopCh chan struct{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.StoreLister, *cacheController) {
|
func (n *NGINXController) createListers(disableNodeLister bool, stopCh chan struct{}) *ingress.StoreLister {
|
||||||
// from here to the end of the method all the code is just boilerplate
|
// from here to the end of the method all the code is just boilerplate
|
||||||
// required to watch Ingress, Secrets, ConfigMaps and Endoints.
|
// required to watch Ingress, Secrets, ConfigMaps and Endoints.
|
||||||
// This is used to detect new content, updates or removals and act accordingly
|
// This is used to detect new content, updates or removals and act accordingly
|
||||||
ingEventHandler := cache.ResourceEventHandlerFuncs{
|
ingEventHandler := cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: func(obj interface{}) {
|
AddFunc: func(obj interface{}) {
|
||||||
addIng := obj.(*extensions.Ingress)
|
addIng := obj.(*extensions.Ingress)
|
||||||
if !class.IsValid(addIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
|
if !class.IsValid(addIng, n.cfg.IngressClass, defIngressClass) {
|
||||||
a, _ := parser.GetStringAnnotation(class.IngressKey, addIng)
|
a, _ := parser.GetStringAnnotation(class.IngressKey, addIng)
|
||||||
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", addIng.Name, class.IngressKey, a)
|
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", addIng.Name, class.IngressKey, a)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ic.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
|
|
||||||
ic.syncQueue.Enqueue(obj)
|
n.recorder.Eventf(addIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", addIng.Namespace, addIng.Name))
|
||||||
|
n.syncQueue.Enqueue(obj)
|
||||||
},
|
},
|
||||||
DeleteFunc: func(obj interface{}) {
|
DeleteFunc: func(obj interface{}) {
|
||||||
delIng, ok := obj.(*extensions.Ingress)
|
delIng, ok := obj.(*extensions.Ingress)
|
||||||
|
@ -94,29 +95,29 @@ func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.Sto
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !class.IsValid(delIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass) {
|
if !class.IsValid(delIng, n.cfg.IngressClass, defIngressClass) {
|
||||||
glog.Infof("ignoring delete for ingress %v based on annotation %v", delIng.Name, class.IngressKey)
|
glog.Infof("ignoring delete for ingress %v based on annotation %v", delIng.Name, class.IngressKey)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ic.recorder.Eventf(delIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", delIng.Namespace, delIng.Name))
|
n.recorder.Eventf(delIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", delIng.Namespace, delIng.Name))
|
||||||
ic.syncQueue.Enqueue(obj)
|
n.syncQueue.Enqueue(obj)
|
||||||
},
|
},
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
UpdateFunc: func(old, cur interface{}) {
|
||||||
oldIng := old.(*extensions.Ingress)
|
oldIng := old.(*extensions.Ingress)
|
||||||
curIng := cur.(*extensions.Ingress)
|
curIng := cur.(*extensions.Ingress)
|
||||||
validOld := class.IsValid(oldIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass)
|
validOld := class.IsValid(oldIng, n.cfg.IngressClass, defIngressClass)
|
||||||
validCur := class.IsValid(curIng, ic.cfg.IngressClass, ic.cfg.DefaultIngressClass)
|
validCur := class.IsValid(curIng, n.cfg.IngressClass, defIngressClass)
|
||||||
if !validOld && validCur {
|
if !validOld && validCur {
|
||||||
glog.Infof("creating ingress %v based on annotation %v", curIng.Name, class.IngressKey)
|
glog.Infof("creating ingress %v based on annotation %v", curIng.Name, class.IngressKey)
|
||||||
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
n.recorder.Eventf(curIng, apiv1.EventTypeNormal, "CREATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
||||||
} else if validOld && !validCur {
|
} else if validOld && !validCur {
|
||||||
glog.Infof("removing ingress %v based on annotation %v", curIng.Name, class.IngressKey)
|
glog.Infof("removing ingress %v based on annotation %v", curIng.Name, class.IngressKey)
|
||||||
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
n.recorder.Eventf(curIng, apiv1.EventTypeNormal, "DELETE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
||||||
} else if validCur && !reflect.DeepEqual(old, cur) {
|
} else if validCur && !reflect.DeepEqual(old, cur) {
|
||||||
ic.recorder.Eventf(curIng, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
n.recorder.Eventf(curIng, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("Ingress %s/%s", curIng.Namespace, curIng.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
ic.syncQueue.Enqueue(cur)
|
n.syncQueue.Enqueue(cur)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +126,7 @@ func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.Sto
|
||||||
if !reflect.DeepEqual(old, cur) {
|
if !reflect.DeepEqual(old, cur) {
|
||||||
sec := cur.(*apiv1.Secret)
|
sec := cur.(*apiv1.Secret)
|
||||||
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
|
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
|
||||||
ic.syncSecret(key)
|
n.syncSecret(key)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DeleteFunc: func(obj interface{}) {
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
@ -144,23 +145,23 @@ func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.Sto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
|
key := fmt.Sprintf("%v/%v", sec.Namespace, sec.Name)
|
||||||
ic.sslCertTracker.DeleteAll(key)
|
n.sslCertTracker.DeleteAll(key)
|
||||||
ic.syncQueue.Enqueue(key)
|
n.syncQueue.Enqueue(key)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
eventHandler := cache.ResourceEventHandlerFuncs{
|
eventHandler := cache.ResourceEventHandlerFuncs{
|
||||||
AddFunc: func(obj interface{}) {
|
AddFunc: func(obj interface{}) {
|
||||||
ic.syncQueue.Enqueue(obj)
|
n.syncQueue.Enqueue(obj)
|
||||||
},
|
},
|
||||||
DeleteFunc: func(obj interface{}) {
|
DeleteFunc: func(obj interface{}) {
|
||||||
ic.syncQueue.Enqueue(obj)
|
n.syncQueue.Enqueue(obj)
|
||||||
},
|
},
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
UpdateFunc: func(old, cur interface{}) {
|
||||||
oep := old.(*apiv1.Endpoints)
|
oep := old.(*apiv1.Endpoints)
|
||||||
ocur := cur.(*apiv1.Endpoints)
|
ocur := cur.(*apiv1.Endpoints)
|
||||||
if !reflect.DeepEqual(ocur.Subsets, oep.Subsets) {
|
if !reflect.DeepEqual(ocur.Subsets, oep.Subsets) {
|
||||||
ic.syncQueue.Enqueue(cur)
|
n.syncQueue.Enqueue(cur)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -169,33 +170,33 @@ func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.Sto
|
||||||
AddFunc: func(obj interface{}) {
|
AddFunc: func(obj interface{}) {
|
||||||
upCmap := obj.(*apiv1.ConfigMap)
|
upCmap := obj.(*apiv1.ConfigMap)
|
||||||
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
|
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
|
||||||
if mapKey == ic.cfg.ConfigMapName {
|
if mapKey == n.cfg.ConfigMapName {
|
||||||
glog.V(2).Infof("adding configmap %v to backend", mapKey)
|
glog.V(2).Infof("adding configmap %v to backend", mapKey)
|
||||||
ic.cfg.Backend.SetConfig(upCmap)
|
n.SetConfig(upCmap)
|
||||||
ic.SetForceReload(true)
|
n.SetForceReload(true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UpdateFunc: func(old, cur interface{}) {
|
UpdateFunc: func(old, cur interface{}) {
|
||||||
if !reflect.DeepEqual(old, cur) {
|
if !reflect.DeepEqual(old, cur) {
|
||||||
upCmap := cur.(*apiv1.ConfigMap)
|
upCmap := cur.(*apiv1.ConfigMap)
|
||||||
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
|
mapKey := fmt.Sprintf("%s/%s", upCmap.Namespace, upCmap.Name)
|
||||||
if mapKey == ic.cfg.ConfigMapName {
|
if mapKey == n.cfg.ConfigMapName {
|
||||||
glog.V(2).Infof("updating configmap backend (%v)", mapKey)
|
glog.V(2).Infof("updating configmap backend (%v)", mapKey)
|
||||||
ic.cfg.Backend.SetConfig(upCmap)
|
n.SetConfig(upCmap)
|
||||||
ic.SetForceReload(true)
|
n.SetForceReload(true)
|
||||||
}
|
}
|
||||||
// updates to configuration configmaps can trigger an update
|
// updates to configuration configmaps can trigger an update
|
||||||
if mapKey == ic.cfg.ConfigMapName || mapKey == ic.cfg.TCPConfigMapName || mapKey == ic.cfg.UDPConfigMapName {
|
if mapKey == n.cfg.ConfigMapName || mapKey == n.cfg.TCPConfigMapName || mapKey == n.cfg.UDPConfigMapName {
|
||||||
ic.recorder.Eventf(upCmap, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("ConfigMap %v", mapKey))
|
n.recorder.Eventf(upCmap, apiv1.EventTypeNormal, "UPDATE", fmt.Sprintf("ConfigMap %v", mapKey))
|
||||||
ic.syncQueue.Enqueue(cur)
|
n.syncQueue.Enqueue(cur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
watchNs := apiv1.NamespaceAll
|
watchNs := apiv1.NamespaceAll
|
||||||
if ic.cfg.ForceNamespaceIsolation && ic.cfg.Namespace != apiv1.NamespaceAll {
|
if n.cfg.ForceNamespaceIsolation && n.cfg.Namespace != apiv1.NamespaceAll {
|
||||||
watchNs = ic.cfg.Namespace
|
watchNs = n.cfg.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
lister := &ingress.StoreLister{}
|
lister := &ingress.StoreLister{}
|
||||||
|
@ -203,34 +204,36 @@ func (ic *GenericController) createListers(disableNodeLister bool) (*ingress.Sto
|
||||||
controller := &cacheController{}
|
controller := &cacheController{}
|
||||||
|
|
||||||
lister.Ingress.Store, controller.Ingress = cache.NewInformer(
|
lister.Ingress.Store, controller.Ingress = cache.NewInformer(
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.ExtensionsV1beta1().RESTClient(), "ingresses", ic.cfg.Namespace, fields.Everything()),
|
cache.NewListWatchFromClient(n.cfg.Client.ExtensionsV1beta1().RESTClient(), "ingresses", n.cfg.Namespace, fields.Everything()),
|
||||||
&extensions.Ingress{}, ic.cfg.ResyncPeriod, ingEventHandler)
|
&extensions.Ingress{}, n.cfg.ResyncPeriod, ingEventHandler)
|
||||||
|
|
||||||
lister.Endpoint.Store, controller.Endpoint = cache.NewInformer(
|
lister.Endpoint.Store, controller.Endpoint = cache.NewInformer(
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "endpoints", ic.cfg.Namespace, fields.Everything()),
|
cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "endpoints", n.cfg.Namespace, fields.Everything()),
|
||||||
&apiv1.Endpoints{}, ic.cfg.ResyncPeriod, eventHandler)
|
&apiv1.Endpoints{}, n.cfg.ResyncPeriod, eventHandler)
|
||||||
|
|
||||||
lister.Secret.Store, controller.Secret = cache.NewInformer(
|
lister.Secret.Store, controller.Secret = cache.NewInformer(
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "secrets", watchNs, fields.Everything()),
|
cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "secrets", watchNs, fields.Everything()),
|
||||||
&apiv1.Secret{}, ic.cfg.ResyncPeriod, secrEventHandler)
|
&apiv1.Secret{}, n.cfg.ResyncPeriod, secrEventHandler)
|
||||||
|
|
||||||
lister.ConfigMap.Store, controller.Configmap = cache.NewInformer(
|
lister.ConfigMap.Store, controller.Configmap = cache.NewInformer(
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "configmaps", watchNs, fields.Everything()),
|
cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "configmaps", watchNs, fields.Everything()),
|
||||||
&apiv1.ConfigMap{}, ic.cfg.ResyncPeriod, mapEventHandler)
|
&apiv1.ConfigMap{}, n.cfg.ResyncPeriod, mapEventHandler)
|
||||||
|
|
||||||
lister.Service.Store, controller.Service = cache.NewInformer(
|
lister.Service.Store, controller.Service = cache.NewInformer(
|
||||||
cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "services", ic.cfg.Namespace, fields.Everything()),
|
cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "services", n.cfg.Namespace, fields.Everything()),
|
||||||
&apiv1.Service{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
&apiv1.Service{}, n.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
||||||
|
|
||||||
var nodeListerWatcher cache.ListerWatcher
|
var nodeListerWatcher cache.ListerWatcher
|
||||||
if disableNodeLister {
|
if disableNodeLister {
|
||||||
nodeListerWatcher = fcache.NewFakeControllerSource()
|
nodeListerWatcher = fcache.NewFakeControllerSource()
|
||||||
} else {
|
} else {
|
||||||
nodeListerWatcher = cache.NewListWatchFromClient(ic.cfg.Client.CoreV1().RESTClient(), "nodes", apiv1.NamespaceAll, fields.Everything())
|
nodeListerWatcher = cache.NewListWatchFromClient(n.cfg.Client.CoreV1().RESTClient(), "nodes", apiv1.NamespaceAll, fields.Everything())
|
||||||
}
|
}
|
||||||
lister.Node.Store, controller.Node = cache.NewInformer(
|
lister.Node.Store, controller.Node = cache.NewInformer(
|
||||||
nodeListerWatcher,
|
nodeListerWatcher,
|
||||||
&apiv1.Node{}, ic.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
&apiv1.Node{}, n.cfg.ResyncPeriod, cache.ResourceEventHandlerFuncs{})
|
||||||
|
|
||||||
return lister, controller
|
controller.Run(n.stopCh)
|
||||||
|
|
||||||
|
return lister
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,6 @@ func init() {
|
||||||
prometheus.MustRegister(reloadOperation)
|
prometheus.MustRegister(reloadOperation)
|
||||||
prometheus.MustRegister(reloadOperationErrors)
|
prometheus.MustRegister(reloadOperationErrors)
|
||||||
prometheus.MustRegister(sslExpireTime)
|
prometheus.MustRegister(sslExpireTime)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -74,11 +73,9 @@ func incReloadErrorCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSSLExpireTime(servers []*ingress.Server) {
|
func setSSLExpireTime(servers []*ingress.Server) {
|
||||||
|
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
if s.Hostname != defServerName {
|
if s.Hostname != defServerName {
|
||||||
sslExpireTime.WithLabelValues(s.Hostname).Set(float64(s.SSLExpireTime.Unix()))
|
sslExpireTime.WithLabelValues(s.Hostname).Set(float64(s.SSLExpireTime.Unix()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,31 +23,36 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/mitchellh/go-ps"
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
|
|
||||||
proxyproto "github.com/armon/go-proxyproto"
|
|
||||||
"github.com/ncabatoff/process-exporter/proc"
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
|
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
"k8s.io/client-go/tools/record"
|
||||||
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/controller"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/class"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/parser"
|
||||||
|
ngx_config "k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/controller/process"
|
||||||
|
ngx_template "k8s.io/ingress-nginx/pkg/ingress/controller/template"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/status"
|
||||||
|
ing_net "k8s.io/ingress-nginx/pkg/net"
|
||||||
"k8s.io/ingress-nginx/pkg/net/dns"
|
"k8s.io/ingress-nginx/pkg/net/dns"
|
||||||
"k8s.io/ingress-nginx/pkg/net/ssl"
|
"k8s.io/ingress-nginx/pkg/net/ssl"
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/config"
|
"k8s.io/ingress-nginx/pkg/task"
|
||||||
ngx_template "k8s.io/ingress-nginx/pkg/nginx/template"
|
|
||||||
"k8s.io/ingress-nginx/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type statusModule string
|
type statusModule string
|
||||||
|
@ -57,8 +62,6 @@ const (
|
||||||
|
|
||||||
defaultStatusModule statusModule = "default"
|
defaultStatusModule statusModule = "default"
|
||||||
vtsStatusModule statusModule = "vts"
|
vtsStatusModule statusModule = "vts"
|
||||||
|
|
||||||
defUpstreamName = "upstream-default-backend"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -71,26 +74,66 @@ var (
|
||||||
// NewNGINXController creates a new NGINX Ingress controller.
|
// NewNGINXController creates a new NGINX Ingress controller.
|
||||||
// If the environment variable NGINX_BINARY exists it will be used
|
// If the environment variable NGINX_BINARY exists it will be used
|
||||||
// as source for nginx commands
|
// as source for nginx commands
|
||||||
func NewNGINXController() *NGINXController {
|
func NewNGINXController(config *Configuration) *NGINXController {
|
||||||
ngx := os.Getenv("NGINX_BINARY")
|
ngx := os.Getenv("NGINX_BINARY")
|
||||||
if ngx == "" {
|
if ngx == "" {
|
||||||
ngx = nginxBinary
|
ngx = nginxBinary
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventBroadcaster := record.NewBroadcaster()
|
||||||
|
eventBroadcaster.StartLogging(glog.Infof)
|
||||||
|
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{
|
||||||
|
Interface: config.Client.CoreV1().Events(config.Namespace),
|
||||||
|
})
|
||||||
|
|
||||||
h, err := dns.GetSystemNameServers()
|
h, err := dns.GetSystemNameServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("unexpected error reading system nameservers: %v", err)
|
glog.Warningf("unexpected error reading system nameservers: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
n := &NGINXController{
|
n := &NGINXController{
|
||||||
|
backendDefaults: ngx_config.NewDefault().Backend,
|
||||||
binary: ngx,
|
binary: ngx,
|
||||||
|
|
||||||
configmap: &apiv1.ConfigMap{},
|
configmap: &apiv1.ConfigMap{},
|
||||||
isIPV6Enabled: isIPv6Enabled(),
|
|
||||||
|
isIPV6Enabled: ing_net.IsIPv6Enabled(),
|
||||||
|
|
||||||
resolver: h,
|
resolver: h,
|
||||||
ports: &config.ListenPorts{},
|
cfg: config,
|
||||||
backendDefaults: config.NewDefault().Backend,
|
sslCertTracker: newSSLCertTracker(),
|
||||||
|
syncRateLimiter: flowcontrol.NewTokenBucketRateLimiter(0.3, 1),
|
||||||
|
|
||||||
|
recorder: eventBroadcaster.NewRecorder(scheme.Scheme, apiv1.EventSource{
|
||||||
|
Component: "nginx-ingress-controller",
|
||||||
|
}),
|
||||||
|
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
stopLock: &sync.Mutex{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n.stats = newStatsCollector(config.Namespace, config.IngressClass, n.binary, n.cfg.ListenPorts.Status)
|
||||||
|
|
||||||
|
n.syncQueue = task.NewTaskQueue(n.syncIngress)
|
||||||
|
|
||||||
|
n.listers = n.createListers(config.DisableNodeList, n.stopCh)
|
||||||
|
|
||||||
|
if config.UpdateStatus {
|
||||||
|
n.syncStatus = status.NewStatusSyncer(status.Config{
|
||||||
|
Client: config.Client,
|
||||||
|
PublishService: n.cfg.PublishService,
|
||||||
|
IngressLister: n.listers.Ingress,
|
||||||
|
ElectionID: config.ElectionID,
|
||||||
|
IngressClass: config.IngressClass,
|
||||||
|
DefaultIngressClass: config.DefaultIngressClass,
|
||||||
|
UpdateStatusOnShutdown: config.UpdateStatusOnShutdown,
|
||||||
|
UseNodeInternalIP: n.cfg.UseNodeInternalIP,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
|
||||||
|
}
|
||||||
|
n.annotations = newAnnotationExtractor(n)
|
||||||
|
|
||||||
var onChange func()
|
var onChange func()
|
||||||
onChange = func() {
|
onChange = func() {
|
||||||
template, err := ngx_template.NewTemplate(tmplPath, onChange)
|
template, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||||
|
@ -107,7 +150,7 @@ Error loading new template : %v
|
||||||
n.t.Close()
|
n.t.Close()
|
||||||
n.t = template
|
n.t = template
|
||||||
glog.Info("new NGINX template loaded")
|
glog.Info("new NGINX template loaded")
|
||||||
n.controller.SetForceReload(true)
|
n.SetForceReload(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngxTpl, err := ngx_template.NewTemplate(tmplPath, onChange)
|
ngxTpl, err := ngx_template.NewTemplate(tmplPath, onChange)
|
||||||
|
@ -122,7 +165,39 @@ Error loading new template : %v
|
||||||
|
|
||||||
// NGINXController ...
|
// NGINXController ...
|
||||||
type NGINXController struct {
|
type NGINXController struct {
|
||||||
controller *controller.GenericController
|
cfg *Configuration
|
||||||
|
|
||||||
|
listers *ingress.StoreLister
|
||||||
|
|
||||||
|
annotations annotationExtractor
|
||||||
|
|
||||||
|
recorder record.EventRecorder
|
||||||
|
|
||||||
|
syncQueue *task.Queue
|
||||||
|
|
||||||
|
syncStatus status.Sync
|
||||||
|
|
||||||
|
// local store of SSL certificates
|
||||||
|
// (only certificates used in ingress)
|
||||||
|
sslCertTracker *sslCertTracker
|
||||||
|
|
||||||
|
syncRateLimiter flowcontrol.RateLimiter
|
||||||
|
|
||||||
|
// stopLock is used to enforce only a single call to Stop is active.
|
||||||
|
// Needed because we allow stopping through an http endpoint and
|
||||||
|
// allowing concurrent stoppers leads to stack traces.
|
||||||
|
stopLock *sync.Mutex
|
||||||
|
|
||||||
|
stopCh chan struct{}
|
||||||
|
|
||||||
|
// ngxErrCh channel used to detect errors with the nginx processes
|
||||||
|
ngxErrCh chan error
|
||||||
|
|
||||||
|
// runningConfig contains the running configuration in the Backend
|
||||||
|
runningConfig *ingress.Configuration
|
||||||
|
|
||||||
|
forceReload int32
|
||||||
|
|
||||||
t *ngx_template.Template
|
t *ngx_template.Template
|
||||||
|
|
||||||
configmap *apiv1.ConfigMap
|
configmap *apiv1.ConfigMap
|
||||||
|
@ -132,8 +207,6 @@ type NGINXController struct {
|
||||||
binary string
|
binary string
|
||||||
resolver []net.IP
|
resolver []net.IP
|
||||||
|
|
||||||
cmdArgs []string
|
|
||||||
|
|
||||||
stats *statsCollector
|
stats *statsCollector
|
||||||
statusModule statusModule
|
statusModule statusModule
|
||||||
|
|
||||||
|
@ -141,25 +214,42 @@ type NGINXController struct {
|
||||||
isIPV6Enabled bool
|
isIPV6Enabled bool
|
||||||
|
|
||||||
// returns true if proxy protocol es enabled
|
// returns true if proxy protocol es enabled
|
||||||
isProxyProtocolEnabled bool
|
IsProxyProtocolEnabled bool
|
||||||
|
|
||||||
isSSLPassthroughEnabled bool
|
isSSLPassthroughEnabled bool
|
||||||
|
|
||||||
isShuttingDown bool
|
isShuttingDown bool
|
||||||
|
|
||||||
proxy *proxy
|
Proxy *TCPProxy
|
||||||
|
|
||||||
ports *config.ListenPorts
|
|
||||||
|
|
||||||
backendDefaults defaults.Backend
|
backendDefaults defaults.Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start start a new NGINX master process running in foreground.
|
// Start start a new NGINX master process running in foreground.
|
||||||
func (n *NGINXController) Start() {
|
func (n *NGINXController) Start() {
|
||||||
n.isShuttingDown = false
|
glog.Infof("starting Ingress controller")
|
||||||
|
|
||||||
n.controller = controller.NewIngressController(n)
|
// initial sync of secrets to avoid unnecessary reloads
|
||||||
go n.controller.Start()
|
glog.Info("running initial sync of secrets")
|
||||||
|
for _, obj := range n.listers.Ingress.List() {
|
||||||
|
ing := obj.(*extensions.Ingress)
|
||||||
|
|
||||||
|
if !class.IsValid(ing, n.cfg.IngressClass, n.cfg.DefaultIngressClass) {
|
||||||
|
a, _ := parser.GetStringAnnotation(class.IngressKey, ing)
|
||||||
|
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", ing.Name, class.IngressKey, a)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n.readSecrets(ing)
|
||||||
|
}
|
||||||
|
|
||||||
|
go n.syncQueue.Run(time.Second, n.stopCh)
|
||||||
|
|
||||||
|
if n.syncStatus != nil {
|
||||||
|
go n.syncStatus.Run(n.stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
go wait.Until(n.checkMissingSecrets, 30*time.Second, n.stopCh)
|
||||||
|
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
cmd := exec.Command(n.binary, "-c", cfgPath)
|
cmd := exec.Command(n.binary, "-c", cfgPath)
|
||||||
|
@ -172,68 +262,55 @@ func (n *NGINXController) Start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Info("starting NGINX process...")
|
glog.Info("starting NGINX process...")
|
||||||
n.start(cmd, done)
|
n.start(cmd)
|
||||||
|
|
||||||
|
// force initial sync
|
||||||
|
n.syncQueue.Enqueue(&extensions.Ingress{})
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
if n.isShuttingDown {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// if the nginx master process dies the workers continue to process requests,
|
// if the nginx master process dies the workers continue to process requests,
|
||||||
// passing checks but in case of updates in ingress no updates will be
|
// passing checks but in case of updates in ingress no updates will be
|
||||||
// reflected in the nginx configuration which can lead to confusion and report
|
// reflected in the nginx configuration which can lead to confusion and report
|
||||||
// issues because of this behavior.
|
// issues because of this behavior.
|
||||||
// To avoid this issue we restart nginx in case of errors.
|
// To avoid this issue we restart nginx in case of errors.
|
||||||
for {
|
if process.IsRespawnIfRequired(err) {
|
||||||
err := <-done
|
process.WaitUntilPortIsAvailable(n.cfg.ListenPorts.HTTP)
|
||||||
|
// release command resources
|
||||||
if n.isShuttingDown {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
|
||||||
waitStatus := exitError.Sys().(syscall.WaitStatus)
|
|
||||||
glog.Warningf(`
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
NGINX master process died (%v): %v
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
`, waitStatus.ExitStatus(), err)
|
|
||||||
}
|
|
||||||
cmd.Process.Release()
|
cmd.Process.Release()
|
||||||
cmd = exec.Command(n.binary, "-c", cfgPath)
|
cmd = exec.Command(n.binary, "-c", cfgPath)
|
||||||
// we wait until the workers are killed
|
// start a new nginx master process if the controller is not being stopped
|
||||||
for {
|
n.start(cmd)
|
||||||
conn, err := net.DialTimeout("tcp", "127.0.0.1:80", 1*time.Second)
|
}
|
||||||
if err != nil {
|
case <-n.stopCh:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
conn.Close()
|
|
||||||
// kill nginx worker processes
|
|
||||||
fs, err := proc.NewFS("/proc")
|
|
||||||
procs, _ := fs.FS.AllProcs()
|
|
||||||
for _, p := range procs {
|
|
||||||
pn, err := p.Comm()
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error obtaining process information: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if pn == "nginx" {
|
|
||||||
osp, err := os.FindProcess(p.PID)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("unexpected error obtaining process information: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
osp.Signal(syscall.SIGQUIT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
// restart a new nginx master process if the controller
|
|
||||||
// is not being stopped
|
|
||||||
n.start(cmd, done)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop gracefully stops the NGINX master process.
|
// Stop gracefully stops the NGINX master process.
|
||||||
func (n *NGINXController) Stop() error {
|
func (n *NGINXController) Stop() error {
|
||||||
n.isShuttingDown = true
|
n.isShuttingDown = true
|
||||||
n.controller.Stop()
|
|
||||||
|
n.stopLock.Lock()
|
||||||
|
defer n.stopLock.Unlock()
|
||||||
|
|
||||||
|
// Only try draining the workqueue if we haven't already.
|
||||||
|
if n.syncQueue.IsShuttingDown() {
|
||||||
|
return fmt.Errorf("shutdown already in progress")
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("shutting down controller queues")
|
||||||
|
close(n.stopCh)
|
||||||
|
go n.syncQueue.Shutdown()
|
||||||
|
if n.syncStatus != nil {
|
||||||
|
n.syncStatus.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
// Send stop signal to Nginx
|
// Send stop signal to Nginx
|
||||||
glog.Info("stopping NGINX process...")
|
glog.Info("stopping NGINX process...")
|
||||||
|
@ -246,192 +323,42 @@ func (n *NGINXController) Stop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the Nginx process disappear
|
// Wait for the Nginx process disappear
|
||||||
waitForNginxShutdown()
|
timer := time.NewTicker(time.Second * 1)
|
||||||
|
for t := range timer.C {
|
||||||
|
glog.V(3).Infof("tick at", t)
|
||||||
|
if !process.IsNginxRunning() {
|
||||||
glog.Info("NGINX process has stopped")
|
glog.Info("NGINX process has stopped")
|
||||||
|
timer.Stop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NGINXController) start(cmd *exec.Cmd, done chan error) {
|
func (n *NGINXController) start(cmd *exec.Cmd) {
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
glog.Fatalf("nginx error: %v", err)
|
glog.Fatalf("nginx error: %v", err)
|
||||||
done <- err
|
n.ngxErrCh <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n.cmdArgs = cmd.Args
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
done <- cmd.Wait()
|
n.ngxErrCh <- cmd.Wait()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackendDefaults returns the nginx defaults
|
|
||||||
func (n NGINXController) BackendDefaults() defaults.Backend {
|
|
||||||
return n.backendDefaults
|
|
||||||
}
|
|
||||||
|
|
||||||
// printDiff returns the difference between the running configuration
|
|
||||||
// and the new one
|
|
||||||
func (n NGINXController) printDiff(data []byte) {
|
|
||||||
if !glog.V(2) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
in, err := os.Open(cfgPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
src, err := ioutil.ReadAll(in)
|
|
||||||
in.Close()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(src, data) {
|
|
||||||
tmpfile, err := ioutil.TempFile("", "nginx-cfg-diff")
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("error creating temporal file: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer tmpfile.Close()
|
|
||||||
err = ioutil.WriteFile(tmpfile.Name(), data, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
diffOutput, err := diff(src, data)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("error computing diff: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Infof("NGINX configuration diff\n")
|
|
||||||
glog.Infof("%v", string(diffOutput))
|
|
||||||
|
|
||||||
os.Remove(tmpfile.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info return build information
|
|
||||||
func (n NGINXController) Info() *ingress.BackendInfo {
|
|
||||||
return &ingress.BackendInfo{
|
|
||||||
Name: "NGINX",
|
|
||||||
Release: version.RELEASE,
|
|
||||||
Build: version.COMMIT,
|
|
||||||
Repository: version.REPO,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultEndpoint returns the default endpoint to be use as default server that returns 404.
|
// DefaultEndpoint returns the default endpoint to be use as default server that returns 404.
|
||||||
func (n NGINXController) DefaultEndpoint() ingress.Endpoint {
|
func (n NGINXController) DefaultEndpoint() ingress.Endpoint {
|
||||||
return ingress.Endpoint{
|
return ingress.Endpoint{
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
Port: fmt.Sprintf("%v", n.ports.Default),
|
Port: fmt.Sprintf("%v", n.cfg.ListenPorts.Default),
|
||||||
Target: &apiv1.ObjectReference{},
|
Target: &apiv1.ObjectReference{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureFlags allow to configure more flags before the parsing of
|
|
||||||
// command line arguments
|
|
||||||
func (n *NGINXController) ConfigureFlags(flags *pflag.FlagSet) {
|
|
||||||
flags.BoolVar(&n.isSSLPassthroughEnabled, "enable-ssl-passthrough", false, `Enable SSL passthrough feature. Default is disabled`)
|
|
||||||
flags.IntVar(&n.ports.HTTP, "http-port", 80, `Indicates the port to use for HTTP traffic`)
|
|
||||||
flags.IntVar(&n.ports.HTTPS, "https-port", 443, `Indicates the port to use for HTTPS traffic`)
|
|
||||||
flags.IntVar(&n.ports.Status, "status-port", 18080, `Indicates the TCP port to use for exposing the nginx status page`)
|
|
||||||
flags.IntVar(&n.ports.SSLProxy, "ssl-passtrough-proxy-port", 442, `Default port to use internally for SSL when SSL Passthgough is enabled`)
|
|
||||||
flags.IntVar(&n.ports.Default, "default-server-port", 8181, `Default port to use for exposing the default server (catch all)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OverrideFlags customize NGINX controller flags
|
|
||||||
func (n *NGINXController) OverrideFlags(flags *pflag.FlagSet) {
|
|
||||||
// we check port collisions
|
|
||||||
if !isPortAvailable(n.ports.HTTP) {
|
|
||||||
glog.Fatalf("Port %v is already in use. Please check the flag --http-port", n.ports.HTTP)
|
|
||||||
}
|
|
||||||
if !isPortAvailable(n.ports.HTTPS) {
|
|
||||||
glog.Fatalf("Port %v is already in use. Please check the flag --https-port", n.ports.HTTPS)
|
|
||||||
}
|
|
||||||
if !isPortAvailable(n.ports.Status) {
|
|
||||||
glog.Fatalf("Port %v is already in use. Please check the flag --status-port", n.ports.Status)
|
|
||||||
}
|
|
||||||
if !isPortAvailable(n.ports.Default) {
|
|
||||||
glog.Fatalf("Port %v is already in use. Please check the flag --default-server-port", n.ports.Default)
|
|
||||||
}
|
|
||||||
|
|
||||||
ic, _ := flags.GetString("ingress-class")
|
|
||||||
wc, _ := flags.GetString("watch-namespace")
|
|
||||||
|
|
||||||
if ic == "" {
|
|
||||||
ic = defIngressClass
|
|
||||||
}
|
|
||||||
|
|
||||||
if ic != defIngressClass {
|
|
||||||
glog.Warningf("only Ingress with class %v will be processed by this ingress controller", ic)
|
|
||||||
}
|
|
||||||
|
|
||||||
flags.Set("ingress-class", ic)
|
|
||||||
|
|
||||||
h, _ := flags.GetInt("healthz-port")
|
|
||||||
n.ports.Health = h
|
|
||||||
|
|
||||||
n.stats = newStatsCollector(wc, ic, n.binary, n.ports.Status)
|
|
||||||
|
|
||||||
if n.isSSLPassthroughEnabled {
|
|
||||||
if !isPortAvailable(n.ports.SSLProxy) {
|
|
||||||
glog.Fatalf("Port %v is already in use. Please check the flag --ssl-passtrough-proxy-port", n.ports.SSLProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.Info("starting TLS proxy for SSL passthrough")
|
|
||||||
n.proxy = &proxy{
|
|
||||||
Default: &server{
|
|
||||||
Hostname: "localhost",
|
|
||||||
IP: "127.0.0.1",
|
|
||||||
Port: n.ports.SSLProxy,
|
|
||||||
ProxyProtocol: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%v", n.ports.HTTPS))
|
|
||||||
if err != nil {
|
|
||||||
glog.Fatalf("%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyList := &proxyproto.Listener{Listener: listener}
|
|
||||||
|
|
||||||
// start goroutine that accepts tcp connections in port 443
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
var conn net.Conn
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if n.isProxyProtocolEnabled {
|
|
||||||
// we need to wrap the listener in order to decode
|
|
||||||
// proxy protocol before handling the connection
|
|
||||||
conn, err = proxyList.Accept()
|
|
||||||
} else {
|
|
||||||
conn, err = listener.Accept()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
glog.Warningf("unexpected error accepting tcp connection: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
glog.V(3).Infof("remote address %s to local %s", conn.RemoteAddr(), conn.LocalAddr())
|
|
||||||
go n.proxy.Handle(conn)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultIngressClass just return the default ingress class
|
|
||||||
func (n NGINXController) DefaultIngressClass() string {
|
|
||||||
return defIngressClass
|
|
||||||
}
|
|
||||||
|
|
||||||
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
// testTemplate checks if the NGINX configuration inside the byte array is valid
|
||||||
// running the command "nginx -t" using a temporal file.
|
// running the command "nginx -t" using a temporal file.
|
||||||
func (n NGINXController) testTemplate(cfg []byte) error {
|
func (n NGINXController) testTemplate(cfg []byte) error {
|
||||||
|
@ -466,7 +393,7 @@ Error: %v
|
||||||
// SetConfig sets the configured configmap
|
// SetConfig sets the configured configmap
|
||||||
func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
|
func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
|
||||||
n.configmap = cmap
|
n.configmap = cmap
|
||||||
n.isProxyProtocolEnabled = false
|
n.IsProxyProtocolEnabled = false
|
||||||
|
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
if cmap != nil {
|
if cmap != nil {
|
||||||
|
@ -477,7 +404,7 @@ func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
|
||||||
if ok {
|
if ok {
|
||||||
b, err := strconv.ParseBool(val)
|
b, err := strconv.ParseBool(val)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n.isProxyProtocolEnabled = b
|
n.IsProxyProtocolEnabled = b
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,16 +421,6 @@ func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
|
||||||
n.backendDefaults = c.Backend
|
n.backendDefaults = c.Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetListers sets the configured store listers in the generic ingress controller
|
|
||||||
func (n *NGINXController) SetListers(lister *ingress.StoreLister) {
|
|
||||||
n.storeLister = lister
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateIngressStatus custom Ingress status update
|
|
||||||
func (n *NGINXController) UpdateIngressStatus(*extensions.Ingress) []apiv1.LoadBalancerIngress {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnUpdate is called by syncQueue in https://github.com/kubernetes/ingress-nginx/blob/master/pkg/ingress/controller/controller.go#L426
|
// OnUpdate is called by syncQueue in https://github.com/kubernetes/ingress-nginx/blob/master/pkg/ingress/controller/controller.go#L426
|
||||||
// periodically to keep the configuration in sync.
|
// periodically to keep the configuration in sync.
|
||||||
//
|
//
|
||||||
|
@ -516,7 +433,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||||
cfg := ngx_template.ReadConfig(n.configmap.Data)
|
cfg := ngx_template.ReadConfig(n.configmap.Data)
|
||||||
cfg.Resolver = n.resolver
|
cfg.Resolver = n.resolver
|
||||||
|
|
||||||
servers := []*server{}
|
servers := []*TCPServer{}
|
||||||
for _, pb := range ingressCfg.PassthroughBackends {
|
for _, pb := range ingressCfg.PassthroughBackends {
|
||||||
svc := pb.Service
|
svc := pb.Service
|
||||||
if svc == nil {
|
if svc == nil {
|
||||||
|
@ -541,7 +458,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Allow PassthroughBackends to specify they support proxy-protocol
|
//TODO: Allow PassthroughBackends to specify they support proxy-protocol
|
||||||
servers = append(servers, &server{
|
servers = append(servers, &TCPServer{
|
||||||
Hostname: pb.Hostname,
|
Hostname: pb.Hostname,
|
||||||
IP: svc.Spec.ClusterIP,
|
IP: svc.Spec.ClusterIP,
|
||||||
Port: port,
|
Port: port,
|
||||||
|
@ -550,7 +467,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.isSSLPassthroughEnabled {
|
if n.isSSLPassthroughEnabled {
|
||||||
n.proxy.ServerList = servers
|
n.Proxy.ServerList = servers
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to check if the status module configuration changed
|
// we need to check if the status module configuration changed
|
||||||
|
@ -671,7 +588,7 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||||
|
|
||||||
cfg.SSLDHParam = sslDHParam
|
cfg.SSLDHParam = sslDHParam
|
||||||
|
|
||||||
tc := config.TemplateConfig{
|
tc := ngx_config.TemplateConfig{
|
||||||
ProxySetHeaders: setHeaders,
|
ProxySetHeaders: setHeaders,
|
||||||
AddHeaders: addHeaders,
|
AddHeaders: addHeaders,
|
||||||
MaxOpenFiles: maxOpenFiles,
|
MaxOpenFiles: maxOpenFiles,
|
||||||
|
@ -687,8 +604,8 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||||
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
||||||
RedirectServers: redirectServers,
|
RedirectServers: redirectServers,
|
||||||
IsSSLPassthroughEnabled: n.isSSLPassthroughEnabled,
|
IsSSLPassthroughEnabled: n.isSSLPassthroughEnabled,
|
||||||
ListenPorts: n.ports,
|
ListenPorts: n.cfg.ListenPorts,
|
||||||
PublishService: n.controller.GetPublishService(),
|
PublishService: n.GetPublishService(),
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := n.t.Write(tc)
|
content, err := n.t.Write(tc)
|
||||||
|
@ -702,7 +619,34 @@ func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
n.printDiff(content)
|
if glog.V(2) {
|
||||||
|
src, _ := ioutil.ReadFile(cfgPath)
|
||||||
|
if !bytes.Equal(src, content) {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "new-nginx-cfg")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tmpfile.Close()
|
||||||
|
err = ioutil.WriteFile(tmpfile.Name(), content, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diffOutput, err := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Infof("NGINX configuration diff\n")
|
||||||
|
glog.Infof("%v\n", string(diffOutput))
|
||||||
|
|
||||||
|
// Do not use defer to remove the temporal file.
|
||||||
|
// This is helpful when there is an error in the
|
||||||
|
// temporal configuration (we can manually inspect the file).
|
||||||
|
// Only remove the file when no error occured.
|
||||||
|
os.Remove(tmpfile.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = ioutil.WriteFile(cfgPath, content, 0644)
|
err = ioutil.WriteFile(cfgPath, content, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -727,46 +671,6 @@ func nginxHashBucketSize(longestString int) int {
|
||||||
return nextPowerOf2(rawSize)
|
return nextPowerOf2(rawSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the healthcheck name
|
|
||||||
func (n NGINXController) Name() string {
|
|
||||||
return "Ingress Controller"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check returns if the nginx healthz endpoint is returning ok (status code 200)
|
|
||||||
func (n NGINXController) Check(_ *http.Request) error {
|
|
||||||
res, err := http.Get(fmt.Sprintf("http://0.0.0.0:%v%v", n.ports.Status, ngxHealthPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
return fmt.Errorf("ingress controller is not healthy")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the nginx master process is running
|
|
||||||
fs, err := proc.NewFS("/proc")
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("%v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := ioutil.ReadFile("/run/nginx.pid")
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("%v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pid, err := strconv.Atoi(strings.TrimRight(string(f), "\r\n"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = fs.NewProc(int(pid))
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("%v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||||
// https://play.golang.org/p/TVSyCcdxUh
|
// https://play.golang.org/p/TVSyCcdxUh
|
||||||
func nextPowerOf2(v int) int {
|
func nextPowerOf2(v int) int {
|
||||||
|
@ -780,32 +684,3 @@ func nextPowerOf2(v int) int {
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
func isIPv6Enabled() bool {
|
|
||||||
cmd := exec.Command("test", "-f", "/proc/net/if_inet6")
|
|
||||||
return cmd.Run() == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isNginxRunning returns true if a process with the name 'nginx' is found
|
|
||||||
func isNginxProcessPresent() bool {
|
|
||||||
processes, _ := ps.Processes()
|
|
||||||
for _, p := range processes {
|
|
||||||
if p.Executable() == "nginx" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func waitForNginxShutdown() {
|
|
||||||
timer := time.NewTicker(time.Second * 1)
|
|
||||||
defer timer.Stop()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timer.C:
|
|
||||||
if !isNginxProcessPresent() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
71
pkg/ingress/controller/process/nginx.go
Normal file
71
pkg/ingress/controller/process/nginx.go
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
ps "github.com/mitchellh/go-ps"
|
||||||
|
"github.com/ncabatoff/process-exporter/proc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsRespawnIfRequired(err error) bool {
|
||||||
|
exitError, ok := err.(*exec.ExitError)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
waitStatus := exitError.Sys().(syscall.WaitStatus)
|
||||||
|
glog.Warningf(`
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
NGINX master process died (%v): %v
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
`, waitStatus.ExitStatus(), err)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func WaitUntilPortIsAvailable(port int) {
|
||||||
|
// we wait until the workers are killed
|
||||||
|
for {
|
||||||
|
conn, err := net.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%v", port), 1*time.Second)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
// kill nginx worker processes
|
||||||
|
fs, err := proc.NewFS("/proc")
|
||||||
|
procs, _ := fs.FS.AllProcs()
|
||||||
|
for _, p := range procs {
|
||||||
|
pn, err := p.Comm()
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error obtaining process information: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if pn == "nginx" {
|
||||||
|
osp, err := os.FindProcess(p.PID)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("unexpected error obtaining process information: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
osp.Signal(syscall.SIGQUIT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNginxRunning returns true if a process with the name 'nginx' is found
|
||||||
|
func IsNginxRunning() bool {
|
||||||
|
processes, _ := ps.Processes()
|
||||||
|
for _, p := range processes {
|
||||||
|
if p.Executable() == "nginx" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/metric/collector"
|
"k8s.io/ingress-nginx/pkg/ingress/controller/metric/collector"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
|
@ -10,19 +10,19 @@ import (
|
||||||
"github.com/paultag/sniff/parser"
|
"github.com/paultag/sniff/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
type server struct {
|
type TCPServer struct {
|
||||||
Hostname string
|
Hostname string
|
||||||
IP string
|
IP string
|
||||||
Port int
|
Port int
|
||||||
ProxyProtocol bool
|
ProxyProtocol bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type proxy struct {
|
type TCPProxy struct {
|
||||||
ServerList []*server
|
ServerList []*TCPServer
|
||||||
Default *server
|
Default *TCPServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxy) Get(host string) *server {
|
func (p *TCPProxy) Get(host string) *TCPServer {
|
||||||
if p.ServerList == nil {
|
if p.ServerList == nil {
|
||||||
return p.Default
|
return p.Default
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ func (p *proxy) Get(host string) *server {
|
||||||
return p.Default
|
return p.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxy) Handle(conn net.Conn) {
|
func (p *TCPProxy) Handle(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
data := make([]byte, 4096)
|
data := make([]byte, 4096)
|
||||||
|
|
|
@ -26,8 +26,8 @@ import (
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
ing_net "k8s.io/ingress-nginx/pkg/net"
|
ing_net "k8s.io/ingress-nginx/pkg/net"
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -41,12 +41,10 @@ const (
|
||||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||||
func ReadConfig(src map[string]string) config.Configuration {
|
func ReadConfig(src map[string]string) config.Configuration {
|
||||||
conf := map[string]string{}
|
conf := map[string]string{}
|
||||||
if src != nil {
|
|
||||||
// we need to copy the configmap data because the content is altered
|
// we need to copy the configmap data because the content is altered
|
||||||
for k, v := range src {
|
for k, v := range src {
|
||||||
conf[k] = v
|
conf[k] = v
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
errors := make([]int, 0)
|
errors := make([]int, 0)
|
||||||
skipUrls := make([]string, 0)
|
skipUrls := make([]string, 0)
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/kylelemons/godebug/pretty"
|
"github.com/kylelemons/godebug/pretty"
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/config"
|
"k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFilterErrors(t *testing.T) {
|
func TestFilterErrors(t *testing.T) {
|
|
@ -37,8 +37,8 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
||||||
|
"k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
ing_net "k8s.io/ingress-nginx/pkg/net"
|
ing_net "k8s.io/ingress-nginx/pkg/net"
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/config"
|
|
||||||
"k8s.io/ingress-nginx/pkg/watch"
|
"k8s.io/ingress-nginx/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,7 +29,7 @@ import (
|
||||||
"k8s.io/ingress-nginx/pkg/ingress"
|
"k8s.io/ingress-nginx/pkg/ingress"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
|
||||||
"k8s.io/ingress-nginx/pkg/nginx/config"
|
"k8s.io/ingress-nginx/pkg/ingress/controller/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -158,7 +158,7 @@ func TestBuildAuthResponseHeaders(t *testing.T) {
|
||||||
|
|
||||||
func TestTemplateWithData(t *testing.T) {
|
func TestTemplateWithData(t *testing.T) {
|
||||||
pwd, _ := os.Getwd()
|
pwd, _ := os.Getwd()
|
||||||
f, err := os.Open(path.Join(pwd, "../../../test/data/config.json"))
|
f, err := os.Open(path.Join(pwd, "../../../../test/data/config.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error reading json file: %v", err)
|
t.Errorf("unexpected error reading json file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,7 @@ func TestTemplateWithData(t *testing.T) {
|
||||||
if dat.ListenPorts == nil {
|
if dat.ListenPorts == nil {
|
||||||
dat.ListenPorts = &config.ListenPorts{}
|
dat.ListenPorts = &config.ListenPorts{}
|
||||||
}
|
}
|
||||||
tf, err := os.Open(path.Join(pwd, "../../../rootfs/etc/nginx/template/nginx.tmpl"))
|
tf, err := os.Open(path.Join(pwd, "../../../../rootfs/etc/nginx/template/nginx.tmpl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error reading json file: %v", err)
|
t.Errorf("unexpected error reading json file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ func TestTemplateWithData(t *testing.T) {
|
||||||
|
|
||||||
func BenchmarkTemplateWithData(b *testing.B) {
|
func BenchmarkTemplateWithData(b *testing.B) {
|
||||||
pwd, _ := os.Getwd()
|
pwd, _ := os.Getwd()
|
||||||
f, err := os.Open(path.Join(pwd, "../../../test/data/config.json"))
|
f, err := os.Open(path.Join(pwd, "../../../../test/data/config.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Errorf("unexpected error reading json file: %v", err)
|
b.Errorf("unexpected error reading json file: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ func BenchmarkTemplateWithData(b *testing.B) {
|
||||||
b.Errorf("unexpected error unmarshalling json: %v", err)
|
b.Errorf("unexpected error unmarshalling json: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tf, err := os.Open(path.Join(pwd, "../../rootfs/etc/nginx/template/nginx.tmpl"))
|
tf, err := os.Open(path.Join(pwd, "../../../rootfs/etc/nginx/template/nginx.tmpl"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Errorf("unexpected error reading json file: %v", err)
|
b.Errorf("unexpected error reading json file: %v", err)
|
||||||
}
|
}
|
|
@ -17,11 +17,6 @@ limitations under the License.
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
@ -54,34 +49,3 @@ func sysctlFSFileMax() int {
|
||||||
}
|
}
|
||||||
return int(rLimit.Max)
|
return int(rLimit.Max)
|
||||||
}
|
}
|
||||||
|
|
||||||
func diff(b1, b2 []byte) ([]byte, error) {
|
|
||||||
f1, err := ioutil.TempFile("", "a")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f1.Close()
|
|
||||||
defer os.Remove(f1.Name())
|
|
||||||
|
|
||||||
f2, err := ioutil.TempFile("", "b")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f2.Close()
|
|
||||||
defer os.Remove(f2.Name())
|
|
||||||
|
|
||||||
f1.Write(b1)
|
|
||||||
f2.Write(b2)
|
|
||||||
|
|
||||||
out, _ := exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isPortAvailable(p int) bool {
|
|
||||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%v", p))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ln.Close()
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2016 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 ingress
|
|
||||||
|
|
||||||
// This package contains the interface is required to implement to build an Ingress controller
|
|
||||||
// A dummy implementation could be
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// dc := newDummyController()
|
|
||||||
// controller.NewIngressController(dc)
|
|
||||||
// glog.Infof("shutting down Ingress controller...")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//where newDummyController returns and implementation of the Controller interface:
|
|
||||||
//
|
|
||||||
// func newDummyController() ingress.Controller {
|
|
||||||
// return &DummyController{}
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// type DummyController struct {
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (dc DummyController) Reload(data []byte) ([]byte, error) {
|
|
||||||
// err := ioutil.WriteFile("/arbitrary-path", data, 0644)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return exec.Command("some command", "--reload").CombinedOutput()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (dc DummyController) Test(file string) *exec.Cmd {
|
|
||||||
// return exec.Command("some command", "--config-file", file)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (dc DummyController) OnUpdate(*api.ConfigMap, Configuration) ([]byte, error) {
|
|
||||||
// return []byte(`<string containing a configuration file>`)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (dc DummyController) BackendDefaults() defaults.Backend {
|
|
||||||
// return ingress.NewStandardDefaults()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (n DummyController) Name() string {
|
|
||||||
// return "dummy Controller"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (n DummyController) Check(_ *http.Request) error {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func (dc DummyController) Info() *BackendInfo {
|
|
||||||
// Name: "dummy",
|
|
||||||
// Release: "0.0.0",
|
|
||||||
// Build: "git-00000000",
|
|
||||||
// Repository: "git://foo.bar.com",
|
|
||||||
// }
|
|
|
@ -72,9 +72,6 @@ type Config struct {
|
||||||
|
|
||||||
DefaultIngressClass string
|
DefaultIngressClass string
|
||||||
IngressClass string
|
IngressClass string
|
||||||
|
|
||||||
// CustomIngressStatus allows to set custom values in Ingress status
|
|
||||||
CustomIngressStatus func(*extensions.Ingress) []apiv1.LoadBalancerIngress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
|
// statusSync keeps the status IP in each Ingress rule updated executing a periodic check
|
||||||
|
@ -252,9 +249,8 @@ func (s *statusSync) runningAddresses() ([]string, error) {
|
||||||
addrs = append(addrs, ip.IP)
|
addrs = append(addrs, ip.IP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, ip := range svc.Spec.ExternalIPs {
|
|
||||||
addrs = append(addrs, ip)
|
addrs = append(addrs, svc.Spec.ExternalIPs...)
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
return addrs, nil
|
||||||
}
|
}
|
||||||
|
@ -307,8 +303,6 @@ func sliceToStatus(endpoints []string) []apiv1.LoadBalancerIngress {
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateStatus changes the status information of Ingress rules
|
// updateStatus changes the status information of Ingress rules
|
||||||
// If the backend function CustomIngressStatus returns a value different
|
|
||||||
// of nil then it uses the returned value or the newIngressPoint values
|
|
||||||
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
||||||
ings := s.IngressLister.List()
|
ings := s.IngressLister.List()
|
||||||
|
|
||||||
|
@ -324,7 +318,7 @@ func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.Queue(runUpdate(ing, newIngressPoint, s.Client, s.CustomIngressStatus))
|
batch.Queue(runUpdate(ing, newIngressPoint, s.Client))
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.QueueComplete()
|
batch.QueueComplete()
|
||||||
|
@ -332,18 +326,13 @@ func (s *statusSync) updateStatus(newIngressPoint []apiv1.LoadBalancerIngress) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdate(ing *extensions.Ingress, status []apiv1.LoadBalancerIngress,
|
func runUpdate(ing *extensions.Ingress, status []apiv1.LoadBalancerIngress,
|
||||||
client clientset.Interface,
|
client clientset.Interface) pool.WorkFunc {
|
||||||
statusFunc func(*extensions.Ingress) []apiv1.LoadBalancerIngress) pool.WorkFunc {
|
|
||||||
return func(wu pool.WorkUnit) (interface{}, error) {
|
return func(wu pool.WorkUnit) (interface{}, error) {
|
||||||
if wu.IsCancelled() {
|
if wu.IsCancelled() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
addrs := status
|
addrs := status
|
||||||
ca := statusFunc(ing)
|
|
||||||
if ca != nil {
|
|
||||||
addrs = ca
|
|
||||||
}
|
|
||||||
sort.SliceStable(addrs, lessLoadBalancerIngress(addrs))
|
sort.SliceStable(addrs, lessLoadBalancerIngress(addrs))
|
||||||
|
|
||||||
curIPs := ing.Status.LoadBalancer.Ingress
|
curIPs := ing.Status.LoadBalancer.Ingress
|
||||||
|
|
|
@ -249,9 +249,6 @@ func buildStatusSync() statusSync {
|
||||||
Client: buildSimpleClientSet(),
|
Client: buildSimpleClientSet(),
|
||||||
PublishService: apiv1.NamespaceDefault + "/" + "foo",
|
PublishService: apiv1.NamespaceDefault + "/" + "foo",
|
||||||
IngressLister: buildIngressListener(),
|
IngressLister: buildIngressListener(),
|
||||||
CustomIngressStatus: func(*extensions.Ingress) []apiv1.LoadBalancerIngress {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,9 +264,6 @@ func TestStatusActions(t *testing.T) {
|
||||||
DefaultIngressClass: "nginx",
|
DefaultIngressClass: "nginx",
|
||||||
IngressClass: "",
|
IngressClass: "",
|
||||||
UpdateStatusOnShutdown: true,
|
UpdateStatusOnShutdown: true,
|
||||||
CustomIngressStatus: func(*extensions.Ingress) []apiv1.LoadBalancerIngress {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
// create object
|
// create object
|
||||||
fkSync := NewStatusSyncer(c)
|
fkSync := NewStatusSyncer(c)
|
||||||
|
|
|
@ -17,15 +17,11 @@ limitations under the License.
|
||||||
package ingress
|
package ingress
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
|
||||||
|
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
extensions "k8s.io/api/extensions/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apiserver/pkg/server/healthz"
|
|
||||||
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/auth"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/authreq"
|
||||||
|
@ -36,7 +32,6 @@ import (
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/ratelimit"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/redirect"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/redirect"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
|
"k8s.io/ingress-nginx/pkg/ingress/annotations/rewrite"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/defaults"
|
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
"k8s.io/ingress-nginx/pkg/ingress/resolver"
|
||||||
"k8s.io/ingress-nginx/pkg/ingress/store"
|
"k8s.io/ingress-nginx/pkg/ingress/store"
|
||||||
)
|
)
|
||||||
|
@ -49,63 +44,6 @@ var (
|
||||||
DefaultSSLDirectory = "/ingress-controller/ssl"
|
DefaultSSLDirectory = "/ingress-controller/ssl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Controller holds the methods to handle an Ingress backend
|
|
||||||
// TODO (#18): Make sure this is sufficiently supportive of other backends.
|
|
||||||
type Controller interface {
|
|
||||||
// HealthzChecker returns is a named healthz check that returns the ingress
|
|
||||||
// controller status
|
|
||||||
healthz.HealthzChecker
|
|
||||||
|
|
||||||
// OnUpdate callback invoked from the sync queue https://k8s.io/ingress/core/blob/master/pkg/ingress/controller/controller.go#L387
|
|
||||||
// when an update occurs. This is executed frequently because Ingress
|
|
||||||
// controllers watches changes in:
|
|
||||||
// - Ingresses: main work
|
|
||||||
// - Secrets: referenced from Ingress rules with TLS configured
|
|
||||||
// - ConfigMaps: where the controller reads custom configuration
|
|
||||||
// - Services: referenced from Ingress rules and required to obtain
|
|
||||||
// information about ports and annotations
|
|
||||||
// - Endpoints: referenced from Services and what the backend uses
|
|
||||||
// to route traffic
|
|
||||||
// Any update to services, endpoints, secrets (only those referenced from Ingress)
|
|
||||||
// and ingress trigger the execution.
|
|
||||||
// Notifications of type Add, Update and Delete:
|
|
||||||
// https://github.com/kubernetes/kubernetes/blob/master/pkg/client/cache/controller.go#L164
|
|
||||||
//
|
|
||||||
// Configuration returns the translation from Ingress rules containing
|
|
||||||
// information about all the upstreams (service endpoints ) "virtual"
|
|
||||||
// servers (FQDN) and all the locations inside each server. Each
|
|
||||||
// location contains information about all the annotations were configured
|
|
||||||
// https://k8s.io/ingress/core/blob/master/pkg/ingress/types.go#L83
|
|
||||||
// The backend returns an error if was not possible to update the configuration.
|
|
||||||
//
|
|
||||||
OnUpdate(Configuration) error
|
|
||||||
// ConfigMap content of --configmap
|
|
||||||
SetConfig(*apiv1.ConfigMap)
|
|
||||||
// SetListers allows the access of store listers present in the generic controller
|
|
||||||
// This avoid the use of the kubernetes client.
|
|
||||||
SetListers(*StoreLister)
|
|
||||||
// BackendDefaults returns the minimum settings required to configure the
|
|
||||||
// communication to endpoints
|
|
||||||
BackendDefaults() defaults.Backend
|
|
||||||
// Info returns information about the ingress controller
|
|
||||||
Info() *BackendInfo
|
|
||||||
// ConfigureFlags allow to configure more flags before the parsing of
|
|
||||||
// command line arguments
|
|
||||||
ConfigureFlags(*pflag.FlagSet)
|
|
||||||
// OverrideFlags allow the customization of the flags in the backend
|
|
||||||
OverrideFlags(*pflag.FlagSet)
|
|
||||||
// DefaultIngressClass just return the default ingress class
|
|
||||||
DefaultIngressClass() string
|
|
||||||
// UpdateIngressStatus custom callback used to update the status in an Ingress rule
|
|
||||||
// This allows custom implementations
|
|
||||||
// If the function returns nil the standard functions will be executed.
|
|
||||||
UpdateIngressStatus(*extensions.Ingress) []apiv1.LoadBalancerIngress
|
|
||||||
// DefaultEndpoint returns the Endpoint to use as default when the
|
|
||||||
// referenced service does not exists. This should return the content
|
|
||||||
// of to the default backend
|
|
||||||
DefaultEndpoint() Endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreLister returns the configured stores for ingresses, services,
|
// StoreLister returns the configured stores for ingresses, services,
|
||||||
// endpoints, secrets and configmaps.
|
// endpoints, secrets and configmaps.
|
||||||
type StoreLister struct {
|
type StoreLister struct {
|
||||||
|
@ -117,29 +55,6 @@ type StoreLister struct {
|
||||||
ConfigMap store.ConfigMapLister
|
ConfigMap store.ConfigMapLister
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackendInfo returns information about the backend.
|
|
||||||
// This fields contains information that helps to track issues or to
|
|
||||||
// map the running ingress controller to source code
|
|
||||||
type BackendInfo struct {
|
|
||||||
// Name returns the name of the backend implementation
|
|
||||||
Name string `json:"name"`
|
|
||||||
// Release returns the running version (semver)
|
|
||||||
Release string `json:"release"`
|
|
||||||
// Build returns information about the git commit
|
|
||||||
Build string `json:"build"`
|
|
||||||
// Repository return information about the git repository
|
|
||||||
Repository string `json:"repository"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bi BackendInfo) String() string {
|
|
||||||
return fmt.Sprintf(`
|
|
||||||
Name: %v
|
|
||||||
Release: %v
|
|
||||||
Build: %v
|
|
||||||
Repository: %v
|
|
||||||
`, bi.Name, bi.Release, bi.Build, bi.Repository)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configuration holds the definition of all the parts required to describe all
|
// Configuration holds the definition of all the parts required to describe all
|
||||||
// ingresses reachable by the ingress controller (using a filter by namespace)
|
// ingresses reachable by the ingress controller (using a filter by namespace)
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
|
@ -261,9 +176,6 @@ type Server struct {
|
||||||
// Location describes an URI inside a server.
|
// Location describes an URI inside a server.
|
||||||
// Also contains additional information about annotations in the Ingress.
|
// Also contains additional information about annotations in the Ingress.
|
||||||
//
|
//
|
||||||
// Important:
|
|
||||||
// The implementation of annotations is optional
|
|
||||||
//
|
|
||||||
// In some cases when more than one annotations is defined a particular order in the execution
|
// In some cases when more than one annotations is defined a particular order in the execution
|
||||||
// is required.
|
// is required.
|
||||||
// The chain in the execution order of annotations should be:
|
// The chain in the execution order of annotations should be:
|
||||||
|
|
|
@ -16,30 +16,6 @@ limitations under the License.
|
||||||
|
|
||||||
package ingress
|
package ingress
|
||||||
|
|
||||||
// Equal tests for equality between two BackendInfo types
|
|
||||||
func (bi1 *BackendInfo) Equal(bi2 *BackendInfo) bool {
|
|
||||||
if bi1 == bi2 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if bi1 == nil || bi2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bi1.Name != bi2.Name {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bi1.Release != bi2.Release {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bi1.Build != bi2.Build {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if bi1.Repository != bi2.Repository {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equal tests for equality between two Configuration types
|
// Equal tests for equality between two Configuration types
|
||||||
func (c1 *Configuration) Equal(c2 *Configuration) bool {
|
func (c1 *Configuration) Equal(c2 *Configuration) bool {
|
||||||
if c1 == c2 {
|
if c1 == c2 {
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
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 k8s
|
|
||||||
|
|
||||||
import (
|
|
||||||
api "k8s.io/api/core/v1"
|
|
||||||
core "k8s.io/api/core/v1"
|
|
||||||
extensions "k8s.io/api/extensions/v1beta1"
|
|
||||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
)
|
|
||||||
|
|
||||||
func EnsureSecret(cl kubernetes.Interface, secret *api.Secret) (*api.Secret, error) {
|
|
||||||
s, err := cl.CoreV1().Secrets(secret.Namespace).Create(secret)
|
|
||||||
if err != nil {
|
|
||||||
if k8sErrors.IsAlreadyExists(err) {
|
|
||||||
return cl.CoreV1().Secrets(secret.Namespace).Update(secret)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnsureIngress(cl kubernetes.Interface, ingress *extensions.Ingress) (*extensions.Ingress, error) {
|
|
||||||
s, err := cl.ExtensionsV1beta1().Ingresses(ingress.Namespace).Update(ingress)
|
|
||||||
if err != nil {
|
|
||||||
if k8sErrors.IsNotFound(err) {
|
|
||||||
return cl.ExtensionsV1beta1().Ingresses(ingress.Namespace).Create(ingress)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnsureService(cl kubernetes.Interface, service *core.Service) (*core.Service, error) {
|
|
||||||
s, err := cl.CoreV1().Services(service.Namespace).Update(service)
|
|
||||||
if err != nil {
|
|
||||||
if k8sErrors.IsNotFound(err) {
|
|
||||||
return cl.CoreV1().Services(service.Namespace).Create(service)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
|
@ -16,9 +16,29 @@ limitations under the License.
|
||||||
|
|
||||||
package net
|
package net
|
||||||
|
|
||||||
import _net "net"
|
import (
|
||||||
|
"fmt"
|
||||||
|
_net "net"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
// IsIPV6 checks if the input contains a valid IPV6 address
|
// IsIPV6 checks if the input contains a valid IPV6 address
|
||||||
func IsIPV6(ip _net.IP) bool {
|
func IsIPV6(ip _net.IP) bool {
|
||||||
return ip.To4() == nil
|
return ip.To4() == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsPortAvailable checks if a TCP port is available or not
|
||||||
|
func IsPortAvailable(p int) bool {
|
||||||
|
ln, err := _net.Listen("tcp", fmt.Sprintf(":%v", p))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ln.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsIPv6Enabled checks if IPV6 is enabled or not
|
||||||
|
func IsIPv6Enabled() bool {
|
||||||
|
cmd := exec.Command("test", "-f", "/proc/net/if_inet6")
|
||||||
|
return cmd.Run() == nil
|
||||||
|
}
|
||||||
|
|
|
@ -41,3 +41,20 @@ func TestIsIPV6(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsPortAvailable(t *testing.T) {
|
||||||
|
if !IsPortAvailable(0) {
|
||||||
|
t.Fatal("expected port 0 to be avilable (random port) but returned false")
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
p := ln.Addr().(*net.TCPAddr).Port
|
||||||
|
if IsPortAvailable(p) {
|
||||||
|
t.Fatalf("expected port %v to not be available", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// RELEASE returns the release version
|
// RELEASE returns the release version
|
||||||
RELEASE = "UNKNOWN"
|
RELEASE = "UNKNOWN"
|
||||||
|
@ -24,3 +26,14 @@ var (
|
||||||
// COMMIT returns the short sha from git
|
// COMMIT returns the short sha from git
|
||||||
COMMIT = "UNKNOWN"
|
COMMIT = "UNKNOWN"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String returns information about the release.
|
||||||
|
func String() string {
|
||||||
|
return fmt.Sprintf(`-------------------------------------------------------------------------------
|
||||||
|
NGINX Ingress controller
|
||||||
|
Release: %v
|
||||||
|
Build: %v
|
||||||
|
Repository: %v
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
`, RELEASE, COMMIT, REPO)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue