mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 16:58:31 +02:00
feat(agent): add container runtime support and enhance agent configuration
- Introduced ContainerRuntime field in AgentConfig and AgentEnvConfig. - Added IterAgents and NumAgents functions for agent pool management. - Updated agent creation and verification endpoints to handle container runtime. - Enhanced Docker Compose template to support different container runtimes. - Added runtime endpoint to retrieve agent runtime information.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"iter"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v4"
|
"github.com/puzpuzpuz/xsync/v4"
|
||||||
"github.com/yusing/go-proxy/internal/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
@@ -52,6 +53,14 @@ func ListAgents() []*AgentConfig {
|
|||||||
return agents
|
return agents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IterAgents() iter.Seq2[string, *AgentConfig] {
|
||||||
|
return agentPool.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
func NumAgents() int {
|
||||||
|
return agentPool.Size()
|
||||||
|
}
|
||||||
|
|
||||||
func getAgentByAddr(addr string) (agent *AgentConfig, ok bool) {
|
func getAgentByAddr(addr string) (agent *AgentConfig, ok bool) {
|
||||||
agent, ok = agentPool.Load(addr)
|
agent, ok = agentPool.Load(addr)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ var (
|
|||||||
AGENT_PORT="{{.Port}}" \
|
AGENT_PORT="{{.Port}}" \
|
||||||
AGENT_CA_CERT="{{.CACert}}" \
|
AGENT_CA_CERT="{{.CACert}}" \
|
||||||
AGENT_SSL_CERT="{{.SSLCert}}" \
|
AGENT_SSL_CERT="{{.SSLCert}}" \
|
||||||
|
{{ if eq .ContainerRuntime "nerdctl" -}}
|
||||||
|
DOCKER_SOCKET="/var/run/containerd/containerd.sock" \
|
||||||
|
RUNTIME="nerdctl" \
|
||||||
|
{{ else if eq .ContainerRuntime "podman" -}}
|
||||||
|
DOCKER_SOCKET="/var/run/podman/podman.sock" \
|
||||||
|
RUNTIME="podman" \
|
||||||
|
{{ else -}}
|
||||||
|
DOCKER_SOCKET="/var/run/docker.sock" \
|
||||||
|
RUNTIME="docker" \
|
||||||
|
{{ end -}}
|
||||||
bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/install-agent.sh)"`
|
bash -c "$(curl -fsSL https://raw.githubusercontent.com/yusing/godoxy/main/scripts/install-agent.sh)"`
|
||||||
installScriptTemplate = template.Must(template.New("install.sh").Parse(installScript))
|
installScriptTemplate = template.Must(template.New("install.sh").Parse(installScript))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AgentConfig struct {
|
type AgentConfig struct {
|
||||||
Addr string `json:"addr"`
|
Addr string `json:"addr"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
|
Runtime ContainerRuntime `json:"runtime"`
|
||||||
|
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
@@ -32,6 +33,7 @@ type AgentConfig struct {
|
|||||||
const (
|
const (
|
||||||
EndpointVersion = "/version"
|
EndpointVersion = "/version"
|
||||||
EndpointName = "/name"
|
EndpointName = "/name"
|
||||||
|
EndpointRuntime = "/runtime"
|
||||||
EndpointProxyHTTP = "/proxy/http"
|
EndpointProxyHTTP = "/proxy/http"
|
||||||
EndpointHealth = "/health"
|
EndpointHealth = "/health"
|
||||||
EndpointLogs = "/logs"
|
EndpointLogs = "/logs"
|
||||||
@@ -122,6 +124,30 @@ func (cfg *AgentConfig) StartWithCerts(ctx context.Context, ca, crt, key []byte)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check agent runtime
|
||||||
|
runtimeBytes, status, err := cfg.Fetch(ctx, EndpointRuntime)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch status {
|
||||||
|
case http.StatusOK:
|
||||||
|
switch string(runtimeBytes) {
|
||||||
|
case "docker":
|
||||||
|
cfg.Runtime = ContainerRuntimeDocker
|
||||||
|
// case "nerdctl":
|
||||||
|
// cfg.Runtime = ContainerRuntimeNerdctl
|
||||||
|
case "podman":
|
||||||
|
cfg.Runtime = ContainerRuntimePodman
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid agent runtime: %s", runtimeBytes)
|
||||||
|
}
|
||||||
|
case http.StatusNotFound:
|
||||||
|
// backward compatibility, old agent does not have runtime endpoint
|
||||||
|
cfg.Runtime = ContainerRuntimeDocker
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("failed to get agent runtime: HTTP %d %s", status, runtimeBytes)
|
||||||
|
}
|
||||||
|
|
||||||
cfg.Version = string(agentVersionBytes)
|
cfg.Version = string(agentVersionBytes)
|
||||||
agentVersion := pkg.ParseVersion(cfg.Version)
|
agentVersion := pkg.ParseVersion(cfg.Version)
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
//go:embed templates/agent.compose.yml
|
//go:embed templates/agent.compose.yml.tmpl
|
||||||
agentComposeYAML string
|
agentComposeYAML string
|
||||||
agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml").Parse(agentComposeYAML))
|
agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml.tmpl").Parse(agentComposeYAML))
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,7 +20,8 @@ const (
|
|||||||
|
|
||||||
func (c *AgentComposeConfig) Generate() (string, error) {
|
func (c *AgentComposeConfig) Generate() (string, error) {
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||||
if err := agentComposeYAMLTemplate.Execute(buf, c); err != nil {
|
err := agentComposeYAMLTemplate.Execute(buf, c)
|
||||||
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
type (
|
type (
|
||||||
AgentEnvConfig struct {
|
ContainerRuntime string
|
||||||
Name string
|
AgentEnvConfig struct {
|
||||||
Port int
|
Name string
|
||||||
CACert string
|
Port int
|
||||||
SSLCert string
|
CACert string
|
||||||
|
SSLCert string
|
||||||
|
ContainerRuntime ContainerRuntime
|
||||||
}
|
}
|
||||||
AgentComposeConfig struct {
|
AgentComposeConfig struct {
|
||||||
Image string
|
Image string
|
||||||
@@ -15,3 +17,9 @@ type (
|
|||||||
Generate() (string, error)
|
Generate() (string, error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ContainerRuntimeDocker ContainerRuntime = "docker"
|
||||||
|
ContainerRuntimePodman ContainerRuntime = "podman"
|
||||||
|
// ContainerRuntimeNerdctl ContainerRuntime = "nerdctl"
|
||||||
|
)
|
||||||
|
|||||||
66
agent/pkg/agent/templates/agent.compose.yml.tmpl
Normal file
66
agent/pkg/agent/templates/agent.compose.yml.tmpl
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
services:
|
||||||
|
agent:
|
||||||
|
image: "{{.Image}}"
|
||||||
|
container_name: godoxy-agent
|
||||||
|
restart: always
|
||||||
|
{{ if eq .ContainerRuntime "podman" -}}
|
||||||
|
ports:
|
||||||
|
- "{{.Port}}:{{.Port}}"
|
||||||
|
{{ else -}}
|
||||||
|
network_mode: host # do not change this
|
||||||
|
{{ end -}}
|
||||||
|
environment:
|
||||||
|
{{ if eq .ContainerRuntime "nerdctl" -}}
|
||||||
|
DOCKER_SOCKET: "/var/run/containerd/containerd.sock"
|
||||||
|
RUNTIME: "nerdctl"
|
||||||
|
{{ else if eq .ContainerRuntime "podman" -}}
|
||||||
|
DOCKER_SOCKET: "/var/run/podman/podman.sock"
|
||||||
|
RUNTIME: "podman"
|
||||||
|
{{ else -}}
|
||||||
|
DOCKER_SOCKET: "/var/run/docker.sock"
|
||||||
|
RUNTIME: "docker"
|
||||||
|
{{ end -}}
|
||||||
|
AGENT_NAME: "{{.Name}}"
|
||||||
|
AGENT_PORT: "{{.Port}}"
|
||||||
|
AGENT_CA_CERT: "{{.CACert}}"
|
||||||
|
AGENT_SSL_CERT: "{{.SSLCert}}"
|
||||||
|
# use agent as a docker socket proxy: [host]:port
|
||||||
|
# set LISTEN_ADDR to enable (e.g. 127.0.0.1:2375)
|
||||||
|
LISTEN_ADDR:
|
||||||
|
POST: false
|
||||||
|
ALLOW_RESTARTS: false
|
||||||
|
ALLOW_START: false
|
||||||
|
ALLOW_STOP: false
|
||||||
|
AUTH: false
|
||||||
|
BUILD: false
|
||||||
|
COMMIT: false
|
||||||
|
CONFIGS: false
|
||||||
|
CONTAINERS: false
|
||||||
|
DISTRIBUTION: false
|
||||||
|
EVENTS: true
|
||||||
|
EXEC: false
|
||||||
|
GRPC: false
|
||||||
|
IMAGES: false
|
||||||
|
INFO: false
|
||||||
|
NETWORKS: false
|
||||||
|
NODES: false
|
||||||
|
PING: true
|
||||||
|
PLUGINS: false
|
||||||
|
SECRETS: false
|
||||||
|
SERVICES: false
|
||||||
|
SESSION: false
|
||||||
|
SWARM: false
|
||||||
|
SYSTEM: false
|
||||||
|
TASKS: false
|
||||||
|
VERSION: true
|
||||||
|
VOLUMES: false
|
||||||
|
volumes:
|
||||||
|
{{ if eq .ContainerRuntime "podman" -}}
|
||||||
|
- /var/run/podman/podman.sock:/var/run/podman/podman.sock
|
||||||
|
{{ else if eq .ContainerRuntime "nerdctl" -}}
|
||||||
|
- /var/run/containerd/containerd.sock:/var/run/containerd/containerd.sock
|
||||||
|
- /var/lib/nerdctl:/var/lib/nerdctl:ro # required to read metadata like network info
|
||||||
|
{{ else -}}
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
{{ end -}}
|
||||||
|
- ./data:/app/data
|
||||||
@@ -50,6 +50,9 @@ func NewAgentHandler() http.Handler {
|
|||||||
mux.HandleEndpoint("GET", agent.EndpointName, func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleEndpoint("GET", agent.EndpointName, func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, env.AgentName)
|
fmt.Fprint(w, env.AgentName)
|
||||||
})
|
})
|
||||||
|
mux.HandleEndpoint("GET", agent.EndpointRuntime, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprint(w, env.Runtime)
|
||||||
|
})
|
||||||
mux.HandleEndpoint("GET", agent.EndpointHealth, CheckHealth)
|
mux.HandleEndpoint("GET", agent.EndpointHealth, CheckHealth)
|
||||||
mux.HandleEndpoint("GET", agent.EndpointSystemInfo, metricsHandler.ServeHTTP)
|
mux.HandleEndpoint("GET", agent.EndpointSystemInfo, metricsHandler.ServeHTTP)
|
||||||
mux.ServeMux.HandleFunc("/", socketproxy.DockerSocketHandler(env.DockerSocket))
|
mux.ServeMux.HandleFunc("/", socketproxy.DockerSocketHandler(env.DockerSocket))
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NewAgentRequest struct {
|
type NewAgentRequest struct {
|
||||||
Name string `form:"name" validate:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
Host string `form:"host" validate:"required"`
|
Host string `json:"host" binding:"required"`
|
||||||
Port int `form:"port" validate:"required,min=1,max=65535"`
|
Port int `json:"port" binding:"required,min=1,max=65535"`
|
||||||
Type string `form:"type" validate:"required,oneof=docker system"`
|
Type string `json:"type" binding:"required,oneof=docker system"`
|
||||||
Nightly bool `form:"nightly" validate:"omitempty"`
|
Nightly bool `json:"nightly" binding:"omitempty"`
|
||||||
|
ContainerRuntime agent.ContainerRuntime `json:"container_runtime" binding:"omitempty,oneof=docker podman" default:"docker"`
|
||||||
} // @name NewAgentRequest
|
} // @name NewAgentRequest
|
||||||
|
|
||||||
type NewAgentResponse struct {
|
type NewAgentResponse struct {
|
||||||
@@ -47,6 +48,7 @@ func Create(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hostport := net.JoinHostPort(request.Host, strconv.Itoa(request.Port))
|
hostport := net.JoinHostPort(request.Host, strconv.Itoa(request.Port))
|
||||||
if _, ok := agent.GetAgent(hostport); ok {
|
if _, ok := agent.GetAgent(hostport); ok {
|
||||||
c.JSON(http.StatusConflict, apitypes.Error("agent already exists"))
|
c.JSON(http.StatusConflict, apitypes.Error("agent already exists"))
|
||||||
@@ -67,10 +69,11 @@ func Create(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cfg agent.Generator = &agent.AgentEnvConfig{
|
var cfg agent.Generator = &agent.AgentEnvConfig{
|
||||||
Name: request.Name,
|
Name: request.Name,
|
||||||
Port: request.Port,
|
Port: request.Port,
|
||||||
CACert: ca.String(),
|
CACert: ca.String(),
|
||||||
SSLCert: srv.String(),
|
SSLCert: srv.String(),
|
||||||
|
ContainerRuntime: request.ContainerRuntime,
|
||||||
}
|
}
|
||||||
if request.Type == "docker" {
|
if request.Type == "docker" {
|
||||||
cfg = &agent.AgentComposeConfig{
|
cfg = &agent.AgentComposeConfig{
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||||
"github.com/yusing/go-proxy/agent/pkg/certs"
|
"github.com/yusing/go-proxy/agent/pkg/certs"
|
||||||
. "github.com/yusing/go-proxy/internal/api/types"
|
. "github.com/yusing/go-proxy/internal/api/types"
|
||||||
config "github.com/yusing/go-proxy/internal/config/types"
|
config "github.com/yusing/go-proxy/internal/config/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VerifyNewAgentRequest struct {
|
type VerifyNewAgentRequest struct {
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
CA PEMPairResponse `json:"ca"`
|
CA PEMPairResponse `json:"ca"`
|
||||||
Client PEMPairResponse `json:"client"`
|
Client PEMPairResponse `json:"client"`
|
||||||
|
ContainerRuntime agent.ContainerRuntime `json:"container_runtime"`
|
||||||
} // @name VerifyNewAgentRequest
|
} // @name VerifyNewAgentRequest
|
||||||
|
|
||||||
// @x-id "verify"
|
// @x-id "verify"
|
||||||
@@ -55,7 +57,7 @@ func Verify(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
nRoutesAdded, err := config.GetInstance().VerifyNewAgent(request.Host, ca, client)
|
nRoutesAdded, err := config.GetInstance().VerifyNewAgent(request.Host, ca, client, request.ContainerRuntime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, Error("invalid request", err))
|
c.JSON(http.StatusBadRequest, Error("invalid request", err))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ import (
|
|||||||
"github.com/yusing/go-proxy/internal/route/provider"
|
"github.com/yusing/go-proxy/internal/route/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cfg *Config) VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, gperr.Error) {
|
func (cfg *Config) VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error) {
|
||||||
for _, a := range cfg.value.Providers.Agents {
|
for _, a := range cfg.value.Providers.Agents {
|
||||||
if a.Addr == host {
|
if a.Addr == host {
|
||||||
return 0, gperr.New("agent already exists")
|
return 0, gperr.New("agent already exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var agentCfg agent.AgentConfig
|
agentCfg := agent.AgentConfig{
|
||||||
agentCfg.Addr = host
|
Addr: host,
|
||||||
|
Runtime: containerRuntime,
|
||||||
|
}
|
||||||
err := agentCfg.StartWithCerts(cfg.Task().Context(), ca.Cert, client.Cert, client.Key)
|
err := agentCfg.StartWithCerts(cfg.Task().Context(), ca.Cert, client.Cert, client.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, gperr.Wrap(err, "failed to start agent")
|
return 0, gperr.Wrap(err, "failed to start agent")
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ type (
|
|||||||
Statistics() map[string]any
|
Statistics() map[string]any
|
||||||
RouteProviderList() []RouteProviderListResponse
|
RouteProviderList() []RouteProviderListResponse
|
||||||
Context() context.Context
|
Context() context.Context
|
||||||
VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair) (int, gperr.Error)
|
VerifyNewAgent(host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, gperr.Error)
|
||||||
AutoCertProvider() *autocert.Provider
|
AutoCertProvider() *autocert.Provider
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user