2016-11-10 22:56:29 +00:00
/ *
Copyright 2015 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 (
2020-03-24 13:44:13 +00:00
"context"
2017-11-05 01:18:28 +00:00
"fmt"
2020-12-04 12:40:42 +00:00
"math/rand" // #nosec
2017-11-05 01:18:28 +00:00
"net/http"
"net/http/pprof"
2016-11-10 22:56:29 +00:00
"os"
"os/signal"
2020-06-11 15:13:10 +00:00
"path/filepath"
"runtime"
2016-11-10 22:56:29 +00:00
"syscall"
"time"
2018-07-07 17:46:18 +00:00
"github.com/prometheus/client_golang/prometheus"
2017-11-05 01:18:28 +00:00
"github.com/prometheus/client_golang/prometheus/promhttp"
2018-12-07 07:42:52 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2017-11-05 01:18:28 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-02-17 20:25:04 +00:00
"k8s.io/apimachinery/pkg/util/wait"
discovery "k8s.io/apimachinery/pkg/version"
2017-11-05 01:18:28 +00:00
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/client-go/kubernetes"
2019-12-05 22:12:54 +00:00
"k8s.io/client-go/rest"
2017-11-05 01:18:28 +00:00
"k8s.io/client-go/tools/clientcmd"
2019-12-05 22:12:54 +00:00
certutil "k8s.io/client-go/util/cert"
2020-08-08 23:31:02 +00:00
"k8s.io/klog/v2"
2017-11-05 01:18:28 +00:00
2019-08-16 00:25:35 +00:00
"k8s.io/ingress-nginx/internal/file"
2020-04-20 21:38:50 +00:00
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/ingress/controller"
2018-07-07 17:46:18 +00:00
"k8s.io/ingress-nginx/internal/ingress/metric"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/k8s"
"k8s.io/ingress-nginx/internal/net/ssl"
2019-09-28 20:30:57 +00:00
"k8s.io/ingress-nginx/internal/nginx"
2017-11-05 01:18:28 +00:00
"k8s.io/ingress-nginx/version"
2016-11-10 22:56:29 +00:00
)
func main ( ) {
2018-12-05 16:27:55 +00:00
klog . InitFlags ( nil )
2018-01-17 12:26:32 +00:00
rand . Seed ( time . Now ( ) . UnixNano ( ) )
2017-11-05 01:18:28 +00:00
fmt . Println ( version . String ( ) )
showVersion , conf , err := parseFlags ( )
if showVersion {
os . Exit ( 0 )
}
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatal ( err )
2017-11-05 01:18:28 +00:00
}
2019-08-16 00:25:35 +00:00
err = file . CreateRequiredDirectories ( )
if err != nil {
klog . Fatal ( err )
}
2019-12-05 22:12:54 +00:00
kubeClient , err := createApiserverClient ( conf . APIServerHost , conf . RootCAFile , conf . KubeConfigFile )
2017-11-05 01:18:28 +00:00
if err != nil {
handleFatalInitError ( err )
}
2018-09-25 03:33:13 +00:00
if len ( conf . DefaultService ) > 0 {
2020-05-14 00:38:00 +00:00
err := checkService ( conf . DefaultService , kubeClient )
2018-09-25 03:33:13 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatal ( err )
2018-09-25 03:33:13 +00:00
}
2017-11-05 01:18:28 +00:00
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Valid default backend" , "service" , conf . DefaultService )
2020-05-14 00:38:00 +00:00
}
2020-04-20 21:38:50 +00:00
2020-05-14 00:38:00 +00:00
if len ( conf . PublishService ) > 0 {
err := checkService ( conf . PublishService , kubeClient )
if err != nil {
klog . Fatal ( err )
2017-11-05 01:18:28 +00:00
}
}
if conf . Namespace != "" {
2020-03-24 13:44:13 +00:00
_ , err = kubeClient . CoreV1 ( ) . Namespaces ( ) . Get ( context . TODO ( ) , conf . Namespace , metav1 . GetOptions { } )
2017-11-05 01:18:28 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "No namespace with name %v found: %v" , conf . Namespace , err )
2017-11-05 01:18:28 +00:00
}
}
2019-08-13 21:14:55 +00:00
conf . FakeCertificate = ssl . GetFakeSSLCert ( )
2020-09-27 20:32:40 +00:00
klog . InfoS ( "SSL fake certificate created" , "file" , conf . FakeCertificate . PemFileName )
2017-11-05 01:18:28 +00:00
2020-10-23 15:58:10 +00:00
var isNetworkingIngressAvailable bool
isNetworkingIngressAvailable , k8s . IsIngressV1Beta1Ready , _ = k8s . NetworkingIngressAvailable ( kubeClient )
if ! isNetworkingIngressAvailable {
2020-09-02 13:08:53 +00:00
klog . Fatalf ( "ingress-nginx requires Kubernetes v1.14.0 or higher" )
2019-06-09 22:49:59 +00:00
}
2020-10-23 15:58:10 +00:00
if k8s . IsIngressV1Beta1Ready {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Enabling new Ingress features available since Kubernetes v1.18" )
2020-04-20 21:38:50 +00:00
k8s . IngressClass , err = kubeClient . NetworkingV1beta1 ( ) . IngressClasses ( ) .
Get ( context . TODO ( ) , class . IngressClass , metav1 . GetOptions { } )
if err != nil {
if ! errors . IsNotFound ( err ) {
if ! errors . IsUnauthorized ( err ) && ! errors . IsForbidden ( err ) {
klog . Fatalf ( "Error searching IngressClass: %v" , err )
}
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Searching IngressClass" , "class" , class . IngressClass )
2020-04-20 21:38:50 +00:00
}
klog . Warningf ( "No IngressClass resource with name %v found. Only annotation will be used." , class . IngressClass )
// TODO: remove once this is fixed in client-go
k8s . IngressClass = nil
}
if k8s . IngressClass != nil && k8s . IngressClass . Spec . Controller != k8s . IngressNGINXController {
2020-11-02 22:30:51 +00:00
klog . Errorf ( ` Invalid IngressClass (Spec.Controller) value "%v". Should be "%v" ` , k8s . IngressClass . Spec . Controller , k8s . IngressNGINXController )
2020-04-20 21:38:50 +00:00
klog . Fatalf ( "IngressClass with name %v is not valid for ingress-nginx (invalid Spec.Controller)" , class . IngressClass )
}
2020-03-31 14:14:03 +00:00
}
2017-11-05 01:18:28 +00:00
conf . Client = kubeClient
2020-09-26 23:27:19 +00:00
err = k8s . GetIngressPod ( kubeClient )
if err != nil {
klog . Fatalf ( "Unexpected error obtaining ingress-nginx pod: %v" , err )
}
2018-07-07 17:46:18 +00:00
reg := prometheus . NewRegistry ( )
2018-07-12 17:18:43 +00:00
reg . MustRegister ( prometheus . NewGoCollector ( ) )
2018-09-22 17:54:11 +00:00
reg . MustRegister ( prometheus . NewProcessCollector ( prometheus . ProcessCollectorOpts {
PidFn : func ( ) ( int , error ) { return os . Getpid ( ) , nil } ,
ReportErrors : true ,
} ) )
2018-07-12 17:18:43 +00:00
2018-12-04 19:59:54 +00:00
mc := metric . NewDummyCollector ( )
if conf . EnableMetrics {
2019-01-21 14:29:36 +00:00
mc , err = metric . NewCollector ( conf . MetricsPerHost , reg )
2018-12-04 19:59:54 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Error creating prometheus collector: %v" , err )
2018-12-04 19:59:54 +00:00
}
2018-07-07 17:46:18 +00:00
}
mc . Start ( )
2017-11-05 01:18:28 +00:00
2018-07-12 17:18:43 +00:00
if conf . EnableProfiling {
2019-09-01 18:21:24 +00:00
go registerProfiler ( )
2018-07-12 17:18:43 +00:00
}
2020-01-25 17:52:31 +00:00
ngx := controller . NewNGINXController ( conf , mc )
mux := http . NewServeMux ( )
2019-09-29 17:37:57 +00:00
registerHealthz ( nginx . HealthPath , ngx , mux )
2018-07-12 17:18:43 +00:00
registerMetrics ( reg , mux )
go startHTTPServer ( conf . ListenPorts . Health , mux )
2020-01-25 17:52:31 +00:00
go ngx . Start ( )
2018-06-14 00:55:07 +00:00
2020-01-25 17:52:31 +00:00
handleSigterm ( ngx , func ( code int ) {
os . Exit ( code )
} )
2016-11-10 22:56:29 +00:00
}
2017-11-22 13:35:47 +00:00
type exiter func ( code int )
func handleSigterm ( ngx * controller . NGINXController , exit exiter ) {
2016-11-10 22:56:29 +00:00
signalChan := make ( chan os . Signal , 1 )
signal . Notify ( signalChan , syscall . SIGTERM )
<- signalChan
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Received SIGTERM, shutting down" )
2016-11-10 22:56:29 +00:00
exitCode := 0
2017-08-28 16:06:58 +00:00
if err := ngx . Stop ( ) ; err != nil {
2020-09-27 20:32:40 +00:00
klog . Warningf ( "Error during shutdown: %v" , err )
2016-11-10 22:56:29 +00:00
exitCode = 1
}
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Handled quit, awaiting Pod deletion" )
2017-11-05 01:18:28 +00:00
time . Sleep ( 10 * time . Second )
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Exiting" , "code" , exitCode )
2017-11-22 13:35:47 +00:00
exit ( exitCode )
2016-11-10 22:56:29 +00:00
}
2017-11-05 01:18:28 +00:00
2018-06-11 09:17:50 +00:00
// createApiserverClient creates a new Kubernetes REST client. apiserverHost is
// the URL of the API server in the format protocol://address:port/pathPrefix,
// kubeConfig is the location of a kubeconfig file. If defined, the kubeconfig
// file is loaded first, the URL of the API server read from the file is then
2018-07-09 21:47:48 +00:00
// optionally overridden by the value of apiserverHost.
2018-09-21 08:19:16 +00:00
// If neither apiserverHost nor kubeConfig is passed in, we assume the
2018-06-11 09:17:50 +00:00
// controller runs inside Kubernetes and fallback to the in-cluster config. If
// the in-cluster config is missing or fails, we fallback to the default config.
2019-12-05 22:12:54 +00:00
func createApiserverClient ( apiserverHost , rootCAFile , kubeConfig string ) ( * kubernetes . Clientset , error ) {
2018-03-01 13:27:53 +00:00
cfg , err := clientcmd . BuildConfigFromFlags ( apiserverHost , kubeConfig )
2017-11-05 01:18:28 +00:00
if err != nil {
return nil , err
}
2020-10-23 15:58:10 +00:00
// TODO: remove after k8s v1.22
cfg . WarningHandler = rest . NoWarnings { }
2020-06-11 15:13:10 +00:00
// Configure the User-Agent used for the HTTP requests made to the API server.
cfg . UserAgent = fmt . Sprintf (
"%s/%s (%s/%s) ingress-nginx/%s" ,
filepath . Base ( os . Args [ 0 ] ) ,
version . RELEASE ,
runtime . GOOS ,
runtime . GOARCH ,
version . COMMIT ,
)
2019-12-05 22:12:54 +00:00
if apiserverHost != "" && rootCAFile != "" {
tlsClientConfig := rest . TLSClientConfig { }
if _ , err := certutil . NewPool ( rootCAFile ) ; err != nil {
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Loading CA config" , "file" , rootCAFile )
2019-12-05 22:12:54 +00:00
} else {
tlsClientConfig . CAFile = rootCAFile
}
cfg . TLSClientConfig = tlsClientConfig
}
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Creating API client" , "host" , cfg . Host )
2017-11-05 01:18:28 +00:00
client , err := kubernetes . NewForConfig ( cfg )
if err != nil {
return nil , err
}
2018-02-17 20:25:04 +00:00
var v * discovery . Info
2018-06-11 09:17:50 +00:00
// The client may fail to connect to the API server in the first request.
2018-02-17 20:25:04 +00:00
// https://github.com/kubernetes/ingress-nginx/issues/1968
defaultRetry := wait . Backoff {
Steps : 10 ,
Duration : 1 * time . Second ,
Factor : 1.5 ,
Jitter : 0.1 ,
}
var lastErr error
retries := 0
2020-09-27 20:32:40 +00:00
klog . V ( 2 ) . InfoS ( "Trying to discover Kubernetes version" )
2018-02-17 20:25:04 +00:00
err = wait . ExponentialBackoff ( defaultRetry , func ( ) ( bool , error ) {
v , err = client . Discovery ( ) . ServerVersion ( )
if err == nil {
return true , nil
}
lastErr = err
2020-09-27 20:32:40 +00:00
klog . V ( 2 ) . ErrorS ( err , "Unexpected error discovering Kubernetes version" , "attempt" , retries )
2018-02-17 20:25:04 +00:00
retries ++
return false , nil
} )
2018-06-11 09:17:50 +00:00
// err is returned in case of timeout in the exponential backoff (ErrWaitTimeout)
2017-11-05 01:18:28 +00:00
if err != nil {
2018-02-17 20:25:04 +00:00
return nil , lastErr
}
// this should not happen, warn the user
if retries > 0 {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Initial connection to the Kubernetes API server was retried %d times." , retries )
2017-11-05 01:18:28 +00:00
}
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Running in Kubernetes cluster" ,
"major" , v . Major ,
"minor" , v . Minor ,
"git" , v . GitVersion ,
"state" , v . GitTreeState ,
"commit" , v . GitCommit ,
"platform" , v . Platform ,
)
2017-11-05 01:18:28 +00:00
return client , nil
}
2018-06-11 09:17:50 +00:00
// Handler for fatal init errors. Prints a verbose error message and exits.
2017-11-05 01:18:28 +00:00
func handleFatalInitError ( err error ) {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Error while initiating a connection to the Kubernetes API server. " +
2018-06-11 09:17:50 +00:00
"This could mean the cluster is misconfigured (e.g. it has invalid API server certificates " +
"or Service Accounts configuration). Reason: %s\n" +
2017-11-05 01:18:28 +00:00
"Refer to the troubleshooting guide for more information: " +
2018-06-11 09:17:50 +00:00
"https://kubernetes.github.io/ingress-nginx/troubleshooting/" ,
err )
2017-11-05 01:18:28 +00:00
}
2019-09-29 17:37:57 +00:00
func registerHealthz ( healthPath string , ic * controller . NGINXController , mux * http . ServeMux ) {
2018-07-12 17:18:43 +00:00
// expose health check endpoint (/healthz)
2019-09-29 17:37:57 +00:00
healthz . InstallPathHandler ( mux ,
healthPath ,
2018-07-12 17:18:43 +00:00
healthz . PingHealthz ,
ic ,
)
}
func registerMetrics ( reg * prometheus . Registry , mux * http . ServeMux ) {
mux . Handle (
"/metrics" ,
promhttp . InstrumentMetricHandler (
reg ,
promhttp . HandlerFor ( reg , promhttp . HandlerOpts { } ) ,
) ,
)
}
2019-09-01 18:21:24 +00:00
func registerProfiler ( ) {
mux := http . NewServeMux ( )
2018-07-12 17:18:43 +00:00
mux . HandleFunc ( "/debug/pprof/" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/heap" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/mutex" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/goroutine" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/threadcreate" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/block" , pprof . Index )
mux . HandleFunc ( "/debug/pprof/cmdline" , pprof . Cmdline )
mux . HandleFunc ( "/debug/pprof/profile" , pprof . Profile )
mux . HandleFunc ( "/debug/pprof/symbol" , pprof . Symbol )
mux . HandleFunc ( "/debug/pprof/trace" , pprof . Trace )
2019-09-01 18:21:24 +00:00
server := & http . Server {
2019-09-28 20:30:57 +00:00
Addr : fmt . Sprintf ( "127.0.0.1:%v" , nginx . ProfilerPort ) ,
2019-09-01 18:21:24 +00:00
Handler : mux ,
}
klog . Fatal ( server . ListenAndServe ( ) )
2018-07-12 17:18:43 +00:00
}
2017-11-05 01:18:28 +00:00
2018-07-12 17:18:43 +00:00
func startHTTPServer ( port int , mux * http . ServeMux ) {
2017-11-05 01:18:28 +00:00
server := & http . Server {
2018-01-18 20:55:56 +00:00
Addr : fmt . Sprintf ( ":%v" , port ) ,
Handler : mux ,
ReadTimeout : 10 * time . Second ,
ReadHeaderTimeout : 10 * time . Second ,
WriteTimeout : 300 * time . Second ,
IdleTimeout : 120 * time . Second ,
2017-11-05 01:18:28 +00:00
}
2018-12-05 16:27:55 +00:00
klog . Fatal ( server . ListenAndServe ( ) )
2017-11-05 01:18:28 +00:00
}
2020-05-14 00:38:00 +00:00
func checkService ( key string , kubeClient * kubernetes . Clientset ) error {
ns , name , err := k8s . ParseNameNS ( key )
if err != nil {
return err
}
_ , err = kubeClient . CoreV1 ( ) . Services ( ns ) . Get ( context . TODO ( ) , name , metav1 . GetOptions { } )
if err != nil {
if errors . IsUnauthorized ( err ) || errors . IsForbidden ( err ) {
2020-09-27 20:32:40 +00:00
return fmt . Errorf ( "✖ the cluster seems to be running with a restrictive Authorization mode and the Ingress controller does not have the required permissions to operate normally" )
2020-05-14 00:38:00 +00:00
}
if errors . IsNotFound ( err ) {
2020-08-11 12:06:14 +00:00
return fmt . Errorf ( "No service with name %v found in namespace %v: %v" , name , ns , err )
2020-05-14 00:38:00 +00:00
}
2020-08-11 12:06:14 +00:00
return fmt . Errorf ( "Unexpected error searching service with name %v in namespace %v: %v" , name , ns , err )
2020-05-14 00:38:00 +00:00
}
return nil
}