이 블로그는 마이크로서비스패턴 (길벗) 책 내용을 스터디를 위해 정리 한 내용 입니다.
책구매는 바로가기 에서 구매 가능합니다.
서비스가 배포되기 위해서는 보안, 구성성, 관측성이 보장 되어야 한다. 이번 장에서는 서비스가 정상적으로 배포되어야 할 요소들을 어떻게 제공하는지에 대해서 알아본다.
보안서비스 개발
애플리케이션 개발자는 주로 다음 네가지의 보안 요소를 구현한다.
- 인증 : 애플리케이션에 접근하는 애플리케이션이나 사람의 신원을 체크한다. 일반적으로 인증키나 로그인 계정과 비밀번호를 사용한다.
- 인가 : 어떤 주체가 요청을 보내왔을 경우 해당 요청을 수행할 수 있는 권한이 있는지 체크 한다.일반적으로 역할기반 (ROLE) 이나 접근제어리스트(ACL Access Control List) 를 함께 사용한다.
- 감사 : 보안이슈탐지, 컴플라이언스 시행 등, 고객지원을 위해 주체가 수행하는 작업을 추적한다.
- 보안 IPC : 드나드는 모든 요청은 TLS 를 경유 하도록 한다.
기존 모놀리식 애플리케이션의 보안
기존 모놀리식 애플리케이션에서 사용자의 인증은 ID/패스워드 방식으로 진행한다. 사용자(클라이언트)가 ID/패스워드를 애플리케이션에 POST 로 요청한다. 애플리케이션은 요청온 자격증명이 맞으면 클라이언트에게 세션토큰을 반환한다. 세션 토큰을 받은 클라이언트는, 다음 요청서부터 세션 토큰을 포함시켜 서버로 전송시킨다. 애플리케이션은 이 HttpSession 객체를 메모리에 보관 해두고 있다가, 클라이언트의 요청에 포함되어있는 session tocken 를 식별해 인증한다.
보안 컨텍스트(Security Context) 는 인증된 정보를 저장하는 것을 말한다. 애플리케이션은 현재 요청을 보내온 사용자 정보 인증 정보를 ThreadLocal 을 이용해 정적 영역에 저장한다. 이러면 요청 내에서 언제든지 사용자의 정보를 불러내 확인할 수 있다.
- 클라이언트가 애플리케이션에 로그인 요청을 한다
- Loginhandler 가 자격 증명을 확인하고, 세션을 생성하고 세션에 주체 정보 저장등 일련의 로그인 요청 처리를 진행한다.
- Loginhandler 가 클라이언트에 세션 토큰을 반환한다. (클라이언트는 세션 토큰을 쿠키에 저장한다)
- 클라이언트는 이후 모든 작업 요청시 세션토큰을 포함해 요청한다.
- SessionBased Security Interceptor 가 요청을 받아 세션 토큰을 확인한 후, 보안 컨텍스트를 설정한다.
- 요청 핸들러는 보안 컨텍스트를 이용해 사용자가 권한이 는지 판단하고 사용자의 신원을 획득한다.
마이크로서비스 아키텍처에서의 보안 구현
마이크로서비스는 각 서비스가 분리되어 있기떄문에 보안을 구현하려면 사용자의 인증/인가를 누가 담당할지 부터 정해야 한다. 또 서비스가 분리되어 있으므로, 모놀리식에서 가능했던 다음 두가지 보안요소는 마이크로 서비스에서 구현이 어렵다.
- 인-메모리 보안 컨텍스트 (in-memory security context) : Threadlocal 등을 인-메모리 보안컨텍스트를 이용해 사용자의 신원을 전달하는 방법이 있으나, 마이크로 서비스에서는 메모리가 공유되지 않으므로 사용할 수 없다.
- 중앙화 세션 (centralized session) : 인-메모리 세션도 보안컨텍스트와 마찬가지로 사용할 수 없다. 우회적으로 마이크로서비스의 느슨한 결합 원칙에 위배되지만, DB에 세션정보를 넣고 각 서비스가 동일하게 접근할 수도 있다.
API 게이트웨이에서 인증 처리
인증을 처리하는 방법은 여러가지나, 개별 서비스에서 인증을 구현할 경우 다음과 같은 문제가 생긴다.
모든 개발자가 제대로 보안을 개발 하리라는 보장이 없기때문에 보안적 취약점이 발생 될 수 있다. 이로인해 미인증 요청이 발생할 수 있다. 또, 클라이언트가 매번 자격증명을 전송한다던지, 애플리케이션이 매번 신규토큰을 생성 할 수 도 있다.
때문에 가장 이상적인 방법은 API 게이트웨이를 이용하는 방식이다. 인증에 문제가 생기면 API 게이트웨이만 바로잡으면 되고, 개별 서비스에서 구현하는 로직을 게이트웨이에만 작성하면 된다.
API 게이트웨이로부터 호출받은 서비스는 게이트웨이로부터 받은 토큰으로 요청을 한번더 검증한다.
인가 처리
인가작업은 클라이언트가 요청한 작업이 권한이 있는지 검사하는 작업이다.
이 작업역시 API 게이트웨이에서 구현 가능하다. 권한이 없는 요청 인입시 라우팅 전에 거부하면 그만이다.
하지만 API 게이트웨이에 인가로직을 두면 URL 등을 게이트웨이가 직접 챙겨야 하므로 게이트웨이와 서비스가 강한 결합이 발생 하게 된다.
따라서 인가로직은 서비스에 구현하는 편이 낫다. 서비스가 역할기반으로 URL 과 METHOD를 인가하고, ACL로 애그리거트 접근을 따로 관리한다.
JWT 로 사용자 신원/역할 전달
게이트웨이가 서비스로 토큰 을 전달시에 어떤 방식의 토큰을 사용할지 결정이 필요하다.
- 난독화 토큰 (opaque token) : 일반적으로 UUID (Universally Unique Identifier) 를 많이 사용한다. 단, 성능 및 가용성이 떨어지고, 지연시간이 길다.
- 투명 토큰 (transparent token) : JWT (Json Web Token) 이 사실상 표준이다. 두 당사자간의 신원/역할등 정보를 안전하게 보관 가능하다. JWT 는 인증정보, 토큰의 만료일자 등이 담긴 JSON 객체를 payload 에 담아 JWT 생성자(게이트웨이) 와 수신자 (서비스) 만 알수있는 코드로 암호화한다. 위변조가 불가능해 보안성이 강력하다. 그러나, JWT 에 토큰이 포함되어 있으므로 취소가 불가능해 토큰이 유출 될 경우 문제가 발생한다. 때문에 유효기간을 가능한 짧게 생성해 유출된 토큰의 오용을 막는다.
OAuth 2.0 응용
결국 마이크로서비스 인증/인가는 자격증명 등의 사용자 정보를 DB로 관리하는 사용자 서비스를 만들고, API게이트웨이는 이 사용자 서비스를 호출해 클라이언트 요청을 인증하고, JWT 를 획득 하는 로직을 구현하면 된다.
이런 로직은 OAuth 2.0 표준으로 제대로 구현된 서비스나 프레임워크로 이미 구현 되어 있다. 가져다 쓰면된다.
OAuth 2.0 의 핵심 개념은 다음과 같다.
- 인증서버 (Authorization Server) : 사용자 인증 및 액세스/리프레시 토큰 획득 API 를 제공한다.
- 액세스 토큰 (Access Token ) : 서비스 접근을 허가하는 토큰이다.
- 리프레시 토큰 (Refresh Token) : 클라이언트가 새 액세스 토큰을 얻기 위해 필요한 토큰이다.
- 리소스 서버 (Resource Server) : 액세스 토큰으로 접근을 허가하는 서비스. 곧 마이크로서비스를 제공하는 서비스를 말한다.
- 클라이언트 : 리소스서버에 접근하려는 클라이언트.
API 클라이언트의 요청을 API 게이트웨이가 인증하는 과정이다.
- 클라이언트는 자격 증명 (ID/PASSWORD) 를 포함해 요청한다.
- API 게이트웨이는 OAuth 2.0 인증서버에 자격증명을 전달해 승인을 요청한다.
- 인증서버는 자격증명을 검증하고 액세스/리프레시 토큰을 반환한다.
- API 게이트웨이는 전달 받은 토큰과 함께 각 서비스로 요청을 보내고, 각 서비스는 토큰으로 요청을 인증한다.
API 게이트웨이가 OAuth 2.0 프로토콜을 이용하여 세션 지향 클라이언트를 처리하는 과정이다.
- 로그인 기반의 클라이언트가 자격증명을 API 게이트웨이에 POST 요청한다.
- API 게이트웨이의 로그인 핸들러는 OAuth 2.0 인증서버에 승인을 요청한다.
- 인증서버는 자격증명 검증후 엑세스/리프레시 토큰을 반환한다.
- api 게이트웨이는 전달받은 두 토큰을 클라이언트에 (쿠키 포멧등) 으로 반환한다.
- 클라이언트는 이후 요청시 API 에 엑세스/리프레시 토큰을 실어 보내 요청한다.
- API 게이트웨이는 토큰을 검증후, 서비스에 토큰을 전달한다.
구성가능한 서비스 설계
서비스가 수행되려면 카프카의 네트워크 위치, DB 의 접속정보, 자격증명등 다양한 프로퍼티 들이 필요하다. 또 이 프로퍼티는 수행 환경, 예를들면 개발, QA, 스테이징, 운영등의 환경에서 정보가 각각 다르다. 때문에 적절하게 이 환경 구성정보를 관리 해야 하는데, 하드코딩으로 작성해 관리를 한다거나 런타임 프로퍼티를 이용해 resource 를 만들어 관리한다면 보안에 취약하고 배포에 한계가 있을수 있다. 떄문에 환경 정보는 외부에 보안저장장치로 안전하게 저장하고, 런타임시 적합한 값을 제공해야 한다.
- 푸시 모델 (Push Model) : 배포인프라가 서비스로 프로퍼티를 전달
- 풀 모델 (Pull Model) : 서비스가 구성서버에서 프로퍼티를 가지고옴
푸시기반의 외부화 구성
푸시기반의 외부화 구성은, 배포환경에서 서비스 인스턴스가 생성될때 프로퍼티를 제공하는 방식이다.
단, 이미 수행중인 환경에서는 프로퍼티값을 변경할 수 없다. 꼭 재시작이 필요하다.
Spring Boot 는 소스의 우선순위대로 프로퍼티값을 가지고 올 수 있다.
- CLI 인수
- OS환경변수, 혹은 JVM 시스템 프로퍼티
- JVM 시스템 프로퍼티
- OS 환경변수
- 현재 디렉토리 구성파일
동일한 속성의 파라메터 명이면 우선순위가 앞선 값을 적용한다.
풀 기반의 외부화 구성
풀 모델은 서비스가 시작될때 구성전용 서버에 접속해서 필요한 프로퍼티값을 가지고 온다.
구성서버는 다음과 같은 종류가 있다.
- 버젼관리 시스템
- SQL/NoSQL DATABASE
- 전용구성서버 (Spring Cloud Gateway) 등
구성 서버의 장점은 다음과 같다.
- 중앙화 구성 : 프로퍼티들을 한곳에 관리해 편리하고 간편하다. 중복데이터를 줄일 수 있다.
- 민감한 데이터의 투명한복호화 : DB 자격증명등 민감한 정보들을 암호화 하고, 서비스에 전달할때 복호화해 전달 함으로써 보안성이 뛰어나다.
- 동적 재구성 : 수정된 프로퍼티값을 자동으로 전파해 갱신한다.
관측 가능한 서비스 설계
서비스가 운영중일때 다양한 상태 정보를 알아야 사용자에게 영향을 끼치기 전에 미리 그 사실을 유추해 낼 수 있다.
다음은 이런 관측 가능한 서비스를 위한 설계 패턴이다.
헬스 체크 API 패턴
서비스는 자신이 구동시, 서비스 준비중일때는 요청 처리를 할 수 없다. 또 장애시에도 마찬가지다. 이럴때를 위해 자신의 상태를 배포 인프라에 알려야 한다.
헬스 기능을 구현할 때에는 서비스 인스턴스의 상태를 보고하는 끝점을 어떻게 구현할 것인지, 배포인프라는 끝점을 어떻게 호출할지 고려해야 한다.
헬스체크 끝점구현
헬스 체크 끝점을 구현한 코드는 서비스의 상태를 판단해야 한다. DB 커넥션의 정상 여부는 쿼리등을 호출 하면된다. Spring Boot Actuator 는 이러한 헬스 체크를 대신 해준다. /acuator/health 을 호출하면 http staus 200 / 500 으로 상태를 반환한다. 예를 들어 JDBC 로 테스트 쿼리를 실행해 상태를 체크하며, RabbitMQ 상태도 체크 한다.
헬스체크 끝점호출
당연하게도 끝점을 만들어놓고 체크하지 않으면 안된다. 배포시스템은 배포후 끝점을 호출해 서비스의 정상 여부를 확인한다.
로그 수집 패턴
마이크로 서비스에서 각각 서비스가 남긴 로그는 각 서버 개별적으로 확인도 힘들고, 클라우드 환경에서 각기 다른 프로세스의 로그를 확인하기 쉽지 않기때문에 로그수집 파이프라인을 이용해 로깅 서버로 보내야 한다.
로깅 서버는 로그를 간편하게 확인할 수 있고, 알림전송도 가능하다. 하지만 로그를 남기는 코딩은 개발자의 몫이다.
서비스로그 생성
서비스 로그는 Log4J, JUL, SLF4J 등 다양한 로깅 라이브러리가 존재 한다. 이런것들을 이용해 소스 곳곳에 로그를 남기도록 코딩한다. 로깅 라이브러리를 남길수 없다면 API를 이용할 수 도 있다. AWS 람다 처럼 영구적 파일 시스템이 없다면 그냥 stdout 으로 로깅한다.
로그수집 인프라
로깅 인프라는 로그를 수집, 저장한다.
대표적인 로깅 인프라로는, 일래스틱서치, 로그 스태시, 키바나가 있다.
분산 추적 패턴
서비스의 성능과 관련된 지표를 수집해 문제가 발생했을때 적절하게 대처할 수 있게 한다. 이때 수집되는 지표에 시작, 종료시간등을 넣어 어느지점에서 얼마나 수행되었는지 체크 한다.
수집된 지표는 트래이스(trace) 로 시각화하고, 트래이스 내의 하위 API 호출이 발생한 스팬(span)을 통해 중첩 호출된 각각의 API 성능 지표를 분석한다.
인스트루멘테이션 라이브러리
인스트루멘테이션 라이브러리는 스팬 트리를 만들어 수집서버로 보낸다. 이 라이브러리는 소스에서 직접 호출 할수 있으나 AOP등을 이용하면 쉽게 코딩 가능하다.
분산 추적서버
분산추적서버는 인스트루멘테이션 라이브러리가 보낸 스팬정보를 짜집기해 완전한 트래이스로 만들어 DB에 저장한다.
애플리케이션 지표 패턴
서비스는 수집, 시각화, 알림등을 제공하는 중앙서버로 수집된 지표를 전송한다. 수집되는 지표는 인프라 수준의 CPU, 메모리 사용율 부터 애플리케이션 수준의 서비스요청시간, 실행수 등까지 다양하다. 수집된 지표로 모니터링과 알림을 서비스한다. 이 지표는 지표명, 수치값, 시간을 표함해 전송해야 한다.
이런 수집을 위해 개발자는, 지표가 수집가능될 수 있도록 로직을 구현해야 하며, 지표를 서버로 전송하는 로직도 함께 구현 해야 한다.
지표서비스에 지표 전달
지표를 지표서비스에 전달하는 방식은 PULL 과 PUSH 방식이 있다.
PULL 방식은 지표서버가 서비스 API를 호출해 지표를 당겨오는 방법으로 대표적으로 프로메테우스가 있다.
PUSH 방식으로는 서비스 인스턴스가 직접 API를 호출히 지표를 밀어 넣는 방식이다.
예외 추적 패턴
예외 (Exception)는 문제의 근본 원인을 식별하는데 중요한단서이며, 버그의 징후일수 있다. 그래서 중복된 예외를 제거하고 알림을 생성하고,해결과정을 관리하는 예외 추적 서비스를 따로 두는것이 좋다. 예외가 발생하면 API 로 예외 추적 서비스에 전송하는 방식이다.
감사 로깅 패턴
감사로깅 패턴은 사용자의 행동(action) 을 기록하는 것이다. 일반적으로 사용자의 고객지원, 컴플라이언스 준수, 수상한 동작 감시 등의 용도로 사용된다.
AOP 의 advice를 이용해 한꺼번에 구현할 수도 있지만 호출된 메서드와 인수만 알수 있어 액션을 비즈니스적인 액션을 감시하는건 어렵다. 그렇기때문에 이벤트 소싱을 이용하는 방법이 있다.
서비스개발 : 마이크로서비스 섀시 패턴
마이크로서비스 섀시란, 예외추적, 로깅 헬스체크, 분산추적등 횡단 관심사에 특화된 프레임워크(들) 을 말한다. 마이크로서비스 섀시 기반으로 서비스를 구축하면 횡단 관심사를 처리하는 로직을 추가 개발할 필요없이 서비스개발에 집중 할 수 있다.
마이크로서비스 섀시
마이크로서비스 섀시 프레임워크는 외부화구성, 헬스체크, 애플리케이션지표, 디스커버리, 회로차단기, 분산추적등을 처리한다.
스프링부트는 외부화구성, 스프링 클라우드는 회로차단기 같은 기능을 제공한다.
이제는 서비스 메시로
마이크로서비스 섀시는 다양한 횡단 관심사를 구현하기는 좋지만 언어마다 달리 개발해야 하는 단점이 있다. 떄문에 일부서비스를 외부에 외치한 메시에 구현한다.
서비스 메시는 서비스와 서비스, 외부 애플리케이션간의 소통을 조정하는 인프라이다. 모든 네트워크 트래픽은 TLS 기반의 IPC로 회로차단, 분산추적 룰기반 라우팅등의 관심사가 구현된 서비스 메시를 통과 한다. 때문에 공통 기능 서비스를 직접 구현할 필요가 없다. 메시 덕분에 마이크로서비스 섀시는 외부화구성, 헬스체크 등의 간단한 기능만 구현하면 된다.