[Logstash on K8S] Azure SQL to ElasticSearch

Azure SQL(이하 sql)의 data를 ElasticSearch(이하 es)로 검색을 하기 위해서 sql 데이터를 es의 index로 옮겨야 한다. 데이터를 옮기는 방법은 여러가지 방법이 있지만 일반적으로 많이 쓰이는 데이터 처리 오픈소스인 Logstash를 사용하여 옮기는 방법을 알아보자.

 Logstash를 운영하는 방식도 다양하지만 간단하게 사용하려면 역시 K8S만 한 것이 없기 때문에 K8S에 구성해보자.

ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-configmap-start
  namespace: search
  labels:
    task: sql-search-init    
data:
  start.sh: |
    #!/bin/bash
    curl -L -O https://go.microsoft.com/fwlink/?linkid=2203102
    tar -xvf ./sqljdbc_11.2.0.0_kor.tar.gz
    mv ./sqljdbc_11.2/enu/mssql-jdbc-11.2.0.jre11.jar ./lib/mssql-jdbc-11.2.0.jre11.jar
  • start.sh 에서는 jdbc 드라이버를 셋팅하는 작업을 수행한다. 이 링크에서 항상 최신 버전의 jdbc 드라이버를 확인하는 것을 권장한다.
apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-configmap
  namespace: search
  labels:
    task: sql-search    
data:
  logstash.yml: |
    http.host: "127.0.0.0"
    path.config: /usr/share/logstash/pipeline
  logstash.conf: 
    input {
      jdbc {
        jdbc_driver_library => "/usr/share/logstash/lib/mssql-jdbc-11.2.0.jre11.jar"
        jdbc_driver_class => "com.microsoft.sqlserver.jdbc.SQLServerDriver"
        jdbc_connection_string => "jdbc:sqlserver://{database server}:1433;database={Database name};encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;"
        jdbc_user => "{user}"
        jdbc_password => "{password}"
        statement => "{SQL Query statement};"        
        schedule => "{CRON}"
        tracking_column => "{tracking column}"
        
      }
    }    
    output {
      elasticsearch {
        hosts => "{elastic host}"
        index => "{index name}"
        document_id => "%{index id name}"
        user => elastic
        password => "{elastic password}"
      }
      stdout {
        codec => rubydebug
      }
    }
  • input
    • jdbc_connection_string 에서는 접속할 Azure SQL의 connection string을 넣어 주면된다.
    • jdbc_user SQL 접속 계정 정보 값을 넣어준다. logstash 전용 계정을 만들어 사용하는 것을 권장한다.
    • jdbc_password 계정 비밀번호 값을 넣어준다.
    • statement index에 담을 table 혹은 작성한 query를 넣어 준다.
    • schedule 얼마 만큼 반복 작업을 할 것인지 스케줄을 정한다. 표기는 CRON을 사용한다.
    • tracking_column 데이터 변경에 대한 기준이 되는 column을 넣어 준다 (ex. timestemp)
  • output
    • hosts elastic host 정보를 넣어준다.
    • index SQL정보를 저장할 index 이름
    • document_id 인덱스에 저장할 문서를 식별할 키 값
    • password elstic에 접속할 비밀번호

Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sql-logstash
  namespace: search
spec:
  replicas: 1
  selector:
    matchLabels:
      task: sql-search      
  template:
    metadata:
      labels:
        task: sql-search        
    spec:
      containers:
      - name: logstash
        image: docker.elastic.co/logstash/logstash:8.4.2
        ports:
          - containerPort: 5044
        imagePullPolicy: Always
        volumeMounts:
        - mountPath: /usr/share/logstash/config
          name: config-volume
        - mountPath: /usr/share/logstash/pipeline
          name: logstash-pipeline-volume
        - mountPath: /start
          name: start
        lifecycle:
          postStart:
            exec:
              command:
               - /start/start.sh
      volumes:
      - name: config-volume
        configMap:
          name: logstash-configmap
          items:
          - key: logstash.yml
            path: logstash.yml
      - name: logstash-pipeline-volume
        configMap:
          name: logstash-configmap
          items:
          - key: logstash.conf
            path: logstash.conf
      - name: start
        configMap:
          name: logstash-configmap-start
          defaultMode: 0777
      securityContext:
        fsGroup: 101

Service

apiVersion: v1
kind: Service
metadata:
  labels:
    task: sql-search
    kubernetes.io/name: logstash
  name: sql-logstash
  namespace: search
spec:
  ports:
  - port: 5000
    targetPort: 5000
  selector:
    k8s-app: logstash

Configmap 부터 Service 까지 순서대로 모두 K8S에 적용해 주면 된다.
(여러 개의 파일로 보는 것이 귀찮다면 하나의 yaml에 모두 넣어서 한번에 실행해도 된다.)

About Raspbian

Raspbian은 하드웨어 제품인 RPi(Raspberry Pi)와 Linux계열의 OS(Operating System)인 Debian의 합성어로 Raspberry Pi Foundation이 개발한 RPi 전용 OS다.

RPi는 Raspberry Pi Foundation에서 학교와 개발도상국에서 기초 컴퓨터 과학의 교육을 증진시키기 위해 개발한 신용카드 크기의 싱글 보드 컴퓨터이다.
크기가 작고 전력 소비가 5V-2A로 동작하도록 설계되어 있기 때문에 Raspbian은 저전력 ARM CPU에 상당히 최적화되 도록 만들어 졌다.

Raspberry Pi 4 board

Raspbian을 사용하기 위해서는 앞서 설명한 RPi라는 하드웨어가 필요하다.

(Desktop 버전을 사용하여 live Disc를 생성하거나, 가상머신을 이용하여 PC에 설치 할 수도 있다.)

RPi Model B+RPi 2 Model BRPi 3 Model BRPi 4 Model B
SoCBCM2835BCM2836BCM2837BCM2711
CPUARM11 @700MHzQuad Cortex
A7@900MHz
Quad Cortex
A53@1.2Ghz
Quad Cortex
A72@1.5Ghz
Instruction SetARMv6ARMv7-AARMv8-AARMv8-A
GPU250MHz VideoCore IV250MHz VideoCore IV400MHz VideoCore IV500MHz VideoCore VI
RAM512MB SDRAM1GB SDRAM1GB SDRAM1, 2 or 4 GB
WirelessNoneNone802.11n/Bluetooth 4.0802.11n/Bluetooth 5.0
VideoHDMI/CompositeHDMI/CompositeHDMI/Composite2x micro-HDMI/Composite
AudioHDMI/HeadphoneHDMI/HeadphoneHDMI/HeadphoneHDMI/Headphone
RPi는 그래픽 성능을 띄어나지만 매우 저렴한 가격(약 25~35$)에 구입할 수 있다.

RPi는 OSHW(Open Source Hardware)라서 하드웨어 스팩은 물론이고 전용 OS인 Raspbian역시 오픈소스로 공개 되어 있기 때문에 하드웨어만 구입하면 나머지 사용하는 것에 대해서는 100% 무료로 사용할 수 있다.

RPi 전용 OS인 Rapbian은 Debian을 기반으로 만들어 졌기 때문에 대부분의 주요 명령어는 Debian과 거의 동일하게 사용하는 하다.

  • APT(Advanced Package Tool)을 통한 소프트웨어 설치 / 업데이트
  • dpkg(Debian package) 형식의 패키지 소프트웨어 사용

PIXEL(Pi Improved Xwindows Environment, Lightweight)이라는 GUI 기능 제공을 한다. 이를 통해서 데스크탑 환경을 사용할 수 있다. 특히, 데스크탑 환경 중 App Store와 동일한 개념의 PI Store 제공하여 호환되는 Package들을 쉽게 제공 받을 수 있도록 되어 있다.

Raspbian Pi Store

오픈소스 싱글 보드계열 중에서 저렴하고 파워풀한 기능을 제공하고 있어서 가성비가 좋은 제품으로 많은 개발자들에게 사랑 받고 있다.

전 세계적으로 많은 개발자들이 사용하고 있다 보니 많은 관련 개발 커뮤니티들도 분야별로 자연스럽게 형성되고 있다. 초급자라면 관심 있는 분야의 커뮤니티에서 많은 정보(Tip)들을 제공 받을 수 있다. (오픈소스의 장점이 잘 살려진 것 같다.)

커뮤니티 링크: https://www.raspberrypi.org/community/

Jupyterhub on Azure Kubernetes Service

데이터 과학을 수행할때 주로 사용되는 언어로는 Python과 R이 있다. 그리고 이 2가지 언어를 지원하는 IDE 환경도 많이 나와 있는데, 그 중 협업 환경에서 많이 선호되는 Jupyterhub 사용에 대해서 알아보겠다.

Jupyterhub은 Project Jupyter라는 비영리 단체에서 개발한 오픈소스 프로젝트다. BSD라이선스를 따르고 있어서 누구나 100% 무료로 사용할 수 있다.

Jupyterhub는 특정 사용자 그룹별로 Jupyter Notebook(이하 Notebook)이라는 가상 개발 환경을 제공한다. 데이터 과학을 수행하는 사용자는 Notebook이라는 가상 개발 환경안에서 업무를 수행하면 된다. 즉, Jupyterhub는 여러 Notebook들을 공유하는 서버인 것이다. 때문에 사용자는 공유 서버를 통해서 자신이 원하는 Notebook 가상 환경 및 리소스를 제공받을 수 있기 때문에 설치 및 유지 관리 작업에 부담을주지 않는다. 또한 특정 사용자 혹은 그룹별로 별도의 가상환경을 구성할 수 있기 때문에 시스템 관리가 용이하다. 

Jupyterhub는 2가지 배포본을 제공되고 있는데 첫 번째는 가상머신 환경에 설치하는 배포본이고 두 번째는 Serverless 환경인 Kubernetes에 설치하는 배포본이다.

클라우드 상에서 운용하기에는 Scale Set을 자유롭게 확장 및 유지관리 할 수 있는 Kubernetes(Serverless framework)환경이 좋기 때문에 가상머신 설치방법은 건너띄고 Jupyterhub를 Kubernetes에 설치 및 구성하는 방법알 알아 보겠다.

참고로, 여기에서 사용된 Kubernetes는 Azure에서 제공하는 AKS(Azure Kubernetes Service)를 이용하였다.

Jupyterhub를 Azure Kubernetes에 설치하기

먼저, Jupyterhub를 설치할 AKS 클러스터에 대한 크리덴셜을 가져오고 최근 환경으로 설정한다.

RESOURCENAME = 'Jupyter'
CLUSTERNAME = 'Jupyterhub'

az aks get-credentials --resource-group=$RESOURCENAME --name=$CLUSTERNAME
kubectl config set-cluster $ClusterName

Jupyterhub를 바로 설치하기 전 jupyterhub를 환경을 구성할 내용을 준비해야 한다.

Jupyterhub 사전 준비작업

Kubernetes는 Serverless 환경이기 때문에 작업한 파일을 영구적으로 보존할 스토리지 볼륨이 필요하다. 다음과 같이 Storage Class를 만들어 준다.

vim storageclass.yaml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: azurefile
provisioner: kubernetes.io/azure-file
mountOptions:
  - dir_mode=0777
  - file_mode=0777
  - uid=1000
  - gid=1000
  - mfsymlinks
  - nobrl
  - cache=none
parameters:
  skuName: Standard_LRS

kubectl apply -f storageclass.yaml

잘 만들어 졌는지 확인한다.

kubectl get storageclass

다음으로 jupyterhub에 접근할 Client들이 사용할 인증 보안 토큰을 다음과 같이 32byte 임의의 16진 문자열로 생성한다.

openssl rand -hex 32
36806100da02acb12199b94067a55c1231172123b05f061a428777eb65b238fd

위에서 준비한 환경설정 정보들을 가지고 다음과 같이 jupyter 환경을 구성한다. StorageClass, 초기 관리자 계정 정보, 인증토큰을 다음과 같이 넣어준다.

vim config.yaml

singleuser:
  extraEnv:
    EDITOR: "vim"
  storage:
    dynamic:
      storageClass: azurefile
auth:
  admin:
    users:
      - administrator
proxy:
  secretToken: "36806100da02acb12199b94067a55c1231172123b05f061a428777eb65b238fd"

마지막으로, Jupyterhub 배포본을 제공 받기 위한 helm repository를 설정해 준다.

helm repo list
helm repo add jupyterhub https://jupyterhub.github.io/helm-chart/
helm repo add stable https://kubernetes-charts.storage.googleapis.com
helm repo update

이제, 준비를 다했으니 설치해 보자.

Jupyterhub 설치하기

Kubernetes에 Jupyterhub가 설치될 네임스페이스를 만들어준다.

RELEASE=jupyterhub
NAMESPACE=jupyterhub

kubectl create namespace  $NAMESPACE 

사전 준비단계에서 만들어 둔 config.yaml을 다음과 같이 실행한다.

helm upgrade --install $RELEASE jupyterhub/jupyterhub --namespace $NAMESPACE --values config.yaml

위 명령을 실행하고 나면 설치 과정이 백그라운드로 실행 되기 때문에 어떻게 진행 되고 있는지 확인 하기 어렵다.
아래 명령어를 통해서 pod가 정상적으로 올라오는지 확인 해야 한다.

kubectl get pod --namespace $NAMESPACE

정상적으로 Running 상태를 확인 했다면, 정상 설치가 된것이다.
아래 명령을 통해서 서비스에 할당된 Public IP를 확인해 본다.

kubectl get service --namespace $NAMESPACE

확인 된 Public IP를 웹브라우저를 통해서 들어가면 로그인 하라고 나올 것이다.
위에서 설정한 admin계정 이름을 사용하여 접속하면 된다.

여기까지 간단히 Jupyterhub를 Azure Kubernetes Service에 설치하고 접속하는 것 까지 알아보았다. 이제 여러 Notebook 가상 환경을 구성하고 여러 사용자 혹은 그룹과 함께 사용해 보길 바란다.

Dependency Injection 개념과 Ninject 사용법

소프트웨어를 잘 만들기 위해서 많은 디자인 패턴들이 사용되는데, 그중에서 DI(Dependency Injection, 의존성 주입)에 대한 개념과 .NET MVC에서 많이 사용되는 DI Framework들 중 하나인 Ninject(Open Source Project)에 대해서 알아보겠다.

DI(Dependency Injection) 란?

프로그래밍에서 사용되는 객체들 사이의 의존관계를 소스코드가 아닌 외부 설정파일 등을 통해 정의하게 하는 디자인 패턴이다. 개발자는 각 객체의 의존관계를 일일이 소스코드에 작성할 필요 없이 설정파일에 의존관계가 필요하다는 정보만 된다. 그러면 객체들이 생성될때, 외부로부터 의존관계를 주입 받아 관계가 설정 된다.

DI를 적용하면, 의존관계 설정이 컴파일시 고정 되는 것이 아니라 실행시 설정파일을 통해 이루어져 모듈간의 결합도(Coupling)을 낮출 수 있다.
결합도가 낮아지면, 코드의 재사용성이 높아져서 모듈을 여러 곳에서 수정 없이 사용할 수 있게 되고, 모의 객체를 구성하기 쉬워지기 때문에 단위 테스트에 용이해 진다.

이제, .NET에서 MVC 프로젝트를 만들때 DI를 구현하기 위해 가장 많이 사용하는 Open Source인 Ninject에 대해서 알아보자.

NInject 알아보기

(공식 페이지 : http://www.ninject.org/)

간단히 이름부터 살펴보면, NInject는, N(Ninja) + Inject로 대표 이미지로도 Ninja로 되어 있다.

홈페이지 대문에 보면 “Be fast, be agile, be precise”라는 슬로건이 있는데 닌자처럼 빠르고 민첩하며 정확하게 프로그램을 만들수 있게 하겠다는 정신이 담겨있는 것 같다.

(Nate Kohari라는 소프트웨어 엔지니어가 최초 개발을 했는데, 개인적인 생각으로는 N을 중의적인 의미로 사용한게 아닌가 싶다. 참고 : https://www.infoq.com/articles/ninject10-released/)

NInject는 Open Source 라이브러리로 Apache License 2.0에 따라 배포되었으며,
2007년 부터 .NET 어플리케이션의 DI를 구현하기 쉽게 해주도록 지원하고 있다.

이제, 실제로 사용해 보자

NInject 사용해 보기

  1. Package Install
    • Visual Studio에서 제공하는 Nuget Package Installer를 사용하여 다음 Package들을 설치한다.
      – Ninject
      – Ninject.Web.WebApi
      – Ninject.Web.Common
      – Ninject.Web.Common.WebHost
  2. Edit Ninject.Web.Common.cs
    • 위 Package 설치가 완료되면, 프로젝트 최상단에서 App_Start 폴더에 Ninject.Web.Common.cs 파일이 생성된 것을 확인 할 수 있다.
    • 해당 파일을 열어 보면, CreateKernel이라는 method가 있는데 다음 코드를 추가한다. 그러면 NInject가 controller의 의존성 주입을 구성해 줄 수 있게 된다.
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
  1. Register Service
    • 스크롤을 조금 내려 보면, RegisterServices라는 method를 확인 할 수 있는데, 실제로 의존관계를 설정(bind)하는 곳이다. 의존성을 주입할 객체들의 관계를 다음과 같이 추가한다.
private static void RegisterServices(IKernel kernel)
{
    kernel.Bind<ICommonStore>().To<CommonStore>();
}
  1. Use it on controller
    • 이제 의존성 주입을 위한 IoC 설정과, 의존관계 설정(bind)작업을 모두 하였으니 Controller에서 사용해보자.
public class CommonStoreController : Controller
{    
    public CommonStoreController(ICommonStore common)
    {
        this.commonStore = common;
    }

    private ICommonStore commonStore;

    public int GetItemCount(string id)
    {
        return commonStore.Add(id);
    }
}

Design Pattern 중 DI(Dependency Injection, 의존성 주입)이라는 패턴에은 표준 프로그래밍을 할 때 중요한 요소이긴하지만 무조건 사용해야 하는 것은 아니다. 간단한 프로그램이나 객체간의 결합이 명확하여 구분하지 않아도 되는 경우 굳이 프로젝트를 무겁게(?) 만들 필요없다.

그리고 Ninject는 .NET에서 많이 사용되는 Open Source DI Framework 중 하나로 DI를 할 때 쉽게 구현할 수 있어서 선호되는 편이지만 다양한 DI Open Source Framework들이 있으며, 성능 면에서도 훨씬 더 좋은 것들이 있으니 확인하고 사용하길 바란다.
(참고 : https://www.claudiobernasconi.ch/2019/01/24/the-ultimate-list-of-net-dependency-injection-frameworks/)