pkg/appgw/health_probes.go (219 lines of code) (raw):
// -------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// --------------------------------------------------------------------------------------------
package appgw
import (
"fmt"
"sort"
"strings"
n "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2021-03-01/network"
"github.com/Azure/go-autorest/autorest/to"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/klog/v2"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/annotations"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/brownfield"
"github.com/Azure/application-gateway-kubernetes-ingress/pkg/sorter"
)
func (c *appGwConfigBuilder) HealthProbesCollection(cbCtx *ConfigBuilderContext) error {
healthProbeCollection, _ := c.newProbesMap(cbCtx)
agicCreatedProbes := make([]n.ApplicationGatewayProbe, 0, len(healthProbeCollection))
for _, probe := range healthProbeCollection {
agicCreatedProbes = append(agicCreatedProbes, probe)
}
if cbCtx.EnvVariables.EnableBrownfieldDeployment {
er := brownfield.NewExistingResources(c.appGw, cbCtx.ProhibitedTargets, nil)
existingBlacklisted, existingNonBlacklisted := er.GetBlacklistedProbes()
brownfield.LogProbes(klog.V(3), existingBlacklisted, existingNonBlacklisted, agicCreatedProbes)
agicCreatedProbes = brownfield.MergeProbes(existingBlacklisted, agicCreatedProbes)
}
sort.Sort(sorter.ByHealthProbeName(agicCreatedProbes))
c.appGw.Probes = &agicCreatedProbes
return nil
}
func (c *appGwConfigBuilder) newProbesMap(cbCtx *ConfigBuilderContext) (map[string]n.ApplicationGatewayProbe, map[backendIdentifier]*n.ApplicationGatewayProbe) {
if c.mem.probesByName != nil && c.mem.probesByBackend != nil {
return *c.mem.probesByName, *c.mem.probesByBackend
}
healthProbeCollection := make(map[string]n.ApplicationGatewayProbe)
probesMap := make(map[backendIdentifier]*n.ApplicationGatewayProbe)
defaultHTTPProbe := defaultProbe(c.appGwIdentifier, n.ApplicationGatewayProtocolHTTP)
defaultHTTPSProbe := defaultProbe(c.appGwIdentifier, n.ApplicationGatewayProtocolHTTPS)
healthProbeCollection[*defaultHTTPProbe.Name] = defaultHTTPProbe
healthProbeCollection[*defaultHTTPSProbe.Name] = defaultHTTPSProbe
klog.V(3).Info("Created default HTTP probe ", *defaultHTTPProbe.Name)
klog.V(3).Info("Created default HTTPS probe ", *defaultHTTPProbe.Name)
for backendID := range c.newBackendIdsFiltered(cbCtx) {
probe := c.generateHealthProbe(backendID)
if probe != nil {
probesMap[backendID] = probe
healthProbeCollection[*probe.Name] = *probe
} else {
probesMap[backendID] = &defaultHTTPProbe
if protocol, _ := annotations.BackendProtocol(backendID.Ingress); protocol == annotations.HTTPS {
probesMap[backendID] = &defaultHTTPSProbe
}
}
klog.V(3).Infof("Created probe %s for ingress %s/%s at service %s", *probesMap[backendID].Name, backendID.Ingress.Namespace, backendID.Ingress.Name, backendID.serviceKey())
}
c.mem.probesByName = &healthProbeCollection
c.mem.probesByBackend = &probesMap
return healthProbeCollection, probesMap
}
func (c *appGwConfigBuilder) generateHealthProbe(backendID backendIdentifier) *n.ApplicationGatewayProbe {
// TODO(draychev): remove GetService
service := c.k8sContext.GetService(backendID.serviceKey())
if service == nil || backendID.Path == nil {
return nil
}
probe := defaultProbe(c.appGwIdentifier, n.ApplicationGatewayProtocolHTTP)
probe.Name = to.StringPtr(generateProbeName(backendID.Path.Backend.Service.Name, serviceBackendPortToStr(backendID.Path.Backend.Service.Port), backendID.Ingress))
probe.ID = to.StringPtr(c.appGwIdentifier.probeID(*probe.Name))
// set defaults
probe.Match = &n.ApplicationGatewayProbeHealthResponseMatch{}
probe.PickHostNameFromBackendHTTPSettings = to.BoolPtr(false)
probe.MinServers = to.Int32Ptr(0)
listenerID := generateListenerID(backendID.Ingress, backendID.Rule, n.ApplicationGatewayProtocolHTTP, nil, false)
hostName := listenerID.getHostNameForProbes()
if hostName != nil {
probe.Host = hostName
}
if hostName, err := annotations.BackendHostName(backendID.Ingress); err == nil {
probe.Host = to.StringPtr(hostName)
}
pathPrefix, err := annotations.BackendPathPrefix(backendID.Ingress)
if err == nil {
probe.Path = to.StringPtr(pathPrefix)
} else if backendID.Path != nil && len(backendID.Path.Path) != 0 {
probe.Path = to.StringPtr(backendID.Path.Path)
}
// check if backend is using port 443
if backendID.Backend.Service != nil {
if serviceBackendPortToStr(backendID.Backend.Service.Port) == "443" {
probe.Protocol = n.ApplicationGatewayProtocolHTTPS
} else if port, err := c.resolveBackendPort(backendID); err == nil && port == Port(443) {
probe.Protocol = n.ApplicationGatewayProtocolHTTPS
}
}
k8sProbeForServiceContainer := c.getProbeForServiceContainer(service, backendID)
if k8sProbeForServiceContainer != nil {
if len(k8sProbeForServiceContainer.HTTPGet.Host) != 0 {
probe.Host = to.StringPtr(k8sProbeForServiceContainer.HTTPGet.Host)
}
if len(k8sProbeForServiceContainer.HTTPGet.Path) != 0 {
probe.Path = to.StringPtr(k8sProbeForServiceContainer.HTTPGet.Path)
}
if len(k8sProbeForServiceContainer.HTTPGet.Port.String()) != 0 {
probe.Port = to.Int32Ptr(k8sProbeForServiceContainer.HTTPGet.Port.IntVal)
}
if k8sProbeForServiceContainer.HTTPGet.Scheme == v1.URISchemeHTTPS {
probe.Protocol = n.ApplicationGatewayProtocolHTTPS
}
// httpGet schema is default to Http if not specified, double check with the port in case for Https
if k8sProbeForServiceContainer.HTTPGet.Scheme == v1.URISchemeHTTP {
if k8sProbeForServiceContainer.HTTPGet.Port.IntVal == 443 {
probe.Protocol = n.ApplicationGatewayProtocolHTTPS
} else {
probe.Protocol = n.ApplicationGatewayProtocolHTTP
}
}
if k8sProbeForServiceContainer.PeriodSeconds != 0 {
probe.Interval = to.Int32Ptr(k8sProbeForServiceContainer.PeriodSeconds)
}
if k8sProbeForServiceContainer.TimeoutSeconds != 0 {
probe.Timeout = to.Int32Ptr(k8sProbeForServiceContainer.TimeoutSeconds)
}
if k8sProbeForServiceContainer.FailureThreshold != 0 {
// UnhealthyThreshold must be in range of 0 - 20, otherwise Application Gateway will not
// accept the configuration and all new pods will not be configured.
if k8sProbeForServiceContainer.FailureThreshold > 20 {
probe.UnhealthyThreshold = to.Int32Ptr(20)
} else {
probe.UnhealthyThreshold = to.Int32Ptr(k8sProbeForServiceContainer.FailureThreshold)
}
}
}
// backend protocol must match http settings protocol
backendProtocol, err := annotations.BackendProtocol(backendID.Ingress)
if err == nil && backendProtocol == annotations.HTTPS {
probe.Protocol = n.ApplicationGatewayProtocolHTTPS
} else if err == nil && backendProtocol == annotations.HTTP {
probe.Protocol = n.ApplicationGatewayProtocolHTTP
}
// override healthcheck probe host with host defined in annotation if exists
probeHost, err := annotations.HealthProbeHostName(backendID.Ingress)
if err == nil && probeHost != "" {
probe.Host = to.StringPtr(probeHost)
}
// override healthcheck probe target port with port defined in annotation if exists
probePort, err := annotations.HealthProbePort(backendID.Ingress)
if err == nil && probePort > 0 && probePort < 65536 {
probe.Port = to.Int32Ptr(probePort)
}
// override healthcheck probe path with path defined in annotation if exists
probePath, err := annotations.HealthProbePath(backendID.Ingress)
if err == nil && probePath != "" {
probe.Path = to.StringPtr(probePath)
}
if probe.Path != nil {
probe.Path = to.StringPtr(strings.TrimRight(*probe.Path, "*"))
}
// override healthcheck probe match status codes with ones defined in annotation if exists
probeStatuses, err := annotations.HealthProbeStatusCodes(backendID.Ingress)
if err == nil && len(probeStatuses) > 0 {
probe.Match.StatusCodes = &probeStatuses
}
// override healthcheck probe interval with value defined in annotation if exists
probeInterval, err := annotations.HealthProbeInterval(backendID.Ingress)
if err == nil && probeInterval > 0 {
probe.Interval = to.Int32Ptr(probeInterval)
}
// override healthcheck probe timeout with value defined in annotation if exists
probeTimeout, err := annotations.HealthProbeTimeout(backendID.Ingress)
if err == nil && probeTimeout > 0 {
probe.Timeout = to.Int32Ptr(probeTimeout)
}
// override healthcheck probe threshold with value defined in annotation if exists
probeThreshold, err := annotations.HealthProbeUnhealthyThreshold(backendID.Ingress)
if err == nil && probeThreshold > 0 {
probe.UnhealthyThreshold = to.Int32Ptr(probeThreshold)
}
// For V1 gateway, port property is not supported
if c.appGw.Sku.Tier == n.ApplicationGatewayTierStandard || c.appGw.Sku.Tier == n.ApplicationGatewayTierWAF {
probe.Port = nil
}
return &probe
}
func (c *appGwConfigBuilder) getProbeForServiceContainer(service *v1.Service, backendID backendIdentifier) *v1.Probe {
// find all the target ports used by the service
allPorts := make(map[int32]interface{})
for _, sp := range service.Spec.Ports {
if sp.Protocol != v1.ProtocolTCP {
continue
}
if backendID.Backend.Service != nil && (fmt.Sprint(sp.Port) == serviceBackendPortToStr(backendID.Backend.Service.Port) ||
sp.Name == serviceBackendPortToStr(backendID.Backend.Service.Port) ||
sp.TargetPort.String() == serviceBackendPortToStr(backendID.Backend.Service.Port)) {
// Matched a service port in the service
if sp.TargetPort.String() == "" {
allPorts[sp.Port] = nil
} else if sp.TargetPort.Type == intstr.Int {
// port is defined as port number
allPorts[sp.TargetPort.IntVal] = nil
} else {
for targetPort := range c.resolvePortName(sp.Name, &backendID) {
allPorts[targetPort] = nil
}
}
}
}
podList := c.k8sContext.ListPodsByServiceSelector(service)
if len(podList) == 0 {
return nil
}
// use the target port to figure out the container and use it's readiness/liveness probe
for _, container := range podList[0].Spec.Containers {
for _, port := range container.Ports {
if _, ok := allPorts[port.ContainerPort]; !ok {
continue
}
// found the container
var probe *v1.Probe
if container.ReadinessProbe != nil && container.ReadinessProbe.HTTPGet != nil {
probe = container.ReadinessProbe
} else if container.LivenessProbe != nil && container.LivenessProbe.HTTPGet != nil {
probe = container.LivenessProbe
}
// if probe port is named, resolve it by going through container port and set it in the probe itself
if probe != nil && probe.HTTPGet.Port.String() != "" && probe.HTTPGet.Port.Type == intstr.String {
for _, port := range container.Ports {
if port.Name == probe.HTTPGet.Port.StrVal {
probe.HTTPGet.Port = intstr.IntOrString{
Type: intstr.Int,
IntVal: port.ContainerPort,
}
break
}
}
}
return probe
}
}
return nil
}