Όποιος αναπτύσσει εφαρμογές ιστού και προσπαθεί να τις εκτελέσει σε δικούς του μη διαχειριζόμενους διακομιστές γνωρίζει την κουραστική διαδικασία που σχετίζεται με την ανάπτυξη της εφαρμογής τους και την προώθηση μελλοντικών ενημερώσεων. Οι πάροχοι πλατφόρμας ως υπηρεσίας (PaaS) έχουν διευκολύνει την ανάπτυξη εφαρμογών ιστού χωρίς να χρειάζεται να περάσουν από τη διαδικασία παροχής και διαμόρφωσης μεμονωμένων διακομιστών, με αντάλλαγμα μια μικρή αύξηση του κόστους και μείωση της ευελιξίας. Το PaaS μπορεί να έχει κάνει τα πράγματα ευκολότερα, αλλά μερικές φορές χρειαζόμαστε ή θέλουμε να αναπτύξουμε εφαρμογές στους δικούς μας μη διαχειριζόμενους διακομιστές. Η αυτοματοποίηση αυτής της διαδικασίας ανάπτυξης εφαρμογών ιστού στον διακομιστή σας μπορεί να ακούγεται συντριπτική στην αρχή, αλλά στην πραγματικότητα η δημιουργία ενός απλού εργαλείου για την αυτοματοποίηση μπορεί να είναι ευκολότερη από ό, τι νομίζετε. Το πόσο εύκολο θα είναι να εφαρμόσετε αυτό το εργαλείο εξαρτάται πολύ από το πόσο απλές είναι οι ανάγκες σας, αλλά σίγουρα δεν είναι δύσκολο να επιτευχθεί και πιθανώς μπορεί να σας βοηθήσει να εξοικονομήσετε πολύ χρόνο και προσπάθεια κάνοντας τα κουραστικά επαναλαμβανόμενα κομμάτια της εφαρμογής ιστού αναπτύξεις.
Πολλά προγραμματιστές έχουν βρει τους δικούς τους τρόπους αυτοματοποίησης των διαδικασιών ανάπτυξης των εφαρμογών Ιστού τους. Επειδή ο τρόπος ανάπτυξης των εφαρμογών ιστού εξαρτάται πολύ από την ακριβή στοίβα τεχνολογίας που χρησιμοποιείται, αυτές οι λύσεις αυτοματισμού διαφέρουν μεταξύ τους. Για παράδειγμα, τα βήματα που εμπλέκονται αυτόματα ανάπτυξη ενός ιστότοπου PHP είναι διαφορετικό από ανάπτυξη μιας εφαρμογής ιστού Node.js . Υπάρχουν και άλλες λύσεις, όπως Ντόκου , είναι αρκετά γενικά και αυτά τα πράγματα (που ονομάζονται buildpacks) λειτουργούν καλά με ένα ευρύτερο φάσμα τεχνολογικής στοίβας.
πώς μπορώ να ξέρω αν έχω διαρροή μνήμης
Σε αυτό το σεμινάριο, θα ρίξουμε μια ματιά στις θεμελιώδεις ιδέες πίσω από ένα απλό εργαλείο που μπορείτε να δημιουργήσετε για να αυτοματοποιήσετε τις αναπτύξεις εφαρμογών ιστού σας χρησιμοποιώντας GitHub webhooks, buildpacks και Procfiles. Ο πηγαίος κώδικας του πρωτοτύπου προγράμματος που θα διερευνήσουμε σε αυτό το άρθρο είναι διαθέσιμο στο GitHub .
Για να αυτοματοποιήσουμε την ανάπτυξη της εφαρμογής μας στο Web, θα γράψουμε ένα απλό πρόγραμμα Go. Εάν δεν είστε εξοικειωμένοι με το Go, μην διστάσετε να ακολουθήσετε, καθώς οι δομές κώδικα που χρησιμοποιούνται σε αυτό το άρθρο είναι αρκετά απλές και πρέπει να είναι κατανοητές. Εάν σας αρέσει, μπορείτε πιθανώς να μεταφέρετε ολόκληρο το πρόγραμμα σε μια γλώσσα της επιλογής σας αρκετά εύκολα.
Πριν ξεκινήσετε, βεβαιωθείτε ότι έχετε εγκαταστήσει τη διανομή Go στο σύστημά σας. Για να εγκαταστήσετε το Go, μπορείτε να ακολουθήσετε το βήματα που περιγράφονται στην επίσημη τεκμηρίωση .
Στη συνέχεια, μπορείτε να κατεβάσετε τον πηγαίο κώδικα αυτού του εργαλείου κλωνοποιώντας το Αποθήκη GitHub . Αυτό θα σας διευκολύνει να ακολουθήσετε καθώς τα αποσπάσματα κώδικα σε αυτό το άρθρο φέρουν ετικέτα με τα αντίστοιχα ονόματα αρχείων τους. Αν θέλετε, μπορείτε Δοκίμασέ το αμέσως.
Ένα σημαντικό πλεονέκτημα της χρήσης του Go για αυτό το πρόγραμμα είναι ότι μπορούμε να το δημιουργήσουμε με τρόπο που έχουμε ελάχιστες εξωτερικές εξαρτήσεις. Στην περίπτωσή μας, για να εκτελέσουμε αυτό το πρόγραμμα σε έναν διακομιστή πρέπει απλώς να διασφαλίσουμε ότι έχουμε εγκαταστήσει το Git και το Bash. Δεδομένου ότι τα προγράμματα Go συγκεντρώνονται σε στατικά συνδεδεμένα δυαδικά αρχεία, μπορείτε να μεταγλωττίσετε το πρόγραμμα στον υπολογιστή σας, να το ανεβάσετε στο διακομιστή και να το εκτελέσετε με σχεδόν μηδενική προσπάθεια. Για τις περισσότερες άλλες δημοφιλείς γλώσσες του σήμερα, αυτό θα απαιτούσε ένα περιβάλλον μαμούθ χρόνου εκτέλεσης ή διερμηνέα εγκατεστημένο στον διακομιστή μόνο για να εκτελέσετε τον αυτοματοποιητή ανάπτυξης. Τα προγράμματα Go, όταν γίνονται σωστά, μπορούν επίσης να είναι πολύ εύκολο να πάτε σε CPU και ΕΜΒΟΛΟ - που είναι κάτι που θέλετε από προγράμματα όπως αυτό.
Με το GitHub Webhooks, μπορείτε να διαμορφώσετε το αποθετήριο GitHub ώστε να εκπέμπει συμβάντα κάθε φορά που κάτι αλλάζει εντός του αποθετηρίου ή κάποιος χρήστης εκτελεί συγκεκριμένες ενέργειες στο φιλοξενούμενο αποθετήριο. Αυτό επιτρέπει στους χρήστες να εγγραφούν σε αυτά τα συμβάντα και να ειδοποιούνται μέσω προσκλήσεων URL για τα διάφορα συμβάντα που λαμβάνουν χώρα γύρω από το αποθετήριο σας.
Η δημιουργία ενός webhook είναι πολύ απλή:
Το GitHub παρέχει εκτεταμένη τεκμηρίωση για Webhooks και πώς ακριβώς λειτουργούν, ποιες πληροφορίες παρέχονται στο ωφέλιμο φορτίο σε απάντηση σε διάφορα γεγονότα, κ.λπ. Για τους σκοπούς αυτού του άρθρου, ενδιαφερόμαστε ιδιαίτερα για το Εκδήλωση 'push' που εκπέμπεται κάθε φορά που κάποιος σπρώχνει σε οποιονδήποτε κλάδο αποθετηρίου.
Τα Buildpacks είναι πολύ τυπικά αυτές τις μέρες. Χρησιμοποιούμενο από πολλούς παρόχους PaaS, τα buildpacks σάς επιτρέπουν να καθορίσετε τον τρόπο διαμόρφωσης της στοίβας πριν από την ανάπτυξη μιας εφαρμογής. Η σύνταξη buildpacks για την εφαρμογή ιστού σας είναι πολύ εύκολη, αλλά πιο συχνά από μια γρήγορη αναζήτηση στον Ιστό μπορεί να σας βρει ένα buildpack που μπορείτε να χρησιμοποιήσετε για την εφαρμογή ιστού σας χωρίς καμία τροποποίηση.
Εάν έχετε αναπτύξει εφαρμογή σε PaaS όπως το Heroku, ίσως γνωρίζετε ήδη τι είναι τα buildpacks και πού να τα βρείτε. Το Heroku έχει κάποια περιεκτική τεκμηρίωση σχετικά με τη δομή των buildpacks , και ένα λίστα με μερικά καλά κατασκευασμένα δημοφιλή buildpacks .
Το πρόγραμμα αυτοματοποίησης θα χρησιμοποιήσει σενάριο μεταγλώττισης για να προετοιμάσει την εφαρμογή πριν την εκκινήσει. Για παράδειγμα, μια έκδοση Node.js από την Heroku αναλύει το αρχείο package.json, κατεβάζει την κατάλληλη έκδοση του Node.js και κατεβάζει εξαρτήσεις NPM για την εφαρμογή. Αξίζει να σημειωθεί ότι για να διατηρήσουμε τα πράγματα απλά, δεν θα έχουμε εκτεταμένη υποστήριξη για buildpacks στο πρωτότυπο πρόγραμμα μας. Προς το παρόν, θα υποθέσουμε ότι τα σενάρια buildpack είναι γραμμένα για εκτέλεση με το Bash και ότι θα εκτελούνται σε μια νέα εγκατάσταση του Ubuntu όπως είναι. Εάν είναι απαραίτητο, μπορείτε εύκολα να το επεκτείνετε στο μέλλον για να αντιμετωπίσετε περισσότερες εσωτερικές ανάγκες.
python τι είναι χαρακτηριστικό
Τα Procfiles είναι απλά αρχεία κειμένου που σας επιτρέπουν να ορίσετε τους διάφορους τύπους διαδικασιών που έχετε στην εφαρμογή σας. Για τις περισσότερες απλές εφαρμογές, στην ιδανική περίπτωση θα έχετε μια μόνο διαδικασία «Ιστού» που θα ήταν η διαδικασία που χειρίζεται αιτήματα HTTP.
Το γράψιμο του Procfiles είναι εύκολο. Ορίστε έναν τύπο διαδικασίας ανά γραμμή πληκτρολογώντας το όνομά του, ακολουθούμενο από άνω και κάτω τελεία, ακολουθούμενο από την εντολή που θα δημιουργήσει τη διαδικασία:
:
Για παράδειγμα, εάν εργαζόσασταν με μια εφαρμογή ιστού που βασίζεται στο Node.js, για να ξεκινήσετε τον διακομιστή ιστού, θα εκτελέσετε την εντολή 'node index.js'. Μπορείτε απλά να δημιουργήσετε ένα Procfile στον βασικό κατάλογο του κώδικα και να το ονομάσετε 'Procfile' με τα εξής:
web: node index.js
Θα απαιτήσουμε εφαρμογές για τον καθορισμό τύπων διεργασιών στο Procfiles, έτσι ώστε να μπορούμε να τα ξεκινήσουμε αυτόματα μετά την εισαγωγή του κώδικα.
Στο πρόγραμμά μας, πρέπει να συμπεριλάβουμε έναν διακομιστή HTTP που θα μας επιτρέπει να λαμβάνουμε εισερχόμενα αιτήματα POST από το GitHub. Θα χρειαστεί να αφιερώσουμε κάποια διαδρομή URL για να χειριστούμε αυτά τα αιτήματα από το GitHub. Η λειτουργία που θα χειριστεί αυτά τα εισερχόμενα ωφέλιμα φορτία θα μοιάζει με αυτό:
// 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 } }) }
Ξεκινάμε επαληθεύοντας τον τύπο συμβάντος που έχει δημιουργήσει αυτό το ωφέλιμο φορτίο. Δεδομένου ότι ενδιαφερόμαστε μόνο για την εκδήλωση 'push', μπορούμε να αγνοήσουμε όλα τα άλλα γεγονότα. Ακόμα κι αν διαμορφώσετε το webhook ώστε να εκπέμπει μόνο συμβάντα 'push', θα εξακολουθεί να υπάρχει τουλάχιστον ένα άλλο είδος συμβάντος που μπορείτε να περιμένετε να λάβετε στο άκρο του άγκιστρου: 'ping'. Ο σκοπός αυτού του συμβάντος είναι να προσδιορίσει εάν το webhook έχει ρυθμιστεί επιτυχώς στο GitHub.
Στη συνέχεια, διαβάζουμε ολόκληρο το εισερχόμενο αίτημα, υπολογίζουμε το HMAC-SHA1 του χρησιμοποιώντας το ίδιο μυστικό που θα χρησιμοποιήσουμε για να διαμορφώσουμε το webhook και να προσδιορίσουμε την εγκυρότητα του εισερχόμενου ωφέλιμου φορτίου συγκρίνοντάς το με την υπογραφή που περιλαμβάνεται στην κεφαλίδα του αίτηση. Στο πρόγραμμά μας, αγνοούμε αυτό το βήμα επικύρωσης εάν δεν έχει διαμορφωθεί το μυστικό. Σε μια σημείωση, μπορεί να μην είναι μια σοφή ιδέα να διαβάσετε ολόκληρο το σώμα χωρίς τουλάχιστον να έχετε κάποιο ανώτερο όριο για το πόσα δεδομένα θα θέλαμε να χειριστούμε εδώ, αλλά ας κρατήσουμε τα πράγματα απλά για να επικεντρωθούμε στις κρίσιμες πτυχές αυτού του εργαλείου.
Στη συνέχεια, χρησιμοποιούμε μια δομή από το Βιβλιοθήκη πελατών GitHub για Go για να αποσυμπιέσετε το εισερχόμενο ωφέλιμο φορτίο. Δεδομένου ότι γνωρίζουμε ότι είναι ένα συμβάν «ώθησης», μπορούμε να χρησιμοποιήσουμε το Δομή PushEvent . Στη συνέχεια, χρησιμοποιούμε την τυπική βιβλιοθήκη κωδικοποίησης json για να αποσυμπιέσουμε το ωφέλιμο φορτίο σε μια παρουσία της δομής. Πραγματοποιούμε μερικούς ελέγχους υγιεινής και αν όλα είναι εντάξει, επικαλούμεθα τη λειτουργία που ξεκινά την ενημέρωση της εφαρμογής μας.
Μόλις λάβουμε μια ειδοποίηση συμβάντος στο τελικό σημείο του webhook, μπορούμε να αρχίσουμε να ενημερώνουμε την εφαρμογή μας. Σε αυτό το άρθρο, θα ρίξουμε μια ματιά σε μια αρκετά απλή εφαρμογή αυτού του μηχανισμού και σίγουρα θα υπάρξει περιθώριο για βελτιώσεις. Ωστόσο, θα πρέπει να είναι κάτι που θα μας κάνει να ξεκινήσουμε με κάποια βασική αυτοματοποιημένη διαδικασία ανάπτυξης.
Αυτή η διαδικασία θα ξεκινήσει με έναν απλό έλεγχο για να προσδιοριστεί αν είναι η πρώτη φορά που προσπαθούμε να αναπτύξουμε την εφαρμογή. Θα το κάνουμε ελέγχοντας εάν υπάρχει ο τοπικός κατάλογος αποθετηρίου Εάν δεν υπάρχει, θα ξεκινήσουμε πρώτα το τοπικό αποθετήριό μας:
// 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(' [email protected] :%s.git', a.Repo)) cmd.Stderr = os.Stderr err = cmd.Run() // Check err return nil }
Αυτή η μέθοδος στην δομή εφαρμογών μπορεί να χρησιμοποιηθεί για την προετοιμασία του τοπικού αποθετηρίου και οι μηχανισμοί της είναι εξαιρετικά απλοί:
Μόλις έχουμε ένα αρχικοποιημένο αποθετήριο, η ανάκτηση αλλαγών θα πρέπει να είναι απλή.
πρότυπο προβολής εβδομαδιαίας ταμειακής ροής
Για τη λήψη αλλαγών από το απομακρυσμένο αποθετήριο, πρέπει απλώς να επικαλεστούμε μία εντολή:
// 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 να μην είναι σε θέση να προωθήσει γρήγορα σε ορισμένα σενάρια. Όχι ότι οι καταναγκαστικές ανακτήσεις είναι κάτι στο οποίο πρέπει να βασίζεστε, αλλά εάν πρέπει να κάνετε μια ώθηση στο απομακρυσμένο αποθετήριο σας, αυτό θα το χειριστεί με χάρη.
Εφόσον χρησιμοποιούμε σενάρια από buildpacks για να συντάξουμε τις εφαρμογές μας που αναπτύσσονται, η δουλειά μας εδώ είναι σχετικά εύκολη:
// 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() }
Ξεκινάμε με την κατάργηση του προηγούμενου καταλόγου εφαρμογών (εάν υπάρχει). Στη συνέχεια, δημιουργούμε ένα νέο και ελέγξτε τα περιεχόμενα του κύριου κλάδου σε αυτό. Στη συνέχεια, χρησιμοποιούμε το σενάριο 'εντοπισμός' από το διαμορφωμένο buildpack για να προσδιορίσουμε αν η εφαρμογή είναι κάτι που μπορούμε να χειριστούμε. Στη συνέχεια, δημιουργούμε έναν κατάλογο 'cache' για τη διαδικασία σύνταξης buildpack, εάν χρειάζεται. Δεδομένου ότι αυτός ο κατάλογος παραμένει σε όλες τις εκδόσεις, ενδέχεται να μην χρειαστεί να δημιουργήσουμε έναν νέο κατάλογο επειδή θα υπάρχει ήδη από κάποια προηγούμενη διαδικασία σύνταξης. Σε αυτό το σημείο, μπορούμε να επικαλεστούμε το σενάριο «μεταγλώττισης» από το buildpack και να το προετοιμάσουμε όλα όσα είναι απαραίτητα για την εφαρμογή πριν από την εκκίνηση. Όταν τα buildpacks εκτελούνται σωστά, μπορούν να χειριστούν την προσωρινή αποθήκευση και την επαναχρησιμοποίηση πόρων που είχαν αποθηκευτεί στο παρελθόν μόνοι τους.
Κατά την εφαρμογή αυτής της αυτοματοποιημένης διαδικασίας ανάπτυξης, θα σταματήσουμε τις παλιές διαδικασίες πριν ξεκινήσουμε τη διαδικασία σύνταξης και, στη συνέχεια, ξεκινήσουμε τις νέες διαδικασίες μόλις ολοκληρωθεί η φάση σύνταξης. Αν και αυτό καθιστά εύκολη την εφαρμογή του εργαλείου, αφήνει μερικούς δυνητικά καταπληκτικούς τρόπους βελτίωσης της αυτοματοποιημένης διαδικασίας ανάπτυξης. Για να βελτιώσετε αυτό το πρωτότυπο, πιθανότατα μπορείτε να ξεκινήσετε διασφαλίζοντας μηδενικό χρόνο διακοπής κατά τη διάρκεια των ενημερώσεων. Προς το παρόν, θα συνεχίσουμε με την απλούστερη προσέγγιση:
// 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 }
Στο πρωτότυπό μας, σταματάμε και ξεκινάμε τις διάφορες διεργασίες επαναλαμβάνοντας μια σειρά κόμβων, όπου κάθε κόμβος είναι μια διαδικασία που αντιστοιχεί σε μία από τις παρουσίες της εφαρμογής (όπως έχει διαμορφωθεί πριν από την εκκίνηση αυτού του εργαλείου στο διακομιστή). Μέσα στο εργαλείο μας, παρακολουθούμε την τρέχουσα κατάσταση της διαδικασίας για κάθε κόμβο. Διατηρούμε επίσης μεμονωμένα αρχεία καταγραφής για αυτά. Πριν από την εκκίνηση όλων των κόμβων, ο καθένας έχει μια μοναδική θύρα ξεκινώντας από έναν δεδομένο αριθμό θύρας:
// 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 }
Με μια ματιά, αυτό μπορεί να φαίνεται λίγο πιο περίπλοκο από αυτό που έχουμε κάνει μέχρι τώρα. Για να καταλάβουμε τα πράγματα εύκολα, ας χωρίσουμε τον παραπάνω κώδικα σε τέσσερα μέρη. Τα δύο πρώτα βρίσκονται στη συνάρτηση 'NewNode'. Όταν καλείται, συμπληρώνει μια παρουσία της δομής «Κόμβος» και δημιουργεί μια ρουτίνα Go που βοηθά στην έναρξη και τη διακοπή της διαδικασίας που αντιστοιχεί σε αυτόν τον Κόμβο. Οι άλλες δύο είναι οι δύο μέθοδοι στο 'Node' δομή: 'Έναρξη' και 'Διακοπή'. Μια διαδικασία ξεκινά ή σταματά με τη μετάδοση ενός 'μηνύματος' μέσω ενός συγκεκριμένου καναλιού που αυτή η ρουτίνα Go ανά κόμβο παρακολουθεί. Μπορείτε είτε να μεταβιβάσετε ένα μήνυμα για να ξεκινήσετε τη διαδικασία είτε ένα διαφορετικό μήνυμα για να το σταματήσετε. Δεδομένου ότι τα πραγματικά βήματα που εμπλέκονται στην έναρξη ή τη διακοπή μιας διαδικασίας συμβαίνουν σε μια ρουτίνα Go, δεν υπάρχει πιθανότητα να λάβετε συνθήκες αγώνα.
Η ρουτίνα Go ξεκινά έναν άπειρο βρόχο όπου περιμένει ένα 'μήνυμα' μέσω του καναλιού 'stateCh'. Εάν το μήνυμα που μεταβιβάζεται σε αυτό το κανάλι ζητά τον κόμβο να ξεκινήσει τη διαδικασία (στο 'case StateUp'), χρησιμοποιεί το Bash για να εκτελέσει την εντολή. Ενώ το κάνει αυτό, διαμορφώνει την εντολή για χρήση των καθορισμένων από το χρήστη μεταβλητών περιβάλλοντος. Επίσης ανακατευθύνει τις τυπικές ροές εξόδου και σφαλμάτων σε ένα προκαθορισμένο αρχείο καταγραφής.
Από την άλλη πλευρά, για να σταματήσει μια διαδικασία (μέσα στο 'Case StateDown'), απλώς τη σκοτώνει. Σε αυτό το σημείο θα μπορούσατε πιθανώς να γίνετε δημιουργικοί, και αντί να σκοτώσετε τη διαδικασία, στείλτε το αμέσως ένα SIGTERM και περιμένετε λίγα δευτερόλεπτα πριν πραγματικά το σκοτώσετε, δίνοντας στη διαδικασία τη δυνατότητα να σταματήσει χαριτωμένα.
Οι μέθοδοι 'Έναρξη' και 'Διακοπή' διευκολύνουν τη μετάδοση του κατάλληλου μηνύματος στο κανάλι. Σε αντίθεση με τη μέθοδο 'Έναρξη', η μέθοδος 'Διακοπή' περιμένει στην πραγματικότητα να σκοτωθούν οι διαδικασίες πριν επιστρέψουν. Το 'Έναρξη' απλώς μεταδίδει ένα μήνυμα στο κανάλι για να ξεκινήσει η διαδικασία και επιστρέφει.
Τέλος, το μόνο που χρειάζεται να κάνουμε είναι να συνδέσουμε τα πάντα μέσα στην κύρια λειτουργία του προγράμματος. Εδώ θα φορτώσουμε και θα αναλύσουμε το αρχείο διαμόρφωσης, θα ενημερώσουμε το buildpack, θα προσπαθήσουμε να ενημερώσουμε την εφαρμογή μας μία φορά και να ξεκινήσουμε τον διακομιστή ιστού για να ακούσουμε εισερχόμενα ωφέλιμα φορτία από το GitHub:
ποιο από αυτά περιγράφει καλύτερα ένα παράδειγμα χρηστικότητας;
// 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) }
Δεδομένου ότι απαιτούμε buildpacks να είναι απλά αποθετήρια Git, το 'UpdateBuildpack' (υλοποιήθηκε το buildpack.go ) εκτελεί απλώς ένα 'git clone' και ένα 'git pull', όπως απαιτείται, με το URL αποθετηρίου για να ενημερώσετε το τοπικό αντίγραφο του buildpack.
Σε περίπτωση που δεν έχετε κλωνοποιηθεί το αποθετήριο ακόμα, μπορείτε να το κάνετε τώρα. Εάν έχετε εγκαταστήσει τη διανομή Go, θα πρέπει να είναι δυνατή η μεταγλώττιση του προγράμματος αμέσως.
mkdir hopper cd hopper export GOPATH=`pwd` go get github.com/hjr265/toptal-hopper go install github.com/hjr265/toptal-hopper
Αυτή η ακολουθία εντολών θα δημιουργήσει έναν κατάλογο με το όνομα hopper, θα τον ορίσει ως GOPATH, θα πάρει τον κώδικα από το GitHub μαζί με τις απαραίτητες βιβλιοθήκες Go και θα μεταγλωττίσει το πρόγραμμα σε ένα δυαδικό λογισμικό που μπορείτε να βρείτε στον κατάλογο '$ GOPATH / bin'. Προτού μπορέσουμε να το χρησιμοποιήσουμε σε έναν διακομιστή, πρέπει να δημιουργήσουμε μια απλή εφαρμογή ιστού για να το δοκιμάσουμε. Για ευκολία, δημιούργησα μια απλή εφαρμογή web Node.js που μοιάζει με 'Γεια, κόσμος' και την ανέβασα ένα άλλο αποθετήριο GitHub που μπορείτε να πιείτε και να επαναχρησιμοποιήσετε για αυτό το τεστ. Στη συνέχεια, πρέπει να ανεβάσετε το μεταγλωττισμένο δυαδικό αρχείο σε έναν διακομιστή και να δημιουργήσουμε ένα αρχείο διαμόρφωσης στον ίδιο κατάλογο:
# 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'. Κατά τη ρύθμιση του GitHub webhook, απλώς αντικαταστήστε το '{host}' με το όνομα τομέα ή τη διεύθυνση IP που οδηγεί στο διακομιστή σας. Βεβαιωθείτε ότι η θύρα είναι ανοιχτή σε περίπτωση που χρησιμοποιείτε κάποιο είδος τείχους προστασίας.
Στη συνέχεια, επιλέγουμε ένα buildpack ορίζοντας το Git URL του. Εδώ χρησιμοποιούμε Το πακέτο Node.js του Heroku .
Στην ενότητα 'εφαρμογή', ορίζουμε το 'repo' στο πλήρες όνομα του αποθετηρίου GitHub που φιλοξενεί τον κωδικό εφαρμογής. Δεδομένου ότι φιλοξενώ το παράδειγμα εφαρμογής στο 'https://github.com/hjr265/hopper-hello.js', το πλήρες όνομα του αποθετηρίου είναι 'hjr265 / hopper-hello.js'.
Στη συνέχεια, ορίσαμε ορισμένες μεταβλητές περιβάλλοντος για την εφαρμογή και τον αριθμό κάθε μιας τύπος διεργασιών χρειαζόμαστε. Και τέλος, επιλέγουμε ένα μυστικό, ώστε να μπορούμε να επαληθεύσουμε τα εισερχόμενα ωφέλιμα φορτία συμβάντων.
Τώρα μπορούμε να ξεκινήσουμε το πρόγραμμα αυτοματοποίησης στον διακομιστή. Εάν όλα έχουν ρυθμιστεί σωστά (συμπεριλαμβανομένων των κλειδιών SSH ανάπτυξης, έτσι ώστε το αποθετήριο να είναι προσβάσιμο από το διακομιστή), το πρόγραμμα θα πρέπει να πάρει τον κώδικα, να προετοιμάσει το περιβάλλον χρησιμοποιώντας το buildpack και να ξεκινήσει την εφαρμογή. Τώρα το μόνο που χρειάζεται να κάνουμε είναι να δημιουργήσουμε ένα webhook στο αποθετήριο GitHub για να εκπέμπει γεγονότα push και να το δείξει στο 'http: // {host}: 26590 / hook'. Βεβαιωθείτε ότι έχετε αντικαταστήσει το '{host}' με το όνομα τομέα ή τη διεύθυνση IP που οδηγεί στο διακομιστή σας.
Τέλος δοκιμή Κάντε κάποιες αλλαγές στην εφαρμογή παραδείγματος και σπρώξτε τις στο GitHub. Θα παρατηρήσετε ότι το εργαλείο αυτοματισμού θα ξεκινήσει αμέσως και θα ενημερώσει το αποθετήριο στον διακομιστή, θα συντάξει την εφαρμογή και θα το επανεκκινήσει.
Από τις περισσότερες από τις εμπειρίες μας, μπορούμε να πούμε ότι αυτό είναι κάτι πολύ χρήσιμο. Η πρωτότυπη εφαρμογή που έχουμε ετοιμάσει σε αυτό το άρθρο μπορεί να μην είναι κάτι που θέλετε να χρησιμοποιήσετε σε ένα σύστημα παραγωγής ως έχει. Υπάρχει πολύς χώρος για βελτίωση. Ένα εργαλείο σαν αυτό θα πρέπει να έχει καλύτερο χειρισμό σφαλμάτων, να υποστηρίζει χαριτωμένους τερματισμούς / επανεκκινήσεις και ίσως θέλετε να χρησιμοποιήσετε κάτι σαν το Docker για να περιέχει τις διαδικασίες αντί να τις εκτελείτε απευθείας. Μπορεί να είναι πιο σοφό να καταλάβετε τι ακριβώς χρειάζεστε για τη συγκεκριμένη σας περίπτωση και να βρείτε ένα πρόγραμμα αυτοματοποίησης για αυτό. Ή ίσως χρησιμοποιήστε κάποια άλλη, πολύ πιο σταθερή, δοκιμασμένη στο χρόνο λύση διαθέσιμη σε όλο το Διαδίκτυο. Αλλά σε περίπτωση που θέλετε να αναπτύξετε κάτι πολύ προσαρμοσμένο, ελπίζω ότι αυτό το άρθρο θα σας βοηθήσει να το κάνετε αυτό και να δείξει πόσο χρόνο και προσπάθεια θα μπορούσατε ενδεχομένως να εξοικονομήσετε μακροπρόθεσμα, αυτοματοποιώντας τη διαδικασία ανάπτυξης της εφαρμογής ιστού.
Σχετίζεται με: Εξηγήθηκε η βελτιωμένη ροή Git