Skip to content

Commit d1fd0a8

Browse files
authored
Merge pull request #120 from postfinance/feat/neighbour_limit
Feat/neighbour limit
2 parents 4fa4b9f + cdcc063 commit d1fd0a8

File tree

16 files changed

+246
-48
lines changed

16 files changed

+246
-48
lines changed

.github/workflows/ci-helm-deploy-nginx.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Setup Go
1313
uses: actions/setup-go@v5
1414
with:
15-
go-version: '1.21'
15+
go-version: '1.22'
1616
- name: GoReleaser
1717
uses: goreleaser/goreleaser-action@v4
1818
with:

.github/workflows/ci-helm-deploy-traefik.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Setup Go
1818
uses: actions/setup-go@v5
1919
with:
20-
go-version: '1.21'
20+
go-version: '1.22'
2121
- name: GoReleaser
2222
uses: goreleaser/goreleaser-action@v4
2323
with:

.github/workflows/ci-kustomize-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Setup Go
1313
uses: actions/setup-go@v5
1414
with:
15-
go-version: '1.21'
15+
go-version: '1.22'
1616
- name: GoReleaser
1717
uses: goreleaser/goreleaser-action@v4
1818
with:

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
steps:
1010
- uses: actions/setup-go@v5
1111
with:
12-
go-version: '1.21'
12+
go-version: '1.22'
1313
- uses: actions/checkout@v4
1414
- uses: golangci/golangci-lint-action@v4
1515
with:
@@ -29,7 +29,7 @@ jobs:
2929
- uses: actions/checkout@v4
3030
- uses: actions/setup-go@v5
3131
with:
32-
go-version: '1.21'
32+
go-version: '1.22'
3333
- name: Run unit tests
3434
run: go test -race -covermode atomic -coverprofile=profile.cov ./...
3535
- name: Send coverage report

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
- name: Setup Go
1414
uses: actions/setup-go@v5
1515
with:
16-
go-version: '1.21'
16+
go-version: '1.22'
1717
- name: Login to DockerHub
1818
uses: docker/login-action@v3
1919
with:

README.md

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/postfinance/kubenurse)
44

55
# Kubenurse
6+
67
kubenurse is a little service that monitors all network connections in a Kubernetes
78
cluster. Kubenurse measures request durations, records errors and exports those metrics in Prometheus format.
89

910
## Deployment
11+
1012
You can get the Docker image from [Docker Hub](https://hub.docker.com/r/postfinance/kubenurse/).
1113
The [examples](https://github.com/postfinance/kubenurse/tree/master/examples) directory
1214
contains manifests which can be used to deploy kubenurse to the kube-system namespace of your cluster.
@@ -45,6 +47,7 @@ The following command can be used to install kubenurse with Helm: `helm upgrade
4547
| insecure | Set `KUBENURSE_INSECURE` environment variable | `true` |
4648
| allow_unschedulable | Sets `KUBENURSE_ALLOW_UNSCHEDULABLE` environment variable | `false` |
4749
| neighbour_filter | Sets `KUBENURSE_NEIGHBOUR_FILTER` environment variable | `app.kubernetes.io/name=kubenurse` |
50+
| neighbour_limit | Sets `KUBENURSE_NEIGHBOUR_LIMIT` environment variable | `10` |
4851
| extra_ca | Sets `KUBENURSE_EXTRA_CA` environment variable | |
4952
| check_api_server_direct | Sets `KUBENURSE_CHECK_API_SERVER_DIRECT` environment variable | `true` |
5053
| check_api_server_dns | Sets `KUBENURSE_CHECK_API_SERVER_DNS` environment variable | `true` |
@@ -74,7 +77,6 @@ dashboards [as this example](./doc/grafana-kubenurse.json) that show network lat
7477
![Grafana ingress view](doc/grafana_ingress.png "Grafana ingress view")
7578
![Grafana path view](doc/grafana_path.png "Grafana path view")
7679
77-
7880
## Configuration
7981
8082
kubenurse is configured with environment variables:
@@ -85,12 +87,13 @@ kubenurse is configured with environment variables:
8587
- `KUBENURSE_EXTRA_CA`: Additional CA cert path for TLS connections
8688
- `KUBENURSE_NAMESPACE`: Namespace in which to look for the neighbour kubenurses
8789
- `KUBENURSE_NEIGHBOUR_FILTER`: A Kubernetes label selector (eg. `app=kubenurse`) to filter neighbour kubenurses
90+
- `KUBENURSE_NEIGHBOUR_LIMIT`: The maximum number of neighbours each kubenurse will query
8891
- `KUBENURSE_ALLOW_UNSCHEDULABLE`: If this is `"true"`, path checks to neighbouring kubenurses are made even if they are running on unschedulable nodes.
8992
- `KUBENURSE_CHECK_API_SERVER_DIRECT`: If this is `"true"` kubenurse will perform the check [API Server Direct](#API Server Direct). default is "true"
9093
- `KUBENURSE_CHECK_API_SERVER_DNS`: If this is `"true"`, kubenurse will perform the check [API Server DNS](#API Server DNS). default is "true"
9194
- `KUBENURSE_CHECK_ME_INGRESS`: If this is `"true"`, kubenurse will perform the check [Me Ingress](#Me Ingress). default is "true"
9295
- `KUBENURSE_CHECK_ME_SERVICE`: If this is `"true"`, kubenurse will perform the check [Me Service](#Me Service). default is "true"
93-
- `KUBENURSE_CHECK_NEIGHBOURHOOD`: If this is `"true"`, kubenurse will perform the check [Neighbourhood](#Neighbourhood). default is "true"
96+
- `KUBENURSE_CHECK_NEIGHBOURHOOD`: If this is `"true"`, kubenurse will perform the check [Neighbourhood](#neighbourhood). default is "true"
9497
- `KUBENURSE_CHECK_INTERVAL`: the frequency to perform kubenurse checks. the string should be formatted for [time.ParseDuration](https://pkg.go.dev/time#ParseDuration). defaults to `5s`
9598
- `KUBENURSE_REUSE_CONNECTIONS`: whether to reuse connections or not for all checks. default is "false"
9699
- `KUBENURSE_HISTOGRAM_BUCKETS`: optional comma-separated list of float64, used in place of the [default prometheus histogram buckets](https://pkg.go.dev/github.com/prometheus/client_golang@v1.16.0/prometheus#DefBuckets)
@@ -152,8 +155,8 @@ The `/alive` endpoint returns a JSON like this with status code 200 if everythin
152155
}
153156
```
154157

155-
156158
## Health Checks
159+
157160
Every five seconds and on every access of `/alive`, the checks described below are run.
158161
Check results are cached for 3 seconds in order to prevent excessive network traffic.
159162

@@ -162,19 +165,22 @@ A little illustration of what communication occurs, is here:
162165
![Communication](doc/Communication.png "Communication")
163166

164167
### API Server Direct
168+
165169
Checks the `/version` endpoint of the Kubernetes API Server through
166170
the direct link (`KUBERNETES_SERVICE_HOST`, `KUBERNETES_SERVICE_PORT`).
167171

168172
Metric type: `api_server_direct`
169173

170174
### API Server DNS
175+
171176
Checks the `/version` endpoint of the Kubernetes API Server through
172177
the Cluster DNS URL `https://kubernetes.default.svc:$KUBERNETES_SERVICE_PORT`.
173178
This also verifies a working `kube-dns` deployment.
174179

175180
Metric type: `api_server_dns`
176181

177182
### Me Ingress
183+
178184
Checks if the kubenurse is reachable at the `/alwayshappy` endpoint behind the ingress.
179185
This address is provided by the environment variable `KUBENURSE_INGRESS_URL` that
180186
could look like `https://kubenurse.example.com`.
@@ -183,6 +189,7 @@ This also verifies a correct upstream DNS resolution.
183189
Metric type: `me_ingress`
184190

185191
### Me Service
192+
186193
Checks if the kubenurse is reachable at the `/alwayshappy` endpoint through the Kubernetes service.
187194
The address is provided by the environment variable `KUBENURSE_SERVICE_URL` that
188195
could look like `http://kubenurse.mynamespace.default.svc:8080`.
@@ -191,6 +198,7 @@ This also verifies a working `kube-proxy` setup.
191198
Metric type: `me_service`
192199

193200
### Neighbourhood
201+
194202
Checks if every neighbour kubenurse is reachable at the `/alwayshappy` endpoint.
195203
Neighbours are discovered by querying the kube-apiserver for every Pod in the
196204
`KUBENURSE_NAMESPACE` with label `KUBENURSE_NEIGHBOUR_FILTER`.
@@ -201,7 +209,44 @@ this can be changed by setting `KUBENURSE_ALLOW_UNSCHEDULABLE="true"`.
201209

202210
Metric type: `path_$KUBELET_HOSTNAME`
203211

212+
#### Neighbourhood filtering
213+
214+
The number of checks for the neighbourhood used to grow as $O(N^2)$, which
215+
rendered `kubenurse` impractical on large clusters, as documented in issue
216+
[#55](https://github.com/postfinance/kubenurse/issues/55).
217+
To combat this, a node filtering feature was implemented, which works as follows
218+
219+
- kubenurse computes the `sha256` checksums for all neighbours' node names
220+
- it sorts those checksums (this is actually implemented with a max-heap)
221+
- it computes its own node name checksum, and queries the next 10 (per default)
222+
nodes in the sorted checksums list
223+
224+
Thanks to this, every node is making queries to the same 10 nodes, unless one
225+
of those nodes disappears, in which case kubenurse will pick the next node in
226+
the sorted checksums list. This comes with several advantages:
227+
228+
- because of the way we first hash the node names, the checks distribution is
229+
randomly distributed, independant of the node names. if we only picked the 10
230+
next nodes in a sorted list of the node names, then we might have biased the
231+
results in environments where node names are sequential
232+
- metrics-wise, a `kubenurse` pod should typically only have entries for ca. 10
233+
other neighbouring nodes worth of checks, which greatly reduces the load on
234+
your monitoring infrastructure
235+
- because we use a deterministic algorithm to choose which nodes to query, the
236+
metrics churn rate stays minimal. (that is, if we randomly picked 10 nodes
237+
for every check, then in the end there would be one prometheus bucket for
238+
every node on the cluster, which would put useless load on the monitoring
239+
infrastructure)
240+
241+
Per default, the neighbourhood filtering is set to 10 nodes, which means that
242+
on cluster with more than 10 nodes, each kubenurse will query 10 nodes, as
243+
described above.
244+
245+
To bypass the node filtering feature, you simply need to set the
246+
`KUBENURSE_NEIGHBOUR_LIMIT` environment variable to 0.
247+
204248
## Metrics
249+
205250
All performed checks expose metrics which can be used to monitor/alert:
206251

207252
- SDN network latencies and errors
@@ -214,5 +259,6 @@ All performed checks expose metrics which can be used to monitor/alert:
214259
- External DNS resolution errors (ingress URL resolution)
215260

216261
At `/metrics` you will find these:
262+
217263
- `kubenurse_errors_total`: Kubenurse error counter partitioned by error type
218264
- `kubenurse_request_duration`: a histogram for Kubenurse request duration partitioned by error type

go.mod

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
module github.com/postfinance/kubenurse
22

3-
go 1.21
4-
5-
toolchain go1.21.5
3+
go 1.22
64

75
require (
86
github.com/prometheus/client_golang v1.19.0

helm/kubenurse/templates/daemonset.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ spec:
5656
value: {{ .Release.Namespace }}
5757
- name: KUBENURSE_NEIGHBOUR_FILTER
5858
value: {{ .Values.neighbour_filter }}
59+
- name: KUBENURSE_NEIGHBOUR_LIMIT
60+
value: {{ .Values.neighbour_limit | quote }}
5961
{{- if .Values.extra_ca }}
6062
- name: KUBENURSE_EXTRA_CA
6163
value: {{ .Values.extra_ca }}

helm/kubenurse/values.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ service_url: ""
3535
allow_unschedulable: false
3636
# KUBENURSE_NEIGHBOUR_FILTER
3737
neighbour_filter: app.kubernetes.io/name=kubenurse
38+
# KUBENURSE_NEIGHBOUR_LIMIT
39+
neighbour_limit: 10
3840
# KUBENURSE_EXTRA_CA
3941
extra_ca: ""
4042
# KUBENURSE_CHECK_API_SERVER_DIRECT

internal/kubenurse/handler.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
)
1010

1111
func (s *Server) readyHandler() func(w http.ResponseWriter, r *http.Request) {
12-
return func(w http.ResponseWriter, r *http.Request) {
12+
return func(w http.ResponseWriter, _ *http.Request) {
1313
s.mu.Lock()
1414
defer s.mu.Unlock()
1515

@@ -34,8 +34,8 @@ func (s *Server) aliveHandler() func(w http.ResponseWriter, r *http.Request) {
3434
servicecheck.Result
3535

3636
// kubediscovery
37-
NeighbourhoodState string `json:"neighbourhood_state"`
38-
Neighbourhood []servicecheck.Neighbour `json:"neighbourhood"`
37+
NeighbourhoodState string `json:"neighbourhood_state"`
38+
Neighbourhood []*servicecheck.Neighbour `json:"neighbourhood"`
3939
}
4040

4141
res := s.checker.LastCheckResult

0 commit comments

Comments
 (0)