Skip to content

Commit

Permalink
Merge pull request #994 from traPtitech/feat/docker/sablier
Browse files Browse the repository at this point in the history
docker backend: sablier によるランタイムアプリの自動停止機能の実装
  • Loading branch information
pirosiki197 authored Jan 14, 2025
2 parents 136bb2b + 5340ecf commit bde6a85
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 5 deletions.
9 changes: 9 additions & 0 deletions .local-dev/config/ns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,15 @@ components:
type: traefik
traefik:
priorityOffset: 0
middleware:
sablier:
enable: true
url: http://sablier:10000
sessionDuration: 5m
dynamic:
theme: ghost
blocking:
timeout: 1m
tls:
certResolver: nsresolver
wildcard:
Expand Down
7 changes: 7 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,13 @@ func init() {
viper.SetDefault("components.controller.docker.ss.url", "")
viper.SetDefault("components.controller.docker.routing.type", "traefik")
viper.SetDefault("components.controller.docker.routing.traefik.priorityOffset", 0)

viper.SetDefault("components.controller.docker.middleware.sablier.enable", true)
viper.SetDefault("components.controller.docker.middleware.sablier.url", "http://sablier:10000")
viper.SetDefault("components.controller.docker.middleware.sablier.sessionDuration", "1h")
viper.SetDefault("components.controller.docker.middleware.sablier.dynamic.theme", "ghost")
viper.SetDefault("components.controller.docker.middleware.sablier.blocking.timeout", "1m")

viper.SetDefault("components.controller.docker.tls.certResolver", "nsresolver")
viper.SetDefault("components.controller.docker.tls.wildcard.domains", nil)

Expand Down
10 changes: 10 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,14 @@ services:
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro

sablier:
image: sablierapp/sablier:1.8.1
command:
- start
- --provider.name=docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock

traefik:
image: traefik:3.1
restart: always
Expand All @@ -409,6 +417,8 @@ services:
- --ping=true
- --metrics.prometheus=true
- --metrics.prometheus.entrypoint=metrics
- --experimental.plugins.sablier.modulename=github.com/sablierapp/sablier
- --experimental.plugins.sablier.version=v1.8.1
ports:
- "80:80"
- "443:443"
Expand Down
17 changes: 17 additions & 0 deletions pkg/infrastructure/backend/dockerimpl/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sync"
"time"

"github.com/samber/lo"
"github.com/traPtitech/neoshowcase/pkg/util/retry"

clitypes "github.com/docker/cli/cli/config/types"
Expand Down Expand Up @@ -180,9 +181,17 @@ func (b *Backend) containerLabels(app *domain.Application) map[string]string {
appLabel: "true",
appIDLabel: app.ID,
appRestartedAtLabel: app.UpdatedAt.Format(time.RFC3339Nano),
"sablier.enable": lo.Ternary(b.useSablier(app), "true", "false"),
"sablier.group": sablierGroupName(app.ID),
})
}

func (b *Backend) useSablier(app *domain.Application) bool {
return b.config.Middleware.Sablier.Enable &&
app.DeployType == domain.DeployTypeRuntime &&
app.Config.BuildConfig.GetRuntimeConfig().AutoShutdown.Enabled
}

func containerName(appID string) string {
return fmt.Sprintf("nsapp-%s", appID)
}
Expand All @@ -202,3 +211,11 @@ func stripMiddlewareName(website *domain.Website) string {
func ssHeaderMiddlewareName(ss *domain.StaticSite) string {
return fmt.Sprintf("nsapp-ss-header-%s", ss.Application.ID)
}

func sablierMiddlewareName(app *domain.Application) string {
return app.ID + "-sablier"
}

func sablierGroupName(appID string) string {
return fmt.Sprintf("nsapp-%s", appID)
}
19 changes: 19 additions & 0 deletions pkg/infrastructure/backend/dockerimpl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,25 @@ type Config struct {
PriorityOffset int `mapstructure:"priorityOffset" yaml:"priorityOffset"`
} `mapstructure:"traefik" yaml:"traefik"`
} `mapstructure:"routing" yaml:"routing"`
// Middleware section defines middleware settings.
Middleware struct {
// Sablier (https://github.com/acouvreur/sablier) starts user apps on demand and shuts them down after a certain time.
Sablier struct {
Enable bool `mapstructure:"enable" yaml:"enable"`
SablierURL string `mapstructure:"url" yaml:"url"`
// SessionDuration defines how long the session should last.
//
// Example: "10m"
SessionDuration string `mapstructure:"sessionDuration" yaml:"sessionDuration"`
Dynamic struct {
Theme string `mapstructure:"theme" yaml:"theme"`
} `mapstructure:"dynamic" yaml:"dynamic"`
Blocking struct {
// Timeout defines how long the blocking should last.
Timeout string `mapstructure:"timeout" yaml:"timeout"`
} `mapstructure:"blocking" yaml:"blocking"`
} `mapstructure:"sablier" yaml:"sablier"`
}
// TLS section defines tls setting for user app ingress.
TLS struct {
CertResolver string `mapstructure:"certResolver" yaml:"certResolver"`
Expand Down
38 changes: 36 additions & 2 deletions pkg/infrastructure/backend/dockerimpl/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type (
a []any
)

func (b *Backend) routerBase(website *domain.Website, svcName string) (router m, middlewares m) {
func (b *Backend) routerBase(app *domain.Application, website *domain.Website, svcName string) (router m, middlewares m) {
middlewares = make(m)

var entrypoints []string
Expand All @@ -42,6 +42,12 @@ func (b *Backend) routerBase(website *domain.Website, svcName string) (router m,
log.Warnf("auth config not available for %s", website.FQDN)
}

if b.useSablier(app) {
middlewareName := sablierMiddlewareName(app)
middlewareNames = append(middlewareNames, middlewareName)
middlewares[middlewareName] = b.sablierMiddleware(app)
}

var rule string
if website.PathPrefix == "/" {
rule = fmt.Sprintf("Host(`%s`)", website.FQDN)
Expand Down Expand Up @@ -99,7 +105,7 @@ func newRuntimeConfigBuilder() *runtimeConfigBuilder {
func (b *runtimeConfigBuilder) addWebsite(backend *Backend, app *domain.Application, website *domain.Website) {
svcName := traefikName(website)

router, middlewares := backend.routerBase(website, svcName)
router, middlewares := backend.routerBase(app, website, svcName)

netName := networkName(app.ID)
svc := m{
Expand Down Expand Up @@ -153,3 +159,31 @@ func (b *Backend) writeConfig(filename string, config any) error {
defer enc.Close()
return enc.Encode(config)
}

func (b *Backend) sablierMiddleware(app *domain.Application) m {
// ref: https://sablierapp.dev/#/plugins/traefik?id=configure-the-plugin-using-the-dynamic-configuration
var config = m{
"sablierUrl": b.config.Middleware.Sablier.SablierURL,
"group": sablierGroupName(app.ID),
"sessionDuration": b.config.Middleware.Sablier.SessionDuration,
}

switch app.Config.BuildConfig.GetRuntimeConfig().AutoShutdown.Startup {
case domain.StartupBehaviorLoadingPage:
config["dynamic"] = m{
"displayName": app.Name,
"showDetails": "true",
"theme": b.config.Middleware.Sablier.Dynamic.Theme,
}
case domain.StartupBehaviorBlocking:
config["blocking"] = m{
"timeout": b.config.Middleware.Sablier.Blocking.Timeout,
}
}

return m{
"plugin": m{
"sablier": config,
},
}
}
5 changes: 3 additions & 2 deletions pkg/infrastructure/backend/dockerimpl/synchronize_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ func (b *Backend) syncAppContainer(ctx context.Context, app *domain.RuntimeDesir
hostConfig := &container.HostConfig{
PortBindings: make(map[nat.Port][]nat.PortBinding),
RestartPolicy: container.RestartPolicy{
Name: "on-failure",
MaximumRetryCount: 5,
Name: "on-failure",
// sablier stops the container, so we don't need to restart it
MaximumRetryCount: lo.Ternary(b.useSablier(app.App), 0, 5),
},
}
for _, p := range app.App.PortPublications {
Expand Down
2 changes: 1 addition & 1 deletion pkg/infrastructure/backend/dockerimpl/synchronize_ss.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func newSSConfigBuilder() *ssConfigBuilder {
}

func (b *ssConfigBuilder) addStaticSite(backend *Backend, site *domain.StaticSite) {
router, newMiddlewares := backend.routerBase(site.Website, traefikSSServiceName)
router, newMiddlewares := backend.routerBase(site.Application, site.Website, traefikSSServiceName)
for name, mw := range newMiddlewares {
b.middlewares[name] = mw
}
Expand Down

0 comments on commit bde6a85

Please sign in to comment.