عند تصميم الحلول القائمة على بنية الخدمة الصغيرة، غالبًا ما نواجه متطلبات الإدارة السريعة والسهلة للنظام بأكمله، وأعلى مستوى ممكن من الأتمتة، دون التعديل اللازم للمكونات الفردية.
يعد هذا تحديًا حقيقيًا ولهذا السبب قررت إعداد برنامج تعليمي يوضح كيفية إنشاء بنية خدمة صغيرة بأبسط طريقة ممكنة، والتي يمكن توسيع نطاقها وتكييفها بسرعة وسهولة مع متطلبات العميل.
لم أكن أرغب في التدخل في التعليمات البرمجية وإعدادات الخدمات الفردية، ولكن التحكم في النظام فقط من خلال تنسيق الحاويات في Docker.
والنتيجة هي بنية خدمة صغيرة بسيطة يمكن توسيع نطاقها بسهولة من خلال بعض التغييرات في إعدادات الحاوية، ويتم توفير كل شيء آخر بواسطة Ocelot كبوابة/موازن تحميل وConsul كوكيل لاكتشاف الخدمة.
تسمح لنا هذه البنية بإعادة نشر خدمة واحدة دون تنسيق النشر ضمن الخدمات الأخرى. يتم تسجيل الخدمة المعاد نشرها تلقائيًا عند اكتشاف الخدمة وتكون متاحة على الفور من خلال البوابة. يمكنك أن تتخيل مدى حجم التعزيز لكل فريق تطوير!
من المؤكد أن استخدام خدمة بوابة واحدة يصبح نقطة فشل واحدة في بنيتنا، لذلك نحتاج إلى نشر مثيلين منها على الأقل للحصول على مستوى عالٍ من التوفر. لكنني سأترك هذه المشكلة لك لتلعب بها.
في العرض التوضيحي السابق، أوضحت كيفية تنفيذ Ocelot كبوابة خدمة وموازن التحميل مع Eureka لاكتشاف الخدمة. بدلاً من ذلك، تستخدم Eureka هذا العرض التوضيحي Consul لاكتشاف الخدمة.
Consul عبارة عن حل شبكي للخدمة يوفر مستوى تحكم كامل الميزات مع وظيفة اكتشاف الخدمة وتكوينها وتقسيمها. يمكن استخدام كل من هذه الميزات بشكل فردي حسب الحاجة، أو يمكن استخدامها معًا لبناء شبكة خدمة كاملة. يتطلب Consul مستوى بيانات ويدعم كلاً من نموذج التكامل الوكيل والأصلي. يأتي Consul مزودًا بوكيل مدمج بسيط بحيث يعمل كل شيء خارج الصندوق، ولكنه يدعم أيضًا تكاملات وكيل الطرف الثالث مثل Envoy.
الملامح الرئيسية للقنصل هي:
اكتشاف الخدمة : يمكن لعملاء Consul تسجيل خدمة، مثل API أو MySQL، ويمكن للعملاء الآخرين استخدام Consul لاكتشاف مقدمي خدمة معينة. باستخدام DNS أو HTTP، يمكن للتطبيقات العثور بسهولة على الخدمات التي تعتمد عليها.
التحقق من الصحة : يمكن لعملاء القنصل تقديم أي عدد من عمليات التحقق من الصحة، إما المرتبطة بخدمة معينة ("هل يقوم خادم الويب بإرجاع 200 موافق")، أو مع العقدة المحلية ("هل استخدام الذاكرة أقل من 90٪"). يمكن للمشغل استخدام هذه المعلومات لمراقبة سلامة المجموعة، ويتم استخدامها بواسطة مكونات اكتشاف الخدمة لتوجيه حركة المرور بعيدًا عن الأجهزة المضيفة غير الصحية.
متجر KV : يمكن للتطبيقات الاستفادة من مخزن المفاتيح/القيمة الهرمي الخاص بـ Consul لأي عدد من الأغراض، بما في ذلك التكوين الديناميكي ووضع علامة على الميزات والتنسيق واختيار القائد والمزيد. واجهة برمجة تطبيقات HTTP البسيطة تجعل من السهل استخدامها.
اتصالات الخدمة الآمنة : يمكن للقنصل إنشاء وتوزيع شهادات TLS للخدمات لإنشاء اتصالات TLS متبادلة. يمكن استخدام النوايا لتحديد الخدمات المسموح لها بالتواصل. يمكن إدارة تجزئة الخدمة بسهولة من خلال نوايا يمكن تغييرها في الوقت الفعلي بدلاً من استخدام طبولوجيا الشبكة المعقدة وقواعد جدار الحماية الثابتة.
مركز بيانات متعدد : يدعم Consul مراكز بيانات متعددة خارج الصندوق. وهذا يعني أن مستخدمي Consul لا داعي للقلق بشأن بناء طبقات إضافية من التجريد للتوسع في مناطق متعددة.
تم تصميم Consul ليكون صديقًا لكل من مجتمع DevOps ومطوري التطبيقات، مما يجعله مثاليًا للبنى التحتية الحديثة والمرنة.
المصدر: مقدمة القنصل
جزء أساسي من البرنامج التعليمي هو استخدام القنصل لاكتشاف نقاط نهاية الخدمة ديناميكيًا. بمجرد تسجيل الخدمة لدى Consul، يمكن اكتشافها باستخدام DNS النموذجي أو واجهة برمجة التطبيقات المخصصة.
يقدم القنصل فحوصات السلامة على مثيلات الخدمة هذه. إذا كانت إحدى مثيلات الخدمة أو الخدمات نفسها غير سليمة أو فشلت في فحص السلامة، فسيعلم السجل بذلك وسيتجنب إعادة عنوان الخدمة. تتم معالجة العمل الذي سيقوم به موازن التحميل بواسطة التسجيل في هذه الحالة.
نظرًا لأننا نستخدم مثيلات متعددة لنفس الخدمة ، فسيقوم Consul بإرسال حركة المرور بشكل عشوائي إلى مثيلات مختلفة. وبالتالي فإنه يوازن الحمل بين مثيلات الخدمات.
يتعامل Consul مع تحديات اكتشاف الفشل وتوزيع الأحمال عبر مثيلات متعددة من الخدمات دون الحاجة إلى نشر موازن أحمال مركزي.
يقوم تلقائيًا بإدارة السجل، والذي يتم تحديثه عند تسجيل أي مثيل جديد للخدمة ويصبح متاحًا لتلقي حركة المرور. وهذا يساعدنا على توسيع نطاق الخدمات بسهولة.
قبل الدخول في تفاصيل التنفيذ حول كيفية تنفيذ التسجيل الذاتي لدى Consul، دعنا ننظر إلى كيفية عمل اكتشاف الخدمة مع التسجيل الذاتي حقًا.
في الخطوة الأولى، يقوم مثيل الخدمة بتسجيل نفسه في خدمة اكتشاف الخدمة من خلال توفير اسمه ومعرفه وعنوانه. بعد ذلك، تتمكن هذه البوابة من الحصول على عنوان هذه الخدمة عن طريق الاستعلام عن اكتشاف خدمة القنصل حسب اسمها/معرفها.
الشيء الأساسي الذي يجب ملاحظته هنا هو أن مثيلات الخدمة مسجلة بمعرف خدمة فريد من أجل إزالة الغموض بين مثيلات الخدمة التي تعمل على نفس وكيل خدمة القنصل. من الضروري أن يكون لجميع الخدمات معرف فريد لكل عقدة ، لذلك إذا كانت الأسماء قد تتعارض (حالتنا)، فيجب توفير معرفات فريدة.
دعونا نلقي نظرة على كيفية تنفيذ التسجيل الذاتي في تطبيق .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 عن طريق تجاوز المعلومات السابقة حول الخدمة إذا كانت موجودة. إذا تم إيقاف الخدمة، فسيتم إلغاء تسجيلها تلقائيًا من سجل القنصل.
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 ) } " ) ;
}
}
}
بمجرد تسجيل خدماتنا في خدمة اكتشاف الخدمة، يمكننا البدء في تنفيذ Gateway API.
يتطلب Ocelot أن تقوم بتوفير ملف تكوين يحتوي على قائمة بالمسارات (التكوين المستخدم لتعيين الطلب الأولي) والتكوين العالمي (تكوينات أخرى مثل جودة الخدمة وتحديد المعدل وما إلى ذلك). في ملف ocelot.json أدناه، يمكنك رؤية كيفية إعادة توجيه طلبات HTTP. يتعين علينا تحديد نوع موازن التحميل الذي سنستخدمه، وهو في حالتنا "RoundRobin" الذي يتنقل عبر الخدمات المتاحة ويرسل الطلبات إلى الخدمات المتاحة. من المهم تعيين القنصل كخدمة اكتشاف الخدمة في GlobalConfiguration لـ ServiceDiscoveryProvider .
{
"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
}
}
}
فيما يلي بعض التوضيحات الضرورية لإعدادات ServiceDiscoveryProvider في قسم GlobalConfiguration :
بعد أن قمنا بتحديد التكوين الخاص بنا، يمكننا البدء في تنفيذ API Gateway. يمكننا أن نرى أدناه تنفيذ خدمة Ocelot API Gateway، التي تستخدم ملف تكوين ocelot.json الخاص بنا و Consul كسجل خدمة.
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 ( ) ;
كما ذكرنا من قبل، سنقوم بنقل جميع الخدمات مع Docker، بما في ذلك Consul ، باستخدام توزيعات GNU/Linux خفيفة الوزن للحاويات.
يبدو ملف 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 .
يقدم القنصل واجهة مستخدم ويب رائعة بمجرد إخراجها من الصندوق. يمكنك الوصول إليه على المنفذ 8500 : http://localhost:8500. دعونا نلقي نظرة على بعض الشاشات.
الصفحة الرئيسية لخدمات واجهة مستخدم القنصل مع جميع المعلومات ذات الصلة المتعلقة بالوكيل القنصلي وفحص خدمة الويب.
لنجري عدة مكالمات من خلال بوابة API: http://localhost:9500/api/values. سيقوم موازن التحميل بالتكرار عبر الخدمات المتاحة وإرسال الطلبات وإرجاع الاستجابات:
أنظمة الخدمة الصغيرة ليست سهلة البناء والصيانة. لكن هذا البرنامج التعليمي أظهر مدى سهولة تطوير ونشر تطبيق باستخدام بنية الخدمة الصغيرة. يتمتع HashiCorp Consul بدعم من الدرجة الأولى لاكتشاف الخدمة والفحص الصحي وتخزين القيمة الأساسية ومراكز البيانات المتعددة. تقوم Ocelot كبوابة بالاتصال بنجاح مع سجل خدمة القنصل واسترداد تسجيلات الخدمة وحلقات موازن التحميل من خلال الخدمات المتاحة وإرسال الطلبات. إن استخدام كليهما يجعل الحياة أسهل بكثير للمطورين الذين يواجهون مثل هذه التحديات. هل توافق؟
يتمتع!
مرخص تحت معهد ماساتشوستس للتكنولوجيا. تواصل معي على لينكدإن.