Skip to content

Commit c271836

Browse files
authored
Merge pull request #2 from MatteoMori/first-cut
First cut
2 parents 9f89aad + 6719d88 commit c271836

20 files changed

Lines changed: 1765 additions & 3 deletions

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
*.so
99
*.dylib
1010

11+
# Project binary
12+
sentinel
13+
1114
# Test binary, built with `go test -c`
1215
*.test
1316

@@ -29,4 +32,7 @@ go.work.sum
2932

3033
# Editor/IDE
3134
# .idea/
32-
# .vscode/
35+
.vscode/
36+
37+
**/.DS_Store
38+
.claude

Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM golang:1.25.6-alpine3.22 AS builder
2+
3+
WORKDIR /go/src/app
4+
COPY . .
5+
RUN env CGO_ENABLED=0 go build -o /sentinel
6+
7+
FROM scratch AS build-image
8+
COPY --from=builder /sentinel /sentinel
9+
COPY sentinel.yaml /etc/sentinel/sentinel.yaml
10+
ENTRYPOINT ["/sentinel"]
11+
CMD ["start"]

Makefile

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Makefile for Sentinel - Kubernetes Controller
2+
3+
# Variables
4+
BINARY_NAME=sentinel
5+
DOCKER_IMAGE=sentinel:latest
6+
KIND_CLUSTER=homelab
7+
8+
# Default target - shows available commands
9+
.PHONY: help
10+
help:
11+
@echo "Available commands:"
12+
@echo " make build - Build the Go binary"
13+
@echo " make docker - Build Docker image"
14+
@echo " make deploy - Build Docker image and deploy to KIND cluster"
15+
@echo " make clean - Remove built binary"
16+
@echo " make test - Run tests"
17+
@echo " make run - Run locally (requires kubeconfig)"
18+
19+
# Build the Go binary
20+
.PHONY: build
21+
build:
22+
@echo "Building $(BINARY_NAME)..."
23+
go build -o $(BINARY_NAME)
24+
@echo "✅ Binary built: ./$(BINARY_NAME)"
25+
26+
# Build Docker image
27+
.PHONY: docker
28+
docker:
29+
@echo "Building Docker image $(DOCKER_IMAGE)..."
30+
docker build -t $(DOCKER_IMAGE) .
31+
@echo "✅ Docker image built: $(DOCKER_IMAGE)"
32+
33+
# Build and deploy to KIND cluster
34+
.PHONY: deploy
35+
deploy: docker
36+
@echo "Loading image into KIND cluster $(KIND_CLUSTER)..."
37+
kind load docker-image $(DOCKER_IMAGE) --name $(KIND_CLUSTER)
38+
@echo "Deploying to Kubernetes..."
39+
kubectl apply -f manifests/install/sentinel.yaml
40+
@echo "✅ Deployed to KIND cluster"
41+
@echo ""
42+
@echo "Check status with: kubectl get pods -n kube-system -l app=sentinel-controller"
43+
44+
# Run locally (useful for development)
45+
.PHONY: run
46+
run: build
47+
@echo "Running $(BINARY_NAME) locally..."
48+
./$(BINARY_NAME) start -v=2
49+
50+
# Clean build artifacts
51+
.PHONY: clean
52+
clean:
53+
@echo "Cleaning build artifacts..."
54+
rm -f $(BINARY_NAME)
55+
@echo "✅ Cleaned"
56+
57+
# Run tests
58+
.PHONY: test
59+
test:
60+
@echo "Running tests..."
61+
go test ./...
62+
63+
# Update dependencies
64+
.PHONY: deps
65+
deps:
66+
@echo "Updating dependencies..."
67+
go mod tidy
68+
@echo "✅ Dependencies updated"

README.md

Lines changed: 277 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,277 @@
1-
# Luna
2-
MCP Server | Kubernetes
1+
# Sentinel
2+
3+
<img src="./images/logo/logo-2.jpg" width="150">
4+
5+
----
6+
7+
**A Kubernetes controller that watches your cluster and exposes container image inventory as Prometheus metrics.**
8+
9+
Sentinel monitors Kubernetes workloads across labeled namespaces and tracks which container images are running in your cluster — exposed as Prometheus metrics with support for dynamic label enrichment from workload annotations and labels.
10+
11+
<br>
12+
13+
## Why Sentinel?
14+
15+
Gain real-time visibility into your cluster's container image landscape. Perfect for:
16+
17+
- **Image Inventory Tracking** – Know exactly what's running, where, and when
18+
- **Security & Compliance** – Monitor image versions across namespaces
19+
- **Version Drift Detection** – Spot outdated or unauthorized images quickly
20+
- **Change Tracking** – Track image updates with old/new tag audit trail
21+
- **Audit & Governance** – Enrich metrics with custom labels like `owner` or `environment`
22+
23+
<br>
24+
25+
26+
27+
## Quick Start
28+
29+
### Prerequisites
30+
31+
- Kubernetes cluster (>= v1.28)
32+
- `kubectl` configured
33+
- KIND or Minikube for local testing
34+
35+
### Installation
36+
37+
1. **Deploy Sentinel to your cluster:**
38+
39+
```bash
40+
kubectl apply -f manifests/install/sentinel.yaml
41+
```
42+
43+
2. **Label the namespaces you want Sentinel to watch:**
44+
45+
```bash
46+
kubectl label namespace my-namespace sentinel.io/controlled=enabled
47+
```
48+
49+
3. **Access metrics:**
50+
51+
Sentinel exposes metrics on port `9090` at `/metrics`:
52+
53+
```bash
54+
kubectl port-forward -n kube-system svc/sentinel-metrics 9090:9090
55+
curl localhost:9090/metrics
56+
```
57+
<br>
58+
<br>
59+
60+
## Metrics Exposed
61+
62+
### `sentinel_container_image_info`
63+
64+
Info metric (Gauge, always `1`) providing a full inventory of container images running in your cluster.
65+
66+
**Labels:**
67+
68+
| Label | Description | Example |
69+
|-------|-------------|---------|
70+
| `namespace` | Kubernetes namespace | `production` |
71+
| `workload_type` | Kind of workload | `Deployment` |
72+
| `workload_name` | Name of the workload | `api-server` |
73+
| `container_name` | Container within the workload | `nginx` |
74+
| `image` | Full image string | `ghcr.io/myorg/app:v1.2.3` |
75+
| `image_registry` | Parsed registry | `ghcr.io` |
76+
| `image_repository` | Parsed repository | `myorg/app` |
77+
| `image_tag` | Parsed tag | `v1.2.3` |
78+
| *dynamic labels* | From `extraLabels` config | `owner`, `env`, etc. |
79+
80+
**Example output:**
81+
82+
```prometheus
83+
sentinel_container_image_info{
84+
namespace="production",
85+
workload_type="Deployment",
86+
workload_name="api-server",
87+
container_name="nginx",
88+
image="nginx:1.28.2-alpine-slim",
89+
image_registry="docker.io",
90+
image_repository="nginx",
91+
image_tag="1.28.2-alpine-slim",
92+
owner="platform-team",
93+
env="production"
94+
} 1
95+
```
96+
<br>
97+
98+
### `sentinel_image_changes_total`
99+
100+
Counter that increments every time a container's image tag changes, providing an audit trail of deployments.
101+
102+
**Labels:**
103+
104+
| Label | Description | Example |
105+
|-------|-------------|---------|
106+
| `namespace` | Kubernetes namespace | `production` |
107+
| `workload_type` | Kind of workload | `Deployment` |
108+
| `workload_name` | Name of the workload | `api-server` |
109+
| `container_name` | Container within the workload | `nginx` |
110+
| `old_image_tag` | Previous image tag | `1.28.2-alpine-slim` |
111+
| `new_image_tag` | New image tag | `1.29.0-alpine-slim` |
112+
113+
**Example output:**
114+
115+
```prometheus
116+
sentinel_image_changes_total{
117+
namespace="production",
118+
workload_type="Deployment",
119+
workload_name="api-server",
120+
container_name="nginx",
121+
old_image_tag="1.28.2-alpine-slim",
122+
new_image_tag="1.29.0-alpine-slim"
123+
} 1
124+
```
125+
126+
**Useful PromQL queries:**
127+
128+
```promql
129+
# Count all image changes in the last 24 hours
130+
sum(increase(sentinel_image_changes_total[24h]))
131+
132+
# Alert: too many image changes in production
133+
sentinel_image_changes_total{namespace="production"} > 5
134+
135+
# Find containers still using :latest
136+
sentinel_container_image_info{image_tag="latest"}
137+
138+
# Count containers per registry
139+
count by (image_registry) (sentinel_container_image_info)
140+
```
141+
142+
<br>
143+
144+
## Dynamic Label Enrichment
145+
146+
Sentinel can extract annotations and labels from your workloads and expose them as Prometheus metric labels. This is configured via `extraLabels`:
147+
148+
```yaml
149+
extraLabels:
150+
- type: "annotation" # Where to look: "annotation" or "label"
151+
key: "sentinel.io/owner" # The key to extract
152+
timeseriesLabelName: "owner" # The Prometheus label name
153+
- type: "label"
154+
key: "environment"
155+
timeseriesLabelName: "env"
156+
```
157+
158+
If a workload doesn't have the specified annotation or label, the metric label will be set to an empty string `""`.
159+
160+
This allows each organization to enrich metrics with the labels that matter to them — team ownership, cost center, environment, or any other metadata.
161+
162+
<br>
163+
164+
165+
## ⚙️ Configuration
166+
167+
Sentinel can be configured via:
168+
169+
### 1. Config file (`/etc/sentinel/sentinel.yaml`)
170+
171+
```yaml
172+
namespaceSelector:
173+
"sentinel.io/controlled": "enabled"
174+
metricsPort: "9090"
175+
verbosity: 2
176+
177+
extraLabels:
178+
- type: "annotation"
179+
key: "sentinel.io/owner"
180+
timeseriesLabelName: "owner"
181+
- type: "label"
182+
key: "environment"
183+
timeseriesLabelName: "env"
184+
```
185+
186+
### 2. Environment variables
187+
188+
```bash
189+
# Note: Complex types (maps, arrays) should be configured via YAML file
190+
# Only simple values can be overridden via environment variables
191+
export METRICSPORT=9090
192+
export VERBOSITY=2
193+
```
194+
195+
### 3. CLI flags
196+
197+
```bash
198+
sentinel start -v=2
199+
```
200+
201+
### Configuration Reference
202+
203+
| Key | Type | Default | Description |
204+
|-----|------|---------|-------------|
205+
| `namespaceSelector` | `map[string]string` | `{"sentinel.io/controlled": "enabled"}` | Label selector for namespaces to watch |
206+
| `metricsPort` | `string` | `"9090"` | Port for Prometheus metrics endpoint |
207+
| `verbosity` | `int` | `0` | Log level: 0=Info, 1=Warn, 2=Debug |
208+
| `extraLabels` | `[]ExtraLabel` | `[]` | Additional labels to extract from workloads |
209+
210+
<br>
211+
212+
## 📊 Grafana Dashboard
213+
214+
A pre-built Grafana dashboard is included in [`dashboard/grafana.json`](dashboard/grafana.json). Import it into your Grafana instance for:
215+
216+
- **Overview stats** – Tracked containers, workloads, image changes, and `:latest` tag usage
217+
- **Image inventory table** – Current container images with color-coded tags (`:latest` in red)
218+
- **Registry distribution** – Donut chart showing image count by registry
219+
- **Change tracking log** – Table of all detected image changes with old → new tags
220+
221+
222+
223+
---
224+
225+
## Local Development
226+
227+
### Build and Run Locally
228+
229+
```bash
230+
# Initialize Go module
231+
go mod tidy
232+
233+
# Build the binary
234+
go build -o sentinel
235+
```
236+
237+
### Test with KIND
238+
239+
```bash
240+
# Build Docker image
241+
docker build -t sentinel:latest .
242+
243+
# Load into KIND cluster
244+
kind load docker-image sentinel:latest --name <cluster-name>
245+
246+
# Deploy Sentinel
247+
kubectl apply -f manifests/install/sentinel.yaml
248+
249+
# Deploy demo workloads
250+
kubectl apply -f manifests/develop/demo-app-1.yaml
251+
kubectl apply -f manifests/develop/demo-app-2.yaml
252+
253+
# Verify metrics
254+
kubectl port-forward -n kube-system svc/sentinel-metrics 9090:9090
255+
curl -s localhost:9090/metrics | grep sentinel_
256+
```
257+
258+
259+
## 🌟 Project Status
260+
261+
Sentinel is in **active development** as a learning project to master Go and Kubernetes controller patterns.
262+
263+
**Current capabilities:**
264+
- ✅ Namespace watching with label selectors
265+
- ✅ Deployment monitoring with real-time informers
266+
- ✅ Container image parsing (registry, repository, tag)
267+
- ✅ Prometheus metrics server
268+
- ✅ Dynamic label enrichment from annotations/labels
269+
- ✅ Image change tracking (old tag → new tag)
270+
- ✅ Grafana dashboard
271+
- 🚧 StatefulSet/DaemonSet/CronJob support (planned)
272+
- 🚧 Init container support (planned)
273+
- 🚧 Metric cleanup on workload deletion (planned)
274+
275+
---
276+
277+
**Built with ❤️ and Go** | [Report an Issue](https://github.com/MatteoMori/sentinel/issues)

0 commit comments

Comments
 (0)