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 .
* /
2017-10-06 20:33:32 +00:00
package controller
2016-11-10 22:56:29 +00:00
import (
"bytes"
2018-03-18 13:13:41 +00:00
"encoding/json"
2016-11-29 01:39:17 +00:00
"errors"
2016-11-10 22:56:29 +00:00
"fmt"
"io/ioutil"
2016-11-29 01:39:17 +00:00
"net"
2018-03-18 13:13:41 +00:00
"net/http"
2016-11-10 22:56:29 +00:00
"os"
"os/exec"
2018-04-24 18:02:52 +00:00
"path/filepath"
2019-08-15 18:57:51 +00:00
"reflect"
2017-03-28 16:39:44 +00:00
"strconv"
2017-04-09 23:51:38 +00:00
"strings"
2017-11-05 01:18:28 +00:00
"sync"
2016-11-29 01:39:17 +00:00
"syscall"
2018-06-21 22:15:18 +00:00
"text/template"
2016-11-29 01:39:17 +00:00
"time"
2016-11-10 22:56:29 +00:00
2018-01-18 19:14:42 +00:00
proxyproto "github.com/armon/go-proxyproto"
2018-02-14 01:46:18 +00:00
"github.com/eapache/channels"
2017-09-17 18:42:31 +00:00
apiv1 "k8s.io/api/core/v1"
2018-11-16 16:48:47 +00:00
"k8s.io/apimachinery/pkg/util/intstr"
2019-01-09 03:33:16 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2017-11-05 01:18:28 +00:00
"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"
2020-08-08 23:31:02 +00:00
"k8s.io/klog/v2"
2019-05-18 10:08:05 +00:00
2020-06-19 06:04:38 +00:00
adm_controller "k8s.io/ingress-nginx/internal/admission/controller"
2017-11-22 13:40:54 +00:00
"k8s.io/ingress-nginx/internal/file"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/ingress"
2019-03-11 15:57:28 +00:00
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
2017-11-07 22:02:12 +00:00
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
"k8s.io/ingress-nginx/internal/ingress/controller/process"
2018-01-18 19:14:42 +00:00
"k8s.io/ingress-nginx/internal/ingress/controller/store"
2017-11-07 22:02:12 +00:00
ngx_template "k8s.io/ingress-nginx/internal/ingress/controller/template"
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/ingress/status"
ing_net "k8s.io/ingress-nginx/internal/net"
"k8s.io/ingress-nginx/internal/net/dns"
"k8s.io/ingress-nginx/internal/net/ssl"
2019-01-21 14:29:36 +00:00
"k8s.io/ingress-nginx/internal/nginx"
2017-11-07 22:02:12 +00:00
"k8s.io/ingress-nginx/internal/task"
2017-11-22 13:40:54 +00:00
"k8s.io/ingress-nginx/internal/watch"
2016-11-29 01:39:17 +00:00
)
const (
2019-01-21 14:29:36 +00:00
tempNginxPattern = "nginx-cfg"
2019-09-24 13:53:22 +00:00
emptyUID = "-1"
2016-11-10 22:56:29 +00:00
)
2017-10-06 20:33:32 +00:00
// NewNGINXController creates a new NGINX Ingress controller.
2019-08-13 21:14:55 +00:00
func NewNGINXController ( config * Configuration , mc metric . Collector ) * NGINXController {
2017-11-05 01:18:28 +00:00
eventBroadcaster := record . NewBroadcaster ( )
2018-12-05 16:27:55 +00:00
eventBroadcaster . StartLogging ( klog . Infof )
2017-11-05 01:18:28 +00:00
eventBroadcaster . StartRecordingToSink ( & v1core . EventSinkImpl {
Interface : config . Client . CoreV1 ( ) . Events ( config . Namespace ) ,
} )
2017-04-11 14:47:49 +00:00
h , err := dns . GetSystemNameServers ( )
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading system nameservers: %v" , err )
2017-04-11 14:47:49 +00:00
}
2017-03-12 15:27:05 +00:00
n := & NGINXController {
2017-11-05 01:18:28 +00:00
isIPV6Enabled : ing_net . IsIPv6Enabled ( ) ,
2017-08-29 19:40:03 +00:00
resolver : h ,
2017-11-05 01:18:28 +00:00
cfg : config ,
2017-12-05 19:07:34 +00:00
syncRateLimiter : flowcontrol . NewTokenBucketRateLimiter ( config . SyncRateLimit , 1 ) ,
2017-11-05 01:18:28 +00:00
recorder : eventBroadcaster . NewRecorder ( scheme . Scheme , apiv1 . EventSource {
Component : "nginx-ingress-controller" ,
} ) ,
stopCh : make ( chan struct { } ) ,
2018-02-14 01:46:18 +00:00
updateCh : channels . NewRingChannel ( 1024 ) ,
2018-01-18 19:14:42 +00:00
2019-08-22 14:25:47 +00:00
ngxErrCh : make ( chan error ) ,
2017-11-05 01:18:28 +00:00
stopLock : & sync . Mutex { } ,
2017-11-06 22:34:30 +00:00
2018-06-13 18:15:45 +00:00
runningConfig : new ( ingress . Configuration ) ,
2018-01-23 20:10:02 +00:00
Proxy : & TCPProxy { } ,
2018-07-07 17:46:18 +00:00
metricCollector : mc ,
2019-02-21 19:45:21 +00:00
command : NewNginxCommand ( ) ,
}
if n . cfg . ValidationWebhook != "" {
n . validationWebhookServer = & http . Server {
Addr : config . ValidationWebhook ,
2020-06-19 06:04:38 +00:00
Handler : adm_controller . NewAdmissionControllerServer ( & adm_controller . IngressAdmission { Checker : n } ) ,
2019-02-21 19:45:21 +00:00
TLSConfig : ssl . NewTLSListener ( n . cfg . ValidationWebhookCertPath , n . cfg . ValidationWebhookKeyPath ) . TLSConfig ( ) ,
}
2017-04-09 23:51:38 +00:00
}
2018-01-25 13:46:20 +00:00
n . store = store . New (
2018-01-18 19:14:42 +00:00
config . Namespace ,
config . ConfigMapName ,
2018-11-16 16:48:47 +00:00
config . TCPConfigMapName ,
config . UDPConfigMapName ,
2018-01-25 13:46:20 +00:00
config . DefaultSSLCertificate ,
2018-01-18 19:14:42 +00:00
config . ResyncPeriod ,
config . Client ,
2018-06-04 21:48:30 +00:00
n . updateCh ,
2018-12-11 21:57:46 +00:00
config . DisableCatchAll )
2017-11-07 16:36:51 +00:00
2017-11-05 01:18:28 +00:00
n . syncQueue = task . NewTaskQueue ( n . syncIngress )
2019-03-08 00:20:34 +00:00
2017-11-05 01:18:28 +00:00
if config . UpdateStatus {
2020-09-26 23:27:19 +00:00
n . syncStatus = status . NewStatusSyncer ( status . Config {
2017-11-05 01:18:28 +00:00
Client : config . Client ,
2017-11-06 01:22:49 +00:00
PublishService : config . PublishService ,
2018-02-27 03:02:19 +00:00
PublishStatusAddress : config . PublishStatusAddress ,
2018-01-18 19:14:42 +00:00
IngressLister : n . store ,
2017-11-05 01:18:28 +00:00
UpdateStatusOnShutdown : config . UpdateStatusOnShutdown ,
2017-11-06 01:22:49 +00:00
UseNodeInternalIP : config . UseNodeInternalIP ,
2017-11-05 01:18:28 +00:00
} )
} else {
2018-12-05 16:27:55 +00:00
klog . Warning ( "Update of Ingress status is disabled (flag --update-status)" )
2017-11-05 01:18:28 +00:00
}
2018-04-25 21:53:49 +00:00
onTemplateChange := func ( ) {
2019-08-13 21:14:55 +00:00
template , err := ngx_template . NewTemplate ( nginx . TemplatePath )
2016-11-10 22:56:29 +00:00
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Error loading new template" )
2016-11-10 22:56:29 +00:00
return
}
n . t = template
2020-09-27 20:32:40 +00:00
klog . InfoS ( "New NGINX configuration template loaded" )
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "template-change" ) )
2016-11-10 22:56:29 +00:00
}
2019-08-13 21:14:55 +00:00
ngxTpl , err := ngx_template . NewTemplate ( nginx . TemplatePath )
2016-11-10 22:56:29 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Invalid NGINX configuration template: %v" , err )
2016-11-10 22:56:29 +00:00
}
n . t = ngxTpl
2016-11-29 01:39:17 +00:00
2019-08-13 21:14:55 +00:00
_ , err = watch . NewFileWatcher ( nginx . TemplatePath , onTemplateChange )
2018-06-16 20:22:59 +00:00
if err != nil {
2019-08-13 21:14:55 +00:00
klog . Fatalf ( "Error creating file watcher for %v: %v" , nginx . TemplatePath , err )
2018-06-16 20:22:59 +00:00
}
2018-02-17 20:24:50 +00:00
2018-06-16 20:22:59 +00:00
filesToWatch := [ ] string { }
err = filepath . Walk ( "/etc/nginx/geoip/" , func ( path string , info os . FileInfo , err error ) error {
2018-02-17 20:24:50 +00:00
if err != nil {
2018-06-16 20:22:59 +00:00
return err
}
if info . IsDir ( ) {
return nil
2018-02-17 20:24:50 +00:00
}
2018-06-16 20:22:59 +00:00
filesToWatch = append ( filesToWatch , path )
return nil
} )
2018-02-20 16:27:02 +00:00
2018-06-16 20:22:59 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Error creating file watchers: %v" , err )
2018-06-16 20:22:59 +00:00
}
2018-02-17 20:24:50 +00:00
2018-06-16 20:22:59 +00:00
for _ , f := range filesToWatch {
_ , err = watch . NewFileWatcher ( f , func ( ) {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "File changed detected. Reloading NGINX" , "path" , f )
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "file-change" ) )
2018-02-17 20:24:50 +00:00
} )
2017-11-22 13:40:54 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "Error creating file watcher for %v: %v" , f , err )
2018-02-17 20:24:50 +00:00
}
2017-11-22 13:40:54 +00:00
}
2017-08-28 16:06:58 +00:00
return n
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// NGINXController describes a NGINX Ingress controller.
2016-11-10 22:56:29 +00:00
type NGINXController struct {
2017-11-05 01:18:28 +00:00
cfg * Configuration
recorder record . EventRecorder
syncQueue * task . Queue
2019-03-08 00:20:34 +00:00
syncStatus status . Syncer
2017-11-05 01:18:28 +00:00
syncRateLimiter flowcontrol . RateLimiter
2018-06-13 18:15:45 +00:00
// stopLock is used to enforce that only a single call to Stop send at
// a given time. We allow stopping through an HTTP endpoint and
2017-11-05 01:18:28 +00:00
// allowing concurrent stoppers leads to stack traces.
stopLock * sync . Mutex
2018-01-18 19:14:42 +00:00
stopCh chan struct { }
2018-02-14 01:46:18 +00:00
updateCh * channels . RingChannel
2017-11-05 01:18:28 +00:00
2018-06-13 18:15:45 +00:00
// ngxErrCh is used to detect errors with the NGINX processes
2017-11-05 01:18:28 +00:00
ngxErrCh chan error
// runningConfig contains the running configuration in the Backend
runningConfig * ingress . Configuration
2019-02-21 19:45:21 +00:00
t ngx_template . TemplateWriter
2016-11-10 22:56:29 +00:00
2017-04-09 23:51:38 +00:00
resolver [ ] net . IP
2017-03-10 13:01:26 +00:00
2017-04-09 18:03:27 +00:00
isIPV6Enabled bool
2017-04-11 14:47:49 +00:00
2017-08-28 16:06:58 +00:00
isShuttingDown bool
2017-11-05 01:18:28 +00:00
Proxy * TCPProxy
2017-08-26 03:46:17 +00:00
2018-01-18 19:14:42 +00:00
store store . Storer
2017-11-06 22:34:30 +00:00
2018-07-07 17:46:18 +00:00
metricCollector metric . Collector
2019-03-11 16:31:38 +00:00
2019-02-21 19:45:21 +00:00
validationWebhookServer * http . Server
command NginxExecTester
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// Start starts a new NGINX master process running in the foreground.
2017-03-12 15:27:05 +00:00
func ( n * NGINXController ) Start ( ) {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting NGINX Ingress controller" )
2017-11-05 01:18:28 +00:00
2018-01-18 19:14:42 +00:00
n . store . Run ( n . stopCh )
2017-11-13 01:43:28 +00:00
2019-03-11 15:57:28 +00:00
// we need to use the defined ingress class to allow multiple leaders
// in order to update information about ingress status
electionID := fmt . Sprintf ( "%v-%v" , n . cfg . ElectionID , class . DefaultClass )
if class . IngressClass != "" {
electionID = fmt . Sprintf ( "%v-%v" , n . cfg . ElectionID , class . IngressClass )
}
2019-03-08 00:20:34 +00:00
setupLeaderElection ( & leaderElectionConfig {
Client : n . cfg . Client ,
2019-03-11 15:57:28 +00:00
ElectionID : electionID ,
2019-03-08 00:20:34 +00:00
OnStartedLeading : func ( stopCh chan struct { } ) {
if n . syncStatus != nil {
go n . syncStatus . Run ( stopCh )
}
2019-03-10 22:12:33 +00:00
2019-03-11 15:57:28 +00:00
n . metricCollector . OnStartedLeading ( electionID )
2019-03-11 16:31:38 +00:00
// manually update SSL expiration metrics
// (to not wait for a reload)
n . metricCollector . SetSSLExpireTime ( n . runningConfig . Servers )
2019-03-08 00:20:34 +00:00
} ,
OnStoppedLeading : func ( ) {
2019-03-11 15:57:28 +00:00
n . metricCollector . OnStoppedLeading ( electionID )
2019-03-08 00:20:34 +00:00
} ,
} )
2017-11-05 01:18:28 +00:00
2019-02-21 19:45:21 +00:00
cmd := n . command . ExecCommand ( )
2017-08-28 16:06:58 +00:00
2018-06-13 18:15:45 +00:00
// put NGINX in another process group to prevent it
2017-08-28 16:06:58 +00:00
// to receive signals meant for the controller
cmd . SysProcAttr = & syscall . SysProcAttr {
Setpgid : true ,
Pgid : 0 ,
}
2018-01-18 19:14:42 +00:00
if n . cfg . EnableSSLPassthrough {
n . setupSSLProxy ( )
}
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting NGINX process" )
2017-11-05 01:18:28 +00:00
n . start ( cmd )
2016-11-29 01:39:17 +00:00
2017-11-27 22:22:59 +00:00
go n . syncQueue . Run ( time . Second , n . stopCh )
2017-11-05 01:18:28 +00:00
// force initial sync
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "initial-sync" ) )
2017-08-28 16:06:58 +00:00
2018-12-03 17:27:29 +00:00
// In case of error the temporal configuration file will
// be available up to five minutes after the error
go func ( ) {
for {
time . Sleep ( 5 * time . Minute )
err := cleanTempNginxCfg ( )
if err != nil {
2020-09-27 20:32:40 +00:00
klog . ErrorS ( err , "Unexpected error removing temporal configuration files" )
2018-12-03 17:27:29 +00:00
}
}
} ( )
2019-02-21 19:45:21 +00:00
if n . validationWebhookServer != nil {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting validation webhook" , "address" , n . validationWebhookServer . Addr ,
"certPath" , n . cfg . ValidationWebhookCertPath , "keyPath" , n . cfg . ValidationWebhookKeyPath )
2019-02-21 19:45:21 +00:00
go func ( ) {
2020-09-27 20:32:40 +00:00
klog . ErrorS ( n . validationWebhookServer . ListenAndServeTLS ( "" , "" ) , "Error listening for TLS connections" )
2019-02-21 19:45:21 +00:00
} ( )
}
2017-11-05 01:18:28 +00:00
for {
select {
2018-03-03 12:23:06 +00:00
case err := <- n . ngxErrCh :
2017-11-05 01:18:28 +00:00
if n . isShuttingDown {
2020-01-25 17:52:31 +00:00
return
2016-11-29 01:39:17 +00:00
}
2017-09-28 14:52:12 +00:00
2020-06-23 20:07:48 +00:00
// if the nginx master process dies, the workers continue to process requests
// until the failure of the configured livenessProbe and restart of the pod.
2017-11-05 01:18:28 +00:00
if process . IsRespawnIfRequired ( err ) {
2020-05-31 16:54:51 +00:00
return
2017-09-28 14:52:12 +00:00
}
2020-05-31 16:54:51 +00:00
2018-02-14 01:46:18 +00:00
case event := <- n . updateCh . Out ( ) :
2018-01-18 19:14:42 +00:00
if n . isShuttingDown {
break
}
2020-01-25 17:52:31 +00:00
2018-02-14 01:46:18 +00:00
if evt , ok := event . ( store . Event ) ; ok {
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Event received" , "type" , evt . Type , "object" , evt . Obj )
2018-02-14 01:46:18 +00:00
if evt . Type == store . ConfigurationEvent {
2018-06-21 14:50:57 +00:00
// TODO: is this necessary? Consider removing this special case
n . syncQueue . EnqueueTask ( task . GetDummyObject ( "configmap-change" ) )
continue
2018-02-14 01:46:18 +00:00
}
2018-06-21 14:50:57 +00:00
n . syncQueue . EnqueueSkippableTask ( evt . Obj )
2018-02-14 01:46:18 +00:00
} else {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Unexpected event type received %T" , event )
2018-01-18 23:04:40 +00:00
}
2017-11-05 01:18:28 +00:00
case <- n . stopCh :
2020-01-25 17:52:31 +00:00
return
2016-11-29 01:39:17 +00:00
}
}
}
2017-08-28 16:06:58 +00:00
// Stop gracefully stops the NGINX master process.
func ( n * NGINXController ) Stop ( ) error {
n . isShuttingDown = true
2017-11-05 01:18:28 +00:00
n . stopLock . Lock ( )
defer n . stopLock . Unlock ( )
if n . syncQueue . IsShuttingDown ( ) {
return fmt . Errorf ( "shutdown already in progress" )
}
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Shutting down controller queues" )
2017-11-05 01:18:28 +00:00
close ( n . stopCh )
go n . syncQueue . Shutdown ( )
if n . syncStatus != nil {
n . syncStatus . Shutdown ( )
}
2017-08-28 16:06:58 +00:00
2019-02-21 19:45:21 +00:00
if n . validationWebhookServer != nil {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Stopping admission controller" )
2019-02-21 19:45:21 +00:00
err := n . validationWebhookServer . Close ( )
if err != nil {
return err
}
}
2018-06-12 13:04:26 +00:00
// send stop signal to NGINX
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Stopping NGINX process" )
2019-02-21 19:45:21 +00:00
cmd := n . command . ExecCommand ( "-s" , "quit" )
2017-08-28 16:06:58 +00:00
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
err := cmd . Run ( )
if err != nil {
return err
}
2018-06-13 18:15:45 +00:00
// wait for the NGINX process to terminate
2017-11-05 01:18:28 +00:00
timer := time . NewTicker ( time . Second * 1 )
2017-11-06 22:38:16 +00:00
for range timer . C {
2019-08-31 00:18:11 +00:00
if ! nginx . IsRunning ( ) {
2020-09-27 20:32:40 +00:00
klog . InfoS ( "NGINX process has stopped" )
2017-11-05 01:18:28 +00:00
timer . Stop ( )
break
}
}
2017-08-28 16:06:58 +00:00
return nil
}
2017-11-05 01:18:28 +00:00
func ( n * NGINXController ) start ( cmd * exec . Cmd ) {
2016-11-10 22:56:29 +00:00
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
if err := cmd . Start ( ) ; err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "NGINX error: %v" , err )
2017-11-05 01:18:28 +00:00
n . ngxErrCh <- err
2016-11-29 01:39:17 +00:00
return
2016-11-10 22:56:29 +00:00
}
2017-03-10 13:01:26 +00:00
2016-11-29 01:39:17 +00:00
go func ( ) {
2017-11-05 01:18:28 +00:00
n . ngxErrCh <- cmd . Wait ( )
2016-11-29 01:39:17 +00:00
} ( )
2016-11-10 22:56:29 +00:00
}
2017-08-24 13:33:26 +00:00
// DefaultEndpoint returns the default endpoint to be use as default server that returns 404.
func ( n NGINXController ) DefaultEndpoint ( ) ingress . Endpoint {
return ingress . Endpoint {
Address : "127.0.0.1" ,
2017-11-05 01:18:28 +00:00
Port : fmt . Sprintf ( "%v" , n . cfg . ListenPorts . Default ) ,
2017-09-17 18:42:31 +00:00
Target : & apiv1 . ObjectReference { } ,
2017-08-24 13:33:26 +00:00
}
}
2019-02-21 19:45:21 +00:00
// generateTemplate returns the nginx configuration file content
func ( n NGINXController ) generateTemplate ( cfg ngx_config . Configuration , ingressCfg ingress . Configuration ) ( [ ] byte , error ) {
2017-03-10 13:01:26 +00:00
2018-01-23 20:10:02 +00:00
if n . cfg . EnableSSLPassthrough {
servers := [ ] * TCPServer { }
for _ , pb := range ingressCfg . PassthroughBackends {
svc := pb . Service
if svc == nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Missing Service for SSL Passthrough backend %q" , pb . Backend )
2018-01-23 20:10:02 +00:00
continue
2017-05-26 18:25:06 +00:00
}
2018-01-23 20:10:02 +00:00
port , err := strconv . Atoi ( pb . Port . String ( ) )
if err != nil {
for _ , sp := range svc . Spec . Ports {
if sp . Name == pb . Port . String ( ) {
port = int ( sp . Port )
break
}
}
} else {
for _ , sp := range svc . Spec . Ports {
if sp . Port == int32 ( port ) {
port = int ( sp . Port )
break
}
2017-05-26 18:25:06 +00:00
}
}
2018-01-23 20:10:02 +00:00
2018-06-13 18:15:45 +00:00
// TODO: Allow PassthroughBackends to specify they support proxy-protocol
2018-01-23 20:10:02 +00:00
servers = append ( servers , & TCPServer {
Hostname : pb . Hostname ,
IP : svc . Spec . ClusterIP ,
Port : port ,
ProxyProtocol : false ,
} )
2017-05-26 18:25:06 +00:00
}
2018-01-23 20:10:02 +00:00
n . Proxy . ServerList = servers
2017-05-26 18:25:06 +00:00
}
2018-06-12 13:04:26 +00:00
// NGINX cannot resize the hash tables used to store server names. For
// this reason we check if the current size is correct for the host
// names defined in the Ingress rules and adjust the value if
// necessary.
2016-11-10 22:56:29 +00:00
// https://trac.nginx.org/nginx/ticket/352
// https://trac.nginx.org/nginx/ticket/631
2017-08-16 07:02:30 +00:00
var longestName int
var serverNameBytes int
2019-01-09 03:33:16 +00:00
2017-08-16 07:02:30 +00:00
for _ , srv := range ingressCfg . Servers {
2020-01-31 16:01:28 +00:00
hostnameLength := len ( srv . Hostname )
if srv . RedirectFromToWWW {
hostnameLength += 4
2017-08-16 07:02:30 +00:00
}
2020-01-31 16:01:28 +00:00
if longestName < hostnameLength {
longestName = hostnameLength
}
for _ , alias := range srv . Aliases {
if longestName < len ( alias ) {
longestName = len ( alias )
}
}
serverNameBytes += hostnameLength
2017-08-16 07:02:30 +00:00
}
2019-01-09 03:33:16 +00:00
2020-01-31 16:01:28 +00:00
nameHashBucketSize := nginxHashBucketSize ( longestName )
if cfg . ServerNameHashBucketSize < nameHashBucketSize {
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting ServerNameHashBucketSize variable" , "value" , nameHashBucketSize )
2016-11-10 22:56:29 +00:00
cfg . ServerNameHashBucketSize = nameHashBucketSize
}
2019-01-09 03:33:16 +00:00
2017-05-24 00:02:12 +00:00
serverNameHashMaxSize := nextPowerOf2 ( serverNameBytes )
if cfg . ServerNameHashMaxSize < serverNameHashMaxSize {
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting ServerNameHashMaxSize variable" , "value" , serverNameHashMaxSize )
2016-11-10 22:56:29 +00:00
cfg . ServerNameHashMaxSize = serverNameHashMaxSize
}
2018-12-27 16:24:09 +00:00
if cfg . MaxWorkerOpenFiles == 0 {
// the limit of open files is per worker process
// and we leave some room to avoid consuming all the FDs available
wp , err := strconv . Atoi ( cfg . WorkerProcesses )
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Worker processes" , "count" , wp )
2018-12-27 16:24:09 +00:00
if err != nil {
wp = 1
}
2019-01-15 20:34:17 +00:00
maxOpenFiles := ( rlimitMaxNumFiles ( ) / wp ) - 1024
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Maximum number of open file descriptors" , "value" , maxOpenFiles )
2018-12-27 16:24:09 +00:00
if maxOpenFiles < 1024 {
// this means the value of RLIMIT_NOFILE is too low.
maxOpenFiles = 1024
}
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting MaxWorkerOpenFiles variable" , "value" , maxOpenFiles )
2018-12-27 16:24:09 +00:00
cfg . MaxWorkerOpenFiles = maxOpenFiles
2017-03-28 16:39:44 +00:00
}
2018-12-27 16:24:09 +00:00
if cfg . MaxWorkerConnections == 0 {
2019-07-08 20:10:38 +00:00
maxWorkerConnections := int ( float64 ( cfg . MaxWorkerOpenFiles * 3.0 / 4 ) )
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Adjusting MaxWorkerConnections variable" , "value" , maxWorkerConnections )
2019-01-15 20:38:30 +00:00
cfg . MaxWorkerConnections = maxWorkerConnections
2017-03-30 13:10:47 +00:00
}
2017-01-19 02:31:33 +00:00
2017-02-07 18:13:08 +00:00
setHeaders := map [ string ] string { }
if cfg . ProxySetHeaders != "" {
2018-01-18 19:14:42 +00:00
cmap , err := n . store . GetConfigMap ( cfg . ProxySetHeaders )
2017-02-07 18:13:08 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading ConfigMap %q from local store: %v" , cfg . ProxySetHeaders , err )
2019-04-02 20:39:42 +00:00
} else {
setHeaders = cmap . Data
2017-02-07 18:13:08 +00:00
}
}
2017-05-18 10:21:03 +00:00
addHeaders := map [ string ] string { }
if cfg . AddHeaders != "" {
2018-01-18 19:14:42 +00:00
cmap , err := n . store . GetConfigMap ( cfg . AddHeaders )
2017-05-18 10:21:03 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading ConfigMap %q from local store: %v" , cfg . AddHeaders , err )
2019-04-02 20:39:42 +00:00
} else {
addHeaders = cmap . Data
2017-05-18 10:21:03 +00:00
}
}
2017-03-08 13:41:55 +00:00
sslDHParam := ""
if cfg . SSLDHParam != "" {
secretName := cfg . SSLDHParam
2018-01-18 19:14:42 +00:00
secret , err := n . store . GetSecret ( secretName )
2017-03-08 13:41:55 +00:00
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error reading Secret %q from local store: %v" , secretName , err )
2019-03-05 19:31:33 +00:00
} else {
nsSecName := strings . Replace ( secretName , "/" , "-" , - 1 )
dh , ok := secret . Data [ "dhparam.pem" ]
if ok {
2019-08-13 21:14:55 +00:00
pemFileName , err := ssl . AddOrUpdateDHParam ( nsSecName , dh )
2019-03-05 19:31:33 +00:00
if err != nil {
klog . Warningf ( "Error adding or updating dhparam file %v: %v" , nsSecName , err )
} else {
sslDHParam = pemFileName
}
2017-03-08 13:41:55 +00:00
}
}
}
cfg . SSLDHParam = sslDHParam
2019-08-13 21:14:55 +00:00
cfg . DefaultSSLCertificate = n . getDefaultSSLCertificate ( )
2017-11-05 01:18:28 +00:00
tc := ngx_config . TemplateConfig {
2019-08-13 21:14:55 +00:00
ProxySetHeaders : setHeaders ,
AddHeaders : addHeaders ,
BacklogSize : sysctlSomaxconn ( ) ,
Backends : ingressCfg . Backends ,
PassthroughBackends : ingressCfg . PassthroughBackends ,
Servers : ingressCfg . Servers ,
TCPBackends : ingressCfg . TCPEndpoints ,
UDPBackends : ingressCfg . UDPEndpoints ,
Cfg : cfg ,
IsIPV6Enabled : n . isIPV6Enabled && ! cfg . DisableIpv6 ,
NginxStatusIpv4Whitelist : cfg . NginxStatusIpv4Whitelist ,
NginxStatusIpv6Whitelist : cfg . NginxStatusIpv6Whitelist ,
RedirectServers : buildRedirects ( ingressCfg . Servers ) ,
IsSSLPassthroughEnabled : n . cfg . EnableSSLPassthrough ,
ListenPorts : n . cfg . ListenPorts ,
PublishService : n . GetPublishService ( ) ,
EnableMetrics : n . cfg . EnableMetrics ,
2020-03-16 07:26:33 +00:00
MaxmindEditionFiles : n . cfg . MaxmindEditionFiles ,
HealthzURI : nginx . HealthPath ,
2020-06-20 06:58:14 +00:00
MonitorMaxBatchSize : n . cfg . MonitorMaxBatchSize ,
2020-03-16 07:26:33 +00:00
PID : nginx . PID ,
StatusPath : nginx . StatusPath ,
StatusPort : nginx . StatusPort ,
StreamPort : nginx . StreamPort ,
2017-08-03 14:51:39 +00:00
}
2018-07-07 17:46:18 +00:00
tc . Cfg . Checksum = ingressCfg . ConfigurationChecksum
2019-02-21 19:45:21 +00:00
return n . t . Write ( tc )
}
// testTemplate checks if the NGINX configuration inside the byte array is valid
// running the command "nginx -t" using a temporal file.
func ( n NGINXController ) testTemplate ( cfg [ ] byte ) error {
if len ( cfg ) == 0 {
return fmt . Errorf ( "invalid NGINX configuration (empty)" )
}
tmpfile , err := ioutil . TempFile ( "" , tempNginxPattern )
if err != nil {
return err
}
defer tmpfile . Close ( )
err = ioutil . WriteFile ( tmpfile . Name ( ) , cfg , file . ReadWriteByUser )
if err != nil {
return err
}
out , err := n . command . Test ( tmpfile . Name ( ) )
if err != nil {
// this error is different from the rest because it must be clear why nginx is not working
oe := fmt . Sprintf ( `
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
Error : % v
% v
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
` , err , string ( out ) )
return errors . New ( oe )
}
os . Remove ( tmpfile . Name ( ) )
return nil
}
// OnUpdate is called by the synchronization loop whenever configuration
// changes were detected. The received backend Configuration is merged with the
// configuration ConfigMap before generating the final configuration file.
// Returns nil in case the backend was successfully reloaded.
func ( n * NGINXController ) OnUpdate ( ingressCfg ingress . Configuration ) error {
cfg := n . store . GetBackendConfiguration ( )
cfg . Resolver = n . resolver
content , err := n . generateTemplate ( cfg , ingressCfg )
2017-06-11 19:56:40 +00:00
if err != nil {
return err
}
2020-01-29 15:20:05 +00:00
err = createOpentracingCfg ( cfg )
if err != nil {
return err
2018-06-21 22:15:18 +00:00
}
2017-06-11 19:56:40 +00:00
err = n . testTemplate ( content )
if err != nil {
return err
}
2020-08-08 23:31:02 +00:00
if klog . V ( 2 ) . Enabled ( ) {
2017-11-05 01:18:28 +00:00
src , _ := ioutil . ReadFile ( cfgPath )
if ! bytes . Equal ( src , content ) {
tmpfile , err := ioutil . TempFile ( "" , "new-nginx-cfg" )
if err != nil {
return err
}
defer tmpfile . Close ( )
2018-06-12 12:40:40 +00:00
err = ioutil . WriteFile ( tmpfile . Name ( ) , content , file . ReadWriteByUser )
2017-11-05 01:18:28 +00:00
if err != nil {
return err
}
2020-02-21 19:41:03 +00:00
diffOutput , err := exec . Command ( "diff" , "-I" , "'# Configuration.*'" , "-u" , cfgPath , tmpfile . Name ( ) ) . CombinedOutput ( )
2018-12-12 01:08:02 +00:00
if err != nil {
2019-08-08 16:53:23 +00:00
if exitError , ok := err . ( * exec . ExitError ) ; ok {
ws := exitError . Sys ( ) . ( syscall . WaitStatus )
if ws . ExitStatus ( ) == 2 {
klog . Warningf ( "Failed to executing diff command: %v" , err )
}
}
2018-12-12 01:08:02 +00:00
}
2017-11-05 01:18:28 +00:00
2020-09-27 20:32:40 +00:00
klog . InfoS ( "NGINX configuration change" , "diff" , string ( diffOutput ) )
2017-11-05 01:18:28 +00:00
2018-06-13 18:15:45 +00:00
// we do not defer the deletion of temp files in order
// to keep them around for inspection in case of error
2017-11-05 01:18:28 +00:00
os . Remove ( tmpfile . Name ( ) )
}
}
2017-06-11 19:56:40 +00:00
2018-06-12 12:40:40 +00:00
err = ioutil . WriteFile ( cfgPath , content , file . ReadWriteByUser )
2017-02-20 02:34:05 +00:00
if err != nil {
2017-06-11 19:56:40 +00:00
return err
2017-02-20 02:34:05 +00:00
}
2019-02-21 19:45:21 +00:00
o , err := n . command . ExecCommand ( "-s" , "reload" ) . CombinedOutput ( )
2017-06-11 19:56:40 +00:00
if err != nil {
2017-06-23 13:55:45 +00:00
return fmt . Errorf ( "%v\n%v" , err , string ( o ) )
2017-02-20 02:34:05 +00:00
}
2017-06-11 19:56:40 +00:00
return nil
2016-11-10 22:56:29 +00:00
}
2018-06-13 18:15:45 +00:00
// nginxHashBucketSize computes the correct NGINX hash_bucket_size for a hash
// with the given longest key.
2017-04-19 02:29:51 +00:00
func nginxHashBucketSize ( longestString int ) int {
2018-06-13 18:15:45 +00:00
// see https://github.com/kubernetes/ingress-nginxs/issues/623 for an explanation
2017-04-19 02:29:51 +00:00
wordSize := 8 // Assume 64 bit CPU
n := longestString + 2
aligned := ( n + wordSize - 1 ) & ^ ( wordSize - 1 )
rawSize := wordSize + wordSize + aligned
return nextPowerOf2 ( rawSize )
}
2016-11-10 22:56:29 +00:00
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
// https://play.golang.org/p/TVSyCcdxUh
func nextPowerOf2 ( v int ) int {
v --
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v ++
return v
}
2018-01-18 19:14:42 +00:00
func ( n * NGINXController ) setupSSLProxy ( ) {
2018-06-04 01:10:41 +00:00
cfg := n . store . GetBackendConfiguration ( )
2018-01-18 19:14:42 +00:00
sslPort := n . cfg . ListenPorts . HTTPS
proxyPort := n . cfg . ListenPorts . SSLProxy
2020-09-27 20:32:40 +00:00
klog . InfoS ( "Starting TLS proxy for SSL Passthrough" )
2018-01-18 19:14:42 +00:00
n . Proxy = & TCPProxy {
Default : & TCPServer {
Hostname : "localhost" ,
IP : "127.0.0.1" ,
Port : proxyPort ,
ProxyProtocol : true ,
} ,
}
listener , err := net . Listen ( "tcp" , fmt . Sprintf ( ":%v" , sslPort ) )
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Fatalf ( "%v" , err )
2018-01-18 19:14:42 +00:00
}
2018-06-04 01:10:41 +00:00
proxyList := & proxyproto . Listener { Listener : listener , ProxyHeaderTimeout : cfg . ProxyProtocolHeaderTimeout }
2018-01-18 19:14:42 +00:00
2018-06-13 18:15:45 +00:00
// accept TCP connections on the configured HTTPS port
2018-01-18 19:14:42 +00:00
go func ( ) {
for {
var conn net . Conn
var err error
if n . store . GetBackendConfiguration ( ) . UseProxyProtocol {
2018-06-13 18:15:45 +00:00
// wrap the listener in order to decode Proxy
// Protocol before handling the connection
2018-01-18 19:14:42 +00:00
conn , err = proxyList . Accept ( )
} else {
conn , err = listener . Accept ( )
}
if err != nil {
2018-12-05 16:27:55 +00:00
klog . Warningf ( "Error accepting TCP connection: %v" , err )
2018-01-18 19:14:42 +00:00
continue
}
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Handling TCP connection" , "remote" , conn . RemoteAddr ( ) , "local" , conn . LocalAddr ( ) )
2018-01-18 19:14:42 +00:00
go n . Proxy . Handle ( conn )
}
} ( )
}
2018-03-18 13:13:41 +00:00
2018-06-04 21:48:30 +00:00
// Helper function to clear Certificates from the ingress configuration since they should be ignored when
// checking if the new configuration changes can be applied dynamically if dynamic certificates is on
func clearCertificates ( config * ingress . Configuration ) {
var clearedServers [ ] * ingress . Server
for _ , server := range config . Servers {
copyOfServer := * server
2019-09-25 01:36:37 +00:00
copyOfServer . SSLCert = nil
2018-06-04 21:48:30 +00:00
clearedServers = append ( clearedServers , & copyOfServer )
}
config . Servers = clearedServers
}
2019-01-24 14:03:33 +00:00
// Helper function to clear endpoints from the ingress configuration since they should be ignored when
// checking if the new configuration changes can be applied dynamically.
func clearL4serviceEndpoints ( config * ingress . Configuration ) {
var clearedTCPL4Services [ ] ingress . L4Service
var clearedUDPL4Services [ ] ingress . L4Service
for _ , service := range config . TCPEndpoints {
copyofService := ingress . L4Service {
Port : service . Port ,
Backend : service . Backend ,
Endpoints : [ ] ingress . Endpoint { } ,
Service : nil ,
}
clearedTCPL4Services = append ( clearedTCPL4Services , copyofService )
}
for _ , service := range config . UDPEndpoints {
copyofService := ingress . L4Service {
Port : service . Port ,
Backend : service . Backend ,
Endpoints : [ ] ingress . Endpoint { } ,
Service : nil ,
}
clearedUDPL4Services = append ( clearedUDPL4Services , copyofService )
}
config . TCPEndpoints = clearedTCPL4Services
config . UDPEndpoints = clearedUDPL4Services
}
2018-06-13 18:15:45 +00:00
// IsDynamicConfigurationEnough returns whether a Configuration can be
// dynamically applied, without reloading the backend.
2018-04-01 20:09:27 +00:00
func ( n * NGINXController ) IsDynamicConfigurationEnough ( pcfg * ingress . Configuration ) bool {
2018-04-24 18:02:52 +00:00
copyOfRunningConfig := * n . runningConfig
copyOfPcfg := * pcfg
2018-03-18 13:13:41 +00:00
copyOfRunningConfig . Backends = [ ] * ingress . Backend { }
copyOfPcfg . Backends = [ ] * ingress . Backend { }
2019-01-24 14:03:33 +00:00
clearL4serviceEndpoints ( & copyOfRunningConfig )
clearL4serviceEndpoints ( & copyOfPcfg )
2019-08-13 21:14:55 +00:00
clearCertificates ( & copyOfRunningConfig )
clearCertificates ( & copyOfPcfg )
2018-06-04 21:48:30 +00:00
2018-03-18 13:13:41 +00:00
return copyOfRunningConfig . Equal ( & copyOfPcfg )
}
2018-06-13 18:15:45 +00:00
// configureDynamically encodes new Backends in JSON format and POSTs the
// payload to an internal HTTP endpoint handled by Lua.
2019-08-15 18:57:51 +00:00
func ( n * NGINXController ) configureDynamically ( pcfg * ingress . Configuration ) error {
backendsChanged := ! reflect . DeepEqual ( n . runningConfig . Backends , pcfg . Backends )
if backendsChanged {
err := configureBackends ( pcfg . Backends )
if err != nil {
return err
2018-04-24 18:02:52 +00:00
}
2019-08-15 18:57:51 +00:00
}
2018-04-24 18:02:52 +00:00
2019-08-15 18:57:51 +00:00
streamConfigurationChanged := ! reflect . DeepEqual ( n . runningConfig . TCPEndpoints , pcfg . TCPEndpoints ) || ! reflect . DeepEqual ( n . runningConfig . UDPEndpoints , pcfg . UDPEndpoints )
if streamConfigurationChanged {
err := updateStreamConfiguration ( pcfg . TCPEndpoints , pcfg . UDPEndpoints )
if err != nil {
return err
2018-04-24 18:02:52 +00:00
}
2018-03-22 02:47:39 +00:00
}
2019-08-15 18:57:51 +00:00
serversChanged := ! reflect . DeepEqual ( n . runningConfig . Servers , pcfg . Servers )
if serversChanged {
err := configureCertificates ( pcfg . Servers )
if err != nil {
return err
}
2019-01-21 14:29:36 +00:00
}
2019-08-15 18:57:51 +00:00
return nil
}
func updateStreamConfiguration ( TCPEndpoints [ ] ingress . L4Service , UDPEndpoints [ ] ingress . L4Service ) error {
2018-11-16 20:33:56 +00:00
streams := make ( [ ] ingress . Backend , 0 )
2019-08-15 18:57:51 +00:00
for _ , ep := range TCPEndpoints {
2019-01-02 17:32:57 +00:00
var service * apiv1 . Service
if ep . Service != nil {
service = & apiv1 . Service { Spec : ep . Service . Spec }
}
2018-11-16 16:48:47 +00:00
key := fmt . Sprintf ( "tcp-%v-%v-%v" , ep . Backend . Namespace , ep . Backend . Name , ep . Backend . Port . String ( ) )
2018-11-16 20:33:56 +00:00
streams = append ( streams , ingress . Backend {
2018-11-16 16:48:47 +00:00
Name : key ,
Endpoints : ep . Endpoints ,
Port : intstr . FromInt ( ep . Port ) ,
2019-01-02 17:32:57 +00:00
Service : service ,
2018-11-16 16:48:47 +00:00
} )
}
2019-08-15 18:57:51 +00:00
for _ , ep := range UDPEndpoints {
2019-01-02 17:32:57 +00:00
var service * apiv1 . Service
if ep . Service != nil {
service = & apiv1 . Service { Spec : ep . Service . Spec }
}
2018-11-16 16:48:47 +00:00
key := fmt . Sprintf ( "udp-%v-%v-%v" , ep . Backend . Namespace , ep . Backend . Name , ep . Backend . Port . String ( ) )
2018-11-16 20:33:56 +00:00
streams = append ( streams , ingress . Backend {
2018-11-16 16:48:47 +00:00
Name : key ,
Endpoints : ep . Endpoints ,
Port : intstr . FromInt ( ep . Port ) ,
2019-01-02 17:32:57 +00:00
Service : service ,
2018-11-16 16:48:47 +00:00
} )
}
2019-09-08 21:14:54 +00:00
buf , err := json . Marshal ( streams )
2018-11-16 20:52:46 +00:00
if err != nil {
return err
}
2020-09-03 02:01:13 +00:00
hostPort := net . JoinHostPort ( "127.0.0.1" , fmt . Sprintf ( "%v" , nginx . StreamPort ) )
conn , err := net . Dial ( "tcp" , hostPort )
2018-11-27 14:53:51 +00:00
if err != nil {
return err
}
2019-09-08 21:14:54 +00:00
defer conn . Close ( )
2018-11-27 14:53:51 +00:00
2019-08-15 18:57:51 +00:00
_ , err = conn . Write ( buf )
if err != nil {
return err
2019-01-21 14:29:36 +00:00
}
2019-08-15 18:57:51 +00:00
_ , err = fmt . Fprintf ( conn , "\r\n" )
2019-08-13 21:14:55 +00:00
if err != nil {
return err
2018-11-16 20:52:46 +00:00
}
return nil
}
2019-08-15 18:57:51 +00:00
func configureBackends ( rawBackends [ ] * ingress . Backend ) error {
backends := make ( [ ] * ingress . Backend , len ( rawBackends ) )
2018-11-16 20:52:46 +00:00
2019-08-15 18:57:51 +00:00
for i , backend := range rawBackends {
var service * apiv1 . Service
if backend . Service != nil {
service = & apiv1 . Service { Spec : backend . Service . Spec }
}
luaBackend := & ingress . Backend {
Name : backend . Name ,
Port : backend . Port ,
SSLPassthrough : backend . SSLPassthrough ,
SessionAffinity : backend . SessionAffinity ,
UpstreamHashBy : backend . UpstreamHashBy ,
LoadBalancing : backend . LoadBalancing ,
Service : service ,
NoServer : backend . NoServer ,
TrafficShapingPolicy : backend . TrafficShapingPolicy ,
AlternativeBackends : backend . AlternativeBackends ,
}
var endpoints [ ] ingress . Endpoint
for _ , endpoint := range backend . Endpoints {
endpoints = append ( endpoints , ingress . Endpoint {
Address : endpoint . Address ,
Port : endpoint . Port ,
} )
}
luaBackend . Endpoints = endpoints
backends [ i ] = luaBackend
2018-11-16 16:48:47 +00:00
}
2019-08-15 18:57:51 +00:00
statusCode , _ , err := nginx . NewPostStatusRequest ( "/configuration/backends" , "application/json" , backends )
2018-11-16 16:48:47 +00:00
if err != nil {
return err
}
2019-08-15 18:57:51 +00:00
if statusCode != http . StatusCreated {
return fmt . Errorf ( "unexpected error code: %d" , statusCode )
2018-11-16 20:33:56 +00:00
}
2018-06-04 21:48:30 +00:00
return nil
}
2019-08-26 14:58:44 +00:00
type sslConfiguration struct {
Certificates map [ string ] string ` json:"certificates" `
Servers map [ string ] string ` json:"servers" `
}
2018-06-04 21:48:30 +00:00
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
// that is handled by Lua
2019-08-15 18:57:51 +00:00
func configureCertificates ( rawServers [ ] * ingress . Server ) error {
2019-08-26 14:58:44 +00:00
configuration := & sslConfiguration {
Certificates : map [ string ] string { } ,
Servers : map [ string ] string { } ,
}
2018-06-04 21:48:30 +00:00
2019-09-24 13:53:22 +00:00
configure := func ( hostname string , sslCert * ingress . SSLCert ) {
uid := emptyUID
2019-06-25 11:49:00 +00:00
2019-09-24 13:53:22 +00:00
if sslCert != nil {
uid = sslCert . UID
2019-03-26 17:24:47 +00:00
2019-09-24 13:53:22 +00:00
if _ , ok := configuration . Certificates [ uid ] ; ! ok {
configuration . Certificates [ uid ] = sslCert . PemCertKey
}
2019-08-26 14:58:44 +00:00
}
2019-09-24 13:53:22 +00:00
configuration . Servers [ hostname ] = uid
}
for _ , rawServer := range rawServers {
configure ( rawServer . Hostname , rawServer . SSLCert )
2019-08-26 14:58:44 +00:00
for _ , alias := range rawServer . Aliases {
2019-09-24 13:53:22 +00:00
if rawServer . SSLCert != nil && ssl . IsValidHostname ( alias , rawServer . SSLCert . CN ) {
configuration . Servers [ alias ] = rawServer . SSLCert . UID
} else {
configuration . Servers [ alias ] = emptyUID
2019-08-26 14:58:44 +00:00
}
2019-03-26 17:24:47 +00:00
}
}
2019-08-15 18:57:51 +00:00
redirects := buildRedirects ( rawServers )
2019-03-26 17:24:47 +00:00
for _ , redirect := range redirects {
2019-09-24 13:53:22 +00:00
configure ( redirect . From , redirect . SSLCert )
2018-06-04 21:48:30 +00:00
}
2019-08-26 14:58:44 +00:00
statusCode , _ , err := nginx . NewPostStatusRequest ( "/configuration/servers" , "application/json" , configuration )
2018-03-18 13:13:41 +00:00
if err != nil {
return err
}
2019-01-21 14:29:36 +00:00
if statusCode != http . StatusCreated {
return fmt . Errorf ( "unexpected error code: %d" , statusCode )
2018-03-18 13:13:41 +00:00
}
return nil
}
2018-06-21 22:15:18 +00:00
const zipkinTmpl = ` {
"service_name" : "{{ .ZipkinServiceName }}" ,
"collector_host" : "{{ .ZipkinCollectorHost }}" ,
2018-06-28 14:42:32 +00:00
"collector_port" : { { . ZipkinCollectorPort } } ,
"sample_rate" : { { . ZipkinSampleRate } }
2018-06-21 22:15:18 +00:00
} `
const jaegerTmpl = ` {
"service_name" : "{{ .JaegerServiceName }}" ,
"sampler" : {
"type" : "{{ .JaegerSamplerType }}" ,
2019-05-21 03:06:41 +00:00
"param" : { { . JaegerSamplerParam } } ,
"samplingServerURL" : "{{ .JaegerSamplerHost }}:{{ .JaegerSamplerPort }}/sampling"
2018-06-21 22:15:18 +00:00
} ,
"reporter" : {
"localAgentHostPort" : "{{ .JaegerCollectorHost }}:{{ .JaegerCollectorPort }}"
2019-09-17 09:35:53 +00:00
} ,
"headers" : {
"TraceContextHeaderName" : "{{ .JaegerTraceContextHeaderName }}" ,
"jaegerDebugHeader" : "{{ .JaegerDebugHeader }}" ,
"jaegerBaggageHeader" : "{{ .JaegerBaggageHeader }}" ,
"traceBaggageHeaderPrefix" : "{{ .JaegerTraceBaggageHeaderPrefix }}"
} ,
2018-06-21 22:15:18 +00:00
} `
2019-02-15 20:20:10 +00:00
const datadogTmpl = ` {
"service" : "{{ .DatadogServiceName }}" ,
"agent_host" : "{{ .DatadogCollectorHost }}" ,
"agent_port" : { { . DatadogCollectorPort } } ,
2020-10-01 21:42:22 +00:00
"environment" : "{{ .DatadogEnvironment }}" ,
2020-01-08 00:59:59 +00:00
"operation_name_override" : "{{ .DatadogOperationNameOverride }}" ,
"sample_rate" : { { . DatadogSampleRate } } ,
"dd.priority.sampling" : { { . DatadogPrioritySampling } }
2019-02-15 20:20:10 +00:00
} `
2018-06-21 22:15:18 +00:00
func createOpentracingCfg ( cfg ngx_config . Configuration ) error {
var tmpl * template . Template
var err error
if cfg . ZipkinCollectorHost != "" {
tmpl , err = template . New ( "zipkin" ) . Parse ( zipkinTmpl )
if err != nil {
return err
}
} else if cfg . JaegerCollectorHost != "" {
2019-02-10 15:59:05 +00:00
tmpl , err = template . New ( "jaeger" ) . Parse ( jaegerTmpl )
2018-06-21 22:15:18 +00:00
if err != nil {
return err
}
2019-02-15 20:20:10 +00:00
} else if cfg . DatadogCollectorHost != "" {
tmpl , err = template . New ( "datadog" ) . Parse ( datadogTmpl )
if err != nil {
return err
}
2018-06-21 22:15:18 +00:00
} else {
tmpl , _ = template . New ( "empty" ) . Parse ( "{}" )
}
tmplBuf := bytes . NewBuffer ( make ( [ ] byte , 0 ) )
err = tmpl . Execute ( tmplBuf , cfg )
if err != nil {
return err
}
2019-02-10 15:59:05 +00:00
// Expand possible environment variables before writing the configuration to file.
2019-07-08 20:10:38 +00:00
expanded := os . ExpandEnv ( tmplBuf . String ( ) )
2019-02-10 15:59:05 +00:00
return ioutil . WriteFile ( "/etc/nginx/opentracing.json" , [ ] byte ( expanded ) , file . ReadWriteByUser )
2018-06-21 22:15:18 +00:00
}
2018-12-03 17:27:29 +00:00
func cleanTempNginxCfg ( ) error {
var files [ ] string
err := filepath . Walk ( os . TempDir ( ) , func ( path string , info os . FileInfo , err error ) error {
if info . IsDir ( ) && os . TempDir ( ) != path {
return filepath . SkipDir
}
dur , _ := time . ParseDuration ( "-5m" )
fiveMinutesAgo := time . Now ( ) . Add ( dur )
if strings . HasPrefix ( info . Name ( ) , tempNginxPattern ) && info . ModTime ( ) . Before ( fiveMinutesAgo ) {
files = append ( files , path )
}
return nil
} )
if err != nil {
return err
}
for _ , file := range files {
err := os . Remove ( file )
if err != nil {
return err
}
}
return nil
}
2019-01-09 03:33:16 +00:00
type redirect struct {
From string
To string
2019-08-13 21:14:55 +00:00
SSLCert * ingress . SSLCert
2019-01-09 03:33:16 +00:00
}
func buildRedirects ( servers [ ] * ingress . Server ) [ ] * redirect {
names := sets . String { }
redirectServers := make ( [ ] * redirect , 0 )
for _ , srv := range servers {
if ! srv . RedirectFromToWWW {
continue
}
to := srv . Hostname
var from string
if strings . HasPrefix ( to , "www." ) {
from = strings . TrimPrefix ( to , "www." )
} else {
from = fmt . Sprintf ( "www.%v" , to )
}
if names . Has ( to ) {
continue
}
2020-09-27 20:32:40 +00:00
klog . V ( 3 ) . InfoS ( "Creating redirect" , "from" , from , "to" , to )
2019-01-09 03:33:16 +00:00
found := false
for _ , esrv := range servers {
if esrv . Hostname == from {
found = true
break
}
}
if found {
klog . Warningf ( "Already exists an Ingress with %q hostname. Skipping creation of redirection from %q to %q." , from , from , to )
continue
}
r := & redirect {
From : from ,
To : to ,
}
2019-08-13 21:14:55 +00:00
if srv . SSLCert != nil {
2019-01-09 03:33:16 +00:00
if ssl . IsValidHostname ( from , srv . SSLCert . CN ) {
r . SSLCert = srv . SSLCert
} else {
klog . Warningf ( "the server %v has SSL configured but the SSL certificate does not contains a CN for %v. Redirects will not work for HTTPS to HTTPS" , from , to )
}
}
redirectServers = append ( redirectServers , r )
names . Insert ( to )
}
return redirectServers
}