源码见:https://github.com/lovercode/GO_OJ.git,demo见:https://codelover.me/run.html
Docker相关
compiler
和runner
都是运行在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