Skip to content

Commit

Permalink
Do apply in stages and fix replicas conflicts
Browse files Browse the repository at this point in the history
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
  • Loading branch information
stefanprodan committed Sep 6, 2021
1 parent b7067df commit a424501
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 31 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ jobs:
kubectl get crd tests.testing.kustomizer.dev 2>&1 | grep NotFound
- name: Load test apply (110 objects)
run: |
./bin/kustomizer apply -k ./testdata/loadtest/ --inventory-name load-test
./bin/kustomizer apply -i load-test -k ./testdata/loadtest/
- name: Load test delete (110 objects)
run: |
./bin/kustomizer delete --inventory-name load-test
./bin/kustomizer delete -i load-test
- name: Test staged apply
run: |
./bin/kustomizer apply -k ./testdata/certs/ --prune --wait --inventory-name cert-test --mode=ApplyAllStaged
./bin/kustomizer apply -i cert-test -k ./testdata/certs/ --prune --wait
kubectl -n kustomizer-cert-test wait issuers/my-ca-issuer --for=condition=ready --timeout=1m
- name: Debug failure
if: failure()
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ build:
CGO_ENABLED=0 go build -o ./bin/kustomizer ./cmd/kustomizer

install:
go install cmd/kustomizer
go install ./cmd/kustomizer

install-dev:
CGO_ENABLED=0 go build -o /usr/local/bin ./cmd/kustomizer
Expand Down
68 changes: 48 additions & 20 deletions cmd/kustomizer/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ package main
import (
"context"
"fmt"
"sort"
"time"

"github.com/spf13/cobra"
"github.com/stefanprodan/kustomizer/pkg/inventory"
"github.com/stefanprodan/kustomizer/pkg/manager"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/cli-utils/pkg/ordering"
)

var applyCmd = &cobra.Command{
Expand All @@ -40,7 +43,6 @@ type applyFlags struct {
wait bool
force bool
prune bool
mode string
}

var applyArgs applyFlags
Expand All @@ -56,8 +58,7 @@ func init() {
applyCmd.Flags().StringVarP(&applyArgs.inventoryName, "inventory-name", "i", "", "The name of the inventory configmap.")
applyCmd.Flags().StringVar(&applyArgs.inventoryNamespace, "inventory-namespace", "default",
"The namespace of the inventory configmap. The namespace must exist on the target cluster.")
applyCmd.Flags().StringVar(&applyArgs.mode, "mode", "Apply",
"The ResourceManager apply method, can be `Apply`, `ApplyAll`, `ApplyAllStaged`.")

rootCmd.AddCommand(applyCmd)
}

Expand All @@ -84,6 +85,10 @@ func runApplyCmd(cmd *cobra.Command, args []string) error {
}
logger.Println(fmt.Sprintf("applying %v manifest(s)...", len(objects)))

for _, object := range objects {
fixReplicasConflict(object, objects)
}

kubeClient, err := newKubeClient(rootArgs.kubeconfig, rootArgs.kubecontext)
if err != nil {
return fmt.Errorf("client init failed: %w", err)
Expand All @@ -101,33 +106,41 @@ func runApplyCmd(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout)
defer cancel()

switch applyArgs.mode {
case "Apply":
for _, object := range objects {
change, err := resMgr.Apply(ctx, object, applyArgs.force)
if err != nil {
return err
}
logger.Println(change.String())
// contains only CRDs and Namespaces
var stageOne []*unstructured.Unstructured

// contains all objects except for CRDs and Namespaces
var stageTwo []*unstructured.Unstructured

for _, u := range objects {
if resMgr.IsClusterDefinition(u.GetKind()) {
stageOne = append(stageOne, u)
} else {
stageTwo = append(stageTwo, u)
}
case "ApplyAll":
changeSet, err := resMgr.ApplyAll(ctx, objects, applyArgs.force)
}

if len(stageOne) > 0 {
changeSet, err := resMgr.ApplyAll(ctx, stageOne, applyArgs.force)
if err != nil {
return err
}
for _, change := range changeSet.Entries {
logger.Println(change.String())
}
case "ApplyAllStaged":
changeSet, err := resMgr.ApplyAllStaged(ctx, objects, applyArgs.force, 30*time.Second)
if err != nil {

if err := resMgr.Wait(stageOne, 2*time.Second, 30*time.Second); err != nil {
return err
}
for _, change := range changeSet.Entries {
logger.Println(change.String())
}

sort.Sort(ordering.SortableUnstructureds(stageTwo))
for _, object := range stageTwo {
change, err := resMgr.Apply(ctx, object, applyArgs.force)
if err != nil {
return err
}
default:
return fmt.Errorf("mode not supported")
logger.Println(change.String())
}

staleObjects, err := resMgr.GetInventoryStaleObjects(ctx, newInventory)
Expand Down Expand Up @@ -170,3 +183,18 @@ func runApplyCmd(cmd *cobra.Command, args []string) error {

return nil
}

// fixReplicasConflict removes the replicas field from the given workload if it's managed by an HPA
func fixReplicasConflict(object *unstructured.Unstructured, objects []*unstructured.Unstructured) {
for _, hpa := range objects {
if hpa.GetKind() == "HorizontalPodAutoscaler" && object.GetNamespace() == hpa.GetNamespace() {
targetKind, found, err := unstructured.NestedFieldCopy(hpa.Object, "spec", "scaleTargetRef", "kind")
if err == nil && found && fmt.Sprintf("%v", targetKind) == object.GetKind() {
targetName, found, err := unstructured.NestedFieldCopy(hpa.Object, "spec", "scaleTargetRef", "name")
if err == nil && found && fmt.Sprintf("%v", targetName) == object.GetName() {
unstructured.RemoveNestedField(object.Object, "spec", "replicas")
}
}
}
}
}
6 changes: 3 additions & 3 deletions pkg/manager/manager_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ func (m *ResourceManager) ApplyAllStaged(ctx context.Context, objects []*unstruc
// contains only CRDs and Namespaces
var stageOne []*unstructured.Unstructured

// contains all objects except for CRDs and Namespaces
// contains all objects except for CRDs and Namespaces
var stageTwo []*unstructured.Unstructured

for _, u := range objects {
if m.isClusterDefinition(u.GetKind()) {
if m.IsClusterDefinition(u.GetKind()) {
stageOne = append(stageOne, u)
} else {
stageTwo = append(stageTwo, u)
Expand Down Expand Up @@ -172,7 +172,7 @@ func (m *ResourceManager) apply(ctx context.Context, object *unstructured.Unstru
return m.client.Patch(ctx, object, client.Apply, opts...)
}

func (m *ResourceManager) isClusterDefinition(kind string) bool {
func (m *ResourceManager) IsClusterDefinition(kind string) bool {
switch strings.ToLower(kind) {
case "customresourcedefinition":
case "namespace":
Expand Down
2 changes: 1 addition & 1 deletion pkg/manager/manager_inventory.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (m *ResourceManager) DeleteInventory(ctx context.Context, i *inventory.Inve
return nil
}

// GetInventoryStaleObjects returns the list of objects subject to pruning.
// GetInventoryStaleObjects returns the list of objects metadata subject to pruning.
func (m *ResourceManager) GetInventoryStaleObjects(ctx context.Context, i *inventory.Inventory) ([]*unstructured.Unstructured, error) {
objects := make([]*unstructured.Unstructured, 0)
existingInventory := inventory.NewInventory(i.Name, i.Namespace)
Expand Down
22 changes: 21 additions & 1 deletion pkg/objectutil/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func ReadObjects(r io.Reader) ([]*unstructured.Unstructured, error) {
continue
}

if IsKubernetesObject(obj) {
if IsKubernetesObject(obj) && !IsKustomization(obj) {
objects = append(objects, obj)
}
}
Expand All @@ -83,6 +83,26 @@ func IsKubernetesObject(object *unstructured.Unstructured) bool {
return true
}

func IsKustomization(object *unstructured.Unstructured) bool {
if object.GetKind() == "Kustomization" && object.GroupVersionKind().GroupKind().Group == "kustomize.config.k8s.io" {
return true
}
return false
}

// ObjectToYAML encodes the given Kubernetes API object to YAML.
func ObjectToYAML(object *unstructured.Unstructured) string {
var builder strings.Builder
data, err := yaml.Marshal(object)
if err != nil {
return ""
}
builder.Write(data)
builder.WriteString("---\n")

return builder.String()
}

// ObjectsToYAML encodes the given Kubernetes API objects to a YAML multi-doc.
func ObjectsToYAML(objects []*unstructured.Unstructured) (string, error) {
var builder strings.Builder
Expand Down
1 change: 1 addition & 0 deletions testdata/plain/backend/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ metadata:
name: backend
namespace: kustomizer-demo
spec:
replicas: 1
minReadySeconds: 3
revisionHistoryLimit: 5
progressDeadlineSeconds: 60
Expand Down
4 changes: 2 additions & 2 deletions testdata/plain/backend/hpa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ spec:
apiVersion: apps/v1
kind: Deployment
name: backend
minReplicas: 1
maxReplicas: 2
minReplicas: 2
maxReplicas: 4
metrics:
- type: Resource
resource:
Expand Down

0 comments on commit a424501

Please sign in to comment.