From aa123c413e98c02081fde905ea87a9ad4afebf42 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 2 Dec 2024 13:13:42 +0100 Subject: [PATCH 01/15] feat: Add Healtchchecks to RadixConfig --- .../templates/radixapplication.yaml | 959 ++++++++++++++++++ json-schema/radixapplication.json | 912 +++++++++++++++++ pkg/apis/radix/v1/radixapptypes.go | 29 + pkg/apis/radix/v1/zz_generated.deepcopy.go | 41 + 4 files changed, 1941 insertions(+) diff --git a/charts/radix-operator/templates/radixapplication.yaml b/charts/radix-operator/templates/radixapplication.yaml index 20133fde9..b96bb889b 100644 --- a/charts/radix-operator/templates/radixapplication.yaml +++ b/charts/radix-operator/templates/radixapplication.yaml @@ -421,6 +421,487 @@ spec: minLength: 1 pattern: ^(([a-z0-9][-a-z0-9]*)?[a-z0-9])?$ type: string + healthChecks: + description: HealthChecks can tell Radix if your application + is ready to receive traffic. Defaults to a TCP check + against your public port. + properties: + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + Defaults to TCP Probe against public port (allows traffic when application starts listening for traffic) + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving + a GRPC port. + properties: + port: + description: Port number of the gRPC service. + Number must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the + request. HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + type: object horizontalScaling: description: |- Configuration for automatic horizontal scaling of replicas. @@ -1166,6 +1647,484 @@ spec: x-kubernetes-list-map-keys: - environment x-kubernetes-list-type: map + healthChecks: + description: HealthChecks can tell Radix if your application + is ready to receive traffic. Defaults to a TCP check against + your public port. + properties: + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + Defaults to TCP Probe against public port (allows traffic when application starts listening for traffic) + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + type: object horizontalScaling: description: |- Configuration for automatic horizontal scaling of replicas. diff --git a/json-schema/radixapplication.json b/json-schema/radixapplication.json index c4c1a3ff3..2e1b8141b 100644 --- a/json-schema/radixapplication.json +++ b/json-schema/radixapplication.json @@ -400,6 +400,462 @@ "pattern": "^(([a-z0-9][-a-z0-9]*)?[a-z0-9])?$", "type": "string" }, + "healthChecks": { + "description": "HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port.", + "properties": { + "livenessProbe": { + "description": "Periodic probe of container liveness.\nContainer will be restarted if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "properties": { + "exec": { + "description": "Exec specifies the action to take.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the\ncommand is root ('/') in the container's filesystem. The command is simply exec'd, it is\nnot run inside a shell, so traditional shell instructions ('|', etc) won't work. To use\na shell, you need to explicitly call out to that shell.\nExit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "grpc": { + "description": "GRPC specifies an action involving a GRPC port.", + "properties": { + "port": { + "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "format": "int32", + "type": "integer" + }, + "service": { + "default": "", + "description": "Service is the name of the service to place in the gRPC HealthCheckRequest\n(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "description": "HTTPGet specifies the http request to perform.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set\n\"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name.\nThis will be canonicalized upon output, so case-variant names will be understood as the same header.", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + }, + "scheme": { + "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "description": "TCPSocket specifies an action involving a TCP port.", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "readinessProbe": { + "description": "Periodic probe of container service readiness.\nContainer will be removed from service endpoints if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes\nDefaults to TCP Probe against public port (allows traffic when application starts listening for traffic)", + "properties": { + "exec": { + "description": "Exec specifies the action to take.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the\ncommand is root ('/') in the container's filesystem. The command is simply exec'd, it is\nnot run inside a shell, so traditional shell instructions ('|', etc) won't work. To use\na shell, you need to explicitly call out to that shell.\nExit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "grpc": { + "description": "GRPC specifies an action involving a GRPC port.", + "properties": { + "port": { + "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "format": "int32", + "type": "integer" + }, + "service": { + "default": "", + "description": "Service is the name of the service to place in the gRPC HealthCheckRequest\n(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "description": "HTTPGet specifies the http request to perform.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set\n\"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name.\nThis will be canonicalized upon output, so case-variant names will be understood as the same header.", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + }, + "scheme": { + "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "description": "TCPSocket specifies an action involving a TCP port.", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "startupProbe": { + "description": "StartupProbe indicates that the Pod has successfully initialized.\nIf specified, no other probes are executed until this completes successfully.\nIf this probe fails, the Pod will be restarted, just as if the livenessProbe failed.\nThis can be used to provide different probe parameters at the beginning of a Pod's lifecycle,\nwhen it might take a long time to load data or warm a cache, than during steady-state operation.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "properties": { + "exec": { + "description": "Exec specifies the action to take.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the\ncommand is root ('/') in the container's filesystem. The command is simply exec'd, it is\nnot run inside a shell, so traditional shell instructions ('|', etc) won't work. To use\na shell, you need to explicitly call out to that shell.\nExit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "grpc": { + "description": "GRPC specifies an action involving a GRPC port.", + "properties": { + "port": { + "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "format": "int32", + "type": "integer" + }, + "service": { + "default": "", + "description": "Service is the name of the service to place in the gRPC HealthCheckRequest\n(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "description": "HTTPGet specifies the http request to perform.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set\n\"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name.\nThis will be canonicalized upon output, so case-variant names will be understood as the same header.", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + }, + "scheme": { + "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "description": "TCPSocket specifies an action involving a TCP port.", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, "horizontalScaling": { "description": "Configuration for automatic horizontal scaling of replicas.\nMore info: https://www.radix.equinor.com/references/reference-radix-config/#horizontalscaling", "properties": { @@ -1141,6 +1597,462 @@ ], "x-kubernetes-list-type": "map" }, + "healthChecks": { + "description": "HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port.", + "properties": { + "livenessProbe": { + "description": "Periodic probe of container liveness.\nContainer will be restarted if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "properties": { + "exec": { + "description": "Exec specifies the action to take.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the\ncommand is root ('/') in the container's filesystem. The command is simply exec'd, it is\nnot run inside a shell, so traditional shell instructions ('|', etc) won't work. To use\na shell, you need to explicitly call out to that shell.\nExit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "grpc": { + "description": "GRPC specifies an action involving a GRPC port.", + "properties": { + "port": { + "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "format": "int32", + "type": "integer" + }, + "service": { + "default": "", + "description": "Service is the name of the service to place in the gRPC HealthCheckRequest\n(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "description": "HTTPGet specifies the http request to perform.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set\n\"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name.\nThis will be canonicalized upon output, so case-variant names will be understood as the same header.", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + }, + "scheme": { + "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "description": "TCPSocket specifies an action involving a TCP port.", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "readinessProbe": { + "description": "Periodic probe of container service readiness.\nContainer will be removed from service endpoints if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes\nDefaults to TCP Probe against public port (allows traffic when application starts listening for traffic)", + "properties": { + "exec": { + "description": "Exec specifies the action to take.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the\ncommand is root ('/') in the container's filesystem. The command is simply exec'd, it is\nnot run inside a shell, so traditional shell instructions ('|', etc) won't work. To use\na shell, you need to explicitly call out to that shell.\nExit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "grpc": { + "description": "GRPC specifies an action involving a GRPC port.", + "properties": { + "port": { + "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "format": "int32", + "type": "integer" + }, + "service": { + "default": "", + "description": "Service is the name of the service to place in the gRPC HealthCheckRequest\n(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "description": "HTTPGet specifies the http request to perform.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set\n\"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name.\nThis will be canonicalized upon output, so case-variant names will be understood as the same header.", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + }, + "scheme": { + "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "description": "TCPSocket specifies an action involving a TCP port.", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + }, + "startupProbe": { + "description": "StartupProbe indicates that the Pod has successfully initialized.\nIf specified, no other probes are executed until this completes successfully.\nIf this probe fails, the Pod will be restarted, just as if the livenessProbe failed.\nThis can be used to provide different probe parameters at the beginning of a Pod's lifecycle,\nwhen it might take a long time to load data or warm a cache, than during steady-state operation.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "properties": { + "exec": { + "description": "Exec specifies the action to take.", + "properties": { + "command": { + "description": "Command is the command line to execute inside the container, the working directory for the\ncommand is root ('/') in the container's filesystem. The command is simply exec'd, it is\nnot run inside a shell, so traditional shell instructions ('|', etc) won't work. To use\na shell, you need to explicitly call out to that shell.\nExit status of 0 is treated as live/healthy and non-zero is unhealthy.", + "items": { + "type": "string" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + } + }, + "type": "object" + }, + "failureThreshold": { + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "grpc": { + "description": "GRPC specifies an action involving a GRPC port.", + "properties": { + "port": { + "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "format": "int32", + "type": "integer" + }, + "service": { + "default": "", + "description": "Service is the name of the service to place in the gRPC HealthCheckRequest\n(see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "httpGet": { + "description": "HTTPGet specifies the http request to perform.", + "properties": { + "host": { + "description": "Host name to connect to, defaults to the pod IP. You probably want to set\n\"Host\" in httpHeaders instead.", + "type": "string" + }, + "httpHeaders": { + "description": "Custom headers to set in the request. HTTP allows repeated headers.", + "items": { + "description": "HTTPHeader describes a custom header to be used in HTTP probes", + "properties": { + "name": { + "description": "The header field name.\nThis will be canonicalized upon output, so case-variant names will be understood as the same header.", + "type": "string" + }, + "value": { + "description": "The header field value", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "type": "array", + "x-kubernetes-list-type": "atomic" + }, + "path": { + "description": "Path to access on the HTTP server.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + }, + "scheme": { + "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "type": "string" + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "initialDelaySeconds": { + "description": "Number of seconds after the container has started before liveness probes are initiated.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + }, + "periodSeconds": { + "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "successThreshold": { + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "format": "int32", + "type": "integer" + }, + "tcpSocket": { + "description": "TCPSocket specifies an action involving a TCP port.", + "properties": { + "host": { + "description": "Optional: Host name to connect to, defaults to the pod IP.", + "type": "string" + }, + "port": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "string" + } + ], + "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", + "x-kubernetes-int-or-string": true + } + }, + "required": [ + "port" + ], + "type": "object" + }, + "terminationGracePeriodSeconds": { + "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + "format": "int64", + "type": "integer" + }, + "timeoutSeconds": { + "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "format": "int32", + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, "horizontalScaling": { "description": "Configuration for automatic horizontal scaling of replicas.\nMore info: https://www.radix.equinor.com/references/reference-radix-config/#horizontalscaling", "properties": { diff --git a/pkg/apis/radix/v1/radixapptypes.go b/pkg/apis/radix/v1/radixapptypes.go index 9dc5f4604..2f2682293 100644 --- a/pkg/apis/radix/v1/radixapptypes.go +++ b/pkg/apis/radix/v1/radixapptypes.go @@ -4,6 +4,7 @@ import ( "strings" commonUtils "github.com/equinor/radix-common/utils" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -346,6 +347,9 @@ type RadixComponent struct { // +optional DockerfileName string `json:"dockerfileName,omitempty"` + // HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port. + HealthChecks *RadixHealthChecks `json:"healthChecks,omitempty"` + // Name of an existing container image to use when running the component. // More info: https://www.radix.equinor.com/references/reference-radix-config/#image // +optional @@ -489,6 +493,9 @@ type RadixEnvironmentConfig struct { // +optional Image string `json:"image,omitempty"` + // HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port. + HealthChecks *RadixHealthChecks `json:"healthChecks,omitempty"` + // Number of desired replicas. // More info: https://www.radix.equinor.com/references/reference-radix-config/#replicas // +kubebuilder:validation:Minimum=0 @@ -805,6 +812,28 @@ type RadixJobComponentPayload struct { Path string `json:"path"` } +type RadixHealthChecks struct { + // Periodic probe of container liveness. + // Container will be restarted if the probe fails. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + LivenessProbe *v1.Probe `json:"livenessProbe,omitempty"` + // Periodic probe of container service readiness. + // Container will be removed from service endpoints if the probe fails. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // Defaults to TCP Probe against public port (allows traffic when application starts listening for traffic) + // +optional + ReadinessProbe *v1.Probe `json:"readinessProbe,omitempty"` + // StartupProbe indicates that the Pod has successfully initialized. + // If specified, no other probes are executed until this completes successfully. + // If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + // This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + // when it might take a long time to load data or warm a cache, than during steady-state operation. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + StartupProbe *v1.Probe `json:"startupProbe,omitempty"` +} + // PrivateImageHubEntries defines authentication information for private image registries. type PrivateImageHubEntries map[string]*RadixPrivateImageHubCredential diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index fa71c6a19..835a643a5 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -1367,6 +1367,11 @@ func (in *RadixBuildSpec) DeepCopy() *RadixBuildSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixComponent) DeepCopyInto(out *RadixComponent) { *out = *in + if in.HealthChecks != nil { + in, out := &in.HealthChecks, &out.HealthChecks + *out = new(RadixHealthChecks) + (*in).DeepCopyInto(*out) + } if in.Ports != nil { in, out := &in.Ports, &out.Ports *out = make([]ComponentPort, len(*in)) @@ -2008,6 +2013,11 @@ func (in *RadixEnvironment) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixEnvironmentConfig) DeepCopyInto(out *RadixEnvironmentConfig) { *out = *in + if in.HealthChecks != nil { + in, out := &in.HealthChecks, &out.HealthChecks + *out = new(RadixHealthChecks) + (*in).DeepCopyInto(*out) + } if in.Replicas != nil { in, out := &in.Replicas, &out.Replicas *out = new(int) @@ -2159,6 +2169,37 @@ func (in *RadixEnvironmentStatus) DeepCopy() *RadixEnvironmentStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixHealthChecks) DeepCopyInto(out *RadixHealthChecks) { + *out = *in + if in.LivenessProbe != nil { + in, out := &in.LivenessProbe, &out.LivenessProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } + if in.ReadinessProbe != nil { + in, out := &in.ReadinessProbe, &out.ReadinessProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } + if in.StartupProbe != nil { + in, out := &in.StartupProbe, &out.StartupProbe + *out = new(corev1.Probe) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixHealthChecks. +func (in *RadixHealthChecks) DeepCopy() *RadixHealthChecks { + if in == nil { + return nil + } + out := new(RadixHealthChecks) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixHorizontalScaling) DeepCopyInto(out *RadixHorizontalScaling) { *out = *in From 73d8c6ce5032820f49c3965e25d508d8dfa5dbb2 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 2 Dec 2024 14:50:15 +0100 Subject: [PATCH 02/15] sync RadixDeployment to KubeDeployment with Healtchchecks --- .../templates/radixapplication.yaml | 18 +- .../templates/radixdeployment.yaml | 475 ++++++++++++++++++ json-schema/radixapplication.json | 8 +- pkg/apis/deployment/jobschedulercomponent.go | 4 + pkg/apis/deployment/kubedeployment.go | 16 +- pkg/apis/deployment/kubedeployment_test.go | 59 ++- pkg/apis/radix/v1/radixapptypes.go | 10 +- pkg/apis/radix/v1/radixdeploytypes.go | 19 + pkg/apis/radix/v1/zz_generated.deepcopy.go | 5 + pkg/apis/utils/deploymentcomponent_builder.go | 13 + 10 files changed, 605 insertions(+), 22 deletions(-) diff --git a/charts/radix-operator/templates/radixapplication.yaml b/charts/radix-operator/templates/radixapplication.yaml index b96bb889b..66424cebd 100644 --- a/charts/radix-operator/templates/radixapplication.yaml +++ b/charts/radix-operator/templates/radixapplication.yaml @@ -422,9 +422,10 @@ spec: pattern: ^(([a-z0-9][-a-z0-9]*)?[a-z0-9])?$ type: string healthChecks: - description: HealthChecks can tell Radix if your application - is ready to receive traffic. Defaults to a TCP check - against your public port. + description: |- + HealthChecks can tell Radix if your application is ready to receive traffic. + Defaults to a TCP check against your first listed port. + If any healthchecks are defined, no defaults will be added and you should add your own readinessProbe. properties: livenessProbe: description: |- @@ -588,7 +589,7 @@ spec: Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - Defaults to TCP Probe against public port (allows traffic when application starts listening for traffic) + Defaults to TCP Probe against the first listed port properties: exec: description: Exec specifies the action to take. @@ -1648,9 +1649,10 @@ spec: - environment x-kubernetes-list-type: map healthChecks: - description: HealthChecks can tell Radix if your application - is ready to receive traffic. Defaults to a TCP check against - your public port. + description: |- + HealthChecks can tell Radix if your application is ready to receive traffic. + Defaults to a TCP check against your first listed port. + If any healthchecks are defined, no defaults will be added and you should add your own readinessProbe. properties: livenessProbe: description: |- @@ -1813,7 +1815,7 @@ spec: Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - Defaults to TCP Probe against public port (allows traffic when application starts listening for traffic) + Defaults to TCP Probe against the first listed port properties: exec: description: Exec specifies the action to take. diff --git a/charts/radix-operator/templates/radixdeployment.yaml b/charts/radix-operator/templates/radixdeployment.yaml index 838678a13..8824468c8 100644 --- a/charts/radix-operator/templates/radixdeployment.yaml +++ b/charts/radix-operator/templates/radixdeployment.yaml @@ -241,6 +241,481 @@ spec: - fqdn type: object type: array + healthChecks: + properties: + livenessProbe: + description: |- + Periodic probe of container liveness. + Container will be restarted if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + readinessProbe: + description: |- + Periodic probe of container service readiness. + Container will be removed from service endpoints if the probe fails. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + Defaults to TCP Probe against the first listed port + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + startupProbe: + description: |- + StartupProbe indicates that the Pod has successfully initialized. + If specified, no other probes are executed until this completes successfully. + If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + when it might take a long time to load data or warm a cache, than during steady-state operation. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: |- + Command is the command line to execute inside the container, the working directory for the + command is root ('/') in the container's filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + a shell, you need to explicitly call out to that shell. + Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + failureThreshold: + description: |- + Minimum consecutive failures for the probe to be considered failed after having succeeded. + Defaults to 3. Minimum value is 1. + format: int32 + type: integer + grpc: + description: GRPC specifies an action involving a GRPC + port. + properties: + port: + description: Port number of the gRPC service. Number + must be in the range 1 to 65535. + format: int32 + type: integer + service: + default: "" + description: |- + Service is the name of the service to place in the gRPC HealthCheckRequest + (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + + If this is not specified, the default behavior is defined by gRPC. + type: string + required: + - port + type: object + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: |- + Host name to connect to, defaults to the pod IP. You probably want to set + "Host" in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: |- + The header field name. + This will be canonicalized upon output, so case-variant names will be understood as the same header. + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Name or number of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: |- + Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: |- + Number of seconds after the container has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + periodSeconds: + description: |- + How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: |- + Minimum consecutive successes for the probe to be considered successful after having failed. + Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + format: int32 + type: integer + tcpSocket: + description: TCPSocket specifies an action involving + a TCP port. + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: |- + Number or name of the port to access on the container. + Number must be in the range 1 to 65535. + Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + terminationGracePeriodSeconds: + description: |- + Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + The grace period is the duration in seconds after the processes running in the pod are sent + a termination signal and the time when the processes are forcibly halted with a kill signal. + Set this value longer than the expected cleanup time for your process. + If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + value overrides the value provided by the pod spec. + Value must be non-negative integer. The value zero indicates stop immediately via + the kill signal (no opportunity to shut down). + This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. + format: int64 + type: integer + timeoutSeconds: + description: |- + Number of seconds after which the probe times out. + Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + format: int32 + type: integer + type: object + type: object horizontalScaling: description: RadixHorizontalScaling defines configuration for horizontal pod autoscaler. diff --git a/json-schema/radixapplication.json b/json-schema/radixapplication.json index 2e1b8141b..fc8e5cc80 100644 --- a/json-schema/radixapplication.json +++ b/json-schema/radixapplication.json @@ -401,7 +401,7 @@ "type": "string" }, "healthChecks": { - "description": "HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port.", + "description": "HealthChecks can tell Radix if your application is ready to receive traffic.\nDefaults to a TCP check against your first listed port.\nIf any healthchecks are defined, no defaults will be added and you should add your own readinessProbe.", "properties": { "livenessProbe": { "description": "Periodic probe of container liveness.\nContainer will be restarted if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", @@ -554,7 +554,7 @@ "type": "object" }, "readinessProbe": { - "description": "Periodic probe of container service readiness.\nContainer will be removed from service endpoints if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes\nDefaults to TCP Probe against public port (allows traffic when application starts listening for traffic)", + "description": "Periodic probe of container service readiness.\nContainer will be removed from service endpoints if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes\nDefaults to TCP Probe against the first listed port", "properties": { "exec": { "description": "Exec specifies the action to take.", @@ -1598,7 +1598,7 @@ "x-kubernetes-list-type": "map" }, "healthChecks": { - "description": "HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port.", + "description": "HealthChecks can tell Radix if your application is ready to receive traffic.\nDefaults to a TCP check against your first listed port.\nIf any healthchecks are defined, no defaults will be added and you should add your own readinessProbe.", "properties": { "livenessProbe": { "description": "Periodic probe of container liveness.\nContainer will be restarted if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", @@ -1751,7 +1751,7 @@ "type": "object" }, "readinessProbe": { - "description": "Periodic probe of container service readiness.\nContainer will be removed from service endpoints if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes\nDefaults to TCP Probe against public port (allows traffic when application starts listening for traffic)", + "description": "Periodic probe of container service readiness.\nContainer will be removed from service endpoints if the probe fails.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes\nDefaults to TCP Probe against the first listed port", "properties": { "exec": { "description": "Exec specifies the action to take.", diff --git a/pkg/apis/deployment/jobschedulercomponent.go b/pkg/apis/deployment/jobschedulercomponent.go index bd705c50c..514185110 100644 --- a/pkg/apis/deployment/jobschedulercomponent.go +++ b/pkg/apis/deployment/jobschedulercomponent.go @@ -21,6 +21,10 @@ func newJobSchedulerComponent(jobComponent *radixv1.RadixDeployJobComponent, rd } } +func (js *jobSchedulerComponent) GetHealthChecks() *radixv1.RadixHealthChecks { + return nil +} + func (js *jobSchedulerComponent) GetImage() string { containerRegistry := os.Getenv(defaults.ContainerRegistryEnvironmentVariable) radixJobScheduler := os.Getenv(defaults.OperatorRadixJobSchedulerEnvironmentVariable) diff --git a/pkg/apis/deployment/kubedeployment.go b/pkg/apis/deployment/kubedeployment.go index d7084319c..cefd6420c 100644 --- a/pkg/apis/deployment/kubedeployment.go +++ b/pkg/apis/deployment/kubedeployment.go @@ -315,11 +315,17 @@ func (deploy *Deployment) setDesiredDeploymentProperties(ctx context.Context, de } desiredDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts - readinessProbe, err := getReadinessProbeForComponent(deployComponent) - if err != nil { - return err + if hc := deployComponent.GetHealthChecks(); hc != nil { + desiredDeployment.Spec.Template.Spec.Containers[0].ReadinessProbe = hc.ReadinessProbe + desiredDeployment.Spec.Template.Spec.Containers[0].LivenessProbe = hc.LivenessProbe + desiredDeployment.Spec.Template.Spec.Containers[0].StartupProbe = hc.StartupProbe + } else { + readinessProbe, err := getDefaultReadinessProbeForComponent(deployComponent) + if err != nil { + return err + } + desiredDeployment.Spec.Template.Spec.Containers[0].ReadinessProbe = readinessProbe } - desiredDeployment.Spec.Template.Spec.Containers[0].ReadinessProbe = readinessProbe environmentVariables, err := GetEnvironmentVariablesForRadixOperator(ctx, deploy.kubeutil, appName, deploy.radixDeployment, deployComponent) if err != nil { @@ -449,7 +455,7 @@ func (deploy *Deployment) isEligibleForGarbageCollectComponent(componentName Rad return componentType != commonComponent.GetType() } -func getReadinessProbeForComponent(component v1.RadixCommonDeployComponent) (*corev1.Probe, error) { +func getDefaultReadinessProbeForComponent(component v1.RadixCommonDeployComponent) (*corev1.Probe, error) { if len(component.GetPorts()) == 0 { return nil, nil } diff --git a/pkg/apis/deployment/kubedeployment_test.go b/pkg/apis/deployment/kubedeployment_test.go index 202b0aa85..df9bcf294 100644 --- a/pkg/apis/deployment/kubedeployment_test.go +++ b/pkg/apis/deployment/kubedeployment_test.go @@ -11,7 +11,10 @@ import ( "github.com/equinor/radix-operator/pkg/apis/test" "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) func teardownReadinessProbe() { @@ -22,7 +25,7 @@ func teardownReadinessProbe() { func TestGetReadinessProbe_MissingDefaultEnvVars(t *testing.T) { teardownReadinessProbe() - probe, err := getReadinessProbeForComponent(&v1.RadixDeployComponent{Ports: []v1.ComponentPort{{Name: "http", Port: int32(80)}}}) + probe, err := getDefaultReadinessProbeForComponent(&v1.RadixDeployComponent{Ports: []v1.ComponentPort{{Name: "http", Port: int32(80)}}}) assert.Error(t, err) assert.Nil(t, probe) } @@ -30,7 +33,7 @@ func TestGetReadinessProbe_MissingDefaultEnvVars(t *testing.T) { func TestGetReadinessProbe_Custom(t *testing.T) { test.SetRequiredEnvironmentVariables() - probe, err := getReadinessProbeForComponent(&v1.RadixDeployComponent{Ports: []v1.ComponentPort{{Name: "http", Port: int32(5000)}}}) + probe, err := getDefaultReadinessProbeForComponent(&v1.RadixDeployComponent{Ports: []v1.ComponentPort{{Name: "http", Port: int32(5000)}}}) assert.Nil(t, err) assert.Equal(t, int32(5), probe.InitialDelaySeconds) @@ -39,6 +42,58 @@ func TestGetReadinessProbe_Custom(t *testing.T) { teardownReadinessProbe() } +func TestComponentWithoutCustomHealthChecks(t *testing.T) { + tu, client, kubeUtil, radixclient, kedaClient, prometheusclient, _, certClient := SetupTest(t) + rd, _ := ApplyDeploymentWithSync(tu, client, kubeUtil, radixclient, kedaClient, prometheusclient, certClient, + utils.ARadixDeployment(). + WithComponents(utils.NewDeployComponentBuilder(). + WithName("comp1")). + WithAppName("any-app"). + WithEnvironment("test")) + + component := rd.GetComponentByName("comp1") + assert.Nil(t, component.HealthChecks) +} +func TestComponentWithCustomHealthChecks(t *testing.T) { + tu, client, kubeUtil, radixclient, kedaClient, prometheusclient, _, certClient := SetupTest(t) + createProbe := func(handler corev1.ProbeHandler, seconds int32) *corev1.Probe { + return &corev1.Probe{ + ProbeHandler: handler, + InitialDelaySeconds: seconds, + TimeoutSeconds: seconds + 1, + PeriodSeconds: seconds + 2, + SuccessThreshold: seconds + 3, + FailureThreshold: seconds + 4, + TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), + } + } + + readynessProbe := createProbe(corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.IntOrString{IntVal: 5000}, + }}, 10) + + livenessProbe := createProbe(corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.IntOrString{IntVal: 5000}, + }}, 20) + startuProbe := createProbe(corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"echo", "hello"}, + }}, 30) + + rd, _ := ApplyDeploymentWithSync(tu, client, kubeUtil, radixclient, kedaClient, prometheusclient, certClient, + utils.ARadixDeployment(). + WithComponents(utils.NewDeployComponentBuilder(). + WithName("comp1"). + WithHealthChecks(startuProbe, readynessProbe, livenessProbe)). + WithAppName("any-app"). + WithEnvironment("test")) + + require.NotNil(t, readynessProbe.ProbeHandler.HTTPGet) + component := rd.GetComponentByName("comp1") + require.NotNil(t, component.HealthChecks) + assert.Equal(t, readynessProbe, component.HealthChecks.ReadinessProbe) + assert.Equal(t, livenessProbe, component.HealthChecks.LivenessProbe) + assert.Equal(t, startuProbe, component.HealthChecks.StartupProbe) +} func Test_UpdateResourcesInDeployment(t *testing.T) { origRequests := map[string]string{"cpu": "10m", "memory": "100M"} diff --git a/pkg/apis/radix/v1/radixapptypes.go b/pkg/apis/radix/v1/radixapptypes.go index 2f2682293..db8b4ecc2 100644 --- a/pkg/apis/radix/v1/radixapptypes.go +++ b/pkg/apis/radix/v1/radixapptypes.go @@ -347,7 +347,9 @@ type RadixComponent struct { // +optional DockerfileName string `json:"dockerfileName,omitempty"` - // HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port. + // HealthChecks can tell Radix if your application is ready to receive traffic. + // Defaults to a TCP check against your first listed port. + // If any healthchecks are defined, no defaults will be added and you should add your own readinessProbe. HealthChecks *RadixHealthChecks `json:"healthChecks,omitempty"` // Name of an existing container image to use when running the component. @@ -493,7 +495,9 @@ type RadixEnvironmentConfig struct { // +optional Image string `json:"image,omitempty"` - // HealthChecks can tell Radix if your application is ready to receive traffic. Defaults to a TCP check against your public port. + // HealthChecks can tell Radix if your application is ready to receive traffic. + // Defaults to a TCP check against your first listed port. + // If any healthchecks are defined, no defaults will be added and you should add your own readinessProbe. HealthChecks *RadixHealthChecks `json:"healthChecks,omitempty"` // Number of desired replicas. @@ -821,7 +825,7 @@ type RadixHealthChecks struct { // Periodic probe of container service readiness. // Container will be removed from service endpoints if the probe fails. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // Defaults to TCP Probe against public port (allows traffic when application starts listening for traffic) + // Defaults to TCP Probe against the first listed port // +optional ReadinessProbe *v1.Probe `json:"readinessProbe,omitempty"` // StartupProbe indicates that the Pod has successfully initialized. diff --git a/pkg/apis/radix/v1/radixdeploytypes.go b/pkg/apis/radix/v1/radixdeploytypes.go index 12e134626..ba9d0a4dd 100644 --- a/pkg/apis/radix/v1/radixdeploytypes.go +++ b/pkg/apis/radix/v1/radixdeploytypes.go @@ -128,6 +128,7 @@ type RadixDeployComponent struct { ExternalDNS []RadixDeployExternalDNS `json:"externalDNS,omitempty"` // Deprecated: For backward compatibility we must still support this field. New code should use ExternalDNS instead. DNSExternalAlias []string `json:"dnsExternalAlias,omitempty"` + HealthChecks *RadixHealthChecks `json:"healthChecks,omitempty"` Monitoring bool `json:"monitoring"` MonitoringConfig MonitoringConfig `json:"monitoringConfig,omitempty"` Resources ResourceRequirements `json:"resources,omitempty"` @@ -142,6 +143,19 @@ type RadixDeployComponent struct { Network *Network `json:"network,omitempty"` } +func (deployComponent *RadixDeployComponent) GetHealthChecks() *RadixHealthChecks { + if deployComponent.HealthChecks == nil { + return nil + } + if deployComponent.HealthChecks.ReadinessProbe == nil && + deployComponent.HealthChecks.LivenessProbe == nil && + deployComponent.HealthChecks.StartupProbe == nil { + return nil + } + + return deployComponent.HealthChecks +} + func (deployComponent *RadixDeployComponent) GetName() string { return deployComponent.Name } @@ -425,6 +439,10 @@ type RadixDeployJobComponent struct { BatchStatusRules []BatchStatusRule `json:"batchStatusRules,omitempty"` } +func (r *RadixDeployJobComponent) GetHealthChecks() *RadixHealthChecks { + return nil +} + type RadixComponentType string const ( @@ -463,6 +481,7 @@ type RadixCommonDeployComponent interface { GetReadOnlyFileSystem() *bool GetRuntime() *Runtime GetNetwork() *Network + GetHealthChecks() *RadixHealthChecks } // RadixCommonDeployComponentFactory defines a common component factory diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index 835a643a5..46f5fc750 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -1613,6 +1613,11 @@ func (in *RadixDeployComponent) DeepCopyInto(out *RadixDeployComponent) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.HealthChecks != nil { + in, out := &in.HealthChecks, &out.HealthChecks + *out = new(RadixHealthChecks) + (*in).DeepCopyInto(*out) + } out.MonitoringConfig = in.MonitoringConfig in.Resources.DeepCopyInto(&out.Resources) if in.HorizontalScaling != nil { diff --git a/pkg/apis/utils/deploymentcomponent_builder.go b/pkg/apis/utils/deploymentcomponent_builder.go index 38b88eb73..c77728952 100644 --- a/pkg/apis/utils/deploymentcomponent_builder.go +++ b/pkg/apis/utils/deploymentcomponent_builder.go @@ -2,6 +2,7 @@ package utils import ( v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + corev1 "k8s.io/api/core/v1" ) // DeployComponentBuilder Handles construction of RD component @@ -23,6 +24,7 @@ type DeployComponentBuilder interface { WithResourceRequestsOnly(map[string]string) DeployComponentBuilder WithResource(map[string]string, map[string]string) DeployComponentBuilder WithVolumeMounts(...v1.RadixVolumeMount) DeployComponentBuilder + WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) DeployComponentBuilder WithNodeGpu(gpu string) DeployComponentBuilder WithNodeGpuCount(gpuCount string) DeployComponentBuilder WithIngressConfiguration(...string) DeployComponentBuilder @@ -70,6 +72,7 @@ type deployComponentBuilder struct { identity *v1.Identity readOnlyFileSystem *bool runtime *v1.Runtime + healtChecks *v1.RadixHealthChecks } func (dcb *deployComponentBuilder) WithVolumeMounts(volumeMounts ...v1.RadixVolumeMount) DeployComponentBuilder { @@ -77,6 +80,15 @@ func (dcb *deployComponentBuilder) WithVolumeMounts(volumeMounts ...v1.RadixVolu return dcb } +func (dcb *deployComponentBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) DeployComponentBuilder { + dcb.healtChecks = &v1.RadixHealthChecks{ + LivenessProbe: livenessProbe, + ReadinessProbe: readynessProbe, + StartupProbe: startupProbe, + } + return dcb +} + func (dcb *deployComponentBuilder) WithNodeGpu(gpu string) DeployComponentBuilder { dcb.node.Gpu = gpu return dcb @@ -257,6 +269,7 @@ func (dcb *deployComponentBuilder) BuildComponent() v1.RadixDeployComponent { DNSExternalAlias: dcb.externalAppAlias, ExternalDNS: dcb.externalDNS, Resources: dcb.resources, + HealthChecks: dcb.healtChecks, HorizontalScaling: dcb.horizontalScaling, VolumeMounts: dcb.volumeMounts, AlwaysPullImageOnDeploy: dcb.alwaysPullImageOnDeploy, From 1e58644320e2d44b4aba70637aa79fb1d1a52fa9 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Mon, 2 Dec 2024 16:03:13 +0100 Subject: [PATCH 03/15] sync RadixApplication to RadixDeployment with Healtchchecks --- pkg/apis/deployment/kubedeployment_test.go | 1 - pkg/apis/deployment/radixcomponent.go | 24 +++++ pkg/apis/deployment/radixcomponent_test.go | 93 +++++++++++++++++++ .../utils/applicationcomponent_builder.go | 13 +++ .../utils/componentenvironment_builder.go | 12 +++ 5 files changed, 142 insertions(+), 1 deletion(-) diff --git a/pkg/apis/deployment/kubedeployment_test.go b/pkg/apis/deployment/kubedeployment_test.go index df9bcf294..842bd474b 100644 --- a/pkg/apis/deployment/kubedeployment_test.go +++ b/pkg/apis/deployment/kubedeployment_test.go @@ -87,7 +87,6 @@ func TestComponentWithCustomHealthChecks(t *testing.T) { WithAppName("any-app"). WithEnvironment("test")) - require.NotNil(t, readynessProbe.ProbeHandler.HTTPGet) component := rd.GetComponentByName("comp1") require.NotNil(t, component.HealthChecks) assert.Equal(t, readynessProbe, component.HealthChecks.ReadinessProbe) diff --git a/pkg/apis/deployment/radixcomponent.go b/pkg/apis/deployment/radixcomponent.go index a9e5e9213..70b4531b0 100644 --- a/pkg/apis/deployment/radixcomponent.go +++ b/pkg/apis/deployment/radixcomponent.go @@ -74,6 +74,7 @@ func GetRadixComponentsForEnv(ctx context.Context, radixApplication *radixv1.Rad deployComponent.AlwaysPullImageOnDeploy = getRadixComponentAlwaysPullImageOnDeployFlag(&radixComponent, environmentSpecificConfig) deployComponent.ExternalDNS = getExternalDNSAliasForComponentEnvironment(radixApplication, componentName, env) deployComponent.SecretRefs = getRadixCommonComponentRadixSecretRefs(&radixComponent, environmentSpecificConfig) + deployComponent.HealthChecks = getRadixCommonComponentHealthChecks(&radixComponent, environmentSpecificConfig) deployComponent.PublicPort = getRadixComponentPort(&radixComponent) deployComponent.Authentication = auth deployComponent.Identity = identity @@ -93,6 +94,29 @@ func GetRadixComponentsForEnv(ctx context.Context, radixApplication *radixv1.Rad return deployComponents, nil } +func getRadixCommonComponentHealthChecks(r *radixv1.RadixComponent, config *radixv1.RadixEnvironmentConfig) *radixv1.RadixHealthChecks { + if r.HealthChecks == nil && (config == nil || config.HealthChecks == nil) { + return nil + } + hc := r.HealthChecks.DeepCopy() + + if config == nil || config.HealthChecks == nil { + return hc + } + + if config.HealthChecks.ReadinessProbe != nil { + hc.ReadinessProbe = config.HealthChecks.ReadinessProbe.DeepCopy() + } + if config.HealthChecks.LivenessProbe != nil { + hc.LivenessProbe = config.HealthChecks.LivenessProbe.DeepCopy() + } + if config.HealthChecks.StartupProbe != nil { + hc.StartupProbe = config.HealthChecks.StartupProbe.DeepCopy() + } + + return hc +} + func getRadixComponentNetwork(component *radixv1.RadixComponent, environmentConfig *radixv1.RadixEnvironmentConfig) (*radixv1.Network, error) { var dst *radixv1.Network if component.Network != nil { diff --git a/pkg/apis/deployment/radixcomponent_test.go b/pkg/apis/deployment/radixcomponent_test.go index dc5157b41..6f107eff9 100644 --- a/pkg/apis/deployment/radixcomponent_test.go +++ b/pkg/apis/deployment/radixcomponent_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) type scenarioDef struct { @@ -1048,6 +1049,98 @@ func Test_GetRadixComponents_Monitoring(t *testing.T) { } } +func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { + createProbe := func(handler corev1.ProbeHandler, seconds int32) *corev1.Probe { + return &corev1.Probe{ + ProbeHandler: handler, + InitialDelaySeconds: seconds, + TimeoutSeconds: seconds + 1, + PeriodSeconds: seconds + 2, + SuccessThreshold: seconds + 3, + FailureThreshold: seconds + 4, + TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), + } + } + + httpProbe := corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{Port: intstr.FromInt32(5000), Path: "/healthz", Scheme: "http"}} + execProbe := corev1.ProbeHandler{Exec: &corev1.ExecAction{Command: []string{"/bin/sh", "-c", "/healthz /healthz"}}} + tcpProbe := corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{Port: intstr.FromInt32(8000)}} + + testCases := []struct { + description string + healthChecks *radixv1.RadixHealthChecks + envHealthChecks *radixv1.RadixHealthChecks + + expectedHealthChecks *radixv1.RadixHealthChecks + }{ + {"No configuration set results in default readieness probe", nil, nil, nil}, + { + "component has healthchecks, no env config", + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + nil, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + }, + { + "Env healthchecks, no component healthchecks", + nil, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + }, + { + "Env healthchecks, component healthchecks, env overrides comp", + &radixv1.RadixHealthChecks{createProbe(execProbe, 1), createProbe(httpProbe, 10), createProbe(tcpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + }, + { + "Env healthchecks, component healthchecks, env merges comp", + &radixv1.RadixHealthChecks{nil, createProbe(httpProbe, 10), createProbe(tcpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), nil}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(tcpProbe, 20)}, + }, + } + + readynessProbe := createProbe(corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ + Port: intstr.IntOrString{IntVal: 5000}, + }}, 10) + + livenessProbe := createProbe(corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.IntOrString{IntVal: 5000}, + }}, 20) + startuProbe := createProbe(corev1.ProbeHandler{Exec: &corev1.ExecAction{ + Command: []string{"echo", "hello"}, + }}, 30) + + for _, testCase := range testCases { + t.Run(testCase.description, func(t *testing.T) { + raBuilder := utils.ARadixApplication(). + WithComponents( + utils.NewApplicationComponentBuilder(). + WithName("comp"). + WithHealthChecks(startuProbe, readynessProbe, livenessProbe). + WithEnvironmentConfig( + utils.NewComponentEnvironmentBuilder().WithEnvironment("dev"). + WithHealthChecks(startuProbe, readynessProbe, livenessProbe))) + ra := raBuilder.BuildRA() + + deployComponents, err := GetRadixComponentsForEnv(context.Background(), ra, nil, "dev", make(pipeline.DeployComponentImages), make(radixv1.EnvVarsMap), nil) + require.NoError(t, err) + require.Len(t, deployComponents, 1) + + if testCase.healthChecks == nil { + assert.Nil(t, deployComponents[0].HealthChecks) + } else { + require.NotNil(t, deployComponents[0].HealthChecks) + assert.Equal(t, testCase.expectedHealthChecks.ReadinessProbe, deployComponents[0].HealthChecks.ReadinessProbe) + assert.Equal(t, testCase.expectedHealthChecks.LivenessProbe, deployComponents[0].HealthChecks.LivenessProbe) + assert.Equal(t, testCase.expectedHealthChecks.StartupProbe, deployComponents[0].HealthChecks.StartupProbe) + } + + }) + } + +} + func Test_GetRadixComponents_ReplicasOverride(t *testing.T) { componentName := "comp" env := "dev" diff --git a/pkg/apis/utils/applicationcomponent_builder.go b/pkg/apis/utils/applicationcomponent_builder.go index bc115de30..15aab5609 100644 --- a/pkg/apis/utils/applicationcomponent_builder.go +++ b/pkg/apis/utils/applicationcomponent_builder.go @@ -2,6 +2,7 @@ package utils import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + corev1 "k8s.io/api/core/v1" ) // RadixApplicationComponentBuilder Handles construction of RA component @@ -10,6 +11,7 @@ type RadixApplicationComponentBuilder interface { WithAlwaysPullImageOnDeploy(bool) RadixApplicationComponentBuilder WithSourceFolder(string) RadixApplicationComponentBuilder WithDockerfileName(string) RadixApplicationComponentBuilder + WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixApplicationComponentBuilder WithImage(string) RadixApplicationComponentBuilder WithImageTagName(imageTagName string) RadixApplicationComponentBuilder WithPublic(bool) RadixApplicationComponentBuilder // Deprecated: For backwards compatibility WithPublic is still supported, new code should use WithPublicPort instead @@ -64,6 +66,7 @@ type radixApplicationComponentBuilder struct { horizontalScaling *radixv1.RadixHorizontalScaling runtime *radixv1.Runtime network *radixv1.Network + healtChecks *radixv1.RadixHealthChecks } func (rcb *radixApplicationComponentBuilder) WithName(name string) RadixApplicationComponentBuilder { @@ -76,6 +79,15 @@ func (rcb *radixApplicationComponentBuilder) WithAlwaysPullImageOnDeploy(val boo return rcb } +func (rcb *radixApplicationComponentBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixApplicationComponentBuilder { + rcb.healtChecks = &radixv1.RadixHealthChecks{ + LivenessProbe: livenessProbe, + ReadinessProbe: readynessProbe, + StartupProbe: startupProbe, + } + return rcb +} + func (rcb *radixApplicationComponentBuilder) WithSourceFolder(sourceFolder string) RadixApplicationComponentBuilder { rcb.sourceFolder = sourceFolder return rcb @@ -230,6 +242,7 @@ func (rcb *radixApplicationComponentBuilder) BuildComponent() radixv1.RadixCompo Name: rcb.name, SourceFolder: rcb.sourceFolder, DockerfileName: rcb.dockerfileName, + HealthChecks: rcb.healtChecks, Image: rcb.image, Ports: rcb.ports, Secrets: rcb.secrets, diff --git a/pkg/apis/utils/componentenvironment_builder.go b/pkg/apis/utils/componentenvironment_builder.go index 73bd4a728..989657d13 100644 --- a/pkg/apis/utils/componentenvironment_builder.go +++ b/pkg/apis/utils/componentenvironment_builder.go @@ -2,6 +2,7 @@ package utils import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + corev1 "k8s.io/api/core/v1" ) // RadixEnvironmentConfigBuilder Handles construction of RA component environment @@ -9,6 +10,7 @@ type RadixEnvironmentConfigBuilder interface { WithEnvironment(string) RadixEnvironmentConfigBuilder WithSourceFolder(string) RadixEnvironmentConfigBuilder WithDockerfileName(string) RadixEnvironmentConfigBuilder + WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixEnvironmentConfigBuilder WithImage(string) RadixEnvironmentConfigBuilder WithReplicas(*int) RadixEnvironmentConfigBuilder WithEnvironmentVariable(string, string) RadixEnvironmentConfigBuilder @@ -50,6 +52,7 @@ type radixEnvironmentConfigBuilder struct { readOnlyFileSystem *bool runtime *radixv1.Runtime network *radixv1.Network + healtChecks *radixv1.RadixHealthChecks } func (ceb *radixEnvironmentConfigBuilder) WithHorizontalScaling(scaling *radixv1.RadixHorizontalScaling) RadixEnvironmentConfigBuilder { @@ -65,6 +68,15 @@ func (ceb *radixEnvironmentConfigBuilder) WithResource(request map[string]string return ceb } +func (ceb *radixEnvironmentConfigBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixEnvironmentConfigBuilder { + ceb.healtChecks = &radixv1.RadixHealthChecks{ + LivenessProbe: livenessProbe, + ReadinessProbe: readynessProbe, + StartupProbe: startupProbe, + } + return ceb +} + func (ceb *radixEnvironmentConfigBuilder) WithVolumeMounts(volumeMounts []radixv1.RadixVolumeMount) RadixEnvironmentConfigBuilder { ceb.volumeMounts = volumeMounts return ceb From d6b357a4d22682761e5b3f3cf083fcc2a346b262 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Dec 2024 08:12:58 +0100 Subject: [PATCH 04/15] fix tests --- pkg/apis/deployment/radixcomponent.go | 7 ++- pkg/apis/deployment/radixcomponent_test.go | 46 ++++++++----------- .../utils/componentenvironment_builder.go | 1 + 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/pkg/apis/deployment/radixcomponent.go b/pkg/apis/deployment/radixcomponent.go index 70b4531b0..924655dd6 100644 --- a/pkg/apis/deployment/radixcomponent.go +++ b/pkg/apis/deployment/radixcomponent.go @@ -98,7 +98,12 @@ func getRadixCommonComponentHealthChecks(r *radixv1.RadixComponent, config *radi if r.HealthChecks == nil && (config == nil || config.HealthChecks == nil) { return nil } - hc := r.HealthChecks.DeepCopy() + hc := &radixv1.RadixHealthChecks{} + if r.HealthChecks != nil { + hc.StartupProbe = r.HealthChecks.StartupProbe.DeepCopy() + hc.ReadinessProbe = r.HealthChecks.ReadinessProbe.DeepCopy() + hc.LivenessProbe = r.HealthChecks.LivenessProbe.DeepCopy() + } if config == nil || config.HealthChecks == nil { return hc diff --git a/pkg/apis/deployment/radixcomponent_test.go b/pkg/apis/deployment/radixcomponent_test.go index 6f107eff9..4d8f01eae 100644 --- a/pkg/apis/deployment/radixcomponent_test.go +++ b/pkg/apis/deployment/radixcomponent_test.go @@ -1067,18 +1067,18 @@ func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { tcpProbe := corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{Port: intstr.FromInt32(8000)}} testCases := []struct { - description string - healthChecks *radixv1.RadixHealthChecks - envHealthChecks *radixv1.RadixHealthChecks + description string + compHealthChecks *radixv1.RadixHealthChecks + envHealthChecks *radixv1.RadixHealthChecks expectedHealthChecks *radixv1.RadixHealthChecks }{ {"No configuration set results in default readieness probe", nil, nil, nil}, { "component has healthchecks, no env config", - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 30), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, nil, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 30), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, }, { "Env healthchecks, no component healthchecks", @@ -1088,9 +1088,9 @@ func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { }, { "Env healthchecks, component healthchecks, env overrides comp", - &radixv1.RadixHealthChecks{createProbe(execProbe, 1), createProbe(httpProbe, 10), createProbe(tcpProbe, 20)}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(execProbe, 30), createProbe(httpProbe, 10), createProbe(tcpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 40), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 40), createProbe(httpProbe, 20)}, }, { "Env healthchecks, component healthchecks, env merges comp", @@ -1100,34 +1100,24 @@ func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { }, } - readynessProbe := createProbe(corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.IntOrString{IntVal: 5000}, - }}, 10) - - livenessProbe := createProbe(corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.IntOrString{IntVal: 5000}, - }}, 20) - startuProbe := createProbe(corev1.ProbeHandler{Exec: &corev1.ExecAction{ - Command: []string{"echo", "hello"}, - }}, 30) - for _, testCase := range testCases { t.Run(testCase.description, func(t *testing.T) { - raBuilder := utils.ARadixApplication(). - WithComponents( - utils.NewApplicationComponentBuilder(). - WithName("comp"). - WithHealthChecks(startuProbe, readynessProbe, livenessProbe). - WithEnvironmentConfig( - utils.NewComponentEnvironmentBuilder().WithEnvironment("dev"). - WithHealthChecks(startuProbe, readynessProbe, livenessProbe))) + envConfig := utils.NewComponentEnvironmentBuilder().WithEnvironment("dev") + if testCase.envHealthChecks != nil { + envConfig.WithHealthChecks(testCase.envHealthChecks.StartupProbe, testCase.envHealthChecks.ReadinessProbe, testCase.envHealthChecks.LivenessProbe) + } + compConfig := utils.NewApplicationComponentBuilder().WithName("comp").WithEnvironmentConfig(envConfig) + if testCase.compHealthChecks != nil { + compConfig.WithHealthChecks(testCase.compHealthChecks.StartupProbe, testCase.compHealthChecks.ReadinessProbe, testCase.compHealthChecks.LivenessProbe) + } + raBuilder := utils.ARadixApplication().WithComponents(compConfig) ra := raBuilder.BuildRA() deployComponents, err := GetRadixComponentsForEnv(context.Background(), ra, nil, "dev", make(pipeline.DeployComponentImages), make(radixv1.EnvVarsMap), nil) require.NoError(t, err) require.Len(t, deployComponents, 1) - if testCase.healthChecks == nil { + if testCase.expectedHealthChecks == nil { assert.Nil(t, deployComponents[0].HealthChecks) } else { require.NotNil(t, deployComponents[0].HealthChecks) diff --git a/pkg/apis/utils/componentenvironment_builder.go b/pkg/apis/utils/componentenvironment_builder.go index 989657d13..14e0c7f22 100644 --- a/pkg/apis/utils/componentenvironment_builder.go +++ b/pkg/apis/utils/componentenvironment_builder.go @@ -176,6 +176,7 @@ func (ceb *radixEnvironmentConfigBuilder) BuildEnvironmentConfig() radixv1.Radix Environment: ceb.environment, SourceFolder: ceb.sourceFolder, DockerfileName: ceb.dockerfileName, + HealthChecks: ceb.healtChecks, Image: ceb.image, Variables: ceb.variables, Replicas: ceb.replicas, From 44966eae29345c25e0ab4297c1ead4e1efee4ada Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Dec 2024 13:41:42 +0100 Subject: [PATCH 05/15] fix linting --- pkg/apis/deployment/radixcomponent_test.go | 23 +++++++++++----------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/apis/deployment/radixcomponent_test.go b/pkg/apis/deployment/radixcomponent_test.go index 4d8f01eae..bc424fbb0 100644 --- a/pkg/apis/deployment/radixcomponent_test.go +++ b/pkg/apis/deployment/radixcomponent_test.go @@ -1075,28 +1075,27 @@ func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { }{ {"No configuration set results in default readieness probe", nil, nil, nil}, { - "component has healthchecks, no env config", - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 30), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, - nil, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 30), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + description: "component has healthchecks, no env config", + compHealthChecks: &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 30), ReadinessProbe: createProbe(execProbe, 10), StartupProbe: createProbe(httpProbe, 20)}, + expectedHealthChecks: &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 30), ReadinessProbe: createProbe(execProbe, 10), StartupProbe: createProbe(httpProbe, 20)}, }, { "Env healthchecks, no component healthchecks", nil, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 1), ReadinessProbe: createProbe(execProbe, 10), StartupProbe: createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 1), ReadinessProbe: createProbe(execProbe, 10), StartupProbe: createProbe(httpProbe, 20)}, }, { "Env healthchecks, component healthchecks, env overrides comp", - &radixv1.RadixHealthChecks{createProbe(execProbe, 30), createProbe(httpProbe, 10), createProbe(tcpProbe, 20)}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 40), createProbe(httpProbe, 20)}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 40), createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(execProbe, 30), ReadinessProbe: createProbe(httpProbe, 10), StartupProbe: createProbe(tcpProbe, 20)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 1), ReadinessProbe: createProbe(execProbe, 40), StartupProbe: createProbe(httpProbe, 20)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 1), ReadinessProbe: createProbe(execProbe, 40), StartupProbe: createProbe(httpProbe, 20)}, }, { "Env healthchecks, component healthchecks, env merges comp", - &radixv1.RadixHealthChecks{nil, createProbe(httpProbe, 10), createProbe(tcpProbe, 20)}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), nil}, - &radixv1.RadixHealthChecks{createProbe(tcpProbe, 1), createProbe(execProbe, 10), createProbe(tcpProbe, 20)}, + &radixv1.RadixHealthChecks{ReadinessProbe: createProbe(httpProbe, 10), StartupProbe: createProbe(tcpProbe, 20)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 1), ReadinessProbe: createProbe(execProbe, 10)}, + &radixv1.RadixHealthChecks{LivenessProbe: createProbe(tcpProbe, 1), ReadinessProbe: createProbe(execProbe, 10), StartupProbe: createProbe(tcpProbe, 20)}, }, } From 952545da72e48bd35ce3ce8f040e8f26febae204 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Dec 2024 15:01:58 +0100 Subject: [PATCH 06/15] Add RA validation --- pkg/apis/radixvalidators/errors.go | 6 ++++ pkg/apis/radixvalidators/validate_ra.go | 37 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/pkg/apis/radixvalidators/errors.go b/pkg/apis/radixvalidators/errors.go index 43ffdedc5..a2ab58f8b 100644 --- a/pkg/apis/radixvalidators/errors.go +++ b/pkg/apis/radixvalidators/errors.go @@ -16,6 +16,7 @@ var ( ErrEnvForDNSExternalAliasNotDefined = errors.New("env for dns external alias not defined") ErrComponentForDNSExternalAliasNotDefined = errors.New("component for dns external alias not defined") ErrComponentForDNSExternalAliasIsNotMarkedAsPublic = errors.New("component for dns external alias is not marked as public") + ErrComponentHasInvalidHealthCheck = errors.New("component has invalid health check") ErrEnvironmentReferencedByComponentDoesNotExist = errors.New("environment referenced by component does not exist") ErrInvalidPortNameLength = errors.New("invalid port name length") ErrPortNameIsRequiredForPublicComponent = errors.New("port name is required for public component") @@ -180,6 +181,11 @@ func ComponentForDNSExternalAliasIsNotMarkedAsPublicErrorWithMessage(component s return errors.WithMessagef(ErrComponentForDNSExternalAliasIsNotMarkedAsPublic, "component %s referred to by dnsExternalAlias is not marked as public", component) } +// ComponentHasInvalidHealthChecks Component has invalid health checks +func ComponentHasInvalidHealthChecks(component string, probeName string, err error) error { + return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health checks %s: %w", probeName, component, err) +} + // EnvironmentReferencedByComponentDoesNotExistErrorWithMessage Environment does not exists func EnvironmentReferencedByComponentDoesNotExistErrorWithMessage(environment, component string) error { return errors.WithMessagef(ErrEnvironmentReferencedByComponentDoesNotExist, "env %s refered to by component %s is not defined", environment, component) diff --git a/pkg/apis/radixvalidators/validate_ra.go b/pkg/apis/radixvalidators/validate_ra.go index 8f1e49c84..20d986cfa 100644 --- a/pkg/apis/radixvalidators/validate_ra.go +++ b/pkg/apis/radixvalidators/validate_ra.go @@ -55,6 +55,7 @@ var ( validateHorizontalScalingConfigForRA, validateVolumeMountConfigForRA, ValidateNotificationsForRA, + validateHealthChecks, } ipOrCidrRegExp = regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$`) @@ -829,6 +830,42 @@ func validateRadixComponentSecrets(component radixv1.RadixCommonComponent, app * return validateConflictingEnvironmentAndSecretRefsNames(component, envsEnvVarsMap) } +func validateHealthChecks(app *radixv1.RadixApplication) error { + var errs []error + + for _, component := range app.Spec.Components { + if component.HealthChecks == nil { + continue + } + + if err := validateProbe(component.HealthChecks.StartupProbe); err != nil { + errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "StartupProbe", err)) + } + if err := validateProbe(component.HealthChecks.ReadinessProbe); err != nil { + errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "ReadinessProbe", err)) + } + if err := validateProbe(component.HealthChecks.LivenessProbe); err != nil { + errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "LivenessProbe", err)) + } + } + + return errors.Join(errs...) +} + +func validateProbe(probe *corev1.Probe) error { + if probe == nil { + return nil + } + + if (probe.HTTPGet != nil && (probe.TCPSocket != nil || probe.Exec != nil)) || + (probe.TCPSocket != nil && (probe.HTTPGet != nil || probe.Exec != nil)) || + (probe.Exec != nil && (probe.HTTPGet != nil || probe.TCPSocket != nil)) { + return fmt.Errorf("HTTPGet, TCPSocket and Exec are mutually exclusive") + } + + return nil +} + func getEnvVarNameMap(componentEnvVarsMap radixv1.EnvVarsMap, envsEnvVarsMap radixv1.EnvVarsMap) map[string]bool { envVarsMap := make(map[string]bool) for name := range componentEnvVarsMap { From b117b4b3e6a9a1bcf684a5a1147fe7041a50bacc Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 3 Dec 2024 15:03:01 +0100 Subject: [PATCH 07/15] Start testing --- pkg/apis/radixvalidators/validate_ra_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/apis/radixvalidators/validate_ra_test.go b/pkg/apis/radixvalidators/validate_ra_test.go index b96a03899..2c16f3878 100644 --- a/pkg/apis/radixvalidators/validate_ra_test.go +++ b/pkg/apis/radixvalidators/validate_ra_test.go @@ -78,6 +78,14 @@ func Test_application_name_casing_is_validated(t *testing.T) { } } +func Test_Invalid_HealthChecks(t *testing.T) { + t.Errorf("Not implemented") +} + +func Test_Valid_HealthChecks(t *testing.T) { + t.Errorf("Not implemented") +} + func Test_invalid_ra(t *testing.T) { validRAFirstComponentName := "app" validRAFirstJobName := "job" From d22b56c3b5e8e9b92db21adb44950772ce878d71 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Dec 2024 14:35:34 +0100 Subject: [PATCH 08/15] replace k8s types with radix, add pointers where optional --- .../templates/radixapplication.yaml | 414 ++++++++---------- .../templates/radixdeployment.yaml | 204 ++++----- json-schema/radixapplication.json | 354 ++++++++------- pkg/apis/deployment/kubedeployment.go | 6 +- pkg/apis/deployment/kubedeployment_test.go | 30 +- pkg/apis/deployment/radixcomponent_test.go | 25 +- pkg/apis/radix/v1/radixapptypes.go | 7 +- pkg/apis/radix/v1/radixhealthchecktypes.go | 209 +++++++++ pkg/apis/radix/v1/zz_generated.deepcopy.go | 193 +++++++- pkg/apis/radixvalidators/errors.go | 2 +- pkg/apis/radixvalidators/validate_ra.go | 32 +- pkg/apis/radixvalidators/validate_ra_test.go | 49 ++- .../utils/applicationcomponent_builder.go | 5 +- .../utils/componentenvironment_builder.go | 5 +- pkg/apis/utils/deploymentcomponent_builder.go | 5 +- 15 files changed, 940 insertions(+), 600 deletions(-) create mode 100644 pkg/apis/radix/v1/radixhealthchecktypes.go diff --git a/charts/radix-operator/templates/radixapplication.yaml b/charts/radix-operator/templates/radixapplication.yaml index 66424cebd..864bcc422 100644 --- a/charts/radix-operator/templates/radixapplication.yaml +++ b/charts/radix-operator/templates/radixapplication.yaml @@ -449,10 +449,12 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving @@ -460,8 +462,9 @@ spec: properties: port: description: Port number of the gRPC service. - Number must be in the range 1 to 65535. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -508,18 +511,19 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the + container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -531,16 +535,19 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform + the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -551,37 +558,22 @@ spec: to, defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the + container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object readinessProbe: @@ -607,10 +599,12 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving @@ -618,8 +612,9 @@ spec: properties: port: description: Port number of the gRPC service. - Number must be in the range 1 to 65535. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -666,18 +661,19 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the + container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -689,16 +685,19 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform + the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -709,37 +708,22 @@ spec: to, defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the + container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object startupProbe: @@ -767,10 +751,12 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for + the probe to be considered failed after having + succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving @@ -778,8 +764,9 @@ spec: properties: port: description: Port number of the gRPC service. - Number must be in the range 1 to 65535. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -826,18 +813,19 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the + container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -849,16 +837,19 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform + the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -869,37 +860,22 @@ spec: to, defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the + container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object type: object @@ -1676,19 +1652,21 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving a GRPC port. properties: port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. + description: Port number of the gRPC service. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -1734,18 +1712,18 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -1757,16 +1735,18 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -1777,37 +1757,21 @@ spec: defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object readinessProbe: @@ -1833,19 +1797,21 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving a GRPC port. properties: port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. + description: Port number of the gRPC service. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -1891,18 +1857,18 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -1914,16 +1880,18 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -1934,37 +1902,21 @@ spec: defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object startupProbe: @@ -1992,19 +1944,21 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving a GRPC port. properties: port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. + description: Port number of the gRPC service. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -2050,18 +2004,18 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -2073,16 +2027,18 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -2093,37 +2049,21 @@ spec: defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object type: object diff --git a/charts/radix-operator/templates/radixdeployment.yaml b/charts/radix-operator/templates/radixdeployment.yaml index 8824468c8..d262bffd2 100644 --- a/charts/radix-operator/templates/radixdeployment.yaml +++ b/charts/radix-operator/templates/radixdeployment.yaml @@ -265,19 +265,21 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving a GRPC port. properties: port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. + description: Port number of the gRPC service. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -323,18 +325,18 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -346,16 +348,18 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -366,37 +370,21 @@ spec: defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object readinessProbe: @@ -422,19 +410,21 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving a GRPC port. properties: port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. + description: Port number of the gRPC service. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -480,18 +470,18 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -503,16 +493,18 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -523,37 +515,21 @@ spec: defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object startupProbe: @@ -581,19 +557,21 @@ spec: x-kubernetes-list-type: atomic type: object failureThreshold: - description: |- - Minimum consecutive failures for the probe to be considered failed after having succeeded. - Defaults to 3. Minimum value is 1. + default: 3 + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. format: int32 + minimum: 1 type: integer grpc: description: GRPC specifies an action involving a GRPC port. properties: port: - description: Port number of the gRPC service. Number - must be in the range 1 to 65535. + description: Port number of the gRPC service. format: int32 + maximum: 65535 + minimum: 1 type: integer service: default: "" @@ -639,18 +617,18 @@ spec: description: Path to access on the HTTP server. type: string port: - anyOf: - - type: integer - - type: string - description: |- - Name or number of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer scheme: description: |- Scheme to use for connecting to the host. Defaults to HTTP. + enum: + - HTTPS + - HTTP type: string required: - port @@ -662,16 +640,18 @@ spec: format: int32 type: integer periodSeconds: - description: |- - How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. + default: 10 + description: How often (in seconds) to perform the probe. format: int32 + minimum: 1 type: integer successThreshold: + default: 1 description: |- Minimum consecutive successes for the probe to be considered successful after having failed. - Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1. + Must be 1 for liveness and startup. format: int32 + minimum: 1 type: integer tcpSocket: description: TCPSocket specifies an action involving @@ -682,37 +662,21 @@ spec: defaults to the pod IP.' type: string port: - anyOf: - - type: integer - - type: string - description: |- - Number or name of the port to access on the container. - Number must be in the range 1 to 65535. - Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true + description: port number to access on the container. + format: int32 + maximum: 65535 + minimum: 1 + type: integer required: - port type: object - terminationGracePeriodSeconds: - description: |- - Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - The grace period is the duration in seconds after the processes running in the pod are sent - a termination signal and the time when the processes are forcibly halted with a kill signal. - Set this value longer than the expected cleanup time for your process. - If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - value overrides the value provided by the pod spec. - Value must be non-negative integer. The value zero indicates stop immediately via - the kill signal (no opportunity to shut down). - This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset. - format: int64 - type: integer timeoutSeconds: + default: 1 description: |- Number of seconds after which the probe times out. - Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes format: int32 + minimum: 1 type: integer type: object type: object diff --git a/json-schema/radixapplication.json b/json-schema/radixapplication.json index fc8e5cc80..c709df81c 100644 --- a/json-schema/radixapplication.json +++ b/json-schema/radixapplication.json @@ -421,16 +421,20 @@ "type": "object" }, "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "default": 3, + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.", "format": "int32", + "minimum": 1, "type": "integer" }, "grpc": { "description": "GRPC specifies an action involving a GRPC port.", "properties": { "port": { - "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "description": "Port number of the gRPC service.", "format": "int32", + "maximum": 65535, + "minimum": 1, "type": "integer" }, "service": { @@ -479,19 +483,18 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "enum": [ + "HTTPS", + "HTTP" + ], "type": "string" } }, @@ -506,13 +509,17 @@ "type": "integer" }, "periodSeconds": { - "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "default": 10, + "description": "How often (in seconds) to perform the probe.", "format": "int32", + "minimum": 1, "type": "integer" }, "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nMust be 1 for liveness and startup.", "format": "int32", + "minimum": 1, "type": "integer" }, "tcpSocket": { @@ -523,16 +530,11 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" } }, "required": [ @@ -540,14 +542,11 @@ ], "type": "object" }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", - "format": "int64", - "type": "integer" - }, "timeoutSeconds": { - "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "default": 1, + "description": "Number of seconds after which the probe times out.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", + "minimum": 1, "type": "integer" } }, @@ -571,16 +570,20 @@ "type": "object" }, "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "default": 3, + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.", "format": "int32", + "minimum": 1, "type": "integer" }, "grpc": { "description": "GRPC specifies an action involving a GRPC port.", "properties": { "port": { - "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "description": "Port number of the gRPC service.", "format": "int32", + "maximum": 65535, + "minimum": 1, "type": "integer" }, "service": { @@ -629,19 +632,18 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "enum": [ + "HTTPS", + "HTTP" + ], "type": "string" } }, @@ -656,13 +658,17 @@ "type": "integer" }, "periodSeconds": { - "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "default": 10, + "description": "How often (in seconds) to perform the probe.", "format": "int32", + "minimum": 1, "type": "integer" }, "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nMust be 1 for liveness and startup.", "format": "int32", + "minimum": 1, "type": "integer" }, "tcpSocket": { @@ -673,16 +679,11 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" } }, "required": [ @@ -690,14 +691,11 @@ ], "type": "object" }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", - "format": "int64", - "type": "integer" - }, "timeoutSeconds": { - "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "default": 1, + "description": "Number of seconds after which the probe times out.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", + "minimum": 1, "type": "integer" } }, @@ -721,16 +719,20 @@ "type": "object" }, "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "default": 3, + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.", "format": "int32", + "minimum": 1, "type": "integer" }, "grpc": { "description": "GRPC specifies an action involving a GRPC port.", "properties": { "port": { - "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "description": "Port number of the gRPC service.", "format": "int32", + "maximum": 65535, + "minimum": 1, "type": "integer" }, "service": { @@ -779,19 +781,18 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "enum": [ + "HTTPS", + "HTTP" + ], "type": "string" } }, @@ -806,13 +807,17 @@ "type": "integer" }, "periodSeconds": { - "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "default": 10, + "description": "How often (in seconds) to perform the probe.", "format": "int32", + "minimum": 1, "type": "integer" }, "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nMust be 1 for liveness and startup.", "format": "int32", + "minimum": 1, "type": "integer" }, "tcpSocket": { @@ -823,16 +828,11 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" } }, "required": [ @@ -840,14 +840,11 @@ ], "type": "object" }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", - "format": "int64", - "type": "integer" - }, "timeoutSeconds": { - "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "default": 1, + "description": "Number of seconds after which the probe times out.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", + "minimum": 1, "type": "integer" } }, @@ -1618,16 +1615,20 @@ "type": "object" }, "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "default": 3, + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.", "format": "int32", + "minimum": 1, "type": "integer" }, "grpc": { "description": "GRPC specifies an action involving a GRPC port.", "properties": { "port": { - "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "description": "Port number of the gRPC service.", "format": "int32", + "maximum": 65535, + "minimum": 1, "type": "integer" }, "service": { @@ -1676,19 +1677,18 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "enum": [ + "HTTPS", + "HTTP" + ], "type": "string" } }, @@ -1703,13 +1703,17 @@ "type": "integer" }, "periodSeconds": { - "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "default": 10, + "description": "How often (in seconds) to perform the probe.", "format": "int32", + "minimum": 1, "type": "integer" }, "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nMust be 1 for liveness and startup.", "format": "int32", + "minimum": 1, "type": "integer" }, "tcpSocket": { @@ -1720,16 +1724,11 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" } }, "required": [ @@ -1737,14 +1736,11 @@ ], "type": "object" }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", - "format": "int64", - "type": "integer" - }, "timeoutSeconds": { - "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "default": 1, + "description": "Number of seconds after which the probe times out.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", + "minimum": 1, "type": "integer" } }, @@ -1768,16 +1764,20 @@ "type": "object" }, "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "default": 3, + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.", "format": "int32", + "minimum": 1, "type": "integer" }, "grpc": { "description": "GRPC specifies an action involving a GRPC port.", "properties": { "port": { - "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "description": "Port number of the gRPC service.", "format": "int32", + "maximum": 65535, + "minimum": 1, "type": "integer" }, "service": { @@ -1826,19 +1826,18 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "enum": [ + "HTTPS", + "HTTP" + ], "type": "string" } }, @@ -1853,13 +1852,17 @@ "type": "integer" }, "periodSeconds": { - "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "default": 10, + "description": "How often (in seconds) to perform the probe.", "format": "int32", + "minimum": 1, "type": "integer" }, "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nMust be 1 for liveness and startup.", "format": "int32", + "minimum": 1, "type": "integer" }, "tcpSocket": { @@ -1870,16 +1873,11 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" } }, "required": [ @@ -1887,14 +1885,11 @@ ], "type": "object" }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", - "format": "int64", - "type": "integer" - }, "timeoutSeconds": { - "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "default": 1, + "description": "Number of seconds after which the probe times out.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", + "minimum": 1, "type": "integer" } }, @@ -1918,16 +1913,20 @@ "type": "object" }, "failureThreshold": { - "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.\nDefaults to 3. Minimum value is 1.", + "default": 3, + "description": "Minimum consecutive failures for the probe to be considered failed after having succeeded.", "format": "int32", + "minimum": 1, "type": "integer" }, "grpc": { "description": "GRPC specifies an action involving a GRPC port.", "properties": { "port": { - "description": "Port number of the gRPC service. Number must be in the range 1 to 65535.", + "description": "Port number of the gRPC service.", "format": "int32", + "maximum": 65535, + "minimum": 1, "type": "integer" }, "service": { @@ -1976,19 +1975,18 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Name or number of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" }, "scheme": { "description": "Scheme to use for connecting to the host.\nDefaults to HTTP.", + "enum": [ + "HTTPS", + "HTTP" + ], "type": "string" } }, @@ -2003,13 +2001,17 @@ "type": "integer" }, "periodSeconds": { - "description": "How often (in seconds) to perform the probe.\nDefault to 10 seconds. Minimum value is 1.", + "default": 10, + "description": "How often (in seconds) to perform the probe.", "format": "int32", + "minimum": 1, "type": "integer" }, "successThreshold": { - "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nDefaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + "default": 1, + "description": "Minimum consecutive successes for the probe to be considered successful after having failed.\nMust be 1 for liveness and startup.", "format": "int32", + "minimum": 1, "type": "integer" }, "tcpSocket": { @@ -2020,16 +2022,11 @@ "type": "string" }, "port": { - "anyOf": [ - { - "type": "integer" - }, - { - "type": "string" - } - ], - "description": "Number or name of the port to access on the container.\nNumber must be in the range 1 to 65535.\nName must be an IANA_SVC_NAME.", - "x-kubernetes-int-or-string": true + "description": "port number to access on the container.", + "format": "int32", + "maximum": 65535, + "minimum": 1, + "type": "integer" } }, "required": [ @@ -2037,14 +2034,11 @@ ], "type": "object" }, - "terminationGracePeriodSeconds": { - "description": "Optional duration in seconds the pod needs to terminate gracefully upon probe failure.\nThe grace period is the duration in seconds after the processes running in the pod are sent\na termination signal and the time when the processes are forcibly halted with a kill signal.\nSet this value longer than the expected cleanup time for your process.\nIf this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this\nvalue overrides the value provided by the pod spec.\nValue must be non-negative integer. The value zero indicates stop immediately via\nthe kill signal (no opportunity to shut down).\nThis is a beta field and requires enabling ProbeTerminationGracePeriod feature gate.\nMinimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", - "format": "int64", - "type": "integer" - }, "timeoutSeconds": { - "description": "Number of seconds after which the probe times out.\nDefaults to 1 second. Minimum value is 1.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + "default": 1, + "description": "Number of seconds after which the probe times out.\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", "format": "int32", + "minimum": 1, "type": "integer" } }, diff --git a/pkg/apis/deployment/kubedeployment.go b/pkg/apis/deployment/kubedeployment.go index cefd6420c..df5cb5df4 100644 --- a/pkg/apis/deployment/kubedeployment.go +++ b/pkg/apis/deployment/kubedeployment.go @@ -316,9 +316,9 @@ func (deploy *Deployment) setDesiredDeploymentProperties(ctx context.Context, de desiredDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts if hc := deployComponent.GetHealthChecks(); hc != nil { - desiredDeployment.Spec.Template.Spec.Containers[0].ReadinessProbe = hc.ReadinessProbe - desiredDeployment.Spec.Template.Spec.Containers[0].LivenessProbe = hc.LivenessProbe - desiredDeployment.Spec.Template.Spec.Containers[0].StartupProbe = hc.StartupProbe + desiredDeployment.Spec.Template.Spec.Containers[0].ReadinessProbe = hc.ReadinessProbe.MapToCoreProbe() + desiredDeployment.Spec.Template.Spec.Containers[0].LivenessProbe = hc.LivenessProbe.MapToCoreProbe() + desiredDeployment.Spec.Template.Spec.Containers[0].StartupProbe = hc.StartupProbe.MapToCoreProbe() } else { readinessProbe, err := getDefaultReadinessProbeForComponent(deployComponent) if err != nil { diff --git a/pkg/apis/deployment/kubedeployment_test.go b/pkg/apis/deployment/kubedeployment_test.go index 842bd474b..3847a4f3b 100644 --- a/pkg/apis/deployment/kubedeployment_test.go +++ b/pkg/apis/deployment/kubedeployment_test.go @@ -12,9 +12,7 @@ import ( "github.com/equinor/radix-operator/pkg/apis/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" ) func teardownReadinessProbe() { @@ -56,26 +54,26 @@ func TestComponentWithoutCustomHealthChecks(t *testing.T) { } func TestComponentWithCustomHealthChecks(t *testing.T) { tu, client, kubeUtil, radixclient, kedaClient, prometheusclient, _, certClient := SetupTest(t) - createProbe := func(handler corev1.ProbeHandler, seconds int32) *corev1.Probe { - return &corev1.Probe{ - ProbeHandler: handler, - InitialDelaySeconds: seconds, - TimeoutSeconds: seconds + 1, - PeriodSeconds: seconds + 2, - SuccessThreshold: seconds + 3, - FailureThreshold: seconds + 4, - TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), + createProbe := func(handler v1.RadixProbeHandler, seconds int32) *v1.RadixProbe { + return &v1.RadixProbe{ + RadixProbeHandler: handler, + InitialDelaySeconds: pointers.Ptr(seconds), + TimeoutSeconds: pointers.Ptr(seconds + 1), + PeriodSeconds: pointers.Ptr(seconds + 2), + SuccessThreshold: pointers.Ptr(seconds + 3), + FailureThreshold: pointers.Ptr(seconds + 4), + // TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), } } - readynessProbe := createProbe(corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{ - Port: intstr.IntOrString{IntVal: 5000}, + readynessProbe := createProbe(v1.RadixProbeHandler{HTTPGet: &v1.RadixProbeHTTPGetAction{ + Port: pointers.Ptr[int32](5000), }}, 10) - livenessProbe := createProbe(corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.IntOrString{IntVal: 5000}, + livenessProbe := createProbe(v1.RadixProbeHandler{TCPSocket: &v1.RadixProbeTCPSocketAction{ + Port: pointers.Ptr[int32](5000), }}, 20) - startuProbe := createProbe(corev1.ProbeHandler{Exec: &corev1.ExecAction{ + startuProbe := createProbe(v1.RadixProbeHandler{Exec: &v1.RadixProbeExecAction{ Command: []string{"echo", "hello"}, }}, 30) diff --git a/pkg/apis/deployment/radixcomponent_test.go b/pkg/apis/deployment/radixcomponent_test.go index bc424fbb0..bde0275cf 100644 --- a/pkg/apis/deployment/radixcomponent_test.go +++ b/pkg/apis/deployment/radixcomponent_test.go @@ -17,7 +17,6 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" storagev1 "k8s.io/api/storage/v1" - "k8s.io/apimachinery/pkg/util/intstr" ) type scenarioDef struct { @@ -1050,21 +1049,21 @@ func Test_GetRadixComponents_Monitoring(t *testing.T) { } func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { - createProbe := func(handler corev1.ProbeHandler, seconds int32) *corev1.Probe { - return &corev1.Probe{ - ProbeHandler: handler, - InitialDelaySeconds: seconds, - TimeoutSeconds: seconds + 1, - PeriodSeconds: seconds + 2, - SuccessThreshold: seconds + 3, - FailureThreshold: seconds + 4, - TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), + createProbe := func(handler radixv1.RadixProbeHandler, seconds int32) *radixv1.RadixProbe { + return &radixv1.RadixProbe{ + RadixProbeHandler: handler, + InitialDelaySeconds: &seconds, + TimeoutSeconds: pointers.Ptr(seconds + 1), + PeriodSeconds: pointers.Ptr(seconds + 2), + SuccessThreshold: pointers.Ptr(seconds + 3), + FailureThreshold: pointers.Ptr(seconds + 4), + // TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), } } - httpProbe := corev1.ProbeHandler{HTTPGet: &corev1.HTTPGetAction{Port: intstr.FromInt32(5000), Path: "/healthz", Scheme: "http"}} - execProbe := corev1.ProbeHandler{Exec: &corev1.ExecAction{Command: []string{"/bin/sh", "-c", "/healthz /healthz"}}} - tcpProbe := corev1.ProbeHandler{TCPSocket: &corev1.TCPSocketAction{Port: intstr.FromInt32(8000)}} + httpProbe := radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz"), Scheme: pointers.Ptr(corev1.URISchemeHTTP)}} + execProbe := radixv1.RadixProbeHandler{Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz /healthz"}}} + tcpProbe := radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](8000)}} testCases := []struct { description string diff --git a/pkg/apis/radix/v1/radixapptypes.go b/pkg/apis/radix/v1/radixapptypes.go index db8b4ecc2..70f27a040 100644 --- a/pkg/apis/radix/v1/radixapptypes.go +++ b/pkg/apis/radix/v1/radixapptypes.go @@ -4,7 +4,6 @@ import ( "strings" commonUtils "github.com/equinor/radix-common/utils" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -821,13 +820,13 @@ type RadixHealthChecks struct { // Container will be restarted if the probe fails. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes // +optional - LivenessProbe *v1.Probe `json:"livenessProbe,omitempty"` + LivenessProbe *RadixProbe `json:"livenessProbe,omitempty"` // Periodic probe of container service readiness. // Container will be removed from service endpoints if the probe fails. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes // Defaults to TCP Probe against the first listed port // +optional - ReadinessProbe *v1.Probe `json:"readinessProbe,omitempty"` + ReadinessProbe *RadixProbe `json:"readinessProbe,omitempty"` // StartupProbe indicates that the Pod has successfully initialized. // If specified, no other probes are executed until this completes successfully. // If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. @@ -835,7 +834,7 @@ type RadixHealthChecks struct { // when it might take a long time to load data or warm a cache, than during steady-state operation. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes // +optional - StartupProbe *v1.Probe `json:"startupProbe,omitempty"` + StartupProbe *RadixProbe `json:"startupProbe,omitempty"` } // PrivateImageHubEntries defines authentication information for private image registries. diff --git a/pkg/apis/radix/v1/radixhealthchecktypes.go b/pkg/apis/radix/v1/radixhealthchecktypes.go new file mode 100644 index 000000000..1ad2460fd --- /dev/null +++ b/pkg/apis/radix/v1/radixhealthchecktypes.go @@ -0,0 +1,209 @@ +package v1 + +import ( + "github.com/equinor/radix-common/utils/pointers" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// RadixProbe describes a health check to be performed against a container to determine whether it is +// alive or ready to receive traffic. +type RadixProbe struct { + // The action taken to determine the health of a container + RadixProbeHandler `json:",inline"` + // Number of seconds after the container has started before liveness probes are initiated. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + InitialDelaySeconds *int32 `json:"initialDelaySeconds,omitempty"` + // Number of seconds after which the probe times out. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +kubebuilder:validation:Minimum=1 + // +default=1 + // +optional + TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` + // How often (in seconds) to perform the probe. + // +kubebuilder:validation:Minimum=1 + // +default=10 + // +optional + PeriodSeconds *int32 `json:"periodSeconds,omitempty"` + // Minimum consecutive successes for the probe to be considered successful after having failed. + // Must be 1 for liveness and startup. + // +kubebuilder:validation:Minimum=1 + // +default=1 + // +optional + SuccessThreshold *int32 `json:"successThreshold,omitempty"` + // Minimum consecutive failures for the probe to be considered failed after having succeeded. + // +kubebuilder:validation:Minimum=1 + // +default=3 + // +optional + FailureThreshold *int32 `json:"failureThreshold,omitempty"` + + // // Optional duration in seconds the pod needs to terminate gracefully upon probe failure. + // // The grace period is the duration in seconds after the processes running in the pod are sent + // // a termination signal and the time when the processes are forcibly halted with a kill signal. + // // Set this value longer than the expected cleanup time for your process. + // // If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this + // // value overrides the value provided by the pod spec. + // // Value must be non-negative integer. The value zero indicates stop immediately via + // // the kill signal (no opportunity to shut down). + // // This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. + // // +kubebuilder:validation:Minimum=1 + // // +default=30 + // // +optional + // TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"` +} + +func (rp *RadixProbe) MapToCoreProbe() *corev1.Probe { + if rp == nil { + return nil + } + + return &corev1.Probe{ + ProbeHandler: rp.RadixProbeHandler.MapToCoreProbe(), + InitialDelaySeconds: pointers.Val(rp.InitialDelaySeconds), + TimeoutSeconds: pointers.Val(rp.TimeoutSeconds), + PeriodSeconds: pointers.Val(rp.PeriodSeconds), + SuccessThreshold: pointers.Val(rp.SuccessThreshold), + FailureThreshold: pointers.Val(rp.FailureThreshold), + // TerminationGracePeriodSeconds: rp.TerminationGracePeriodSeconds, + } +} + +// RadixProbeHandler defines a specific action that should be taken in a probe. +// One and only one of the fields must be specified. +type RadixProbeHandler struct { + // Exec specifies the action to take. + Exec *RadixProbeExecAction `json:"exec,omitempty"` + // HTTPGet specifies the http request to perform. + HTTPGet *RadixProbeHTTPGetAction `json:"httpGet,omitempty"` + // TCPSocket specifies an action involving a TCP port. + TCPSocket *RadixProbeTCPSocketAction `json:"tcpSocket,omitempty"` + // GRPC specifies an action involving a GRPC port. + GRPC *RadixProbeGRPCAction `json:"grpc,omitempty"` +} + +func (p RadixProbeHandler) MapToCoreProbe() corev1.ProbeHandler { + return corev1.ProbeHandler{ + Exec: p.Exec.MapToCoreProbe(), + HTTPGet: p.HTTPGet.MapToCoreProbe(), + TCPSocket: p.TCPSocket.MapToCoreProbe(), + GRPC: p.GRPC.MapToCoreProbe(), + } +} + +// RadixProbeHTTPGetAction describes an action based on HTTP Get requests. +type RadixProbeHTTPGetAction struct { + // Path to access on the HTTP server. + // +optional + Path *string `json:"path,omitempty"` + // port number to access on the container. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port *int32 `json:"port"` + // Host name to connect to, defaults to the pod IP. You probably want to set + // "Host" in httpHeaders instead. + // +optional + Host *string `json:"host,omitempty"` + // Scheme to use for connecting to the host. + // Defaults to HTTP. + // +optional + // +kubebuilder:validation:Enum=HTTPS;HTTP + Scheme *corev1.URIScheme `json:"scheme,omitempty"` + // Custom headers to set in the request. HTTP allows repeated headers. + // +optional + // +listType=atomic + HTTPHeaders []corev1.HTTPHeader `json:"httpHeaders,omitempty"` +} + +func (a *RadixProbeHTTPGetAction) MapToCoreProbe() *corev1.HTTPGetAction { + if a == nil { + return nil + } + + var port intstr.IntOrString + if a.Port != nil { + port = intstr.FromInt32(*a.Port) + } + + return &corev1.HTTPGetAction{ + Path: pointers.Val(a.Path), + Port: port, + Host: pointers.Val(a.Host), + Scheme: pointers.Val(a.Scheme), + HTTPHeaders: a.HTTPHeaders, + } +} + +// RadixProbeExecAction describes a "run in container" action. +type RadixProbeExecAction struct { + // Command is the command line to execute inside the container, the working directory for the + // command is root ('/') in the container's filesystem. The command is simply exec'd, it is + // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use + // a shell, you need to explicitly call out to that shell. + // Exit status of 0 is treated as live/healthy and non-zero is unhealthy. + // +optional + // +listType=atomic + Command []string `json:"command,omitempty"` +} + +func (a *RadixProbeExecAction) MapToCoreProbe() *corev1.ExecAction { + if a == nil { + return nil + } + + return &corev1.ExecAction{ + Command: a.Command, + } +} + +// RadixProbeTCPSocketAction describes an action based on opening a socket +type RadixProbeTCPSocketAction struct { + // port number to access on the container. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port *int32 `json:"port"` + // Optional: Host name to connect to, defaults to the pod IP. + // +optional + Host *string `json:"host,omitempty"` +} + +func (a *RadixProbeTCPSocketAction) MapToCoreProbe() *corev1.TCPSocketAction { + if a == nil { + return nil + } + var port intstr.IntOrString + if a.Port != nil { + port = intstr.FromInt32(*a.Port) + } + + return &corev1.TCPSocketAction{ + Port: port, + Host: pointers.Val(a.Host), + } +} + +type RadixProbeGRPCAction struct { + // Port number of the gRPC service. + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` + + // Service is the name of the service to place in the gRPC HealthCheckRequest + // (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md). + // + // If this is not specified, the default behavior is defined by gRPC. + // +optional + // +default="" + Service *string `json:"service"` +} + +func (a *RadixProbeGRPCAction) MapToCoreProbe() *corev1.GRPCAction { + if a == nil { + return nil + } + + return &corev1.GRPCAction{ + Port: a.Port, + Service: a.Service, + } +} diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index 46f5fc750..af753a6e4 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -2179,17 +2179,17 @@ func (in *RadixHealthChecks) DeepCopyInto(out *RadixHealthChecks) { *out = *in if in.LivenessProbe != nil { in, out := &in.LivenessProbe, &out.LivenessProbe - *out = new(corev1.Probe) + *out = new(RadixProbe) (*in).DeepCopyInto(*out) } if in.ReadinessProbe != nil { in, out := &in.ReadinessProbe, &out.ReadinessProbe - *out = new(corev1.Probe) + *out = new(RadixProbe) (*in).DeepCopyInto(*out) } if in.StartupProbe != nil { in, out := &in.StartupProbe, &out.StartupProbe - *out = new(corev1.Probe) + *out = new(RadixProbe) (*in).DeepCopyInto(*out) } return @@ -2842,6 +2842,193 @@ func (in *RadixPrivateImageHubCredential) DeepCopy() *RadixPrivateImageHubCreden return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixProbe) DeepCopyInto(out *RadixProbe) { + *out = *in + in.RadixProbeHandler.DeepCopyInto(&out.RadixProbeHandler) + if in.InitialDelaySeconds != nil { + in, out := &in.InitialDelaySeconds, &out.InitialDelaySeconds + *out = new(int32) + **out = **in + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int32) + **out = **in + } + if in.PeriodSeconds != nil { + in, out := &in.PeriodSeconds, &out.PeriodSeconds + *out = new(int32) + **out = **in + } + if in.SuccessThreshold != nil { + in, out := &in.SuccessThreshold, &out.SuccessThreshold + *out = new(int32) + **out = **in + } + if in.FailureThreshold != nil { + in, out := &in.FailureThreshold, &out.FailureThreshold + *out = new(int32) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixProbe. +func (in *RadixProbe) DeepCopy() *RadixProbe { + if in == nil { + return nil + } + out := new(RadixProbe) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixProbeExecAction) DeepCopyInto(out *RadixProbeExecAction) { + *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixProbeExecAction. +func (in *RadixProbeExecAction) DeepCopy() *RadixProbeExecAction { + if in == nil { + return nil + } + out := new(RadixProbeExecAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixProbeGRPCAction) DeepCopyInto(out *RadixProbeGRPCAction) { + *out = *in + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixProbeGRPCAction. +func (in *RadixProbeGRPCAction) DeepCopy() *RadixProbeGRPCAction { + if in == nil { + return nil + } + out := new(RadixProbeGRPCAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixProbeHTTPGetAction) DeepCopyInto(out *RadixProbeHTTPGetAction) { + *out = *in + if in.Path != nil { + in, out := &in.Path, &out.Path + *out = new(string) + **out = **in + } + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int32) + **out = **in + } + if in.Host != nil { + in, out := &in.Host, &out.Host + *out = new(string) + **out = **in + } + if in.Scheme != nil { + in, out := &in.Scheme, &out.Scheme + *out = new(corev1.URIScheme) + **out = **in + } + if in.HTTPHeaders != nil { + in, out := &in.HTTPHeaders, &out.HTTPHeaders + *out = make([]corev1.HTTPHeader, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixProbeHTTPGetAction. +func (in *RadixProbeHTTPGetAction) DeepCopy() *RadixProbeHTTPGetAction { + if in == nil { + return nil + } + out := new(RadixProbeHTTPGetAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixProbeHandler) DeepCopyInto(out *RadixProbeHandler) { + *out = *in + if in.Exec != nil { + in, out := &in.Exec, &out.Exec + *out = new(RadixProbeExecAction) + (*in).DeepCopyInto(*out) + } + if in.HTTPGet != nil { + in, out := &in.HTTPGet, &out.HTTPGet + *out = new(RadixProbeHTTPGetAction) + (*in).DeepCopyInto(*out) + } + if in.TCPSocket != nil { + in, out := &in.TCPSocket, &out.TCPSocket + *out = new(RadixProbeTCPSocketAction) + (*in).DeepCopyInto(*out) + } + if in.GRPC != nil { + in, out := &in.GRPC, &out.GRPC + *out = new(RadixProbeGRPCAction) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixProbeHandler. +func (in *RadixProbeHandler) DeepCopy() *RadixProbeHandler { + if in == nil { + return nil + } + out := new(RadixProbeHandler) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RadixProbeTCPSocketAction) DeepCopyInto(out *RadixProbeTCPSocketAction) { + *out = *in + if in.Port != nil { + in, out := &in.Port, &out.Port + *out = new(int32) + **out = **in + } + if in.Host != nil { + in, out := &in.Host, &out.Host + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RadixProbeTCPSocketAction. +func (in *RadixProbeTCPSocketAction) DeepCopy() *RadixProbeTCPSocketAction { + if in == nil { + return nil + } + out := new(RadixProbeTCPSocketAction) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixPromoteSpec) DeepCopyInto(out *RadixPromoteSpec) { *out = *in diff --git a/pkg/apis/radixvalidators/errors.go b/pkg/apis/radixvalidators/errors.go index a2ab58f8b..ccc829e89 100644 --- a/pkg/apis/radixvalidators/errors.go +++ b/pkg/apis/radixvalidators/errors.go @@ -183,7 +183,7 @@ func ComponentForDNSExternalAliasIsNotMarkedAsPublicErrorWithMessage(component s // ComponentHasInvalidHealthChecks Component has invalid health checks func ComponentHasInvalidHealthChecks(component string, probeName string, err error) error { - return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health checks %s: %w", probeName, component, err) + return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health checks %s: %s", probeName, component, err.Error()) } // EnvironmentReferencedByComponentDoesNotExistErrorWithMessage Environment does not exists diff --git a/pkg/apis/radixvalidators/validate_ra.go b/pkg/apis/radixvalidators/validate_ra.go index 20d986cfa..99929d179 100644 --- a/pkg/apis/radixvalidators/validate_ra.go +++ b/pkg/apis/radixvalidators/validate_ra.go @@ -838,13 +838,13 @@ func validateHealthChecks(app *radixv1.RadixApplication) error { continue } - if err := validateProbe(component.HealthChecks.StartupProbe); err != nil { + if err := validateProbe(component.HealthChecks.StartupProbe, "StartupProbe"); err != nil { errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "StartupProbe", err)) } - if err := validateProbe(component.HealthChecks.ReadinessProbe); err != nil { + if err := validateProbe(component.HealthChecks.ReadinessProbe, "ReadinessProbe"); err != nil { errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "ReadinessProbe", err)) } - if err := validateProbe(component.HealthChecks.LivenessProbe); err != nil { + if err := validateProbe(component.HealthChecks.LivenessProbe, "LivenessProbe"); err != nil { errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "LivenessProbe", err)) } } @@ -852,18 +852,38 @@ func validateHealthChecks(app *radixv1.RadixApplication) error { return errors.Join(errs...) } -func validateProbe(probe *corev1.Probe) error { +func validateProbe(probe *radixv1.RadixProbe, name string) error { if probe == nil { return nil } + var errs []error if (probe.HTTPGet != nil && (probe.TCPSocket != nil || probe.Exec != nil)) || (probe.TCPSocket != nil && (probe.HTTPGet != nil || probe.Exec != nil)) || (probe.Exec != nil && (probe.HTTPGet != nil || probe.TCPSocket != nil)) { - return fmt.Errorf("HTTPGet, TCPSocket and Exec are mutually exclusive") + errs = append(errs, fmt.Errorf("HTTPGet, TCPSocket and Exec are mutually exclusive")) } - return nil + if probe.InitialDelaySeconds != nil && *probe.InitialDelaySeconds < 1 { + errs = append(errs, fmt.Errorf("InitialDelaySeconds must be equal or greater than 1")) + } + + if probe.TimeoutSeconds != nil && *probe.TimeoutSeconds < 1 { + errs = append(errs, fmt.Errorf("TimeoutSeconds must be equal or greater than 1")) + } + + if probe.PeriodSeconds != nil && *probe.PeriodSeconds < 1 { + errs = append(errs, fmt.Errorf("PeriodSeconds must be equal or greater than 1")) + } + + if probe.SuccessThreshold != nil && *probe.SuccessThreshold < 1 { + errs = append(errs, fmt.Errorf("SuccessThreshold must be equal or greater than 1")) + } + if (name == "StartupProbe" || name == "LivenessProbe") && probe.SuccessThreshold != nil && *probe.SuccessThreshold != 1 { + errs = append(errs, fmt.Errorf("SuccessThreshold must be equal to 1 for Liveness and Startup probes")) + } + + return errors.Join(errs...) } func getEnvVarNameMap(componentEnvVarsMap radixv1.EnvVarsMap, envsEnvVarsMap radixv1.EnvVarsMap) map[string]bool { diff --git a/pkg/apis/radixvalidators/validate_ra_test.go b/pkg/apis/radixvalidators/validate_ra_test.go index 2c16f3878..fa160e4d1 100644 --- a/pkg/apis/radixvalidators/validate_ra_test.go +++ b/pkg/apis/radixvalidators/validate_ra_test.go @@ -78,14 +78,6 @@ func Test_application_name_casing_is_validated(t *testing.T) { } } -func Test_Invalid_HealthChecks(t *testing.T) { - t.Errorf("Not implemented") -} - -func Test_Valid_HealthChecks(t *testing.T) { - t.Errorf("Not implemented") -} - func Test_invalid_ra(t *testing.T) { validRAFirstComponentName := "app" validRAFirstJobName := "job" @@ -614,6 +606,47 @@ func Test_invalid_ra(t *testing.T) { rr.Spec.Components[0].EnvironmentConfig[0].Authentication.OAuth2.Cookie.Expire = "30m" rr.Spec.Components[0].EnvironmentConfig[0].Authentication.OAuth2.Cookie.Refresh = "1h" }}, + {"no healthchecks are valid", nil, func(rr *radixv1.RadixApplication) { + rr.Spec.Components[0].HealthChecks = nil + }}, + {"custom healthchecks are valid", nil, func(rr *radixv1.RadixApplication) { + rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ + LivenessProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}}, + }, + ReadinessProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}}, + }, + StartupProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}}, + }, + } + }}, + {"invalid healthchecks are invalid", radixvalidators.ErrComponentHasInvalidHealthCheck, func(rr *radixv1.RadixApplication) { + rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ + LivenessProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{ + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}, + Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}, + TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}, + }, + }, + ReadinessProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{ + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}, + Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}, + TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}, + }, + }, + StartupProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{ + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}, + Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}, + TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}, + }, + }, + } + }}, {"duplicate name in job/component boundary", radixvalidators.DuplicateComponentOrJobNameErrorWithMessage([]string{validRAFirstComponentName}), func(ra *radixv1.RadixApplication) { job := *ra.Spec.Jobs[0].DeepCopy() job.Name = validRAFirstComponentName diff --git a/pkg/apis/utils/applicationcomponent_builder.go b/pkg/apis/utils/applicationcomponent_builder.go index 15aab5609..dae81bd3b 100644 --- a/pkg/apis/utils/applicationcomponent_builder.go +++ b/pkg/apis/utils/applicationcomponent_builder.go @@ -2,7 +2,6 @@ package utils import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - corev1 "k8s.io/api/core/v1" ) // RadixApplicationComponentBuilder Handles construction of RA component @@ -11,7 +10,7 @@ type RadixApplicationComponentBuilder interface { WithAlwaysPullImageOnDeploy(bool) RadixApplicationComponentBuilder WithSourceFolder(string) RadixApplicationComponentBuilder WithDockerfileName(string) RadixApplicationComponentBuilder - WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixApplicationComponentBuilder + WithHealthChecks(startupProbe, readynessProbe, livenessProbe *radixv1.RadixProbe) RadixApplicationComponentBuilder WithImage(string) RadixApplicationComponentBuilder WithImageTagName(imageTagName string) RadixApplicationComponentBuilder WithPublic(bool) RadixApplicationComponentBuilder // Deprecated: For backwards compatibility WithPublic is still supported, new code should use WithPublicPort instead @@ -79,7 +78,7 @@ func (rcb *radixApplicationComponentBuilder) WithAlwaysPullImageOnDeploy(val boo return rcb } -func (rcb *radixApplicationComponentBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixApplicationComponentBuilder { +func (rcb *radixApplicationComponentBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *radixv1.RadixProbe) RadixApplicationComponentBuilder { rcb.healtChecks = &radixv1.RadixHealthChecks{ LivenessProbe: livenessProbe, ReadinessProbe: readynessProbe, diff --git a/pkg/apis/utils/componentenvironment_builder.go b/pkg/apis/utils/componentenvironment_builder.go index 14e0c7f22..c53c56e55 100644 --- a/pkg/apis/utils/componentenvironment_builder.go +++ b/pkg/apis/utils/componentenvironment_builder.go @@ -2,7 +2,6 @@ package utils import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - corev1 "k8s.io/api/core/v1" ) // RadixEnvironmentConfigBuilder Handles construction of RA component environment @@ -10,7 +9,7 @@ type RadixEnvironmentConfigBuilder interface { WithEnvironment(string) RadixEnvironmentConfigBuilder WithSourceFolder(string) RadixEnvironmentConfigBuilder WithDockerfileName(string) RadixEnvironmentConfigBuilder - WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixEnvironmentConfigBuilder + WithHealthChecks(startupProbe, readynessProbe, livenessProbe *radixv1.RadixProbe) RadixEnvironmentConfigBuilder WithImage(string) RadixEnvironmentConfigBuilder WithReplicas(*int) RadixEnvironmentConfigBuilder WithEnvironmentVariable(string, string) RadixEnvironmentConfigBuilder @@ -68,7 +67,7 @@ func (ceb *radixEnvironmentConfigBuilder) WithResource(request map[string]string return ceb } -func (ceb *radixEnvironmentConfigBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) RadixEnvironmentConfigBuilder { +func (ceb *radixEnvironmentConfigBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *radixv1.RadixProbe) RadixEnvironmentConfigBuilder { ceb.healtChecks = &radixv1.RadixHealthChecks{ LivenessProbe: livenessProbe, ReadinessProbe: readynessProbe, diff --git a/pkg/apis/utils/deploymentcomponent_builder.go b/pkg/apis/utils/deploymentcomponent_builder.go index c77728952..984c3ea62 100644 --- a/pkg/apis/utils/deploymentcomponent_builder.go +++ b/pkg/apis/utils/deploymentcomponent_builder.go @@ -2,7 +2,6 @@ package utils import ( v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - corev1 "k8s.io/api/core/v1" ) // DeployComponentBuilder Handles construction of RD component @@ -24,7 +23,7 @@ type DeployComponentBuilder interface { WithResourceRequestsOnly(map[string]string) DeployComponentBuilder WithResource(map[string]string, map[string]string) DeployComponentBuilder WithVolumeMounts(...v1.RadixVolumeMount) DeployComponentBuilder - WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) DeployComponentBuilder + WithHealthChecks(startupProbe, readynessProbe, livenessProbe *v1.RadixProbe) DeployComponentBuilder WithNodeGpu(gpu string) DeployComponentBuilder WithNodeGpuCount(gpuCount string) DeployComponentBuilder WithIngressConfiguration(...string) DeployComponentBuilder @@ -80,7 +79,7 @@ func (dcb *deployComponentBuilder) WithVolumeMounts(volumeMounts ...v1.RadixVolu return dcb } -func (dcb *deployComponentBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *corev1.Probe) DeployComponentBuilder { +func (dcb *deployComponentBuilder) WithHealthChecks(startupProbe, readynessProbe, livenessProbe *v1.RadixProbe) DeployComponentBuilder { dcb.healtChecks = &v1.RadixHealthChecks{ LivenessProbe: livenessProbe, ReadinessProbe: readynessProbe, From 813d9ef4b40bbd0dd0b53e0feab1153f658198c3 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Dec 2024 14:45:09 +0100 Subject: [PATCH 09/15] bump chart --- charts/radix-operator/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index 73ff60c1c..1c982b056 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.46.4 -appVersion: 1.66.4 +version: 1.47.0 +appVersion: 1.67.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: From b80eeae16366407cb0125e8c17d242c0ab1474cc Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Dec 2024 14:54:07 +0100 Subject: [PATCH 10/15] fix correct error component name --- pkg/apis/radixvalidators/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/radixvalidators/errors.go b/pkg/apis/radixvalidators/errors.go index ccc829e89..88a768101 100644 --- a/pkg/apis/radixvalidators/errors.go +++ b/pkg/apis/radixvalidators/errors.go @@ -183,7 +183,7 @@ func ComponentForDNSExternalAliasIsNotMarkedAsPublicErrorWithMessage(component s // ComponentHasInvalidHealthChecks Component has invalid health checks func ComponentHasInvalidHealthChecks(component string, probeName string, err error) error { - return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health checks %s: %s", probeName, component, err.Error()) + return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health checks %s: %s", component, probeName, err.Error()) } // EnvironmentReferencedByComponentDoesNotExistErrorWithMessage Environment does not exists From 4ced5b65a2bdd375116fb9dc9396fc65e420c98d Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 4 Dec 2024 14:57:17 +0100 Subject: [PATCH 11/15] fix correct error component name --- pkg/apis/radixvalidators/errors.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/apis/radixvalidators/errors.go b/pkg/apis/radixvalidators/errors.go index 88a768101..5350fcd45 100644 --- a/pkg/apis/radixvalidators/errors.go +++ b/pkg/apis/radixvalidators/errors.go @@ -183,7 +183,7 @@ func ComponentForDNSExternalAliasIsNotMarkedAsPublicErrorWithMessage(component s // ComponentHasInvalidHealthChecks Component has invalid health checks func ComponentHasInvalidHealthChecks(component string, probeName string, err error) error { - return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health checks %s: %s", component, probeName, err.Error()) + return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health check %s: %s", component, probeName, err.Error()) } // EnvironmentReferencedByComponentDoesNotExistErrorWithMessage Environment does not exists From 8dc3a7f64de0ea1a8061556bbf99bdd6e51ce278 Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 10 Dec 2024 14:55:19 +0100 Subject: [PATCH 12/15] simplify structures --- pkg/apis/deployment/kubedeployment_test.go | 14 +-- pkg/apis/deployment/radixcomponent_test.go | 14 +-- pkg/apis/radix/v1/radixapptypes.go | 22 ----- pkg/apis/radix/v1/radixhealthchecktypes.go | 92 ++++++++++---------- pkg/apis/radix/v1/zz_generated.deepcopy.go | 57 +----------- pkg/apis/radixvalidators/validate_ra.go | 42 ++++----- pkg/apis/radixvalidators/validate_ra_test.go | 16 ++-- 7 files changed, 85 insertions(+), 172 deletions(-) diff --git a/pkg/apis/deployment/kubedeployment_test.go b/pkg/apis/deployment/kubedeployment_test.go index 3847a4f3b..54c694fa2 100644 --- a/pkg/apis/deployment/kubedeployment_test.go +++ b/pkg/apis/deployment/kubedeployment_test.go @@ -57,21 +57,21 @@ func TestComponentWithCustomHealthChecks(t *testing.T) { createProbe := func(handler v1.RadixProbeHandler, seconds int32) *v1.RadixProbe { return &v1.RadixProbe{ RadixProbeHandler: handler, - InitialDelaySeconds: pointers.Ptr(seconds), - TimeoutSeconds: pointers.Ptr(seconds + 1), - PeriodSeconds: pointers.Ptr(seconds + 2), - SuccessThreshold: pointers.Ptr(seconds + 3), - FailureThreshold: pointers.Ptr(seconds + 4), + InitialDelaySeconds: seconds, + TimeoutSeconds: seconds + 1, + PeriodSeconds: seconds + 2, + SuccessThreshold: seconds + 3, + FailureThreshold: seconds + 4, // TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), } } readynessProbe := createProbe(v1.RadixProbeHandler{HTTPGet: &v1.RadixProbeHTTPGetAction{ - Port: pointers.Ptr[int32](5000), + Port: 5000, }}, 10) livenessProbe := createProbe(v1.RadixProbeHandler{TCPSocket: &v1.RadixProbeTCPSocketAction{ - Port: pointers.Ptr[int32](5000), + Port: 5000, }}, 20) startuProbe := createProbe(v1.RadixProbeHandler{Exec: &v1.RadixProbeExecAction{ Command: []string{"echo", "hello"}, diff --git a/pkg/apis/deployment/radixcomponent_test.go b/pkg/apis/deployment/radixcomponent_test.go index bde0275cf..f787646c7 100644 --- a/pkg/apis/deployment/radixcomponent_test.go +++ b/pkg/apis/deployment/radixcomponent_test.go @@ -1052,18 +1052,18 @@ func Test_GetRadixComponents_CustomHealthChecks(t *testing.T) { createProbe := func(handler radixv1.RadixProbeHandler, seconds int32) *radixv1.RadixProbe { return &radixv1.RadixProbe{ RadixProbeHandler: handler, - InitialDelaySeconds: &seconds, - TimeoutSeconds: pointers.Ptr(seconds + 1), - PeriodSeconds: pointers.Ptr(seconds + 2), - SuccessThreshold: pointers.Ptr(seconds + 3), - FailureThreshold: pointers.Ptr(seconds + 4), + InitialDelaySeconds: seconds, + TimeoutSeconds: seconds + 1, + PeriodSeconds: seconds + 2, + SuccessThreshold: seconds + 3, + FailureThreshold: seconds + 4, // TerminationGracePeriodSeconds: pointers.Ptr(int64(seconds + 5)), } } - httpProbe := radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz"), Scheme: pointers.Ptr(corev1.URISchemeHTTP)}} + httpProbe := radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz", Scheme: corev1.URISchemeHTTP}} execProbe := radixv1.RadixProbeHandler{Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz /healthz"}}} - tcpProbe := radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](8000)}} + tcpProbe := radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: 8000}} testCases := []struct { description string diff --git a/pkg/apis/radix/v1/radixapptypes.go b/pkg/apis/radix/v1/radixapptypes.go index 70f27a040..7d604d68b 100644 --- a/pkg/apis/radix/v1/radixapptypes.go +++ b/pkg/apis/radix/v1/radixapptypes.go @@ -815,28 +815,6 @@ type RadixJobComponentPayload struct { Path string `json:"path"` } -type RadixHealthChecks struct { - // Periodic probe of container liveness. - // Container will be restarted if the probe fails. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // +optional - LivenessProbe *RadixProbe `json:"livenessProbe,omitempty"` - // Periodic probe of container service readiness. - // Container will be removed from service endpoints if the probe fails. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // Defaults to TCP Probe against the first listed port - // +optional - ReadinessProbe *RadixProbe `json:"readinessProbe,omitempty"` - // StartupProbe indicates that the Pod has successfully initialized. - // If specified, no other probes are executed until this completes successfully. - // If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. - // This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, - // when it might take a long time to load data or warm a cache, than during steady-state operation. - // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes - // +optional - StartupProbe *RadixProbe `json:"startupProbe,omitempty"` -} - // PrivateImageHubEntries defines authentication information for private image registries. type PrivateImageHubEntries map[string]*RadixPrivateImageHubCredential diff --git a/pkg/apis/radix/v1/radixhealthchecktypes.go b/pkg/apis/radix/v1/radixhealthchecktypes.go index 1ad2460fd..d8e67ece4 100644 --- a/pkg/apis/radix/v1/radixhealthchecktypes.go +++ b/pkg/apis/radix/v1/radixhealthchecktypes.go @@ -1,11 +1,32 @@ package v1 import ( - "github.com/equinor/radix-common/utils/pointers" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" ) +type RadixHealthChecks struct { + // Periodic probe of container liveness. + // Container will be restarted if the probe fails. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + LivenessProbe *RadixProbe `json:"livenessProbe,omitempty"` + // Periodic probe of container service readiness. + // Container will be removed from service endpoints if the probe fails. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // Defaults to TCP Probe against the first listed port + // +optional + ReadinessProbe *RadixProbe `json:"readinessProbe,omitempty"` + // StartupProbe indicates that the Pod has successfully initialized. + // If specified, no other probes are executed until this completes successfully. + // If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. + // This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, + // when it might take a long time to load data or warm a cache, than during steady-state operation. + // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes + // +optional + StartupProbe *RadixProbe `json:"startupProbe,omitempty"` +} + // RadixProbe describes a health check to be performed against a container to determine whether it is // alive or ready to receive traffic. type RadixProbe struct { @@ -14,42 +35,31 @@ type RadixProbe struct { // Number of seconds after the container has started before liveness probes are initiated. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes // +optional - InitialDelaySeconds *int32 `json:"initialDelaySeconds,omitempty"` + InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"` // Number of seconds after which the probe times out. // More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes // +kubebuilder:validation:Minimum=1 // +default=1 // +optional - TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"` + TimeoutSeconds int32 `json:"timeoutSeconds,omitempty"` // How often (in seconds) to perform the probe. // +kubebuilder:validation:Minimum=1 // +default=10 // +optional - PeriodSeconds *int32 `json:"periodSeconds,omitempty"` + PeriodSeconds int32 `json:"periodSeconds,omitempty"` // Minimum consecutive successes for the probe to be considered successful after having failed. // Must be 1 for liveness and startup. // +kubebuilder:validation:Minimum=1 // +default=1 // +optional - SuccessThreshold *int32 `json:"successThreshold,omitempty"` + SuccessThreshold int32 `json:"successThreshold,omitempty"` // Minimum consecutive failures for the probe to be considered failed after having succeeded. // +kubebuilder:validation:Minimum=1 // +default=3 // +optional - FailureThreshold *int32 `json:"failureThreshold,omitempty"` - - // // Optional duration in seconds the pod needs to terminate gracefully upon probe failure. - // // The grace period is the duration in seconds after the processes running in the pod are sent - // // a termination signal and the time when the processes are forcibly halted with a kill signal. - // // Set this value longer than the expected cleanup time for your process. - // // If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this - // // value overrides the value provided by the pod spec. - // // Value must be non-negative integer. The value zero indicates stop immediately via - // // the kill signal (no opportunity to shut down). - // // This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. - // // +kubebuilder:validation:Minimum=1 - // // +default=30 - // // +optional + FailureThreshold int32 `json:"failureThreshold,omitempty"` + + // Todo: This is a beta property that we might want to take in in the future // TerminationGracePeriodSeconds *int64 `json:"terminationGracePeriodSeconds,omitempty"` } @@ -60,12 +70,11 @@ func (rp *RadixProbe) MapToCoreProbe() *corev1.Probe { return &corev1.Probe{ ProbeHandler: rp.RadixProbeHandler.MapToCoreProbe(), - InitialDelaySeconds: pointers.Val(rp.InitialDelaySeconds), - TimeoutSeconds: pointers.Val(rp.TimeoutSeconds), - PeriodSeconds: pointers.Val(rp.PeriodSeconds), - SuccessThreshold: pointers.Val(rp.SuccessThreshold), - FailureThreshold: pointers.Val(rp.FailureThreshold), - // TerminationGracePeriodSeconds: rp.TerminationGracePeriodSeconds, + InitialDelaySeconds: rp.InitialDelaySeconds, + TimeoutSeconds: rp.TimeoutSeconds, + PeriodSeconds: rp.PeriodSeconds, + SuccessThreshold: rp.SuccessThreshold, + FailureThreshold: rp.FailureThreshold, } } @@ -95,20 +104,20 @@ func (p RadixProbeHandler) MapToCoreProbe() corev1.ProbeHandler { type RadixProbeHTTPGetAction struct { // Path to access on the HTTP server. // +optional - Path *string `json:"path,omitempty"` + Path string `json:"path,omitempty"` // port number to access on the container. // +kubebuilder:validation:Minimum=1 // +kubebuilder:validation:Maximum=65535 - Port *int32 `json:"port"` + Port int32 `json:"port"` // Host name to connect to, defaults to the pod IP. You probably want to set // "Host" in httpHeaders instead. // +optional - Host *string `json:"host,omitempty"` + Host string `json:"host,omitempty"` // Scheme to use for connecting to the host. // Defaults to HTTP. // +optional // +kubebuilder:validation:Enum=HTTPS;HTTP - Scheme *corev1.URIScheme `json:"scheme,omitempty"` + Scheme corev1.URIScheme `json:"scheme,omitempty"` // Custom headers to set in the request. HTTP allows repeated headers. // +optional // +listType=atomic @@ -120,16 +129,11 @@ func (a *RadixProbeHTTPGetAction) MapToCoreProbe() *corev1.HTTPGetAction { return nil } - var port intstr.IntOrString - if a.Port != nil { - port = intstr.FromInt32(*a.Port) - } - return &corev1.HTTPGetAction{ - Path: pointers.Val(a.Path), - Port: port, - Host: pointers.Val(a.Host), - Scheme: pointers.Val(a.Scheme), + Path: a.Path, + Port: intstr.FromInt32(a.Port), + Host: a.Host, + Scheme: a.Scheme, HTTPHeaders: a.HTTPHeaders, } } @@ -161,24 +165,20 @@ type RadixProbeTCPSocketAction struct { // port number to access on the container. // +kubebuilder:validation:Minimum=1 // +kubebuilder:validation:Maximum=65535 - Port *int32 `json:"port"` + Port int32 `json:"port"` // Optional: Host name to connect to, defaults to the pod IP. // +optional - Host *string `json:"host,omitempty"` + Host string `json:"host,omitempty"` } func (a *RadixProbeTCPSocketAction) MapToCoreProbe() *corev1.TCPSocketAction { if a == nil { return nil } - var port intstr.IntOrString - if a.Port != nil { - port = intstr.FromInt32(*a.Port) - } return &corev1.TCPSocketAction{ - Port: port, - Host: pointers.Val(a.Host), + Port: intstr.FromInt32(a.Port), + Host: a.Host, } } diff --git a/pkg/apis/radix/v1/zz_generated.deepcopy.go b/pkg/apis/radix/v1/zz_generated.deepcopy.go index af753a6e4..6205a61b3 100644 --- a/pkg/apis/radix/v1/zz_generated.deepcopy.go +++ b/pkg/apis/radix/v1/zz_generated.deepcopy.go @@ -2846,31 +2846,6 @@ func (in *RadixPrivateImageHubCredential) DeepCopy() *RadixPrivateImageHubCreden func (in *RadixProbe) DeepCopyInto(out *RadixProbe) { *out = *in in.RadixProbeHandler.DeepCopyInto(&out.RadixProbeHandler) - if in.InitialDelaySeconds != nil { - in, out := &in.InitialDelaySeconds, &out.InitialDelaySeconds - *out = new(int32) - **out = **in - } - if in.TimeoutSeconds != nil { - in, out := &in.TimeoutSeconds, &out.TimeoutSeconds - *out = new(int32) - **out = **in - } - if in.PeriodSeconds != nil { - in, out := &in.PeriodSeconds, &out.PeriodSeconds - *out = new(int32) - **out = **in - } - if in.SuccessThreshold != nil { - in, out := &in.SuccessThreshold, &out.SuccessThreshold - *out = new(int32) - **out = **in - } - if in.FailureThreshold != nil { - in, out := &in.FailureThreshold, &out.FailureThreshold - *out = new(int32) - **out = **in - } return } @@ -2929,26 +2904,6 @@ func (in *RadixProbeGRPCAction) DeepCopy() *RadixProbeGRPCAction { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixProbeHTTPGetAction) DeepCopyInto(out *RadixProbeHTTPGetAction) { *out = *in - if in.Path != nil { - in, out := &in.Path, &out.Path - *out = new(string) - **out = **in - } - if in.Port != nil { - in, out := &in.Port, &out.Port - *out = new(int32) - **out = **in - } - if in.Host != nil { - in, out := &in.Host, &out.Host - *out = new(string) - **out = **in - } - if in.Scheme != nil { - in, out := &in.Scheme, &out.Scheme - *out = new(corev1.URIScheme) - **out = **in - } if in.HTTPHeaders != nil { in, out := &in.HTTPHeaders, &out.HTTPHeaders *out = make([]corev1.HTTPHeader, len(*in)) @@ -2983,7 +2938,7 @@ func (in *RadixProbeHandler) DeepCopyInto(out *RadixProbeHandler) { if in.TCPSocket != nil { in, out := &in.TCPSocket, &out.TCPSocket *out = new(RadixProbeTCPSocketAction) - (*in).DeepCopyInto(*out) + **out = **in } if in.GRPC != nil { in, out := &in.GRPC, &out.GRPC @@ -3006,16 +2961,6 @@ func (in *RadixProbeHandler) DeepCopy() *RadixProbeHandler { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RadixProbeTCPSocketAction) DeepCopyInto(out *RadixProbeTCPSocketAction) { *out = *in - if in.Port != nil { - in, out := &in.Port, &out.Port - *out = new(int32) - **out = **in - } - if in.Host != nil { - in, out := &in.Host, &out.Host - *out = new(string) - **out = **in - } return } diff --git a/pkg/apis/radixvalidators/validate_ra.go b/pkg/apis/radixvalidators/validate_ra.go index 99929d179..304a8591e 100644 --- a/pkg/apis/radixvalidators/validate_ra.go +++ b/pkg/apis/radixvalidators/validate_ra.go @@ -838,52 +838,42 @@ func validateHealthChecks(app *radixv1.RadixApplication) error { continue } - if err := validateProbe(component.HealthChecks.StartupProbe, "StartupProbe"); err != nil { + if err := validateProbe(component.HealthChecks.StartupProbe); err != nil { errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "StartupProbe", err)) } - if err := validateProbe(component.HealthChecks.ReadinessProbe, "ReadinessProbe"); err != nil { + if err := validateProbe(component.HealthChecks.ReadinessProbe); err != nil { errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "ReadinessProbe", err)) } - if err := validateProbe(component.HealthChecks.LivenessProbe, "LivenessProbe"); err != nil { + if err := validateProbe(component.HealthChecks.LivenessProbe); err != nil { errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "LivenessProbe", err)) } + + // SuccessTreshold must be 0 (unset) or 1 for Startup Probe + if component.HealthChecks.StartupProbe != nil && component.HealthChecks.StartupProbe.SuccessThreshold > 1 { + errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "StartupProbe", fmt.Errorf("SuccessThreshold must be equal to 1"))) + } + + // SuccessTreshold must be 0 (unset) or 1 for Startup Probe + if component.HealthChecks.LivenessProbe != nil && component.HealthChecks.LivenessProbe.SuccessThreshold > 1 { + errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "LivenessProbe", fmt.Errorf("SuccessThreshold must be equal to 1"))) + } } return errors.Join(errs...) } -func validateProbe(probe *radixv1.RadixProbe, name string) error { +func validateProbe(probe *radixv1.RadixProbe) error { if probe == nil { return nil } - var errs []error if (probe.HTTPGet != nil && (probe.TCPSocket != nil || probe.Exec != nil)) || (probe.TCPSocket != nil && (probe.HTTPGet != nil || probe.Exec != nil)) || (probe.Exec != nil && (probe.HTTPGet != nil || probe.TCPSocket != nil)) { - errs = append(errs, fmt.Errorf("HTTPGet, TCPSocket and Exec are mutually exclusive")) - } - - if probe.InitialDelaySeconds != nil && *probe.InitialDelaySeconds < 1 { - errs = append(errs, fmt.Errorf("InitialDelaySeconds must be equal or greater than 1")) - } - - if probe.TimeoutSeconds != nil && *probe.TimeoutSeconds < 1 { - errs = append(errs, fmt.Errorf("TimeoutSeconds must be equal or greater than 1")) - } - - if probe.PeriodSeconds != nil && *probe.PeriodSeconds < 1 { - errs = append(errs, fmt.Errorf("PeriodSeconds must be equal or greater than 1")) - } - - if probe.SuccessThreshold != nil && *probe.SuccessThreshold < 1 { - errs = append(errs, fmt.Errorf("SuccessThreshold must be equal or greater than 1")) - } - if (name == "StartupProbe" || name == "LivenessProbe") && probe.SuccessThreshold != nil && *probe.SuccessThreshold != 1 { - errs = append(errs, fmt.Errorf("SuccessThreshold must be equal to 1 for Liveness and Startup probes")) + return fmt.Errorf("HTTPGet, TCPSocket and Exec are mutually exclusive") } - return errors.Join(errs...) + return nil } func getEnvVarNameMap(componentEnvVarsMap radixv1.EnvVarsMap, envsEnvVarsMap radixv1.EnvVarsMap) map[string]bool { diff --git a/pkg/apis/radixvalidators/validate_ra_test.go b/pkg/apis/radixvalidators/validate_ra_test.go index fa160e4d1..afc728ce0 100644 --- a/pkg/apis/radixvalidators/validate_ra_test.go +++ b/pkg/apis/radixvalidators/validate_ra_test.go @@ -612,13 +612,13 @@ func Test_invalid_ra(t *testing.T) { {"custom healthchecks are valid", nil, func(rr *radixv1.RadixApplication) { rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ LivenessProbe: &radixv1.RadixProbe{ - RadixProbeHandler: radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}}, + RadixProbeHandler: radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz"}}, }, ReadinessProbe: &radixv1.RadixProbe{ RadixProbeHandler: radixv1.RadixProbeHandler{Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}}, }, StartupProbe: &radixv1.RadixProbe{ - RadixProbeHandler: radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}}, + RadixProbeHandler: radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: 5000}}, }, } }}, @@ -626,23 +626,23 @@ func Test_invalid_ra(t *testing.T) { rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ LivenessProbe: &radixv1.RadixProbe{ RadixProbeHandler: radixv1.RadixProbeHandler{ - HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}, + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz"}, Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}, - TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}, + TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: 5000}, }, }, ReadinessProbe: &radixv1.RadixProbe{ RadixProbeHandler: radixv1.RadixProbeHandler{ - HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}, + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz"}, Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}, - TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}, + TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: 5000}, }, }, StartupProbe: &radixv1.RadixProbe{ RadixProbeHandler: radixv1.RadixProbeHandler{ - HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: pointers.Ptr[int32](5000), Path: pointers.Ptr("/healthz")}, + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz"}, Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}, - TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: pointers.Ptr[int32](5000)}, + TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: 5000}, }, }, } From 3e09af3ff9289888ced494bca9c9699f3de360fe Mon Sep 17 00:00:00 2001 From: Richard87 Date: Tue, 10 Dec 2024 15:55:04 +0100 Subject: [PATCH 13/15] bump charts --- charts/radix-operator/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/radix-operator/Chart.yaml b/charts/radix-operator/Chart.yaml index 1c982b056..0f91986ac 100644 --- a/charts/radix-operator/Chart.yaml +++ b/charts/radix-operator/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: radix-operator -version: 1.47.0 -appVersion: 1.67.0 +version: 1.48.0 +appVersion: 1.68.0 kubeVersion: ">=1.24.0" description: Radix Operator keywords: From 702e22727d87847847ee028a11493c286a16a75a Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 11 Dec 2024 14:01:38 +0100 Subject: [PATCH 14/15] simpler validateProbe, remove unneeded tests --- pkg/apis/radixvalidators/errors.go | 7 +- .../radixvalidators/testdata/radixconfig.yaml | 30 +++++++- pkg/apis/radixvalidators/validate_ra.go | 76 ++++++++++++------- pkg/apis/radixvalidators/validate_ra_test.go | 28 +++---- 4 files changed, 90 insertions(+), 51 deletions(-) diff --git a/pkg/apis/radixvalidators/errors.go b/pkg/apis/radixvalidators/errors.go index 7a3f6227f..c0a7f4a84 100644 --- a/pkg/apis/radixvalidators/errors.go +++ b/pkg/apis/radixvalidators/errors.go @@ -27,6 +27,8 @@ var ( ErrMemoryResourceRequirementFormat = errors.New("memory resource requirement format") ErrCPUResourceRequirementFormat = errors.New("cpu resource requirement format") ErrInvalidVerificationType = errors.New("invalid verification") + ErrInvalidHealthCheckProbe = errors.New("probe configuration error, only one action allowed") + ErrSuccessThresholdMustBeOne = errors.New("success threshold must be equal to one") ErrResourceRequestOverLimit = errors.New("resource request over limit") ErrInvalidResource = errors.New("invalid resource") ErrDuplicateExternalAlias = errors.New("duplicate external alias") @@ -182,11 +184,6 @@ func ComponentForDNSExternalAliasIsNotMarkedAsPublicErrorWithMessage(component s return errors.WithMessagef(ErrComponentForDNSExternalAliasIsNotMarkedAsPublic, "component %s referred to by dnsExternalAlias is not marked as public", component) } -// ComponentHasInvalidHealthChecks Component has invalid health checks -func ComponentHasInvalidHealthChecks(component string, probeName string, err error) error { - return errors.WithMessagef(ErrComponentHasInvalidHealthCheck, "component %s has invalid health check %s: %s", component, probeName, err.Error()) -} - // EnvironmentReferencedByComponentDoesNotExistErrorWithMessage Environment does not exists func EnvironmentReferencedByComponentDoesNotExistErrorWithMessage(environment, component string) error { return errors.WithMessagef(ErrEnvironmentReferencedByComponentDoesNotExist, "env %s refered to by component %s is not defined", environment, component) diff --git a/pkg/apis/radixvalidators/testdata/radixconfig.yaml b/pkg/apis/radixvalidators/testdata/radixconfig.yaml index b02dd0637..5a65b9b71 100644 --- a/pkg/apis/radixvalidators/testdata/radixconfig.yaml +++ b/pkg/apis/radixvalidators/testdata/radixconfig.yaml @@ -31,6 +31,20 @@ spec: clientId: 11111111-2222-BBBB-cccc-555555555555 runtime: architecture: arm64 + healthChecks: + startupProbe: + tcpSocket: + port: 8000 + livenessProbe: + tcpSocket: + port: 8000 + successThreshold: 1 + readinessProbe: + successThreshold: 1 + periodSeconds: 30 + httpGet: + port: 8000 + path: /healthz network: ingress: public: @@ -54,6 +68,20 @@ spec: refresh: 30m expire: 168h sameSite: "strict" + healthChecks: + startupProbe: + tcpSocket: + port: 8000 + livenessProbe: + tcpSocket: + port: 8000 + successThreshold: 1 + readinessProbe: + successThreshold: 1 + periodSeconds: 30 + httpGet: + port: 8000 + path: /healthz resources: limits: memory: "512Mi" @@ -216,4 +244,4 @@ spec: dnsAlias: - environment: prod component: app2 - alias: my-alias \ No newline at end of file + alias: my-alias diff --git a/pkg/apis/radixvalidators/validate_ra.go b/pkg/apis/radixvalidators/validate_ra.go index f5df2b918..78a1bfc3f 100644 --- a/pkg/apis/radixvalidators/validate_ra.go +++ b/pkg/apis/radixvalidators/validate_ra.go @@ -55,7 +55,6 @@ var ( validateHorizontalScalingConfigForRA, validateVolumeMountConfigForRA, ValidateNotificationsForRA, - validateHealthChecks, } ipOrCidrRegExp = regexp.MustCompile(`^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$`) @@ -332,6 +331,10 @@ func validateComponent(app *radixv1.RadixApplication, component radixv1.RadixCom errs = append(errs, fmt.Errorf("invalid network configuration: %w", err)) } + if err := validateHealthChecks(component.HealthChecks); err != nil { + errs = append(errs, fmt.Errorf("invalid health check configuration: %w", err)) + } + for _, environment := range component.EnvironmentConfig { if err := validateComponentEnvironment(app, component, environment); err != nil { errs = append(errs, fmt.Errorf("invalid configuration for environment %s: %w", environment.Environment, err)) @@ -373,6 +376,10 @@ func validateComponentEnvironment(app *radixv1.RadixApplication, component radix errs = append(errs, fmt.Errorf("invalid network configuration: %w", err)) } + if err := validateHealthChecks(environment.HealthChecks); err != nil { + errs = append(errs, fmt.Errorf("invalid health check configuration: %w", err)) + } + return errors.Join(errs...) } @@ -873,33 +880,31 @@ func validateRadixComponentSecrets(component radixv1.RadixCommonComponent, app * return validateConflictingEnvironmentAndSecretRefsNames(component, envsEnvVarsMap) } -func validateHealthChecks(app *radixv1.RadixApplication) error { - var errs []error +func validateHealthChecks(healthChecks *radixv1.RadixHealthChecks) error { + if healthChecks == nil { + return nil + } - for _, component := range app.Spec.Components { - if component.HealthChecks == nil { - continue - } + var errs []error - if err := validateProbe(component.HealthChecks.StartupProbe); err != nil { - errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "StartupProbe", err)) - } - if err := validateProbe(component.HealthChecks.ReadinessProbe); err != nil { - errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "ReadinessProbe", err)) - } - if err := validateProbe(component.HealthChecks.LivenessProbe); err != nil { - errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "LivenessProbe", err)) - } + if err := validateProbe(healthChecks.StartupProbe); err != nil { + errs = append(errs, fmt.Errorf("probe %s is invald: %w", "StartupProbe", err)) + } + if err := validateProbe(healthChecks.ReadinessProbe); err != nil { + errs = append(errs, fmt.Errorf("probe %s is invald: %w", "ReadinessProbe", err)) + } + if err := validateProbe(healthChecks.LivenessProbe); err != nil { + errs = append(errs, fmt.Errorf("probe %s is invald: %w", "LivenessProbe", err)) + } - // SuccessTreshold must be 0 (unset) or 1 for Startup Probe - if component.HealthChecks.StartupProbe != nil && component.HealthChecks.StartupProbe.SuccessThreshold > 1 { - errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "StartupProbe", fmt.Errorf("SuccessThreshold must be equal to 1"))) - } + // SuccessTreshold must be 0 (unset) or 1 for Startup Probe + if healthChecks.StartupProbe != nil && healthChecks.StartupProbe.SuccessThreshold > 1 { + errs = append(errs, fmt.Errorf("probe %s is invald: %w", "StartupProbe", ErrSuccessThresholdMustBeOne)) + } - // SuccessTreshold must be 0 (unset) or 1 for Startup Probe - if component.HealthChecks.LivenessProbe != nil && component.HealthChecks.LivenessProbe.SuccessThreshold > 1 { - errs = append(errs, ComponentHasInvalidHealthChecks(component.Name, "LivenessProbe", fmt.Errorf("SuccessThreshold must be equal to 1"))) - } + // SuccessTreshold must be 0 (unset) or 1 for Startup Probe + if healthChecks.LivenessProbe != nil && healthChecks.LivenessProbe.SuccessThreshold > 1 { + errs = append(errs, fmt.Errorf("probe %s is invald: %w", "LivenessProbe", ErrSuccessThresholdMustBeOne)) } return errors.Join(errs...) @@ -910,10 +915,25 @@ func validateProbe(probe *radixv1.RadixProbe) error { return nil } - if (probe.HTTPGet != nil && (probe.TCPSocket != nil || probe.Exec != nil)) || - (probe.TCPSocket != nil && (probe.HTTPGet != nil || probe.Exec != nil)) || - (probe.Exec != nil && (probe.HTTPGet != nil || probe.TCPSocket != nil)) { - return fmt.Errorf("HTTPGet, TCPSocket and Exec are mutually exclusive") + definedProbes := 0 + if probe.HTTPGet != nil { + definedProbes++ + } + + if probe.TCPSocket != nil { + definedProbes++ + } + + if probe.Exec != nil { + definedProbes++ + } + + if probe.GRPC != nil { + definedProbes++ + } + + if definedProbes > 1 { + return ErrInvalidHealthCheckProbe } return nil diff --git a/pkg/apis/radixvalidators/validate_ra_test.go b/pkg/apis/radixvalidators/validate_ra_test.go index ad2c4dce1..d65a6e903 100644 --- a/pkg/apis/radixvalidators/validate_ra_test.go +++ b/pkg/apis/radixvalidators/validate_ra_test.go @@ -606,23 +606,7 @@ func Test_invalid_ra(t *testing.T) { rr.Spec.Components[0].EnvironmentConfig[0].Authentication.OAuth2.Cookie.Expire = "30m" rr.Spec.Components[0].EnvironmentConfig[0].Authentication.OAuth2.Cookie.Refresh = "1h" }}, - {"no healthchecks are valid", nil, func(rr *radixv1.RadixApplication) { - rr.Spec.Components[0].HealthChecks = nil - }}, - {"custom healthchecks are valid", nil, func(rr *radixv1.RadixApplication) { - rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ - LivenessProbe: &radixv1.RadixProbe{ - RadixProbeHandler: radixv1.RadixProbeHandler{HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz"}}, - }, - ReadinessProbe: &radixv1.RadixProbe{ - RadixProbeHandler: radixv1.RadixProbeHandler{Exec: &radixv1.RadixProbeExecAction{Command: []string{"/bin/sh", "-c", "/healthz"}}}, - }, - StartupProbe: &radixv1.RadixProbe{ - RadixProbeHandler: radixv1.RadixProbeHandler{TCPSocket: &radixv1.RadixProbeTCPSocketAction{Port: 5000}}, - }, - } - }}, - {"invalid healthchecks are invalid", radixvalidators.ErrComponentHasInvalidHealthCheck, func(rr *radixv1.RadixApplication) { + {"invalid healthchecks are invalid", radixvalidators.ErrInvalidHealthCheckProbe, func(rr *radixv1.RadixApplication) { rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ LivenessProbe: &radixv1.RadixProbe{ RadixProbeHandler: radixv1.RadixProbeHandler{ @@ -647,6 +631,16 @@ func Test_invalid_ra(t *testing.T) { }, } }}, + {"invalid healthchecks are invalid", radixvalidators.ErrSuccessThresholdMustBeOne, func(rr *radixv1.RadixApplication) { + rr.Spec.Components[0].HealthChecks = &radixv1.RadixHealthChecks{ + LivenessProbe: &radixv1.RadixProbe{ + RadixProbeHandler: radixv1.RadixProbeHandler{ + HTTPGet: &radixv1.RadixProbeHTTPGetAction{Port: 5000, Path: "/healthz"}, + }, + SuccessThreshold: 5, + }, + } + }}, {"duplicate name in job/component boundary", radixvalidators.DuplicateComponentOrJobNameErrorWithMessage([]string{validRAFirstComponentName}), func(ra *radixv1.RadixApplication) { job := *ra.Spec.Jobs[0].DeepCopy() job.Name = validRAFirstComponentName From 9f815c7dd9b5323633f179237648e3807c141afe Mon Sep 17 00:00:00 2001 From: Richard87 Date: Wed, 11 Dec 2024 14:46:44 +0100 Subject: [PATCH 15/15] fix typo --- pkg/apis/radixvalidators/validate_ra.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/apis/radixvalidators/validate_ra.go b/pkg/apis/radixvalidators/validate_ra.go index 78a1bfc3f..8cff539fc 100644 --- a/pkg/apis/radixvalidators/validate_ra.go +++ b/pkg/apis/radixvalidators/validate_ra.go @@ -888,23 +888,23 @@ func validateHealthChecks(healthChecks *radixv1.RadixHealthChecks) error { var errs []error if err := validateProbe(healthChecks.StartupProbe); err != nil { - errs = append(errs, fmt.Errorf("probe %s is invald: %w", "StartupProbe", err)) + errs = append(errs, fmt.Errorf("probe StartupProbe is invalid: %w", err)) } if err := validateProbe(healthChecks.ReadinessProbe); err != nil { - errs = append(errs, fmt.Errorf("probe %s is invald: %w", "ReadinessProbe", err)) + errs = append(errs, fmt.Errorf("probe ReadinessProbe is invalid: %w", err)) } if err := validateProbe(healthChecks.LivenessProbe); err != nil { - errs = append(errs, fmt.Errorf("probe %s is invald: %w", "LivenessProbe", err)) + errs = append(errs, fmt.Errorf("probe LivenessProbe is invalid: %w", err)) } // SuccessTreshold must be 0 (unset) or 1 for Startup Probe if healthChecks.StartupProbe != nil && healthChecks.StartupProbe.SuccessThreshold > 1 { - errs = append(errs, fmt.Errorf("probe %s is invald: %w", "StartupProbe", ErrSuccessThresholdMustBeOne)) + errs = append(errs, fmt.Errorf("probe StartupProbe is invalid: %w", ErrSuccessThresholdMustBeOne)) } // SuccessTreshold must be 0 (unset) or 1 for Startup Probe if healthChecks.LivenessProbe != nil && healthChecks.LivenessProbe.SuccessThreshold > 1 { - errs = append(errs, fmt.Errorf("probe %s is invald: %w", "LivenessProbe", ErrSuccessThresholdMustBeOne)) + errs = append(errs, fmt.Errorf("probe LivenessProbe is invalid: %w", ErrSuccessThresholdMustBeOne)) } return errors.Join(errs...)