3.从0实现Online Judge(go语言)-Docker相关

源码见:https://github.com/lovercode/GO_OJ.git,demo见:https://codelover.me/run.html

Docker相关

compilerrunner都是运行在docker容器中,可以进行资源限制和隔离,主要的实现如下。

初始化compiler容器

主要是根据配置文件来启动多个compiler的容器

func init() {
    for num := uint64(0); num < conf.GlobalConfig.CompilerNum; num++ {
        go initCompilerDocker(conf.GlobalConfig.CompilerDocker)
    }

}

func initCompilerDocker(Conf conf.DockerConfig) {
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.WithVersion("1.18"))
    if err != nil {
        panic(err)
    }

    if err != nil {
        panic(err)
    }
    Config := container.Config{
        Image:      Conf.DockerName,
        Cmd:        Conf.Cmd,
        WorkingDir: Conf.WorkDir,
    }
    var mounts []mount.Mount
    for k, v := range Conf.DockerMount {
        mounts = append(mounts, mount.Mount{
            Type:   mount.TypeBind,
            Source: k,
            Target: v,
        })
    }

    HostConfig := container.HostConfig{
        Mounts: mounts,
        // Resources:      Resources,
        ReadonlyRootfs: Conf.ReadonlyRootfs,
        // Runtime:        "runsc",
    }
    if Conf.Memory >= 4194304 { //<4m设置无效
        Resources := container.Resources{
            Memory:    Conf.Memory,   //4m
            CPUQuota:  Conf.CpuQuota, //CPU资源的绝对限制,一般和CpuPeriod结合在一起,CpuQuota/CpuPeriod,就是能够使用的核数
            CPUPeriod: Conf.CpuPeriod,
        }
        HostConfig.Resources = Resources
    }
    resp, err := cli.ContainerCreate(ctx, &Config, &HostConfig, nil, "")
    defer cli.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})

    if err != nil {
        panic(err)
    }

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

    var res container.ContainerWaitOKBody
    statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)

    select {
    case err := <-errCh:
        if err != nil {
            log.Println("编译机出错退出")
            goto end
        }
    case res = <-statusCh:
        if res.StatusCode != 0 {

        }

        log.Println("编译机退出,准备重启...")
        goto tag
    }
end:
    log.Println("编译机退出...")

}

  • 配置文件
CompilerNum=1
CompileDataPath="/home/codelover/go/src/go_oj/bin/run"
[CompilerDocker]
    Memory=-1
    CpuQuota=100000
    CpuPeriod=100000
    DockerName = "compiler:v2"
    Cmd = ["/bin/sh", "-c", "./compiler"]
    WorkDir = "/compiler"
    [CompilerDocker.DockerMount]
        "/home/codelover/go/src/go_oj/bin" = "/compiler"

初始化所有runner容器

与初始化compiler一样,读取配置直接初始化


func init() {
    for _, v := range conf.GlobalConfig.RunnerDocker {
        for num := 0; num < v.RunNum; num++ {
            go initRunnerDocker(v)
        }
    }
}

func initRunnerDocker(Conf conf.DockerConfig) {
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.WithVersion("1.37"))
    if err != nil {
        panic(err)
    }

    if err != nil {
        panic(err)
    }
    Config := container.Config{
        Image:      Conf.DockerName,
        Cmd:        Conf.Cmd,
        WorkingDir: Conf.WorkDir,
    }
    var mounts []mount.Mount
    for k, v := range Conf.DockerMount {
        mounts = append(mounts, mount.Mount{
            Type:   mount.TypeBind,
            Source: k,
            Target: v,
            // ReadOnly: true,
        })
    }
    Init := true
    HostConfig := container.HostConfig{
        Mounts:         mounts,
        ReadonlyRootfs: Conf.ReadonlyRootfs,
        Init:           &Init,
        // Runtime:        "runsc",
    }
    if Conf.Memory >= 4194304 { //<4m设置无效
        Resources := container.Resources{
            Memory:    Conf.Memory,   //4m
            CPUQuota:  Conf.CpuQuota, //CPU资源的绝对限制,一般和CpuPeriod结合在一起,CpuQuota/CpuPeriod,就是能够使用的核数
            CPUPeriod: Conf.CpuPeriod,
        }
        HostConfig.Resources = Resources
    }
    resp, err := cli.ContainerCreate(ctx, &Config, &HostConfig, nil, "")
    defer cli.ContainerRemove(ctx, resp.ID, types.ContainerRemoveOptions{Force: true})

    if err != nil {
        panic(err)
    }

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

    var res container.ContainerWaitOKBody
    statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)

    select {
    case err := <-errCh:
        if err != nil {
            log.Println("运行机出错退出", err)
            goto end
        }
    case res = <-statusCh:
        if res.StatusCode != 0 {

        }

        log.Println("运行机退出,准备重启...", res)
        goto tag
    }
end:
    log.Println("运行机退出...")


}
  • 配置如下
#c语言
[RunnerDocker.1]
    RunNum=1
    Memory=419430400
    CpuQuota=50000
    CpuPeriod=100000
    ReadonlyRootfs=false
    DockerName="alpine_c:latest"
    Cmd = ["./runner","conf/runner_config_1.toml"]
    WorkDir = "/runner"
    [RunnerDocker.1.DockerMount]
        "/home/codelover/go/src/go_oj/bin" = "/runner"

#php
[RunnerDocker.5]
    RunNum=1
    Memory=-1
    CpuQuota=100000
    CpuPeriod=100000
    ReadonlyRootfs=false
    DockerName="alpine_php:latest"
    Cmd = ["./runner","conf/runner_config_5.toml"]
    WorkDir = "/runner"
    [RunnerDocker.5.DockerMount]
        "/home/codelover/go/src/go_oj/bin" = "/runner"
        

dockerfile

  • 编译机需要有所有编译环境
FROM frolvlad/alpine-glibc:latest
RUN apk add --no-cache gcc &&\
    apk add --no-cache g++ &&\
    apk add --no-cache php &&\
    apk add --no-cache openjdk8 &&\
    apk add --no-cache pyflakes &&\
    apk add --no-cache go &&\
    apk add --no-cache libstdc++ 
ENV PATH "$PATH:/usr/lib/jvm/default-jvm/bin"
    

  • 运行机dockerfile示例(php)
    user用于运行用户程序
FROM frolvlad/alpine-glibc:latest
RUN adduser -D user0 -G nogroup &&\
    adduser -D user1 -G nogroup &&\
    adduser -D user2 -G nogroup &&\
    adduser -D user3 -G nogroup &&\
    adduser -D user4 -G nogroup &&\
    adduser -D user5 -G nogroup &&\
    adduser -D user6 -G nogroup &&\
    adduser -D user7 -G nogroup &&\
    adduser -D user8 -G nogroup &&\
    adduser -D user9 -G nogroup &&\
    adduser -D user10 -G nogroup &&\
    apk add --no-cache php7 &&\
    apk add --no-cache libseccomp-dev
    

推荐阅读更多精彩内容