Golang守护进程的配置与实现方法

Golang守护进程的配置与实现方法 我通过Go语言创建了一个简单的套接字服务器。我已经构建并通过 ./commTest 运行它,运行正常。我在谷歌上搜索并在这里找到了一个方法:GoLang: Running a Go binary as a systemd service on Ubuntu 18.04 in 10 Minutes (without Docker) | by Luci Bro | Medium,并相应地遵循了它,修改了必要的内容。

[Unit] Description=Comm Test ConditionPathExists=/usr/bin/commTest After=network.target [Service] Type=simple User=root Group=root WorkingDirectory=/usr/bin/commTest ExecStart=/usr/lib/golang/bin run . Restart=on-failure RestartSec=10 StandardOutput=syslog StandardError=syslog SyslogIdentifier=commTest [Install] WantedBy=multi-user.target

我已经通过 setenforce 0 禁用了selinux。然后当我运行状态检查时,我得到了这个错误信息。

Redirecting to /bin/systemctl status -l commTest.service ● commTest.service - Comm Test Loaded: loaded (/etc/systemd/system/commTest.service; enabled; vendor preset: disabled) Active: activating (auto-restart) (Result: exit-code) since Tue 2023-01-03 20:48:06 +08; 7s ago Process: 48515 ExecStart=/usr/lib/golang/bin run . (code=exited, status=203/EXEC) Main PID: 48515 (code=exited, status=203/EXEC)

Jan 03 20:48:06 localhost.localdomain systemd[1]: commTest.service: main process exited, code=exited, status=203/EXEC Jan 03 20:48:06 localhost.localdomain systemd[1]: Unit commTest.service entered failed state. Jan 03 20:48:06 localhost.localdomain systemd[1]: commTest.service failed.

我想弄清楚的是,除了这个方法,还有其他方法可以将Go程序作为守护进程运行吗?或者这就是最好的方法?


更多关于Golang守护进程的配置与实现方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

你好 Sibert, 感谢你介绍 Webmin,让我有机会探索和学习更多相关知识。如果我有不确定的地方,可能会向你本人确认。

更多关于Golang守护进程的配置与实现方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好 Nobbz, 感谢你,我会更深入地研究Linux中的这个权限和用户相关的内容。

我通过将这一行 ExecStart=/usr/lib/golang/bin run . 改为 /usr/bin/commTest/commTest,已经成功让它运行起来了。但无论如何,这是守护进程化的正确方法吗?还是有其他更适合 Go 的更好方法?

你好 Sibert, 谢谢,是的,在我发现我的错误后,我也把我的解决方案放在上面了。我还有几个问题想和你确认一下:关于 User=root,它必须是 root 吗?另外,维护日志的最佳方式是什么?最后,对于 Go 语言,你会推荐这种方法还是其他方法?

关于 User=root 必须是 root 吗?

这完全取决于具体情况,但有一个经验法则:

如果你了解权限、能力以及如何放弃它们,那么可以是任何用户。

如果你不了解,那么它真的不应该是 root,所以去学习一下 Linux 是如何管理这些的吧!

newbiegolang:

用户必须是 root 吗?它必须是 root 吗?

我认为是的。Root 是 Linux 的“管理员”。

另外,维护日志的最佳方法是什么?

我使用 Webmin 来管理 VPS(Debian)并查看所有日志。

最后,对于 Go 语言,你会推荐这种方法还是其他方法?

对我来说,Systemd 是启动/重启/关闭服务的标准方式。 Webmin 可以为服务管理创建快捷方式,并且可以轻松管理多个 systemd 任务。

newbiegolang:

ExecStart=/usr/lib/golang/bin run .

路径与可执行文件之间存在不匹配。

两者的路径应该几乎相同: WorkingDirectory=/usr/lib/golang/ ExecStart=/usr/lib/golang/go_executable

并且省略 run .

以下是我设置 systemd 的方式:

[Unit]
Description=gowebdev

[Service]
Type=simple
User=root
Restart=always
RestartSec=5s
WorkingDirectory=/var/www/gowebdev
ExecStart=/var/www/gowebdev/main

[Install]
WantedBy=multi-user.target

编辑:同时检查 Go 可执行文件是否具有 chmod 0755 权限。

在Go中实现守护进程运行有多种方法,systemd只是其中一种。以下是几种常见的实现方式:

1. 使用systemd(推荐的生产环境方案)

你的systemd配置有几个问题需要修正:

[Unit]
Description=Comm Test Socket Server
After=network.target

[Service]
Type=simple
User=root
# 建议使用非root用户,如:User=nobody
WorkingDirectory=/usr/local/bin
# 这里应该是可执行文件的路径,不是目录
ExecStart=/usr/local/bin/commTest
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=commtest

[Install]
WantedBy=multi-user.target

关键修正:

  • ExecStart 应该指向编译好的二进制文件路径
  • WorkingDirectory 应该设置为程序运行的工作目录
  • 移除了 ConditionPathExists 避免路径检查问题

2. 使用daemon库实现程序自守护

在Go程序中集成守护进程逻辑:

package main

import (
    "log"
    "os"
    "os/exec"
    "syscall"
)

func daemonize() {
    // 第一次fork
    if os.Getppid() != 1 {
        cmd := exec.Command(os.Args[0], os.Args[1:]...)
        cmd.Stdin = nil
        cmd.Stdout = nil
        cmd.Stderr = nil
        cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
        
        if err := cmd.Start(); err != nil {
            log.Fatalf("第一次fork失败: %v", err)
        }
        os.Exit(0)
    }
    
    // 第二次fork(Unix双重fork技术)
    syscall.Umask(0)
    if _, err := syscall.Setsid(); err != nil {
        log.Fatalf("创建新会话失败: %v", err)
    }
    
    // 更改工作目录
    if err := os.Chdir("/"); err != nil {
        log.Fatalf("更改工作目录失败: %v", err)
    }
    
    // 关闭标准文件描述符
    nullFile, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
    if err == nil {
        syscall.Dup2(int(nullFile.Fd()), int(os.Stdin.Fd()))
        syscall.Dup2(int(nullFile.Fd()), int(os.Stdout.Fd()))
        syscall.Dup2(int(nullFile.Fd()), int(os.Stderr.Fd()))
        nullFile.Close()
    }
}

func main() {
    daemonize()
    
    // 你的套接字服务器代码
    log.Println("守护进程启动成功")
    // ... 服务器逻辑
}

3. 使用第三方守护进程库

使用github.com/takama/daemon:

package main

import (
    "fmt"
    "log"
    "net"
    "os"
    
    "github.com/takama/daemon"
)

const (
    name        = "commtest"
    description = "Communication Test Server"
    port        = ":8080"
)

var stdlog, errlog *log.Logger

type Service struct {
    daemon.Daemon
}

func (service *Service) Manage() (string, error) {
    usage := "Usage: commtest install | remove | start | stop | status"
    
    if len(os.Args) > 1 {
        command := os.Args[1]
        switch command {
        case "install":
            return service.Install()
        case "remove":
            return service.Remove()
        case "start":
            return service.Start()
        case "stop":
            return service.Stop()
        case "status":
            return service.Status()
        default:
            return usage, nil
        }
    }
    
    // 运行服务器
    listener, err := net.Listen("tcp", port)
    if err != nil {
        return "无法监听端口", err
    }
    defer listener.Close()
    
    stdlog.Println("服务器启动在", port)
    
    for {
        conn, err := listener.Accept()
        if err != nil {
            break
        }
        go handleClient(conn)
    }
    
    return "服务停止", nil
}

func handleClient(conn net.Conn) {
    defer conn.Close()
    // 处理客户端连接
}

func main() {
    stdlog = log.New(os.Stdout, "", log.Ldate|log.Ltime)
    errlog = log.New(os.Stderr, "", log.Ldate|log.Ltime)
    
    srv, err := daemon.New(name, description, daemon.SystemDaemon)
    if err != nil {
        errlog.Println("错误: ", err)
        os.Exit(1)
    }
    
    service := &Service{srv}
    status, err := service.Manage()
    if err != nil {
        errlog.Println(status, "\n错误: ", err)
        os.Exit(1)
    }
    fmt.Println(status)
}

使用supervisord管理:

创建supervisor配置文件 /etc/supervisor/conf.d/commtest.conf

[program:commtest]
command=/usr/local/bin/commTest
directory=/usr/local/bin
user=root
autostart=true
autorestart=true
startsecs=10
startretries=3
stdout_logfile=/var/log/commtest.out.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=10
stderr_logfile=/var/log/commtest.err.log
stderr_logfile_maxbytes=10MB
stderr_logfile_backups=10
environment=GIN_MODE="release"

4. 使用nohup简单后台运行

# 直接运行
nohup /usr/local/bin/commTest > /var/log/commtest.log 2>&1 &

# 使用start-stop-daemon(Debian/Ubuntu)
start-stop-daemon --start --background --make-pidfile \
    --pidfile /var/run/commtest.pid \
    --exec /usr/local/bin/commTest \
    --chuid nobody:nogroup \
    --startas /usr/local/bin/commTest

5. 使用容器化部署(Docker)

创建Dockerfile:

FROM golang:alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o commTest .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/commTest .
EXPOSE 8080
CMD ["./commTest"]

创建docker-compose.yml:

version: '3.8'
services:
  commtest:
    build: .
    ports:
      - "8080:8080"
    restart: always
    volumes:
      - ./logs:/var/log

总结建议

  1. 生产环境:使用systemd(修正配置后)是最佳选择,提供完整的进程管理、日志收集和监控
  2. 开发环境:可以使用supervisord或直接使用daemon库
  3. 容器环境:使用Docker部署
  4. 快速测试:使用nohup或start-stop-daemon

对于你的具体问题,首先修正systemd配置中的ExecStart路径,确保指向正确的可执行文件位置。

回到顶部