diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index e210c77c3..cad82aaf6 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -60,6 +60,15 @@ spec: containers: - image: controller:latest name: manager + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name volumeMounts: - name: config-volume mountPath: /app/conf/config.yaml diff --git a/config/samples/config.yaml b/config/samples/config.yaml index 3bfe2f3ab..47f4bb1e9 100644 --- a/config/samples/config.yaml +++ b/config/samples/config.yaml @@ -6,6 +6,16 @@ controller_name: gateway.api7.io/api7-ingress-controller # The controller name # The default value is "gateway.api7.io/api7-ingress-controller". leader_election_id: "api7-ingress-controller-leader" # The leader election ID for the API7 Ingress Controller. # The default value is "api7-ingress-controller-leader". +leader_election: + lease_duration: 15s # lease_duration is the duration that non-leader candidates will wait + # after observing a leadership renewal until attempting to acquire leadership of a + # leader election. + renew_deadline: 10s # renew_deadline is the time in seconds that the acting controller + # will retry refreshing leadership before giving up. + retry_period: 2s # retry_period is the time in seconds that the acting controller + # will wait between tries of actions with the controller. + disable: false # Whether to disable leader election. + ingress_class: api7 # The ingress class name of the API7 Ingress Controller. ingress_publish_service: "" # The service name of the ingress publish service. ingress_status_address: [] # The status address of the ingress. diff --git a/internal/controller/config/config.go b/internal/controller/config/config.go index 09ca8e2d2..932ae2c8d 100644 --- a/internal/controller/config/config.go +++ b/internal/controller/config/config.go @@ -7,7 +7,9 @@ import ( "os" "strings" "text/template" + "time" + "github.com/api7/api7-ingress-controller/internal/types" "gopkg.in/yaml.v2" ) @@ -29,6 +31,16 @@ func NewDefaultConfig() *Config { ProbeAddr: DefaultProbeAddr, MetricsAddr: DefaultMetricsAddr, IngressClass: DefaultIngressClass, + LeaderElection: NewLeaderElection(), + } +} + +func NewLeaderElection() *LeaderElection { + return &LeaderElection{ + LeaseDuration: types.TimeDuration{Duration: 15 * time.Second}, + RenewDeadline: types.TimeDuration{Duration: 10 * time.Second}, + RetryPeriod: types.TimeDuration{Duration: 2 * time.Second}, + Disable: false, } } diff --git a/internal/controller/config/types.go b/internal/controller/config/types.go index 497815cc0..a821e7bb0 100644 --- a/internal/controller/config/types.go +++ b/internal/controller/config/types.go @@ -1,5 +1,9 @@ package config +import ( + "github.com/api7/api7-ingress-controller/internal/types" +) + const ( // IngressAPISIXLeader is the default election id for the controller // leader election. @@ -36,6 +40,7 @@ type Config struct { IngressClass string `json:"ingress_class" yaml:"ingress_class"` IngressPublishService string `json:"ingress_publish_service" yaml:"ingress_publish_service"` IngressStatusAddress []string `json:"ingress_status_address" yaml:"ingress_status_address"` + LeaderElection *LeaderElection `json:"leader_election" yaml:"leader_election"` } type GatewayConfig struct { @@ -49,3 +54,10 @@ type ControlPlaneConfig struct { Endpoints []string `json:"endpoints" yaml:"endpoints"` TLSVerify *bool `json:"tls_verify" yaml:"tls_verify"` } + +type LeaderElection struct { + LeaseDuration types.TimeDuration `json:"leaseDuration,omitempty" yaml:"leaseDuration,omitempty"` + RenewDeadline types.TimeDuration `json:"renewDeadline,omitempty" yaml:"renewDeadline,omitempty"` + RetryPeriod types.TimeDuration `json:"retryPeriod,omitempty" yaml:"retryPeriod,omitempty"` + Disable bool `json:"disable,omitempty" yaml:"disable,omitempty"` +} diff --git a/internal/manager/run.go b/internal/manager/run.go index 317608a0f..a5352e746 100644 --- a/internal/manager/run.go +++ b/internal/manager/run.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/metrics/filters" @@ -97,9 +98,12 @@ func Run(ctx context.Context, logger logr.Logger) error { Metrics: metricsServerOptions, WebhookServer: webhookServer, HealthProbeBindAddress: cfg.ProbeAddr, - LeaderElection: true, + LeaderElection: !config.ControllerConfig.LeaderElection.Disable, LeaderElectionID: cfg.LeaderElectionID, LeaderElectionNamespace: namespace, + LeaseDuration: ptr.To(config.ControllerConfig.LeaderElection.LeaseDuration.Duration), + RenewDeadline: ptr.To(config.ControllerConfig.LeaderElection.RenewDeadline.Duration), + RetryPeriod: ptr.To(config.ControllerConfig.LeaderElection.RetryPeriod.Duration), // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly diff --git a/internal/types/duration.go b/internal/types/duration.go new file mode 100644 index 000000000..f0b2cc2f1 --- /dev/null +++ b/internal/types/duration.go @@ -0,0 +1,72 @@ +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package types + +import ( + "encoding/json" + "fmt" + "time" +) + +// TimeDuration is yet another time.Duration but implements json.Unmarshaler +// and json.Marshaler, yaml.Unmarshaler and yaml.Marshaler interfaces so one +// can use "1h", "5s" and etc in their json/yaml configurations. +// +// Note the format to represent time is same as time.Duration. +// See the comments about time.ParseDuration for more details. +type TimeDuration struct { + time.Duration `json:",inline"` +} + +func (d *TimeDuration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +func (d *TimeDuration) UnmarshalJSON(data []byte) error { + var value interface{} + if err := json.Unmarshal(data, &value); err != nil { + return err + } + switch v := value.(type) { + case float64: + d.Duration = time.Duration(v) + case string: + dur, err := time.ParseDuration(v) + if err != nil { + return err + } + d.Duration = dur + default: + return fmt.Errorf("unknown type: %T", v) + } + return nil +} + +func (d *TimeDuration) MarshalYAML() (interface{}, error) { + return d.Duration.String(), nil +} + +func (d *TimeDuration) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + dur, err := time.ParseDuration(s) + if err != nil { + return err + } + d.Duration = dur + return nil +} diff --git a/test/e2e/framework/manifests/ingress.yaml b/test/e2e/framework/manifests/ingress.yaml index 0a7296821..f2d3ba249 100644 --- a/test/e2e/framework/manifests/ingress.yaml +++ b/test/e2e/framework/manifests/ingress.yaml @@ -350,6 +350,15 @@ spec: spec: containers: - image: api7/api7-ingress-controller:dev + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name volumeMounts: - name: ingress-config mountPath: /app/conf/config.yaml