Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions content/en/docs/v1.4/applications/postgres.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,10 @@ See:

### Application-specific parameters

| Name | Description | Type | Value |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------- | ----- |
| `postgresql` | PostgreSQL server configuration. | `object` | `{}` |
| `postgresql.parameters` | PostgreSQL server parameters. All values must be strings (quote numbers: "100"). BLOCKED (enable arbitrary code execution): archive_command, restore_command, ssl_passphrase_command, dynamic_library_path, local_preload_libraries, session_preload_libraries, shared_preload_libraries. Do NOT override CloudNativePG-managed parameters: archive_mode, primary_conninfo, wal_level, max_replication_slots. | `map[string]string` | `{}` |
| Name | Description | Type | Value |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- |
| `postgresql` | PostgreSQL server configuration. | `object` | `{}` |
| `postgresql.parameters` | PostgreSQL server parameters. Values may be strings or integers; integers are coerced to strings by the template (e.g. both `max_connections: 100` and `max_connections: "100"` are accepted). BLOCKED (enable arbitrary code execution): archive_command, restore_command, ssl_passphrase_command, archive_cleanup_command, recovery_end_command, dynamic_library_path, local_preload_libraries, session_preload_libraries, shared_preload_libraries. Do NOT override CloudNativePG-managed parameters: archive_mode, primary_conninfo, wal_level, max_replication_slots. | `map[string]intOrString` | `{}` |


### Quorum-based synchronous replication
Expand Down
2 changes: 1 addition & 1 deletion content/en/docs/v1.4/kubernetes/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ See the reference for components utilized in this service:

## Breaking Changes

- **`ephemeralStorage` renamed to `diskSize`**: The `nodeGroups[name].ephemeralStorage` field has been renamed to `nodeGroups[name].diskSize` to better reflect its purpose (persistent disk for kubelet and containerd data). There is no backward-compatibility fallback; users MUST update their configurations to use `diskSize` instead of `ephemeralStorage`. If `ephemeralStorage` is still present in values, Helm template rendering will fail with an error directing you to use `diskSize`. When upgrading the CRD directly (bypassing Helm), the unrecognized field is silently dropped and kubelet storage reverts to the default 20Gi. Existing VMs will be automatically rolling-updated via CAPI when the new values are applied. State persists across same-VM reboots (virt-launcher restart, guest reboot, node failure); VM replacement by CAPI (e.g. nodeGroup field change, MachineHealthCheck remediation) provisions a fresh PVC.
- **`ephemeralStorage` renamed to `diskSize`** (v1.4): The `nodeGroups[name].ephemeralStorage` field has been renamed to `nodeGroups[name].diskSize` to better reflect its purpose (persistent disk for kubelet and containerd data). Existing clusters are migrated transparently by platform migration 41 during the pre-upgrade hook — no manual action is required. Newly written values should use `diskSize`. Existing VMs will be automatically rolling-updated via CAPI when the new values are applied. State persists across same-VM reboots (virt-launcher restart, guest reboot, node failure); VM replacement by CAPI (e.g. nodeGroup field change, MachineHealthCheck remediation) provisions a fresh PVC.

## Parameters

Expand Down
120 changes: 120 additions & 0 deletions content/en/docs/v1.4/operations/services/ingress.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,112 @@ source: https://github.com/cozystack/cozystack/blob/release-1.4/packages/extra/i
-->


This package deploys an [ingress-nginx](https://github.com/kubernetes/ingress-nginx)
controller that serves as the HTTP(S) entry point for applications running in a
tenant. It is one of the cluster services a tenant can run on its own or inherit
from its parent, alongside `etcd`, `monitoring`, and `seaweedfs`.

## How ingress works

### One controller per tenant

Ingress is not a single shared component. Each tenant that needs it runs its own
ingress-nginx controller, deployed into the tenant's namespace. A tenant opts in
through the `ingress: true` field on its `Tenant` resource; when it is left at the
default (`false`), the tenant has no controller of its own and inherits its
parent's instead (see [Sharing across tenants](#sharing-across-tenants)).

Every controller is fully isolated: it has its own Deployment (named
`<tenant>-ingress`, e.g. `root-ingress` for `tenant-root`), its own replica count,
and its own resource budget. Defaults are `replicas: 2` and the `t1.micro`
resource preset; both are tunable through the parameters below.

The NGINX admission webhook is enabled only for the root tenant (`tenant-root`).
Child-tenant controllers run with it disabled to avoid the per-namespace webhook
certificate bootstrap and its overhead.

### IngressClasses and routing

Each controller owns a dedicated `IngressClass` named after its namespace. The
root controller registers the `tenant-root` class; a tenant named `tenant-foo`
registers `tenant-foo`, and so on. The class points at a namespace-scoped
controller via the value `k8s.io/ingress-nginx-<namespace>`, so controllers never
fight over the same `Ingress` resources even though they all run ingress-nginx.

An application is routed by a given controller when its `Ingress` sets
`spec.ingressClassName` to that controller's namespace name. Applications do not
hardcode this — they read it from the namespace configuration (see below), so the
correct class is selected automatically whether the tenant runs its own controller
or borrows one.

### Sharing across tenants

When you create a `Tenant`, Cozystack records which ingress controller its
applications should use and exposes it two ways on the tenant namespace:

- the label `namespace.cozystack.io/ingress`, and
- the `_namespace.ingress` value injected into every application in that namespace.

If the tenant enables its own ingress, both resolve to the tenant's own namespace.
If it does not, they resolve to the parent tenant's ingress namespace. This is how
a child tenant transparently routes through its parent's controller without
deploying anything: its applications inherit the parent's `IngressClass` name.

### How applications attach

Managed applications that expose an HTTP UI (Harbor, Grafana, the Kubernetes
dashboard, SeaweedFS, and others) render their own `Ingress` and read the
namespace configuration to wire it up. A typical application `Ingress` sets:

- `spec.ingressClassName` from `_namespace.ingress`, selecting the right
controller,
- `cert-manager.io/cluster-issuer` and the ACME HTTP-01 ingress-class annotation
from the cluster certificate settings, and
- a per-application TLS secret and a host derived from the tenant's published host.

You do not configure ingress per application — enabling ingress on the tenant and
publishing it externally is enough for application URLs to start working.

### External exposure

Running a controller does not by itself make it reachable from outside the cluster.
Exactly one controller is published externally: the one whose namespace matches
`publishing.ingressName` in the platform configuration (default `tenant-root`).
Every other tenant controller is rendered as a `LoadBalancer` Service with
`externalTrafficPolicy: Local`, leaving address assignment to the cluster's load
balancer.

How the published controller's Service is shaped depends on
`publishing.exposure` (platform-level), which selects between assigning the
addresses in `publishing.externalIPs` directly to a `ClusterIP` Service and
provisioning a `LoadBalancer` backed by Cilium LB IPAM. The exact rendered shapes,
along with the upstream deprecation of `Service.spec.externalIPs`, are covered in
[Exposure mode](#exposure-mode) below.

### TLS certificates

HTTPS is handled by cert-manager. The platform ships `letsencrypt-prod`,
`letsencrypt-stage`, and a self-signed `ClusterIssuer`; applications request
certificates against `publishing.certificates.issuerName` (default
`letsencrypt-prod`) using the configured solver (`http01` by default, `dns01`
optionally). Certificates are issued into per-application TLS secrets and renewed
automatically. With the HTTP-01 solver, cert-manager validates through the same
tenant controller the application uses, so the published controller must be
reachable on the application's hostname for issuance to succeed.

### Access control

Two parameters on this package adjust how the controller treats incoming traffic:

- `whitelist` — when set, NGINX is configured with `whitelist-source-range`, so
only the listed client networks (CIDRs) reach the controller; everyone else gets
a `403`. Leave it empty to accept traffic from anywhere.
- `cloudflareProxy` — when Cloudflare proxying is in front of the cluster, enabling
this trusts Cloudflare's published IP ranges as `set_real_ip_from`, reads the
client IP from the `CF-Connecting-IP` header, and turns on forwarded headers, so
logs, metrics, and `whitelist` rules see the real visitor IP instead of
Cloudflare's edge.

## Parameters

### Common parameters
Expand All @@ -24,3 +130,17 @@ source: https://github.com/cozystack/cozystack/blob/release-1.4/packages/extra/i
| `resources.memory` | Memory (RAM) available to each replica. | `quantity` | `""` |
| `resourcesPreset` | Default sizing preset used when `resources` is omitted. | `string` | `t1.micro` |


## Exposure mode

The ingress Service type is driven by the cluster-wide `publishing.exposure` value in the platform chart, not by any key in this package. Two modes exist:

- `externalIPs` (default) has three rendered shapes:
- Release namespace matches `publishing.ingressName` AND `publishing.externalIPs` is non-empty → Service is `ClusterIP` with `Service.spec.externalIPs` set from that list and `externalTrafficPolicy: Cluster`.
- Release namespace matches `publishing.ingressName` but `publishing.externalIPs` is empty → Service falls back to `type: LoadBalancer` with `externalTrafficPolicy: Local`.
- Release namespace does not match `publishing.ingressName` (non-root tenants) → Service is `type: LoadBalancer` with `externalTrafficPolicy: Local`.
`Service.spec.externalIPs` is deprecated upstream in Kubernetes v1.36 (KEP-5707); plan migration before v1.40.
- `loadBalancer` — Service is `type: LoadBalancer` with `externalTrafficPolicy: Local`, and a `CiliumLoadBalancerIPPool` makes the addresses in `publishing.externalIPs` allocatable via Cilium LB IPAM. Requires `publishing.externalIPs` to contain at least one non-empty address (render fails otherwise) and assumes the addresses are already routed to a cluster node (floating IP / upstream router). See the inline comment on `publishing.exposure` in the platform chart for full caveats, including the note that switching the value on a running cluster causes the ingress Service to be recreated.

This setting only migrates ingress-nginx away from `Service.spec.externalIPs`. Other cozystack components that use the same deprecated field (e.g. the `vpn` app) must be migrated separately before Kubernetes v1.40 flips the `AllowServiceExternalIPs` feature gate off.