Skip to content

Commit 46f2104

Browse files
committed
cozyplane: split the API group; PVC-backed apiserver etcd
Sync of the cozyplane charts after the API-group split (lllamnyp/cozyplane, docs/api-groups.md). The tenant kinds (VPC, Port, SecurityGroup, HostFirewall, ...) are now served ONLY by the aggregated apiserver and have no CRDs at all. One group served by two mechanisms cannot work: a CRD keeps publishing its OpenAPI paths after an APIService takes the group over, the two specs collide on duplicated paths, the group's schema never serves, and `kubectl apply` of every cozyplane object fails client-side with "failed to download openapi" while core types keep working. That is why the takeover is gone rather than fixed — there is nothing to take over. The CNI package now ships only the LOCAL group's CRDs (local.sdn.cozystack.io): FabricIP, the underlay IPAM claim, whose dependency floor is the kube API and nothing else — everything above it, cert-manager and etcd and the aggregated server included, runs as default-network pods. The apiserver's etcd moves from tmpfs to a PVC on the replicated class. Its objects are cluster state: memory-backed, a full-cluster restart loses every VPC, Port and policy — and a node reboot is exactly that. This is the follow-up the values file already promised ("a persistent mode is a follow-up once storage is sequenced"), so the component now dependsOn cozystack.linstor. No cycle: linstor depends on the CNI, not on this. Also new in the charts: NetworkPolicy/HostFirewall/SecurityGroup RBAC for the agent, FabricIP RBAC for the agent and controller. Assisted-by: LLM Signed-off-by: Timofei Larkin <[email protected]>
1 parent f6ccb5b commit 46f2104

9 files changed

Lines changed: 201 additions & 1475 deletions

File tree

‎packages/core/platform/sources/cozyplane-apiserver.yaml‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,25 @@ spec:
1010
namespace: cozy-system
1111
path: /
1212
variants:
13-
# The aggregated API server for sdn.cozystack.io (VPC tenancy). Separate from
14-
# the cozyplane CNI component on purpose: the CNI installs before everything
15-
# (including cert-manager), while this needs cert-manager Certificates and the
16-
# etcd-operator. Until it installs, the group is served by the CNI package's
17-
# bootstrap CRDs; its APIService takes the group over atomically when it lands.
13+
# The aggregated API server for sdn.cozystack.io (VPC tenancy, policy). It is a
14+
# separate component on purpose: the CNI installs before everything (including
15+
# cert-manager), while this needs cert-manager Certificates, the etcd-operator
16+
# and — since its etcd is PVC-backed — storage.
17+
#
18+
# It is the ONLY server of the group: the tenant kinds have no CRDs. Until this
19+
# component lands, sdn.cozystack.io simply is not served, and the default
20+
# network keeps working regardless (the CNI's own CRDs are the separate LOCAL
21+
# group, local.sdn.cozystack.io — underlay IPAM, which must come up beneath
22+
# cert-manager, etcd and this server, all of which run as pods).
1823
- name: default
1924
dependsOn:
2025
- cozystack.cert-manager
2126
- cozystack.etcd-operator
27+
# Its etcd is PVC-backed now (the aggregated API's objects are cluster
28+
# state; a memory-backed etcd loses every VPC, Port and policy on a
29+
# full-cluster restart — which is what a node reboot is), so the storage
30+
# layer must exist first. No cycle: linstor depends on the CNI, not on this.
31+
- cozystack.linstor
2232
components:
2333
- name: cozyplane-apiserver
2434
path: system/cozyplane-apiserver

‎packages/core/platform/templates/bundles/system.yaml‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,12 @@
175175
{{- $controllerValues := dict "cozystackController" (dict "disableTelemetry" true) -}}
176176
{{- $_ := set $cozystackEngineComponents "cozystack-controller" (dict "values" $controllerValues) -}}
177177
{{- end -}}
178-
{{- /* cozyplane owns the sdn.cozystack.io group (bootstrap CRDs, then the
179-
cozyplane-apiserver aggregated server takes it over), so cozystack-api must
180-
not aggregate it — a second APIService for the group would hijack it and
181-
every non-SecurityGroup kind (VPC, Port, …) 404s. */ -}}
178+
{{- /* cozyplane owns the sdn.cozystack.io group — served ONLY by the
179+
cozyplane-apiserver aggregated server; it has no CRDs (a group served by
180+
both a CRD and an APIService makes the kube-apiserver's OpenAPI merge
181+
collide on duplicated paths, and the group's schema then never serves).
182+
So cozystack-api must not aggregate it: a second APIService for the group
183+
would hijack it and every non-SecurityGroup kind (VPC, Port, …) 404s. */ -}}
182184
{{- if eq (.Values.bundles.system.networkingVariant | default "kubeovn-cilium") "cozyplane" -}}
183185
{{- $apiValues := index $cozystackEngineComponents "cozystack-api" | default (dict "values" (dict)) -}}
184186
{{- $apiInner := index $apiValues "values" | default (dict) -}}
@@ -204,10 +206,12 @@
204206
Cilium variants the cilium chart provides the CRDs, so skip it there. */ -}}
205207
{{- if eq (.Values.bundles.system.networkingVariant | default "kubeovn-cilium") "cozyplane" }}
206208
{{include "cozystack.platform.package.default" (list "cozystack.cozyplane-cilium-crds" $) }}
207-
{{- /* The aggregated sdn.cozystack.io apiserver: a separate component precisely so
208-
the CNI slot stays cert-manager-free — this one dependsOn cert-manager +
209-
etcd-operator (see sources/cozyplane-apiserver.yaml). Its APIService takes
210-
the group over from the CNI package's bootstrap CRDs when it lands. */ -}}
209+
{{- /* The aggregated sdn.cozystack.io apiserver: a separate component precisely
210+
so the CNI slot stays cert-manager-free — this one dependsOn cert-manager,
211+
etcd-operator and (for its PVC-backed etcd) storage. There is no takeover:
212+
the tenant kinds have no CRDs at all. The CNI package ships only the LOCAL
213+
group's CRDs (local.sdn.cozystack.io — underlay IPAM, which must work
214+
before cert-manager, etcd and this server, all of which are pods). */ -}}
211215
{{include "cozystack.platform.package.default" (list "cozystack.cozyplane-apiserver" $) }}
212216
{{- end }}
213217
{{include "cozystack.platform.package.default" (list "cozystack.cert-manager" $) }}

‎packages/system/cozyplane-apiserver/templates/apiserver.yaml‎

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ rules:
7474
- apiGroups: ["apiregistration.k8s.io"]
7575
resources: ["apiservices"]
7676
verbs: ["get", "create", "patch"]
77+
# ...and then removes the bootstrap CRDs it superseded. They are shadowed for
78+
# routing but NOT for OpenAPI: while they exist, the group's spec fails to
79+
# merge (duplicated paths) and `kubectl apply` of any object in it dies with
80+
# "failed to download openapi".
81+
- apiGroups: ["apiextensions.k8s.io"]
82+
resources: ["customresourcedefinitions"]
83+
verbs: ["get", "list", "delete"]
7784
---
7885
apiVersion: rbac.authorization.k8s.io/v1
7986
kind: ClusterRoleBinding
@@ -91,8 +98,28 @@ subjects:
9198
namespace: {{ $ns }}
9299
---
93100
{{- if not .Values.etcd.operator.enabled }}
94-
# Built-in single-pod etcd (dev/evaluation; ephemeral by default). Production
95-
# uses the operator-managed EtcdCluster instead — see templates/etcd-cluster.yaml.
101+
{{- if .Values.etcd.persistence.enabled }}
102+
# The single-pod etcd's storage. The aggregated API's objects are cluster state:
103+
# an emptyDir would lose every VPC, Port and policy the moment the pod moves.
104+
apiVersion: v1
105+
kind: PersistentVolumeClaim
106+
metadata:
107+
name: cozyplane-etcd-data
108+
namespace: {{ $ns }}
109+
labels:
110+
{{- include "cozyplane-apiserver.labels" . | nindent 4 }}
111+
spec:
112+
accessModes: [ReadWriteOnce]
113+
{{- with .Values.etcd.persistence.storageClassName }}
114+
storageClassName: {{ . | quote }}
115+
{{- end }}
116+
resources:
117+
requests:
118+
storage: {{ .Values.etcd.persistence.size }}
119+
---
120+
{{- end }}
121+
# Built-in single-pod etcd. Production uses the operator-managed EtcdCluster
122+
# instead — see templates/etcd-cluster.yaml.
96123
apiVersion: apps/v1
97124
kind: Deployment
98125
metadata:
@@ -103,6 +130,10 @@ metadata:
103130
{{- include "cozyplane-apiserver.labels" . | nindent 4 }}
104131
spec:
105132
replicas: 1
133+
# Recreate, not RollingUpdate: two etcd pods must never hold the same RWO
134+
# volume, and a rolling update would try.
135+
strategy:
136+
type: Recreate
106137
selector:
107138
matchLabels:
108139
app: cozyplane-etcd
@@ -244,9 +275,11 @@ spec:
244275
- --ensure-apiservice-service={{ $ns }}/cozyplane-apiserver
245276
- --ensure-apiservice-ca-injection={{ $ns }}/cozyplane-apiserver-serving
246277
{{- if .Values.etcd.operator.enabled }}
247-
# The v1alpha2 operator's client Service (members carry
248-
# operator-assigned names, so dial the Service, not per-member DNS).
249-
- --etcd-servers=https://cozyplane-etcd.{{ $ns }}.svc:2379
278+
{{- $eps := list }}
279+
{{- range $i := until (int .Values.etcd.operator.replicas) }}
280+
{{- $eps = append $eps (printf "https://cozyplane-etcd-%d.cozyplane-etcd-headless.%s.svc:2379" $i $ns) }}
281+
{{- end }}
282+
- --etcd-servers={{ join "," $eps }}
250283
- --etcd-cafile=/etc/etcd-certs/ca.crt
251284
- --etcd-certfile=/etc/etcd-certs/tls.crt
252285
- --etcd-keyfile=/etc/etcd-certs/tls.key

‎packages/system/cozyplane-apiserver/templates/etcd-cluster.yaml‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,19 @@ spec:
113113
version: {{ .Values.etcd.operator.version | quote }}
114114
replicas: {{ $replicas }}
115115
storage:
116-
# Memory-backed (see the file header). `medium` is immutable post-create;
117-
# size is the tmpfs cap, not a reservation. Non-zero is required for Memory.
116+
{{- if .Values.etcd.operator.persistent }}
117+
# PVC-backed: the aggregated API's objects (VPCs, Ports, policies) are
118+
# cluster state. A memory-backed etcd loses every one of them on a
119+
# full-cluster restart — which is exactly what a node reboot is.
120+
medium: ""
121+
{{- with .Values.etcd.operator.storageClass }}
122+
storageClassName: {{ . | quote }}
123+
{{- end }}
124+
{{- else }}
125+
# tmpfs: no storage layer needed, but NOT durable across a full-cluster
126+
# restart. `medium` is immutable post-create; size is the tmpfs cap.
118127
medium: Memory
128+
{{- end }}
119129
size: {{ .Values.etcd.operator.size }}
120130
options:
121131
autoCompactionMode: periodic

‎packages/system/cozyplane-apiserver/values.yaml‎

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# Container image (shared with the cozyplane package — the apiserver binary
2-
# lives in the same image). Digest-pinned; update on each published build.
3-
image: ghcr.io/lllamnyp/cozyplane@sha256:3d6fece3355f4f0accf47c4ab02d9b75919f002db4dee444a08f157d888c2a7b
1+
# Container image (shared with the cozyplane chart — the apiserver binary lives
2+
# in the same image). Digest-pinned (no floating tag); update on each published
3+
# build.
4+
image: ghcr.io/lllamnyp/cozyplane@sha256:c3858ecc3d590b05de81599f65bf2ed1ecd4708f41f6be80244ddd566b303101
45
imagePullPolicy: IfNotPresent
56
imagePullSecrets: []
67

@@ -13,28 +14,39 @@ resources:
1314
memory: 256Mi
1415

1516
etcd:
16-
# Operator-managed etcd (etcd-operator.cozystack.io/v1alpha2; Cozystack ships
17-
# it): replicated, TLS end to end via the operator's cert-manager
18-
# integration. MEMORY-BACKED (no PVC/storageClass) so the API's etcd needs no
19-
# storage layer — this component installs after cert-manager but before (or
20-
# independent of) Linstor. Trade-off: not durable across a full-cluster
21-
# restart; a persistent (storageClassName) mode is a follow-up once storage
22-
# is sequenced after the CNI.
17+
# Operator-managed etcd (the aenix-io etcd-operator; Cozystack ships it):
18+
# replicated, PVC-backed, TLS end to end. The production path — without it
19+
# the built-in single-pod etcd below is used, and with its default
20+
# emptyDir storage a single pod reschedule erases every sdn.cozystack.io
21+
# object in the cluster.
2322
operator:
2423
enabled: true
25-
version: "3.6.11"
2624
replicas: 3
27-
# Memory-backed: `size` is the tmpfs cap (not a reservation), so keep it
28-
# modest — the sdn.cozystack.io object set is small.
29-
size: 1Gi
30-
# StorageClass for a future PVC-backed mode ("" = cluster default);
31-
# ignored while storage.medium=Memory.
32-
storageClass: ""
33-
# Built-in single-pod etcd (dev/evaluation only; unused while the operator
34-
# mode above is enabled).
25+
size: 4Gi
26+
# persistent: PVC-backed (durable) vs tmpfs. The aggregated API's objects
27+
# are cluster state; a memory-backed etcd loses all of them on a
28+
# full-cluster restart, which is what a node reboot is. Requires a storage
29+
# layer, so the component must be sequenced after it.
30+
persistent: true
31+
# StorageClass for the members' PVCs ("" = cluster default). Replicated, so
32+
# a member's data survives losing the node it was scheduled on.
33+
storageClass: "replicated"
34+
# Built-in single-pod etcd (dev/evaluation only).
3535
image: registry.k8s.io/etcd:3.5.16-0
36+
# Storage for the built-in etcd. The default is ephemeral (emptyDir); use a
37+
# persistentVolumeClaim volume source to survive a pod restart.
38+
# Storage for the built-in single-pod etcd. Default is a PVC — the aggregated
39+
# API's objects (VPCs, Ports, policies) are cluster state, and an emptyDir
40+
# loses them on a pod move. Set `emptyDir: {}` explicitly for a throwaway
41+
# cluster with no storage class.
3642
storage:
37-
emptyDir: {}
43+
persistentVolumeClaim:
44+
claimName: cozyplane-etcd-data
45+
# The PVC the chart creates when storage above references it.
46+
persistence:
47+
enabled: true
48+
storageClassName: "" # "" = the cluster default
49+
size: 2Gi
3850
resources:
3951
requests:
4052
cpu: 50m

‎packages/system/cozyplane/templates/agent.yaml‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,20 @@ rules:
2020
resources: ["nodes"]
2121
verbs: ["get", "list", "watch", "patch"]
2222
# The plugin (using this SA) reads pod annotations to resolve VPC membership,
23-
# and the agent reads pods to distinguish revocation from ordinary teardown.
23+
# the agent reads pods to distinguish revocation from ordinary teardown, and
24+
# the NetworkPolicy compiler watches them for identities/membership
25+
# (docs/network-policy.md).
2426
- apiGroups: [""]
2527
resources: ["pods"]
26-
verbs: ["get"]
28+
verbs: ["get", "list", "watch"]
29+
# Namespace labels resolve namespaceSelector peers; NetworkPolicies are the
30+
# net-0 policy source (docs/network-policy.md).
31+
- apiGroups: [""]
32+
resources: ["namespaces"]
33+
verbs: ["get", "list", "watch"]
34+
- apiGroups: ["networking.k8s.io"]
35+
resources: ["networkpolicies"]
36+
verbs: ["get", "list", "watch"]
2737
# Tenant network ids from VPC objects (namespaced; read across all namespaces).
2838
- apiGroups: ["sdn.cozystack.io"]
2939
resources: ["vpcs"]
@@ -48,11 +58,27 @@ rules:
4858
- apiGroups: ["sdn.cozystack.io"]
4959
resources: ["securitygroups"]
5060
verbs: ["get", "list", "watch"]
61+
# FabricIPs: the CNI plugin claims and releases underlay addresses through
62+
# this SA (the agent writes its kubeconfig), and the agent watches them.
63+
- apiGroups: ["local.sdn.cozystack.io"]
64+
resources: ["fabricips"]
65+
verbs: ["get", "list", "watch", "create", "delete"]
66+
# HostFirewalls: the agent compiles the objects selecting its node into the
67+
# hf_allow map and arms enforcement (docs/host-firewall.md).
68+
- apiGroups: ["sdn.cozystack.io"]
69+
resources: ["hostfirewalls"]
70+
verbs: ["get", "list", "watch"]
5171
# FloatingIPs: the agent programs the floating map and advertises the address
5272
# for those whose target Port is on this node.
5373
- apiGroups: ["sdn.cozystack.io"]
5474
resources: ["floatingips"]
5575
verbs: ["get", "list", "watch"]
76+
# ExternalPools: the agent ensures the datapath serves every pool's link on
77+
# every node — LB/NodePort frontends arrive wherever the provider attracts
78+
# them, and a DSR reply must leave by that link (docs/lb-ingress.md).
79+
- apiGroups: ["sdn.cozystack.io"]
80+
resources: ["externalpools"]
81+
verbs: ["get", "list", "watch"]
5682
# ServiceVIPs: the agent programs the svc_vips datapath map from them, the
5783
# resolver answers attached ClusterIP services with the VIP, and the CNI
5884
# plugin counts them as used addresses when claiming a Port IP.

‎packages/system/cozyplane/templates/controller.yaml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ rules:
9393
- apiGroups: [""]
9494
resources: ["pods"]
9595
verbs: ["get", "list", "watch", "delete"]
96+
# FabricIPs: the controller reclaims an underlay address whose pod is gone —
97+
# the GC the host-local file store never had.
98+
- apiGroups: ["local.sdn.cozystack.io"]
99+
resources: ["fabricips"]
100+
verbs: ["get", "list", "watch", "delete"]
96101
---
97102
apiVersion: rbac.authorization.k8s.io/v1
98103
kind: ClusterRoleBinding

0 commit comments

Comments
 (0)