Skip to content

Commit 09f0a0f

Browse files
committed
fixes
1 parent e33bb7e commit 09f0a0f

File tree

2 files changed

+71
-11
lines changed

2 files changed

+71
-11
lines changed

docs/new_features.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Kubernetes Version Monitoring
2+
3+
version-checker now includes built-in Kubernetes cluster version monitoring capabilities. This feature automatically compares your cluster's current Kubernetes version against the latest available versions from official Kubernetes release channels.
4+
5+
### How It Works
6+
7+
The Kubernetes version checker:
8+
- Fetches the current cluster version using the Kubernetes Discovery API
9+
- Compares it against the latest version from the configured Kubernetes release channel (using official `https://dl.k8s.io/release/` endpoints)
10+
- Exposes the comparison as Prometheus metrics for monitoring and alerting
11+
- Strips metadata from versions for accurate semantic version comparison (e.g., `v1.28.2-gke.1` becomes `v1.28.2`)
12+
13+
### Configuration
14+
15+
You can configure the Kubernetes version checking behavior using the following CLI flags:
16+
17+
- `--kube-channel`: Specifies which Kubernetes release channel to check against (default: `"stable"`)
18+
- Examples: `stable`, `latest`, `stable-1.28`, `latest-1.29`
19+
- `--kube-interval`: How often to check for Kubernetes version updates (default: same as `--cache-sync-period`, 5 hours)
20+
21+
### Metrics
22+
23+
The Kubernetes version monitoring exposes the following Prometheus metric:
24+
25+
```
26+
version_checker_is_latest_kube_version{current_version="1.28.2", latest_version="1.29.1", channel="stable"} 0
27+
```
28+
29+
- Value `1`: Cluster is running the latest version from the specified channel
30+
- Value `0`: Cluster is not running the latest version (update available)
31+
32+
### Examples
33+
34+
```bash
35+
# Use the default stable channel
36+
./version-checker
37+
38+
# Monitor against the latest channel
39+
./version-checker --kube-channel=latest
40+
41+
# Monitor against a specific version channel with custom interval
42+
./version-checker --kube-channel=stable-1.28 --kube-interval=1h
43+
```

pkg/controller/kube_controller.go

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"net/url"
78
"strings"
89
"time"
910

@@ -76,10 +77,10 @@ func (s *ClusterVersionScheduler) reconcile(_ context.Context) error {
7677
return fmt.Errorf("getting cluster version: %w", err)
7778
}
7879

79-
// Get latest stable version
80-
latest, err := getLatestStableVersion(s.channel)
80+
// Get latest version from specified channel
81+
latest, err := getLatestVersion(s.channel)
8182
if err != nil {
82-
return fmt.Errorf("fetching latest stable version: %w", err)
83+
return fmt.Errorf("fetching latest version from channel %s: %w", s.channel, err)
8384
}
8485

8586
latestSemVer := semver.Parse(latest)
@@ -101,20 +102,23 @@ func (s *ClusterVersionScheduler) reconcile(_ context.Context) error {
101102

102103
s.log.WithFields(logrus.Fields{
103104
"currentVersion": currentSemVerNoMeta,
104-
"latestStable": latestSemVerNoMeta,
105+
"latestVersion": latestSemVerNoMeta,
105106
"channel": s.channel,
106107
}).Info("Cluster version check complete")
107108

108109
return nil
109110
}
110111

111-
func getLatestStableVersion(channel string) (string, error) {
112+
func getLatestVersion(channel string) (string, error) {
112113
if !strings.HasSuffix(channel, ".txt") {
113114
channel += ".txt"
114115
}
115116

116-
// We don't need a `/` here as its should be in the channelURLSuffix
117-
channelURL := fmt.Sprintf("%s%s", channelURLSuffix, channel)
117+
// Use url.JoinPath to safely join the base URL and channel path
118+
channelURL, err := url.JoinPath(channelURLSuffix, channel)
119+
if err != nil {
120+
return "", fmt.Errorf("failed to join channel URL: %w", err)
121+
}
118122

119123
client := retryablehttp.NewClient()
120124
client.RetryMax = 3
@@ -125,14 +129,27 @@ func getLatestStableVersion(channel string) (string, error) {
125129

126130
resp, err := client.Get(channelURL)
127131
if err != nil {
128-
return "", err
132+
return "", fmt.Errorf("failed to fetch from channel URL %s: %w", channelURL, err)
133+
}
134+
defer func() {
135+
if cerr := resp.Body.Close(); cerr != nil {
136+
fmt.Printf("warning: failed to close response body: %v\n", cerr)
137+
}
138+
}()
139+
140+
if resp.StatusCode != 200 {
141+
return "", fmt.Errorf("unexpected status code %d when fetching channel %s", resp.StatusCode, channel)
129142
}
130-
defer resp.Body.Close()
131143

132144
body, err := io.ReadAll(resp.Body)
133145
if err != nil {
134-
return "", err
146+
return "", fmt.Errorf("failed to read response body: %w", err)
147+
}
148+
149+
version := strings.TrimSpace(string(body))
150+
if version == "" {
151+
return "", fmt.Errorf("empty version returned from channel %s", channel)
135152
}
136153

137-
return strings.TrimSpace(string(body)), nil
154+
return version, nil
138155
}

0 commit comments

Comments
 (0)