애플리케이션 구성 정보를 코드에서 분리하는 것은 매우 중요하다. 대개 이 말은 코드에 하드코딩된 값을 사용하지 않도록 하라는 의미이다. 이 원칙을 무시하면 구성 정보가 변경될 때마다 애플리케이션을 재컴파일하고 재배포해야 하므로 애플리케이션이 복잡해진다.
애플리케이션 코드에서 구성 정보를 완전히 분리하면 개발자와 운영자는 재컴파일 없이 구성 정보를 변경할 수 있다. 그러나 이로 인해 개발자에게는 애플리케이션과 함께 관리하고 배포해야 할 추가적인 산출물이 생기므로 복잡성이 증가한다.
많은 개발자들이 프로퍼티 파일(YAML, JSON, XML)을 사용해 구성 정보를 저장한다. 이러한 파일을 통해 애플리케이션 구성을 설정하는 작업은 간단하고, 대부분의 개발자는 구성 파일을 소스 제어 시스템에 추가하고 애플리케이션 하위 부분으로 배포하는 것 이상을 하지 않는다. 소규모 애플리케이션에는 효과적일 수 있지만, 수백 개의 마이크로서비스가 다양한 인스턴스에서 실행되는 클라우드 기반 애플리케이션에서는 이 방식이 문제를 일으킬 수 있다. 이렇게 되면 처음에는 단순하던 과정이 갑자기 큰 문제로 변하면서, 전체 팀은 방대한 구성 파일을 관리하는 데 고군분투하게 된다.
예를 들어 수백 개의 마이크로서비스가 있고 각 서비스마다 세 가지 환경에 대해 서로 다른 구성 파일이 있다고 가정해보자. 만약 이 파일들이 외부에서 관리되지 않는다면, 구성 변경이 있을 때마다 코드 저장소에서 해당 파일을 찾아야 하고, 그 후 통합 과정(있다면)을 거쳐 애플리케이션을 재시작해야 한다. 이러한 비효율적인 작업은 큰 문제가 될 수 있다. 이를 피하기 위해 클라우드 기반 마이크로서비스 개발에서는 다음과 같은 모범 사례를 고려해야 한다.
- 배포되는 실제 코드와 구성 정보를 완전히 분리한다.
- 여러 환경에서도 절대 변경되지 않는 불변 (immutable) 애플리케이션 이미지를 빌드한다.
- 서버가 시작할 때 마이크로서비스가 읽어오는 환경 변수 또는 중앙 저장소를 통해 모든 애플리케이션 구성 정보를 주입한다.
5.1 구성 (그리고 복잡성) 관리
클라우드에서 실행되는 마이크로서비스는 신속한 시작과 최소한의 인간 개입을 요구하므로, 애플리케이션 구성을 체계적으로 관리하는 것이 필수적이다. 만약 사람이 수동으로 구성하거나 배포 과정에서 직접 개입해야 한다면, 이는 구성 불일치, 예기치 못한 장애, 확장성 문제에 대한 지연 등의 문제를 초래할 수 있다. 이러한 이유로, 자동화된 구성 관리가 마이크로서비스의 안정성과 효율성을 보장하는 핵심 요소로 작용한다.
- 분리 (segregate): 서비스의 물리적 배포와 서비스 구성 정보를 철저히 분리하는 것이 중요하다. 애플리케이션 구성 정보는 서비스 인스턴스와 함께 포함되어 배포되어서는 안 된다. 대신, 서비스 시작 시 환경 변수로 전달되거나, 중앙화된 저장소에서 필요한 구성을 읽어 들이는 방식이 바람직하다. 이를 통해 구성 관리의 유연성과 보안을 강화하고, 서비스 인스턴스 간의 일관성을 유지할 수 있다.
- 추상화 (abstract): 서비스 인터페이스 뒤편의 구성 데이터에 대한 접근 방식은 추상화하는 것이 중요하다. 애플리케이션 구성 데이터를 처리할 때, 직접 파일 기반 저장소나 JDBC 데이터베이스에서 데이터를 읽는 코드를 작성하는 대신, REST 기반의 JSON 서비스를 통해 데이터를 조회하는 방식을 사용하는 것이 좋다. 이는 구성 데이터의 접근을 표준화하고, 유지보수성과 확장성을 높이는 데 기여한다.
- 중앙 집중화 (centralize): 클라우드 기반 애플리케이션에서는 수백 개의 서비스가 동시에 실행될 수 있으므로, 구성 데이터를 저장하는 저장소의 수를 최소화하는 것이 필수적이다. 이는 관리 복잡성을 줄이고 일관성을 유지하기 위해 중요하며, 가능한 한 구성 정보를 통합하고 간소화하여 관리 효율성을 높여야 한다.
- 견고화 (harden): 애플리케이션 구성 정보가 배포된 서비스와 완전히 분리되어 중앙 집중화되면, 이를 관리하는 솔루션은 높은 가용성과 이중화를 갖춰야 한다. 이는 구성 정보의 안정적인 접근과 장애 발생 시 신속한 복구를 보장하기 위해 필수적이다.
구성 정보를 코드 외부로 분리할 때 가장 중요한 점 중 하나는 외부 의존성이 생기므로 이를 체계적으로 관리하고 버전 제어하는 것이다. 애플리케이션 구성 데이터를 적절히 관리하지 않으면, 예기치 않은 장애나 원인을 파악하기 어려운 버그가 발생할 가능성이 높다. 따라서 구성 데이터는 반드시 추적 가능하고 버전 관리가 철저히 이루어져야 한다는 점을 간과해서는 안 된다.
5.1.1 구성 관리 아키텍처
마이크로서비스의 구성 관리는 부트스트래핑 단계에서 일어난다.
- 마이크로서비스 인스턴스가 실행되면, 동작 중인 환경에 적합한 구성 정보를 가져오기 위해 지정된 서비스 엔드포인트를 호출한다. 이때 구성 관리 서비스에 연결하기 위한 접속 정보 (인증 자격 증명, 서비스 엔드포인트)는 마이크로서비스가 시작될 때 환경 변수나 초기 설정을 통해 전달된다.
- 구체적인 구성 정보는 중앙 저장소에 안전하게 보관되며, 저장소의 구현 방식에 따라 데이터를 관리하는 여러 방법을 선택할 수 있다. 예를 들어, 버전 관리가 가능한 파일 시스템, 관계형 데이터베이스, 또는 키-값 기반의 데이터 저장소와 같은 방식이 활용될 수 있다.
- 애플리케이션 구성 데이터는 배포 방식과 분리하여 관리되며, 변경 사항은 주로 빌드 및 배포 파이프라인을 통해 처리된다. 이를 통해 구성 변경 사항에 버전 태그를 부여하고, 개발, 스테이징, 운영 환경 등 다양한 환경에 안정적으로 배포할 수 있다.
- 관리 중인 구성 정보에 변경이 발생하면, 이를 사용하는 서비스는 해당 변경 사항에 대한 알림을 받아야 하며, 애플리케이션 내부에서 사용하는 구성 데이터 복제본을 즉시 갱신해야 한다.
5.1.2 구현 솔루션 설계
우리는 시장에서 검증된 많은 오픈 소프 프로젝트를 선택해서 구성 관리 솔루션을 구현할 수 있다.
프로젝트 이름 | 설명 | 특징 |
etcd | Go 언어로 작성된 오픈 소스 프로젝트로 서비스 디스커버리와 키-값 관리에 사용된다. 분산 컴퓨팅 모델을 위해 rat 프로토콜을 사용한다. | * 매우 빠르며 확장 가능 * 분산 가능 * 명령중 기반 *사용 및 설치 용이 |
유레카 (Eureka) | 넷플릭스가 만들었으며 엄격한 실전 테스트를 거쳤다. 서비스 디스커버리 키-값 관리에 사용된다. | *분산형 키-값 저장소 *유연하지만 구축하는데 공수 소요 *기본적으로 클라이언트의 동적 갱신 기능 제공 |
콘솔 (Console) | 하시코프 (HashiCorp)가 만들었다. etcd나 유레카와 유사하지만 분산 컴퓨팅 모델에 다른 알고리즘을 사용한다. | *빠르다 *직접 DNS와 통합해서 네이티브 서비스 디스커버리 기능을 제공한다. *클라이언트 동적 갱신은 기본 기능에 포함하지 않는다. |
주키퍼 (Zookeeper) | 아파치 프로젝트로 분산 잠금 (distributed locking) 기능을 제공한다. 주로 키-값 데이터를 액세스하는 구성 관리 솔루션으로 사용된다. | *가장 오래되어 실전에서 검증된 솔루션 *사용하기 가장 복잡하다 *구성 관리에 사용가능하지만 이미 아키텍처에서 사용 중일 때만 고려해야 한다. |
스프링 클라우드 구성 서버 (Spring Cloud Configuration Server) |
오픈 소스 프로젝트로 다양한 백엔드와 함께 전반적인 구성 관리 솔류션을 제공한다. | *비분산형 키-값 저장소 *스프링 및 스프링이 아닌 서비스와 긴밀한 통합 *구성 데이터 저장을 위해 공유 파일 시스템, 유레카, 콘솔, 깃 등 다양한 백엔드 사용 가능 |
이 교제에서는 스프링 마이크로서비스 아키텍처에 완벽하게 통합된 스프랑 클라우드 구성 서버 (스프링 클라우드 컨피그 서버 / 컨피그 서버)를 사용한다.
해당 솔루션을 채탁한 이유는 아래와 같다.
- 스프링 클라우드 구성 서버는 설치하기 쉽고, 사용하기도 쉽다.
- 스프랑 클라우드 구성 서버는 스프링 부트와 밀접하게 통합되어 있다.실제 몇 가지 간단한 어노테이션을 사용하여 애플리케이션의 모든 구성 데이터를 읽어 올 수 잇다.
- 스프링 클라우드 구성 서버는 구성 데이터를 저장하는 많은 백앤드를 지원한다.
- 스프링 클라우드 구성 서버는 깃 소스 제어 플랫폼이나 하시모프 볼트와 바로 통합할 수 있다.
5.2 스프링 클라우드 컨피그 서버 구축
스프링 클라우드 컨피그 서버는 스프링 부트 기반의 RESTful 애플리케이션으로 제공되며, 독립형 서버로 동작하지 않습니다. 기존 스프링 부트 애플리케이션에 구성 서버 기능을 추가하거나 새로운 스프링 부트 프로젝트를 생성하여 구성 서버를 구현할 수 있습니다. 가장 효과적인 방법은 구성 서버와 애플리케이션을 분리하여 독립적으로 관리하는 것입니다.
Spring Initializer로 스프링 부트 프로젝트를 생성할 수 있다. 생성 과정을 아래와 같다.
- 프로젝트 타입은 Maven을 선택
- 언어는 Java를 선택
- 최신 또는 안정적인 스프링 버전을 선택
- 프로젝트 메타데이터에서 프로젝트 산출물 그룹과 ID 입력
- 패키징에 JAR을 선택
- 자바 버전을 17을 선택
- 디펜더시에 Config Server와 Spring Boot Actuator을 추가
※ 예제 및 예제 설명 생략
스프링 클라우드 컨피그 서버를 설정하려면, 서버의 핵심 구성을 정의할 파일을 추가로 설정해야 한다. 이 파일은 애플리케이션의 구성 정보를 제공하는 중요한 역할을 한다. 일반적으로 설정 파일로 application.properties, application.yml, bootstrap.properties, 또는 bootstrap.yml 중 하나를 사용한다.
부트스트랩(bootstrap) 파일은 스프링 클라우드에서 중요한 설정 파일로, 애플리케이션의 초기 설정을 정의한다. 이 파일에는 스프링 애플리케이션의 이름, 스프링 클라우드 구성 서버의 위치, 암호화 및 복호화 정보 등이 포함된다. 부트스트랩 파일은 스프링 애플리케이션의 부모 컨텍스트인 ApplicationContext로 먼저 로드되며, 이후 application.properties나 application.yml 파일을 로드하는 컴포넌트들이 처리된다.
yml과 properties 파일 확장자는 단순히 데이터를 표현하는 포맷의 차이일 뿐, 선택은 개인의 취향에 따라 다를 수 있다. 이 책에서는 bootstrap.yml 파일을 사용하여 구성 서버와 마이크로서비스의 설정 정보를 정의한다.
5.2.1 스프링 클라우드 컨피그 부트스트랩 클래스 설정
※ 예제 및 예제 설명 생략
5.2.2 스프링 클라우드 컨피그 서버에 파일 시스템 사용
※ 예제 및 예제 설명 생략
native는 클라우드 컨피그 서버용으로만 생성된 프로파일이며, 구성 파일 클래스 패스나 파일 시스템에서 검색하고 읽도록 지시하는 프로파일이다.
5.2.3 서비스의 구성 파일 설정
스프링 클라우드 컨피그는 계층 구조로 동작하며, 애플리케이션 구성 정보는 애플리케이션 이름과 환경별로 구성된 프로퍼티를 기준으로 설정된다. 이렇게 하면 각 환경에 맞는 설정을 쉽게 관리하고, 필요한 경우 특정 애플리케이션에 대한 구성 정보를 독립적으로 업데이트할 수 있다.
※ 예제 및 예제 설명 생략
5.3 스프링 클라우드 컨피그와 스프링 부트 클라이언트 통합
PostgreSQL, 또는 Postgres는 오픈 소스 관계형 데이터베이스 관리 시스템 중에서 가장 진보적이고 주목받는 시스템으로, 엔터프라이즈 환경에서도 널리 사용된다. 이 데이터베이스의 첫 번째 장점은 누구나 무료로 사용할 수 있는 라이선스를 제공한다는 점이다. 두 번째로, PostgreSQL은 데이터 양이 많아져도 쿼리의 복잡성을 증가시키지 않으면서도 더 많은 데이터를 효율적으로 처리할 수 있는 기능을 제공한다.
- Postgres는 다중 버전 동시성 제어(MVCC)를 사용하여 각 트랜잭션에 데이터베이스 상태의 스냅샷을 추가함으로써 더 높은 성능과 일관된 트랜잭션을 보장한다. 이 방식은 여러 트랜잭션이 동시에 실행될 때도 데이터의 일관성을 유지하면서 성능을 최적화하는 데 도움을 준다.
- Postgres는 트랜잭션을 실행할 떄 읽기 잠금 (reading locks)를 사용하지 않는다.
- Postgres는 핫 스탠바이(hot standby) 기능을 제공하여 서버가 복구 모드나 대기 모드에 있을 때도 클라이언트가 데이터를 검색할 수 있도록 한다. 이 기능을 통해 데이터베이스는 완전히 잠기지 않고 유지관리 작업을 수행할 수 있으며, 시스템의 가용성을 높이는 데 도움이 된다.
Postgres의 주요 특징
- C, C++, 자바, PHP, 파이썬 등의 언어에서 지원된다.
- 차단 없이 테이블에서 동일한 정보를 전달하는 동안 많은 클라이언트에 서비스를 제공할 수 있다.
- 뷰 (View)를 지원하므로 사용자는 데이터가 저장된 방식과 다르게 쿼리할 수 있다.
- 객체 관계형 데이터베이스로, 데이터를 객체처럼 다룰 수 있게 하며 객체지향 매커니즘을 제공한다.
5.3.1 라이선싱 서비스의 스프링 클라우드 컨피그 서비스 의존성 설정
※ 예제 및 예제 설명 생략
5.3.2 스프링 클라우드 컨피그 사용을 위한 라이선싱 서비스 구성
bootstrap.yml 파일은 다른 구성 정보 파일들보다 먼저 애플리케이션 프로퍼티를 로드한다. 따라서 이 파일에는 서비스 이름과 스프링 클라우드 컨피그 서버와 연결할 URI 등의 정보를 포함시킨다. 한편, 스프링 클라우드 컨피그 서버에 저장되지 않고 로컬에서 유지하려는 서비스의 구성 정보는 application.yml 파일에서 설정할 수 있다.
application.yml 파일에는 스프링 클라우드 컨피그 서버가 가용하지 않을 때에도 서비스를 계속 사용할 수 있도록 하는 구성 데이터를 저장한다. bootstrap.yml과 application.yml 파일은 모두 프로젝트의 src/main/resources 디렉터리 안에 위치한다.
※ 예제 및 예제 설명 생략
5.3.3 스프링 클라우드 컨피그 서버를 사용하여 데이터 소스 연결
※ 예제 및 예제 설명 생략
5.3.4 @ConfigurationProperties를 사용하여 프로퍼티 직접 읽기
※ 예제 및 예제 설명 생략
5.3.5 스프링 클라우드 컨피그 서버를 사용하여 프로퍼티 갱신
컨피그 서버는 항상 최신 프로퍼티 버전을 제공한다. 내부 저장소에서 프로퍼티가 변경되면 항상 최신 상태로 유지된다.
스프링 부트 애플리케이션은 시작 시에만 프로퍼티를 읽기 때문에, 컨피그 서버에서 변경된 프로퍼티가 자동으로 애플리케이션에 반영되지 않는다. 하지만 개발팀은 스프링 부트 액추에이터의 @RefreshScope 어노테이션을 사용하여, 애플리케이션이 구성 정보를 다시 읽도록 유도할 수 있다. 이를 위해 /refresh 엔드포인트에 접근하면 된다.
@RefreshScope 어노테이션을 사용할 때 몇 가지 유의할 사항이 있다. 이 어노테이션은 애플리케이션 구성에 있는 사용자 정의 스프링 프로퍼티만 다시 로드한다. 즉, 데이터베이스 구성 정보와 같은 스프링 데이터 관련 설정은 이 어노테이션의 영향을 받지 않으며, 갱신되지 않는다.
5.3.6 깃과 함께 스프링 클라우드 컨피그 서버 사용
스프링 클라우드 컨피그 서버에서 파일 시스템을 백엔드 저장소로 사용하는 것은 클라우드 기반 애플리케이션에 실용적이지 않다. 이유는 개발 팀이 컨피그 서버의 모든 인스턴스에 마운트될 공유 파일 시스템을 설정하고 관리해야 하기 때문이다. 또한, 스프링 클라우드 컨피그 서버는 다양한 백엔드 저장소와 통합될 수 있어, 파일 시스템을 사용하는 대신 더 유연한 저장소 옵션을 활용하는 것이 바람직하다.
Git을 사용하면 구성 관리할 프로퍼티를 소스 제어 하에 두는 모든 이점을 얻을 수 있으며, 프로퍼티 관리 파일의 배포를 빌드 및 배포 파이프라인에 쉽게 통합할 수 있다. Git을 백엔드 저장소로 사용하려면, 스프링 클라우드 컨피그 서버의 bootstrap.yml 파일에 Git 저장소와 관련된 구성을 추가해야 한다. 이를 통해 스프링 클라우드 컨피그는 Git에서 구성 파일을 가져와 애플리케이션에 적용할 수 있다.
깃 허브 사용권한이 필요하다면 사용자 이름이나 패스워드 (또는 개인 토큰이나 SSH 구성 정보)등 깃 구성 정보를 bootstrap.yml 파일에 설정해야 한다.
스프링 클라우드 컨피그에서 환경 저장소의 기본 구현체는 깃 백엔드다.
5.3.7 볼트와 스프링 클라우드 컨피그 서비스 통합
하시코프 볼트(Hasicorp Vault), 줄여서 볼트는 시크릿에 안전하게 접근할 수 있도록 설계된 도구이다. 여기서 시크릿이란 패스워드, 인증서, API 키 등과 같이 접근을 제한하거나 보호가 필요한 모든 정보를 의미한다. 볼트는 이러한 시크릿을 안전하게 저장하고 관리하며, 필요 시 안전한 방식으로 제공한다.
스프링 컨피그 서비스에서 하시코프 볼트를 구성하려면 볼트 프로파일을 설정해야 한다. 이 프로파일을 통해 스프링 컨피그 서비스가 볼트와 통합되어 마이크로서비스의 애플리케이션 프로퍼티를 안전하게 관리할 수 있다. 볼트와의 통합을 위해 도커를 활용하여 볼트 컨테이너를 생성하고 실행하며, 이를 통해 간편하게 볼트 환경을 구축할 수 있다.
- VAULT_DEV_ROOT_TOKEN_ID: 이 매개변수는 생성된 루트 토큰 ID를 설정한다. 루트 토큰은 볼트 구성을 시작하는 초기 액세스 토큰이다. 초기 생성된 토큰 ID를 주어진 값으로 설정한다.
- VAULT_DEV_LISTEN_ADDRESS: 이 매개변수는 개발 서버의 IP 주소와 포트를 설정한다. 기본 값은 0.0.0.0:8200이다.
5.3.8 볼트 UI
볼트는 시크릿 생성을 지원하는 통합 사용자 인터페이스(UI)를 제공하며, 이를 통해 보다 쉽게 시크릿을 관리할 수 있다. UI에 접근하려면 http://0.0.0.0:8200/ui/vault/auth 주소로 접속하면 된다. 이 URI는 VAULT_DEV_LISTEN_ADDRESS 매개변수를 통해 설정된 것으로, 볼트 개발 환경에서 기본적으로 사용된다.
5.4 중요한 구성 정보 보호
스프링 클라우드 컨피그 서버는 기본적으로 애플리케이션 구성 파일에 있는 모든 프로퍼티를 암호화하지 않고 평문으로 저장한다. 여기에는 데이터베이스 자격 증명과 같은 민감한 정보도 포함될 수 있다. 이러한 중요한 자격 증명을 평문으로 소스 코드 저장소에 보관하는 것은 매우 부적절한 관행이며, 예상외로 이러한 실수는 빈번하게 발생한다.
스프링 클라우드 컨피그는 민감한 프로퍼티를 쉽게 암호화할 수 있는 기능을 제공하며, 대칭 키 (공유 시크릿)와 비대칭 키 (공개/비공개 키) 방식을 모두 지원한다. 비대칭 암호화는 최신 알고리즘을 활용해 대칭 암호화보다 더 높은 보안을 제공한다. 하지만 대칭 키는 설정이 간단하며, 컨피그 서버의 bootstrap.yml 파일에 단일 프로퍼티만 정의하면 되기 때문에 간편함을 이유로 대칭 암호화를 선택하는 경우도 있다.
5.4.1 대칭 암호화 키 설정
대칭 암호화 키는 암호화와 복호화에 동일한 공유 시크릿을 사용하는 방식이다. 스프링 클라우드 컨피그 서버에서 대칭 암호화 키는 bootstrap.yml 파일에 설정하거나, ENCRYPT_KEY라는 운영체제 환경 변수로 애플리케이션에 전달하여 사용할 수 있다. 이 키는 암호화된 데이터를 생성하거나 복호화할 때 활용된다.
대칭 키의 길이는 12문자 이상이 되어야하고 불규칙 문자열이 이상적이다.
예제 및 예제 설명 생략
5.4.2 프로퍼티 암호화와 복호화
O-stock 데이터에 액세스하는데 사용하는 라이선싱 서비스의 Postgres 데이터베이스 패스워드를 암호화한다.
스프링 클라우드 컨피그 인스턴스를 실행하면 프레임워크가 ENCRYPT_KEY 환경 변수나 bootstrap.yml 파일의 설정을 감지하여 /encrypt와 /decrypt라는 두 개의 엔드포인트를 자동으로 추가한다. 이 엔드포인트들은 각각 데이터를 암호화하고 복호화하는 기능을 제공하며, 이를 통해 중요한 애플리케이션 프로퍼티를 안전하게 관리할 수 있다.
결과값을 복호화하려면 /decrypt 엔드포인트에 암호화된 문자열을 전달해서 복호화한다.
스프링 클라우드 컨피그 서버는 암호화된 프로퍼티 앞에 { cipher }가 필요하다. { cipher }는 컨피그 서버가 암호화된 값을 처리하도록 지정한다.
'스프링 마이크로서비스' 카테고리의 다른 글
7장. 나쁜 상황에 대비한 스프링 클라우드와 Resilience4j를 사용한 회복성 패턴 (0) | 2024.12.16 |
---|---|
6장. 서비스 디스커버리 (2) | 2024.12.12 |
4장. 도커 (0) | 2024.12.11 |
3장. 스프링 부트로 마이크로서비스 구축하기 (1) | 2024.12.10 |
2장. 스프링 클라우드와 함께 마이크로서비스 세계 탐험 (4) | 2024.12.10 |