NiFi 2.x 클러스터를 Kubernetes 위에 StatefulSet 으로 올린다. 노드마다 고정된 이름과 고정된 저장소가 필요하므로 Deployment 가 아니라 StatefulSet 과 headless Service 를 쓴다. 보안을 쓰지 않는 개발용 구성(HTTP 8080, 인증 없음)을 기준으로 적고, 설정은 전부 ConfigMap 으로 관리한다.
노드 주소가 고정돼야 하는 이유는 NiFi 클러스터 프로토콜이 노드끼리 서로의 호스트 이름으로 접속하기 때문이다. Pod 이 재기동돼도 nifi-0.nifi-headless.<namespace>.svc.cluster.local 이라는 이름이 유지되어야 클러스터가 다시 붙는다.
| 오브젝트 | 역할 |
|---|---|
StatefulSet nifi |
노드 Pod. serviceName 에 headless Service 이름을 준다 |
Service nifi-headless (clusterIP: None) |
Pod 별 DNS 이름 제공. 클러스터 프로토콜·로드밸런스 통신 경로 |
Service nifi (ClusterIP) |
UI·REST API 진입점 |
ConfigMap nifi-conf |
nifi.properties · bootstrap.conf · state-management.xml · logback.xml |
PVC (volumeClaimTemplates) |
database · flowfile · content · provenance repository, conf, logs |
| Ingress | UI 외부 노출 |
HTTP 전용 구성에서는 HTTPS 관련 값을 빈 값으로 두어야 한다. 값이 일부만 채워져 있으면 기동 중 SSL 컨텍스트 초기화에서 실패한다.
nifi.web.http.host=
nifi.web.http.port=8080
nifi.web.https.host=
nifi.web.https.port=
nifi.web.proxy.host=dev-nifi.example.com:443
nifi.web.proxy.context.path=
nifi.remote.input.secure=false
nifi.remote.input.socket.port=10443
nifi.cluster.is.node=true
nifi.cluster.node.address=${HOSTNAME}.nifi-headless.staging.svc.cluster.local
nifi.cluster.node.protocol.port=11443
nifi.cluster.protocol.is.secure=false
nifi.cluster.node.connection.timeout=30 sec
nifi.cluster.node.read.timeout=30 sec
nifi.cluster.flow.election.max.wait.time=1 min
nifi.cluster.flow.election.max.candidates=3
nifi.cluster.load.balance.host=${HOSTNAME}.nifi-headless.staging.svc.cluster.local
nifi.cluster.load.balance.port=6342
nifi.sensitive.props.key=${NIFI_SENSITIVE_PROPS_KEY}
nifi.state.management.configuration.file=./conf/state-management.xml
nifi.state.management.provider.cluster=zk-provider
nifi.web.http.host 를 비워 두면 모든 인터페이스에 바인딩한다. 반대로 Pod 이 갖고 있지 않은 주소를 적으면 기동 시 BindException: Cannot assign requested address 가 난다.
nifi.cluster.flow.election.max.candidates 는 replica 수와 맞춘다. 이 값이 채워질 때까지, 또는 max.wait.time 이 지날 때까지 플로우 선출을 기다린다. 1 로 두면 첫 번째로 올라온 노드의 플로우가 곧바로 정본이 되므로, 롤링 재기동 중 빈 플로우가 선출될 위험이 있다.
nifi.sensitive.props.key 는 NiFi 1.14 이후 반드시 12 자 이상 값이 있어야 기동한다. Secret 으로 넣고 환경변수로 치환한다.
클러스터 범위 상태(프로세서 state, 프라이머리 노드 선출)는 상태 공급자가 담당한다. NiFi 2.x 는 두 방식을 지원한다.
기존 ZooKeeper 앙상블이 있으면 state-management.xml 의 클러스터 공급자를 그대로 쓴다.
<cluster-provider>
<id>zk-provider</id>
<class>org.apache.nifi.controller.state.providers.zookeeper.ZooKeeperStateProvider</class>
<property name="Connect String">zookeeper-0.zookeeper-headless.staging.svc.cluster.local:2181,zookeeper-1.zookeeper-headless.staging.svc.cluster.local:2181,zookeeper-2.zookeeper-headless.staging.svc.cluster.local:2181</property>
<property name="Root Node">/nifi</property>
<property name="Session Timeout">10 seconds</property>
<property name="Access Control">Open</property>
</cluster-provider>
ZooKeeper 를 두고 싶지 않으면 Kubernetes 자체의 Lease 와 ConfigMap 을 쓴다. 이때 nifi.properties 와 state-management.xml 을 다음처럼 바꾼다.
nifi.cluster.leader.election.implementation=KubernetesLeaderElectionManager
nifi.state.management.provider.cluster=kubernetes-provider
<cluster-provider>
<id>kubernetes-provider</id>
<class>org.apache.nifi.kubernetes.state.provider.KubernetesConfigMapStateProvider</class>
</cluster-provider>
이 구성은 Pod 의 ServiceAccount 에 네임스페이스 범위의 leases(coordination.k8s.io) 와 configmaps 에 대한 get · list · watch · create · update · delete 권한을 요구한다. Role 과 RoleBinding 을 함께 만들어야 한다. ZooKeeper 없이 클러스터를 구성할 수 있는 것은 이 방식뿐이며, "NiFi 2.x 는 내장 선출로 ZooKeeper 가 필요 없다" 는 설명은 정확하지 않다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nifi
namespace: staging
spec:
serviceName: nifi-headless
replicas: 3
podManagementPolicy: Parallel
selector:
matchLabels:
app: nifi
template:
metadata:
labels:
app: nifi
spec:
serviceAccountName: nifi
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: nifi
image: registry.example.com/nifi:2.5.0
command: ['/opt/nifi/bin/nifi.sh', 'run']
env:
- name: NIFI_HOME
value: /opt/nifi
- name: JAVA_HOME
value: /usr/lib/jvm/java-21-openjdk
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NIFI_SENSITIVE_PROPS_KEY
valueFrom:
secretKeyRef:
name: nifi-secret
key: sensitive-props-key
ports:
- name: http
containerPort: 8080
- name: cluster
containerPort: 11443
- name: loadbalance
containerPort: 6342
startupProbe:
httpGet:
path: /nifi-api/system-diagnostics
port: http
periodSeconds: 10
failureThreshold: 60
readinessProbe:
httpGet:
path: /nifi-api/system-diagnostics
port: http
periodSeconds: 10
livenessProbe:
httpGet:
path: /nifi-api/system-diagnostics
port: http
periodSeconds: 30
failureThreshold: 6
volumeMounts:
- name: conf
mountPath: /opt/nifi/conf
- name: data
mountPath: /opt/nifi/data
- name: logs
mountPath: /opt/nifi/logs
volumes:
- name: conf
configMap:
name: nifi-conf
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ['ReadWriteOnce']
resources:
requests:
storage: 100Gi
- metadata:
name: logs
spec:
accessModes: ['ReadWriteOnce']
resources:
requests:
storage: 20Gi
NiFi 는 기동 시 conf 디렉터리 안의 파일을 갱신하려 들 수 있으므로, ConfigMap 을 conf 에 통째로 마운트하면 쓰기가 막혀 문제가 되는 경우가 있다. 안전한 방법은 ConfigMap 을 /opt/nifi/conf-tpl 에 마운트하고 initContainer 또는 진입 스크립트에서 conf 로 복사한 뒤 환경변수를 치환하는 것이다.
프로브 경로 /nifi-api/system-diagnostics 는 인증을 끈 구성에서만 200 을 돌려준다. 보안을 켜면 401 이 되므로 /nifi-api/access/config 처럼 인증 없이 응답하는 경로로 바꿔야 한다. 기동이 오래 걸리므로 startupProbe 를 두고 liveness 를 늦춰야 재시작 루프에 빠지지 않는다.
apiVersion: v1
kind: Service
metadata:
name: nifi-headless
namespace: staging
spec:
clusterIP: None
publishNotReadyAddresses: true
selector:
app: nifi
ports:
- name: cluster
port: 11443
- name: loadbalance
port: 6342
- name: http
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nifi
namespace: staging
spec:
ingressClassName: nginx-staging
rules:
- host: dev-nifi.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nifi
port:
number: 8080
headless Service 에 publishNotReadyAddresses: true 를 둬야 아직 Ready 가 아닌 노드끼리도 DNS 로 서로를 찾아 클러스터를 이룰 수 있다. 이 값이 없으면 최초 기동에서 서로를 찾지 못해 선출이 끝나지 않는다.
Ingress 로 접근할 도메인은 nifi.web.proxy.host 에 반드시 넣는다. 없으면 NiFi 가 호스트 헤더를 거부해 Invalid Host header 로 응답한다.
메모리와 GC 는 bootstrap.conf 에서 정한다.
java.arg.2=-Xms4g
java.arg.3=-Xmx16g
java.arg.13=-XX:+UseG1GC
java.arg.14=-XX:MaxGCPauseMillis=200
컨테이너 메모리 limit 은 힙보다 충분히 커야 한다. NiFi 는 힙 외에 콘텐츠 저장소 메모리 맵과 스레드 스택을 쓰므로 힙의 1.5 배 정도를 잡는다.
nifi.sh run 은 포그라운드로 동작한다. 다만 표준 출력으로 로그가 나오려면 logback.xml 의 root 로거가 CONSOLE appender 를 바라봐야 한다. 기본 설정은 파일 appender 만 쓰므로 컨테이너에서는 다음처럼 바꾼다.
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
NiFi 2.x 는 Java 21 이상을 요구한다. 이미지 안의 JAVA_HOME 이 21 을 가리키는지 확인한다.
기본 배포본은 HTTPS 단일 사용자 인증으로 뜬다. HTTP 로 쓰려면 위처럼 nifi.web.https.* 를 비우고 nifi.security.* 값을 모두 지워야 한다. 하나라도 남아 있으면 기동 실패한다.
클러스터 노드 수를 줄일 때는 UI 에서 해당 노드를 먼저 Disconnect → Offload → Delete 한 뒤 replica 를 줄인다. 그냥 줄이면 남은 노드가 사라진 노드를 계속 기다린다.