Personal Toy Project - mumu tv
Introduction
안녕하세요? Nurd Worker입니다! 반갑습니다.😊
이번에 소개해드릴 프로젝트는 mumu tv프로젝트입니다~
네트워크 국비교육과정중에 개인마다 자율 토이프로젝트 과제가 나와서 만든 프로젝트입니다.
주제는 도커와 도메인이구요~ 당시에 누누티비 이슈가 뉴스로 굉장히 핫했어서 제작한 스토리텔링형 프로젝트입니다 ㅎㅎ
어려운 기술은 없으나, 발표때 수강생분들이 많이들 재밋어 하셨습니다ㅎㅎㅎ
직접 vm과 서버를 돌리면서 발표를 했어서 흥미로워하시더라구요~
소개영상은 스토리위주로 만든 영상이구 포스팅은 기술적인 내용입니다~
기술적인 내용은 미리 포스팅했었구 1년지나서 포트폴리오로 쓰려고 컨텐츠를 추가하느라 현재 소갯글과는 어투가 좀 다릅니다 ㅋㅋㅋ
그럼 재밋게 읽어주세요!
++ 추가해서 시연영상원본은 포스팅 하단에 추가하였습니다~!
mumu tv 링크
mumu tv introduction video : https://youtu.be/V3I2LtgOGnU?si=wAskYFOCaI4WncCf
mumu tv github repository : https://github.com/nurdworker/mumutv
GitHub - nurdworker/mumutv
Contribute to nurdworker/mumutv development by creating an account on GitHub.
github.com
기술 스택
무무티비 프로젝트의 메인 주제는 도커와 DNS입니다.
무무티비 프로젝트에서 나오는 모든 임시 사이트들은 도커이미지를 사용한 3tier 아키텍쳐로 구현하였어요~(사실 db는 mysql이미지가 필요없었슴..ㅋㅋ)
또한 직접 가상네트워크대역에서 dns서버 역할을 하는 vm을 만들었습니다. centos os에 bind모듈을 사용하였구요.
임시 사이트들은 실제 서비스는 아니구요 ㅋㅋ(거의다 불법..)
프론트앤드만 그럴듯하계? 제작하였습니다. 물론 익숙한 vuejs... 깃허브 코드를 보시면 사이트들 ui가 꼭필요한 기능들만 따로 코드로 구현했구 나머지 ui들은 그냥 배경이미지 통째로 갖더넣은거에요 ㅋㅋ 웹파일은 vuejs로 코딩하였고 실제배포는 도커의 nginx이미지로 하였습니다^^
백엔드는 nodejs 도커이미지를 사용하였고 쉘스크립트는 백엔드 프로세스가 서버를 조작하기위해서 사용했습니다(처음배운거라 많이어려웠어요ㅠㅠ)
os는 dns서버만 centos로, 웹서버들은 우분투 이미지로 만들었고, 클라이언트역을 위한 gui환경을 위해 mint os를 사용하였습니다.
무무티비 아키텍쳐
굉장히 복잡한데요 ㅋㅋ
아래 포스팅에 어떤 설계를 했는지 차근차근 작성해놨습니다~
박스당 vm이구요, 각각 웹서버, dns서버, 등등이 어떻게 돌아가는지, 어떤 포트가 열려있는지 등을 대략적으로 이미지로 그려놨습니다.
클라이언트와 경찰관이 저 컨테이너들로 배포한 웹사이트를 접속한다는 가정으로 스토리를 만들었습니다^^
그럼 시작하겠습니다~!
존경하는 판사님
무무티비는 실존하는 서비스가 아니라
코찔이가 포폴용으로 넣으려고 만든 가짜 사이트입니다 ㅠㅠ
#1 mumu_web
일단 파일 구조는 이렇게 생겼다
mumu_web VM에서 최상위 디렉토리에 /mumuweb을 디렉토리를 만들어줬고
하위에는 이러한 도커파일과 웹 파일들이 들어있다.
저기 빌드명령어라고 적혀있는 /mumuweb디렉토리에서 명령어를 쳐주고 컨테이너를 돌려주면
211.183.3.150:80으로 웹사이트가 띄워진다.
# mumu web
웹 빌드 : docker build -t web_mumu:1.0 -f ./web_Dockerfile .
와스 빌드 : docker build -t was_mumu:1.0 -f ./was_Dockerfile .
db 빌드 : docker build -t db_mumu:1.0 -f ./db_Dockerfile .
웹 런 : docker run -dp 80:80 -v /mumuweb/web_volume:/usr/share/nginx/html --link was_mumu --link db_mumu --name web_mumu web_mumu:1.0
와스 런 : docker run -d -v /mumuweb/was_volume/server.js:/app/server.js -v /mumuweb/was_volume/package.json:/app/package.json --link db_mumu --name was_mumu was_mumu:1.0
디비 런 : docker run -d --name db_mumu db_mumu:1.0 --default-authentication-plugin=mysql_native_password
https://jacobowl.tistory.com/235
위 포스팅에 대충 명령어들 적어놨다.
최대한 요약해서 적어보자면..
- 웹은 nginx 이미지로 띄웠고, 요기서 컨테이너내 프론트 파일(유저한테 보낼파일들)이 담긴 디렉토리(/usr/share/nginx/html)가 web_volume디렉토리와 마운팅된다.
포트는 80번포트로 퍼블리싱해줘서 브라우저에서 211.183.3.150으로 접속하면 알아서 80번포트에 연결되게 해줬다.
링크로 was쪽과 db쪽 아이피를 로컬도메인으로 쓸수 있게 해줬다. (사실 db는 필요없긴한데..)
web_defaultconf파일은 nginx에서 프록시설정을 해주려고 통째로 걍 갖다 넣어줬다.
- was는 node이미지를 사용하였고, /was_volume디렉토리를 컨테이너 내부의 /app디렉토리에 넣어줬다. 여기에 넣어주면 node컨테이너가 실행되면서 이안쪽에 server.js파일을 실행시켜주더라. 디렉토리끼리 걍 마운트해서 명령어를 좀 줄일걸 그럤다. 걍 써놓고 복붙해서 써서..
모듈다운을 위한 package.json파일도 넣어줬다. npm install 명령어는 Dockerfile에서 써놨다.
그리고 링크로 db쪽 아이피를 로컬도메인으로 지정해줬음.
- db는 뭐 mysql이미지를 기본으로 썼고 뭐 루트 사용자? 이런거 만들려고 저런옵션달았음. 사실 뭔지 잘모름. 사실 이번 프로젝트에선 db자체를 안썼는데 3 tier로 만든다는 의미로 db컨테이너도 띄워준거다.
- web_volume에는 프론트파일 넣어주면 바로 적용이 되고 was_volume에는 파일 넣고 컨테이너를 재시작해주면 적용이 되서 관리하기도 많이 편해진다~
암튼 이렇게 컨테이너를 돌려주면 웹사이트가 정상작동한다~
mumu web뿐 아니라 이번 프로젝트에서 작동시킨 모든 웹사이트는 이렇게 docker를 이용해서 3tier로 띄웠다.
내가 만든 무무티비 웹사이트가 잘 띄워진걸 확인할 수 있다.
# dns서버를 만들어보자~
이제 dns서버를 만들어서 client가 도메인으로 위에 만든 mumu web에 접속가능하게 만들어보자.
일단 VM 운영체제는 centos 미니로 깔았고 여기에 bind를 깔아서 도메인서버로 작동하게 해줬다.
옆에 dns_service도 그림에있는데 이건 일단 생략하겠다.
도메인서버가 작동하는 방식은 이렇다.
일단 클라이언트에서 기본 DNS서버를 211.183.3.53으로 정해줬다.
클라이언트가 브라우저에 mumu.com을 입력하면, DNS서버에 mumu.com의 도메인에대한 아이피를 요청하게 되고,
public_dns의 네임서버는 이에대한 아이피를 응답해준다.
그럼 브라우저에서 접속한거니까 211.183.3.150:80으로 접속하게 되는것이다.
+추가해서 클라이언트는 centos mint os로 만들어줬다. 그냥 내 host pc에서 하려면 기본도메인서버를 정해줘야하고
링크에대한 네트워크접속이 도메인서버를 바꾼거니까 다 끊겨버려서 아예 VM으로 만들어버린거다..
mumu.com으로 잘 접속이 되어버렸다..
이제 누구나 내가만든 무무티비를 이용할 수 있게되었다.
누누티비의 차기작, mumu티비로 떼돈 벌 예정 개꿀
# police
무무티비를 잘 운영하던중 경찰에게 걸려버렸다.
무무티비의 존재를 제보받은 경찰은 사이버수사대 홈페이지로 접속한다.
저기 가운데에 보이는 which domain ban? 이란곳에 입력란이 있다.
이곳에 벤해버릴 도메인을 적고 버튼을 누르면 그 사이트는 벤이 되어버린다.
때문에 mumu.com을 벤하고 catch!버튼을 누르고 domain을 벤해버렸다.
다시 무무티비 이용자들은 mumu tv를 접속하려고 하자,
이럴수가.. mumu.com도메인이 경찰에게 벤당했다는 웹사이트가 떠버렸다.
더이상 무무티비를 이용할 수 없게 되었다..
police web의 설계는 아래와 같다.
police_web VM은 이렇게 최상위에 police디렉토리와, police_detected디렉토리가 있다.
웹사이트는 역시 docker로 컨테이너화해서 띄웠는데 특이한점은 한 VM에 각기다른 포트로 두 웹사이트를 띄웠다는 점이다.
빌드&런 명령어는 이렇게 생겼다.
# police web
웹 빌드 : docker build -t web_police:1.0 -f ./web_Dockerfile .
와스 빌드 : docker build -t was_police:1.0 -f ./was_Dockerfile .
db 빌드 : docker build -t db_police:1.0 -f ./db_Dockerfile .
웹 런 : docker run -dp 112:80 -v /police/web_volume:/usr/share/nginx/html --link was --link db --name web web_police:1.0
와스 런 : docker run -d -v /police/was_volume/server.js:/app/server.js -v /police/was_volume/package.json:/app/package.json --link db --name was was_police:1.0
디비 런 : docker run -d --name db db_police:1.0 --default-authentication-plugin=mysql_native_password
# detacted web
웹 빌드 : docker build -t web_detected:1.0 -f ./web_Dockerfile .
와스 빌드 : docker build -t was_detected:1.0 -f ./was_Dockerfile .
db 빌드 : docker build -t db_detected:1.0 -f ./db_Dockerfile .
웹 런 : docker run -dp 80:80 -v /police_detected/web_volume:/usr/share/nginx/html --link was_detected --link db_detected --name web_detected web_detected:1.0
와스 런 : docker run -d -v /police_detected/was_volume/server.js:/app/server.js -v /police_detected/was_volume/package.json:/app/package.json --link db_detected --name was_detected was_detected:1.0
디비 런 : docker run -d --name db_detected db_detected:1.0 --default-authentication-plugin=mysql_native_password
그림으로 표현해보자면,
이렇게 211.183.3.112:80으로 들어가면 detected웹이 띄워지게 되고,
211.183.3.112:112로 들어가면 police웹이 뜬다.
이렇게 두 사이트가 모두 정상작동하는걸 볼 수 있다.
암튼
211.183.3.112:112의 police웹에서 mumu.com을 벤하겠다고 하고 catch! 버튼을 누르면
이렇게 confirm : mumu.com이라고 알림창이 뜬다.
그리고 다시 client가 mumu.com을 접속하게 되면,
이렇게 detected 웹이 뜨게 된다.
이 동작의 원리는 아까 설명을 생략했던 dns_service에 있다.
대충 그림으로 그려보자면..
경찰관이 police web에서 mumu.com을 입력해서 police web의 was에다가 해당 스트링을 전달하고,
police web의 was에서는 해당 스트링을 211.183.3.53의 5000번 포트로 api요청을 보낸다.
post요청으로 보내서 mumu.com이라는 스트링을 받고, api /ban요청은 child process모듈을 사용해서
ban.sh파일에 mumu.com을 인자로 넣어서 실행시킨다.
이 ban.sh파일은 dns서버에서 /var/named/mumu.com.zone파일내용중 211.183.3.150의 아이피 번호를
211.183.3.112로 바꿔버린다. 그리고 네임서버 재시작
그럼 mumu.com으로 접속하는 사람들은 211.183.3.112:80으로 연결된 웹이 detected웹이니까
detected웹이 떠보이게 되는것이다..!
코드를 좀 보자면
app.post("/was", (req, ress)=> {
console.log(req.body.link)
axios.post('http://211.183.3.53:5000/ban',{link :req.body.link}).then((res)=>{
console.log(req.body.link)
ress.send('comfirm : '+req.body.link)
}
).catch((err)=>{
console.error(err.message)
})
});
police web의 was단 코드인데, /was로 api post요청을 받으면 해당 스트링을 211.183.3.53:5000의 /ban요청을 보낸다.
app.post("/ban", (req, res) => {
const domain = req.body.link;
console.log(`${domain} 밴작업 시작`);
const command = `./ban.sh ${domain}`;
exec(command, { encoding: "euc-kr" }, (error, stdout, stderr) => {
if (error) {
console.error(error.message);
res.status(500).send("Internal Server Error");
console.log(JSON.stringify(stderr));
return;
} else {
console.log(`${domain} 밴작업 끝`);
res.send("ok");
}
});
});
그리고 dns_service에서 /ban으로 post요청을 받으면, 그 도메인 스트링을 child process의 모듈을 사용해서 ban.sh쉘파일에 인자로 넣어서 실행시켜버린다.
프로젝트 기간이 너무 짧게 급하게만들어서 코드 대충 짠건 양해좀 ㅠ
dns_service의 server.js파일이 위치하는곳의 디렉토리 구조는 이렇게 생겼다.
#!/bin/bash
domain=$1
domainPath=/var/named/$domain.zone
echo $domainPath
grep=$(cat $domainPath | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
ip=$(echo $grep | cut -d " " -f1)
sed -i "s/$ip/211.183.3.112/g" $domainPath
systemctl restart named
이건 ban.sh파일 내용인데
인자로 받은 mumu.com을 domain이라는 식별자에 할당하고,
/var/named/mumu.zom의 아이피부분을 grep과 정규식으로 잡아서
sed로 경찰아이피 211.183.3.112로 바꿔버리는거다.
그리고 최종적으로 named재시작.
때문에 클라이언트는 mumu.com으로 접속하려고 하면 211.183.3.112:80인 detected웹으로 접속하게 되어버린거다.
이럴수가.. 도메인이 벤당해버려서 더이상 무무티비 운영이 불가능해져버렸다..
존경하는 판사님
경찰사이트도 로컬에서만 돌아가는 임시 사이트입니다 ㅠㅠ
# 무무 트위터
무무티비 도메인이 경찰한테 걸려서 벤을 당해버렸다..
때문에 새로운 도메인을 구매해서 무무티비 아이피에 연결해놨는데 이걸 무무티비 클라이언트가 알 방법이 없다..
그래서 트위터를 만들어 새로운 도메인을 알려주기로 했다.
mumutwitter.com으로 접속하니 mumu.com은 detected(저거만들때 스펠링 detact인줄 ㅠ)라고 경찰한테 걸렸다고 뜨고
새로운 도메인 mumu1.com으로 접속하라고 나와있다.
mumu.com으로 접속했을땐 막혔던 무무티비 사이트가 mumu1.com으로 제대로 다시 동작하게 되었다.
뭐 그냥 겉으로 보면 단순하다. 클라이언트가 mumu_twitter로 들어가서 새로운 무무티비의 주소를 확인한다음, 해당 도메인으로 public_dns서버에다가 아이피를 요청하고 받은다음 다시 mumu_web으로 접속 가능하게 된거다.
하지만 매번 무무티비 주소가 벤을 먹을때마다 새로운 도메인이 나왔다고 트윗을 올려줘야하고, 이 텀이 길면 클라이언트들이 불편함을 느껴 새로운 스트리밍 서비스를 찾게될것이다. 때문에 생각한게 자동화...!
존경하는 판사님
트위터도 연습용인 가짜에요..
# 자동화 = 무무티비의 도메인이 벤먹을때마다 자동으로 새로운 도메인을 구매하여 트윗에 올려주기
아까 새로만들어진 mumu1.com또 경찰이 벤을 해버리려고 한다.
몇초후 mumu1.com이 경찰한테 벤이되었다고 떳다.
그리고 곧이어 mumu2.com이 새로운 도메인이라고 떳고
클라이언트는 mumu2.com으로 무무티비에 다시 접속할 수 있게 되었다.
이렇게 새로운 도메인이 생길때 무무티비 운영자인 나는 잠자고 있는중이었다.
이건 자동으로 도메인이 감지된걸 확인하고 바로 새로운 도메인 mumu2.com을 구매하게 한거다.
이 과정은 dns_service의 두 가지 api를 알아봐야하는데, /info요청과, /buy요청이 있다.
# api /info요청
dns_service의 /info api요청 코드는 이와 같다.
app.post("/info", (req, res) => {
const domain = req.body.link;
console.log(`${domain} info 작업 시작`);
const command = `bash info.sh ${domain}`;
exec(command, { encoding: "euc-kr" }, (error, stdout, stderr) => {
if (error) {
console.error(error.message);
console.log(JSON.stringify(stderr));
return;
} else {
const decodedOutput = iconv.decode(stdout, "EUC-KR");
let isDomain = decodedOutput;
if (isDomain !== "false") {
res.json({ ip: decodedOutput });
} else {
res.json({ ip: false });
}
}
});
});
역시 커맨드는 child process로 받았고, info.sh파일을 api post요청으로 받은 도메인을 인자로 넣어서 실행시켜주는거다.
그리고 info.sh파일이 실행되고 echo로 뱉어내는 값을 decodedOutput으로 받아서 조건문으로 응답해주는거다.
info.sh파일의 코드는
#!/bin/bash
domain=$1
domainPath="/var/named/${domain}.zone"
# 도메인 정보 있는지 체크
if [ -e "$domainPath" ]; then
# 아이피 수집
grep=$(cat $domainPath | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
ip=$(echo $grep | cut -d " " -f1)
echo $ip
else
echo "false"
fi
인자로 받은 도메인을 변수로 지정해서 /var/named/도메인.zone파일이 있는지 체크하고 만약에 해당 존파일이 없다면 false를 echo로 뱉어내고, 존파일이 있다면 해당파일 내용을 grep과 정규식으로 잡아서 해당 도메인의 현재 아이피를 응답해주는거다.
이렇게되면 dns_service는 해당도메인이 존재한다면
res.json({ ip: decodedOutput });
으로 해당도메인에 현재 연결된 아이피를 json객체로 보내주게되고
만약 해당도메인자체가 존재하지 않으면
res.json({ ip: false });
으로 ip값은 false로 보내주게된다.
# api /buy요청
app.post("/buy", (req, res) => {
const domain = req.body.link;
const ip = req.body.ip;
const command = `bash buy.sh ${domain} ${ip}`;
console.log(`${ip} customer wants to buy domain "${domain}"`);
exec(command, { encoding: "euc-kr" }, (error, stdout, stderr) => {
if (error) {
console.error(error.message);
console.log(JSON.stringify(stderr));
return;
} else {
//작업
const decodedOutput = iconv.decode(stdout, "EUC-KR");
if (decodedOutput.startsWith("Start")) {
console.log(`${ip} customer has purchase domain "www.${domain}"`);
res.json({ isSold: true });
} else {
res.json({ isSold: false });
}
}
});
});
얘는 api요청에서 두가지 인자를 받는데, 도메인과 아이피를 받는다.
이 두가지 인자를 buy.sh파일에 인자로 넣어서 동작시킨다.
응답내용이 Start로 시작되면 아이피 커스터머가 도메인을 구매했다고 콘솔로 찍어주고 api응답을 isSold : true값을 보내준다.
만약 아니면 구매안됬다는 의미고 api응답을 isSold : false값을 보내주게 된다.
#!/bin/bash
domain="$1"
ip="$2"
named_file_path="/etc/named.conf"
zonePath=/var/named/$domain.zone
zonedomain=$domain.zone
count_row=$(($(cat "$named_file_path" | wc -l) - 2))
echo $count_row
if [ -z "$domain" ] || [ -z "$ip" ]; then
echo "도메인을 제대로 입력하세요. 사용법: $0 <도메인>"
exit 1
fi
result=$(./check.sh $domain)/
echo $result
if [ "$result" == "canbuy/" ]; then
echo "Start to sell $domain to cutomer $ip"
# /etc/named.conf 수정
new_content="zone \"$domain\" IN {type master;file \"$zonedomain\";allow-update { none; };};"
sed -i "${count_row}i\\${new_content}" "$named_file_path"
# zone 파일 생성
cp /var/named/default.zone /var/named/${1}.zone
grep=$(cat $zonePath | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
fake_ip=$(echo $grep | cut -d " " -f1)
sed -i "s/$fake_ip/$ip/g" $zonePath
# 네임서버 재시작
systemctl restart named
elif [ "$result" == "cannot/" ]; then
echo "Customer $ip can not buy www.$domain"
else
echo "Unknown result"
fi
얘가 좀 복잡한데 ㅠㅠ 고생좀함..
일단 도메인을 새로 등록을 해야하기때문에 세가지 과정이 필요하다.
1. /var/named/도메인.zone파일을 생성해줘야함.
2. /etc/named.conf파일의 내용에 존설정 코드를 넣어줘야함.
3. 마지막으로 named 재시작
일단 도메인과 아이피 형식이 올바르지 않으면 코드를 나가버리는 코드를 써줬고,
check.sh파일에 도메인스트링을 인자로 넣어준다음, echo로 뱉는걸 결과값으로 받았다.
check.sh는 아래와 같이 생겼다.
#!/bin/bash
file_path="/etc/named.conf"
search_string=$1
if grep -q "$search_string" "$file_path"; then
echo "cannot"
else
echo "canbuy"
fi
만약에 /etc/named.conf에서 해당 도메인이 존으로 설정되어있다면 이미 도메인이 등록되어있으니 구매할수 없다.
때문에 cannot이라는 스트링을 뱉고,
없다면 도메인이 아직 등록안되어있어서 구매가 가능하니 canbuy라는 스트링을 뱉어준다.
다시 buy.sh파일로 돌아가서 이 check.sh파일이 뱉는값에따라서 if문으로 또 나누어줬다.
다시 이 부분에 해당하는 코드를 자세히 보자면,
if [ "$result" == "canbuy/" ]; then
echo "Start to sell $domain to cutomer $ip"
# /etc/named.conf 수정
new_content="zone \"$domain\" IN {type master;file \"$zonedomain\";allow-update { none; };};"
sed -i "${count_row}i\\${new_content}" "$named_file_path"
# zone 파일 생성
cp /var/named/default.zone /var/named/${1}.zone
grep=$(cat $zonePath | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
fake_ip=$(echo $grep | cut -d " " -f1)
sed -i "s/$fake_ip/$ip/g" $zonePath
# 네임서버 재시작
systemctl restart named
elif [ "$result" == "cannot/" ]; then
echo "Customer $ip can not buy www.$domain"
else
echo "Unknown result"
fi
만약에 구매할수 있는 도메인이면
echo로 start to sell~이라면서 ip머쩌고 커스터머가 domain을 구매가능하다고 뱉어주고 (위에서 api요청코드에서 startwith으로 이걸 받은거임) 그다음 /etc/named.conf의 밑에 아래에서 두번째줄에 new_content스트링을 넣어준다.
그리고 zone 파일을 /var/named/디렉토리에 만들어줘야하는데
저기 코드에서 defalut.zone파일을 복사한다.
내용은 이렇다.
$TTL 3
@ SOA @ root (
0 ; serial
1D ; refresh
1H ; retry
1W ; expire
3H ) ; minimum
IN NS @
IN A 1.1.1.1
www IN A 1.1.1.1
흔한 존파일 양식인데 난 직접 sed로 넣으려면 양식 꾸겨질까봐 걍 default.zone파일 자체를 복사해서 아이피만 정규식으로 찾아 바꾸는 방법을 선택했다. 위 코드에서 1.1.1.1이 아이피 부분으로 들어가는거다.
그리고 최종적으로 systemctl restart named로 도메인서버를 재시작시켜주고 해당 도메인을 적용시켜주는거다.
암튼 이 시발이거 두개만 해도 존나긴데..
# 그럼 이 api요청은 누가하는걸까?
바로 무무트위터의 was단이다.
일단 이 트위터의 was단에는 이런 객체형식으로 원하는 도메인을 저장해놓는다.
const domain_list = {
"mumu.com": "empty",
"mumu1.com": "empty",
"mumu2.com": "empty",
"mumu3.com": "empty",
"mumu4.com": "empty",
"mumu5.com": "empty",
};
이게 원하는 도메인들을 객체의 프로퍼티로 넣어준 목록이고 프로퍼티키는 empty로 다 되어있다.
empty는 아직 구매안한 도메인이라는거다.
위에서 예시로 보여준 상황에선 mumu.com과 mumu1.com은 벤당했고, 현재 mumu2.com이 활성화 되었으니
객체는 이렇게 생겼을 것이다.
const domain_list = {
"mumu.com": "detacted",
"mumu1.com": "detacted",
"mumu2.com": "ok",
"mumu3.com": "empty",
"mumu4.com": "empty",
"mumu5.com": "empty",
};
만약 경찰에 걸린 도메인이라면 프로퍼티값이 detacted로 되고, 현재 활성화된 도메인이라면 ok로 되어있다.
이걸 함수로 mumu_twitter의 was단에서 체크하고 모니터링 하게 해주고 dns_service에 api요청까지 해주게 하는것이다.
mumu_twitter의 was단의 자동화관련 코드는 아래와 같다.
const find_empty_domain = () => {
let empty_domain;
for (let is_empty_domain in domain_list) {
if (domain_list[is_empty_domain] == "empty") {
empty_domain = is_empty_domain;
console.log("Let's buy : " + empty_domain);
return empty_domain;
}
}
};
const is_alive_domain = () => {
for (const domain in domain_list) {
if (domain_list[domain] == "ok") {
return true;
}
}
};
const buy_domain = (wanted_domain) => {
axios
.post("http://211.183.3.53:5000/buy", {
link: wanted_domain,
ip: "211.183.3.150",
})
.then((res) => {
if (res.data.isSold) {
domain_list[wanted_domain] = "ok";
console.log(`${wanted_domain}구매완료`);
} else {
console.log(`${wanted_domain}은 구매할 수 없습니다.`);
}
})
.catch((err) => {
console.error(err.message);
});
};
const check_domain = (domain) => {
axios
.post("http://211.183.3.53:5000/info", { link: domain })
.then((res) => {
const ip = res.data.ip.replace(/[\n\r]/g, "");
if (ip == "211.183.3.112") {
// 리스트에 해당 domain키값 detacted로 바꾸기
domain_list[domain] = "detacted";
// 리스트 순회후 empty인거 찾고 하나 골라서 /buy요청보냄
console.log("detacted!! : " + domain);
if (is_alive_domain()) {
//하나라도 살아있다면
console.log("there is alive domain");
} else {
//다죽었다면
let new_domain = find_empty_domain();
// axios요청
buy_domain(new_domain);
}
} else if (ip == "false") {
// skip
console.log("not yet purchased");
} else if (ip == "211.183.3.150") {
domain_list[domain] = "ok";
// insert domain to domain list
console.log("mine");
} else {
console.log("??");
}
})
.catch((err) => {
console.error(err.message);
});
};
// check_domain(domain);//
const mornitoring = (work) => {
const domains = Object.keys(domain_list);
let currentIndex = 0;
setInterval(() => {
const currentDomain = domains[currentIndex];
// console.log(`Key: ${currentDomain}, Value: ${domain_list[currentDomain]}`);
//작업
work(currentDomain);
//작업
if (currentIndex + 2 == Object.keys(domain_list).length) {
currentIndex = 0;
} else {
currentIndex++;
}
}, 3000);
};
mornitoring(check_domain);
코드가 길어서 하나하나 설명은 좀 어렵고..
걍 간단하게 mornitoring함수는 아까 도메인객체에서 도메인을 3초마다 한번씩 순서대로 순회하게 setinterval함수로 돌려주는거다,.콜백으론 check_domain함수를 콜백으로 넣어줬는데 이 함수는 한번씩 순회하는 도메인을 넣어서 실행되는데, 여기서 dns_service에 api /info요청을 보내는거다.
이걸로 해당 도메인이 경찰한테 걸렸는지 안걸렸는지 확인하고, 만약에 걸렸고, ok인 도메인이 하나도 없다면, 남아있는 도메인중 하나(empty인거)를 가지고 buy_domain요청을 하게 된다. 그럼 여기서 api요청 /buy요청을 보내는데 도메인과 mumu_web의 아이피 211.183.3.150을 같이 객체로 넣어서 보내준다.
그럼 dns_service에서는 도메인을 구입하는 요청을 받은거니까 새로 도메인을 등록해주는거다.
사실 객체로 도메인정보를 넣어주는건 was단에서 server.js코드 업데이트하거나 재시작할때마다 리셋되서 비효율적이라 db에 넣고 하는게 좋은것같은데 걍 귀찮아서 저렇게 했다.
이렇게 동작하는거다..
자세한그림은 뭐 위에 설명한걸로 퉁치자.
따라서 dns_service의 콘솔로 찍히는 로그와 mumu_twitterr의 was단 로그는 이렇게 된다.
dns_service의 로그를 보면 info작업을 계속 받고있고 중간에 '벤작업시작'이라고 떠있다. 경찰이 mumu.com과 mumu1.com을 벤해버린거다.. 또 자세히 보면 몇 초 후에 211.183.3.150 customer wants to buy domain ~이렇게 뜬다. buy요청을 받은거다. mumu_twitter의 was가 다른 도메인을 구매하는 api요청 /buy를 보내므로 211.183.3.150에서 도메인 mumu1.com과 mumu2.com을 구매하고 싶다고 요청이 와서 도메인을 새로 등록해주는거다.
트위터 was단의 로그인데, 중간중간 도메인이 경찰한테 걸렸다고 뜨고 새로 도메인을 구매하는거다. 사실 여기 로그찍는걸 관리를 안했더니 좀 드럽게 잡힌다 ㅠ 암튼 동작함니다..
이렇게 mumuTV는 법망을 피해 운영을 하게 되었습니다..
## 어려웠던점
# child process모듈.
dns_service에서 nodejs로 서버를 하나 띄울때 이 서버단의 server.js코드가 해당 public_dns VM에 어떻게 커맨드를 칠수 있을까 하다가 child process모듈을 알게되었다.
대충 이렇게 생겼는데
const { exec } = require('child_process');
// 'ls' 명령어를 실행하고 결과를 콘솔에 표시합니다.
exec('ls -l -a', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
딱봐도 커맨드 한줄한줄 쓸수록 함수 깊이가 깊어지게 생겼다.
뭐 한줄에 커맨드 여러줄 칠수도 있겠지만 여러 경우의 수를 나누기위해서 이걸 커맨드 한줄한줄 실행되게하면 딱봐도 콜백지옥같이 코드 존~나 깊어질거같아서 난 shell script를 사용하기로 했다.
그냥 쉘스크립트 실행하는 커맨드 한줄만써주면되니까..
사실 이건 위험한 모듈이다. 만약 커맨드 내용을 해커가 개입하게된다면 init커맨드를 실행하게 할수도 있고, 코드 전체를 털어가는 커맨드 명령을 칠수도 있고... 뭐 여러가지 보안상 위험한 모듈이다. js의 eval함수같은 느낌이랄까?
여러 클라우드 서비스가 api로 동작하니 약간 이런느낌이 아닐까 해서 찾아봤고 child process모듈이 있다길래 써봤다.
# shell script
난 이거도 아예몰랐다 ㅠ 그래서 프로젝트 기간 총 5일중 2일을 이거 배우는데 사용했다.. 기초문법 변수, 인수 뭐 if문 for반복문 정도까지 공부하고 급하게 코드짠것같다.
근데 굉장히 흥미로운 분야라서 나중에 책사서 꼭 한번 깊이 보고 싶다.
문법과 신택스같은건 chat gpt도움을 많이 받았다.
특히 어려웠던 점은 buy.sh에서 /etc/named.conf에다가 zone영역 설정해줘야하는 스트링을 넣는거였는데
줄바꿈이 있어서였는지 계속 sh파일에서 못넣더라. gpt도 뭐가 잘못된지 못잡아줌 ㅠㅠ 프로젝트도 급하게 한거고 shell스크립트 기본문법만 찍먹한 수준에서 이걸 해결하기 힘들었다.
그래서 혹시 줄바꿈 없애고 이걸 한줄로 해서 넣어볼까 하고 넣어본다음 dns서버 재시작해보니 도메인 적용되더라!
이 그림에서 일반적인 zone영역 스트링이 1번이고, 2번은 sh파일에서 한줄로 넣는 형식으로 넣은 zone영역정보다.
2번으로 넣어도 정상작동한다!
buy.sh파일에서 new_content변수에 할당한게 저 스트링이다.
# CDN
웹사이트도 굳이 프론트 작업을 깊게안했고 필요한것들만 썼는데 난 라이브러리 많이 갖다 쓴다. axios랑 bootstrap등등 갖다 쓰는데 이게 거의다 CDN으로 동작하는거다.
하지만 문제점은!! client를 mint VM으로 팠는데 기본 도메인서버를 public_dns VM아이피(211.183.3.53)로 설정했어서 public_domain VM이 도메인서버로 동작하게했지만 대신 외부로 도메인으로 통신할수가 없다.
즉 링크들이 제대로 작동안하게된다는얘기..
CDN은 도메인 링크기반으로 라이브러리가 동작하는거라 ㅅㅂ bootstrap이나 axios가 동작하지 않는다 ㅠㅠ
그래서 부트스트랩 대처는 css 걍 직접 대충 코딩했고, axios같은경우는 자스 기본인 fetch를 사용해서 구현했다.
# TTL
/etc/named/도메인.zone파일에 내용은 이렇게 생겼는데
TTL은 dns서버가 캐시를 얼마나 갖고 있는지 정하는거다.
실제로 테스트할때 난 저 ip부분을 바꾸고 도메인서버를 재시작했는데 바뀐 도메인 아이피 정보가 바로바로 적용이 안되는경우가 많았다. 그래서 최대한 짧게 1로 해보고 했는데 안되서 클라이언트 브라우저 캐시를 지워보고 dns서버에서도 캐시를 지우고 별 지랄을 해도 바로 안되는경우가 많았다.
하긴 우리가 도메인서버에서 도메인사더라도 적용되는데 시간이 좀 걸리니까 그러려니 하는데..
그래서 난 직접 초를 재보니까 40~50초 사이에 새로운 도메인 정보가 등록이 되더라..
암튼 어떻게 바로 적용되게 하는지 모르곘음.
## 시연 동영상 원본
Conclusion
무무티비 프로젝트는 소갯글에서도 적어놨듯이, 국비과정에서 토이프로젝트로 제작한 프로젝트구 발표시간때 훈련생분들이 많이 재밋어해주셨어요 ㅋㅋ 불법인 상황과 숨막히는 추격전을 코딩으로 풀어냈어서 재밋어해주신것같습니다.
발표하면서 실제로 노트북 화면을 띄워서 프로세스들을 런타임상황에서 실시간으로 보여드렸는데요~
vm도 많이 만들었고 컨테이너도 열개이상띄워서 중간에 꺼지거 오류생길까봐 조마조마했네요 ㅠㅠ
dns서버에 대한 이해와 도커를 사용해서 서비스를 동작시킬수 있다 등, 추가해서 여러 서버와 클라이언트들의 네트워크 구조등에 대한 이해도를 어필할 수 있었습니다.
지금와서 회고하면 국비과정을 처음갔을때 수업과정이 저에게는 너무 어려운데 다른 훈련생분들은 다들 잘 따라가셔서 정말 기를쓰고 따라가려 헀었습니다 ㅠ 처음에 기도 많이 죽고 제가 많이 부족하다고 생각했었는데요.
토이프로젝트로 무무티비 프로젝트를 발표하니 훈련생분들이 많이들 인정해주신것같아 나름 자신감을 얻게된 계기가된 프로젝트입니다! (실제로 발표끝나고 몇 훈련생분들이 오셔서 막 질문해주셨음ㅎㅎㅎ)
덕분에 스스로에 대한 자신감을 갖게된 프로젝트였었습니다 ㅎㅎ
솔직히 포스팅과 영상제작이 개발보다 더 빡셌네요 ㅋㅋㅋ
읽어주셔서 감사합니다~!