تعد عملية نشر نسخ instances من تطبيق ويب يدويًا على خادم أو أكثر عملية رتيبة وتستغرق الكثير من الوقت، لكن ببذل جهد بسيط يمكنك جعل عملية نشر تطبيق الويب مؤتمتة دون الحاجة لتدخل يدوي. سنسلط الضوء في هذا المقال على طريقة بسيطة لأتمتة نشر تطبيقات الويب باستخدام كل من خطافات الويب webhooks وحزم البناء buildpacks وملفات Procfiles.
لطالما كانت عملية تشغيل تطبيقات الويب على الخادم ودفع push أية تحديثات لاحقة إليه عملية مضجرة وشاقة لمطوري الويب، مما يدفعهم للاستعانة بأحد مزودي المنصة كخدمة Platform as a service أو PaaS اختصارًا فهذه المنصات توفر لهم بيئة متكاملة لتطوير وتشغيل التطبيقات دون الحاجة إلى إدارة البنية التحتية، كما تُسهّل نشر التطبيقات دون أن يضطر المطوّر للاهتمام بإجراءات حجز الخادم وإعداده مقابل زيادة طفيفة في التكلفة ونقص بسيط في المرونة.
لكن قد يحتاج المطور أو مسؤول DevOps لنشر التطبيقات على خوادم مدارة دون الاستعانة بطرف ثالث. وفي هذه الحالة يفضل أن يبتكر أداة بسيطة لأتمتة عملية النشر، وهو أمر غير معقّد، ويغدو أسهل كلما كانت المتطلبات أبسط، لذا سنتعلم في مقال اليوم كيف نطور هذه الأداة وندعها تنفذ الأجزاء الروتينية والمملة من عمليات نشر تطبيقات الويب نيابة عنا ونوفر وقتنا وجهدنا.
ملاحظة: تختلف خطوات أتمتة نشر موقع مبني بلغة PHP عن خطوات نشر تطبيق Node.js. كما توجد حلول جاهزة أشمل تدعى حزم البناء buildpacks مثل دوكو Dokku تناسب مجموعة أوسع من التقنيات. سنشرح في هذا المقال أداة بسيطة لأتمتة عمليات نشر تطبيقات الويب الخاصة بنا، وذلك باستخدام خطافات الويب webhooks وحزم البناء buildpacks وملفات Procfiles في غيت هب GitHub. ويمكن الاطلاع على الشيفرة المصدرية source code للنموذج الأولي منها على موقع غيت هب GitHub. ملاحظة: تستخدم حزم البناء buildpacks لتحديد كيفية تكوين بيئة التطبيق قبل نشره، وتحدد ملفات Procfiles النصية أنواع العمليات في التطبيق، مما يسهل تشغيلها تلقائياً. وسنشرحها مفصلًا في الفقرات التالية.
بناء تطبيق الويب
ستكون أولى خطواتنا كتابة برنامج بسيط بلغة غو Go، لا تقلق إذا كنت لا تألف العمل بلغة غو GO فبنية الشيفرة البرمجية المستخدمة بسيطة نسبيًا وستجدها سهلة الفهم. ويمكن كتابة البرنامج باللغة التي تناسبنا. قبل البدء لنتأكد من تثبيت Go distribution على نظام التشغيل باتباع الخطوات المذكورة الموقع الرسمي، بعدها يفضل تنزيل الشيفرة المصدرية لأداتنا باستنساخ clone مستودع غيت هب GitHub repository، فهذا سيسهّل متابعة تتمة الخطوات، فمقاطع الشيفرة البرمجية في المستودع مسمّاة بنفس أسماء الملفات التي سنحددها في المقال. إن استخدام برنامج غو Go دونًا عن غيره لكتابة برنامجنا سيحدّ من حاجتنا للاستعانة باعتماديات dependencies خارجية، ففي حالتنا لن نحتاج لتشغيل برنامج غو على الخادم إلا لتثبيت غيت Git وباش Bash، كما أن البرنامج لا يستهلك الكثير من موارد الجهاز مثل الذاكرة رام RAM والمعالج CPU عندما نضبطه بطريقة صحيحة.
ما هي خطافات الويب Webhooks في غيت هب
تمكّننا خطافات الويب في غيت هب من ضبط المستودع GitHub repository ليصدّر الأحداث events كلما طرأ تغيير ضمن المستودع أو أجرى مستخدم إجراء معينًا في المستودع المستضاف، وهذا يتيح للمستخدمين التسجيل subscribe في هذه الأحداث وتلقي إشعارات بمختلف الأحداث التي تجري في مستودعنا وما يتعلق به من خلال استدعاءات الروابط URL invocations حيث سيستدعى عنوان URL معين تلقائيًا عندما يقع حدث معين في المستودع.
خطوات إنشاء Webhooks في غيت هب
يعد إنشاء خطاف ويب Webhooks عملية بسيطة سنلخصها في الخطوات التالية:
- نفتح صفحة الإعدادات settings في مستودعنا
- نضغط على خطافات الويب والخدمات Webhooks & Services في قائمة الخيارات
- نضغط على زر إضافة خطاف ويب Add webhook.
- ندخل رابط، ونضيف إن شئنا رمزًا سريًا secret يتيح للمستقبل التحقق من حمولة البيانات الواردة payload
- نحدد الاختيارات الأخرى في الصفحة حسب متطلباتنا
- أخيرًا، نضغط على الزر الأخضر Add webhook.
يقدم غيت هب توثيقًا مستفيضًا لخطافات الويب وجميع التفاصيل المرتبطة بها، لكن الجزء الذي سنحتاج إليه في مقالنا هذا هو حدث الدفع push event الذي يُصدَّر كلما أجرى مستخدم عملية دفع إلى أي فرع branch في المستودع repository.
حزم البناء Buildpacks
أصبحت حزم البناء جزءًا لا يتجزأ من عملية نشر التطبيقات في وقتنا هذا، ويستخدمها العديد من مزودي المنصات كخدمة PaaS. حيث تتيح لنا حزم البناء تحديد كيفية ضبط مكدس التقنيات stack التي سنحتاجها لتشغيل تطبيقنا قبل أن ننشره، وكتابتها بغاية السهولة، ففي معظم الأحيان لن نحتاج حتى لكتابتها بنفسنا حيث تستطيع بإجراء بحث سريع على الإنترنت إيجاد حزم بناء جاهزة يمكننا استخدامها في عملية نشر تطبيقنا دون الحاجة إلى تعديلها. فمثلًا تقدم منصة Heroku توثيقًا شاملًا عن بنية حزم البناء وقائمة تضم أكثر حزم البناء المستخدمة وأفضلها بنيةً. تستخدم أداة الأتمتة التي نبنيها سكربت تصريف compile script لتجهيز التطبيق قبل تشغيله. ولا بد أن ننوه أننا لن نستفيض في كتابة حزم البناء، وإنما سنفترض أن سكربتات حزم البناء buildpack scripts الجاهزة تتوافق مع بيئة سطر الأوامر باش Bash، وأنها ستُشغَّل على نظام تشغيل أبونتو Ubuntu مثُبّت حديثًا ولم يطرأ عليه أي تعديل، ويمكن لاحقًا التوسّع أكثر فيها حسب متطلبات عملنا.
ملفات Procfiles
هي ملفات نصية بسيطة تمكّننا من تحديد مختلف أنواع الإجرائيات processes المستخدمة في تطبيقنا، وتُستخدم عادةً في معظم التطبيقات البسيطة إجرائية ويب web واحدة فقط تعالج طلبات بروتوكول HTTP. لكتابة هذه الملفات ما علينا سوى تحديد نوع إجرائية واحد في كل سطر وذلك بكتابة اسمها متبوعًا بنقطة مزدوجة :
متبوعة بالأمر الذي سيولّد spawn هذه الإجرائية كمل يلي:
<type>: <command>
على سبيل المثال، إذا كان تطبيقنا مكتوب بنود جي إس Node.js، علينا تنفيذ الأمر node index.js
لتشغيل خادم الويب. يمكن إذًا أن ننشئ ملف Procfile في المجلد الأساسي للشيفرة البرمجية ونسميه Procfile ونكتب ما يلي ضمنه:
web: node index.js
نلزم تطبيقاتنا بتحديد أنواع الإجرائيات في ملفات Procfiles لنتمكن من تشغيلها تلقائيًا بعد سحب pulling الشيفرة البرمجية.
معالجة الأحداث
نحتاج في برنامجنا إلى إضافة خادم HTTP مهمته استقبال طلبات POST الواردة من غيت هب، لذا علينا أن نخصص مسار رابط URL path لمعالجة هذه الطلبات. ونقدم في النموذج التالي مثالًا عن بنية الدالة function التي ستعالج الحمولات payloads الواردة من هذا النوع:
// hook.go type HookOptions struct { App *App Secret string } func NewHookHandler(o *HookOptions) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { evName := r.Header.Get("X-Github-Event") if evName != "push" { log.Printf("Ignoring '%s' event", evName) return } body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if o.Secret != "" { ok := false for _, sig := range strings.Fields(r.Header.Get("X-Hub-Signature")) { if !strings.HasPrefix(sig, "sha1=") { continue } sig = strings.TrimPrefix(sig, "sha1=") mac := hmac.New(sha1.New, []byte(o.Secret)) mac.Write(body) if sig == hex.EncodeToString(mac.Sum(nil)) { ok = true break } } if !ok { log.Printf("Ignoring '%s' event with incorrect signature", evName) return } } ev := github.PushEvent{} err = json.Unmarshal(body, &ev) if err != nil { log.Printf("Ignoring '%s' event with invalid payload", evName) http.Error(w, "Bad Request", http.StatusBadRequest) return } if ev.Repo.FullName == nil || *ev.Repo.FullName != o.App.Repo { log.Printf("Ignoring '%s' event with incorrect repository name", evName) http.Error(w, "Bad Request", http.StatusBadRequest) return } log.Printf("Handling '%s' event for %s", evName, o.App.Repo) err = o.App.Update() if err != nil { return } }) }
نبدأ بالتحقق من نوع الحدث event الذي ولّد هذه الحمولة payload، وبما أن حدث الدفع push هو الحدث الوحيد الذي يهمنا يمكننا تجاهل بقية الأحداث. لكن حتى لو ضبطنا خطاف الويب ليصدّر أحداث الدفع فقط، لا بد أن نتوقع استقبال نوع آخر على الأقل من الأحداث على نقطة اتصال خطافنا hook endpoint التي تمثل الرابط الذي تُرسَل إليه البيانات وهو الحدث Ping
.
الغاية من هذا الحدث التأكد من ضبط خطاف الويب بطريقة صحيحة على غيت هب. الخطوة التالية هي قراءة محتوى الطلب الوارد بأكمله، ثم حساب قيمة التشفير باستخدام خوارزمية التشفير HMAC-SHA1 والرمز السري secret ذاته الذي حددناه عند ضبط خطاف الويب، ومقارنتها بالبصمة signature المتضمَّنة في ترويسة الطلب للتحقق من صلاحية الحمولة payload الواردة، وفي حالتنا اخترنا تجاهل خطوة التحقق هذه إذا لم يكن الرمز السري محدَّدًا.
ملاحظة: ننصح بوضع حد أقصى لحجم البيانات الذي يمكن التعامل معه قبل قراءة محتوى الطلب كاملًا، لكننا لن نتطرق إلى هذا في مقالنا وسنركز على الجوانب الأساسية للأداة فقط. بعدها، نستخدم هيكل بيانات struct من مكتبة GitHub client library for Go لتفريغ أو تحويل unmarshal حمولة البيانات الواردة فيها، وبما أننا نعلم أنه حدث دفع push فيمكننا استخدام بنية حدث الدفع. ثم نستخدم مكتبة التشفير القياسية بصيغة Json لتفريغ الحمولة إلى نسخة من البنية، ستنفذ بعدها بعض عمليات التحقق من الصحة وفي حال خلوها من أية مشكلة ستُستدعى الدالة التي تبدأ بتحديث تطبيقنا.
تحديث التطبيق
فور وصول إشعار بالحدث إلى طرفية خطاف الويب لدينا يمكننا البدء بتحديث تطبيقنا. سنتناول في هذا المقال تطبيق بسيط نوعًا ما لهذه الآلية الهدف منه التعرف على أساسيات إجراء عمليات أتمتة النشر، ويمكنك دومًا التوسّع فيها في حال رغبتك.
تهيئة المستودع المحلي
تبدأ هذه العملية بإجراء تحقق بسيط لنحدد إذا كانت هذه المرة الأولى التي نحاول نشر التطبيق فيها، وذلك بالتحقق من وجود مجلد المستودع المحلي local repository، فإذا لم يكن موجودًا نبدأ أولى خطواتنا بتهيئة initialize المستودع المحلي لدينا:
// app.go func (a *App) initRepo() error { log.Print("Initializing repository") err := os.MkdirAll(a.repoDir, 0755) // Check err cmd := exec.Command("git", "--git-dir="+a.repoDir, "init") cmd.Stderr = os.Stderr err = cmd.Run() // Check err cmd = exec.Command("git", "--git-dir="+a.repoDir, "remote", "add", "origin", fmt.Sprintf("git@github.com:%s.git", a.Repo)) cmd.Stderr = os.Stderr err = cmd.Run() // Check err return nil }
يمكننا تهيئة المستودع المحلي لتطبيقنا بالخطوات التالية:
- ننشئ مجلدًا للمستودع المحلي local repository في حال عدم وجوده.
-
نستخدم بالأمر
git init
لإنشاء مستودع فارغ bare repository -
نضيف رابطًا للمستودع البعيد remote repository إلى مستودعنا المحلي ونسميه
origin
. بعد تهيئة المستودع سيكون جلب التغييرات عملية بسيطة.
جلب التغييرات
لجلب التغييرات من المستودع البعيد لن نحتاج سوى لاستدعاء أمر واحد، وذلك وفق ما يلي:
// app.go func (a *App) fetchChanges() error { log.Print("Fetching changes") cmd := exec.Command("git", "--git-dir="+a.repoDir, "fetch", "-f", "origin", "master:master") cmd.Stderr = os.Stderr return cmd.Run() }
إن تنفيذ أمر جلب التغييرات git fetch
إلى مستودعنا المحلي بهذه الطريقة يجنبنا مشاكل عجز Git عن التقدم السريع fast-forward في بعض الحالات. وصحيح أننا لا ننصح بالاعتماد على عمليات الجلب الإجبارية، لكن إن احتجنا إلى تنفيذ عملية دفع إجبارية إلى مستودعنا البعيد يمكن اتباع هذه الطريقة.
تصريف التطبيق
بما أننا نستخدم سكربتات من حزم البناء buildpacks لتصريف compile تطبيقاتنا التي ننشرها، تتمثل مهمتنا بتنفيذ الخطوات البسيطة التالية من أجل تحضير وتصريف التطبيق بشكل آلي:
// app.go func (a *App) compileApp() error { log.Print("Compiling application") _, err := os.Stat(a.appDir) if !os.IsNotExist(err) { err = os.RemoveAll(a.appDir) // Check err } err = os.MkdirAll(a.appDir, 0755) // Check err cmd := exec.Command("git", "--git-dir="+a.repoDir, "--work-tree="+a.appDir, "checkout", "-f", "master") cmd.Dir = a.appDir cmd.Stderr = os.Stderr err = cmd.Run() // Check err buildpackDir, err := filepath.Abs("buildpack") // Check err cmd = exec.Command("bash", filepath.Join(buildpackDir, "bin", "detect"), a.appDir) cmd.Dir = buildpackDir cmd.Stderr = os.Stderr err = cmd.Run() // Check err cacheDir, err := filepath.Abs("cache") // Check err err = os.MkdirAll(cacheDir, 0755) // Check err cmd = exec.Command("bash", filepath.Join(buildpackDir, "bin", "compile"), a.appDir, cacheDir) cmd.Dir = a.appDir cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() }
لنشرح الكود أعلاه خطوة بخطوة:
- نبدأ بحذف المجلد السابق الخاص بتطبيقنا في حال وجوده، ثم ننشئ مجلدًا جديدًا ونسحب checkout محتويات الفرع الرئيسي master branch إليه
- نستخدم بعد ذلك سكريبت detect الموجود ضمن حزمة البناء التي ضبطناها لنحدد إذا كان باستطاعتنا معالجة التطبيق
- ننشئ مجلد لذاكرة التخزين المؤقتة ونسميه cache من أجل عملية تصريف حزمة البناء إذا دعت الحاجة لذلك، علمًا أن المجلد قد يكون موجودًا مسبقًا في حال أجرينا عمليات تصريف سابقة
- يمكننا في هذه المرحلة استدعاء سكربت التصريف المسمى compile من حزمة البناء وجعله يجهّز كل ما يحتاجه التطبيق قبل تشغيله عندما تُشغَّل حزم البناء بطريقة صحيحة فستتكمن من التعامل مع التخزين المؤقت caching وإعادة استخدام الموارد مسبقة التخزين
إعادة تشغيل التطبيق
سنعمل في تطبيق أتمتة النشر الذي نعدّه وفق نهج إيقاف الإجرائيات القديمة قبل تشغيل عملية التصريف، وتشغيل الإجرائيات الجديدة عند اكتمالها، ونجد هذا في الشيفرة التالية. في حال رغبنا بتطوير هذا النموذج الأولي يمكن تعديله بحيث نضمن عدم حدوث أي فترة انقطاع downtime خلال عمليات التحديث.
// app.go func (a *App) stopProcs() error { log.Print(".. stopping processes") for _, n := range a.nodes { err := n.Stop() if err != nil { return err } } return nil } func (a *App) startProcs() error { log.Print("Starting processes") err := a.readProcfile() if err != nil { return err } for _, n := range a.nodes { err = n.Start() if err != nil { return err } } return nil }
في هذا النموذج من برنامجنا نوقف مختلف الإجرائيات ونشغلها بالمرور على مصفوفة من العقد nodes، تكون فيها كل عقدة عبارة عن إجرائية تقابل إحدى نسخ التطبيق وفق الإعدادات المضبوطة قبل تشغيل أداة الأتمتة على الخادم. نراقب في أداتنا الحالة الأساسية لإجرائية كل عقدة، كما نحتفظ بملفات سجل log files عنها. قبل تشغيل جميع العقد يُسنَد إلى كل منها رقم منفذ فريد، بدءًا من رقم نحدده وفق ما يلي:
// node.go func NewNode(app *App, name string, no int, port int) (*Node, error) { logFile, err := os.OpenFile(filepath.Join(app.logsDir, fmt.Sprintf("%s.%d.txt", name, no)), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { return nil, err } n := &Node{ App: app, Name: name, No: no, Port: port, stateCh: make(chan NextState), logFile: logFile, } go func() { for { next := <-n.stateCh if n.State == next.State { if next.doneCh != nil { close(next.doneCh) } continue } switch next.State { case StateUp: log.Printf("Starting process %s.%d", n.Name, n.No) cmd := exec.Command("bash", "-c", "for f in .profile.d/*; do source $f; done; "+n.Cmd) cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", n.App.appDir)) cmd.Env = append(cmd.Env, fmt.Sprintf("PORT=%d", n.Port)) cmd.Env = append(cmd.Env, n.App.Env...) cmd.Dir = n.App.appDir cmd.Stdout = n.logFile cmd.Stderr = n.logFile err := cmd.Start() if err != nil { log.Printf("Process %s.%d exited", n.Name, n.No) n.State = StateUp } else { n.Process = cmd.Process n.State = StateUp } if next.doneCh != nil { close(next.doneCh) } go func() { err := cmd.Wait() if err != nil { log.Printf("Process %s.%d exited", n.Name, n.No) n.stateCh <- NextState{ State: StateDown, } } }() case StateDown: log.Printf("Stopping process %s.%d", n.Name, n.No) if n.Process != nil { n.Process.Kill() n.Process = nil } n.State = StateDown if next.doneCh != nil { close(next.doneCh) } } } }() return n, nil } func (n *Node) Start() error { n.stateCh <- NextState{ State: StateUp, } return nil } func (n *Node) Stop() error { doneCh := make(chan int) n.stateCh <- NextState{ State: StateDown, doneCh: doneCh, } <-doneCh return nil }
قد تبدو كتابة هذه الشيفرة البرمجية للوهلة الأولى أكثر الخطوات تعقيدًا حتى الآن، لذا دعنا نبسطها بتقسيمها إلى 4 أجزاء: يمثل مضمون دالة NewNode
أول جزئين، فعندما تُستدعَى هذه الدالة تهيئ populate نسخة من هيكل بيانات Node
بإعدادات محددة وتولّد برنامجًا routine بلغة غو Go مهمته إيقاف الإجرائيات المقابلة لهذه العقدة وتشغيلها.
يمثل التابعان Start
وStop
في بنية Node
الجزئين التاليين، حيث توقَف الإجرائية أو تشغَّل بتمرير رسالة message
خلال قناة معينة تفيد بأن برنامج غو هذا الذي ينفَّذ على كل عقدة على حدة يستمر في المراقبة. يمكن تمرير رسالة لتشغيل إجرائية ورسالة مختلفة لإيقافها.
بما أن الخطوات الفعلية لإيقاف أو تشغيل إجرائية تحدث ضمن برنامج Go routine، فلا مجال لحدوث حالات تسابق race conditions بسبب الطريقة التي تتم بها إدارة تشغيل الأكواد بشكل متوازي يبدأ برنامج غو Go routine حلقة لا نهائية infinite loop تبقى بانتظار رسالة message
في قناة stateCh
، فإذا مرت الرسالة في القناة يُرسَل طلب إلى العقدة node بتشغيل الإجرائية ضمن case StateUp
، ونستخدم برنامج باش Bash لتنفيذ هذا الأمر.
أثناء هذه العملية تُضبط العقدة الأمر بحيث يستخدم متغيرات البيئة environment variables التي حددها المستخدم، ويعيد توجيه الخرج القياسي standard output ومجاري الخطأ error streams إلى ملف سجل log file سبق تحديده. أما لإيقاف الإجرائية ضمن case StateDown
فإن البرنامج يوقفها قسريًا باستخدام أمر kill
. في حال رغبنا بإيقاف الإجرائية تدريجيًا يمكننا جعله يرسل إشارة SIGTERM والانتظار بضع ثوان قبل إيقافها. يسهّل تابعا Start
و Stop
تمرير الرسالة المناسبة إلى القناة، إذ يرسل التابع Start
رسالة إلى القناة بتشغيل الإجرائية ويعود، في حين ينتظر تابع Stop
أن تتوقف الإجرائية قبل أن يعود.
جمع الخطوات السابقة
لم يتبقَ لنا الآن سوى إضافة جميع الشيفرات السابقة إلى الدالة الرئيسية لبرنامجنا، حيث سيُنفَّذ تحميل ملف الإعدادت وتحليله parse، وتحديث حزم البناء، ومحاولة تحديث تطبيقنا لمرة واحدة، وتشغيل خادم الويب ليصغي إلى حمولات حدث الدفع push event التي ترد من غيت هب.
// main.go func main() { cfg, err := toml.LoadFile("config.tml") catch(err) url, ok := cfg.Get("buildpack.url").(string) if !ok { log.Fatal("buildpack.url not defined") } err = UpdateBuildpack(url) catch(err) // Read configuration options into variables repo (string), env ([]string) and procs (map[string]int) // ... app, err := NewApp(repo, env, procs) catch(err) err = app.Update() catch(err) secret, _ := cfg.Get("hook.secret").(string) http.Handle("/hook", NewHookHandler(&HookOptions{ App: app, Secret: secret, })) addr, ok := cfg.Get("core.addr").(string) if !ok { log.Fatal("core.addr not defined") } err = http.ListenAndServe(addr, nil) catch(err) }
بما أن أداتنا تتطلب أن تكون حزم البناء مستودعات غيت بسيطة، فلن تنفذ دالة UpdateBuildpack
المتضمنة في شيفرة buildpack.go سوى عمليتي نسخ git clone
وسحب git pull
لرابط المستودع عند الضرورة وذلك لتحديث النسخة المحلية من حزمة البناء.
تجربة الأداة
في حال لم نستنسخ المستودع بعد فيمكن استنساخه الآن. وإذا كان بGo distribution مثبتًا فيمكن البدء بتصريف compile البرنامج.
mkdir hopper cd hopper export GOPATH=`pwd` go get github.com/hjr265/toptal-hopper go install github.com/hjr265/toptal-hopper
لنطلع على ما تنفذه هذه الأوامر على التوالي:
- إنشاء مجلد وتسميته hopper
- الانتقال إليه
- جعله المجلد الخاص بمساحة العمل GOPATH
-
جلب الشيفرة من غيت هب إلى جانب مكتبات غو Go الضرورية، وتصريف البرنامج لشيفرة binary يمكن إيجادها في مجلد
$GOPATH/bin
قبل استخدام هذه الأداة على خادم علينا أن ننشئ تطبيق ويب بسيط لنختبر هذه الأداة عليه. في حال رغبنا بالحصول على تطبيق جاهز قد أنشأ صاحب المقال تطبيق ويب بسيط يعرض عبارة "Hello World" بلغة Node.js ورفعه إلى مستودع غيت هب آخر يمكننا اشتقاقه fork وإعادة استخدامه لاختبار الأداة. علينا في الخطوة التالية رفع الشيفرة الثنائية المصرَّفة compiled binary إلى خادم وإنشاء ملف إعدادات في المجلد ذاته:
# config.tml [core] addr = ":26590" [buildpack] url = "https://github.com/heroku/heroku-buildpack-nodejs.git" [app] repo = "hjr265/hopper-hello.js" [app.env] GREETING = "Hello" [app.procs] web = 1 [hook] secret = ""
لنبدأ بأول خيار في ملف الإعدادات لدينا وهو core.addr
الذي يتيح لنا ضبط منفذ HTTP لخادم الويب الداخلي لبرنامجنا، وقد اخترناه في مثالنا هذا ليكون 26590:
وهذا يعني أن برنامجنا سيصغي إلى حمولات حدث الدفع عن طريق الرابط http://{host}:26590/hook
.
عندما نضبط خطاف الويب في غيت هب ما عليك سوى تبديل {host}
أي المضيف باسم النطاق أو عنوان IP الذي يشير إلى خادمنا. وفي حال كنا نستخدم جدار حماية على خادمنا لا ننسى أن فتح هذا المنفذ عليه. ثم نحدد حزمة البناء بإضافة رابط غيت الذي يشير إليها، ونستخدم في مثالنا حزمة بناء بلغة Node.js الخاصة بهيروكو Heroku.
أما خيار app
فقد حددنا ضمنه في خيار repo
الاسم الكامل لمستودع غيت هب الذي يستضيف شيفرة التطبيق البرمجية، وبما أن رابط استضافة شيفرة التطبيق المستخدم في مثالنا https://github.com/hjr265/hopper-hello.js
حددنا الاسم الكامل للمستودع hjr265/hopper-hello.js
، ثم حددنا بعض متغيرات البيئة الخاصة بالتطبيق وعدد كل نوع من الإجرائيات التي نحتاجها، وأنهينا الملف باختيار رمز سري لنتحقق من حمولات حدث الدفع الواردة.
يمكننا الآن تشغيل برنامج الأتمتة الذي بنيناه على الخادم. في حال كانت جميع إعداداتنا صحيحة إضافة إلى ضبط مفاتيح SSH Keys حتى يُسمح للخادم بالوصول إلى المستودع، سينجح البرنامج في جلب الشيفرة البرمجية وتجهيز البيئة باستخدام حزمة البناء وتشغيل التطبيق.
ما علينا الآن سوى إعداد خطاف ويب في مستودع غيت هب لتصدير أحداث الدفع وتوجيهها إلى الرابط http://{host}:26590/hook
. ولا ننسى بالطبع استبدال {host}
باسم النطاق أو عنوان IP الذي يشير إلى الخادم. لنختبر عمل الأداة التي بنيناها، نحاول إجراء بعض التغييرات على التطبيق وندفعها إلى غيت هب، ستلاحظ حينها أن أداة الأتمتة ستبدأ العمل فورًا وتٌحدّث المستودع على الخادم وتصّرف التطبيق ثم تعيد تشغيله.
الخاتمة
نأمل أن تساعدكم هذه المقالة في بناء أداة أتمتة نشر تطبيقات الويب يمكن تعديلها وفق متطلبات العمل. بالطبع لا ننصح بتطبيقها بشكلها الحالي في بيئة تشغيلية دون تطويرها وتحسينها، إذ يمكن مثلًا تحسين تعاملها مع الأخطاء، وجعل الأداة تدعم عمليات الإيقاف وإعادة التشغيل التدريجية، واستخدام دوكر Docker لاحتواء الإجرائيات عوضًا عن تشغيلها مباشرة. أما في حال لم يتطلب عملنا تصميم أداة مخصصة لتنفيذ متطلبات معينة فيمكن دومًا استخدام أحد الحلول الجاهزة الموجودة على الإنترنت والتي تكون مجرَّبة ومستقرة.
ترجمة -وبتصرّف- للمقال Deploy Web Applications Automatically Using GitHub Webhooks لصاحبه Mahmud Ridwan.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.