예제로 이용된 재료는 이전에 만든  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




docker vs vagrant vs virtualenv 


도커도 안쓰고 virtualenv도 안쓰고 잘 지내 왔다. 사실 지금도 굳이 도커나 virtualenv를 써서 괜한 복잡도를 올릴 필요는 없는 상황이긴 하지만, (현재는 virtualbox 같은 이미지레벨도 아닌, os 자체를 클론질라로 이미지 떠서 관리하고 있다)  앞으로 둘 중 하나를 반드시 써야할 상황은 올것이다. (분산 텐서플로우를 사용한다던지.. 도커안에 virtualenv 를 사용 할 수도)


참고로 virtualenv 를 가상환경이라고 다들 말해서 , 먼가 거창해보이지만 그냥 유저독립적 python 환경일뿐이다. 따라서 virtualenv 를 윈도우즈,우분투등에서 사용하다보면 OS의 다름 때문에 먼가 잘 안될 때도 있는거 같다. 그 경우에는 레벨이 한단계 아래인 도커가 나을것이며, 도커보다 더 한 단계 아래의 작업 (OS 부팅시 자동로그인되며,브라우저들이 자동으로 뜨고, 각종 서비스들이 시작되는등) 을 하고 싶다면 나처럼...OS 를 굽는수 밖에..


여튼 기타 자세한 사항등은 나중에 정말 필요 할 때 찾아보는 것으로 하고 간략 정리+번역1개 하고 마무리~:-)


참고로 도커로 Pycharm 사용하는것에 관한 글은 아래를 참고 

 

PyCharm + Docker로 파이썬 개발환경 셋업하기 



짧은 정의 

docker

- 리눅스의 컨테이너 기반의 가상화 플랫폼 
컨터이너의 스타트업 시간은 겨우 몇초
- 각각의 내용을 포함한 이미지를 가지고, 그 이미지를 실행함 

vagrant 

-  OS 기반의 가상화 플랫폼 (리눅스,윈도우등) 
- 기존 VirtualBox 등을 통한 가상화를 좀 더 편하게 해주기 위함. 
- 도커보다 무겁다. (스타트업 시간 몇분) 

virtualenv

- 초간단 (하지만 아무것도 안하는거보단 복잡 ㅎ) 
- 독립 파이썬 인터프리터/라이브러리 환경 (라이브러리 설치시 독립적으로 관리된다) 
- OS 독립이 아니다. 즉 우분투에서 만든 virtualenv 환경이 윈도우에서 잘 안될 수도. 
- 윈도우 기반의 virtualenv 에서는 pycrypto 같은거 설치하기 어려움
- 당연하게도 파이썬 코드,파이썬 패키지,모듈 이외의 프로그램 (DB,미들웨어등) 에 필요한 요소들에 대해선 따로   먼갈 해줘야한다. 그냥 Docker 쓰면 되는데..

Docker vs Vagrant 



Vagrant와 Docker가 경쟁자로 보일지라도, 진취적인 관리자들은 서로를 보완하기 위해 함께 사용하는 방법을 발견했다. 이러한 시나리오에서는 Vagrant를 사용하여 기본 VM을 만들고 이 기본 VM을 모두 사용하는 다른 구성을 만들어야 할 때 Docker를 사용하여 다양한 경량 버전을 준비하고 만들며 당연하게 이 두개의 구성위에 virtualenv 를 통해 독립적인 파이썬 환경을 만들 수도 있겠다.


Docker vs Virtualenv

위에 간략정리에서 봤다시피 이 두개의 요소도 노는물이 다르다. 즉 경쟁상대가 아니다. 
그냥 전반적인 개발환경을 도커로 만들어두고, 내부에 파이썬 프로젝트가 하나만 있으면 virtualenv 사용하지 말고, 내부 프로젝트가 늘어날 예정이거나, 여러가지면 virtualenv 를 사용한다. 



virtualenv 는 살아있다 ! [Foot번역] 


https://hynek.me/articles/virtualenv-lives/


PyPI를 통해 패키지를 설치 할 수 있도록 Python을 설치하는 것은 짜증나고 시간이 오래 걸릴 수 있다. 더 나쁜 것은 OS가 숨겨진 오류 메시지를 던지기 시작하는 것인데  특히 데스크톱은 그 경향이 있으며, reddit에서 들어보았을지 모를 어떤 반짝거리는 패키지를 설치하여 서버의 전체 툴 체인을 무너 뜨리는 것도 가능하다.

virtualenv 너머의 글로벌 site-packages 에는 아무것도 설치하지 마십시오.

혹시 님에게는 봉창두드리는 소리?  웬만하면 따르시구요. 적당히 하세요. 

virtualenv in 2014 

virtualenv는 얼마 동안 Python 소프트웨어를 설치하기 위해 받아 들여졌던 표준이었지만 슬프게도, 현재는 몇몇 선교사들이 대담하게 virtualenv의 끝을 선포하고 있다. 일반적으로 컨테이너, 한마디로 Docker 때문에..

나는 그것이 불행하고 근시안적인 것이라고 생각하며 솔직히, 그들은 전체 그림을 보지 못한다 : virtualenv의 임무는 프로젝트를 서로 분리하는 것이 아니라 그것의 임무는 운영체제의 Python 설치와 설치된 패키지와 섞여지지 않게  당신의 것을 분리시키는 것이다. (역주: 비슷한데? ) 
이제 아주 유명한 virtualenv-killer 인  Docker를 사용하여 이것이 왜 좋은 아이디어인지 살펴 보겠다. 이를 위해 python-pip를 신뢰할 수 있는 컨테이너 1에 설치 한 후 얻을 수있는 사전 설치된 패키지를 살펴 보겠다



argparse (1.2.1) chardet (2.0.1) colorama (0.2.5) html5lib (0.999) pip (1.5.4) requests (2.2.1) setuptools (3.3) six (1.5.2) urllib3 (1.7.1) wsgiref (0.1.2)

Surprised? 

새로운 requests , html5lib 또는 colorama를 설치하면 어떻게 됩니까?
나는 당신에게 말하고 싶은것은 :  먼가가 망가지기 시작하고 있으니 정신차리세요.

이러한 일은 언제든지 발생할 수 있으며 시스템을 허약하게 만들 수 있다. 모든 기능을 갖춘 우분투 서버는 당연히 더 많은 물건을 운반하며 파이썬으로 작성된 시스템 툴을 설치할 때마다 어떤 알 수 없는 종류의 파손이 일어날 수 있다. 데비안 패키지 개발자는 pip이 어떻게 작동하고 패치 할 것인지에 대해 마음에 들지 않는다고 결정할 때마다 "폭발 할 것인가?"라는 추첨의 일부로 무의식적으로 참여하게 된다.

OS X 도 전혀 다르지 않다. 그것은 수십 개의 Python 패키지와 함께 제공된다.
데스크탑에서도 - 플랫폼에 상관없이! - 상황이 더욱 열악하다. 보통의 사이트 패키지가 혼란스러우며 대부분의 사용자는 특정 패키지가 왜 설치되어 있는지 알지 못 하고 있다. 튜토리얼 멘토가 확인하는대로 설치 전체를 중단하는 단계는 매우 짧습니다.

운영체제 Python의 site-packages3는 운영체제에 속합니다.

나는 오랫동안 OS 벤더가 다른 곳에서 자신의 물건에 대한 가상 환경을 생성하고 사용자가 시스템 사이트 패키지를 갖도록 한다면 더 좋아할 것이라고 말하고있다. 그러나 그것은 일어나지 않으며  아무도 알지 못하는 일부 시스템 도구가 프로젝트 요구 사항과 호환되지 않는 버전의 라이브러리를 설치하지 않는다고 보장 할 수는 없다.

해당 OS의 버전 명시적으로 작성된 소프트웨어만 site-packages 에  넣어라. 즉, OS에서 제공하는 Python 패키지만 사용하는 시스템 도구이다. 다른 모든 것을 virtualenv에 보관하라. 

virtualenv 대 시스템 격리(도커) 에 대해 논의하는 것을 그만 두십시오. 한 번에 두 가지를 모두 사용해야하고 다른 한 가지로 대체 해서는 안됩니다.

1. Docker / lxc / jails / zones / kvm / VMware / ...를 사용하여 응용 프로그램 당 하나의 컨테이너 / VM으로 응용 프로그램 서버의 OS를 호스트에서 격리하라.

2. 하지만 그들 내부는 시스템  site-packages로 되있으므로  virtualenv를 사용하여 파이썬 환경을 격리시켜라.





http://www.markbetz.net/2014/01/17/python-if-you-have-docker-do-you-need-virtualenv/

https://www.theodo.fr/blog/2015/04/docker-and-virtualenv-a-clean-way-to-locally-install-python-dependencies-with-pip-in-docker/

https://www.quora.com/Is-there-any-advantage-of-running-Tensorflow-in-a-docker-container-rather-than-just-pip-installing-it

https://stackoverflow.com/questions/40177240/what-is-the-difference-between-vagrant-docker-virtualenv-or-just-a-virtual-mac

https://readme.skplanet.com/?p=13470

+ Recent posts