Skip to content

Commit b985f03

Browse files
nightkrmaltesander
andauthored
Support Zookeeperless NiFi (#775)
* Resync Cargo.nix * Add Kubernetes state provider * Use raw strings to clean up escaping... * Prefix the Kubernetes state configmaps by the actual stacklet name, rather than hard-coding it * Make clustering mode configurable, add Kubernetes mode * Regenerate CRD * Rewrite zookeeperConfigMapName docs to emphasize that it is optional for NiFi 2.x * Update docs to mention Kubernetes backend * Rename clustering_mode to clustering_backend * Make the nifi prepare script more idempotent Previously, these bits would error when re-executed, causing confusing errors when templating fails. * Only include the used cluster state provider in xml config * Add test Copied from smoke since I'm not aware of a native way to exclude specific dimension combinations. * Explicitly fail when trying to use NiFi 1.x in zookeeperless mode * Minor docs rewording * Fix a compile error that I missed * refmt with the right rustfmt version * refmt python * Changelog * Update CHANGELOG.md Co-authored-by: Malte Sander <[email protected]> * split smoke test into v1 and v2 * newline * rename dimensions * remove trailing spaces * Formatting --------- Co-authored-by: Malte Sander <[email protected]>
1 parent 1590b98 commit b985f03

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+770
-185
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
1111
- Use `--file-log-max-files` (or `FILE_LOG_MAX_FILES`) to limit the number of log files kept.
1212
- Use `--file-log-rotation-period` (or `FILE_LOG_ROTATION_PERIOD`) to configure the frequency of rotation.
1313
- Use `--console-log-format` (or `CONSOLE_LOG_FORMAT`) to set the format to `plain` (default) or `json`.
14+
- NiFi 2.x now supports storing cluster state in Kubernetes instead of ZooKeeper ([#775]).
1415
- Add test for Apache Iceberg integration ([#785]).
1516

1617
### Changed
@@ -38,6 +39,7 @@ All notable changes to this project will be documented in this file.
3839
[#771]: https://github.com/stackabletech/nifi-operator/pull/771
3940
[#772]: https://github.com/stackabletech/nifi-operator/pull/772
4041
[#774]: https://github.com/stackabletech/nifi-operator/pull/774
42+
[#775]: https://github.com/stackabletech/nifi-operator/pull/775
4143
[#776]: https://github.com/stackabletech/nifi-operator/pull/776
4244
[#782]: https://github.com/stackabletech/nifi-operator/pull/782
4345
[#785]: https://github.com/stackabletech/nifi-operator/pull/785

deploy/helm/nifi-operator/crds/crds.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ spec:
2626
description: A NiFi cluster stacklet. This resource is managed by the Stackable operator for Apache NiFi. Find more information on how to use it and the resources that the operator generates in the [operator documentation](https://docs.stackable.tech/home/nightly/nifi/).
2727
properties:
2828
clusterConfig:
29+
anyOf:
30+
- required:
31+
- zookeeperConfigMapName
32+
- {}
2933
description: Settings that affect all roles and role groups. The settings in the `clusterConfig` are cluster wide settings that do not need to be configurable at role or role group level.
3034
properties:
3135
authentication:
@@ -194,12 +198,14 @@ spec:
194198
nullable: true
195199
type: string
196200
zookeeperConfigMapName:
197-
description: NiFi requires a ZooKeeper cluster connection to run. Provide the name of the ZooKeeper [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) here. When using the [Stackable operator for Apache ZooKeeper](https://docs.stackable.tech/home/nightly/zookeeper/) to deploy a ZooKeeper cluster, this will simply be the name of your ZookeeperCluster resource.
201+
description: |-
202+
NiFi can either use ZooKeeper or Kubernetes for managing its cluster state. To use ZooKeeper, provide the name of the ZooKeeper [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery) here. When using the [Stackable operator for Apache ZooKeeper](https://docs.stackable.tech/home/nightly/zookeeper/) to deploy a ZooKeeper cluster, this will simply be the name of your ZookeeperCluster resource.
203+
204+
The Kubernetes provider will be used if this field is unset. Kubernetes is only supported for NiFi 2.x and newer, NiFi 1.x requires ZooKeeper.
198205
type: string
199206
required:
200207
- authentication
201208
- sensitiveProperties
202-
- zookeeperConfigMapName
203209
type: object
204210
clusterOperation:
205211
default:

deploy/helm/nifi-operator/templates/roles.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,11 @@ rules:
133133
- apiGroups:
134134
- ""
135135
resources:
136-
- configmaps
137136
- secrets
138137
- serviceaccounts
138+
# This is redundant with the rule for specifically about configmaps
139+
# (due to clustering), but we read them for other purposes too
140+
- configmaps
139141
verbs:
140142
- get
141143
- apiGroups:
@@ -144,6 +146,29 @@ rules:
144146
- events
145147
verbs:
146148
- create
149+
# Required for Kubernetes-managed clustering, see https://nifi.apache.org/nifi-docs/administration-guide.html#kubernetes-clustering
150+
- apiGroups:
151+
- coordination.k8s.io
152+
resources:
153+
- leases
154+
verbs:
155+
- create
156+
- get
157+
- update
158+
# undocumented but required
159+
- patch
160+
# Required for Kubernetes cluster state provider, see https://nifi.apache.org/nifi-docs/administration-guide.html#kubernetes-configmap-cluster-state-provider
161+
- apiGroups:
162+
- ""
163+
resources:
164+
- configmaps
165+
verbs:
166+
- create
167+
- delete
168+
- get
169+
- list
170+
- patch
171+
- update
147172
{{ if .Capabilities.APIVersions.Has "security.openshift.io/v1" }}
148173
- apiGroups:
149174
- security.openshift.io

docs/modules/nifi/pages/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Every role group is accessible through it's own Service, and there is a Service
3535

3636
== Dependencies
3737

38-
Apache NiFi depends on Apache ZooKeeper which you can run in Kubernetes with the xref:zookeeper:index.adoc[].
38+
Apache NiFi 1.x depends on Apache ZooKeeper which you can run in Kubernetes with the xref:zookeeper:index.adoc[].
3939

4040
== [[demos]]Demos
4141

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
= Clustering
2+
:description: Apache NiFi requires a backend for cluster management, and supports either Kubernetes or Apache ZooKeeper.
3+
:page-aliases: usage_guide/zookeeper-connection.adoc
4+
5+
Apache NiFi requires{empty}footnote:[Apache NiFi also supports a single-node mode with no cluster backend, but this is not supported by the Stackable Operator for Apache NiFi. The Stackable Operator does require a cluster backend.] an external backend for state management and leader election.
6+
7+
Currently, the Stackable Operator for Apache NiFi supports the following backends:
8+
9+
- xref:#backend-kubernetes[]
10+
- xref:#backend-zookeeper[]
11+
12+
CAUTION: The cluster backend of an existing cluster should never be changed. Otherwise data loss may occur, both due to losing NiFi processor state, and due to potential split-brain scenarios during the migration.
13+
14+
[#backend-kubernetes]
15+
== Kubernetes
16+
17+
NOTE: The Kubernetes provider is only supported by Apache NiFi 2.0 or newer. When using NiFi 1.x, use the xref:#backend-zookeeper[] backend instead.
18+
19+
The Kubernetes backend is used by default (unless the xref:#backend-zookeeper[] backend is configured), and stores all state in Kubernetes objects, in the same namespace as the `NifiCluster` object.
20+
21+
It takes no configuration.
22+
23+
[#backend-zookeeper]
24+
== Apache ZooKeeper
25+
26+
NiFi can also be configured to store its state in Apache ZooKeeper.
27+
28+
NiFi in cluster mode requires an Apache ZooKeeper ensemble for state management and leader election purposes, the Stackable operator for Apache NiFi does not support single node deployments without ZooKeeper, hence this is a required setting.
29+
30+
This is enabled by setting the `spec.clusterConfig.zookeeperConfigMapName` to a xref:concepts:service-discovery.adoc[discovery ConfigMap]:
31+
32+
[source,yaml]
33+
----
34+
spec:
35+
clusterConfig:
36+
zookeeperConfigMapName: simple-nifi-znode
37+
----
38+
39+
The ConfigMap needs to contain two keys: `ZOOKEEPER_HOSTS` containing the value being the ZooKeeper connection string, and `ZOOKEEPER_CHROOT` containing the ZooKeeper chroot.
40+
41+
The xref:zookeeper:index.adoc[Stackable operator for Apache ZooKeeper] automatically creates this ConfigMap for every ZookeeperZnode object.

docs/modules/nifi/pages/usage_guide/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ spec:
4343
replicas: 3
4444
----
4545

46-
<1> The xref:usage_guide/zookeeper-connection.adoc[ZooKeeper instance] to use.
46+
<1> The xref:usage_guide/clustering.adoc#backend-zookeeper[ZooKeeper instance] to use.
4747
<2> How users should xref:usage_guide/security.adoc[authenticate] themselves.
4848
<3> xref:usage_guide/extra-volumes.adoc[Extra volumes] with files that can be referenced in custom workflows.
4949
<4> xref:usage_guide/resource-configuration.adoc[CPU and memory configuration] can be set per role group.

docs/modules/nifi/pages/usage_guide/zookeeper-connection.adoc

Lines changed: 0 additions & 14 deletions
This file was deleted.

docs/modules/nifi/partials/nav.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
** xref:nifi:getting_started/first_steps.adoc[]
44
* xref:nifi:usage_guide/index.adoc[]
55
** xref:nifi:usage_guide/listenerclass.adoc[]
6-
** xref:nifi:usage_guide/zookeeper-connection.adoc[]
6+
** xref:nifi:usage_guide/clustering.adoc[]
77
** xref:nifi:usage_guide/extra-volumes.adoc[]
88
** xref:nifi:usage_guide/security.adoc[]
99
** xref:nifi:usage_guide/resource-configuration.adoc[]

rust/operator-binary/src/config/mod.rs

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55

66
use jvm::build_merged_jvm_config;
77
use product_config::{ProductConfigManager, types::PropertyNameKind};
8-
use snafu::{ResultExt, Snafu};
8+
use snafu::{ResultExt, Snafu, ensure};
99
use stackable_operator::{
1010
commons::resources::Resources,
1111
memory::MemoryQuantity,
@@ -20,7 +20,7 @@ use strum::{Display, EnumIter};
2020
use crate::{
2121
crd::{
2222
HTTPS_PORT, NifiConfig, NifiConfigFragment, NifiRole, NifiStorageConfig, PROTOCOL_PORT,
23-
v1alpha1,
23+
v1alpha1::{self, NifiClusteringBackend},
2424
},
2525
operations::graceful_shutdown::graceful_shutdown_config_properties,
2626
security::{
@@ -96,6 +96,11 @@ pub enum Error {
9696

9797
#[snafu(display("failed to generate OIDC config"))]
9898
GenerateOidcConfig { source: oidc::Error },
99+
100+
#[snafu(display(
101+
"NiFi 1.x requires ZooKeeper (hint: upgrade to NiFi 2.x or set .spec.clusterConfig.zookeeperConfigMapName)"
102+
))]
103+
Nifi1RequiresZookeeper,
99104
}
100105

101106
/// Create the NiFi bootstrap.conf
@@ -143,13 +148,15 @@ pub fn build_nifi_properties(
143148
overrides: BTreeMap<String, String>,
144149
product_version: &str,
145150
) -> Result<String, Error> {
151+
// TODO: Remove once we dropped support for all NiFi 1.x versions
152+
let is_nifi_1 = product_version.starts_with("1.");
153+
146154
let mut properties = BTreeMap::new();
147155
// Core Properties
148156
// According to https://cwiki.apache.org/confluence/display/NIFI/Migration+Guidance#MigrationGuidance-Migratingto2.0.0-M1
149157
// The nifi.flow.configuration.file property in nifi.properties must be changed to reference
150158
// "flow.json.gz" instead of "flow.xml.gz"
151-
// TODO: Remove once we dropped support for all 1.x.x versions
152-
let flow_file_name = if product_version.starts_with("1.") {
159+
let flow_file_name = if is_nifi_1 {
153160
"flow.xml.gz"
154161
} else {
155162
"flow.json.gz"
@@ -250,7 +257,10 @@ pub fn build_nifi_properties(
250257
// The ID of the cluster-wide state provider. This will be ignored if NiFi is not clustered but must be populated if running in a cluster.
251258
properties.insert(
252259
"nifi.state.management.provider.cluster".to_string(),
253-
"zk-provider".to_string(),
260+
match spec.cluster_config.clustering_backend {
261+
v1alpha1::NifiClusteringBackend::ZooKeeper { .. } => "zk-provider".to_string(),
262+
v1alpha1::NifiClusteringBackend::Kubernetes { .. } => "kubernetes-provider".to_string(),
263+
},
254264
);
255265
// Specifies whether or not this instance of NiFi should run an embedded ZooKeeper server
256266
properties.insert(
@@ -559,47 +569,84 @@ pub fn build_nifi_properties(
559569
"".to_string(),
560570
);
561571

562-
// zookeeper properties, used for cluster management
563-
// this will be replaced via a container command script
564-
properties.insert(
565-
"nifi.zookeeper.connect.string".to_string(),
566-
"${env:ZOOKEEPER_HOSTS}".to_string(),
567-
);
568-
569-
// this will be replaced via a container command script
570-
properties.insert(
571-
"nifi.zookeeper.root.node".to_string(),
572-
"${env:ZOOKEEPER_CHROOT}".to_string(),
573-
);
572+
match spec.cluster_config.clustering_backend {
573+
v1alpha1::NifiClusteringBackend::ZooKeeper { .. } => {
574+
properties.insert(
575+
"nifi.cluster.leader.election.implementation".to_string(),
576+
"CuratorLeaderElectionManager".to_string(),
577+
);
578+
579+
// this will be replaced via a container command script
580+
properties.insert(
581+
"nifi.zookeeper.connect.string".to_string(),
582+
"${env:ZOOKEEPER_HOSTS}".to_string(),
583+
);
584+
585+
// this will be replaced via a container command script
586+
properties.insert(
587+
"nifi.zookeeper.root.node".to_string(),
588+
"${env:ZOOKEEPER_CHROOT}".to_string(),
589+
);
590+
}
591+
592+
v1alpha1::NifiClusteringBackend::Kubernetes {} => {
593+
ensure!(!is_nifi_1, Nifi1RequiresZookeeperSnafu);
594+
595+
properties.insert(
596+
"nifi.cluster.leader.election.implementation".to_string(),
597+
"KubernetesLeaderElectionManager".to_string(),
598+
);
599+
600+
// this will be replaced via a container command script
601+
properties.insert(
602+
"nifi.cluster.leader.election.kubernetes.lease.prefix".to_string(),
603+
"${env:STACKLET_NAME}".to_string(),
604+
);
605+
}
606+
}
574607

575608
// override with config overrides
576609
properties.extend(overrides);
577610

578611
Ok(format_properties(properties))
579612
}
580613

581-
pub fn build_state_management_xml() -> String {
614+
pub fn build_state_management_xml(clustering_backend: &NifiClusteringBackend) -> String {
615+
// Inert providers are ignored by NiFi itself, but templating still fails if they refer to invalid environment variables,
616+
// so only include the actually used provider.
617+
let cluster_provider = match clustering_backend {
618+
NifiClusteringBackend::ZooKeeper { .. } => {
619+
r#"<cluster-provider>
620+
<id>zk-provider</id>
621+
<class>org.apache.nifi.controller.state.providers.zookeeper.ZooKeeperStateProvider</class>
622+
<property name="Connect String">${env:ZOOKEEPER_HOSTS}</property>
623+
<property name="Root Node">${env:ZOOKEEPER_CHROOT}</property>
624+
<property name="Session Timeout">10 seconds</property>
625+
<property name="Access Control">Open</property>
626+
</cluster-provider>"#
627+
}
628+
NifiClusteringBackend::Kubernetes {} => {
629+
r#"<cluster-provider>
630+
<id>kubernetes-provider</id>
631+
<class>org.apache.nifi.kubernetes.state.provider.KubernetesConfigMapStateProvider</class>
632+
<property name="ConfigMap Name Prefix">${env:STACKLET_NAME}</property>
633+
</cluster-provider>"#
634+
}
635+
};
582636
format!(
583-
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>
637+
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
584638
<stateManagement>
585639
<local-provider>
586-
<id>local-provider</id>
640+
<id>local-provider</id>
587641
<class>org.apache.nifi.controller.state.providers.local.WriteAheadLocalStateProvider</class>
588-
<property name=\"Directory\">{}</property>
589-
<property name=\"Always Sync\">false</property>
590-
<property name=\"Partitions\">16</property>
591-
<property name=\"Checkpoint Interval\">2 mins</property>
642+
<property name="Directory">{local_state_path}</property>
643+
<property name="Always Sync">false</property>
644+
<property name="Partitions">16</property>
645+
<property name="Checkpoint Interval">2 mins</property>
592646
</local-provider>
593-
<cluster-provider>
594-
<id>zk-provider</id>
595-
<class>org.apache.nifi.controller.state.providers.zookeeper.ZooKeeperStateProvider</class>
596-
<property name=\"Connect String\">${{env:ZOOKEEPER_HOSTS}}</property>
597-
<property name=\"Root Node\">${{env:ZOOKEEPER_CHROOT}}</property>
598-
<property name=\"Session Timeout\">10 seconds</property>
599-
<property name=\"Access Control\">Open</property>
600-
</cluster-provider>
601-
</stateManagement>",
602-
&NifiRepository::State.mount_path(),
647+
{cluster_provider}
648+
</stateManagement>"#,
649+
local_state_path = NifiRepository::State.mount_path(),
603650
)
604651
}
605652

0 commit comments

Comments
 (0)