حان الوقت لمعالجة الحاويات
بادئ ذي بدء ، نستخدم أحدث صور Linux Alpine. Linux Alpine هو توزيع Linux خفيف الوزن تم تصميمه وتحسينه لتشغيل تطبيقات الويب في Docker. بمعنى آخر ، يحتوي Linux Alpine على ما يكفي من التبعيات والوظائف لتشغيل معظم التطبيقات. هذا يعني أن حجم الصورة حوالي 8 ميغابايت!
مقارنة بـ ، على سبيل المثال ... جهاز Ubuntu الظاهري بسعة حوالي 1 غيغابايت ، لهذا السبب أصبحت صور Docker أكثر طبيعية بالنسبة للخدمات الصغيرة والحوسبة السحابية.
لذا ، آمل الآن أن ترى قيمة في نقل الحاويات ، ويمكننا أن نبدأ في "إرساء" الخدمة الأولى لدينا. دعونا إنشاء Dockerfile
$ touch شحنة الخدمة / Dockerfile .
الجزء الاولمستودع EwanValentine الأصليالمقال الأصليفي Dockerfile ، أضف ما يلي:
FROM alpine:latest RUN mkdir /app WORKDIR /app ADD consignment-service /app/consignment-service CMD ["./consignment-service"]
ثم نقوم بإنشاء دليل جديد لاستضافة طلبنا. ثم نضيف ثنائي المترجمة لدينا إلى حاوية Docker لدينا وتشغيله.
الآن ، دعونا نقوم بتحديث سجل الإنشاء الخاص بنا على Makefile لإنشاء صورة Docker.
build: ... GOOS=linux GOARCH=amd64 go build docker build -t consignment .
أضفنا خطوتين أخريين ، وأود أن أوضحهما بمزيد من التفاصيل. بادئ ذي بدء ، نقوم بإنشاء Go Go الثنائية. ومع ذلك ، ستلاحظ متغيرين للبيئة قبل أن ندير بناء go $. تتيح لك GOOS و GOARCH إمكانية ترجمة ثنائي اللغة لنظام تشغيل آخر. بما أنني أقوم بتطوير جهاز Macbook ، لا يمكنني تجميع التطبيق القابل للتنفيذ ثم تشغيله في حاوية Docker تستخدم Linux. لن يكون الثنائي بلا معنى تمامًا في حاوية Docker الخاصة بك وسيؤدي ذلك إلى حدوث خطأ.
الخطوة الثانية التي أضفتها هي عملية بناء عامل ميناء. سيقوم Docker بقراءة Dockerfile الخاص بك وإنشاء صورة باسم خدمة الشحنة ، تشير النقطة إلى مسار الدليل ، لذلك نريد فقط أن تنظر عملية الإنشاء إلى الدليل الحالي.
سأضيف إدخالًا جديدًا إلى Makefile:
run: docker run -p 50051:50051 shippy-service-consignment
نحن هنا نطلق صورة عامل الميناء من خلال فتح المنفذ 50051. نظرًا لأن Docker يعمل على طبقة شبكة منفصلة ، فأنت بحاجة إلى إعادة توجيه المنفذ. على سبيل المثال ، إذا كنت ترغب في بدء هذه الخدمة على المنفذ 8080 ، يجب عليك تغيير الوسيطة -p إلى 8080: 50051. يمكنك أيضًا تشغيل الحاوية في الخلفية عن طريق تضمين علامة -d. على سبيل المثال ،
تشغيل عامل التشغيل -d -p 50051: 50051 خدمة الشحن .
قم بتشغيل
$ make run ، ثم في لوحة طرفية منفصلة مرة أخرى
$ go run main.go وتحقق من أنها لا تزال تعمل.
عندما تقوم بتشغيل بناء $ docker ، تقوم بتضمين الشفرة ووقت التشغيل في الصورة. صور عامل الميناء هي صور محمولة لبيئتك وتوابعها. يمكنك مشاركة صور Docker عن طريق نشرها على Docker Hub. الذي يشبه npm أو مستودع yum لصور عامل ميناء. عندما تحدد FROM في Dockerfile الخاص بك ، فإنك تخبر Docker بسحب هذه الصورة من مستودع Docker لاستخدامها كقاعدة. يمكنك بعد ذلك توسيع وإعادة تعريف أجزاء من هذا الملف الأساسي ، وإعادة تعريفها كما تريد. لن ننشر صور عامل ميناء ، ولكن لا تتردد في تصفح مستودع عامل ميناء ولاحظ أن أي برنامج تقريبا قد تم بالفعل تعبئتها في حاويات. تم إرساء بعض الأشياء الرائعة حقا.
يتم تخزين كل إعلان في Dockerfile مؤقتًا لأول مرة يتم إنشاؤه. هذا يلغي الحاجة إلى إعادة بناء وقت التشغيل بأكمله في كل مرة تقوم فيها بإجراء تغييرات. عامل الميناء ذكي بما يكفي لمعرفة التفاصيل التي تغيرت والتي تحتاج إلى إعادة بنائها. هذا يجعل عملية البناء سريعة بشكل لا يصدق.
يكفي عن الحاويات! دعنا نعود إلى كودنا.
عند إنشاء خدمة gRPC ، هناك الكثير من التعليمات البرمجية القياسية لإنشاء الاتصالات ، وتحتاج إلى ترميز موقع عنوان الخدمة في العميل أو خدمة أخرى حتى تتمكن من الاتصال بها. هذا أمر صعب لأنه عند بدء تشغيل الخدمات في السحابة ، قد لا يستخدمون نفس المضيف ، أو قد يتغير العنوان أو عنوان IP بعد إعادة نشر الخدمة.
هذا هو المكان الذي تدخل فيه خدمة الاكتشاف. تقوم خدمة الاكتشاف بتحديث دليل جميع خدماتك ومواقعها. يتم تسجيل كل خدمة في وقت التشغيل وإلغاء تسجيلات عند الإغلاق. ثم يتم تعيين اسم أو معرف لكل خدمة. وبالتالي ، حتى لو كان لديه عنوان IP جديد أو عنوان مضيف ، بشرط أن يظل اسم الخدمة كما هو ، فلن تحتاج إلى تحديث المكالمات إلى هذه الخدمة من خدمات أخرى.
كقاعدة عامة ، هناك طرق عديدة لهذه المشكلة ، ولكن ، مثل معظم الأشياء في البرمجة ، إذا كان شخص ما قد تعامل مع هذه المشكلة بالفعل ، فلا معنى لإعادة اختراع العجلة. Chuhnk (عاصم أسلم) ، منشئ
Go-micro ، يحل هذه المشاكل بوضوح رائع وسهولة الاستخدام. وهو ينتج بمفرده برامج رائعة. يرجى النظر في مساعدته إذا كنت تحب ما تراه!
الذهاب الصغير
Go-micro هو إطار عمل ميكروسرفيسي قوي مكتوب بلغة Go ، للاستخدام ، في معظم الأحيان ، مع Go. ومع ذلك ، يمكنك استخدام Sidecar للتفاعل مع لغات أخرى.
يحتوي Go-micro على ميزات مفيدة لإنشاء خدمات micros في Go. لكننا سنبدأ مع المشكلة الأكثر شيوعًا التي يحلها ، وهذا هو اكتشاف الخدمة.
سنحتاج إلى إجراء العديد من التحديثات لخدمتنا من أجل العمل مع go-micro. يتكامل Go-micro كمكون إضافي Protoc ، وفي هذه الحالة يستبدل مكون gRPC القياسي الذي نستخدمه حاليًا. لذلك ، دعونا نبدأ من خلال استبدال هذا في Makefile لدينا.
تأكد من تثبيت تبعيات go-micro:
go get -u github.com/micro/protobuf/{proto,protoc-gen-go}
حدّث Makefile الخاص بنا لاستخدام البرنامج المساعد go-micro بدلاً من البرنامج المساعد gRPC:
build: protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto GOOS=linux GOARCH=amd64 go build docker build -t consignment . run: docker run -p 50051:50051 shippy-service-consignment
نحن الآن بحاجة إلى تحديث شيبى-خدمة-شحنة / main.go لاستخدام go-micro. هذا يستخلص معظم كود gRPC السابق. إنه يعالج التسجيل بسهولة ويسرع كتابة الخدمة.
شيبى الخدمة شحنة / main.go التغيير الرئيسي هنا هو الطريقة التي ننشئ بها خادم gRPC الخاص بنا ، والذي تم استخلاصه بدقة من mico.NewService () ، والذي يتعامل مع تسجيل خدماتنا. وأخيرًا ، الدالة service.Run () ، التي تعالج الاتصال نفسه. كما كان من قبل ، نسجّل تطبيقنا ، لكن هذه المرة بطريقة مختلفة قليلاً.
يتعلق التغيير الثاني الأكبر بأساليب الخدمة نفسها: يتم تعديل الوسائط وأنواع الاستجابات بشكل طفيف لقبول كل من الطلب وبنية الاستجابة كوسائط ، والآن لا يعرض سوى الخطأ. في أساليبنا ، وضعنا استجابة عمليات go-micro.
أخيرًا ، لم نعد نبرمج المنفذ. يجب تكوين Go-micro باستخدام متغيرات البيئة أو وسيطات سطر الأوامر. لتعيين العنوان ، استخدم MICRO_SERVER_ADDRESS =: 50051. بشكل افتراضي ، يستخدم Micro mdns (الإرسال المتعدد dns) كوسيط اكتشاف خدمة للاستخدام المحلي. عادةً لا تستخدم mdns لاكتشاف الخدمات في بيئة الإنتاج ، لكننا نريد تجنب الاضطرار إلى تشغيل شيء مثل القنصل أو غيرها محلياً للاختبار. المزيد عن هذا في وقت لاحق.
دعونا تحديث Makefile لدينا لتعكس هذا.
build: protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto GOOS=linux GOARCH=amd64 go build docker build -t consignment . run: docker run -p 50051:50051 \ -e MICRO_SERVER_ADDRESS=:50051 \ shippy-service-consignment
-e هو علامة متغير البيئة ، فهو يسمح لك بتمرير متغيرات البيئة إلى حاوية Docker الخاصة بك. يجب أن يكون لديك علامة لكل متغير ، على سبيل المثال -e ENV = التدريج -e DB_HOST = مضيف محلي ، إلخ.
الآن ، إذا قمت بتشغيل $ make run ، فستحصل على خدمة Dockerised مع اكتشاف الخدمة. لذلك ، دعونا نقوم بتحديث أداة Cli الخاصة بنا لاستخدام هذا.
شحنة-المبادرة القطرية package main import ( "encoding/json" "io/ioutil" "log" "os" "context" pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment" micro "github.com/micro/go-micro" ) const ( address = "localhost:50051" defaultFilename = "consignment.json" ) func parseFile(file string) (*pb.Consignment, error) { var consignment *pb.Consignment data, err := ioutil.ReadFile(file) if err != nil { return nil, err } json.Unmarshal(data, &consignment) return consignment, err } func main() { service := micro.NewService(micro.Name("shippy.cli.consignment")) service.Init() client := pb.NewShippingServiceClient("shippy.service.consignment", service.Client())
هنا ، استوردنا مكتبات go-micro لإنشاء عملاء واستبدلنا رمز الاتصال الموجود برمز عميل go-micro ، والذي يستخدم إذن الخدمة بدلاً من الاتصال مباشرة بالعنوان.
ومع ذلك ، إذا قمت بتشغيل هذا ، فلن يعمل. هذا لأننا نطلق الآن خدماتنا في حاوية Docker ، التي لها mdns الخاصة بها ، منفصلة عن مضيف mdns الذي نستخدمه حاليًا. تتمثل أسهل طريقة لإصلاح ذلك في التأكد من تشغيل كل من الخدمة والعميل في dockerland ، بحيث يعمل كلاهما على نفس المضيف ويستخدم طبقة الشبكة نفسها. لذلك ، دعونا إنشاء جعل شحنة- CLI / Makefile وإنشاء بعض الإدخالات.
build: GOOS=linux GOARCH=amd64 go build docker build -t shippy-cli-consignment . run: docker run shippy-cli-consignment
كما كان من قبل ، نريد أن نبني ثنائياً لنظام Linux. عندما نطلق صورة عامل الإرساء ، نريد تمرير متغير بيئة لإعطاء الأمر go-micro لاستخدام mdns.
الآن دعونا ننشئ Dockerfile لأداة CLI الخاصة بنا:
FROM alpine:latest RUN mkdir -p /app WORKDIR /app ADD consignment.json /app/consignment.json ADD consignment-cli /app/consignment-cli CMD ["./shippy-cli-consignment"]
هذا مشابه جدًا لخدمة Dockerfile التي نقدمها ، إلا أنه يستخرج أيضًا ملف بيانات json الخاص بنا.
الآن عندما تقوم بتشغيل $ make run في شحنة shi-cli ، سترى Created: true ، تمامًا كما كان من قبل.
الآن ، يبدو أن الوقت قد حان لإلقاء نظرة على ميزة Docker الجديدة: بناء متعدد المراحل. هذا يسمح لنا باستخدام صور Docker متعددة في Dockerfile واحد.
هذا مفيد بشكل خاص في حالتنا ، حيث يمكننا استخدام صورة واحدة لإنشاء ملفنا الثنائي بكل التبعيات الصحيحة. ثم استخدم الصورة الثانية لإطلاقها. لنجرب هذا ، سأترك تعليقات مفصلة مع الكود:
شحنة الخدمة / Dockerfile # consignment-service/Dockerfile # golang, # . `as builder`, # , . FROM golang:alpine as builder RUN apk --no-cache add git # gopath WORKDIR /app/shippy-service-consignment # COPY . . RUN go mod download # , # Alpine. RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-consignment # FROM, # Docker . FROM alpine:latest # , - RUN apk --no-cache add ca-certificates # , . RUN mkdir /app WORKDIR /app # , , # `builder` # , # , , # . ! COPY --from=builder /app/shippy-service-consignment/shippy-service-consignment . # ! # # run time . CMD ["./shippy-service-consignment"]
الآن سأنتقل إلى ملفات Docker الأخرى وأتبع هذا النهج الجديد. أوه ، ولا تنسَ إزالة بنية go $ من Makefiles!
خدمة السفينة
لنقم بإنشاء خدمة ثانية. لدينا خدمة (شيبى خدمة شحنة) ، والتي تتعامل مع تنسيق دفعة من الحاويات مع السفينة ، والتي هي الأنسب لهذه الدفعة. لمطابقة الدُفعة لدينا ، يجب أن نرسل وزن الحاويات وعددها إلى خدمة السفن الجديدة الخاصة بنا ، والتي ستعثر بعد ذلك على سفينة قادرة على معالجة هذه الدُفعة.
قم بإنشاء دليل جديد في دليل
خدمة أوعية الجذر الخاص بـ
mkdir $ ، والآن قم بإنشاء دليل فرعي لتعريف خدمات protobuf الجديد ،
$ mkdir -p shippy-service-vessel / proto / vessel . الآن ، لنقم بإنشاء ملف protobuf جديد ،
$ touch shippy-service-vessel / proto / vessel / vessel.proto .
نظرًا لأن تعريف protobuf هو في جوهر تصميم برامجنا ، فلنبدأ به.
سفينة / سفينة // shippy-service-vessel/proto/vessel/vessel.proto syntax = "proto3"; package vessel; service VesselService { rpc FindAvailable(Specification) returns (Response) {} } message Vessel { string id = 1; int32 capacity = 2; int32 max_weight = 3; string name = 4; bool available = 5; string owner_id = 6; } message Specification { int32 capacity = 1; int32 max_weight = 2; } message Response { Vessel vessel = 1; repeated Vessel vessels = 2; }
كما ترون ، هذا يشبه إلى حد كبير خدمتنا الأولى. نحن ننشئ خدمة بأسلوب واحد لكل إجراء ، يسمى FindAvailable. هذا يأخذ نوعًا من المواصفات ويعيد نوعًا من الاستجابة. إرجاع نوع الاستجابة إما نوع السفينة أو السفن متعددة باستخدام حقل التكرار.
الآن نحن بحاجة إلى إنشاء Makefile للتعامل مع منطق البناء الخاص بنا ونص بدء التشغيل الخاص بنا.
$ touch shippy-service-vessel / Makefile . افتح هذا الملف وأضف ما يلي:
// vessel-service/Makefile build: protoc -I. --go_out=plugins=micro:. \ proto/vessel/vessel.proto docker build -t shippy-service-vessel . run: docker run -p 50052:50051 -e MICRO_SERVER_ADDRESS=:50051 shippy-service-vessel
هذا مماثل تقريبا لأول Makefile أنشأناه لخدمة الشحن لدينا ، ومع ذلك لاحظ أن أسماء الخدمات والموانئ تغيرت قليلا. لا يمكننا تشغيل حاويتي رصيف على نفس المنفذ ، لذلك نستخدم إعادة توجيه منفذ Dockers بحيث تعيد هذه الخدمة التوجيه من 50051 إلى 50052 على الشبكة المضيفة.
نحتاج الآن إلى Dockerfile باستخدام تنسيقنا الجديد متعدد المراحل:
# vessel-service/Dockerfile FROM golang:alpine as builder RUN apk --no-cache add git WORKDIR /app/shippy-service-vessel COPY . . RUN go mod download RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-vessel FROM alpine:latest RUN apk --no-cache add ca-certificates RUN mkdir /app WORKDIR /app COPY --from=builder /app/shippy-service-vessel . CMD ["./shippy-service-vessel"]
أخيرًا ، يمكننا كتابة تنفيذنا:
الآن دعنا ننتقل إلى الجزء المثير للاهتمام. عندما ننشئ شحنة ، نحتاج إلى تغيير خدمة مناولة البضائع الخاصة بنا للاتصال بخدمة البحث عن السفينة والعثور على السفينة وتحديث المعلمة ship_id في الشحنة التي تم إنشاؤها:
شيبي / شحنة الخدمة / main.go package main import ( "context" "fmt" "log" "sync" pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment" vesselProto "github.com/EwanValentine/shippy-service-vessel/proto/vessel" "github.com/micro/go-micro" ) const ( port = ":50051" ) type repository interface { Create(*pb.Consignment) (*pb.Consignment, error) GetAll() []*pb.Consignment }
أنشأنا هنا مثيل العميل لخدمة الشحن الخاصة بنا ، والذي يسمح لنا باستخدام اسم الخدمة ، أي shipy.service.vessel للاتصال بخدمة السفينة كعميل والتفاعل مع أساليبها.
في هذه الحالة ، طريقة واحدة فقط (FindAvailable). نحن نشحن وزن الدفعة جنبًا إلى جنب مع عدد الحاويات التي نريد شحنها كمواصفات لخدمة السفينة. الذي يعيد لنا السفينة المقابلة لهذه المواصفات.قم بتحديث ملف consignment-cli / consignment.json ، وحذف ship_id الثابت الترميز ، لأننا نريد تأكيد أن خدمة البحث عن السفن الخاصة بنا تعمل. أيضا ، دعونا نضيف بعض الحاويات أكثر وزيادة الوزن. على سبيل المثال:
{ "description": " ", "weight": 55000, "containers": [ { "customer_id": "_001", "user_id": "_001", "origin": "--" }, { "customer_id": "_002", "user_id": "_001", "origin": "" }, { "customer_id": "_003", "user_id": "_001", "origin": "" } ] }
الآن قم بتشغيل $ make build && make run في شحنة- CLI. يجب أن ترى إجابة مع قائمة البضائع التي تم إنشاؤها. في الأطراف الخاصة بك ، سترى أنه تم تعيين المعلمة vessel_id.لذلك ، لدينا اثنين من microservices مترابطة واجهة سطر الأوامر!في الجزء التالي من هذه السلسلة ، سننظر في حفظ بعض هذه البيانات باستخدام MongoDB. سنضيف أيضًا خدمة ثالثة وسنستخدم عامل إنشاء الرصيف لإدارة نظامنا البيئي المتنامي للحاويات محليًا.الجزء الأولمستودع EwanValentine الأصلي