|
1 | | -# sinker |
| 1 | +# Sinker |
2 | 2 |
|
3 | | -Copy k8s resources (or parts thereof) across clusters. |
| 3 | +Sinker is a Kubernetes controller that keeps resources in sync across clusters. It watches `ResourceSync` custom |
| 4 | +resources (CRs), reads a source object, then projects it onto a target object—optionally in a different cluster—while |
| 5 | +preserving the fields you care about. Sinker keeps watching both ends so that drifts introduced by other actors are |
| 6 | +reconciled automatically. |
| 7 | + |
| 8 | +## Features |
| 9 | + |
| 10 | +- Cross-cluster or in-cluster synchronization for any Kubernetes API resource using a single declarative CR. |
| 11 | +- JSONPath-based field mappings that let you clone entire objects or copy specific subtrees into the target. |
| 12 | +- Safe lifecycle management through finalizers plus explicit per-resource annotations to control deletion behavior |
| 13 | + during outages. |
| 14 | +- Remote watchers that observe both source and target objects and trigger reconciliations whenever something changes. |
| 15 | +- Admin HTTP server exposing readiness and liveness probes (and, via the `kubert` runtime, metrics) so the controller |
| 16 | + integrates cleanly with cluster operations tooling. |
| 17 | + |
| 18 | +## Getting Started |
| 19 | + |
| 20 | +### Prerequisites |
| 21 | + |
| 22 | +- Rust toolchain 1.85 or later (see `rust-toolchain.toml`) if you plan to build from source. |
| 23 | +- Docker or another OCI-compatible builder to produce a controller image. |
| 24 | +- Access to at least one Kubernetes cluster (1.33+) with `kubectl` and the ability to create CRDs, roles, and service |
| 25 | + accounts. |
| 26 | +- Optional: access credentials for any remote clusters you want Sinker to read from or write to. These must be stored as |
| 27 | + Kubernetes secrets containing kubeconfigs such as those created by [CAPI](https://cluster-api.sigs.k8s.io/). |
| 28 | + |
| 29 | +### Build the controller |
| 30 | + |
| 31 | +```bash |
| 32 | +git clone https://github.com/influxdata/sinker.git |
| 33 | +cd sinker |
| 34 | +cargo build --release |
| 35 | +``` |
| 36 | + |
| 37 | +The compiled binary lives at `target/release/sinker`. To build a container image: |
| 38 | + |
| 39 | +```bash |
| 40 | +docker build -t <registry>/<repo>/sinker:<tag> . |
| 41 | +docker push <registry>/<repo>/sinker:<tag> |
| 42 | +``` |
| 43 | + |
| 44 | +### Deploy to Kubernetes |
| 45 | + |
| 46 | +1. **Create an image pull secret** (if needed) in the namespace where Sinker will run, for example: |
| 47 | + ```bash |
| 48 | + kubectl create secret docker-registry gar-auth-sinker \ |
| 49 | + --docker-server=<registry> \ |
| 50 | + --docker-username=<username> \ |
| 51 | + --docker-password=<password> \ |
| 52 | + --namespace sinker |
| 53 | + ``` |
| 54 | +2. **Update the controller image** in `manifests/deployment.yml` (or overlay with Kustomize) to point at the image you |
| 55 | + pushed. |
| 56 | +3. **Apply the bundled manifests** (CRDs, RBAC, Deployment, ServiceAccount, etc.): |
| 57 | + ```bash |
| 58 | + kubectl apply -k manifests |
| 59 | + ``` |
| 60 | +4. **Verify deployment**: |
| 61 | + ```bash |
| 62 | + kubectl -n sinker get pods |
| 63 | + kubectl -n sinker logs deploy/sinker |
| 64 | + ``` |
| 65 | +5. When the pod is ready, the controller listens on port `8080` for `/live`, `/ready`, and `/metrics` (Prometheus |
| 66 | + format) endpoints exposed by the admin server. |
| 67 | + |
| 68 | +### Running locally against a cluster |
| 69 | + |
| 70 | +You can also run the controller directly from your workstation: |
| 71 | + |
| 72 | +```bash |
| 73 | +cargo run -- --kubeconfig /path/to/kubeconfig --context my-context |
| 74 | +``` |
| 75 | + |
| 76 | +Key CLI flags: |
| 77 | + |
| 78 | +- `--log-level` (or `SINKER_LOG`) controls tracing filters (default `sinker=info,warn`). |
| 79 | +- `--log-format` selects `plain` or `json`. |
| 80 | +- `--kubeconfig`, `--context`, `--cluster`, and `--user` mirror the standard `kubectl` flags for choosing credentials. |
| 81 | +- `--as` and `--as-group` let you impersonate another Kubernetes user or group. |
| 82 | +- `--kube-api-response-headers-timeout` configures the Kubernetes client timeout (default `9s`). |
| 83 | +- `--admin-addr` sets the admin HTTP server bind address (default `0.0.0.0:8080`). |
| 84 | + |
| 85 | +## Defining resource syncs |
| 86 | + |
| 87 | +Sinker ships two custom resources: |
| 88 | + |
| 89 | +### ResourceSync |
| 90 | + |
| 91 | +`ResourceSync` objects describe how to mirror a Kubernetes resource. |
| 92 | + |
| 93 | +| Field | Required | Description | |
| 94 | +|---------------------------|----------|-------------------------------------------------------------------------------------------------------------------------------------| |
| 95 | +| `spec.source.resourceRef` | ✓ | API reference (`apiVersion`, `kind`, `name`) pointing to the object you want to copy. | |
| 96 | +| `spec.source.cluster` | | Optional remote cluster reference. When omitted, Sinker reads the source from the same cluster and namespace as the `ResourceSync`. | |
| 97 | +| `spec.target.resourceRef` | ✓ | API reference for the target object. | |
| 98 | +| `spec.target.cluster` | | Optional remote cluster reference. When omitted, the target lives in the same cluster as the `ResourceSync`. | |
| 99 | +| `spec.mappings[]` | | Optional list of field mapping rules. See below. | |
| 100 | + |
| 101 | +#### Cluster references |
| 102 | + |
| 103 | +When you provide `spec.{source|target}.cluster`, Sinker reads a kubeconfig from a secret to talk to the remote cluster: |
| 104 | + |
| 105 | +```yaml |
| 106 | +cluster: |
| 107 | + namespace: other-namespace # optional override; defaults to the Remote kubeconfig's namespace |
| 108 | + kubeConfig: |
| 109 | + secretRef: |
| 110 | + name: remote-kubeconfig |
| 111 | + key: value |
| 112 | +``` |
| 113 | +
|
| 114 | +Create the secret by embedding a standard kubeconfig: |
| 115 | +
|
| 116 | +```bash |
| 117 | +kubectl -n <ns> create secret generic remote-kubeconfig \ |
| 118 | + --from-file=value=/path/to/kubeconfig |
| 119 | +``` |
| 120 | + |
| 121 | +Within the remote cluster, RBAC for the kubeconfig user limits what Sinker can access. For local clusters, Sinker uses |
| 122 | +its in-cluster credentials. |
| 123 | + |
| 124 | +#### Mappings |
| 125 | + |
| 126 | +- When `spec.mappings` is empty, Sinker clones the entire source object, copying annotations and labels while cleaning |
| 127 | + the `kubectl.kubernetes.io/last-applied-configuration` annotation. |
| 128 | +- When mappings are supplied, each mapping entry has `fromFieldPath` and/or `toFieldPath`: |
| 129 | + - `fromFieldPath` is a JSONPath evaluated against the source. Use `spec.data.someField` to copy a nested field, or |
| 130 | + leave it blank/omit it to select the entire object. |
| 131 | + - `toFieldPath` is a JSONPath-like dotted path inside the target (`metadata.*` and `spec|status|data` are |
| 132 | + supported). Omit `toFieldPath` to replace the entire target with the selected subtree. |
| 133 | +- Sinker enforces that at least one of the fields is present per mapping. If `toFieldPath` targets `metadata`, the |
| 134 | + controller keeps metadata in sync using server-side apply. |
| 135 | + |
| 136 | +Example `ResourceSync`: |
| 137 | + |
| 138 | +```yaml |
| 139 | +apiVersion: sinker.influxdata.io/v1alpha1 |
| 140 | +kind: ResourceSync |
| 141 | +metadata: |
| 142 | + name: demo |
| 143 | + namespace: default |
| 144 | +spec: |
| 145 | + source: |
| 146 | + resourceRef: |
| 147 | + apiVersion: v1 |
| 148 | + kind: ConfigMap |
| 149 | + name: remote-demo |
| 150 | + cluster: |
| 151 | + namespace: default |
| 152 | + kubeConfig: |
| 153 | + secretRef: |
| 154 | + name: k3-test-27-kubeconfig |
| 155 | + key: value |
| 156 | + target: |
| 157 | + resourceRef: |
| 158 | + apiVersion: v1 |
| 159 | + kind: ConfigMap |
| 160 | + name: demo |
| 161 | + mappings: |
| 162 | + - fromFieldPath: data.remote |
| 163 | + toFieldPath: data.remote |
| 164 | + - fromFieldPath: data.foo |
| 165 | + toFieldPath: data.bar |
| 166 | +``` |
| 167 | +
|
| 168 | +### SinkerContainer |
| 169 | +
|
| 170 | +`SinkerContainer` is a lightweight CRD that stores arbitrary structured data under `.spec`. Use it as either a source or |
| 171 | +target when you want Sinker to materialize generated configuration into a typed CR (for example, to copy an inner spec |
| 172 | +from one object into another). |
| 173 | + |
| 174 | +## Annotations and finalizers |
| 175 | + |
| 176 | +Sinker adds a finalizer (`sinker.influxdata.io/target`) to each `ResourceSync` so it can clean up target objects when |
| 177 | +the CR is deleted. Two optional annotations modify that behavior: |
| 178 | + |
| 179 | +- `sinker.influxdata.io/force-delete: "true"` – if set and the controller cannot contact a remote cluster while |
| 180 | + deleting, Sinker removes its finalizer so the `ResourceSync` can be garbage-collected. |
| 181 | +- `sinker.influxdata.io/disable-target-deletion: "true"` – skip deleting the target object when the `ResourceSync` is |
| 182 | + removed. Useful when you want to manage the target lifecycle manually. |
| 183 | + |
| 184 | +## Status and observability |
| 185 | + |
| 186 | +- Sinker publishes a `ResourceSyncFailing` condition in `.status.conditions[]` with timestamps, reasons, and messages if |
| 187 | + reconciliation fails. |
| 188 | +- Logs use the standard `tracing` crate; adjust verbosity with `SINKER_LOG`. |
| 189 | +- The admin server exposes `/live`, `/ready`, and `/metrics` on port 8080. Wire these into your cluster’s probes and |
| 190 | + monitoring. |
| 191 | +- Remote watchers reconcile whenever the source or target resource changes, even on remote clusters, which keeps |
| 192 | + synchronized objects fresh with minimal polling. |
| 193 | + |
| 194 | +## Generating CRDs programmatically |
| 195 | + |
| 196 | +To print the latest CRDs (e.g., for Helm packaging), use the built-in command: |
| 197 | + |
| 198 | +```bash |
| 199 | +cargo run -- manifests > out.yml |
| 200 | +``` |
| 201 | + |
| 202 | +The output includes both `ResourceSync` and `SinkerContainer` definitions. |
| 203 | + |
| 204 | +## Development notes |
| 205 | + |
| 206 | +- Format and lint with `cargo fmt` and `cargo clippy --all-targets --all-features`. |
| 207 | +- Run tests with `cargo test`. |
| 208 | +- When developing new features, update `manifests` or regenerate CRDs via `sinker manifests` before deploying. |
| 209 | +- The project uses MIT licensing; see `LICENSE` for details. |
0 commit comments