2016-11-10 22:56:29 +00:00
package controller
import (
2016-11-16 18:24:26 +00:00
"encoding/json"
2016-11-10 22:56:29 +00:00
"flag"
"fmt"
"net/http"
"net/http/pprof"
"os"
2017-08-27 21:39:51 +00:00
"strings"
2016-11-10 22:56:29 +00:00
"syscall"
2017-08-04 18:22:06 +00:00
"time"
2016-11-10 22:56:29 +00:00
"github.com/golang/glog"
2016-11-29 01:39:17 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2016-11-10 22:56:29 +00:00
"github.com/spf13/pflag"
2017-09-17 18:42:31 +00:00
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-04-01 14:39:42 +00:00
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
2017-09-17 18:42:31 +00:00
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2016-11-16 18:24:26 +00:00
2017-10-06 20:33:32 +00:00
"k8s.io/ingress-nginx/pkg/ingress"
"k8s.io/ingress-nginx/pkg/k8s"
2016-11-10 22:56:29 +00:00
)
2016-11-11 23:43:35 +00:00
// NewIngressController returns a configured Ingress controller
func NewIngressController ( backend ingress . Controller ) * GenericController {
2016-11-10 22:56:29 +00:00
var (
flags = pflag . NewFlagSet ( "" , pflag . ExitOnError )
2017-01-19 05:05:46 +00:00
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." )
2016-11-10 22:56:29 +00:00
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 . ` )
2016-11-11 23:43:35 +00:00
ingressClass = flags . String ( "ingress-class" , "" ,
2016-11-10 22:56:29 +00:00
` 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
2017-09-01 22:26:12 +00:00
service with the format namespace / serviceName and the port of the service could be a
2016-11-10 22:56:29 +00:00
number of the name of the port .
2016-11-11 23:43:35 +00:00
The ports 80 and 443 are not allowed as external ports . This ports are reserved for the backend ` )
2016-11-10 22:56:29 +00:00
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
2017-09-01 22:26:12 +00:00
service with the format namespace / serviceName and the port of the service could be a
2016-11-10 22:56:29 +00:00
number of the name of the port . ` )
2017-08-04 18:22:06 +00:00
resyncPeriod = flags . Duration ( "sync-period" , 600 * time . Second ,
` Relist and confirm cloud resources this often. Default is 10 minutes ` )
2016-11-10 22:56:29 +00:00
2017-09-17 18:42:31 +00:00
watchNamespace = flags . String ( "watch-namespace" , apiv1 . NamespaceAll ,
2016-11-10 22:56:29 +00:00
` 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/ ` )
2017-09-01 22:26:12 +00:00
defSSLCertificate = flags . String ( "default-ssl-certificate" , "" , ` Name of the secret
2016-11-10 22:56:29 +00:00
that contains a SSL certificate to be used as default for a HTTPS catch - all server ` )
2017-09-01 22:26:12 +00:00
defHealthzURL = flags . String ( "health-check-path" , "/healthz" , ` Defines
2016-11-10 22:56:29 +00:00
the URL to be used as health check inside in the default server in NGINX . ` )
2017-01-20 22:01:37 +00:00
2017-09-01 22:26:12 +00:00
updateStatus = flags . Bool ( "update-status" , true , ` Indicates if the
2017-01-20 22:01:37 +00:00
ingress controller should update the Ingress status IP / hostname . Default is true ` )
2017-02-27 07:35:04 +00:00
electionID = flags . String ( "election-id" , "ingress-controller-leader" , ` Election id to use for status update. ` )
2017-04-13 01:50:54 +00:00
forceIsolation = flags . Bool ( "force-namespace-isolation" , false ,
2017-09-01 22:26:12 +00:00
` Force namespace isolation . This flag is required to avoid the reference of secrets or
2017-04-13 01:50:54 +00:00
configmaps located in a different namespace than the specified in the flag -- watch - namespace . ` )
2017-06-20 01:22:08 +00:00
2017-09-01 22:26:12 +00:00
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
2017-06-20 01:22:08 +00:00
is being stopped . Default is true ` )
2017-07-13 00:35:36 +00:00
2017-08-24 13:33:26 +00:00
sortBackends = flags . Bool ( "sort-backends" , false ,
2017-07-13 00:35:36 +00:00
` Defines if backends and it's endpoints should be sorted ` )
2017-10-08 17:29:19 +00:00
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 ` )
2017-10-25 04:06:48 +00:00
showVersion = flags . Bool ( "version" , false ,
` Shows release information about the NGINX Ingress controller ` )
2016-11-10 22:56:29 +00:00
)
flags . AddGoFlagSet ( flag . CommandLine )
2017-05-25 00:03:52 +00:00
backend . ConfigureFlags ( flags )
2016-11-10 22:56:29 +00:00
flags . Parse ( os . Args )
2017-10-17 15:24:03 +00:00
// Workaround for this issue:
// https://github.com/kubernetes/kubernetes/issues/17162
flag . CommandLine . Parse ( [ ] string { } )
2017-10-25 04:06:48 +00:00
if * showVersion {
fmt . Println ( backend . Info ( ) . String ( ) )
os . Exit ( 0 )
}
2017-04-04 11:15:06 +00:00
backend . OverrideFlags ( flags )
2016-11-10 22:56:29 +00:00
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" )
}
2017-01-19 05:05:46 +00:00
kubeClient , err := createApiserverClient ( * apiserverHost , * kubeConfigFile )
2016-11-10 22:56:29 +00:00
if err != nil {
2017-01-19 05:05:46 +00:00
handleFatalInitError ( err )
2016-11-10 22:56:29 +00:00
}
2017-09-17 16:34:29 +00:00
ns , name , err := k8s . ParseNameNS ( * defaultSvc )
if err != nil {
glog . Fatalf ( "invalid format for service %v: %v" , * defaultSvc , err )
}
2017-09-17 18:42:31 +00:00
_ , err = kubeClient . Core ( ) . Services ( ns ) . Get ( name , metav1 . GetOptions { } )
2016-11-10 22:56:29 +00:00
if err != nil {
2017-08-27 21:39:51 +00:00
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" )
}
2016-11-10 22:56:29 +00:00
glog . Fatalf ( "no service with name %v found: %v" , * defaultSvc , err )
}
glog . Infof ( "validated %v as the default backend" , * defaultSvc )
if * publishSvc != "" {
2017-09-17 16:34:29 +00:00
ns , name , err := k8s . ParseNameNS ( * publishSvc )
if err != nil {
glog . Fatalf ( "invalid service format: %v" , err )
}
2017-09-17 18:42:31 +00:00
svc , err := kubeClient . CoreV1 ( ) . Services ( ns ) . Get ( name , metav1 . GetOptions { } )
2016-11-10 22:56:29 +00:00
if err != nil {
2017-09-17 16:34:29 +00:00
glog . Fatalf ( "unexpected error getting information about service %v: %v" , * publishSvc , err )
2016-11-10 22:56:29 +00:00
}
if len ( svc . Status . LoadBalancer . Ingress ) == 0 {
2017-05-09 14:23:40 +00:00
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 )
2016-11-10 22:56:29 +00:00
}
}
2017-08-27 21:39:51 +00:00
2017-03-13 20:50:27 +00:00
if * watchNamespace != "" {
2017-09-17 18:42:31 +00:00
_ , err = kubeClient . CoreV1 ( ) . Namespaces ( ) . Get ( * watchNamespace , metav1 . GetOptions { } )
2016-11-10 22:56:29 +00:00
if err != nil {
2017-03-13 20:50:27 +00:00
glog . Fatalf ( "no watchNamespace with name %v found: %v" , * watchNamespace , err )
2016-11-10 22:56:29 +00:00
}
}
2017-08-04 18:22:06 +00:00
if resyncPeriod . Seconds ( ) < 10 {
glog . Fatalf ( "resync period (%vs) is too low" , resyncPeriod . Seconds ( ) )
}
2017-02-28 06:06:50 +00:00
err = os . MkdirAll ( ingress . DefaultSSLDirectory , 0655 )
if err != nil {
glog . Errorf ( "Failed to mkdir SSL directory: %v" , err )
}
2016-11-10 22:56:29 +00:00
config := & Configuration {
2017-04-13 01:50:54 +00:00
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 ,
2017-09-01 22:26:12 +00:00
DisableNodeList : * disableNodeList ,
2017-08-24 13:33:26 +00:00
UpdateStatusOnShutdown : * updateStatusOnShutdown ,
SortBackends : * sortBackends ,
2017-10-08 17:29:19 +00:00
UseNodeInternalIP : * useNodeInternalIP ,
2016-11-10 22:56:29 +00:00
}
ic := newIngressController ( config )
go registerHandlers ( * profiling , * healthzPort , ic )
return ic
}
2016-11-11 23:43:35 +00:00
func registerHandlers ( enableProfiling bool , port int , ic * GenericController ) {
2016-11-10 22:56:29 +00:00
mux := http . NewServeMux ( )
2016-11-27 00:09:59 +00:00
// expose health check endpoint (/healthz)
healthz . InstallHandler ( mux ,
healthz . PingHealthz ,
ic . cfg . Backend ,
)
2016-11-10 22:56:29 +00:00
2016-11-29 01:39:17 +00:00
mux . Handle ( "/metrics" , promhttp . Handler ( ) )
2016-11-10 22:56:29 +00:00
mux . HandleFunc ( "/build" , func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
2016-11-16 18:24:26 +00:00
b , _ := json . Marshal ( ic . Info ( ) )
w . Write ( b )
2016-11-10 22:56:29 +00:00
} )
mux . HandleFunc ( "/stop" , func ( w http . ResponseWriter , r * http . Request ) {
2017-04-13 08:38:07 +00:00
err := syscall . Kill ( syscall . Getpid ( ) , syscall . SIGTERM )
if err != nil {
glog . Errorf ( "unexpected error: %v" , err )
}
2016-11-10 22:56:29 +00:00
} )
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 ( ) )
}
2017-01-19 05:05:46 +00:00
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
)
2017-04-01 14:39:42 +00:00
// 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 {
2017-09-17 18:42:31 +00:00
ClusterInfo : clientcmdapi . Cluster {
2017-04-01 14:39:42 +00:00
Server : masterURL ,
} ,
} ) . ClientConfig ( )
}
2017-01-19 05:05:46 +00:00
// 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
2017-04-01 14:39:42 +00:00
func createApiserverClient ( apiserverHost string , kubeConfig string ) ( * kubernetes . Clientset , error ) {
cfg , err := buildConfigFromFlags ( apiserverHost , kubeConfig )
2017-01-19 05:05:46 +00:00
if err != nil {
return nil , err
}
cfg . QPS = defaultQPS
cfg . Burst = defaultBurst
cfg . ContentType = "application/vnd.kubernetes.protobuf"
2017-08-27 21:39:51 +00:00
glog . Infof ( "Creating API client for %s" , cfg . Host )
2017-01-19 05:05:46 +00:00
2017-04-01 14:39:42 +00:00
client , err := kubernetes . NewForConfig ( cfg )
2017-08-27 21:39:51 +00:00
if err != nil {
return nil , err
}
2017-01-19 05:05:46 +00:00
2017-08-27 21:39:51 +00:00
v , err := client . Discovery ( ) . ServerVersion ( )
2017-01-19 05:05:46 +00:00
if err != nil {
return nil , err
}
2017-08-27 21:39:51 +00:00
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 )
2017-01-19 05:05:46 +00:00
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: " +
2017-10-13 13:55:03 +00:00
"https://github.com/kubernetes/ingress-nginx/blob/master/docs/troubleshooting.md" , err )
2017-01-19 05:05:46 +00:00
}