概要
最近遇到一個需求,golang應用部署在遠程機器,遠程機器在內網,部署之后不方便再次登錄此遠程機器去升級,
因此,需要golang應用自動檢查是否需要升級,如果需要升級,則下載二進制后自升級,
自升級庫
golang自升級的庫有好幾個,比較之后決定采用: https://github.com/jpillora/overseer
此庫不是最全面的,但是實作原理和提供的介面比較簡單,代碼量也不大,便于定制,
overseer 庫簡介
overseer 將升級的程式啟動在主協程上,真正完成功能的部分作為 Program(這個可以當做實際程式的 main 函式)運行,
其中最重要的2個部分是 **Config **和 Fetcher,
Config
overseer 通過 Config 結構提供了一些引數來控制自更新,
// Config defines overseer's run-time configuration
type Config struct {
//Required will prevent overseer from fallback to running
//running the program in the main process on failure.
Required bool
//Program's main function
Program func(state State)
//Program's zero-downtime socket listening address (set this or Addresses)
Address string
//Program's zero-downtime socket listening addresses (set this or Address)
Addresses []string
//RestartSignal will manually trigger a graceful restart. Defaults to SIGUSR2.
RestartSignal os.Signal
//TerminateTimeout controls how long overseer should
//wait for the program to terminate itself. After this
//timeout, overseer will issue a SIGKILL.
TerminateTimeout time.Duration
//MinFetchInterval defines the smallest duration between Fetch()s.
//This helps to prevent unwieldy fetch.Interfaces from hogging
//too many resources. Defaults to 1 second.
MinFetchInterval time.Duration
//PreUpgrade runs after a binary has been retrieved, user defined checks
//can be run here and returning an error will cancel the upgrade.
PreUpgrade func(tempBinaryPath string) error
//Debug enables all [overseer] logs.
Debug bool
//NoWarn disables warning [overseer] logs.
NoWarn bool
//NoRestart disables all restarts, this option essentially converts
//the RestartSignal into a "ShutdownSignal".
NoRestart bool
//NoRestartAfterFetch disables automatic restarts after each upgrade.
//Though manual restarts using the RestartSignal can still be performed.
NoRestartAfterFetch bool
//Fetcher will be used to fetch binaries.
Fetcher fetcher.Interface
}
一般用不到這么多引數,核心的是:
- Program
- Fetcher
常用有:
- Address
- Addresses
- MinFetchInterval
- PreUpgrade
Fetcher
除了 Config,overseer 中另一個重要的介面就是 Fetcher,
Fetcher 介面定義了程式如何初始化和更新
package fetcher
import "io"
// Interface defines the required fetcher functions
type Interface interface {
//Init should perform validation on fields. For
//example, ensure the appropriate URLs or keys
//are defined or ensure there is connectivity
//to the appropriate web service.
Init() error
//Fetch should check if there is an updated
//binary to fetch, and then stream it back the
//form of an io.Reader. If io.Reader is nil,
//then it is assumed there are no updates. Fetch
//will be run repeatedly and forever. It is up the
//implementation to throttle the fetch frequency.
Fetch() (io.Reader, error)
}
overseer 只帶了幾個實作好了的 Fetcher,可以滿足大部分需求,也可以自己繼承 Fetcher 介面實作自己的 Fetcher,

簡單的自升級示例
演示自動升級,我們需要編譯2個版本的程式,
示例如下:
package main
import (
"fmt"
"time"
"github.com/jpillora/overseer"
"github.com/jpillora/overseer/fetcher"
)
const version = "v0.1"
// 控制自升級
func main() {
overseer.Run(overseer.Config{
Program: actualMain,
TerminateTimeout: 10 * time.Second,
Fetcher: &fetcher.HTTP{
URL: "http://localhost:9000/selfupgrade",
Interval: 1 * time.Second,
},
PreUpgrade: preUpgrade,
})
// mainWithSelfUpdate()
}
// 升級前的動作,引數是下載的程式的臨時位置,如果回傳 error,則不升級
func preUpgrade(tempBinaryPath string) error {
fmt.Printf("download binary path: %s\n", tempBinaryPath)
return nil
}
// 這里一般寫是實際的業務,此示例是不斷列印 version
func actualMain(state overseer.State) {
for {
fmt.Printf("%s: current version: %s\n", time.Now().Format("2006-01-02 15:04:05"), version)
time.Sleep(3 * time.Second)
}
}
上面的程式編譯后啟動,
$ go build -o selfupgrade
$ ./selfupgrade
2022-05-21 00:46:52: current version: v0.1
2022-05-21 00:46:55: current version: v0.1
2022-05-21 00:46:58: current version: v0.1
2022-05-21 00:47:01: current version: v0.1
2022-05-21 00:47:04: current version: v0.1
啟動之后開始不斷的列印版本號(間隔3秒),不要停止此程式,
然后我們修改 version,并且將 actualMain 中的間隔修改為5秒,
const version = "v0.2" // v0.1 => v0.2
// ,,, 省略,,,
// 這里一般寫是實際的業務,此示例是不斷列印 version
func actualMain(state overseer.State) {
for {
fmt.Printf("%s: current version: %s\n", time.Now().Format("2006-01-02 15:04:05"), version)
time.Sleep(5 * time.Second)
}
}
修改之后,再編譯一個版本到 ~/tmp 目錄(如果不存在提前創建),
然后啟動一個檔案服務,我用python自帶的方法啟動了一個服務,服務埠對應代碼中的升級URL("http://localhost:9000/selfupgrade")
$ go build -o ~/tmp/selfupgrade
$ cd ~/tmp
$ python -m http.server 9000
過一會兒之后,就能看到之前啟動程式已經更新,
更新之后版本號變成 v0.2,時間間隔變成了5秒
2022-05-21 01:27:22: current version: v0.1
2022-05-21 01:27:25: current version: v0.1
download binary path: /tmp/overseer-5c0865554eb0f83a
2022-05-21 01:27:28: current version: v0.1
2022-05-21 01:27:31: current version: v0.1
2022-05-21 01:27:34: current version: v0.1
2022-05-21 01:27:37: current version: v0.1
2022-05-21 01:27:37: current version: v0.2
2022-05-21 01:27:42: current version: v0.2
2022-05-21 01:27:47: current version: v0.2
Web服務自升級示例
web服務與之類似,比如:
func actualMainServer(state overseer.State) {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s: current version: %s\n", time.Now().Format("2006-01-02 15:04:05"), version)
}))
http.ListenAndServe(":8000", nil)
}
將上面函式替換 overseer.Config 的Program即可,
通過觀察行程的變化,可以看出升級之后就是將子行程重啟,主行程沒變,
升級前:
$ ps -ef | ag self
wangyub+ 8058 4443 1 09:58 pts/12 00:00:00 ./selfupgrade
wangyub+ 8067 8058 0 09:58 pts/12 00:00:00 ./selfupgrade
wangyub+ 8130 3548 0 09:59 pts/11 00:00:00 ag self
升級后:
$ ps -ef | ag self
wangyub+ 8058 4443 0 09:58 pts/12 00:00:00 ./selfupgrade
wangyub+ 8196 8058 0 09:59 pts/12 00:00:00 ./selfupgrade
wangyub+ 8266 3548 0 09:59 pts/11 00:00:00 ag self
上面的寫法,會導致埠的服務中斷一會兒,如果要保持埠持續暢通,可以用官方示例中的寫法,
overseer.Run(overseer.Config{
// ,,,省略,,,
Address: ":8000", // 服務的埠
})
實際的server中使用 state 中的 Listener,
func actualMainServer(state overseer.State) {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s: current version: %s\n", time.Now().Format("2006-01-02 15:04:05"), version)
}))
http.Serve(state.Listener, nil) // 這里使用 state 中的 Listener,也就是 Config中的 Address
}
總結
總的來說,overseer 滿足了自升級的各種需求,
但是自帶的Fetcher功能比較簡單,比如HTTP的Fetcher,升級的程序可能只有一個URL還不夠,還有更加復雜的版本檢查和比較,
實際場景下可能需要定制一個適合自己應用的Fetcher,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/479200.html
標籤:Go
上一篇:Go編譯程序
