Apache Nifiクラスタ構成の備忘録


# はじめに
Apache Nifiを最近知った。

WebSocketが使えたり、簡単なMQサーバに出来たり、・・・とデータフローを視覚的にデザインしスケジュールし分散実行することができる。とても面白い。

スタンドアロンは簡単にできたものの、クラスタ構成を作るのに、ずいぶん手間取ったので、設定などの備忘録。

# 前提
- Dockerでやる（何台もAWSにEC2立てる金がもったいないので、便宜上です。）
- ３つのNifiノード(それぞれ、`nifi1`,`nifi2`,`nifi3`というホスト名）
- １つのZookeeperノード（`zookeeper`というホスト名)
- NiFiにある内蔵Zookeeperは利用しない

(※実運用環境などではZookeeperも複数ノード用意すべきだが、そこは目的とは異なるので割愛）

# 手順

## Dockerイメージの作成
結構ええ加減な作り方してます。使いやすくするには環境変数とかで、パラメータ調整できるようにすべきかもしれませんが、設定ファイルを直接マウントする形で進めます。
### Apache Nifi

#### Dockerfile

```dockerfile
FROM openjdk:11-jre

# nifiユーザの作成
RUN addgroup --system --gid 1001 nifi &&\
    adduser --system --home /home/nifi --shell /bin/bash --uid 1001 --gid 1001 nifi &&\
    cd /home/nifi
USER nifi
WORKDIR /home/nifi

# Apache Nifiのダウンロードと展開とパス通す
RUN wget ftp://ftp.riken.jp/net/apache/nifi/1.12.1/nifi-1.12.1-bin.tar.gz &&\
    tar -xf nifi-1.12.1-bin.tar.gz
ENV NIFI_HOME=/home/nifi/nifi-1.12.1
ENV PATH=$PATH:$NIFI_HOME/bin

EXPOSE 8080
CMD ["nifi.sh","run"]
```

ビルドする

```sh
$ sudo docker build -t mynifi .
```
#### nifi.properties

Dockerイメージをビルド後、nifi.propertiesをHOST側へ持っていって編集。各ノード分用意する。

```sh
$ docker run --rm mynifi cat /home/nifi/nifi-1.12.1/conf/nifi.properties > nifi1.properties
$ vi nifi1.properties
```

変更するところだけ抜き出し

```properties
#(中略）
#ノードのホスト名と合わせる
nifi.remote.input.host=nifi1

#適当なポートを設定
nifi.remote.input.socket=8090

#ノードのホスト名と合わせる
nifi.web.http.host=nifi1

#trueに変更
nifi.cluster.is.node=true

#ノードのホスト名と合わせる
nifi.cluster.node.address=nifi1

# 適当なポートを設定
nifi.cluster.node.protocol.port=8091

# ノードのリーダー選挙時間。デフォ５分が長くて待つのが面倒だったので、ちょっと短くした。
nifi.cluster.flow.election.max.wait.time=1 min

# 複数Zookeeperを指定する時はカンマ区切りで並べる
nifi.zookeeper.connect.string=zookeeper1:2181
```

#### state-management.xml

```sh
$ sudo docker run --rm mynifi cat /home/nifi/nifi-1.12.1/conf/state-management.xml > state-management.xml
$ vi state-management.xml
```

```xml

<!-- 中略 -->
<cluster-provider>
    <id>zk-provider</id>
    <class>org.apache.nifi.controller.state.providers.zookeeper.ZooKeeperStateProvider</class>
    <!-- Zookeeperの宛先を設定 -->
    <property name="Connect String">zookeeper1:2181</property>
    <property name="Root Node">/nifi</property>
    <property name="Session Timeout">10 seconds</property>
    <property name="Access Control">Open</property>
</cluster-provider>
```

### Apache Zookeeper
NiFiには内蔵Zookeeperがあり、`nifi.state.management.embeded.zookeeper.start=true`とすればNiFiと同時にZookeeperも起動できるが、より実運用に近いイメージで外部のZookeeperを使うようにする。

Apache Kafkaなどで他に立てているZookeeperノードがあるなら、そちらに繋げても問題ないと思う。

#### Dockerfile

```dockerfile

FROM openjdk:11-jre

RUN mkdir /home/zookeeper && mkdir /var/lib/zookeeper
WORKDIR /home/zookeeper

RUN wget https://downloads.apache.org/zookeeper/zookeeper-3.6.2/apache-zookeeper-3.6.2-bin.tar.gz &&\
    tar xzf apache-zookeeper-3.6.2-bin.tar.gz

ENV ZOOKEEPER_HOME=/home/zookeeper/apache-zookeeper-3.6.2-bin
ENV PATH=$PATH:$ZOOKEEPER_HOME/bin

COPY Start.sh /home/zookeeper/Start.sh
CMD ["./Start.sh"]
```

#### Start.sh

Zookeeperは`myid`ファイルをZookeeperのデータディレクトリ(/var/lib/zookeeper)に作成しておく必要がある。Dockerコンテナ実行時に環境変数で渡して、生成するようにシェルスクリプトを作成しておく。

```bash
#!/bin/bash
echo $ZOOKEEPER_MYID > /var/lib/zookeeper/myid
zkServer.sh start-foreground
```

#### zoo.cfg

Zookeeperのconfディレクトリにある、`zoo_sample.cfg`ファイルをコピーして、`zoo.cfg`という名前で編集する。

```
$ sudo docker build -t myzookeeper .
$ sudo docker run --rm myzookeeper cat /home/zookeeper/apache-zookeeper-3.6.2/conf/zoo_sample.cfg > zoo.cfg
$ vi zoo.cfg
```


```properties
# 最終行にでも以下を追記する。この1は先ほどのZOOKEEPER_MYIDと紐づく
server.1=zookeeper:2888:3888
```

## Docker-Composeの作成

Dockerイメージの準備ができたので、Docker-Composeファイル作成する

#### docker-compose.yaml

```yaml
version: '3'
services:
  zookeeper:
    build: ./zookeeper
    image: "myzookeeper"
    container_name: "zookeeper"
    environment:
      ZOOKEEPER_MYID: 1
    volumes:
      - ./zookeeper/zoo.cfg:/home/zookeeper/apache-zookeeper-3.6.2-bin/conf/zoo.cfg
    
  nifi1:
    build: ./nifi
    image: "mynifi"
    container_name: "nifi1"
    ports:
      - 8080:8080
    volumes:
      - ./nifi/nifi1.properties:/home/nifi/nifi-1.12.1/conf/nifi.properties
      - ./nifi/state-management.xml:/home/nifi/nifi-1.12.1/conf/state-management.xml
  nifi2:
    build: ./nifi
    image: "mynifi"
    container_name: "nifi2"
    ports:
      - 8081:8080
    volumes:
      - ./nifi/nifi2.properties:/home/nifi/nifi-1.12.1/conf/nifi.properties
      - ./nifi/state-management.xml:/home/nifi/nifi-1.12.1/conf/state-management.xml    
  nifi3:
    build: ./nifi
    image: "mynifi"
    container_name: "nifi3"
    ports:
      - 8082:8080
    volumes:
      - ./nifi/nifi3.properties:/home/nifi/nifi-1.12.1/conf/nifi.properties
      - ./nifi/state-management.xml:/home/nifi/nifi-1.12.1/conf/state-management.xml  
```

### 出来上がったディレクトリ構成

```text
+ /project_root
  - docker-compose.yaml
  + /nifi
    - Dockerfile
    - nifi1.properties
    - nifi2.properties
    - nifi3.properteis
    - state-mnagement.xml
  + /zookeeper
    - Dockerfile
    - zoo.cfg
    - Start.sh
```

## 実行！

```
$ sudo docker-compose up -d
```
１分ほどリーダ選挙が行われるのでしばらく、待つ。`docker exec`でApache NiFiの`$NIFI_HOME/logs/nifi-app.log`を`tail -f`して眺めてもいい。

`http://localhost:8080/nifi`にブラウザでアクセス。クラスタマークが３／３になってれば成功。

![ss1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116285/db67f282-38ba-3fbe-8310-fef998f4115b.png)

右側のパン屑から、「Cluster」を選択し各ノードの状態をみる。Nifi2ノードがプライマリにNifi3ノードがコーディネータになった模様。

![ss2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116285/3ea68b72-8c0e-931e-ead6-71238a19da1f.png)