# 12.3 應用部署
程序開發完畢之后,我們現在要部署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的一個實現思路,使用Commond來執行自身的應用,如果真想實現,那么推薦這種方案
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應用程序管理起來。
## links
* [目錄](<preface.md>)
* 上一章: [網站錯誤處理](<12.2.md>)
* 下一節: [備份和恢復](<12.4.md>)
- 目錄
- Go環境配置
- Go安裝
- GOPATH 與工作空間
- Go 命令
- Go開發工具
- 小結
- Go語言基礎
- 你好,Go
- Go基礎
- 流程和函數
- struct
- 面向對象
- interface
- 并發
- 小結
- Web基礎
- web工作方式
- Go搭建一個簡單的web服務
- Go如何使得web工作
- Go的http包詳解
- 小結
- 表單
- 處理表單的輸入
- 驗證表單的輸入
- 預防跨站腳本
- 防止多次遞交表單
- 處理文件上傳
- 小結
- 訪問數據庫
- database/sql接口
- 使用MySQL數據庫
- 使用SQLite數據庫
- 使用PostgreSQL數據庫
- 使用beedb庫進行ORM開發
- NOSQL數據庫操作
- 小結
- session和數據存儲
- session和cookie
- Go如何使用session
- session存儲
- 預防session劫持
- 小結
- 文本文件處理
- XML處理
- JSON處理
- 正則處理
- 模板處理
- 文件操作
- 字符串處理
- 小結
- Web服務
- Socket編程
- WebSocket
- REST
- RPC
- 小結
- 安全與加密
- 預防CSRF攻擊
- 確保輸入過濾
- 避免XSS攻擊
- 避免SQL注入
- 存儲密碼
- 加密和解密數據
- 小結
- 國際化和本地化
- 設置默認地區
- 本地化資源
- 國際化站點
- 小結
- 錯誤處理,調試和測試
- 錯誤處理
- 使用GDB調試
- Go怎么寫測試用例
- 小結
- 部署與維護
- 應用日志
- 網站錯誤處理
- 應用部署
- 備份和恢復
- 小結
- 如何設計一個Web框架
- 項目規劃
- 自定義路由器設計
- controller設計
- 日志和配置設計
- 實現博客的增刪改
- 小結
- 擴展Web框架
- 靜態文件支持
- Session支持
- 表單支持
- 用戶認證
- 多語言支持
- pprof支持
- 小結
- 參考資料