ARMボード色々でおうちDockerSwarmクラスタ SeaweedFS編

クラスタを作っている所(進行形)なのでそのメモを残していきます。この記事ではおうちARMボードに、SeaweedFSを入れます。

# おうちクラスタシリーズ
- 第1弾: [ハードウェア編](https://qiita.com/Domao/items/093c93ff5f3ee6f49cea) 
- 第2弾: Portainer編
- 第3弾: Ceph編(失敗)
- 第4弾: SeaweedFS編

# SeaweedFSについて
https://github.com/chrislusf/seaweedfs

複数のストレージを使って分散型オブジェクトストレージを作れるGo製のソフトウェアRAID的なやつです。S3、ファイルシステム、FUSEマウント、WebDAV、Hadoop互換ファイルシステム、WebUIなどなど色々対応してるみたいです。2014年頃から開発されていて、もちろんDocker対応、ARMにもたぶん対応している、らしいです。シングルバイナリになっているので、同じDockerイメージを使い、渡すコマンドを変えるだけで各サーバーを建てられるらしいです。READMEによると、アーキテクチャはGlusterFSやCephとほぼ同じものの、小さなサイズのファイルに特化して、高速でシンプルを目指しているとのことです。

## なんでCephを使わないの?
Cephは前回入れようとしたのですが、導入が結構複雑だったのと、(ARMv7/ARM64用のDockerImageがないようで?)途中で詰んでしまったので断念しました。更に更に導入済みの友人に、結構要求スペックが高いと言われてしまったので、ちょっとラズパイではきついのかもしれない。

## なんでSeaweedFSを使うの?
https://www.reddit.com/r/selfhosted/comments/l9n5d4/raspberry_pi_docker_swarm_distributed_storage/

Redditの[ラズパイで組んだDockerSwarmで使う分散ストレージのおすすめを訊くスレッド](https://www.reddit.com/r/selfhosted/comments/l9n5d4/raspberry_pi_docker_swarm_distributed_storage/)にて、SeaweedFS開発者さんが宣伝してるコメントを見て気になったのでやってみることにしました。あとQiita内にSeaweedFSを紹介している記事がなかったので、検討してる人のための資料を増やします。

# SeaweedFSの用語
https://github.com/chrislusf/seaweedfs/wiki/Components
そこまでややこしい仕様はなくかなりシンプルなものになっています。
SeaweedFSで立ち上げられるサーバーについては項目を分けて書きます。

## replication
https://ja.wikipedia.org/wiki/%E3%83%AC%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

https://github.com/chrislusf/seaweedfs/wiki/Replication

同じデータの冗長化/複製をやって、信頼性を上げることを指す用語っぽいです。

## volume
SeaweedFSでは多数の小さなファイルを1つの大きなファイルにまとめてボリュームという単位で保存するようです。デフォルトでは、マスターの起動時に、30GBのボリュームが8つ構成されます。レプリケーションやTTLはボリューム単位で設定します。

## collection
SeaweedFSではボリュームの集まりをコレクションと呼ぶようです。S3互換APIでバケットを作った際に、コレクションが作成されます。これによりバケットの削除や管理を簡単にしているようです。コレクションとなっていても、レプリケーションやTTLはボリュームそれぞれに設定します。コレクションの作成時にボリュームをまとめて用意するので、容量が不足してしまう場合はボリュームを小さくすることがおすすめだそうです。

# SeaweedFSの各種サーバー
最低限masterとvolumeさえあればCUIからファイルの読み書きができます。実際にはCUI以外からアクセスする手段が必要になるケースが多いため、使いたい手法に合わせて追加でサーバーを用意します。各サーバーはパラメータを渡しさえすれば、1コンテナでまとめて兼ねさせることもできるようですが、あとでメンテしづらそうなのでおすすめしません。

## master
| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | なし |
| 役割       | ボリューム等クラスタ全体に指示を出す |
| 要求台数       | 1台以上 |
| 負荷 | そこまでかからない |

名前通りで、これがないとクラスタ全体が使えなくなります。複数台マスターを用意する場合は、多数派の指示を発生させるために、奇数台用意する必要があります。

##  volume
| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | master |
| 役割       | ストレージに対してファイルを読み書きする |
| 要求台数       | 1台以上 |
| 負荷 | そこそこかかる |

複数のファイルを1ファイルのボリュームという概念にまとめて読み書きします。レプリケーションはボリューム単位で行われます。

## filer
https://github.com/chrislusf/seaweedfs/wiki/FUSE-Mount

| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | master,volume |
| 役割       | HTTPアクセスを可能にする+FUSEマウントの前提サーバーとなる |
| 要求台数       | 任意 |
| 負荷 | 結構重い |

HTTPAPI経由でファイルをPOST/PUT/GET/DELETE等できるようにしたい場合に利用します。一般的なファイルシステムとしてアクセスしたい場合、前提として必要になります。重いので本当に必要な場合のみ使うことが推奨されています。

## s3
https://github.com/chrislusf/seaweedfs/wiki/Amazon-S3-API

| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | master,volume,filer |
| 役割       | AWSのS3互換のバケットアクセスを可能にする |
| 要求台数       | 任意 |
| 負荷 | ? |

S3互換のAPIを使いたい場合に利用します。

## webdav
https://github.com/chrislusf/seaweedfs/wiki/WebDAV

| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | master,volume,filer |
| 役割       | webdavでのアクセスを可能にする |
| 要求台数       | 任意 |
| 負荷 | ? |

WebDavでマウントできるようにしたい場合は有効化します。認証機能は無いようなので、実際にはあまり使わないほうがいい気がします(個人的意見)


## mount
https://github.com/chrislusf/seaweedfs/wiki/FUSE-Mount

| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | master,volume,filer |
| 役割       | FUSEマウントを行う |
| 要求台数       | 任意 |
| 負荷 | ? |

LinuxまたはOSXで、FUSEマウントを行いたい場合に使います。Windowsで使いたい場合は、マウントしたLinux上のSambaを経由すると読み書きできます。(あまりにも大きなファイルだと途中でbrokenpipeが発生します)


## cronjob
https://github.com/chrislusf/seaweedfs/wiki/Erasure-Coding-for-warm-storage

| 項目 | 内容 |
|:-----------:|:------------:|
| 依存 | master |
| 役割       | 一定時間毎にレプリケーションを再実行および修復する |
| 要求台数       | 任意 |
| 負荷 | ? |

ボリュームサーバーが複数ある場合は、一定時間毎にレプリケーションを修復するコマンドを走らせることができるようです。(Dockerfileの中に実行されるコマンドが書かれています)

# SeaweedFS導入
このシステムで使われる用語やクイックスタートのガイドはあったのですが、実際デプロイする構成例があまりなかったので少し困りました。ここでは、マスターが3台、ボリュームが3台、ファイラーが3台、S3が3台、マウントが6台、cronjobが1台の構成を目指しました。

## DockerSwarmを使う方法(失敗)

### 起動方法
[README.md](https://github.com/chrislusf/seaweedfs/blob/master/docker/README.md)によると、[seaweed-compose.yml](https://github.com/chrislusf/seaweedfs/blob/master/docker/seaweedfs-compose.yml)をDLして `docker-compose up` すれば、最小限の構成が動くみたいです。

`Build and push a multiarch build` って欄にARMの言及があるので、[DockerHub](https://hub.docker.com/r/chrislusf/seaweedfs/tags?page=1&ordering=last_updated)に、ARMイメージが上がってるのかと思ったのですが、ありませんでした。ビルドは各自でやらないとダメみたいです。

### ARM用のビルドとRegistryへのPush
ビルドするコマンドはリポジトリに書かれた通りです。私は今後メンテするかわからないイメージをPublicに上げるのは気が引けるので、TreeScaleに上げました。(以前の記事でビルダーの設定はしてあるので省いてます)

```shell:イメージをお好みで作って、適当なところにPushする
git clone https://github.com/chrislusf/seaweedfs
cd seaweedfs/docker
docker buildx build --pull --push --platform linux/amd64,linux/arm64,linux/arm/v7 . -t repo.treescale.com/ユーザー名/seaweedfs:latest
```

### DockerSwarmの定義ファイルで動かそうとした(失敗)
様々な構成を試しましたが、どうあがいても、filerコンテナとmasterコンテナ間の通信がどうしてもうまくいかず、gRPCエラーを吐きます。ボリューム、マスター、ファイラーが1台ずつの例、もしくはdocker-compose内では動作しますが、マスター1、ファイラー1、ボリューム3をswarmで構成しようとすると、gRPCエラーを吐きます。下記は一応やってみようとしたメモを残します(なにか解決策わかる人は教えて下さい!)

<details><summary>DockerSwarmではできない理想(各replicaのIPアドレスが起動前にわからないため設定できない)</summary><div>

```shell:swarmで動いてほしかった(理想)
version: '3'

services:
  master:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # Masterサービス
    ports:
      - 9333:9333
      - 19333:19333
    command: "master -ip=master"
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 100M
      placement:
        max_replicas_per_node: 1
        constraints:
          - node.labels.seaweed == true

  volume:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # ボリュームサービス
    ports:
      - 8080:8080
      - 18080:18080
      - 9325:9325
    command: 'volume -mserver="master:9333" -port=8080  -metricsPort=9325'
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1000M
      placement:
        max_replicas_per_node: 1
        constraints:
          - node.labels.seaweed == true

  filer:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # ファイラーサービス
    ports:
      - 8888:8888
      - 18888:18888
      - 9326:9326
    command: 'filer -master="master:9333"  -metricsPort=9326'
    tty: true
    stdin_open: true
    depends_on:
      - master
      - volume
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1000M
      placement:
        max_replicas_per_node: 1
        constraints:
          - node.labels.seaweed == true
      
  cronjob:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # cronジョブ
    command: 'cronjob'
    environment:
      # Run re-replication every 2 minutes
      CRON_SCHEDULE: '*/2 * * * * *' # Default: '*/5 * * * * *'
      WEED_MASTER: master:9333 # Default: localhost:9333
    depends_on:
      - master
      - volume
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 300M
      placement:
        max_replicas_per_node: 1
        constraints:
          - node.labels.seaweed == true
      
  s3:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # S3サービス
    ports:
      - 8333:8333
      - 9327:9327
    command: 's3 -filer="filer:8888" -metricsPort=9327'
    depends_on:
      - master
      - volume
      - filer
    deploy:
      replicas: 3
      placement:
        max_replicas_per_node: 1
        constraints:
          - node.labels.seaweed == true
```
</div></details>


<details><summary>動かない最小構成Swarm(masterからvolumeに接続できずrpcエラー)</summary><div>

```shell:そもそも動かない最小構成(masterからvolumeの接続でrpcエラー)
version: '3'

services:
  master:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 9333:9333
      - 19333:19333
    command: "master -ip.bind='0.0.0.0' -ip=master"
  volume:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8080:8080
      - 18080:18080
    command: 'volume -ip.bind="0.0.0.0" -mserver="master:9333" -port=8080'
    depends_on:
      - master
  filer:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8888:8888
      - 18888:18888
    command: 'filer -ip.bind="0.0.0.0" -master="master:9333"'
    tty: true
    stdin_open: true
    depends_on:
      - master
      - volume
```

</div></details>

<details><summary>masterx3 volumex3 filerx3 s3x3 mountx3 cronx1 prometheusx1のSwarm定義ファイル</summary><div>

```shell:闇が深いがこうせざるをえない定義ファイル
version: '3'

services:
  master1:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 9333:9333 # HTTP endpoint (dashboard)
      - 19333:19333 # gRPC endpoint
    command: "master -ip=master1 -port=9333 -master.peers=master1:9333,master2:9333,master3:9333 -mdir=/data -defaultReplication=020 -volumeSizeLimitMB=2048"
    volumes:
      - /mnt/hdd2/seaweedfs/master1:/data
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M
      placement:
        constraints:
          - node.labels.seaweed-master1 == true
          
  master2:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 9334:9333 # HTTP endpoint (dashboard)
      - 19334:19333 # gRPC endpoint
    command: "master -ip=master2 -port=9333 -master.peers=master1:9333,master2:9333,master3:9333 -mdir=/data -defaultReplication=020 -volumeSizeLimitMB=2048"
    volumes:
      - /mnt/hdd2/seaweedfs/master2:/data
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M
      placement:
        constraints:
          - node.labels.seaweed-master2 == true
          
  master3:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 9335:9333 # HTTP endpoint (dashboard)
      - 19335:19333 # gRPC endpoint
    command: "master -ip=master3 -port=9333 -master.peers=master1:9333,master2:9333,master3:9333 -mdir=/data -defaultReplication=020 -volumeSizeLimitMB=2048"
    volumes:
      - /mnt/hdd2/seaweedfs/master3:/data
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 256M
      placement:
        constraints:
          - node.labels.seaweed-master3 == true

  volume:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8081:8080 # HTTP endpoint
      - 18080:18080 # gRPC endpoint
      - 9325:9325 # metrics endpoint
    command: 'volume -mserver="master1:9333,master2:9333,master3:9333" -fileSizeLimitMB=16384 -concurrentUploadLimitMB=16384 -max=100 -minFreeSpace=10 -port=8080 -metricsPort=9325 -index=leveldb -dir=./data -dataCenter=home -disk=hdd'
    depends_on:
      - master1
      - master2
      - master3
    volumes:
      - /mnt/hdd2/seaweedfs/volume:/data
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed == true

  filer1:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8888:8888 # HTTP endpoint
      - 18888:18888 # gRPC endpoint
      - 9326:9326 # metrics endpoint
    command: 'filer -master="master1:9333,master2:9333,master3:9333" -peers="filer1:8888,filer2:8888,filer3:8888" -metricsPort=9326'
    tty: true
    stdin_open: true
    depends_on:
      - master1
      - master2
      - master3
      - volume
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed1 == true
          
  filer2:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8889:8888 # HTTP endpoint
      - 18889:18888 # gRPC endpoint
      - 9327:9326 # metrics endpoint
    command: 'filer -master="master1:9333,master2:9333,master3:9333" -peers="filer1:8888,filer2:8888,filer3:8888" -metricsPort=9326'
    tty: true
    stdin_open: true
    depends_on:
      - master1
      - master2
      - master3
      - volume
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed2 == true
          
  filer3:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8890:8888 # HTTP endpoint
      - 18890:18888 # gRPC endpoint
      - 9328:9326 # metrics endpoint
    command: 'filer -master="master1:9333,master2:9333,master3:9333" -peers="filer1:8888,filer2:8888,filer3:8888" -metricsPort=9326'
    tty: true
    stdin_open: true
    depends_on:
      - master1
      - master2
      - master3
      - volume
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed3 == true
          
  s3:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8333:8333 # HTTP endpoint
      - 9350:9327 # metrics endpoint
    command: 's3 -filer="filer1:8888,filer2:8888,filer3:8888" -metricsPort=9327'
    depends_on:
      - master1
      - master2
      - master3
      - volume
      - filer1
      - filer2
      - filer3
    deploy:
      replicas: 3
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed == true
          
  mount:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: 'mount -filer="filer1:8888,filer2:8888,filer3:8888" -dirAutoCreate -dir=/mnt/seaweedfs -cacheCapacityMB=100  -concurrentWriters=64'
    depends_on:
      - master1
      - master2
      - master3
      - volume
      - filer1
      - filer2
      - filer3
    volumes:
      - /mnt/seaweedfs:/mnt/seaweedfs
    deploy:
      replicas: global
      resources:
        limits:
          memory: 256M
      placement:
        constraints:
          - node.labels.seaweed == true
      
  cronjob:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: 'cronjob'
    environment:
      # Run re-replication every 2 minutes
      CRON_SCHEDULE: '*/2 * * * * *' # Default: '*/5 * * * * *'
      WEED_MASTER: master:9333 # Default: localhost:9333
    depends_on:
      - master1
      - master2
      - master3
      - volume
      - filer1
      - filer2
      - filer3
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 300M
      placement:
        constraints:
          - node.labels.seaweed == true
          
  prometheus:
    image: prom/prometheus:latest
    ports:
      - 9000:9090
    volumes:
      - ./prometheus:/etc/prometheus
    command: --web.enable-lifecycle  --config.file=/etc/prometheus/prometheus.yml
    depends_on:
      - s3

```
</div></details>

<details><summary>masterx1 volumex3 filerx1 s3x3 cronx1 prometheusx1のSwarm定義ファイル</summary><div>

```shell:最終チャレンジ
version: '3.8'

services:
  master:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: "master -mdir=/data -volumeSizeLimitMB=2048"
    networks:
      - weed
    volumes:
      - /mnt/hdd2/master1:/data
    deploy:
      mode: replicated
      replicas: 1
      resources:
        limits:
          memory: 256M
      placement:
        constraints:
          - node.labels.seaweed-master1 == true

  volume:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: 'volume -mserver=master:9333 -fileSizeLimitMB=16384 -concurrentUploadLimitMB=16384 -max=100 -minFreeSpace=10 -dataCenter=home -rack=jet -metricsPort=9325 -dir=./data'
    depends_on:
      - master
    networks:
      - weed
    volumes:
      - /mnt/hdd2/volume:/data
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed1 == true
          
  volume2:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: 'volume -mserver=master:9333 -fileSizeLimitMB=16384 -concurrentUploadLimitMB=16384 -max=100 -minFreeSpace=10 -dataCenter=home -rack=pie -metricsPort=9325 -dir=./data'
    depends_on:
      - master
    networks:
      - weed
    volumes:
      - /mnt/hdd2/volume:/data
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed2 == true
          
  volume3:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: 'volume -mserver=master:9333 -fileSizeLimitMB=16384 -concurrentUploadLimitMB=16384 -max=100 -minFreeSpace=10 -dataCenter=home -rack=rock -metricsPort=9325 -dir=./data'
    depends_on:
      - master
    networks:
      - weed
    volumes:
      - /mnt/hdd2/volume:/data
    deploy:
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed3 == true

  filer:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - "8888:8888/tcp"
    command: 'filer -master=master:9333 -metricsPort=9326'
    depends_on:
      - master
      - volume
    networks:
      - weed
    deploy:
      mode: replicated
      replicas: 1
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed-master1 == true
          
  s3:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    ports:
      - 8333:8333 # HTTP endpoint
      - 9350:9327 # metrics endpoint
    command: 's3 -filer="filer:8888" -metricsPort=9327'
    depends_on:
      - master
      - volume
      - filer
    networks:
      - weed
    deploy:
      mode: replicated
      replicas: 3
      resources:
        limits:
          memory: 1024M
      placement:
        constraints:
          - node.labels.seaweed == true
      
  cronjob:
    image: repo.treescale.com/dosugamea/seaweedfs:latest # use a remote image
    command: 'cronjob'
    environment:
      # Run re-replication every 2 minutes
      CRON_SCHEDULE: '*/2 * * * * *' # Default: '*/5 * * * * *'
      WEED_MASTER: master:9333 # Default: localhost:9333
    depends_on:
      - master
      - volume
      - filer
    networks:
      - weed
    deploy:
      mode: replicated
      replicas: 1
      resources:
        limits:
          memory: 300M
      placement:
        constraints:
          - node.labels.seaweed-master1 == true
          
  prometheus:
    image: prom/prometheus:v2.28.1
    ports:
      - 9999:9090
    volumes:
      - /mnt/hdd2/prometheus:/etc/prometheus
    command: --web.enable-lifecycle  --config.file=/etc/prometheus/prometheus.yml
    depends_on:
      - s3
    networks:
      - weed
    deploy:
      mode: replicated
      replicas: 1
      resources:
        limits:
          memory: 256M
      placement:
        constraints:
          - node.labels.seaweed-master1 == true

networks:
  weed:
    attachable: true
    driver: overlay
```
</div></details>

## Dockerを使わない方法(直接サービスを設定)
Docker上でやるのとは違い、LAN内であれば(設定さえ済ませれば)rpcエラーを起こさず接続できました。

注意点として、`weed server` コマンドで、master/filer/volumeを丸ごと1サービスで定義することもできると書いてありましたが、volumeの保存先を指定できない(なぜかidxファイルの保存先しか指定できず、dat/vifファイルが/tmpに保存されていきメモリ不足になる)ので、サービスを分けましょう。

### 前提
192.168.0.11, 192.168.0.12, 192.168.0.13 の3台のノードがあり、それぞれでmasterをポート9333、filerをポート8888、volumeをポート7070、s3をポート8333を使用するように設定します。同一容量のHDDがそれぞれにつながっていて、パーティションを半分で区切って分けてあり、/mnt/hdd2をSeaweedFS専用にするものとします。mountもそれぞれのノードでさせ、/mnt/seaweedfsにマウントさせます。SeaweedFS専用のユーザーとグループを作成し実行させます。

### 実行ファイルをダウンロードして展開
[GithubのReleases](https://github.com/chrislusf/seaweedfs/releases)でビルド済みバイナリが配布されているので、それをDLする。Dockerコンテナとは違い、これはARMビルドも完備している。恐らくはどこに置いてもいいと思うが、今回は既存のチュートリアルに合わせて/usr/local/binに配置しました。

```shell:ARM64なら
wget https://github.com/chrislusf/seaweedfs/releases/download/2.59/linux_arm64.tar.gz
tar xzvf linux_arm64.tar.gz
sudo mv weed /usr/local/bin
```

```shell:ARMv7なら
wget https://github.com/chrislusf/seaweedfs/releases/download/2.59/linux_arm.tar.gz
tar xzvf linux_arm.tar.gz
sudo mv weed /usr/local/bin
```

### 方法1 サービスをそれぞれ分ける場合
1つ1つ書くと ものすごく長くなったので折りたたみました。

<details><summary>ユーザーとフォルダの作成 (クリックで展開)</summary><div>

```shell:実行ユーザーを作成する
# 実行グループを作成
sudo groupadd seaweed
# seaweedというユーザーをhome無しでプライマリグループをseaweedとして作成
sudo useradd seaweed -M -g seaweed
```

```shell:必要なフォルダを作成する
# Masterサービスで使用
sudo mkdir /mnt/hdd2/master
# Volumeサービスで使用
sudo mkdir /mnt/hdd2/volume
# S3サービスで使用
sudo mkdir /mnt/hdd2/buckets
# Filerサービスで使用
sudo mkdir /mnt/hdd2/filerldb2
# Mountサービスで使用
sudo mkdir /mnt/seaweedfs
```

```shell:フォルダの所有者を変更する
# HDD丸ごとseaweedユーザーのものとする
sudo chown seaweed:seaweed /mnt/hdd2 -R
```

</div></details>

<details><summary>Masterサービス(クリックで展開)</summary><div>

```shell:/etc/systemd/system/seaweed-master.service
[Unit]
Description=SeaweedFS Master
After=network.target

[Service]
Type=simple
User=seaweed
Group=seaweed

ExecStart=/usr/local/bin/weed master -mdir="/mnt/hdd2/master" -ip=192.168.0.11 -peers="192.168.0.11:9333,192.168.0.12:9333,192.168.0.13:9333" -volumeSizeLimitMB=2048 -defaultReplication=020 -port=9333 
WorkingDirectory=/usr/local/bin/
SyslogIdentifier=seaweedfs-master

[Install]
WantedBy=multi-user.target
```
</div></details>

<details><summary>Volumeサービス(クリックで展開)</summary><div>

```shell:/etc/systemd/system/seaweed-volume.service
[Unit]
Description=SeaweedFS Volume
After=network.target

[Service]
Type=simple
User=seaweed
Group=seaweed

ExecStart=/usr/local/bin/weed volume -ip=192.168.0.11 -mserver="192.168.0.11:9333,192.168.0.12:9333,192.168.0.13:9333"  -fileSizeLimitMB=16384 -concurrentUploadLimitMB=16384 -max=100 -minFreeSpace=10 -port=7070 -metricsPort=9325 -index=leveldb -dir="/mnt/hdd2/volume" -dataCenter=home -rack=rock -disk=hdd -publicUrl=192.168.0.11
WorkingDirectory=/usr/local/bin/
SyslogIdentifier=seaweedfs-volume

[Install]
WantedBy=multi-user.target
```
</div></details>

<details><summary>Filerサービス(クリックで展開)</summary><div>

```shell:/etc/systemd/system/seaweed-filer.service
[Unit]
Description=SeaweedFS Filer
After=network.target

[Service]
Type=simple
User=seaweed
Group=seaweed

ExecStart=/usr/local/bin/weed filer -ip=192.168.0.11 -master="192.168.0.11:9333,192.168.0.12:9333,192.168.0.13:9333" -peers="192.168.0.11:8888,192.168.0.12:8888,192.168.0.13:8888" -port=8888 -metricsPort=9326
WorkingDirectory=/usr/local/bin/
SyslogIdentifier=seaweedfs-filer

[Install]
WantedBy=multi-user.target
```
</div></details>

<details><summary>S3サービス(クリックで展開)</summary><div>

```shell:/etc/systemd/system/seaweed-s3.service
[Unit]
Description=SeaweedFS S3
After=network.target

[Service]
Type=simple
User=seaweed
Group=seaweed

ExecStart=/usr/local/bin/weed s3 -port=8333 -filer="192.168.0.11:8888,192.168.0.12:8888,192.168.0.13:8888" -config=s3.json -metricsPort=9327
WorkingDirectory=/usr/local/bin/
SyslogIdentifier=seaweedfs-s3

[Install]
WantedBy=multi-user.target
```
</div></details>

<details><summary>Mountサービス(クリックで展開)</summary><div>

```shell:/etc/systemd/system/seaweed-mount.service
[Unit]
Description=SeaweedFS Mount
After=network.target

[Service]
Type=simple
User=root
Group=root

ExecStart=/usr/local/bin/weed mount -filer="192.168.0.11:8888,192.168.0.12:8888,192.168.0.13:8888" -dir="/mnt/seaweedfs"
WorkingDirectory=/usr/local/bin/
SyslogIdentifier=seaweedfs-mount

[Install]
WantedBy=multi-user.target
```
</div></details>

<details><summary>Filerの設定ファイル(クリックで展開)</summary><div>

```shell:/etc/seaweedfs/filer.toml
# A sample TOML config file for SeaweedFS filer store
# Used with "weed filer" or "weed server -filer"
# Put this file to one of the location, with descending priority
#    ./filer.toml
#    $HOME/.seaweedfs/filer.toml
#    /etc/seaweedfs/filer.toml

####################################################
# Customizable filer server options
####################################################
[filer.options]
# with http DELETE, by default the filer would check whether a folder is empty.
# recursive_delete will delete all sub folders and files, similar to "rm -Rf"
recursive_delete = false
# directories under this folder will be automatically creating a separate bucket
buckets_folder = "/mnt/hdd2/buckets"

####################################################
# The following are filer store options
####################################################

[leveldb2]
# local on disk, mostly for simple single-machine setup, fairly scalable
# faster than previous leveldb, recommended.
enabled = true
# ファイラーメタデータの保存先(各自で変えてください)
dir = "/mnt/hdd2/filerldb2"                    # directory to store level db files

[leveldb3]
# similar to leveldb2.
# each bucket has its own meta store.
enabled = false
dir = "./filerldb3"                    # directory to store level db files

[rocksdb]
# local on disk, similar to leveldb
# since it is using a C wrapper, you need to install rocksdb and build it by yourself
enabled = false
dir = "./filerrdb"                    # directory to store rocksdb files

[sqlite]
# local on disk, similar to leveldb
enabled = false
dbFile = "./filer.db"                # sqlite db file

[mysql]  # or memsql, tidb
# CREATE TABLE IF NOT EXISTS filemeta (
#   dirhash     BIGINT               COMMENT 'first 64 bits of MD5 hash value of directory field',
#   name        VARCHAR(1000) BINARY COMMENT 'directory or file name',
#   directory   TEXT                 COMMENT 'full path to parent directory',
#   meta        LONGBLOB,
#   PRIMARY KEY (dirhash, name)
# ) DEFAULT CHARSET=utf8;

enabled = false
hostname = "localhost"
port = 3306
username = "root"
password = ""
database = ""              # create or use an existing database
connection_max_idle = 2
connection_max_open = 100
connection_max_lifetime_seconds = 0
interpolateParams = false
# if insert/upsert failing, you can disable upsert or update query syntax to match your RDBMS syntax:
enableUpsert = true
upsertQuery = """INSERT INTO ` + "`%s`" + ` (dirhash,name,directory,meta) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE meta = VALUES(meta)"""

[mysql2]  # or memsql, tidb
enabled = false
createTable = """
  CREATE TABLE IF NOT EXISTS ` + "`%s`" + ` (
    dirhash BIGINT,
    name VARCHAR(1000) BINARY,
    directory TEXT,
    meta LONGBLOB,
    PRIMARY KEY (dirhash, name)
  ) DEFAULT CHARSET=utf8;
"""
hostname = "localhost"
port = 3306
username = "root"
password = ""
database = ""              # create or use an existing database
connection_max_idle = 2
connection_max_open = 100
connection_max_lifetime_seconds = 0
interpolateParams = false
# if insert/upsert failing, you can disable upsert or update query syntax to match your RDBMS syntax:
enableUpsert = true
upsertQuery = """INSERT INTO ` + "`%s`" + ` (dirhash,name,directory,meta) VALUES(?,?,?,?) ON DUPLICATE KEY UPDATE meta = VALUES(meta)"""

[postgres] # or cockroachdb, YugabyteDB
# CREATE TABLE IF NOT EXISTS filemeta (
#   dirhash     BIGINT,
#   name        VARCHAR(65535),
#   directory   VARCHAR(65535),
#   meta        bytea,
#   PRIMARY KEY (dirhash, name)
# );
enabled = false
hostname = "localhost"
port = 5432
username = "postgres"
password = ""
database = "postgres"          # create or use an existing database
schema = ""
sslmode = "disable"
connection_max_idle = 100
connection_max_open = 100
connection_max_lifetime_seconds = 0
# if insert/upsert failing, you can disable upsert or update query syntax to match your RDBMS syntax:
enableUpsert = true
upsertQuery = """INSERT INTO "%[1]s" (dirhash,name,directory,meta) VALUES($1,$2,$3,$4) ON CONFLICT (dirhash,name) DO UPDATE SET meta = EXCLUDED.meta WHERE "%[1]s".meta != EXCLUDED.meta"""

[postgres2]
enabled = false
createTable = """
  CREATE TABLE IF NOT EXISTS "%s" (
    dirhash   BIGINT,
    name      VARCHAR(65535),
    directory VARCHAR(65535),
    meta      bytea,
    PRIMARY KEY (dirhash, name)
  );
"""
hostname = "localhost"
port = 5432
username = "postgres"
password = ""
database = "postgres"          # create or use an existing database
schema = ""
sslmode = "disable"
connection_max_idle = 100
connection_max_open = 100
connection_max_lifetime_seconds = 0
# if insert/upsert failing, you can disable upsert or update query syntax to match your RDBMS syntax:
enableUpsert = true
upsertQuery = """INSERT INTO "%[1]s" (dirhash,name,directory,meta) VALUES($1,$2,$3,$4) ON CONFLICT (dirhash,name) DO UPDATE SET meta = EXCLUDED.meta WHERE "%[1]s".meta != EXCLUDED.meta"""

[cassandra]
# CREATE TABLE filemeta (
#    directory varchar,
#    name varchar,
#    meta blob,
#    PRIMARY KEY (directory, name)
# ) WITH CLUSTERING ORDER BY (name ASC);
enabled = false
keyspace = "seaweedfs"
hosts = [
    "localhost:9042",
]
username = ""
password = ""
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
superLargeDirectories = []
# Name of the datacenter local to this filer, used as host selection fallback.
localDC = ""

[hbase]
enabled = false
zkquorum = ""
table = "seaweedfs"

[redis2]
enabled = false
address = "localhost:6379"
password = ""
database = 0
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
superLargeDirectories = []

[redis_cluster2]
enabled = false
addresses = [
    "localhost:30001",
    "localhost:30002",
    "localhost:30003",
    "localhost:30004",
    "localhost:30005",
    "localhost:30006",
]
password = ""
# allows reads from slave servers or the master, but all writes still go to the master
readOnly = false
# automatically use the closest Redis server for reads
routeByLatency = false
# This changes the data layout. Only add new directories. Removing/Updating will cause data loss.
superLargeDirectories = []

[etcd]
enabled = false
servers = "localhost:2379"
timeout = "3s"

[mongodb]
enabled = false
uri = "mongodb://localhost:27017"
option_pool_size = 0
database = "seaweedfs"

[elastic7]
enabled = false
servers = [
    "http://localhost1:9200",
    "http://localhost2:9200",
    "http://localhost3:9200",
]
username = ""
password = ""
sniff_enabled = false
healthcheck_enabled = false
# increase the value is recommend, be sure the value in Elastic is greater or equal here
index.max_result_window = 10000



##########################
##########################
# To add path-specific filer store:
#
# 1. Add a name following the store type separated by a dot ".". E.g., cassandra.tmp
# 2. Add a location configuraiton. E.g., location = "/tmp/"
# 3. Copy and customize all other configurations.
#     Make sure they are not the same if using the same store type!
# 4. Set enabled to true
#
# The following is just using redis as an example
##########################
[redis2.tmp]
enabled = false
location = "/tmp/"
address = "localhost:6379"
password = ""
database = 1
```
</div></details>

<details><summary>S3サービスの設定ファイル(クリックで展開)</summary><div>

```shell:/etc/systemd/system/s3.json
{
  "identities": [
    {
      "name": "anonymous",
      "actions": [
        "Read"
      ]
    },
    {
      "name": "admin_user",
      "credentials": [
        {
          "accessKey": "some_access_key1",
          "secretKey": "some_secret_key1"
        }
      ],
      "actions": [
        "Admin",
        "Read",
        "List",
        "Tagging",
        "Write"
      ]
    },
    {
      "name": "normal_user",
      "credentials": [
        {
          "accessKey": "some_access_key3",
          "secretKey": "some_secret_key3"
        }
      ],
      "actions": [
        "Read",
        "List",
        "Tagging",
        "Write"
      ]
    }
  ]
}
```
</div></details>

<details><summary>サービスの有効化/起動(クリックで展開)</summary><div>

```shell:サービスの有効化コマンド
sudo systemctl daemon-reload
sudo systemctl enable seaweed-master
sudo systemctl enable seaweed-volume
sudo systemctl enable seaweed-filer
sudo systemctl enable seaweed-s3
sudo systemctl enable seaweed-mount
sudo systemctl start seaweed-master
sudo systemctl start seaweed-volume
sudo systemctl start seaweed-filer
sudo systemctl start seaweed-s3
sudo systemctl start seaweed-mount
```
</div></details>

### 方法2 サービスを分けない場合(tmpに保存されます)
```shell:丸ごと1サービスとして動かしたい場合(実際にはvolumeのうち、idxファイル以外が/tmpに保存されてしまう)
[Unit]
Description=SeaweedFS
After=network.target

[Service]
Type=simple
User=root
Group=root

ExecStart=/usr/local/bin/weed server -dataCenter=home -rack=rock -master -master.dir=/mnt/hdd2/master -master.peers="192.168.0.11:9333,192.168.0.12:9333,192.168.0.13:9333" -master.volumeSizeLimitMB=2048 -master.defaultReplication=020 -master.port=9333 -volume -volume.dir.idx=/mnt/hdd2/volume -volume.port=8080 -volume.max=100 -volume.minFreeSpace=10 -volume.fileSizeLimitMB=16384 -volume.concurrentUploadLimitMB=16384 -volume.disk=hdd -volume.publicUrl=192.168.0.11 -filer -filer.peers="192.168.0.11:8888,192.168.0.12:8888,192.168.0.13:8888" -filer.port=8888 -s3 -s3.port=8333 -metricsPort=9326
WorkingDirectory=/usr/local/bin/
SyslogIdentifier=seaweedfs-master

[Install]
WantedBy=multi-user.target
```

## 完走した感想
なんとかDockerSwarmで動かせないか努力したんですが結局どうにもならず、動かせるまで、2週間ぐらいかかりました... CephやGlusterFSと比べると情報が全くなかったので今後なにか起こったときが怖いです。導入は簡単だったかというと、簡単じゃなかったし、これもこれでうーんという感じでした。(そもそも今からDockerSwarmを使おうとしたのがよろしくなかった感じがします)

## 参考
[chrislusf/seaweedfs - Github](https://github.com/chrislusf/seaweedfs)
[Setup SeaweedFS Distributed Object Storage Cluster on Ubuntu 20.04](https://computingforgeeks.com/setup-seaweedfs-distributed-storage-cluster-ubuntu/)
weed help <サービス名> で出てくるヘルプ