Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Dockerfile.router
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Multi-stage build for agentcube-router
FROM golang:1.24.9-alpine AS builder

# Build arguments for multi-architecture support
ARG TARGETOS=linux
ARG TARGETARCH

WORKDIR /workspace

# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY cmd/ cmd/
COPY pkg/ pkg/
COPY client-go/ client-go/

# Build with dynamic architecture support
# Supports amd64, arm64, arm/v7, etc.
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -o agentcube-router ./cmd/router

# Runtime image
FROM alpine:3.19

RUN apk --no-cache add ca-certificates

WORKDIR /app

# Copy binary from builder
COPY --from=builder /workspace/agentcube-router .

# Run as non-root user
RUN adduser -D -u 1000 router
USER router

EXPOSE 8080

ENTRYPOINT ["/app/agentcube-router"]
CMD ["--port=8080", "--debug"]
30 changes: 18 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ build: generate ## Build agentcube-apiserver binary
@echo "Building agentcube-apiserver..."
go build -o bin/agentcube-apiserver ./cmd/workload-manager

build-router: ## Build agentcube-router binary
@echo "Building agentcube-router..."
go build -o bin/agentcube-router ./cmd/router

build-agentd: generate ## Build agentd binary
@echo "Building agentd..."
go build -o bin/agentd ./cmd/agentd
Expand All @@ -80,30 +84,27 @@ build-test-tunnel: ## Build test-tunnel tool
@echo "Building test-tunnel..."
go build -o bin/test-tunnel ./cmd/test-tunnel

build-all: build build-agentd build-test-tunnel ## Build all binaries
build-all: build build-router build-agentd build-test-tunnel ## Build all binaries

# Run server (development mode)
run:
@echo "Running agentcube-apiserver..."
go run ./cmd/workload-manager/main.go \
--port=8080 \
--ssh-username=sandbox \
--ssh-port=22
--debug

# Run server (with kubeconfig)
run-local:
@echo "Running agentcube-apiserver with local kubeconfig..."
go run ./cmd/workload-manager/main.go \
--port=8080 \
--kubeconfig=${HOME}/.kube/config \
--ssh-username=sandbox \
--ssh-port=22
--debug

# Clean build artifacts
clean:
@echo "Cleaning..."
rm -rf bin/
rm -f agentcube-apiserver agentd
rm -f agentcube-router agentd
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The clean target was modified to remove agentcube-router but it no longer removes agentcube-apiserver. Since the build target for agentcube-apiserver still exists, it should probably be included in the clean target to avoid leaving stale artifacts.

rm -f agentcube-apiserver agentcube-router agentd


# Install dependencies
deps:
Expand Down Expand Up @@ -138,18 +139,23 @@ lint: golangci-lint ## Run golangci-lint

# Install to system
install: build
@echo "Installing agentcube-apiserver..."
sudo cp bin/agentcube-apiserver /usr/local/bin/
@echo "Installing agentcube-router..."
sudo cp bin/agentcube-router /usr/local/bin/

# Docker image variables
APISERVER_IMAGE ?= agentcube-apiserver:latest
ROUTER_IMAGE ?= agentcube-router:latest
IMAGE_REGISTRY ?= ""

# Docker and Kubernetes targets
docker-build:
@echo "Building Docker image..."
docker build -t $(APISERVER_IMAGE) .

docker-build-router: ## Build router Docker image
@echo "Building router Docker image..."
docker build -f Dockerfile.router -t $(ROUTER_IMAGE) .

# Multi-architecture build (supports amd64, arm64)
docker-buildx:
@echo "Building multi-architecture Docker image..."
Expand Down Expand Up @@ -177,15 +183,15 @@ docker-push: docker-build

k8s-deploy:
@echo "Deploying to Kubernetes..."
kubectl apply -f k8s/agentcube-apiserver.yaml
kubectl apply -f k8s/agentcube-router.yaml

k8s-delete:
@echo "Deleting from Kubernetes..."
kubectl delete -f k8s/agentcube-apiserver.yaml
kubectl delete -f k8s/agentcube-router.yaml

k8s-logs:
@echo "Showing logs..."
kubectl logs -n agentcube -l app=agentcube-apiserver -f
kubectl logs -n agentcube -l app=agentcube-router -f

# Load image to kind cluster
kind-load:
Expand Down
75 changes: 75 additions & 0 deletions cmd/router/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"context"
"flag"
"log"
"os"
"os/signal"
"syscall"

"github.com/volcano-sh/agentcube/pkg/router"
)

func main() {
var (
port = flag.String("port", "8080", "Router API server port")
enableTLS = flag.Bool("enable-tls", false, "Enable TLS (HTTPS)")
tlsCert = flag.String("tls-cert", "", "Path to TLS certificate file")
tlsKey = flag.String("tls-key", "", "Path to TLS key file")
debug = flag.Bool("debug", true, "Enable debug mode")
maxConcurrentRequests = flag.Int("max-concurrent-requests", 1000, "Maximum number of concurrent requests")
requestTimeout = flag.Int("request-timeout", 30, "Request timeout in seconds")
maxIdleConns = flag.Int("max-idle-conns", 100, "Maximum number of idle connections")
maxConnsPerHost = flag.Int("max-conns-per-host", 10, "Maximum number of connections per host")
)

// Parse command line flags
flag.Parse()

// Create Router API server configuration
config := &router.Config{
Port: *port,
Debug: *debug,
EnableTLS: *enableTLS,
TLSCert: *tlsCert,
TLSKey: *tlsKey,
MaxConcurrentRequests: *maxConcurrentRequests,
RequestTimeout: *requestTimeout,
MaxIdleConns: *maxIdleConns,
MaxConnsPerHost: *maxConnsPerHost,
}

// Create Router API server
server, err := router.NewServer(config)
if err != nil {
log.Fatalf("Failed to create Router API server: %v", err)
}

// Setup signal handling with context cancellation
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()

// Start Router API server in goroutine
errCh := make(chan error, 1)
go func() {
log.Printf("Starting agentcube Router server on port %s", *port)
if err := server.Start(ctx); err != nil {
errCh <- err
}
}()

// Wait for signal or error
select {
case <-ctx.Done():
log.Println("Received shutdown signal, shutting down gracefully...")
// Cancel the context to trigger server shutdown
cancel()
// Wait for server goroutine to exit after graceful shutdown is complete
<-errCh
case err := <-errCh:
log.Fatalf("Server error: %v", err)
}
Comment on lines +63 to +72
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There's a race condition in the graceful shutdown logic. If the server fails to start (e.g., port already in use) and a shutdown signal is received at the same time, the select statement has non-deterministic behavior. If the <-ctx.Done() case is selected, the startup error from errCh will be consumed but ignored, causing the program to exit silently without reporting the fatal startup error. This can make debugging startup issues very difficult.

To fix this, you should ensure that any error from errCh is handled correctly, regardless of which select case is chosen. Here is a suggested change to at least log the error in the shutdown path, which mitigates the issue of silently ignoring it.

Suggested change
select {
case <-ctx.Done():
log.Println("Received shutdown signal, shutting down gracefully...")
// Cancel the context to trigger server shutdown
cancel()
// Wait for server goroutine to exit after graceful shutdown is complete
<-errCh
case err := <-errCh:
log.Fatalf("Server error: %v", err)
}
select {
case <-ctx.Done():
log.Println("Received shutdown signal, shutting down gracefully...")
// The server's own context handling will trigger shutdown.
// We wait for it to complete and check for any unexpected errors.
// A `http.ErrServerClosed` is expected on graceful shutdown, so we ignore it.
if err := <-errCh; err != nil && err.Error() != "http: Server closed" {
log.Printf("Server shutdown with error: %v", err)
}
case err := <-errCh:
log.Fatalf("Server error: %v", err)
}


log.Println("Router server stopped")
}
Loading
Loading