Skip to content

Commit d36fd0d

Browse files
Make v1 CRD and conversion webhook optional
1 parent 24cfada commit d36fd0d

9 files changed

Lines changed: 239 additions & 57 deletions

File tree

doc/user/content/self-managed-deployments/installation/install-on-local-kind.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,13 @@ Starting in v26.0, Self-Managed Materialize requires a license key.
107107
kubectl get nodes --show-labels
108108
```
109109

110-
1. Install cert-manager
110+
1. Recommended: Install cert-manager
111111

112112
Cert-manager is used for generating TLS certificates needed by the materialize operator
113-
for CRD conversion webhooks.
113+
for CRD conversion webhooks. It is currently only required if you enable the v1
114+
version of the Materialize CRD by setting `operator.args.installV1CRD=true`
115+
when installing the operator, but certificates will become required in a
116+
future version of Materialize.
114117

115118
```shell
116119
helm install cert-manager oci://quay.io/jetstack/charts/cert-manager \

misc/helm-charts/operator/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,12 +143,13 @@ The following table lists the configurable parameters of the Materialize operato
143143
| `operator.affinity` | Affinity to use for the operator pod | ``{}`` |
144144
| `operator.args.enableInternalStatementLogging` | | ``true`` |
145145
| `operator.args.enableLicenseKeyChecks` | | ``false`` |
146+
| `operator.args.installV1CRD` | Whether to install the v1 version of the Materialize CRD and the conversion webhook that converts between v1 and v1alpha1. When false, only the v1alpha1 CRD version is installed and no webhook serving certificate or service is created. | ``false`` |
146147
| `operator.args.startupLogFilter` | Log filtering settings for startup logs | ``"INFO,mz_orchestratord=TRACE"`` |
147-
| `operator.args.webhookCertReloadInterval` | How often orchestratord reloads its webhook TLS certificate from disk and, when the CA changes, refreshes the conversion webhook's CA bundle. Must be shorter than the certificate's lifetime. Accepts a humantime duration (e.g. "1h", "30m"). Leave null to use the binary default. | ``nil`` |
148+
| `operator.args.webhookCertReloadInterval` | How often orchestratord reloads its webhook TLS certificate from disk and, when the CA changes, refreshes the conversion webhook's CA bundle. Must be shorter than the certificate's lifetime. Accepts a humantime duration (e.g. "1h", "30m"). Leave null to use the binary default. Only used if `installV1CRD` is true. | ``nil`` |
148149
| `operator.certificate.caDuration` | Lifetime of the root CA that signs the webhook serving certificate, when `source` is "cert-manager". The serving certificate is signed by this CA, so the CA outlives individual serving-certificate rotations. | ``"87600h"`` |
149150
| `operator.certificate.caRenewBefore` | How long before the root CA expires to renew it. Must be less than `caDuration`. | ``"8760h"`` |
150151
| `operator.certificate.secretName` | Name of a secret in the operator's namespace containing ca.crt, tls.crt, and tls.key entries. Only used if `source` is "secret". | ``nil`` |
151-
| `operator.certificate.source` | Where to obtain the certificate for orchestratord. Valid values are 'cert-manager' and 'secret'. | ``"cert-manager"`` |
152+
| `operator.certificate.source` | Where to obtain the certificate for orchestratord. Valid values are 'cert-manager' and 'secret'. Only used if `operator.args.installV1CRD` is true. | ``"cert-manager"`` |
152153
| `operator.cloudProvider.providers.aws.accountID` | When using AWS, accountID is required | ``""`` |
153154
| `operator.cloudProvider.providers.aws.enabled` | | ``false`` |
154155
| `operator.cloudProvider.providers.aws.iam.roles.connection` | ARN for CREATE CONNECTION feature | ``""`` |

misc/helm-charts/operator/templates/certificate.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# the Business Source License, use of this software will be governed
88
# by the Apache License, Version 2.0.
99

10-
{{- if eq .Values.operator.certificate.source "cert-manager" -}}
10+
{{- if and .Values.operator.args.installV1CRD (eq .Values.operator.certificate.source "cert-manager") -}}
1111
# We provision the webhook serving certificate from a stable root CA rather
1212
# than as a bare self-signed certificate. The serving certificate rotates
1313
# frequently, but it is always signed by the same long-lived CA, so the

misc/helm-charts/operator/templates/deployment.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ spec:
6666
{{- if not .Values.operator.args.enableLicenseKeyChecks }}
6767
- "--disable-license-key-checks"
6868
{{- end }}
69+
{{- if .Values.operator.args.installV1CRD }}
70+
- "--install-v1-crd"
6971
{{- if .Values.operator.args.webhookCertReloadInterval }}
7072
- "--webhook-cert-reload-interval={{ .Values.operator.args.webhookCertReloadInterval }}"
7173
{{- end }}
74+
{{- end }}
7275

7376
{{/* AWS Configuration */}}
7477
{{- if eq .Values.operator.cloudProvider.type "aws" }}
@@ -245,15 +248,19 @@ spec:
245248
- >
246249
--additional-crd-columns={{ toJson .Values.operator.additionalMaterializeCRDColumns }}
247250
{{- end }}
251+
{{- if .Values.operator.args.installV1CRD }}
248252
- "--webhook-service-name"
249253
- {{ include "materialize-operator.fullname" . }}
250254
- "--webhook-service-namespace"
251255
- {{ .Release.Namespace }}
256+
{{- end }}
252257
ports:
253258
- containerPort: 3100
254259
name: metrics
260+
{{- if .Values.operator.args.installV1CRD }}
255261
- containerPort: 8001
256262
name: webhook
263+
{{- end }}
257264
resources:
258265
{{- toYaml .Values.operator.resources | nindent 10 }}
259266
securityContext:
@@ -265,6 +272,7 @@ spec:
265272
runAsNonRoot: true
266273
seccompProfile:
267274
type: RuntimeDefault
275+
{{- if .Values.operator.args.installV1CRD }}
268276
livenessProbe:
269277
httpGet:
270278
path: /healthz
@@ -289,3 +297,4 @@ spec:
289297
defaultMode: 256
290298
optional: false
291299
secretName: {{ if eq .Values.operator.certificate.source "cert-manager" }}{{ include "materialize-operator.fullname" . }}-cert{{ else }}{{ .Values.operator.certificate.secretName }}{{ end }}
300+
{{- end }}

misc/helm-charts/operator/templates/service.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# the Business Source License, use of this software will be governed
88
# by the Apache License, Version 2.0.
99

10+
{{- if .Values.operator.args.installV1CRD -}}
1011
---
1112
apiVersion: v1
1213
kind: Service
@@ -23,3 +24,4 @@ spec:
2324
protocol: TCP
2425
port: 8001
2526
targetPort: 8001
27+
{{- end -}}

misc/helm-charts/operator/values.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ operator:
2424
enableInternalStatementLogging: true
2525
# Newer versions ignore this setting and always enforce license key checks.
2626
enableLicenseKeyChecks: false
27+
# -- Whether to install the v1 version of the Materialize CRD and the
28+
# conversion webhook that converts between v1 and v1alpha1. When false,
29+
# only the v1alpha1 CRD version is installed and no webhook serving
30+
# certificate or service is created.
31+
installV1CRD: false
2732
# -- (string) How often orchestratord reloads its webhook TLS certificate
2833
# from disk and, when the CA changes, refreshes the conversion webhook's CA
2934
# bundle. Must be shorter than the certificate's lifetime. Accepts a
3035
# humantime duration (e.g. "1h", "30m"). Leave null to use the binary
31-
# default.
36+
# default. Only used if `installV1CRD` is true.
3237
webhookCertReloadInterval: null
3338

3439
# -- Additional columns to display when printing the Materialize CRD in table format.
@@ -40,8 +45,10 @@ operator:
4045
# priority: 2
4146
# type: "string"
4247

48+
# Webhook serving certificate configuration. Only used if
49+
# `operator.args.installV1CRD` is true.
4350
certificate:
44-
# -- (string) Where to obtain the certificate for orchestratord. Valid values are 'cert-manager' and 'secret'.
51+
# -- (string) Where to obtain the certificate for orchestratord. Valid values are 'cert-manager' and 'secret'. Only used if `operator.args.installV1CRD` is true.
4552
source: cert-manager
4653
# -- (string) Name of a secret in the operator's namespace containing ca.crt, tls.crt, and tls.key entries. Only used if `source` is "secret".
4754
secretName: null

src/orchestratord/src/bin/orchestratord.rs

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use mz_orchestrator_kubernetes::{KubernetesImagePullPolicy, util::create_client}
3939
use mz_orchestrator_tracing::{StaticTracingConfig, TracingCliArgs};
4040
use mz_orchestratord::{
4141
controller,
42-
k8s::register_crds,
42+
k8s::{ConversionWebhookConfig, register_crds},
4343
metrics::{self, Metrics},
4444
tls::DefaultCertificateSpecs,
4545
webhook,
@@ -66,10 +66,17 @@ pub struct Args {
6666
#[clap(long, default_value = "[::]:8001")]
6767
webhook_listen_address: SocketAddr,
6868

69-
#[clap(long)]
70-
webhook_service_name: String,
71-
#[clap(long)]
72-
webhook_service_namespace: String,
69+
/// Whether to install the v1 version of the Materialize CRD and the
70+
/// conversion webhook between v1 and v1alpha1. When false, only the
71+
/// v1alpha1 version is installed and the webhook server is not started.
72+
#[clap(long)]
73+
install_v1_crd: bool,
74+
/// Required when --install-v1-crd is set.
75+
#[clap(long, required_if_eq("install_v1_crd", "true"))]
76+
webhook_service_name: Option<String>,
77+
/// Required when --install-v1-crd is set.
78+
#[clap(long, required_if_eq("install_v1_crd", "true"))]
79+
webhook_service_namespace: Option<String>,
7380
#[clap(long, default_value = "8001")]
7481
webhook_service_port: u16,
7582
#[clap(long, default_value = "/etc/tls/ca.crt")]
@@ -293,7 +300,7 @@ async fn run(args: Args) -> Result<(), anyhow::Error> {
293300
let tls_cert = args.tls_cert;
294301
let tls_key = args.tls_key;
295302
let tls_ca = args.tls_ca;
296-
let reload_config = {
303+
let reload_config = if args.install_v1_crd {
297304
let config = OpenSSLConfig::from_pem_file(&tls_cert, &tls_key).unwrap();
298305
let reload_config = config.clone();
299306
let webhook_listen_address = args.webhook_listen_address;
@@ -307,21 +314,27 @@ async fn run(args: Args) -> Result<(), anyhow::Error> {
307314
}
308315
});
309316

310-
reload_config
317+
Some(reload_config)
318+
} else {
319+
None
311320
};
312321

313322
let (client, namespace) = create_client(args.kubernetes_context.clone()).await?;
314323
let additional_crd_columns = args.additional_crd_columns.unwrap_or_default();
315-
let webhook_service_name = args.webhook_service_name;
316-
let webhook_service_namespace = args.webhook_service_namespace;
317-
let webhook_service_port = args.webhook_service_port;
324+
let conversion_webhook = args.install_v1_crd.then(|| ConversionWebhookConfig {
325+
service_name: args
326+
.webhook_service_name
327+
.expect("clap requires --webhook-service-name with --install-v1-crd"),
328+
service_namespace: args
329+
.webhook_service_namespace
330+
.expect("clap requires --webhook-service-namespace with --install-v1-crd"),
331+
service_port: args.webhook_service_port,
332+
ca_cert_path: tls_ca.clone(),
333+
});
318334
register_crds(
319335
client.clone(),
320336
additional_crd_columns.clone(),
321-
webhook_service_name.clone(),
322-
webhook_service_namespace.clone(),
323-
webhook_service_port,
324-
tls_ca.clone(),
337+
conversion_webhook.clone(),
325338
)
326339
.await?;
327340

@@ -336,9 +349,12 @@ async fn run(args: Args) -> Result<(), anyhow::Error> {
336349
// Kubernetes API server would reject every conversion request. Refreshing
337350
// the CA bundle when the CA changes keeps the webhook working across CA
338351
// rotations.
339-
{
352+
if let Some(reload_config) = reload_config {
353+
let conversion_webhook = conversion_webhook
354+
.expect("conversion webhook config is set whenever the webhook server is started");
340355
let reload_interval = args.webhook_cert_reload_interval;
341356
let client = client.clone();
357+
let additional_crd_columns = additional_crd_columns.clone();
342358
mz_ore::task::spawn(|| "webhook certificate reload", async move {
343359
let mut last_ca = tokio::fs::read(&tls_ca).await.ok();
344360
let mut interval = tokio::time::interval(reload_interval);
@@ -367,10 +383,7 @@ async fn run(args: Args) -> Result<(), anyhow::Error> {
367383
match register_crds(
368384
client.clone(),
369385
additional_crd_columns.clone(),
370-
webhook_service_name.clone(),
371-
webhook_service_namespace.clone(),
372-
webhook_service_port,
373-
tls_ca.clone(),
386+
Some(conversion_webhook.clone()),
374387
)
375388
.await
376389
{
@@ -664,3 +677,42 @@ async fn run(args: Args) -> Result<(), anyhow::Error> {
664677

665678
future::pending().await
666679
}
680+
681+
#[cfg(test)]
682+
mod tests {
683+
use clap::Parser;
684+
685+
use super::Args;
686+
687+
const REQUIRED_ARGS: &[&str] = &[
688+
"orchestratord",
689+
"--cloud-provider=local",
690+
"--region=kind",
691+
"--console-image-tag-default=latest",
692+
];
693+
694+
#[mz_ore::test]
695+
fn webhook_service_args_required_with_install_v1_crd() {
696+
let args = Args::try_parse_from(REQUIRED_ARGS).expect("parses without webhook args");
697+
assert!(!args.install_v1_crd);
698+
699+
assert!(
700+
Args::try_parse_from(REQUIRED_ARGS.iter().copied().chain(["--install-v1-crd"]))
701+
.is_err(),
702+
"--install-v1-crd should require the webhook service args"
703+
);
704+
705+
let args = Args::try_parse_from(REQUIRED_ARGS.iter().copied().chain([
706+
"--install-v1-crd",
707+
"--webhook-service-name=orchestratord",
708+
"--webhook-service-namespace=materialize",
709+
]))
710+
.expect("parses with webhook args");
711+
assert!(args.install_v1_crd);
712+
assert_eq!(args.webhook_service_name.as_deref(), Some("orchestratord"));
713+
assert_eq!(
714+
args.webhook_service_namespace.as_deref(),
715+
Some("materialize")
716+
);
717+
}
718+
}

src/orchestratord/src/k8s.rs

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -99,51 +99,71 @@ where
9999
}
100100
}
101101

102+
/// Configuration for the conversion webhook that serves the v1 version of the
103+
/// Materialize CRD. When present, the v1 version is registered alongside
104+
/// v1alpha1 with webhook conversion between them; when absent, only v1alpha1
105+
/// is registered.
106+
#[derive(Debug, Clone)]
107+
pub struct ConversionWebhookConfig {
108+
pub service_name: String,
109+
pub service_namespace: String,
110+
pub service_port: u16,
111+
pub ca_cert_path: String,
112+
}
113+
102114
pub async fn register_crds(
103115
client: Client,
104116
additional_crd_columns: Vec<CustomResourceColumnDefinition>,
105-
webhook_service_name: String,
106-
webhook_service_namespace: String,
107-
webhook_service_port: u16,
108-
ca_cert_path: String,
117+
conversion_webhook: Option<ConversionWebhookConfig>,
109118
) -> Result<(), anyhow::Error> {
110-
let ca_bytes = tokio::fs::read(ca_cert_path).await?;
111-
let mut mz_crd = crd::materialize::v1::Materialize::crd();
112-
let default_columns = mz_crd.spec.versions[0]
119+
let (mut mz_crds, mz_conversion) = match conversion_webhook {
120+
Some(config) => {
121+
let ca_bytes = tokio::fs::read(config.ca_cert_path).await?;
122+
let conversion = CustomResourceConversion {
123+
strategy: "Webhook".to_owned(),
124+
webhook: Some(WebhookConversion {
125+
client_config: Some(WebhookClientConfig {
126+
ca_bundle: Some(ByteString(ca_bytes)),
127+
service: Some(ServiceReference {
128+
name: config.service_name,
129+
namespace: config.service_namespace,
130+
path: Some("/convert".to_owned()),
131+
port: Some(config.service_port.into()),
132+
}),
133+
url: None,
134+
}),
135+
conversion_review_versions: vec!["v1".to_owned()],
136+
}),
137+
};
138+
(
139+
vec![
140+
crd::materialize::v1::Materialize::crd(),
141+
crd::materialize::v1alpha1::Materialize::crd(),
142+
],
143+
Some(conversion),
144+
)
145+
}
146+
None => (vec![crd::materialize::v1alpha1::Materialize::crd()], None),
147+
};
148+
let default_columns = mz_crds[0].spec.versions[0]
113149
.additional_printer_columns
114150
.take()
115151
.expect("should contain ImageRef and UpToDate columns");
116-
mz_crd.spec.versions[0].additional_printer_columns = Some(
152+
mz_crds[0].spec.versions[0].additional_printer_columns = Some(
117153
additional_crd_columns
118154
.into_iter()
119155
.chain(default_columns)
120156
.collect(),
121157
);
122-
let mz_conversion = CustomResourceConversion {
123-
strategy: "Webhook".to_owned(),
124-
webhook: Some(WebhookConversion {
125-
client_config: Some(WebhookClientConfig {
126-
ca_bundle: Some(ByteString(ca_bytes)),
127-
service: Some(ServiceReference {
128-
name: webhook_service_name,
129-
namespace: webhook_service_namespace,
130-
path: Some("/convert".to_owned()),
131-
port: Some(webhook_service_port.into()),
132-
}),
133-
url: None,
134-
}),
135-
conversion_review_versions: vec!["v1".to_owned()],
136-
}),
137-
};
138158
tokio::time::timeout(
139159
Duration::from_secs(120),
140160
register_versioned_crds(
141161
client.clone(),
142162
vec![
143163
VersionedCrd {
144-
crds: vec![mz_crd, crd::materialize::v1alpha1::Materialize::crd()],
164+
crds: mz_crds,
145165
stored_version: String::from("v1alpha1"),
146-
conversion: Some(mz_conversion),
166+
conversion: mz_conversion,
147167
},
148168
VersionedCrd {
149169
crds: vec![crd::balancer::v1alpha1::Balancer::crd()],

0 commit comments

Comments
 (0)