mirror of
https://github.com/dokku/dokku.git
synced 2026-08-29 10:08:53 +02:00
Kubernetes pulls the app image itself, so a k3s host is free to reap its local copy while the workload keeps running, which the `registry` plugin already does on its own. Deploys, restarts, `dokku run`, and in-cluster cron no longer assert that the image is present locally, falling back to the metadata recorded in the app's current Helm release. A `ps:restart` naming a single process type now rolls only that process type's pods rather than silently redeploying every one. Apps with an `app.json` postdeploy task still require the image locally, as that task runs on the Dokku host.
147 lines
5.1 KiB
Go
147 lines
5.1 KiB
Go
package scheduler_k3s
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/dokku/dokku/plugins/common"
|
|
)
|
|
|
|
// ImageMetadata holds the image-derived values a chart or job needs that would
|
|
// otherwise require the image to be present on the local docker daemon.
|
|
type ImageMetadata struct {
|
|
SourceType string
|
|
WorkingDir string
|
|
}
|
|
|
|
// resolveImageMetadata determines the builder type and working directory for an
|
|
// image, preferring a local inspect and falling back to values carried by the
|
|
// app's current Helm release.
|
|
//
|
|
// k3s workloads run in the cluster and kubelet pulls the image itself, so the
|
|
// dokku host is free to reap its local copy while the app keeps running - the
|
|
// registry plugin's own imageCleanup does exactly that. Without a fallback,
|
|
// every later deploy of such an app would fail. A fallback alone is not enough
|
|
// either: common.IsImageCnbBased, common.IsImageHerokuishBased and
|
|
// common.GetWorkingDir all report false/"" when the inspect errors, so a
|
|
// missing image would otherwise be silently misread as a dockerfile app with no
|
|
// working directory, dropping the herokuish /start wrapper from the start
|
|
// command.
|
|
func resolveImageMetadata(appName string, image string, fallback *ImageMetadata) (ImageMetadata, error) {
|
|
if common.VerifyImage(image) {
|
|
metadata := ImageMetadata{SourceType: "dockerfile"}
|
|
if common.IsImageCnbBased(image) {
|
|
metadata.SourceType = "pack"
|
|
} else if common.IsImageHerokuishBased(image, appName) {
|
|
metadata.SourceType = "herokuish"
|
|
}
|
|
|
|
metadata.WorkingDir = common.GetWorkingDir(appName, image)
|
|
return metadata, nil
|
|
}
|
|
|
|
if fallback != nil && fallback.SourceType != "" {
|
|
return *fallback, nil
|
|
}
|
|
|
|
return ImageMetadata{}, fmt.Errorf("App image (%s) not found locally and no deployed release to read image metadata from", image)
|
|
}
|
|
|
|
// releaseValues reads an app's current Helm release values, which every deploy
|
|
// writes and which therefore make the cluster the authoritative record of how
|
|
// the running workload was built. Returns nil when there is no readable
|
|
// release, leaving callers to treat the values as unavailable rather than
|
|
// failing - a first deploy has no release yet. Existence is checked up front so
|
|
// a never-deployed app does not surface a not-found through the agent's logger.
|
|
func releaseValues(helmAgent *HelmAgent, releaseName string) map[string]interface{} {
|
|
exists, err := helmAgent.ChartExists(releaseName)
|
|
if err != nil || !exists {
|
|
return nil
|
|
}
|
|
|
|
values, err := helmAgent.GetValues(releaseName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return values
|
|
}
|
|
|
|
// imageMetadataFromValues extracts image metadata from a set of Helm release values.
|
|
func imageMetadataFromValues(values map[string]interface{}) *ImageMetadata {
|
|
globalValues, ok := values["global"].(map[string]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
imageValues, ok := globalValues["image"].(map[string]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
sourceType, ok := imageValues["type"].(string)
|
|
if !ok || sourceType == "" {
|
|
return nil
|
|
}
|
|
|
|
workingDir, _ := imageValues["working_dir"].(string)
|
|
return &ImageMetadata{
|
|
SourceType: sourceType,
|
|
WorkingDir: workingDir,
|
|
}
|
|
}
|
|
|
|
// processDeploymentIDsFromValues extracts the per-process deployment ids
|
|
// recorded in an app's current Helm release, keyed by process type.
|
|
//
|
|
// A targeted restart must leave the untargeted processes' pod templates byte
|
|
// identical, otherwise Kubernetes rolls them too. Their ids therefore have to
|
|
// come from what is already deployed rather than being regenerated. Releases
|
|
// written before per-process ids existed only carry a global id, so that is
|
|
// used as the per-process default.
|
|
func processDeploymentIDsFromValues(values map[string]interface{}) map[string]string {
|
|
deploymentIDs := map[string]string{}
|
|
|
|
globalDeploymentID := ""
|
|
if globalValues, ok := values["global"].(map[string]interface{}); ok {
|
|
globalDeploymentID, _ = globalValues["deployment_id"].(string)
|
|
}
|
|
|
|
processes, ok := values["processes"].(map[string]interface{})
|
|
if !ok {
|
|
return deploymentIDs
|
|
}
|
|
|
|
for processType, rawValues := range processes {
|
|
processValues, ok := rawValues.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
deploymentID, _ := processValues["deployment_id"].(string)
|
|
if deploymentID == "" {
|
|
deploymentID = globalDeploymentID
|
|
}
|
|
if deploymentID != "" {
|
|
deploymentIDs[processType] = deploymentID
|
|
}
|
|
}
|
|
|
|
return deploymentIDs
|
|
}
|
|
|
|
// resolveProcessDeploymentID picks the deployment id for a single process type.
|
|
// Every process type gets the fresh id unless a different one was targeted for
|
|
// restart and a prior id is known for this one, in which case holding that id
|
|
// steady leaves the process's pod template untouched so Kubernetes does not roll
|
|
// it. A targeted restart against an app with no prior release rolls everything,
|
|
// which is correct - there is nothing running to preserve.
|
|
func resolveProcessDeploymentID(processType string, deploymentID int64, restartProcessType string, priorDeploymentIDs map[string]string) string {
|
|
if restartProcessType != "" && restartProcessType != processType {
|
|
if priorDeploymentID := priorDeploymentIDs[processType]; priorDeploymentID != "" {
|
|
return priorDeploymentID
|
|
}
|
|
}
|
|
|
|
return fmt.Sprint(deploymentID)
|
|
}
|