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


Archive»



Spinnaker #2 - 설치


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


설치


설치 문서는 https://www.spinnaker.io/setup/  를 참고하면 된다.

설치 가이드를 보면 Quick Install 가이드와 수동 인스톨 가이드를 제공하고 있다.



퀵 인스톨 가이드는 대규모 운영용으로는 어렵고 하나의 인스턴스에, 모든 마이크로 서비스가 인스톨 되는 모델로, 소규모 운영이나 또는 데모용으로 손쉽게 사용이 가능하다. 수동으로 인스톨 하는 방법은 다소 까다롭기 때문에, Quick Install 부터 진행하는 것을 권장한다.


Quick Install 페이지에 들어가면, 각 클라우드와 쿠버네티스 클러스터에 설치 하는 방법이 가이드 되어 있다.


구글 클라우드에 Spinnaker 설치

구글 클라우드에 Spinnaker를 설치하려면,간단하게  Google Cloud Launcher를 이용하면 손쉽게 설치가 가능하다.

설치에 앞서서, Spinnaker는 Google Cloud API를 이용하여, 인스턴스 생성과 스토리지등을 접근하기 때문에, 구글 클라우드 프로젝트에서 해당 API들을 활성화 해줘야 한다. (매우 중요) 활성화 해야하는 API는 아래와 같다.


다음은 Spinnaker를 설치해보자.

Cloud Launcher 페이지에서 Spinnaker를 선택한 후



“Launch on compute engine” 을 선택하면, 4 CPU/20GB VM에 Spinnaker가 설치된다.

아래와 같이 인스턴스 이름을 입력하고, Zone을 선택하면 된다.만약에 필요하다면 VM의 Machine Type을 좀 큰 인스턴스로 해서 운영환경에 적용해도 된다.




만약에 Kubernetes Cluster를 연동해서 사용하고자 한다면, https://www.spinnaker.io/guides/tutorials/codelabs/gcp-kubernetes-source-to-prod/#enable-apis 설치 가이드를 참고하기 바란다.

실행

Spinnaker 를 디폴트로 설치하고 나면 별도의 ID 인증 기능이 설정되어 있지 않기 때문에, 접속 포트가 인터넷으로 열려 있지 않고 local host 로만 접속을 허용한다.

그래서 SSH 터널링을 통해서 로컬 PC의 Local port 9000번과 8084번 포트를 Spinnaker VM의 포트와 맵핑을 시키도록 한다.

9000번은 웹사이트 8084번은 웹사이트가 호출하는 gate 컴포넌트이다.

SSH 터널링은 gcloud compute ssh명령을 이용하면 된다.

사용 방법은 다음과 같다.

gcloud compute ssh --project={구글 클라우드 프로젝트명} --zone={Spinnaker 인스턴스가 배포된 존 이름} {인스턴스명} -- -L 9000:localhost:9000  -L 8084:localhost:8084


다음은 terrycho-sandbox라는 프로젝트 명을 가지고 spinnaker-demo 라는 인스턴스를 asia-northeast-1c에 배포해놓고 접속한 예이다.


gcloud compute ssh --project=terrycho-sandbox --zone=asia-northeast1-c spinnaker-demo -- -L 9000:localhost:9000  -L 8084:localhost:8084


명령을 실행한 후에, 브라우져에서 localhost:9000 번으로 접속하면 Spinnaker 콘솔을 볼 수 있다.



Spinnaker #1 - 소개


Spinnaker

Spinnaker 는 넷플릭스에서 개발하여 오픈 소스화한 멀티 클라우드를 지원하는 Continuous Delivery Platform 이다. 구글 클라우드, 아마존, 마이크로소프트등 대부분의 메이져 클라우드를 지원하며, Kubernetes 나, OpenStack 과 같은 오픈소스 기반의 클라우드 또는 컨테이너 플랫폼을 동시에 지원한다.

시나리오

Spinnaker 의 특징은 멀티 클라우드 지원성뿐만 아니라, 오케스트레이션 파이프라인 구조를 지원한다 특징인데,  배포 단계는 여러개의 스텝이 복합적으로 수행되는 단계이기 때문에, 복잡한 워크 플로우에 대한


관리가 필요하다.

하나의 배포 시나리오를 통해서 오케스트레이션 파이프라인에 대해서 이해해보도록 하자

  • 코드를 받아서 빌드를 하고,

  • 빌드된 코드를 VM에 배포하여 이미지로 만든 후에, 해당 이미지를 테스트한다.

  • 테스트가 끝나면, Red/Black 배포를 위해서 새버전이 배포된 클러스터를 생성한 후에

  • 새 클러스터에 대한 테스트를 끝내고

  • 새 클러스터가 문제가 없으면 트래픽을 새 클러스터로 라우팅한다.

  • 다음으로는 구버전 클러스터를 없앤다.

각 단계에서 다음 단계로 넘어가기 위해서는 선행 조건이 필요하다. 예를 들어 이미지가 빌드가 제대로 되었는지 안되었는지, 새 클러스터가 제대로 배포가 되었는지 안되었는지에 대한 선/후행 조건의 확인 들이 필요하다.

Spinnaker에서는 이러한 오케스트레이션 파이프라인을 “파이프라인”이라는 개념으로 구현하였다. 파이프라인 흐름에 대한 예를 보면 다음과 같다.


위의 파이프라인은 이미지를 찾아서 Red/Black 배포를 위해서 Production에 새로운 이미지를 배포하고, Smoke 테스트를 진행한 후에, 구 버전을 Scale down 시키고, 소스를 태깅 한다. 이때 구 버전을 Destory 하기 전에, Manual Approval (사람이 메뉴얼로 승인) 을 받고 Destory 하는 흐름으로 되어 있다.


또한  각 단계별로 하위 테스크가 있는 경우가 있다. 예를 들어 새로운 클러스터를 배포하기 위해서는 클라우드 내에 클러스터 그룹을 만들고, 그 안에 VM들을 배포한 후에, VM 배포가 완료되면 앞에 로드 밸런서를 붙이고, Health check를 설정해야 한다. 그리고 설정이 제대로 되었는지 체크를 한다음에 다음 단계로 넘어간다.


이러한 개념을 Spinnaker에서는 Stage / Steps/ Tasks/ Operation 이라는 개념으로 하위 태스크를 구현하였다. 개념을 보면 다음과 같다.



파이프라인 컴포넌트

파이프라인은 워크 플로우 형태로 구성이 가능하다. 아래 그림은 파이프라인을 정의하는 화면의 예시이다.


<그림. 파이프라인 예제>

출처 http://www.tothenew.com/blog/introduction-to-spinnaker-global-continuous-delivery/


파이프라인에서 스테이지별로 수행할 수 있는 테스크를 선택할 수 있다.  샘플로 몇가지 스테이지를 보면 다음과 같다.

  • Bake : VM 이미지를 생성한다.

  • Deploy : VM 이미지 (또는 컨테이너)를 클러스터에 배포한다.

  • Check Preconditions : 다음 단계로 넘어가기전에 조건을 체크한다. 클러스터의 사이즈 (EX. 얼마나 많은 VM이 생성되서 준비가 되었는지)

  • Jenkins : Jenkins Job 을 실행한다.

  • Manual Judgement : 사용자로 부터 입력을 받아서 파이프라인 실행 여부를 결정한다

  • Enable/Disable Server Group : 이미 생성된 Server Group을 Enable 또는  Disable 시킨다

  • Pipeline : 다른 파이프라인을 수행한다.

  • WebHook : HTTP 로 다른 시스템을 호출한다. 통상적으로 HTTP REST API를 호출하는 형


개념 구조


Spinnaker는 리소스를 관리하기 위해서, 리소스에 대한 계층구조를 정의하고 있다.



<그림. Spinnaker의 자료 구조 >

출처 : ttp://www.tothenew.com/blog/introduction-to-spinnaker-global-continuous-delivery/



가장 최상위에는 Project, 다음은 Application 을 가지고 있고, Application 마다 Cluster Service를 가지고 있고, 각 Cluster Service는 Server Group으로 구성된다. 하나하나 개념을 보자면,


Server Group 은, 동일한 서버(같은 VM과 애플리케이션)로 이루어진 서버군이다. Apache 웹서버 그룹이나 이미지 업로드 서버 그룹식으로 그룹을 잡을 수 도 있고, 이미지 서버 그룹 Version 1, 이미지 서버 그룹 Version 2 등으로 버전별로 잡는등 유연하게 서버군집의 구조를 정의할 수 있다.

이러한 서버 그룹은 Cluster 라는 단위로 묶일 수 있다.


아래 예제 그림을 통해서 개념을 좀더 상세하게 살펴보자


위의 그림은 이미지 서비스(Image service)를 제공하는 서비스를 Cluster로 정의한것이다.

위의 구조는 Image Service를 Service Group으로 정의했는데, v1,v2,v3 버전을 가지고 있고 각 버전이 Service Group으로 정의된다 (이런 이유는 멀티 버전을 이용한 카날리 테스트나 Red/Black 배포를 이용하기 위해서 여러 버전을 함께 운용하는 경우가 생긴다.)

그리고, 리전별로 별도의 Image Service를 각각 배포하는 모델이다.

리전과 멀티 클라우드의 개념은 Spinnaker 문서에 나온 자료 구조 이외에, 중요한 자료 구조인데, 리소스를 정의할때 클라우드 계정을 선택함으로써 클라우드를 선택할 수 있고, 서비스의 종류에 따라 리전을 선택하는 경우가 있는데 이 경우 리전별로 리소스를 분류해서 보여준다.


Cluster는 Application 내에서 생성될때 , Service Group을 생성시 입력하는  {Account}-{stack}-{Detail} 을 식별자로하여 Cluster를 식별한다. 같은 식별자를 가진 Service Group을 하나의 Cluster로 묶는다.

아래는 Service Group을 생성하는 화면으로 Account, Stack, Detail을 입력하는 메뉴가 있는 것을 확인할 수 있다.



아래 그림은 myapplication 이라는 이름을 갖는 Application 내에, 각각 MY-GOOGLE-ACCOUNT라는 account를 이용하여, myapplication-nodestack-cluster1과, myapplication-nodestack-cluster2 두개의 클러스터를 생성한 예제이다.





또는 자주 쓰는 구성 방식중 하나는 Red/Black (또는 Blue/Green  이라고도 함) 형태를 위해서 하나의 클러스터에 구버전과 새버전 서버 그룹을 각각 정의해놓고 구성하는 방법이 있다.


Application은 Cluster의 집합이고, Project는 Application의 집합이다.

개발하고 배포하고자 하는 시스템의 구조에 따라서 Project, Application, Cluster를 어떻게 정의할지를 고민하는 것이 중요하다.


예를 들어 하나의 서비스가 여러개의 애플리케이션으로 구성되어 있는 경우, 예를 들어 페이스북 처럼, 페이스북 앱, 웹 그리고 앱 기반 페북 메신져가 있는 경우에는 페이스북이라는 프로젝트 아래, 페이스북 앱 백앤드, 웹 백앤드, 앱 백앤드로 Application을 정의할 수 있고,각각의 Application에는 마이크로 서비스 아키텍쳐 (MSA) 방식으로 각각서 서비스를 Cluster로 정의할 수 있다.

아키텍쳐

마지막으로 Spinnaker의 내부 아키텍쳐를 살펴보도록 하자.

Spinnaker는 MSA (마이크로 서비스 아키텍쳐) 구조로 구성이 되어 있으며, 아래 그림과 같이 약 9 개의 컴포넌트로 구성이 되어 있다.



각 컴포넌트에 대해서 알아보도록 하자


  • Deck : Deck 컴포넌트는 UI 컴포넌트로, Spinnaker의 UI 웹사이트 컴포넌트이다.

  • Gate : Spinnaker는 MSA 구조로, 모든 기능을 API 로 Expose 한다, Gate는 API Gateway로, Spinnaker의 기능을 API로 Expose 하는 역할을 한다.

  • Igor : Spinnaker는 Jenkins CI 툴과 연동이 되는데, Jenkins에서 작업이 끝나면, Spinnaker Pipeline을 Invoke 하는데, 이를 위해서 Jenkins의 작업 상태를 Polling을 통해서 체크한다. Jenkins의 작업을 Polling으로 체크 하는 컴포넌트가 Igor이다.

  • Echo : 외부 통신을 위한 Event Bus로, 장애가 발생하거나 특정 이벤트가 발생했을때, SMS, Email 등으로 notification을 보내기 위한 Connector라고 생각하면 된다

  • Rosco : Rosco는 Bakering 컴포넌트로, Spinnaker는 VM또는 Docker 이미지 형태로 배포하는 구조를 지원하는데, 이를 위해서 VM이나 도커 이미지를 베이커링(굽는) 단계가 필요하다. Spinnaker는 Packer를 기반으로 하여 VM이나 도커 이미지를 베이커링 할 수 있는 기능을 가지고 있으며, Rosco가 이 기능을 담당 한다.

  • Rush : Rush는 Spinnaker에서 사용되는 스크립트를 실행하는 스크립트 엔진이다.

  • Front50 : Front 50은 파이프라인이나 기타 메타 정보를 저장하는 스토리지 컴포넌트이다.

  • Orca : Oraca는 이 모든 컴포넌트를 오케스트레이션하여, 파이프라인을 관리해주는 역할을 한다.

  • CloudDriver : 마지막으로 Cloud Driver는 여러 클라우드 플랫폼에 명령을 내리기 위한 아답터 역할을 한다.




빅쿼리 대쉬 보드를 위한 오픈소스 메타 베이스


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


빅쿼리 분석 결과를 시각화 하는 도구로 구글에서 제공되는 툴은 일반 비지니스 사용자나, 초보자를 위한 데이타 스튜디오, 그리고 데이타 사이언티스트를 위한 DataLab 등이 있다.


그러다 보니, 데이타 사이언티스트는 아니면서 고급 사용자를 위한 데이타 분석툴 영역에 다른 툴이 필요하게 되는데, 상용 도구로는 타블루와 같은 설치형 도구나 Looker 등의 클라우드 서비스를 사용할 수 있는데, 유료이기 때문에, 대안적인 툴을 찾는 경우가 많다.


오픈 소스 도구로는 Redash가 있는데, 이 외에, Metabase(메타 베이스) 라는 도구가 있어서 소개한다.


쿼리 및 분석 기능

분석을 위해서 기본적인 화면상에서 쿼리가 가능하고, 쿼리 결과는 아래 그림과 같이 테이블이나 그래프 형태로 출력이 가능하기 때문에, AdHoc  분석이 손쉽게 가능하다. 




대쉬 보드 기능

이렇게 쿼리하고 분석한 내용을 바로 아래 그림과 같이 대쉬 보드에 추가할 수 있다. 



사용자 관리 기능

메타 베이스의 장점 중 하나가, 어느정도 규모가 되는 조직에서 사용이 가능하도록, 사용자 계정 관리 기능을 가지고 있다.  사용자 그룹을 통한 권한 관리 등이 가능하다.


관리자 기능

사용자 권한 관리를 하기 때문에 당연히 관리자 기능이 있는데, 재미있는 것은 필터나 매트릭등을 관리자가 정해놓고, 사용자가 이 매트릭을 불러다가 분석이나 리포팅에 사용할 수 있다.





<그림. 관리자 패널에서 필터를 정의하는 화면 >



빅쿼리와 메타 베이스를 연결하는 방법은 다음과 같다.

https://www.metabase.com/docs/latest/administration-guide/databases/bigquery.html


설치는 metabase.com 문서를 참고해야 하는데 mysql이나 postgres와 같은 외부 데이타 베이스를 설정해야 한다. 

https://www.metabase.com/docs/latest/operations-guide/running-the-metabase-jar-file.html




CI/CD 레퍼런스 아키텍쳐


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


Continuous Deployment를  구현하기 위해서는 여러가지 프레임웍을 조합할 수 있다. 배포를 위한 Chef,Puppet과 같은 Configuration management tools, 그리고 네트워크, VM등을 코드로 설정하기 위한 Terraform 과 같은 Infrastructure as a code, VM 이미지를 만들기 위한 Packer 등 다양한 솔루션 조합이 가능한데, 이 글에서는 이러한 솔루션을 조합하여 어떻게 Continuous Deployment 파이프라인을 구현할 수 있는지에 대해서 설명하고, 구체적인 솔루션 제안을 통하여 레퍼런스 아키텍쳐를 제안하고자 한다.

1. Terraform + Ansible 기반의 Continuous Delivery

가장 기본적인 조합으로는 Terraform 을 이용해서 코드로 정의된 설정을 이용하여 인프라를 설정한 후에,

VM에, Ansible을 이용하여 애플리케이션 서버등의 소프트웨어를 설치한 후,  애플리케이션 코드를 배포하는 방식이다.

아래 그림은 Terraform으로 먼저 VM 인스턴스 그룹을 만든 후에, Load Balancer에 연결하고, CloudSQL (DB)인스턴스를 배포하는 구조이다.




이후에, 각 VM에 대한 설치는 Ansible을 이용하는 구조이다 Ansible은 Jenkins와 같은 CD 툴에 의해서 코드 변경등이 있으면 호출되서 자동화 될 수 있다.


이러한 구조는 전통적인 Continuous Delivery 기반의 애플리케이션 배포 자동화 구조이다.


2. Packer를 추가한 Foundation Image 사용방식

앞의 구조에서 VM은 애플리케이션 서버를 코드 배포 단계에서 배포할 수 도 있지만 애플리케이션 코드 이외에는 변경이 없기 때문에, Terraform으로 인프라를 배포할때, Packer와 Ansible을 이용하여, 애플리케이션이 설치되어 있는 이미지를 만들어놓고, 이를 이용해서 배포할 수 있다. (이미지를 만드는 과정을 베이킹 = 굽는다. 라고 한다.)

아래 그림을 보면, Terraform에서, Packer를 호출하고, Packer가 VM 이미지를 만드는데, 이 과정에서 Ansible을 이용하여, 애플리케이션 서버를 설치하도록 설정하는 구조를 가지고 있다.



위의 구조에서는 node.js server 애플리케이션 서버를 사용했지만, 실제 인프라를 구축할때는 redis나 웹서버등 다양한 애플리케이션의 설치가 필요하기 때문에, 이 구조를 사용하면 전체 인프라 구축을 코드로 정의하여 자동화를 할 수 있다.

3. Spinnaker를 이용한 Continuous Deployment 구조

코드만 배포하고 업데이트 할 경우, 서버의 패치 적용등의 자동화가 어렵기 때문에, 매번 배포시 마다, VM 설정에서 부터 OS 설치와 패치 그리고 애플리케이션 설치와 코드 배포까지 일원화하여 VM 단위로 배포할 수 있는데, 이를 Continuous Deployment 라고 한다.


솔루션 구성은 2번의 구조와 유사하나, Terraform으로는 VM과 로드밸런서를 제외한 다른 인프라를 설정하고 Spinnaker를 이용하여, 로드밸런서와 VM을 이용한 배포를 실행한다.


Spinnaker로 배포할 수 있는 범위는 방화벽, 로드밸런서, VM 과 같이 워크로드를 받는 부분인데, Spinnaker는 Packer와 Ansible과 협업하여, VM에 모든 스택을 설치하고, 이를 VM 단위로 배포할 수 있도록 해준다. 복잡한 네트워크 설정이나, CloudSQL과 같은 클라우드 전용 서비스는 Spinnaker로 설정이 불가능하기 때문에, 먼저 Terraform으로 기본 인프라를 설정하고, VM관련된 부분만을 Spinnaker를 사용한다.

이렇게 VM전체를 배포하는 전략을 피닉스 서버 아키텍쳐라고 한다. 피닉스 서버 패턴은 http://bcho.tistory.com/1224?category=502863 글을 참고하기 바란다.


Spinnaker를 이용한 배포 전략

Spinnker를 이용하면, VM 기반의 배포뿐 아니라, 다양한 배포 전략을 수행할 있다.



그림 https://sdtimes.com/cloud/google-open-source-platform-spinnaker-1-0/


Blue/Green deployment

블루 그린 배포 전략은 새버전의 서버그룹을 모두 배포 완료한 후에, 로드밸런서에서 트래픽을 구버전에서 새버전으로 일시에 바꾸는 방식이다.

Rolling deployment

롤링 배포는, 새버전의 서버를 만들어가면서 트래픽을 구버전 서버에서 새버전으로 점차적으로 옮겨가는 방식이다. 예를 들어 구서버가 10대가 있을때, 새 서버 1대가 배포되면, 구서버 9대와 새서버 1대로 부하를 옮기고, 새서버 2대가 배포되면 구서버:새서버에 8:2 비율로 부하를 주면서 7:3,6:4,5:5,.... 이런식으로 부하를 옮겨가며 전체 부하를 새 서버로 옮기는 방식이다.


블루 그린 배포 전략은 서버 대수의 2배수의 서버가 필요한 반면, 롤링 배포 방식은 같은 서버의 수 (위의 예의 경우 10대만 있으면 됨)를 가지고 배포를 할 수 있기 때문에 서버 자원이 한정되어 있는 경우에 유리하게 사용할 수 있다.

Canary deployment

카날리 배포를 설명하기 전에 카날리 테스트에 대한 용어를 이해할 필요가 있다.

카날리 테스트는 옛날에 광부들이 광산에서 유독가스가 나오는 것을 알아내기 위해서 가스에 민감한 카나리아를 광산안에서 키웠다고 한다. 카나리아가 죽으면 유독가스가 나온것으로 판단하고 조치를 취했다고 하는데, 이 개념을 개발에서 사용하는것이 카날리 테스트 방식이다.

예를 들어 사용자가 1000명이 접속해 있을때, 일부 사용자에게만 새 버전을 사용하도록 하고, 문제가 없으면 전체 사용자가 새 버전을 사용하도록 하는 방식인데, 안드로이드 앱 배포의 경우에도 10%의 사용자에게만 새 버전을 배포해보고 문제가 없으면 100%에 배포하는 것과 같은 시나리오로 사용된다.


이 개념을 배포에 적용한것이 카날리 배포 방식인데, 일부 서버에만 새 버전을 배포하여 운영한 후에, 문제가 없는 것이 확인되면 전체 서버에 새 버전을 배포하는 방식이다.

Docker를 이용한 배포 효율화

이러한 VM 기반의 Continuous deployment 구조는 피닉스 서버 패턴을 기반으로 하여, 모든 업데이트 추적이 가능하다는 장점을 가지고 있지만, 매번 VM을 베이킹해야 하기 때문에 시간이 많이 걸리고, VM 이미지는 사이즈가 커서 스토리지를 많이 사용한다는 단점이 있다.

이러한 배포 구조와 잘 맞는 것이 Docker (Docker 개념 http://bcho.tistory.com/805 ) 인데, Docker는 컨테이너 기반으로 경량화가 되어 있기 때문에, 이미지 베이킹 시간이 상대적으로 짧고, 이미지 사이즈가 작아서 저장이 용이하며, 이미지를 저장하기 위한 리파지토리와 같은 개념이 잘되어 있다.


Spinnaker의 경우 이런 Docker 기반의 피닉스 서버 패턴 기반의 배포를 지원하는데, 특히 Kubernetes 클러스터를 매우 잘 지원하기 때문에, 오히려 VM 기반의 배포 보다는 Docker + Kubernetes 배포 구조를 선택하는 것이 좋다.


이 경우 인프라 배포에 있어서는 애플리케이션을 서비스하는 VM워크로드는 도커를 사용하되, Redis, RDBMS와 같은 미들웨어 솔루션은 재 배포가 거의 발생하지 않기 때문에, VM에 배포하여 사용하는 것이 성능적으로 더 유리하기 때문에, 도커와 VM 을 하이브리드 구조로 배포하는 방식을 권장한다.


클라우드 전용 배포 솔루션  VS 오픈소스 (Terraform)

앞에서 설명한 아키텍쳐에서 사용한 솔루션은 모두 오픈 소스 기반이다. 클라우드 벤더의 경우에는 구글은 Deployment Manager와, 아마존은 CloudFormation을 이용하여, 코드 기반의 배포 (Terraform과 동일)를 지원하는데, 그렇다면, 클라우드에서 제공하는 전용 솔루션을 쓰는 것이 좋은가? 아니면 오픈소스나 벤더에 종속적이지 않은 솔루션을 사용하는 것이 좋은가

오픈소스의 배포툴의 경우에는 요즘 트랜드가 다른 영역으로 확장을 해가는 추세가 있기 때문에, 코드 기반의 인프라 배포 이외에도 애플리케이션 코드 배포등 점점 더 넓은 영역을 커버할 수 있는 장점이 있고, 오픈 소스 생태계내에서 다른 제품들와 연동이 쉬운점이 있다. 그리고 특정 클라우드 벤더나 인프라에 종속성이 없기 때문에 조금 더 유연하게 사용이 가능하지만, 클라우드 벤더에서 제공되는 새로운 서비스나 기능 변화를 지원하는 것에는 상대적으로 클라우드 벤더에서 제공하는 도구보다 느리다. 예를 들어 구글 클라우드에서 새로운 서비스가 나왔을때, 테라폼에서 이 기능을 지원하는데 까지는 시간이 걸린다는 것이다.


양쪽다 좋은 선택지가 될 수 있기 때문에, 현재 환경에 맞는 솔루션을 선택하는 것을 권장한다.




Packer와 Ansible을 이용하여, node.js 이미지 생성하기


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


앞서 글에서 패커를 이용한 이미지 생성 및, 이미지 타입(http://bcho.tistory.com/1226) 에 대해서 알아보았다. 이번 글에서는 node.js 가 깔려있는 파운데이션 타입의 구글 클라우드 VM이미지를 패커와 앤서블을 이용해서 구현해 보도록 한다. 이 글을 이해하기 위해서는 http://bcho.tistory.com/1225 에 대한 이해가 필요하다.


구성은 다음과 같다. 패커를 이용하여, Debian OS 기반의 이미지를 만든 후에, 패커의 Provisioner를 이용하여 Ansible을 설치하고, 이 설치된 Ansible을 이용하여 node.js등을 설치하는 playbook 을 실행하는 순서로 node.js용 이미지를 만든다.  



패커 스크립트는 다음과 같다.

builder 부분은 예전과 같다.(http://bcho.tistory.com/1225) Debian 이미지를 기반으로 VM을 생성한다.

VM 생성후에, 소프트웨어 설치등을 정의하는 부분은 provisioner 라는 부분에 정의되는데, 두 타입의 Provisioner가 사용되었다. 첫번째는 shell 타입이고 두번째는 ansible-local 형태의 provisioner이다.


{

 "variables":{

   "project_id":"terrycho-sandbox",

   "prefix":"debian-9-nodejs"

 },

 "builders":[

  {

   "type":"googlecompute",

   "account_file":"/Users/terrycho/keys/terrycho-sandbox-projectowner.json",

   "project_id":"{{user `project_id`}}",

   "source_image":"debian-9-stretch-v20180105",

   "zone":"us-central1-a",

   "ssh_username":"ubuntu",

   "image_name":"{{user `prefix`}}-{{timestamp}}",

   "machine_type":"n1-standard-4"

  }

 ],

 "provisioners":[

   {

     "type":"shell",

     "execute_command":"echo 'install ansible' | {{ .Vars }} sudo -E -S sh '{{ .Path }}'",

     "inline":[

               "sleep 30",

               "apt-add-repository ppa:rquillo/ansible",

               "/usr/bin/apt-get update",

               "/usr/bin/apt-get -y install ansible"

               ]

   },

   {

      "type":"ansible-local",

      "playbook_file":"./nodejs_playbook.yml"

   }


 ]


}


첫번째 provisioner에서는 ansible을 apt-get으로 설치하기 위해서 sudo 권한으로 apt-get update를 실행하여, 리파지토리 정보를 업데이트 한후에, apt-get -y install ansible을 이용하여, ansible을 설치한다.


두번째 provisioner는 ansible-local provisioner로, 앞단계에서 설치된 ansible을 로컬에서 실행하여, playbook을 실행해주는 코드이다.

ansible은 Configuration management & Deployment 도구로, 나중에 기회가 되면 다른글을 이용해서 소개하도록 한다.

이 코드에서 호출된 nodejs_playbook.yml 파일의 내용은 다음과 같다.

- hosts: all

 tasks:

       - name : create user node

         become : true

         user :

             name: nodejs

             state : present

       - name : update apt-get install

         shell : curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -

       - name : install node.js LTS

         become : true

         #become_user: nodejs

         apt : pkg=nodejs state=installed update_cache=true


hosts:all로, ansible에 등록된 모든 호스트에 대해서 스크립트를 실행하도록 한다. 여기서는 별도의 호스트를 등록하지 않았고, ansible-local 타입으로 실행하였기 때문에, 이 호스트 (localhost)에만 스크립트가 실행된다.

크게 3단계로 실행이 되는데, 첫번째가 nodejs라는 사용자를 만드는 단계로, user 라는 모듈을 사용하여 nodejs라는 사용자를 생성하였다. 이 사용자 계정은 향후 애플리케이션이 배포되었을때, nodejs를 실행할 계정으로 사용된다. 사용자 계정을 만들기 위해서는 root 계정을 획득해야하기 때문에, become: true로 하여 sudo 로 명령을 실행하도록 하였다.

두번째는 node.js를 인스톨하기 위해서 설치전 사전 스크립트를 실행하는 부분이다. apt-get install을 디폴트 상태에서 실행하게 되면 node.js 4.x 버전이 인스톨된다. 최신  8.X 버전을 인스톨하기 위해서, 스크립트를 실행한다. 앤서블 모듈중에서 shell 모듈을 이용하여 쉘 명령어를 실행하였다.

세번째 마지막은 apt 모듈을 이용하여, node.js를 인스톨하도록 한다.


스크립트 작업이 끝났으면, 이미지를 생성해보자

%packer build node.json


으로 실행을 하면 이미지가 생성된다. 생성된 이미지는 구글 클라우드 콘솔의 GCE (Google Compute Engine)의 Images 메뉴에서 확인이 가능하다.

다음과 같이 debian-9-nodejs-*로 새로운 이미지가 생성된것을 확인할 수 있다.



생성된 이미지가 제대로 되었는지를 확인하기 위해서, 이 이미지로 VM을 생성해서 nodejs 버전을 확인해보면 다음과 같이 8.9.4 가 인스톨 되었음을 확인할 수 있다.

또한 nodejs로 된 계정이 생성되었는지를 확인하기 위해서 /etc/passwd 내에 사용자 정보가 생성되었는지를 확인해보면 아래와 같이 nodejs 이름으로 계정이 생성되었음을 확인할 수 있다.



참고 : https://blog.codeship.com/packer-ansible/


배포 자동화 시스템을 Packer와 Ansible을 이용해서 만드려고 하나씩 살표보는데, Ansible이 SSH 기반이다.

SSH로 다른 호스트를 접근하려면, 처음에, 해당 호스트의 FingerPrint를 등록할것인지를 물어보는데, 이로 인해서 Ansible 스크립트를 처음 실행할때, 이 물어보는 프롬프트 때문에, 스크립트가 중간에 멈추거나 또는 입력을 받지 못해서 대상 호스트로 접속이 안될 수 있다.


한번 Finger Print를 등록해놓으면, 다음부터는 물어보지 않기 때문에 문제는 없지만, 이를 해결하기 위해서는 처음에도 물어보지 않도록 미리 등록을 해놓아야 한다.


대략 내용을 보니, Finger Print를 등록하는 프롬프트에서 등록을 하게 되면, 해당 호스트는 ~/.ssh/known_hosts 라는 파일에 등록이 된다. 


미리 등록하는 방법은 여러가지 방법이 있는데, 키 체킹등을 패스하는 방법등을 사용하면 보안상 문제가 될 수 있기 때문에 (https://stackoverflow.com/questions/32297456/how-to-ignore-ansible-ssh-authenticity-checking 많은 사람들이 Ansible의 경우 키 체킹을 패스 하는 방법을 쓰는데, MINTM Attack 에 취약하고, 이렇게 보안 취약점이 생기는 만큼 ) , 단순하게 "되는 방법보다", "보안적으로 문제가 없는" 방법을 찾아봐야 겠다.


-- 1/13일 추가


known_host에 호스트명을 추가하면 되는데, 방법은


ssh-keyscan -t rsa host명 >> ~/.ssh/known_host


또는 여러 호스트를 한꺼번에 입력하고자 할때는


ssh-keyscan -t rsa -f host명들 이들어있는 파일명 >> ~/.ssh/known_host



피닉스 패턴의 VM 이미지 타입


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


피닉스 서버 패턴을 이용해서 이미지를 만들때, 그러면 이미지에 어디까지 패키징이 되어야할지 결정할 필요가 있다. 정답은 없지만 몇가지 정형화된 패턴을 찾을 수 는 있다


OS Image

가상화 환경이나 클라우드를 사용하면 디폴트로 사용하는 패턴으로 이미지가 OS 단위로 되어 있는 패턴이다. 우분투 이미지, 윈도우 이미지와 같이 OS 단위로 이미지가 되어 있다.




피닉스 패턴을 사용할 경우 애플리케이션 배포시, 이미지를 이용해서 VM 을 생성하고 VM 이 기동될때, Configuration management 도구를 이용하여 소프트웨어 스택 (미들웨어, 라이브러리등)과 애플리케이션 코드를 배포하는 방식이다.

Foundation Image

Foundation Image는 이미지를 OS단위가 아니라 서비스 플랫폼, 예를 들어 Ruby on rails 환경, PHP환경과 같은 환경 별로 관리하는 방법이다.



일종의 PaaS와 같은 개념의 이미지로 생각되는데, 가장 적절한 절충안이 아닌가 싶다.


Immutable Image

마지막으로는 Immutable Image (불변) 이미지인데, 이 이미지 타입은 배포마다 매번 새롭게 이미지를 만드는 패턴이다.


항상 OS 부터 애플리케이션 까지 전체 스택이 같이 이미지화 되어 배포되기 때문에, 최신 업데이트를 유지하기가 좋지만, 빌드 시간이 많이 걸리고 관리해야 하는 이미지 양이 많아진다.

이 패턴으로 갈거면 도커를 쓰는게 오히려 정답이 아닐까 싶다.


 OS 이미지 패턴의 경우 VM이 올라오면서 소프트웨어들이 설치되고 애플리케이션이 설치되는 모델인데, 소프트웨어 특히 npm이나 pip들을 이용해서 라이브러리를 설치할때 외부 저장소를 이용하는 경우, 외부 저장소가 장애가 날 경우 소프트웨어 설치가 안되기 때문에 외부 시스템 장애에 대한 의존성을 가지고 있고 설치 시간이 길기 때문에 그다지 좋은 패턴으로는 판단이 안되고, immutable 패턴은 위에서도 언급했듯이 빌드 시간이 길고, 여러 이미지를 관리해야하기 때문에 그다지 권장하고 싶지 않지만, 전체를 매번 묶어서 배포함으로써 일관성 유지가 가능한 장점이 있기 때문에 만약에 해야 한다면 도커를 이용해서 구현하는 것이 어떨까 한다. Foundation Image 패턴이 가장적절한 패턴으로 판단되는데, 다음글에서는 Packer를 이용하여, Foundation Image 타입을 만드는 방법을 알아보도록 하겠다.


Packer


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


Packer (https://www.packer.io/) 는 HashiCorp에서 개발한 가상 머신 이미지를 만들어주는 오픈소스이다.

예를 들어서, 아마존 클라우드 AMI이미지나, 구글 클라우드 이미지를 스크립트를 이용하여 생성이 가능하다.

하나의 스크립트를 이용하여, 구글 클라우드, VMWare, 아마존 클라우드 등 여러 클라우드 환경 (가상화 환경)과 컨테이너 엔진용 이미지를 생성할 수 있다.


Chef,Puppet,Ansible과 같은 Configuration management 툴과 혼동이 될 수 있지만, Packer는 OS 이미지를 만들어주는 역할을 하고, Configuration management 툴들은 이렇게 만들어진 이미지 위에 소프트웨어를 설치하고, 이를 설정하는 상호 보완적인 역할을 하게 된다.

특히 피닉스 서버 패턴에서 VM 이미지를 생성하는데 매우 유용하게 사용될 수 있다. 피닉스 서버 패턴은 http://bcho.tistory.com/1224 를 참고하기 바란다.

템플릿

전체 컨셉은 VM의 설정을 JSON 파일에 정의해놓고, packer 툴을 이용하여 이미지를 생성하는 방식이다.

VM의 설정을 정의한 파일을 템플릿 파일이라고 하는데, 다음과 같은 구조를 가지고 있다.


  • Variable : 변수를 정의하는 섹션으로, 동적으로 변경될 수 있는 클라우드 프로젝트명, 리전명등을 정의하는 부분이다. 메인 템플릿내에 섹션으로 정의할 수 도 있고, 또는 환경 변수나 별도의 변수만 지정한 파일 또는 CLI 옵션으로도 변수값을 전달할 수 있다.

  • Builder : 가장 핵심이 되는 부분으로 OS 버전등 VM 설정에 대한 부분을 정의한다.

  • Provisioner : 이미지에서 OS 설정이 끝난후에, 소프트웨어 스택을 설치하는 부분을 정의한다. 앞에서도 언급하였지만 Packer는 다양한 가상환경에 대한 이미지 생성에 최적화 되어 있지 소프트웨어 설치를 용도로 하지 않기 때문에, Provisioner에서는 다른 configuration management 툴과의 연계를 통해서 소프트웨어를 설치하도록 지원한다. 간단한 쉘을 이용하는것에서 부터, ansible,chef,puppet,salt stack등 다양한 configuration management 도구를 지원하도록 되어 있다. https://www.packer.io/docs/provisioners/index.html
    이 과정에서 OS 설치 후, 소프트웨어 스택 설치 뿐만 아니라, 패치 및 기타 OS 설정 작업을 진행할 수 있다.

  • Post-Processor : Builder와 Provisioner에 의한 이미지 생성이 끝나면 다음으로 실행되는 명령이다.

간단한 예제

이해를 돕기 위해서 직접 간단한 이미지를 만들어보도록 하자.

예제는 맥북에서 packer를 사용하여 구글 클라우드 이미지를 만드는 예제이다. 구글 클라우드의 간단한 사용법은 http://bcho.tistory.com/1107 문서를 참고하기 바란다.

설치 하기

설치는 매우 간단하다. packer는 커맨드 라인 형태의 툴이기 때문에, https://www.packer.io/downloads.html 에서 다운로드 받은후에, 압축을 푼후, PATH에 추가해서 사용하면 된다.


환경 준비

구글 클라우드 API 활성화

packer의 이미지 생성은 구글 클라우드에 접속하여 VM을 만들어서 이미지를 생성하고 이를 구글 클라우드에 등록하는 방식이기 때문에, 구글 클라우드의 관련 API들을 호출해야 한다.

그래서 이 API를 외부에서 호출이 가능하도록 Enable 해줘야 하는데, 아래와 같이 구글 클라우드 메뉴에서 APIs & Services 항목에 들어가면 필요한 API들을 활성화 할 수 있다.



필요한 API는

  • Google Cloud Billing API

  • Google Compute Engine API

를 Enable(활성화) 해줘야 한다

Service Account 파일 생성

packer가 구글 클라우드 API를 사용하기 위해서는 API를 호출하기 위한 인증과 권한 인가가 필요하다. 구글 클라우드는 여러가지 방법을 제공하는데, 여기서 사용할 방법은 service account 를 이용하는 방법이다. 콘솔에서 service account를 생성하고, 이 계정에 여러가지 권한을 부여할 수 있는데, 이렇게 생성된 service account에 대한 인증 정보는 파일로 생성이 된다. 이 예제에서는 이 파일의 경로를 지정하여 service account의 권한을 이용하여 이미지를 생성하도록 하였다.

Service account 파일을 생성하는 자세한 방법은 http://bcho.tistory.com/1166 를 참고하기 바란다.

예제 코드

준비가 끝났으면 이제 실제로 간단한 이미지를 만들어보자. 아래는 gce.json 파일로, n1-standard-4 사이즈 VM에 debian-9 OS로 된 이미지를 구글 클라우드에 만드는 예제이다.


{

 "variables":{

   "project_id":"terrycho-sandbox",

   "prefix":"terrycho-packer"

 },

 "builders":[

  {

   "type":"googlecompute",

   "account_file":"/Users/terrycho/keys/terrycho-sandbox-projectowner.json",

   "project_id":"{{user `project_id`}}",

   "source_image":"debian-9-stretch-v20180105",

   "zone":"us-central1-a",

   "ssh_username":"ubuntu",

   "image_name":"{{user `prefix`}}-{{timestamp}}",

   "machine_type":"n1-standard-4"

  }

 ]

}

이 예제가 제대로 작동하기 위해서는 variables의 project_id를 본인것으로 변경해야 하고,  account_file 부분에 본인이 생성하여 다운로드 받은 service account 파일의 경로를 지정해야 한다.


설정 파일의 내용을 상세하게 알아보도록 하자.

  • account_file : 구글 클라우드 API 에 접근하기 위한 Service account 파일 경로

  • project_id : 이미지 생성에 사용할 구글 클라우드 프로젝트 ID를 지정한다. 여기서는 프로젝트 이름을 terrycho-sandbox로 지정하였다.

  • source_image : 이미지를 생성할때 베이스 이미지를 선택한다. 베이스 이미지는 구글 클라우드에 등록된 이미지를 기준으로 하는데, 이미지 목록은 google cloud CLI 명령인 gcloud  명령을 이용하면 된다.
    %gcloud  compute images list
    명령을 이용하면 현재 가용한 이미지 목록을 볼 수 있고, 거기서 필요한 이미지 이름을 사용하면 된다. 여기서는 debian-9 이미지를 사용하였다.

  • image_name : 생성될 이미지 명. 구글 클라우드 GCE에 이 이름으로 이미지가 등록된다.

  • zone : 이미지를 만드는 방식이 실제 구글 클라우드에서 VM 인스턴스를 만들었다가 이를 기반으로해서 이미지를 추출하는 방식이기 때문에, 이미지를 추출하기 위한 이 VM을 어느 zone에서 기동 시킬지를 지정한다.

예제 실행


파일 작성이 끝난후 아래와 같이 프롬프트 상에서 packer build 명령을 이용하면 이미지 생성이 시작된다.

%packer build gce.json


아래는 명령어를 실행한 결과이다. 로그를 보면 구글 클라우드내에서 설정에 따라서 VM을 만들었다가 이미지를 추출하고, VM을 지우는 과정을 확인할 수 있다.



이 과정이 끝나고 구글 클라우드 콘솔에서 Compute Engine > Image 메뉴를 들어가보면 아래 그림과 같이 terrycho-packer-.... 라는 이름으로 이미지가 생성되어 등록된것을 확인할 수 있다.



이 이미지를 이용하여, 새로운 VM을 만들면 된다. 



피닉스 서버

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


근래에 들어서 인프라 스트럭쳐를 소프트웨어로 정의하는 Infrastructure As a Code (줄여서 IaC라고 부름)를 관심있게 보고 있는데, CI/CD의 단순 연장선상의 하나의 툴링정도로 생각했는데, 생각보다 상당히 넓은 생태계라서 좀더 깊게 보고 있다.

IaC는 일반적인 툴이나 단순한 프로세스가 아니라 하나의 사상이기 때문에 이를 제대로 이해하기 위해서는 툴링 관점의 접근 보다는 사상과 배경에 대해서 제대로 이해할 필요가 있다.

IaC 개념을 이해하는데 도움이 되는 개념으로 Snowflakes Server (스노우플레이크 서버)와 Phoenix Server(피닉스 서버) 두 가지 개념에 대해서 알아볼 필요가 있다.

Snowflakes Server (스노우 플레이크 서버)

예전에 일반적으로 서버를 운영하는 방법은 서버를 설치한 후 OS를 인스톨한 후에, 필요한 소프트웨어와 애플리케이션을 설치하여 운영하는 형태였다. 여기에 문제가 생긴 경우 패치를 하거나 정기적인 보안 패치 튜닝들을 해당 서버에 지속적으로 적용하고, 애플리케이션은 CI/CD 등의 툴을 이용하여 배포하는 구조를 가지고 있었다.


이렇게 한번 설치한 서버에 계속해서 설정을 변경하고 패치를 적용하는 등의 업데이트를 지속적으로 적용하여 운영하는 서버를 스노우 플레이크 서버 (눈송이 서버)라고 하는데, 이렇게 설정된 서버는 다시 똑같이 설정하기가 매우 어렵다. 모든 설정과정이 문서화가 잘되어 있으면 모르겠지만 대부분 문서화가 꼼꼼한 경우도 드물뿐더러,  담당자가 바뀌거나 관리 조직이 바뀌는 경우에는 그 이력이 제대로 유지되는 경우가 없다. 그래서 장비를 업그레이드 하거나 OS를 새로 인스톨해서 같은 환경을 꾸미고자 할때 예전 환경과 동일한 환경을 구성하기가 어렵고 그래서, 누락된 설정이나 패치등에 의해서 장애가 발생하는 경우가 많다.

이렇게 한번 설정을 하고 다시 설정이 불가능한, “마치 눈처럼 녹아버리는" 서버의 형태를 스노우 플레이크 서버라고 한다.


재 구성의 문제뿐 아니라, 이런 스노우 플레이크 서버는 구성 편차를 유발하기도 하는데, 여러대의 웹서버를 운영하고 있는 조직에서, 문제가 있어서 특정 서버를 패치한 경우, 다른 동일한 웹서버를 모두 패치 하지 않는 이상 구성이 달라진다.  이는 또 운영상의 문제를 일으킬 수 있다.

Phoenix Server (피닉스 서버)

그래서 나온 서버 패턴이 피닉스 서버 패턴인데, 피닉스(불사조)는 불멸로도 알려져있지만 정확히는 불속에서 다시 태어나는 re-born (재탄생)의 개념을 가지고 있다. 이 재탄생의 개념을 서버 설정 방식에 적용한 패턴이 피닉스 서버 패턴이다.

새로운 소프트웨어를 인스톨하거나 설정을 변경할때 기존 서버에 변경 작업을 더 하는 것이 아니라, 처음 OS 설치에서 부터, 소프트웨어 인스톨, 설정 변경까지를 다시 반복하는 것이다.

예를 들어 우분투 16, 톰캣 7.0 버전으로 운영되고 있는 서버가 있을때, 이 서버에 로그 수집 모듈은 fluentd를 설치하는 케이스가 있다고 가정하자.


스노우플레이크 서버 패턴의 경우에는 이미 운영되고 있는 서버에 새롭게 fluentd를 일일이 설치하거나 자동화가 되어 있는 경우에는 ansible 이나 chef등의 configuration management 도구를 이용하여 fluentd를 설치하게 된다.


피닉스 서버 패턴의 경우에는 새 VM을 다시 만들고, 우분투 16 OS를 설치하고, 톰캣 7.0을 설치하고 fluentd를 설치한 다음. 이 VM으로 기존 VM 을 교체한다.


매번 전체 설치를 반복한다면 매우 많은 시간이 들텐데, 어떻하냐? 물론 매번 이렇게 새롭게  모든 스택을 설치하지 않는다. 어느정도 공통 스택은 가상머신의 베이스 이미지 (VM Base Image)로 만들어놓고, 이 이미지를 이용하여 VM을 생성한 후에, 차이가 나는 부분만 설정을 하는 구조를 설정하는 구조를 사용하게 되고 이 과정은 스크립트 코드를 이용해서 자동화하기 때문에, 그렇게 많은 시간이 소요되지 않는다.

피닉스 서버 패턴에서는 매번 전체를 스크립트를 이용해서 설치하기 때문에, 다음과 같은 장점을 가지게 된다.


  • 스크립트에 모든 설정 정보가 유지되게 된다.

  • 이 스크립트 코드를, git와 같은 소스 코드 관리 시스템을 이용해서 관리하게 되면, 어떤 부분을 누가 어떻게 수정을 했는지 추적이 가능하게 된다.

피닉스 서버 패턴을 이용한 배포 구조

그러면 이 피닉스 서버 패턴을 이용한 배포 구조를 보도록 하자

앞의 예제와 같이 Ubuntu 16, Tomcat 7이 설치된 VM서버 2대가 서비스되고 있고, 이 서버들은 앞단에 로드밸런서에 연결되어 있다고 생각하자. 이 VM들은 Ubuntu16, Tomcat 7이 설치되어 있는 Base Image로 생성했다고 가정하자.



Fluentd를 설치하기 위해서는 피닉스 서버 패턴 처럼, Fluentd가 설치된 새로운 base image를 생성하고, 이 이미지를 이용하여 새로운 VM들을 만든다. 이 VM들 그룹은 아직 서비스가 되기 전으로, 이 그룹을 Pre-production 그룹이라고 한다.



Pre-production 그룹이 정상적으로 들어왔으면 로드밸런서를 변경하여 기존의 구 버전 VM으로 들어가던 트래픽을 pre-production 그룹으로 옮겨준다.


그리고 마지막으로, 기존의 운영환경을 지워주면 아래와 같이 새 환경으로 서비스 하는 환경만 남게 된다.


이렇게 서버가 구 버전에서 새버전으로 재탄생(re-born)하는 것이 피닉스 서버 패턴이다.


이 패턴의 문제는 VM 이미지를 빌드하기 때문에, 빌드 및 배포 시간이 상대적으로 오래 걸리고, 또한 배포 당시 기존 Production 환경과 Pre-production 환경이 공존하기 때문에, 다소 추가 비용이 소요될 수 있다.


이 피닉스 서버 패턴에서 중요한것은 기존 이미지를 이용하여 변경을 주고, 이를 다시 이미지로 만들어낼 수 있지만, 이렇게 하면 스노우플레이크 서버이지 피닉스 서버가 아니다. 피닉스 서버는 기존 이미지를 재 사용하는게 아니라 처음부터 모든 스택을 새로 설치해야 하기 때문에, 처음부터 모든 스택을 설치하여 이미지를 만들 수 있는 자동화 툴이 필요하다. 다음 글에서는 이미지 생성을 자동화 해주는 HashiCorp의 Packer라는 오픈소스에 대해서 알아보도록 하겠다.


https://github.com/NVIDIA/DeepRecommender


북마크


K Fold Cross Validation

빅데이타/머신러닝 | 2018.01.02 01:04 | Posted by 조대협

K Fold Cross Validation


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


K 폴드 크로스 벨리데이션 / 교차 검증이라고도 함. 용어 정리.

별거 있는건 아니고 전체 데이타를 K개로 나눈다음. (각각을 폴드라고함), 

첫번째 학습에서는 첫번째 폴드를 테스트 데이타로 쓰고

두번째 학습에서는 두번째 폴드를 테스트 데이타로 쓰고

N번째 학습에서는  N번째 폴드를 테스트 데이타로 쓴다.



(출처 : http://library.bayesia.com/pages/viewpage.action?pageId=16319010)


그래서 폴드가 5개면 5 Fold CV (Cross validation)이라고 한다.


Apache Beam (Dataflow)를 이용하여, 이미지 파일을 tfrecord로 컨버팅 하기


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



개요

텐서플로우 학습에 있어서 데이타 포맷은 학습의 성능을 결정 짓는 중요한 요인중의 하나이다. 특히 이미지 파일의 경우 이미지 목록과 이미지 파일이 분리되어 있어서 텐서플로우에서 학습시 이미지 목록을 읽으면서, 거기에 있는 이미지 파일을 매번 읽어야 하기 때문에, 코딩이 다소 지저분해지고,IO 성능이 떨어질 수 있다

텐서플로우에서는 이러한 학습 데이타를 쉽게 읽을 수 있도록 tfrecord (http://bcho.tistory.com/1190)라는 파일 포맷을 지원한다.


이 글에서는 이미지 데이타를 읽어서 tfrecord 로 컨버팅하는 방법을 설명하며, 분산 데이타 처리 프레임웍인 오픈소스 Apache Beam을 기준으로 설명하나, tfrecord 변환 부분은 Apache Beam과 의존성이 없이 사용이 가능하기 때문에, 필요한 부분만 참고해도 된다. 이 Apache Beam을 구글의 Apache Beam 런타임 (매니지드 서비스)인 구글 클라우드의 Dataflow를 이용하여, 클러스터를 이용하여 빠르게 데이타를 처리하는 방법에 대해서 알아보도록 한다.


전체 코드는 https://github.com/bwcho75/cifar-10/blob/master/pre-processing/4.%20Convert%20Pickle%20file%20to%20TFRecord%20by%20using%20Apache%20Beam.ipynb 에 있다.


이 코드는 CIFAR-10 이미지 데이타를 Apache Beam 오픈 소스를 이용하여, 텐서플로우 학습용 데이타 포맷인  tfrecord 형태로 변환 해주는 코드이다.


Apache Beam은 데이타 처리를 위한 프레임웍으로, 구글 클라우드 상에서 실행하거나 또는 개인 PC나 Spark 클러스터상 여러 환경에서 실행이 가능하며, 구글 클라우드 상에서 실행할 경우 오토스케일링이나 그래프 최적화 기능등으로 최적화된 성능을 낼 수 있다.


CIFAR-10 데이타 셋은 32x32 PNG 이미지 60,000개로 구성된 데이타 셋으로 해당 코드 실행시 최적화가 되지 않은 상태에서 약 16분 정도의 처리 시간이 소요된다. 이 중 6분 정도는 Apache Beam 코드를 구글 클라우드로 업로드 하는데 소요되는 시간이고 실제 처리시간은 10분정도가 소요된다. 전처리 과정에 Apache Beam을 사용하기 전에 고려해야 할 요소는 다음과 같다.

  • 데이타가 아주 많아서 전처리 시간이 수시간 이상 소요될 경우 Apache Beam + Google Cloud를 고려하여 여러 머신에서 동시에 처리하여 빠른 시간내에 수행되도록 할 수 있다.

  • 데이타가 그다지 많지 않고 싱글 머신에서 멀티 쓰레드로 처리를 원할 경우에는 Apache Beam으로 멀티 쓰레드 기반의 병렬 처리를 하는 방안을 고려할 수 있다. 이 경우 클라우드에 대한 의존성을 줄일 수 있다.

  • 다른 대안으로는 Spark/Hadoop 등의 오픈소스를 사용하여, On Prem에서 여러 머신을 이용하여 전처리 하는 방안을 고려할 수 있다.

여기서는 아주 많은 대량의 이미지 데이타에 대한 처리를 하는 것을 시나리오로 가정하였다.

전처리 파이프라인

Apache Beam을 이용한 데이타 전처리 파이프라인의 구조는 다음과 같다.

이미지 파일 준비

CIFAR-10 데이타셋 원본은 이미지 파일 형태가 아니라 PICKLE이라는 파일 포맷으로 되어 있기 때문에,  실제 개발 환경에서는 원본데이타가 이미지인것으로 가정하기 위해서 https://github.com/bwcho75/cifar-10/tree/master/pre-processing 의 1~2번 코드를 통해서 Pickle 파일을 이미지 파일로 변경하고, *.csv 파일에 {파일명},{레이블} 형태로 인덱스 데이타를 생성하였다.

생성된 이미지 파일과 *.csv 파일은 gsutil 명령어를 이용하여 Google Cloud Storage (aka GCS)에 업로드 하였다. 업로드 명령은 https://github.com/bwcho75/cifar-10/blob/master/pre-processing/2.%20Convert%20CIFAR-10%20Pickle%20files%20to%20image%20file.ipynb 에 설명되어 있다.


전처리 파이프라인의 구조

Apache Beam으로 구현된 파이프라인의 구조는 다음과 같다.


1. TextIO의 ReadFromText로 CSV 파일에서 한 라인 단위로 문자열을 읽는다.

2. parseLine에서 라인을 ,로 구분하여 filename과 label을 추출한다.

3. readImage 에서 filename을 가지고, 이미지 파일을 읽어서, binary array 형태로 변환한다.

4. TFExampleFromImageDoFn에서 이미지 바이너리와 label을 가지고 TFRecord 데이타형인 TFExample 형태로 변환한다.

5. 마지막으로 TFRecordIOWriter를 통해서 TFExample을 *.tfrecord 파일에 쓴다.

코드 주요 부분 설명

환경 설정 부분

이 코드는 구글 클라우드와 로컬 환경 양쪽에서 모두 실행이 가능하도록 구현되었다.

SRC_DIR_DEV는 로컬환경에서 이미지와 CSV 파일이 위치한 위치이고, DES_DIR_DEV는 로컬환경에서 tfrecord 파일이 써지는 위치이다.

구글 클라우드에서 실행할 경우 파일 저장소를  GCS (Google Cloud Storage)를 사용한다. DES_BUCKET은 GCS 버킷 이름이다. 코드 실행전에 반드시 구글 클라우드 콘솔에서 GCS 버킷을 생성하기 바란다.  SRC_DIR_PRD와 DES_DIR_PRD는 GCS 버킷내의 각각 image,csv 파일의 경로와 tfrecord 파일이 써질 경로 이다. 이 경로에 맞춰서 구글 클라우드 콘솔에서 디렉토리를 먼저 생성해 놓기를 바란다.




PROJECT는 구글 클라우드 프로젝트 명이고, 마지막으로 DEV_MODE가 True이면 로컬에서 수행이되고 False이면 구글 클라우드에서 실행하도록 하는 환경 변수이다.

의존성 설정 부분

로컬에서 실행할 경우필요한  파이썬 라이브러리가 이미 설치되어야 있어야 한다.

만약에 구글 클라우드에서 실행할 경우 이 Apache Beam 코드가 사용하는 파이썬 모듈을 명시적으로 정의해놔야 한다. 클라우드에서 실행시에는 Apache Beam 코드만 업로드가 되기 때문에(의존성 라이브러리를 같이 업로드 하는 방법도 있는데, 이는 추후에 설명한다.), 의존성 라이브는 구글 클라우드에서 Dataflow 실행시 자동으로 설치할 수 있도록 할 수 있는데, 이를 위해서는 requirements.txt 파일에 사용하는 파이썬 모듈들을 정의해줘야 한다. 다음은 requirements.txt에 의존성이 있는 파이썬 모듈등을 정의하고 저장하는 부분이다.


Apache Beam 코드

Apache Beam의 코드 부분은 크게 복잡하지 않기 때문에 주요 부분만 설명하도록 한다.

Service account 설정

Apache Beam 코드를 구글 클라우드에서 실행하기 위해서는 코드 실행에 대한 권한을 줘야 한다. 구글 클라우드에서는 사용자가 아니라 애플리케이션에 권한을 부여하는 방법이 있는데, Service account라는 것을 사용한다. Service account는 json 파일로 실행 가능한 권한을 정의하고 있다.

Service account 파일을 생성하는 방법은 http://bcho.tistory.com/1166 를 참고하기 바란다.

Service account 파일이 생성되었으면, 이 파일을 적용해야 하는데 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 Service account  파일의 경로를 정의해주면 된다. 파이썬 환경에서 환경 변수를 설정하는 방법은 os.envorin[‘환경변수명']에 환경 변수 값을 지정해주면 된다.

Jobname 설정

구글 클라우드에서 Apache Beam 코드를 실행하면, 하나의 실행이 하나의 Job으로 생성되는데, 이 Job을 구별하기 위해서 Job 마다 ID 를 설정할 수 있다. 아래는 Job ID를 ‘cifar-10’+시간 형태로 지정하는 부분이다


환경 설정

Apache Beam 코드를 구글 클라우드에서 실행하기 위해서는 몇가지 환경을 지정해줘야 한다.


  • staging_location은 클라우드 상에서 실행시 Apache Beam 코드등이 저장되는 위치이다. GCS 버킷 아래 /staging이라는 디렉토리로 지정했는데, 실행 전에 반드시 버킷아래 디렉토리를 생성하기 바란다.

  • temp_location은 기타 실행중 필요한 파일이 저장되는 위치이다. 실행 전에 반드시 버킷아래 디렉토리를 생성하기 바란다.

  • zone은 dataflow worker가 실행되는 존으로 여기서는 asia-northeast1-c  (일본 리전의 c 존)으로 지정하였다.


DEV_MODE 에 따른 환경 설정

로컬 환경이나 클라우드 환경에서 실행이냐에 따라서 환경 변수 설정이 다소 달라져야 한다.


디렉토리 경로를 바꿔서 지정해야 하고, 중요한것은 RUNNER인데, 로컬에서 실행하기 위해서는 DirectRunner를 구글 클라우드 DataFlow 서비스를 사용하기 위해서는 DataflowRunner를 사용하면 된다.


readImage 부분

Read Image는 이미지 파일을 읽어서 byte[] 로 리턴하는 부분인데, 로컬 환경이냐, 클라우드 환경이냐에 따라서 동작 방식이 다소 다르다.

클라우드 환경에서는 이미지 파일이 GCS에 저장되어 있기 때문에 파이썬의 일반 파일 open 명령등을 사용할 수 없다.

그래서 클라우드 환경에서 동작할 경우에는 GCS에서 파일을 읽어서 Worker의 로컬 디스크에 복사를 해놓고 이미지를 읽어서 byte[]로 변환한 후에, 해당 파일을 지우는 방식을 사용한다.


아래 코드에서 보면 DEV_MODE가 False 인경우 GCS에서 파일을 읽어서 로컬에 저장하는 코드가 있다.


storageClient는 GCS 클라이언트이고 bucket 을 얻어온후, bucket에서 파일을 get_blob 명령어를 이용하여 경로를 저장하여 blob.download_to_file을 이용하여 로컬 파일에 저장하였다.

실행

코드 작성이 끝났으면 실행을 한다. 실행 상태는 구글 클라우드 콘솔의 Dataflow  메뉴에서 확인이 가능하다.

아래와 같이 실행중인 그리고 실행이 끝난 Job 리스트들이 출력된다.




코드 실행중에, 파이프라인 실행 상황 디테일을 Job 을 선택하면 볼 수 있다.


여기서 주목할만한 점은 우측 그래프인데, 우측 그래프는 Worker의 수를 나타낸다. 초기에 1대로 시작했다가 오토 스케일링에 의해서 9대 까지 증가한것을 볼 수 있다.

처음 실행이었기 때문에 적정한 인스턴스수를 몰랐기 때문에 디폴트로 1로 시작하고 오토스케일링을 하도록 했지만, 어느정도 테스트를 한후에 적정 인스턴수를 알면 오토 스케일링을 기다릴 필요없이 디폴트 인스턴스 수를 알면 처음부터 그 수만큼 인스턴스 수로 시작하도록 하면 실행 시간을 줄일 수 있다.

만약에 파이프라인 실행시 에러가 나면 우측 상단에 LOGS 버튼을 누르면 상세 로그를 볼 수 있다.


아래 그림은 파이프라인 실행이 실패한 예에서 STACK TRACES를 통해서 에러 내용을 확인하는 화면이다.



해당 로그를 클릭하면 Stack Driver (구글의 모니터링 툴)의 Error Reporting 시스템 화면으로 이동하게 된다.

여기서 디테일한 로그를 볼 수 있다.

아래 화면을 보면 ReadImage 단계에서 file_path라는 변수명을 찾을 수 없어서 나는 에러를 확인할 수 있다.


TFRecord 파일 검증

파이프라인 실행이 끝나면, GCS 버킷에 tfrecord 파일이 생성된것을 확인할 수 있다.


해당 파일을 클릭하면 다운로드 받을 수 있다.

노트북 아래 코드 부분이 TFRecord를 읽어서 확인하는 부분이다. 노트북에서 tfrecord 파일의 경로를 다운로드 받은 경로로 변경하고 실행을 하면 파일이 제대로 읽히는 지 확인할 수 있다.


파일 경로 부분은 코드상에서 다음과 같다.



정상적으로 실행이 된 경우, 다음과 같이 tfrecord에서 읽은 이미지와 라벨값이 출력됨을 확인할 수 있다.


라벨 값은 Label 줄에 values 부분에 출력된다. 위의 그림에서는 순서대로 라벨 값이 4와 2가 된다.



NMF 알고리즘을 이용한 유사 문서 검색과 구현(2/2)

sklearn을 이용한 구현


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


http://bcho.tistory.com/1216 를 통하여 tf-idf를 이용하여 문서를 벡터화 하고, nmf를 이용하여 문서의 특성을 추출한 다음, 코싸인 유사도를 이용하여 유사 문서를 검색하는 알고리즘에 대해서 알아보았다. 이번글에서는 이 알고리즘을 직접 sklearn을 이용해서 구현해보도록 하자. sklearn은 이용하면 분산 학습을 이용한 대규모 데이타 처리는 불가능하지만, 작은 수의 문서나 모델에는 사용이 가능하다. 무엇보다 sklearn의 경우 대부분의 모델을 라이브러리화 해놓았기 때문에, 복잡한 구현이 없이 쉽게 사용이 가능하다.


전체 소스 코드는 https://github.com/bwcho75/dataanalyticsandML/blob/master/NMF%20based%20document%20recommendation/NMF%20based%20similar%20document%20recommendation.ipynb 에 공유되어 있다.


샘플 데이타

여기서 사용할 데이타 셋은 sklearn 테스트 데이타셋에 있는 20개의 뉴스 그룹의 이메일 데이타를 사용한다. 총 20개의 토픽으로 약 18000개의 포스팅으로 구성되어 있다.


이메일 텍스트 형식으로, 제목과, 날짜등의 헤더 정보와 이메일 내용으로 구성되어 있다. 첫번째 코드에서는 이 데이타를 읽어서 제목과 본문만을 추출하여, Pandas data frame에 저장하도록 한다.


from sklearn.datasets import fetch_20newsgroups
import StringIO
import pandas as pd

newsgroups_train = fetch_20newsgroups(subset='train')

def parseDocument(data):
   buf = StringIO.StringIO(data)
   line=buf.readline()
   data=[]
   subject=''
   while line:
       if(line.startswith('Subject:')):
           subject = line[8:].strip()
       elif (line.startswith('Lines:')):
              lines = line[6:]
              while line :
                   line = buf.readline()
                   data.append(line)
       line=buf.readline()
   text = ''.join(data)
   
   return subject,text


textlist = []
df = pd.DataFrame(columns=['text'])
for data in newsgroups_train.data[0:1000]:
   subject,text = parseDocument(data)
   df.loc[subject]=text
df.head()


제목은 ‘Subject:’로 시작하는 줄에 들어 있고, 본문은 ‘Lines:’로 시작하는 줄에 있다. 이 내용들만을 추출하여 pandas data frame에 저장하였다. 본문은 data frame 상에 ‘text’라는 이름으로된 컬럼에 저장하였다.

Tfidf 를 이용한 단어의 벡터화 구현

단어를 벡터로 변환하기 위해서 앞에서 설명한 tfidf 모델을 이용한다. sklearn에 이미 구현이 되어 있기 때문에 어렵지 않게 구현이 가능하다.


from sklearn.feature_extraction.text import TfidfVectorizer

vectorizer = TfidfVectorizer()
vectors = vectorizer.fit_transform(df['text'].tolist())
print(vectors.shape)


간단하게, TfidfVectorizer를 로딩한다음에, fit_transform을 이용하여, 문장의 본문이 있는 데이타 프레임의 ‘text’ 컬럼을 배열 형태로 변환하여 리턴해주면 된다.


NMF를 이용하여 본문에서 특성 추출

문서가 tf-idf를 이용하여 벡터화가 되었으면, NMF를 이용하여, 각 문서별로 특성을 추출한다.

NMF역시 sklearn에 NMF라는 모듈로 미리 정의되어 있다. 단지, 몇개의 특징으로 압축을 해낼것인지만 지정하면 되는데, 여기서는 n_components 인자를 이용하여 총 40개의 특징으로 특성을 압축하여 추출하도록 하였다.


from sklearn.decomposition import NMF

vector_array = vectors.toarray()
nmf = NMF(n_components=40)
nmf.fit(vector_array)
features = nmf.transform(vector_array)


추출된 피쳐는 features 변수에 저장하였다.

피쳐 정규화

추출된 피쳐가 피쳐마다 또는 문서마다 변화의 폭이 클 수 있기 때문에, Normalizer를 이용하여 0~1사이로 스케일링을 한다. 이 정규화 역시 간단하게 아래와 같이 Normalizer 모듈을 이용하면 된다.


from sklearn.preprocessing import Normalizer

normalizer = Normalizer()
norm_features=normalizer.fit_transform(features)

print(norm_features[0:2])


정규화된 피쳐가 배열 형태이기 때문에, 사용 편의상 데이타 프레임에 로딩한다.


df_features = pd.DataFrame(norm_features,index=df.index.tolist())


df_features 변수에 문서별 특징과 문서 제목을 가지고 데이타 프레임을 만들어서 생성하였다.

인덱스는 문서의 이름이 될것이고 0~39 컬럼은 각문서별 특징이 된다. 출력해보면 대략 다음과 같은 모양이 된다.


문서 유사도 계산

문서별로 특징을 계산이 끝났으면 특정 문서와 유사한 문서를 찾도록 해보자.

앞의 글에서도 설명했지만, 문서의 유사도는 코싸인 유사도를 사용한다.

공식을 다시 기억해보면


여기서 A는 문서 A의 특성 행렬, B는 B 의 특성 행렬이 된다. |A|와 |B|는 각 문서 특성 행렬의 벡터의 길이인데, 앞에서 정규화 Normalization 을 하면서 각 문서의 행렬의 크기가 1이 되었기 때문에, 여기서 코싸인 유사도는 A*B / |A|*|B| = A*B / 1*1 = A*B 가된다.

즉 두 문서의 특성 행렬을 곱한 값이 코싸인 유사도가 된다.


데이타 프레임을 이용하면, 하나의 문서 특성 행렬을 전체 문서에 대해서 곱할 수 있다. .dot 함수를 이용하면 되는데,


article = df_features.loc['WHAT car is this!?']


“WHAT car is this!?” 라는 문서의 유사한 문서를 찾아보도록 하자. df_features에서 “WHAT car is this!?” 의 특성 행렬을 찾아내서 article 변수에 저장하고


similarities=df_features.dot(article)


전체 문서의 특성행렬에서 각 문서의 특성 행렬과 article 문서의 특성행렬을 곱한다. 그러면 article 문서에 대해서 각문서에 대한 유사도가 계산이된다.


top=similarities.nlargest()


이 값을 큰 순서대로 소팅해서 top 이라는 변수에 저장해놓고, 유사도가 높은 문서데로 문서의 제목과 유사도를 출력해본다.


texts = df.loc[top.index]['text'].tolist()
i = 0
for text in texts:
   print('TITLE :'+top.index[i]+" Similarities:"+ str(top[i]))
   #print(text+'\n')
   i = i+1



다음은 실행 결과이다.

TITLE :WHAT car is this!? Similarities:1.0
TITLE :Re: WHAT car is this!? Similarities:0.999080385281
TITLE :Re: New break pads & exhausts after 96K km (60K mi) on '90 Maxima? Similarities:0.980421814633
TITLE :Insurance Rates on Performance Cars SUMMARY Similarities:0.945184088039
TITLE :Re: What is " Volvo " ? Similarities:0.935911211878


간단하게 tf-idf와 NMF를 이용한 문서 유사도 측정을 구현해봤다. 조금 더 높은 정확도와 대규모 학습을 위해서는 이보다는 Word2Vector를 이용한 문서의 벡터화와, 딥러닝을 이용한 문서의 유사도 분석을 하면 훨씬 정확도를 높일 수 있다. 전체 기본 개념은 유사하다고 보면 된다.


HBase와 구글의 빅테이블

#2 설치와 기본 사용 방법

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

HBase 설치

HBase를 개발 또는 운영환경에서 사용하기 위해서는 직접 HBase를 다운 받아서 설치하거나 또는 구글 클라우드의 빅테이블을 사용하면 된다 각각 설치 방법은 다음과 같다.


로컬 환경에 HBase 설치하기

설치 방법은 https://hbase.apache.org/book.html#quickstart 를 참고하도록 한다. 운영 환경용은 주키퍼등 여러 환경 설치가 뒤따라야 하기 때문에 여기서는 자세하게 설명하지 않는다.

$JAVA_HOME 환경 변수를 설정한 후에, HBase를 다운로드 받고, 압축을 푼다.

다음 ./bin/start-hbase.sh 을 수행하면 Hbase를 가동할 수 있다.

클라우드 빅테이블 이용하기

구글 클라우드에서 빅테이블을 사용하는 방법은 간단하다.

구글 클라우드 콘솔에서 빅테이블 메뉴로 이동한 다음




다음 메뉴에서 CREATE INSTANCE 버튼을 이용하여 빅테이블 클러스터를 생성한다.

이때 Production과 Development 모드를 설정할 수 있는데, Production 모드는 최소 3개의 노드를 사용해야 하며, Development 모드는 하나의 노드만 설정할 수 있기 때문에 개발시에는 Development 모드를, 운영시에는 Production 모드를 설정하기 바란다.


다음 데이타를 저장할 디스크 타입을 결정한다.




SSD의 경우 노드당 읽기,쓰기 각 10,000QPS (초당 쿼리 처리)를 보장하며, 쿼리 시간은 일반 put/get 쿼리의 경우에는 6ms 정도가 나온다. 테이블 스캔을 할경우, 노드당 220MB를 초당 스캔할 수 있다.


HDD의 경우에는 노드당 읽기 500 QPS, 읽기 속도는 쿼리당 200ms, 쓰기는 노드당 5000 QPS, 쓰기 속도는 50ms 가 나오며, 스캔은 초당 180MB를 스캔할 수 있다.


고속 데이타 조회가 필요한 경우에는 SSD를 사용하도록 하고 저속 (저속이라고는 하지만, 쓰기 50ms면 느린 속도가 아니다. )에 저비용을 선호하는 경우에는 HDD를 사용한다. 한번 디스크 타입을 정하면 클러스터를 새로 만들때 까지는 변경이 불가능하니 주의하기 바란다.

HBASE CLI 명령어 사용해보기

HBase는 Hbase CLI명령을 이용하여 데이타를 접근할 수 있으며, 빅테이블 역시 HBase CLI 명령을 이용하여 접근이 가능하다.

로컬 환경

로컬 환경에서 설치한 Hbase CLI명령을 사용하려면 $HBASE_HOME/bin/hbase shell 명령을 입력하면 CLI를 사용할 수 있다.

간단한 예제를 통하여 사용법을 알아보도록 하자

테이블 생성

테이블 생성은 create ‘테이블명’, ‘컬럼패밀리1’,’컬럼패밀리2’,... 형태로 지정하면된다.

아래는 컬럼패밀리 ‘cf1’만을 가지고 있는 ‘my-table’ 이라는 테이블 하나를 생성한 예이다.



데이타 입력

데이타 입력은 put 명령어를 사용하면 되며, put ‘테이블명',’로우키’,’컬럼패밀리명:컬럼명’,’값' 형태로 사용한다. 아래는 my-table이라는 테이블에 rowkey1이라는 키로 컬럼패밀리 cf1, 컬럼 c1에 “test-value”라는 값을 저장한 결과이다.


조회

조회는 get을 사용하면 되고 get ‘테이블명',’로우키' 로 조회를 하면 된다.


테이블 삭제

테이블 삭제는 drop ‘테이블명' 으로 하면 되고, 테이블 사용 정지는 disable ‘테이블명'을 하면 된다.




명령어들을 간단하게 설명하기는 했지만, get에서 특정 timestamp 범위만 쿼리한다던지, scan을 통해서 특정 범위의 특정 조건 데이타만 쿼리한다던지 다양한 기능이 가능하다.  자세한 내용은 HBase 메뉴얼을 참고하기 바랍니다.

구글 클라우드 환경

빅테이블은 HBase 호환이기 때문에, hbase shell 명령어를 그대로 사용할 수 있다. 단 접속 방식이 다르기 때문에, 별도로 빅테이블용 hbase shell 클라이언트를 다운 받아서 컴파일 하여 사용해야 한다.


구글 클라우드 환경에 접속하기 위해서는 gcloud CLI 명령어가 필요한데, 다운로드 및 인스톨 방법은 https://cloud.google.com/sdk/downloads 를 참고하기 바란다.

다음 빅테이블에 접근하기 위해서는 사용자 인증 과정이 필요한데,  다음 명령어를 실행하면, 웹브라우져가 자동으로 열리면서 구글 클라우드 계정을 입력하여 인증하게 되며, 향후에 gcloud 명령어 및 hbase shell 명령어는 여기서 로그인한 계정의 권한으로 수행되게 된다.

gcloud auth application-default login

이제 빅테이블용 Hbase 클라이언트를 인스톨하고자 하는 환경에, maven과 JDK를 설치한 후에,

다음 명령어를 이용하여 소스코드를 다운 받는다.

git clone https://github.com/GoogleCloudPlatform/cloud-bigtable-examples.git

코드가 다운되었으면, 다운로드된 디렉토리로 들어가서 ./quickstart.sh 명령어를 실행하면, 실행에 필요한 파일들을 자동으로 받아서 컴파일 하고, 자동으로 hbase shell을 수행해준다.




Hbase shell 이 수행되면, 사용 방법은 앞에서 설명한 로컬 환경의 HBase에 접속하는 방법과 다르지 않다.


빅테이블 CLI 명령 사용하기

빅테이블의 경우에는 HBase shell을 이용하지 않고, 별도의 전용 클라이언트를 사용할 수 있다.

앞에서 설치한 gcloud 명령어를 이용하면 되는데, 현재는 베타 상태이기 때문에 gcloud 명령어를 베타 명령어를 지원할 수 있도록 업데이트 해야 한다.

다음 명령어를 이용하여 gcloud를 업데이트하고, cbt (Cloud BigTable) 명령어를 추가하도록 한다.

gcloud components update
gcloud components install cbt

다음, gcloud 명령으로 빅테이블을 사용할때 구글 클라우드 상의 어떤 빅테이블 인스턴스인지를 정해줘야 하는데, 사용자 홈 디렉토리에 .cbtrc에 구글 프로젝트ID와 빅테이블 인스턴스 ID를 적어주면 된다.

홈 디렉토리에서 아래 명령을 이용하면 .cbtrc에 프로젝트 ID와 빅테이블 인스턴스 ID를 지정할 수 있다.

echo project = [PROJECT_ID] > ~/.cbtrc
echo instance = [INSTANCE_ID] >> ~/.cbtrc

PROJECT_ID는 본인의 구글 프로젝트 ID이며, INSTANCE_ID는 생성한 구글 빅테이블의 인스턴스 ID이다.

빅테이블 CLI명령어를 사용할 준비가 모두 끝났다. 하나씩 명령어를 실행해보도록 하자.

앞의 Hbase 의 명령과 거의 유사하기 때문에 별도의 설명은 자세하게 하지 않는다.

테이블 생성


생성된 테이블 확인



컬럼 패밀리 추가

my-table 테이블에 컬럼 패밀리 cf1추가


추가된 컬럼 패밀리 확인


데이타 삽입

my-table 테이블의 컬럼패밀리 cf1에 mycolumn이라는 컬럼에 test-value 라는 값을 추가


삽입된 데이타 확인


테이블 삭제


다음글에서는 마지막으로 JAVA API를 이용하여 HBase와 빅테이블에 대한 프로그래밍을 하는 방법을 알아보도록 한다.


HBase 와 구글의 빅테이블

#1 아키텍쳐


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

HBase

HBase 는 아파치 오픈소스 NoSQL 솔루션으로 구글의 빅테이블  (https://research.google.com/archive/bigtable.html) 논문을 기반으로 개발되었다.

Key/Value Store 기반의 NoSQL이며, 대용량 데이타를 빠르게 처리할 수 있는 기능을 가지고 있다.

데이타 모델

HBase는 컬럼 패밀리라는 데이타 모델을 사용하는데, 대략적인 구조를 보면 다음과 같다.

각 행은 하나의 로우키(rowkey)를 가지고 있다. 이 키는 RDBMS의 프라이머리 키와 같은 키라고 보면 된다.

각각의 행에는 컬럼이 정의되어 있는데, RDBMS 테이블의 일반 컬럼과 같은 개념이라고 보면 된다. 특이 사항은 이 컬럼들이 컬럼 패밀리 (Column family)라는 것으로 묶이게 되는데, 이렇게 컬럼 패밀리로 묶인 컬럼의 데이타는 물리적으로 같은곳에 저장이 된다. 그래서, 데이타 접근시에 한꺼번에 접근되는 데이타의 경우에는 컬럼 패밀리로 묶는 것이 유리하다.

위의 그림은 name과 contact, 그리고 company라는 컬럼 패밀리를 가지고 있고,

  • name 컬럼 패밀리는 lastname,firstname 컬럼

  • contact 컬럼 패밀리는 phone, mobile, email 컬럼

  • company 컬럼 패밀리는 company라는 컬럼

을 가지고 있다.

내부적으로 데이타는 rowkey에 의해서 오름차순으로 정렬이 되서 저장이 된다.



각 컬럼의 값을 셀이라고 하는데, 데이타 셀에는 timestamp가 있어서 이전의 값이 같이 저장되며, 일정 기간까지 그 값을 유지하도록 한다.


조인이나 인덱스등을 지원하지는 않지만 대용량 데이타를 안전하고 빠르게 저장 및 억세스가 가능하기 때문에, 광고 클릭 데이타나, 사용자 행동 데이타 수집, 로그 수집, IOT의 센서 데이타, 금융에서 시계열 데이타 등을 저장하는데 유용하게 사용할 수 있다.

아키텍쳐

아래 아키텍쳐는 HBase의 원조인 빅테이블의 아키텍쳐이다.




주키퍼등 몇몇 시스템들이 빠져 있지만, 큰 구조는 유사하다고 보면 된다.

데이타 노드에 SSTable 이라는 파일 형태로 데이타가 저장되어 있고, 위에 연산 노드가 붙어서 클러스터를 이룬다. 각 노드는 데이타를 저장하고 있는데, 로우키에 따라서 그 데이타가 분산되어 저장된다. 예를 들어 키가 1~3000의 범위를 가지고 노드가 3개이면 1번 노드는 1~1000, 2번은 2~2000, 3번은 3~3000 데이타를 저장하고 처리하게 된다.


각 노드의 구조는 다음과 같다.

쓰기 연산이 들어오면, 쓰기에 대한 로그를 tablet log 라는 파일에 남긴다. RDBMS의 백로그와 같은 개념으로 보면 되는데, 장애가 나더라도, tablet log가 남아 있기 때문에 이를 통해서 디스크에 쓰여지지 않은 데이타를 복구할 수 있게 된다.




데이타 로그를 쓰고 나면, 실제 데이타는 memtable 이라는 메모리 기반의 중간 저장소에 저장이 되고, 이 memtable이 꽉차게 되면, 데이타를 SSTable로 플러슁하고, tablet log에 있는 데이타를 지우게 된다. 이 과정을 Minor compaction이라고 한다.


읽기 연산이 들어오면, 먼저 memtable을 뒤져보고, 없을 경우 SSTable을 뒤져서 데이타를 읽게되는데, SSTable은 물리적으로 다음과 같은 모양을 하고 있다.

name,address,gender 라는 컬럼은 실제 SSTable 내에서 각 셀단위로 쪼게 져서 셀단위로 row key와 컬럼패밀리, 컬럼 명을 키로 하고, 그 안에 값을 저장한다. 만약에 같은 키의 셀을 업데이트 하더라도 그 데이타 셀을 업데이트 하는것이 아니라 새로운 시간 timestamp를 달아서, append 하는 방식으로 데이타를 저장한다.


계속 append 만하면, 저장 공간이 부족해지기 때문에, 어느 일정 시간이 되면 오래된 데이타를 지워야 하는데 이를  compaction이라고 하며 주기적으로 이 작업이 일어나게 된다.

핫스팟

아키텍쳐를 이해하면, 데이타가 어떻게 분산되는지를 이해할 수 있는데, 그래서 생기는 문제가 HOTKEY라는 문제가 발생한다.

예를 들어 주민등록 번호를 로우키로 사용하는 서비스가 있는데 98년생~08년생들에게 특히 인기가 있다고 하면, 그 키 범위내에 데이타가 다른 연령대에 비해서 많을 것이고, 98~08년 로우키 범위를 담당하는 노드에 부하가 많이 갈것이기 때문에 제대로 된 성능을 내기 어려워진다. 이와 같이 특정 로우키범위에 데이타가 볼리는 곳을 핫스팟이라고 하는데, 이를 방지하기 위해서는 키의 값을 UUID와 같은 랜덤 스트링이나 해쉬값등을 사용하여 전체적으로 분포가 골고를 키를 사용하는 것이 좋다.

구글 빅테이블

구글의 빅테이블은 HBase의 원조가 되는 서비스로, 구글 내부에서 지메일과 광고플랫폼등 여러 분야에 사용되고 있으며, 현존하는 단일 데이타베이스 시스템중 가장 큰 데이타 시스템이다.

개발 초기 당시에는 GFS (하둡파일 시스템 HDFS의 전신)을 사용하였으나, 콜러서스라는 고속 파일 시스템으로 변경하면서 매우 빠른 성능을 낼 수 있게 되었다.

구글 빅테이블은 구글 클라우드 (http://cloud.google.com)을 통해서 서비스가 제공되며, HBase API와 호환이 되기 때문에, 별도의 변경 없이 기존 HBase 애플리케이션 및 HBase 관련 도구를 사용할 수 있다는 장점이 있다.

성능은 HBase에 비해서, 초당 처리 성능은 대략 2.5배, 응답 시간은 50배 정도 빠르다.


(성능 비교 자료 http://www.i-programmer.info/news/197-data-mining/8594-google-cloud-bigtable-beta.html)


수십 페타의 데이타를 저장하더라도 일반적인 읽기나 쓰기의 경우 한자리 ms (~9ms)내의 응답성을 보장하기 때문에 빅데이타 핸들링에 매우 유리하며, 안정적인 구조로 서비스가 가능하다. 빠른 응답 시간 때문에 앞단에 캐쉬 서버를 두지 않아도 되서 전체 시스템 아키텍쳐를 단순화할 수 있는 장점을 가지고 있다.


빅테이블의 내부 아키텍쳐는 다음과 같은 모양으로 되어 있다.





성능 저하 없는 안정적인 확장

HBase와 유사한 구조이지만, 큰 특징이 데이타 노드와 연산노드 (Bigtable node)가 물리적으로 분리되어 있고, 하단의 파일시스템이 무제한 확장 구조를 갖는 구조를 가지고 있다.


즉 무슨 이야기인가 하면 데이타 파일이 콜로서스 파일 시스템 내에 아래와 같이 배치 되어 있고, 연산 노드는 자기가 관리한 SSTable 파일을 포인팅 하는 구조로 되어 있다.


이게 무슨 장점을 가지냐 하면, 보통 NoSQL이나 분산 시스템은 리밸런성이라는 작업을 하게 되는데, 데이타가 점점 쌓여가면, 그 연산노드와 데이타 노드에 데이타가 부하가 몰리기 때문에 그 데이타 청크를 나눠야 하는 일이 발생을 하게 되는데 이를 리밸런싱이라고 한다. 이때 물리적으로 데이타 노드의 데이타를 다른 노드로 이동해야 하기 때문에, 이 이동/복사 작업이 부하를 일으켜서 전체 시스템의 성능에 영향을 주게 된다.


아래 그림을 예로 들어보자




첫번째 노드에서 데이타 파티션, A,B,C를 관리하고 있다가 데이타가 많아져서 C를 두번째 노드로 이동하고자 하면, C데이타를 물리적으로 복사하여 두번째 노드의 저장소로 옮겨줘야 한다.

그러나 빅테이블의 경우 콜로서스에 파일이 공유 스토리지 형식으로 저장되어 있기 때문에, 물리적인 파일 이동은 필요없고,  연산 노드에서 어떤 데이타 파티션을 처리할지에 대한 포인터만 변경해주면 된다.



이런 이유 때문에, 내부적으로 리밸런싱이 일어나더라도, 리밸런싱에 의한 성능 저하나 충격이 전혀 없다는 장점을 가지고 있다.

마찬가지 원리로 연산 노드가 추가되거나 삭제될때도 HBase의 경우에는 리밸런싱에 의한 실제 데이타 이동 부하가 발생하는데, 빅테이블의 경우에는 새로운 노드를 추가하고, 각 노드에서 처리하는 데이타 포인트만 변경하면 되기 때문에, 성능 저하 없이 안정적으로 확장이 가능하다.


다음글에서는 HBase 를 로컬에 설치하는 방법과, 구글 클라우드 빅테이블을 설정하는 방법을 알아보고 CLI 명령을 이용하여 간단한 테이블 생성 및 데이타를 조작하는 방법에 대해서 알아보겠다.


NMF 알고리즘을 이용한 유사한 문서 검색과 구현(1/2)



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


앞의 글들에서, 데이타의 특징을 뽑아내는 방법으로 차원 감소 (Dimension reduction) 기법에 대해서 설명하였다. 구체적인 알고리즘으로는  PCA와 t-SNE 알고리즘을 소개하였는데, 오늘은 차원 감소 기법중의 하나인 행렬 인수분해 (Matrix Factorization)에 대해서 알아보고자 한다.

문서 유사도 검색

행렬 인수 분해를 설명하기 위해서 유사한 문서를 찾는 시나리오를 예를 들어서 설명하겠다.

문서 유사도 검색의 원리는 다음과 같다


  1. 문서에 나온 각 단어들을 숫자(벡터)로 변환하여 행렬화 한다.

  2. 행렬화된 문서에서 차원 감소 기법을 이용하여, 문서의 특징을 추출한다.

  3. 추출된 특징을 기반으로, 해당 문서와 특징이 유사한 문서를 찾아서 유사값을 기반으로 소팅하여, 유사 문서를 찾아낸다.


각 과정에서 사용할 알고리즘을 보면 다음과 같다.


  1. 문서의 단어들을 숫자화 하여, 행렬로 변환하는 과정에서는 여러가지 word2Vec (요즘 대세) 알고리즘이 있지만, 간단하게 tfidf 라는 알고리즘을 사용하겠다.

  2. 다음 문서의 행렬을 값을 가지고 특징을 추천하기 위해서는 앞에서 언급한 행렬 인수 분해 (Matrix Factorization) 알고리즘을 이용하여, 행렬의 차원을 줄일것이고

  3. 해당 문서와 특징 값이 유사한 문서를 찾기 위한 방법으로는 벡터간의 거리를 측정하는 방법을 사용하여 유사도를 측정하는데, Consine distance (코싸인 거리) 알고리즘을 사용하도록 한다.


각 알고리즘에 대한 간략한 개념을 설명하고 구현은 파이썬의 sklearn의 라이브러리를 사용해서 구현하도록 하겠다.그러면 각 알고리즘에 대한 설명을 보자

TF-IDF (Term Frequency Inverse Document Frequency)

TF-IDF를 이해하기 위해서는 먼저 TF(Term Frequency)와 DF(Document Frequency)에 대한 개념을 먼저 이해해야 한다

  • TF(Term Frequency) : TF랑, 하나의 문서에서 그 단어(Term) 가 얼마나 자주 나타나는가(Frequency)를 정의한 값이다.
    예를 들어, 한문서에서 “조대협" 이라는 단어가 10번 등장했다면 조대협에 대한 TF값은 10이 된다.

  • DF(Document Frequency) : DF란, 전체 문서(Document)에서 그 단어(Term)가 등장한 문서의 수를 나타낸다.  “조대협" 이라는 단어가 20개의 문서에 나타났다고 하면 DF 값은 20이 된다.

그러면 TF-IDF란 무엇인가 TF값을 DF값으로 나눈 값을 TFIDF라고 하는데, 위의 설명에서 “조대협" 이라는 단어에 대한 TFIDF값은 10/20=0.5 가 된다. (정확하게는 IDF는 log 를 포함한 다른 수식을 사용하지만 의미상으로 DF를 나눈것과 같이 문서에 등장하는 단어의 밀도를 나타낸다고 이해하면 된다.)


TF-IDF값의 의미는 무엇일까?

10년치 뉴스 문서가 있다고 가정하자.

그리고 우리가 뉴스에  많이 사용하는 단어로 “예를 들어" 라는 단어가 있다고 가정하자. (원래는 단어별로 잘라서 생각해야 하지만 설명을 쉽게 하기 위해서 두 단어를 하나의 단어로 생각하자). “예를 들어" 라는 단어는 어떤 문서의 특징을 나타내기는 사실 어렵다. 너무 일반적으로 사용되는 말이기 때문인데, 이런 단어의 경우에는 거의 모든 문서에 나타날 수 있기 때문에 DF 값이 매우 커진다. 그래서 “예를 들어"의 TF-IDF 값은 거의 0으로 수렴하게 되고, “세월호"와 같은 단어는 세월호를 언급한 뉴스 기사에만 있기 때문에, DF 값은 낮아질것이고, 결과적으로 TF-IDF 값은 커질 수 있는데, 세월호 라는 단어가 많이 언급된 문서일 수 록 TF-IDF 값이 커지게 된다.


눈치가 빠른 사람은 벌써 이해했겠지만, TFIDF의 기본 원리는 전체 문서에 널리 사용되는 일반적인 단어는 특징에서 배제하고, 문서에 특정 단어가 많이 언급될 수 록 그 단어의 TF-IDF값을 크게 하여, 문서의 특징을 나타내는데 사용할 수 있다.

NMF (Non-negative Matrix Factorization)

NMF (비음수 행렬 인수 분해)는 차원 감소 기법으로, 컴퓨터 시각 처리나, 우리가 하려는 문서 분류 그리고 음파 분석등에 널리 사용된다. 앞의 글들에서 소개했던 PCA나 t-SNE와 같은 차원 감소 기법에 대비한 차이를 보면, NMF에 의해 추출된 특징의 경우에는 해석이 가능하다는 장점을 가지고 있다.

PCA나 t-SNE는 원본 데이타의 특징을 추출하여 새로운 특징 셋으로 표현을 하지만 새로운 특징셋이 원본 특징과 어떤 연관 관계를 가지는지는 해석이 불가능하다. NMF의 경우 새로운 특징셋이 어떻게 원본 데이타 들과 관계를 가지는지 확인이 가능한데 이 부분은 NMF 알고리즘을 먼저 이해하고 하도록 하자


NMF는 행렬 인수 분해 알고리즘 중 하나로, 행렬 인수 분해란 다음과 같다.

우리가 원본 행렬 V가 있다고 했을때, 행렬 인수 분해는 이 V 행렬을 두개의 행렬로 분리 하는 것이다.

아래 그림은 원본 행렬 V 를 V=W*H 로 분리한 예이다.


예를 들어 TF-IDF를 이용하여 책 제목과 단어로 이루어진 행렬 V의 모양이 다음과 같다고 하자



책제목

협상

스타트업

투자

비지니스

데이타

...

...

협상의법칙

0.9

0

0.3

0.8

0



린스타트업

0

0.8

0.7

0.9

0.3



빅데이타

0

0

0.5

0

0.8



< 그림. 책 제목과, 그 책에 나온 단어의 TFIDF 값으로 이루어진 행렬 V >


이를 행렬 분해를 통하면


행렬 W는 다음과 같은 모양을 가지게 되고

책제목

특징1

특징2

특징3

특징4

협상의법칙

0.9

0

0.1

0.2

린스타트업

0

0.8

0

0

빅데이타

0.2

0.1

0.8

0.1


행렬 H는 다음과 같은 모양을 가지게 된다.


협상

스타트업

투자

비지니스

데이타

...

...

특징1

0.92

0

0.1

0.2

0



특징2

0

0.85

0.5

0.3

0.3



특징3

0

0

0.3

0

0.8



특징4

0

0

0

0

0

...



여기서 W를 가중치 행렬 (Weight Matrix), H를 특성 행렬 (Feature Matrix)라고 한다.

W는 카테고리 (책제목) 에 특성과의 관계를 나타내며, H는 원래 특성(협상,스타트업,...)에 대비한 새로운 특성(특징 1,2,3…)에 대한 관계를 나타낸다.


W 값을 보면 특징 1은 “협상의 법칙"에 자주 나오는 단어들 빈도와 관련이 높은 특성임을 알 수 있고, 특징 2는 “린스타트업", 특징3은 “빅데이타"에 자주 나오는 단어들의 빈도와 관련이 높은 특성임을 예측할 수 있다.

또한 특성 행렬 H를 보면 특징 1은 “협상" 이라는 단어와 관련이 많고, 특징 2는 “스타트업" 이라는 단어와 관련이 많은 것을 알 수 있다.

아래는 그림은  특징을 NMF를 이용하여 추출한 특성 행렬 H를 나타내는데, 해당 특징이 어떤 단어들에 의해서 반응 하는지를 알 수 있다.


그래서 NMF를 PCA나 t-SNE와 같은 다른 알고리즘과 비교했을때 특성을 해석 가능하다고 이야기 하는 것이다.


이 데이타셋에 만약에 이 데이타에 “스타트업 데이타 분석" 이라는 책이 들어왔고, 이 책은 빅데이타와 스타트업에 대해 다루고 있다면, 아마도 가중치 행렬 W에서 “스타트업 데이타 분석"은 다음과 같이 특징2와 3과 관련이 높은 형태를 나타낼 것이다.


책제목

특징1

특징2

특징3

특징4

협상의법칙

0.9

0

0.1

0.2

린스타트업

0

0.8

0

0

빅데이타

0.2

0.1

0.8

0.1

스타트업 데이타 분석

0

0.9

0.7

0


여기서 중요한 것은 차원을 줄일때 몇개의 특징으로 기존의 특성을 압축(줄일가)인데, 여기서는 4개의 특징으로 전체 특성을 줄이는 것으로 하였다. (특징을 몇개로 표현할 것인가가 매우 중요한 튜닝 패러미터가 된다.)


지금까지 설명한 행렬 인수 분해 방식은 행렬의 값이 양수일때 사용하는 행렬 분해 방식으로 “비음수 행렬 인수 분해 (Non negative Matrix Factorization)이라고 한다. NMF 방식에도 여러가지 다양한 발전된 알고리즘들이 있으며, 알고리즘 리스트는 https://en.wikipedia.org/wiki/Non-negative_matrix_factorization 를 참고하기 바란다.



코싸인 거리기반의 유사도 측정

NMF등을 이용하여 압축되어 있는 특성을 기반으로 하여 유사한 문서를 찾는 방법에 대해서 알아보자. 특성을 기반으로 유사도를 측정하는 방법은 여러가지가 있다. 주로 특성값을 벡터 공간에 맵핑 한후, 벡터간의 거리를 기반으로 계산하는 방법이 많이 사용되는데, 유클리디안 거리, 코사인 거리, 자카드 거리, 피어슨 상관 계수, 맨해튼 거리등이 있다. 여기서는 코사인 거리를 사용하여 문서간의 유사도를 측정한다.


코사인 거리의 기본 원리는 다음과 같다.


특성 값을 나타내는 벡터 A와 B가 있을때, 이 벡터 A와 B사이의 각도가 가까울 수록, 두 개의 특성이 유사하다고 판단하기로 한다. 즉 A와 B의 각도 θ 가 최소일 수 록 값이 유사하다고 판단하면 된다. 그러면 벡터 A 와 B만을 가지고, 어떻게 각도 θ를 구할 수 있는가?

벡터의 내적을 사용하면 이 θ 가 크고 작음을 알아낼 수 있는데 기본 원리는 다음과 같다.

벡터 a와 b 가 있을 때 이 두 벡터의 내적 ab = |a|*|b|*cos(θ) 가 된다.

cos(θ)를 좌변으로 옮기면


cos(θ) = ab / |a|*|b


가 되고, 이를 계산하는 공식은 ab 은 벡터 a 행렬의 각 항의 값 Ai 과  b행렬의 각 항의 값 Bi를 순차적으로 곱하여 더하면 된다. (A1*B1+A2*B2 …. + An*Bn)

|a|는 a 벡터의 길이로, sqrt(A1^2 + A2^2 + …. An^2)로 계산을 하고, |b|역시 sqrt(B1^2 + B2^2 + …  Bn^2로 계산한다.)


이를 수식으로 풀어보면 다음과 같다.




이렇게 계산하여, cos(θ) 의 값이 1에 가까우면 유사도가 높고, 0에 가까우면 유사도가 낮은 것으로 판단할 수 있다.


정리해보면  유사도를 파악하고자 하는 문서를 정한 후에, NMF를 이용하여 각 문서의 특성을 추출한후에 NMF에 의해 추출된 가중치 행렬을 가지고, 유사도를 파악하고자 하는 문서와 다른 문서들간의 코싸인 거리를 구하여, 이 거리 값이 가장 큰 문서가 가장 유사한 문서가 된다.


여기까지 간단한게 TF-IDF,NMF 그리고 코사인 유사도를 이용하여 유사한 문서를 찾는 방법을 설명하였다.코싸인 유사도를 적용하지 않고 NMF로 찾아낸 특성값을 기반으로 문서를 군집화 하는 클러스터링에도 활용이 가능하다.


다음글에서는 이 알고리즘을 실제로 sklearn을 이용해서 구현해보도록 한다.





실습 데이타

분류없음 | 2017.12.12 16:42 | Posted by 조대협

Dataprep_labs_files.zip


구글 스택드라이버를 이용한 애플리케이션 로그 모니터링

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

스택드라이버 소개

스택드라이버는 구글 클라우드에서 서비스로 제공되는 시스템 로그 및 모니터링 시스템이다. CPU,메모리사용량과 같은 하드웨어에 대한 정보에서 부터 웹서버나 OS와 같은 미들웨어 및 애플리케이션 로그를 수집, 검색 및 분석할 수 있으며, 여러 오픈 소스 (MongoDB, CouchDB, Redis - https://cloud.google.com/monitoring/agent/plugins/ )등에 대한 모니터링도 가능하다.

구글 클라우드 뿐 아니라, AWS에 대한 모니터링을 통합으로 지원하는 등, 상당히 많은 기능을 가지고 있다.

이 글에서는 스택드라이버를 이용하여 애플리케이션 로그를 수집하고 이를 분석하는 방법에 대해서 설명하고자 한다.

자바를 기반으로 애플리케이션 로깅을 설명한다. 자바 애플리케이션에서 스택드라이버로 로그를 남기는 방법은 여러가지가 있으나, 일반적으로 자바 프로그래밍 언어에서 많이 사용하는 로깅 프레임웍은 SL4J 를 이용한 로깅과, 스택드라이버 SDK를 이용하여 JSON 형태로 로그를 저장하는 방법에 대해서 알아보도록 한다.

API 인증

스택 드라이버를 사용하기 위해서는 로그 API에 대한 인증이 필요하다. 인증에는 여러가지 방법이 있는다. 사용이 쉬운 방법을 설명한다.

로컬 환경 또는 타 환경에서 인증

로컬 개발환경이나 클라우드에서 인증을 하는 방법은 서비스 어카운트 (Service Account)를 사용하는 방법이 있다. 서비스 어카운트는 구글 클라우드 콘솔에서  IAM 메뉴에서 생성할 수 있다. 서비스 어카운트 메뉴를 아래와 같이 선택한 다음.


상단 메뉴에서 Create Service Account  버튼을 누르고 서비스 어카운트 생성한다.


서비스 어카운트에는 서비스 어카운트의 권한을 설정할 수 있는데, Project Owner로 설정하면 모든 권한을 다 가질 수 있고, 여기서는 로깅 권한만을 줄것이기 때문에, Logs Writer 권한만을 지정한다.


계정 생성을 하면 json 파일이 다운로드 된다.

이 파일은 환경 변수 GOOGLE_APPLICATION_CREDENTIALS 에 파일 경로를 지정해주면 된다.

예시 $ export GOOGLE_APPLICATION_CREDENTIALS=/Users/terrycho/keys/terrycho-sandbox-projectowner.json




구글 클라우드 VM 내에서 인증

구글 클라우드 VM내에서 자바 코드를 실행할 경우 VM 자체에 API 접근 권한을 부여할 수 있다. 보통 운영환경에서는 이 방법이 권장된다.

아래와 같이 VM 생성시 “Identity and API access” 에서 API 접근 권한을 주면 된다. Set access for each API를 써서 Logging write 권한만을 줄 수 있고, 아니면 Allow full access to all Cloud APIs 를 이용해서 전체 API에 대한 권한을 줄 수 도 있다.




SL4J를 이용한 로깅

sl4j를 이용한 로깅은, 기존의 sl4j 로거를 그대로 사용하기 때문에 코드 변환이 거의 없고, 단지 maven 에서 라이브러리 의존성을 스택드라이버 로거로만 변경해주면 되기 때문에 별도의 학습이 필요없고 사용법이 단순하다는 장점이 있다. sl4j 로깅은 단순하다.

의존성 추가

먼저  pom.xml 에 아래와 같은 의존성을 추가 한다.

<dependency>
<groupId>com.google.cloud</groupId>       <artifactId>google-cloud-logging-logback</artifactId> <version>0.30.0-alpha</version>
</dependency>

logback.xml

다음 필요에 따라서 sl4j에 대한 설정을 위해서 logback.xml 을 추가 설정할 수 있다. 여기서 로깅 레벨등을 지정할 수 있으나, sl4j에 대한 내용이기 때문에 별도로 설명하지는 않는다.

자주 실수 하는 부분이 logback.xml은 클래스 패스의 경로내에 들어가 있어야 하는데 다른 방법으로는 자바 옵션으로 -Dlogback.configurationFile 으로 logback.xml 경로를 설정하면 된다.



코드

코드를 보자

package com.google.example.stackdriver;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;



public class App {

 private static final Logger logger = LoggerFactory.getLogger(App.class);

 

 public static void main(String[] args) {

   logger.info("My Hello Log4j");

 }

}


코드는 간단하다. logger를 선언한 후에, .info, .error, .warning 등의 메서드로 텍스트 문자열을 남기면 된다.


자바 로거 연동은 sl4j이외에도 java.util.logging 도 연동이 가능하다. 자세한 내용은 https://cloud.google.com/logging/docs/setup/java 를 참고하기 바란다.

Logger를 이용한 로깅

sl4j는 사용이 간편한 반면에 텍스트 문자열로 로깅이 되기 때문에, 구조화된 정보 (JSON)이나 여러 필드를 가지는 로그를 남기기가 쉽지 않다는 단점을 가지고 있다. 스택드라이버 전용 SDK를 사용하면, JSON등 다양한 포맷으로 로그를 쉽게 남길 수 있다. (sl4j의 경우에도 LoggingEnahncer를 사용하면 가능하기는 하다)


전체 코드는 다음과 같다.


package com.google.example.stackdriver;

import com.google.cloud.MonitoredResource;

import com.google.cloud.logging.LogEntry;

import com.google.cloud.logging.Logging;

import com.google.cloud.logging.LoggingOptions;

import com.google.cloud.logging.Payload.JsonPayload;

import com.google.cloud.logging.Payload.StringPayload;

import com.google.cloud.logging.Severity;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;


public class LogWithLabel {

 //https://cloud.google.com/logging/docs/reference/libraries

 final static String LOG_NAME="terry-tutorial";

 /** Expects a new or existing Stackdriver log name as the first argument.*/

 public static void main(String... args) throws Exception {


   // Instantiates a client

   Logging logging = LoggingOptions.getDefaultInstance().getService();


   // The data to write to the log

   String text = "Hello, world!";

   Map<String, Object> jsonMap = new HashMap<String, Object>();

   jsonMap.put("elapsedtime", 11);

   

   for(int i=0;i<1000;i++){

    jsonMap.put("count", i);

   LogEntry entry

    //= LogEntry.newBuilder(StringPayload.of(text))

    // 한페이로드만 사용이 가능함. 오버라이드됨.

    = LogEntry.newBuilder(JsonPayload.of(jsonMap))

.setSeverity(Severity.ERROR)

       .setLogName(LOG_NAME)

       .setResource(MonitoredResource.newBuilder("global").build())

       .addLabel("instancename", "instance-1")

       .build();

   // Writes the log entry asynchronously

   logging.write(Collections.singleton(entry));

   }


   System.out.printf("Logged: %s%n", text);

 }

}


먼저 Logging 객체를 가지고 와야 한다. 별도의 설정 없이 다음과 같이 설정하면 되고, 프로젝트 및 인증은 앞에서 설정한 Service Account 파일의 정보를 그대로 사용한다.

Logging logging = LoggingOptions.getDefaultInstance().getService();


이 예제는 JSON 포맷으로 데이타를 저장하는 방법인데, 단순하게 1 레이어의 JSON을 저장하도록 하였다. Map을 이용하여 jsonMap을 정의하고, put을 이용하여 key, value 값을 저장한다.


   String text = "Hello, world!";

   Map<String, Object> jsonMap = new HashMap<String, Object>();

   jsonMap.put("elapsedtime", 11);


다음 로그를 저장하기 위해서는 LogEntry 객체를 이용해야 하는데, LogEntry는 LogEntry.newBuilder(PayLoad)를 이용하여 생성한다. Text 로그를 저장하는 TextPayLoad를 사용하거나 다른 페이로드도 있지만 여기서는 JsonPayLoad를 사용하였다.

LogEntry.newBuilder(JsonPayload.of(jsonMap))


다음 로그 Serverity (INFO,ERROR,WARNING)는 setServerity로 정할 수 있다. 스택 드라이버 로그는 정보 구조에서 계층 구조를 가질 수 있는데, 다음과 같은 개념을 가지고 있다.

리소스

리소스는 이 로그가 어떤 자원에 속하는지를 정의한다. 예를 들어, VM, 빅쿼리와 같이 어떤 인프라에 속하는지를 정의할 수 있는데, 애플리케이션의 경우 일반적으로 “global” 리소스로 정의한다.

리소스 명은 setResource메서드를 이용해서 지정이 가능하다.

라벨

다음 로그에 라벨을 달 수 있다. 예를 들어 이 리소스가 VM인데, 어떤 VM인지 식별을 하기 위해서 키를 name, 값을 인스턴스명 등으로 지정할 수 있다. 또는 개발/운영 환경인지를 구별하기 위해서 env 라는 키를 이용해서 환경에 따라 값을 dev,qa,prod 등으로 달 수 있다. 하나의 로그에는 여러개의 라벨을 붙이는 것이 가능하다. 라벨은 키,밸류 형태로 .addLabel(키,값)으로 추가가 가능하다.

로그 이름

로그 이름은 로그를 그룹핑할 수 있는데, 애플리케이션 종류등으로 그룹핑을할 수 있다. 이 로그는 사용자 로그, 게임 로그 등으로 그룹핑이 가능하다. 그룹 명을 setLogName으로 지정이 가능하다.


아래는 리소스를 global, 로그 이름을 LOG_NAME, 라벨에 instancename을 키로, instance-1이라는 값을 지정한 코드 예제이다.

       .setLogName(LOG_NAME)

       .setResource(MonitoredResource.newBuilder("global").build())

       .addLabel("instancename", "instance-1")

로그 확인

로그는 구글 클라우드 콘솔에서 STACKDRIVER > Logs 항목에서 확인이 가능하다.


위 그림과 같이 메뉴로 진입한 후에, 로그를 볼 수 있다.


리스트 박스에서 첫번째 박스는 리소스를 선택하는 화면으로 애플리케이션 로그는 앞의 예제에서 리소스를 global로 선택하였기 때문에, global을 선택한다. 그리고 두번째는 로그 이름을 고르는 화면인데, 앞에 예제에서 terry-tutorial로 로그 이름을 지정하였기 때문에 terry-tutorial을 선택한다.

다음 위의 화면에서 버튼을 누르면 실시간으로 로그를 볼 수 있는데, 통상 1분이내의 딜레이가 소요된다고 보면 된다.

로그에서 각 항목을 펼쳐보면 디테일을 볼 수 있다. 아래는 하나의 디테일인데, 중요한 부분은 timestamp에서 시간이 기록되고, serverity에 에러 레벨이 기록된다. 그리고 앞에서 지정한 Json PayLoad가 jsonPayLoad 라는 항목으로 들어간다.  라벨은 labels라는 항목에 키/밸류 형식으로 지정이 되는 것을 볼 수 있다.


로그 검색 및 필터링

스택드라이버의 강력한 기능중 하나가 로그에 대한 검색과 필터링인데, 스택 드라이버 콘솔 상단 화면에서 필터링(검색) 조건을 넣으면 각 필드 값에 따라서 다양한 형태로 로그 검색이 가능하다.


이 조건은 resource가 global이고, 그중에서 jsonPayload.count 가 900 보다 큰 로그만을 추출하는 방법이다. (Advanced filter를 사용하엿음)

표현식이 어렵지 않으니, https://cloud.google.com/logging/docs/view/advanced_filters 를 참고하면 손쉽게 로그 검색이 가능하다.

EXPORT

스택 드라이버의 다른 장점 중의 하나는 저장된 로그를 다른 시스템으로 EXPORT할 수 있는데, 크게 다음 3가지로 EXPORT가 가능하다.

  • GCS (파일) : Google Cloud Storage에 파일로 로그를 저장이 가능하다.

  • Pub/Sub (실시간 스트리밍) : 실시간으로 로그를 Pub/Sub 큐로 저장이 가능하다. Pub/Sub 뒤에 컨슈머를 둬서 다양한 처리가 가능하고 (알럿등) Apache Beam (Dataflow)연동을 통해서 실시간으로 로그를 분석 하는 것이 가능하다

  • BigQuery (데이타 베이스) : 실시간으로 데이타를 대용량 데이타 베이스는 빅쿼리에 저장하여 다양한 쿼리 및 시각화가 가능하다.


로그 EXPORT는 상단 메뉴의 CREATE EXPORT 버튼을 이용하면 EXPORT 정의가 가능하다.


이때 흥미로운 점은 로그 EXPORT시 필터에 조건을 걸어놓으면, 필터에 맞는 조건에 있는 로그만 EXPORT가 된다. 즉 로그 레벨이 CRITICAL한 로그만 Pub/Sub으로 로깅해서 알럿을 보내는 것과 같은 작업이 가능하게 된다.

빅쿼리로 EXPORT

그럼 그중에서 빅쿼리로 로그를 EXPORT하는 방법에 대해서 알아보기로 한다.

빅쿼리로 EXPORT하기 위해서는 CREATE EXPORT를 누른 후에, 로그 SINK 명을 지정하고 데이타셋을 지정해야 하는데, 데이타셋을 새로 생성하면 된다.


이 예제에서는 필터를 추가하여 label에서 instancename이 “instance-1”인 로그만 빅쿼리로 저장하도록 EXPORT 설정을 하였다.


http://bigquery.google.com에 들어가면 앞에 지정한 이름으로 데이타셋이 생긴것을 확인할 수 있고, 테이블명은 앞에서 지정한 로그명인 terry_tutorial 로 지정된것을 확인할 수 있다.

다음은 로그 시간과, JsonPay로드의 elapsedtime과, count 값을 조회하는 쿼리와 결과 이다.



쿼리 결과




데이타 스튜디오를 이용한 로그 시각화

이렇게 빅쿼리에 저장된 데이타는 구글 데이타 스튜디오를 이용하여 손쉽게 시각화가 가능하다.

https://datastudio.google.com에 접속한 후에, Start New Report에서 Blank Report 만들기를 선택한다.

새로운 리포트 화면이 나오면 우측 하단의

를 선택하여 빅쿼리 테이블과 연결을 한다.


좌측 커넥터를 선택하는 화면에서 BigQuery를 선택한후


MY PROJECT에서 내 프로젝트를 고르고, 데이타셋과 테이블은 선택한다.


다음으로 상단의 CONNECT 버튼을 눌러서 테이블을 연결한다. 또는 프로젝트를 선택하는 대신 CUSTOM QUERY를 누르면, 직접 SQL을 써서 특정 필드만 조회할 수 있다.


여기서는 전체 테이블을 불러오는 것으로 진행하도록 한다.

다음 화면에서는 필드 선택 및 제거, 그리고 타입 설정등이 가능하다.


적절하게 사용할 필드를 선택하고, 타입을 지정한후, 우측 상단의 ADD TO REPORT를 선택한다.

타임 스탬프는 일반적으로 일단위로 컨버팅 되기 때문에, 세밀한 로그를 원하면 분단위 등으로 변경하거나 커스텀 쿼리를 이용해서 초단위 값으로 컨버팅하기를 권장한다.

다음 메뉴에서 그래프나 표를 선택하여 적절하게 그리고, X 축은 Deminsion에 설정한다. 아래는 Dimension을 timestamp로 선택하고, Y축은 Metric 값으로 jsonPayload.count를 준 예이다.



혹시 테이블을 그린후에 데이타가 나오지 않는 경우가 있는데, 이 경우는 대부분 DataStudio의 Time zone과 빅쿼리에 저장된 Time이 맞지 않아서, 쿼리 범위에서 제외되는 경우인데, 이 경우는 그래프의 Property에서 날짜 범위를 다음과 같이 조정해주면 된다.



이외에도 다양한 기능이 있는데, 다음 문서들을 참고하기 바란다.



t-SNE를 이용한 차원 감소


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


PCA 기반 차원 감소의 문제점

앞의 글에서 차원 감소에 대한 개념과, 차원 감소 알고리즘의 하나인 PCA 알고리즘에 대해서 살펴보았다.

PCA의 경우 선형 분석 방식으로 값을 사상하기 때문에 차원이 감소되면서 군집화 되어 있는 데이타들이 뭉게져서 제대로 구별할 수 없는 문제를 가지고 있다. 아래 그림을 보자


출처 https://www.youtube.com/watch?v=NEaUSP4YerM


이 그림은 2차원에서 1차원으로 PCA 분석을 이용하여 차원을 줄인 예인데, 2차원에서는 파란색과 붉은색이 구별이 되는데, 1차원으로 줄면서 1차원상의 위치가 유사한 바람에, 두 군집의 변별력이 없어져 버렸다.

t-SNE

이런 문제를 해결하기 위한 차원 감소 방법으로는 t-SNE (티스니라고 읽음) 방식이 있는데, 대략적인 원리는 다음과 같다.


먼저 점을 하나 선택한다. 아래는 검정색점을 선택했는데, 이 점에서 부터 다른점까지의 거리를 측정한다.



다음 T 분포 그래프를 이용하여, 검정 점(기준점) 을 T 분포 상의 가운데 위치한다면, 기준점으로부터 상대점 까지 거리에 있는 T 분포의 값을 선택(위의 T 분포 그래프에서 파란점에서 위로 점섬이 올라가서 T분포 그래프상에 붉은 색으로 X 표가 되어 있는 값)하여, 이 값을 친밀도 (Similarity)로 하고, 이 친밀도가 가까운 값끼리 묶는다.


이 경우 PCA 처럼 군집이 중복되지 않는 장점은 있지만, 매번 계산할때 마다 축의 위치가 바뀌어서, 다른 모양으로 나타난다. 단 데이타의 군집성과 같은 특성들은 유지 되기 때문에 시각화를 통한 데이타 분석에는 유용하지만, 매번 값이 바뀌는 특성으로 인하여, 머신러닝 모델의 학습 피쳐로 사용하기는 다소 어려운점이 있다.


아래 그림은 같은 데이타로 t-SNE 분석을 각각 한번씩한 결과를 시각화 해서 표현한 결과 인데, 보는 것과 같이 군집에 대한 특성은 그대로 유지 되지만 값 자체는 변화가 된것을 확인할 수 있다.




sklearn 을 이용한 t-SNE 구현

전체 코드는 https://github.com/bwcho75/dataanalyticsandML/blob/master/dimension%20reduction/2.%20t-SNE%20visualization.ipynb 에 공개되어 있으니 참고하기 바란다.


# Perform the necessary imports
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

model = TSNE(learning_rate=100)
transformed = model.fit_transform(feature)

xs = transformed[:,0]
ys = transformed[:,1]
plt.scatter(xs,ys,c=labels)

plt.show()


사실 코드가 너무 간단해서 설명할것이 없다. TSNE 객체를 선언하고 학습속도 (learning_rate)를 지정한다음 fit_transform 하면 끝이다. (싸이킷런 만세…)


다음글에서는 차원 감소 방법중에 마지막을 Matrix Factorization (행렬 인수 분해) 방법에 대해서 알아보도록 하겠다.







차원 감소와 PCA 분석

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

차원 감소 (Dimension reduction)

데이타를 분석할때 피쳐가 많으면 데이타 분석이 어렵고, 특히 3개 이상 (3차원)의 피쳐가 존재할 경우 시각화가 어려워진다. 머신러닝의 경우에 학습용 데이타의 피쳐가 많으면, 연산량이 많아지고, 특히 학습을 위해서 더 많은 데이타가 필요해진다. 이렇게 피쳐가 많음 으로써 발생하는 문제를 차원의 저주 (Dimension Curse)라고 이야기 하는데, 이 차원의 수를 줄이는 방법을 Dimension reduction / 차원 감소 방법이라고 한다.

차원 수를 줄인 다는 것은 다른 말로는 피쳐의 수를 줄인다는 말과 같고, 앞에서 언급한 바와 같이 데이타 분석에서는 차원을 줄여서 시각화를 가능하게 해서 데이타 분석을 용이하게 할 수 있다. 데이타 분석에 있어서 여전히 사람의 눈과 직관을 통한 분석은 중요한데, 3차원이 넘어가는 데이타는 시각화가 불가능하다. 그래서 차원을 줄여서 데이타의 특성을 파악할 필요가 있고, 또한 머신러닝에 있어서 학습 데이타의 수를 줄이고, 학습에 필요한 컴퓨팅 파워를 절약하기 위해서 차원 감소는 유용한 기법이 된다.

차원 감소 방식

차원을 감소 시키는 피쳐 선택 (Feature Selection)과 피쳐 추출 (Feature extraction) 두 가지 방식이 있다. 피쳐 선택의 경우는 여러개의 피쳐중에서 데이타의 특성을 가장 잘 나타내는 주요 필드 몇개만을 선택하여 대표 피쳐로 선택하는 방법이다.

예를 들어 [7,1,2],[100,1,3],[92,1,5] 가 있을때, 이 세개의 행렬에서 각 첫번째 열과 세번째 열이 그 변화 폭이 가장 크기 때문에, 첫번째와 세번째 열만을 대표 피쳐로 사용하여 다음과 같이 선택한다. [7,2],[100,3],[92,5] 이렇게 원래 피쳐에서 부분 집합만을 선택하는 방식을 피쳐 선택 방법이라고 한다.


다음은 피쳐 추출 (Feature extraction) 방식이 있는데, 이건 원본 데이타와 전혀 다른 형태의 데이타를 추출해낸다. 예를 들어 [7,1,2] 를 일정 공식에 의해서 [3,4] 등으로 변환하여 특성을 표현하는 방법인데, 이렇게는 이해가 약간 어려우니 PCA 기반의 피쳐 추출 방법에 대해서 알아보도록 하자


PCA

PCA 분석

다음과 같은 데이타가 있다고 하자.


PCA 분석에서는 데이타의 변화의 폭이 가장 큰 축을 정하고, 그 다음 그와 직교하는 축을 구한다


그리고 데이타의 중심점에 축을 위치 시켜서 0,0을 중심으로 데이타가 양쪽으로 균등하게 퍼지도록 분포를 시켜서 축을 뒤틀어서 아래와 같이 원래의 데이타를 변화 시킨다.


이렇게 PCA 분석을 하면, 데이타의 중심축을 0,0으로 위치 시킬 수 있고, 가장 데이타의 변화의 폭이 큰 순으로 X,Y축등을 지정하여 데이타를 볼 수 있다.

PCA를 이용한 차원의 감소

그러면 PCA 분석을 이용하여 차원을 어떻게 감소 시키는가?

PCA 분석을 하더라도 단순히 축을 틀어버린것이기 때문에 차원의 수는 줄어들지 않는다. PCA 분석을 하면, 각 피쳐 (축) 별로, 값의 변화도 (Variance : 해당 축의 값이 얼마나 크게 변하는가)를 볼 수 있는데, 다음은 PCA Variance 값의 예제이다.



그래프에서 보는것과 같이 0번 피쳐의 경우 Variance가 매우 심하고, 1,2는 상대적으로 많이 약한것을 볼 수 있다. 그래서 0만 피쳐로 사용하거나 또는 0,1만 피쳐로 사용하더라도 데이타 특징의 대부분을 나타낼 수 있다.

앞의 예제 데이타에서 2차원 데이타를  PCA 분석을 해서 첫번째 피쳐가 Variance가 가장 높다고 했을 때 이를 변환하면 다음과 같이 PCA  변환된 데이타의 X축의 값만을 사용하도록 해서 2차원을 1차원으로 줄일 수 있다.




물론 차원을 줄이면, 원래 데이타가 가지고 있는 특징이 다소 사라지는 단점이 있지만, 전체적인 데이타의 특성을 파악하는 대세에는 큰 영향이 없기 때문에, 더 장점이 많다.

Sklearn을 이용한 PCA 분석과 차원 감소

그러면 이를 파이썬 sklearn 라이브러리로 구현해보자. 여기서 사용할 데이타는 IRIS 데이타를 샘플 데이타로 사용하였다. 이 예제에서는 3차원인 IRIS 데이타를 PCA 분석을 통해서 2차원으로 줄여보도록 하겠다.

원본 데이타를 생성하고 시각화 하는 코드는 다음과 같다.


from sklearn import datasets
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import pandas as pd
iris = datasets.load_iris()

labels = pd.DataFrame(iris.target)
labels.columns=['labels']
data = pd.DataFrame(iris.data,columns=['Sepal length','Sepal width','Petal length','Petal width'])

fig = plt.figure( figsize=(6,6))
ax = Axes3D(fig, rect=[0, 0, .95, 1], elev=48, azim=134)
ax.scatter(data['Sepal length'],data['Sepal width'],data['Petal length'],c=labels,alpha=0.5)
ax.set_xlabel('Sepal lenth')
ax.set_ylabel('Sepal width')
ax.set_zlabel('Petal length')
plt.show()




PCA 분석을 통해서, 각 피쳐별 Variance를 분석하는 코드는 다음과 같다.


from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
import matplotlib.pyplot as plt

# Create scaler: scaler
scaler = StandardScaler()

# Create a PCA instance: pca
pca = PCA()

# Create pipeline: pipeline
pipeline = make_pipeline(scaler,pca)

# Fit the pipeline to 'samples'
pipeline.fit(data)

features = range(pca.n_components_)
plt.bar(features, pca.explained_variance_)
plt.xlabel('PCA feature')
plt.ylabel('variance')
plt.xticks(features)
plt.show()


분석을 해보면 PCA 분석에 의해서 변환된 피쳐 0,1의 Variation이 큰것을 확인할 수 있다.



그래서 PCA 변환된 피쳐중 0,1번 피쳐만 사용해서 시각화를 해보면 다음과 같다.


from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

model = PCA(n_components=2)
pca_features = model.fit_transform(data)

xf = pca_features[:,0]
yf = pca_features[:,1]
plt.scatter(xf,yf,c=labels);
plt.show();


그림처럼 2차원으로 줄여도 IRIS 군집화의 특성이 어느정도 남아 있는 것을 확인할 수 있다.


다음은 1차원으로 줄여서 시각화를 한 예인데


from sklearn.decomposition import PCA
import matplotlib.pyplot as plt

model = PCA(n_components=1)
pca_features = model.fit_transform(data)

xf = pca_features[:,0]
yf = len(xf)*[0]
plt.scatter(xf,yf,c=labels);
plt.show();



2차원에 비해서 -1~4사이에 분포된 두개의 클래스 (녹색과 노랑색)이 다소 겹치는 부분이 있지만, 전체적으로 봤을때 1차원으로 변환해도 어느정도 분류 특성을 유지하고 있는 것을 볼 수 있다.


이런 중첩 현상을 줄여주는 차원 감소 기법으로는 t-SNE라는 방법이 있는데, 이는 다음글에서 설명하도록 하겠다.