Ingress Info Service

Introduction

Status: Approved Status: Approved

The Ingress Info Service exposes a read-only, filtered, and typed view of Kubernetes Ingress resources to internal cluster clients. The service MUST provide a single REST endpoint to list ingresses, protect business endpoints via bearer keys, and support deterministic caching with ETag. The scope excludes active reachability probing and any mutating APIs.

Primary use case: power an end-user links portal by exposing host- and path-level metadata for web services deployed in the cluster (see ADR-0001).

1. Glossary

Term Definition
Ingress Kubernetes Networking resource mapping hosts/paths to backends
Endpoint/EndpointSlice Kubernetes resources exposing ready backends for Services
Projection In-memory view derived from informers, used to serve API
ETag Hash-based validator computed from resourceVersions for cache control
Huma HTTP framework used to define typed operations and expose OpenAPI/UI

2. Goals and Non-Goals

  • Goals:
    • G-001: Provide a deterministic, typed list of ingresses with host/path metadata and best-effort reachability.
    • G-002: Offer conditional GET via ETag (If-None-Match) for efficient caching.
    • G-003: Secure API endpoints under /v1/* with static bearer keys from a Kubernetes Secret.
    • G-004: Expose OpenAPI JSON and a minimal UI via Huma v2.
    • G-005: Provide readiness/liveness probes and Prometheus metrics for operations.
    • G-006: Optimize the response shape for a links portal client by exposing a top-level hosts array with nested paths (per ADR-0001).
  • Non-Goals:
    • NG-001: No mutation of cluster resources.
    • NG-002: No active network probing; reachability remains passive and best-effort.
    • NG-003: No pagination or detail-by-name endpoint in MVP.

3. Stakeholders

Role Responsibilities Contact
Platform Team Owns build/release, operations, SLOs, and roadmap platform-team
Service Consumers Read ingresses to build internal dashboards/pages App teams
Security Reviews auth controls and Secret handling SecOps

4. System Context

  • Context overview and external dependencies
    • Runs as a Deployment in-cluster (ClusterIP Service), consumes the Kubernetes API (in-cluster or kubeconfig when run locally), reads a ConfigMap for selection/mapping and a Secret for API keys. Exposes HTTP on a configurable address (default :8080).
    • Primary consumer is an end-user Links Portal that renders web-service links using host and path metadata from this service (see ADR-0001).
  • Diagram:
flowchart TD
  subgraph Cluster[Kubernetes Cluster]
    LinksPortal[Links Portal (primary consumer)] -->|GET /v1/ingresses| API[(Ingress Info Service)]
    Client[Other Internal Clients] -->|GET /v1/ingresses| API
    API -->|List/Watch| K8sAPI[(Kubernetes API)]
    ConfigMap[(ConfigMap ingress-info-config)] --> API
    Secret[(Secret ingress-info-keys)] --> API
    API -->|/metrics| Prometheus[(Prometheus)]
    API -->|/openapi.json + UI| Devs[Developers]
  end

5. Technology Stack

Enumerate the selected technologies and rationale. This section MUST be used by subsequent plans and breakdowns.

Area Selection Version/Target Rationale
Programming Language Go 1.25 Modern features, matches module; strong Kubernetes ecosystem
Framework/Runtime Huma v2 over net/http v2.x Typed handlers, OpenAPI generation, built-in UI
Data Store None N/A Read-only view; in-memory projection only
Messaging/Streaming None N/A Not required
API Style REST RFC 7231 Simple interoperability
AuthN/AuthZ Static Bearer Keys (K8s Secret) N/A Simple internal auth; easy rotation
Observability Prometheus + structured JSON logs client_golang 1.23.x Standard metrics and logs in platform
Testing Go test stdlib Native testing, table-driven style
CI/CD GitHub Actions multi-job Hosted runners, caching, standard workflows
Packaging/Deploy Docker + Kubernetes linux (amd64, arm64) Standard platform; scratch runtime image

6. Functional Requirements

Use FR-XXX identifiers; each MUST be testable.

ID Statement Rationale Acceptance Criteria (AC-IDs)
FR-001 The service SHALL expose GET /v1/ingresses returning a typed list of ingresses. Core product capability AC-001, AC-002
FR-002 The service SHALL compute and return an ETag header derived deterministically from resourceVersions. Efficient caching AC-003
FR-003 The service SHALL honor If-None-Match and return 304 when the ETag matches. HTTP cache validation AC-004
FR-004 The service SHALL protect all /v1/* endpoints with Authorization: Bearer KEY validated against the Secret. Access control AC-005
FR-005 The service SHALL expose /healthz and /readyz, reporting ready after initial informer sync and successful config load. K8s readiness AC-006
FR-006 The service SHALL expose /metrics with Prometheus metrics for requests, durations, and projection size. Observability AC-007
FR-007 The service SHALL serve OpenAPI JSON and a minimal UI via Huma v2. Developer experience AC-008
FR-008 The service SHALL sort results deterministically by (namespace, name, host, path). Stable ETag and diffability AC-002

7. Non-Functional Requirements

Use NFR-XXX identifiers covering performance, reliability, security, compliance, operability.

ID Category Statement Metric/Target
NFR-001 Performance List endpoint median latency under low load p50 < 50ms in-cluster
NFR-002 Performance List endpoint tail latency p99 < 500ms in-cluster
NFR-003 Reliability Startup readiness after cache sync and config load < 30s under normal cluster conditions
NFR-004 Security No secrets in logs; bearer tokens never logged Zero occurrences (audit)
NFR-005 Operability Service reports request metrics and durations http_requests_total and http_response_duration_seconds present
NFR-006 Compatibility Support Kubernetes 1.30–1.33 Verified in CI matrix
NFR-007 Portability Configurable listen address LISTEN_ADDR env; default :8080

8. Domain and Data Model

8.1 Domain Entities

classDiagram
  class IngressRecord {
    string namespace
    string name
    HostEntry[] hosts
  }
  class HostEntry {
    string host
    string servicename
    string user_group
    string description
    string contact_information
    bool missing_annotations
    PathEntry[] paths
  }
  class PathEntry {
    string path
    string pathType
    string reachability  // reachable | unreachable | unknown
    string backendService
  }
  IngressRecord --> HostEntry
  HostEntry --> PathEntry

8.2 Data Schemas

Provide JSON Schemas for API-visible structures.

Ingress list response (Snapshot):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:ingress-info:HostsList",
  "type": "object",
  "properties": {
    "hosts": {
      "type": "array",
      "items": { "$ref": "#/$defs/HostEntry" }
    }
  },
  "required": ["hosts"],
  "$defs": {
    "HostEntry": {
      "type": "object",
      "properties": {
        "host": { "type": "string", "minLength": 1 },
        "servicename": { "type": "string" },
        "user_group": { "type": "string" },
        "description": { "type": "string" },
        "contact_information": { "type": "string" },
        "missing_annotations": { "type": "boolean" },
        "paths": {
          "type": "array",
          "items": { "$ref": "#/$defs/PathEntry" }
        }
      },
      "required": ["host", "missing_annotations", "paths"],
      "additionalProperties": false
    },
    "PathEntry": {
      "type": "object",
      "properties": {
        "path": { "type": "string" },
        "pathType": { "type": "string", "enum": ["Exact", "Prefix", "ImplementationSpecific", ""] },
        "reachability": { "type": "string", "enum": ["reachable", "unreachable", "unknown"] },
        "backendService": { "type": "string" }
      },
      "required": ["path", "reachability"],
      "additionalProperties": false
    }
  }
}

Error response model:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:ingress-info:Error",
  "type": "object",
  "properties": {
    "message": { "type": "string" },
    "code": { "type": "string" },
    "details": { "type": "object", "additionalProperties": true }
  },
  "required": ["message"],
  "additionalProperties": false
}

9. API Specification

Use API-XXX identifiers per endpoint. Include method, path, auth, request/response schemas, status codes, examples, and error model.

API-001: GET /v1/ingresses

  • Auth: Authorization: Bearer KEY
  • Request Headers:
    • If-None-Match (optional)
  • Query/Path Params: none
  • Request Body: none
  • Responses:
    • 200 OK β€” application/json schema: HostsList
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "hosts": [
    {
      "host": "shop.example.com",
      "servicename": "shop-frontend",
      "user_group": "web",
      "description": "Customer storefront",
      "contact_information": "oncall@example.com",
      "missing_annotations": false,
      "paths": [
        { "path": "/", "pathType": "Prefix", "backendService": "shop-frontend", "reachability": "reachable" }
      ]
    }
  ]
}
  • 304 Not Modified β€” no body
  • 401 Unauthorized β€” Error schema
1
{ "message": "unauthorized", "code": "UNAUTHORIZED" }
  • 500 Internal Server Error β€” Error schema
1
{ "message": "internal error", "code": "INTERNAL" }
  • Headers:
    • ETag: strong validator computed from resourceVersions map
    • Cache-Control: no-store (SHOULD be configurable per deployment policy)

10. Configuration

Enumerate all configuration keys, sources, defaults, and validation.

Key Source Type Default Required Description
LISTEN_ADDR Env string :8080 false HTTP listen address (host:port or :port)
INGRESS_INFO_CONFIG_FILE Env string /etc/ingress-info/config.yaml false Path to YAML/JSON config file; if absent, fall back to ConfigMap
INGRESS_INFO_KEYS_FILE Env string empty false Optional file with one key per line; overrides Secret for local runs
POD_NAMESPACE Env string default false Namespace for ConfigMap/Secret lookup when in-cluster
KUBECONFIG Env string empty false kubeconfig path for out-of-cluster runs
ConfigMap name K8s string ingress-info-config true Name of ConfigMap providing labelSelector and mapping.* keys
Secret name K8s string ingress-info-keys true Name of Secret providing valid API keys
config.labelSelector File/ConfigMap string none true Label selector for ingresses
config.mapping.service_name File/ConfigMap string empty false annotation.KEY or label.KEY or plain key name; populates host service_name
config.mapping.user_group File/ConfigMap string empty false annotation.KEY or label.KEY; populates user_group
config.mapping.description File/ConfigMap string empty false annotation.KEY or label.KEY; populates description
config.mapping.contact_information File/ConfigMap string empty false annotation.KEY or label.KEY; populates contact_information

Validation rules:

  • LISTEN_ADDR MUST parse as valid TCP address.
  • labelSelector MUST be a valid Kubernetes label selector string.

11. Security

Use SEC-XXX identifiers. Cover authentication, authorization, data protection, secrets, and supply chain.

ID Area Control/Requirement
SEC-001 AuthN /v1/* requires Authorization: Bearer KEY; exact-match against keys loaded from Secret or file.
SEC-002 Secrets Keys are loaded into memory and never logged.
SEC-003 Transport In-cluster traffic; TLS termination may be handled by ingress/gateway if externally exposed.
SEC-004 Least Privilege ServiceAccount bound to ClusterRole with read-only on Ingress, Endpoints, EndpointSlice.
SEC-005 Logging No sensitive headers or token values in logs; only derived client_id may be logged.
SEC-006 Supply Chain Build with pinned versions and reproducible Docker (multi-stage; scratch runtime).

12. Observability

Use OBS-XXX identifiers. Define metrics, logs, traces, and alerts.

Note: For the Links Portal use case, request metrics and handler latency for GET /v1/ingresses, as well as projection_size, are especially relevant to monitor perceived portal responsiveness and data freshness.

ID Type Name/Field Labels Semantics
OBS-001 Metric http_requests_total method, path, code Count of HTTP requests; code is HTTP status text
OBS-002 Metric http_response_duration_seconds method, path Histogram of handler latency (seconds)
OBS-003 Metric projection_size none Count of typed objects in the projection snapshot (ingresses + endpoints + endpointSlices)
OBS-004 Logs request fields request_id, client_id, method, path, status, latency_ms Structured JSON log per request
OBS-005 Probes /healthz, /readyz n/a Readiness gates on initial cache sync and config load

Alerts (suggested):

  • High 5xx rate on /v1/ingresses over 5 minutes.
  • Ready=false for > 5 minutes after rollout.

13. Operations

Runbooks, SLOs, scaling, rollout, rollback, backups, DR, upgrades.

ID Area Procedure
OPS-001 Deployment Roll out new image via standard Deployment; readiness probe ensures safe traffic shift.
OPS-002 Key Rotation Update Secret ingress-info-keys; service reloads in background within 30s.
OPS-003 Config Change Update ConfigMap ingress-info-config; mount file or reload via periodic loader; restart if required.
OPS-004 Scaling Horizontal scaling supported; clients use ClusterIP Service.
OPS-005 Upgrade Policy Validate against K8s 1.30–1.33; bump client-go as needed.

14. Compatibility and Versioning

Use COMP-XXX identifiers. Define versioning policy, API stability, and deprecation.

  • COMP-001: API versioning via path prefix /v1; backward-compatible additions allowed; breaking changes require /v2.
  • COMP-002: ETag semantics MUST remain stable (resourceVersions-hash) within a minor line.
  • COMP-003: Supported Kubernetes versions: 1.30–1.33; other versions are best-effort.

15. Dependencies

Use DEP-XXX identifiers. List runtime and build-time dependencies with versions and licenses.

ID Name Version License Purpose
DEP-001 k8s.io/api v0.34.1 Apache-2.0 Object types
DEP-002 k8s.io/apimachinery v0.34.1 Apache-2.0 Meta/runtime utilities
DEP-003 k8s.io/client-go v0.34.1 Apache-2.0 Informers and clients
DEP-004 github.com/prometheus/client_golang v1.23.2 Apache-2.0 Metrics exposition
DEP-005 sigs.k8s.io/yaml v1.6.0 Apache-2.0 YAML parsing
DEP-006 github.com/danielgtaylor/huma v2.x MIT HTTP framework + OpenAPI
DEP-007 github.com/alecthomas/kong vX MIT CLI parsing

16. Risks, Assumptions, Decisions

Type ID Statement
Risk RISK-001 Reachability heuristic may misclassify when host/service resolution is ambiguous.
Risk RISK-002 Static keys require operational rotation discipline.
Decision DECISION-001 Adopt Huma v2 for routing and OpenAPI/UI.
Decision DECISION-002 Keep best-effort reachability; no active probes.
Decision DECISION-003 Deterministic ordering by (namespace, name, host, path).

Assumptions are maintained in Appendix (ASM-###).

17. Acceptance Criteria

Use AC-XXX identifiers; map each to FR/NFR.

ID Verifiable Statement Maps To
AC-001 GET /v1/ingresses returns HTTP 200 with schema-valid body when authorized. FR-001
AC-002 Hosts and nested paths are sorted deterministically by (host, path). FR-008
AC-003 Response includes an ETag header that changes when underlying resourceVersions change. FR-002
AC-004 If-None-Match equal to ETag yields HTTP 304 with no body. FR-003
AC-005 Missing/invalid Authorization returns HTTP 401. FR-004, SEC-001
AC-006 /readyz returns 200 only after informer cache sync and config load. FR-005
AC-007 /metrics exposes http_requests_total and http_response_duration_seconds. FR-006, NFR-005
AC-008 /openapi.json is served and the UI renders the list operation. FR-007

18. Test Plan

Use TEST-XXX identifiers. Outline unit, integration, e2e, performance, and security tests. Link ACs.

ID Type Scope Tools ACs
TEST-001 Unit Projection ETag computation go test AC-003
TEST-002 Unit Auth middleware (valid/invalid bearer) go test AC-005
TEST-003 Unit Sorting order of hosts and paths go test AC-002
TEST-004 Unit Reachability classification go test AC-001
TEST-005 Integration Ready after cache sync + config load go test / kind AC-006
TEST-006 Integration Conditional GET 304 path go test / http client AC-004
TEST-007 Integration Metrics presence curl /metrics AC-007
TEST-008 Integration OpenAPI served and validates curl /openapi.json AC-008

19. Documentation Plan

Following DiΓ‘taxis Framework principles. Use DOC-XXX identifiers for traceability.

ID Type Audience Purpose Location Linked Requirements
DOC-001 Tutorial New users Run locally against a kubeconfig using simulator docs/tutorials/ FR-001, AC-001
DOC-002 How-to Guide Practitioners Rotate API keys safely docs/how-to/ OPS-002, SEC-002
DOC-003 Reference All users API reference (OpenAPI link) docs/reference/ API-001, AC-008
DOC-004 Explanation Architects Rationale for passive reachability docs/explanations/ DECISION-002

Documentation Types

  • Tutorials: Step-by-step learning experiences for new users
  • How-to Guides: Task-oriented instructions for specific goals
  • Reference: Complete, accurate descriptions of the system
  • Explanations: Conceptual discussions of design decisions and architecture

20. Out of Scope

  • Pagination and detail-by-name endpoints
  • Multi-tenant authorization and per-key scoping
  • Active reachability probes (HTTP/TCP checks)
  • Non-Kubernetes deployments

21. Appendix

ADR Changes

  • Changed by ADR-0001: Hosts Top-Level Response Shape for GET /v1/ingresses on 2025-09-15.

References, prior art, ADR links, and diagrams.

Assumptions Ledger

ID Statement Source Rationale Status
ASM-001 Service is cluster-internal (ClusterIP). Inferred Deployment/Service internal usage. Accepted
ASM-002 MVP exposes only GET /v1/ingresses. Detected Single handler wired; minimizes scope. Accepted
ASM-003 ETag is a hash of resourceVersions and remains stable. Detected Current implementation; deterministic. Accepted
ASM-004 Static bearer keys are sufficient; no JWT/OIDC. User-Provided Simplicity for internal use. Accepted
ASM-005 Canonical port is 8080 and is configurable. User-Provided Aligns manifests; flexibility. Accepted
ASM-006 Use Huma v2 with OpenAPI and UI. User-Provided Developer experience and typed contract. Accepted
ASM-007 Reachability is best-effort via Endpoints/EndpointSlice only. Detected No active probing. Accepted
ASM-008 Supported Kubernetes range is 1.30–1.33. User-Provided Target platforms. Accepted
ASM-009 Build with Go 1.25; scratch runtime. Detected Align toolchain and image. Accepted
ASM-010 ConfigMap/Secret names remain ingress-info-config/ingress-info-keys. User-Provided Consistency with manifests. Accepted

Provenance

  • Interactive Q&A conducted with one-at-a-time confirmations.
  • Key confirmations: name (Ingress Info Service), port 8080 (configurable), static bearer auth, single endpoint, ETag-only, Huma v2 + OpenAPI/UI, strict typed schemas, K8s 1.30–1.33, Go 1.25, object names unchanged, structured logs with request_id/client_id, CI with GitHub Actions.

Additional Notes

  • Implementation MUST update server listen address to be configurable and default to :8080.
  • Implementation SHOULD wire Huma v2 as the primary router, exposing /openapi.json and the built-in UI. Legacy net/http mux MAY be retained internally if needed during migration.
  • Deterministic ordering MUST be enforced for the response body to aid diffability; ETag remains based on the resourceVersions map and is independent of host/path order.

Change History

  • 2025-09-15: Specification approved; status set to Approved
  • 2025-09-15: Updated response schema and examples to use top-level hosts per ADR-0001 (docs/adr/adr-0001-hosts-top-level-response.md)