249 lines
6.6 KiB
Go
249 lines
6.6 KiB
Go
package exec
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/docker/client"
|
|
)
|
|
|
|
var DockerContainer *dockerContainer
|
|
|
|
func init() {
|
|
//检测docker服务是否已运行
|
|
go func() {
|
|
maxRetries := 5
|
|
retryCount := 0
|
|
for {
|
|
// Initialize DockerContainer with memory, CPU, and disk limits
|
|
container, err := NewDockerContainer(int64(2*1024*1024*1024), int64(1000000000))
|
|
if err != nil {
|
|
retryCount++
|
|
if retryCount > maxRetries {
|
|
log.Fatalf("Failed to initialize Docker container after %d retries: %v", maxRetries, err)
|
|
}
|
|
log.Printf("NewDockerContainer failed (attempt %d/%d): %v", retryCount, maxRetries, err)
|
|
time.Sleep(2 * time.Second)
|
|
continue
|
|
}
|
|
|
|
if err := container.CreateAndStartContainer(); err != nil {
|
|
retryCount++
|
|
if retryCount > maxRetries {
|
|
log.Printf("Failed to create and start container after %d retries: %v", maxRetries, err)
|
|
return
|
|
}
|
|
log.Printf("CreateAndStartContainer failed (attempt %d/%d): %v", retryCount, maxRetries, err)
|
|
time.Sleep(2 * time.Second)
|
|
continue
|
|
}
|
|
|
|
DockerContainer = container
|
|
log.Println("Docker container successfully initialized")
|
|
return
|
|
}
|
|
}()
|
|
}
|
|
|
|
type dockerContainer struct {
|
|
Client *client.Client
|
|
Ctx context.Context
|
|
ContainerID string
|
|
Memory int64 // Memory in bytes
|
|
CPU int64 // CPU in NanoCPUs
|
|
}
|
|
|
|
// NewDockerContainer initializes Docker client, context, and resource limits
|
|
func NewDockerContainer(memory int64, cpu int64) (*dockerContainer, error) {
|
|
ctx := context.Background()
|
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cli.NegotiateAPIVersion(ctx)
|
|
|
|
return &dockerContainer{
|
|
Client: cli,
|
|
Ctx: ctx,
|
|
Memory: memory,
|
|
CPU: cpu,
|
|
}, nil
|
|
}
|
|
|
|
// CreateAndStartContainer creates a container, sets resource limits, and starts it
|
|
// The container runs `tail -f /dev/null` to keep running, waiting for new commands
|
|
func (dc *dockerContainer) CreateAndStartContainer() error {
|
|
if dc == nil {
|
|
return fmt.Errorf("no container found, please create and start the container first")
|
|
}
|
|
// 如果容器已经存在,则跳过创建
|
|
if dc.ContainerID != "" {
|
|
log.Printf("Container already exists, skipping creation.")
|
|
return nil
|
|
}
|
|
|
|
// Pull debian image if not available locally
|
|
_, err := dc.Client.ImagePull(dc.Ctx, "python:3.13", image.PullOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Define resource limits based on DockerContainer struct fields
|
|
hostConfig := &container.HostConfig{
|
|
Resources: container.Resources{
|
|
Memory: dc.Memory, // Set memory limit
|
|
NanoCPUs: dc.CPU, // Set CPU limit
|
|
},
|
|
}
|
|
|
|
// Use the custom network to isolate the container
|
|
networkConfig := &network.NetworkingConfig{
|
|
EndpointsConfig: map[string]*network.EndpointSettings{
|
|
"qq_bot_net": {}, // Attach container to the custom network
|
|
},
|
|
}
|
|
|
|
// Create a container that runs `tail -f /dev/null` to keep it alive
|
|
resp, err := dc.Client.ContainerCreate(dc.Ctx, &container.Config{
|
|
Image: "python:3.13",
|
|
Cmd: []string{"tail", "-f", "/dev/null"}, // Keep container running
|
|
Tty: true,
|
|
}, hostConfig, networkConfig, nil, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store container ID in the struct
|
|
dc.ContainerID = resp.ID
|
|
|
|
// Start the container
|
|
if err := dc.Client.ContainerStart(dc.Ctx, resp.ID, container.StartOptions{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("Container created and started, waiting for commands...")
|
|
return nil
|
|
}
|
|
|
|
// ExecCommandInContainer executes a command in the existing container and returns its output
|
|
func (dc *dockerContainer) ExecCommandInContainer(command string) (string, error) {
|
|
if dc == nil {
|
|
return "", fmt.Errorf("no container found, please create and start the container first")
|
|
}
|
|
|
|
execConfig := container.ExecOptions{
|
|
Cmd: []string{"sh", "-c", command},
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
Tty: true,
|
|
}
|
|
var cancel context.CancelFunc
|
|
timeout := 10 * time.Second
|
|
ctxWithTimeout, cancel := context.WithTimeout(dc.Ctx, timeout)
|
|
defer cancel()
|
|
|
|
// 创建一个 exec 实例
|
|
execIDResp, err := dc.Client.ContainerExecCreate(ctxWithTimeout, dc.ContainerID, execConfig)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// 开始执行命令
|
|
resp, err := dc.Client.ContainerExecAttach(ctxWithTimeout, execIDResp.ID, container.ExecStartOptions{
|
|
Tty: true,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Close()
|
|
|
|
// Capture the output
|
|
var outputBuffer bytes.Buffer
|
|
_, err = io.Copy(&outputBuffer, resp.Reader)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return outputBuffer.String(), nil
|
|
}
|
|
|
|
// RestartAndCleanContainer stops, removes, and recreates the container
|
|
func (dc *dockerContainer) RestartAndCleanContainer() error {
|
|
if dc == nil {
|
|
return nil
|
|
}
|
|
if dc.ContainerID == "" {
|
|
return fmt.Errorf("no container to restart")
|
|
}
|
|
|
|
// Stop the container
|
|
timeout := 10 * time.Second
|
|
timeoutSecs := int(timeout.Seconds())
|
|
if err := dc.Client.ContainerStop(dc.Ctx, dc.ContainerID, container.StopOptions{Timeout: &timeoutSecs}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Remove the container to clean it
|
|
if err := dc.Client.ContainerRemove(dc.Ctx, dc.ContainerID, container.RemoveOptions{Force: true}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Recreate the container with the same resource limits
|
|
resp, err := dc.Client.ContainerCreate(dc.Ctx, &container.Config{
|
|
Image: "python:3.13",
|
|
Cmd: []string{"tail", "-f", "/dev/null"},
|
|
Tty: true,
|
|
}, &container.HostConfig{
|
|
Resources: container.Resources{
|
|
Memory: dc.Memory, // Reuse memory limit
|
|
NanoCPUs: dc.CPU, // Reuse CPU limit
|
|
},
|
|
}, nil, nil, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store new container ID
|
|
dc.ContainerID = resp.ID
|
|
|
|
// Start the new container
|
|
if err := dc.Client.ContainerStart(dc.Ctx, resp.ID, container.StartOptions{}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("Container restarted and cleaned.")
|
|
return nil
|
|
}
|
|
|
|
// RemoveContainer stops and removes the container
|
|
func (dc *dockerContainer) RemoveContainer() error {
|
|
if dc == nil {
|
|
return nil
|
|
}
|
|
if dc.ContainerID == "" {
|
|
return fmt.Errorf("no container to remove")
|
|
}
|
|
|
|
// Stop the container
|
|
timeout := 10 * time.Second
|
|
timeoutSecs := int(timeout.Seconds())
|
|
if err := dc.Client.ContainerStop(dc.Ctx, dc.ContainerID, container.StopOptions{Timeout: &timeoutSecs}); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Remove the container
|
|
if err := dc.Client.ContainerRemove(dc.Ctx, dc.ContainerID, container.RemoveOptions{Force: true}); err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("Container removed.")
|
|
return nil
|
|
}
|