Nodejs Docker 프로젝트 만들고 배포해보기

목차

개발의 목적

이번에 EC2를 이용한 VPN을 해보면서 Docker을 편리함을 많이 느끼게 되었다. 물론 Docker를 처음 다뤄보는 만큼 이미지-컨테이너의 개념이나 명령어에 미숙해 많이 돌아간 것도 있지만 그래도 그러면서 많이 배울 수 있었던 것 같다. Docker에 익숙해질겸 처녀작으로 매주 들어가봤지만 거의 1달 동안 배송에 기약이 없는 LG 물류배송 사이트의 변화를 자동으로 감지해서 콘솔창에 띄워주는 Nodejs Docker 이미지를 만들어보려고 한다.

결론

HTML 분석은 잘되었지만, 생각보다 세세한 부분(Ex. 외부 광고, 구글 애널리틱스)까지 잡아내고 원하는 부분을 추려내는것이 손이 많이가서 실생활에 사용하기에는 무리가 있었다. 그래서 사용하기위한 도구보다는 Docker에 익숙해지는 것에 좀 더 의미를 두고 개발을 진행했다.

프로젝트 빌드가 아닌 Dockerhub에서 바로 내려받고 싶다면 아래 Dockerhub에서 소개된대로 사용하면된다!

DockerHub 링크
Github 링크

출력결과

HTML에서 차이가 있을경우:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 ## docker run -i --rm -e url1="https://www.google.com" -e url2="https://www.google.com" swoho0325/url-compare

HTML fragments are different, changes:
In node html > head:
Removed: <script nonce="PTJ5/ijqSwO4qRiLPqv/jg==">...</script>
In node html > head:
Added: <script nonce="oMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > head:
Removed: <script nonce="PTJ5/ijqSwO4qRiLPqv/jg==">...</script>
In node html > head:
Added: <script nonce="oMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > div#mngb > div#gb:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">window.gbar&&gbar.eli&&gbar.eli()</script>
In node html > body > div#mngb > div#gb > div#gbw > div#gbz > ol#gbzc > li.gbt:nth-of-type(9):
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > div#mngb > div#gb > div#gbw > div#gbz > ol#gbzc > li.gbt:nth-of-type(9) > div#gbd > div#gbmmb > ol#gbmm > li.gbmtc:nth-of-type(10):
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > div#mngb > div#gb > div#gbw > div#gbg > ol.gbtc > li.gbt:nth-of-type(3):
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > div#mngb > div#gb:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">window.gbar&&gbar.elp&&gbar.elp()</script>
In node html > body > center > form > table > tr > td:nth-of-type(2) > div.ds > div:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > center > form > table > tr > td:nth-of-type(2) > span.ds:nth-of-type(2) > span.lsbb:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > center > form > table > tr > td:nth-of-type(2) > span.ds:nth-of-type(2) > span.lsbb:
Modified: <input value="AINFCbYAAAAAYMrB9BxXB6KEXud0enWVnphN1r_VgGiEAINFCbYAAAAAYMrB9Xe4iNzGs_foZDJFJ6VgU7L5zkIV" name="iflsig" type="hidden">
In node html > body > center > form:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body > center > span#footer > div > div#WqQANb:
Modified: <a href="https://www.google.com/setprefdomain?prefdom=KR&amp;prev=https://www.google.co.kr/&amp;sig=K_MhAZ_yydx8JNvgqOAa4cFdzYbWIK_AHT7LVbMGJjag_3ls_Mh1VFZZS0%3D">Google.co.kr</a>
In node html > body:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">...</script>
In node html > body:
Modified: <script nonce="PTJ5/ijqSwO4qRiLPqv/jgoMLWBL4DGH2psoaMW1zr8Q==">(function(){google.xjs={ck:'',cs:'',excm:[],pml:false};})();</script>
In node html > body:
Removed: <script nonce="PTJ5/ijqSwO4qRiLPqv/jg==">...</script>
In node html > body:
Added: <script nonce="oMLWBL4DGH2psoaMW1zr8Q==">...</script>

HTML이 같을 경우:

1
2
3
## docker run -i --rm --name url-compare -e url1="https://ehtls.lge.co.kr/dt/deliveryTracking/DeliveryTrackingDtl.do;jsessionid=68H2hvAfVyy3qr+Utf2ZPTIh.node_htls_13?devStatusType=order&typeAbsence=&originSeqNo=DE435C805CF389C0116EA2053BB75017%7CTALK&talkSendType=R&devStatusNm=%EC%A3%BC%EB%AC%B8%EC%99%84%EB%A3%8C&ordNo=1073011386-1.1.2&originOrdNo=1073011386-1.1.2&originShpSubNo=1&modelSuffixCd=OLED55BXFNA&shpSubNo=1&shpQty=1&delivWishYmd=&arriveExpectTime=&storeFullnm=LG%EC%A0%84%EC%9E%90+%EA%B3%B5%EC%8B%9D%ED%8C%90%EB%A7%A4%EC%A0%90+%EC%8B%A0%EC%98%81&imgPath=%2Ficon-television.png&prodType=TV&ordStatusNm=%EC%A3%BC%EAB%AC%B8%EC%9D%B4+%EC%A0%91%EC%88%98%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4." -e url2="https://ehtls.lge.co.kr/dt/deliveryTracking/DeliveryTrackingDtl.do;jsessionid=68H2hvAfVyy3qr+Utf2ZPTIh.node_htls_13?devStatusType=order&typeAbsence=&originSeqNo=DE435C805CF389C0116EA2053BB75017%7CTALK&talkSendType=R&devStatusNm=%EC%A3%BC%EB%AC%B8%EC%99%84%EB%A3%8C&ordNo=1073011386-1.1.2&originOrdNo=1073011386-1.1.2&originShpSubNo=1&modelSuffixCd=OLED55BXFNA&shpSubNo=1&shpQty=1&delivWishYmd=&arriveExpectTime=&storeFullnm=LG%EC%A0%84%EC%9E%90+%EA%B3%B5%EC%8B%9D%ED%8C%90%EB%A7%A4%EC%A0%90+%EC%8B%A0%EC%98%81&imgPath=%2Ficon-television.png&prodType=TV&ordStatusNm=%EC%A3%BC%EB%AC%B8%EC%9D%B4+%EC%A0%91%EC%88%98%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4." swoho0325/url-compare

No changes found.

개발에 사용할 도구들

Docker

Docker는 기존 sudo npm install **** 처럼 프로그램/모듈을 설치하는 것이 아닌 이미 세팅된 프로그램을 리눅스 컨테이너로 불러와 사용하는 것이다. 그 덕분에 세팅하는 시간도 줄어들고 프로그램을 관리할때도 장점이 많다. 다만 컨테이너별로 IP가 할당되기에 다른 컨터이너와 통신을 하려면 조금 까다라운 부분도 있다. 필자도 Docker 장점을 처음에는 잘 몰랐지만 이번에 개발을 하며 Docker을 편리함을 많이 느낄 수 있었다.

Visual Studio Code

Visual Studio Code는 리눅스 리눅스의 Vim같은 텍스트 에디터지만 Visual studio처럼 인텔리센스와 내장된 터미널이 있다는 것이 큰 장점이다. 다만 자바나 파이썬같은 컴파일러는 언어마다 따로 설치해야한다. Visual Studio Code는 어디까지나 쉽게 코딩하고 디버깅할 수 있게 도와주는 도구지 IDE가 아니다! 필자도 Visual Studio Code의 여러 종류의 플러그인을 같이 사용해서 다재다능하게 사용할 수 있다는 점이 강점이라고 생각한다.

개발 과정

  1. 윈도우에 처음 Docker를 설치한다면 공식 Docker 가이드를 따라 설치해보자

  2. 이제 Visual Studio Code에 Docker 플러그인을 설치하자

  3. 평소 Nodejs 개발처럼 폴더를 만들고 코드를 실행할 환경을 만들어보자. 이번에는 필자의 경우를 예시로 들어보았다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 디렉토리 폴더로 안으로 이동이후!

    cd /Wanted_directory

    # 빈 app.js 파일 생성

    npm init # package.json 파일을 생성한다.

    # package.json에 스크립트는 node app.js로 입력해주자
    # "scripts": {
    # "test": "node app.js"
    # }

    npm install --save request
    npm install --save hiff # package.json에 의존성이 잘 기록되는지 확인하자!
  4. 다음처럼 app.js 파일을 입력해주자

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
     // app.js
    /*
    이 코드는 docker run -e url1="주소" , url2 = "주소"로 주어지는 두개의 URL을 비교하고 결과값을 출력합니다. 원래 목표는 택배등 배송추척할때 추척 사이트에 변화가 생기면 쉽게 알 수 있도록 하는 것이었습니다.
    하지만 광고 등에도 반응하다보니 단순한 HTML 비교용으로 쓰거나 시간마다 사이트의 변화내용을 보는데 사용하는 것이 좋을 것 같습니다.
    */

    var request = require('request');
    var hiff = require('hiff');

    var url1 = process.env.url1; // docker run -e url1에서 변수를 받아옵니다. 주소에 반드시 http(s)://가 포함되어야합니다!
    var url2 = process.env.url2; // docker run -e url2에서 변수를 받아옵니다. 주소에 반드시 http(s)://가 포함되어야합니다!

    // 콜백으로 각각의 주소에서 HTML을 받아옵니다. 원래는 한개로 합치고 싶었지만 에러가 넘 많아서 2개로 나누어서 진행합니다.
    var get_html1 = function(callback) {

    request.get(url1,function(error, response, body){
    var body1 = body;
    return callback(body1);
    });
    };
    var get_html2 = function(callback) {

    request.get(url2,function(error, response, body){
    var body2 = body;
    return callback(body2);
    });
    };

    // 각각의 request에서 받아온 HTML을 비교합니다. 원래는 request에서 각가의 URL의 HTML을 전역변수로 넘겨주는 방식을 쓰고 싶었지만
    // Callback을 잘 못 다뤄서인지, 쉽게 되지않아서 2개의 콜백을 동시에 걸고 비교를 했습니다.
    // url1 -> get_html1 -> body1 / url2 -> get_html2 -> body2

    get_html1(function(body1){

    get_html2(function(body2){

    var result = hiff.compare(body1, body2);

    if (result.different) {
    console.log("HTML fragments are different, changes:");
    result.changes.map(function(change) {
    console.log("In node " + change.before.parentPath + ":\n\t" + change.message);
    });
    } else {
    console.log("No changes found.");
    }
    });
    });

    // 혹시 Callback이 잘 넘어가지 않을때는 아래 콘솔로 찍어내서 확인해보시는 것이 좋습니다.

    /*
    get_html1(function(body1){
    console.log(body1);
    });
    */

    //테스트 명령어: docker run -i --rm -e url1="https://www.google.com" -e url2="https://www.google.com" url-compare
    //위 처럼하면 두개의 url이 다르다고 나오는데 정상입니다!
  5. node app.js 등의 명령으로 잘 작동하는 것을 확인한 후에는 Visual Studio의 커맨드 창에서 Dockerfile 생성을 해보자. F1 누르고 add docker files to workspace를 선택하면된다.

  6. 파일 생성이 완료되면 터미널로 넘어간다. package.json이 의존성이나 스크립트 등이 잘 반영되어있는지 꼭 확인하자!

1
2
3
4
5
6
7
8
# app.js와 package.json이 있는 디렉토리에서! 

docker build -t "이미지 이름" .
docker images # 이미지가 잘 생성되었는지 확인한다.
docker run -i --rm --name "컨테이너 이름" -e url1="https://www.google.com" -e url2="https://www.google.com" "이미지 이름"
# docker rmi "이미지 이름"
# docker rm -f "컨테이너 이름"
# 혹시라도 내용 변경을 위에 새로 빌드해야할때는 위 2개의 명령으로 완전히 지우고 새로 이미지를 만들어보자
  1. 결과창에 출력결과처럼 잘 나오는지 확인해보자

시행착오

Q1: 도커 설치 중에 WSL 설치 미완료라고 오류가 나온다
A1: 위에 공식 Docker 가이드에 커널 업데이트가 같이 있다. 설치 후에 재부팅하고 다시 도커를 설치해보자

Q2: parent … 오류가 나온다
A2: 반드시 http(s)://까지 주소에 붙여야한다! 필자도 그냥 변수명에 포함시켜놓을까 고민했지만 의외로 SSL을 쓰지않는 사이트가 꽤 많아 결국에는 풀 주소로 입력하도록 만들었다.

참고한 자료들과 같이보기 좋은 자료들

  1. https://docs.docker.com/docker-for-windows/install/
  2. https://www.daleseo.com/docker-containers/
  3. https://docs.microsoft.com/ko-kr/azure/developer/javascript/tutorial/tutorial-vscode-docker-node/tutorial-vscode-docker-node-04
  4. https://www.daleseo.com/docker-nodejs/
  5. https://medium.com/@felipedutratine/pass-environment-variables-from-docker-to-my-nodejs-or-golang-app-a1f2ddec31f5
  6. https://subicura.com/2017/02/10/docker-guide-for-beginners-create-image-and-deploy.html
  7. https://2hyes.tistory.com/91