From a66193101c797f68efa010b26ad298e9ba1c4239 Mon Sep 17 00:00:00 2001 From: naoki-take Date: Wed, 6 Mar 2024 04:53:39 +0000 Subject: [PATCH 1/5] Support PDB for egress Signed-off-by: naoki-take --- docs/usage.md | 5 + v2/api/v2/egress_types.go | 95 ++++++++++++++++++ v2/api/v2/egress_webhook_test.go | 38 ++++++++ v2/api/v2/zz_generated.deepcopy.go | 6 ++ .../crd/bases/coil.cybozu.com_egresses.yaml | 97 +++++++++++++++++++ v2/config/rbac/coil-controller_role.yaml | 11 +++ v2/controllers/egress_controller.go | 56 +++++++++++ v2/controllers/egress_controller_test.go | 22 +++++ v2/e2e/manifests/egress.yaml | 2 + v2/go.mod | 10 +- v2/go.sum | 18 ++-- 11 files changed, 343 insertions(+), 17 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 14b8d44f..156f126b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -173,6 +173,7 @@ Step 5 and 6 are automatically done by Coil. It defines an egress portal of the cluster for some destinations. Coil creates a `Deployment` and `Service` for each `Egress`. +`PodDisruptionBudget` can also be created by specifying `spec.podDisruptionBudget`. Here is an example `Egress` resource for the Internet: @@ -231,6 +232,8 @@ spec: sessionAffinityConfig: clientIP: timeoutSeconds: 43200 + podDisruptionBudget: + maxUnavailable: 1 ``` Only `destinations` are mandatory. Other fields in `spec` are optional. @@ -244,6 +247,7 @@ You may customize the container of egress Pods as shown in the above example. | `template` | [PodTemplateSpec][] | Copied to Deployment's `spec.template`. | | `sessionAffinity` | `ClusterIP` or `None` | Copied to Service's `spec.sessionAffinity`. Default is `ClusterIP`. | | `sessionAffinityConfig` | [SessionAffinityConfig][] | Copied to Service's `spec.sessionAffinityConfig`. | +| `podDisruptionBudget` | [PodDisruptionBudgetSpec] | Copied to PDB's spec. `selector` is not allowed since it is set by the controller. | ### Client Pods @@ -368,3 +372,4 @@ The example of Grafana dashboard is [here](../v2/dashboard/coil.json). [PodTemplateSpec]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#podtemplatespec-v1-core [SessionAffinityConfig]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#sessionaffinityconfig-v1-core [NetworkPolicy]: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +[PodDisruptionBudgetSpec]: https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/pod-disruption-budget-v1/ \ No newline at end of file diff --git a/v2/api/v2/egress_types.go b/v2/api/v2/egress_types.go index d58ce55a..5df544a1 100644 --- a/v2/api/v2/egress_types.go +++ b/v2/api/v2/egress_types.go @@ -2,11 +2,18 @@ package v2 import ( "net" + "strconv" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + + apivalidation "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/apimachinery/pkg/util/sets" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -59,6 +66,10 @@ type EgressSpec struct { // The default is false. // +optional FouSourcePortAuto bool `json:"fouSourcePortAuto,omitempty"` + + // PodDisruptionBudget is an optional PodDisruptionBudget for Egress NAT pods. + // +optional + PodDisruptionBudget *policyv1.PodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"` } // EgressPodTemplate defines pod template for Egress @@ -119,6 +130,11 @@ func (es EgressSpec) validate() field.ErrorList { } } + if es.PodDisruptionBudget != nil { + pp := p.Child("podDisruptionBudget") + allErrs = append(allErrs, validatePodDisruptionBudgetSpec(*es.PodDisruptionBudget, pp)...) + } + return allErrs } @@ -126,6 +142,85 @@ func (es EgressSpec) validateUpdate() field.ErrorList { return es.validate() } +// For validating PodDisruptionBudgetSpec. +// Original code is in https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/policy/validation/validation.go + +type UnhealthyPodEvictionPolicyType string + +const ( + IfHealthyBudget UnhealthyPodEvictionPolicyType = "IfHealthyBudget" + AlwaysAllow UnhealthyPodEvictionPolicyType = "AlwaysAllow" +) + +var supportedUnhealthyPodEvictionPolicies = sets.NewString( + string(IfHealthyBudget), + string(AlwaysAllow), +) + +func validatePodDisruptionBudgetSpec(spec policyv1.PodDisruptionBudgetSpec, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if spec.MinAvailable != nil && spec.MaxUnavailable != nil { + allErrs = append(allErrs, field.Invalid(fldPath, spec, "minAvailable and maxUnavailable cannot be both set")) + } + + if spec.MinAvailable != nil { + allErrs = append(allErrs, validatePositiveIntOrPercent(*spec.MinAvailable, fldPath.Child("minAvailable"))...) + allErrs = append(allErrs, isNotMoreThan100Percent(*spec.MinAvailable, fldPath.Child("minAvailable"))...) + } + + if spec.MaxUnavailable != nil { + allErrs = append(allErrs, validatePositiveIntOrPercent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...) + allErrs = append(allErrs, isNotMoreThan100Percent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...) + } + + if spec.Selector != nil { + allErrs = append(allErrs, field.Invalid(fldPath, spec, "selector is automatically set by the controller")) + } + + if spec.UnhealthyPodEvictionPolicy != nil && !supportedUnhealthyPodEvictionPolicies.Has(string(*spec.UnhealthyPodEvictionPolicy)) { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("unhealthyPodEvictionPolicy"), *spec.UnhealthyPodEvictionPolicy, supportedUnhealthyPodEvictionPolicies.List())) + } + + return allErrs +} + +func validatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + switch intOrPercent.Type { + case intstr.String: + for _, msg := range utilvalidation.IsValidPercent(intOrPercent.StrVal) { + allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, msg)) + } + case intstr.Int: + allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(intOrPercent.IntValue()), fldPath)...) + default: + allErrs = append(allErrs, field.Invalid(fldPath, intOrPercent, "must be an integer or percentage (e.g '5%%')")) + } + return allErrs +} + +func isNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + value, isPercent := getPercentValue(intOrStringValue) + if !isPercent || value <= 100 { + return nil + } + allErrs = append(allErrs, field.Invalid(fldPath, intOrStringValue, "must not be greater than 100%")) + return allErrs +} + +func getPercentValue(intOrStringValue intstr.IntOrString) (int, bool) { + if intOrStringValue.Type != intstr.String { + return 0, false + } + if len(utilvalidation.IsValidPercent(intOrStringValue.StrVal)) != 0 { + return 0, false + } + value, _ := strconv.Atoi(intOrStringValue.StrVal[:len(intOrStringValue.StrVal)-1]) + return value, true +} + // EgressStatus defines the observed state of Egress type EgressStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster diff --git a/v2/api/v2/egress_webhook_test.go b/v2/api/v2/egress_webhook_test.go index 3d4f09c0..2b5316c7 100644 --- a/v2/api/v2/egress_webhook_test.go +++ b/v2/api/v2/egress_webhook_test.go @@ -7,8 +7,10 @@ import ( . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" ) func makeEgress() *Egress { @@ -175,4 +177,40 @@ var _ = Describe("Egress Webhook", func() { err = k8sClient.Update(ctx, r) Expect(err).NotTo(HaveOccurred()) }) + + It("should allow valid PDB", func() { + r := makeEgress() + maxUnavailable := intstr.FromInt(1) + r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable} + err := k8sClient.Create(ctx, r) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should deny invalid PDB", func() { + r := makeEgress() + maxUnavailable := intstr.FromInt(1) + minAvailable := intstr.FromInt(1) + r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable, MinAvailable: &minAvailable} + err := k8sClient.Create(ctx, r) + Expect(err).To(HaveOccurred()) + + r = makeEgress() + maxUnavailable = intstr.FromString("120%") + r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable} + err = k8sClient.Create(ctx, r) + Expect(err).To(HaveOccurred()) + + r = makeEgress() + maxUnavailable = intstr.FromInt(1) + r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable, Selector: &metav1.LabelSelector{}} + err = k8sClient.Create(ctx, r) + Expect(err).To(HaveOccurred()) + + r = makeEgress() + maxUnavailable = intstr.FromInt(1) + policyType := policyv1.UnhealthyPodEvictionPolicyType("Invalid") + r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable, UnhealthyPodEvictionPolicy: &policyType} + err = k8sClient.Create(ctx, r) + Expect(err).To(HaveOccurred()) + }) }) diff --git a/v2/api/v2/zz_generated.deepcopy.go b/v2/api/v2/zz_generated.deepcopy.go index 67002104..5156b992 100644 --- a/v2/api/v2/zz_generated.deepcopy.go +++ b/v2/api/v2/zz_generated.deepcopy.go @@ -7,6 +7,7 @@ package v2 import ( "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/runtime" ) @@ -369,6 +370,11 @@ func (in *EgressSpec) DeepCopyInto(out *EgressSpec) { *out = new(corev1.SessionAffinityConfig) (*in).DeepCopyInto(*out) } + if in.PodDisruptionBudget != nil { + in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget + *out = new(policyv1.PodDisruptionBudgetSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressSpec. diff --git a/v2/config/crd/bases/coil.cybozu.com_egresses.yaml b/v2/config/crd/bases/coil.cybozu.com_egresses.yaml index 33a14d73..96280004 100644 --- a/v2/config/crd/bases/coil.cybozu.com_egresses.yaml +++ b/v2/config/crd/bases/coil.cybozu.com_egresses.yaml @@ -52,6 +52,103 @@ spec: If set to true, the kernel picks a flow based on the flow hash of the encapsulated packet. The default is false. type: boolean + podDisruptionBudget: + description: PodDisruptionBudget is an optional PodDisruptionBudget + for Egress NAT pods. + properties: + maxUnavailable: + anyOf: + - type: integer + - type: string + description: An eviction is allowed if at most "maxUnavailable" + pods selected by "selector" are unavailable after the eviction, + i.e. even in absence of the evicted pod. For example, one can + prevent all voluntary evictions by specifying 0. This is a mutually + exclusive setting with "minAvailable". + x-kubernetes-int-or-string: true + minAvailable: + anyOf: + - type: integer + - type: string + description: An eviction is allowed if at least "minAvailable" + pods selected by "selector" will still be available after the + eviction, i.e. even in the absence of the evicted pod. So for + example you can prevent all voluntary evictions by specifying + "100%". + x-kubernetes-int-or-string: true + selector: + description: Label query over pods whose evictions are managed + by the disruption budget. A null selector will match no pods, + while an empty ({}) selector will select all pods within the + namespace. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + unhealthyPodEvictionPolicy: + description: "UnhealthyPodEvictionPolicy defines the criteria + for when unhealthy pods should be considered for eviction. Current + implementation considers healthy pods, as pods that have status.conditions + item with type=\"Ready\",status=\"True\". \n Valid policies + are IfHealthyBudget and AlwaysAllow. If no policy is specified, + the default behavior will be used, which corresponds to the + IfHealthyBudget policy. \n IfHealthyBudget policy means that + running pods (status.phase=\"Running\"), but not yet healthy + can be evicted only if the guarded application is not disrupted + (status.currentHealthy is at least equal to status.desiredHealthy). + Healthy pods will be subject to the PDB for eviction. \n AlwaysAllow + policy means that all running pods (status.phase=\"Running\"), + but not yet healthy are considered disrupted and can be evicted + regardless of whether the criteria in a PDB is met. This means + perspective running pods of a disrupted application might not + get a chance to become healthy. Healthy pods will be subject + to the PDB for eviction. \n Additional policies may be added + in the future. Clients making eviction decisions should disallow + eviction of unhealthy pods if they encounter an unrecognized + policy in this field. \n This field is beta-level. The eviction + API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy + is enabled (enabled by default)." + type: string + type: object replicas: default: 1 description: |- diff --git a/v2/config/rbac/coil-controller_role.yaml b/v2/config/rbac/coil-controller_role.yaml index 866636aa..d40259de 100644 --- a/v2/config/rbac/coil-controller_role.yaml +++ b/v2/config/rbac/coil-controller_role.yaml @@ -94,6 +94,17 @@ rules: - get - patch - update +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - get + - list + - patch + - update + - watch - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/v2/controllers/egress_controller.go b/v2/controllers/egress_controller.go index 817adee5..3154e4ae 100644 --- a/v2/controllers/egress_controller.go +++ b/v2/controllers/egress_controller.go @@ -8,6 +8,7 @@ import ( "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -32,6 +33,7 @@ type EgressReconciler struct { // +kubebuilder:rbac:groups=coil.cybozu.com,resources=egresses/status,verbs=get;update;patch // +kubebuilder:rbac:groups="",resources=services;serviceaccounts,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch +// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch // coil-controller needs to have access to Pods to grant egress service accounts the same privilege. // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch @@ -81,6 +83,11 @@ func (r *EgressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, err } + if err := r.reconcilePDB(ctx, logger, eg); err != nil { + logger.Error(err, "failed to reconcile pod disruption budget") + return ctrl.Result{}, err + } + if err := r.updateStatus(ctx, logger, eg); err != nil { logger.Error(err, "failed to update status") return ctrl.Result{}, err @@ -366,6 +373,54 @@ func (r *EgressReconciler) reconcileService(ctx context.Context, log logr.Logger return nil } +func (r *EgressReconciler) reconcilePDB(ctx context.Context, log logr.Logger, eg *coilv2.Egress) error { + if eg.Spec.PodDisruptionBudget == nil { + return nil + } + + pdb := &policyv1.PodDisruptionBudget{} + pdb.Namespace = eg.Namespace + pdb.Name = eg.Name + "-pdb" + + result, err := ctrl.CreateOrUpdate(ctx, r.Client, pdb, func() error { + if pdb.DeletionTimestamp != nil { + return nil + } + + if pdb.Labels == nil { + pdb.Labels = make(map[string]string) + } + labels := selectorLabels(eg.Name) + for k, v := range labels { + pdb.Labels[k] = v + } + + // set immutable fields only for a new object + if pdb.CreationTimestamp.IsZero() { + if err := ctrl.SetControllerReference(eg, pdb, r.Scheme); err != nil { + return err + } + } + + eg.Spec.PodDisruptionBudget.DeepCopyInto(&pdb.Spec) + pdb.Spec.Selector = &metav1.LabelSelector{ + MatchLabels: map[string]string{ + constants.LabelAppName: "coil", + }, + } + + return nil + }) + if err != nil { + return err + } + + if result != controllerutil.OperationResultNone { + log.Info(string(result) + " pod disruption budget") + } + return nil +} + func (r *EgressReconciler) updateStatus(ctx context.Context, log logr.Logger, eg *coilv2.Egress) error { depl := &appsv1.Deployment{} if err := r.Get(ctx, client.ObjectKey{Namespace: eg.Namespace, Name: eg.Name}, depl); err != nil { @@ -404,6 +459,7 @@ func (r *EgressReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&coilv2.Egress{}). Owns(&appsv1.Deployment{}). Owns(&corev1.Service{}). + Owns(&policyv1.PodDisruptionBudget{}). Complete(r) } diff --git a/v2/controllers/egress_controller_test.go b/v2/controllers/egress_controller_test.go index 550815bc..c9b7772e 100644 --- a/v2/controllers/egress_controller_test.go +++ b/v2/controllers/egress_controller_test.go @@ -11,6 +11,7 @@ import ( . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/util/intstr" @@ -510,4 +511,25 @@ var _ = Describe("Egress reconciler", func() { return nil }).Should(Succeed()) }) + + It("should create PDB", func() { + By("creating an Egress") + eg := makeEgress("eg7") + minAvailable := intstr.FromInt(1) + eg.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{ + MinAvailable: &minAvailable, + } + err := k8sClient.Create(ctx, eg) + Expect(err).ShouldNot(HaveOccurred()) + + By("checking PodDisruptionBudget") + var pdb *policyv1.PodDisruptionBudget + Eventually(func() error { + pdb = &policyv1.PodDisruptionBudget{} + return k8sClient.Get(ctx, client.ObjectKey{Namespace: eg.Namespace, Name: eg.Name + "-pdb"}, pdb) + }).Should(Succeed()) + + Expect(*pdb.Spec.MinAvailable).To(Equal(intstr.FromInt(1))) + Expect(pdb.Spec.Selector.MatchLabels).To(HaveKeyWithValue(constants.LabelAppName, "coil")) + }) }) diff --git a/v2/e2e/manifests/egress.yaml b/v2/e2e/manifests/egress.yaml index 49d312aa..1db0710f 100644 --- a/v2/e2e/manifests/egress.yaml +++ b/v2/e2e/manifests/egress.yaml @@ -17,3 +17,5 @@ spec: operator: Exists containers: - name: egress + podDisruptionBudget: + maxUnavailable: 1 diff --git a/v2/go.mod b/v2/go.mod index dc827d5e..b281757a 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -37,11 +37,11 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.10.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/jsonreference v0.20.1 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -52,7 +52,7 @@ require ( github.com/google/pprof v0.0.0-20230728192033-2ba5b33183c6 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -74,7 +74,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/vishvananda/netns v0.0.4 // indirect - go.uber.org/multierr v1.11.0 // indirect + go.uber.org/multierr v1.10.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect @@ -91,7 +91,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.27.7 // indirect k8s.io/component-base v0.27.7 // indirect - k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect sigs.k8s.io/yaml v1.3.0 // indirect diff --git a/v2/go.sum b/v2/go.sum index 538af0f6..a2a5e172 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -9,7 +9,6 @@ github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJR github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -18,7 +17,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v1.4.1 h1:+sJRRv8PKhLkXIl6tH1D7RMi+CbbHutDGU+ErLBORWA= @@ -34,13 +32,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ= -github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -60,8 +57,8 @@ github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= +github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -116,8 +113,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -259,7 +256,6 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -376,7 +372,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -384,7 +379,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 6883ead52dc4b16d646a1c16c0b0b9a2b7cb0f7b Mon Sep 17 00:00:00 2001 From: naoki-take Date: Tue, 2 Apr 2024 09:38:19 +0000 Subject: [PATCH 2/5] Reflect comments Signed-off-by: naoki-take --- docs/usage.md | 7 +- v2/api/v2/egress_types.go | 59 +++++-------- v2/api/v2/egress_webhook_test.go | 20 +---- v2/api/v2/zz_generated.deepcopy.go | 29 ++++++- .../crd/bases/coil.cybozu.com_egresses.yaml | 86 +------------------ v2/config/rbac/coil-controller_role.yaml | 1 + v2/controllers/egress_controller.go | 15 ++-- v2/controllers/egress_controller_test.go | 4 +- 8 files changed, 71 insertions(+), 150 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 156f126b..f192dfaa 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -173,7 +173,7 @@ Step 5 and 6 are automatically done by Coil. It defines an egress portal of the cluster for some destinations. Coil creates a `Deployment` and `Service` for each `Egress`. -`PodDisruptionBudget` can also be created by specifying `spec.podDisruptionBudget`. +It also creates `PodDisruptionBudget` when `spec.podDisruptionBudget` is specified. Here is an example `Egress` resource for the Internet: @@ -247,7 +247,7 @@ You may customize the container of egress Pods as shown in the above example. | `template` | [PodTemplateSpec][] | Copied to Deployment's `spec.template`. | | `sessionAffinity` | `ClusterIP` or `None` | Copied to Service's `spec.sessionAffinity`. Default is `ClusterIP`. | | `sessionAffinityConfig` | [SessionAffinityConfig][] | Copied to Service's `spec.sessionAffinityConfig`. | -| `podDisruptionBudget` | [PodDisruptionBudgetSpec] | Copied to PDB's spec. `selector` is not allowed since it is set by the controller. | +| `podDisruptionBudget` | `EgressPDB` | `minAvailable` and `maxUnavailable` are supported. | ### Client Pods @@ -371,5 +371,4 @@ The example of Grafana dashboard is [here](../v2/dashboard/coil.json). [DeploymentStrategy]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#deploymentstrategy-v1-apps [PodTemplateSpec]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#podtemplatespec-v1-core [SessionAffinityConfig]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#sessionaffinityconfig-v1-core -[NetworkPolicy]: https://kubernetes.io/docs/concepts/services-networking/network-policies/ -[PodDisruptionBudgetSpec]: https://kubernetes.io/docs/reference/kubernetes-api/policy-resources/pod-disruption-budget-v1/ \ No newline at end of file +[NetworkPolicy]: https://kubernetes.io/docs/concepts/services-networking/network-policies/ \ No newline at end of file diff --git a/v2/api/v2/egress_types.go b/v2/api/v2/egress_types.go index 5df544a1..55f8c23d 100644 --- a/v2/api/v2/egress_types.go +++ b/v2/api/v2/egress_types.go @@ -6,13 +6,10 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - policyv1 "k8s.io/api/policy/v1" - apivalidation "k8s.io/apimachinery/pkg/api/validation" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/apimachinery/pkg/util/sets" utilvalidation "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" ) @@ -69,7 +66,7 @@ type EgressSpec struct { // PodDisruptionBudget is an optional PodDisruptionBudget for Egress NAT pods. // +optional - PodDisruptionBudget *policyv1.PodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"` + PodDisruptionBudget *EgressPDB `json:"podDisruptionBudget,omitempty"` } // EgressPodTemplate defines pod template for Egress @@ -86,6 +83,17 @@ type EgressPodTemplate struct { Spec corev1.PodSpec `json:"spec,omitempty"` } +// EgressPDB defines PDB for Egress +type EgressPDB struct { + // MinAvailable is the minimum number of pods that must be available at any given time. + // +optional + MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"` + + // MaxUnavailable is the maximum number of pods that can be unavailable at any given time. + // +optional + MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"` +} + // Metadata defines a simplified version of ObjectMeta. type Metadata struct { // Annotations are optional annotations @@ -132,7 +140,7 @@ func (es EgressSpec) validate() field.ErrorList { if es.PodDisruptionBudget != nil { pp := p.Child("podDisruptionBudget") - allErrs = append(allErrs, validatePodDisruptionBudgetSpec(*es.PodDisruptionBudget, pp)...) + allErrs = append(allErrs, validatePodDisruptionBudget(*es.PodDisruptionBudget, pp)...) } return allErrs @@ -142,44 +150,21 @@ func (es EgressSpec) validateUpdate() field.ErrorList { return es.validate() } -// For validating PodDisruptionBudgetSpec. -// Original code is in https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/policy/validation/validation.go - -type UnhealthyPodEvictionPolicyType string - -const ( - IfHealthyBudget UnhealthyPodEvictionPolicyType = "IfHealthyBudget" - AlwaysAllow UnhealthyPodEvictionPolicyType = "AlwaysAllow" -) - -var supportedUnhealthyPodEvictionPolicies = sets.NewString( - string(IfHealthyBudget), - string(AlwaysAllow), -) - -func validatePodDisruptionBudgetSpec(spec policyv1.PodDisruptionBudgetSpec, fldPath *field.Path) field.ErrorList { +func validatePodDisruptionBudget(pdb EgressPDB, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} - if spec.MinAvailable != nil && spec.MaxUnavailable != nil { - allErrs = append(allErrs, field.Invalid(fldPath, spec, "minAvailable and maxUnavailable cannot be both set")) - } - - if spec.MinAvailable != nil { - allErrs = append(allErrs, validatePositiveIntOrPercent(*spec.MinAvailable, fldPath.Child("minAvailable"))...) - allErrs = append(allErrs, isNotMoreThan100Percent(*spec.MinAvailable, fldPath.Child("minAvailable"))...) - } - - if spec.MaxUnavailable != nil { - allErrs = append(allErrs, validatePositiveIntOrPercent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...) - allErrs = append(allErrs, isNotMoreThan100Percent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...) + if pdb.MinAvailable != nil && pdb.MaxUnavailable != nil { + allErrs = append(allErrs, field.Invalid(fldPath, pdb, "minAvailable and maxUnavailable cannot be both set")) } - if spec.Selector != nil { - allErrs = append(allErrs, field.Invalid(fldPath, spec, "selector is automatically set by the controller")) + if pdb.MinAvailable != nil { + allErrs = append(allErrs, validatePositiveIntOrPercent(*pdb.MinAvailable, fldPath.Child("minAvailable"))...) + allErrs = append(allErrs, isNotMoreThan100Percent(*pdb.MinAvailable, fldPath.Child("minAvailable"))...) } - if spec.UnhealthyPodEvictionPolicy != nil && !supportedUnhealthyPodEvictionPolicies.Has(string(*spec.UnhealthyPodEvictionPolicy)) { - allErrs = append(allErrs, field.NotSupported(fldPath.Child("unhealthyPodEvictionPolicy"), *spec.UnhealthyPodEvictionPolicy, supportedUnhealthyPodEvictionPolicies.List())) + if pdb.MaxUnavailable != nil { + allErrs = append(allErrs, validatePositiveIntOrPercent(*pdb.MaxUnavailable, fldPath.Child("maxUnavailable"))...) + allErrs = append(allErrs, isNotMoreThan100Percent(*pdb.MaxUnavailable, fldPath.Child("maxUnavailable"))...) } return allErrs diff --git a/v2/api/v2/egress_webhook_test.go b/v2/api/v2/egress_webhook_test.go index 2b5316c7..863698b9 100644 --- a/v2/api/v2/egress_webhook_test.go +++ b/v2/api/v2/egress_webhook_test.go @@ -7,7 +7,6 @@ import ( . "github.com/onsi/gomega" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - policyv1 "k8s.io/api/policy/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -181,7 +180,7 @@ var _ = Describe("Egress Webhook", func() { It("should allow valid PDB", func() { r := makeEgress() maxUnavailable := intstr.FromInt(1) - r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable} + r.Spec.PodDisruptionBudget = &EgressPDB{MaxUnavailable: &maxUnavailable} err := k8sClient.Create(ctx, r) Expect(err).NotTo(HaveOccurred()) }) @@ -190,26 +189,13 @@ var _ = Describe("Egress Webhook", func() { r := makeEgress() maxUnavailable := intstr.FromInt(1) minAvailable := intstr.FromInt(1) - r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable, MinAvailable: &minAvailable} + r.Spec.PodDisruptionBudget = &EgressPDB{MaxUnavailable: &maxUnavailable, MinAvailable: &minAvailable} err := k8sClient.Create(ctx, r) Expect(err).To(HaveOccurred()) r = makeEgress() maxUnavailable = intstr.FromString("120%") - r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable} - err = k8sClient.Create(ctx, r) - Expect(err).To(HaveOccurred()) - - r = makeEgress() - maxUnavailable = intstr.FromInt(1) - r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable, Selector: &metav1.LabelSelector{}} - err = k8sClient.Create(ctx, r) - Expect(err).To(HaveOccurred()) - - r = makeEgress() - maxUnavailable = intstr.FromInt(1) - policyType := policyv1.UnhealthyPodEvictionPolicyType("Invalid") - r.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{MaxUnavailable: &maxUnavailable, UnhealthyPodEvictionPolicy: &policyType} + r.Spec.PodDisruptionBudget = &EgressPDB{MaxUnavailable: &maxUnavailable} err = k8sClient.Create(ctx, r) Expect(err).To(HaveOccurred()) }) diff --git a/v2/api/v2/zz_generated.deepcopy.go b/v2/api/v2/zz_generated.deepcopy.go index 5156b992..2191c8be 100644 --- a/v2/api/v2/zz_generated.deepcopy.go +++ b/v2/api/v2/zz_generated.deepcopy.go @@ -7,8 +7,8 @@ package v2 import ( "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - policyv1 "k8s.io/api/policy/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -330,6 +330,31 @@ func (in *EgressList) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EgressPDB) DeepCopyInto(out *EgressPDB) { + *out = *in + if in.MinAvailable != nil { + in, out := &in.MinAvailable, &out.MinAvailable + *out = new(intstr.IntOrString) + **out = **in + } + if in.MaxUnavailable != nil { + in, out := &in.MaxUnavailable, &out.MaxUnavailable + *out = new(intstr.IntOrString) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressPDB. +func (in *EgressPDB) DeepCopy() *EgressPDB { + if in == nil { + return nil + } + out := new(EgressPDB) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EgressPodTemplate) DeepCopyInto(out *EgressPodTemplate) { *out = *in @@ -372,7 +397,7 @@ func (in *EgressSpec) DeepCopyInto(out *EgressSpec) { } if in.PodDisruptionBudget != nil { in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget - *out = new(policyv1.PodDisruptionBudgetSpec) + *out = new(EgressPDB) (*in).DeepCopyInto(*out) } } diff --git a/v2/config/crd/bases/coil.cybozu.com_egresses.yaml b/v2/config/crd/bases/coil.cybozu.com_egresses.yaml index 96280004..491eee7d 100644 --- a/v2/config/crd/bases/coil.cybozu.com_egresses.yaml +++ b/v2/config/crd/bases/coil.cybozu.com_egresses.yaml @@ -60,94 +60,16 @@ spec: anyOf: - type: integer - type: string - description: An eviction is allowed if at most "maxUnavailable" - pods selected by "selector" are unavailable after the eviction, - i.e. even in absence of the evicted pod. For example, one can - prevent all voluntary evictions by specifying 0. This is a mutually - exclusive setting with "minAvailable". + description: MaxUnavailable is the maximum number of pods that + can be unavailable at any given time. x-kubernetes-int-or-string: true minAvailable: anyOf: - type: integer - type: string - description: An eviction is allowed if at least "minAvailable" - pods selected by "selector" will still be available after the - eviction, i.e. even in the absence of the evicted pod. So for - example you can prevent all voluntary evictions by specifying - "100%". + description: MinAvailable is the minimum number of pods that must + be available at any given time. x-kubernetes-int-or-string: true - selector: - description: Label query over pods whose evictions are managed - by the disruption budget. A null selector will match no pods, - while an empty ({}) selector will select all pods within the - namespace. - properties: - matchExpressions: - description: matchExpressions is a list of label selector - requirements. The requirements are ANDed. - items: - description: A label selector requirement is a selector - that contains values, a key, and an operator that relates - the key and values. - properties: - key: - description: key is the label key that the selector - applies to. - type: string - operator: - description: operator represents a key's relationship - to a set of values. Valid operators are In, NotIn, - Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If - the operator is In or NotIn, the values array must - be non-empty. If the operator is Exists or DoesNotExist, - the values array must be empty. This array is replaced - during a strategic merge patch. - items: - type: string - type: array - required: - - key - - operator - type: object - type: array - matchLabels: - additionalProperties: - type: string - description: matchLabels is a map of {key,value} pairs. A - single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is "key", - the operator is "In", and the values array contains only - "value". The requirements are ANDed. - type: object - type: object - x-kubernetes-map-type: atomic - unhealthyPodEvictionPolicy: - description: "UnhealthyPodEvictionPolicy defines the criteria - for when unhealthy pods should be considered for eviction. Current - implementation considers healthy pods, as pods that have status.conditions - item with type=\"Ready\",status=\"True\". \n Valid policies - are IfHealthyBudget and AlwaysAllow. If no policy is specified, - the default behavior will be used, which corresponds to the - IfHealthyBudget policy. \n IfHealthyBudget policy means that - running pods (status.phase=\"Running\"), but not yet healthy - can be evicted only if the guarded application is not disrupted - (status.currentHealthy is at least equal to status.desiredHealthy). - Healthy pods will be subject to the PDB for eviction. \n AlwaysAllow - policy means that all running pods (status.phase=\"Running\"), - but not yet healthy are considered disrupted and can be evicted - regardless of whether the criteria in a PDB is met. This means - perspective running pods of a disrupted application might not - get a chance to become healthy. Healthy pods will be subject - to the PDB for eviction. \n Additional policies may be added - in the future. Clients making eviction decisions should disallow - eviction of unhealthy pods if they encounter an unrecognized - policy in this field. \n This field is beta-level. The eviction - API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy - is enabled (enabled by default)." - type: string type: object replicas: default: 1 diff --git a/v2/config/rbac/coil-controller_role.yaml b/v2/config/rbac/coil-controller_role.yaml index d40259de..99da96c7 100644 --- a/v2/config/rbac/coil-controller_role.yaml +++ b/v2/config/rbac/coil-controller_role.yaml @@ -100,6 +100,7 @@ rules: - poddisruptionbudgets verbs: - create + - delete - get - list - patch diff --git a/v2/controllers/egress_controller.go b/v2/controllers/egress_controller.go index 3154e4ae..e66de3ad 100644 --- a/v2/controllers/egress_controller.go +++ b/v2/controllers/egress_controller.go @@ -33,7 +33,7 @@ type EgressReconciler struct { // +kubebuilder:rbac:groups=coil.cybozu.com,resources=egresses/status,verbs=get;update;patch // +kubebuilder:rbac:groups="",resources=services;serviceaccounts,verbs=get;list;watch;create;update;patch // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch -// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch +// +kubebuilder:rbac:groups=policy,resources=poddisruptionbudgets,verbs=get;list;watch;create;update;patch;delete // coil-controller needs to have access to Pods to grant egress service accounts the same privilege. // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch @@ -380,7 +380,7 @@ func (r *EgressReconciler) reconcilePDB(ctx context.Context, log logr.Logger, eg pdb := &policyv1.PodDisruptionBudget{} pdb.Namespace = eg.Namespace - pdb.Name = eg.Name + "-pdb" + pdb.Name = eg.Name result, err := ctrl.CreateOrUpdate(ctx, r.Client, pdb, func() error { if pdb.DeletionTimestamp != nil { @@ -402,11 +402,14 @@ func (r *EgressReconciler) reconcilePDB(ctx context.Context, log logr.Logger, eg } } - eg.Spec.PodDisruptionBudget.DeepCopyInto(&pdb.Spec) + if eg.Spec.PodDisruptionBudget.MinAvailable != nil { + pdb.Spec.MinAvailable = eg.Spec.PodDisruptionBudget.MinAvailable + } + if eg.Spec.PodDisruptionBudget.MaxUnavailable != nil { + pdb.Spec.MaxUnavailable = eg.Spec.PodDisruptionBudget.MaxUnavailable + } pdb.Spec.Selector = &metav1.LabelSelector{ - MatchLabels: map[string]string{ - constants.LabelAppName: "coil", - }, + MatchLabels: selectorLabels(eg.Name), } return nil diff --git a/v2/controllers/egress_controller_test.go b/v2/controllers/egress_controller_test.go index c9b7772e..20c237eb 100644 --- a/v2/controllers/egress_controller_test.go +++ b/v2/controllers/egress_controller_test.go @@ -516,7 +516,7 @@ var _ = Describe("Egress reconciler", func() { By("creating an Egress") eg := makeEgress("eg7") minAvailable := intstr.FromInt(1) - eg.Spec.PodDisruptionBudget = &policyv1.PodDisruptionBudgetSpec{ + eg.Spec.PodDisruptionBudget = &coilv2.EgressPDB{ MinAvailable: &minAvailable, } err := k8sClient.Create(ctx, eg) @@ -526,7 +526,7 @@ var _ = Describe("Egress reconciler", func() { var pdb *policyv1.PodDisruptionBudget Eventually(func() error { pdb = &policyv1.PodDisruptionBudget{} - return k8sClient.Get(ctx, client.ObjectKey{Namespace: eg.Namespace, Name: eg.Name + "-pdb"}, pdb) + return k8sClient.Get(ctx, client.ObjectKey{Namespace: eg.Namespace, Name: eg.Name}, pdb) }).Should(Succeed()) Expect(*pdb.Spec.MinAvailable).To(Equal(intstr.FromInt(1))) From 2c86454322e41cab20d2c0491a45227eb7c71928 Mon Sep 17 00:00:00 2001 From: naoki-take Date: Tue, 2 Apr 2024 09:39:34 +0000 Subject: [PATCH 3/5] Add PDB deletion test Signed-off-by: naoki-take --- v2/e2e/coil_test.go | 33 ++++++++++++++++++++++--- v2/e2e/manifests/egress-sport-auto.yaml | 5 ++++ v2/e2e/manifests/egress-updated.yaml | 5 ++++ v2/e2e/manifests/egress.yaml | 5 ++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/v2/e2e/coil_test.go b/v2/e2e/coil_test.go index b5558120..eb832f60 100644 --- a/v2/e2e/coil_test.go +++ b/v2/e2e/coil_test.go @@ -15,6 +15,7 @@ import ( "github.com/prometheus/common/expfmt" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" ) var testIPv6 = os.Getenv("TEST_IPV6") == "true" @@ -244,9 +245,6 @@ var _ = Describe("Coil", func() { }) It("should be able to run Egress pods", func() { - By("creating internet namespace") - kubectlSafe(nil, "create", "ns", "internet") - By("defining Egress in the internet namespace") kubectlSafe(nil, "apply", "-f", "manifests/egress.yaml") @@ -260,6 +258,16 @@ var _ = Describe("Coil", func() { return int(depl.Status.ReadyReplicas) }).Should(Equal(2)) + By("checking PDB") + Eventually(func() error { + pdb := &policyv1.PodDisruptionBudget{} + err := getResource("internet", "pdb", "egress", "", pdb) + if err != nil { + return err + } + return nil + }).Should(Succeed()) + By("defining Egress with fouSourcePortAuto in the internet namespace") kubectlSafe(nil, "apply", "-f", "manifests/egress-sport-auto.yaml") @@ -272,9 +280,25 @@ var _ = Describe("Coil", func() { } return int(depl.Status.ReadyReplicas) }).Should(Equal(2)) + + By("deleting Egress") + kubectlSafe(nil, "delete", "-f", "manifests/egress.yaml") + + By("checking PDB deletion") + Eventually(func() error { + pdb := &policyv1.PodDisruptionBudget{} + err := getResource("internet", "pdb", "egress", "", pdb) + if err != nil { + return err + } + return nil + }).ShouldNot(Succeed()) }) It("should be able to run NAT client pods", func() { + By("defining Egress in the internet namespace") + kubectlSafe(nil, "apply", "-f", "manifests/egress.yaml") + By("creating a NAT client pod") kubectlSafe(nil, "apply", "-f", "manifests/nat-client.yaml") @@ -295,6 +319,9 @@ var _ = Describe("Coil", func() { return nil }).Should(Succeed()) + By("defining Egress with fouSourcePortAuto in the internet namespace") + kubectlSafe(nil, "apply", "-f", "manifests/egress-sport-auto.yaml") + By("creating a NAT client pod for fouSourcePortAuto") kubectlSafe(nil, "apply", "-f", "manifests/nat-client-sport-auto.yaml") diff --git a/v2/e2e/manifests/egress-sport-auto.yaml b/v2/e2e/manifests/egress-sport-auto.yaml index 5ef44082..25d28051 100644 --- a/v2/e2e/manifests/egress-sport-auto.yaml +++ b/v2/e2e/manifests/egress-sport-auto.yaml @@ -1,3 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: internet +--- apiVersion: coil.cybozu.com/v2 kind: Egress metadata: diff --git a/v2/e2e/manifests/egress-updated.yaml b/v2/e2e/manifests/egress-updated.yaml index 18824739..592804f0 100644 --- a/v2/e2e/manifests/egress-updated.yaml +++ b/v2/e2e/manifests/egress-updated.yaml @@ -1,3 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: internet +--- apiVersion: coil.cybozu.com/v2 kind: Egress metadata: diff --git a/v2/e2e/manifests/egress.yaml b/v2/e2e/manifests/egress.yaml index 1db0710f..ac84aa86 100644 --- a/v2/e2e/manifests/egress.yaml +++ b/v2/e2e/manifests/egress.yaml @@ -1,3 +1,8 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: internet +--- apiVersion: coil.cybozu.com/v2 kind: Egress metadata: From ba71ceb28f35246f62c12adb20c740335839ef9c Mon Sep 17 00:00:00 2001 From: naoki-take Date: Thu, 4 Apr 2024 03:07:41 +0000 Subject: [PATCH 4/5] Reflect comments Signed-off-by: naoki-take --- docs/usage.md | 6 +++--- v2/api/v2/egress_types.go | 8 +++++--- v2/api/v2/egress_webhook_test.go | 6 +++--- v2/api/v2/zz_generated.deepcopy.go | 10 +++++----- v2/controllers/egress_controller.go | 3 +-- v2/controllers/egress_controller_test.go | 2 +- v2/e2e/coil_test.go | 2 +- 7 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index f192dfaa..3b44a62d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -173,7 +173,7 @@ Step 5 and 6 are automatically done by Coil. It defines an egress portal of the cluster for some destinations. Coil creates a `Deployment` and `Service` for each `Egress`. -It also creates `PodDisruptionBudget` when `spec.podDisruptionBudget` is specified. +It also creates a `PodDisruptionBudget` when `spec.podDisruptionBudget` is specified. Here is an example `Egress` resource for the Internet: @@ -247,7 +247,7 @@ You may customize the container of egress Pods as shown in the above example. | `template` | [PodTemplateSpec][] | Copied to Deployment's `spec.template`. | | `sessionAffinity` | `ClusterIP` or `None` | Copied to Service's `spec.sessionAffinity`. Default is `ClusterIP`. | | `sessionAffinityConfig` | [SessionAffinityConfig][] | Copied to Service's `spec.sessionAffinityConfig`. | -| `podDisruptionBudget` | `EgressPDB` | `minAvailable` and `maxUnavailable` are supported. | +| `podDisruptionBudget` | `EgressPDBSpec` | `minAvailable` and `maxUnavailable` are copied to PDB's spec. | ### Client Pods @@ -371,4 +371,4 @@ The example of Grafana dashboard is [here](../v2/dashboard/coil.json). [DeploymentStrategy]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#deploymentstrategy-v1-apps [PodTemplateSpec]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#podtemplatespec-v1-core [SessionAffinityConfig]: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#sessionaffinityconfig-v1-core -[NetworkPolicy]: https://kubernetes.io/docs/concepts/services-networking/network-policies/ \ No newline at end of file +[NetworkPolicy]: https://kubernetes.io/docs/concepts/services-networking/network-policies/ diff --git a/v2/api/v2/egress_types.go b/v2/api/v2/egress_types.go index 55f8c23d..3622ef26 100644 --- a/v2/api/v2/egress_types.go +++ b/v2/api/v2/egress_types.go @@ -66,7 +66,7 @@ type EgressSpec struct { // PodDisruptionBudget is an optional PodDisruptionBudget for Egress NAT pods. // +optional - PodDisruptionBudget *EgressPDB `json:"podDisruptionBudget,omitempty"` + PodDisruptionBudget *EgressPDBSpec `json:"podDisruptionBudget,omitempty"` } // EgressPodTemplate defines pod template for Egress @@ -84,7 +84,7 @@ type EgressPodTemplate struct { } // EgressPDB defines PDB for Egress -type EgressPDB struct { +type EgressPDBSpec struct { // MinAvailable is the minimum number of pods that must be available at any given time. // +optional MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"` @@ -150,7 +150,9 @@ func (es EgressSpec) validateUpdate() field.ErrorList { return es.validate() } -func validatePodDisruptionBudget(pdb EgressPDB, fldPath *field.Path) field.ErrorList { +// For validation of PodDisruptionBudget +// Ref. https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/policy/validation/validation.go +func validatePodDisruptionBudget(pdb EgressPDBSpec, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if pdb.MinAvailable != nil && pdb.MaxUnavailable != nil { diff --git a/v2/api/v2/egress_webhook_test.go b/v2/api/v2/egress_webhook_test.go index 863698b9..16e3988d 100644 --- a/v2/api/v2/egress_webhook_test.go +++ b/v2/api/v2/egress_webhook_test.go @@ -180,7 +180,7 @@ var _ = Describe("Egress Webhook", func() { It("should allow valid PDB", func() { r := makeEgress() maxUnavailable := intstr.FromInt(1) - r.Spec.PodDisruptionBudget = &EgressPDB{MaxUnavailable: &maxUnavailable} + r.Spec.PodDisruptionBudget = &EgressPDBSpec{MaxUnavailable: &maxUnavailable} err := k8sClient.Create(ctx, r) Expect(err).NotTo(HaveOccurred()) }) @@ -189,13 +189,13 @@ var _ = Describe("Egress Webhook", func() { r := makeEgress() maxUnavailable := intstr.FromInt(1) minAvailable := intstr.FromInt(1) - r.Spec.PodDisruptionBudget = &EgressPDB{MaxUnavailable: &maxUnavailable, MinAvailable: &minAvailable} + r.Spec.PodDisruptionBudget = &EgressPDBSpec{MaxUnavailable: &maxUnavailable, MinAvailable: &minAvailable} err := k8sClient.Create(ctx, r) Expect(err).To(HaveOccurred()) r = makeEgress() maxUnavailable = intstr.FromString("120%") - r.Spec.PodDisruptionBudget = &EgressPDB{MaxUnavailable: &maxUnavailable} + r.Spec.PodDisruptionBudget = &EgressPDBSpec{MaxUnavailable: &maxUnavailable} err = k8sClient.Create(ctx, r) Expect(err).To(HaveOccurred()) }) diff --git a/v2/api/v2/zz_generated.deepcopy.go b/v2/api/v2/zz_generated.deepcopy.go index 2191c8be..7313c960 100644 --- a/v2/api/v2/zz_generated.deepcopy.go +++ b/v2/api/v2/zz_generated.deepcopy.go @@ -331,7 +331,7 @@ func (in *EgressList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *EgressPDB) DeepCopyInto(out *EgressPDB) { +func (in *EgressPDBSpec) DeepCopyInto(out *EgressPDBSpec) { *out = *in if in.MinAvailable != nil { in, out := &in.MinAvailable, &out.MinAvailable @@ -345,12 +345,12 @@ func (in *EgressPDB) DeepCopyInto(out *EgressPDB) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressPDB. -func (in *EgressPDB) DeepCopy() *EgressPDB { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EgressPDBSpec. +func (in *EgressPDBSpec) DeepCopy() *EgressPDBSpec { if in == nil { return nil } - out := new(EgressPDB) + out := new(EgressPDBSpec) in.DeepCopyInto(out) return out } @@ -397,7 +397,7 @@ func (in *EgressSpec) DeepCopyInto(out *EgressSpec) { } if in.PodDisruptionBudget != nil { in, out := &in.PodDisruptionBudget, &out.PodDisruptionBudget - *out = new(EgressPDB) + *out = new(EgressPDBSpec) (*in).DeepCopyInto(*out) } } diff --git a/v2/controllers/egress_controller.go b/v2/controllers/egress_controller.go index e66de3ad..190c999b 100644 --- a/v2/controllers/egress_controller.go +++ b/v2/controllers/egress_controller.go @@ -390,8 +390,7 @@ func (r *EgressReconciler) reconcilePDB(ctx context.Context, log logr.Logger, eg if pdb.Labels == nil { pdb.Labels = make(map[string]string) } - labels := selectorLabels(eg.Name) - for k, v := range labels { + for k, v := range selectorLabels(eg.Name) { pdb.Labels[k] = v } diff --git a/v2/controllers/egress_controller_test.go b/v2/controllers/egress_controller_test.go index 20c237eb..51af6eeb 100644 --- a/v2/controllers/egress_controller_test.go +++ b/v2/controllers/egress_controller_test.go @@ -516,7 +516,7 @@ var _ = Describe("Egress reconciler", func() { By("creating an Egress") eg := makeEgress("eg7") minAvailable := intstr.FromInt(1) - eg.Spec.PodDisruptionBudget = &coilv2.EgressPDB{ + eg.Spec.PodDisruptionBudget = &coilv2.EgressPDBSpec{ MinAvailable: &minAvailable, } err := k8sClient.Create(ctx, eg) diff --git a/v2/e2e/coil_test.go b/v2/e2e/coil_test.go index eb832f60..f8600623 100644 --- a/v2/e2e/coil_test.go +++ b/v2/e2e/coil_test.go @@ -282,7 +282,7 @@ var _ = Describe("Coil", func() { }).Should(Equal(2)) By("deleting Egress") - kubectlSafe(nil, "delete", "-f", "manifests/egress.yaml") + kubectlSafe(nil, "delete", "egress", "-n", "internet", "egress") By("checking PDB deletion") Eventually(func() error { From ad8fa7c7b0267b45c944fee47c1aba226cf9313d Mon Sep 17 00:00:00 2001 From: naoki-take Date: Thu, 4 Apr 2024 07:17:53 +0000 Subject: [PATCH 5/5] Update go.mod and go.sum Signed-off-by: naoki-take --- v2/go.mod | 2 +- v2/go.sum | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/v2/go.mod b/v2/go.mod index b281757a..4b98d395 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -1,6 +1,6 @@ module github.com/cybozu-go/coil/v2 -go 1.22 +go 1.22.2 require ( github.com/bits-and-blooms/bitset v1.13.0 diff --git a/v2/go.sum b/v2/go.sum index a2a5e172..95ef0c63 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -9,6 +9,7 @@ github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJR github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -17,6 +18,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containernetworking/cni v1.1.2 h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ= github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= github.com/containernetworking/plugins v1.4.1 h1:+sJRRv8PKhLkXIl6tH1D7RMi+CbbHutDGU+ErLBORWA= @@ -38,6 +40,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -226,8 +229,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -256,6 +259,7 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -372,6 +376,7 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -395,8 +400,8 @@ k8s.io/component-base v0.27.7 h1:kngM58HR9W9Nqpv7e4rpdRyWnKl/ABpUhLAZ+HoliMs= k8s.io/component-base v0.27.7/go.mod h1:YGjlCVL1oeKvG3HSciyPHFh+LCjIEqsxz4BDR3cfHRs= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515 h1:OmK1d0WrkD3IPfkskvroRykOulHVHf0s0ZIFRjyt+UI= -k8s.io/kube-openapi v0.0.0-20230525220651-2546d827e515/go.mod h1:kzo02I3kQ4BTtEfVLaPbjvCkX97YqGve33wzlb3fofQ= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/utils v0.0.0-20240310230437-4693a0247e57 h1:gbqbevonBh57eILzModw6mrkbwM0gQBEuevE/AaBsHY= k8s.io/utils v0.0.0-20240310230437-4693a0247e57/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.15.3 h1:L+t5heIaI3zeejoIyyvLQs5vTVu/67IU2FfisVzFlBc=