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"
|
2017-10-08 22:37:19 +00:00
|
|
|
"encoding/base64"
|
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"
|
2016-11-10 22:56:29 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2017-11-12 13:33:18 +00:00
|
|
|
"runtime"
|
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"
|
|
|
|
"time"
|
2016-11-10 22:56:29 +00:00
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2017-01-20 22:37:59 +00:00
|
|
|
|
2017-09-17 18:42:31 +00:00
|
|
|
apiv1 "k8s.io/api/core/v1"
|
2017-07-28 22:57:33 +00:00
|
|
|
extensions "k8s.io/api/extensions/v1beta1"
|
2017-11-05 01:18:28 +00:00
|
|
|
"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"
|
2017-11-06 22:34:30 +00:00
|
|
|
"k8s.io/kubernetes/pkg/util/filesystem"
|
2016-11-16 18:24:26 +00:00
|
|
|
|
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"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/annotations/class"
|
|
|
|
ngx_config "k8s.io/ingress-nginx/internal/ingress/controller/config"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/controller/process"
|
|
|
|
ngx_template "k8s.io/ingress-nginx/internal/ingress/controller/template"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/defaults"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/status"
|
|
|
|
"k8s.io/ingress-nginx/internal/ingress/store"
|
|
|
|
ing_net "k8s.io/ingress-nginx/internal/net"
|
|
|
|
"k8s.io/ingress-nginx/internal/net/dns"
|
|
|
|
"k8s.io/ingress-nginx/internal/net/ssl"
|
|
|
|
"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
|
|
|
)
|
|
|
|
|
2017-03-10 13:01:26 +00:00
|
|
|
type statusModule string
|
|
|
|
|
2016-11-29 01:39:17 +00:00
|
|
|
const (
|
|
|
|
ngxHealthPath = "/healthz"
|
2017-03-10 13:01:26 +00:00
|
|
|
|
|
|
|
defaultStatusModule statusModule = "default"
|
|
|
|
vtsStatusModule statusModule = "vts"
|
2016-11-10 22:56:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2017-11-22 13:35:47 +00:00
|
|
|
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
|
|
|
cfgPath = "/etc/nginx/nginx.conf"
|
|
|
|
nginxBinary = "/usr/sbin/nginx"
|
2016-11-10 22:56:29 +00:00
|
|
|
)
|
|
|
|
|
2017-10-06 20:33:32 +00:00
|
|
|
// NewNGINXController creates a new NGINX Ingress controller.
|
2016-11-10 22:56:29 +00:00
|
|
|
// If the environment variable NGINX_BINARY exists it will be used
|
|
|
|
// as source for nginx commands
|
2017-11-22 13:40:54 +00:00
|
|
|
func NewNGINXController(config *Configuration, fs file.Filesystem) *NGINXController {
|
2016-11-10 22:56:29 +00:00
|
|
|
ngx := os.Getenv("NGINX_BINARY")
|
|
|
|
if ngx == "" {
|
2017-09-28 14:52:12 +00:00
|
|
|
ngx = nginxBinary
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
2017-04-11 14:47:49 +00:00
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
eventBroadcaster := record.NewBroadcaster()
|
|
|
|
eventBroadcaster.StartLogging(glog.Infof)
|
|
|
|
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 {
|
|
|
|
glog.Warningf("unexpected error reading system nameservers: %v", err)
|
|
|
|
}
|
|
|
|
|
2017-03-12 15:27:05 +00:00
|
|
|
n := &NGINXController{
|
2017-11-05 01:18:28 +00:00
|
|
|
backendDefaults: ngx_config.NewDefault().Backend,
|
2017-08-29 19:40:03 +00:00
|
|
|
binary: ngx,
|
2017-11-05 01:18:28 +00:00
|
|
|
|
|
|
|
configmap: &apiv1.ConfigMap{},
|
|
|
|
|
|
|
|
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-11-07 16:36:51 +00:00
|
|
|
sslCertTracker: store.NewSSLCertTracker(),
|
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{}),
|
|
|
|
stopLock: &sync.Mutex{},
|
2017-11-06 22:34:30 +00:00
|
|
|
|
2017-11-22 13:40:54 +00:00
|
|
|
fileSystem: fs,
|
2017-11-27 22:22:59 +00:00
|
|
|
|
|
|
|
// create an empty configuration.
|
|
|
|
runningConfig: &ingress.Configuration{},
|
2017-04-09 23:51:38 +00:00
|
|
|
}
|
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
n.listers, n.controllers = n.createListers(n.stopCh)
|
|
|
|
|
2017-11-22 13:40:54 +00:00
|
|
|
n.stats = newStatsCollector(config.Namespace, class.IngressClass, n.binary, n.cfg.ListenPorts.Status)
|
2017-11-05 01:18:28 +00:00
|
|
|
|
|
|
|
n.syncQueue = task.NewTaskQueue(n.syncIngress)
|
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
n.annotations = annotations.NewAnnotationExtractor(n)
|
2017-11-05 01:18:28 +00:00
|
|
|
|
|
|
|
if config.UpdateStatus {
|
|
|
|
n.syncStatus = status.NewStatusSyncer(status.Config{
|
|
|
|
Client: config.Client,
|
2017-11-06 01:22:49 +00:00
|
|
|
PublishService: config.PublishService,
|
2017-11-05 01:18:28 +00:00
|
|
|
IngressLister: n.listers.Ingress,
|
|
|
|
ElectionID: config.ElectionID,
|
2017-11-22 13:40:54 +00:00
|
|
|
IngressClass: class.IngressClass,
|
|
|
|
DefaultIngressClass: class.DefaultClass,
|
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 {
|
|
|
|
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
|
|
|
|
}
|
|
|
|
|
2016-11-10 22:56:29 +00:00
|
|
|
var onChange func()
|
|
|
|
onChange = func() {
|
2017-11-22 13:40:54 +00:00
|
|
|
template, err := ngx_template.NewTemplate(tmplPath, fs)
|
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
|
|
|
|
glog.Errorf(`
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
Error loading new template : %v
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
`, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n.t = template
|
|
|
|
glog.Info("new NGINX template loaded")
|
2017-11-05 01:18:28 +00:00
|
|
|
n.SetForceReload(true)
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2017-11-22 13:40:54 +00:00
|
|
|
ngxTpl, err := ngx_template.NewTemplate(tmplPath, fs)
|
2016-11-10 22:56:29 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Fatalf("invalid NGINX template: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
n.t = ngxTpl
|
2016-11-29 01:39:17 +00:00
|
|
|
|
2017-11-22 13:40:54 +00:00
|
|
|
// TODO: refactor
|
|
|
|
if _, ok := fs.(filesystem.DefaultFs); !ok {
|
|
|
|
watch.NewDummyFileWatcher(tmplPath, onChange)
|
|
|
|
} else {
|
|
|
|
_, err = watch.NewFileWatcher(tmplPath, onChange)
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatalf("unexpected error watching template %v: %v", tmplPath, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-28 16:06:58 +00:00
|
|
|
return n
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NGINXController ...
|
|
|
|
type NGINXController struct {
|
2017-11-05 01:18:28 +00:00
|
|
|
cfg *Configuration
|
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
listers *ingress.StoreLister
|
|
|
|
controllers *cacheController
|
2017-11-05 01:18:28 +00:00
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
annotations annotations.Extractor
|
2017-11-05 01:18:28 +00:00
|
|
|
|
|
|
|
recorder record.EventRecorder
|
|
|
|
|
|
|
|
syncQueue *task.Queue
|
|
|
|
|
|
|
|
syncStatus status.Sync
|
|
|
|
|
|
|
|
// local store of SSL certificates
|
|
|
|
// (only certificates used in ingress)
|
2017-11-07 16:36:51 +00:00
|
|
|
sslCertTracker *store.SSLCertTracker
|
2017-11-05 01:18:28 +00:00
|
|
|
|
|
|
|
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
|
2016-11-10 22:56:29 +00:00
|
|
|
|
2017-09-17 18:42:31 +00:00
|
|
|
configmap *apiv1.ConfigMap
|
2017-01-20 22:37:59 +00:00
|
|
|
|
2017-04-09 23:51:38 +00:00
|
|
|
binary string
|
|
|
|
resolver []net.IP
|
2017-03-10 13:01:26 +00:00
|
|
|
|
2017-03-12 15:27:05 +00:00
|
|
|
stats *statsCollector
|
2017-03-10 13:01:26 +00:00
|
|
|
statusModule statusModule
|
2017-04-09 18:03:27 +00:00
|
|
|
|
|
|
|
// returns true if IPV6 is enabled in the pod
|
|
|
|
isIPV6Enabled bool
|
2017-04-11 14:47:49 +00:00
|
|
|
|
2017-04-09 23:51:38 +00:00
|
|
|
// returns true if proxy protocol es enabled
|
2017-11-05 01:18:28 +00:00
|
|
|
IsProxyProtocolEnabled bool
|
2017-04-09 23:51:38 +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
|
|
|
|
|
|
|
backendDefaults defaults.Backend
|
2017-11-06 22:34:30 +00:00
|
|
|
|
|
|
|
fileSystem filesystem.Filesystem
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
2016-11-16 18:24:26 +00:00
|
|
|
// Start start a new NGINX master process running in foreground.
|
2017-03-12 15:27:05 +00:00
|
|
|
func (n *NGINXController) Start() {
|
2017-11-05 01:18:28 +00:00
|
|
|
glog.Infof("starting Ingress controller")
|
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
n.controllers.Run(n.stopCh)
|
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
// initial sync of secrets to avoid unnecessary reloads
|
|
|
|
glog.Info("running initial sync of secrets")
|
|
|
|
for _, obj := range n.listers.Ingress.List() {
|
|
|
|
ing := obj.(*extensions.Ingress)
|
|
|
|
|
2017-11-22 13:35:47 +00:00
|
|
|
if !class.IsValid(ing) {
|
2017-11-28 22:27:38 +00:00
|
|
|
a := ing.GetAnnotations()[class.IngressKey]
|
2017-11-05 01:18:28 +00:00
|
|
|
glog.Infof("ignoring add for ingress %v based on annotation %v with value %v", ing.Name, class.IngressKey, a)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
n.readSecrets(ing)
|
|
|
|
}
|
2017-08-28 16:06:58 +00:00
|
|
|
|
2017-11-13 01:43:28 +00:00
|
|
|
if n.cfg.EnableSSLChainCompletion {
|
|
|
|
go wait.Until(n.checkSSLChainIssues, 60*time.Second, n.stopCh)
|
|
|
|
}
|
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
if n.syncStatus != nil {
|
2018-01-02 20:43:25 +00:00
|
|
|
go n.syncStatus.Run()
|
2017-11-05 01:18:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
go wait.Until(n.checkMissingSecrets, 30*time.Second, n.stopCh)
|
2016-11-29 01:39:17 +00:00
|
|
|
|
|
|
|
done := make(chan error, 1)
|
2016-11-10 22:56:29 +00:00
|
|
|
cmd := exec.Command(n.binary, "-c", cfgPath)
|
2017-08-28 16:06:58 +00:00
|
|
|
|
|
|
|
// put nginx in another process group to prevent it
|
|
|
|
// to receive signals meant for the controller
|
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
|
|
Setpgid: true,
|
|
|
|
Pgid: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
glog.Info("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
|
|
|
|
n.syncQueue.Enqueue(&extensions.Ingress{})
|
2017-08-28 16:06:58 +00:00
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case err := <-done:
|
|
|
|
if n.isShuttingDown {
|
2016-11-29 01:39:17 +00:00
|
|
|
break
|
|
|
|
}
|
2017-09-28 14:52:12 +00:00
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
// 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
|
|
|
|
// reflected in the nginx configuration which can lead to confusion and report
|
|
|
|
// issues because of this behavior.
|
|
|
|
// To avoid this issue we restart nginx in case of errors.
|
|
|
|
if process.IsRespawnIfRequired(err) {
|
|
|
|
process.WaitUntilPortIsAvailable(n.cfg.ListenPorts.HTTP)
|
|
|
|
// release command resources
|
|
|
|
cmd.Process.Release()
|
|
|
|
cmd = exec.Command(n.binary, "-c", cfgPath)
|
|
|
|
// start a new nginx master process if the controller is not being stopped
|
|
|
|
n.start(cmd)
|
2017-09-28 14:52:12 +00:00
|
|
|
}
|
2017-11-05 01:18:28 +00:00
|
|
|
case <-n.stopCh:
|
|
|
|
break
|
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()
|
|
|
|
|
|
|
|
// 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()
|
|
|
|
}
|
2017-08-28 16:06:58 +00:00
|
|
|
|
|
|
|
// Send stop signal to Nginx
|
|
|
|
glog.Info("stopping NGINX process...")
|
|
|
|
cmd := exec.Command(n.binary, "-c", cfgPath, "-s", "quit")
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the Nginx process disappear
|
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 {
|
2017-11-05 01:18:28 +00:00
|
|
|
if !process.IsNginxRunning() {
|
|
|
|
glog.Info("NGINX process has stopped")
|
|
|
|
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 {
|
|
|
|
glog.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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-10 22:56:29 +00:00
|
|
|
// 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 {
|
2017-04-27 02:34:36 +00:00
|
|
|
if len(cfg) == 0 {
|
|
|
|
return fmt.Errorf("invalid nginx configuration (empty)")
|
|
|
|
}
|
2016-11-10 22:56:29 +00:00
|
|
|
tmpfile, err := ioutil.TempFile("", "nginx-cfg")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tmpfile.Close()
|
2017-03-03 07:17:32 +00:00
|
|
|
err = ioutil.WriteFile(tmpfile.Name(), cfg, 0644)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-11-24 00:14:14 +00:00
|
|
|
out, err := exec.Command(n.binary, "-t", "-c", tmpfile.Name()).CombinedOutput()
|
2017-05-16 20:06:33 +00:00
|
|
|
if err != nil {
|
2016-11-10 22:56:29 +00:00
|
|
|
// this error is different from the rest because it must be clear why nginx is not working
|
2016-11-16 18:24:26 +00:00
|
|
|
oe := fmt.Sprintf(`
|
2016-11-10 22:56:29 +00:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
Error: %v
|
|
|
|
%v
|
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
`, err, string(out))
|
2016-11-16 18:24:26 +00:00
|
|
|
return errors.New(oe)
|
2016-11-10 22:56:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
os.Remove(tmpfile.Name())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-02-07 18:13:08 +00:00
|
|
|
// SetConfig sets the configured configmap
|
2017-09-17 18:42:31 +00:00
|
|
|
func (n *NGINXController) SetConfig(cmap *apiv1.ConfigMap) {
|
2017-01-20 22:37:59 +00:00
|
|
|
n.configmap = cmap
|
2017-11-05 01:18:28 +00:00
|
|
|
n.IsProxyProtocolEnabled = false
|
2017-09-07 16:15:36 +00:00
|
|
|
|
|
|
|
m := map[string]string{}
|
|
|
|
if cmap != nil {
|
|
|
|
m = cmap.Data
|
2017-04-09 23:51:38 +00:00
|
|
|
}
|
|
|
|
|
2017-09-07 16:15:36 +00:00
|
|
|
val, ok := m["use-proxy-protocol"]
|
2017-04-09 23:51:38 +00:00
|
|
|
if ok {
|
|
|
|
b, err := strconv.ParseBool(val)
|
|
|
|
if err == nil {
|
2017-11-05 01:18:28 +00:00
|
|
|
n.IsProxyProtocolEnabled = b
|
2017-04-09 23:51:38 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-26 03:46:17 +00:00
|
|
|
|
2017-10-08 22:37:19 +00:00
|
|
|
c := ngx_template.ReadConfig(m)
|
|
|
|
if c.SSLSessionTicketKey != "" {
|
|
|
|
d, err := base64.StdEncoding.DecodeString(c.SSLSessionTicketKey)
|
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("unexpected error decoding key ssl-session-ticket-key: %v", err)
|
|
|
|
c.SSLSessionTicketKey = ""
|
|
|
|
}
|
|
|
|
ioutil.WriteFile("/etc/nginx/tickets.key", d, 0644)
|
|
|
|
}
|
|
|
|
|
|
|
|
n.backendDefaults = c.Backend
|
2017-01-20 22:37:59 +00:00
|
|
|
}
|
|
|
|
|
2017-11-07 16:36:51 +00:00
|
|
|
// OnUpdate is called periodically by syncQueue to keep the configuration in sync.
|
|
|
|
//
|
|
|
|
// 1. converts configmap configuration to custom configuration object
|
|
|
|
// 2. write the custom template (the complexity depends on the implementation)
|
|
|
|
// 3. write the configuration file
|
2016-11-10 22:56:29 +00:00
|
|
|
//
|
|
|
|
// returning nill implies the backend will be reloaded.
|
|
|
|
// if an error is returned means requeue the update
|
2017-06-11 19:56:40 +00:00
|
|
|
func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
2017-01-20 22:37:59 +00:00
|
|
|
cfg := ngx_template.ReadConfig(n.configmap.Data)
|
2017-04-11 14:47:49 +00:00
|
|
|
cfg.Resolver = n.resolver
|
2017-03-10 13:01:26 +00:00
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
servers := []*TCPServer{}
|
2017-05-26 18:25:06 +00:00
|
|
|
for _, pb := range ingressCfg.PassthroughBackends {
|
|
|
|
svc := pb.Service
|
|
|
|
if svc == nil {
|
|
|
|
glog.Warningf("missing service for PassthroughBackends %v", pb.Backend)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: Allow PassthroughBackends to specify they support proxy-protocol
|
2017-11-05 01:18:28 +00:00
|
|
|
servers = append(servers, &TCPServer{
|
2017-05-26 18:25:06 +00:00
|
|
|
Hostname: pb.Hostname,
|
|
|
|
IP: svc.Spec.ClusterIP,
|
|
|
|
Port: port,
|
|
|
|
ProxyProtocol: false,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-01-02 17:48:42 +00:00
|
|
|
if n.cfg.EnableSSLPassthrough {
|
2017-11-05 01:18:28 +00:00
|
|
|
n.Proxy.ServerList = servers
|
2017-08-21 01:34:31 +00:00
|
|
|
}
|
2017-05-26 18:25:06 +00:00
|
|
|
|
2017-03-10 13:01:26 +00:00
|
|
|
// we need to check if the status module configuration changed
|
2017-03-12 15:27:05 +00:00
|
|
|
if cfg.EnableVtsStatus {
|
|
|
|
n.setupMonitor(vtsStatusModule)
|
|
|
|
} else {
|
|
|
|
n.setupMonitor(defaultStatusModule)
|
|
|
|
}
|
2016-11-10 22:56:29 +00:00
|
|
|
|
2017-08-15 07:14:08 +00:00
|
|
|
// NGINX cannot resize the hash tables used to store server names.
|
2016-11-10 22:56:29 +00:00
|
|
|
// For this reason we check if the defined size defined is correct
|
|
|
|
// for the FQDN defined in the ingress rules adjusting the value
|
|
|
|
// if is required.
|
|
|
|
// 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
|
2017-08-19 21:13:02 +00:00
|
|
|
redirectServers := make(map[string]string)
|
2017-08-16 07:02:30 +00:00
|
|
|
for _, srv := range ingressCfg.Servers {
|
|
|
|
if longestName < len(srv.Hostname) {
|
|
|
|
longestName = len(srv.Hostname)
|
|
|
|
}
|
|
|
|
serverNameBytes += len(srv.Hostname)
|
2017-08-19 21:13:02 +00:00
|
|
|
if srv.RedirectFromToWWW {
|
|
|
|
var n string
|
|
|
|
if strings.HasPrefix(srv.Hostname, "www.") {
|
|
|
|
n = strings.TrimLeft(srv.Hostname, "www.")
|
|
|
|
} else {
|
|
|
|
n = fmt.Sprintf("www.%v", srv.Hostname)
|
|
|
|
}
|
2017-08-22 20:16:59 +00:00
|
|
|
glog.V(3).Infof("creating redirect from %v to %v", srv.Hostname, n)
|
2017-08-19 21:13:02 +00:00
|
|
|
if _, ok := redirectServers[n]; !ok {
|
|
|
|
found := false
|
|
|
|
for _, esrv := range ingressCfg.Servers {
|
|
|
|
if esrv.Hostname == n {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
|
|
|
redirectServers[n] = srv.Hostname
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-08-16 07:02:30 +00:00
|
|
|
}
|
2017-04-14 23:59:10 +00:00
|
|
|
if cfg.ServerNameHashBucketSize == 0 {
|
2017-08-16 07:02:30 +00:00
|
|
|
nameHashBucketSize := nginxHashBucketSize(longestName)
|
2017-04-14 23:59:10 +00:00
|
|
|
glog.V(3).Infof("adjusting ServerNameHashBucketSize variable to %v", nameHashBucketSize)
|
2016-11-10 22:56:29 +00:00
|
|
|
cfg.ServerNameHashBucketSize = nameHashBucketSize
|
|
|
|
}
|
2017-05-24 00:02:12 +00:00
|
|
|
serverNameHashMaxSize := nextPowerOf2(serverNameBytes)
|
|
|
|
if cfg.ServerNameHashMaxSize < serverNameHashMaxSize {
|
2017-04-14 23:59:10 +00:00
|
|
|
glog.V(3).Infof("adjusting ServerNameHashMaxSize variable to %v", serverNameHashMaxSize)
|
2016-11-10 22:56:29 +00:00
|
|
|
cfg.ServerNameHashMaxSize = serverNameHashMaxSize
|
|
|
|
}
|
|
|
|
|
2017-01-19 02:31:33 +00:00
|
|
|
// the limit of open files is per worker process
|
|
|
|
// and we leave some room to avoid consuming all the FDs available
|
2017-03-28 16:39:44 +00:00
|
|
|
wp, err := strconv.Atoi(cfg.WorkerProcesses)
|
2017-06-12 20:20:40 +00:00
|
|
|
glog.V(3).Infof("number of worker processes: %v", wp)
|
2017-03-28 16:39:44 +00:00
|
|
|
if err != nil {
|
|
|
|
wp = 1
|
|
|
|
}
|
|
|
|
maxOpenFiles := (sysctlFSFileMax() / wp) - 1024
|
2017-06-12 20:20:40 +00:00
|
|
|
glog.V(3).Infof("maximum number of open file descriptors : %v", sysctlFSFileMax())
|
|
|
|
if maxOpenFiles < 1024 {
|
2017-03-30 13:10:47 +00:00
|
|
|
// this means the value of RLIMIT_NOFILE is too low.
|
|
|
|
maxOpenFiles = 1024
|
|
|
|
}
|
2017-01-19 02:31:33 +00:00
|
|
|
|
2017-02-07 18:13:08 +00:00
|
|
|
setHeaders := map[string]string{}
|
|
|
|
if cfg.ProxySetHeaders != "" {
|
2017-11-27 22:22:59 +00:00
|
|
|
cmap, exists, err := n.listers.ConfigMap.GetByKey(cfg.ProxySetHeaders)
|
2017-02-07 18:13:08 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("unexpected error reading configmap %v: %v", cfg.ProxySetHeaders, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
2017-09-17 18:42:31 +00:00
|
|
|
setHeaders = cmap.(*apiv1.ConfigMap).Data
|
2017-02-07 18:13:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-18 10:21:03 +00:00
|
|
|
addHeaders := map[string]string{}
|
|
|
|
if cfg.AddHeaders != "" {
|
2017-11-27 22:22:59 +00:00
|
|
|
cmap, exists, err := n.listers.ConfigMap.GetByKey(cfg.AddHeaders)
|
2017-05-18 10:21:03 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("unexpected error reading configmap %v: %v", cfg.AddHeaders, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
2017-09-17 18:42:31 +00:00
|
|
|
addHeaders = cmap.(*apiv1.ConfigMap).Data
|
2017-05-18 10:21:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-08 13:41:55 +00:00
|
|
|
sslDHParam := ""
|
|
|
|
if cfg.SSLDHParam != "" {
|
|
|
|
secretName := cfg.SSLDHParam
|
2017-11-27 22:22:59 +00:00
|
|
|
s, exists, err := n.listers.Secret.GetByKey(secretName)
|
2017-03-08 13:41:55 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("unexpected error reading secret %v: %v", secretName, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if exists {
|
2017-09-17 18:42:31 +00:00
|
|
|
secret := s.(*apiv1.Secret)
|
2017-03-08 13:41:55 +00:00
|
|
|
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
|
|
|
|
|
|
|
dh, ok := secret.Data["dhparam.pem"]
|
|
|
|
if ok {
|
|
|
|
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh)
|
|
|
|
if err != nil {
|
|
|
|
glog.Warningf("unexpected error adding or updating dhparam %v file: %v", nsSecName, err)
|
|
|
|
} else {
|
|
|
|
sslDHParam = pemFileName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg.SSLDHParam = sslDHParam
|
|
|
|
|
2017-11-12 13:33:18 +00:00
|
|
|
// disable features are not available in some platforms
|
|
|
|
switch runtime.GOARCH {
|
|
|
|
case "arm", "arm64", "ppc64le":
|
|
|
|
cfg.EnableModsecurity = false
|
|
|
|
case "s390x":
|
|
|
|
cfg.EnableModsecurity = false
|
|
|
|
cfg.EnableBrotli = false
|
|
|
|
}
|
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
tc := ngx_config.TemplateConfig{
|
2017-08-21 01:34:31 +00:00
|
|
|
ProxySetHeaders: setHeaders,
|
|
|
|
AddHeaders: addHeaders,
|
|
|
|
MaxOpenFiles: maxOpenFiles,
|
|
|
|
BacklogSize: sysctlSomaxconn(),
|
|
|
|
Backends: ingressCfg.Backends,
|
|
|
|
PassthroughBackends: ingressCfg.PassthroughBackends,
|
|
|
|
Servers: ingressCfg.Servers,
|
|
|
|
TCPBackends: ingressCfg.TCPEndpoints,
|
|
|
|
UDPBackends: ingressCfg.UDPEndpoints,
|
|
|
|
HealthzURI: ngxHealthPath,
|
|
|
|
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
|
|
|
|
Cfg: cfg,
|
|
|
|
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
|
|
|
RedirectServers: redirectServers,
|
2018-01-02 17:48:42 +00:00
|
|
|
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
|
2017-11-05 01:18:28 +00:00
|
|
|
ListenPorts: n.cfg.ListenPorts,
|
|
|
|
PublishService: n.GetPublishService(),
|
2017-08-03 14:51:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
content, err := n.t.Write(tc)
|
2017-06-11 19:56:40 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = n.testTemplate(content)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-11-06 01:22:49 +00:00
|
|
|
// executing diff can return exit code != 0
|
|
|
|
diffOutput, _ := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
|
2017-11-05 01:18:28 +00:00
|
|
|
|
|
|
|
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).
|
2017-11-05 23:11:33 +00:00
|
|
|
// Only remove the file when no error occurred.
|
2017-11-05 01:18:28 +00:00
|
|
|
os.Remove(tmpfile.Name())
|
|
|
|
}
|
|
|
|
}
|
2017-06-11 19:56:40 +00:00
|
|
|
|
|
|
|
err = ioutil.WriteFile(cfgPath, content, 0644)
|
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
|
|
|
}
|
|
|
|
|
2017-06-23 13:55:45 +00:00
|
|
|
o, err := exec.Command(n.binary, "-s", "reload", "-c", cfgPath).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
|
|
|
}
|
|
|
|
|
2017-04-19 02:29:51 +00:00
|
|
|
// nginxHashBucketSize computes the correct nginx hash_bucket_size for a hash with the given longest key
|
|
|
|
func nginxHashBucketSize(longestString int) int {
|
2017-10-13 13:55:03 +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
|
|
|
|
}
|