블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

쿠버네티스 #4

아키텍쳐


조대협 (http://bcho.tistory.com)


쿠버네티스에 대한 개념 이해가 끝났으면, 이제 쿠버네티스가 실제로 어떤 구조로 구현이 되어 있는지 아키텍쳐를 살펴보도록 하자. 아키텍쳐를 이용하면 동작 원리를 이해할 수 있기 때문에, 쿠버네티스의 사용법을 이해하는데 도움이 된다.




<그림. 쿠버네티스 아키텍쳐>

출처 https://kubernetes.io/docs/concepts/architecture/

마스터와 노드

쿠버네티스는 크게 마스터(Master)와 노드(Node) 두 개의 컴포넌트로 분리된다.

마스터는 쿠버네티스의 설정 환경을 저장하고 전체 클러스터를 관리하는 역할을 맏고있고, 노드는 파드나 컨테이너 처럼 쿠버네티스 위에서 동작하는 워크로드를 호스팅하는 역할을 한다.

마스터

쿠버네티스 클러스터 전체를 컨트럴 하는 시스템으로, 크게 다음과 API 서버, 스케쥴러, 컨트롤러 매니져, etcd 로 구성되어 있다.


API 서버

쿠버네티스는 모든 명령과 통신을 API를 통해서 하는데, 그 중심이 되는 서버가 API서버이다.

쿠버네티스의 모든 기능들을 REST API로 제공하고 그에 대한 명령을 처리한다.

Etcd

API 서버가 명령을 주고 받는 서버라면, 쿠버네티스 클러스터의 데이타 베이스 역할이 되는 서버로 설정값이나 클러스터의 상태를 저장하는 서버이다.  etcd라는 분산형 키/밸류 스토어 오픈소스 ()https://github.com/coreos/etcd) 로 쿠버네티스 클러스터의 상태나 설정 정보를 저장한다.

스케쥴러

스케쥴러는 Pod,서비스등 각 리소스들을 적절한 노드에 할당하는 역할을 한다.

컨트롤러 매니져

컨트롤러 매니저는 컨트롤러(Replica controller, Service controller, Volume Controller, Node controller 등)를 생성하고 이를 각 노드에 배포하며 이를 관리하는 역할을 한다.


DNS

그림에는 빠져있는데, 쿠버네티스는 리소스의 엔드포인트(Endpoint)를 DNS로 맵핑하고 관리한다. Pod나 서비스등은 IP를 배정받는데, 동적으로 생성되는 리소스이기 때문에 그 IP 주소가 그때마다 변경이 되기 때문에, 그 리소스에 대한 위치 정보가 필요한데, 이러한 패턴을 Service discovery 패턴이라고 하는데, 쿠버네티스에서는 이를 내부 DNS서버를 두는 방식으로 해결하였다.

새로운 리소스가 생기면, 그 리소스에 대한 IP와 DNS 이름을 등록하여, DNS 이름을 기반으로 리소스에 접근할 수 있도록 한다.

노드

노드는 마스터에 의해 명령을 받고 실제 워크로드를 생성하여 서비스 하는 컴포넌트이다.

노드에는 Kubelet, Kube-proxy,cAdvisor 그리고 컨테이너 런타임이 배포된다.

Kubelet

노드에 배포되는 에이전트로, 마스터의 API서버와 통신을 하면서, 노드가 수행해야 할 명령을 받아서 수행하고, 반대로 노드의 상태등을 마스터로 전달하는 역할을 한다.

Kube-proxy

노드로 들어오거는 네트워크 트래픽을 적절한 컨테이너로 라우팅하고, 로드밸런싱등 노드로 들어오고 나가는 네트워크 트래픽을 프록시하고, 노드와 마스터간의 네트워크 통신을 관리한다.

Container runtime (컨테이너 런타임)

Pod를 통해서 배포된 컨테이너를 실행하는 컨테이너 런타임이다. 컨테이너 런타임은 보통 도커 컨테이너를 생각하기 쉬운데, 도커 이외에도 rkt (보안이 강화된 컨테이너), Hyper container 등 다양한 런타임이 있다.

cAdvisor

cAdvisor는 각 노드에서 기동되는 모니터링 에이전트로, 노드내에서 가동되는 컨테이너들의 상태와 성능등의 정보를 수집하여, 마스터 서버의 API 서버로 전달한다.

이 데이타들은 주로 모니터링을 위해서 사용된다.


전체적인 구조 자체는 복잡하지 않다. 모듈화가 되어 있고, 기능 확장을 위해서 플러그인을 설치할 수 있는 구조로 되어 있다. 예를 들어 나중에 설명하겠지만 모니터링 정보를 저장하는 데이타베이스로는 많이 사용되는 Influx 데이타 베이스 또는 Prometheus 와 같은 데이타 베이스를 선택해서 설치할 수 있고 또는 커스텀 인터페이스 개발을 통해서, 알맞은 저장소를 개발하여 연결이 가능하다.






Google 앱앤진에 node.js 애플리케이션을 배포하기


조대협 (http://bcho.tistory.com)

PaaS 서비스란?

PaaS란 Platform as a service의 약자로, 간단하게 설명하면, Linux VM등에 직접 node.js나 기반 환경을 설치하고 디스크와 네트워크 구성, 오토스케일링등의 구성이 필요 없이, 개발자가 작성한 코드만 올려주면, 이 모든 것을 클라우드에서 대행해주는 서비스의 형태이다.

인프라 운영을 위한 별도의 작업이 필요하지 않기 때문에, 숫자가 적은 기업이나 개발에만 집중하고 싶은 기업에는 적절한 모델이라고 볼 수 있다.

근래에 스타트업 비지니스가 발달하고 또한 사용하는 기술 스택들이 복잡해짐에 따라 각각의 기술 스택에 대한 설치나 운영에 대한 인력을 두기보다는 PaaS와 같은 매니지드 서비스를 이용해서 애플리케이션을 개발하는  방식이 스타트업을 중심으로 많이 이루어지고 있다.

구글 클라우드 소개

구글 클라우드는 전세계에 걸쳐 유투브와 구글 검색, 이메일등의 서비스를 하는 구글의 인프라의 경험을 구글 외부의 개발자들에게 개방하기 위해서 개발된 클라우드 서비스이다.

글로벌을 대상으로 서비스를 하던 경험을 기반으로 글로벌 그리고 대용량 서비스에 적절한 특징들을 많이 가지고 있는데, 그 중에서 눈여겨 볼만한것이 네트워크와 빅데이타 서비스이다.

네트워크

구글은 자체 서비스를 위해서 전세계에 걸쳐서 많은 네트워크 망을 가지고 있다. 특히 전세계에 걸쳐 70개 이상의 Pop (Point of Presence)라는 서버들을 보유하고 있는데, 구글 클라우드에서 동작하는 애플리케이션은 클라이언트가 직접 그 데이타 센터의 서버로 인터넷을 통해서 접속하는 것이 아니라, 먼저 가장 가까운 Pop 서버로 연결이 된 후, Pop 서버와 구글 클라우드 데이타 센터간은 구글의 전용 광케이블을 이용해서 접속이 되기 때문에 빠른 네트워크 성능을 보장한다.

예를 들어 한국에서 미국의 서버를 접속하게 되면 일반 인터넷망을 타는 것이 아니라, 가까운 일본에 있는 구글 Pop 서버를 접속한 후에, 구글 광케이블망을 통해서 미국 데이타 센터에 접속하게 된다. 얼마전 7월에 일본-미국을 연결하는 20TB의 구글 클라우드 전용망이 오픈되었기 때문에, 훨씬 더 빠른 접속 속도를 보장받을 수 있다.

이런 특징 때문에, 구글 클라우드를 사용할 경우 여러 국가에 서비스를 제공하는 글로벌 서비스의 경우 이러한 망 가속의 잇점을 볼 수 있다.


또한 내부 네트워크 역시, 1 CPU당 2 GB의 대역폭을 제공한다. (실제 테스트해보면 1.6~1.8 GB정도가 나온다) 최대 8 CPU에 16 GB의 대역폭 까지 지원하기 때문에, 내부 노드간 연산이 많거나 또는 노드간 통신이 많은 클러스터링 솔루션을 사용할때 별도의 비용을 지불하지 않고도 넉넉한 네트워크 대역폭 사용이 가능하다.

빅데이타

구글은 사람들에게 알려진바와 같이 데이타를 기반으로 한 서비스에 강하며 특히 빅데이타를 처리하는 능력이 뛰어난 회사이다. 이러한 빅데이타 처리 기술이 클라우드 서비스로 제공되는데, 알파고와 같은 예측 통계학적인 머신러닝이나 딥러닝 이외에도, 분석 통계학적인 데이타 분석 서비스가 많은데 그중에서 특출난 서비스들로 Pub/Sub, Dataflow, BigQuery 등이 있다.

Pub/Sub은 전달 보장이 가능한 대용량 큐 시스템이다. 오픈소스 Kafka와 같은 서비스라고 보면 되고, 이러한 분산큐를 직접 깔아서 운영하기 어려운 사람들에게 쉽게 대용량 큐 서비스를 제공한다.

Dataflow는 스트리밍 및 배치 분석 시스템인데, Spark Streaming이나 Apache Flink와 같은 스트리밍 프레임웍과 유사하다고 보면된다. 윈도우,트리거,워터마크와 같은 스트리밍 서비스에서 발전된 개념을 이미 내장하고 있다.

마지막으로 BigQuery는 대용량 데이타 저장/분석 시스템으로 SQL과 같은 문법으로 데이타 분석이 가능하며, 데이타 저장 비용은 구글에서도 가장 싸다는 데이타 저장소인 CloudStorage 보다 저렴하며, 1000억개, 4TB의 레코드에 대해서 like 검색을 하는데 소요되는 시간이 불과 30여초로, 빠른 성능을 보장한다. (이 30초 동안에 CPU가 수천개, DISK역시 수천개가 사용된다.)


구글 클라우드 앱앤진

오늘 살펴볼 서비스는 구글 클라우드 중에서도 앱앤진이라는 PaaS 서비스이다. 구글은 처음 클라우드 서비스를 시작할때 PaaS 기반의 서비스를 제공하였고, 내부적으로도 PaaS 서비스를 오랫동안 제공해온 만큼, 진보된 형태의 PaaS 플랫폼을 가지고 있다.

그중에서 이번에는 새롭게 앱앤진 Flex environment 라는 새로운 서비스를 소개 하였다.

Flex environment에는 기존의 Java,PHP뿐 아니라, 요즘 스타트업등에서 많이 사용되는 Python, Ruby on rails 그리고 node.js를 지원한다.

그리고 재미있는 점 중의 하나는 일반적인 PaaS가 VM에 대한 로그인 (telnet 이나 SSH)를 지원하지 않는데 반하여, Flex environment 는 SSH 로그인을 제공함으로써, 고급 사용자들에게 많은 기능과 디버깅에 있어서 편의성을 제공한다.

계정 가입

자아 그러면 이제, 구글 앱앤진 Flex environment를 이용하여, node.js 애플리케이션을 배포해보도록 하자.

먼저 GCP 클라우드를 사용하기 위해서는 구글 계정에 가입한다. 기존에 gmail 계정이 있으면 gmail 계정을 사용하면 된다. http://www.google.com/cloud 로 가서, 좌측 상당에 Try it Free 버튼을 눌러서 구글 클라우드에 가입한다.




다음 콘솔에서 상단의 Google Cloud Platform 을 누르면 좌측에 메뉴가 나타나는데, 메뉴 중에서 “결제" 메뉴를 선택한후 결제 계정 추가를 통해서 개인 신용 카드 정보를 등록한다.


개인 신용 카드 정보를 등록해야 모든 서비스를 제한 없이 사용할 수 있다.  단 Trial의 경우 자동으로 한달간 300$의 비용을 사용할 수 있는 크레딧이 자동으로 등록되니, 이 범위를 넘지 않으면 자동으로 결제가 되는 일이 없으니 크게 걱정할 필요는 없다. (사용자가 Plan을 올려야 실제 카드에서 결재가 된다. 그전에는 사용자의 카드에서 결재가 되지 않으니 걱정하지 마시기를)


프로젝트 생성

계정 생성 및 결제 계정 세팅이 끝났으면 프로젝트를 생성한다.

프로젝트는 VM이나 네트워크 자원, SQL등 클라우드 내의 자원을 묶어서 관리하는 하나의 집합이다. 여러 사람이 하나의 클라우드를 사용할때 이렇게 프로젝트를 별도로 만들어서 별도로 과금을 하거나 각 시스템이나 팀별로 프로젝트를 나눠서 정의하면 관리하기가 용이하다.


화면 우측 상단에서 프로젝트 생성 메뉴를  선택하여 프로젝트를 생성한다.



프로젝트 생성 버튼을 누르면 아래와 같이 프로젝트 명을 입력 받는 창이 나온다. 여기에 프로젝트명을 넣으면 된다.


node.js 애플리케이션 준비

클라우드 프로젝트가 준비되었으면, 이제 구글 클라우드에 배포할 node.js 애플리케이션을 준비해보자.

Node.js 애플리케이션은 Express 프레임웍을 이용한 간단한 웹 애플리케이션을 작성해서 배포하도록 한다.

기본적으로 nodejs와 npm이 설치되어 있다고 가정한다. 이 글에서는 node.js 4.4.4 버전과 npm 2.15.1 버전을 기준으로 작성하였다.

Express generator 설치

Express 프로젝트는 Express 프로젝트 고유의 디렉토리 구조와 의존되는 파일들을 가지고 있다. 그래서 프로젝트를 생성하려면 Express generator가 필요하다. Express generator가 Express 프로젝트에 맞는 프로젝트 구조를 생성해주는데, Express generator를 설치하려면 아래 그림과 같이

% npm install express-generator -g

명령을 사용하면 설치가 된다.



프로젝트 생성

Express generator 설치가 끝났으면 이제, express 프로젝트를 생성해보자.

다음 명령어를 이용하여 helloappengine이라는 Express 프로젝트를 생성한다.

% express --session --ejs --css stylus helloappengine




Express 프로젝트 및 Express 프레임웍에 대해서는 http://bcho.tistory.com/887 http://bcho.tistory.com/888 글을 참고하기 바란다.

코드 수정

생성된 코드에서  ~/routes/index.js 파일을 다음과 같이 수정한다.


var express = require('express');

var router = express.Router();


/* GET home page. */

router.get('/', function(req, res, next) {

 res.render('index', { title: 'Hello Appengine' });

 console.log('Hello Appengine');

});


module.exports = router;


의존성 모듈 설치와 node.js 런타임 버전 정의

Express 애플리케이션 개발이 다 끝났다. Express 애플리케이션을 실행하려면, Express 프로젝트에 필요한 모듈들을 설치해야 하는데, 의존 모듈들은 ~/package.json에 이미 자동으로 생성되어 있다.

이 파일을 다음과 같이 수정한다.

“engines”라는 항목에 node.js의 버전을 아래와 같이 명기하는데, 이는 구글 앱앤진이 이 애플리케이션을 수행할때 어느 버전의 node.js를 가지고 수행을 할지를 지정한다.


{

 "name": "helloappengine",

 "version": "0.0.0",

 "private": true,

 "scripts": {

   "start": "node ./bin/www"

 },

 "engines":{

    "node":"4.4.4"

 },

 "dependencies": {

   "body-parser": "~1.15.1",

   "cookie-parser": "~1.4.3",

   "debug": "~2.2.0",

   "ejs": "~2.4.1",

   "express": "~4.13.4",

   "morgan": "~1.7.0",

   "serve-favicon": "~2.3.0",

   "stylus": "0.54.5"

 }

}




이 의존성 모듈 설치는 package.json 파일이 있는 디렉토리에서 아래와 같이

% npm install

명령어만 실행해주면 자동으로 설치된다.


테스트

샘플 애플리케이션 개발 및, 이를 실행하기 위한 환경 설정이 모두 끝났다.

그러면 이제 샘플 애플리케이션을 로컬에서 실행해보자.

실행은 ~/ 디렉토리에서 다음과 같은 명령을 실행하면된다.

% node ./bin/www



실행한 후, 결과를 확인하려면 http://localhost:3000 번으로 접속하면 아래와 같은 결과가 나오는 것을 확인할 수 있다.



구글 앱앤진으로 배포 준비

애플리케이션 개발 및 테스트가 끝났으면 이제 구글 앱앤진으로 이 애플리케이션을 배포해보자.

app.yaml 파일 작성

구글 앱앤진 Flex environment 로 애플리케이션을 배포하기 위해서는 애플리케이션에 대한 기본 정보를 app.yaml에 정의해야 한다. 이 파일은 배포할 node.js 애플리케이션이 있는 ~/ 디렉토리에 위치 시킨다.

runtime은 어떤 언어 (node.js , ruby 등)을 지정하고, VM으로 실행할지를 vm:true 로 정의한다.


runtime: nodejs
vm: true


Google Cloud SDK 설치

다음으로 배포를 하려면, gcloud라는 명령을 사용해야 하는데, 이 명령어는 Google Cloud SDK를 설치해야 사용할 수 있다. Google Cloud SDK는 https://cloud.google.com/sdk/downloads 에서 다운 받아서 설치한다.

Google cloud SDK 설정하기

Google cloud SDK 설치가 끝났으면, 혹시 google cloud SDK가 업데이트 되었을 수 있으니, 다음 명령어를 이용하여 최신 SDK로 업데이트 한다. (알파,베타 버전과 같은 신 기능이 들어가면 업데이트가 된다.)


% gcloud component update


라는 명령어를 수행하면 다음과 같이 업데이트 된 내용이 나오면서 업데이트 를 할 것인지 물어보고 “Y”를 선택하면 자동으로 Google Cloud SDK를 업데이트 한다.



Google Cloud SDK 배포가 끝났으면 gcloud 명령을 이용하기 위해서 초기화 작업을 해야 한다. 초기화 작업은 gcloud 명령을 사용했을때, 내 구글 클라우드 프로젝트 중 어느 프로젝트에 명령을 내릴 것인지, 그리고 어떠한 구글 계정을 사용할것인지 그리고 명령을 내릴때 어떤 리전을 디폴트로 해서 명령을 내릴것인지를 정하는 것이다. 사용자에 따라서 여러개의 프로젝트를 가질 수 도 있고, 또한 관리 목적상 여러개의 클라우드 계정을 가질 수 도 있기 때문이다.

초기화 방법은 다음과 같이 명령어를 실행하면 된다

% gcloud init

이 명령을 사용하면, 앞에서 언급한 바와 같이, gcloud 명령을 내릴 때 사용할 계정과, 디폴트 프로젝트 그리고, 리전을 선택하게 된다.


이제 배포를 위한 모든 준비가 끝났다.

구글 앱앤진으로 배포

이제 배포를 해보자. 배포는 매우매우 쉽다.

앞서 작성한 Express 애플리케이션이 있는 ~/ 디렉토리에 가서 다음 명령어를 수행하면 된다.

% gcloud app deploy

명령을 실행하면 다음과 같이 node.js 애플리케이션이 배포 되는 과정을 볼 수 있다.

배포는 수분이 소요된다. (내부적으로는 Docker 컨테이너를 이용하여 배포가 된다.)

서비스 기동확인

배포가 완료되면 자동으로 서비스가 기동이 된다.

서비스 기동 확인은 http:{프로젝트명}.appspot.com 으로 자동으로 서비스가 뜨게 된다.

해당 URL을 접속해보자. 여기서는 프로젝트명이 “useful-hour-138023”이다.


서비스가 정상적으로 기동 됨을 확인하였다.

모니터링

구글 클라우드 콘솔에 들어가 보면 현재 기동중인 node.js 앱을 확인할 수 있다.

콘솔에서 “App Engine”을 선택하면 다음과 같은 화면이 나온다.



아래 Instances 부분을 보면 Flexible 이라는 이름으로 현재 2개의 인스턴스가 기동되고 있고, 평균 QPS (Query Per Second : 초당 처리량)이 10.898 인것을 확인할 수 있다.


매니지드 서비스이기 때문에, 부하가 늘어나면 자동으로 인스턴스를 추가하고 줄어들면 자동으로 삭제하여 부하에 탄력적으로 대응을 자동으로 해준다.


로그 모니터링은 클라우드 콘솔에서 “Logging”이라는 메뉴를 선택하면 아래와 같은 화면이 나온다.


앱앤진 관련 request log 및 기타 로그들이 다 나오는데, 앞서 샘플코드에서 console.log(“Hello Appengine”) 명령어를 이용하여 Stdout으로 “Hello Appengine”이라는 문자열을 출력하도록 하였기 때문에, 이 문자열이 제대로 출력되었는지를 확인해보자.

App Engine 을 선택하고, 다음 “stdout”을 선택하면 위와 같이 앱앤진의 Stdout으로 출력한 로그들만 볼 수 있는데, 위와 같이 “Hello Appengine” 문자열이 출력된것을 확인할 수 있다.

수정 및 재 배포

그러면 다음으로 애플리케이션을 수정해서 배포해보자. routes/index.js를 다음과 같이 수정해보자


var express = require('express');

var router = express.Router();


/* GET home page. */

router.get('/', function(req, res, next) {

 res.render('index', { title: 'Hello Appengine is updated' });

 console.log('Hello Appengine is updated');

});


module.exports = router;



코드 수정이 끝났으면 배포를 해보자. 재 배포 역시 간단하게

%gcloud app deploy

명령어를 수행하면 간단하게 배포가 된다.

배포가 완료된 후 URL로 들어가서 확인을 해보면


애플리케이션이 업데이트 된것을 확인할 수 있다.

롤백

앱앤진의 장점 중에 하나가 배포도 쉽지만, 롤백도 매우 쉽게 가능하다는 것이다.

클라우드 콘솔에서 App Engine을 선택하고, 아래와 같이 Versions라는 메뉴를 선택하면 아래와 같은 그림을 볼 수 있다.




현재 두개의 버전이 배포되어 있고, 위의 최신 버전에 Traffic Allocation이 100%로 되어 있는 것을 확인할 수 있다. 이는 새로 배포된 버전으로만 트래픽이 100% 가고 있다는 의미인데, 롤백은 트래픽을 예전 버전으로 라우팅 해주고, 새버전의 서비스를 정지 시키면 된다.


아래 그림과 같이 예전 버전을 선택한 후에, 상단 메뉴에서 “Migrate Traffic”을 선택한다.



그러면 아래와 같이 트래픽이 이전 버전으로 100% 가고 있음을 확인할 수 있다.

그리고 나서 새 버전을 선택한 후 상단의 STOP 버튼을 눌러주면 아래 그림과 같이 새버전의 상태가 Serving 에서 Stopped로 변경된것을 확인할 수 있다.




예전 버전으로 롤백이 잘 되었는지를 확인해보자.

아래 그림처럼 예전 버전으로 롤백이 되었음을 확인할 수 있다.



버전별로 트래픽 분산하기

앱앤진의 장점 중의 하나가 여러 버전을 유지할 수 있고, 버전간에 트래픽을 자유롭게 조절할 수 있다. 예를 들어서 v1으로 90%, v2로 10% 의 트래픽을 보내는 것등이 가능하다.


이렇게 트래픽을 조절하는 것은 크게 2가지 방법으로 활용이 가능한데, 서버단의 A/B 테스팅과 카날리 테스팅이다.

A/B 테스팅은 사용자를 두개 이상의 집단으로 나눈후, 기능 A,B 에 대한 반응을 본 후, 반응이 좋은 기능을 선택하는 방식으로, 모바일 클라이언트단에서 많이 사용되고, 서버단에는 구현이 쉽지 않았는데, 트래픽을 버전별로 나눠서 주는 기능을 사용하면 손쉽게 A/B 테스팅을 수행할 수 있다.


다음으로 카날리 테스트는 옛날에 광부들이 광산에 들어갈때 카나리아 새를 데리고 들어가서 유독가스가 나오면 카나리아 새가 먼저 죽는 것을 보고 위험을 감지하는 방법에서 유래된것으로 서버 배포에서는 전체 사용자를 대상으로 새 버전을 배포하는 것이 아니라 1~2%의 사용자에게 배포해보고 문제가 없으면 전체 사용자에게 배포하는 개념으로, 마찬가지로 v2에 1~2%의 트래픽만 할당하고 나머지 98~99%는 v1에 할당하는 방식으로, 해서 v2에 대한 안정성 검증을 한 후에, v2를 전체 배포 하는데 활용할 수 있다.


버전간 트래픽 비율을 지정하는 방법은 클라우드 콘솔의 앱앤진 메뉴에서 Version을 선택하고 상단 메뉴에서 → 가 두개 겹쳐진 아이콘 (맨 우측)

를 선택하면 아래와 같이 Split traffic 이라는 메뉴가 나온다.

여기서 1개 이상의 Version을 추가한 후에, 각 버전으로 보낼 트래픽의 비중 (%)을 정의한다.






위의 예제에서는 xxx4403 버전으로 30%, xxxx3136 버전으로 70%의 트래픽을 보내도록 하였다.

설정을 완료한 후에 SAVE를 누르고, 다시 Version 부분을 보면, 수분 있다가 아래 그림과 같이 xxxx3136 버전에 70% 트래픽이 xxx4403 버전으로 가게 된다.


부가 기능에 대해서

구글 클라우드에서 앱앤진만 쓸 수 있는 몇가지 좋은 서비스 들이 있는데, 다음과 같다.

매니지드 memcached

앱앤진에서는 구글 매니지드 memcached 서비스를 사용할 수 있다 별도의 설정 없이 바로 사용이 가능하며, 최대 100GB 까지 사용이 가능하다. (100GB 이상도 요청하면 사용이 가능) Shared memcached의 경우에는 별도의 비용없이 일반적인 캐쉬로 사용이 가능하지만, 용량이나 성능등이 보장이 되지 않으니 주의가 필요하다.

https://cloud.google.com/appengine/docs/python/memcache/

보안 스캐닝 (Security Scanning)

매우 유용한 기능중의 하나가 보안 스캐닝 기능이다 앱앤진 메뉴에서 Security Scanning이라는 메뉴를 선택하면 앱앤진으로 개발한 웹 사이트에 대한 보안 스캐닝을 할 수 있는데, 한번만 스캐닝할 수 도 있고 자동 스케쥴 방식으로, 주기적으로 스캐닝 하는 것도 가능하다.


보안 정책이 수시로 업데이트 되기 때문에, 정기적으로 보안 스캔을 하는 것을 권장한다.

텍스트 검색 (Search)

node.js에는 지원하지 않지만, Python,Java,PHP,Go (앱앤진 Standard environment)에서만 지원되는 기능중 하나는 구글 검색 엔진과 같은 검색 엔진을 지원한다. 텍스트 검색 HTML 그리고 GEO_POINT (위/경도 기반 검색)이 가능하다.

https://cloud.google.com/appengine/docs/java/search/?hl=en_US

결론

지금까지 간략하게나마 구글 클라우드에 대한 소개, 앱앤진에 대한 개념 및, 간단한 node.js express 애플리케이션을 작성해서 배포해봤다. 다음글은 앱앤진에 대한 조금 더 구체적인 환경에 대해서 알아보도록 하겠다.


구글 클라우드의 대용량 분산 큐 서비스인 Pub/Sub 소개 #2


node.js를 통하여 메세지를 보내고 받기

조대협 (http://bcho.tistory.com)


node.js에서 메세지 보내고 받기


이번 글에서는 node.js를 이용하여 실제로 pub/sub에 메세지를 보내고 받도록 해보자


키 파일 준비 하기

Pub/Sub에 접속하기 위해서는 보안 인증을 위해서 키 파일이 필요하다.

키 파일은 구글 클라우드 콘솔에서, API manager 메뉴로 들어가서 Credential 부분에서 Create Credential을 선택하면 아래와 같은 화면이 나온다.

다음으로, 메뉴에서 Service account key를 선택하여 키를 생성한다.


키가 생성이 되면 json 파일로 다운로드가 된다.

여기서는 편의상 키 파일명을 “pubsub-key.json”이라고 하겠다.


메세지 보내기

node.js를 이용해서 메세지를 보내보자. 먼저 코드를 보자


var gcloud = require('gcloud');

var pubsub = gcloud.pubsub({

projectId:'terrycho-sandbox',

keyFilename: '/Users/terrycho/keys/pubsub-key.json'

});


var topic = pubsub.topic('projects/terrycho-sandbox/topics/repository-changes.default');


for(var i=0;i<3;i++){

topic.publish({

data:{

userId:process.argv[2]+i,

name:'terry.cho'

}

},function(err){

if(err != null) console.log("Error :"+err);

});

};


pub/sub을 사용하려면 구글 클라우드 라이브러리인 gcloud 모듈이 필요하다.

명령어 창에서 npm install gcloud를 이용해서, gcloud모듈을 먼저 인스톨 해놓자.

다음으로, gcloud라이브러리에서 pubsub 객체를 만든다. 여기서는 projectId와, keyFilename을 지정한다.

projectId는 사용하고자 하는 본인의 구글 프로젝트 ID를 넣으면 되고 (여기서는 ‘terrycho-sandbox’), 키 파일은 앞에서 준비한 키 JSON 파일의 경로를 설정하면 된다.


pubsub 객체가 생성되었으면, 메세지를 보낼 topic을 가져와야 한다. topic은 다음과 같은 pubsub.topic메서드를 이용해서 불러올 수 있다. 이때, topic의 경로를 아래와 같이 적어준다.

var topic = pubsub.topic('projects/terrycho-sandbox/topics/repository-changes.default');

여기서 사용한 topic은 projects/terrycho-sandbox/topics/repository-changes.default 이다.

topic을 받아왔으면, 이 topic에 실제로 메세지를 publish하면 되는데, topic.publish( {메세지},{error callback 함수}); 형태로 지정하면 된다.


pub/sub은 앞의 글에서 설명한바와 같이, message와, message attribute 두가지로 분리가 되는데,

message는

data :{

// 여기에 메세지 정의

}


형태로 정의해서 전달하고,message attribute는

attributes:{

  key1:’value1’,

  key2:’value2’
}


형태로 전달한다.

실제 사용예를 보면 다음과 같다.

var registerMessage = {
 data: {
   userId: 3,
   name: 'Stephen',
   event: 'new user'
 },
 attributes: {
   key: 'value',
   hello: 'world'
 }
};


위의 보내기 예제에서는 userId와, name 필드 두개만, 메세지로 3번 보내도록 하였다.

data:{

userId:process.argv[2]+i,

name:'terry.cho'

}

메세지 받기

메세지를 전달하였으면 이제 메세지를 읽어보도록 한다. 전체 코드는 다음과 같다.


var gcloud = require('gcloud');

var pubsub = gcloud.pubsub({

projectId:'terrycho-sandbox',

keyFilename: '/Users/terrycho/keys/pubsub-key.json'

});


var topic = pubsub.topic('projects/terrycho-sandbox/topics/repository-changes.default');

var options = {

 reuseExisting: true, // if the subscription is already exist reuse subscription, option is not changed

 interval:10,

 maxInProgress:5,

 autoAck:false

};


topic.subscribe('nodejs-subscription',options,function(err,subscription,apiResponse){

if(err != null){

console.log('subscription creation failed :'+err);

exit(1);

}

console.log('Subscription :'+subscription);

subscription.on('error',function(err){

console.log('error:'+err);

});


subscription.on('message',function(message){

// read message from queue

console.log(message);

// send ack

subscription.ack(message.ackId,function(err,apiResponse){

console.log('info:sent ack');

});

});

});

topic을 생성하는 것까지는 앞의 메세지 보내기 부분의 코드와 같다.

topic으로 부터 메세지를 받기 위해서, subscription에 대한 옵션을 설정한다.


var options = {

 reuseExisting: true,

 interval:10,

 maxInProgress:5,

 autoAck:false

};

reuseExisting은 별도로 subscription을 생성하지 않고, 기존의 subscription을 사용한다. 이 경우에는, 기존 subscription의 옵션이 그대로 적용되며, 새로운 option이 적용되지 않는다.

interval은 10초 단위로 subscription을 polling 하는 것이고, maxInProgress는 한번에 읽어올 수 있는 메세지 수를 정의한다.

autoAck는 메세지를 보낸 후에, 자동으로 ack를 보내는 옵션인데, 여기서는 false로 하였기 때문에, 수동으로 ack를 보내야 한다.


옵션을 정의하였으면 아래와 같이 topic에 대한 subscription에 대해서, 메세지를 subscription 한다.

topic.subscribe('nodejs-subscription',options,function(err,subscription,apiResponse){

‘nodejs-subscription’은 subscription 이름이고, options는 subscription에 대한 옵션 그리고 마지막은 콜백함수 있다.

메세지를 받았을때 처리 방법을 정의해야 하는데, 앞의 콜백 함수에서 전달되는 subscription객체에 “message”라는 이벤트에 대해서 핸들러를 작성하면 메세지를 받을 수 있다.


아래는 핸들러 코드이다.

subscription.on('message',function(message){

// read message from queue

console.log(message);

// send ack

subscription.ack(message.ackId,function(err,apiResponse){

console.log('info:sent ack');

});

});


메세지가 들어오면 console에 메세지를 출력하고, subscription.ack를 이용하여 ack를 보낸다. 이때, 메세지에서 들어오는 message.ackId를 인자로 하여, 그 메세지에 대해서 ack를 보낸다.


다음은 명령어를 실행하여 메세지를 보내는 실행화면이다.


그리고 다음은 메세지를 받는 프로그램을 수행하여 실제로 메세지를 받은 결과 화면이다.




참고

  • node.js pub/sub 라이브러리 레퍼런스 https://googlecloudplatform.github.io/gcloud-node/#/docs/v0.36.0/pubsub


빠르게 훝어보는 node.js

#14 - Passport를 이용한 사용자 인증 구축

조대협 (http://bcho.tistory.com)


Passport node.js용 범용 인증 모듈이다. 기본적인 HTTP Basic Auth에서 부터 HTTP digest authentication, OAuth,OpenID 등 다양한 인증 프로토콜을 지원하며, Facebook이나 Twitter,Google등과의 연계된 SSO 인증을 포함하여 약 140가지의 인증 모듈을 포함한다. 그러면Passport를 이용하여, 어떻게 사용자 인증을 구현할 수 있는지 살펴보도록 하자.

이러한 다양한 인증에 대해서 미리 구현해 놓은 것을 passport에서는 Strategy라고 한다. Facebook 인증을 위해서는 facebook strategy Twitter 인증을 위해서는 Twitter Strategy를 사용한다.

Local Strategy

먼저 id,password를 받아서 자체 인증 처리를 하는 가장 간단한 LocalStrategy에 대해서 살펴보면서 개념을 이해해보도록 하자. Express 프로젝트를 생성한후

먼저 웹 인증을 하기 위해서는 HTML Form으로 부터 사용자 id passwd를 받기 위한 페이지를 생성하자.

HTML파일은 express에서 static 페이지를 저장하는 /public 디렉토리 아래에 /public/login.html로 아래와 같이 정의 하였다.


<!DOCTYPE html>

<html>

<head>

    <title></title>

</head>

<body>

<form action="/login" method="post">

    <div>

        <label>UserId:</label>

        <input type="text" name="userid"/><br/>

    </div>

    <div>

        <label>Password:</label>

        <input type="password" name="password"/>

    </div>

    <div>

        <input type="submit" value="Submit"/>

    </div>

</form>

</body>

</html>

이 폼에서, 사용자 id“userid”라는 필드로, 사용자 비밀번호는 “password”라는 이름의 필드로 입력 받도록 하였다.



Passport LocalStrategy 는 디폴트로, username이라는 필드와 password라는 필드로 각각 로그인 id와 비밀번호를 받도록 되어 있는데, 이 예제에서는 username 대신 앞의 폼에서 정의한 userid라는 필드로 사용자 id를 입력 받도록 변경할것이다.

app.js에서 Passport local strategy를 사용하기 위해서 모듈을 불러 들이자.

var passport = require('passport')

    , LocalStrategy = require('passport-local').Strategy;

다음으로, passport LocalStrategy를 정의하고, Local Strategy에 의해서 인증시에 호출되는 인증 메서드를 정의한다..

passport.use(new LocalStrategy({

        usernameField : 'userid',

        passwordField : 'password',

        passReqToCallback : true

    }

    ,function(req,userid, password, done) {

        if(userid=='hello' && password=='world'){

            var user = { 'userid':'hello',

                          'email':'hello@world.com'};

            return done(null,user);

        }else{

            return done(null,false);

        }

    }

));

LocalStrategy 객체를 생성한 후에, 첫번째로 세팅값을 설정한다.

usernameField passwordField라는 인자는,LocalStrategy HTML 폼으로 어떤 필드를 각각 사용자 id  pasword 읽어드릴지를 정의하는 옵션이다. 별도로 정의하지 않았을 경우 “username” “password”라는 필드를 사용하지만, 여기서는 “userid” 라는 필드로 부터 값을 읽어드리도록 변경하였다.

passReqToCallback이라는 옵션은 인증을 수행하는 인증 함수로 HTTP request 그대로  전달할지 여부를 결정한다.

다음으로는 인증시에 호출되는 인증 함수를 정의한다.인증 함수로는 4개의 인자가 전달된다. HTTP request 전달하는 req, 사용자 id,passwd 전달하는 인자가 각각 들어가고 마지막으로 callback 함수인 done 전달한다.

함수에서, 실제로 사용자 인증을 수행하게 된다.

예제는 아주 간단한 예제이기 때문에, 사용자 id “hello”이고, 비밀번호가 “world” 경우 인증이 성공한것으로 처리하도록 하였다. 실제 프로그램에서는 사용자 데이타베이스에서 사용자 id 비밀번호를 조회해서 비교하도록 하면 된다.

인증이 성공하였을 경우 done callback함수에, null 함께, user라는 인자를 넘기게 된다.

user라는 인자는 객체로 사용자에 대한 로그인 정보를 저장하는 객체로 사용자 이름이나, ID등의 정보를 저장할 있다. 로그인 후에는 HTTP Session 저장된다. 예제에서는 간단하게, 사용자 id 사용자 email 저장하도로 하였다.

만약에 인증이 실패하였을 경우에는 done callback함수에 done(null,false) 리턴하게 되면 인증 실패 메세지를 내게 된다.


Serialize Deserialize

로그인이 성공하면, serializeUser 메서드를 이용하여 사용자 정보를 Session 저장할 있다.

passport.serializeUser(function(user, done) {

    console.log('serialize');

    done(null, user);

});

serializeUser 메서드에서는 function(user,done) 이용해서 session 저장할 정보를 done(null,user) 같이 두번째 인자로 넘기면 된다. 이때 user 넘어오는 정보는 앞의 LocalStrategy 객체의 인증함수에서 done(null,user) 의해 리턴된 값이 넘어온다.

예제에서는 user 객체 전체를 사용자 session 저장하였다.

다음으로, node.js 모든 페이지에 접근할때, 로그인이 되어 있을 경우 모든 사용자 페이지를 접근할 경우 deserilizeUser 발생한다. deserializeUser에서는 session 저장된 값을 이용해서, 사용자 Profile 찾은 , HTTP Request  리턴한다.

// 인증 , 페이지 접근시 마다 사용자 정보를 Session에서 읽어옴.

passport.deserializeUser(function(user, done) {

    //findById(id, function (err, user) {

    console.log('deserialize');   

    done(null, user);

    //});

});

deserializeUser callback함수의 첫번째 인자로 넘어오는 내용”user” 세션에 저장된 사용자 정보이다. 예제에서는 session 해당 사용자의 정보를 저장하였기 때문에 별도의 변경없이 done(null,user) 이용해서 그대로 session에서 읽은 내용을 리턴한다.

이렇겍 리턴된 내용은 HTTP Request “req.user” 값으로 다른 페이지에 전달된다.

설명이 복잡한데, 배경을 설명하면 다음과 같다. Session 사용자 정보를 저장하고자할 경우, 사용자 정보가 크다면, 메모리가 많이 소모되기 때문에, serializeUser시에, 사용자 id 같은 정보만 저장하도록 하고, 페이지가 접근될때 마다 deserilizeUser 수행되면,세션에 저장된 사용자 id 이용하여 데이타베이스에서 사용자 정보를 추가로 select해서 HTTP request 붙여서 리턴하는 형태를 사용한다.

실제로도 PassPort 공식 메뉴얼을 보면 다음과 같이 가이드를 하고 있다.

passport.serializeUser(function(user, done) {

          done(null, user.id);

        });

 

passport.deserializeUser(function(id, done) {

          User.findById(id, function(err, user) {

            done(err, user);

          });

        });

serialize시에 session에서는 user.id 사용자 id값만 저장하고, deserialize시에는 session 저장된 id 이용해서, DB 매번 사용자 정보를 select하는 모습을 있다.

그러나 이렇게 하게 되면, 실제 Session 저장되는 데이타가 적어서 메모리는 어느 정도 절약할 있지만 페이지 접근시마다 매번 DB select 발생하기 때문에 성능에 많은 저하가 오기 때문에 권장하고 싶지 않다. 아주 데이타를 넣지 않는 이상 10~20개정도의 필드는 HTTP Session 저장해도 문제가 없기 때문에, 앞에서 필자가 소개한 방식대로 사용자 로그인 데이타를 모두 serialize시에 session 넣는 것을 권장한다. 만약에 데이타가 너무 많아서 메모리 사용량이 우려될 경우에는 redis 같은 외부 메모리 DB 이용해서 session 정보를 저장하도록 하는 방법을 사용하자.

다음으로, Express 객체를 생성한 후에, express에서 passport initialize하고, passport에서 session 사용하도록 설정한다.

app.use(passport.initialize());

app.use(passport.session());


Route 모듈과 연동

이제 LocalStrategy에 대한 인증 모듈과 인증후 사용자 정보를 세션에 넣고 빼는 serilizeUser deserializeUser 메서드를 구현했으니 이제, login 창에서 들어오는 userid passwd를 가지고 인증 모듈을 실행하도록 연결을 해보자.

Passport connect 미들웨어의 형태로 동작한다. 특정 URL로 들어오는 log in 요청에 대해서 connect 미들웨어 형태로, passport 인증 모듈을 수행하도록 할 수 있다, Express route 미들웨어 등과 통합이 가능하다. passport.authenticate라는 메서드를 이용하는데,

app.get(/URL,passport.authenticate(‘{strategy}’),function(req,res){

           :

        }

이 코드는 ‘/URL’로 들어오는 요청에 대해서 사용자 인증을 처리하도록 passport.authentication 을 중간에 미들웨어 형태로 끼워 놓은 것이다. 인증에 성공하게 되면, 뒤에 정의된 callback 함수 인 function(req,res)로 요청이 연결되고, 만약에 인증이 실패하면 401 Unauthorized HTTP response를 리턴한다. Authenticate의 첫번째 인자로 정의된 ‘{strategy}’passport의 인증 Strategy를 정의한다.

위의 코드에서 처럼 passport 미들웨어에서 인증이 완료된 call back function을 실행하는 방법 말고, 인증 성공/실패에 따라서 다른 페이지로 redirect할 수 있도록 설정할 수 있다.

app.post(‘/URL,passport.authenticate(‘{strategy}’,{successRedirect:’/success’

        failureRedirect:’/failure’});

 

위의 코드는 인증이 성공했을때에는 /success URL로 리다이렉트하고, 실패했을 경우에는 /failure라는 URL redirect하도록 한다

그러면 두번째 소개한 redirect 방식으로, passport.authenticate 메서드를 연결해보자

app.post('/login',

    passport.authenticate('local', { failureRedirect: '/login_fail', failureFlash: true }),

    function(req, res) {

        res.redirect('/login_success');

    });

login.html에서 POST action으로 들어온 인증처리를 /login에서 하도록 하고 passport.authenticate ‘local’ strategy 호출한다.

그리고 로그인이 성공했을 경우에는 ‘/login_sucess’ redirect하고, 실패했을 경우에는 ‘/login_fail’ redirect하도록 하였다.

페이지별로 로그인이 되었는지를 확인하고, 로그인이 되어 있을 경우 HTTP request에서 사용자 정보를 가지고 오는 코드는 다음과 같다.

app.get('/login_success', ensureAuthenticated, function(req, res){

    res.send(req.user);

   // res.render('users', { user: req.user });

});

request.user 이용하면, deserializeUser 의해서 저장된 사용자 정보를 꺼내볼 있다

Connect 미들웨어의 특성을 이용하여, 호출시마다 ensureAuthenticated라는 메서드를 호출하게 해서, 로그인이 되어 있는지를 확인하도록 있는데,

function ensureAuthenticated(req, res, next) {

    // 로그인이 되어 있으면, 다음 파이프라인으로 진행

    if (req.isAuthenticated()) { return next(); }

    // 로그인이 안되어 있으면, login 페이지로 진행

    res.redirect('/login.html');

}

같이 로그인이 되어 있으면, req.isAuthenticated() true 리턴된다.

 

지금까지 설명한 내용을 정리해보자



passport에서 인증은 express router에서 passort.authenticate 메서드를 정의함으로써 인증 메서드를 호출할 있다. 이때 passport에서 사용한 인증 Strategy 정의한다.

authenticate에서 정의한 인증 Strategy 대한 개게를 passport.use 이용해서 정의하고, 안에, 인증 함수를 구현한다. 인증함수에서는 HTML form 필드를 통해서 받은 id password 전달되는데, DB등을 연동하여, 들어온 id passwd 비교한후에, 성공하였을 경우 done(null,user) 정보로 리턴을 한다.

이렇게 인증 성공으로 리턴이 되면 사용자에 대한 정보를 세션에 저장할 있도록 passport.serailizeUser 앞에서 done(null,user) 통해서 전달한 user 정보가 전달된다.

그러면 passport.serializeUser에서는 전달된 user 객체중에서 세션에 저장할 정보만을 done(null,user) HTTP session 저장한다.



일단 인증이 되어 로그인이된 사용자는 request마다 passport.desrializeUser 메서드를 호출하게 되는데, 앞에서 passport.serializeUser에서 저장한 사용자 id 키를 이용해서, 사용자 정보를 DB등에서 조회하거나 하여, done(null,user) 리턴하면, HTTP request 함께 리턴이 된다.

해당 사용자가 로그인이되어 있는지를 확인하려면 req.isAuthenticated 이용하여 로그인 되었는지 여부를 확인할 있고, 페이지별로 로그인이 되어 있는지에 대해서 접근제어를 하려면 함수를 하나 정의한 후에, Express router connect 미들웨어로 전처리로 호출하게 해서 인증 여부를 확인하도록 하면 된다.

 

지금까지 간단하게 LocalStrategy 통해서 Passport 대해서 알아보았다.다음에는 Passport-facebook Strategy 통해서 OAuth2 facebook 인증에 대해서 알아보도록 하겠다.


sample code : https://github.com/bwcho75/node.js_study/tree/master/Passport-Localstrategy

자바스크립트 스터디 노트 #1

프로그래밍/JavaScript | 2014.06.18 18:04 | Posted by 조대협

자료형

변수

null과 undefined 이외에는 모든 변수는 객체로 됨
Set을 사용할때는 key는 String만 사용 가능하며, value는 리터럴 모두 가능하다.

var a ={ test:function(){

}};
a.test();

리터럴에 대한 탐색 (순서를 보정하지 않는다.)
cf. 배열은 순서를 보장한다.

var a={1:[],2:[]};
1,2 순서대로 나오지 않는다.

배열 선언

var a = new Array(5); 길이가 5인것
var a = [5]; 멤버가 5인것을 사용. 리터럴 방식의 선언 방식
아래 리터럴 방식의 선언이 실행 속도가 더 빠름
배열의 형 체크

아래와 같이하면, a는 Object로 판별됨

typeof a

그래서, 아래와 같은 방법을 체크하는 게 좋은데,

a instanceof Array

배열을 선언하는 방식에 따라서 Array로 인식이 안될 수 있기 때문에, jQuery등에서 제공되는 함수를 사용하는 것이 좋음

함수 선언

var a = function(){
  console.log('hello');
}

function a(){

}

new Function('a)';

앞의 두가지 방식이 많이 사용됨. 세번째 방식은 사용하지 않는 것이 좋음

홀스팅

 var b=2
 function a(){
      console.log(b);
      var b=1;
  }
 a();

결과는 undefined로 나온다. 변수의 선언을 나중에 했더라도, 변수 선언은 function내에서 유효하다. 단 값은 그 행을 수행해야 값이 assign 된다.

 function(a,b){
 }
 function(){
   arguments[0]; // a
   arguments[1]; // b
 }

arguments를 사용하면, 변수의 인자를 순서대로 받을 수 있다. java의 argv[]와 유사.

this 컨택스트

this 는 해당 메서드를 호출하는 컨텍스트를 지칭한다.

 function a(){
  console.log(this);
}

a();
`

이 경우, 이 스크립트가 속한 windows 객체가 된다.
a()는 window.a()와 같기 때문에 이 window를 this로 리턴한다.
this는 실행시키는 주체를 리턴한다.

function a(){
  console.log(this);
}

var b={
  a:a // 뒤의 a는 function이 됨
};

b.a();

이 경우는 b가 a()를 호출하였기 때문에, b를 this로 인식하고 리턴한다.

var widget = new Widget();
widget.hello();

hello 메서드를 호출하는 주체가 ‘widget’ 객체이기 때문에, widget 객체가 this가 된다.
주로 callback이나 다른 프레임웍 (Jquery)등에 컨텍스트를 넘길때 사용한다.

방안 1. 이런 것을 해결하기 위해서 function을 부를때, f.call(this,변수들)식으로 넘긴다.

function f(p,h){
  console.log(this);
  console.log(p,h);
}
f.call(this,1,2);
f.apply(this,[1,2]);

방안 2.JQuery의 var a=$.proxy 라는 방식을 이용하여, this를 재정의 할 수 있다.

방안 3. 클로저를 사용한다. - 많이 사용하는 방식

var self = this
functiona(){
 self
}

클로저 (중요함)

‘부모의 유효 범위를 가지고 있는다.’
‘부모의 유효 범위에 있는 변수를 사용할 수 있다.’

var test=( function(){
  var a=1;

  return function(){ // 이것이 클로져임. 
    console.log(a);
  };
})();

var a = test();
setTimeout(function(){
  f();
},1000);

위의 예제에서 변수 a가 함수 f가 없어질때까지 계속 따라다님.

var a = [1,2,3];
for(var i = 0,l= a.length;i<l;i++){
  console.log('i'+a[i]);
  setTimeout(function(){
    console.log(i+':'+a[i]);
  },1000);
}

a[i]가 undefined로 찍힘

var a = [1,2,3];
for(var i = 0,l= a.length;i<l;i++){
  console.log('i'+a[i]);
  (function(){
    var index=i;
    setTimeout(function(){

      console.log(i+':'+a[index]);
    },1000);
  })();
}

클로저를 하나 더 만들어서 스코프를 유지함

※ 자바스크립트에서 가장 잘 잡아야 되는 것은 this와 클로져의 개념이다.

객체 생성

var Test = function(){
    // constructor와 같이
    this.member =1 ;
};

var test = new Test();
test.member = 1;

}

다른 방식으로는 prototype을 사용할 수 있음
이건 따로 공부해야 할듯

모듈 패턴

( function(){
  var a = 1;
  return {
    method: function(){

    }
  };
})();


abcd.method();

위에서 처럼 별도의 SCOPE을 정해놓고, method들을 정의한후, 그 파일을 다른 파일에서 불러다 쓴다.

DOM 처리

  • document.getElementById(‘key’);
  • document.querySelector를 사용하는 방식이 있음 (IE 옛날 버전에서는 지원이 안됨) -> JQuery로 대처해서 사용됨.
    ※ JQuery는 느리다.

HTML

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div id='a b c' class='a b c' style='border-color'  ></div>
</body>
</html>

스타일을 css에 만들어놓고 class에서 불러서 적용하거나, 직접 스타일을 style attribute로 적용할 수 있음.

CSS

.a{
  width:100px;
  height:100px;
  background-color:red;
}
.b{
  border:10px solid blue;

}

이벤트

이벤트 바인딩

DOM에 이벤트를 할당하는 3가지 방법
HTML이 다음과 같을때

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div id='a' class='a b c' style='border-color'  ></div>
  <button id='_test'>버튼</button>
</body>
</html>

JQuery를 사용하는 방법

$('._test').on('click',function{
               alert(1);
               });

Native

document.querySelector('._test').onclick=function(){
 alert(1);
 }

또는

document.addEventHandler

HTML에서 마크업에서 직접 정의하는 방법

<button onclick='alert();'>

이벤트 delegation,캡춰링,버블링

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>JS Bin</title>
</head>
<body>
  <div>
    <div>
      <button>버튼</div>
      </div>
    </div>
</body>
</html>

부모 자식간에서

  • 캡춰링 : 부모한테서 이벤트가 자식에게 내려가는 것
    body > div > div > button
  • 버블링 : 자식에게서 부모한테로 이벤트가 올라가는것
    button > div >div >body

이벤트 발생은 캡춰링과 버블링이 순서대로 항상 같이 발생한다.

예를 들어서 부모에게만 click 이벤트를 걸어놓으면, 아래에 여러개의 버튼이 있을때, 부모에게서 받은 이벤트를 받아놓은 후에, 어떤 버튼을 클릭했는지를 받아서 분기 처리 (Selector라고 )한다.

$(document.body).on('click','button',function(e){ console.log(e.currentTarget);
 });

위의 예제는 document body에 이벤트를 걸어놓고, 하위에 버튼을 눌렀을때, 함수를 실행하는 것이다. ‘button’이라고 정해놓은것을 selector 라고 한다.

: 이렇게 부모에게 이벤트 핸들러를 잡아놓고, 차일드에서의 이벤트들을 부모에서 대신 처리하는 것을 Event delegation이라고 한다.

※ JQuery는 버블링만 지원한다. (IE7이 캡춰링을 지원하지 않기 때문에)

document.body.addEventLister((‘click’,function(){},true));
마지막 인자가 true이면 캡춰링, false이면 버블링이다.

캡춰링과 버블링은 같은 이벤트를 걸어놨을때, 어느 순서(레벨)부터 수행이 되는가등을 정할때 사용한다.

보통 버블링을 사용한다.

... on('click',function(e)){
e.stopPropagation();
});
}

버블링이나 캡춰링을 전파(다음 단계로 넘어가는 것)을 중지 시킬 수 있다.
cf. e.preventDefault(); 현재 발생된 이벤트를 취소 시킨다.

실습

  • <script &rt; 태그를 맨아래 /body 앞에 넣는 이유는 브라우져는 script 태그를 만나면 모든 일을 멈추고 스크립트 수행에 집중하기 때문에, 화면 렌더링을 멈춘다. 그래서 UX 관점에서 화면을 다 그리고 스크립트 처리를 하도록 하기 위해서, 맨 아래에 넣는 것이 좋다.

API 사용하기

동일 출처 원칙 - 다른 사이트에서 정보를 얻어오는 건 불가하다. daum.net에서 실행되는 자바스크립트로 naver.com 에서 정보를 읽어올 수 없다. 이를 해결하는 방법이 JSONP가 있다.
동일 출처 원칙의 영향을 받지 않는 것은 script를 사용하는 방식 (HTTP GET만 사용할 수 있음)

<script src="http://타사이트/a.js">
</script>
<script>
_callback({name:'john'});
</script>

<script>
_callback = function(data){
 data..
 };
}
</script>

이런 원리로 된다는 것인데, HTTP GET 방식만 호출할 수 있음

아래는 JQuery ajax의 예

$.ajax({
url:'API URL',
datatype:'jonsp',
success: function(data){ // data : 리턴되는 데이타
console.log(data);
}
});

}
})

예제에 사용할 API URL http://apis.daum.net/socialpick/search
API 설명 - http://dna.daum.net/apis/socialpick/ref#search

템플릿

Widget.prototype._getTemplate = function(id,values){
    return $('#'+id).text().replace(/{{([^}]+)}}/g,function(text,matchedText){
        console.log(arguments); // for debug
        return values && typeof values[matchedText] !== 'undefined' ? values[matchedText] : '';
    });
};

String의 replace 명령을 이용하여, regular expression을 사용한다.


빠르게 훝어보는 node.js

#9 - Socket.IO (1/4)

조대협 (http://bcho.tistory.com)


웹의 발전과 함께, 클라이언트의 요청에 대해서 응답만을 하는 단방향성이 아닌 양방향성의 사이트가 유행하게 되었는데, Socket.IO 자바스크립트 모듈로, 양방향 통신이 가능한 웹사이트를 구축하기 위해서 HTTP 클라이언트로 푸쉬 메시지를 보내줄 있는 모듈이다. 넓은 브라우져 지원성과 사용의 편의성 때문에 널리 사용되고 있고, node.js 인기 있어 지는 이유 중의 하나는 socket.io 때문이 아닐까 한다.

배경

Socket.io를 설명하기 전에, 웹에서의 푸쉬 개념에 대해서 이해할 필요가 있다. 웹은 기반적으로 클라이언트에서 서버로 가는 단방향성이지만, 채팅과 같은 실시간 양방향 애플리케이나 쪽지와 같이 서버에서 클라이언트로 알림을 보내줘야 하는 요구 사항이 생겼다. 그래서 여러가지 기법이 생겨났는데, 자바스크립트 기반의 AJAX가 유행하면서 몇가지 기법이 생겨났는데, 그 내용을 살펴보면 다음과 같다.



웹 푸쉬 방식 비교 : Polling vs Long Polling vs Streaming

출처 : https://blogs.oracle.com/theaquarium/entry/slideshow_example_using_comet_dojo


Polling

가장 기본적인 기법으로, 클라이언트가 서버에 주기적으로 폴링(request를 보내는 기법)이다. 주기적으로 클라이언트가 자기가 처리해야할 이벤트가 있는지 없는지를 체크하는 것이다.

서버가 폴링 요청이 들어올때 마다 이를 처리해야 하고, 다음 폴링이 이루어지기 전까지는 어떤 이벤트가 오는지를 모르기 때문에, 결정적으로 실시간성이 보장이안된다.

예를 들어 폴링 주기가 10분이라고 할때 폴링 이후에 바로 이벤트가 들어왔을때, 다음 폴링주기 (10)을 기다려야 된다. 그리고 폴링 주기가 짧을 수 록 서버가 받는 부하가 크다, 예를 클라이언트가 작업할 내용이 있는지 확인하려면, DB드에 작업 내용을 저장해놓고, 폴링 때마다 체크해야 하는데, 이때 매번 DB 쿼리를 해야 한다면, 폴링 때마다 서버가 DB를 쿼리해야하기 때문에, 받아야 하는 트렌젝션이 매우 많고 서버의 부담이 기하 급수적으로 늘어난다. 따라서, 짧은 폴링 주기는 서버에 많은 부하를 주기 때문에, 적절하지 않으며 클라이언트로 보내는 푸쉬 메시지의 실시간성이 필요하지 않은 경우에 적절하며, 서버의 부하가 상대적으로 적고(폴링 주기가 길 경우) 기존의 웹백엔드 인프라 (Tomcat과 같은 미들웨어)를 그대로 활용할 수 있는 장점을 가지고 있다.


Long Polling

Long Polling Polling과 비슷하나 즉시성을 갖는다. 방식은 클라이언트가 HTTP request를 보내고, 바로 request를 닫는 것이 아니라, 일정 시간 동안(오랫동안) 열어 놓고 있다가 서버에서 클라이언트로 보내는 메시지가 있으면 메시지를 HTTP response로 실어 보내고, 해당 Connection을 끊는다. 만약에 일정 시간동안 보낼 메시지가 없으면 HTTP 연결을 끊는다.

응답 메시지를 받건 안받건, 끊어진 연결은 다시 연결한다. 기본적으로 클라이언트가 연결을 해서 응답을 요청하는 Polling 형태이고, 응답이 오는지 기다리는 기간이 길기 때문에 이를 Long Polling 이라고 한다.

Long Polling의 경우 서버에 클라이언트들이 거의 항상 연결되어 있는 형태이기 때문에, 동시  서버의 동시에 연결할 수 있는 서버가 지원할 수 있는 동시 연결(Connection)수에 따라 결정된다.

예를 들어 Tomcat과 같은 WAS의 경우에는 HTTP 연결이 열려 있는 경우에는 1개의 Thread가 그 요청을 처리하기 위해서 사용되기 때문에, Tomcat Thread 100개인 경우, 1 Tomcat당 처리할 수 있는 Long Polling 가능한 클라이언트 수는 100개로 한정이 된다. (기존의 HTTP 요청을 처리하는 인프라로 핸들링하기 어렵다.)

서버로부터 푸쉬 메시지를 받으면 재 연결을 해야 하기 때문에, 클라이언트로 푸쉬하는 내용이 적을 경우에 유리하며 실시간 채팅과 같이 푸쉬해야 하는 메시지가 많은 경우에는 적절하지 않다. (채팅 메시지가 하나 왔다갔다 할 때 마다 재 연결을 해야 한다.)


Streaming

마지막으로 Streaming 기법인데, 이 기법은 일반적인 TCP Connection 처럼, 클라이언트가 서버로 연결을 맺은 후에, 그 연결을 통해서 서버가 이벤트를 보내는 방식이다. Long Polling이 이벤트를 받을 때마다 연결을 끊고 재 연결을 한가면, Streaming 방식은 한번 연결되면 계속해서 그 연결을 통해서 이벤트 메시지를 보내는 방식으로 재연결에 대한 부하가 없다.


WebSocket

이러한 푸쉬 로직을 AJAX 자바스크립트로 구현하다가 구현 방식이 브라우져들 마다 각기 상이하기 때문에 나온 표준이 WebSocket이라는 표준이다. http:// 대신 ws:// 로 시작하며 Streaming과 유사한 방식으로 푸쉬를 지원한다.

그러나 문제는 이 WebSocket 기술이 근래에 나왔기 때문에, 예전 브라우져는 지원하지 않는다는 것이다.



<그림: 웹소켓 지원 브라우져 현황>

출처 : http://caniuse.com

Socket.IO

Socket.IO 클라이언트로의 푸쉬를 지원하는 모듈인데, WebSocket 한계를 뛰어 넘어주는 모듈이다. 개발자는 Socket.IO 개발을 하고 클라이언트로 푸쉬 메시지를 보내기만 하면, WebSocket 지원하지 않는 브라우져의 경우, 브라우져 모델과 버전에 따라서 AJAX Long Polling, MultiPart Streaming, Iframe 이용한 푸쉬, JSONP Polling, Flash Socket 다양한 방법으로 내부적으로 푸쉬 메시지를 보내준다.

WebSocket 지원하지 않는 어느 브라우져라도 푸쉬 메시지를 일관된 모듈로 보낼 있다.

간단한 예제를 살펴보자


간단한 채팅 프로그램

간단한 채팅 프로그램을 하나 살펴보자. 아래 프로그램은 브라우져에 접속하면 메시지를 입력 받을 있는 Input Box 띄워주고, 여기에 메시지를 입력하면 현재 연결되어 있는 모든 웹브라우져에 메시지를 보내주는 프로그램이다.

먼저 서버쪽 코드를 보자

/**

 * Module dependencies.

 */

 

var express = require('express');

var routes = require('./routes');

var http = require('http');

var path = require('path');

 

var app = express();

app.use(express.static(path.join(__dirname, 'public')));

 

var httpServer =http.createServer(app).listen(8080, function(req,res){

  console.log('Socket IO server has been started');

});

// upgrade http server to socket.io server

var io = require('socket.io').listen(httpServer);

 

io.sockets.on('connection',function(socket){

   socket.emit('toclient',{msg:'Welcome !'});

   socket.on('fromclient',function(data){

       socket.broadcast.emit('toclient',data); // 자신을 제외하고 다른 클라이언트에게 보냄

       socket.emit('toclient',data); // 해당 클라이언트에게만 보냄. 다른 클라이언트에 보낼려면?

       console.log('Message from client :'+data.msg);

   })

});

 

<코드. App.js>

Express에서 Socket IO 사용한 예제인데, 기존에 Express에서 사용한것과 같은 방식으로 httpServer 생성한다. 다음에, httpServer socketIO 지원하는 서버로 다음과 같이 업그레이드를 한다.

var io = require('socket.io').listen(httpServer);

다음으로, 클라이언트가 socket.io 채널로 접속이 되었을때에 대한 이벤트를 정의한다.

io.sockets.on('connection',function(socket){

같이 클라이언트가 접속이 되면, callback 수행하는데, 이때, 연결된 클라이언트의 socket 객체를 같이 넘긴다. socket 객체를 받아서, 코드에서는 연결된 클라이언트에게 “Welcome !”이라는 메시지를 보냈다.

socket.emit('toclient',{msg:'Welcome !'});

일반적인 이벤트 처리 방식과 같게, 해당 클라이언트 소켓에 emit 메서드를 이용하여, 이벤트를 전송하면 된다. 여기서는 “toclient”라는 이벤트 명으로 msg라는 키를 갖고, value ‘Welcome !’ 이라는 값을 가지는 메시지를 전송하였다.

다음으로, 클라이언트로부터 오는 메시지를 처리하는 루틴인데, 채팅창에서 글을 쓰고 엔터를 누르면 서버로 “fromclient” 라는 이벤트를 보내도록 작성해놓았다. 그러면 서버쪽에서는 다음과 같이 socket.on(‘fromclient’ 라는 메서드를 이용하여 해당 이벤트에 따른 처리를 한다. 이때 들어오는 데이터는 채팅 문자열이 {msg:”문자열”} 형식으로 data라는 변수를 통해서 아래와 같이 들어오는데,

socket.on('fromclient',function(data){

채팅에서 이메세지를 다른 클라이언트들과 자신에게 다시 보낸다.

       socket.broadcast.emit('toclient',data);

       socket.emit('toclient',data);

socket.broadcast.emit 자신을 제외한 다른 모든 클라이언트에게 이벤트를 보내는 메서드이고, socket.emit 자신의 클라이언트()에게 이벤트를 보내는 메서드이다.

그러면 이제 클라이언트()쪽의 코드를 보자

<html>

<head>

 

    <title></title>

    <script src="/socket.io/socket.io.js"></script>

    <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>

 

</head>

<body>

    <b>Send message</b><p>

    Message  <input type="text" id="msgbox"/>

    <br>

    <span id="msgs"></span>

 

    <script type="text/javascript">

        var socket = io.connect('http://localhost');

        $("#msgbox").keyup(function(event) {

            if (event.which == 13) {

                socket.emit('fromclient',{msg:$('#msgbox').val()});

                $('#msgbox').val('');

            }

        });

        socket.on('toclient',function(data){

            console.log(data.msg);

            $('#msgs').append(data.msg+'<BR>');

        });

    </script>

</body>

</html>

<코드. index.html>

먼저 socket.io 사용하기 위해서 script src 아래와 같이 정의하고

<script src="/socket.io/socket.io.js"></script>

다음으로, 자바 스크립트가 실행되면, socket.io 서버로 연결을 한다.

var socket = io.connect('http://localhost');

그리고, input box에서 엔터를 누르면, input box 메시지를 읽어서, ‘fromclient’라는 이벤트를 서버에 전송한다.

socket.emit('fromclient',{msg:$('#msgbox').val()});

그리고 반대로, 서버로부터, ‘toclient’라는 이벤트가 들어오면, 들어온 문자열을 msgs라는 id 갖는 <span> 영역에 append 한다.

socket.on('toclient',function(data){

            console.log(data.msg);

            $('#msgs').append(data.msg+'<BR>');

클라이언트와 서버쪽 코드가 완성되었으면, 이를 배포하고 node.js 실행해서 테스트를 해보자.



<그림. socket.io 이용한 채팅 프로그램 화면>

다음에는 Socket.IO API들에 대한 소개와 1:1 귓속말, 그리고 그룹의 개념을 가지는 채팅방 예제에 대해서 설명하도록 한다.


node.js용 개발도구가 여러가지가 있다.

Eclipse와 IntelliJ를 개발한 JetBrain의 WebStorm 과 같은 상용툴이 있지만, Eclipse의 경우, 자바기반이라서 무겁기도 하고 속도도 느리고, WebStorm은 금전적인 부담이 있다. 근래에 MS에서 node.js용 Viual Studio 플러그인을 제공하는데 Native 기반이라서 빠르기도 하고, 무료로 사용을 할 수 있는 장점이 있다.


먼저 Visual Studio 2013 Web 버전을 설치한후 (무료)

https://nodejstools.codeplex.com/ NTVS (Node js Tool for Visual Studio) 를 설치하면 된다

소개 동영상은 https://www.youtube.com/watch?feature=player_embedded&v=W_1_UqUDx2s


설치되서 실행하는 화면



아무래도, WebStorm에 익숙해져 있어져 그런지, 약간은 불편하다. node.js를 띄우면 위의 화면 처럼 별도의 dos창에서 실행이 되지만, 그외에, 에디팅이나 자동 완성 기능이나  등은 쓸만한듯 하다.