Skip to content

Commit 9867801

Browse files
committed
Add health checking from NGINX to individual endpoints.
1 parent 0b1fc38 commit 9867801

File tree

6 files changed

+132
-1
lines changed

6 files changed

+132
-1
lines changed

controllers/nginx/configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ The default mime type list to compress is: `application/atom+xml application/jav
319319

320320
**use-http2:** Enables or disables [HTTP/2](http://nginx.org/en/docs/http/ngx_http_v2_module.html) support in secure connections.
321321

322+
**use-upstream-health-checks:** Enables or disables the use of upstream health checks provided by the [nginx_upstream_check_module](https://github.com/yaoweibin/nginx_upstream_check_module) module. If enabled, NGINX will do health checking based on the `readinessProbe` in the pod definition.
322323

323324
**use-proxy-protocol:** Enables or disables the [PROXY protocol](https://www.nginx.com/resources/admin-guide/proxy-protocol/) to receive client connection (real IP address) information passed through proxy servers and load balancers such as HAProxy and Amazon Elastic Load Balancer (ELB).
324325

controllers/nginx/pkg/config/config.go

+6
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,11 @@ type Configuration struct {
227227
// Defines the number of worker processes. By default auto means number of available CPU cores
228228
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
229229
WorkerProcesses int `json:"worker-processes,omitempty"`
230+
231+
// Enables or disables the use of upstream health checks provided by the
232+
// nginx_upstream_check_module module. If enabled, NGINX will do health checking
233+
// based on the readinessProbe in the pod definition.
234+
UseUpstreamHealthChecks bool `json:"use-upstream-health-checks"`
230235
}
231236

232237
// NewDefault returns the default nginx configuration
@@ -260,6 +265,7 @@ func NewDefault() Configuration {
260265
WorkerProcesses: runtime.NumCPU(),
261266
VtsStatusZoneSize: "10m",
262267
UseHTTP2: true,
268+
UseUpstreamHealthChecks: false,
263269
Backend: defaults.Backend{
264270
ProxyBodySize: bodySize,
265271
ProxyConnectTimeout: 5,

controllers/nginx/rootfs/etc/nginx/template/nginx.tmpl

+13
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,13 @@ http {
188188
{{ end }}
189189
{{ range $server := $upstream.Endpoints }}server {{ $server.Address }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }};
190190
{{ end }}
191+
192+
{{ if $cfg.UseUpstreamHealthChecks -}}
193+
{{ if $upstream.UpstreamCheck -}}
194+
check interval={{ $upstream.UpstreamCheck.IntervalMillis }} rise={{ $upstream.UpstreamCheck.Rise }} fall={{ $upstream.UpstreamCheck.Fall }} timeout={{ $upstream.UpstreamCheck.TimeoutMillis }} port={{ $upstream.UpstreamCheck.Port }} type=http;
195+
check_http_send "{{ $upstream.UpstreamCheck.HttpSend }}";
196+
{{- end }}
197+
{{- end }}
191198
}
192199
{{ end }}
193200

@@ -365,6 +372,12 @@ http {
365372
access_log off;
366373
return 200;
367374
}
375+
376+
{{ if $cfg.UseUpstreamHealthChecks }}
377+
location /upstream_status {
378+
check_status html;
379+
}
380+
{{ end }}
368381

369382
location /nginx_status {
370383
{{ if $cfg.EnableVtsStatus }}

core/pkg/ingress/controller/controller.go

+93-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ limitations under the License.
1717
package controller
1818

1919
import (
20+
"bytes"
2021
"fmt"
22+
"net/http"
2123
"reflect"
2224
"sort"
2325
"strconv"
@@ -35,6 +37,7 @@ import (
3537
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
3638
"k8s.io/kubernetes/pkg/client/record"
3739
"k8s.io/kubernetes/pkg/fields"
40+
"k8s.io/kubernetes/pkg/labels"
3841
"k8s.io/kubernetes/pkg/util/flowcontrol"
3942
"k8s.io/kubernetes/pkg/util/intstr"
4043

@@ -66,7 +69,8 @@ const (
6669

6770
var (
6871
// list of ports that cannot be used by TCP or UDP services
69-
reservedPorts = []string{"80", "443", "8181", "18080"}
72+
reservedPorts = []string{"80", "443", "8181", "18080"}
73+
httpSendReplacer = strings.NewReplacer("\r", "\\r", "\n", "\\n", "\"", "\\\"")
7074
)
7175

7276
// GenericController holds the boilerplate code required to build an Ingress controlller.
@@ -78,12 +82,14 @@ type GenericController struct {
7882
svcController *cache.Controller
7983
secrController *cache.Controller
8084
mapController *cache.Controller
85+
podController *cache.Controller
8186

8287
ingLister cache_store.StoreToIngressLister
8388
svcLister cache.StoreToServiceLister
8489
endpLister cache.StoreToEndpointsLister
8590
secrLister cache_store.StoreToSecretsLister
8691
mapLister cache_store.StoreToConfigmapLister
92+
podLister cache.StoreToPodLister
8793

8894
annotations annotationExtractor
8995

@@ -302,6 +308,19 @@ func newIngressController(config *Configuration) *GenericController {
302308
glog.Warning("Update of ingress status is disabled (flag --update-status=false was specified)")
303309
}
304310

311+
ic.podLister.Indexer, ic.podController = cache.NewIndexerInformer(
312+
cache.NewListWatchFromClient(ic.cfg.Client.Core().RESTClient(), "pods", ic.cfg.Namespace, fields.Everything()),
313+
&api.Pod{},
314+
ic.cfg.ResyncPeriod,
315+
cache.ResourceEventHandlerFuncs{},
316+
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
317+
318+
ic.syncStatus = status.NewStatusSyncer(status.Config{
319+
Client: config.Client,
320+
PublishService: ic.cfg.PublishService,
321+
IngressLister: ic.ingLister,
322+
})
323+
305324
ic.annotations = newAnnotationExtractor(ic)
306325

307326
return &ic
@@ -312,6 +331,7 @@ func (ic *GenericController) controllersInSync() bool {
312331
ic.svcController.HasSynced() &&
313332
ic.endpController.HasSynced() &&
314333
ic.secrController.HasSynced() &&
334+
ic.podController.HasSynced() &&
315335
ic.mapController.HasSynced()
316336
}
317337

@@ -746,13 +766,84 @@ func (ic *GenericController) createUpstreams(data []interface{}) map[string]*ing
746766
continue
747767
}
748768
upstreams[name].Endpoints = endp
769+
770+
probe, err := ic.findProbeForService(svcKey, &path.Backend.ServicePort)
771+
if err != nil {
772+
glog.Errorf("Failed to check for readinessProbe for %v: %v", name, err)
773+
}
774+
if probe != nil {
775+
check, err := ic.getUpstreamCheckForProbe(probe)
776+
if err != nil {
777+
glog.Errorf("Failed to create health check for probe: %v", err)
778+
} else {
779+
upstreams[name].UpstreamCheck = check
780+
}
781+
}
782+
749783
}
750784
}
751785
}
752786

753787
return upstreams
754788
}
755789

790+
func (ic *GenericController) findProbeForService(svcKey string, servicePort *intstr.IntOrString) (*api.Probe, error) {
791+
svcObj, svcExists, err := ic.svcLister.Indexer.GetByKey(svcKey)
792+
if err != nil {
793+
return nil, fmt.Errorf("error getting service %v from the cache: %v", svcKey, err)
794+
}
795+
796+
if !svcExists {
797+
err = fmt.Errorf("service %v does not exists", svcKey)
798+
return nil, err
799+
}
800+
801+
svc := svcObj.(*api.Service)
802+
803+
selector := labels.SelectorFromSet(svc.Spec.Selector)
804+
pods, err := ic.podLister.List(selector)
805+
if err != nil {
806+
return nil, fmt.Errorf("Failed to get pod listing: %v", err)
807+
}
808+
for _, pod := range pods {
809+
for _, container := range pod.Spec.Containers {
810+
for _, port := range container.Ports {
811+
if servicePort.Type == intstr.Int && int(port.ContainerPort) == servicePort.IntValue() ||
812+
servicePort.Type == intstr.String && port.Name == servicePort.String() {
813+
if container.ReadinessProbe != nil {
814+
if container.ReadinessProbe.HTTPGet == nil || container.ReadinessProbe.HTTPGet.Scheme != "HTTP" {
815+
continue
816+
}
817+
return container.ReadinessProbe, nil
818+
}
819+
}
820+
}
821+
}
822+
}
823+
return nil, nil
824+
}
825+
826+
func (ic *GenericController) getUpstreamCheckForProbe(probe *api.Probe) (*ingress.UpstreamCheck, error) {
827+
var headers http.Header = make(http.Header)
828+
for _, header := range probe.HTTPGet.HTTPHeaders {
829+
headers.Add(header.Name, header.Value)
830+
}
831+
headersWriter := new(bytes.Buffer)
832+
headers.Write(headersWriter)
833+
834+
httpSend := httpSendReplacer.Replace(
835+
fmt.Sprintf("GET %s HTTP/1.0\r\n%s\r\n", probe.HTTPGet.Path, string(headersWriter.Bytes())))
836+
837+
return &ingress.UpstreamCheck{
838+
HttpSend: httpSend,
839+
Port: probe.HTTPGet.Port.IntValue(),
840+
Rise: probe.SuccessThreshold,
841+
Fall: probe.FailureThreshold,
842+
TimeoutMillis: probe.TimeoutSeconds * 1000,
843+
IntervalMillis: probe.PeriodSeconds * 1000,
844+
}, nil
845+
}
846+
756847
// serviceEndpoints returns the upstream servers (endpoints) associated
757848
// to a service.
758849
func (ic *GenericController) serviceEndpoints(svcKey, backendPort string,
@@ -1018,6 +1109,7 @@ func (ic GenericController) Start() {
10181109
go ic.svcController.Run(ic.stopCh)
10191110
go ic.secrController.Run(ic.stopCh)
10201111
go ic.mapController.Run(ic.stopCh)
1112+
go ic.podController.Run(ic.stopCh)
10211113

10221114
go ic.secretQueue.Run(5*time.Second, ic.stopCh)
10231115
go ic.syncQueue.Run(5*time.Second, ic.stopCh)

core/pkg/ingress/types.go

+12
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ type BackendInfo struct {
102102
Repository string `json:"repository"`
103103
}
104104

105+
// UpstreamCheck is used to configure ingress health checks
106+
type UpstreamCheck struct {
107+
HttpSend string
108+
Port int
109+
IntervalMillis int32
110+
Fall int32
111+
Rise int32
112+
TimeoutMillis int32
113+
}
114+
105115
// Configuration holds the definition of all the parts required to describe all
106116
// ingresses reachable by the ingress controller (using a filter by namespace)
107117
type Configuration struct {
@@ -134,6 +144,8 @@ type Backend struct {
134144
Secure bool `json:"secure"`
135145
// Endpoints contains the list of endpoints currently running
136146
Endpoints []Endpoint `json:"endpoints"`
147+
148+
UpstreamCheck *UpstreamCheck
137149
}
138150

139151
// Endpoint describes a kubernetes endpoint in an backend

images/nginx-slim/build.sh

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export LUA_UPSTREAM_VERSION=0.06
2929
export MORE_HEADERS_VERSION=0.32
3030
export NGINX_DIGEST_AUTH=7955af9c77598c697ac292811914ce1e2b3b824c
3131
export NGINX_SUBSTITUTIONS=bc58cb11844bc42735bbaef7085ea86ace46d05b
32+
export UPSTREAM_CHECK_VERSION=d6341aeeb86911d4798fbceab35015c63178e66f
3233

3334
export BUILD_PATH=/tmp/build
3435

@@ -105,6 +106,8 @@ get_src 9b1d0075df787338bb607f14925886249bda60b6b3156713923d5d59e99a708b \
105106
get_src 8eabbcd5950fdcc718bb0ef9165206c2ed60f67cd9da553d7bc3e6fe4e338461 \
106107
"https://github.com/yaoweibin/ngx_http_substitutions_filter_module/archive/$NGINX_SUBSTITUTIONS.tar.gz"
107108

109+
get_src 35983b0b6ae812bee9fb4de37db6bf68cea68f7e82a9fc274ab29d574e321e98 \
110+
"https://github.com/yaoweibin/nginx_upstream_check_module/archive/$UPSTREAM_CHECK_VERSION.tar.gz"
108111

109112
#https://blog.cloudflare.com/optimizing-tls-over-tcp-to-reduce-latency/
110113
curl -sSL -o nginx__dynamic_tls_records.patch https://raw.githubusercontent.com/cloudflare/sslconfig/master/patches/nginx__1.11.5_dynamic_tls_records.patch
@@ -115,6 +118,9 @@ cd "$BUILD_PATH/nginx-$NGINX_VERSION"
115118
echo "Applying tls nginx patches..."
116119
patch -p1 < $BUILD_PATH/nginx__dynamic_tls_records.patch
117120

121+
echo "Applying nginx_upstream_check patch.."
122+
patch -p0 < $BUILD_PATH/nginx_upstream_check_module-$UPSTREAM_CHECK_VERSION/check_1.11.5+.patch
123+
118124
./configure \
119125
--prefix=/usr/share/nginx \
120126
--conf-path=/etc/nginx/nginx.conf \
@@ -158,6 +164,7 @@ patch -p1 < $BUILD_PATH/nginx__dynamic_tls_records.patch
158164
--add-module="$BUILD_PATH/nginx-goodies-nginx-sticky-module-ng-$STICKY_SESSIONS_VERSION" \
159165
--add-module="$BUILD_PATH/nginx-http-auth-digest-$NGINX_DIGEST_AUTH" \
160166
--add-module="$BUILD_PATH/ngx_http_substitutions_filter_module-$NGINX_SUBSTITUTIONS" \
167+
--add-module="$BUILD_PATH/nginx_upstream_check_module-$UPSTREAM_CHECK_VERSION" \
161168
--add-module="$BUILD_PATH/lua-upstream-nginx-module-$LUA_UPSTREAM_VERSION" || exit 1 \
162169
&& make || exit 1 \
163170
&& make install || exit 1

0 commit comments

Comments
 (0)