마이크로 서비스 아키텍처를 기반으로 솔루션을 설계할 때 개별 구성 요소를 필요한 조정 없이 전체 시스템을 빠르고 쉽게 관리하고 가능한 최고의 자동화에 대한 요구 사항에 직면하는 경우가 많습니다.
이는 정말 어려운 일이며, 이것이 바로 클라이언트 요구 사항에 맞게 빠르고 쉽게 확장하고 조정할 수 있는 가장 간단한 방법으로 마이크로 서비스 아키텍처를 구축하는 방법을 보여주는 튜토리얼을 준비하기로 결정한 이유입니다.
개별 서비스의 코드와 설정을 방해하고 싶지 않고 Docker에서 컨테이너를 오케스트레이션하여 시스템을 제어하고 싶었습니다.
그 결과 컨테이너 설정을 몇 가지 변경하여 쉽게 확장할 수 있는 간단한 마이크로 서비스 아키텍처가 탄생했습니다. 그 밖의 모든 것은 Ocelot에서 게이트웨이/로드 밸런서로, Consul에서 서비스 검색 에이전트로 제공됩니다.
이러한 아키텍처를 사용하면 다른 서비스 내에서 배포를 조정하지 않고도 단일 서비스를 재배포할 수 있습니다. 재배치된 서비스는 서비스 검색 시 자동으로 등록되며, 게이트웨이를 통해 즉시 이용 가능합니다. 이것이 모든 개발 팀에게 얼마나 큰 도움이 되는지 상상할 수 있습니다!
물론, 단일 게이트웨이 서비스를 사용하면 아키텍처의 단일 실패 지점이 되므로 고가용성을 확보하려면 최소 두 개의 인스턴스를 배포해야 합니다. 하지만 그 문제는 여러분에게 맡기겠습니다.
이전 데모에서는 서비스 검색을 위해 Eureka와 함께 Ocelot을 서비스 게이트웨이 및 로드 밸런서로 구현하는 방법을 보여주었습니다. 대신 Eureka 이 데모에서는 서비스 검색을 위해 Consul을 사용합니다.
Consul은 서비스 검색, 구성 및 세분화 기능을 갖춘 완전한 기능의 제어 평면을 제공하는 서비스 메시 솔루션입니다. 이러한 각 기능은 필요에 따라 개별적으로 사용하거나 함께 사용하여 전체 서비스 메시를 구축할 수 있습니다. Consul은 데이터 플레인이 필요하며 프록시와 기본 통합 모델을 모두 지원합니다. Consul은 간단한 내장 프록시와 함께 제공되므로 모든 것이 즉시 작동할 뿐만 아니라 Envoy와 같은 타사 프록시 통합도 지원합니다.
Consul의 주요 기능은 다음과 같습니다.
서비스 검색 : Consul의 클라이언트는 api 또는 mysql과 같은 서비스를 등록할 수 있으며, 다른 클라이언트는 Consul을 사용하여 특정 서비스의 공급자를 검색할 수 있습니다. DNS 또는 HTTP를 사용하여 애플리케이션은 자신이 의존하는 서비스를 쉽게 찾을 수 있습니다.
상태 확인 : Consul 클라이언트는 특정 서비스("웹 서버가 200 OK를 반환하는지") 또는 로컬 노드("메모리 사용률이 90% 미만인지")와 관련된 상태 확인을 원하는 수만큼 제공할 수 있습니다. 이 정보는 운영자가 클러스터 상태를 모니터링하는 데 사용할 수 있으며, 서비스 검색 구성 요소가 비정상 호스트에서 트래픽을 라우팅하는 데 사용됩니다.
KV 스토어 : 애플리케이션은 동적 구성, 기능 플래그 지정, 조정, 리더 선택 등을 포함한 다양한 목적으로 Consul의 계층적 키/값 저장소를 사용할 수 있습니다. 간단한 HTTP API를 사용하면 쉽게 사용할 수 있습니다.
안전한 서비스 통신 : Consul은 상호 TLS 연결을 설정하기 위해 서비스에 대한 TLS 인증서를 생성 및 배포할 수 있습니다. 의도를 사용하여 통신이 허용되는 서비스를 정의할 수 있습니다. 복잡한 네트워크 토폴로지와 정적인 방화벽 규칙을 사용하는 대신 실시간으로 변경 가능한 의도로 서비스 세분화를 쉽게 관리할 수 있습니다.
다중 데이터 센터 : Consul은 기본적으로 다중 데이터 센터를 지원합니다. 이는 Consul 사용자가 여러 지역으로 확장하기 위해 추가 추상화 계층을 구축하는 것에 대해 걱정할 필요가 없음을 의미합니다.
Consul은 DevOps 커뮤니티와 애플리케이션 개발자 모두에게 친숙하도록 설계되어 현대적이고 탄력적인 인프라에 적합합니다.
출처 : 영사소개
튜토리얼의 핵심 부분은 Consul을 사용하여 서비스 엔드포인트를 동적으로 검색하는 것입니다. 서비스가 Consul에 등록되면 일반적인 DNS 또는 사용자 정의 API를 사용하여 검색할 수 있습니다.
Consul은 이러한 서비스 인스턴스에 대한 상태 확인을 제공합니다. 서비스 인스턴스 또는 서비스 자체 중 하나가 비정상이거나 상태 확인에 실패하면 레지스트리는 이에 대해 알고 서비스 주소 반환을 방지합니다. 이 경우 로드 밸런서가 수행하는 작업은 레지스트리에 의해 처리됩니다.
동일한 서비스의 여러 인스턴스를 사용하기 때문에 Consul은 트래픽을 무작위로 다른 인스턴스로 보냅니다. 따라서 서비스 인스턴스 간의 로드 균형을 조정합니다.
Consul은 중앙 집중식 로드 밸런서를 배포할 필요 없이 여러 서비스 인스턴스에 대한 오류 감지 및 로드 분산 문제를 처리합니다.
이는 서비스의 새 인스턴스가 등록되고 트래픽을 수신할 수 있게 되면 업데이트되는 레지스트리를 자동으로 관리합니다. 이를 통해 서비스를 쉽게 확장할 수 있습니다.
Consul에 자가 등록을 구현하는 방법에 대한 자세한 구현 내용을 살펴보기 전에 자가 등록을 통한 서비스 검색이 실제로 어떻게 작동하는지 살펴보겠습니다.
첫 번째 단계에서 서비스 인스턴스는 이름, ID 및 주소를 제공하여 서비스 검색 서비스에 등록합니다. 이 게이트웨이는 이름/ID로 Consul 서비스 검색을 쿼리하여 이 서비스의 주소를 얻을 수 있습니다.
여기서 주목해야 할 핵심 사항은 동일한 Consul 서비스 에이전트에서 실행되는 서비스 인스턴스를 명확하게 하기 위해 서비스 인스턴스가 고유한 서비스 ID 로 등록된다는 것입니다. 모든 서비스에는 노드당 고유 ID가 있어야 하므로 이름이 충돌할 수 있는 경우(우리의 경우) 고유 ID를 제공해야 합니다.
.NET 애플리케이션에서 자체 등록을 구현하는 방법을 살펴보겠습니다. 먼저 docker-compose.override.yml 파일을 통해 전달된 환경 변수에서 서비스 검색에 필요한 구성을 읽어야 합니다.
public static class ServiceConfigExtensions
{
public static ServiceConfig GetServiceConfig ( this IConfiguration configuration )
{
ArgumentNullException . ThrowIfNull ( configuration ) ;
ServiceConfig serviceConfig = new ( )
{
Id = configuration . GetValue < string > ( "ServiceConfig:Id" ) ,
Name = configuration . GetValue < string > ( "ServiceConfig:Name" ) ,
ApiUrl = configuration . GetValue < string > ( "ServiceConfig:ApiUrl" ) ,
Port = configuration . GetValue < int > ( "ServiceConfig:Port" ) ,
ConsulUrl = configuration . GetValue < Uri > ( "ServiceConfig:ConsulUrl" ) ,
HealthCheckEndPoint = configuration . GetValue < string > ( "ServiceConfig:HealthCheckEndPoint" ) ,
} ;
return serviceConfig ;
}
}
서비스 검색 서비스에 도달하는 데 필요한 구성을 읽은 후 이를 사용하여 서비스를 등록할 수 있습니다. 아래 코드는 서비스에 대한 이전 정보가 있는 경우 이를 재정의하여 Consul 에 서비스를 등록하는 백그라운드 작업 (호스팅 서비스)으로 구현됩니다. 서비스가 종료되면 Consul 레지스트리에서 자동으로 등록이 취소 됩니다.
public class ServiceDiscoveryHostedService (
ILogger < ServiceDiscoveryHostedService > logger ,
IConsulClient client ,
ServiceConfig config )
: IHostedService
{
private AgentServiceRegistration _serviceRegistration ;
/// <summary>
/// Registers service to Consul registry
/// </summary>
public async Task StartAsync ( CancellationToken cancellationToken )
{
_serviceRegistration = new AgentServiceRegistration
{
ID = config . Id ,
Name = config . Name ,
Address = config . ApiUrl ,
Port = config . Port ,
Check = new AgentServiceCheck ( )
{
DeregisterCriticalServiceAfter = TimeSpan . FromSeconds ( 5 ) ,
Interval = TimeSpan . FromSeconds ( 15 ) ,
HTTP = $ "http:// { config . ApiUrl } : { config . Port } /api/values/ { config . HealthCheckEndPoint } " ,
Timeout = TimeSpan . FromSeconds ( 5 )
}
} ;
try
{
await client . Agent . ServiceDeregister ( _serviceRegistration . ID , cancellationToken ) . ConfigureAwait ( false ) ;
await client . Agent . ServiceRegister ( _serviceRegistration , cancellationToken ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
logger . LogError ( ex , $ "Error while trying to deregister in { nameof ( StartAsync ) } " ) ;
}
}
/// <summary>
/// If the service is shutting down it deregisters service from Consul registry
/// </summary>
public async Task StopAsync ( CancellationToken cancellationToken )
{
try
{
await client . Agent . ServiceDeregister ( _serviceRegistration . ID , cancellationToken ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
logger . LogError ( ex , $ "Error while trying to deregister in { nameof ( StopAsync ) } " ) ;
}
}
}
서비스 검색 서비스에 서비스를 등록하면 게이트웨이 API 구현을 시작할 수 있습니다.
Ocelot에서는 경로 목록(업스트림 요청을 매핑하는 데 사용되는 구성) 및 GlobalConfiguration (QoS, 속도 제한 등과 같은 기타 구성)이 포함된 구성 파일을 제공해야 합니다. 아래의 ocelot.json 파일에서 HTTP 요청을 전달하는 방법을 확인할 수 있습니다. 사용할 로드 밸런서 유형을 지정해야 합니다. 우리의 경우 이는 사용 가능한 서비스를 반복하고 사용 가능한 서비스에 요청을 보내는 "RoundRobin" 입니다. ServiceDiscoveryProvider 에 대한 GlobalConfiguration 에서 Consul을 서비스 검색 서비스로 설정하는 것이 중요합니다.
{
"Routes" : [
{
"Servicename" : " ValueService " ,
"DownstreamPathTemplate" : " /{everything} " ,
"DownstreamScheme" : " http " ,
"UpstreamPathTemplate" : " /{everything} " ,
"UpstreamHttpMethod" : [ " GET " ],
"UseServiceDiscovery" : true ,
"RouteIsCaseSensitive" : false ,
"LoadBalancerOptions" : {
"Type" : " RoundRobin "
},
"QoSOptions" : {
"ExceptionsAllowedBeforeBreaking" : 3 ,
"DurationOfBreak" : 5000 ,
"TimeoutValue" : 2000
}
}
],
"GlobalConfiguration" : {
"RequestIdKey" : " OcelotRequestId " ,
"UseServiceDiscovery" : true ,
"ServiceDiscoveryProvider" : {
"Host" : " consul " ,
"Port" : 8500 ,
"Type" : " PollConsul " ,
"PollingInterval" : 100
}
}
}
다음은 GlobalConfiguration 섹션의 ServiceDiscoveryProvider 설정에 필요한 몇 가지 설명입니다.
구성을 정의한 후 API 게이트웨이 구현을 시작할 수 있습니다. 아래에서는 ocelot.json 구성 파일과 Consul을 서비스 레지스트리로 사용하는 Ocelot API Gateway 서비스의 구현을 볼 수 있습니다.
IHostBuilder hostBuilder = Host . CreateDefaultBuilder ( args )
. UseContentRoot ( Directory . GetCurrentDirectory ( ) )
. ConfigureWebHostDefaults ( webBuilder =>
{
webBuilder . ConfigureServices ( services =>
services
. AddOcelot ( )
. AddConsul < MyConsulServiceBuilder > ( )
. AddCacheManager ( x =>
{
x . WithDictionaryHandle ( ) ;
} )
. AddPolly ( ) ) ;
webBuilder . Configure ( app =>
app . UseOcelot ( ) . Wait ( ) )
. ConfigureAppConfiguration ( ( hostingContext , config ) =>
{
config
. SetBasePath ( hostingContext . HostingEnvironment . ContentRootPath )
. AddJsonFile ( "appsettings.json" , false , true )
. AddJsonFile ( $ "appsettings. { hostingContext . HostingEnvironment . EnvironmentName } .json" , true , true )
. AddJsonFile ( "ocelot.json" , false , true )
. AddEnvironmentVariables ( ) ;
} )
. ConfigureLogging ( ( builderContext , logging ) =>
{
logging . ClearProviders ( ) ;
logging . AddConsole ( ) ;
logging . AddDebug ( ) ;
} ) ;
} ) ;
IHost host = hostBuilder . Build ( ) ;
await host . RunAsync ( ) ;
앞서 언급했듯이 컨테이너 용 경량 GNU/Linux 배포판을 사용하여 Consul을 포함한 Docker로 모든 서비스를 컨테이너화할 것입니다.
모든 컨테이너에 대한 설정이 포함된 docker-compose.yml 파일은 다음과 같습니다.
services :
services :
consul :
image : hashicorp/consul
container_name : consul
command : consul agent -dev -log-level=warn -ui -client=0.0.0.0
hostname : consul
networks :
- common_network
valueservice1.openapi :
image : valueservice.openapi:latest
container_name : valueservice1.openapi
restart : on-failure
hostname : valueservice1.openapi
build :
context : .
dockerfile : src/ValueService.OpenApi/Dockerfile
networks :
- common_network
valueservice2.openapi :
image : valueservice.openapi:latest
container_name : valueservice2.openapi
restart : on-failure
hostname : valueservice2.openapi
build :
context : .
dockerfile : src/ValueService.OpenApi/Dockerfile
networks :
- common_network
valueservice3.openapi :
image : valueservice.openapi:latest
container_name : valueservice3.openapi
restart : on-failure
hostname : valueservice3.openapi
build :
context : .
dockerfile : src/ValueService.OpenApi/Dockerfile
networks :
- common_network
services.gateway :
image : services.gateway:latest
container_name : services.gateway
restart : on-failure
hostname : services.gateway
build :
context : .
dockerfile : src/Services.Gateway/Dockerfile
networks :
- common_network
networks :
common_network :
driver : bridge
우리 서비스에는 구성 파일이 포함되어 있지 않습니다. 이를 위해 Docker-compose.override.yml 파일을 사용할 것입니다.
services :
consul :
ports :
- " 8500:8500 "
valueservice1.openapi :
# Swagger UI: http://localhost:9100/index.html
# http://localhost:9100/api/values
environment :
- ASPNETCORE_ENVIRONMENT=Development
- ServiceConfig__ApiUrl=valueservice1.openapi
- ServiceConfig__ConsulUrl=http://consul:8500
- ServiceConfig__HealthCheckEndPoint=healthcheck
- ServiceConfig__Id=ValueService.OpenApi-9100
- ServiceConfig__Name=ValueService
- ServiceConfig__Port=8080
ports :
- 9100:8080
depends_on :
- consul
valueservice2.openapi :
# Swagger UI: http://localhost:9200/index.html
# http://localhost:9200/api/values
environment :
- ASPNETCORE_ENVIRONMENT=Development
- ServiceConfig__ApiUrl=valueservice2.openapi
- ServiceConfig__ConsulUrl=http://consul:8500
- ServiceConfig__HealthCheckEndPoint=healthcheck
- ServiceConfig__Id=ValueService.OpenApi-9200
- ServiceConfig__Name=ValueService
- ServiceConfig__Port=8080
ports :
- 9200:8080
depends_on :
- consul
valueservice3.openapi :
# Swagger UI: http://localhost:9300/index.html
# http://localhost:9300/api/values
environment :
- ASPNETCORE_ENVIRONMENT=Development
- ServiceConfig__ApiUrl=valueservice3.openapi
- ServiceConfig__ConsulUrl=http://consul:8500
- ServiceConfig__HealthCheckEndPoint=healthcheck
- ServiceConfig__Id=ValueService.OpenApi-9300
- ServiceConfig__Name=ValueService
- ServiceConfig__Port=8080
ports :
- 9300:8080
depends_on :
- consul
services.gateway :
# Call first available service: http://localhost:9500/api/values
environment :
- ASPNETCORE_ENVIRONMENT=Development
ports :
- 9500:8080
depends_on :
- consul
- valueservice1.openapi
- valueservice2.openapi
- valueservice3.openapi
작성 파일을 실행하려면 Powershell을 열고 루트 폴더의 작성 파일로 이동하십시오. 그런 다음 모든 서비스를 시작하고 실행하는 docker-compose up -d --build --remove-orphans 명령을 실행합니다. -d 매개변수는 분리된 명령을 실행합니다. 이는 컨테이너가 백그라운드에서 실행되고 Powershell 창을 차단하지 않음을 의미합니다. 실행 중인 모든 컨테이너를 확인하려면 docker ps 명령을 사용하세요.
Consul은 바로 사용할 수 있는 멋진 웹 사용자 인터페이스를 제공합니다. 포트 8500 (http://localhost:8500)에서 접속할 수 있습니다. 화면 중 일부를 살펴보겠습니다.
Consul 에이전트 및 웹 서비스 확인과 관련된 모든 관련 정보가 포함된 Consul UI 서비스의 홈 페이지입니다.
API 게이트웨이(http://localhost:9500/api/values)를 통해 여러 번 호출해 보겠습니다. 로드 밸런서는 사용 가능한 서비스를 반복하여 요청을 보내고 응답을 반환합니다.
마이크로 서비스 시스템은 구축하고 유지 관리하기가 쉽지 않습니다. 하지만 이 튜토리얼에서는 마이크로 서비스 아키텍처를 사용하여 애플리케이션을 개발하고 배포하는 것이 얼마나 쉬운지 보여주었습니다. HashiCorp Consul은 서비스 검색, 상태 확인, 키-값 스토리지 및 다중 데이터 센터에 대한 최고 수준의 지원을 제공합니다. 게이트웨이인 Ocelot은 Consul 서비스 레지스트리와 성공적으로 통신하고 서비스 등록을 검색하며, 로드 밸런서는 사용 가능한 서비스를 순환하고 요청을 보냅니다. 두 가지를 모두 사용하면 이러한 문제에 직면한 개발자의 삶이 훨씬 쉬워집니다. 동의하시나요?
즐기다!
MIT 라이센스를 받았습니다. LinkedIn에서 저에게 연락하세요.