본문 바로가기

기타

IPFS Tutorial: IPFS Cluster 로 Private IPFS Network 구성하기

기본 환경

Host
  VMware® Workstation 14 Player 14.0.0 build-6661328
OS

  Ubuntu 16.04.6 LTS
Application 
  - Docker : 19.03.5, build 633a0ea838
  - docker-compose : 1.11.2, build dfed245
  - git : 2.7.4

  - go: go1.14 lunux/amd64

  - node: v8.17.0

  - npm: 6.13.4

 

IPFS Network

IPFS 네트워크에는 공용(public) 및 개인용(Private)의 두 가지 유형이 있습니다. 공용 IPFS 네트워크의 모든 파일은 모든 사람이 액세스 할 수 있습니다. 대부분의 비즈니스 응용 프로그램, 특히 엔터프라이즈 솔루션은 데이터에 대한 완전한 제어가 필요하기 때문에 네트워크를 공개적으로 사용할 수 있도록하는 것은 옵션이 아닙니다. IPFS 개인 정보 보호 기능이 특정 엔터티의 네트워크를 닫는 데 도움이 될 수 있습니다.

이 IPFS 튜토리얼에서는 도커(Docker) 기반으로 Private IPFS 네트워크를 만들고 Private IPFS 네트워크 위에 데이터 복제를 위한 IPFS- cluster 를 설정하는 프로세스를 살펴봅니다.

 

IPFS: 분산 파일 시스템에서 하이퍼 미디어를 저장하고 공유하는 콘텐츠 주소 지정이 가능한 P2P 방식을 생성하도록 설계된 프로토콜 및 네트워크입니다.

 

사설 IPFS: IPFS가 공유 비밀 키(secret key)를 가진 다른 피어에만 연결할 수 있도록 합니다. IPFS 사설 네트워크를 사용하면  각 노드가 연결할 다른 노드를 지정합니다. 해당 네트워크의 노드는 해당 네트워크 외부 노드의 통신에 응답하지 않습니다.

 

IPFS-Cluster: IPFS-Cluster는 독립적인 애플리케이션이며 IPFS 데몬 클러스터에서 핀을 할당, 복제 및 추적하는 CLI 클라이언트입니다. IPFS-Cluster 는 리더 기반 합의 알고리즘 Raft 를 사용하여 핀셋의 스토리지를 조정하고 참여 노드에 데이터 세트를 배포합니다.

 

사설 네트워크는 핵심 IPFS 기능 내에서 구현되는 기본 기능 이고 IPFS-Cluster 는 별도의 앱 이라는 점은 주목할 가치가 있습니다. IPFS 및 IPFS-Cluster 애플리케이션은 서로 다른 패키지로 설치되고 별도의 프로세스로 실행되며 API 엔드 포인트 및 포트는 물론 서로 다른 피어 ID를 갖습니다. IPFS-Cluster 데몬은 IPFS 데몬에 의존하며 나중에 시작해야합니다.

 

기본적으로 IPFS 및 IPFS-Cluster는 다음 포트를 사용합니다.

IPFS

PORT 설명
4001 다른 노드와 통신
5001 API 서버
8080 게이트웨이 서버

IPFS-CLUSTER

PORT 설명
9094 HTTP API 엔드 포인트
9095 IPFS 프록시 엔드 포인트
9096 클러스터 노드 간 통신에 사용되는 클러스터들

두 개의 VM이 있다고 가정합니다.

 

도커의 볼륨을 다음과 같이 사용할 것입니다. 미리 디렉터리를 생성 후 폴더 권한 부여를 해주세요.

/data/ipfs: ipfs 에서 필요한 데이터 볼륨

/data/ipfs-staging: ipfs 업/다운로드할 파일 경로

/data/ipfs-cluster: ipfs-cluster 에서 필요한 데이터 볼륨

1단계: IPFS Docker 로 설치하기

최신 버전의 go-ipfs 도커 이미지로 설치합니다.

 

docker run \
 -d \
--restart always \
--name ipfs-node \
-v /data/ipfs:/data/ipfs \
-v /data/ipfs-staging:/staging \
-p 8080:8080 \
-p 4001:4001 \
-p 5001:5001 \
--network="ipfs" \
ipfs/go-ipfs:latest

 

NOTE: permission denide 에러가 난다면 도커 볼륨에 권한이 없어서 나는 오류일 것입니다. 폴더에 권한 부여를 해주세요. 예시) `sudo mkdir /data/ipfs && sudo chown -R hyper:hyper /data/ipfs`

 

docker container logs ipfs-node -f

 

다음과 같은 로그가 출력 됬다면 성공적으로 실행된 것입니다.

 

 

다음을 두 노드 에 실행시켜주세요.

2단계: Private 네트워크 생성 준비

모든 노트에 IPFS 를 설치했으면 다음 명령어를 실행하여 secret key 를 생성하여 네트워크 피어에게 이 비밀키를 공유하는 피어와 통신할 수 있도록 하겠습니다.

이 명령은 하나의 노드에서만 실행할 것입니다. 하나의 노드에서 생성한 다음 나머지 노드에 복사할 것입니다.

 

od  -vN 32 -An -tx1 /dev/urandom | tr -d ' \n' && echo ""

 

 

출력한 값을 텍스트 에디터에 복사해주세요.

다음 명령어로 각각 클러스터 노드의 ID와 Private key 를 생성해 줄 것입니다.

 

# Key 생성 
go get github.com/whyrusleeping/ipfs-key
ipfs-key | base64 | tr -d ' \n' && echo ""

 

NOTE: go get 이 정상적으로 동작하지 않는다면 go 버전(1.14)을 잘 확인해주세요. 

 

 

다음과 같이 ID와 Private key 를 한 세트로 노드 갯수만큼(노드 2대를 사용한다면 2번) 실행시킨 다음 텍스트 에디터에 복사해주세요.

네트워크 생성에 필요한 값을 모두 출력했으니, 적용을 해보겠습니다.

/data/ipfs-cluster 에서 service.json 이라는 제목의 파일을 하나 생성해주고 내용은 다음과 같게 노드별로 채워넣어 주세요.

 

cd /data/ipfs-cluster
vi service.json

 

다음 양식에 맞춰서 노드별로 생성해주세요.

 

{
  "cluster": {
    "id": 노드별 ID,
    "peername": 노드 이름,
    "private_key": 노드 프라이빗 키,
    "secret": 노드 간 통신을 위한 비밀 키,
    "leave_on_shutdown": false,
    "listen_multiaddress": "/ip4/0.0.0.0/tcp/9096",
    "state_sync_interval": "10m0s",
    "ipfs_sync_interval": "2m10s",
    "replication_factor_min": -1,
    "replication_factor_max": -1,
    "monitor_ping_interval": "15s",
    "peer_watch_interval": "5s",
    "disable_repinning": false
  },
  "consensus": {
    "raft": {
      "init_peerset": [
        노드 ID 들(자신포함),
      ],
      "wait_for_leader_timeout": "2m",
      "network_timeout": "20s",
      "commit_retries": 1,
      "commit_retry_delay": "200ms",
      "backups_rotate": 6,
      "heartbeat_timeout": "5s",
      "election_timeout": "5s",
      "commit_timeout": "500ms",
      "max_append_entries": 64,
      "trailing_logs": 10240,
      "snapshot_interval": "2m0s",
      "snapshot_threshold": 8192,
      "leader_lease_timeout": "1s"
    }
  },
  "api": {
    "ipfsproxy": {
      "node_multiaddress": "/dns4/ipfs-node/tcp/5001",
      "listen_multiaddress": "/ip4/0.0.0.0/tcp/9095",
      "read_timeout": "0s",
      "read_header_timeout": "5s",
      "write_timeout": "0s",
      "idle_timeout": "1m0s"
    },
    "restapi": {
      "http_listen_multiaddress": "/ip4/0.0.0.0/tcp/9094",
      "read_timeout": "0s",
      "read_header_timeout": "5s",
      "write_timeout": "0s",
      "idle_timeout": "2m0s",
      "basic_auth_credentials": null,
      "headers": {},
      "cors_allowed_origins": ["*"],
      "cors_allowed_methods": ["GET"],
      "cors_allowed_headers": [],
      "cors_exposed_headers": ["Content-Type", "X-Stream-Output", "X-Chunked-Output", "X-Content-Length"],
      "cors_allow_credentials": true,
      "cors_max_age": "0s"
    }
  },
  "ipfs_connector": {
    "ipfshttp": {
      "node_multiaddress": "/dns4/ipfs-node/tcp/5001",
      "connect_swarms_delay": "30s",
      "pin_method": "refs",
      "ipfs_request_timeout": "5m0s",
      "pin_timeout": "24h0m0s",
      "unpin_timeout": "3h0m0s"
    }
  },
  "pin_tracker": {
    "maptracker": { "max_pin_queue_size": 50000, "concurrent_pins": 10 },
    "stateless": { "max_pin_queue_size": 50000, "concurrent_pins": 10 }
  },
  "monitor": {
    "monbasic": { "check_interval": "15s" },
    "pubsubmon": { "check_interval": "15s" }
  },
  "informer": {
    "disk": { "metric_ttl": "30s", "metric_type": "freespace" },
    "numpin": { "metric_ttl": "10s" }
  }
}

 

예시

 

{
  "cluster": {
    "id": "QmPjp4tUXFaiQw5PNj5Cwfew1VufsytrzxHDDQCMgxeJt46BU",
    "peername": "NODE01",
    "private_key": "CAASqAkwggSkAgEAAoIBAQC6MsmUewdIgqs8M6sPwLgQnw3hDvn3ewecVoba/qB5qADUPEDL27/OvvNSyr3gyMchKN++tkfh8J+XOyzAf4wP99Lqy+Pk9ZpRSuqpmoId1RoPaypbFl4VVBnPNPDN17M4CRAJ5n10qGVLMxeAtaiOzl7ZI+I5Hd7i4lhcBgUwzWH/HLas1ClZwrQmqG8QyIAz1y8EBUd/oDvp5K8X0jk8DygOOXvxI/96rWvuSn++CoG0TqTy89i8BbneoJoUFB6Gic7IgLQnESZeOkPpncqjkYGewLNC7cSMQ0rFj6KJubIRQJSg6KbBabFDxXvIbRkEh5H62DjlctJdg/PSqu/NVXCJbAgMBAAECggEAUKGFoYAFJ1jJBjV2MBUlFOVVOC86BmgNF1naMdT9JRTCvSSTxlpjUS2jQzlDnsAyTluT20/7uF1M7cIFDIhaAP7ID8rIGDs1Z1PE1QhAORLZz58Z4gP1Sb70GfD+FQUWfUyBr6VfscVOmtwHsdHU4iygkFpL58M3GzO+p81/J+TlnExAUbSoFzO/BvH59E3rPHSW8K0pL5W0cHYQFesGoCE+puQ5nd8w22FMYBZbWvTZPgNibdnCgw9GHqeN2iUANkiATcklfHFXbkuXkwW2dqCOlLGTEtnCLHdS/Mn+8Do90LC5YMlV3j4fAi/rzD5n03Tb4P2PLXEmcUci2+ZrYQKBgQDQHxINmLDaIC+cMZRnTjG8bywfrG7+incufycjyenEfIb9g/eWphjUx+9uhLxD9++Vlddj/g0pkh3IGQqaTpgOZBB8tBsQMASVJ7VwbjKP7TACDpqPrLD/UVaksrywD3iRcuToBgweHtXREdgmS7MxJBaKyL0VStFkLhz4LVcAFwKBgQDlCJpSdbnHcbaOBFxxrSjMJ1jZHi5rBXuniBIehoWILc7p4Ilpz2rKge3hkfIzj2bFeMSDMwrZkhpoSvDBmjEPVkhLe2dT+X54FkECo+IOyIRRyPHIkmGD1O3cNlHf7E3tmchj3UgSbL/IF7Eps20Nwsepwy9TObUuZQufOo32XQKBgQCilTMoNfkqXWx0C3NQhJBsETkaAT2wUYErbJ+p4KljGpjl4TsAj/7j8tDDuKPOQRkD9UuPTL8Bk6j6UT2sL+uvilIq6RQfQDPERYIDM8MCFpujb78ksBaRTfxuq0amD1/z2Bqke1zqBtKoAdWmjQqOQA/wGUNFJ+6N4Uw2QE0vvwKBgQCCVe30RRhu3lBD60lsS10vKKkQDXPe5WTkBrRA+M6em2rnfybTtvyPt3bW6gYJv259q+cwvmhLPjCW7yapFgUbND+57MT1bcLBtfBS/04SmZtrK04klOC3dAHUUnkvU7tZHi2CXxe3nHla9diU4Y2KyjBzdCbHVsy7VTdS6rCE5QKBgD/AQlhq2xDDi++w40BJ10kyVfjMpPX67poOfCQe0h639RXTDX4U4AKJRbfCpJ4CZOYpNrUEn8ykV6ZgXaXAwk/3hkIIUiKSuTsbmvU9Z+/urhk3gBVEOY8ZO2phxzYkkg2oStSxjP7oJNxbmQ+2tL7m/JvZMTtdT9VegUeB1lmT",
    "secret": "636c1291342c641ebe5d6a3c3e6cc064f30023f0146af04877fd3443a4d85dea9952bcae",
    "leave_on_shutdown": false,
    "listen_multiaddress": "/ip4/0.0.0.0/tcp/9096",
    "state_sync_interval": "10m0s",
    "ipfs_sync_interval": "2m10s",
    "replication_factor_min": -1,
    "replication_factor_max": -1,
    "monitor_ping_interval": "15s",
    "peer_watch_interval": "5s",
    "disable_repinning": false
  },
  "consensus": {
    "raft": {
      "init_peerset": [
        "QmPjp4tUXFaiQw5PNj5Cwfew1VufsytrzxHDDQCMgxeJt46BU",
        "QmUUhF6BaKhrhq2RMt6xdfstDsLp2Zy67Pto3Hq1HeyVWE8fe",
      ],
      "wait_for_leader_timeout": "2m",
      "network_timeout": "20s",
      "commit_retries": 1,
      "commit_retry_delay": "200ms",
      "backups_rotate": 6,
      "heartbeat_timeout": "5s",
      "election_timeout": "5s",
      "commit_timeout": "500ms",
      "max_append_entries": 64,
      "trailing_logs": 10240,
      "snapshot_interval": "2m0s",
      "snapshot_threshold": 8192,
      "leader_lease_timeout": "1s"
    }
  },
  "api": {
    "ipfsproxy": {
      "node_multiaddress": "/dns4/ipfs-node/tcp/5001",
      "listen_multiaddress": "/ip4/0.0.0.0/tcp/9095",
      "read_timeout": "0s",
      "read_header_timeout": "5s",
      "write_timeout": "0s",
      "idle_timeout": "1m0s"
    },
    "restapi": {
      "http_listen_multiaddress": "/ip4/0.0.0.0/tcp/9094",
      "read_timeout": "0s",
      "read_header_timeout": "5s",
      "write_timeout": "0s",
      "idle_timeout": "2m0s",
      "basic_auth_credentials": null,
      "headers": {},
      "cors_allowed_origins": ["*"],
      "cors_allowed_methods": ["GET"],
      "cors_allowed_headers": [],
      "cors_exposed_headers": ["Content-Type", "X-Stream-Output", "X-Chunked-Output", "X-Content-Length"],
      "cors_allow_credentials": true,
      "cors_max_age": "0s"
    }
  },
  "ipfs_connector": {
    "ipfshttp": {
      "node_multiaddress": "/dns4/ipfs-node/tcp/5001",
      "connect_swarms_delay": "30s",
      "pin_method": "refs",
      "ipfs_request_timeout": "5m0s",
      "pin_timeout": "24h0m0s",
      "unpin_timeout": "3h0m0s"
    }
  },
  "pin_tracker": {
    "maptracker": { "max_pin_queue_size": 50000, "concurrent_pins": 10 },
    "stateless": { "max_pin_queue_size": 50000, "concurrent_pins": 10 }
  },
  "monitor": {
    "monbasic": { "check_interval": "15s" },
    "pubsubmon": { "check_interval": "15s" }
  },
  "informer": {
    "disk": { "metric_ttl": "30s", "metric_type": "freespace" },
    "numpin": { "metric_ttl": "10s" }
  }
}

 

또 작성할 파일이 있습니다. IP 로 피어를 찾기 위한 peerstore 파일을 방금 생성했던 service.json 의 위치에 peerstore 라는 제목으로 생성해주세요.

 

/ip4/노드1에 해당하는 IP/tcp/9096/ipfs/노드1에 해당하는 ID
/ip4/노드2에 해당하는 IP/tcp/9096/ipfs/노드2에 해당하는 ID

 

예시

 

# 노드1 IP 220.0.0.1
# 노드2 IP 220.0.0.2

/ip4/220.0.0.1/tcp/9096/ipfs/QmPjp4tUXFaiQw5PNj5Cwfew1VufsytrzxHDDQCMgxeJt46BU
/ip4/220.0.0.2/tcp/9096/ipfs/QmUUhF6BaKhrhq2RMt6xdfstDsLp2Zy67Pto3Hq1HeyVWE8fe

 

이제 준비는 끝났습니다. ipfs-cluster를 실행 시켜보겠습니다.

 

3단계: ipfs-cluster 실행하기

ipfs-cluster 최신버전의 도커 이미지로 실행시켜줍니다.

 

docker run \
-d \
--restart=always \
--name=ipfs-cluster \
-v /data/ipfs-cluster:/data/ipfs-cluster \
-v /data/ipfs:/data/ipfs \
-p "9094:9094" \
-p "9096:9096" \
--network="ipfs" \
ipfs/ipfs-cluster:latest

 

실행 후 `docker container logs ipfs-cluster -f` 명령러로 로그를 확인해보세요. 다음과 같은 로그가 뜨면 성공적으로 실행 된 것입니다. 

 

 

노드 간에 연결이 잘됬는지 확인해보시려면 `docker container exec ipfs-cluster ipfs-cluster-ctl peers ls` 명령어로 확인해보세요.

 

참고

cluster.ipfs.io/

 

IPFS Cluster

Pinset orchestration for IPFS

cluster.ipfs.io

labs.eleks.com/2019/03/ipfs-network-data-replication.html

 

Building Private IPFS Network with IPFS-Cluster for Data Replication

In this IPFS tutorial, we will go through the process of creating a private IPFS network and setting up an IPFS-Cluster for data replication.

labs.eleks.com