feat: implement healthcheck support
This commit is contained in:
@@ -65,6 +65,42 @@ type Formation struct {
|
||||
// MaxParallel is the maximum number of processes to start in parallel
|
||||
MaxParallel *int `json:"max_parallel"`
|
||||
}
|
||||
type Healthcheck struct {
|
||||
Attempts int32 `json:"attempts,omitempty"`
|
||||
Command []string `json:"command,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
HTTPHeaders []HTTPHeader `json:"httpHeaders,omitempty"`
|
||||
InitialDelay int32 `json:"initialDelay,omitempty"`
|
||||
Listening bool `json:"listening,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Scheme string `json:"scheme,omitempty"`
|
||||
Timeout int32 `json:"timeout,omitempty"`
|
||||
Type HealthcheckType `json:"type,omitempty"`
|
||||
Uptime int32 `json:"uptime,omitempty"`
|
||||
Wait int32 `json:"wait,omitempty"`
|
||||
Warn bool `json:"warn,omitempty"`
|
||||
OnFailure *OnFailure `json:"onFailure,omitempty"`
|
||||
}
|
||||
|
||||
type HealthcheckType string
|
||||
|
||||
const (
|
||||
HealthcheckType_Liveness HealthcheckType = "liveness"
|
||||
HealthcheckType_Readiness HealthcheckType = "readiness"
|
||||
HealthcheckType_Startup HealthcheckType = "startup"
|
||||
)
|
||||
|
||||
type HTTPHeader struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type OnFailure struct {
|
||||
Command []string `json:"command,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Healthcheck is a struct that represents a single healthcheck from an app.json file
|
||||
type Healthcheck struct {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
appjson "github.com/dokku/dokku/plugins/app-json"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
traefikv1alpha1 "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
|
||||
orderedmap "github.com/wk8/go-ordered-map/v2"
|
||||
@@ -45,6 +46,7 @@ type Deployment struct {
|
||||
Image string
|
||||
ImagePullSecrets string
|
||||
ImageSourceType string
|
||||
Healthchecks []appjson.Healthcheck
|
||||
Namespace string
|
||||
PrimaryPort int32
|
||||
PortMaps []PortMap
|
||||
@@ -402,6 +404,89 @@ func templateKubernetesDeployment(input Deployment) (appsv1.Deployment, error) {
|
||||
deployment.Spec.Template.Spec.Containers[0].Args = input.Command
|
||||
}
|
||||
|
||||
if len(input.Healthchecks) > 0 {
|
||||
livenessChecks := []corev1.Probe{}
|
||||
readinessChecks := []corev1.Probe{}
|
||||
startupChecks := []corev1.Probe{}
|
||||
uptimeSeconds := []int32{}
|
||||
for _, healthcheck := range input.Healthchecks {
|
||||
probe := corev1.Probe{
|
||||
ProbeHandler: corev1.ProbeHandler{},
|
||||
InitialDelaySeconds: healthcheck.InitialDelay,
|
||||
PeriodSeconds: healthcheck.Wait,
|
||||
TimeoutSeconds: healthcheck.Timeout,
|
||||
FailureThreshold: healthcheck.Attempts,
|
||||
SuccessThreshold: int32(1),
|
||||
}
|
||||
if len(healthcheck.Command) > 0 {
|
||||
probe.ProbeHandler.Exec = &corev1.ExecAction{
|
||||
Command: healthcheck.Command,
|
||||
}
|
||||
} else if healthcheck.Listening {
|
||||
probe.ProbeHandler.TCPSocket = &corev1.TCPSocketAction{
|
||||
Port: intstr.FromInt32(input.PrimaryPort),
|
||||
}
|
||||
for _, header := range healthcheck.HTTPHeaders {
|
||||
if header.Name == "Host" {
|
||||
probe.ProbeHandler.TCPSocket.Host = header.Value
|
||||
}
|
||||
}
|
||||
} else if healthcheck.Path != "" {
|
||||
probe.ProbeHandler.HTTPGet = &corev1.HTTPGetAction{
|
||||
Path: healthcheck.Path,
|
||||
Port: intstr.FromInt32(input.PrimaryPort),
|
||||
HTTPHeaders: []corev1.HTTPHeader{},
|
||||
}
|
||||
|
||||
if healthcheck.Scheme != "" {
|
||||
probe.ProbeHandler.HTTPGet.Scheme = corev1.URIScheme(strings.ToUpper(healthcheck.Scheme))
|
||||
}
|
||||
|
||||
for _, header := range healthcheck.HTTPHeaders {
|
||||
probe.ProbeHandler.HTTPGet.HTTPHeaders = append(probe.ProbeHandler.HTTPGet.HTTPHeaders, corev1.HTTPHeader{
|
||||
Name: header.Name,
|
||||
Value: header.Value,
|
||||
})
|
||||
}
|
||||
} else if healthcheck.Uptime > 0 {
|
||||
uptimeSeconds = append(uptimeSeconds, healthcheck.Uptime)
|
||||
}
|
||||
|
||||
if healthcheck.Type == appjson.HealthcheckType_Liveness {
|
||||
livenessChecks = append(livenessChecks, probe)
|
||||
} else if healthcheck.Type == appjson.HealthcheckType_Readiness {
|
||||
readinessChecks = append(readinessChecks, probe)
|
||||
} else if healthcheck.Type == appjson.HealthcheckType_Startup {
|
||||
startupChecks = append(startupChecks, probe)
|
||||
}
|
||||
}
|
||||
if len(livenessChecks) > 1 {
|
||||
common.LogWarn("Multiple liveness checks are not supported, only the first one will be used")
|
||||
}
|
||||
if len(readinessChecks) > 1 {
|
||||
common.LogWarn("Multiple readiness checks are not supported, only the first one will be used")
|
||||
}
|
||||
if len(startupChecks) > 1 {
|
||||
common.LogWarn("Multiple startup checks are not supported, only the first one will be used")
|
||||
}
|
||||
if len(uptimeSeconds) > 1 {
|
||||
common.LogWarn("Multiple uptime checks are not supported, only the first one will be used")
|
||||
}
|
||||
|
||||
if len(livenessChecks) > 0 {
|
||||
deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &livenessChecks[0]
|
||||
}
|
||||
if len(readinessChecks) > 0 {
|
||||
deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &readinessChecks[0]
|
||||
}
|
||||
if len(startupChecks) > 0 {
|
||||
deployment.Spec.Template.Spec.Containers[0].StartupProbe = &startupChecks[0]
|
||||
}
|
||||
if len(uptimeSeconds) > 0 {
|
||||
deployment.Spec.MinReadySeconds = uptimeSeconds[0]
|
||||
}
|
||||
}
|
||||
|
||||
if input.ProcessType == "web" {
|
||||
for _, portMap := range input.PortMaps {
|
||||
protocol := "TCP"
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
appjson "github.com/dokku/dokku/plugins/app-json"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/dokku/dokku/plugins/config"
|
||||
"github.com/dokku/dokku/plugins/cron"
|
||||
@@ -221,6 +222,11 @@ data:
|
||||
}
|
||||
}
|
||||
|
||||
appJSON, err := appjson.GetAppJSON(appName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting app.json for deployment: %w", err)
|
||||
}
|
||||
|
||||
workingDir := common.GetWorkingDir(appName, image)
|
||||
deployments := map[string]appsv1.Deployment{}
|
||||
i := 0
|
||||
@@ -239,7 +245,11 @@ data:
|
||||
i++
|
||||
replicaCountPlaceholder := int32(i * 1000)
|
||||
|
||||
// todo: implement healthchecks
|
||||
healthchecks, ok := appJSON.Healthchecks[processType]
|
||||
if !ok {
|
||||
healthchecks = []appjson.Healthcheck{}
|
||||
}
|
||||
|
||||
// todo: implement deployment annotations
|
||||
// todo: implement pod annotations
|
||||
// todo: implement volumes
|
||||
@@ -249,6 +259,7 @@ data:
|
||||
Image: image,
|
||||
ImagePullSecrets: imagePullSecrets,
|
||||
ImageSourceType: imageSourceType,
|
||||
Healthchecks: healthchecks,
|
||||
Namespace: namespace,
|
||||
PrimaryPort: primaryPort,
|
||||
PortMaps: portMaps,
|
||||
|
||||
Reference in New Issue
Block a user