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


Archive»


 
 

쿠버네티스 #6

Replication Controller를 이용하여 서비스 배포하기

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


1. 도커 파일 만들기

node.js로 간단한 웹서버를 만들어서 도커로 패키징 해보자.

실습을 진행하기 위해서 로컬 환경에 도커와, node.js 가 설치되어 있어야 한다. 이 두 부분은 생략하도록 한다.

여기서 사용한 실습 환경은 node.js carbon 버전 (8.11.3), 도커 맥용 18.05.0-ce, build f150324 을 사용하였다.

node.js 애플리케이션 준비하기

node.js로 간단한 웹 애플리케이션을 제작해보자 server.js라는 이름으로 아래 코드를 작성한다.

var os = require('os');

 

var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm "+os.hostname());

 

 //log

 console.log("["+

               Date(Date.now()).toLocaleString()+

               "] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 코드는 8080 포트로 웹서버를 띄워서 접속하면 “Hello World!” 문자열과 함께, 서버의 호스트명을 출력해준다. 그리고 stdout에 로그로, 시간과 서버의 호스트명을 출력해준다.

코드 작성이 끝났으면, 서버를 실행해보자

%node server.js


다음 브라우저로 접속하면 다음과 같은 결과를 얻을 수 있다.


그리고 콘솔화면에는 아래와 같이 시간과 호스트명이 로그로 함께 출력된다.

도커로 패키징하기

그러면 이 node.js 애플리케이션을 도커 컨테이너로 패키징 해보자

Dockerfile 이라는 파일을 만들고 아래 코드를 작성한다.

FROM node:carbon

EXPOSE 8080

COPY server.js .

CMD node server.js > log.out


이 코드는 node.js carborn (8.11.3) 컨테이너 이미지를 베이스로 한후에,  앞서 작성한 server.js 코드를 복사한후에, node server.js > log.out 명령어를 실행하도록 하는 컨테이너를 만드는 설정파일이다.

설정 파일이 준비되었으면,  도커 컨테이너 파일을 만들어보자


% docker build -t gcr.io/terrycho-sandbox/hello-node:v1 .


docker build  명령은 컨테이너를 만드는 명령이고, -t는 빌드될 이미지에 대한 태그를 정하는 명령이다.

빌드된 컨테이너 이미지는 gcr.io/terrycho-sandbox/hello-node로  태깅되는데, 이는 향후에 구글 클라우드 컨테이너 레지스트리에 올리기 위해서 태그 명을 구글 클라우드 컨테이너 레지스트리의 포맷을 따른 것이다. (참고 https://cloud.google.com/container-registry/docs/pushing-and-pulling)

포맷은 [HOST_NAME]/[GOOGLE PROJECT-ID]/[IMAGE NAME]


gcr.io/terrycho-sandbox는 도커 이미지가 저장될 리파지토리의 경로를 위의 규칙에 따라 정의한 것인데,

  • gcr.io는 구글 클라우드 컨테이너 리파지토리 US 리전을 지칭하며,

  • terrycho-sandbox는 본인의 구글 프로젝트 ID를 나타낸다.

  • 이미지명을 hello-node 로 지정하였다.

  • 마지막으로 콜론(:) 으로 구별되어 정의한 부분은 태그 부분으로, 여기서는 “v1”으로 태깅을 하였다.


이미지는 위의 이름으로 지정하여 생성되어 로컬에 저장된다.




빌드를 실행하면 위와 같이 node:carbon 이미지를 읽어와서 필요한 server.js 파일을 복사하고 컨테이너 이미지를 생성한다.

컨테이너 이미지가 생성되었으면 로컬 환경에서 이미지를 기동 시켜보자


%docker run -d -p 8080:8080 gcr.io/terrycho-sandbox/hello-node:v1


명령어로 컨테이너를 실행할 수 있다.

  • -d 옵션은 컨테이너를 실행하되, 백그라운드 모드로 실행하도록 하였다.

  • -p는 포트 맵핑으로 뒤의 포트가 도커 컨테이너에서 돌고 있는 포트이고, 앞의 포트가 이를 밖으로 노출 시키는 포트이다 예를 들어 -p 9090:8080 이면 컨테이너의 8080포트를 9090으로 노출 시켜서 서비스 한다는 뜻이다. 여기서는 컨테이너 포트와 서비스로 노출 되는 포트를 동일하게 8080으로 사용하였다.


컨테이너를 실행한 후에, docker ps 명령어를 이용하여 확인해보면 아래와 같이 hello-node:v1 이미지로 컨테이너가 기동중인것을 확인할 수 있다.



다음 브라우져를 통해서 접속을 확인하기 위해서 localhost:8080으로 접속해보면 아래와 같이 Hello World 와 호스트명이 출력되는 것을 확인할 수 있다.


로그가 제대로 출력되는지 확인하기 위해서 컨테이너 이미지에 쉘로 접속해보자

접속하는 방법은


% docker exec -i -t [컨테이너 ID] /bin/bash

를 실행하면 된다. 컨테이너 ID 는 앞의 docker ps 명령을 이용하여 기동중인 컨테이너 명을 보면 처음 부분이 컨테이너 ID이다.

hostname 명령을 실행하여 호스트명을 확인해보면 위에 웹 브라우져에서 출력된 41a293ba79a7과 동일한것을 확인할 수 있다. 디렉토리에는 server.js 파일이 복사되어 있고, log.out 파일이 생성된것을 볼 수 있다.  

cat log.out을 이용해서 보면, 시간과 호스트명이 로그로 출력된것을 확인할 수 있다.



2. 쿠버네티스 클러스터 준비

구글 클라우드 계정 준비하기

구글 클라우드 계정 생성은 http://bcho.tistory.com/1107 문서를 참고하기 바란다.

쿠버네티스 클러스터 생성하기

쿠버네티스 클러스터를 생성해보자, 클러스터 생성은 구글 클라우드 콘솔의 Kubernetes Engine > Clusters 메뉴에서 Create 를 선택하면 클러스터 생성이 가능하다.



클러스터 이름을 넣어야 하는데, 여기서는 terry-gke-10 을 선택하였다. 구글 클라우드에서 쿠버네티스 클러스터는 싱글 존에만 사용가능한 Zonal 클러스터와 여러존에 노드를 분산 배포하는 Regional 클러스터 두 가지가 있는데, 여기서는 하나의 존만 사용하는 Zonal 클러스터를 설정한다. (Regional은 차후에 다루도록 하겠다.)

다음 클러스터를 배포한 존을 선택하는데, asia-northeast1-c (일본)을 선택하였다.

Cluster Version은 쿠버네티스 버전인데, 1.10.2 버전을 선택한다.

그리고 Machine type은 쿠버네티스 클러스터의 노드 머신 타입인데, 간단한 테스트 환경이기 때문에,  2 CPU에 7.5 메모리를 지정하였다.

다음으로 Node Image는 노드에 사용할 OS 이미지를 선택하는데, Container Optimized OS를 선택한다. 이 이미지는 컨테이너(도커)를 운영하기 위해 최적화된 이미지이다.

다음으로는 노드의 수를 Size에서 선택한다. 여기서는 3개의 노드를 운용하도록 설정하였다.


아래 부분에 보면  Automatic node upgrades 라는 기능이 있다.


구글 클라우드의 재미있는 기능중 하나인데, 쿠버네티스 버전이 올라가면 자동으로 버전을 업그레이드 해주는 기능으로, 이 업그레이드는 무정지로 진행 된다.


gcloud 와 kubectl 설치하기

클러스터 설정이 끝났으면 gloud (Google Cloud SDK 이하 gcloud)를 인스톨한다.

gcloud 명령어의 인스톨 방법은 OS마다 다른데, https://cloud.google.com/sdk/docs/quickstarts 문서를 참고하면 된다.

별다른 어려운 작업은 없고, 설치 파일을 다운 받아서 압축을 푼후에, 인스톨 스크립트를 실행하면 된다.


kubectl은 쿠버네티스의 CLI (Command Line Interface)로, gcloud를 인스톨한후에,

%gcloud components install kubectl

명령을 이용하면 인스톨할 수 있다.

쿠버네티스 클러스터 인증 정보 얻기

gcloud와 kubectl 명령을 설치하였으면, 이 명령어들을 사용할때 마다 쿠버네티스에 대한 인증이 필요한데, 인증에 필요한 인증 정보는 아래 명령어를 이용하면, 자동으로 사용이 된다.

gcloud container clusters get-credentials CLUSTER_NAME

여기서는 클러스터명이 terry-gke10이기 때문에,

%gcloud container clusters get-credentials terry-gke-10

을 실행한다.


명령어 설정이 끝났으면, gcloud 명령이 제대로 작동하는지를 확인하기 위해서, 현재 구글 클라우드내에 생성된 클러스터 목록을 읽어오는 gcloud container clusters list 명령어를 실행해보자



위와 같이 terry-gke-10 이름으로 asia-northeast1-c 존에 쿠버네티스 1.10.2-gke.3 버전으로 클러스터가 생성이 된것을 볼 수 있고, 노드는 총 3개의 실행중인것을 확인할 수 있다.

3. 쿠버네티스에 배포하기

이제 구글 클라우드에 쿠버네티스 클러스터를 생성하였고, 사용을 하기 위한 준비가 되었다.

앞에서 만든 도커 이미지를 패키징 하여, 이 쿠버네티스 클러스터에 배포해보도록 하자.

여기서는 도커 이미지를 구글 클라우드내의 도커 컨테이너 레지스트리에 등록한 후, 이 이미지를 이용하여 ReplicationController를 통해 총 3개의 Pod를 구성하고 서비스를 만들어서 이 Pod들을 외부 IP를 이용하여 서비스를 제공할 것이다.

도커 컨테이너 이미지 등록하기

먼저 앞에서 만든 도커 이미지를 구글 클라우드 컨테이너 레지스트리(Google Container Registry 이하 GCR) 에 등록해보자.

GCR은 구글 클라우드에서 제공하는 컨테이너 이미지 저장 서비스로, 저장 뿐만 아니라, CI/CD 도구와 연동하여, 자동으로 컨테이너 이미지를 빌드하는 기능, 그리고 등록되는 컨테이너 이미지에 대해서 보안적인 문제가 있는지 보안 결함을 스캔해주는 기능과 같은 다양한 기능을 제공한다.


컨테이너 이미지를 로컬환경에서 도커 컨테이너 저장소에 저장하려면 docker push라는 명령을 사용하는데, 여기서는 GCR을 컨테이너 이미지 저장소로 사용할 것이기 때문에, GCR에 대한 인증이 필요하다.

인증은 한번만 해놓으면 되는데

%gcloud auth configure-docker

명령을 이용하면, 인증 정보가 로컬 환경에 자동으로 저장된다.



인증이 완료되었으면, docker push 명령을 이용하여 이미지를 GCR에 저장한다.

%docker push gcr.io/terrycho-sandbox/hello-node:v1


명령어를 실행하면, GCR에 hello-node 이미지가 v1 태그로 저장된다.


이미지가 GCR에 잘 저장되었는지를 확인하기 위해서 구글 클라우드 콘솔에 Container Registry (GCR)메뉴에서 Images라는 메뉴를 들어가보자




아래와 같이 hello-node 폴더에 v1이라는 태그로 이미지가 등록된것을 확인할 수 있다.

ReplicationController 등록

컨테이너 이미지가 등록되었으면 이 이미지를 이용해서 Pod를 생성해보자,  Pod 생성은 Replication Controller (이하 rc)를 생성하여, rc가 Pod 생성 및 컨트롤을 하도록 한다.


다음은 rc 생성을 위한 hello-node-rc.yaml 파일이다.


apiVersion: v1

kind: ReplicationController

metadata:

 name: hello-node-rc

spec:

 replicas: 3

 selector:

   app: hello-node

 template:

   metadata:

     name: hello-node-pod

     labels:

       app: hello-node

   spec:

     containers:

     - name: hello-node

       image: gcr.io/terrycho-sandbox/hello-node:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


hello-node-rc 라는 이름으로 rc를 생성하는데, replica 를 3으로 하여, 총 3개의 pod를 생성하도록 한다.

템플릿 부분에 컨테이너 스팩에 컨테이너 이름은 hello-node로 하고 이미지는 앞서 업로드한 gcr.io/terrycho-sandbox/hello-node:v1 를 이용해서 컨테이너를 만들도록 한다. 컨테이너의 포트는 8080을 오픈한다. 템플릿 부분에서 app 이라는 이름의 라벨을 생성하고 그 값을 hello-node로 지정하였다. 이 라벨은 나중에 서비스 (service)에 의해 외부로 서비스될 pod들을 선택하는데 사용 된다.


여기서 imagePullPolicy:Always  라고 설정한 부분이 있는데, 이는 Pod를 만들때 마다 매번 컨테이너 이미지를 확인해서 새 이미지를 사용하도록 하는 설정이다.  컨테이너 이미지는 한번 다운로드가 되면 노드(Node) 에 저장이 되어 있게 되고, 사용이 되지 않는 이미지 중에 오래된 이미지는 Kublet이 가비지 컬렉션 (Garbage collection) 정책에 따라 이미지를 삭제하게 되는데, 문제는 노드에 이미 다운되어 있는 이미지가 있을 경우 컨테이너 생성시 노드에 이미 다운로드 되어 있는 이미지를 사용한다. 컨테이너 리파지토리에 같은 이름으로 이미지를 업데이트 하거나 심지어 그 이미지를 삭제하더라도 노드에 이미지가 이미 다운로드 되어 있으면 다운로드된 이미지를 사용하기 때문에, 업데이트 부분이 반영이 안된다.

이를 방지하기 위해서 imagePullPolicy:Always로 해주면 컨테이너 생성시마다 이미지 리파지토리를 검사해서 새 이미지를 가지고 오기 때문에, 업데이트된 내용을 제대로 반영할 수 있다.


%kubectl create -f hello-node-rc.yaml


명령어를 실행해서 rc와 pod를 생성한다.




위의 그림과 같이 3개의 Pod가 생성된것을 확인할 수 있는데, Pod가 제대로 생성되었는지 확인하기 위해서 hello-node-rc-rsdzl pod에서 hello-node-rc-2phgg pod의 node.js 웹서버에 접속을 해볼 것이다.

아직 서비스를 붙이지 않았기 때문에, 이 pod들은 외부 ip를 이용해서 서비스가 불가능하기 때문에, 쿠버네티스 클러스터 내부의 pod를 이용하여 내부 ip (private ip)간에 통신을 해보기 위해서 pod에서 pod를 호출 하는 것이다. kubectl describe pod  [pod 명] 명령을 이용하면, 해당 pod의 정보를 볼 수 있다. hello-node-rc-2hpgg pod의 cluster ip (내부 ip)를 확인해보면 10.20.1.27 인것을 확인할 수 있다.


kubectl exec 명령을 이용하면 쉘 명령어를 실행할 수 있는데, 다음과 같이 hello-node-rc-rsdzl pod에서 첫번째 pod인 hello-node-rc-2phgg의 ip인 10.20.1.27의 8080 포트로 curl 을 이용해 HTTP 요청을 보내보면 다음과 같이 정상적으로 응답이 오는 것을 볼 수 있다.


Service 등록

rc와 pod 생성이 끝났으면 이제 서비스를 생성해서 pod들을 외부 ip로 서비스 해보자

다음은 서비스를 정의한 hello-node-svc.yaml 파일이다.


hello-node-svc.yaml

apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


Selector 부분에 app:hello-node 로 지정하여, pod들 중에 라벨의 키가 app이고 값이 hello-node인 pod 들만 서비스에 연결하도록 지정하였다. 다음 서비스의 포트는 80으로 지정하였고, pod의 port는 8080으로 지정하였다.


서비스가 배포되면 위와 같은 구조가 된다.

%kubectl create -f hello-node-svc.yaml

명령을 이용하면 서비스가 생성이 된다.


다음 생성된 서비스의 외부 ip를 얻기 위해서 kubectl get svc 명령을 실행해보자

아래 그림과 같이 35.200.40.161 IP가 할당된것을 확인할 수 있다.


이 IP로 접속을 해보면 아래와 같이 정상적으로 응답이 오는 것을 확인할 수 있다.


RC 테스트

rc는 pod의 상태를 체크하다가 문제가 있으면 다시, pod를 기동해주는 기능을 한다.

이를 테스트하기 위해서 강제적으로 모든 pod를 제거해보자. kubectl delete pod --all을 이용하면 모든 pod를 제거할 수 있는데, 아래 그림을 보면, 모든 pod를 제거했더니 3개의 pod가 제거되고 새롭게 3개의 pod가 기동되는 것을 확인할 수 있다.



운영중에 탄력적으로 pod의 개수를 조정할 수 있는데, kubectl scale 명령을 이용하면 된다.

kubectl scale --replicas=[pod의 수] rc/[rc 명] 식으로 사용하면 된다. 아래는 pod의 수를 4개로 재 조정한 내용이다.



자원 정리

테스트가 끝났으면 서비스, rc,pod를 삭제해보자.

  • 서비스 삭제는 kubectl delete svc --all 명령어를 이용한다.

  • rc 삭제는 kubectl delete rc --all

  • pod 삭제는 kubectl delete pod --all

을 사용한다.

삭제시 주의할점은 pod를 삭제하기 전에 먼저 rc를 삭제해야 한다. 아니면, pod가 삭제된 후 rc에 의해서 다시 새로운 pod가 생성될 수 있다.





Kubernetes #1 - 소개

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

배경

도커와 쿠버네티스를 알게 된건 수년전인데, 근래에 들어서 다시 쿠버네티스를 보기 시작하였다.

컨테이너 기반의 환경은 배포에 장점이 있고 마이크로 서비스 아키텍쳐 구조에 잘 맞아들어가는 듯 싶지만, 컨테이너가 약간 빠르다는 장점은 있지만, 가상 머신으로도 충분히 패키징이 가능하고, 로컬의 개발환경을 동기화 시키는 장점은 vagrant 로도 충분하다는 생각을 가지고 있었다.


그리고 결정적으로 도커 컨테이너를 운용하기 위한 컨테이너 관리 환경이 그다지 성숙하지 못했었다. Mesosphere, Swarm, Kubernetes 등 다양한 환경이 나오기는 하였지만 기능적으로 부족한 부분도 많았고, 딱히 어떤 플랫폼이 대세라고 정해진것도 없었다.


마이크로 서비스 아키텍쳐 발전

그러나 근래에 들어서 재미있어지는 현상이 마이크로 서비스 아키텍쳐가 단순 개념에서 부터 점점 더 발전하기 시작하였고, 디자인 패턴과 이를 구현하기 위한 다양한 인프라 플랫폼들이 소개되기 시작하였다.

또한 서비스가 점점 작아지면서, 1~2 코어로도 운영할 수 있는 작은 서비스들이 다수 등장하게 되었고 이런 작은 서비스는 VM 환경으로 운영하기에는 낭비가 너무 심하다. (VM 이미지 크기도 너무 크고, 다양한 이미지를 VM으로 관리 배포하기에는 배포 속도등 다양한 문제가 발생한다.)


솔루션의 발전

배포 방식도 예전에 서버에 계속해서 애플리케이션 코드만 업데이트 하는 방식이 아니라, VM이나 컨테이너 단위로 배포하는 피닉스 서버 패턴과 이를 구현하기 위한 Spinnaker  와 같은 솔루션이 나오고 있고, 지능형 라우팅과 분산 트렌젝션 로그 추적을 하는 기능들이 Envoy 라는 솔루션으로 나오고 이를 중앙 통제하기 위한 Istio.io 와 같은 서비스 메쉬 솔루션 까지 나오기에 이르렀다.


데브옵스 모델의 성숙화

데브옵스 모델도 나온지는 오래되었지만, 운영을 데브옵스라는 이름으로 바꾼 것일뿐 실제적인 변화가 없는 팀들이 많았고, 또는 데브옵스라는 이름아래에서 개발팀이 개발과/운영 역할을 병행해서 하는 사례가 오히려 많았다.

이런 데브옵스의 개념도 근래에 들어서 정리가 되어가고 있는데, 개발팀이 개발과 시스템에 대한 배포/운영을 담당한다면, 데브옵스팀은 개발팀이 이를 쉽게할 수 있는 아랫단의 플랫폼과 자동화를 하는데 목표를 두는 역할로 역할이 명확해지고 있다.


이러한 배경에서 슬슬 컨테이너 기반의 환경이 실질적으로 적용될만하다는 것으로 판단하였고, 다시 컨테이너 환경에 대해서 살펴보기 시작하였다.

왜 하필이면 쿠버네티스인가?

그렇다면 Swarm,Mesosphere 가 아니라 왜 하필이면 쿠버네티스인가? 컨테이너 운용 환경은 여러 오픈소스에 의해서 표준이 없이 혼돈이었다가 작년말을 기점으로 해서 쿠버네티스가 de-facto 표준으로 되어가는 형국이다. 아래 트랜드 그래프에서 보면 알 수 있듯이 쿠버네티스의 트랜드가 지속적으로 올라가서 가장 높은 것을 확인할 수 있다.



또한 주요 클라우드 벤더인 아마존,구글,애저 모두 컨테이너 관리 환경을 쿠버네티스를 지원하는 정책으로 변화된것은 물론이고 IBM이나 시스코와 같은 온프렘(on-premise) 솔루션 업체들도 경쟁적으로 쿠버네티스를 지원하고 있다.

컨테이너 운영환경이 무엇인데?

컨테이너 (도커)에 필요성과 마이크로 서비스의 관계등에 대해서는 워낙 소개된 글들이 많아서 생략한다. 그렇다면 쿠버네티스가 제공하는 컨테이너 운영환경이란 무엇인가? 이를 이해하기 위해서는 먼저 컨테이너에 대해서 이해할 필요가 있는데, 컨테이너의 가장 대표적인 예로는 도커가 있다. 도커에 대한 자세한 설명은 링크를 참고하기 바란다.


그러면 단순하게 도커 컨테이너를 하드웨어나 VM에 배포하면 사용하면 되지 왜 컨테이너 운영환경이 필요한가?


작은 수의 컨테이너라면 수동으로 VM이나 하드웨어에 직접 배포하면 되지만, VM이나 하드웨어의 수가 많아지고 컨테이너의 수가 많아지면, 이 컨테이너를 어디에 배포해야 하는지에 대한 결정이 필요하다.

16 CPU, 32 GB 메모리 머신들에 컨테이너를 배포할때 컨테이너 사이즈가 2 CPU, 3 CPU, 8 CPU등 다양할 수 있기 때문에, 자원을 최대한 최적으로 사용하기 위해서 적절한 위치에 배포해야 하고, 애플리케이션 특성들에 따라서, 같은 물리 서버에 배포가 되어야 하거나 또는 가용성을 위해서 일부러 다른 물리서버에 배포되어야 하는 일이 있다. 이렇게 컨테이너를 적절한 서버에 배포해주는 역할을 스케쥴링이라고 한다.


이러한 스케쥴링 뿐만이 아니라 컨테이너가 정상적으로 작동하고 있는지 체크하고 문제가 있으면 재 기동등을 해주고, 모니터링, 삭제관리등 컨테이너에 대한 종합적인 관리를 해주는 환경이 필요한데, 이를 컨테이너 운영환경이라고 한다.

쿠버네티스란?

이런 컨테이너 운영환경중 가장 널리 사용되는 솔루션이 쿠버네티스 (Kubernetes, 약어로 k8s)라고 한다.

구글은 내부 서비스를 클라우드 환경에서 운영하고 있으며, 일찌감치 컨테이너 환경을 사용해왔다. 구글의 내부 컨테이너 서비스를 Borg라고 하는데, 이 구조를 오픈소스화한것이 쿠버네티스이다.

GO 언어로 구현이되었으며, 특히 재미있는 것은 벤더나 플랫폼에 종속되지 않기 때문에, 대부분의 퍼블릭 클라우드 (구글,아마존,애저)등에 사용이 가능하고 오픈 스택과 같은 프라이빗 클라우드 구축 환경이나 또는 베어메탈 (가상화 환경을 사용하지 않는 일반 서버 하드웨어)에도 배포가 가능하다.

이런 이유 때문에 여러 퍼블릭 클라우드를 섞어서 사용하는 환경이나 온프렘/퍼블릭 클라우드를 혼용해서 쓰는 환경에도 동일하게 적용이 가능하기 때문에 하이브리드 클라우드 솔루션으로 많이 각광 받고 있다.


흔히들 컨테이너를 이야기 하면 도커를 떠올리기 쉬운데, 도커가 물론 컨테이너 엔진의 대표격이기는 하지만 이 이외도 rkt나 Hyper container(https://hypercontainer.io/) 등 다양한 컨테이너 엔진들이 있으며, 쿠버네티스는 이런 다양한 컨테이너 엔진을 지원한다.

컨테이너 환경을 왜 VM에 올리는가?

온프렘 환경(데이타센터)에서 쿠버네티스를 올릴때 궁금했던점 중의 하나가, 바로 베어메탈 머신위에 쿠버네티스를 깔면 되는데, 보통 배포 구조는 VM(가상화 환경)을 올린 후에, 그 위에 쿠버네티스를 배포하는 구조를 갖는다. 왜 이렇게 할까 한동안 고민을 한적이 있었는데, 나름데로 내린 결론은 하드웨어 자원 활용의 효율성이다. 컨테이너 환경은 말그대로 하드웨어 자원을 컨테이너화하여 isolation 하는 기능이 주다. 그에 반해 가상화 환경은 isolation 기능도 가지고 있지만, 가상화를 통해서 자원 , 특히 CPU의 수를 늘릴 수 있다.


예를 들어 설명하면, 8 CPU 머신을 쿠버네티스로 관리 운영하면, 8 CPU로밖에 사용할 수 없지만, 가상화 환경을 중간에 끼면, 8 CPU를 가상화 해서 2배일 경우 16 CPU로, 8배일 경우 64 CPU로 가상화 하여 좀 더 자원을 잘게 나눠서 사용이 가능하기 때문이 아닌가 하는 결론을 내렸다.

이 이외에도 스토리지 자원의 활용 용이성이나 노드 확장등을 유연하게 할 수 있는 장점이 있다고 한다.


다음 글에서는 쿠버네티스를 구성하는 컴포넌트들의 구성과 개념에 대해서 설명하도록 한다.



구글 클라우드 로드밸런서를 이용한 인스턴스간 부하 분산

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


클라우드 VM  생성하는 방법을 숙지 하였으면, 다음으로 여러개의 VM 사이에 부하를 분산할 수 있는 로드밸런서 기능에 대해서 알아보자.

구글의 로드 밸런서는 일반적인 L4 스위치와 같이 일반적인 TCP/UDP 프로토콜에 대한 라우팅이 가능하다.

여기에 더해서

  • HTTP 프로토콜에 대해서는 HTTPS Termination 뿐만 아니라, HTTP URI에 따라서 가까운 서버나 특정 서버로 라우팅이 가능한 L7과 유사한 기능을 가지고 있다. (자세한 내용은 http://bcho.tistory.com/1111)를 참고

  • 다른 글에서도 여러번 언급했지만, 구글의 클라우드 로드밸런서를 사용하게 되면, 서버로 들어오는 트래픽을 인터넷이 아니라 구글이 전세계에 배포한 엣지 노드중 가장 위치적으로 가까운 노드에 접속을 한 후에, 구글의 광케이블 백본망을 통해서 서버로 트래픽을 전송하기 때문에 네트워크 구간 속도를 획기적으로 줄일 수 있다. (자세한 내용은 http://bcho.tistory.com/1109 참고)

또한 몇가지 재미있는 특징이

Pre-warm up 이 필요 없음

몇몇 클라우드 로드밸런서의 경우에 갑자기 높은 부하가 들어오면 그 부하를 받아내지 못하는 경우가 있다. 이를 방지 하려면 미리 그 만한 부하를 수십분 동안 줘서 로드밸런서의 크기를 키워 주는 Pre-warm up 작업을 하거나 직접 클라우드 업체에 연락을 해서 용량 증설 요청을 해야 한다. 그러나 이러한 방식은 예측하지 못한 부하가 들어왔을 때 대응이 어렵다는 단점이 있고 Pre-warm 이라는 개념을 모르고 있는 사용자의 경우 종종 준비 못한 상황에서 트래픽을 받아내지 못해서 운영상에 문제를 겪는 경우가 종종있다. 구글 클라우드의 로드밸런서는 이러한 제약 사항이 없이 들어오는 부하에 맞춰서 Pre-warm up이 없이 바로 스케링이 가능하다.

리전 레벨의 부하 분산이 가능함

멀티 리전에 백앤드를 배포하였을때, 구글의 클라우드 로드밸런서는 단일 IP주소로 리전간에 로드밸런싱이 가능하다. 로드밸런싱은 클라이언트가 가까운 리전을 우선으로 라우팅을 하고, 만약 리전 장애시에는 다른 리전으로 자동으로 Fail over한다. DNS 라운드 로빈 방식등의 부가적인 방식을 쓰는 것이 아니라 단일 정적 IP를 사용하기 때문에 별다르 설정이 필요 없고, 방화벽 규칙등 인프라 규칙등을 설정하는데 용이 하다.

모든 트래픽에 대해서 Logging이 가능

모든 부하에 대해서 로깅이 가능하다. 보통 로드밸런서의 경우에는 부하량이 많아서 로그를 남기지 않는 경우가 많은데, 외부로 부터의 해킹 시도나 오류가 발생했을때, 맨 앞단의 로드밸런서의 access log가 중요하다. 구글 클라우드의 경우에는 이 모든 Log를 남겨주고 API를 통해서 수집도 가능하기 때문에 차후 분석이 가능하다.


Managed group vs unmanaged group

로드밸런서 설정에 앞서서 managed group과 unmanaged group에 대한 개념을 이해할 필요가 있다.

그룹은 인스턴스의 집합으로, 이 단위로 로드밸런서에 연결할 수 있다.

그룹은 크게 unmanaged group과 managed group 두가지로 정의된다.

Unmanaged group

Unmanaged group 은 인스턴스를 수동으로 묶어 놓은 그룹이다. 인스턴스의 사이즈나 종류에 상관 없이 그냥 묶어 놓는 묶음이다. 수동으로 아무 종류의 인스턴스나 추가해 넣을 수 있기 때문에 오토 스케일링은 불가능하다.  Unmanaged group을 이해하려면 반대로 managed group의 개념을 이해하면된다.

Managed group

Managed group의 인스턴스는 사용자가 직접 추가할 수 는 없고, managed group을 만들때, 템플릿을 만들어서 인스턴스 생성을 클라우드가 하게 한다.  템플릿이란 인스턴스를 만들기 위한 붕어빵(틀?)과 같은 건데, VM의 사이즈, OS 이미지등 설정을 미리 정해놓으면, 클라우드가 인스턴스를 생성할때 이 템플릿에 정해진 설정에 따라서 인스턴스를 추가하게 된다. 템플릿 생성은 약간 내용이 길기 때문에 차후에 다른 글에서 따로 설명하도록 한다.


대략적인 특징을 이해했으면, 로드밸런서를 통한 부하 분산 구성을 구현해보자.

Managed group은 템플릿 생성등 절차가 더 복잡하기 때문에, 나중에 따로 설명하기로 하고, 이 글에서는 unmanaged group 기반으로 그룹을 생성해서 부하를 분산하는 구성을 설정한다.

인스턴스 준비하기

먼저 부하를 받을 인스턴스를 구성한다. 간단한 node.js express 웹 애플리케이션을 구성하여 배포한다.

애플리케이션은 / 로 접속했을 경우, 접속한 VM 인스턴스의 IP 정보등을 간단하게 리턴해주는 애플리케이션이다. (아래 “/” 경로에 대한 route 코드 참고)


var router = express.Router();

var os = require('os');

/* GET home page. */

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

             res.json(os.networkInterfaces());

});

module.exports = router;


코드가 준비되었으면, VM 을 생성하자. lb-test-1 이라는 이름의 인스턴스로 asia-east1-a에 small 타입의 인스턴스로 생성을 하였다.

Allow HTTP traffic 옵션을 ON해서 HTTP 트래픽을 받도록 하였으며, Internal IP와 External IP를 자동 할당하도록 설정하였다.

External IP를 설정한 이유는 로드밸런서가 제대로 작동함을 테스트하기 위해서 로드밸런서를 거친 요청과 로드밸런서를 거치지 않은 요청 둘을 각각 보내서 테스트하기 위함이고, 만약 운영 환경에서 실제로 서비스 할때는 굳이 External IP를 부여할 필요 없이 Internal Ip만 부여하고 외부에서 직접 접근을 차단하고 로드밸런서를 통한 HTTP 접근만을 허용하는 것이 보안상 유리 하다.





추가적인 설정으로는 아래 그림과 같이 management tab에서 Automation의 Startup script에 아래와 같은 Shell 스크립트를 추가한다. Start up 스크립트는 VM이 기동되자 마자 자동으로 수행되는 스크립트로, 이 예제에서는 VM이 기동 된후 자동으로 node.js 인스턴스를 구동하도록 npm start 를 수행하도록 하였다.



VM이 생성되었으면, VM에 node.js 소스코드를 배포 한다. (scp, ftp나 git repository를 사용하건 본인 취향에 맞는 방법으로 복제한다.)

인스턴스 디스크 스냅샷 추출하기

앞에서 만든 인스턴스를 마스터 인스턴스로 같은 내용의 인스턴스를 복제할것인데, 복제를 위해서 디스크를 복제할 필요가 있다. 현재 디스크의 복제본을 “스냅샷"이라는 형태로 떠놓는데, Compute Engine 메뉴에서 snapshots 메뉴로 들어가면 디스크의 스냅샷을 뜰 수 있다.




스냅샷 메뉴로 들어온 후 상단의 CREATE SNAPSHOT 메뉴를 선택한 후, 아래 그림처럼 스냅샷 이름 “restapi-instance-snapshot” 이라는 이름으로 입력하고 Source disk는 앞에서 생성한 lb-test-1 의 디스크를 선택해서 스냅샷을 생성한다.


인스턴스 복제 하기

첫번째 인스턴스를 생성하였으니, 이번에는 부하 분산을 위해 두번째 인스턴스를 생성해보자, 두번째 인스턴스는 첫번째 인스턴스와 동일한 설정을 사용할 것이기 때문에 첫번째 인스턴스를 복제해서 사용한다.

첫번째 인스턴스에 대한 상세 정보에 들어가면 상단에 “CLONE”을 누르면 같은 설정의 인스턴스를 생성할 수 있다.



CLONE을 누르면 다음과 같이 동일 설정으로 인스턴스 생성이 자동으로 진행되는 것을 확인할 수 있다.

이때 디스크 이미지를 앞에서 생성한 lb-test-1과 동일한 이미지를 사용해야 하기 때문에, 앞서 생성한 “rest-instance-snapshot” 이미지를 선택한다.


내 외부 IP 설정도 자동 배정으로 되는 것을 아래 그림과 같이 확인하고


스타트업 스크립트도 동일한지를 확인한다.


접속 확인

로드 밸런서를 테스트할 lb-test-1, lb-test-2 VM이 생성이 되었으면 정상적으로 웹 애플리케이션이 올라왔는지 확인해보자. 예제에서 할당된 주소는  lb-test-1이 외부/내부 IP가 104.155.213.74/10.140.0.4, lb-test-2가 107.167.186.59/10.140.0.5 가 부여되었다.

각각의 외부 IP로 접속해보면 아래와 같이 각각 부여된 내부 IP를 리턴하는 것을 확인할 수 있다.






인스턴스 그룹 생성하기

로드밸런서 테스트를 하기 위한 VM 생성이 모두 끝났다. 이제 로드 밸런서에 이 인스턴스를 연결하기 위해서, 인스턴스들을 그룹으로 묶어 보자.

인스턴스 그룹 생성은 Compute Engine 메뉴에서 Instance groups를 선택한다.




create instance group을 선택한후, unmanaged group 생성을 선택한다.

다음 아래 그림과 같이 instance group 이름을 입력하고, zone은 인스턴스를 생성한 zone과 같은 zone으로 선택한다. 다음 create method에서 앞서 생성한 두개의 인스턴스를 선택할 것이기 때문에, “Select existing instances”를 선택한다.

그리고 VM instances에서 앞에서 생성한 lb-test-1, lb-test-2를 선택하여 추가한다.


이제 로드밸런서에 연결할 인스턴스 그룹이 생성되었다. 이제 로드밸런서를 설정하고, 생성한 그룹을 로드 밸런서에 연결해보자

STATIC IP 할당하기

로드밸런서 설정에 앞서서 로드밸런서에 지정할 정적 IP를 생성하자


위의 그림 처럼 Networking 메뉴에서 External IP addresses를 선택한 후에, 정적 IP 예약 한다. lb-test-ip라는 이름으로 생성하고 타입을 Global 로 선택한다. Regional IP는 특정 Region에만 부여가 가능하고, Global IP는 여러 Region에 걸쳐서 사용할 수 있는  IP 이다. 여기서 예약된 IP는 뒤에 로드밸런서가 생성된 후에, 이 로드밸런서에 부여하게 된다.

로드밸런서 생성

이제 로드밸런서를 설정하기 위한 모든 준비가 끝났다.

로드 밸런서를 생성하기 위해서 Networking에 Load balancing 메뉴로 들어가자.

로드밸런서 생성은 크게 다음과 같은 단계를 통해서 생성이 된다.

  • 프로토콜 선택

  • 백앤드 서비스 설정

  • Host & Path rule 설정

  • 프론트 앤드 설정

각각의 디테일 내용에 대해서 알아보자

프로토콜 선택

로드밸런서 생성을 시작하면 제일 처음 프로토콜을 선택하게 되는데, HTTP, TCP,UDP 3가지 프로토콜을 지원한다. 이 예제에서는 HTTP 프로토콜을 사용한다.



백앤드 서비스 선택

프로토콜 선택이 끝났으면 백앤드 서비스를 선택하는데, 백앤드 서비스는 앞에서 생성한 인스턴스 그룹이 된다.


참고 :

하나의 로드밸런서에 하나의 인스턴스 그룹을 설정하는 것 뿐 아니라, 동시에 다른 인스턴스 그룹을 각각 다른 백앤드로 정의하여 인스턴스 그룹간의 부하 분산도 가능하다.


두개 이상의 인스턴스 그룹을 이용한 롤링 업그레이드 구성

이런 기능을 이용하면, 두개의 인스턴스 그룹을 만들어서 A,B 그룹으로 부하 분산을 하고, 서버 패치나 배포시 A 그룹을 정지시키고 배포를 한후, 재기동 하고, B 그룹을 정지 시키고 배포를 한후 재 기동 하는 롤링 업그레이드 방식으로 무정지 서비스 배포가 가능하다.


리전간 (또는 존간) 라우팅 구성

또는 아래 그림과 같은 같은 기능의 백앤드를 각각 다른 리전에 배포하면 리전 단위의 장애가 발생하였을때, 정상적인 리전으로 요청을 라우팅하여 리전 단위의 HA (고가용성) 구성이 가능하다. 물론 같은 방식으로 존(ZONE)간의 라우팅을 통한 HA 구성도 역시 가능하다.


HTTP URI 기반의 라우팅

HTTP의 URI 또는 호스트명(서로 다른 호스트명도 하나의 로드밸런서로 라우팅이 가능하다)을 기반으로 특정 서버 그룹으로 요청을 라우팅 할 수 있는 기능이다. 일반적인 네트워크 장비인 L4등에서는 구현이 불가능한 기능인데 원리는 단순하다.

아래 그림처럼 /static URL를 갖는 요청은 “static-resources” 라는 서버 그룹으로 라우팅 하고, /video URL을 갖는 요청은 “video-resources”라는 서버 그룹으로 라우팅 하는 식이다.


매우 간단한 기능이기는 하지만 그 활용도가 매우 높다.

웹서버, 스트리밍 서버등으로 컨텐츠 타입에 따라 서버를 나눌 수 도 있지만,

마이크로 서비스 아키텍쳐 (MSA)로 되어 있는 경우, 각 서비스 컴포넌트가 다른 URL을 가지기 때문에, 앞단에 API Gateway와 같이 URL에 따라 라우팅을 해주는 프록시 계층이 필요한데, 구글의 로드밸런서는 이 기능을 지원하기 때문에, 백앤드 서비스가 MSA로 구성되어 있을 경우 이 기능을 유용하게 사용할 수 있다.



백앤드 서비스의 구성은 백앤드 서비스를 선택하면 기존에 있는 백앤드 서비스를 선택하거나 또는 새로 생성하는 것을 선택할 수 있는데, 여기서는 새로운 백앤드 서비스를 구성한다. 새로운 서비스 생성을 선택하면 아래 그림과 같이 백앤드 서비스의 이름을 입력하고 (여기서는 lb-test-backend 로 구성하였다.) 앞에서 생성한 Instance group인 lb-test-group을 선택하면 된다.

다음으로는 로드밸런서에서 들어온 트래픽을 instance group의 인스턴스들의 어느 포트로 전달할것인지를  “Port numbers”에 정의하는데, 여기서는 HTTP 트래픽을 전달하는 것이기 때문에, 80을 선택한다.


다음으로 하단에 보면 “Health check” 라는 메뉴가 보이고, 선택하게 되어있는데, Health check는 인스턴스 그룹내의 인스턴스가 양호한지를 체크하고, 만약에 정상적이지 않으면 비정상 노드는 부하분산에서 빼버리는 기능을 수행한다. Health check에 대한 설정을 하지 않았기 때문에, 새로 생성하기를 선택하면 아래와 같은  화면이 출력된다.



새롭게 생성하는  Health check의 이름을 입력하고,  Health check 방식을 그 아래에 입력한다.

Protocol은 HTTP를 사용하기 때문에 HTTP를 입력하고, Port에는 HTTP 포트인 80포트 (실제 VM Instance에서 HTTP 80포트를 이용하여 서비스를 제공한다.) 그리고 서비스가 정상인지를 확인하는 URL을 /로 입력하였다.

이렇게 입력하면 로드밸런서는 백앤드 서비스내의 인스턴스에 http://xxx.xxx:80/ 로 HTTP 요청을 보내고 요청이 HTTP 200 OK로 리턴되면 정상이라고 판단한다. 이 체크 주기를 조정할  수 있는데, 아래 Health check criteria 에서 정의 한다. Check Interval은 체크 주기이고, Timeout은 이 시간(초)내에 응답이 없으면 장애라고 판단을 한다.


여기서는 하나의 백앤드 서비스만 구성을 했지만 앞의 ‘참고'에서 설명한 바와 같이 하나의 로드밸런서에 여러개의 백앤드 서비스를 구성하여 연결할 수 있다.

Host & Path rule 설정

Host & Path는 앞의 참고에서 설명한 것과 같이 HTTP 요청의 호스트명( photo.example.com , api.example.com)에 따라 백앤드 서비스로 라우팅을 하는 규칙을 설정하는 것과 HTTP URI를 기반으로 라우팅 규칙을 설정하는 단계로, 여기서는 모든 트래픽을 모두 한 백앤드 서비스로 라우팅을 할것이기 때문에 host나 URI 부분에 별도의 규칙을 명기하지 않고 비워놓은 상태에서 앞서 생성한 lb-test-backend 백앤드 서비스를 지정한다.



프론트앤드 설정

맨 마지막으로 프론트 앤드 서비스를 설정하는데, 여기서는 어떤 프로토콜을 어떤 IP와 포트로 받을 것인지, HTTPS의 경우에는 SSL Certificate (인증서)를 설정하는 작업을 한다.

여기서는 HTTP 프로토콜을 80 포트로 설정하기 때문에 아래와 같이 설정하고, IP는 앞에서 할당한 정적 IP 인 lb-test-ip 를 할당한다.



모든 설정이 완료되었다.

테스트

모든 설정이 완료 되었기 때문에 테스트를 진행해보자. 테스트를 바로 진행하면 아래와 같은 화면이 뜰 것이다.



로드밸런서가 초기 셋업되는 시간까지 약 30초정도 시간이 걸린다. 30 초 정도 후에 로드 밸런서의 주소 (이 예제의 경우 130.211.8.67)로 해보면 다음과 같이 앞에서 앞에서 생성한 10.140.0.4 와 10.140.0.5로 번갈아가면서 부하가 분산이 됨을 확인할 수 있다.




다음에는 managed  group을 이용한 auto scale out 방법에 대해서 소개하도록 한다.



구글 클라우드 로드밸런서 소개

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


클라우드 플랫폼에서 가장 필요한 기능중의 하나가 로드밸런서이다.

그중에서 구글 클라우드의 로드밸런서는 L7 스위치 이상의 기능을 가지면서 로드밸런서와 api gateway의 일부 기능을 수행할 수 있는데, 어떤 특징이 있는지 살펴보자. (개인적인 생각이지만 이게 정말 물건이다..)

HTTP 프로토콜 지원

TCP,UDP 뿐 아니라 HTTP 레이어의 로드밸런싱을 지원한다. HTTPS Termination을 당연히 지원하고 HTTP 모드로 부하분산을 할 경우 HTTP URI에 따라 다양한 라우팅을 할 수 있다.

No warming

다른 클라우드 로드밸런서와는 달리 트래픽이 갑자기 많이 들어오더라도 별도의 워밍업작업 없이 트래픽을 받을 수 있다. 클라우드 로드밸런서의 경우에는 종종 수십분간 워밍(부하를 지속해서 넣어서 로드밸런서의 용량을 늘려주는 행위)을 해주지 않으면 큰 대역폭의 트래픽을 못 받는 경우가 있다.

리전간 부하 분산과 서버 그룹간 부하 분산

여기 부터가 재미있는 기능인데, 들어오는 트래픽에 따라서 특정 리전(국가)로 라우팅을 하도록 설정할 수 있고, 별도의 설정이 없으면 자동으로 클라이언트가 가까운 데이타 센터로 트래픽을 라우팅해줄 수 있다.


아래 그림은 HTTP 요청을 US와 EU 리전에 있는 각각의 서버 그룹으로 분산하고, HTTPS요청도 US와 EU에 있는 각각의 리전으로 분산하는 구조를 설명한 그림이다.


(출처 : https://cloud.google.com/compute/docs/load-balancing/http/cross-region-example#overview)


리전간 부하 분산을 하면 리전 단위의 장애 대응이 가능할 뿐만 아니라, 가장 가까운 데이타 센터로 부터 호출을 할 수 있기 때문에 네트워크 구간 시간을 줄일 수 있다.


위의 그림에서 봐야 할것중 하나가 서버 그룹간의 부하 분산인데, 같은 애플리케이션이라도 “인스턴스 그룹" 이라는 개념으로 서버들을 묶을 수 있다. 위에 HTTP 를 지원하는 애플리케이션은 두개의 그룹으로 정의되어 있는데, 이렇게 서버를 여러개의 그룹으로 묶으면 좋은 점이 롤링 업그레이드가 가능하다는 것이다. 즉 그룹 A,B가 있을때, A를 로드 밸런서에서 떼어서 업데에트 한 후 다시 붙이고, 그 다음은 그룹 B를 로드밸런서에서에서 떼어서 업데이트 한 후 다시 붙여서 서버 배포나 업데이트시에 무장애로 진행이 가능하다.

HTTP URI 기반의 부하 분산

매우 흥미로운 기능중 하나가 HTTP의 URI를 기반으로 특정 서버 그룹으로 요청을 라우팅 할 수 있는 기능이다. 일반적인 네트워크 장비인 L4등에서는 구현이 불가능한 기능인데 원리는 단순하다.

아래 그림처럼 /static URL를 갖는 요청은 “static-resources” 라는 서버 그룹으로 라우팅 하고, /video URL을 갖는 요청은 “video-resources”라는 서버 그룹으로 라우팅 하는 식이다.



출처 : https://cloud.google.com/compute/docs/load-balancing/http/content-based-example#overview


매우 간단한 기능이기는 하지만 그 활용도가 매우 높다.

웹서버, 스트리밍 서버등으로 컨텐츠 타입에 따라 서버를 나눌 수 도 있지만,

마이크로 서비스 아키텍쳐 (MSA)로 되어 있는 경우, 각 서비스 컴포넌트가 다른 URL을 가지기 때문에, 앞단에 API Gateway와 같이 URL에 따라 라우팅을 해주는 프록시 계층이 필요한데, 구글의 로드밸런서는 이 기능을 지원하기 때문에, 백앤드 서비스가 MSA로 구성되어 있을 경우 이 기능을 유용하게 사용할 수 있다.

글로벌 엣지 서버와 통합을 통한 네트워크 가속

다른 글에서도 설명했던 내용인데, 구글 클라우드 로드 밸런서를 사용하게 되면 전세계에 흩어져 있는 약 70여개의 엣지 노드를 통해서 요청 트래픽을 받는다. 가장 가까운 엣지 노드로 트래픽을 받은 후, 엣지노드에서 서버까지 구글의 광케이블을 통해서 트래픽을 전달해줘서, 글로벌 서비스의 경우 네트워크 지연 시간을 절약할 수 있다.



자세한 내용은 http://bcho.tistory.com/1109 를 참고


MSA 아키텍쳐 구현을 위한 API 게이트웨이의 이해 #2

API 게이트웨이 기반의 디자인 패턴

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



API 게이트 웨이는 여러개의 엔드포인트를 설정하고, 각 엔드포인트 마다 메세지 흐름을 워크 플로우 엔진 설정을 통해서 API 에 대한 Mediation, Aggregation 설정을 할 수 있는 미들웨어 이다. 쉽게 말하면 설정과 프로그래밍이 가능한 툴일 뿐이다. 그래서, API 게이트 웨이를 도입한다고 게이트웨이가 재 역할을 하는 것이 아니라, 게이트웨이 를 이용하여 적절한 게이트 웨이 아키텍쳐를 설계해야 한다. 


여기서는 API 게이트 웨이를 이용한 아키텍쳐 설계시 참고할 수 있는 디자인 패턴에 대해서 소개 한다. 

※ 이 패턴들은 예전에 ESB 기반으로 SOA 프로젝트를 했을 때 사용했던 패턴으로 일반적인 이론이 아니라 실제 적용 및 검증을 거친 패턴이다.


다중 API 게이트웨이 아키텍쳐


API 게이트 웨이를 배포할때는 하나의 게이트웨이가 아니라 용도나 목적에 따라서 게이트 웨이를 분리하는 전략을 취할 수 있다. 몇가지 분리 패턴에 대해서 알아보도록 하자

내부 게이트웨이와 외부 게이트 웨이 엔드포인트 분리

가장 유용한 패턴중의 하나는 외부 서비스용 게이트웨이와 내부 서비스용 게이트웨이를 분리하는 방안이다. 물리적으로 게이트 웨이 자체를 두개로 나누는 방법도 있지만, 하나의 게이트웨이에서 엔드포인트를 내부용과 외부용으로 분리하는 방안이다.




<그림. 외부 및 내부 게이트웨이를 분리한 패턴>


같은 내부 API를 외부와 내부로 나눠서 서비스를 하되, 외부 엔드포인트에 대해서는 인증/인가 로직을 거치도록 한다.

내부 API 엔드포인트는 내부 IP를 가지도록 하고, 방화벽 안에서만 오픈하되 별도의 인증/인가 로직을 거치지 않고 내부 서버들이 API를 호출하는데 사용할 수 있도록 한다. 


파일 업/다운로드 엔드포인트 분리


API 게이트웨이는 내부 구조는 쓰레드 풀 기반의 멀티 쓰레드나 또는 비동기 IO 기반의 싱글 쓰레드 모델을 사용한다.

쓰레드 풀 모델은 톰캣같은 WAS와 비슷한 모델로, 쓰레드 풀내의 하나의 쓰레드가 하나의 API 요청에 대해서 응답까지 모두 처리하는 모델로, API 요청이 들어오면 응답을 보낼때 까지 그 쓰레드는 해당 API 호출에 의해서 점유 된다 그래서, 이러한 모델의 API 게이트웨이는 일반적인 WAS와 마찬가지로 동시에 서비스 할 수 있는 트렌젝션 수가 쓰레드풀의 전체수밖에 되지 않는다.

싱글 쓰레드 모델은 비동기 IO 기반의 방식으로 멀티 쓰레드 모델에 비해서 많은 클라이언트를 처리할 수 있다.

(비동기 IO에 대한 개념은 http://bcho.tistory.com/881 을 참고하기 바란다. Node.js가 대표적인 비동기 IO 기반의 싱글 쓰레드 모델이다.)

파일 업로드나 다운로드와 같은 트렌젝션은 CPU는 많이 사용하지 않지만, 요청 처리에 시간이 많이 걸리는 작업이기 때문에, 쓰레드 풀 형태의 API 게이트 웨이 제품에서는 파일 업/다운로드에 쓰레드가 오랫동안 잡혀있기 때문에, 서비스를 할 수 있는 유휴 쓰레드 수가 적게 되고, 다른 일반 API 서비스에 영향을 줄 수 있다.

싱글 쓰레드 기반의 비동기 IO 게이트웨이의 경우에는 비동기 IO이기 때문에 파일 업/다운로드에는 다소 유리할 수 있지만, 네트워크 대역폭을 상당 부분 소모해버리기 때문에 마찬가지로 다른 API  서비스를 하는데 영향을 줄 수 있다.

그래서 이러한 파일 업/다운로드는 가급적이면 게이트 웨이를 거치지 않고 별도의 독립된 엔드포인트나 프록시를 사용하는 것이 좋은데, 다음은 별도의 프록시를 넣는 아키텍쳐 설계 방식의 예이다.

 


<그림. 파일 업/다운로드를 API 게이트웨이에서 분리해내는 방법>


1. API 클라이언트가 파일 서버에 API를 이용하여 파일 다운로드를 요청한다.

2. 파일 서버는 API에 대한 응답으로 파일을 바로 내리는 것이 아니라, 파일을 다운로드 받을 수 있는 URL과 함께, 임시 인증 토큰을 발급(현재 API 클라이언트에 IP 에만 유효하고, 특정시간 예를 들어 발급후 30분 이내만 사용이 가능한 토큰)하여, API 클라이언트에게 리턴한다.

3. API 클라이언트는 2에서 받은 URL로 임시 인증 토큰과 함께 파일 다운로드를 파일 다운로드 프로젝시를 통해서 요청한다.

4. 파일 다운로드 프록시는 임시 인증 토큰을 인증한 다음에, 파일 다운로드를 수행한다.


파일 다운로드 프록시는 일반적인 리버스 프록시 (HA Proxy, Nginx,Apache)등을 사용할 수 있으며 여기에 간단하게 다운로드용 임시 인증 토큰 로직을 넣으면 된다. 또는 아마존 클라우드의 CDN과 같은 서비스들도 임시 다운로드 토큰과 같은 서비스를 제공하기 때문에, CDN 사용시에도 유사하게 적용할 수 있는 아키텍쳐이다.


특수 목적 엔드포인트 분리


파일 업로드/다운로드 엔드 포인트를 분리한 것 처럼, 특수 목적의 엔드포인트는 별도의 API 게이트웨이로 분리해 내는 것이 좋다.

예를 들어 인증등이 없이 고속으로 많은 로그를 업로드 하는 엔드 포인트같은 경우, 부하량이 많기 때문에 다른 일반 API 엔드포인트에 부담을 주지 않기 위해서 분리 할 수 있다.


트렌젝션 ID 추적 패턴


MSA 아키텍쳐를 기반으로 하게 되면, 클라이언트에서 호출된, 하나의 API 요청은 API 게이트웨이와 여러개의 서버를 거쳐서 처리된 후에, 최종적으로 클라이언트에 전달된다.

만약에 중간에 에러가 났을 경우, 어떤 호출이 어떤 서버에서 에러가 났는지를 연결해서 판단해야 할 수 가 있어야 한다. 예를 들어 서버 A,B,C를 거쳐서 처리되는 호출의 경우 서버 C에서 에러가 났을때, 이 에러가 어떤 메세지에 의해서 에러가 난 것이고, 서버 A,B에서는 어떻게 처리되었는 찾으려면, 각 서버에서 나오는 로그를 해당 호출에 대한 것인지 묶을 수 있어야 한다.

하나의 API 호출을 트렌젝션이라고 정의하자, 그리고 각 트렌젝션에 ID를 부여하자. 그래서 API 호출시, HTTP Header에 이 트렌젝션 ID를 넣어서 서버간의 호출시에도 넘기면 하나의 트렌젝션을 구별할 수 있다.

여기에 추가적인 개념이 필요한데, 서버 A,B,C가 있을때, API 서버 B가 하나의 API 호출에서 동 두번 호출된다고 가정하자. 그러면 에러가 났을때 B 서버에 있는 로그중에, 이 에러가 첫번째 호출에 대한 에러인지, 두번째 호출에 대한 에러인지 어떻게 구분할까?

아래 그림을 서버 A->B로의 첫번째 호출과, 두번째 호출 모두 트렌젝션 ID가 txid:1로, 이 txid로는 구별이 불가하다.

 


<그림. 단일 트렌젝션 ID를 사용했을때 문제>

그래서 이러한 문제를 해결하기 위해서는 글로벌 트렌젝션 ID(gtxid)와, 로컬 트렌젝션 ID (ltxid)의 개념을 도입할 수 있다.

API 호출을 하나의 트렌젝션으로 정의하면 이를 글로벌 트렌젝션 gtx라고 하고, 개별 서버에 대한 호출을 로컬 트렌젝션 ltx라고 한다. 이렇게 하면 아래 그림과 같이 하나의 API호출은 gtxid로 모두 연결될 수 있고 각 서버로의 호출은 ltxid로 구분될 수 있다

※ 사실 이 개념은 2개 이상의 데이타 베이스를 통한 분산 트렌젝션을 관리하기 위한 개념으로, 글로벌 트렌젝션과 로컬 트렌젝션의 개념을 사용하는데, 그 개념을 차용한것이다.

 


<그림. 글로벌 트렌젝션과 로컬 트렌젝션 개념을 이용한 API 트렌젝션 추적 방법>


이런 글로벌 트렌젝션과 로컬 트렌젝션 개념을 API 게이트웨이와 연동하여 아키텍쳐를 설계하면 다음과 같은 모양이된다.

다음 그림을 보자.

 


<그림, gtxid,ltxid를 이용한 API 트렌젝션 추적 아키텍쳐>


API 클라이언트는 API를 호출한다. API 클라이언트는 트렌젝션 ID에 대한 개념이 없다.

API 게이트 웨이에서, HTTP 헤더를 체크해서 x-gtxid (글로벌 트렌젝션 ID)가 없으면 신규 API  호출로 판단하고 트렌젝션 ID를 생성해서 HTTP 헤더에 채워 넣는다. 로컬 트렌젝션 ID로 1로 세팅한다.

2번에서, API 서버 A가 호출을 받으면, 서버 A는 헤더를 체크하여 ltxid를 체크하고, ltxid를 하나 더 더해서, 로그 출력에 사용한다. 같은 gtxid를 이용해서 같은 API호출임을 알 수 있고, ltxid가 다르기 때문에 해당 API서버로는 다른 호출임을 구별할 수 있다.

이런식으로 서버B,C도 동일한 gtxid를 가지지만, 다른 ltxid를 갖게 된다.

각 API 서버는 이 gtxid와  ltxid로 로그를 출력하고, 중앙에서 로그를 수집해서 보면, 만약에 에러가 발생한 경우, 그 에러가 발생한 gtxid로 검색을 하면, 어떤 어떤 서버를 거쳐서 트렌젝션이 수행되었고 어떤 서버의 몇번째 호출에서 에러가 발생하였는지 쉽게 판별이 가능하다.

작은 팁중에 하나로, 자바로 API 서버를 개발할 경우 서블릿 필터를 넣어서, gtxid가 헤더로 들어오면 이 gtxid를 TheadLocal 변수에 저장하고, ltxid는 새로 생성해서 ThreadLocal 변수에 저장 해놓으면, 로그를 출력할때 ThreadLocal 변수에 있는 gtxid와 ltxid를 꺼내서 같이 출력하면 번거롭게 클래스의 메서드등으로 gtxid와 ltxid를 넘길 필요가 없다.  그리고 로그 수집은 SL4J나 Log4J와 같은 일반 로깅 프레임웍을 이용해서 gtxid와 ltxid 기반으로 로그를 출력하고 출력된 로그를 파일이 아니라 logstash를 이용해서 모아서, ElasticSearch에 저장하고, Kibana로 대쉬 보드를 만들면, 손쉽게 트렌젝션 ID기반으로 API 호출 로그 추적이 가능해진다.




MSA 아키텍쳐 구현을 위한 API 게이트웨이의 이해 #1

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


MSA(마이크로 서비스 아키텍쳐, 이하 MSA)와 함께 근래에 떠오르고 있는것이 API 게이트 웨이이다. API 게이트웨이는 API서버 앞단에서 모든 API 서버들의 엔드포인트를 단일화하여 묶어주고 API에 대한 인증과 인가 기능에서 부터 메세지에 따라서 여러 서버로 라우팅 하는 고급기능 까지 많은 기능을 담당할 수 있다.

API 게이트웨이의 시작은 MSA가 SOA(서비스 지향 아키텍쳐)에서 시작한것 처럼 ESB (Enterprise Service Bus)에서 부터 시작 되었다. 그래서 ESB의 대부분의 컨셉을 많이 승계했는데, ESB의 실패와 단점을 보완해서 만들어진 사상이 API 게이트웨이이다. ESB가 SOAP/XML 웹서비스 기반의 많은 기능을 가지는 구조였다면, API 게이트 웨이는 JSON/REST 기반에 최소한의 기능을 처리하는 경량화 서비스 이다. 그리고 ESB는 SOA의 사상에서 개념적으로 탄생한 솔루션이라면, API 게이트 웨이는 ESB의 실패와, MSA, REST 구현 사례를 통해서 필요에 의해서 탄생한 솔루션이기 때문에, 그 실용성이 차이가 난다.


MSA에 대한 개념은 http://bcho.tistory.com/948 를 참고하기 바라며, 이 글은 API 게이트웨이에 대한 바른 이해를 돕고, API 게이트웨이를 도입하고자 하는데 필요한 내용을 서술하고자 한다.


API 게이트웨이의 주요 기능


먼저 API 게이트웨이의 주요 기능에 대해서 알아보자


인증/인가에 관련된 기능


API 게이트웨이의 가장 기본적인 기능은 API 에 대한 인증과 인가 관련 기능이다. 인증은, API 를 호출하는 클라이언트에 대한 identity(신분)를 확인 해주는 기능이고, 인가는 클라이언트가 API를 호출할 수 있는 권한이 있는지를 확인해주는 기능이다. 

쉽게 이야기 하면 내가 페이스북 계정을 가지고 있는 사용자가 맞는지 , API 호출시 어느 권한 (일반사용자, 관리자 권한)까지 호출할 수 있는지를 판단하여 API 호출을 허가하는 기능이라고 볼 수 있다.


API 토큰 발급


인증 인가를 거칠때 마다 매번 사용자의 인가/인증 절차를 거치기는 불편하다. 사용자로 부터 매번 사용자 ID와 비밀 번호를 받기는 번거롭고, 그렇다고 사용자 ID와 비밀 번호를 저장해놓는 것은 해킹의 빌미를 제공한다.

그래서 보통 사용하는 방식이 토큰이라는 방식을 사용하는데, 사용자 인가가 끝나면, 사용자가 API를 호출할 수 있는 토큰을 발급해준다. API 서버는 이 토큰으로 사용자의 identity 와 권한을 확인한후, API 호출을 허가해준다.


API 게이트 웨이는 클라이언트를 인증한 후, 이러한 API 토큰을 생성 및 발급해주는 역할을 한다.


 

<그림. 일반적은 토큰 발급 절차>


토큰 발급을 위해서는 먼저 클라이언트를 인증해야 한다.


클라이언트를 인증하는 방법은 가장 간단하게 사용자의 id와 password를 넣는 방법에서 부터, 공인 인증서를 이용하는 방법, 지문이나 OTP (One time password) 등을 이용하는 방법등 다양한 방법이 있다. 각 보안 요건에 요구 되는 다양한 방식에 따라서 클라이언트를 인증한 후에, apitoken을 발급하게 된다.


이때, 클라이언트에 대한 인증은 직접적으로 API 게이트웨이가 하지 않고 뒷단에 있는 인증 서버가 이를 수행하는데, 간단하게는 내부 계정 관리를 위한 Active Directory, LDAP 또는 RDBMS등이 될 수 도 있으며, 외부 인증 서버로는 예를 들어서 온라인 게임 서비스에 가입할때, 페이스북 계정을 사용하는 경우, 온라인 게임 서버가 페이스북에 이 사용자의 인증을 요청하고, 페이스북이 인증을 해주면 온라인 게임서버가 apitoken을 발급해주는 흐름등을 들 수 있다.


그래서 API 게이트웨이의 중요한 기능중의 하나는 다양한 인증 서버와 연계가 가능한 것이 좋다.


이렇게 발급된 토큰을 API를 호출할 수 있는 권한 정보와 연관이 되는데, 이 권한 정보를 토큰 자체에 저장하느냐 또는 서버에 저장해놓느냐에 따라서 두 가지 종류로 나눌 수 있다.

토큰 자체가 이러한 정보를 갖는 형태를 클레임 기반의 토큰 (Claim based token)이라고 하는데, 근래에 유행하는 JWT (JSON Web Token)이나 SAML 토큰등이 이에 해당한다. 예를 들어 토큰 자체가 아래와 같은 정보를 가지고 있는 형태라고 생각하면 된다. 


{

“name”:”Terry”,

“role”:[“admmin”,”enduser”]

“org”:”petstore”

}

<그림. 클레임 기반의 토큰 예>

JWT가 이러한 형태의 토큰인데 JWT에 대한 자세한 설명은 http://bcho.tistory.com/999 와 http://bcho.tistory.com/1000 를 참고하기 바란다.

클레임 기반의 토큰이 아닌 경우, 이러한 클레임 정보를 서버에 저장해놓게 되는데, 클라이언트로는 unique한 string만을 리턴해주는 경우이다.

 


<그림. 서버에 토큰을 저장하는 경우>


이 서버 기반의 토큰이 현재 일반적으로 가장 많이 사용되는 형태인데, token에 연관되는 정보가 서버에 저장되기 때문에 안전하고, 많은 정보를 저장할 수 있으며, token에 대한 정보를 수정하기가 용이하다. 그러나 서버단에서 별도의 토큰 저장소를 유지해야 하기 때문에 구현 노력이 더 높게 든다. 토큰은 매 API 호출마다 정보를 가지고 와야 하기 때문에, DBMS와 같은 FILE IO 기반의 저장소 보다는 redis, memcached와 같이 메모리 기반의 고속 스토리지를 사용하는 것이 좋다.


클레임 기반의 토큰은 이러한 토큰 저장소가 필요 없다는 장점이 있어서 구현은 용이하지만, 토큰 자체에 클레임 정보가 들어가 있기 때문에, 토큰의 길이가 커지기 때문에 일정 양 이상의 정보를 담기가 어려우며, 한번 발급된 토큰은 변경이 어렵다. 예를 들어 role:admin으로 관리자 권한으로 발급된 토큰은 서버쪽에서 파기가 불가능하기 때문에 토큰 통제가 어렵다는 단점을 가지고 있다. 그래서, 클레임 기반의 토큰을 사용할때는 토큰의 유효기간을 둬서 반드시 강제적으로 토큰을 주기적으로 재발급 받도록 하는 것이 좋다.


엔드포인트별 API 호출 인증


Apitoken이 발급되었으면, 클라이언트는 이 apitoken을 이용하여 API를 호출하는데, 이 때 API 게이트웨이는 apitoken을 검증함으로써 API 호출을 승인할지 여부를 결정한다.

서버에 토큰 정보가 저장되는 형태의 경우 매 API 호출 마다 해당 apitoken을 가지고 연관 정보를 토큰 저장소로 부터 읽어와서 비교한후, 그 정보를 기반으로 API 호출 가능 여부를 결정한다.

 




<그림. Apitoken을 이용한 API 호출 인증>


클레임 기반의 토큰의 경우에는 이러한 작업이 없이 그냥 API 게이트 웨이에서 apitoken을 까보고, 그 안에 있는 내용을 가지고 API 호출 가능 여부를 결정한다.


이렇게 api token으로 인증을 하는 방법이 일반적인 방법이지만, 서버대 서버간의 통신은 내부 서버의 경우 별도의 인증 없이 API 를 제공하는 경우도 있고, 외부 서버와의 통신은 특정 ip 주소와 통신을 허용 하거나 높은 보안을 요구하는 경우 양방향 SSL등의 인증 방식을 사용함으로써 apitoken없이도 API 호출을 인증하는 방법도 있다..

이렇게 각각의 클라이언트나 서비스 별로 제공되는 엔드포인트에 대해서 API 인증 방식이 다르기 때문에, API 게이트웨이에서는 각 엔드 포인트 별로 다양한 형태의 인증 방식을 제공해야 한다. API 게이트를 이용하여 다양한 엔드포인트를 통해 서비스를 제공하는 방법은 뒤에서 다시 설명하도록 한다.


엔드포인트별 API 요청 인가


인증(Authentication)과 인가(Authorization)은 다른 의미를 갖는데, API를 호출 하는 것이 Terry가 맞다는 것을  확인 해주는 것을 인증이라고 한다면, 이 Terry가 이 API를 호출할 수 있는 권한이 있는 것을 확인해주는 것이 인가(Authorization)이다. 쉽게 생각하면, 일반 사용자용 API와 관리자용 API를 생각하면 이해가 쉽다.


이렇게 권한을 제어하는 방식은 여러가지가 있는데, 각 개별 권한을 토큰에 부여 하는 방식과 역할(ROLE) 기반으로  권한을 부여하는 방식이 대표적이다.


개별 권한을 토큰에 부여 하는 방식은 다양한 권한 정책을 세밀하게 관리할 수 있다는 장점을 가지고 있다.

 


<그림. 토큰에 역할을 부여하는 방식>


토큰에 제한적으로 권한을 부여할 수 있다는 장점을 가지고 있는데, 페이스북이 이런 형태의 권한 통제 모델을 사용한다. 

https://developers.facebook.com/docs/facebook-login/permissions/v2.2?locale=ko_KR

에 보면 api 토큰에 연동할 수 있는 권한 리스트들이 있는데, 페이스북의 써드파티 애플리케이션을 보면, 페이스북의 API의 권한을 일부 요청 하는 형태로 토큰에 권한을 연결한다.


그렇지만,이 방식의 경우에는 권한의 종류가 많을 경우, 관리가 어려워 지고 복잡해지기 때문에, 일반적으로 역할(ROLE)기반으로 권한을 관리 하는 방식을 많이 사용한다.


직접 권한을 토큰에 연결하는 것이 아니라, 역할이라는 개념을 두고, 역할별로 권한을 연결한 다음에, 이 역할을 토큰에 부여하는 개념이다 쉽게 이야기 하면, 관리자용 기능과 일반 사용자용 기능을 분리한 다음에, 관리자나 일반 사용자와 같은 역할(ROLE)을 토큰에 부여하는 방식이다. 이를 RBAC (Role Based Access Control)이라고 한다.


이 RBAC 기반으로 하면, 통제 해야 하는 권한의 숫자가 줄어들기 때문에, 다음과 같이 엔드포인트를 나눠서 권한 접근 제어가 가능하다. (예를 들어 총 권한이 100개가 있다고 했을때, 이를 관리자용 기능과 일반 사용자용 기능으로 나누어 버리면, 관리해야 하는 두개의 권한 집합을 나뉘어 진다.) 


이런 경우 관리자용 API 엔드포인트(/service/admin), 일반 사용자용 API 엔드포인트(/service/users) 두 개로 나눈 다음에, apitoken에 권한을 admin,user 두가지 종류로 정의한 후에, /service/admin 엔드포인트에서는 api token이 admin 권한이 있을 경우에만, 호출을 허용하도록 하면 된다. 


 

<그림. 역할(ROLE)별로 엔드포인트를 나눠서 권한 인가를 하는 구조>

API 라우팅


API 게이트웨이에서 다음으로 유용한 기능중의 하나가 API 호출을 라우팅 하는 기능이다. 같은 API라도 사용하는 서비스나 클라이언트에 따라서 다른 엔드포인트를 이용해서 서비스를 제공하거나, 데이타 센터가 여러개일때, 데이타 센터간의 라우팅등을 지원하는 기능이다. 주요 기능들을 보면 다음과 같다.


백엔드 API 서버로의 로드 밸런싱


가장 기본적인 기능으로는 로드밸런서 기능이다. API 게이트 웨이 뒷단에 다수의 API 서버가 있다고 할때, 여러개의 API 서버로 부하를 분산하는 기능이 필요하다.

 


<그림. API 게이트 웨이를 통한 API 서버로의 로드 밸런싱>


단순하게 Round Robin 방식으로 부하를 분산하는 기능뿐만 아니라, 각 서버 하드웨어에 따라 부하를 가중치를 줘서 분산하는 기능등을 고려해볼 수 있겠고, 무엇보다 중요한 기능은 API 서버가 장애가 났을때 이를 감지해서 로드 밸런싱 리스트에서 빼고, 복구 되었을때 다시 로드 밸런싱 기능에 넣는 기능들이 필요하다.


단순하게, HA Proxy와 같은 L4의 기능처럼, 뒷단의 서버가 살아 있으면 부하를 보내고 죽었으면 부하를 안보내는 기능에서 부터, 고급 기능으로는 API 서버가 Hang up (멈춤)에 걸렸을 때 이를 인지해서 부하를 안보내는 기능등을 고려해볼 수 있다. 이러한 고급 기능은 API 서버의 애플리케이션 상태를 인지해야 하기 때문에 단순히 IP 포트가 살아 있음을 가지고 판단 하는 것이 아니라 쓰레드 수, 응답 시간등으로  서버의 장애 상태를 판단해야 한다.  


서비스 및 클라이언트 별 엔드포인트 제공


또 다른 유용한 기능중의 하나는, 같은 API를 여러개의 엔드포인트를 통해서 서비스를 제공할 수있다는 점인데, 하나의 시스템이 다양한 서비스나, 다양한 클라이언트등으로 서비스를 제공할때, 각각 다른 서비스 별 또는 클라이언트 별로 다른 엔드포인트를 제공할 수 있다.

예를 들어서 IOT 플랫폼 서비스가 있다고 하자. 이 플랫폼은 REST API를 제공하는데, 이를 통해서 센서로 부터 데이타를 수집해서 분석하는 시스템이라고 가정하자.

이 시스템은 선박용 서비스, 비행기용 서비스, 차량용 서비스를 지원한다고 가정하자.

각 서비스별로 API의 특성이나 노출되는 API가 다소 다를 수 있는데, 각 서비스 별로

  • 선박용 /ships/
  • 비행기용 /airplanes/
  • 차량용 /cars/

라는 식으로 각각의 다른 엔드 포인트를 제공할 수 있다.

그리고, 이 서비스에서는 센서로 부터 데이타를 수집하는 시나리오와, 관리자가 웹을 통해서 시스템을 관리하기 위한 API가 있다고 가정하면, 앞의 API는 다음과 같이 클라이언트의 종류에 따라서 분리 될 수 있다.


  • 선박 센서용 /ships/sensors/, 선박 관리자 웹 /ships/admin
  • 비행기 센서용 /airplanes/sensors/, 비행기 관리자용 /airplanes/admin
  • 차량 센서용 /cars/sensors, 차량 관리자용 /cars/admin

그리고 각각의 엔드포인트 별로 노출(expose)하는 API를 다르게 할 수 있다.

 


< 그림. API를 엔드포인트 별로 다르게 노출>


API 게이트 웨이는 API 서버가 공통적인 API를 가지더라도, 각 서비스나 클라이언트 타입에 따라서 각각 다른 API 를 선별적으로 서비스 할 수 있도록 해준다.


※ 실제로 멀티 서비스를 제공하는 플랫폼형태의 경우에는 이 기능이 매우 유용하다.특히 같은 API라도 클라이언트의 종류에 따라서 인증 방식이 다를 수 있고 보안 메커니즘이 다를 수 있다.


메세지 또는 헤더기반 라우팅


라우팅에서 유용한 기능중의 하나는 메세지 내용을 기반으로 하는 라우팅이다. 예를 들어 그림과같이 HTTP 헤더에 country code가 있을 경우, country code에 따라서 유럽에 있는 API를 호출하거나 또는 미국에 있는 API 서버를 호출할 수 있도록 Routing을 할 수 있다.

 


<그림. 메세지 기반의 글로벌 라우팅 예시>


특히 글로벌 단위로 배포되는 시스템인 경우 각 데이타 센터간에 메세지를 라우팅 시키는데 유용하게 사용할 수 있다. 위의 예에서 처럼, 특정 데이타 센터로 조건에 따라 라우팅을 할 수 도 있고, 또는 중앙 집중형 시스템의 경우, 각 지역에 API 게이트 웨이를 두고, 클라이언트는 가까운 API  게이트 웨이를 호출하지만, 중앙 데이타 센터에만 있는 API 서버의 경우 중앙 데이타 센터로 호출을 라우팅 할 수 있다.


데이타 복제가 필요할 경우, 미국에 있는 API 게이트웨이로 호출하면 API 게이트 웨이가 미국 API서버와, 유럽 API 서버를 동시에 호출해서, 업데이트성 트렌젝션을 모든 데이타 센터에 복제함으로써 API를 통한 데이타 복제가 가능해진다.

라우팅에 있어서 고려해야할 사항은 먼저 메세지에 대한 라우팅인데, REST API를 기준으로 설명하면, REST API는 HTTP URL,HTTP Header,HTTP Body 3가지로 구분이 된다.


메세지를 기반으로 라우팅을 하기 위해서는 API 게이트 웨이가 이 메세지를 파싱해야 한다.

예를 들어 country_code가 HTTP Body에 JSON으로 다음과 같이 들어가 있다고 가정하자


{

“country_code”:”US”

  :

}


이 경우 이 API 호출에 대해서 라우팅 정보를 추출하기 위해서 매번 HTTP Body에 있는 JSON을 API 게이트웨이가 파싱해서 열어봐야 한다. 이는 빠르게 메세지가 통과해야 하는 API 게이트웨이의 역할에 많은 부담을 준다. 만약에 이러한 라우팅 정보를 HTTP Header로 옮긴다면, HTTP Body는 파싱하지 않고, Header만 파싱한후, Body 정보는 라우팅되는 서버로 그냥 포워딩만 해도 된다.


그래서 메세지 기반의 라우팅을 사용할 때는 이러한 파싱에 대한 오버헤드를 잘 고려하고, 가능하면, HTTP URL이나 HTTP Header에 라우팅 필드를 넣는 것이 좋다. 


부득이하게, HTTP Body에 있는 내용으로 라우팅을 해야 하는 경우에는 호출 빈도가 적은 API인 경우 API 게이트웨이에서 담당하고, 다른 경우에는 별도의 게이트웨이 인스턴스(프로세스)로 분리를 하거나 뒷단의 API서버가 라우팅을 하도록 하는 것도 하나의 방안이 된다.


공통 로직 처리


API 게이트웨이는 특성상 모든 API 서버 앞쪽에 위치 하기 때문에, 모든 API 호출이 이 API 게이트를 거쳐간다. 그렇기 때문에, 모든 API 가 공통적으로 처리해야 하는 공통 기능이 필요할 경우 이러한 공통 기능을 API 게이트웨이로 옮기게 되면 별도로 API 서버에서 이러한 기능을 개발할 필요 없이 비지니스 로직 자체 구현에만 집중할 수 있게 된다.

아래 그림은 각 API 서버에서 인증과, 로깅에 관련된 로직을 API 게이트웨이로 이전한 구조이다.

API 로깅이나 인증은 전체 시스템에 대해 공통된 기능으로, 공통 계층에서 처리하게 되면 개발 중복을 줄일 수 있는 장점뿐만 아니라, 표준 준수가 더 쉽다는 장점을 가지고 있다. 

 


<그림 API 게이트웨이를 이용하여 공통 로직을 API 서버에서 API 게이트웨이로 이전한 구조>


메디에이션 기능 (Mediation)


메디에이션이란, 한글로 “중재”또는 “조정” 이라는 의미를 갖는데, API서버에서 제공되는 API가 클라이언트가 원하는 API 형태와 다를때, API 게이트웨이가 이를 변경해주는 기능을 이야기 한다. 구체적인 기능을 보자


메세지 포맷 변환 (Message format transformation)


메세지 포맷을 변환하는 기능이란, JSON으로 된 요청(Request) 메세지가 들어왔을때, 이를 API 서버로 보낼때 변환 해서 보내거나, 또는 API 서버에서 생성된 응답을 클라이언트에 리턴할때 변경해서 보내는 기능을 의미한다.


예를 들어보자, 아래와 같이 terry의 연봉(salary) 정보를 구하는 API가 필요하다고 하자. 그런데, 시스템에는 연봉 정보만 주는 API는 없고, 전체 사용자 정보를 리턴하는 API만 있는 상황이다.


이런 경우, API 게이트 웨이를 통해서 /users/salary라는 새로운 API를 제공하고, 이를 기존에 전체 사용자 정보를 주는 /users/details라는 API로 라우팅 한다. /users/details에서 사용자 정보를 뽑았을때, 클라이언트에게 응답을 줄때는 API 게이트웨이에서 아래 그림과 같이 name과 salary 정보만 뽑아서 리턴하도록 한다.

 


<그림. 메세지 포맷 변환의 예시>


일단 간단한 기능으로 구현이 가능하기 때문에 서술은 해놨지만, 그다지 권장하고 싶지 않은 기능이다. 메세지 포맷이 변환이 된다면, 차라리 필요한 포맷에 맞는 API를 따로 뽑아 내는 것이 났지 않나 싶다.


프로토콜 변환 


다양한 서비스나 클라이언트를 지원하게 되면, 클라이언트나 서비스별로 다른 통신 프로토콜을 사용해야 하는 경우가 있다. 웹에서는 JSON기반의 REST가 많이 사용되지만, 배나 비행기에 사용되는 센서들의 경우에는 REST도 무겁기 때문에 바이너리 기반의 경량 프토토콜을 사용하거나, 또는 예전 엔터프라이즈 시스템의 경우 XML 기반의 웹서비스를 이용할 수 도 있다.


이렇게 다양한 타입의 프로토콜을 지원하기 위해서, 각 서비스들이 새롭게 구현을 하는 것이 아니라 API 게이트웨이 계층에서 프로토콜 변환을 통하여, 같은 API를 다른 프로토콜로 서비스 할 수 있도록 할 수 있다.

 


<그림. API 게이트 웨이를 통한 프로토콜 변환>


실제로 유용한 기능인데, 내부 API는 REST가 아니라 페이스북 Thrift나 구글의 Protocol Buffer로 구현을 하고, 외부에 제공하는 API는 API 게이트 웨이단에서 REST 로 변경해서 서비스 하는 구조를 이용하면, 내부 API 성능을 올리고, 외부로는 REST API로 서비스 함으로써 범용성을 확보할 수 있다. (실제 사례가 있었다.)


또한 근래에 M2M이나 IOT (Internet of things)와 같은 개념이 활성화 되면서, HTTP REST 뿐 아니라 기존의 센서에서 통신에 사용되는 다양한 프로토콜을 지원하여 백엔드 API 서버의 프로토콜로 맞춰줘야 하는 필요가 점점 증대되고 있다.


메세지 호출 패턴 변환 (Message Exchange Pattern : MEP)


메세지 호출 패턴, 보통 MEP(Message Exchange Pattern)라고 하는데, 동기,비동기 호출과 같은 API를 호출하는 메세지 패턴을 정의한다.

API 게이트웨이의 좋은 기능중의 하나가 이 MEP를 변경할 수 있다는 건데, 쉽게는 Async API호출을 Sync 호출로 바꿔 준다거나, 하나의 API 호출을 여러 데이타 센터로 복제 해준다거나 하는 형태의 메세징 패턴을 변화 시킬 수 있다.

 


<그림. 비동기 호출을 API게이트웨이를 통해서, 동기 호출로 변경한 구조>


위의 그림의 예제는 로그를 수집하는 시스템에 대한 구조이다.뒷단의 로그저장 API 서버가 대용량 트래픽에 대한 대응 능력이 없을때, API 게이트 웨이에서 큐를 이용해서 API 요청을 받고 (1), 바로 클라이언트에 ACK를 준후에, 메세지큐 연동을 이용하여 메세지를 저장한후, 로그 저장 API 서버의 성능에 맞게 흘려주는 방식이다. 클라이언트 입장에서는 동기 호출이지만 실제 메세지 흐름은 큐를 이용한 비동기 구조로 변경되었다.


어그레게이션 (aggregation)


SOA에서는 Orchestration(오케스트레이션)이라고 불렀는데, 여러개의 API를 묶어서 하나의 API로 만드는 작업을 이야기 한다. 예를 들어서, 계좌 이체를 한다고 했을때,


A은행에서 잔액 확인

A은행에서 인출

B은행으로 입금


하는 3개의 API 호출을 하나의 API 인 POST transfer(인출계좌,입급계좌,금액)으로 구현한다고 하자.이를 API 게이트웨이에서 구현 하면 다음과 같은 형태로 구현할 수 있다.

 


<그림. API 게이트 웨이를 이용한 API Aggregation>


대부분의 API 게이트 웨이 제품들은 이러한 aggregation을 할 수 있는 일종의 워크플로우 엔진과 유사한 기능들을 가지고 있다.


 


<그림. Apigee 제품의 워크플로우 저작도구 화면>


이러한 aggregation 기능이 언뜻 보면 좋아보이지만, 하나의 플로우에서, 여러 API를 호출해야 하고, 비지니스 로직을 수행하면서 실제로 API 메세지 BODY까지 파싱해야 하기 때문에, API 게이트 웨이 입장에서는 부하가 매우 크다. 


MSA 의 전신인 SOA에서 API 게이트웨이와 유사한 역할을 했던 ESB역시 이러한 aggregation (ESB에서는 보통 오케스트레이셔이라고 함)을 남발해서, ESB의 성능이 떨어져서 시스템 개발이 실패하는 아키텍쳐를 많이 봤다.

그래서 본인의 경우에는 이러한 무거운 aggregation 로직은 별도의 Mediator API 서버라는 계층을 만들어서, API 게이트웨이 밖에서 따로 하는 방법을 권장한다.


아래 그림과 같이 여러 API를 조합하는 목적의 API 서버를 별도로 둬서, 이러한 기능을 API 게이트웨이에서 제거한다.

 


<그림. API aggregation을 API 게이트웨이에서 Mediation API 서버로 분리한 구조>


aggregation 로직을 API 게이트웨이 안에 넣으면 확실하게  게이트웨이가 받는 부하량은 올라간다. 설치형 API 게이트웨이의 경우, 이는 추가적인 하드웨어 박스를 더 구매하고, 상용 API 게이트웨이의 경우 라이센스를 더 구매해야 한다는 것을 의미하기 때문에, Mediation API 서버 계층을 사용하는 것을 권장한다.


클라우드형 API 게이트웨이의 경우, 호출 수로 과금을 하기 때문에 aggregation 로직을 API 게이트웨이에 넣는 방안을 고려해볼 수 있으나, aggregation 로직이 게이트웨이 안에 있으면 디버깅이나 테스팅이 쉽지 않기 때문에, 이를 적절히 고민한 후 판단해서 aggregation 로직의 위치를 결정해야 한다.


로깅 및 미터링


API 게이트웨이의 비기능적인 요건으로 중요한 기능이 로깅과 미터링이다. 


API 호출 로깅


앞서 공통 로직 처리 부분에서도 언급하였지만, API 호출시 API 게이트웨이는 공통적으로 호출되는 부분인 만큼 모든 로그를 중간에서 수집하기가 가장좋다.


근래의 애플리케이션 아키텍쳐가 클라이언트와 서버간의 통신이 모두 API를 기반하는 형태로 변경이되어감에 따라 API 호출 패턴을 분석하면 사용자의 사용 패턴을 분석해낼 수 있기 때문에, 빅데이타 영역과 연계하여 API 호출 로그는 아주 중요한 자산으로 다루어지고 있다.


또한 API 호출 로그는 차후 문제가 발생하였을때, 문제를 추적하기 위한 중요한 자료로 사용된다. (Audit: ‘감사’의 목적) 그래서, API 로그 수집은 단순 분석 목적뿐 아니라, 향후 감사 목적용으로도 저장되어야 한다.


근래에 출시되고 서비스되는 클라우드형 API 게이트웨이의 경우에는 특히나 이 API에 대한 로그 분석 기능을 강화해서 출시되고 있다.

 


<그림. Apigee.com의 API 모니터링>


API 미터링 & 차징 (Metering & Charing)


API 미터링과 차징은 유료 API 서비스를 위한 기능으로,  미터링은 과금을 위한 API 호출 횟수,클라이언트 IP, API 종류,IN/OUT 용량등을 측정 기록하는 서비스이고,

차징은 미터링이 된 자료를 기반으로 하여, API 서비스 사용 금액을 금액 정책에 따라서 계산 해내는 서비스이다. 

대부분의 SNS 오픈 API 서비스는 무료인 경우가 많지만, 구글 API 의 경우에도, 특정 호출 횟수(/일)을 넘어가면 과금등을 하도록 되어 있다.


QoS 조정 (Quality of service)


마지막으로 QoS 조정 기능이란, API 서비스를 클라이언트 대상에 따라서 서비스 레벨을 조정하는 기능이다.

유료 서비스가 있는  API 서비스라고 가정할때, 무료 사용자의 경우 1일 1000건으로 호출횟수를 제한 한다거나, 전송 용량이나, 네트워크 대역폭을 유료/무료 사용자에 따라 다르게 적용하는 것과 같은 기능을 QoS 기능이라고 한다.

유료 서비스인 경우만 아니라, 플랫폼 차원에서 다양한 클라이언트나 다양한 서비스로 API 를 제공하는 경우, 각 클라이언트나 서비스에 따라서 이 QoS를 조정하는 기능은 유용하게 사용될 수 있다. 특정 서비스나 클라이언트가 폭주하여 API를 과도하게 사용하여 다른 서비스들이 API를 사용할 수 없게 한다던가 그런 문제를 미연에 예방할 수 있다.


결론


지금까지 간단하게나마 API 게이트웨이의 대략적인 기능에 대해서 알아보았다. 다음에는 API 게이트웨이 기반 아키텍쳐를 확장하는 방법과 API 게이트웨이의 안티패턴과 설계 방법론 등에 대해서 소개하도록 한다.


참고

API 플랫폼의 이해 http://bcho.tistory.com/808

대용량 분산 시스템을 위한 마이크로서비스 아키텍쳐 http://bcho.tistory.com/948