블록체인

400라인의 go코드로 구현한 하이퍼레저 패브릭 [3]- 도커로 디플로이

[하마] 이승현 (wowlsh93@gmail.com) 2019. 2. 14. 16:19

예제로 이용된 재료는 이전에 만든  400라인의 go코드로 구현한 하이퍼레저 패브릭 [2]- 블록전파/Gossip 프로토콜 소스(앞으로는 gossip분산 서비스로 지칭)를 사용였는데 간단한 분산 네트워킹 예제이므로 도커/쿠버네이트 공부를 위한 좋은 재료가 될 것 입니다. 이 글은 그 예제의 연속성 상에서 기획된 글이며, 도커/쿠버네이트 내용을 한번 정도 읽어 봤다는 혹은 아래 참고 링크를 공부하면서 진행한다는 가정하에 실습을 위해 정리 한 포스트임을 알려드립니다. 각각의 기술에 대해 구체적으로 알고 싶은 분은 아래 레퍼런스를 참고 하시거나, 추가 구글링을 통해 확인 하십시요.

도커

아래 처럼 Dockerfile 을 만듭니다.

# Start from a Debian image with the latest version of Go installed
# and a workspace (GOPATH) configured at /go.
FROM golang

# Copy the local package files to the container's workspace.
ADD . /go/src/github.com/wowlsh93/hyperledger-fabric-400-gossip

# Build the gossip command inside the container.
# (You may fetch or manage dependencies here,
# either manually or with a tool like "godep".)
RUN go install github.com/wowlsh93/hyperledger-fabric-400-gossip/gossip

위의 도커파일을 가지고 gossip이라는 이름의 도커이미지를 만듭니다. 

docker build -t gossip .

기반 이미지는 golang 을 사용하며, 빌드시 현재 프로젝트(예제를 확인하시고 github 에서 가져오세요)를 컨네이너의 /go/src/github.com/wowlsh93/hyperledger-fabric-400-gossip 위치에 복사해 놓습니다. 그리고 소스를 인스톨하여 go/src/gossip 에 실행파일을 위치 시켜 놓습니다.

docker images

만들어진 이미지를 확인 하는 명령어는 위와 같습니다. 

docker run -p 28000:28000 --name gossip1 --rm gossip /go/bin/gossip -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader

실행은 run 명령어를 사용하며 옵션이 의미하는 바는  

-p 옵션으로 외부포트:내부포트를 포워딩 하고
-name : 이름은 gossip1 으로 임의로 정합니다.
-rm :  옵션으로 컨테이너가 내려가면 자동으로 삭제되게 하고 
컨테이너 실행시  /go/bin/gossip -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader 명령을 실행해 리더피어 서비스를 시작해 줍니다.  

두번째 터미널을 열어서 리더피어 말고 일반 피어를 위해 컨테이너를 띄어 봅시다. 
docker run -p 28001:28001 --net host --name  gossip2 --rm 
gossip /go/bin/gossip -name 127.0.0.1:28001 -ip 127.0.0.1 -port 28001  -bootstrap 127.0.0.1:28000
근데 컨테이너를 띄우면 에러가 나는데  컨테이너간 네트워킹을 할 수 없기 때문이며 이를 해결하기 위해 네트워킹 설정을 해야 합니다. 

도커 네트워킹 

1) 같은 노드에 있는 컨테이너 실습 - host 방식 

도커를 처음 설치하면 none, host, bridge 세 가지 종류의 네트워크가 자동으로 설정되는데  none방식(-net=none) 으로 하면 격리된 네트워킹을 갖기 때문에 안되고,  -net 옵션으로 host를 주면 모든 컨테이너가 호스트 네트워크를 같이 사용하게 되기 때문에 컨테이너간에 네트워킹이 가능해 집니다.

docker run -p 28000:28000 --net host --name gossip1 --rm gossip /go/bin/gossip -name 127.0.0.1:28000 -ip 127.0.0.1 -port 28000 -leader

위 처럼 -net host 를 각 컨테이너를 시작 할 때 넣어 주면 됩니다. 동일한 네트워크에 있기 때문에 포트만으로도 통신이 되는거죠.


2) 같은 노드에 있는 컨테이너 실습 - bridge 방식 

브리지방식은 도커의 디폴트 방식이기에 -net 옵션 없으면 자동으로 선택되는데 브리지 방식으로는 각각의 컨테이너가 다른 사설IP 를 갖게 되므로 포트만을 구분하는 예제(Gossip 서비스)에서는 안되는게 당연하며 추가정보가 필요합니다. 

docker network create --driver bridge gossip-network

일단 bridge형식의 네트워크를 하나 추가로 만듭니다. 이름은 gossip-network 로 하구요.

docker network ls

NETWORK ID NAME DRIVER SCOPE d87103af938c bridge bridge local 659368a11889 composer_default bridge local 17b35b1ad8c5 gossip-network bridge local 541d637a227c host host local de0e0715ded6 net_basic bridge local 6b6f56779a9a none null local

docker network ls 명령어로 gossip-network 가 만들어진것을 확인합니다.

docker run -it --network=gossip-network --name gossip1 gossip /go/bin/gossip -name gossip1:28000 -ip gossip1 -port 28000 -leader
docker run -it --network=gossip-network --name gossip2 gossip /go/bin/gossip -name gossip2:28001 -ip gossip2 -port 28001 -bootstrap gossip1:28000
docker run -it --network=gossip-network --name gossip3 gossip /go/bin/gossip -name gossip2:28002 -ip gossip3 -port 28002 -bootstrap

이제 각 도커 컨테이너를 시작시 옵션으로 -network=gossip-network 로 네트워크 설정을 하고, 불편한 IP주소 대신 gossip1~3처럼 컨네이너 명으로 컨테이너 끼리 통신 하게 할 수 있습니다. 컨테이너 끼리 NAT 테이블 안에서 브리지로 통신하기 때문에 굳이 -p 옵션으로 포트포워딩은 필요 없습니다. 참고로 다시 시작 할 때 컨네이터가 중단된 채 존재하기 때문에 안될 수 있는데 삭제 한 후에 해야합니다. 전체 컨테이너 삭제 명령어는 docker rm `docker ps -a -q` 입니다.


3) 같은 노드에 있는 컨테이너 실습 -  Docker Compose 

version: '2'
services:
leader:
image: gossip
container_name: gossip1
command: /go/bin/gossip -name gossip1:28000 -ip gossip1 -port 28000 -leader
restart:
on-failure


peer1:
image: gossip
container_name: gossip2
command: /go/bin/gossip -name gossip2:28001 -ip gossip2 -port 28001 -bootstrap gossip1:28000
restart:
on-failure
depends_on:
- leader
peer2:
image: gossip
container_name: gossip3
command: /go/bin/gossip -name gossip3:28002 -ip gossip3 -port 28002 -bootstrap gossip1:28000
restart:
on-failure
depends_on:
- leader

도커 컴포즈는 여러개의 컨테이너를 한방에 시작 시킬때 유용합니다. docker-compose.yaml  파일을 만들어 위의 내용을 작성합니다. 각 서비스를 구분했으며  옵션은 쉽게 이해 될 것입니다. 주의 할 점은 일반 피어는 리더피어가 생성된 후에 떠야하기때문에 depends_on옵션이 추가되었습니다. 

docker-compose -p gossipnetwork up -d

-p옵션으로 네트워크를 설정해주며,  -d 는 detached mode으로 로그가 안 보이기 때문에. -d 옵션을 빼거나 

docker logs -t -f gossip1

같은 명령어로 각 컨테이너들의 stdout 로그를 확인합니다.


4) 다른 노드에 있는 컨테이너 실습 - Overlay Network by etcd

일단 만들어진 이미지를 도커허브에 올려서 각 노드에서 편하게 사용 하려고 합니다.  참고

이제 어디서에나 도커만 있다면 이미지를 다운로드 (이미지 이름이 gossip 에서 wowlsh93/gossip:1 로 변경됨) 받아서 실행 할 수 있습니다.

docker pull wowlsh93/gossip:1

도커(컨테이너)의 장점이 gossip 서비스를 돌리기 위한 어떤 환경(go컴파일러등)이 갖추어져 있지 않더라도, 도커만 있는 곳이라면 실행 할 수 있어서 인프라 설치를 위한 공을 들일 필요가 없는거니까요.

이제 본격적으로 여러 노드에서 gossip분산 서비스를 gossip-network 를 만들어서 실행시켜 보시면 잘 될리가 없습니다. 각 네트워크는 각 노드 내부에서만 활동하기 때문인데요. 외부 네트워크주소를 입력하는 부분도 없잖아요. 당연한거죠. 다른 방법이 필요 합니다. 

여기서 다룰 오버레이 네트워크는 여러 머신에서 각각의 네트워크에서 돌아가는 서비스들이 하나의 네트워크에 있는 것 처럼 만들어 주는 방식(docker swarm, hadoop yarn, apache mesos, kubernetes 등) 으로 다양하게 이루어 질 수 있는데, 여기서는 Docker 자체에서 제공하는 방식으로 만들어 볼 예정입니다.

* 사실 우리 프로그램 경우 그냥 실행시 name 옵션에서 정확한 외부 ip 를 입력해주면 잘 동작합니다. 굳이 이딴거(오버레이/스웜/쿠버네티스) 사용 할 필요도 없긴합니다. 실제 하이퍼레저 패브릭도 마찬가지입니다. 도커,쿠버네티스 같은거 사용 안해도 돌아가는 건 상관이 없어요. 각 호스트에서 디펜던시를 따로 다 설치하는 귀찮음이 있긴 하지만 오히려 첨에 실습해보는데는 더 클리어 하구요. 다만 초기 실습이 아니라 일이 되면 여러모로 추상화/자동화가 편하겠지요. 

도커 없이 ip만 입력해 주면 됨) 
 gossip -name  192.168.0.5:28001 -ip  192.168.0.5 -port 28001  -bootstrap   192.168.0.2:28000 

도커만 사용해도 ip만 입력해 주면 됨) 
 docker run -p 28001:28001 --net host --name  gossip1 --rm wowlsh93/gossip:1 /go/bin/gossip -name   192.168.0.5::28001 -ip   192.168.0.5  -port 28001  -bootstrap  192.168.0.2:28000

                                          (이미지 출처: https://blog.naver.com/alice_k106/220772125819)

위 그림처럼 각각의 서버에 위치하더라도 하나의 네트워크처럼 작동하도록 하기 위해 이 글에서는  etcd (분산 key-value store로 zookeeper 같은 분산코디네이팅 역할을 함) 라는 것을 사용 할 건데요. 5개의 서버를 활용하여 3개에는 도커를 설치하여 우리가 만든 이미지를 다운로드 해주시고, 2개에는 etcd를 설치합니다.  저 같은 경우는 VirtualBox에 Host-only-adapter 방식으로 5개의 호스트 네트워크를 구성하였습니다. bridge 로 해도 되고 서로 ping 날려서 확인만 되면 됩니다. 

etcd 설치 - etcd node 1

ubuntu@docker-node1:~$ wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node1:~$ tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node1:~$ cd etcd-v3.0.12-linux-amd64

ubuntu@docker-node1:~$ nohup ./etcd --name docker-node1 --initial-advertise-peer-urls http://192.168.205.10:2380 \
--listen-peer-urls http://192.168.205.10:2380 \
--listen-client-urls http://192.168.205.10:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.205.10:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster docker-node1=http://192.168.205.10:2380,docker-node2=http://192.168.205.11:2380 \
--initial-cluster-state new&


etcd 설치 - etcd node 2

ubuntu@docker-node2:~$ wget https://github.com/coreos/etcd/releases/download/v3.0.12/etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node2:~$ tar zxvf etcd-v3.0.12-linux-amd64.tar.gz
ubuntu@docker-node2:~$ cd etcd-v3.0.12-linux-amd64/

ubuntu@docker-node2:~$ nohup ./etcd --name docker-node2 --initial-advertise-peer-urls http://192.168.205.11:2380 \
--listen-peer-urls http://192.168.205.11:2380 \
--listen-client-urls http://192.168.205.11:2379,http://127.0.0.1:2379 \
--advertise-client-urls http://192.168.205.11:2379 \
--initial-cluster-token etcd-cluster \
--initial-cluster docker-node1=http://192.168.205.10:2380,docker-node2=http://192.168.205.11:2380 \
--initial-cluster-state new&

ubuntu@docker-node2:~/etcd-v3.0.12-linux-amd64$ ./etcdctl cluster-health
member 21eca106efe4caee is healthy: got healthy result from http://192.168.205.10:2379
member 8614974c83d1cc6d is healthy: got healthy result from http://192.168.205.11:2379
cluster is healthy


ondocker-node1
if docker version >= 17.09

ubuntu@docker-node1:~$ sudo service docker stop ubuntu@docker-node1:~$ sudo /usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.205.10:2379 --cluster-advertise=192.168.205.12:2375

On docker-node2

ubuntu@docker-node2:~$ sudo service docker stop ubuntu@docker-node2:~$ sudo /usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.205.10:2379 --cluster-advertise=192.168.205.13:2375

On docker-node3

ubuntu@docker-node2:~$ sudo service docker stop ubuntu@docker-node2:~$ sudo /usr/bin/docker daemon -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --cluster-store=etcd://192.168.205.10:2379 --cluster-advertise=192.168.205.14:2375


모든 설치가 끝나고, 아무 도커호스트에서 아래처럼 오버레이 네트워크를 만듭니다.

docker network create -d overlay gossip-overlay

모든 노드에서 동일한 네트워크가 만들어 졌음을 확인합니다. 이제 해당 네트워크를 통해서 2)번의 명령어와 동일하게 실행 할 수 있습니다. 물론 네트워크이름은 바꿔야죠.

docker run -it --network=gossip-overlay --name gossip1 gossip /go/bin/gossip -name gossip1:28000 -ip gossip1 -port 28000 -leader
docker run -it --network=gossip-overlay --name gossip2 gossip /go/bin/gossip -name gossip2:28001 -ip gossip2 -port 28001 -bootstrap gossip1:28000
docker run -it --network=gossip-overlay --name gossip3 gossip /go/bin/gossip -name gossip2:28002 -ip gossip3 -port 28002 -bootstrap


5) 다른 노드에 있는 컨테이너 실습 - Docker Swarm

..진행중..


참고:

도커 / 도커허브
https://subicura.com/2017/02/10/docker-guide-for-beginners-create-image-and-deploy.html

도커 네트워크
https://mesosphere.com/blog/networking-docker-containers/

도커 컴포즈 네트워크
https://medium.com/@caysever/docker-compose-network-b86e424fad82

도커 오버레이 네트워크 by etcd
https://docker-k8s-lab.readthedocs.io/en/latest/docker/docker-etcd.html

쿠버네티스 개론 및 실습 동영상
https://www.youtube.com/watch?v=l42GttmnnZ4

쿠버네티스 설치
https://medium.com/@dirty49374/kubeadm%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-kubernetes-%EC%84%A4%EC%B9%98%ED%95%98%EA%B8%B0-ubuntu-18-04-61710f0b4db8