「GoCN酷Go推荐」golang 跨平台部署利器
研究背景
go程序部署时,直接将编译好的文件在服务器上运行即可,一般无需安装所依赖的第三方库。
Linux下部署分为以下几种方式:
使用nohup 命令 使用 Supervisord管理 使用systemd管理 
windows下部署方式:
注册成windows服务,方便进行管理 
其实golang自己也可以实现以服务的形式常驻后台。
现在介绍一个包 github.com/kardianos/service ,它可以让 go 程序支持在不同系统上作为服务:注册,卸载,启动,停止。
安装部署
go get github.com/kardianos/service
使用方法
通过两个例子,我们来了解其用法。
3.1 示例程序: simple
package main
import (
 "log"
 "github.com/kardianos/service"
)
var logger service.Logger //service的日志对象
type program struct{} //定义结构体,并实现service.Interface的start和stop接口
func (p *program) Start(s service.Service) error {
 // Start should not block. Do the actual work async.
 go p.run()
 return nil
}
func (p *program) run() {
 // Do work here
}
func (p *program) Stop(s service.Service) error {
 // Stop should not block. Return with a few seconds.
 return nil
}
func main() {
 svcConfig := &service.Config{
  Name:        "GoServiceExampleSimple",
  DisplayName: "Go Service Example",
  Description: "This is an example Go service.",
 }
 prg := &program{}
 s, err := service.New(prg, svcConfig)
 if err != nil {
  log.Fatal(err)
 }
 logger, err = s.Logger(nil)
 if err != nil {
  log.Fatal(err)
 }
 err = s.Run()
 if err != nil {
  logger.Error(err)
 }
}
套路如下:
一、定义结构体program,并实现service的接口函数Start和Stop
需要实现的service接口Interface如下:
type Interface interface {
   // Start provides a place to initiate the service. The service doesn't
   // signal a completed start until after this function returns, so the
   // Start function must not take more then a few seconds at most.
   Start(s Service) error
   // Stop provides a place to clean up program execution before it is terminated.
   // It should not take more then a few seconds to execute.
   // Stop should not call os.Exit directly in the function.
   Stop(s Service) error
}
二、初始化service.Config结构体,并调用service.New创建service对象
三、为service对象设置日志Logger
四、调用service的Run方法来运行程序
3.2 示例程序2
package main
import (
 "encoding/json"
 "flag"
 "fmt"
 "log"
 "os"
 "os/exec"
 "path/filepath"
 "github.com/kardianos/service"
)
// Config is the runner app config structure.
type Config struct {
 Name, DisplayName, Description string
 Dir  string
 Exec string
 Args []string
 Env  []string
 Stderr, Stdout string
}
var logger service.Logger
type program struct {
 exit    chan struct{}
 service service.Service
 *Config
 cmd *exec.Cmd
}
func (p *program) Start(s service.Service) error {
 // Look for exec.
 // Verify home directory.
 fullExec, err := exec.LookPath(p.Exec)
 if err != nil {
  return fmt.Errorf("Failed to find executable %q: %v", p.Exec, err)
 }
 p.cmd = exec.Command(fullExec, p.Args...)
 p.cmd.Dir = p.Dir
 p.cmd.Env = append(os.Environ(), p.Env...)
 go p.run()
 return nil
}
func (p *program) run() {
 logger.Info("Starting ", p.DisplayName)
 defer func() {
  if service.Interactive() {
   p.Stop(p.service)
  } else {
   p.service.Stop()
  }
 }()
 if p.Stderr != "" {
  f, err := os.OpenFile(p.Stderr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
  if err != nil {
   logger.Warningf("Failed to open std err %q: %v", p.Stderr, err)
   return
  }
  defer f.Close()
  p.cmd.Stderr = f
 }
 if p.Stdout != "" {
  f, err := os.OpenFile(p.Stdout, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777)
  if err != nil {
   logger.Warningf("Failed to open std out %q: %v", p.Stdout, err)
   return
  }
  defer f.Close()
  p.cmd.Stdout = f
 }
 err := p.cmd.Run()
 if err != nil {
  logger.Warningf("Error running: %v", err)
 }
 return
}
func (p *program) Stop(s service.Service) error {
 close(p.exit)
 logger.Info("Stopping ", p.DisplayName)
 if p.cmd.Process != nil {
  p.cmd.Process.Kill()
 }
 if service.Interactive() {
  os.Exit(0)
 }
 return nil
}
func getConfigPath() (string, error) {
 fullexecpath, err := os.Executable()
 if err != nil {
  return "", err
 }
 dir, execname := filepath.Split(fullexecpath)
 ext := filepath.Ext(execname)
 name := execname[:len(execname)-len(ext)]
 return filepath.Join(dir, name+".json"), nil
}
func getConfig(path string) (*Config, error) {
 f, err := os.Open(path)
 if err != nil {
  return nil, err
 }
 defer f.Close()
 conf := &Config{}
 r := json.NewDecoder(f)
 err = r.Decode(&conf) //将json字符串转为Config对象
 if err != nil {
  return nil, err
 }
 return conf, nil
}
func main() {
 svcFlag := flag.String("service", "", "Control the system service.")
 flag.Parse()
 configPath, err := getConfigPath() //获取配置文件路径
 if err != nil {
  log.Fatal(err)
 }
 config, err := getConfig(configPath) //获取配置
 if err != nil {
  log.Fatal(err)
 }
 //初始化service配置属性
 svcConfig := &service.Config{
  Name:        config.Name,
  DisplayName: config.DisplayName,
  Description: config.Description,
 }
 prg := &program{
  exit: make(chan struct{}),
  Config: config,
 }
 s, err := service.New(prg, svcConfig)
 if err != nil {
  log.Fatal(err)
 }
 prg.service = s
 errs := make(chan error, 5)
 logger, err = s.Logger(errs)
 if err != nil {
  log.Fatal(err)
 }
 go func() {
  for {
   err := <-errs
   if err != nil {
    log.Print(err)
   }
  }
 }()
 if len(*svcFlag) != 0 {
     //通过命令行参数来控制服务
  err := service.Control(s, *svcFlag)
  if err != nil {
   log.Printf("Valid actions: %q\n", service.ControlAction)
   log.Fatal(err)
  }
  return
 }
 err = s.Run()
 if err != nil {
  logger.Error(err)
 }
}
其配置文件runner.json如下
{
 "Name": "builder",
 "DisplayName": "Go Builder",
 "Description": "Run the Go Builder",
 
 "Dir": "C:\\dev\\go\\src",
 "Exec": "C:\\windows\\system32\\cmd.exe",
 "Args": ["/C","C:\\dev\\go\\src\\all.bat"],
 "Env": [
  "PATH=C:\\TDM-GCC-64\\bin;C:\\Program Files (x86)\\Git\\cmd",
  "GOROOT_BOOTSTRAP=C:\\dev\\go_ready",
  "HOMEDRIVE=C:",
  "HOMEPATH=\\Documents and Settings\\Administrator"
 ],
 
 "Stderr": "C:\\builder_err.log",
 "Stdout": "C:\\builder_out.log"
}
可将service相关的配置信息存储到配置文件中。通过读取配置文件中的配置信息来生成service。
接口定义(源码剖析)
这个库有两个接口
4.1 Service接口
其中Service接口有Run、Start、Stop、Restart、Install、Uninstall等核心方法可实现,以对应服务的运行、启动、停止、重启、安装、卸载操作。
type Service interface {
 // Run should be called shortly after the program entry point.
 // After Interface.Stop has finished running, Run will stop blocking.
 // After Run stops blocking, the program must exit shortly after.
 Run() error
 // Start signals to the OS service manager the given service should start.
 Start() error
 // Stop signals to the OS service manager the given service should stop.
 Stop() error
 // Restart signals to the OS service manager the given service should stop then start.
 Restart() error
 // Install setups up the given service in the OS service manager. This may require
 // greater rights. Will return an error if it is already installed.
 Install() error
 // Uninstall removes the given service from the OS service manager. This may require
 // greater rights. Will return an error if the service is not present.
 Uninstall() error
 // Opens and returns a system logger. If the user program is running
 // interactively rather then as a service, the returned logger will write to
 // os.Stderr. If errs is non-nil errors will be sent on errs as well as
 // returned from Logger's functions.
 Logger(errs chan<- error) (Logger, error)
 // SystemLogger opens and returns a system logger. If errs is non-nil errors
 // will be sent on errs as well as returned from Logger's functions.
 SystemLogger(errs chan<- error) (Logger, error)
 // String displays the name of the service. The display name if present,
 // otherwise the name.
 String() string
}
其中aixService、freebsdService、solarisService、systemd、sysv、upstart、windowsService分别对应不同的操作系统aix,freebsd,solaris,linux,windows操作系统上的服务。
4.2 Interface接口
Interface接口中有Start和Stop
type Interface interface {
 // Start provides a place to initiate the service. The service doesn't not
 // signal a completed start until after this function returns, so the
 // Start function must not take more then a few seconds at most.
 Start(s Service) error
 // Stop provides a place to clean up program execution before it is terminated.
 // It should not take more then a few seconds to execute.
 // Stop should not call os.Exit directly in the function.
 Stop(s Service) error
}
《酷Go推荐》招募:
各位Gopher同学,最近我们社区打算推出一个类似GoCN每日新闻的新栏目《酷Go推荐》,主要是每周推荐一个库或者好的项目,然后写一点这个库使用方法或者优点之类的,这样可以真正的帮助到大家能够学习到
新的库,并且知道怎么用。
大概规则和每日新闻类似,如果报名人多的话每个人一个月轮到一次,欢迎大家报名!戳「阅读原文」,即可报名
扫码也可以加入 GoCN 的大家族哟~
