鍍金池/ 教程/ GO/ 12.3 應(yīng)用部署
7 文本處理
3 Web基礎(chǔ)
14 擴(kuò)展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫進(jìn)行ORM開發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計(jì)一個(gè)Web框架
14.3 表單及驗(yàn)證支持
12 部署與維護(hù)
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗(yàn)證表單的輸入
10.1 設(shè)置默認(rèn)地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯(cuò)誤處理,調(diào)試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復(fù)
8.1 Socket編程
13.1 項(xiàng)目規(guī)劃
13.4 日志和配置設(shè)計(jì)
7.6 字符串處理
13.2 自定義路由器設(shè)計(jì)
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點(diǎn)
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計(jì)
2.6 interface
14.4 用戶認(rèn)證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯(cuò)誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯(cuò)誤處理
6 session和數(shù)據(jù)存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實(shí)現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫
14.7 小結(jié)
3.2 Go搭建一個(gè)Web服務(wù)器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫
4 表單
3.5 小結(jié)
1.4 Go開發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫
4.6 小結(jié)
8.4 RPC

12.3 應(yīng)用部署

程序開發(fā)完畢之后,我們現(xiàn)在要部署Web應(yīng)用程序了,但是我們?nèi)绾蝸聿渴疬@些應(yīng)用程序呢?因?yàn)镚o程序編譯之后是一個(gè)可執(zhí)行文件,編寫過C程序的讀者一定知道采用daemon就可以完美的實(shí)現(xiàn)程序后臺持續(xù)運(yùn)行,但是目前Go還無法完美的實(shí)現(xiàn)daemon,因此,針對Go的應(yīng)用程序部署,我們可以利用第三方工具來管理,第三方的工具有很多,例如Supervisord、upstart、daemontools等,這小節(jié)我介紹目前自己系統(tǒng)中采用的工具Supervisord。

daemon

目前Go程序還不能實(shí)現(xiàn)daemon,詳細(xì)的見這個(gè)Go語言的bug:<http://code.google.com/p/go/issues/detail?id=227>,大概的意思說很難從現(xiàn)有的使用的線程中fork一個(gè)出來,因?yàn)闆]有一種簡單的方法來確保所有已經(jīng)使用的線程的狀態(tài)一致性問題。

但是我們可以看到很多網(wǎng)上的一些實(shí)現(xiàn)daemon的方法,例如下面兩種方式:

  • MarGo的一個(gè)實(shí)現(xiàn)思路,使用Commond來執(zhí)行自身的應(yīng)用,如果真想實(shí)現(xiàn),那么推薦這種方案

      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的方案,但是這個(gè)方案并不完善:

      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
      }   

上面提出了兩種實(shí)現(xiàn)Go的daemon方案,但是我還是不推薦大家這樣去實(shí)現(xiàn),因?yàn)楣俜竭€沒有正式的宣布支持daemon,當(dāng)然第一種方案目前來看是比較可行的,而且目前開源庫skynet也在采用這個(gè)方案做daemon。

Supervisord

上面已經(jīng)介紹了Go目前是有兩種方案來實(shí)現(xiàn)他的daemon,但是官方本身還不支持這一塊,所以還是建議大家采用第三方成熟工具來管理我們的應(yīng)用程序,這里我給大家介紹一款目前使用比較廣泛的進(jìn)程管理軟件:Supervisord。Supervisord是用Python實(shí)現(xiàn)的一款非常實(shí)用的進(jìn)程管理工具。supervisord會(huì)幫你把管理的應(yīng)用程序轉(zhuǎn)成daemon程序,而且可以方便的通過命令開啟、關(guān)閉、重啟等操作,而且它管理的進(jìn)程一旦崩潰會(huì)自動(dòng)重啟,這樣就可以保證程序執(zhí)行中斷后的情況下有自我修復(fù)的功能。

我前面在應(yīng)用中踩過一個(gè)坑,就是因?yàn)樗械膽?yīng)用程序都是由Supervisord父進(jìn)程生出來的,那么當(dāng)你修改了操作系統(tǒng)的文件描述符之后,別忘記重啟Supervisord,光重啟下面的應(yīng)用程序沒用。當(dāng)初我就是系統(tǒng)安裝好之后就先裝了Supervisord,然后開始部署程序,修改文件描述符,重啟程序,以為文件描述符已經(jīng)是100000了,其實(shí)Supervisord這個(gè)時(shí)候還是默認(rèn)的1024個(gè),導(dǎo)致他管理的進(jìn)程所有的描述符也是1024.開放之后壓力一上來系統(tǒng)就開始報(bào)文件描述符用光了,查了很久才找到這個(gè)坑。

Supervisord安裝

Supervisord可以通過sudo easy_install supervisor安裝,當(dāng)然也可以通過Supervisord官網(wǎng)下載后解壓并轉(zhuǎn)到源碼所在的文件夾下執(zhí)行setup.py install來安裝。

  • 使用easy_install必須安裝setuptools

    打開http://pypi.python.org/pypi/setuptools#files,根據(jù)你系統(tǒng)的python的版本下載相應(yīng)的文件,然后執(zhí)行sh setuptoolsxxxx.egg,這樣就可以使用easy_install命令來安裝Supervisord。

Supervisord配置

Supervisord默認(rèn)的配置文件路徑為/etc/supervisord.conf,通過文本編輯器修改這個(gè)文件,下面是一個(gè)示例的配置文件:

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

[inet_http_server]
# Web管理界面設(shè)定
port=9001
username = admin
password = yourpassword

[supervisorctl]
; 必須和'unix_http_server'里面的設(shè)定匹配
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

; 管理的單個(gè)進(jìn)程的配置,可以添加多個(gè)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安裝完成后有兩個(gè)可用的命令行supervisor和supervisorctl,命令使用解釋如下:

  • supervisord,初始啟動(dòng)Supervisord,啟動(dòng)、管理配置中設(shè)置的進(jìn)程。
  • supervisorctl stop programxxx,停止某一個(gè)進(jìn)程(programxxx),programxxx為[program:blogdemon]里配置的值,這個(gè)示例就是blogdemon。
  • supervisorctl start programxxx,啟動(dòng)某個(gè)進(jìn)程
  • supervisorctl restart programxxx,重啟某個(gè)進(jìn)程
  • supervisorctl stop all,停止全部進(jìn)程,注:start、restart、stop都不會(huì)載入最新的配置文件。
  • supervisorctl reload,載入最新的配置文件,并按新的配置啟動(dòng)、管理所有進(jìn)程。

小結(jié)

這小節(jié)我們介紹了Go如何實(shí)現(xiàn)daemon化,但是由于目前Go的daemon實(shí)現(xiàn)的不足,需要依靠第三方工具來實(shí)現(xiàn)應(yīng)用程序的daemon管理的方式,所以在這里介紹了一個(gè)用python寫的進(jìn)程管理工具Supervisord,通過Supervisord可以很方便的把我們的Go應(yīng)用程序管理起來。