This is a creation in Article, where the information may have evolved or changed.
Preface
The HTTP section of the Golang standard library provides powerful Web application support, coupled with the support of middleware frameworks such as Negroni, to develop high-performance Web applications, such as providing RESTful API services. Typically these Web applications are deployed on multiple Linux operating system application servers, using Nginx as the reverse proxy for highly available cluster services. When applying version upgrades, how do you implement a more elegant version update of polymorphic servers?
Problem analysis
Web App updates, I think there are a couple of things that might need to be considered:
- The compiled application binary files and configuration files are uploaded to the server;
- The application server can sense that there is a new version upload;
- In the absence of a service stop, the hot update version;
- It is best to have all of the update procedures scripted to reduce manual operation errors.
Scheme
In fact, there are some open source projects in the go community that can automatically detect changes in Web applications and automate updates, but these applications are detecting source code, resource file updates, starting the build process, enabling automatic compilation and restart, such as gin and fresh, which are suitable for use in the development and testing phases, may not be suitable for application deployment and updates, but it provides a good idea.
Deployment Environment directory and version of the upload I will publish the application binaries and configuration files, stored in a directory, such as ~/app/release, each version is retained in this directory, such as app.1.0, app.1.1, app.2.0, once found a problem, can be timely rollback. Also, under the ~/app directory, use the soft link file to point to the latest version, such as
ln -s ~/app/release/app.2.0 ~/app/app.bin
Also, use a text file that is saved under ~/app/release to indicate the current version of the app, such as current.conf:
{ "bin.file": "~/app/release/app.2.0", "cfg.file": "~/app/release/cfg.2.0"}
When you need to update the version of the server, you can invoke the SCP through a script, upload the new version to the release directory, and then update the current.conf file. Monitor the current.conf file, learn that the version update current.conf file is the current version, once this file changes, that is, the version needs to be updated (or rolled back), we only need to monitor the change of this file, once the change, then do the corresponding processing. The monitoring of files can be achieved by fsnotify.
func watch() { watcher, err := fsnotify.NewWatcher() if err != nil { logger.Fatal(err) } defer watcher.Close() go func() { for { select { case event := <-watcher.Events: logger.Println("event:", event) if event.Op&fsnotify.Write == fsnotify.Write { logger.Println("notify runner to do the ln -s and restart server.") restartChan <- true } case err := <-watcher.Errors: logger.Println("error:", err) } } }() err = watcher.Add("/path/to/current.conf") if err != nil { logger.Fatal(err) } <- make(chan bool)}
After restarting the service to monitor changes to the current.conf file, the next step is to restart the service. In order for the service to be uninterrupted and gracefully restarted, endless can be used to replace the listenandserve of the standard library net/http:
n := negroni.New() n.Use(middleware.NewRecovery()) n.Use(middleware.NewMaintainMiddleware()) n.Use(middleware.NewLogMiddleware()) n.Use(middleware.NewStatic(http.Dir("static"))) n.UseHandler(router.NewRouter()) log.Fatal(endless.ListenAndServe(":3000", n))
After the current.conf change, first point the soft link file under ~/app to the latest version, and then use
kill -HUP
Notifies the app to restart.
Func run () {for {<-Restartchan c, err: = Ioutil. ReadFile ("/path/to/current.conf") if err! = Nil {logger. PRINTLN ("current.conf read error:", err) return} var j interface{} Err = json. Unmarshal (c, &j) if err! = Nil {logger. Println ("Current.conf Parse Error:", err) return} parsed, OK: = J. (map[string]interface{}) If!ok {logger. Println ("Current.conf Parse error:mapping errors") return} exec. Command ("rm", "App.bin"). Run () exec. Command ("ln", "-S", parsed["Bin.file"]. ( String), "App.bin"). Run () exec. Command ("rm", "app.conf"). Run () exec. Command ("ln", "-S", parsed["Cfg.file"]. ( String), "App.cfg"). Run () If!started {cmd: = exec. Command ("./app.bin", "-C", "app.cfg") started = true} else {processes, _: = PS. Processes () for _, V: = Range Processes { If strings. Contains (V.executable (), parsed["Bin.file"]) {process, _: = OS. Findprocess (V.pid ()) process. Signal (Syscall. SIGHUP)}}}}