반응형

Host OS: Ubuntu 16.04 (amd64)

Host Spec (VM): Quad core 2.6GHz (Intel Zeon, Haswell), 4GB RAM

LXC template: ubuntu



지난 여름에는 라즈베리파이 4개를 가지고 무선 메쉬 네트워크를 만들고, 각각의 라즈베리파이가 AP 역할을 함으로써 서로 다른 AP에 물린 클라이언트들 사이에 통신이 되도록 하였고, 이를 중개하는 multi-constrained 라우팅 프로토콜을 Java로 개발해서 테스트를 했었다.


사실 성능을 생각하면 C로 개발해야 하지만 무지막지하게 늘어나는 개발 시간 때문에 그나마 익숙하게 다룰 수 있는 Java로 개발을 했었고, 이제 와서 보니 이게 ns-3에서 시뮬레이션을 돌릴 때 결국 C나 파이썬으로 포팅해야 되는 어려움으로 돌아오고 말았다. ㄷㄷ


하지만 ns-3는 real-world program을 돌릴 수 있는 몇 가지 방법을 제시하고 있었고, 그 중에서 Linux Container (LXC)를 이용한 방법이 있어서 튜토리얼을 따라하며 환경을 구축해 보았다.


실제로 예제를 그대로 따라해서 2개의 노드가 IEEE 802.11g로 서로 애드혹 네트워크로 연결되고 각각의 노드에서 ping, ssh 등을 테스트하는 데에는 아무 문제가 없었다.

여기서 더 나가서 내가 개발한 Java 기반의 라우팅 프로토콜을 실행시키는 과정이 생각보다 오래 걸렸지만, 이것도 각 컨테이너를 인터넷에 먼저 연결시키고 apt-get으로 필요한 패키지를 다 설치하고 나니 어쨌든 내가 라즈베리파이에서 돌리던 모양 그대로 실행시킬 수 있었다.


여기까지는 일단 그대로 실행이 되는지부터 봐야 해서 노드 3개까지만 만들어서 해 본 거였고, 이제 본격적으로 시뮬레이션을 돌리기 위해서 노드 생성과 Java 프로그램 실행을 자동화하는 스크립팅 작업을 했는데 이것 또한 시간이 은근히 많이 걸렸다. ㅜㅜ


우여곡절 끝에 이제 내가 마음대로 원하는 만큼의 노드(컨테이너)를 자동으로 생성/실행할 수 있게 되었고, 네트워크 토폴로지는 ns-3에서 역시나 마음대로 설정해 주면 되었다.


드디어 대망의 첫 실험...

자신있게 10개의 노드를 명령어 한 줄로 간지나게(?) 자동으로 뙇 생성하고, 토폴로지는 4-4-2 그리드 형태로 만든 다음, 실험 시작을 누르고 나서 시범적으로 9번 노드에서 이웃 노드인 10번 노드한테 ping을 날려 봤더니...


root@node9:/# ping 10.0.0.10

PING 10.0.0.10 (10.0.0.10) 56(84) bytes of data.

64 bytes from 10.0.0.10: icmp_seq=1 ttl=64 time=3665 ms

64 bytes from 10.0.0.10: icmp_seq=2 ttl=64 time=4356 ms

64 bytes from 10.0.0.10: icmp_seq=3 ttl=64 time=5375 ms

64 bytes from 10.0.0.10: icmp_seq=4 ttl=64 time=7245 ms

64 bytes from 10.0.0.10: icmp_seq=5 ttl=64 time=8166 ms

64 bytes from 10.0.0.10: icmp_seq=6 ttl=64 time=9149 ms

64 bytes from 10.0.0.10: icmp_seq=7 ttl=64 time=10225 ms

64 bytes from 10.0.0.10: icmp_seq=8 ttl=64 time=11106 ms

64 bytes from 10.0.0.10: icmp_seq=9 ttl=64 time=12252 ms

64 bytes from 10.0.0.10: icmp_seq=10 ttl=64 time=13146 ms

64 bytes from 10.0.0.10: icmp_seq=11 ttl=64 time=14062 ms

64 bytes from 10.0.0.10: icmp_seq=12 ttl=64 time=15261 ms

64 bytes from 10.0.0.10: icmp_seq=13 ttl=64 time=16784 ms

64 bytes from 10.0.0.10: icmp_seq=14 ttl=64 time=18056 ms

64 bytes from 10.0.0.10: icmp_seq=15 ttl=64 time=19286 ms

64 bytes from 10.0.0.10: icmp_seq=16 ttl=64 time=20347 ms

64 bytes from 10.0.0.10: icmp_seq=17 ttl=64 time=21576 ms

64 bytes from 10.0.0.10: icmp_seq=18 ttl=64 time=22208 ms

64 bytes from 10.0.0.10: icmp_seq=19 ttl=64 time=22970 ms

64 bytes from 10.0.0.10: icmp_seq=20 ttl=64 time=23673 ms

64 bytes from 10.0.0.10: icmp_seq=21 ttl=64 time=24549 ms

64 bytes from 10.0.0.10: icmp_seq=22 ttl=64 time=24565 ms

64 bytes from 10.0.0.10: icmp_seq=23 ttl=64 time=24311 ms

64 bytes from 10.0.0.10: icmp_seq=24 ttl=64 time=24205 ms

64 bytes from 10.0.0.10: icmp_seq=25 ttl=64 time=23996 ms

64 bytes from 10.0.0.10: icmp_seq=26 ttl=64 time=23627 ms

64 bytes from 10.0.0.10: icmp_seq=27 ttl=64 time=23452 ms

64 bytes from 10.0.0.10: icmp_seq=28 ttl=64 time=23325 ms

64 bytes from 10.0.0.10: icmp_seq=29 ttl=64 time=23048 ms

64 bytes from 10.0.0.10: icmp_seq=30 ttl=64 time=22669 ms

64 bytes from 10.0.0.10: icmp_seq=31 ttl=64 time=22337 ms

64 bytes from 10.0.0.10: icmp_seq=32 ttl=64 time=21869 ms

64 bytes from 10.0.0.10: icmp_seq=33 ttl=64 time=21127 ms

64 bytes from 10.0.0.10: icmp_seq=34 ttl=64 time=20186 ms

64 bytes from 10.0.0.10: icmp_seq=35 ttl=64 time=19246 ms

64 bytes from 10.0.0.10: icmp_seq=36 ttl=64 time=18327 ms

64 bytes from 10.0.0.10: icmp_seq=37 ttl=64 time=17383 ms

64 bytes from 10.0.0.10: icmp_seq=38 ttl=64 time=16449 ms

64 bytes from 10.0.0.10: icmp_seq=39 ttl=64 time=15512 ms

64 bytes from 10.0.0.10: icmp_seq=40 ttl=64 time=14573 ms

64 bytes from 10.0.0.10: icmp_seq=41 ttl=64 time=13607 ms

64 bytes from 10.0.0.10: icmp_seq=42 ttl=64 time=12676 ms

64 bytes from 10.0.0.10: icmp_seq=43 ttl=64 time=11745 ms

64 bytes from 10.0.0.10: icmp_seq=44 ttl=64 time=10816 ms

64 bytes from 10.0.0.10: icmp_seq=45 ttl=64 time=9876 ms

64 bytes from 10.0.0.10: icmp_seq=46 ttl=64 time=8953 ms

64 bytes from 10.0.0.10: icmp_seq=47 ttl=64 time=8012 ms

64 bytes from 10.0.0.10: icmp_seq=48 ttl=64 time=7037 ms

64 bytes from 10.0.0.10: icmp_seq=49 ttl=64 time=6042 ms

64 bytes from 10.0.0.10: icmp_seq=50 ttl=64 time=5064 ms

64 bytes from 10.0.0.10: icmp_seq=51 ttl=64 time=4069 ms

64 bytes from 10.0.0.10: icmp_seq=52 ttl=64 time=3074 ms

64 bytes from 10.0.0.10: icmp_seq=53 ttl=64 time=2080 ms

64 bytes from 10.0.0.10: icmp_seq=54 ttl=64 time=1087 ms

64 bytes from 10.0.0.10: icmp_seq=55 ttl=64 time=102 ms

64 bytes from 10.0.0.10: icmp_seq=56 ttl=64 time=5.94 ms

64 bytes from 10.0.0.10: icmp_seq=57 ttl=64 time=5.57 ms

64 bytes from 10.0.0.10: icmp_seq=58 ttl=64 time=5.82 ms

64 bytes from 10.0.0.10: icmp_seq=59 ttl=64 time=5.49 ms

64 bytes from 10.0.0.10: icmp_seq=60 ttl=64 time=5.55 ms

^C

--- 10.0.0.10 ping statistics ---

60 packets transmitted, 60 received, 0% packet loss, time 59063ms

rtt min/avg/max/mdev = 5.498/13466.053/24565.805/8177.166 ms, pipe 25

root@node9:/# 



Aㅏ...

저기 중간에 점도 찍히지 않고 당당하게 5자리를 찍어주는 저 결과는 뭥미?

24205는 뭔가요? 저게 진정 와이파이 RTT인가여? 털썩...


각 컨테이너에서 아무 프로그램도 돌고 있지 않을 때는 이웃노드 간에 ping을 날리면 약 5ms가 되어야 정상인데, 보는 것처럼 데이터 패킷도 아닌 ICMP 패킷 하나가 바로 옆으로 가는 데 24초를 넘어섰다. ㄷㄷㄷ 저것도 계속 RTT가 커지기만 하길래 24초쯤 되는 상황에서 돌아가는 모든 Java 프로그램을 죽였더니, 한참이 지나고 나서야 ping이 정상으로 돌아온 것이다.


내가 Java 프로그램을 잘못 만들었구나 자책하려고 했지만, Host 머신에서 CPU 사용량을 확인해 보니 ns-3 프로세스가 혼자 97% ~ 110% 사이를 왔다갔다 하고 있었고, 정작 내가 개발한 Java 프로세스들은 별로 CPU를 차지하고 있지 않았다. (라즈베리파이2에서도 3-9% 수준이긴 했음)


일차적으로 생각해 본 원인은 ns-3 프로세스가 L2 이하를 시뮬레이션 시키고 있는데, 10개의 노드 각각이 라우팅을 위해서 Hello 패킷을 비롯한 컨트롤 패킷을 매 초마다 발생시키니까 싱글 코어만으로는 계산하는 양이 감당이 안돼서 응답이 늦어지는 바람에 ping이 20초를 넘어서는 저런 사단이 난 것 같았다.


아~망했어요 ㅠㅠ

나는 ns-3 코어가 적어도 100노드 정도의 scalability는 책임질 수 있을 거라 생각했는데... 이건 뭐 컨테이너가 제아무리 가벼워도 각자 발생시키는 패킷의 양이 늘어나면 ns-3 코어가 감당을 못하는 상황이라니...


현재까지는 ns-3에 멀티코어 활용하도록 고친다던지, 아예 컨테이너도 없이 real-world 프로그램을 직접 네트워크와 연동하는 방법(DCE라고 하는데... 결국 컨테이너로 해결을 못하면 이것도 공부해야 함...)은 아직 확인하지 못했다.


내가 ns-3에 멀티코어 활용 방법을 아직 몰라서 이런 것이라고 믿고 싶다. ㅜㅜ 

그런데 왠지 보니까 기본적으로 멀티코어를 활용하도록 설계했을 법도 한데 안한 점과 구글 검색(ns-3 multi core)에서 첫 페이지에서 그런 옵션을 켜는 법이라던지 하는 간단한 해결방법이 보이지 않는 것을 보면... 


야~신난다 @_@

대안으로 나온 것이 컨테이너도 없이 DCE라고 직접 프로그램 코드를 바로 실행하는 방법도 있지만 이건 C++만 된다고 한다. DCE에 Java 프로그램을 붙이는 방법이 있는지 찾아보고, 이마저도 없으면 쌩으로 Java를 모두 C++ 또는 파이썬으로 포팅하는 대규모 토건사업이 펼쳐지겠지 ㄹㅇㅁㄴㄹㅇㅁㄹㅁㅈ 일자리 창출 인정? =_=


정신줄 그만 놓고, 해결 방법을 찾아봐야겠다. ㅜㅜ


반응형
블로그 이미지

Bryan_

,
반응형

Host OS: Ubuntu 16.04 (amd64)

lxc version: 2.0.4


Linux Container에 외부(즉 Host)에서 명령어를 주입하는 방법은 Container 시작할 때 명령어 같이 실행하는 방법과 이미 실행중일 때 명령어를 실행하는 방법으로 나뉜다.


일단 시작하지 않은(stop 또는 lxc-create만 해둔 상태) container는 lxc-start 또는 lxc-execute를 통해서 특정 명령어를 같이 실행할 수 있다.


# lxc-execute -n 컨테이너_이름 -- 실행할_명령어


그런데 이미 실행중인 container에 대해서 lxc-execute를 하면 이미 실행중이라는 에러가 나면서 실행되지 않는다. 이 때에는 lxc-attach를 쓰면 된다.


# lxc-attach -n 컨테이너_이름 -- 실행할_명령어


lxc-attach를 추가 명령어 없이 실행하면 해당 노드의 프롬프트로 바뀌어 버리는데, 실행할 명령어를 추가하게 되면 container의 프롬프트로 바뀌지 않은 채 명령어만 실행하고 Host의 쉘을 유지하게 된다. 이렇게 함으로써 host가 여러 개의 container에 대해서 같은 명령어를 일괄 실행할 수 있는 장점이 있다.


예를 들어, node4라는 이름의 container에 default route를 추가하는 명령어를 실행한 결과가 아래 그림과 같다. 원래 실행중인 container에 없던 default 경로가, lxc-attach와 함께 실행하는 route 명령어를 통해서 새로 추가되었음을 확인할 수 있다. 그리고 프롬프트가 바뀌지 않는 것도 확인할 수 있다.



다만 프롬프트만 바뀌지 않을 뿐, 실행 결과에 대한 출력은 host의 쉘에 모두 표시되므로 에러 여부도 판별할 수 있다.



<참고자료>

[1] "9.6 Starting a Command Inside a Running Container",

 https://docs.oracle.com/cd/E37670_01/E37355/html/ol_attach_containers.html



반응형
블로그 이미지

Bryan_

,