feat(uptime-monitor): deploy IngressMonitorController with zero monitors (REN-6)
Stakater IngressMonitorController in its own namespace, with zero EndpointMonitor resources, so it creates nothing on arrival. - Vendored EndpointMonitor CRD from chart 2.2.13, image pinned to v2.2.13 and deliberately kept out of Flux image automation. - Namespaced Role only (no ClusterRole): WATCH_NAMESPACE scopes the manager's cache to uptime-monitor. Verbs derived from what v2.2.13 actually calls; every omission is justified inline in rbac.yaml. - The ExternalSecret now renders IMC's config.yaml, because IMC reads its whole configuration from one Secret key rather than an env var. Config stays in git; only the key is injected. - Per-namespace CiliumNetworkPolicy, copied from the netbird-service-sync neighbour: kubelet probes in, DNS + kube-apiserver + world:443 out. - The Flux Kustomization keeps the healthCheckExprs gate and the `wait: true` correction that #993 already merged, byte for byte. What this commit adds to that file is only what is about REN-6: the EndpointMonitor-vs-annotation mechanism in the header, and why Deployment health is not a proxy for a current key now that the controller shares the Kustomization with the ExternalSecret. Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
committed by
atlas-bot
co-authored by
Paperclip
Claude Opus 5
parent
3d6c9940d6
commit
c40c337e36
@@ -0,0 +1,464 @@
|
||||
# VENDORED, DO NOT EDIT BY HAND.
|
||||
#
|
||||
# Source: stakater/IngressMonitorController chart 2.2.13,
|
||||
# charts/ingressmonitorcontroller/crds/endpointmonitor.stakater.com_endpointmonitors.yaml
|
||||
# Copied verbatim at commit aaffd8a866aa09a77030979d938e766895bde5d4.
|
||||
#
|
||||
# Vendored rather than installed via HelmRelease because this Kustomization
|
||||
# ships hand-written RBAC (see rbac.yaml) — the chart's own Role grants pods,
|
||||
# services and `secrets: list`, none of which this controller uses. Pulling the
|
||||
# chart for the CRD alone would drag those back in.
|
||||
#
|
||||
# To update: bump the image tag in deployment.yaml and re-copy this file from
|
||||
# the matching chart tag. Nothing here is Renovate-managed, so the tag and this
|
||||
# file move together, by hand, in one reviewable commit.
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.16.1
|
||||
name: endpointmonitors.endpointmonitor.stakater.com
|
||||
spec:
|
||||
group: endpointmonitor.stakater.com
|
||||
names:
|
||||
kind: EndpointMonitor
|
||||
listKind: EndpointMonitorList
|
||||
plural: endpointmonitors
|
||||
singular: endpointmonitor
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: EndpointMonitor is the Schema for the endpointmonitors API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: EndpointMonitorSpec defines the desired state of EndpointMonitor
|
||||
properties:
|
||||
appInsightsConfig:
|
||||
description: Configuration for AppInsights Monitor Provider
|
||||
properties:
|
||||
frequency:
|
||||
description: 'Sets how often the test should run from each test
|
||||
location. Possible values: `300,600,900` seconds'
|
||||
type: integer
|
||||
retryEnable:
|
||||
description: 'If its `true`, falied test will be retry after a
|
||||
short interval. Possible values: `true, false`'
|
||||
type: boolean
|
||||
statusCode:
|
||||
description: Returned status code that is counted as a success
|
||||
type: integer
|
||||
type: object
|
||||
forceHttps:
|
||||
description: Force monitor endpoint to use HTTPS
|
||||
type: boolean
|
||||
gcloudConfig:
|
||||
description: Configuration for Google Cloud Monitor Provider
|
||||
properties:
|
||||
projectId:
|
||||
description: Google Cloud Project ID
|
||||
type: string
|
||||
type: object
|
||||
grafanaConfig:
|
||||
description: Configuration for Grafana Cloud Monitor Provider
|
||||
properties:
|
||||
alertSensitivity:
|
||||
default: none
|
||||
description: |-
|
||||
The alertSensitivity value defaults to none if there are no alerts or can be set to low, medium,
|
||||
or high to correspond to the check alert levels.
|
||||
enum:
|
||||
- none
|
||||
- low
|
||||
- medium
|
||||
- high
|
||||
type: string
|
||||
frequency:
|
||||
description: The frequency value specifies how often the check
|
||||
runs in milliseconds
|
||||
format: int64
|
||||
type: integer
|
||||
probes:
|
||||
description: |-
|
||||
Probes are the monitoring agents responsible for simulating user interactions with your web applications
|
||||
or services. These agents periodically send requests to predefined URLs and record the responses,
|
||||
checking for expected outcomes and measuring performance.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tenantId:
|
||||
format: int64
|
||||
type: integer
|
||||
type: object
|
||||
healthEndpoint:
|
||||
type: string
|
||||
pingdomConfig:
|
||||
description: Configuration for Pingdom Monitor Provider
|
||||
properties:
|
||||
alertContacts:
|
||||
description: '`-` separated contact id''s (e.g. "1234567_8_9-9876543_2_1")'
|
||||
type: string
|
||||
alertIntegrations:
|
||||
description: '`-` separated set list of integrations ids (e.g.
|
||||
"91166-12168")'
|
||||
type: string
|
||||
basicAuthUser:
|
||||
description: Required for basic-authentication
|
||||
type: string
|
||||
notifyWhenBackUp:
|
||||
description: Set to "false" to disable recovery notifications
|
||||
type: boolean
|
||||
paused:
|
||||
description: Set to "true" to pause checks
|
||||
type: boolean
|
||||
postDataEnvVar:
|
||||
description: |-
|
||||
Data that should be posted to the web page, for example submission data for a sign-up or login form.
|
||||
The data needs to be formatted in the same way as a web browser would send it to the web server.
|
||||
Because post data contains sensitive secret this field is only a reference to an environment variable.
|
||||
type: string
|
||||
requestHeaders:
|
||||
description: Custom request headers
|
||||
type: string
|
||||
requestHeadersEnvVar:
|
||||
description: |-
|
||||
Custom request headers that should be read from an environment variable as it possibly contains sensitive data.
|
||||
An example would be an API token.
|
||||
type: string
|
||||
resolution:
|
||||
description: The pingdom check interval in minutes
|
||||
type: integer
|
||||
sendNotificationWhenDown:
|
||||
description: How many failed check attempts before notifying
|
||||
type: integer
|
||||
shouldContain:
|
||||
description: Set to text string that has to be present in the
|
||||
HTML code of the page
|
||||
type: string
|
||||
sslDownDaysBefore:
|
||||
description: |-
|
||||
Consider down prior to certificate expiring
|
||||
Select the number of days prior to your certificate expiry date that you want to consider the check down.
|
||||
At this day your check will be considered down and if applicable a down alert will be sent.
|
||||
type: integer
|
||||
tags:
|
||||
description: Comma separated set of tags to apply to check (e.g.
|
||||
"testing,aws")
|
||||
type: string
|
||||
teamAlertContacts:
|
||||
description: '`-` separated team id''s (e.g. "1234567_8_9-9876543_2_1")'
|
||||
type: string
|
||||
verifyCertificate:
|
||||
description: |-
|
||||
Monitor SSL/TLS certificate
|
||||
Monitor the validity of your SSL/TLS certificate. With this enabled Uptime checks will be considered DOWN when
|
||||
the certificate becomes invalid or expires.
|
||||
SSL/TLS certificate monitoring is available for HTTP checks.
|
||||
type: boolean
|
||||
type: object
|
||||
pingdomTransactionConfig:
|
||||
description: Configuration for Pingdom Transaction Monitor Provider
|
||||
properties:
|
||||
alertContacts:
|
||||
description: '`-` separated contact id''s (e.g. "1234567_8_9-9876543_2_1")'
|
||||
type: string
|
||||
alertIntegrations:
|
||||
description: '`-` separated set list of integrations ids (e.g.
|
||||
"91166-12168")'
|
||||
type: string
|
||||
custom_message:
|
||||
description: Custom message that is part of the email and webhook
|
||||
alerts
|
||||
type: string
|
||||
interval:
|
||||
description: 'TMS test intervals in minutes. Allowed intervals:
|
||||
5,10,20,60,720,1440. The interval you''re allowed to set may
|
||||
vary depending on your current plan.'
|
||||
enum:
|
||||
- 5
|
||||
- 10
|
||||
- 20
|
||||
- 60
|
||||
- 720
|
||||
- 1440
|
||||
type: integer
|
||||
paused:
|
||||
description: 'Check status: active or inactive'
|
||||
type: boolean
|
||||
region:
|
||||
description: 'Name of the region where the check is executed.
|
||||
Supported regions: us-east, us-west, eu, au'
|
||||
enum:
|
||||
- us-east
|
||||
- us-west
|
||||
- eu
|
||||
- au
|
||||
type: string
|
||||
send_notification_when_down:
|
||||
description: Send notification when down X times
|
||||
format: int64
|
||||
type: integer
|
||||
severity_level:
|
||||
description: 'Check importance- how important are the alerts when
|
||||
the check fails. Allowed values: low, high'
|
||||
enum:
|
||||
- low
|
||||
- high
|
||||
type: string
|
||||
steps:
|
||||
description: steps to be executed as part of the check
|
||||
items:
|
||||
description: PingdomStep respresents a step of the script to
|
||||
run a transcaction check
|
||||
properties:
|
||||
args:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
contains the html element with assigned value
|
||||
the key element is always lowercase for example {"url": "https://www.pingdom.com"}
|
||||
see available values at https://pkg.go.dev/github.com/karlderkaefer/pingdom-golang-client@latest/pkg/pingdom/client/tmschecks#StepArg
|
||||
type: object
|
||||
function:
|
||||
description: |-
|
||||
contains the function that is executed as part of the step
|
||||
commands: go_to, click, fill, check, uncheck, sleep, select_radio, basic_auth, submit, wait_for_element, wait_for_contains
|
||||
validations: url, exists, not_exists, contains, not_contains, field_contains, field_not_contains, is_checked, is_not_checked, radio_selected, dropdown_selected, dropdown_not_selected
|
||||
see updated list https://docs.pingdom.com/api/#section/TMS-Steps-Vocabulary/Script-transaction-checks
|
||||
type: string
|
||||
required:
|
||||
- args
|
||||
- function
|
||||
type: object
|
||||
type: array
|
||||
tags:
|
||||
description: List of tags for a check. The tag name may contain
|
||||
the characters 'A-Z', 'a-z', '0-9', '_' and '-'. The maximum
|
||||
length of a tag is 64 characters.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
teamAlertContacts:
|
||||
description: '`-` separated team id''s (e.g. "1234567_8_9-9876543_2_1")'
|
||||
type: string
|
||||
required:
|
||||
- steps
|
||||
type: object
|
||||
providers:
|
||||
description: Comma separated list of providers
|
||||
type: string
|
||||
statusCakeConfig:
|
||||
description: Configuration for StatusCake Monitor Provider
|
||||
properties:
|
||||
basicAuthSecret:
|
||||
description: Basic Auth Secret Name
|
||||
type: string
|
||||
basicAuthUser:
|
||||
description: Basic Auth User
|
||||
type: string
|
||||
checkRate:
|
||||
default: 300
|
||||
description: Set Check Rate for the monitor.
|
||||
type: integer
|
||||
confirmation:
|
||||
description: Confirmation value ranges from (0,10)
|
||||
maximum: 10
|
||||
minimum: 0
|
||||
type: integer
|
||||
contactGroup:
|
||||
description: Contact Group to be alerted.
|
||||
type: string
|
||||
enableSslAlert:
|
||||
description: Enable SSL Alert
|
||||
type: boolean
|
||||
findString:
|
||||
description: String to look for within the response. Considered
|
||||
down if not found
|
||||
type: string
|
||||
followRedirect:
|
||||
description: Enable ingress redirects
|
||||
type: boolean
|
||||
paused:
|
||||
description: Pause the service
|
||||
type: boolean
|
||||
pingUrl:
|
||||
description: Webhook for alerts
|
||||
type: string
|
||||
port:
|
||||
description: TCP Port
|
||||
type: integer
|
||||
rawPostData:
|
||||
description: RawPostData can be used to send parameters within
|
||||
the URL. Changes the request from a GET to a POST
|
||||
type: string
|
||||
realBrowser:
|
||||
description: Enable Real Browser
|
||||
type: boolean
|
||||
regions:
|
||||
description: Comma separated list of Node Location IDs
|
||||
type: string
|
||||
statusCodes:
|
||||
description: Comma separated list of HTTP codes to trigger error
|
||||
on
|
||||
type: string
|
||||
testTags:
|
||||
description: Comma separated list of tags
|
||||
type: string
|
||||
testType:
|
||||
description: Set Test type - HTTP, TCP, PING, or Heartbeat
|
||||
enum:
|
||||
- HTTP
|
||||
- TCP
|
||||
- PING
|
||||
- Heartbeat
|
||||
type: string
|
||||
timeout:
|
||||
description: Timeout is used to set a user agent string.
|
||||
maximum: 75
|
||||
minimum: 5
|
||||
type: integer
|
||||
triggerRate:
|
||||
description: Minutes to wait before sending an alert
|
||||
type: integer
|
||||
userAgent:
|
||||
description: UserAgent is used to set a user agent string.
|
||||
type: string
|
||||
type: object
|
||||
x-kubernetes-validations:
|
||||
- message: checkRate must be between 30 and 172800 seconds for Heartbeat
|
||||
monitors
|
||||
rule: self.testType != 'Heartbeat' || (self.checkRate >= 30 && self.checkRate
|
||||
<= 172800)
|
||||
- message: 'checkRate for uptime monitors must be one of: 0, 30, 60,
|
||||
300, 900, 1800, 3600, 86400'
|
||||
rule: self.testType == 'Heartbeat' || self.checkRate in [0, 30,
|
||||
60, 300, 900, 1800, 3600, 86400]
|
||||
updownConfig:
|
||||
description: Configuration for Updown Monitor Provider
|
||||
properties:
|
||||
enable:
|
||||
description: Enable or disable checks
|
||||
type: boolean
|
||||
period:
|
||||
description: The pingdom check interval in seconds
|
||||
type: integer
|
||||
publishPage:
|
||||
description: Make status page public or not
|
||||
type: boolean
|
||||
requestHeaders:
|
||||
description: Additional request headers for API calls
|
||||
type: string
|
||||
type: object
|
||||
uptimeConfig:
|
||||
description: Configuration for Uptime Monitor Provider
|
||||
properties:
|
||||
checkType:
|
||||
description: The uptime check type that can be HTTP/DNS/ICMP etc.
|
||||
type: string
|
||||
contacts:
|
||||
description: Add one or more contact groups separated by `,`
|
||||
type: string
|
||||
interval:
|
||||
description: The uptime check interval in seconds
|
||||
type: integer
|
||||
locations:
|
||||
description: Add different locations for the check
|
||||
type: string
|
||||
tags:
|
||||
description: Add one or more tags for the check separated by `,`
|
||||
type: string
|
||||
type: object
|
||||
uptimeRobotConfig:
|
||||
description: Configuration for UptimeRobot Monitor Provider
|
||||
properties:
|
||||
alertContacts:
|
||||
description: The uptimerobot alertContacts to be associated with
|
||||
this monitor
|
||||
type: string
|
||||
customHTTPStatuses:
|
||||
description: |-
|
||||
Defines which http status codes are treated as up or down
|
||||
For ex: 200:0_401:1_503:1 (to accept 200 as down and 401 and 503 as up)
|
||||
type: string
|
||||
interval:
|
||||
description: The uptimerobot check interval in seconds
|
||||
minimum: 60
|
||||
type: integer
|
||||
keywordExists:
|
||||
description: Alert if value exist (yes) or doesn't exist (no)
|
||||
(Only if monitor-type is keyword)
|
||||
enum:
|
||||
- "yes"
|
||||
- "no"
|
||||
type: string
|
||||
keywordValue:
|
||||
description: keyword to check on URL (e.g.'search' or '404') (Only
|
||||
if monitor-type is keyword)
|
||||
type: string
|
||||
maintenanceWindows:
|
||||
description: Specify maintenanceWindows i.e. once or recurring
|
||||
“do-not-monitor periods”
|
||||
type: string
|
||||
monitorType:
|
||||
description: The uptimerobot monitor type (http or keyword)
|
||||
enum:
|
||||
- http
|
||||
- keyword
|
||||
type: string
|
||||
statusPages:
|
||||
description: The uptimerobot public status page ID to add this
|
||||
monitor to
|
||||
type: string
|
||||
type: object
|
||||
url:
|
||||
description: URL to monitor
|
||||
type: string
|
||||
urlFrom:
|
||||
description: URL to monitor from either an ingress or route reference
|
||||
properties:
|
||||
ingressRef:
|
||||
description: IngressURLSource selects an Ingress to populate the
|
||||
URL with
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
routeRef:
|
||||
description: RouteURLSource selects a Route to populate the URL
|
||||
with
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: object
|
||||
type: object
|
||||
status:
|
||||
description: EndpointMonitorStatus defines the observed state of EndpointMonitor
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
# Stakater IngressMonitorController (REN-6).
|
||||
#
|
||||
# It ships with zero EndpointMonitor resources, which is the whole point of this
|
||||
# first deployment: the reconciler is keyed on one custom resource per event
|
||||
# (endpointmonitor_controller.go:62-107) and never enumerates the UptimeRobot
|
||||
# account, so with no EndpointMonitors it reconciles nothing and makes no API
|
||||
# call to UptimeRobot at all. The 9 monitors already in that account are not
|
||||
# read, not updated and not deleted.
|
||||
#
|
||||
# Plain manifests, not the upstream HelmRelease: the chart's RBAC grants pods,
|
||||
# services and `secrets: list`, and it also creates a Service and optional
|
||||
# cert-manager objects for a metrics endpoint that is disabled below. The
|
||||
# maintained thing we depend on is the upstream image, which is pinned here.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ingressmonitorcontroller
|
||||
namespace: uptime-monitor
|
||||
labels:
|
||||
app.kubernetes.io/name: ingressmonitorcontroller
|
||||
annotations:
|
||||
# The API key is read ONCE, at startup (pkg/config/config.go
|
||||
# `LoadControllerConfig` runs before the manager starts), so a rotated
|
||||
# Secret is not picked up until the pod restarts. Reloader — which this
|
||||
# cluster already runs — does that restart. Same idiom as
|
||||
# apps/external-dns/helmrelease.yaml.
|
||||
secret.reloader.stakater.com/reload: uptime-robot-api-key
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
# Recreate, not RollingUpdate, and no --leader-elect. A rolling update would
|
||||
# briefly run two reconcilers against the same UptimeRobot account; Recreate
|
||||
# makes "at most one controller pod, ever" a property of the Deployment
|
||||
# instead of something a Lease has to arbitrate. It costs a few seconds of
|
||||
# controller downtime per upgrade, which for a monitor-syncing controller is
|
||||
# nothing, and it removes the leases/events RBAC entirely.
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: ingressmonitorcontroller
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: ingressmonitorcontroller
|
||||
spec:
|
||||
serviceAccountName: ingressmonitorcontroller
|
||||
automountServiceAccountToken: true
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: manager
|
||||
# Pinned, and deliberately NOT under Flux image automation. rbac.yaml
|
||||
# is derived from what this exact version calls; an automatic bump
|
||||
# could add a call the Role does not cover, silently.
|
||||
image: ghcr.io/stakater/ingressmonitorcontroller:v2.2.13
|
||||
command: ["/manager"]
|
||||
args:
|
||||
- --health-probe-bind-address=:8081
|
||||
# Metrics off. Enabling them means either TLS certs or
|
||||
# `--metrics-secure=false`, and the authn/authz filter
|
||||
# (cmd/main.go, `filters.WithAuthenticationAndAuthorization`) wants a
|
||||
# ClusterRole for TokenReview/SubjectAccessReview — a cluster-scoped
|
||||
# grant this namespace otherwise does not need. A ServiceMonitor for
|
||||
# this controller is worth having, but it is its own change.
|
||||
- --metrics-bind-address=0
|
||||
env:
|
||||
# Scopes the manager's cache to this namespace
|
||||
# (cmd/main.go `buildDefaultNamespaces`), which is what makes the
|
||||
# namespaced Role in rbac.yaml sufficient — there is no ClusterRole.
|
||||
- name: WATCH_NAMESPACE
|
||||
value: uptime-monitor
|
||||
# The namespace the config Secret is looked up in. Read from the
|
||||
# ServiceAccount token file when unset; set explicitly so the lookup
|
||||
# is visible in the manifest.
|
||||
- name: OPERATOR_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
# IMC reads its whole configuration from key `config.yaml` of this
|
||||
# Secret — providers, API key and enableMonitorDeletion together.
|
||||
# ESO renders that key; see externalsecret.yaml for the contents and
|
||||
# for why the config lives in git while the key does not.
|
||||
- name: CONFIG_SECRET_NAME
|
||||
value: uptime-robot-api-key
|
||||
# Both probes are served by the manager on :8081 and are dialled by
|
||||
# the kubelet, which Cilium identifies as the `host` entity — the only
|
||||
# ingress the namespace policy admits
|
||||
# (infrastructure/network-policies/uptime-monitor-ns.yaml). The port is
|
||||
# not exposed as a containerPort because nothing else may reach it.
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8081
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: 8081
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
terminationGracePeriodSeconds: 10
|
||||
@@ -12,12 +12,45 @@
|
||||
# refreshInterval 1h matches the un-rotated ExternalSecrets here (velero,
|
||||
# wiki-*). The rotated ones use 10m so an automated roll propagates fast — if
|
||||
# kubernetes/infrastructure/openbao/rotation/ ever gains an uptime-robot
|
||||
# rotator, drop this to 10m and annotate the controller Deployment for
|
||||
# stakater/reloader, which the cluster already runs.
|
||||
# rotator, drop this to 10m. The controller Deployment already carries the
|
||||
# stakater/reloader annotation, so a new value restarts the pod that has the
|
||||
# old one cached.
|
||||
#
|
||||
# The `eso` role already reads this path: eso-read grants `homelab/data/*`
|
||||
# (kubernetes/infrastructure/openbao/policies/eso-read.hcl:31). No OpenBao
|
||||
# policy change is needed for this key.
|
||||
#
|
||||
# ---
|
||||
# Why this Secret holds a rendered `config.yaml` rather than a bare key (REN-6):
|
||||
# IngressMonitorController does not read an API key from an env var. It reads
|
||||
# its ENTIRE configuration — provider list, API key, enableMonitorDeletion —
|
||||
# from one Secret key named `config.yaml` (pkg/config/config.go,
|
||||
# `IngressMonitorControllerSecretConfigKey`), at startup, through the manager's
|
||||
# APIReader. So the choice is not "key or config"; it is which Secret the config
|
||||
# lives in. Templating it here keeps every non-secret decision in git and in
|
||||
# review, with only the key itself injected at sync time.
|
||||
#
|
||||
# The template replaces the Secret's data rather than merging, so `apiKey` is a
|
||||
# template input and the rendered `config.yaml` is the single output key.
|
||||
#
|
||||
# Two values are load-bearing:
|
||||
#
|
||||
# apiURL — REQUIRED. `Setup()` assigns `monitor.url = p.ApiURL` with no
|
||||
# default (pkg/monitors/uptimerobot/uptime-monitor.go:39-44); omit it and
|
||||
# every request goes to a relative URL.
|
||||
#
|
||||
# enableMonitorDeletion — true, so that removing an EndpointMonitor removes
|
||||
# its monitor, which is what the mission asks for. It is bounded: deletion is
|
||||
# reachable only via `GetByName`, which filters `m.FriendlyName == name` —
|
||||
# exact equality — against the name the controller itself computed from the
|
||||
# default template `{{.Name}}-{{.Namespace}}`. Every name it can compute ends
|
||||
# in `-uptime-monitor`; none of the 9 monitors already in the account does.
|
||||
# Set it to false for a create/update-only kill switch.
|
||||
#
|
||||
# monitorNameTemplate is deliberately absent: an empty value makes
|
||||
# GetNameTemplateFormat fall back to that same default (pkg/util/template-utils.go:9),
|
||||
# and writing it out here would mean escaping Go template syntax inside a Go
|
||||
# template for no gain.
|
||||
apiVersion: external-secrets.io/v1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
@@ -30,8 +63,17 @@ spec:
|
||||
name: openbao
|
||||
target:
|
||||
name: uptime-robot-api-key
|
||||
template:
|
||||
engineVersion: v2
|
||||
data:
|
||||
config.yaml: |
|
||||
providers:
|
||||
- name: UptimeRobot
|
||||
apiKey: "{{ .apiKey }}"
|
||||
apiURL: https://api.uptimerobot.com/v2/
|
||||
enableMonitorDeletion: true
|
||||
data:
|
||||
- secretKey: api-key
|
||||
- secretKey: apiKey
|
||||
remoteRef:
|
||||
key: infrastructure/uptime-robot
|
||||
property: api_key
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- crd-endpointmonitors.yaml
|
||||
- externalsecret.yaml
|
||||
- rbac.yaml
|
||||
- deployment.yaml
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
# ServiceAccount + namespaced RBAC for IngressMonitorController (REN-6).
|
||||
#
|
||||
# There is NO ClusterRole here, on purpose. The Deployment sets
|
||||
# WATCH_NAMESPACE=uptime-monitor, which makes the manager build a
|
||||
# namespace-scoped cache (cmd/main.go `buildDefaultNamespaces`), so every read
|
||||
# the controller performs is confined to this namespace. A ClusterRole would
|
||||
# grant reach the process cannot use.
|
||||
#
|
||||
# The rules below were derived from what the pinned image actually calls, not
|
||||
# from the chart's rules or the upstream kubebuilder markers. Deliberately NOT
|
||||
# granted, and why:
|
||||
#
|
||||
# - `""/pods`, `""/services` — read from exactly one place,
|
||||
# `tryGetHealthEndpointFromIngress` (pkg/kube/wrappers/ingress-wrapper.go:126),
|
||||
# which is reached only from the `urlFrom.ingressRef` path and only when the
|
||||
# EndpointMonitor sets no explicit `healthEndpoint`. It is a best-effort
|
||||
# guess at a readiness-probe path: both the Get and the List log the error
|
||||
# and return `"", false` on failure, so a denial costs a log line, not a
|
||||
# reconcile. Since the ingressRef path can only reach this namespace anyway
|
||||
# (see the ingresses rule below) and nothing here serves HTTP, the grant
|
||||
# would be inert. Add it if an Ingress ever lands in `uptime-monitor`.
|
||||
# - `route.openshift.io/routes` — OpenShift only; `kube.IsOpenshift` is false
|
||||
# here, so the Route scheme is never even registered (cmd/main.go init()).
|
||||
# - `endpointmonitors/finalizers` — IMC uses no finalizers. Deletion is driven
|
||||
# by the reconciler's IsNotFound branch, not by a finalizer
|
||||
# (endpointmonitor_controller.go:74-81); `grep -rn Finalizer internal/controller/`
|
||||
# has no hits.
|
||||
# - leases / events — no leader election (see the Recreate strategy comment in
|
||||
# deployment.yaml) and the reconciler holds no EventRecorder.
|
||||
#
|
||||
# Re-check this list whenever the image tag in deployment.yaml moves: it is
|
||||
# pinned and not Renovate-managed precisely so that never happens silently.
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ingressmonitorcontroller
|
||||
namespace: uptime-monitor
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: ingressmonitorcontroller
|
||||
namespace: uptime-monitor
|
||||
rules:
|
||||
# Its own custom resource — the only thing the reconciler is keyed on.
|
||||
- apiGroups: ["endpointmonitor.stakater.com"]
|
||||
resources: ["endpointmonitors"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# Not exercised by v2.2.13 (`grep -rn "Status()" internal/controller/` is
|
||||
# empty), but granted: it is this CRD's own subresource, so it confers no
|
||||
# reach outside the resource already listed above, and it is the one write an
|
||||
# operator of this shape is expected to gain.
|
||||
- apiGroups: ["endpointmonitor.stakater.com"]
|
||||
resources: ["endpointmonitors/status"]
|
||||
verbs: ["get", "update", "patch"]
|
||||
# Read-only. Used to resolve an EndpointMonitor's URL from an Ingress via
|
||||
# `spec.urlFrom.ingressRef`. Note that pkg/kube/util/url.go:71 resolves that
|
||||
# ref against the EndpointMonitor's OWN namespace, so this grant can only ever
|
||||
# reach Ingresses in `uptime-monitor` — it cannot read an app's Ingress.
|
||||
- apiGroups: ["networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# The controller loads its entire configuration — including the UptimeRobot
|
||||
# API key — from one Secret key at startup (pkg/config/config.go
|
||||
# `LoadControllerConfig`). It reads it through the manager's APIReader, i.e. a
|
||||
# direct GET by name and not a cached list, which is why `get` alone is enough
|
||||
# and why resourceNames can pin it to the single Secret ESO renders.
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["uptime-robot-api-key"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: ingressmonitorcontroller
|
||||
namespace: uptime-monitor
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: ingressmonitorcontroller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: ingressmonitorcontroller
|
||||
namespace: uptime-monitor
|
||||
@@ -1,7 +1,11 @@
|
||||
# Automated uptime monitoring (REN-2): Stakater IngressMonitorController keeps
|
||||
# UptimeRobot monitors in sync with `monitor.stakater.com/enabled` annotations on
|
||||
# Ingress objects. This Kustomization starts with the API key only; the
|
||||
# controller, its ServiceAccount and its RBAC land here in REN-6.
|
||||
# UptimeRobot monitors in sync with `EndpointMonitor` custom resources. (Not
|
||||
# Ingress annotations — `monitor.stakater.com/enabled` does nothing in IMC v2;
|
||||
# there is no Ingress reconciler in the source, and docs/migration-guide.md
|
||||
# calls the annotation mechanism obsolete. The opt-in property survives intact:
|
||||
# creating an EndpointMonitor per host is more explicit than an annotation, and
|
||||
# the default state is still nothing-monitored.) This Kustomization holds the
|
||||
# API key, the controller Deployment, its ServiceAccount and its RBAC (REN-6).
|
||||
#
|
||||
# A DEDICATED Kustomization rather than adding the ExternalSecret to
|
||||
# ./kubernetes/infrastructure/post-crds, which is where the other
|
||||
@@ -36,6 +40,22 @@
|
||||
# holds this Kustomization NotReady, and it is safe to fail loudly here
|
||||
# precisely because the blast radius is this Kustomization alone: nothing
|
||||
# `dependsOn` it.
|
||||
#
|
||||
# That gate earns its keep now that the controller is here (REN-6) rather than
|
||||
# only the key. IMC GETs its whole configuration from the Secret once, at
|
||||
# startup, through the manager's APIReader, and never revalidates. Which failure
|
||||
# it papers over depends on which one happens:
|
||||
#
|
||||
# * Secret absent (first sync never succeeded — `creationPolicy` is the default
|
||||
# Owner, so no successful sync means no Secret at all): IMC panics in
|
||||
# `LoadControllerConfig` and the pod CrashLoopBackOffs. That one is loud.
|
||||
# * Secret present but stale, because a LATER refresh failed: the pod starts
|
||||
# against the old value, reports Available, and the first sign of trouble is
|
||||
# a rejected UptimeRobot call on some future reconcile. Nothing about
|
||||
# Deployment health says the key is current.
|
||||
#
|
||||
# The second case is the live one — the Secret exists today — and the CEL rule
|
||||
# below is what turns it into a NotReady Kustomization instead of a green one.
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
@@ -43,9 +63,10 @@ metadata:
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 10m
|
||||
# 2m, not the 1m most app Kustomizations use: the one object here is an
|
||||
# ExternalSecret against a key a human may still be placing in OpenBao, and a
|
||||
# retry every minute against a 404 is just noise in the ESO and Flux logs.
|
||||
# 2m, not the 1m most app Kustomizations use: what stalls this Kustomization
|
||||
# is an ExternalSecret against a key a human may still be placing in OpenBao,
|
||||
# and a retry every minute against a 404 is just noise in the ESO and Flux
|
||||
# logs.
|
||||
retryInterval: 2m
|
||||
timeout: 5m
|
||||
sourceRef:
|
||||
@@ -55,10 +76,10 @@ spec:
|
||||
prune: true
|
||||
wait: true
|
||||
# No name/namespace qualifier, so this is evaluated against every
|
||||
# ExternalSecret in this Kustomization's inventory. Today that is exactly one
|
||||
# (apps/uptime-monitor/externalsecret.yaml); when the IMC Deployment joins in
|
||||
# REN-6 it is still one, and any ExternalSecret added here later should be held
|
||||
# to the same bar anyway.
|
||||
# ExternalSecret in this Kustomization's inventory. That is exactly one
|
||||
# (apps/uptime-monitor/externalsecret.yaml) — the IMC Deployment added in
|
||||
# REN-6 brought no second one — and any ExternalSecret added here later should
|
||||
# be held to the same bar anyway.
|
||||
#
|
||||
# `external-secrets.io/v1` is the version every one of the 54 ExternalSecret
|
||||
# manifests in this repo is written against, so it is the GVK Flux records in
|
||||
|
||||
@@ -245,14 +245,14 @@ kind: Namespace
|
||||
metadata:
|
||||
name: paperclip
|
||||
---
|
||||
# IngressMonitorController, which syncs UptimeRobot monitors from Ingress
|
||||
# annotations (REN-2). Declared here rather than in the workload Kustomization
|
||||
# IngressMonitorController, which syncs UptimeRobot monitors from
|
||||
# `EndpointMonitor` custom resources (REN-2; not Ingress annotations — those are
|
||||
# obsolete in IMC v2). Declared here rather than in the workload Kustomization
|
||||
# for the reason spelled out above `woodpecker`: the `infrastructure` KS creates
|
||||
# namespaces, and `infrastructure-crds` (post-crds, which includes
|
||||
# ../network-policies/) runs after it — so declaring it in the workload KS would
|
||||
# invert that order the moment this namespace gains a CiliumNetworkPolicy, which
|
||||
# it will when the controller arrives and needs scoped egress to the UptimeRobot
|
||||
# API. It holds nothing but the API key Secret until then.
|
||||
# invert that order the moment this namespace gains a CiliumNetworkPolicy. It
|
||||
# has one now: ../network-policies/uptime-monitor-ns.yaml.
|
||||
#
|
||||
# No pod-security labels: the controller needs no hostPath, hostNetwork or
|
||||
# privilege, so the cluster's default `baseline` enforcement is enough.
|
||||
|
||||
@@ -29,4 +29,5 @@ resources:
|
||||
- wordpress-ns.yaml
|
||||
- ai-ns.yaml
|
||||
- navidrome-ns.yaml
|
||||
- uptime-monitor-ns.yaml
|
||||
- allow-cilium-health.yaml
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
# Baseline policy for the `uptime-monitor` namespace (REN-6).
|
||||
#
|
||||
# Copied from netbird-service-sync-ns.yaml, which is the closest existing
|
||||
# neighbour in this directory: a controller that serves nothing and only calls
|
||||
# out — kube-apiserver plus one external HTTPS API. Same shape here.
|
||||
#
|
||||
# No Traefik ingress rule, because nothing in this namespace is behind an
|
||||
# Ingress. (For anyone reaching for a wiki note: the selector to admit Traefik
|
||||
# lives in `ingress` with label `app.kubernetes.io/name: traefik`, as in
|
||||
# paperclip-ns.yaml. An `ingress-nginx` selector matches nothing in this cluster
|
||||
# and would admit nothing, silently.)
|
||||
#
|
||||
# Deliberately no OpenBao egress: the controller never talks to OpenBao. ESO, in
|
||||
# the external-secrets namespace, resolves the key and writes the Secret; the
|
||||
# controller only reads that Secret through the API server.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: default-deny
|
||||
namespace: uptime-monitor
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
# Kubelet liveness/readiness probes on :8081 arrive with the `host`
|
||||
# identity. This is the only ingress the namespace admits — without it the
|
||||
# controller pod fails its probes and CrashLoopBackOffs, with nothing in its
|
||||
# own logs to say why.
|
||||
- fromEntities:
|
||||
- host
|
||||
- remote-node
|
||||
egress:
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": kube-system
|
||||
k8s-app: kube-dns
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "53"
|
||||
protocol: UDP
|
||||
- port: "53"
|
||||
protocol: TCP
|
||||
# EndpointMonitor watch, Ingress reads, and the startup GET of the config
|
||||
# Secret.
|
||||
- toEntities:
|
||||
- kube-apiserver
|
||||
# api.uptimerobot.com. Not reached at all until an EndpointMonitor exists:
|
||||
# the controller makes no API call at startup.
|
||||
- toEntities:
|
||||
- world
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "443"
|
||||
protocol: TCP
|
||||
Reference in New Issue
Block a user