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"
|
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"
|
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"
|
2018-12-05 16:27:55 +00:00
|
|
|
"k8s.io/klog"
|
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"
|
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"
|
2018-11-20 20:29:20 +00:00
|
|
|
"k8s.io/ingress-nginx/internal/k8s"
|
2017-11-07 22:02:12 +00:00
|
|
|
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
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2018-11-16 20:52:46 +00:00
|
|
|
ngxHealthPath = "/healthz"
|
|
|
|
nginxStreamSocket = "/tmp/ingress-stream.sock"
|
2016-11-10 22:56:29 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-06-14 00:55:07 +00:00
|
|
|
tmplPath = "/etc/nginx/template/nginx.tmpl"
|
2016-11-10 22:56:29 +00:00
|
|
|
)
|
|
|
|
|
2017-10-06 20:33:32 +00:00
|
|
|
// NewNGINXController creates a new NGINX Ingress controller.
|
2018-07-07 17:46:18 +00:00
|
|
|
func NewNGINXController(config *Configuration, mc metric.Collector, fs file.Filesystem) *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
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
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
|
|
|
|
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,
|
2017-04-09 23:51:38 +00:00
|
|
|
}
|
|
|
|
|
2018-11-20 20:29:20 +00:00
|
|
|
pod, err := k8s.GetPodDetails(config.Client)
|
|
|
|
if err != nil {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Fatalf("unexpected error obtaining pod information: %v", err)
|
2018-11-20 20:29:20 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 13:46:20 +00:00
|
|
|
n.store = store.New(
|
|
|
|
config.EnableSSLChainCompletion,
|
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,
|
|
|
|
fs,
|
2018-06-04 21:48:30 +00:00
|
|
|
n.updateCh,
|
2018-11-20 20:29:20 +00:00
|
|
|
config.DynamicCertificatesEnabled,
|
|
|
|
pod)
|
2017-11-07 16:36:51 +00:00
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
n.syncQueue = task.NewTaskQueue(n.syncIngress)
|
|
|
|
|
2018-01-18 19:14:42 +00:00
|
|
|
n.annotations = annotations.NewAnnotationExtractor(n.store)
|
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,
|
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
|
|
|
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 {
|
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() {
|
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
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Errorf(`
|
2016-11-10 22:56:29 +00:00
|
|
|
-------------------------------------------------------------------------------
|
2018-06-13 18:15:45 +00:00
|
|
|
Error loading new template: %v
|
2016-11-10 22:56:29 +00:00
|
|
|
-------------------------------------------------------------------------------
|
|
|
|
`, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
n.t = template
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Info("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
|
|
|
}
|
|
|
|
|
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 {
|
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
|
|
|
|
2017-11-22 13:40:54 +00:00
|
|
|
if _, ok := fs.(filesystem.DefaultFs); !ok {
|
2018-06-16 20:22:59 +00:00
|
|
|
// do not setup watchers on tests
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = watch.NewFileWatcher(tmplPath, onTemplateChange)
|
|
|
|
if err != nil {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Fatalf("Error creating file watcher for %v: %v", tmplPath, 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() {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Infof("File %v changed. Reloading NGINX", 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
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
t *ngx_template.Template
|
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
|
|
|
|
|
|
|
fileSystem filesystem.Filesystem
|
2018-07-07 17:46:18 +00:00
|
|
|
|
|
|
|
metricCollector metric.Collector
|
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() {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Info("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
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
if n.syncStatus != nil {
|
2018-10-29 16:01:41 +00:00
|
|
|
go n.syncStatus.Run()
|
2017-11-05 01:18:28 +00:00
|
|
|
}
|
|
|
|
|
2018-06-14 00:55:07 +00:00
|
|
|
cmd := nginxExecCommand()
|
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()
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.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
|
2018-06-21 14:50:57 +00:00
|
|
|
n.syncQueue.EnqueueTask(task.GetDummyObject("initial-sync"))
|
2017-08-28 16:06:58 +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 {
|
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()
|
|
|
|
// start a new nginx master process if the controller is not being stopped
|
2018-06-14 00:55:07 +00:00
|
|
|
cmd = nginxExecCommand()
|
2018-03-08 14:58:54 +00:00
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
|
|
Setpgid: true,
|
|
|
|
Pgid: 0,
|
|
|
|
}
|
2017-11-05 01:18:28 +00:00
|
|
|
n.start(cmd)
|
2017-09-28 14:52:12 +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
|
|
|
|
}
|
2018-02-14 01:46:18 +00:00
|
|
|
if evt, ok := event.(store.Event); ok {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.V(3).Infof("Event %v received - object %v", evt.Type, 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:
|
|
|
|
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()
|
|
|
|
|
|
|
|
if n.syncQueue.IsShuttingDown() {
|
|
|
|
return fmt.Errorf("shutdown already in progress")
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Info("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
|
|
|
|
2018-06-12 13:04:26 +00:00
|
|
|
// send stop signal to NGINX
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Info("Stopping NGINX process")
|
2018-06-14 00:55:07 +00:00
|
|
|
cmd := nginxExecCommand("-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 {
|
2017-11-05 01:18:28 +00:00
|
|
|
if !process.IsNginxRunning() {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Info("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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 {
|
2018-08-30 15:09:04 +00:00
|
|
|
return fmt.Errorf("invalid NGINX configuration (empty)")
|
2017-04-27 02:34:36 +00:00
|
|
|
}
|
2016-11-10 22:56:29 +00:00
|
|
|
tmpfile, err := ioutil.TempFile("", "nginx-cfg")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tmpfile.Close()
|
2018-06-12 12:40:40 +00:00
|
|
|
err = ioutil.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser)
|
2017-03-03 07:17:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-06-14 00:55:07 +00:00
|
|
|
out, err := nginxTestCommand(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
|
|
|
|
}
|
|
|
|
|
2018-06-13 18:15:45 +00:00
|
|
|
// 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.
|
2017-06-11 19:56:40 +00:00
|
|
|
func (n *NGINXController) OnUpdate(ingressCfg ingress.Configuration) error {
|
2018-01-18 19:14:42 +00:00
|
|
|
cfg := n.store.GetBackendConfiguration()
|
2017-04-11 14:47:49 +00:00
|
|
|
cfg.Resolver = n.resolver
|
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
|
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.") {
|
2018-05-07 11:29:04 +00:00
|
|
|
n = strings.TrimPrefix(srv.Hostname, "www.")
|
2017-08-19 21:13:02 +00:00
|
|
|
} else {
|
|
|
|
n = fmt.Sprintf("www.%v", srv.Hostname)
|
|
|
|
}
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.V(3).Infof("Creating redirect from %q to %q", 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)
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.V(3).Infof("Adjusting ServerNameHashBucketSize variable to %d", 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 {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.V(3).Infof("Adjusting ServerNameHashMaxSize variable to %d", 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)
|
|
|
|
klog.V(3).Infof("Number of worker processes: %d", wp)
|
|
|
|
if err != nil {
|
|
|
|
wp = 1
|
|
|
|
}
|
|
|
|
maxOpenFiles := (sysctlFSFileMax() / wp) - 1024
|
|
|
|
klog.V(3).Infof("Maximum number of open file descriptors: %d", maxOpenFiles)
|
|
|
|
if maxOpenFiles < 1024 {
|
|
|
|
// this means the value of RLIMIT_NOFILE is too low.
|
|
|
|
maxOpenFiles = 1024
|
|
|
|
}
|
|
|
|
klog.V(3).Infof("Adjusting MaxWorkerOpenFiles variable to %d", maxOpenFiles)
|
|
|
|
cfg.MaxWorkerOpenFiles = maxOpenFiles
|
2017-03-28 16:39:44 +00:00
|
|
|
}
|
2018-12-27 16:24:09 +00:00
|
|
|
|
|
|
|
if cfg.MaxWorkerConnections == 0 {
|
|
|
|
klog.V(3).Infof("Adjusting MaxWorkerConnections variable to %d", cfg.MaxWorkerOpenFiles)
|
|
|
|
cfg.MaxWorkerConnections = cfg.MaxWorkerOpenFiles
|
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)
|
2017-02-07 18:13:08 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 19:14:42 +00:00
|
|
|
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)
|
2017-05-18 10:21:03 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 19:14:42 +00:00
|
|
|
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)
|
2017-03-08 13:41:55 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 19:14:42 +00:00
|
|
|
nsSecName := strings.Replace(secretName, "/", "-", -1)
|
|
|
|
|
|
|
|
dh, ok := secret.Data["dhparam.pem"]
|
|
|
|
if ok {
|
|
|
|
pemFileName, err := ssl.AddOrUpdateDHParam(nsSecName, dh, n.fileSystem)
|
|
|
|
if err != nil {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Warningf("Error adding or updating dhparam file %v: %v", nsSecName, err)
|
2018-01-18 19:14:42 +00:00
|
|
|
} else {
|
|
|
|
sslDHParam = pemFileName
|
2017-03-08 13:41:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg.SSLDHParam = sslDHParam
|
|
|
|
|
2017-11-05 01:18:28 +00:00
|
|
|
tc := ngx_config.TemplateConfig{
|
2018-10-09 22:36:10 +00:00
|
|
|
ProxySetHeaders: setHeaders,
|
|
|
|
AddHeaders: addHeaders,
|
|
|
|
BacklogSize: sysctlSomaxconn(),
|
|
|
|
Backends: ingressCfg.Backends,
|
|
|
|
PassthroughBackends: ingressCfg.PassthroughBackends,
|
|
|
|
Servers: ingressCfg.Servers,
|
2018-11-16 16:48:47 +00:00
|
|
|
TCPBackends: ingressCfg.TCPEndpoints,
|
|
|
|
UDPBackends: ingressCfg.UDPEndpoints,
|
2018-10-09 22:36:10 +00:00
|
|
|
HealthzURI: ngxHealthPath,
|
|
|
|
CustomErrors: len(cfg.CustomHTTPErrors) > 0,
|
|
|
|
Cfg: cfg,
|
|
|
|
IsIPV6Enabled: n.isIPV6Enabled && !cfg.DisableIpv6,
|
|
|
|
NginxStatusIpv4Whitelist: cfg.NginxStatusIpv4Whitelist,
|
|
|
|
NginxStatusIpv6Whitelist: cfg.NginxStatusIpv6Whitelist,
|
|
|
|
RedirectServers: redirectServers,
|
|
|
|
IsSSLPassthroughEnabled: n.cfg.EnableSSLPassthrough,
|
|
|
|
ListenPorts: n.cfg.ListenPorts,
|
|
|
|
PublishService: n.GetPublishService(),
|
|
|
|
DynamicCertificatesEnabled: n.cfg.DynamicCertificatesEnabled,
|
2018-12-04 19:59:54 +00:00
|
|
|
EnableMetrics: n.cfg.EnableMetrics,
|
2017-08-03 14:51:39 +00:00
|
|
|
}
|
|
|
|
|
2018-07-07 17:46:18 +00:00
|
|
|
tc.Cfg.Checksum = ingressCfg.ConfigurationChecksum
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2018-06-21 22:15:18 +00:00
|
|
|
if cfg.EnableOpentracing {
|
|
|
|
err := createOpentracingCfg(cfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-11 19:56:40 +00:00
|
|
|
err = n.testTemplate(content)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
if klog.V(2) {
|
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
|
|
|
|
}
|
|
|
|
|
2018-12-12 01:08:02 +00:00
|
|
|
diffOutput, err := exec.Command("diff", "-u", cfgPath, tmpfile.Name()).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
klog.Warningf("Failed to executing diff command: %v", err)
|
|
|
|
}
|
2017-11-05 01:18:28 +00:00
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Infof("NGINX configuration diff:\n%v", 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
|
|
|
}
|
|
|
|
|
2018-06-14 00:55:07 +00:00
|
|
|
o, err := nginxExecCommand("-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
|
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Info("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
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.V(3).Infof("Handling connection from remote address %s to local %s", conn.RemoteAddr(), 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
|
2018-06-05 13:51:22 +00:00
|
|
|
copyOfServer.SSLCert = ingress.SSLCert{PemFileName: copyOfServer.SSLCert.PemFileName}
|
2018-06-04 21:48:30 +00:00
|
|
|
clearedServers = append(clearedServers, ©OfServer)
|
|
|
|
}
|
|
|
|
config.Servers = clearedServers
|
|
|
|
}
|
|
|
|
|
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{}
|
2018-11-27 14:53:51 +00:00
|
|
|
copyOfRunningConfig.ControllerPodsCount = 0
|
|
|
|
copyOfPcfg.ControllerPodsCount = 0
|
2018-03-18 13:13:41 +00:00
|
|
|
|
2018-06-04 21:48:30 +00:00
|
|
|
if n.cfg.DynamicCertificatesEnabled {
|
|
|
|
clearCertificates(©OfRunningConfig)
|
|
|
|
clearCertificates(©OfPcfg)
|
|
|
|
}
|
|
|
|
|
2018-03-18 13:13:41 +00:00
|
|
|
return copyOfRunningConfig.Equal(©OfPcfg)
|
|
|
|
}
|
|
|
|
|
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.
|
2018-06-04 21:48:30 +00:00
|
|
|
func configureDynamically(pcfg *ingress.Configuration, port int, isDynamicCertificatesEnabled bool) error {
|
2018-03-22 02:47:39 +00:00
|
|
|
backends := make([]*ingress.Backend, len(pcfg.Backends))
|
|
|
|
|
|
|
|
for i, backend := range pcfg.Backends {
|
2018-08-03 13:50:53 +00:00
|
|
|
var service *apiv1.Service
|
|
|
|
if backend.Service != nil {
|
|
|
|
service = &apiv1.Service{Spec: backend.Service.Spec}
|
|
|
|
}
|
2018-04-24 18:02:52 +00:00
|
|
|
luaBackend := &ingress.Backend{
|
2018-09-18 18:05:32 +00:00
|
|
|
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,
|
2018-04-24 18:02:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var endpoints []ingress.Endpoint
|
|
|
|
for _, endpoint := range backend.Endpoints {
|
|
|
|
endpoints = append(endpoints, ingress.Endpoint{
|
2018-10-09 01:29:58 +00:00
|
|
|
Address: endpoint.Address,
|
|
|
|
Port: endpoint.Port,
|
2018-04-24 18:02:52 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
luaBackend.Endpoints = endpoints
|
|
|
|
backends[i] = luaBackend
|
2018-03-22 02:47:39 +00:00
|
|
|
}
|
|
|
|
|
2018-06-04 21:48:30 +00:00
|
|
|
url := fmt.Sprintf("http://localhost:%d/configuration/backends", port)
|
|
|
|
err := post(url, backends)
|
2018-03-18 13:13:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-16 20:33:56 +00:00
|
|
|
streams := make([]ingress.Backend, 0)
|
2018-11-16 16:48:47 +00:00
|
|
|
for _, ep := range pcfg.TCPEndpoints {
|
|
|
|
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 04:35:17 +00:00
|
|
|
Service: ep.Service,
|
2018-11-16 16:48:47 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
for _, ep := range pcfg.UDPEndpoints {
|
|
|
|
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 04:35:17 +00:00
|
|
|
Service: ep.Service,
|
2018-11-16 16:48:47 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-11-16 20:52:46 +00:00
|
|
|
err = updateStreamConfiguration(streams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:53:51 +00:00
|
|
|
url = fmt.Sprintf("http://localhost:%d/configuration/general", port)
|
|
|
|
err = post(url, &ingress.GeneralConfig{
|
|
|
|
ControllerPodsCount: pcfg.ControllerPodsCount,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-11-16 20:52:46 +00:00
|
|
|
if isDynamicCertificatesEnabled {
|
|
|
|
err = configureCertificates(pcfg, port)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateStreamConfiguration(streams []ingress.Backend) error {
|
|
|
|
conn, err := net.Dial("unix", nginxStreamSocket)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
2018-11-16 16:48:47 +00:00
|
|
|
buf, err := json.Marshal(streams)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = conn.Write(buf)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-11-16 20:33:56 +00:00
|
|
|
_, err = fmt.Fprintf(conn, "\r\n")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-06-04 21:48:30 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// configureCertificates JSON encodes certificates and POSTs it to an internal HTTP endpoint
|
|
|
|
// that is handled by Lua
|
|
|
|
func configureCertificates(pcfg *ingress.Configuration, port int) error {
|
|
|
|
var servers []*ingress.Server
|
|
|
|
|
|
|
|
for _, server := range pcfg.Servers {
|
|
|
|
servers = append(servers, &ingress.Server{
|
|
|
|
Hostname: server.Hostname,
|
|
|
|
SSLCert: ingress.SSLCert{
|
|
|
|
PemCertKey: server.SSLCert.PemCertKey,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
url := fmt.Sprintf("http://localhost:%d/configuration/servers", port)
|
2018-10-30 23:46:48 +00:00
|
|
|
return post(url, servers)
|
2018-06-04 21:48:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func post(url string, data interface{}) error {
|
|
|
|
buf, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.V(2).Infof("Posting to %s", url)
|
2018-03-18 13:13:41 +00:00
|
|
|
resp, err := http.Post(url, "application/json", bytes.NewReader(buf))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if err := resp.Body.Close(); err != nil {
|
2018-12-05 16:27:55 +00:00
|
|
|
klog.Warningf("Error while closing response body:\n%v", err)
|
2018-03-18 13:13:41 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusCreated {
|
2018-08-30 15:09:04 +00:00
|
|
|
return fmt.Errorf("unexpected error code: %d", resp.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 }}",
|
|
|
|
"param": {{ .JaegerSamplerParam }}
|
|
|
|
},
|
|
|
|
"reporter": {
|
|
|
|
"localAgentHostPort": "{{ .JaegerCollectorHost }}:{{ .JaegerCollectorPort }}"
|
|
|
|
}
|
|
|
|
}`
|
|
|
|
|
|
|
|
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 != "" {
|
|
|
|
tmpl, err = template.New("jarger").Parse(jaegerTmpl)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tmpl, _ = template.New("empty").Parse("{}")
|
|
|
|
}
|
|
|
|
|
|
|
|
tmplBuf := bytes.NewBuffer(make([]byte, 0))
|
|
|
|
err = tmpl.Execute(tmplBuf, cfg)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.WriteFile("/etc/nginx/opentracing.json", tmplBuf.Bytes(), file.ReadWriteByUser)
|
|
|
|
}
|