Skip to content

Controller: Correctly identify other pods on shutdown. #13444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions charts/ingress-nginx/templates/controller-daemonset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ spec:
labels:
{{- include "ingress-nginx.labels" . | nindent 8 }}
app.kubernetes.io/component: controller
{{- if not .Values.controller.disableLeaderElection }}
nginx.ingress.kubernetes.io/election-id: {{ include "ingress-nginx.controller.electionID" . }}
{{- end }}
{{- with .Values.controller.labels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
3 changes: 3 additions & 0 deletions charts/ingress-nginx/templates/controller-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ spec:
labels:
{{- include "ingress-nginx.labels" . | nindent 8 }}
app.kubernetes.io/component: controller
{{- if not .Values.controller.disableLeaderElection }}
nginx.ingress.kubernetes.io/election-id: {{ include "ingress-nginx.controller.electionID" . }}
{{- end }}
{{- with .Values.controller.labels }}
{{- toYaml . | nindent 8 }}
{{- end }}
Expand Down
25 changes: 25 additions & 0 deletions charts/ingress-nginx/tests/controller-daemonset_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,28 @@ tests:
- equal:
path: spec.template.spec.runtimeClassName
value: myClass

- it: should create a DaemonSet with election ID label
set:
controller.kind: DaemonSet
asserts:
- equal:
path: spec.template.metadata.labels["nginx.ingress.kubernetes.io/election-id"]
value: RELEASE-NAME-ingress-nginx-leader

- it: should create a DaemonSet with custom election ID label if `controller.electionID` is set
set:
controller.kind: DaemonSet
controller.electionID: custom-election-id
asserts:
- equal:
path: spec.template.metadata.labels["nginx.ingress.kubernetes.io/election-id"]
value: custom-election-id

- it: should create a DaemonSet without election ID label if `controller.disableLeaderElection` is true
set:
controller.kind: DaemonSet
controller.disableLeaderElection: true
asserts:
- notExists:
path: spec.template.metadata.labels["nginx.ingress.kubernetes.io/election-id"]
22 changes: 22 additions & 0 deletions charts/ingress-nginx/tests/controller-deployment_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,25 @@ tests:
- equal:
path: spec.template.spec.runtimeClassName
value: myClass

- it: should create a Deployment with the electionID label on the pod template when leader election is enabled
set:
asserts:
- equal:
path: spec.template.metadata.labels['nginx.ingress.kubernetes.io/election-id']
value: RELEASE-NAME-ingress-nginx-leader

- it: should create a Deployment with the custom electionID label on the pod template when controller.electionID is set and leader election is enabled
set:
controller.electionID: custom-election-id
asserts:
- equal:
path: spec.template.metadata.labels['nginx.ingress.kubernetes.io/election-id']
value: custom-election-id

- it: should create a Deployment without the electionID label on the pod template when leader election is disabled
set:
controller.disableLeaderElection: true
asserts:
- notExists:
path: spec.template.metadata.labels['nginx.ingress.kubernetes.io/election-id']
1 change: 1 addition & 0 deletions docs/user-guide/cli-arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ They are set in the container spec of the `ingress-nginx-controller` Deployment
| `--udp-services-configmap` | Name of the ConfigMap containing the definition of the UDP services to expose. The key in the map indicates the external port to be used. The value is a reference to a Service in the form "namespace/name:port", where "port" can either be a port name or number. |
| `--update-status` | Update the load-balancer status of Ingress objects this controller satisfies. Requires setting the publish-service parameter to a valid Service reference. (default true) |
| `--update-status-on-shutdown` | Update the load-balancer status of Ingress objects when the controller shuts down. Requires the update-status parameter. (default true) |
| `--use-election-id-selector-on-shutdown` | Determine if other pods are running based on the electionID label, rather than all pod labels. When true, the controller looks for pods with the specific nginx.ingress.kubernetes.io/electionID label matching the controller's election ID. When false, it uses all standard Kubernetes app labels to identify other controller pods. (default true) |
| `--shutdown-grace-period` | Seconds to wait after receiving the shutdown signal, before stopping the nginx process. (default 0) |
| `--size-buckets` | Set of buckets which will be used for prometheus histogram metrics such as BytesSent. (default `[10, 100, 1000, 10000, 100000, 1e+06, 1e+07]`) |
| `-v, --v Level` | number for the log level verbosity |
Expand Down
2 changes: 2 additions & 0 deletions internal/ingress/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ type Configuration struct {

DisableSyncEvents bool

UseElectionIDSelectorOnShutdown bool

EnableTopologyAwareRouting bool
}

Expand Down
14 changes: 8 additions & 6 deletions internal/ingress/controller/nginx.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,14 @@ func NewNGINXController(config *Configuration, mc metric.Collector) *NGINXContro

if config.UpdateStatus {
n.syncStatus = status.NewStatusSyncer(status.Config{
Client: config.Client,
PublishService: config.PublishService,
PublishStatusAddress: config.PublishStatusAddress,
IngressLister: n.store,
UpdateStatusOnShutdown: config.UpdateStatusOnShutdown,
UseNodeInternalIP: config.UseNodeInternalIP,
Client: config.Client,
PublishService: config.PublishService,
PublishStatusAddress: config.PublishStatusAddress,
IngressLister: n.store,
UpdateStatusOnShutdown: config.UpdateStatusOnShutdown,
UseNodeInternalIP: config.UseNodeInternalIP,
UseElectionIDSelectorOnShutdown: config.UseElectionIDSelectorOnShutdown,
ElectionID: config.ElectionID,
})
} else {
klog.Warning("Update of Ingress status is disabled (flag --update-status)")
Expand Down
58 changes: 40 additions & 18 deletions internal/ingress/status/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ import (
"k8s.io/ingress-nginx/pkg/apis/ingress"
)

const (
// ElectionIDLabelKey is the label key used to identify controller pods by election ID
ElectionIDLabelKey = "nginx.ingress.kubernetes.io/electionID"
)

// UpdateInterval defines the time interval, in seconds, in
// which the status should check if an update is required.
var UpdateInterval = 60
Expand Down Expand Up @@ -68,6 +73,10 @@ type Config struct {

UseNodeInternalIP bool

UseElectionIDSelectorOnShutdown bool

ElectionID string

IngressLister ingressLister
}

Expand Down Expand Up @@ -175,6 +184,35 @@ func nameOrIPToLoadBalancerIngress(nameOrIP string) v1.IngressLoadBalancerIngres

// runningAddresses returns a list of IP addresses and/or FQDN where the
// ingress controller is currently running
// listControllerPods returns a list of running pods with controller labels
func (s *statusSync) listControllerPods(useElectionID bool) (*apiv1.PodList, error) {
podLabel := make(map[string]string)

if useElectionID {
// When using electionID, only look for pods with the electionID label
// This is more specific and will correctly identify pods belonging to the same controller group
// Note: This requires the electionID label to be set on the pods (done by helm chart)
podLabel[ElectionIDLabelKey] = s.ElectionID
} else {
// As a standard, app.kubernetes.io are "reserved well-known" labels.
// In our case, we add those labels as identifiers of the Ingress
// deployment in this namespace, so we can select it as a set of Ingress instances.
// As those labels are also generated as part of a HELM deployment, we can be "safe" they
// cover 95% of the cases
for k, v := range k8s.IngressPodDetails.Labels {
// Skip labels that are frequently modified by deployment controllers
if k != "pod-template-hash" && k != "controller-revision-hash" && k != "pod-template-generation" {
podLabel[k] = v
}
}
}
return s.Client.CoreV1().Pods(k8s.IngressPodDetails.Namespace).List(
context.TODO(),
metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(podLabel).String(),
})
}

func (s *statusSync) runningAddresses() ([]v1.IngressLoadBalancerIngress, error) {
if s.PublishStatusAddress != "" {
re := regexp.MustCompile(`,\s*`)
Expand All @@ -191,9 +229,7 @@ func (s *statusSync) runningAddresses() ([]v1.IngressLoadBalancerIngress, error)
}

// get information about all the pods running the ingress controller
pods, err := s.Client.CoreV1().Pods(k8s.IngressPodDetails.Namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(k8s.IngressPodDetails.Labels).String(),
})
pods, err := s.listControllerPods(s.UseElectionIDSelectorOnShutdown)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -230,21 +266,7 @@ func (s *statusSync) runningAddresses() ([]v1.IngressLoadBalancerIngress, error)
}

func (s *statusSync) isRunningMultiplePods() bool {
// As a standard, app.kubernetes.io are "reserved well-known" labels.
// In our case, we add those labels as identifiers of the Ingress
// deployment in this namespace, so we can select it as a set of Ingress instances.
// As those labels are also generated as part of a HELM deployment, we can be "safe" they
// cover 95% of the cases
podLabel := make(map[string]string)
for k, v := range k8s.IngressPodDetails.Labels {
if k != "pod-template-hash" && k != "controller-revision-hash" && k != "pod-template-generation" {
podLabel[k] = v
}
}

pods, err := s.Client.CoreV1().Pods(k8s.IngressPodDetails.Namespace).List(context.TODO(), metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(podLabel).String(),
})
pods, err := s.listControllerPods(s.UseElectionIDSelectorOnShutdown) // Use election ID-compatible labels if configured
if err != nil {
return false
}
Expand Down
Loading