[远程] 高级Golang开发工程师招聘 - 数据科学开发者工具项目

[远程] 高级Golang开发工程师招聘 - 数据科学开发者工具项目 dstack.ai 是一家早期初创公司,致力于开发一款工具,该工具能在不牺牲可复现性的前提下,自动化深度学习模型训练工作流程。我们目前正在寻找一位充满热情且经验丰富的软件工程师,来负责构建我们称之为“运行器”的特定工具。

当前的工作需要(完全远程)在未来3到6个月内投入20-30小时。开发者将按小时计酬。在此期间结束后,我们完全愿意以公司创始成员的身份,并提供股权,来正式聘用此人。

任务描述

  • 该工具应能根据通过预安装的Docker从dstack后端获取的请求,在任何虚拟机(Linux、macOS、Windows)上运行工作流。

关于您

  • 具备在专业环境中使用Go和Docker进行编程的经验
  • 对数据科学充满好奇,并乐于在现代技术栈上使用尖端技术
  • 积极主动,能够在最少的监督下独立工作
  • 能够以异步方式与小型团队进行良好的远程协作

如果您感兴趣,请直接申请,或将您的简历及相关工作经验文件/链接发送至 team@dstack.ai


更多关于[远程] 高级Golang开发工程师招聘 - 数据科学开发者工具项目的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

您好, 我很乐意为您提供帮助。 已向您发送包含更多细节的电子邮件。 谢谢!

更多关于[远程] 高级Golang开发工程师招聘 - 数据科学开发者工具项目的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


您好,

我很乐意为您提供帮助。

如需进一步详细讨论,请通过邮件 garry@cisinlabs.com 或 Skype: cis.garry 与我联系。

期待您的回复。

谢谢。

这是一个非常有意思的项目,专注于解决数据科学工作流自动化和复现性的痛点。从技术角度看,这个“运行器”工具的核心挑战在于构建一个跨平台、安全、且能与Docker及远程后端高效协同的守护进程或客户端。以下是针对该职位技术栈和任务的一些专业分析和示例:

1. 核心架构分析 “运行器”本质上是一个需要在用户本地或云虚拟机上常驻的代理(Agent)。它需要:

  • dstack后端通信:通常使用gRPC或WebSocket进行长连接,以接收任务(运行工作流的请求)。
  • 任务管理与隔离:核心是调用Docker API,根据后端提供的规格(镜像、命令、环境变量、卷挂载等)创建并管理容器。
  • 状态上报与日志流式传输:实时将容器状态(开始、运行中、失败、成功)和stdout/stderr日志回传至后端。
  • 资源管理:可能涉及本地GPU资源的发现与分配。

2. 关键技术点与Go实现示例

  • 与Docker引擎交互:推荐使用官方的github.com/docker/docker/client包。
package main

import (
    "context"
    "fmt"
    "io"
    "os"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/client"
)

func runJob(ctx context.Context, image string, cmd []string) error {
    // 1. 创建Docker客户端
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        return err
    }
    defer cli.Close()

    // 2. 拉取镜像(如果本地不存在)
    out, err := cli.ImagePull(ctx, image, types.ImagePullOptions{})
    if err != nil {
        return err
    }
    io.Copy(os.Stdout, out) // 显示拉取进度
    out.Close()

    // 3. 创建容器配置
    config := &container.Config{
        Image: image,
        Cmd:   cmd,
        Tty:   false,
    }
    hostConfig := &container.HostConfig{
        // 示例:挂载当前工作目录到容器内的 /workspace
        Binds: []string{fmt.Sprintf("%s:/workspace", os.Getenv("PWD"))},
        // 可根据请求动态设置资源限制,如GPU
        // Resources: container.Resources{...},
    }

    // 4. 创建并启动容器
    resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, "")
    if err != nil {
        return err
    }

    if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
        return err
    }

    // 5. 流式传输容器日志(同时输出到本地和上报后端)
    logsOpts := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true}
    out, err = cli.ContainerLogs(ctx, resp.ID, logsOpts)
    if err != nil {
        return err
    }
    defer out.Close()

    // 此处简化处理:将日志同时打印到标准输出。
    // 实际项目中,需要解析out流,并将其通过gRPC流式发送回dstack后端。
    go func() {
        io.Copy(os.Stdout, out)
    }()

    // 6. 等待容器执行完成,并获取退出状态码
    statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    select {
    case err := <-errCh:
        return err
    case status := <-statusCh:
        fmt.Printf("Container exited with status code: %d\n", status.StatusCode)
        // 根据状态码上报任务成功或失败
        return nil
    }
}
  • 与后端通信(gRPC示例):定义proto文件并生成代码,实现双向流。
// runner.proto (部分定义)
service RunnerService {
    rpc Connect(stream RunnerStatus) returns (stream JobRequest) {}
}

message JobRequest {
    string job_id = 1;
    string docker_image = 2;
    repeated string command = 3;
    map<string, string> environment = 4;
}

message RunnerStatus {
    string runner_id = 1;
    oneof status {
        JobProgress progress = 2;
        JobResult result = 3;
    }
}
// Go客户端连接与处理循环示例
func (r *Runner) ConnectAndServe(ctx context.Context) error {
    stream, err := r.grpcClient.Connect(ctx)
    if err != nil {
        return err
    }
    // 发送初始状态(如就绪)
    stream.Send(&pb.RunnerStatus{RunnerId: r.id, Status: &pb.RunnerStatus_Ready{Ready: &pb.Ready{}}})

    for {
        req, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        // 收到新任务,在goroutine中异步执行
        go r.executeJob(ctx, req, stream)
    }
    return nil
}

func (r *Runner) executeJob(ctx context.Context, req *pb.JobRequest, stream pb.RunnerService_ConnectClient) {
    // 1. 通过stream发送“任务开始”状态
    stream.Send(&pb.RunnerStatus{Progress: &pb.JobProgress{JobId: req.JobId, State: pb.JobState_RUNNING}})
    
    // 2. 调用前述的runJob函数,并捕获日志和退出码
    // 3. 将日志行实时封装为JobProgress消息,通过stream.Send发送
    // 4. 任务结束时,发送JobResult消息
}
  • 跨平台兼容性:Go本身编译具有跨平台特性,但需要注意:
    • Docker Socket路径:在Windows(npipe:////./pipe/docker_engine)和Unix系统(unix:///var/run/docker.sock)上不同。
    • 文件路径:使用path/filepath包处理路径分隔符。
    • 系统信号:使用os/signal包统一处理SIGINTSIGTERM来优雅停止。

3. 项目可能涉及的其他Go技术栈

  • 配置管理:使用github.com/spf13/viper
  • 日志记录:使用github.com/sirupsen/logrusgo.uber.org/zap
  • 进程管理:对于需要更细粒度控制(非Docker)的任务,可使用golang.org/x/sysos/exec
  • 并发模型:大量使用goroutine和channel进行任务调度和通信,需注意并发安全。

这个职位要求开发者不仅要有扎实的Go和Docker功底,还需要对系统编程、网络通信和异步处理有深刻理解。构建的这个“运行器”是连接用户环境与dstack.ai云服务的核心桥梁,其稳定性和性能直接关系到用户体验。

回到顶部