應用部署

程式開發完畢之後,我們現在要部署 Web 應用程式了,但是我們如何來部署這些應用程式呢?因為 Go 程式編譯之後是一個可執行檔案,編寫過 C 程式的讀者一定知道採用 daemon 就可以完美的實現程式後臺持續執行,但是目前 Go 還無法完美的實現 daemon,因此,針對 Go 的應用程式部署,我們可以利用第三方工具來管理,第三方的工具有很多,例如 Supervisord、upstart、daemontools 等,這小節我介紹目前自己系統中採用的工具 Supervisord。

daemon

目前 Go 程式還不能實現 daemon,詳細的見這個 Go 語言的 bug:`http://code.google.com/p/go/issues/detail?id=227`,大概的意思說很難從現有的使用的執行緒中 fork 一個出來,因為沒有一種簡單的方法來確保所有已經使用的執行緒的狀態一致性問題。

但是我們可以看到很多網上的一些實現 daemon 的方法,例如下面兩種方式:

  • MarGo 的一個實現思路,使用 Command 來執行自身的應用,如果真想實現,那麼推薦這種方案

d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)")
if *d {
    cmd := exec.Command(os.Args[0],
        "-close-fds",
        "-addr", *addr,
        "-call", *call,
    )
    serr, err := cmd.StderrPipe()
    if err != nil {
        log.Fatalln(err)
    }
    err = cmd.Start()
    if err != nil {
        log.Fatalln(err)
    }
    s, err := ioutil.ReadAll(serr)
    s = bytes.TrimSpace(s)
    if bytes.HasPrefix(s, []byte("addr: ")) {
        fmt.Println(string(s))
        cmd.Process.Release()
    } else {
        log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err)
        cmd.Process.Kill()
    }
}
  • 另一種是利用 syscall 的方案,但是這個方案並不完善:

package main

import (
    "log"
    "os"
    "syscall"
)

func daemon(nochdir, noclose int) int {
    var ret, ret2 uintptr
    var err uintptr

    darwin := syscall.OS == "darwin"

    // already a daemon
    if syscall.Getppid() == 1 {
        return 0
    }

    // fork off the parent process
    ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)
    if err != 0 {
        return -1
    }

    // failure
    if ret2 < 0 {
        os.Exit(-1)
    }

    // handle exception for darwin
    if darwin && ret2 == 1 {
        ret = 0
    }

    // if we got a good PID, then we call exit the parent process.
    if ret > 0 {
        os.Exit(0)
    }

    /* Change the file mode mask */
    _ = syscall.Umask(0)

    // create a new SID for the child process
    s_ret, s_errno := syscall.Setsid()
    if s_errno != 0 {
        log.Printf("Error: syscall.Setsid errno: %d", s_errno)
    }
    if s_ret < 0 {
        return -1
    }

    if nochdir == 0 {
        os.Chdir("/")
    }

    if noclose == 0 {
        f, e := os.OpenFile("/dev/null", os.O_RDWR, 0)
        if e == nil {
            fd := f.Fd()
            syscall.Dup2(fd, os.Stdin.Fd())
            syscall.Dup2(fd, os.Stdout.Fd())
            syscall.Dup2(fd, os.Stderr.Fd())
        }
    }

    return 0
}

上面提出了兩種實現 Go 的 daemon 方案,但是我還是不推薦大家這樣去實現,因為官方還沒有正式的宣佈支援 daemon,當然第一種方案目前來看是比較可行的,而且目前開源函式庫 skynet 也在採用這個方案做 daemon。

Supervisord

上面已經介紹了 Go 目前是有兩種方案來實現他的 daemon,但是官方本身還不支援這一塊,所以還是建議大家採用第三方成熟工具來管理我們的應用程式,這裡我給大家介紹一款目前使用比較廣泛的程序管理軟體:Supervisord。Supervisord 是用 Python 實現的一款非常實用的程序管理工具。supervisord 會幫你把管理的應用程式轉成 daemon 程式,而且可以方便的透過命令開啟、關閉、重啟等操作,而且它管理的程序一旦崩潰會自動重啟,這樣就可以保證程式執行中斷後的情況下有自我修復的功能。

我前面在應用中踩過一個坑,就是因為所有的應用程式都是由 Supervisord 父程序生出來的,那麼當你修改了作業系統的檔案描述符之後,別忘記重啟 Supervisord,光重啟下面的應用程式沒用。當初我就是系統安裝好之後就先裝了 Supervisord,然後開始部署程式,修改檔案描述符,重啟程式,以為檔案描述符已經是 100000 了,其實 Supervisord 這個時候還是預設的 1024 個,導致他管理的程序所有的描述符也是 1024.開放之後壓力一上來系統就開始報檔案描述符用光了,查了很久才找到這個坑。

Supervisord 安裝

Supervisord 可以透過sudo easy_install supervisor安裝,當然也可以透過 Supervisord 官網下載後解壓並轉到原始碼所在的資料夾下執行setup.py install來安裝。

  • 使用 easy_install 必須安裝 setuptools

開啟`http://pypi.python.org/pypi/setuptools#files`,根據你係統的 python 的版本下載相應的檔案,然後執行`sh setuptoolsxxxx.egg`,這樣就可以使用 easy_install 命令來安裝 Supervisord。

Supervisord 配置

Supervisord 預設的配置檔案路徑為/etc/supervisord.conf,透過文字編輯器修改這個檔案,下面是一個範例的配置檔案:

;/etc/supervisord.conf
[unix_http_server]
file = /var/run/supervisord.sock
chmod = 0777
chown= root:root

[inet_http_server]
# Web 管理介面設定
port=9001
username = admin
password = yourpassword

[supervisorctl]
; 必須和'unix_http_server'裡面的設定匹配
serverurl = unix:///var/run/supervisord.sock

[supervisord]
logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB       ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10          ; (num of main logfile rotation backups;default 10)
loglevel=info               ; (log level;default info; others: debug,warn,trace)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=true              ; (start in foreground if true;default false)
minfds=1024                 ; (min. avail startup file descriptors;default 1024)
minprocs=200                ; (min. avail process descriptors;default 200)
user=root                 ; (default is current user, required if root)
childlogdir=/var/log/supervisord/            ; ('AUTO' child log dir, default $TEMP)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

; 管理的單個程序的配置,可以新增多個 program

[program:blogdemon]
command=/data/blog/blogdemon
autostart = true
startsecs = 5
user = root
redirect_stderr = true
stdout_logfile = /var/log/supervisord/blogdemon.log

Supervisord 管理

Supervisord 安裝完成後有兩個可用的命令列 supervisor 和 supervisorctl,命令使用解釋如下:

  • supervisord,初始啟動 Supervisord,啟動、管理配置中設定的程序。

  • supervisorctl stop programxxx,停止某一個程序(programxxx),programxxx 為 [program:blogdemon] 裡配置的值,這個範例就是 blogdemon。

  • supervisorctl start programxxx,啟動某個程序

  • supervisorctl restart programxxx,重啟某個程序

  • supervisorctl stop all,停止全部程序,注:start、restart、stop 都不會載入最新的配置檔案。

  • supervisorctl reload,載入最新的配置檔案,並按新的配置啟動、管理所有程序。

小結

這小節我們介紹了 Go 如何實現 daemon 化,但是由於目前 Go 的 daemon 實現的不足,需要依靠第三方工具來實現應用程式的 daemon 管理的方式,所以在這裡介紹了一個用 python 寫的程序管理工具 Supervisord,透過 Supervisord 可以很方便的把我們的 Go 應用程式管理起來。

Last updated