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


Archive»


 

'MSA'에 해당되는 글 25

  1. 2018.11.25 Istio #4 - Istio 설치와 BookInfo 예제 (2)
  2. 2018.11.23 Istio #3- Istio에 대한 소개
  3. 2018.11.20 Istio #1 - 마이크로 서비스와 서비스 매쉬
  4. 2018.11.19 EAI,ESB,API 게이트웨이,서비스 매쉬 - 서비스 통합의 역사
  5. 2018.10.20 마이크로 서비스 아키텍쳐와 컨테이너 환경
  6. 2018.09.27 쿠버네티스 개념 설명과 에코 시스템 (Spinnaker, Istio, KNative 설명) (3)
  7. 2018.06.17 쿠버네티스 #6 - 실제 서비스 배포해보기 (2)
  8. 2018.05.19 쿠버네티스 #1 - 소개 (5)
  9. 2018.05.08 MSA를 위한 L7 Proxy - EnvoyProxy #1
  10. 2018.04.18 MSA에서 Service discovery 패턴
  11. 2018.04.15 Circuit breaker 패턴을 이용한 장애에 강한 MSA 서비스 구현하기 #2 - Spring에서 Circuit breaker 구현
  12. 2018.04.04 Circuit breaker 패턴을 이용한 장애에 강한 MSA 서비스 구현하기 #1 - Circuit breaker와 넷플릭스 Hystrix
  13. 2018.03.26 Zipkin을 이용한 MSA 환경에서 분산 트렌젝션의 추적 #3 -Stackdriver를 zipkin으로 사용하기
  14. 2018.03.25 Zipkin을 이용한 MSA 환경에서 분산 트렌젝션의 추적 #2 - Spring과 Zipkin을 이용한 추적
  15. 2018.03.24 Zipkin을 이용한 MSA 환경에서 분산 트렌젝션의 추적 #1
  16. 2016.06.09 구글 클라우드 로드밸런서를 이용한 부하 분산 (3)
  17. 2016.06.02 다양한 라우팅 기능을 제공하는 구글의 클라우드 로드 밸런서 (2)
  18. 2016.02.25 사람이 최대한 관계를 유지할 수 있는 숫자는 150명이다. (Dunbar's law)
  19. 2016.02.25 MSA의 중복 개발에 대한 단상 (3)
  20. 2015.03.18 마이크로서비스 아키텍쳐(MSA)는 선택이 아니라 필수다
 

Istio #4 - 설치 및 BookInfo 예제

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

Istio 설치

그러면 직접 Istio 를 설치해보자, 설치 환경은 구글 클라우드의 쿠버네티스 환경을 사용한다. (쿠버네티스는 오픈소스이고, 대부분의 클라우드에서 지원하기 때문에 설치 방법은 크게 다르지 않다.)


참고 https://docs.google.com/document/d/1S5EaVR3Xq011JHcJQ0G84hkasboVUNUrcQzBlq9mJ14/edit

쿠버네티스 클러스터 생성

콘솔에서 아래 그림과 같이 istio 라는 이름으로 쿠버네티스 클러스터를 생성한다. 테스트용이기 때문에, 한존에 클러스터를 생성하고, 전체 노드는 3개 각 노드는 4 CPU/15G 메모리로 생성하였다.



다음 작업은 구글 클라우드 콘솔에서 Cloud Shell내에서 진행한다.

커맨드 라인에서 작업을 할것이기 때문에, gCloud SDK를 설치(https://cloud.google.com/sdk/gcloud/) 한후에,

%gcloud auth login

gcloud 명령어에 사용자 로그인을 한다.


그리고 작업을 편리하게 하기 위해서 아래와 같이 환경 변수를 설정한다. 쿠버네티스 클러스터를 생성한 리전과 존을 환경 변수에 아래와 같이 설정한다. 예제에서는 asia-southeast1 리전에 asia-southeast1-c 존에 생성하였다. 그리고 마지막으로 생성한 쿠버네티스 이름을 환경 변수로 설정한다. 예제에서 생성한 클러스터명은 istio이다.

export GCP_REGION=asia-southeast1
export GCP_ZONE=asia-southeast1-c
export GCP_PROJECT_ID=$(gcloud info --format='value(config.project)')
export K8S_CLUSTER_NAME=istio

다음 kubectl 명령어를 사용하기 위해서, 아래과 같이 gcloud 명령어를 이용하여 Credential을 세팅한다

% gcloud container clusters get-credentials $K8S_CLUSTER_NAME \
   --zone $GCP_ZONE \
   --project $GCP_PROJECT_ID

Credential 설정이 제대로 되었는지

% kubectl get pod -n kube-system

명령어를 실행하여, 쿠버네티스 시스템 관련 Pod 목록이 잘 나오는지 확인한다.

Istio 설치

쿠버네티스 클러스터가 준비되었으면, 이제 Istio를 설치한다.

Helm 설치

Istio는 Helm 패키지 매니져를 통해서 설치 한다.

% curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
% chmod 700 get_helm.sh
% ./get_helm.sh

Istio 다운로드

Istio 를 다운로드 받는다. 아래는 1.0.4 버전을 다운 받는 스크립트이다.

% cd ~

% curl -L https://git.io/getLatestIstio | sh -

% cd istio-1.0.4

% export PATH=$PWD/bin:$PATH

Helm 초기화

Istio를 설치하기 위해서 Helm용 서비스 어카운트를 생성하고, Helm을 초기화 한다.

% kubectl create -f install/kubernetes/helm/helm-service-account.yaml

% helm init --service-account tiller

Istio 설치

다음 명령어를 이용하여 Istio를 설치한다. 설치시 모니터링을 위해서 모니터링 도구인 kiali,servicegraph 그리고 grafana 설치 옵션을 설정하여 아래와 같이 추가 설치 한다.

% helm install install/kubernetes/helm/istio \

--name istio \

--namespace istio-system \

--set tracing.enabled=true \

--set global.mtls.enabled=true \

--set grafana.enabled=true \

--set kiali.enabled=true \

--set servicegraph.enabled=true


설치가 제대로 되었는지 kubectl get pod명령을 이용하여, istio 네임스페이스의 Pod 목록을 확인해보자

% kubectl get pod -n istio-system




BookInfo 샘플 애플리케이션 설치

Istio 설치가 끝났으면, 사용법을 알아보기 위해서 간단한 예제 애플리케이션을 설치해보자, Istio에는 BookInfo (https://istio.io/docs/examples/bookinfo/)  라는 샘플 애플리케이션이 있다.

BookInfo 애플리케이션의 구조

아래 그림과 같이 productpage 서비스 안에, 책의 상세 정보를 보여주는 details 서비스와 책에 대한 리뷰를 보여주는 reviews 서비스로 구성이 되어 있다.  


시스템의 구조는 아래와 같은데, 파이썬으로 개발된 productpage 서비스가, 자바로 개발된 review 서비스과 루비로 개발된 details 서비스를 호출하는 구조이며, review 서비스는 v1~v3 버전까지 배포가 되어 있다. Review 서비스 v2~v3는 책의 평가 (별점)를 보여주는  Rating 서비스를 호출하는 구조이다



< 그림 Book Info 마이크로 서비스 구조 >

출처 : https://istio.io/docs/examples/bookinfo/

BookInfo 서비스 설치

Istio의 sidecar injection 활성화

Bookinfo 서비스를 설치하기 전에, Istio의 sidecar injection 기능을 활성화 시켜야 한다.

앞에서도 설명하였듯이 Istio는 Pod에 envoy 를 sidecar 패턴으로 삽입하여, 트래픽을 컨트롤 하는 구조이 다. Istio는 이 sidecar를 Pod 생성시 자동으로 주입 (inject)하는 기능이 있는데, 이 기능을 활성화 하기 위해서는 쿠버네티스의 해당 네임스페이스에 istio-injection=enabled 라는 라벨을 추가해야 한다.

다음 명령어를 이용해서 default 네임 스페이스에 istio-injection=enabled 라벨을 추가 한다.

% kubectl label namespace default istio-injection=enabled


라벨이 추가되었으면

% kubectl get ns --show-labels

를 이용하여 라벨이 제대로 적용이 되었는지 확인한다.


Bookinfo 애플리케이션 배포

Bookinfo 애플리케이션의 쿠버네티스 배포 스크립트는 samples/bookinfo 디렉토리에 들어있다. 아래 명령어를 실행해서 Bookinfo 앺ㄹ리케이션을 배포하자.

% kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml


배포를 완료한 후 kubectl get pod 명령어를 실행해보면 다음과 같이 productpage, detail,rating 서비스가 배포되고, reviews 서비스는 v1~v3까지 배포된것을 확인할 수 있다.



Kubectl get svc 를 이용해서 배포되어 있는 서비스를 확인하자



Prodcutpcage,rating,reviews,details 서비스가 배포되어 있는데, 모두 ClusterIP 타입으로 배포가 되어 있기 때문에 외부에서는 접근이 불가능하다.


Istio gateway 설정

이 서비스를 외부로 노출 시키는데, 쿠버네티스의 Ingress나 Service는 사용하지 않고, Istio의 Gateway를 이용한다.

Istio의 Gateway는 쿠버네티스의 커스텀 리소스 타입으로, Istio로 들어오는 트래픽을 받아주는 엔드포인트 역할을 한다. 여러 방법으로 구현할 수 있으나, Istio에서는 디폴트로 배포되는 Gateway는 Pod 형식으로 배포되어 Load Balancer 타입의 서비스로 서비스 된다.


먼저 Istio Gateway를 등록한후에, Gateway를 통해 서비스할 호스트를 Virtual Service로 등록한다.


아래는 bookinfo에 대한 Gateway를 등록하는 Yaml 파일이다.


apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

 name: bookinfo-gateway

spec:

 selector:

   istio: ingressgateway # use istio default controller

 servers:

 - port:

     number: 80

     name: http

     protocol: HTTP

   hosts:

   - "*"


selector를 이용해서 gateway 타입을 istio에서 디폴트로 제공하는 Gateway를 사용하였다. 그리고, HTTP프로토콜을 80 포트에서 받도록 하였다.

다음에는 이 Gateway를 통해서 트래픽을 받을 서비스를 Virtual Service로 등록해야 하는데, 그 구조는 다음과 같다.


apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: bookinfo

spec:

 hosts:

 - "*"

 gateways:

 - bookinfo-gateway

 http:

 - match:

   - uri:

       exact: /productpage

   - uri:

       exact: /login

   - uri:

       exact: /logout

   - uri:

       prefix: /api/v1/products

   route:

   - destination:

       host: productpage

       port:

         number: 9080


spec에서 gateways 부분에 앞에서 정의한 bookinfo-gateway를 사용하도록 한다. 이렇게 하면 앞에서 만든 Gateway로 들어오는 트래픽은 이 Virtual Servivce로 들어와서 서비스 디는데, 여기서 라우팅 룰을 정의 한다 라우팅룰은 URL에 때해서 어느 서비스로 라우팅할 지를 정하는데 /productpage,/login,/lougout,/api/v1/products URL은 productpage:9080 으로 포워딩해서 서비스를 제공한다.


Gateway와 Virtual service 배포에 앞서서, Istio에 미리 설치되어 있는 gateway를 살펴보면, Istio default gateway는 pod로 배포되어 있는데, istio=ingressgateway 라는 라벨이 적용되어 있다. 확인을 위해서 kubectl get 명령을 이용해서 확인해보면 다음과 같다.

%kubectl get pod -n istio-system -l istio=ingressgateway



이 pod들은 istio-ingressgateway라는 이름으로 istio-system 네임스페이스에 배포되어 있다. kubectl get svc로 확인해보면 다음과 같다.

%kubectl get svc istio-ingressgateway -n istio-system --show-labels



그러면 bookinfo를 istio gateway에 등록해서 외부로 서비스를 제공해보자

% istioctl create -f samples/bookinfo/networking/bookinfo-gateway.yaml


게이트 웨이 배포가 끝나면, 앞에서 조회한 Istio gateway service의 IP (여기서는 35.197.159.13)에 접속해서 확인해보자

브라우져를 열고 http://35.197.159.13/productpage 로 접속해보면 아래와 같이 정상적으로 서비스에 접속할 수 있다.



모니터링 툴

서비스 설치가 끝났으면 간단한 테스트와 함께 모니터링 툴을 이용하여 서비스를 살펴보자

Istio를 설치하면 Prometheus, Grafana, Kiali,Jaeger 등의 모니터링 도구가 기본적으로 인스톨 되어 있다. 각각의 도구를 이용해서 지표들을 모니터링 해보자

Grafana를 이용한 서비스별 지표 모니터링

Grafana를 이용해서는 각 서비스들의 지표를 상세하게 모니터링할 수 있다.

먼저 아래 스크립트를 사용해서 간단하게 부하를 주자. 아래 스크립트는 curl 명령을 반복적으로 호출하여 http://35.197.159.13/productpage 페이지를 불러서 부하를 주는 스크립이다.


for i in {1..100}; do

curl -o /dev/null -s -w "%{http_code}" http://35.197.159.13/productpage

done


다음 Grafana 웹 콘솔에 접근해야 하는데, Grafana는 외부 서비스로 노출이 안되도록 설정이 되어 있기 때문에 kubectl을 이용해서 Grafana 콘솔에 트래픽을 포워딩 하도록 하자. Grafana는 3000번 포트에서 돌고 있기 때문에, localhost:3000 → Grafana Pod의 3000 번 포트로 트래픽을 포워딩 하도록 설정하자


kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000:3000 &


다음 localhost:3000 번으로 접속해보면 다음과 같은 화면을 볼 수 있다.

각 서비스 productpage,review,rating,detail 페이지의 응답시간과 OPS (Operation Per Sec : 초당 처리량)을 볼 수 있다.




각 서비스를 눌러보면 다음과 같이 서비스별로 상세한 내용을 볼 수 있다. 응답 시간이나 처리량에 대한 트렌드나, Request의 사이즈등 다양한 정보를 볼 수 있다.



Jaeger를 이용한 분산 트렌젝션 모니터링

다음은 Jaeger 를 이용해 개별 분산 트렌젝션에 대해서 각 구간별 응답 시간을 모니터링 할 수 있다.

Istio는 각 서비스별로 소요 시간을 수집하는데, 이를 Jaeger 오픈소스를 쓰면 손쉽게 모니터링이 가능하다.

마찬가지로 Jaeger 역시 외부 서비스로 노출이 되지 않았기 때문에, kubectl 명령을 이용해서 로컬 PC에서 jaeger pod로 포트를 포워딩하도록 한다. Jaerger는 16686 포트에서 돌고 있기 localhost:16686 → Jaeger pod:16686으로 포워딩한다.


kubectl port-forward -n istio-system $(kubectl get pod -n istio-system -l app=jaeger -o jsonpath='{.items[0].metadata.name}') 16686:16686 &


Jaeger UI에 접속해서, 아래는 productpage의 호출 기록을 보는 화면이다. 화면 상단에는 각 호출별로 응답시간 분포가 나오고 아래는 개별 트렉젝션에 대한 히스토리가 나온다.



그중 하나를 선택해보면 다음과 같은 그림을 볼 수 있다.



호출이 istio-ingressgateway로 들어와서 Productpage를 호출하였다.

productpage는 순차적으로 productpage → detail 서비스를 호출하였고, 다음 productpage→ reviews → ratings 서비스를 호출한것을 볼 수 있고, 많은 시간이 reviews 호출에 소요된것을 확인할 수 있다.


Servicegraph를 이용한 서비스 토폴로지 모니터링

마이크로 서비스는 서비스간의 호출 관계가 복잡해서, 각 서비스의 관계를 시각화 해주는 툴이 있으면 유용한데, 대표적인 도구로는 service graph라는 툴과 kiali 라는 툴이 있다. BookInfo 예제를 위한 Istio 설정에는 servicegraph가 디폴트로 설치되어 있다.


마찬가지로 외부 서비스로 노출 되서 서비스 되지 않고 클러스터 주소의 8088 포트를 통해서 서비스 되고 있기 때문에, 아래와 같이 kubectl 명령을 이용해서 localhost:8088 → service graph pod의 8088포트로 포워딩하도록 한다.


kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=servicegraph -o jsonpath='{.items[0].metadata.name}') 8088:8088 &


그 후에, 웹 브루우져에서 http://localhost:8088/dotviz 를 접속해보면 서비스들의 관계를 볼 수 있다.



다음 글에서는 예제를 통해서 Istio에서 네트워크 경로 설정하는 부분에 대해서 더 자세히 알아보도록 하겠다.


ISTIO


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

Envoy를 이용해서 서비스 매쉬를 구현하기 위해서는 Envoy로 구성된 데이타 플레인을 컨트롤할 솔루션이 필요하다. Envoy를 데이타 플레인으로 사용하고 이를 컨트롤 해주는 오픈 소스 솔루션이 Istio 이다. (http://istio.io)

아키텍쳐

먼저 Istio의 구조를 보자


<그림, Istio 아키텍쳐 >

출처 : https://istio.io/docs/concepts/what-is-istio/

데이타 플레인

데이타 플레인은 envoy를 서비스 옆에 붙여서 사이드카 형식으로 배포를 해서, 서비스로 들어오고 나가는 트래픽을 envoy를 통해서 통제하게 된다.

envoy는 서비스에서 서비스로 호출할때 상대편 서비스의 IP 주소를 알아야 하는데, 이를 서비스 디스커버리 (Service discovery : 참고 http://bcho.tistory.com/1252?category=431297 )

그러면 envoy는 서비스들의 IP 주소 (엔드포인트)를 어떻게 알 수 있을까? 서비스들에 대한 엔드포인트 정보는 컨트롤 플레인의 파일럿(Pilot)이라는 컴포넌트에 저장되어 있고, envoy는 이 데이타를 참고하여 엔드포인트를 알 수 있다.

컨트롤 플레인

컨트롤 플레인은 데이타 플레인에 배포된 envoy를 컨트롤 하는 부분으로, 파일럿 (Pilot), 믹서 (Mixer), 시타델(Citadel) 3개의 모듈로 구성이 되어 있다.

파일럿 (Pilot)

파일럿은 envoy에 대한 설정 관리를 하는 역할을 한다.

먼저 앞에서 언급했듯이 서비스들의 엔드포인트(EndPoint)들의 주소를 얻을 수 있는 서비스 디스커버리 기능을 제공한다.

Istio에 유용한 기능중의 하나가 트래픽의 경로를 컨트롤 하는 기능인데, 서비스에서 서비스로 호출하는 경로를 컨트롤 할 수 있다. 이외도 서비스의 안정성을 제공하기 위해서 서비스간에 호출이 발생할때 재시도(retry), 장애 전파를 막기 위한 써킷 브레이커 (Circuit breaker), Timeout 등의 기능을 제공한다.

믹서(Mixer)

믹서가 하는 일은 액세스 컨트롤, 정책 통제 그리고 각종 모니터링 지표의 수집이다.

예를 들어서 서비스의 총 처리량을 정책으로 지정하여, 그 처리량 이상으로 요청을 못받게 하거나 특정 헤더값이 일치해야 요청을 받을 수 있게 하는 등의 다양한 정책을 정의하고 이를 컨트롤 할 수 있다.

또한 서비스의 응답 시간이나 평균 처리량과 같은 다양한 지표를 수집하여 저장하는 역할을 한다.

시타델(Citadel)

시타델은 보안에 관련된 기능을 담당하는 모듈이다. 서비스를 사용하기 위한 사용자 인증 (Authentication)과 인가 (Authorization)을 담당한다. 또한 Istio는 통신을 TLS(SSL)을 이용하여 암호화할 수 있는데, TLS 암호화나 또는 사용자 인증에 필요한 인증서(Certification)을 관리하는 역할을 한다.  

기능

대략적인 구조를 이해했으면, Istio가 어떤 기능을 제공하는지 주요 기능을 살펴보도록 하자.

트래픽 통제

트래픽 분할

트래픽 분할은 서로 다른 버전의 서비스를 배포해놓고, 버전별로 트래픽의 양을 조절할 수 있는 기능이다. 예를 들어 새 버전의 서비스를 배포할때, 기존 버전으로 95%의 트래픽을 보내고, 새 버전으로 5%의 트래픽만 보내서 테스트하는 것이 가능하다. (카날리 테스트)


컨텐츠 기반의 트래픽 분할

단순하게 커넥션 기반으로 트래픽을 분할하는 것이 아니라, 조금 더 발전된 기능으로 네트워크 패킷의 내용을 기반으로 라우팅이 가능하다. 예를 들어 아래 우측 그림과 같이 HTTP 헤더의 User-agent 필드에 따라서, 클라이언트가 안드로이드일 경우에는 안드로이드 서비스로 라우팅을 하고, IPhone일 경우에는 IOS 서비스로 라우팅을 할 수 있다.


서비스간 안정성 제공 (Resilience)

파일럿은 트래픽 통제를 통해서 서비스 호출에 대한 안정성을 제공한다.

헬스체크 및 서비스 디스커버리

파일럿은 대상 서비스가 여러개의 인스턴스로 구성이 되어 있으면 이를 로드 밸런싱하고, 이 서비스들에 대해서 주기적으로 상태 체크를 하고, 만약에 장애가 난 서비스가 있으면 자동으로 서비스에서 제거한다.


Retry,Timeout,Circuit breaker

서비스간의 호출 안정성을 위해서, 재시도 횟수를 통제할 수 있다. 호출을 했을때 일정 시간 (Timeout)이상 응답이 오지 않으면 에러 처리를 할 수 있고, 앞에서 설명한 마이크로 서비스 아키텍쳐 패턴중 하나인 써킷 브레이커 (Circuit breaker) 패턴을 지원한다.

보안

Istio의 특징은 서비스에 대한 보안 기능을 추가해준다가는 것이다.

통신 보안

기본적으로 envoy를 통해서 통신하는 모든 트래픽을 자동으로 TLS를 이용해서 암호화한다. 즉 서비스간의 통신이 디폴트로 암호화 된다.


암호화를 위해서 인증서를 사용하는데, 이 인증서는 시타델(Citadel)에 저장되어 있는 인증서를 다운 받아서, 이 인증서를 이용하여 암호화된 TLS 통신을 한다.

서비스 인증과 인가

Istio는 서비스에 대한 인증 (Authentication)을 제공하는데, 크게 서비스와 서비스간 호출에서, 서비스를 인증하는 기능과, 서비스를 호출하는 클라이언트를 직접인증 할 수 있다.  

서비스간 인증

서비스간 인증은 인증서를 이용하여 양방향 TLS (Mutual TLS) 인증을 이용하여, 서비스가 서로를 식별하고 인증한다.

서비스와 사용자간 인증

서비스간 인증뿐 아니라, 엔드 유저 즉 사용자 클라이언트를 인증할 수 있는 기능인데, JWT 토큰을 이용해서 서비스에 접근할 수 있는 클라이언트를 인증할 수 있다.

인가를 통한 권한 통제 (Authorization)

인증뿐만 아니라, 서비스에 대한 접근 권한을 통제 (Authorization)이 가능하다. 기본적으로 역할 기반의 권한 인증 (RBAC : Role based authorization control)을 지원한다.

앞에서 인증된 사용자(End User)나 서비스는 각각 사용자 계정이나 쿠버네티스의 서비스 어카운트로 계정이 정의 되고, 이 계정에 역할(Role)을 부여해서 역할을 기반으로 서비스 접근 권한을 정의할 수 있다.


apiVersion: "rbac.istio.io/v1alpha1"

kind: ServiceRole

metadata:

 name: products-viewer

 namespace: default

spec:

 rules:

 - services: ["products.default.svc.cluster.local"]

   methods: ["GET", "HEAD"]


예를 들어 위의 역할은 products-viewer라는 권한으로 products.default.svc.cluster.local 서비스에 대해서 HTTP GET, HEAD를 지원하는 권한을 정의한것이다.

이렇게 정의된 역할 (Role)을 계정에 적용해야 하는데 아래는 products-viewer 역할을 서비스 어카운트와 엔드유저 계정에 반영한 예제이다.

apiVersion: "rbac.istio.io/v1alpha1"

kind: ServiceRoleBinding

metadata:

 name: test-binding-products

 namespace: default

spec:

 subjects:

 - user: "service-account-a"

 - user: "istio-ingress-service-account"

   properties:

     request.auth.claims[email]: "a@foo.com"

 roleRef:

   kind: ServiceRole

   name: "products-viewer"


“Service-account-a” 서비스 어카운트에 권한을 부여한 것이고, Istio의 Ingress를 통해서 들어오는 사용자 트래픽에 대해서 사용자 ID가 “a@foo.com”인 사용자에게도 같은 역할을 부여한것이다.


모니터링

마이크로 서비스에서 문제점중의 하나는 서비스가 많아 지면서 어떤 서비스가 어떤 서비스를 부르는지 의존성을 알기가 어렵고, 각 서비스를 개별적으로 모니터링 하기가 어렵다는 문제가 있다. Istio는 네트워크 트래픽을 모니터링함으로써, 서비스간에 호출 관계가 어떻게 되고, 서비스의 응답 시간, 처리량등의 다양한 지표를 수집하여 모니터링할 수 있다.

<그림. Mixer에서 서비스 관련 지표를 수집하는 구조>


서비스 A가 서비스 B를 호출할때 호출 트래픽은 각각의 envoy 프록시를 통하게 되고, 호출을 할때, 응답 시간과 서비스의 처리량이 Mixer로 전달된다. 전달된 각종 지표는 Mixer에 연결된 Logging Backend에 저장된다.


Mixer는 위의 그림과 같이 플러그인이 가능한 아답터 구조로, 운영하는 인프라에 맞춰서 로깅 및 모니터링 시스템을 손쉽게 변환이 가능하다.  쿠버네티스에서 많이 사용되는 Heapster나 Prometheus에서 부터 구글 클라우드의 StackDriver 그리고, 전문 모니터링 서비스인 Datadog 등으로 저장이 가능하다.

이렇게 저장된 지표들은 여러 시각화 도구를 이용해서 시각화 될 수 있는데, 아래 그림은 Grafana를 이용해서 서비스의 지표를 시각화 한 그림이다.





그리고 근래에 소개된 오픈소스 중에서 흥미로운 오픈 소스중의 하나가 Kiali (https://www.kiali.io/)라는 오픈소스인데, Istio에 의해서 수집된 각종 지표를 기반으로, 서비스간의 관계를 아래 그림과 같이 시각화하여 나타낼 수 있다.  아래는 그림이라서 움직이는 모습이 보이지 않지만 실제로 트래픽이 흘러가는 경로로 에니메이션을 이용하여 표현하고 있고, 서비스의 각종 지표, 처리량, 정상 여부, 응답 시간등을 손쉽게 표현해준다.





다음 글에서는 쿠버네티스 클러스터에 Istio를 설치해보고, 마이크로 서비스를 배포해보도록 하겠다.

Istio #1

마이크로 서비스 아키텍처와 서비스 매쉬

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


마이크로 서비스 아키텍쳐는 여러가지 장점을 가지고 있는 아키텍쳐 스타일이기는 하지만, 많은 단점도 가지고 있다. 마이크로 서비스는 기능을 서비스라는 단위로 잘게 나누다 보니, 전체 시스템이 커질 수 록 서비스가 많아지고, 그로 인해서 서비스간의 연결이 복잡해지고 여러가지 문제를 낳게 된다



<그림. 넷플릭스의 마이크로 서비스 구조 >

출처 : https://www.slideshare.net/BruceWong3/the-case-for-chaos?from_action=save


서비스간의 전체 연결 구조를 파악하기 어려우며 이로 인해서 장애가 났을때, 어느 서비스에서 장애가 났는지 추적이 어려워진다.

또한 특정 서비스의 장애가 다른 서비스에 영향을 주는 문제들을 겪을 수 있다.



예를 들어 클라이언트→ 서비스 A → 서비스 B의 호출 구조가 있다고 하자. 만약 서비스 B가 느려지거나 응답이 없는 상태가 되어 버리면, 서비스 B를 호출 하는 서비스 A 안의 쓰레드는 서비스 B로 부터 응답을 기다리기 위해 대기 상태가 되고, 이 상태에서 클라이언트에서 호출이 계속 되면, 같은 원리로 서비스 A의 다른 쓰레드들도 응답을 받기 위해서 대기 상태가 된다. 이런 상태가 반복되면, 서비스 A에 남은 쓰레드는 없어지고 결과적으로 서비스 A도 응답을 할 수 없는 상태가 되서 장애 상태가 된다. 이런 현상을 장애 전파 현상이라고 한다.  

마이크로 서비스 아키텍쳐 패턴

이런 문제들이 패턴화 되고 이를 풀어내기 위한 방법이 디자인 패턴으로 묶이기 시작하였다.

예를 들어 앞의 문제와 같은 장애 전파의 예는 써킷 브레이커 (Circuit breaker)라는 디자인 패턴으로 해결할 수 있다.



<그림, 써킷 브레이커(Circuit breaker) 패턴 >


서비스 A와 서비스 B에 써킷 브레이커라는 개념을 정의해서, 네트워크 트래픽을 통과 시키도록 하고, 서비스 B가 장애가 나거나 응답이 없을 경우에는 그 네트워크 연결을 끊어서 서비스 A가 바로 에러를 받도록 하는 것이다. 이렇게 하면 서비스 B가 응답이 느리거나 또는 응답을 할 수 없는 상태일 경우에는 써킷 브레이커가 바로 연결을 끊어서, 서비스 A내에서 서비스 B를 호출한 쓰레드가 바로 에러를 받아서 더 이상 서비스 B로 부터 응답을 기다리지 않고, 쓰레드를 풀어주서 서비스 A가 쓰레드 부족으로 장애가 되는 것을 막는다.

이 외에도 분산 시스템에 대한 로그 수집등 다양한 패턴들이 있는데, https://microservices.io/ 를 보면 잘 정리가 되어 있다.

이런 패턴은 디자인 패턴일 뿐이고, 이를 사용하기 위해서는 시스템에서 구현을 해야 하는데, 당연히 구현에 대한 노력이 많이 들어서 구체화 하기가 어려웠는데, 넷플릭스에서 이러한 마이크로 서비스 아키텍쳐 패턴을 오픈소스화 하여 구현하여 공개하였다. 예를 들어 위에서 언급한 써킷 브레이커 패턴의 경우에는 Hystrix (https://github.com/Netflix/hystrix/wiki)라는 오픈 소스로 공개가 되어 있다.

Hystrix 이외에도, 서비스 디스커버리 패턴은 Eureka, 모니터링 서비스인 Turbine 등 다양한 오픈 소스를 공개했다.



<그림. 넷플릭스의 마이크로 서비스 프레임웍 오픈소스 >

출처 : https://jsoftgroup.wordpress.com/2017/05/09/micro-service-using-spring-cloud-and-netflix-oss/


문제는 이렇게 오픈소스로 공개를 했지만, 여전히 그 사용법이 복잡하다는 것이다. Hystrix 하나만을 적용하는데도 많은 노력이 필요한데, 여러개의 프레임웍을 적용하는 것은 여간 어려운 일이 아니다.

그런데 여기서 스프링 프레임웍이 이런 문제를 풀어내는 기여를 한다. 스프링 프레임웍에 넷플릭스의 마이크로 서비스 오픈 소스 프레임웍을 통합 시켜 버린것이다. (http://spring.io/projects/spring-cloud-netflix)

복잡한 부분을 추상화해서 스프링 프레임웍을 적용하면 손쉽게 넷플릭스의 마이크로 서비스 프레임웍을 사용할 수 있게 해줬는데, 마지막 문제가 남게 된다. 스프링은 자바 개발 프레임웍이다. 즉 자바에만 적용이 가능하다.

서비스 매쉬

프록시

이러한 마이크로 서비스의 문제를 풀기 위해서 소프트웨어 계층이 아니라 인프라 측면에서 이를 풀기 위한 노력이 서비스 매쉬라는 아키텍쳐 컨셉이다.

아래와 같이 서비스와 서비스간의 호출이 있을때


이를 직접 서비스들이 호출을 하는 것이 아니라 서비스 마다 프록시를 넣는다.


이렇게 하면 서비스로 들어오거나 나가는 트래픽을 네트워크 단에서 모두 통제가 가능하게 되고, 트래픽에 대한 통제를 통해서 마이크로 서비스의 여러가지 문제를 해결할 수 있다.

예를 들어 앞에서 설명한 써킷 브레이커와 같은 경우에는 호출되는 서비스가 응답이 없을때 프록시에서 이 연결을 끊어서 장애가 전파되지 않도록 하면된다.


또는 서비스가 클라이언트 OS에 따라서 다른 서비스를 호출해야 한다면, 서비스가 다른 서비스를 호출할때, 프록시에서 메세지의 헤더를 보고 “Client”라는 필드가 Android면, 안드로이드 서비스로 라우팅을 하고, “IOS”면 IOS 서비스로 라우팅 하는 지능형 라우팅 서비스를 할 수 있다.


이런 다양한 기능을 수행하기 위해서는 기존의 HA Proxy,nginx, Apache 처럼 TCP 기반의 프록시로는 한계가 있다. 예를 들어서 위에서 언급한 HTTP 헤더 기반의 라우팅이나 조금더 나가면 메세지 본문을 기반으로 하는 라우팅들이 필요하기 때문에, L7 계층의 지능형 라우팅이 필요하다.

서비스 매쉬

그러면 이러한 마이크로 서비스에 대한 문제를 소프트웨어 계층이 아니라, 프록시를 이용해서 인프라 측면에서 풀어낼 수 있다는 것을 알았다. 그렇지만 마이크로 서비스는 한두개의 서비스가 아니라 수백, 수천의 서비스로 구성된다. 프록시를 사용해서 여러 기능을 구성할 수 있지만 문제는 서비스 수에 따라 프록시 수도 증가하기 때문에, 이 프록시에 대한 설정을 하기가 어려워진다는 것이다.



그래서 이런 문제를 해결하기 위해서, 각 프록시에 대한 설정 정보를 중앙 집중화된 컨트롤러가 통제하는 구조를 취할 수 있다. 아래 구조와 같이 되는데,

각 프록시들로 이루어져서 트래픽을 설정값에 따라 트래픽을 컨트롤 하는 부분을 데이타 플레인(Data Plane)이라고 하고, 데이타 플레인의 프록시 설정값들을 저장하고, 프록시들에 설정값을 전달하는 컨트롤러 역할을 하는 부분을 컨트롤 플레인(Control Plane) 이라고 한다.


다음 글에서는 이러한 서비스 매쉬 구조를 구현한 오픈 소스 솔루션인 Istio에 대해서 알아보도록 하겠다.



EAI, ESB, API 게이트 웨이,서비스 매쉬

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


서비스간의 연동은 작게 보면 마이크로 서비스 아키텍쳐로 인한 문제 같지만, 서비스간의 연동은 마이크로 서비스 아키텍쳐 이전에도 자주 있어왔던 전통적인 문제이다. 이러한 문제를 소프트웨어 개발 프레임웍이 아니라, 솔루션 차원에서 풀기 위한 여러가지 노력들이 있었다.


시스템 통합 문제

메인 프레임 시대에서 유닉스 시스템으로 내려오면서 부터 시스템들은 업무 단위로 분리가 되기 시작했다. ERP,CRM 등과 같은 시스템으로, 은행은 대내,대외,정보계와 같이 시스템으로 잘게 잘게 나눠지기 시작했는데, 당연히 이렇게 나눠진 시스템 사이에는 통신이 필요하게 되었고, 시스템이 거대화 되가면서, 시스템간에 직접 P2P로 통신하는 구조는 한계에 다다르기 시작하였다.  



시스템이 서로 얽히고 어느 시스템이 어떤 시스템과 통신하는지 통제가 어렵게 되는 상황이 되었다.

EAI (Enterprise Application Integration)

이러한 시스템간의 문제를 해결하기 위해서 등장한 솔루션이 EAI인데, 통합을 원하는 시스템을 기존에는 직접 1:1로 붙였다면, EAI는 EAI가 중앙에 허브 역할을 하면서, 모든 통신을 EAI를 거치도록 하였다.



EAI의 가장 큰 특징은 표준화 되지 않은 이기종 시스템간의 연동을 가능하게 해준다는 것이다. 메인프레임 → Unix ERP 시스템으로 데이타를 전송하게 한다던지 Oracle → CRM 시스템으로 데이타를 전송해주는 것과 같은 시스템 통합을 지원했는데, 이는 이기종간에 통신 프로토콜이나 통합 방식을 변경할 수 있는 아답터를 제공하기 때문이다. EAI는 복잡한 메세지 처리나 변동, 라우팅같은 다양한 기능을 가지고 있었지만, 주로 이 기종간의 메세지 변환이 가장 많이 사용되었다.

어쨌거나 이런 EAI는 중앙 통제를 통해서 1:1 / 다:다로 통신되는 복잡한 토폴로지를 통합하는 의미가 있다.


EAI 시스템이 점점 더 많아지자. 시스템 통합 아키텍쳐도 패턴화 되었다. EIP (Enterprise Integration Pattern)이라는 형태로 정리되었는데, 아직도 참고할만한 구조가 많으니 관심이 있으면 참고하기 바란다.

https://www.enterpriseintegrationpatterns.com/patterns/messaging/toc.html

SOA / ESB

이 기종간의 통합이 많아지고, 시스템이 점점 분리되다 보니, 아예 이를 표준화하고자 하는 작업이 진행되었는데, 이것이 바로 SOA (Service Oriented Archtiecture / 서비스 지향 아키텍쳐) 이다.

SOA 아키텍쳐의 컨셉 자체는 MSA와 유사하지만, XML 기반의 웹서비스와 맞춰져서 웹서비스를 대표하는 아키텍쳐가 되어버렸다.

(사실 SOA는 아키텍쳐 구현 컨셉이지 XML/HTTP를 대표하는 것이 아니지만, 시대적으로 벤더들에 의해 웹서비스로 포장되었다. SOA는 시스템을 서비스로 나눈 다음 표준화된 인터페이스로 통신한다는 컨셉으로, 요즘의 MSA도 이 SOA의 부분 집합이라고 할 수 있다. )


웹서비스 기반으로 통신이 표준화되었기 때문에 서비스간의 통신은 EAI처럼 별도의 아답터가 필요없어졌다. 대신 서비스간의 통신을 서비스 버스라는 통신 백본을 이용하여 통신을 하는 구조가 되었다.




이론적으로는 매우 좋은 구조지만 웹서비스 자체가 스펙이 너무 복잡했고, 그 당시의 컴퓨팅 파워가 복잡한 XML을 파싱하기에는 충분하지 않았기 때문에 그다지 성공하지는 못한 아키텍쳐가 되었다.

특히나 ESB내에서 서비스간의 통신시에, 복잡한 XML 변환등을 사용하다가 ESB에 부하가 걸리게 되고, 제대로된 성능을 내지 못하는 결과를 낳게 되었다.

API Gateway

엔터프라이즈에 의해서 IT가 주도되는 시대가 끝나고 웹과 서비스의 시대가 오면서 개방형 API 즉, 오픈 API가 각광 받는 시대가 오면서 시스템 내부간의 통합도 중요했지만 외부로 API를 서비스 하는 클라이언트 대 서버간의 통합 그리고 외부 서버와 내부 서버와의 통합이 중요한 요건으로 대두되었다.

웹 기반의 서비스들은 복잡한 형태의 메세징을 필요로하지 않았기 때문에 XML을 버리고 상대적으로 간단한 JSON이 메인으로 사용되었고, 이를 HTTP 통신에 사용하면서 HTTP/JSON 기반의 REST 아키텍쳐가 유행하기 시작했다.

그래서  API 게이트 웨이가 외부로 서비스를 제공하기 위한 솔루션으로 제공되고, 시스템 내부간의 통합도 HTTP REST 를 이용하여 마치 ESB처럼 내부 버스로써 내부 시스템 통합을 지원하였다.



그러나 API 게이트웨이의 주요 목적은 앞에서 언급한 시스템간의 통합보다는, 클라이언트와 서버 중간에서 API에 대한 라우팅이나 인증 처리와 같이 내부 API를 외부 서비스로 제공하는데 촛점이 맞추어졌다.

물론 API에 대한 중간 통로 역할을 한다는 의미에서 ESB의 대안 모델로 사용이 가능하기 때문에, API 게이트웨이를 내부 서비스간 통신용으로 위치시키는 아키텍쳐도 있지만,  원래 목적 자체가 API를 외부에 서비스 하기 위한 목적으로 디자인된 아키텍쳐이기 때문에 여러모로 맞지 않는 부분이 있다.

특히나 대부분의 API 게이트 웨이는 게이트 웨이 인스턴스(클러스터)를 포진 시키는 방식이라서 서비스간의 통합 포인트가 많아서 복잡도가 올라가는 경우 API 게이트 웨이에 많은 부하가 걸려서 성능 저하가 발생하고, API 게이트 웨이가 장애가 날 경우 전체 서비스에 영향을 주는 (SPOF : Single Point of Failure) 가 된다는 단점이 있다.  

Service Mesh

이런 단점을 보안한 아키텍쳐가 서비스 매쉬 아키텍쳐이다.

  • API 게이트웨이와는 달리 외부로 서비스를 노출하기 보다는 내부 서비스간의 통신을 조율에 중점을 둔다.

  • 구조적으로 중앙 집중화 구조를 벗어나서, 분산형 아키텍쳐를 취하는 구조이다. 그래서 SPOF를 생성하지 않고 스케일한 서비스를 지원하기 좋다.


그래서 근래의 마이크로 서비스 아키텍쳐에서 안정적인 구조는 외부 서비스는 API 게이트 웨이를 통해서 노출하고, 내부 서비스는 서비스 매쉬를 통해서 통제하는 구조가 된다.



서비스 매쉬의 개념은 다음과 같다.

서비스가 서비스를 호출 하는 구조가 있을때


위의 그림과 같이 서비스가 서비스를 바로 직접 호출 하는 것이 아니라, 아래 그림과 같이 프록시를 둬서, 프록시를 통해서 호출을 하게 하는 구조이다.


이러한 구조를 가지게 되면, 네트워크 트래픽을 컨트롤 하는 것을 서비스의 소스 코드 변경없이도 가능하다. 예를 들어 서비스가 다른 대상 서비스를 호출할때 아래 그림과 같이, HTTP 헤더에서, 클라이언트의 종류에 따라 트래픽을 안드로이드용 서비스나 IOS용 서비스로 라우팅을 할 수 있다.


이러한 구조의 장점은 서비스를 호출하는 쪽이나 호출 받는 쪽이나 소스 코드 변환이 필요 없이 가능하다는 것이다.

서비스 매쉬의 가장 대표적인 솔루션은 istio 라는 솔루션으로 다음 글에서는 Istio에 대해서 설명하도록 하겠다.




마이크로 서비스 아키텍쳐와 컨테이너

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

모노리틱 아키텍처

마이크로 서비스 아키텍쳐를 이해하려면 먼저 이에 상반되는 모노리틱 아키텍쳐를 이해할 필요가 있다. 모노리틱 아키텍쳐는 전통적인 아키텍쳐 스타일로 애플리케이션이 하나의 서버에 배포 되고, 데이타 베이스도 마찬가지로 하나의 데이타 베이스에 모든 데이타를 저장하는 방식이다.


예전에 하나의 큰 서버를 놓고, 그 안에 하나의 애플리케이션으로 개발하는 방식인데, 수퍼돔과 같이 큰 머신을 하나 놓고, 오라클 데이타베이스에 모든 데이타를 저장하고, 애플리케이션 바이너리를 하나로 개발하는 방식이다. 중앙 관리된 구조에서 통제가 편리하고, 같은 솔루션을 사용한다는데 있어서 장점이 있다.

마이크로 서비스 아키텍처

마이크로 서비스 아키텍처 (Micro service architecture,이하 MSA)는 비지니스 기능 마다 애플리케이션 서버와 데이타 베이스 서버를 분리하는 방식이다.

아래 그림과 같이 상품 정보, 상품 리뷰, 상품 상세 정보 서비스를 각각 별도의 애플리케이션으로 나누고 데이타 베이스도 각각 나누는 방식이다.  


이렇게 서버를 나누는 이유는 몇가지가 있는다.

첫번째는 서비스를 개발하는 조직이 다른 경우, 각 조직에 서비스 기획,개발,운영에 대한 독립적인 권한을 부여함으로써, 각 서비스 개발을 할때 다른 서비스에 대한 의존성이 없이 빠르게 개발할 수 있게 하는 목적을 가진다.

두번째는 인프라의 변화에 의한 요인을 들 수 있는데, 기존에는 수억의 고성능 서버에 하나의 애플리케이션을 넣는 방식을 이용했다면 근래에는 x86 서버기반으로 상대적으로 저비용,저성능 서버를 더 많이 사용하는 방식을 이용하기 때문에 하나의 큰 애플리케이션을 만드는 것보다, 작은 애플리케이션으로 나눠서 여러 서버에 분산하는 것이 유리하기 때문이다.

모노리틱 아키텍쳐와 비교

모노리틱 아키텍쳐에 비해서, 마이크로 서비스 아키텍쳐는 앞에서 언급한바와 같이 서비스 개발에 대해서 독립성을 가짐으로써 개발의 속도를 높일 수 있는 장점이 있고, 이외에도 서비스별로 다른 기술을 사용할 수 있다는 장점이 있는데, 이는 서비스 특성에 따라서 적절한 기술을 사용할 수 있다는 것을 의미한다.


반드시 장점만 있는 것이 아니라, 단점도 있는데, 여러가지 기술을 혼용해서 사용하다 보니, 그 기술의 표준을 통제하기가 어렵다. 희귀한 기술로 개발을 진행한 서비스가 있을때 개발자가 팀을 떠나게 되면, 해당 서비스를 계속해서 유지하기 어려울 수 있고, 같은 기술을 사용하더라도 프레임웍이나 코드 표준화 개발 프로세스 표준화 등에서 어려움을 겪을 수 있다.

마이크로 서비스 아키텍쳐가 어려운점 중의 하나는 서비스가 증가될 수 록, 서비스간의 연계가 복잡해져서 장애가 발생했을때 어느 서비스에서 장애가 발생했는지 찾아서 조치하는 것이 어렵다는 것이다. 그래서 마이크로서비스에서는 특히 분산된 서비스간의 모니터링을 구현하는데 더 많은 노력을 기울여야 한다.

조직 구조

마이크로 서비스의 목적은 서비스를 작은 단위로 쪼겐 후에, 각 팀이 각 서비스에 대한 개발과 운영 전체에 대한 책임을 부여함으로써, 서비스 개발의 속도를 높이는데 있다. 그래서, 마이크로 서비스 아키텍쳐를 적용하는데 있어서 가장 중요한 것은 조직 구조이다.


소프트웨어 아키텍쳐와 조직문화에 대해서 재미있는 이론이 하나 있는데, 콘웨이의 법칙이다.  콘웨이의 법칙은 “소프트웨어 아키텍쳐는, 소프트웨어를 개발하는 팀의 구조를 따라간다" 라는 이론이다.

이를 재 해석하면 마이크로 서비스 아키텍쳐에서 서비스의 분류 단위는 이를 개발하는 팀의 단위가 된다.


아무리 아키텍쳐를 논리적으로 잘 설계해서 서비스별로 나눴다하더라도, 팀의 구조와 맞지 않으면, 결국에는 아키텍쳐가 무너진다. 예전에 시스템을 설계할때, 미국,캐나다,인도,한국팀으로 팀을 나눠서 일한적이 있었다. 마이크로 서비스 아키텍쳐를 도입했었는데, 개발을 진행함에 따라서 서비스 컴포넌트의 기능이 처음 설계대로 진행되지 않고 한쪽으로 몰리는 현상이 발생하게 되었다. 한국에 기획이 있었기 때문에 비지니스에 연계된 결정은 대부분 한국에서 나오게 되었고 많은 커뮤니케이션이 필요하였기 때문에, 한국팀이 상대적으로 비지니스에 연계된 중요 컴포넌트를 많이 맏게 되었다. 미국과 캐나다는 기술이 상대적으로 좋았기 때문에 기술 난이도가 높은 기능들이 미국과 캐나다 서비스로 몰리게 되었고, 시간대가 같기 때문에 커뮤니케이션이 활발하게 일어났고 결과적으로 미국과 캐나다 서비스는 타이트하게 서로 맞물리는 구조가 되었다.

인도의 경우 시간대가 다르고 커뮤니케이션의 어려움 때문에 한번 커뮤니케이션하는데 시간이 많이 소요되었고 그로 인해서 상대적으로 덜 중요한 기능이 인도팀으로 점점 배정되었다.


사례에서 볼 수 있듯이, 마이크로 서비스 아키텍쳐 설계에서 가장 중요한 점은 기술적으로 아키텍쳐를 잘 설계하는 것이 아니라, 팀의 구조를 아키텍쳐에 맞는 구조로 만들고, 그 안에 원할한 커뮤니케이션 환경을 만들어주는 것이 가장 중요하다.  

조직문화

팀을 분할해서 독립성을 부여해서 개발의 속도를 높일려면 단순하게 팀만을 분할해서는 불가능하다. 각 팀이 맏은 컴포넌트 또는 서비스에 개발에 대한 독립적인 책임을 부여하려면 권한도 같이 부여해야 한다.

기존의 모노리틱 시스템의 개발팀을 운영하는 구조가 중앙 조직이 의사 결정을 하고 전체 프로젝트를 진행하는 중앙 집중형 거버넌스 모델이었다면, 마이크로 서비스 아키텍쳐는 설계와 프로젝트 진행 각각에 대한 의사 결정을 각 서비스 팀에 내리는 분산형 거버넌스 구조가 바람직하다. 중앙팀에서 의사 결정을 받고 그에 따라 개발을 진행하면 커뮤니케이션 오버헤드가 발생하고 그로 인하여 빠른 개발이 어렵기 때문에 그 권한을 개발 주체에게 분산하는 모델이다.


의사 결정 권한을 각 팀에게 분산하려면 각 팀이 의사 결정을 할 수 있는 능력이 있는 팀 구조가 되어야 한다.

팀내에서 의사 결정을 하고 스스로 서비스를 개발할 수 있는 능력을 갖춰야 하는데, 이를 위해서는 기획,개발, 테스트 및 운영등 모든 역할이 한 팀안에 있어야 한다. 팀의 크기는 보통 5~8명 정도가 적절한데, 이를 2-피자팀이라고도 이야기 한다. (2개의 피자로 팀원들이 모두 먹을 수 있는 정도의 팀의 크기) 이 인원을 넘어서면 커뮤니케이션이 어려워지고 작으면 제대로 된 서비스를 개발하기 어렵기 때문에 이 정도 팀의 크기를 추천한다.


DEVOPS

Devops는 운영과 개발을 한팀에서 하는 모델을 말하는데, 팀이 개발과 운영을 모두 담당함으로써 개발과 운영 사이에서 오는 간극을 해결하고 개발된 시스템을 빠르게 배포하고, 운영 과정에서 얻은 노하우를 개발에 반영해서 시장의 요구 사항에 빠르게 반응 하는데 그 목적을 둔다.

개발과 운영을 한팀에서 담당함에도 불구하고,  Devops 엔지니어, SRE (Site Reliability engineer)등과 같이 기존의 운영팀이 하던 일을 하는 역할이 여전히 남아 있는데, 그렇다면 Devops로 넘어왔음에도 불구하고 이러한 역할이 계속 남아 있는 이유와 정확한 역할은 무엇일까?


앞에서도 언급했듯이 Devops는 개발팀이 개발/배포/운영을 모두 담당하는 셀프 서비스 모델이다. 셀프 서비스를 하기 위해서는 인프라가 플랫폼화 되어 있어야 한다. 개발팀이 직접 데이타 센터에 가서 서버를 설치하고 OS를 설치하고 네트워크 구성을 하기는 어렵고, 온라인으로 서버를 설치하고 네트워크를 구성할 수 있어야 하고, 무엇보다 쉬워야 한다. 인프라 구성뿐 아니라 그위에 소프트웨어를 쉽게 빌드 및  배포하고 운영 중인 시스템에 대한 모니터링이 가능해야 하는데, 이러한 인프라를 일일이 구성하기는 어렵기 때문에 플랫폼화가 되어 있어야 하는데, Devops 엔지니어의 역할은 이러한 플랫폼을 만드는 역할이 된다.



위의 그림과 같이 Devops 팀은, 시스템을 실행할 수 있는 런타임 인프라를 개발 배포하고, 런타임 시스템에 대한 모니터링과 로깅을 제공하며, 이 시스템에 자동으로 배포할 수 있는 CI/CD 플랫폼을 구축한다.

이렇게 개발된 플랫폼에 개발팀은 개발된 시스템을 스스로 배포하고 운영하는 모델이다.

이러한 모델은 구글의 SRE (Site Reliability Engineering)에서 좋은 사례를 찾아볼 수 있는데, SRE 엔지니어는 시스템이 개발된 후에, 인프라 시스템에 대한 플랫폼화 작업을 수행하고, 이 플랫폼이 완성되어 안정화 될때까지 지속적인 지원을 하며, 플랫폼에 대한 안정화 작업이 끝나면 플랫폼의 운영을 개발팀에 맏기고 다른 프로젝트를 위한 플랫폼 작업을 하는 방식이다.

컨테이너

이러한 플랫폼을 지원하기 위해서는 벤더 종속적이지 않고, 개발자가 손쉽게 운영 및 접근할 수 있는 인프라 관리 기술이 필요한데, 이런 기술로 많이 언급되는 기술이 컨테이너이다.




가상머신 (VM)의 경우에는 하이퍼바이저의 종류에 따라서, 호환이 되지 않는 경우가 있고, 무엇보다 가상 머신 이미지의 사이즈가 매우 크기 때문에 (수백~기가 이상) 손쉽게 이식하기가 쉽지 않다.

또한 하드웨어 계층 부터 가상화 하기 때문에 실행하는데 컨테이너에 비해서 상대적으로 많은 자원이 소요된다.

컨테이너의 경우 가상 머신을 사용하지 않고 호스트 OS의 커널에서 바로 실행이 된다. 실행되는 컨테이너의 OS가 호스트 OS와 다른 경우, 이 다른 부분 (알파)만을 컨테이너에서 추가로 패키징하여 실행이 된다.

예를 들어 호스트 이미지에 기능이 A,B,C가 있고, 컨테이너는 A,B,C,D가 필요하다면, 컨테이너에는 다른 부분인 D만 묶어서 패키징 하는 개념이다.  그래서 가상머신에 비해서 크기가 훨씬 작고 가상화 계층을 거치지 않기 때문에 훨씬 효율적이라고 말할 수 있다.

컨테이너 관리 솔루션

컨테이너를 소규모로 사용한다면 물리 서버를 직접 지정해서 배포하면 되지만, 대규모로 컨테이너를 운영하고자 할때는 어떤 서버에 컨테이너를 배치해야 하는 가에 대한 문제가 생긴다.


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



이렇게 컨테이너 스케쥴링을 해주는 솔루션으로는 Kubernetes, Mesosphere, OpenStack 등 다양한 솔루션이 난립해서 혼돈이었는데, 작년말 (2017년말)을 기점으로 해서 쿠버네티스가 de-facto 표준으로 되어가는 형국이다. 아래 트랜드 그래프에서 보면 알 수 있듯이 쿠버네티스의 트랜드가 지속적으로 올라가서 가장 높은 것을 확인할 수 있다.



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

가상 머신위의 컨테이너

보통 이런 컨테이너 환경을 운영할때 베어메탈 (하드웨어)위에 바로 컨테이너 솔루션을 올리지 않고, 가상화 환경을 올린 후에, 그 위에 컨테이너 환경을 올리는 경우가 많다.


베어메탈 위에 바로 컨테이너 환경을 올리면 성능적 이점도 있고, 계층도 줄어들어 관리도 편리한데, 왜 가상화 계층을 한번 더 두는 것일까? 이유는 컨테이너 환경을 조금더 유연하게 사용할 수 있기 때문이다. 먼저 가상 머신을 이용해서 컨테이너 환경을 isolation할 수 있고, 가상화를 통해서 자원의 수를 더 늘려서 이를 잘게 쪼게서 사용이 가능하다. 예를 들어 설명하면, 8 CPU 머신을 쿠버네티스로 관리 운영하면, 8 CPU로밖에 사용할 수 없지만, 가상화 환경을 중간에 끼면, 8 CPU를 가상화 해서 2배일 경우 16 CPU로, 8배일 경우 64 CPU로 가상화 하여 좀 더 자원을 잘게 나눠서 사용이 가능하다.




구글 클라우드 해커톤 세션으로 진행한 강의 내용입니다

50분 정도 인데, 짧게 쿠버네티스에 대한 설명과, 마이크로서비스에 대한 설명

그리고 쿠버네티스 에코 시스템인 Spinnaker, Istio, KNative 등에 대해서 설명합니다



쿠버네티스 #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로 가상화 하여 좀 더 자원을 잘게 나눠서 사용이 가능하기 때문이 아닌가 하는 결론을 내렸다.

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


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




Envoyproxy

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

배경

마이크로 서비스 아키텍쳐가 발전하면서 서비스간의 통신을 라우팅하는 요건이 많아지면서 이를 소프트웨어 단이 아리나 인프라 단에서 처리할 수 있는 기술로 프록시 서버가 매우 유용하다. 기존의 대표적인 프록시 솔루션으로는 nginx, haproxy, apache 서버등이 있는데, 이러한 프록시들은 보통 TCP/IP 레이어에서 L4 로 작동을 하였다. 그러나 마이크로 서비스에서는 조금더 복잡한 라우팅 요건이 필요한데 예를 들어서 HTTP URL에 따른 라우팅에서 부터, HTTP Header를 이용한 라우팅등 다양한 요건이 필요해지면서 L4보다는 애플리케이션 레이어인 L7 기능이 필요해지게 되었다.

마이크로 서비스 아키텍처

특히 마이크로 서비스 아키텍쳐 (이하 MSA) 가 유행하면서 서비스간 라우팅이나 인증등 여러 기능들이 소프트웨어 레이어에서 구현이 되었는데, (넷플릭스 OSS가 대표적인 사례) 이 경우 서비스를 개발하는 각팀의 능력에 따라서 아키텍쳐의 성숙도의 편차가 크게 되었고, 소프트웨어가 특정 기술에 종속성을 가질 수 있는 문제점이 있었다.

특히 서비스간 라우팅, 헬스체크등 서비스간의 통제 기능은 궂이 애플리케이션 코드단에서 구현을 하지 않더라도 프록시 서버와 같은 인프라 서버를 이용해서 구현이 가능하다


아래 그림과 같이 서비스 사이에 프록시를 위치 시키게 되면, 이 프록시가 서비스간의 부하 분산, 로그 수집, Circuit breaker와 같은 다양한 기능을 수행할 수 있다.


Envoy Proxy

이런 배경에 맞춰서 마이크로 서비스 아키텍쳐에 적절한 프록시로 envoy 라는 프록시가 2016년에 소개되었다. Lyft사에서 개발되었으면 오픈소스로 공개되었다.

기존 프록시 L4기능 뿐 아니라 L7 기능도 지원하면서 HTTP 뿐아니라 HTTP 2.0,TCP,gRPC까지 다양한 프로토콜을 지원한다.


성능 지표를 보면 아래 Twillo에서 2017년에 테스트 한 자료를 참고할만 한데, (원본 https://www.twilio.com/blog/2017/10/http2-issues.html) HAProxy 보다 약간 느린것을 확인할 수 있다. 아무래도 L4가 아닌 L7단의 로드밸런서이다 보니 다소 성능 감소는 부담해야 한다.




(참고. 위의 문서를 보면 Envoy HTTP2 의 성능이 낮게 나오는데, 이는 Envory 자체 문제라가 보다는 HTTP/2가 Connection을 reuse하는 특성에서 온다고 볼 수 있는데, 성능에 대한 이슈가 있는 만큼 HTTP/2를 사용할 경우에는 별도의 검증 등이 필요하리라 본다.)


주요 기능적인 특성을 보면 다음과 같다.


  • HTTP, TCP, gRPC 프로토콜을 지원

  • TLS client certification 지원

  • HTTP L7 라우팅 지원을 통한 URL 기반 라우팅, 버퍼링, 서버간 부하 분산량 조절등

  • HTTP2 지원

  • Auto retry, circuit breaker, 부하량 제한등 다양한 로드밸런싱 기능 제공

  • 다양한 통계 추적 기능 제공 및 Zipkin 통합을 통한 MSA 서비스간의 분산 트렌젝션 성능 측정 제공함으로써 서비스에 대한 다양한 가시성 (visibility)을 제공

  • Dynamic configuration 지원을 통해서, 중앙 레파지토리에 설정 정보를 동적으로 읽어와서 서버 재시작없이 라우팅 설정 변경이 가능함

  • MongoDB 및 AWS Dynamo 에 대한 L7 라우팅 기능 제공


등 매우 다양한 기능을 제공한다.


Envoy 배포 아키텍처

Envoy 프록시는 배포 위치에 따라서 다양한 기능을 수행할 수 있는데, 크게 다음과 같이 4가지 구조에 배포가 가능하다.



Front envoy proxy

특정 서비스가 아니라, 전체 시스템 앞의 위치하는 프록시로, 클라이언트에서 들어오는 호출을 받아서 각각의 서비스로 라우팅을 한다. URL 기반으로 라우팅을 하는 기능 이외에도, TLS(SSL) 처리를 하는 역할들을 할 수 있다. 통상적으로 nginx나 apache httpd가 리버스프록시로 이 용도로 많이 사용되었다.

Service to service ingress listener

특정 서비스 앞에 위치하는 배포 방식으로 서비스로 들어오는 트래픽에 대한 처리를 하는데, 트래픽에 대한 버퍼링이나 Circuit breaker 와 같은 역할을 수행한다.

Service to service egress listener

특정 서비스 뒤에서 서비스로부터 나가는 트래픽을 통제 하는데, 서비스로 부터 호출 대상이 되는 서비스에 대한 로드 밸런싱, 호출 횟수 통제 (Rate limiting)와 같은 기능을 수행한다.

External service egress listener

내부서비스에서 외부 서비스로 나가는 트래픽을 관리하는 역할인데, 외부 서비스에 대한 일종의 대행자(Delegator)와 같은 역할을 한다.


시스템 앞 부분이나 또는 시스템을 구성하는 서비스의 앞뒤에 배치할 수 있는 구조지만, 서비스 앞뒤로 붙는다고 실제로 배포를 할때 하나의 서비스 앞뒤로 두개의 envoy proxy를 배치하지는 않는다.

다음과 같이 하나의 서비스에 하나의 Envoy를 배치 한후, ingress/egress 두 가지 용도로 겸용해서 사용한다.



Envoy 설정 구조

다음은 Envoy 설정 파일을 살펴 보자

Envoy의 설정은 크게 아래 그림과 같이 크게 Listener, Filter, Cluster 세가지 파트로 구성된다.



  • Listener
    Listener는 클라이언트로 부터 프로토콜을 받는 부분으로, TCP Listener, HTTP Listener 등이 있다.

  • Filter
    Filter는 Listener 로 부터 많은 메시지를 중간 처리하는 부분으로, 압축이나 들어오는 Traffic 에 대한 제한 작업등을 한후, Router를 통해서 적절한 클러스터로 메시지를 라우팅 하는 역할을 한다.

  • Cluster
    Cluster는 실제로 라우팅이 될 대상 서버(서비스)를 지정한다.


이렇게 Listener를 통해서 메시지를 받고, Filter를 이용하여 받은 메시지를 처리한 후에, 라우팅 규칙에 따라서 적절한 Cluster로 라우팅을 해서 적절한 서비스로 메시지를 보내는 형식이다.


Envoy 설치

Envoyproxy를 빌드하고 설치하는 방법은 여러가지가 있다. 소스코드로 부터 빌드를 하는 방법이나 이미 빌드된 바이너리를 사용해서 설치하는 방법 그리고 이미 빌딩된 도커 이미지를 사용하는 방법이 있다.

소스코드로 빌드하는 방법의 경우에는 bazel (make와 같은 빌드 도구) 빌드를 이용해서 빌드해야 하고, 빌드된 바이너리는 특정 플랫폼에 대해서만 미리 빌드가 되어 있기 때문에, 모든 플랫폼에 사용하기가 어렵다.

마지막으로는 도커 이미지 방식이 있는데, 이 방식이 배포면에서 여러모로 편리하기 때문에 도커 이미지를 이용한 배포 방식을 설명하도록 하겠다.


다음 명령어 처럼

docker pull을 이용하여 envoyproxy 도커 이미지 최신 버전을 가지고 오고, 다음 docker run 명령을 이용하여, 해당 이미지  (envoyproxy/envoy:latest)를 기동한다. 이때 -p 10000:10000 포트를 도커의 10000번 포트를 VM의 10000포트로 포워딩하도록 설정한다.


$ docker pull envoyproxy/envoy:latest
$ docker run --rm -d -p 10000:10000 envoyproxy/envoy:latest
$ curl -v localhost:10000


배포가 끝났으면, curl을 이용하여 localhost:10000번에 호출 하는 테스트를 하도록 한다.

설정에는 디폴트로, 10000 번 포트로 들어오는 모든 트래픽을 *.google.com으로 라우팅 하도록 설정되어 있다.


원본 설정 파일은 https://github.com/envoyproxy/envoy/blob/master/configs/google_com_proxy.v2.yaml 에 있고,  상세 내용을 보면 아래와 같다.


  • admin:
    이 부분은 envoyproxy의 admin 서버를 기동하는 부분으로, envoy 서버의 각종 설정이나 상태 정보를 127.0.0.1:9901로 들어오는 요청은 admin 기능으로 라우팅하도록 한다.

  • static_resources:
    Listener와 Filter 설정에 해당하는 부분으로, 아래 부면, listeners로 정의가 되어 있고 socket_address 부분에 0.0.0.0에 포트 10000 으로 들어오는 요청을 처리하도록 하였다.

    다음 filter_chain 부분에 filter들을 연속해서 정의하는데, http_connection_manager를 이용하여 모든 트래픽을 service_google이라는 클러스터로 라우팅 하도록 설정하였다.

  • clusters:
    마지막으로 clusters 부분에는 “service_google”이라는 클러스터를 정의했으며, 이 호스트의 URL은 google.com 443 포트로 정의하였다.


admin:

access_log_path: /tmp/admin_access.log

address:

  socket_address: { address: 127.0.0.1, port_value: 9901 }


static_resources:

listeners:

- name: listener_0

  address:

    socket_address: { address: 0.0.0.0, port_value: 10000 }

  filter_chains:

  - filters:

    - name: envoy.http_connection_manager

      config:

        stat_prefix: ingress_http

        route_config:

          name: local_route

          virtual_hosts:

          - name: local_service

            domains: ["*"]

            routes:

            - match: { prefix: "/" }

              route: { host_rewrite: www.google.com, cluster: service_google }

        http_filters:

        - name: envoy.router

clusters:

- name: service_google

  connect_timeout: 0.25s

  type: LOGICAL_DNS

  # Comment out the following line to test on v6 networks

  dns_lookup_family: V4_ONLY

  lb_policy: ROUND_ROBIN

  hosts: [{ socket_address: { address: google.com, port_value: 443 }}]

  tls_context: { sni: www.google.com }


참고 자료



MSA에서 Service discovery 패턴의 이해


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


MSA와 같은 분산 환경은 서비스 간의 원격 호출로 구성이 된다. 원격 서비스 호출은 IP 주소와 포트를 이용하는 방식이 되는다.

클라우드 환경이 되면서 서비스가 오토 스케일링등에 의해서 동적으로 생성되거나 컨테이너 기반의 배포로 인해서, 서비스의 IP가 동적으로 변경되는 일이 잦아졌다.


그래서 서비스 클라이언트가 서비스를 호출할때 서비스의 위치 (즉 IP주소와 포트)를 알아낼 수 있는 기능이 필요한데, 이것을 바로 서비스 디스커버리 (Service discovery)라고 한다.


다음 그림을 보자 Service A의 인스턴스들이 생성이 될때, Service A에 대한 주소를 Service registry (서비스 등록 서버) 에 등록해놓는다. Service A를 호출하고자 하는 클라이언트는 Service registry에 Service A의 주소를 물어보고 등록된 주소를 받아서 그 주소로 서비스를 호출한다.


Client side discovery vs server side discovery

이러한 Service discovery 기능을 구현하는 방법으로는 크게 client discovery 방식과 server side discovery 방식이 있다.

앞에서 설명한 service client가 service registry에서 서비스의 위치를 찾아서 호출 하는 방식을 client side discovery 라고 한다.


다른 접근 방법으로는 호출이 되는 서비스 앞에 일종의 proxy 서버 (로드밸런서)를 넣는 방식인데, 서비스 클라이언트는 이 로드밸런서를 호출하면 로드밸런서가 Service registry로 부터 등록된 서비스의 위치를 리턴하고, 이를 기반으로 라우팅을 하는 방식이다.



가장 흔한 예제로는 클라우드에서 사용하는 로드밸런서를 생각하면 된다. AWS의 ELB나 구글 클라우드의 로드 밸런서가 대표적인 Server side discovery 방식에 해당 한다.

Service registry

그러면 서비스를 등록하는 Service registry는 어떻게 구현을 해야 할까?

가장 쉬운 방법으로는 DNS 레코드에 하나의 호스트명에 여러개의 IP를 등록하는 방식으로 구현이 가능하다. 그러나 DNS는 레코드 삭제시 업데이트 되는 시간등이 소요되기 때문에, 그다지 적절한 방법은 아니기 때문에, 솔루션을 사용하는 방법이 있는데, ZooKeeper나 etcd 와 같은 서비스를 이용할 수 있고 또는 Service discovery에 전문화된 솔루션으로는 Netflix의 Eureka나 Hashcorp의 Consul과 같은 서비스가 있다.


향상된 기능

Service discovery 기능은 기본적으로 서비스를 등록하고 등록된 서비스의 목록을 리턴하는 기능이지만, 지능화된 기능을 이용하여 조금 더 향상된 기능을 제공할 수 있다.

예를 들어 Service registry에 등록된 서비스들의 Health check를 통해서 현재 서비스가 가능한 서비스를 판별한후, 서비스가 가능한 서비스 목록만 리턴을 한다던가. 서비스간의 부하 분산 비율을 조정하는 등의 고급 기능을 추가할 수 있고, 서버 목록에서 Master/Slave 서버의 정보를 리턴한다던가. 또는 서버에 접속하기 위한 인증키 정보등을 리턴하는 기능등 다양한 기능으로 확장이 가능하다.


참고 자료


Circuit breaker 패턴을 이용한 장애에 강한 MSA 서비스 구현하기 #2

Spring을 이용한 Circuit breaker 구현


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


앞의 글에서는 넷플릭스 Hystrix를 이용하여 Circuit break를 구현해보았다.

실제 개발에서 Hystix로 개발도 가능하지만, 보통 자바의 경우에는 Spring framework을 많이 사용하기 때문에 이번 글에서는 Spring framework을 이용한 Circuit breaker를 구현하는 방법을 알아보도록 한다.


다행이도 근래에 Spring은 넷플릭스의 MSA 패턴들을 구현화한 오픈 소스들을 Spring 오픈 소스 프레임웍안으로 활발하게 합치는 작업을 진행하고 있어서 어렵지 않게 구현이 가능하다.


구현하고자 하는 시나리오는 앞의 글에서 예제로 사용한 User service에서 Item Service를 호출하는 구조를 구현하고, User service에 circuit breaker를 붙여보도록 하겠다.

User service 코드 전체는 https://github.com/bwcho75/msa_pattern_sample/tree/master/user-spring-hystrix 에 그리고 Item Service 코드 전체는 https://github.com/bwcho75/msa_pattern_sample/tree/master/item-spring-hystrix 에 있다


Spring Circuit breaker 구현

User service pom.xml 정의

Hystrix circuit breaker를 사용하기 위해서는 pom.xml에 다음과 같이 hystrix 관련 라이브러리에 대한 의존성을 정의해줘야 한다.

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-hystrix</artifactId>

<version>1.4.4.RELEASE</version>

</dependency>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>

<version>1.4.4.RELEASE</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-actuator</artifactId>

<version>1.5.11.RELEASE</version>

</dependency>


spring-cloud-starter-hystrix 는 Hystrix circuit breaker를 이용한 의존성이고 hystrix-dashboard와 actuator 는 hystix dash 보드를 띄우기 위한 의존성이다.



User service 구현

UserApplication

Circuit breaker를 이용하기 위해서는 User Service의 메인 함수인 UserApplication 에 Annotation으로 선언을 해준다.



package com.terry.circuitbreak.User;




import org.springframework.boot.SpringApplication;


import org.springframework.boot.autoconfigure.SpringBootApplication;


import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;


import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;




@SpringBootApplication


@EnableCircuitBreaker


@EnableHystrixDashboard


public class UserApplication {





public static void main(String[] args) {


SpringApplication.run(UserApplication.class, args);


}


}


위의 코드와 같이 @EnableCircuitBreaker Annotation을 추가해주면 Circuit breaker를 사용할 수 있고, 그리고 추가적으로 Hystrix 대쉬 보드를 사용할것이기 때문에, @EnableHystrixDashboard Annotation을 추가한다.

Item Service를 호출

그러면 UserSerivce에서 ItemService를 호출하는 부분을 구현해보도록 하자. Hystrix와 마찬가지로 Spring Hystrix에서도 타 서비스 호출은 Command로 구현한다.  아래는 Item Service에서 Item 목록을 가지고 오는 GetItemCommand 코드이다.

GetItemCommand

Hystrix Command와 거의 유사하지만 Command를  상속 받아서 사용하지 않고, Circuit breaker를 적용한 메서드에 간단하게  @HystrixCommand Annotation만을 추가하면 된다.


아래 코드를 자세하게 보자. 주의할점은 Item Service 호출을 RestTemplate API를 통해서하는데, RestTemplate 객체인 resetTemplate는 Autowrire로 생성한다.



@Service


public class GetItemCommand {



@Autowired


RestTemplate restTemplate;



  @Bean


  public RestTemplate restTemplate() {


      return new RestTemplate();


  }





// GetItem command


@HystrixCommand(fallbackMethod = "getFallback")


public List<User> getItem(String name)  {


List<User> usersList = new ArrayList<User>();



List<Item> itemList = (List<Item>)restTemplate.exchange("http://localhost:8082/users/"+name+"/items"


,HttpMethod.GET,null


,new ParameterizedTypeReference<List<Item>>() {}).getBody();


usersList.add(new User(name,"myemail@mygoogle.com",itemList));



return usersList;


}



// fall back method


// it returns default result


@SuppressWarnings("unused")


public List<User> getFallback(String name){


List<User> usersList = new ArrayList<User>();


usersList.add(new User(name,"myemail@mygoogle.com"));



return usersList;


}


}


Item Service를 호출하는 코드는 getItem(String name) 메서드이다. 여기에 Circuit breaker를 적용하기 때문에, 메서드 앞에  @HystrixCommand(fallbackMethod = "getFallback") Annotation을 정의하였다. 그리고 Item Service 장애시 호출한 fallback 메서드는 getFallback 메서드로 지정하였다.

getItem안에서는 ItemService를 RestTemplate을 이용하여 호출하고 그 결과를 List<User> 타입으로 반환한다.


앞서 정의한 Fallback은 getFallback() 메서드로 Circuit breaker를 적용한 원래 함수와 입력 (String name)과 출력 (List<User>) 인자가 동일하다.

Circuit breaker 테스트


User service와 Item Service를 기동한 상태에서 user service를 호출하면 아래와 같이 itemList에 Item Service가 리턴한 내용이 같이 반환 되는 것을 확인할 수 있다.


terrycho-macbookpro:~ terrycho$ curl localhost:8081/users/terry

[  

  {  

     "name":"terry",

     "email":"myemail@mygoogle.com",

     "itemList":[  

        {

           "name":"computer",

           "quantity":1

        },

        {

           "name":"mouse",

           "quantity":2

        }

     ]

  }

]


Item Service를 내려놓고 테스트를 해보면 지연 응답 없이 User service로 부터 응답이 리턴되고, 앞서 정의한 fallback 메서드에 의해서 itemList에 아무 값이 없인할 수 있다.


terrycho-macbookpro:~ terrycho$ curl localhost:8081/users/terry

[  

  {  

     "name":"terry",

     "email":"myemail@mygoogle.com",

     "itemList":[]

  }

]


Hystrix Dashboard

User service에서 Hystrix Dash board를 사용하도록 설정하였기 때문에, User Service의 호출 상태를 실시간으로 확인할 수 있다.


User serivce 서버의 URL인 localhost:8081에서 localhost:8081/hystrix.stream을 호출 해보면

아래와 같이 Circuit Breaker가 적용된 메서드의 상태 현황 정보가 계속해서 업데이트 되면서 출력하는 것을 확인할 수 있다.




그러면 대쉬보드에 접속해보자 대쉬 보드 URL은 http://{user service}/hystrix 이다. User service url이 localhost:8081이기 때문에 http://localhost:8081/hystrix로 접속해보자


대쉬 보드에서는 모니터링 할 서비스의 스트림 URL을 넣어줘야 하는데 위에서 설명한 http://localhost:8081/hystrix.stream 을 입력한다.


URL을 입력하고 모니터링을 하면 아래와 같이 Circuit breaker가 등록된 서비스들이 모니터링 된다.

아래 그림은 부하가 없을때 상태이다.


실제로 부하를 주게 되면 아래와 같이 그래프가 커져가면서 정상적인 호출이 늘어가는 것을 확인할 수 있고, 응답 시간들도 모니터링이 가능하다.


아래는 Circuit breaker를 통해서 호출되는 Item service를 죽였을때인데, 그래프가 붉은색으로 표시되면서 붉은색 숫자가 증가하는 것을 볼 수 있고 Item service가 장애이기 때문에, Circuit 의 상태가 Close에서 Open을 변경된것을 확인할 수 있다.



운영 적용에 앞서서 고려할점

앞에서 예제로 사용한 Dashboard는 어디까지나 테스트 수준에서 사용할만한 수준이지 실제 운영환경에 적용할때는 여러가지 고려가 필요하다. 특히 /hystrix , /hystrix.stream이 외부에서 접근이 가능하기 때문에,, 이에 대해서 이 두 URL이 외부로 접근하는 것을 막아야 하며, circuit의 상태에 대한 정보를 하나의 서비스만 아니라 여러 서비스에서 대용량 서비스에 적용할시에는 중앙 집중화된 대쉬보드가 필요하고 또한 많은 로그를 동시에 수집해야 하기 때문에, 대용량 백앤드가 필요하다. 이를 지원하기 위해서 넷플릭스에서는 터빈 (Turbine)이라는 이름으로, 중앙 집중화된 Hystrix 대쉬 보드 툴을 지원하고 있다. (https://github.com/Netflix/turbine/wiki)


이번 글에서는 Spring 프레임웍을 이용하여 Circuit breaker 패턴을 Hystrix 프레임웍을 이용하여 적용하는 방법을 알아보았다.


Spring을 사용하면 편리는 하지만 자바 스택만을 지원한다는 한계점을 가지고 있다. Circuit breaker를 이처럼 소프트웨어로 지원할 수 도 있지만, 소프트웨어가 아닌 인프라 설정을 이용해서 적용이 가능한데, envoryproxy 를 이용하면 코드 변경 없이 모든 플랫폼에 적용이 가능하다. 다음 글에서는 envoy proxy를 이용하여, circuit breaker를 사용하는 방법에 대해서 알아보도록 한다.

Circuit breaker 패턴을 이용한 장애에 강한 MSA 서비스 구현하기 #1

Circuit breaker와 넷플릭스 Hystrix

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

MSA에서 서비스간 장애 전파

마이크로 서비스 아키텍쳐 패턴은 시스템을 여러개의 서비스 컴포넌트로 나눠서 서비스 컴포넌트간에 호출하는 개념을 가지고 있다. 이 아키텍쳐는 장점도 많지만 반대로 몇가지 단점을 가지고 있는데 그중에 하나는 하나의 컴포넌트가 느려지거나 장애가 나면 그 장애가난 컴포넌트를 호출하는 종속된 컴포넌트까지 장애가 전파되는 특성을 가지고 있다.


이해를 돕기 위해서 아래 그림을 보자


Service A가 Service B를 호출하는 상황에서 어떤 문제로 인하여 Service B가 응답을 못하거나 또는 응답 속도가 매우 느려진 상황이라고 가정하자. Service A가 Service B에 대한 호출 시도를 하면, Service A에서 Service B를 호출한 쓰레드는 응답을 받지 못하기 때문에, 계속 응답을 기다리는 상태로 잡혀있게 된다. 지속해서 Service A가 Service B를 호출을 하게 되면 앞과 같은 원리로 각 쓰레드들이 응답을 기다리는 상태로 변하게 되고 결과적으로는 남은 쓰레드가 없어서 다른 요청을 처리할 수 없는 상태가 된다.

이렇게 Service B의 장애가 Service A에 영향을 주는 경우를 장애가 전파 되었다고 한다. 이 상황에서 Service A를 호출하는 서비스가 또 있다면, 같은 원리로 인하여 그 서비스까지 장애가 전파되서 전체 시스템이 장애 상태로 빠질 수 있다.

Circuit breaker 패턴

이런 문제를 해결하는 디자인 패턴이 Circuit breaker 라는 패턴이 있다.

기본적인 원리는 다음과 같다. 서비스 호출 중간 즉 위의 예제에서는 Service A와 Service B에 Circuit Breaker를 설치한다. Service B로의 모든 호출은 이 Circuit Breaker를 통하게 되고 Service B가 정상적인 상황에서는 트래픽을 문제 없이 bypass 한다.

.


만약에 Service B가 문제가 생겼음을 Circuit breaker가 감지한 경우에는 Service B로의 호출을 강제적으로 끊어서 Service A에서 쓰레드들이 더 이상 요청을 기다리지 않도록 해서 장애가 전파하는 것을 방지 한다. 강제적으로 호출을 끊으면 에러 메세지가 Service A에서 발생하기 때문에 장애 전파는 막을 수 있지만, Service A에서 이에 대한 장애 처리 로직이 별도로 필요하다.

이를 조금 더 발전 시킨것이 Fall-back 메시징인데, Circuit breaker에서 Service B가 정상적인 응답을 할 수 없을 때, Circuit breaker가 룰에 따라서 다른 메세지를 리턴하게 하는 방법이다.



예를 들어 Service A가 상품 목록을 화면에 뿌려주는 서비스이고, Service B가 사용자에 대해서 머신러닝을 이용하여 상품을 추천해주는 서비스라고 했을때, Service B가 장애가 나면 상품 추천을 해줄 수 없다.

이때 상품 진열자 (MD)등이 미리 추천 상품 목록을 설정해놓고, Service B가 장애가 난 경우 Circuit breaker에서 이 목록을 리턴해주게 하면 머신러닝 알고리즘 기반의 상품 추천보다는 정확도는 낮아지지만 최소한 시스템이 장애가 나는 것을 방지 할 수 있고 다소 낮은 확률로라도 상품을 추천하여 꾸준하게 구매를 유도할 수 있다.


이 패턴은 넷플릭스에서 자바 라이브러리인 Hystrix로 구현이 되었으며, Spring 프레임웍을 통해서도 손쉽게 적용할 수 있다.

이렇게 소프트웨어 프레임웍 차원에서 적용할 수 있는 방법도 있지만 인프라 차원에서 Circuit breaker를 적용하는 방법도 있는데, envoy.io 라는 프록시 서버를 이용하면 된다.

소프트웨어를 사용하는 경우 관리 포인트가 줄어드는 장점은 있지만, 코드를 수정해야 하는 단점이 있고, 프로그래밍 언어에 따른 종속성이 있다.

반대로 인프라적인 접근의 경우에는 코드 변경은 필요 없으나, Circuit breaker용 프록시를 관리해야하는 추가적인 운영 부담이 늘어나게 된다.


이 글에서는 넷플릭스의 Hystrix, Spring circuit breaker를 이용한 소프트웨어적인 접근 방법과 envoy.io를 이용한 인프라적인 접근 방법 양쪽을 모두 살펴보기로 한다.


넷플릭스 Hystrix

넷플릭스는 MSA를 잘 적용하고 있는 기업이기도 하지만, 적용되어 있는 MSA 디자인 패턴 기술들을 오픈소스화하여 공유하는 것으로도 유명하다. Hystrix는 그중에서 Circuit breaker 패턴을 자바 기반으로 오픈소스화한 라이브러리이다.  


Circuit breaker 자체를 구현한것 뿐만 아니라, 각 서비스의 상태를 한눈에 알아볼 수 있도록 대쉬보드를 같이 제공한다.


Hystrix 라이브러리 사용방법

Hystrix를 사용하기 위해서는 pom.xml에 다음과 같이 라이브러리 의존성을 추가해야 한다.

<dependency>

<groupId>com.netflix.hystrix</groupId>

<artifactId>hystrix-core</artifactId>

<version>1.5.4</version>

</dependency>

<dependency>

<groupId>com.netflix.rxjava</groupId>

<artifactId>rxjava-core</artifactId>

<version>0.20.7</version>

</dependency>


Circuit breaker는 Hystrix 내에서 Command 디자인 패턴으로 구현된다. 먼저 아래 그림과 같이 HystrixCommand 클래스를 상속받은 Command 클래스를 정의한 후에, run() 메서드를 오버라이드하여, run 안에 실제 명령어를 넣으면 된다. HystrixCommand 클래스를 상속받을때 runI()메서드에서 리턴값으로 사용할 데이타 타입을 <>에 정의한다.


public class CommandHelloWorld extends HystrixCommand<String>{

private String name;

CommandHelloWorld(String name){

super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));

this.name = name;

}

@Override

protected String run() {

return "Hello" + name +"!";

}


이렇게 Command가 정의되었으면 호출 방법은 아래와 같다.


CommandHelloWorld helloWorldCommand = new CommandHelloWorld("World");

assertEquals("Hello World", helloWorldCommand.execute());


먼저 Command 클래스의 객체를 생성한 다음에, 객체.execute()를 이용해서 해당 command 를 실행하면 된다. 이렇게 하면, Command 클래스가 응답을 제대로 받지 못할때는 Circuit Breaker를 이용하여 연결을 강제적으로 끊고 에러 메세지등을 리턴하도록 된다.


전체 코드 샘플은 https://github.com/bwcho75/msa_pattern_sample/tree/master/hystrix 를 참고하기 바란다.

웹서비스에 적용하는 방법

대략적인 개념을 이해하였으면 실제로 이 패턴을 REST API로 구성된 MSA 기반의 서비스에 적용해보자.

두 개의 서비스 User와 Item이 있다고 가정하자 User 서비스가 REST API 호출을 이용하여 Item 서비스를 호출하는 구조라고 할때 이 User → Item 서비스로의 호출을 HystrixCommand를 이용하여 Circuit breaker로 구현해보도록 하자.


User 서비스의 전체 코드는 https://github.com/bwcho75/msa_pattern_sample/tree/master/UserService , Item 서비스의 전체코드는 https://github.com/bwcho75/msa_pattern_sample/tree/master/ItemService 에 있다.

각 코드는 Spring Web을 이용하여 구현되었으며 User → Item으로의 호출을 resttemplate을 이용하였다.


User → Item 서비스를 호출하여 해당 사용자에 속한 Item 목록을 읽어오는 Command를 GetCommand라고 하자, 코드는 대략 아래와 같다.


public class GetItemCommand extends HystrixCommand<List<User>>{

String name;

public GetItemCommand(String name) {

super(HystrixCommandGroupKey.Factory.asKey("ItemServiceGroup"));

this.name = name;

}


@Override

protected List<User> run() throws Exception {

List<User> usersList = new ArrayList<User>();

// call REST API

                                                (생략)

return usersList;

}

@Override

protected List<User> getFallback(){

List<User> usersList = new ArrayList<User>();

usersList.add(new User(name,"myemail@mygoogle.com"));

return usersList;

}

}


리턴 값이 List<User>이기 때문에, HystrixCommand <List<User>>를 상속하여 구현하였고, Item 서비스를 호출하는 부분은 run() 메서드에 구현한다. (restTemplate을 이용하여 호출하는 내용은 생략하였다.)


여기서 주목해야할 부분은 getFallBack() 함수인데, 호출되는 서비스 Item이 장애 일때는 이를 인지하고 getFallBack의 리턴값을 fallback 메세지로 호출한다.


Item과 User 서비스를 각각 실행한다.

%java -jar ./target/User-0.0.1-SNAPSHOT.jar

%java -jar ./target/Item-0.0.1-SNAPSHOT.jar


두 서비스를 실행 한후에 아래와 같이 User 서비스를 호출하면 다음과 같이 ItemList가 채워져서 정상적으로 리턴되는 것을 볼 수 있다.


terrycho-macbookpro:~ terrycho$ curl localhost:8081/users/terry

[{"name":"terry","email":"myemail@mygoogle.com","itemList":[{"name":"computer","qtetertertertertetttt


Item 서비스 서버를 인위적으로 죽인 상태에서 호출을 하면 다음과 같이 위에서 정의한 fall back 메세지와 같이 email이 “myemail@mygoogle.com”으로 호출되고 itemList는 비어 있는채로 리턴이 된다.


terrycho-macbookpro:~ terrycho$ curl localhost:8081/users/terry

[{"name":"terry","email":"myemail@mygoogle.com","itemList":[]}]


지금까지 간단하게나마 Circuit breaker 패턴과 넷플릭스의 Hystrix 오픈소스를 이용하여 Circuit breaker를 구현하는 방법에 대해서 알아보았다.

서비스 상태에 따라서 Circuit을 차단하는 방법등도 다양하고, Command 패턴을 처리하는 방법 (멀티 쓰레드, 세마포어 방식)등이 다양하기 때문에, 자세한 내부 동작 방법 및 구현 가이드는 https://github.com/Netflix/Hystrix/wiki/How-it-Works 를 참고하기 바란다.


Circuit breaker 패턴은 개인적인 생각에서는 MSA에서는 거의 필수적으로 적용해야 하는 패턴이라고 생각을 하지만 Hystrix를 이용하면 Command를 일일이 작성해야 하고, 이로 인해서 코드 복잡도가 올라갈 수 있다. 이를 간소화 하기 위해서 Spring 오픈소스에 이 Hystrix를 잘 추상화 해놓은 기능이 있는데, 그 부분 구현에 대해서는 다음글을 통해서 살펴보도록 한다.



Zipkin을 이용한 MSA 환경에서 분산 트렌젝션의 추적 #3

Stackdriver를 zipkin으로 사용하기


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


앞의 예제에서는 간단하게 Zipkin 서버를 메모리 스토리지를 이용해서 올렸는데, 운영환경에서는 적절하지 않다. 실 운영환경에서는 대규모 트래픽 저장 및 쿼리를 위해서 Cassandra나 Elastic Search 등을 사용해야 하는데, 설정과 운영이 어렵다.

이에 대한 대안으로 구글 클라우드에는 분산 트렌젝션 추적을 위한 Stack driver trace (https://cloud.google.com/trace/) 라는 기능이 있다. 자체적인 SDK를 이용하여 트렌젝션을 추적하는 것도 가능하지만, Zipkin 클라이언트로 부터 로그를 수집할 수 있다.

즉 개발단은 Zipkin을 사용하고, 뒷단에는 복잡한 Zipkin 서버 대신 Stack driver trace를 사용하는 방법이다.


개념적으로 보면 다음과 같다. Zipkin 서버 대신 Zipkin/stack driver collector 라는 서버를 띄우면 이 서버가 Stackdriver 로 로그를 저장하고 시각화 해준다.



Zipkin/stack driver collector는 zipkin 서버를 대치하는 역할로, zipkin 클라이언트가 zipkin 서버 대신 이 zipkin/stack driver collector 를 바라보도록 주소와 포트만 변경해주면 된다.

흥미로운 점은 구글 클라우드 뿐 아니라, 로컬 환경, AWS,Azure,On Prem 등 다양한 환경에 설치가 가능하다. 그래서 모든 애플리케이션 서비스를 통합해서 Stack driver 로 trace가 가능하다.


Zipkin/stack driver collector를 설치하는 방법은 다음과 같다.

https://cloud.google.com/trace/docs/zipkin

Docker 이미지를 이용해도 되고 java jar 파일을 다운로드 받아서 사용해도 된다.

구글 클라우드 VM이나 도커로 실행할때는 상관이 없지만 구글 클라우드 인프라 밖에서 Zipkin Stackdriver collector를 실행할때는 추가적인 인증 정보를 설정해야 한다.


Stack driver collector가 Stackdriver 서버(클라우드)로 로그를 전달하기 위해서는 아무 로그나 받으면 안되고 인증된 로그만 받아야 하니 추가 인증 체계가 필요한데, 구글 클라우드에서는 애플리케이션 인증을 위해서 Service Account라는 JSON 파일을 사용한다.  Service Account 생성 방법은 https://medium.com/google-cloud/distributed-tracing-spring-boot-microservices-with-stackdriver-trace-7fe42c6de3f3 문서를 참고하기 바란다.


Service Account 파일이 생성되면, 아래와 같이 GOOGLE_APPLICATION_CREDENTAILS 환경 변수에 Service account 파일의 경로를 지정한다.

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/credentials.json"
export PROJECT_ID="my_project_id"

다음 구글 클라우드의 어느 프로젝트에 있는 Stack Driver 와 연결할지를 지정해야 하는데, “PROJECT_ID” 환경 변수에 프로젝트 명을 지정해주면 된다.

환경 변수 설정이 끝나면 java -jar collector-0.6.0.jar 명령으로 collector를 실행한다.

아래는 환경 변수 설정과 collector 를 실행하는 스크립트 예제이다.


export GOOGLE_APPLICATION_CREDENTIALS="./terrycho-sandbox-zipkin-collector.json"

export PROJECT_ID="terrycho-sandbox"


echo $GOOGLE_APPLICATION_CREDENTIALS

echo $PROJECT_ID

java -jar collector-*.jar


포트는 디폴트로 9411을 사용하게 되어 있다. 이전 예제에서 zipkin 서버 대신 collector만 대신 띄운 후에 부하를 주면 로그를 수집할 수 있다.

아래는 로그를 수집한 후에, 분석화면의 일부분이다.


Zipkin UI와 동일하게 각 단일 트렌젝션에 대해서 Trace/Span 정보를 확인할 수 있고, Spot 그래프를 이용한 응답 시간 분포 확인이 가능하다.




아울러 각 서비스 별로 응답 시간에 대한 분포도를 아래와 같이 시각화 해준다.




참고

구글 클라우드내에서 Zipkin과 StackDriver 연결 방법 https://codelabs.developers.google.com/codelabs/cloud-spring-cloud-gcp-trace/index.html?index=..%2F..%2Findex#6





Zipkin을 이용한 MSA 환경에서 분산 트렌젝션의 추적 #2 

 Spring Sleuth를 이용한 Zipkin 연동


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



앞글에 이어서 이번에는 실제로 어플리케이션에서 분산 로그를 추적해보도록 한다.

스프링 부트 애플리케이션을 Zipkin과 연동하기 위해서는 Sleuth라는 라이브러리를 사용하면 된다.

구조

우리가 구현하고자 하는 예제의 구조는 다음과 같다.


API Client는 User 서비스를 호출하고, User 서비스는 Item 서비스를 호출하여 사용자의 Item 정보를 리턴 받아서 리턴 받은 내용을 API Client에 호출한다.

User와 Item 서비스는 모두 Spring Boot 1.5 버전으로 개발하였다. Spring 2.0은 아직 나온지가 얼마되지 않아서 Zipkin 이 지원되지 않는다.

이 예제에 대한 전체 코드는 https://github.com/bwcho75/zipkin-spring-example 에 있다.

User 서비스 코드

User 서비스 코드를 살펴보도록 하자

maven pom.xml

먼저 maven 빌드 스크립트인 pom.xml에는, zipkin 연동을 위해서 sleuth 라이브러리를 사용하기 위해서 이에 대한 의존성을 추가한다. 아래와 같이 zipkin과 sleuth 라이브러리의 버전은 1.3.2.RELEASE 버전을 사용하였다. 참고로 스프링 부트의 버전은 1.5.5.RELEASE 버전을 사용하였다.


<dependency>

   <groupId>org.springframework.cloud</groupId>

   <artifactId>spring-cloud-starter-zipkin</artifactId>

   <version>1.3.2.RELEASE</version>

</dependency>

<dependency>

   <groupId>org.springframework.cloud</groupId>

   <artifactId>spring-cloud-starter-sleuth</artifactId>

   <version>1.3.2.RELEASE</version>

</dependency>


Controller 클래스

다음은 /users URL을 처리하는  Rest Controller 부분의 코드를 살펴보자, 코드는 다음과 같다.


@RestController

@RequestMapping("/users")

public class UserController {

   @Autowired

   RestTemplate restTemplate;

   

   @Bean

   public RestTemplate getRestTemplate() {

       return new RestTemplate();

   }

   

   @Bean

   public AlwaysSampler alwaysSampler() {

       return new AlwaysSampler();

   }

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

@RequestMapping(value="/{name}",method=RequestMethod.GET)

public List<User> getUsers(@PathVariable String name){

logger.info("User service "+name);

List<User> usersList = new ArrayList<User>();

List<Item> itemList = (List<Item>)restTemplate.exchange("http://localhost:8082/users/"+name+"/items"

,HttpMethod.GET,null

,new ParameterizedTypeReference<List<Item>>() {}).getBody();

usersList.add(new User(name,"myemail@mygoogle.com",itemList));

return usersList;

}


}


getUsers() 함수에서 /users/{name}으로 들어오는 요청을 받아서 RestTemplate을 이용하여 localhost:8082/users/{name}/items로 호출하는 코드이다.

여기서 중요한것이 RestTemplate 객체를 생성하는 방법은데, restTeamplte을 @AutoWrire로 하게 하고, getRestTemplate을 @Bean으로 정해줘야 한다. (아래 문서 참조 내용 참고)


https://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/1.2.1.RELEASE/#_baggage_vs_span_tags

그리고 @Bean으로 정의된 alwaysSampler()를 정의하는데, Sampler란 zipkin으로 트레이싱 하는 트렌젝션을 100%를 다할것인지 일부만 할것인지를 결정하는 것이다. 여기서는 100%를 다하도록 하였다.

100%를 샘플링하면 정확하게 트렌젝션을 추적할 수 있지만, 반대 급부로 매번 샘플링 및 로그를 서버에 전송해야하기 때문에 성능 저하를 유발할 수 있기 때문에 이 비율을 적절하게 조정할 수 있다. 비율 조정은 뒤에 설명할 설정파일에서 조정이 가능하다.

applicaiton.yml

Zipkin 서버의 URL과, 샘플링 비율등을 설정하기 위해서는 src/main/resources/application.yml에 이 설정 정보를 지정해놓는다. 아래는  application.yml 파일이다.


server:

 port: 8081

spring:

 application:

   name: zipkin-demo-server1

 zipkin:

   baseUrl: http://127.0.0.1:9411/

 sleuth:

   enabled: true

   sampler:

     probability: 1.0

sample:

 zipkin:

   enabled: true


port는 이 서비스가 listen할 TCP 포트로 8081로 listen을 하도록 하였다.

spring.zipkin에 baseUrl 부분에 zipkin 서버의 URL을 지정한다. 이 예제에서는 zipkin 서버를 localhost(127.0.0.1):9411 에 기동하였기 때문에 위와 같이 URL을 지정하였다.

다음은 sleuth 활성화를 위해서 spring.sleuth.enabled를 true로 하고 sampler에서 probability를 1.0으로 지정하였다.

Item 서비스 코드

Item 서비스 코드는 User 서비스 코드와 크게 다르지 않다. 전체 코드는 https://github.com/bwcho75/zipkin-spring-example/tree/master/zipkin-service2 를 참고하기 바란다.

Item 서비스는 8082 포트로 기동되도록 설정하였다.

테스트

서비스 개발이 끝났으면 컴파일을 한 후에 User 서비스와 Item  서비스를 기동해보자.

Zipkin 서버 구동

Zipkin 서버를 설치하는 방법은 https://zipkin.io/pages/quickstart 를 참고하면 된다. 도커 이미지를 사용하는 방법등 다양한 방법이 있지만 간단하게 자바 jar 파일을 다운 받은 후에, java -jar로 서버를 구동하는게 간편하다.

wget -O zipkin.jar 'https://search.maven.org/remote_content?g=io.zipkin.java&a=zipkin-server&v=LATEST&c=exec'
java -jar zipkin.jar

이때 주의할점은 zipkin 서버를 통해서 HTTP로 Trace 로그를 받을때, 별도의 보안이나 인증 메커니즘이 없기 때문에, zipkin 서버는 반드시 방화벽 안에 놓고, 서비스 서버로부터만 HTTP 호출을 받을 수 있도록 해야 한다.

부하주기

모든 서버가 기동 되었으면 부하를 줘서 로그를 수집해보자. 부하 발생은 간단하게 apache ab 툴을 이용하였다.

%ab -n 1000 http://localhost:8081/users/terry

위의 명령어는  localhost:8081/users/terry로 HTTP GET 요청을 1000번 보내는 명령이다.

결과 확인

부하 발생이 끝난후에 http://localhost:9411 화면으로 들어가서 Find Traces 버튼을 눌러보면 다음과 같은 트레이스 화면을 볼 수 있다. 개개별 트렌젝션 결과가 나오고,


개별 트렌젝션을 눌러보면 다음과 같은 결과가 나오는 것을 볼 수 있다. 아래를 보면 /users/terry가 전체 58.944 ms가 소요되고, users/terry/items는 2 ms가 소요되는 것을 확인할 수 있다. 앞에는 서비스 명인데, 첫번째 서비스는 zipkin-demo-server1, 두번째 서버는 zipkin-demo-server2 로 출력이 된다. 이 서버명은 application.yml 파일에서 지정하면 된다.



재미있는 기능중 하나는 각 서비스의 의존성을 시각화 해주는 기능이 있는데, 화면 위쪽에 dependency 버튼을 누르면 아래 그림과 같이 로그 기반으로하여 서비스간의 호출 의존성을 보여준다.



지금까지 간략하게 Spring Sleuth와 Zipkin을 이용한 분산 로그 추적 기능을 구현해보았다.

여기서 구현한 내용은 어디까지나 튜토리얼 수준이다. Zipkin 서버의 스토리지 구성이 메모리로 되어 있기 때문에 실 운영환경에서는 적합하지 않다. 다음 글에서는 클라우드 환경을 이용하여 운영 수준의 Zipkin 서비스를 구성하는 방법에 대해서 알아보도록 하겠다.


참고 자료

https://howtodoinjava.com/spring/spring-boot/spring-boot-tutorial-with-hello-world-example/

https://howtodoinjava.com/spring/spring-cloud/spring-cloud-zipkin-sleuth-tutorial/



Zipkin을 이용한 MSA 환경에서 분산 트렌젝션의 추적 #1

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

개념

분산 트렌젝션이랑 여러개의 서비스를 걸쳐서 이루어 지는 트렌젝션을 추적하는 기능을 정의한다.

마이크로 서비스 아키텍쳐 (이하 MSA)와 같은 구조에서는 하나의 HTTP 호출이 내부적으로 여러개의 서비스를 거쳐서 일어나게 되는데, 그러면 어느 구간에서 병목이 생기는지 추적하기가 어려워진다.

아래 그림을 보면 클라이언트가 Service A를 호출하고, Service A 가 Service B,D 를, Service B가 Service C를 호출한다.


이렇게 트렌젝션이 여러 컴포넌트의 조합을 통해서 발생하기 때문에 Jennifer와 같은 전통적인 APM (Application Performance Monitoring) 도구를 이용해서 추적하기가 어렵기 때문에 별도의 분산 로그 추적 시스템이라는 것이 필요하다.

작동 원리

그러면 이러한 분산 로그는 어떻게 수집 및 추적하는 것일까? 통상적으로 Trace와 Span 이라는 개념을 사용한다.



클라이언트가 서버로 호출한 하나의 호출을 Trace라고 했을 때, 서비스 컴포넌트간의 호출을 Span이라고 한다.각 서비스 컴포넌트들은 하나의 클라이언트 호출을 추적하기 위해서 같은 Trace Id를 사용하고, 각 서비스간의 호출은 각각 다른 Span Id를 사용한다. 이렇게 함으로써 전체 트렌젝션 시간을 Trace로 추적이 가능하고, 각 서비스별 구간 시간은 Span으로 추적할 수 있다.

솔루션

이러한 분산 로그 추적을 위한 솔루션 중에 오픈소스로는 트위터에서 개발된 ZipKin(https://zipkin.io/) , Jagger(https://jaeger.readthedocs.io/en/latest/) , Opencensus(https://opencensus.io/) 등이 있는데, 이러한 분산 로그 추적은 구글의 Dapper 논문을 기초로 디자인 되어 개발되었다.

Zipkin

그 중에서, 가장 활성화 되어 있는 오픈소스 중 하나가 Zipkin인데, 오픈 소스 생태계가 활발해서 플러그인이나 부가적인 도구들이 많다.

전체적인 구조는 다음과 같다.


<그림 . Zipkin 아키텍쳐 >


지원 프로토콜

Zipkin으로 추적할 수 있는 분산 트렌젝션은 HTTP를 기본으로 지원하고 , 이외에도 많이 사용되는 리모트 프로토콜인 gRPC를 함께 지원한다.

클라이언트 라이브러리

Zipkin 클라이언트 SDK는 https://zipkin.io/pages/existing_instrumentations 에 있는데, Zipkin에서 공식적으로 지원하는 라이브러는 아래와 같이 C#, Go, Java, Javascript,Ruby,Scala 등이 있다.




이외에도 오픈 소스 커뮤니티에서 지원하는 라이브러리로 파이썬, PHP등 대부분의 언어가 지원이 가능하다.

Zipkin 라이브러리는 수집된 트렌젝션 정보를 zipkin 서버의 collector 모듈로 전송한다. 이 때 다양한 프로토콜을 사용할 수 있는데, 일반적으로 HTTP를 사용하고, 시스템의 규모가 클 경우에는 Kafka 큐를 넣어서 Kafka 프로토콜로 전송이 가능하다.

스토리지

Zipkin 클라이언트 SDK에 의해서 전송된 정보는 스토리지에 저장된다.

사용할 수 있는 스토리지는 다음과 같다

  • In-memory

  • MySQL

  • Cassandra

  • Elastic Search

메모리는 별도의 스토리지 설치가 필요없기 때문에 간단하게 로컬에서 테스트할 수 있는 정도로 사용하는 것이 좋고, MySQL은 소규모 서비스에 적절하다. 실제로 운영환경에 적용하려면 Cassandra나 Elastic Search를 저장소로 사용하는 것이 바람직하다.

대쉬 보드

이렇게 수집된 정보는 대쉬 보드를 이용하여 시각화가 가능하다. Zipkin 서버의 대쉬보드를 사용할 수 있고, Elastic Search 백앤드를 이용한 경우에는 Kibana를 이용하여 시각화가 가능하다.


Spring Sleuth

Zipkin 라이브러리 중에서 주목해서 살펴볼 부분은 Spring / Java 지원인데, Spring에서 Sleuth라는 모듈 이름으로 공식적으로 Zipkin을 지원하기 때문에, Spring (& Springboot) 연동이 매우 쉽다.

자바 애플리케이션에서 Trace 정보와 Span 정보를 넘기는 원리는 다음과 같다.


여러개의 클래스의 메서드들을 거쳐서 트렌젝션이 완성될때, Trace 정보와 Span 정보 Context가 유지가 되어야 하는데, 자바 애플리케이션에서는 쓰레드마다 할당되는 쓰레드의 일종의 전역변수인 Thread Local 변수에 이 Trace와 Span Context 정보를 저장하여 유지한다.


분산 트렌젝션은 HTTP나 gRPC로 들어오기 때문에, Spring Sleuth는 HTTP request가 들어오는 시점과 HTTP request가 다른 서비스로 나가는 부분을 랩핑하여 Trace와 Span Context를 전달한다.

아래 그림과 같이 HTTP로 들어오는 요청의 경우에는 Servlet filter를 이용하여, Trace Id와 Span Id를 받고 (만약에 이 서비스가 맨 처음 호출되는 서비스라서 Trace Id와 Span Id가 없을 경우에는 이를 생성한다.)

, 다른 서비스로 호출을 할 경우에는 RestTemplate 을 랩핑하여, Trace Id와 Span Id와 같은 Context 정보를 실어서 보낸다.



HTTP를 이용한 Trace와 Span 정보는 HTTP Header를 통해서 전달되는데


위의 그림과 같이 x-b3로 시작하는 헤더들과 x-span-name 등을 이용하여 컨택스트를 전달한다.

이렇게 ServletFilter와 RestTemplate을 Spring 프레임웍단에서 랩핑해줌으로써, 개발자는 별도의 트레이스 코드를 넣을 필요 없이 Spring을 이용한다면 분산 트렌젝션을 추적할 수 있도록 해준다.


다음글에서는 실제로 Spring Sleuth와 Zipkin을 이용하여 분산로그를 추적하는 예제를 구현해보도록 하겠다.


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

조대협 (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 를 참고