在 5 分鐘之內部署一個 Go 應用

在有些程序人寫完了他們的 Go 應用之後,這總會成爲一個大問題——“我剛寫的這個 Go 應用,當它崩潰的時候我要怎麼重啓?”,因爲你沒法用 go run main.go 或者 ./main 這樣的命令讓它持續運行,並且當程序崩潰的時候能夠重啓。

一個普通使用的好辦法是使用 Docker。但是,設置 Docker 以及爲容器配置你的應用需要花費時間,當你的程序需要和 MySQL、Redis 這樣的服務器 / 進程交互時更是如此。對於一個大型或長期項目來說,毋庸置疑這是一個正確的選擇。但是如果在你手上的是個小應用,你想要快速部署並且實時地服務器上查看狀態,那麼你可能需要考慮別的選擇。

另一個選擇就是在你的 Linux 服務器上創建一個守護進程,然後讓它作爲一個服務運行,但是這需要花費一些額外的工夫。而且,如果你並不具備 Linux 系統和服務相關的知識的話,這就不是一件簡單的事情了。所以,這裏有一個最簡單的解決方案——使用 Supervisor[1] 來部署你的 Go 應用,然後它會爲你處理好其餘的工作。它是一個能夠幫你監控你的應用程序並在其崩潰時進行重啓的工具。

本文是 Go 語言中文網組織的 GCTT 翻譯,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

安裝

安裝 Supervisor 相當簡單,在 Ubuntu 上這條命令就會在你的系統上安裝 Supervisor。

sudo apt install supervisor

然後你需要將 Supervisor 添加到系統的用戶組中:

sudo addgroup --system supervisor

現在,在創建 Supervisor 的配置文件之前,我們先寫一個簡單的 Go 程序。這個程序將會讀取 .env 文件中的配置項,然後和 MySQL 數據庫進行交互。代碼如下:

(爲了方便演示,我們會讓代碼簡單些)

package main

import (
 "database/sql"
 "encoding/json"
 "fmt"
 "log"
 "net/http"
 "os"

 _ "github.com/go-sql-driver/mysql"
 "github.com/gorilla/mux"
 "github.com/joho/godotenv"
)

type User struct {
 Email    string `json:"email"`
 Password string `json:"password"`
}

var db *sql.DB

func init() {
 var err error
 err = godotenv.Load()
 if err != nil {
  log.Println("Error readin .env: ", err)
  os.Exit(1)
 }

 dbUserName := os.Getenv("DB_USERNAME")
 dbPassword := os.Getenv("DB_PASSWORD")
 dbNAME := os.Getenv("DB_NAME")

 dsn := dbUserName + ":" + dbPassword + "@/" + dbNAME

 db, err = sql.Open("mysql", dsn)

 if err != nil {
  log.Println(err)
  os.Exit(1)
 }

}

func main() {

 r := mux.NewRouter()

 r.Use(middleware)

 r.HandleFunc("/", rootHandler)
 r.HandleFunc("/user", createUserHandler).Methods("POST")

 fmt.Println("Listening on :8070")
 if err := http.ListenAndServe(":8070", r); err != nil {
  // 退出程序
  log.Println("Failed starting server ", err)
  os.Exit(1)
 }

}

func rootHandler(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintf(w, "This is root handler")
}

func createUserHandler(w http.ResponseWriter, r *http.Request) {

 user := &User{}

 err := json.NewDecoder(r.Body).Decode(user)

    // 對請求響應 JSON 數據
    // 在實際應用中你可能想要創建一個進行錯誤處理的函數
 if err != nil {
  // 我們也可以這麼做
  // errREsp := `"error""Invalid input""status": 400`
  // w.Header().Set("Content-Type""application/json")
  // w.WriteHeader(400)
        // w.Write([]byte(errREsp))

        // 然而我們會讓服務器崩潰
  log.Fatal(err)

  return
 }

    // 在實際應用中必須對密碼進行哈希,可以使用 bcrypt 算法
 _, err = db.Exec("INSERT INTO users(email, password) VALUES(?,?)", user.Email, user.Password)

 if err != nil {
  log.Println(err)
        // 簡單起見,發送明文字符串
        // 創建一個有效的 JSON 響應
  errREsp := `"error""Internal error""status": 500` // 返回 500 狀態碼,因爲這是我們而非用戶的問題
  w.Header().Set("Content-Type""application/json")
  w.WriteHeader(500)
  w.Write([]byte(errREsp))

  return
 }

}

// 一個簡單的中間件,只用來記錄請求的 URI
var middleware = func(next http.Handler) http.Handler {

 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

  requestPath := r.URL.Path
  log.Println(requestPath)
  next.ServeHTTP(w, r) // 在中間件調用鏈中進行處理!

 })
}

現在,如果我們想要用 Supervisor 來運行這個程序,我們需要構建程序的二進制文件。同時在項目的根目錄下創建一個 .env 文件 —— 如果你想把配置文件和項目放在一起的話,在這個文件中寫上 MySQL 數據庫需要的變量。

將這個倉庫克隆到你想要運行的服務器上。確保你遵循了 Go 目錄路徑的慣例:

$ Go build .

Go 的這個命令最終會創建一個以項目根目錄命名的二進制文件,所以如果項目的根目錄是 myapp,那麼文件的名稱就是 myapp

現在,在服務器上創建 Supervisor 的配置文件 /etc/supervisor/conf.d

#/etc/supervisor/conf.d/myapp.conf

[program:myapp]
directory=/root/gocode/src/github.com/monirz/myapp
command=/root/gocode/src/github.com/monirz/myapp/myapp
autostart=true
autorestart=true
stderr_logfile=/var/log/myapp.err
stdout_logfile=/var/log/myapp.log
environment=CODENATION_ENV=prod
environment=GOPATH="/root/gocode"

這裏的 directory 和 command 變量很重要。directory 變量應該設置爲項目的根目錄,因爲程序將會嘗試在 directory 指定的路徑下讀取 .env 文件或是其他需要的配置文件。autorestart 變量設置爲 true,這樣當程序崩潰時就會重啓。

現在通過下面的命令重新加載 Supervisor:

$ sudo supervisorctl reload

來檢查下它的狀態。

$ sudo supervisorctl status

一切都正確配置的話,你應該會看到類似下面的輸出內容:

myapp     RUNNING   pid 2023, uptime 0:00:03

我們名爲 myapp 的 Go 服務端程序正在後臺運行。

現在向我們剛寫的 API 發起一些請求。首先檢查 rootHandler 是否正在工作。然後向 /user 結點發送一個包含無效 JSON 格式數據的請求。這應當會讓服務器崩潰。但是服務器上沒有存儲任何日誌,不是嗎?因爲我們還沒有實現日誌功能?

等等,Supervisor 實際上已經爲我們處理了日誌。如果你到 /var/log 目錄下查看 myapp.log 文件,你就會看到它記錄着已經向服務器發起過的請求的 URI 路徑。

$ cat /var/log/myapp.log

錯誤日誌也是如此。好了,我們的服務器程序已經運行了——崩潰的話會重啓,還會記錄每個請求和錯誤信息。我覺得我們應該是在大約 5 分鐘以內做完了這些事吧?(大概是吧,誰在乎呢。)但關鍵是,用 Supervisor 來部署和監控你的 Go 應用程序時十分簡單的。

你覺得呢?毫不猶豫地回覆我吧。週末愉快。


via: https://medium.com/@monirz/deploy-golang-app-in-5-minutes-ff354954fa8e

作者:Monir Zaman[2] 譯者:maxwellhertz[3] 校對:polaris1119[4]

本文由 GCTT[5] 原創編譯,Go 中文網 [6] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。

參考資料

[1]

Supervisor: http://supervisord.org/

[2]

Monir Zaman: https://medium.com/@monirz

[3]

maxwellhertz: https://github.com/maxwellhertz

[4]

polaris1119: https://github.com/polaris1119

[5]

GCTT: https://github.com/studygolang/GCTT

[6]

Go 中文網: https://studygolang.com/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ENKYHrWv_Ayz2XdjvQ3sVQ