chore: load Caddy config via admin API instead of watching Caddyfile change on fs

This commit is contained in:
Pasha Sviderski
2025-09-09 19:10:32 +10:00
parent d25864e52f
commit 5baa8087e5
4 changed files with 172 additions and 107 deletions
+47 -35
View File
@@ -23,11 +23,12 @@ const (
// proxy. The generated configuration allows Caddy to route external traffic to service containers across the internal
// network.
type Controller struct {
machineID string
configDir string
generator *CaddyfileGenerator
store *store.Store
log *slog.Logger
machineID string
caddyfilePath string
generator *CaddyfileGenerator
client *CaddyAdminClient
store *store.Store
log *slog.Logger
}
func NewController(machineID, configDir, adminSock string, store *store.Store) (*Controller, error) {
@@ -39,15 +40,16 @@ func NewController(machineID, configDir, adminSock string, store *store.Store) (
}
log := slog.With("component", "caddy-controller")
validator := NewCaddyAdminValidator(adminSock)
generator := NewCaddyfileGenerator(machineID, validator, log)
client := NewCaddyAdminClient(adminSock)
generator := NewCaddyfileGenerator(machineID, client, log)
return &Controller{
machineID: machineID,
configDir: configDir,
generator: generator,
store: store,
log: log,
machineID: machineID,
caddyfilePath: filepath.Join(configDir, "Caddyfile"),
generator: generator,
client: client,
store: store,
log: log,
}, nil
}
@@ -59,11 +61,11 @@ func (c *Controller) Run(ctx context.Context) error {
c.log.Info("Subscribed to container changes in the cluster to generate Caddy configuration.")
containers = filterHealthyContainers(containers)
if err = c.generateCaddyfile(ctx, containers); err != nil {
return fmt.Errorf("generate Caddyfile configuration: %w", err)
}
c.generateAndLoadCaddyfile(ctx, containers)
// TODO: left for backward compatibility, remove later.
if err = c.generateJSONConfig(containers); err != nil {
return fmt.Errorf("generate Caddy JSON configuration: %w", err)
c.log.Error("Failed to generate Caddy JSON configuration to disk.", "err", err)
}
for {
@@ -80,15 +82,12 @@ func (c *Controller) Run(ctx context.Context) error {
continue
}
containers = filterHealthyContainers(containers)
c.generateAndLoadCaddyfile(ctx, containers)
if err = c.generateCaddyfile(ctx, containers); err != nil {
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
}
// TODO: left for backward compatibility, remove later.
if err = c.generateJSONConfig(containers); err != nil {
c.log.Error("Failed to generate Caddy JSON configuration.", "err", err)
c.log.Error("Failed to generate Caddy JSON configuration to disk.", "err", err)
}
c.log.Info("Updated Caddy configuration.", "dir", c.configDir)
case <-ctx.Done():
return nil
}
@@ -109,22 +108,35 @@ func filterHealthyContainers(containers []store.ContainerRecord) []store.Contain
return healthy
}
func (c *Controller) generateCaddyfile(ctx context.Context, containers []store.ContainerRecord) error {
func (c *Controller) generateAndLoadCaddyfile(ctx context.Context, containers []store.ContainerRecord) {
caddyfile, err := c.generateCaddyfile(ctx, containers)
if err != nil {
c.log.Error("Failed to generate Caddyfile configuration.", "err", err)
return
}
if err = c.client.Load(ctx, caddyfile); err != nil {
c.log.Error("Failed to load new Caddy configuration into local Caddy instance.",
"err", err, "path", c.caddyfilePath)
} else {
c.log.Info("New Caddy configuration loaded into local Caddy instance.", "path", c.caddyfilePath)
}
}
func (c *Controller) generateCaddyfile(ctx context.Context, containers []store.ContainerRecord) (string, error) {
caddyfile, err := c.generator.Generate(ctx, containers)
if err != nil {
return fmt.Errorf("generate Caddyfile: %w", err)
}
caddyfilePath := filepath.Join(c.configDir, "Caddyfile")
// TODO: use atomic file write to avoid partial loads on Caddy watch reload.
if err = os.WriteFile(caddyfilePath, []byte(caddyfile), 0o640); err != nil {
return fmt.Errorf("write Caddyfile to file '%s': %w", caddyfilePath, err)
}
if err = fs.Chown(caddyfilePath, "", CaddyGroup); err != nil {
return fmt.Errorf("change owner of Caddyfile '%s': %w", caddyfilePath, err)
return "", fmt.Errorf("generate Caddyfile: %w", err)
}
return nil
if err = os.WriteFile(c.caddyfilePath, []byte(caddyfile), 0o640); err != nil {
return "", fmt.Errorf("write Caddyfile to file '%s': %w", c.caddyfilePath, err)
}
if err = fs.Chown(c.caddyfilePath, "", CaddyGroup); err != nil {
return "", fmt.Errorf("change owner of Caddyfile '%s': %w", c.caddyfilePath, err)
}
return caddyfile, nil
}
func (c *Controller) generateJSONConfig(containers []store.ContainerRecord) error {
@@ -142,7 +154,7 @@ func (c *Controller) generateJSONConfig(containers []store.ContainerRecord) erro
if err != nil {
return fmt.Errorf("marshal Caddy configuration: %w", err)
}
configPath := filepath.Join(c.configDir, "caddy.json")
configPath := filepath.Join(filepath.Dir(c.caddyfilePath), "caddy.json")
if err = os.WriteFile(configPath, configBytes, 0o640); err != nil {
return fmt.Errorf("write Caddy configuration to file '%s': %w", configPath, err)