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


Archive»


 
 

API 게이트 웨이

What is API 게이트웨이

API 게이트웨이는 API 클라이언트와 서비스 사이에 프록시 처럼 위치하여, 클라이언트로 부터 온, API 요청을 받아서, 서비스로 라우팅 하는 역할을 한다. 

각각의 서비스에서 구현해야 하는 기능을 API 게이트웨이단으로 통합함으로써, 서비스 개발을 간편하게 할 수 있고, 메세지 포맷 변경이나, 플로우 컨트롤과 같은 고급 기능을 사용하여, 기존 API 호출에 추가적인 기능을 더할 수 있는 유연성을 더할 수 있게 된다.


여러가지 기능이 있겠지만, 크게 아래와 같이 5가지로 나눠볼 수 있다.

  • 인증

  • 모니터링,로깅

  • 플로우 컨트롤

  • 메시지 변경

  • 오케스트레이션(매쉬업)





<출처 : 마이크로소프트 애저 홈페이지>


인증

API를 호출할때, API 호출에 대한 인증을 수행한다. 서버간의 API 통신일 경우에는 간단하게 API 키를 사용하는 방법등이 있고, 다수의 클라이언트 (모바일 단말, 웹 자바스크립트 클라이언트)의 경우에는 사용자 계정 인증 후에, JWT 토큰을 발급하는 방식이 있다. 

모니터링 & 로깅

API 호출에 대한 모니터링을 통해서 API 별 응답시간, 장애율,호출 횟수를 모니터링 할 수 있게 하고, 경우에 따라서 개별 API 호출을 로깅한다. 

Metering & Charging

API를 대외로 서비스 하는 경우, API의 호출량을 모니터링 해야 하는데, 이 양을 기반으로 해서 API 서비스 호출 횟수를 통제하거나 또는 유료 API의 경우에는 과금을 해야 하기 때문에, 대외로 서비스를 하는 오픈 API 플랫폼인 경우에는 이러한 기능이 필요하다. 

메시지 플로우 컨트롤

메시지 플로우 컨트롤은 클라이언트로 부터 들어온 메시지의 흐름을 바꾸는 방법인데, 예를 들어 같은 API라도 버전에 따라서 구버전과 새버전으로 트래픽을 9:1로 라우팅하는 카날리 배포 방식이라던지, 들어온 API를 클라이언트의 위치에 따라 미국이나,한국에 있는 서비스로 라우팅을 하는 등의 로직을 구현할 수 있다. 

Limiting (throttling)

조금 더 고급화된 구현방법으로는 Limiting 기법이 있는데, 특정 API에 대해서 정해진 양 이상의 트래픽을 받지 못하도록 하여, 서비스를 보호하거나 또는 유료 API 서비스인 경우 QoS (Quality Of Service)를 보장하기 위해서 특정 양까지만 트래픽을 허용하도록 컨트롤할 수 있다. 

메시지 변환

메시지 변환은 고급 기능 중의 하나인데, 헤더에 있는 내용을 메시지 바디로 붙이거나, 다른 API를 호출해서 응답메시지를 두 API를 합해서 응답을 해주는 기능등을 구현할 수 있다. 

프로토콜 변환

그외에도 서로 다른 프로토콜에 대한 변환이 가능한데, 예를 들어 클라이언트로 부터는 gRPC로 호출을 받고, API서버로는 REST로 호출한다던가. SOAP/XML 기반의 메시지를 REST로 변환하는 등의 기능 구현이 가능하다. 

호출 패턴 변환

API 서버가 큐를 기반으로한 비동기 메시지 처리 패턴일때, API 게이트 웨이에서 이를 동기화 처리 패턴으로 바꿔서, 클라이언트 입장에서는 동기로 호출을 하면, API 서버에서는 비동기로 호출하는 형태와 같이 호출 패턴을 변화시킬 수 있다. 

오케스트레이션(매시업)

API 클라이언트가 한번 호출을 할때, 동시에 여러개의 API를 호출하도록 API 게이트웨이에서 호출을 해주는 방식이다. 예를 들어 API 클라이언트가 “상품 구매" 라는 API를 호출 하였을때, API 게이트웨이가 “상품 구매" API 서비스를 호출하고, “상품 추천" 이라는 API도 호출해서, 클라이언트로 구매 완료 메시지와 함께 추천 상품 리스트를 한번에 리턴하는 방식이다. 


경량형 게이트웨이와 중량형 게이트웨이

API 게이트 웨이는 근래에 마이크로 서비스가 각광을 받으면서 언급되기 시작하고 있지만, 사실 API 게이트 웨이라는 기술 자체는 오래된 기술이다. API 게이트 웨이 이전에는 SOA 제품군의 일부로 ESB (Enterprise Service Bus)가 SOAP/XML 웹서비스 기반으로 이러한 역할을 해주었는데, 잘못된 구현 패턴을 사용해서 문제가 된 케이스가 많다. 예를 들어서 과도하게 오케스트레이션 기능을 사용하였을 경우 API 게이트웨이 자체에 부하가 걸려서 전체 API 서비스에 성능 문제를 야기 하거나, 또는 파일 업로드 요청을 API 게이트 웨이를 통해서 함으로써 게이트웨이의 부하때문에 성능 문제가 생기는 경우가 있었다. 

SOAP/XML에서 REST API로 오면서, API 게이트웨이도 이러한 문제를 피하는 패턴형식으로 API 게이트 웨이가 포지셔닝이 달라졌다. 사실 매시업이나, 메시지 라우팅, 메시지 변환등과 같은 기능들은 사실 사용되는 일이 그렇게 많지 않고, 클라이언트단에서 처리해도 충분하기 때문에, 이러한 고급 기능 보다는 API 인증, 로깅/모니터링 위주의 최소한의 기능만 가지면서 경량화된 형태로 배포 및 운영이 간편한 경량형 API 게이트 웨이들이 소개 되기 시작하였고, 반대로 오픈 API를 고객을 대상으로 제공하면서 메시지 변환이나, 과금등 고급 기능을 제공하는 중량형 API 게이트 웨이들도 꾸준히 제품들이 제공되고 있다.


이러한 제품들에 대한 명시적인 구분은 없지만, 필자의 기준으로 봤을때, 경량형 API 게이트 웨이는 Kong등을 들 수 있고, 중량형 API 게이트 웨이는 APIgee, MuleSoft 등을 들 수 있다. 


전통적으로 중량형 API 게이트웨이의 아키텍쳐 구조는 API 게이트웨이가 클러스터 형태로 배포 된 후에, 그 뒤에 API 서버들이 배포되는 형태라면, 경량형 API 게이트웨이는 nginx나 node.js와 같은 가벼운 프록시 서버로 배포하고, 용량이 필요하다면 개별의 인스턴스들을 스케일링으로 늘리는 방법을 사용한다. 근래에는 중량형 API 게이트 웨이도 이러한 구조를 많이 참고해서 구조를 개선하고 있다. 

쿠버네티스와 API 게이트 웨이

이러한 API 게이트웨이들이 쿠버네티스의 등장과 함께 주목 받으면서, 쿠버네티스 지원을 넓혀나가고 있는데, 이 과정에서 흥미로운 아키텍쳐 구성을 보인다. 쿠버네티스는 이미 Service라는 리소스를 통해서 마이크로 서비스를 정의할 수 있고, Service를 묶어서 로드밸런싱을 하는 Ingress라는 리소스를 가지고 있다. 즉 Ingress는 Service간의 라우팅을 할 수 있는 일종의 초경량 API 게이트웨이로써의 기능을 가지는 구조인데, API 게이트웨이의 쿠버네티스 연동은, 새롭게 애플리케이션을 배포하고 설정하는 방식이 아니라 쿠버네티스 리소스 자체에 API 게이트웨이를 녹이는 방식으로 접근하고 있다.


아래 Kong API 게이트웨이의 구조를 보면, Kong 전용으로 컨테이너를 배포하지 않고, Kong API 게이트웨이용 Ingress를 정의해서 사용자가 Ingress를 사용하게 되면 API 게이트웨이를 사용할 수 있게 하였고, 메시지 변환이나 기타 기능이 필요할 경우 이를 CRD(Custom Resource Definition) 로 미리 등록해놔서 이를 사용하도록 하고 있다. 



<Kong API 게이트 웨이 아키텍처 출처  > 


구글의 Apigee의 경우에는 Apigee Hybrid라는 이름으로 Kubernetes를 지원하는데, 구조는 Istio Ingress를 앞단에 둬서 라우터로 이용하고 API 게이트웨이에서 처리하는 각종 로직을 Ingress 뒤에 Pod(아래 그림 4번)로 배포하여 기능을 수행하도록 하고 있다. 

API 호출을 통과 시키는 프록시는 이렇게 단순화 시키고, 관리 콘솔은 구글 클라우드로 위치 시켜서 운영을 최대한 단순화 시켰다. 


<출처 : 구글 apigee hybrid 문서 > 

구글의 API 게이트 웨이 - Cloud Endpoint

구글의 Cloud Endpoint는 경량형 API이다. 

기본적으로 nginX로 배포되서 프록시 처럼 동작하며, 기능은 API 인증과 모니터링/로깅정도 밖에 없지만 구조와 기능이 단순하기 때문에 사용이 편리하며, 또한 nginX 기반의 배포 방식으로 인해서 구글 클라우드 뿐만 아니라, 다른 클라우드나 ON-PREM에도 사용이 가능하다는 장점이 있다. 


구조는 다음과 같이 되어 있다.



<그림. Cloud Endpoint ESP 아키텍쳐 / 출처 >


ESP는 Cloud Endpoint의 Proxy, 즉 nginX를 이야기 한다. 프록시는 실제로 쿠버네티스 클러스터에 배포되고, 모니터링과 로깅, 컨트롤러는 구글 클라우드의 콘솔을 이용하게 된다. 

쿠버네티스에 배포하는 형태는 다음 그림과 같다. 

애플리케이션 컨테이너와 함께, 같은 Pod로 패키징 되고, ESP에서는 애플리케이션으로 트래픽을 localhost로 포워딩한다. 


<그림. ESP의 쿠버네티스 배포 형태 >


이런 구조를 가지기 때문에, Ingress, Istio 등 다른 쿠버네티스 플랫폼들과 유연하게 통합이 된다. 

API 인증은 여러가지 방법을 지원하는데, Firebase authentication, Google ID,SAML(SSO) 방식으로 사용자를 인증한후에, API 키(JWT)를 발급하는 방법이나, 서버간일 경우 Service Account를 사용하는 방법 또는 단순하게 API KEY를 사용하는 방법이 있고 또는 Custom 인증을 구현하여 JWT 토큰으로 인증할 수 있다. 인증 방식은 이 문서를 참고하면 된다.

로깅/모니터링

로깅과 모니터링은 End Point 콘솔에서 볼 수 있으며, 아래와 같이 서비스 단위로 응답 시간과 에러율등을 출력해준다


OPEN API (Swagger) 기반 포탈

그리고 중요한 기능중의 하나인데, API 스펙 문서를 포탈 형태로 보여준다. Swagger 문서 형태로 작성을 하면 아래와 같이 독립된 웹사이트 형태로 API 스펙 문서를 보여준다.


<그림. Cloud endpoint developer portal / 출처


멀티/하이브리드 클라우드

마지막으로, Cloud End Point는 ngnix 기반으로 배포가 가능하기 때문에, 구글 클라우드 뿐 아니라 타 클라우드나 온프렘 데이타 센터내에도 프록시(ESP)를 배포해서 사용하는 것이 가능하다. 


참고 문서


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

오픈소스 API 게이트웨이 Kong

#2 아키텍쳐와 간단한 테스트

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

Kong 아키텍쳐

Kong API 서버의 배포 아키텍쳐는 다음과 같다. 


출처 : stackoverflow.com

Kong API 게이트웨이 각각 nginx 로 되어 있고, DB 모드의 경우에는 별도의 데이타 베이스 인스턴스를 사용한다. 지원되는 데이타 베이스로는 카산드라나 postgres를 사용할 수 있다. 

데이타 베이스를 사용하더라도 변경된 설정은 인스턴스에 바로 반영되지 않는다. 각 인스턴스가 설정 내용을 캐슁하기 때문인데, 캐쉬가 리프레쉬되어야 설정 내용이 반영된다. 

클러스터에서 Kong API 게이트웨이 인스턴스간의 로드 밸런싱을 하기 위해서는 각 인스턴스 앞에 로드밸런서를 배치 시킨다. 만약에 Session Affinity가 필요하다면 L4에서 IP나 Session based affinity를 설정해야 한다. 


Kong API 게이트웨이에서 각종 기능들은 nginX의 Lua 스크립트 모듈은 OpenResty를 이용한다. 

간단한 API 테스트

개념 이해가 끝났으면 간단한 테스트를 해보자

파이썬으로 간단한 API 서버를 구현하였다. app.py로 simpleapi:9001/api URL로 HTTP GET으로 간단하게 서빙하는 API가 있다고 할때


kong.yml을 다음과 같이 구현한다. 

_format_version: "1.1"


services:

- name: simpleapi

  url: http://simpleapi:9001

  routes:

  - name: simpleapi-route

    paths:

    - /simpleapiservice



simpleapi라는 이름으로 서비스를 등록하고 API 서버의 호스트 경로는 http://simpleapi:9001로 등록한다.

위의 설정은 {Kong IP}/simpleapiservice URL을 —> http://simpleapi:9001로 라우팅하도록 한 설정이다.

%curl localhost:8000/simpleapiservice/api


실행하면 http://simpleapi:9001/api 를 호출한 결과가 리턴된다. 


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

Spring Boot + slf4j + MDC + Zipkin

 

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

 

아래 예제는 MDC를 이용해서 여러 메서드간의 컨텍스트를 연결하는 것을 확장해서, 서로 다른 프로세스와 서버간에 로그를 연결하는 방법이다. 서로 다른 프로세스 또는 서버간에 컨텍스트를 전달하려면 HTTP 헤더등을 통해러 리모트로 컨텍스트를 전달해야 하는데, 이를 가능하게 하는 오픈소스로 Zipkin이 있다. (자세한 설명은 이글을 참고하기 바란다. )

Zipkin은 원래 분산 로그 추적용으로 개발된 오픈소스가 아니라 원래 목적은 분산 시스템에서 각 구간별 레이턴시(지연시간)을 측정해서 구간별 소요 시간을 측정하는 트레이스용도로 개발이 되었지만, 구간별 소요 시간을 측정하기 위해서는 각 개별 서비스를 연결해야 하기 때문에, 트레이스 ID가 필요하게 되었고, 트레이스 ID를 로그에 같이 저장하였기 때문에, 부가적인 효과로 분산 로그 추적에도 사용할 수 있다.

Zipkin을 Spring Boot와 연결하는 방법은 오픈소스인 Spring Sleuth를 이용하면 쉽게 된다.

 

아래 예제는 앞의 글인  Spring Boot에서 MDC를 사용하는 예제에 Zipkin 연동만을 추가한 예제이다.

Spring Boot로 간단한 REST API를 구현한후, 로깅을 하는 예제이다. 로거는 slf4j와 logback을 사용하였고,  MDC를 이용해서 userId와 같은 컨택스트 정보를 넘기도록 하였고, JSON 포맷으로 로그를 출력하였다. 마이크로 서비스와 같은 분산 서비스간에 로그 추적성을 제공하기 위해서 ZipKin 라이브러리를 사용하였다. 스프링에서는 ZipKin 라이브러리 통합을 Spring Sleuth를 통해서 지원하기 때문에, Spring Sleuth와 Zipkin을 연결하여 코드를 작성하였다. 전체 코드는 여기를 참고하면 된다.

 

아래는 Spring Boot에서 Zipkin을 사용하기 위해서 메이븐 pom.xml에 의존성을 추가한 내용이다.

<dependency>

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

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

</dependency>

<dependency>

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

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

<version>2.1.1.RELEASE</version>

</dependency>

<dependency>

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

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

<version>2.1.1.RELEASE</version>

</dependency>

<dependency>

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

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

<scope>test</scope>

</dependency>

<dependency>

<groupId>com.google.cloud</groupId>

<artifactId>google-cloud-logging-logback</artifactId>

<version>0.84.0-alpha</version>

</dependency>


<!-- slf4j & logback dependency -->

<dependency>

<groupId>ch.qos.logback.contrib</groupId>

<artifactId>logback-json-classic</artifactId>

<version>0.1.5</version>

</dependency>

<dependency>

<groupId>ch.qos.logback.contrib</groupId>

<artifactId>logback-jackson</artifactId>

<version>0.1.5</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.9.3</version>

</dependency>

<코드.pom.xml >

 

다음은 logback 로거를 사용하기 위해서 logback에 대한 설정을 한 logback.xml이다. JSON 포맷으로 로깅을 하도록 설정 하였다.

 

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

   <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">

       <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">

           <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">

               <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSX</timestampFormat>

               <timestampFormatTimezoneId>Etc/UTC</timestampFormatTimezoneId>

               <appendLineSeparator>true</appendLineSeparator>


               <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">

                   <!--

                   <prettyPrint>true</prettyPrint>

                    -->`

               </jsonFormatter>

           </layout>

       </encoder>

   </appender>


   <root level="info">

       <appender-ref ref="stdout"/>

   </root>

</configuration>

<코드. /resources/logback.xml >

 

Zipkin을 사용할 경우, 트레이스 정보를 zipkin 서버로 전송해야 하는데, 이를 위해서 zipkin 서버에 대한 정보를 설정해야 한다. 보통 zipkin 에이전트가 로컬에서 돌기 때문에, 포트만 지정하면 된다. 아래와 같이 zipkin 서버에 대한 포트를 8081로 지정하였고, 이 애플레케이션의 이름을 zipkin-server1으로 지정하였다. 이 예제에서는 zipkin을 분산로그 추적용으로만 사용하였기 때문에, 실제로 zipkin 서버 에이전트는 실행하지 않았다.

server.port = 8081

spring.application.name = zipkin-server1

<코드. /resources/application.properties >

 

다음은 Spring Boot의 REST API Controller 코드의 일부이다.

@RestController
@RequestMapping("/orders")
public class OrderController {
Logger log = LoggerFactory.getLogger("com.terry.logging.controller.OrderController");
@RequestMapping(value="/{id}",method=RequestMethod.GET)
public Order getOrder(@PathVariable int id,
      @RequestHeader(value="userid") String userid) {
  MDC.put("userId", userid);
  MDC.put("ordierId",Integer.toString(id));
  Order order = queryOrder(id,userid);
  log.info("Get Order");
  MDC.clear();
  return order;
}

Order queryOrder(int id,String userid) {
  String name = "laptop";
  Order order = new Order(id,name);
  order.setUser(userid);
  order.setPricePerItem(100);
  order.setQuantity(1);
  order.setTotalPrice(100);
  log.info("product name:"+name);
  return order;
}

<코드. /resources/application.properties >

 

Spring Sleuth를 사용하게 되면 자동으로 Zipkin 코드를 의존성 주입 (Dependency Injection)을 이용해서 코드에 삽입해주는데, 이때 몇가지 제약사항이 있다. Spring Boot로 들어오는 트래픽은 Servlet Filter를 통해서 의존성 주입을 하는데, Spring Boot에서 다른 서비스로 나가는 트래픽의 경우에는 Rest Template 이나, Feign Client 와 같은 특정한 방법만을 지원한다. 지원되는 라이브러리의 범위에 대해서는 이 링크를 참고하기 바란다.

 

위의 예제는 HTTP Header에서 들어온 userId를 MDC 컨텍스트에 저장하는 예제이다.

위의 REST 서비스를 호출해보면 다음과 같은 결과가 나온다.

<그림. PostMan을 통해서 REST 요청과 응답을 받은 화면 >

 

그리고, 호출후에 나온 로그는 다음과 같다.

 

{  

  "timestamp":"2019-04-14T05:49:52.573Z",

  "level":"INFO",

  "thread":"http-nio-8081-exec-1",

  "mdc":{  

     "traceId":"270b7b7b5a8d4b5c",

     "spanId":"270b7b7b5a8d4b5c",

     "spanExportable":"false",

     "X-Span-Export":"false",

     "ordierId":"1",

     "X-B3-SpanId":"270b7b7b5a8d4b5c",

     "X-B3-TraceId":"270b7b7b5a8d4b5c",

     "userId":"terry"

  },

  "logger":"com.terry.logging.controller.OrderController",

  "message":"Get Order",

  "context":"default"

}

<코드. /resources/application.properties >

 

위의 결과와 같이 MDC 부분에, Zipkin이 자동으로 traceId를 선언해서 삽입해 준다. MDC에 저장한 userId도 위처럼 한꺼번에 출력되는 것을 확인할 수 있다.

 

Spring Sleuth는 slf4j를 사용하는 경우에만 MDC 컨텍스트에 트레이스 ID를 넣어주기 때문에, 다른 자바 로깅 프레임웍을 slf4j없이 사용하는 경우 자동으로 트레이스 ID를 넣어주지 않기 때문에 이점을 주의하기 바란다.

(참고 : "Adds trace and span ids to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator.")

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요