├── .dockerignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yaml │ ├── docker-image.yaml │ ├── e2e-advanced-deployment-1.19.yaml │ ├── e2e-advanced-deployment-1.23.yaml │ ├── e2e-advanced-deployment-1.26.yaml │ ├── e2e-cloneset-1.19.yaml │ ├── e2e-cloneset-1.23.yaml │ ├── e2e-cloneset-1.26.yaml │ ├── e2e-custom.yaml │ ├── e2e-daemonset-1.19.yaml │ ├── e2e-daemonset-1.23.yaml │ ├── e2e-daemonset-1.26.yaml │ ├── e2e-deployment-1.19.yaml │ ├── e2e-deployment-1.23.yaml │ ├── e2e-deployment-1.26.yaml │ ├── e2e-gateway.yaml │ ├── e2e-multi-network-provider.yaml │ ├── e2e-others-1.19.yaml │ ├── e2e-others-1.23.yaml │ ├── e2e-others-1.26.yaml │ ├── e2e-statefulset-1.19.yaml │ ├── e2e-statefulset-1.23.yaml │ ├── e2e-statefulset-1.26.yaml │ ├── e2e-v1beta1-bluegreen-1.19.yaml │ ├── e2e-v1beta1-bluegreen-1.23.yaml │ ├── e2e-v1beta1-bluegreen-1.26.yaml │ ├── e2e-v1beta1-jump-1.19.yaml │ ├── e2e-v1beta1-jump-1.23.yaml │ ├── e2e-v1beta1-jump-1.26.yaml │ └── license.yml ├── .gitignore ├── .golangci.yml ├── .license ├── README.md └── dependency_decisions.yml ├── .lift.toml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile_multiarch ├── LICENSE ├── Makefile ├── OWNERS ├── PROJECT ├── README.md ├── api ├── addtoscheme_apps_v1alpha1.go ├── addtoscheme_apps_v1beta1.go ├── apis.go ├── v1alpha1 │ ├── batchrelease_plan_types.go │ ├── batchrelease_types.go │ ├── conversion.go │ ├── deployment_types.go │ ├── groupversion_info.go │ ├── rollout_types.go │ ├── rollouthistory_types.go │ ├── trafficrouting_types.go │ └── zz_generated.deepcopy.go └── v1beta1 │ ├── batchrelease_plan_types.go │ ├── batchrelease_types.go │ ├── convertion.go │ ├── deployment_types.go │ ├── groupversion_info.go │ ├── rollout_types.go │ ├── trafficrouting.go │ └── zz_generated.deepcopy.go ├── config ├── crd │ ├── bases │ │ ├── rollouts.kruise.io_batchreleases.yaml │ │ ├── rollouts.kruise.io_rollouthistories.yaml │ │ ├── rollouts.kruise.io_rollouts.yaml │ │ └── rollouts.kruise.io_trafficroutings.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_batchreleases.yaml │ │ ├── cainjection_in_rollouts.yaml │ │ ├── webhook_in_batchreleases.yaml │ │ └── webhook_in_rollouts.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── batchrelease_editor_role.yaml │ ├── batchrelease_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── rollout_editor_role.yaml │ ├── rollout_viewer_role.yaml │ └── service_account.yaml ├── samples │ ├── rollouts_v1alpha1_batchrelease.yaml │ └── rollouts_v1alpha1_rollout.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ ├── patch_manifests.yaml │ └── service.yaml ├── docs ├── contributing │ └── debug.md ├── getting_started │ ├── installation.md │ └── introduction.md ├── images │ ├── approve_rollout.png │ ├── continuous_release.png │ ├── deploy_rollout.png │ ├── echoserver_application.png │ ├── rollback_echoserver.png │ ├── rollout-arch.png │ ├── rollout_canary.png │ ├── rollout_intro.png │ ├── upgrade_echoserver.png │ └── upgrade_failed.png ├── proposals │ ├── 20220803-rollouthistory.md │ └── 20231107-v1beta1-apis.md └── tutorials │ └── basic_usage.md ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── lua_configuration ├── convert_test_case_to_lua_object.go ├── networking.istio.io │ ├── DestinationRule │ │ ├── testdata │ │ │ └── traffic_routing_with_a_match.yaml │ │ └── trafficRouting.lua │ └── VirtualService │ │ ├── testdata │ │ ├── rollout_with_three_steps.yaml │ │ ├── traffic_routing_with_a_match.yaml │ │ ├── traffic_routing_with_matches.yaml │ │ └── traffic_routing_with_weight.yaml │ │ └── trafficRouting.lua └── trafficrouting_ingress │ ├── aliyun-alb.lua │ ├── higress.lua │ ├── mse.lua │ └── nginx.lua ├── main.go ├── pkg ├── controller │ ├── batchrelease │ │ ├── batchrelease_controller.go │ │ ├── batchrelease_controller_test.go │ │ ├── batchrelease_event_handler.go │ │ ├── batchrelease_event_handler_test.go │ │ ├── batchrelease_executor.go │ │ ├── batchrelease_status.go │ │ ├── context │ │ │ ├── context.go │ │ │ └── context_test.go │ │ ├── control │ │ │ ├── apis.go │ │ │ ├── bluegreenstyle │ │ │ │ ├── cloneset │ │ │ │ │ ├── control.go │ │ │ │ │ └── control_test.go │ │ │ │ ├── control_plane.go │ │ │ │ ├── deployment │ │ │ │ │ ├── control.go │ │ │ │ │ └── control_test.go │ │ │ │ ├── hpa │ │ │ │ │ ├── hpa.go │ │ │ │ │ └── hpa_test.go │ │ │ │ └── interface.go │ │ │ ├── canarystyle │ │ │ │ ├── control_plane.go │ │ │ │ ├── deployment │ │ │ │ │ ├── canary.go │ │ │ │ │ ├── control.go │ │ │ │ │ ├── control_test.go │ │ │ │ │ └── stable.go │ │ │ │ └── interface.go │ │ │ ├── interface.go │ │ │ ├── partitionstyle │ │ │ │ ├── cloneset │ │ │ │ │ ├── control.go │ │ │ │ │ └── control_test.go │ │ │ │ ├── control_plane.go │ │ │ │ ├── daemonset │ │ │ │ │ ├── control.go │ │ │ │ │ └── control_test.go │ │ │ │ ├── deployment │ │ │ │ │ ├── control.go │ │ │ │ │ └── control_test.go │ │ │ │ ├── interface.go │ │ │ │ └── statefulset │ │ │ │ │ ├── control.go │ │ │ │ │ └── control_test.go │ │ │ ├── util.go │ │ │ └── util_test.go │ │ └── labelpatch │ │ │ ├── filter.go │ │ │ ├── filter_test.go │ │ │ ├── patcher.go │ │ │ └── patcher_test.go │ ├── deployment │ │ ├── controller.go │ │ ├── deployment_controller.go │ │ ├── deployment_controller_test.go │ │ ├── deployment_event_handler.go │ │ ├── progress.go │ │ ├── rolling.go │ │ ├── rolling_test.go │ │ ├── sync.go │ │ └── util │ │ │ ├── deployment_util.go │ │ │ └── deployment_util_test.go │ ├── rollout │ │ ├── rollout_bluegreen.go │ │ ├── rollout_bluegreen_test.go │ │ ├── rollout_canary.go │ │ ├── rollout_canary_test.go │ │ ├── rollout_controller.go │ │ ├── rollout_controller_test.go │ │ ├── rollout_event_handler.go │ │ ├── rollout_event_handler_test.go │ │ ├── rollout_progressing.go │ │ ├── rollout_progressing_test.go │ │ ├── rollout_releaseManager.go │ │ ├── rollout_status.go │ │ └── rollout_status_test.go │ ├── rollouthistory │ │ ├── rollouthistory_controller.go │ │ ├── rollouthistory_controller_test.go │ │ ├── rollouthistory_event_handler.go │ │ └── util.go │ └── trafficrouting │ │ ├── trafficrouting_controller.go │ │ └── trafficrouting_controller_test.go ├── feature │ ├── rollout_features.go │ └── switch.go ├── trafficrouting │ ├── manager.go │ ├── manager_test.go │ └── network │ │ ├── composite.go │ │ ├── customNetworkProvider │ │ ├── custom_network_provider.go │ │ ├── custom_network_provider_test.go │ │ └── lua_configuration │ │ │ ├── networking.error.io │ │ │ └── LuaError │ │ │ │ └── trafficRouting.lua │ │ │ └── networking.istio.io │ │ │ ├── DestinationRule │ │ │ └── trafficRouting.lua │ │ │ └── VirtualService │ │ │ └── trafficRouting.lua │ │ ├── gateway │ │ ├── gateway.go │ │ └── gateway_test.go │ │ ├── ingress │ │ ├── ingress.go │ │ └── ingress_test.go │ │ └── interface.go ├── util │ ├── client │ │ ├── client.go │ │ ├── delegating_client.go │ │ └── no_deepcopy_lister.go │ ├── condition.go │ ├── configuration │ │ └── configuration.go │ ├── constant.go │ ├── controller_finder.go │ ├── controller_finder_test.go │ ├── errors │ │ └── types.go │ ├── expectation │ │ ├── resource_expectations.go │ │ └── resource_expectations_test.go │ ├── feature │ │ └── feature_gate.go │ ├── grace │ │ ├── grace_expectations.go │ │ ├── grace_expectations_test.go │ │ ├── grace_wrapper.go │ │ └── grace_wrapper_test.go │ ├── labels │ │ ├── labels.go │ │ └── labels_test.go │ ├── lua_configuration.go │ ├── luamanager │ │ ├── json.go │ │ ├── lua.go │ │ └── lua_test.go │ ├── meta.go │ ├── parse_utils.go │ ├── parse_utils_test.go │ ├── patch │ │ ├── patch_utils.go │ │ └── patch_utils_test.go │ ├── pod_utils.go │ ├── ratelimiter │ │ └── rate_limiter.go │ ├── rollout_utils.go │ ├── slices_utils.go │ ├── workloads_utils.go │ └── workloads_utils_test.go └── webhook │ ├── add_rollout.go │ ├── add_workload.go │ ├── rollout │ └── validating │ │ ├── rollout_create_update_handler.go │ │ ├── rollout_create_update_handler_test.go │ │ ├── validate_v1alphal_rollout.go │ │ └── webhooks.go │ ├── server.go │ ├── util │ ├── configuration │ │ └── configuration.go │ ├── controller │ │ └── webhook_controller.go │ ├── crd │ │ └── crd.go │ ├── generator │ │ ├── certgenerator.go │ │ ├── fake │ │ │ └── certgenerator.go │ │ ├── selfsigned.go │ │ └── util.go │ ├── matcher.go │ ├── util.go │ └── writer │ │ ├── atomic │ │ └── atomic_writer.go │ │ ├── certwriter.go │ │ ├── error.go │ │ ├── fs.go │ │ └── secret.go │ └── workload │ └── mutating │ ├── unified_update_handler.go │ ├── unified_update_handler_test.go │ ├── webhooks.go │ ├── workload_update_handler.go │ └── workload_update_handler_test.go ├── rollouts.rollouts.kruise.io-rollouts-demo.yaml ├── scripts └── deploy_kind.sh └── test ├── e2e ├── deployment_test.go ├── rollout_test.go ├── rollout_v1beta1_test.go ├── suite_test.go └── test_data │ ├── batchrelease │ ├── cloneset.yaml │ ├── cloneset_number_100.yaml │ ├── cloneset_percentage_100.yaml │ ├── cloneset_percentage_50.yaml │ ├── deployment.yaml │ ├── deployment_number_100.yaml │ ├── deployment_percentage_100.yaml │ ├── deployment_percentage_50.yaml │ └── sts.yaml │ ├── crds │ └── httproutes.yaml │ ├── customNetworkProvider │ ├── destinationrule.yaml │ ├── istio_crd.yaml │ ├── lua_script_configmap.yaml │ ├── rollout_with_multi_trafficrouting.yaml │ ├── rollout_with_trafficrouting.yaml │ ├── rollout_without_trafficrouting.yaml │ ├── trafficrouting.yaml │ └── virtualservice_without_destinationrule.yaml │ ├── deployment │ └── deployment.yaml │ ├── gateway │ ├── httproute-test.yaml │ └── rollout-test.yaml │ └── rollout │ ├── advanced_statefulset.yaml │ ├── cloneset.yaml │ ├── daemonset.yaml │ ├── deployment.yaml │ ├── deployment_disabled.yaml │ ├── headless_service.yaml │ ├── hpa_v1.yaml │ ├── hpa_v2.yaml │ ├── native_statefulset.yaml │ ├── nginx_ingress.yaml │ ├── rollout-configuration.yaml │ ├── rollout_canary_base.yaml │ ├── rollout_canary_daemonset_base.yaml │ ├── rollout_canary_daemonset_interrupt.yaml │ ├── rollout_disabled.yaml │ ├── rollout_v1beta1_bluegreen_base.yaml │ ├── rollout_v1beta1_bluegreen_cloneset_base.yaml │ ├── rollout_v1beta1_canary_base.yaml │ ├── rollout_v1beta1_partition_base.yaml │ └── service.yaml ├── images └── image_utils.go └── kind-conf.yaml /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore all files which are not go type 3 | !**/*.go 4 | !**/*.mod 5 | !**/*.sum 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ### Ⅰ. Describe what this PR does 6 | 7 | 8 | ### Ⅱ. Does this pull request fix one issue? 9 | 10 | 11 | ### Ⅲ. Special notes for reviews 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | pull_request: {} 9 | workflow_dispatch: {} 10 | 11 | env: 12 | # Common versions 13 | GO_VERSION: '1.19' 14 | GOLANGCI_VERSION: 'v1.52' 15 | 16 | jobs: 17 | 18 | golangci-lint: 19 | runs-on: ubuntu-22.04 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | - name: Setup Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: ${{ env.GO_VERSION }} 29 | - name: Cache Go Dependencies 30 | uses: actions/cache@v4 31 | with: 32 | path: ~/go/pkg/mod 33 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 34 | restore-keys: ${{ runner.os }}-go- 35 | - name: Code generate 36 | run: | 37 | make generate 38 | - name: Lint golang code 39 | uses: golangci/golangci-lint-action@v6 40 | with: 41 | version: ${{ env.GOLANGCI_VERSION }} 42 | args: --verbose 43 | 44 | unit-tests: 45 | runs-on: ubuntu-22.04 46 | steps: 47 | - uses: actions/checkout@v2 48 | with: 49 | submodules: true 50 | - name: Fetch History 51 | run: git fetch --prune --unshallow 52 | - name: Setup Go 53 | uses: actions/setup-go@v2 54 | with: 55 | go-version: ${{ env.GO_VERSION }} 56 | - name: Cache Go Dependencies 57 | uses: actions/cache@v4 58 | with: 59 | path: ~/go/pkg/mod 60 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 61 | restore-keys: ${{ runner.os }}-go- 62 | - name: Run Unit Tests 63 | run: | 64 | make test 65 | git status 66 | - name: Publish Unit Test Coverage 67 | uses: codecov/codecov-action@v1 68 | with: 69 | flags: unittests 70 | file: cover.out 71 | - name: Check diff 72 | run: '[[ -z $(git status -s) ]] || (printf "Existing modified/untracked files.\nPlease run \"make generate manifests\" and push again.\n"; exit 1)' 73 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yaml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | # Declare default permissions as read only. 7 | permissions: read-all 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v3 19 | - name: Login to Docker Hub 20 | uses: docker/login-action@v3 21 | with: 22 | username: ${{ vars.DOCKERHUB_USERNAME }} 23 | password: ${{ secrets.HUB_KRUISE }} 24 | - name: Build the Docker image 25 | run: | 26 | docker buildx create --use --platform=linux/amd64,linux/arm64,linux/ppc64le --name multi-platform-builder 27 | docker buildx ls 28 | IMG=openkruise/kruise-rollout:${{ github.ref_name }} make docker-multiarch 29 | -------------------------------------------------------------------------------- /.github/workflows/e2e-custom.yaml: -------------------------------------------------------------------------------- 1 | name: E2E-Custom 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | pull_request: {} 9 | workflow_dispatch: {} 10 | 11 | env: 12 | # Common versions 13 | GO_VERSION: '1.19' 14 | KIND_VERSION: 'v0.18.0' 15 | KIND_IMAGE: 'kindest/node:v1.26.3' 16 | KIND_CLUSTER_NAME: 'ci-testing' 17 | 18 | jobs: 19 | 20 | rollout: 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: true 26 | - name: Setup Go 27 | uses: actions/setup-go@v2 28 | with: 29 | go-version: ${{ env.GO_VERSION }} 30 | - name: Setup Kind Cluster 31 | uses: helm/kind-action@v1.2.0 32 | with: 33 | version: ${{ env.KIND_VERSION }} 34 | node_image: ${{ env.KIND_IMAGE }} 35 | cluster_name: ${{ env.KIND_CLUSTER_NAME }} 36 | config: ./test/kind-conf.yaml 37 | - name: Build image 38 | run: | 39 | export IMAGE="openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID}" 40 | docker build --pull --no-cache . -t $IMAGE 41 | kind load docker-image --name=${KIND_CLUSTER_NAME} $IMAGE || { echo >&2 "kind not installed or error loading image: $IMAGE"; exit 1; } 42 | - name: Install Kruise Rollout 43 | run: | 44 | set -ex 45 | kubectl cluster-info 46 | IMG=openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID} ./scripts/deploy_kind.sh 47 | for ((i=1;i<10;i++)); 48 | do 49 | set +e 50 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 51 | set -e 52 | if [ "$PODS" -eq "1" ]; then 53 | break 54 | fi 55 | sleep 3 56 | done 57 | set +e 58 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 59 | kubectl get node -o yaml 60 | kubectl get all -n kruise-rollout -o yaml 61 | set -e 62 | if [ "$PODS" -eq "1" ]; then 63 | echo "Wait for kruise-rollout ready successfully" 64 | else 65 | echo "Timeout to wait for kruise-rollout ready" 66 | exit 1 67 | fi 68 | - name: Run E2E Tests 69 | run: | 70 | export KUBECONFIG=/home/runner/.kube/config 71 | kubectl apply -f ./test/e2e/test_data/customNetworkProvider/istio_crd.yaml 72 | kubectl apply -f ./test/e2e/test_data/customNetworkProvider/lua_script_configmap.yaml 73 | make ginkgo 74 | set +e 75 | ./bin/ginkgo -timeout 60m -v --focus='Canary rollout with custom network provider' test/e2e 76 | retVal=$? 77 | # kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout 78 | restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}') 79 | if [ "${restartCount}" -eq "0" ];then 80 | echo "Kruise-rollout has not restarted" 81 | else 82 | kubectl get pod -n kruise-rollout --no-headers 83 | echo "Kruise-rollout has restarted, abort!!!" 84 | kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout 85 | exit 1 86 | fi 87 | exit $retVal 88 | -------------------------------------------------------------------------------- /.github/workflows/e2e-gateway.yaml: -------------------------------------------------------------------------------- 1 | name: E2E-Gateway 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | pull_request: {} 9 | workflow_dispatch: {} 10 | 11 | env: 12 | # Common versions 13 | GO_VERSION: '1.19' 14 | KIND_IMAGE: 'kindest/node:v1.23.3' 15 | KIND_CLUSTER_NAME: 'ci-testing' 16 | 17 | jobs: 18 | 19 | rollout: 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | - name: Setup Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: ${{ env.GO_VERSION }} 29 | - name: Setup Kind Cluster 30 | uses: helm/kind-action@v1.2.0 31 | with: 32 | node_image: ${{ env.KIND_IMAGE }} 33 | cluster_name: ${{ env.KIND_CLUSTER_NAME }} 34 | config: ./test/kind-conf.yaml 35 | - name: Build image 36 | run: | 37 | export IMAGE="openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID}" 38 | docker build --pull --no-cache . -t $IMAGE 39 | kind load docker-image --name=${KIND_CLUSTER_NAME} $IMAGE || { echo >&2 "kind not installed or error loading image: $IMAGE"; exit 1; } 40 | - name: Install Kruise Rollout 41 | run: | 42 | set -ex 43 | kubectl cluster-info 44 | IMG=openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID} ./scripts/deploy_kind.sh 45 | for ((i=1;i<10;i++)); 46 | do 47 | set +e 48 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 49 | set -e 50 | if [ "$PODS" -eq "1" ]; then 51 | break 52 | fi 53 | sleep 3 54 | done 55 | set +e 56 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 57 | kubectl get node -o yaml 58 | kubectl get all -n kruise-rollout -o yaml 59 | set -e 60 | if [ "$PODS" -eq "1" ]; then 61 | echo "Wait for kruise-rollout ready successfully" 62 | else 63 | echo "Timeout to wait for kruise-rollout ready" 64 | exit 1 65 | fi 66 | - name: Run E2E Tests 67 | run: | 68 | export KUBECONFIG=/home/runner/.kube/config 69 | make ginkgo 70 | set +e 71 | ./bin/ginkgo -timeout 60m -v --focus='Canary rollout with Gateway API' test/e2e 72 | retVal=$? 73 | # kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout 74 | restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}') 75 | if [ "${restartCount}" -eq "0" ];then 76 | echo "Kruise-rollout has not restarted" 77 | else 78 | kubectl get pod -n kruise-rollout --no-headers 79 | echo "Kruise-rollout has restarted, abort!!!" 80 | kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout 81 | exit 1 82 | fi 83 | exit $retVal 84 | -------------------------------------------------------------------------------- /.github/workflows/e2e-multi-network-provider.yaml: -------------------------------------------------------------------------------- 1 | name: E2E-Multiple-NetworkProvider 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | pull_request: {} 9 | workflow_dispatch: {} 10 | 11 | env: 12 | # Common versions 13 | GO_VERSION: '1.19' 14 | KIND_VERSION: 'v0.18.0' 15 | KIND_IMAGE: 'kindest/node:v1.26.3' 16 | KIND_CLUSTER_NAME: 'ci-testing' 17 | 18 | jobs: 19 | 20 | rollout: 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: true 26 | - name: Setup Go 27 | uses: actions/setup-go@v2 28 | with: 29 | go-version: ${{ env.GO_VERSION }} 30 | - name: Setup Kind Cluster 31 | uses: helm/kind-action@v1.2.0 32 | with: 33 | version: ${{ env.KIND_VERSION }} 34 | node_image: ${{ env.KIND_IMAGE }} 35 | cluster_name: ${{ env.KIND_CLUSTER_NAME }} 36 | config: ./test/kind-conf.yaml 37 | - name: Build image 38 | run: | 39 | export IMAGE="openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID}" 40 | docker build --pull --no-cache . -t $IMAGE 41 | kind load docker-image --name=${KIND_CLUSTER_NAME} $IMAGE || { echo >&2 "kind not installed or error loading image: $IMAGE"; exit 1; } 42 | - name: Install Kruise Rollout 43 | run: | 44 | set -ex 45 | kubectl cluster-info 46 | IMG=openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID} ./scripts/deploy_kind.sh 47 | for ((i=1;i<10;i++)); 48 | do 49 | set +e 50 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 51 | set -e 52 | if [ "$PODS" -eq "1" ]; then 53 | break 54 | fi 55 | sleep 3 56 | done 57 | set +e 58 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 59 | kubectl get node -o yaml 60 | kubectl get all -n kruise-rollout -o yaml 61 | set -e 62 | if [ "$PODS" -eq "1" ]; then 63 | echo "Wait for kruise-rollout ready successfully" 64 | else 65 | echo "Timeout to wait for kruise-rollout ready" 66 | exit 1 67 | fi 68 | - name: Run E2E Tests 69 | run: | 70 | export KUBECONFIG=/home/runner/.kube/config 71 | kubectl apply -f ./test/e2e/test_data/customNetworkProvider/istio_crd.yaml 72 | kubectl apply -f ./test/e2e/test_data/customNetworkProvider/lua_script_configmap.yaml 73 | make ginkgo 74 | set +e 75 | ./bin/ginkgo -timeout 60m -v --focus='Canary rollout with multiple network providers' test/e2e 76 | retVal=$? 77 | # kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout 78 | restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}') 79 | if [ "${restartCount}" -eq "0" ];then 80 | echo "Kruise-rollout has not restarted" 81 | else 82 | kubectl get pod -n kruise-rollout --no-headers 83 | echo "Kruise-rollout has restarted, abort!!!" 84 | kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout 85 | exit 1 86 | fi 87 | exit $retVal 88 | -------------------------------------------------------------------------------- /.github/workflows/e2e-others-1.19.yaml: -------------------------------------------------------------------------------- 1 | name: E2E-Others-1.19 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | pull_request: {} 9 | workflow_dispatch: {} 10 | 11 | env: 12 | # Common versions 13 | GO_VERSION: '1.19' 14 | KIND_IMAGE: 'kindest/node:v1.19.16' 15 | KIND_CLUSTER_NAME: 'ci-testing' 16 | 17 | jobs: 18 | 19 | rollout: 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | - name: Setup Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: ${{ env.GO_VERSION }} 29 | - name: Setup Kind Cluster 30 | uses: helm/kind-action@v1.2.0 31 | with: 32 | node_image: ${{ env.KIND_IMAGE }} 33 | cluster_name: ${{ env.KIND_CLUSTER_NAME }} 34 | config: ./test/kind-conf.yaml 35 | - name: Build image 36 | run: | 37 | export IMAGE="openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID}" 38 | docker build --pull --no-cache . -t $IMAGE 39 | kind load docker-image --name=${KIND_CLUSTER_NAME} $IMAGE || { echo >&2 "kind not installed or error loading image: $IMAGE"; exit 1; } 40 | - name: Install Kruise 41 | run: | 42 | set -ex 43 | kubectl cluster-info 44 | make helm 45 | helm repo add openkruise https://openkruise.github.io/charts/ 46 | helm repo update 47 | helm install kruise openkruise/kruise --version 1.7.0 48 | for ((i=1;i<10;i++)); 49 | do 50 | set +e 51 | PODS=$(kubectl get pod -n kruise-system | grep '1/1' | grep kruise-controller-manager | wc -l) 52 | set -e 53 | if [ "$PODS" -eq "2" ]; then 54 | break 55 | fi 56 | sleep 3 57 | done 58 | set +e 59 | PODS=$(kubectl get pod -n kruise-system | grep '1/1' | grep kruise-controller-manager | wc -l) 60 | set -e 61 | if [ "$PODS" -eq "2" ]; then 62 | echo "Wait for kruise-manager ready successfully" 63 | else 64 | echo "Timeout to wait for kruise-manager ready" 65 | exit 1 66 | fi 67 | - name: Install Kruise Rollout 68 | run: | 69 | set -ex 70 | kubectl cluster-info 71 | IMG=openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID} ./scripts/deploy_kind.sh 72 | for ((i=1;i<10;i++)); 73 | do 74 | set +e 75 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 76 | set -e 77 | if [ "$PODS" -eq "1" ]; then 78 | break 79 | fi 80 | sleep 3 81 | done 82 | set +e 83 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 84 | kubectl get node -o yaml 85 | kubectl get all -n kruise-rollout -o yaml 86 | set -e 87 | if [ "$PODS" -eq "1" ]; then 88 | echo "Wait for kruise-rollout ready successfully" 89 | else 90 | echo "Timeout to wait for kruise-rollout ready" 91 | exit 1 92 | fi 93 | - name: Run E2E Tests 94 | run: | 95 | export KUBECONFIG=/home/runner/.kube/config 96 | make ginkgo 97 | set +e 98 | ./bin/ginkgo -timeout 60m -v --focus='Others' test/e2e 99 | retVal=$? 100 | # kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout 101 | restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}') 102 | if [ "${restartCount}" -eq "0" ];then 103 | echo "Kruise-rollout has not restarted" 104 | else 105 | kubectl get pod -n kruise-rollout --no-headers 106 | echo "Kruise-rollout has restarted, abort!!!" 107 | kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout 108 | exit 1 109 | fi 110 | exit $retVal 111 | -------------------------------------------------------------------------------- /.github/workflows/e2e-others-1.23.yaml: -------------------------------------------------------------------------------- 1 | name: E2E-Others-1.23 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - release-* 8 | pull_request: {} 9 | workflow_dispatch: {} 10 | 11 | env: 12 | # Common versions 13 | GO_VERSION: '1.19' 14 | KIND_IMAGE: 'kindest/node:v1.23.3' 15 | KIND_CLUSTER_NAME: 'ci-testing' 16 | 17 | jobs: 18 | 19 | rollout: 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | - name: Setup Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: ${{ env.GO_VERSION }} 29 | - name: Setup Kind Cluster 30 | uses: helm/kind-action@v1.2.0 31 | with: 32 | node_image: ${{ env.KIND_IMAGE }} 33 | cluster_name: ${{ env.KIND_CLUSTER_NAME }} 34 | config: ./test/kind-conf.yaml 35 | - name: Build image 36 | run: | 37 | export IMAGE="openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID}" 38 | docker build --pull --no-cache . -t $IMAGE 39 | kind load docker-image --name=${KIND_CLUSTER_NAME} $IMAGE || { echo >&2 "kind not installed or error loading image: $IMAGE"; exit 1; } 40 | - name: Install Kruise 41 | run: | 42 | set -ex 43 | kubectl cluster-info 44 | make helm 45 | helm repo add openkruise https://openkruise.github.io/charts/ 46 | helm repo update 47 | helm install kruise openkruise/kruise --version 1.7.0 48 | for ((i=1;i<10;i++)); 49 | do 50 | set +e 51 | PODS=$(kubectl get pod -n kruise-system | grep '1/1' | grep kruise-controller-manager | wc -l) 52 | set -e 53 | if [ "$PODS" -eq "2" ]; then 54 | break 55 | fi 56 | sleep 3 57 | done 58 | set +e 59 | PODS=$(kubectl get pod -n kruise-system | grep '1/1' | grep kruise-controller-manager | wc -l) 60 | set -e 61 | if [ "$PODS" -eq "2" ]; then 62 | echo "Wait for kruise-manager ready successfully" 63 | else 64 | echo "Timeout to wait for kruise-manager ready" 65 | exit 1 66 | fi 67 | - name: Install Kruise Rollout 68 | run: | 69 | set -ex 70 | kubectl cluster-info 71 | IMG=openkruise/kruise-rollout:e2e-${GITHUB_RUN_ID} ./scripts/deploy_kind.sh 72 | for ((i=1;i<10;i++)); 73 | do 74 | set +e 75 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 76 | set -e 77 | if [ "$PODS" -eq "1" ]; then 78 | break 79 | fi 80 | sleep 3 81 | done 82 | set +e 83 | PODS=$(kubectl get pod -n kruise-rollout | grep '1/1' | wc -l) 84 | kubectl get node -o yaml 85 | kubectl get all -n kruise-rollout -o yaml 86 | set -e 87 | if [ "$PODS" -eq "1" ]; then 88 | echo "Wait for kruise-rollout ready successfully" 89 | else 90 | echo "Timeout to wait for kruise-rollout ready" 91 | exit 1 92 | fi 93 | - name: Run E2E Tests 94 | run: | 95 | export KUBECONFIG=/home/runner/.kube/config 96 | make ginkgo 97 | set +e 98 | ./bin/ginkgo -timeout 60m -v --focus='Others' test/e2e 99 | retVal=$? 100 | # kubectl get pod -n kruise-rollout --no-headers | grep manager | awk '{print $1}' | xargs kubectl logs -n kruise-rollout 101 | restartCount=$(kubectl get pod -n kruise-rollout --no-headers | awk '{print $4}') 102 | if [ "${restartCount}" -eq "0" ];then 103 | echo "Kruise-rollout has not restarted" 104 | else 105 | kubectl get pod -n kruise-rollout --no-headers 106 | echo "Kruise-rollout has restarted, abort!!!" 107 | kubectl get pod -n kruise-rollout --no-headers| awk '{print $1}' | xargs kubectl logs -p -n kruise-rollout 108 | exit 1 109 | fi 110 | exit $retVal 111 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | name: License 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - release-* 7 | workflow_dispatch: {} 8 | pull_request: 9 | branches: 10 | - master 11 | - release-* 12 | 13 | jobs: 14 | license_check: 15 | runs-on: ubuntu-latest 16 | name: Check for unapproved licenses 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 2.6 23 | - name: Install dependencies 24 | run: gem install license_finder 25 | - name: Run tests 26 | run: license_finder --decisions_file .license/dependency_decisions.yml -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin/ 9 | testbin/ 10 | vendor/ 11 | .temp 12 | 13 | # Test binary, build with `go test -c` 14 | *.test 15 | test/e2e/generated/bindata.go 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Kubernetes Generated files - skip generated files, except for vendored files 21 | 22 | !vendor/**/zz_generated.* 23 | 24 | # editor and IDE paraphernalia 25 | .idea 26 | *.swp 27 | *.swo 28 | *~ 29 | .vscode 30 | 31 | .DS_Store 32 | 33 | lua_configuration/networking.istio.io/**/testdata/*.lua 34 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # options for analysis running 2 | run: 3 | # default concurrency is a available CPU number 4 | concurrency: 4 5 | 6 | # timeout for analysis, e.g. 30s, 5m, default is 1m 7 | deadline: 5m 8 | 9 | # exit code when at least one issue was found, default is 1 10 | issues-exit-code: 1 11 | 12 | # include test files or not, default is true 13 | tests: true 14 | 15 | # list of build tags, all linters use it. Default is empty list. 16 | #build-tags: 17 | # - mytag 18 | 19 | # which dirs to skip: they won't be analyzed; 20 | # can use regexp here: generated.*, regexp is applied on full path; 21 | # default value is empty list, but next dirs are always skipped independently 22 | # from this option's value: 23 | # third_party$, testdata$, examples$, Godeps$, builtin$ 24 | skip-dirs: 25 | - vendor 26 | 27 | # which files to skip: they will be analyzed, but issues from them 28 | # won't be reported. Default value is empty list, but there is 29 | # no need to include all autogenerated files, we confidently recognize 30 | # autogenerated files. If it's not please let us know. 31 | skip-files: 32 | # - ".*\\.my\\.go$" 33 | # - lib/bad.go 34 | 35 | # output configuration options 36 | output: 37 | # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" 38 | format: colored-line-number 39 | 40 | # print lines of code with issue, default is true 41 | print-issued-lines: true 42 | 43 | # print linter name in the end of issue text, default is true 44 | print-linter-name: true 45 | 46 | 47 | # all available settings of specific linters 48 | linters-settings: 49 | golint: 50 | # minimal confidence for issues, default is 0.8 51 | min-confidence: 0.8 52 | gofmt: 53 | # simplify code: gofmt with `-s` option, true by default 54 | simplify: true 55 | goimports: 56 | # put imports beginning with prefix after 3rd-party packages; 57 | # it's a comma-separated list of prefixes 58 | #local-prefixes: github.com/openkruise/rollouts 59 | misspell: 60 | # Correct spellings using locale preferences for US or UK. 61 | # Default is to use a neutral variety of English. 62 | # Setting locale to US will correct the British spelling of 'colour' to 'color'. 63 | locale: default 64 | #ignore-words: 65 | # - someword 66 | 67 | linters: 68 | fast: false 69 | disable-all: true 70 | enable: 71 | # TODO Enforce the below linters later 72 | - gofmt 73 | - goimports 74 | - ineffassign 75 | - misspell 76 | issues: 77 | exclude: 78 | # staticcheck 79 | - 'SA1019: package github.com/golang/protobuf/proto is deprecated: Use the "google.golang.org/protobuf/proto" package instead' 80 | -------------------------------------------------------------------------------- /.license/README.md: -------------------------------------------------------------------------------- 1 | # License Checker 2 | 3 | Our license checker CI rely on [LicenseFinder](https://github.com/pivotal/LicenseFinder). 4 | 5 | ## How to add a new license 6 | 7 | LicenseFinder is a ruby project, so make sure you have ruby installed. 8 | 9 | ### Install the tool 10 | 11 | ```shell 12 | gem install license_finder 13 | ``` 14 | 15 | ### Add a license 16 | 17 | ```shell 18 | license_finder permitted_licenses add MIT --decisions_file .license/dependency_decisions.yml 19 | ``` 20 | -------------------------------------------------------------------------------- /.license/dependency_decisions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - - :permit 3 | - MIT 4 | - :who: 5 | :why: 6 | :versions: [] 7 | :when: 2021-03-12 07:35:34.645031000 Z 8 | - - :permit 9 | - Apache 2.0 10 | - :who: 11 | :why: 12 | :versions: [] 13 | :when: 2021-03-12 07:19:18.243194000 Z 14 | - - :permit 15 | - New BSD 16 | - :who: 17 | :why: 18 | :versions: [] 19 | :when: 2021-03-12 07:19:28.540675000 Z 20 | - - :permit 21 | - Simplified BSD 22 | - :who: 23 | :why: 24 | :versions: [] 25 | :when: 2021-03-12 07:20:01.774212000 Z 26 | - - :permit 27 | - Mozilla Public License 2.0 28 | - :who: 29 | :why: 30 | :versions: [] 31 | :when: 2021-03-12 07:21:05.194536000 Z 32 | - - :permit 33 | - unknown 34 | - :who: 35 | :why: 36 | :versions: [] 37 | :when: 2021-03-12 07:21:43.379269000 Z 38 | - - :permit 39 | - ISC 40 | - :who: 41 | :why: 42 | :versions: [] 43 | :when: 2021-03-12 07:22:07.265966000 Z 44 | -------------------------------------------------------------------------------- /.lift.toml: -------------------------------------------------------------------------------- 1 | # Ignore results from vendor directories 2 | ignoreFiles = """ 3 | vendor/ 4 | lua_configuration/ 5 | """ 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.19-alpine3.17 AS builder 3 | 4 | WORKDIR /workspace 5 | 6 | # Copy the Go Modules manifests 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | 10 | # Copy the go source 11 | COPY main.go main.go 12 | COPY api/ api/ 13 | COPY pkg/ pkg/ 14 | 15 | # Build 16 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go 17 | 18 | # Use distroless as minimal base image to package the manager binary 19 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 20 | FROM gcr.io/distroless/static:nonroot 21 | WORKDIR / 22 | COPY --from=builder /workspace/manager . 23 | COPY lua_configuration /lua_configuration 24 | USER 65532:65532 25 | 26 | ENTRYPOINT ["/manager"] 27 | -------------------------------------------------------------------------------- /Dockerfile_multiarch: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | ARG BASE_IMAGE=alpine 3 | ARG BASE_IMAGE_VERION=3.17 4 | FROM --platform=$BUILDPLATFORM golang:1.19-alpine3.17 as builder 5 | 6 | WORKDIR /workspace 7 | 8 | # Copy the Go Modules manifests 9 | COPY go.mod go.mod 10 | COPY go.sum go.sum 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | COPY api/ api/ 15 | COPY pkg/ pkg/ 16 | 17 | # Build 18 | ARG TARGETOS 19 | ARG TARGETARCH 20 | RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} CGO_ENABLED=0 GO111MODULE=on go build -a -o manager main.go 21 | 22 | ARG BASE_IMAGE 23 | ARG BASE_IMAGE_VERION 24 | FROM ${BASE_IMAGE}:${BASE_IMAGE_VERION} 25 | 26 | RUN set -eux; \ 27 | apk --no-cache --update upgrade && \ 28 | apk --no-cache add ca-certificates && \ 29 | apk --no-cache add tzdata && \ 30 | rm -rf /var/cache/apk/* && \ 31 | update-ca-certificates && \ 32 | echo "only include root and nobody user" && \ 33 | echo -e "root:x:0:0:root:/root:/bin/ash\nnobody:x:65534:65534:nobody:/:/sbin/nologin" | tee /etc/passwd && \ 34 | echo -e "root:x:0:root\nnobody:x:65534:" | tee /etc/group && \ 35 | rm -rf /usr/local/sbin/* && \ 36 | rm -rf /usr/local/bin/* && \ 37 | rm -rf /usr/sbin/* && \ 38 | rm -rf /usr/bin/* && \ 39 | rm -rf /sbin/* && \ 40 | rm -rf /bin/* 41 | 42 | WORKDIR / 43 | COPY --from=builder /workspace/manager . 44 | COPY lua_configuration /lua_configuration 45 | USER 65534 46 | 47 | ENTRYPOINT ["/manager"] 48 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See: https://go.k8s.io/owners 2 | approvers: 3 | - FillZpp 4 | - furykerry 5 | - zmberg 6 | reviewers: 7 | - FillZpp 8 | - furykerry 9 | - zmberg 10 | - veophi 11 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: kruise.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | projectName: rollouts 5 | repo: github.com/openkruise/rollouts 6 | resources: 7 | - api: 8 | crdVersion: v1 9 | namespaced: true 10 | domain: kruise.io 11 | group: rollouts 12 | kind: Rollout 13 | path: github.com/openkruise/rollouts/api/v1alpha1 14 | version: v1alpha1 15 | - api: 16 | crdVersion: v1 17 | namespaced: true 18 | controller: true 19 | domain: kruise.io 20 | group: rollouts 21 | kind: BatchRelease 22 | path: github.com/openkruise/rollouts/api/v1alpha1 23 | version: v1alpha1 24 | version: "3" 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rollouts 2 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 3 | 4 | ## Introduction 5 | Kruise Rollouts is a **Bypass** component that offers **Advanced Progressive Delivery Features**. Its support for canary, multi-batch, and A/B testing delivery modes can be helpful in achieving smooth and controlled rollouts of changes to your application, while its compatibility with Gateway API and various Ingress implementations makes it easier to integrate with your existing infrastructure. Overall, Kruise Rollouts is a valuable tool for Kubernetes users looking to optimize their deployment processes! 6 | 7 |
8 | 9 | ## Why Kruise Rollouts? 10 | - **Functionality**: 11 | - Supports canary and multi-batch delivery for various workloads, such as Deployment, CloneSet, and StatefulSet. 12 | - Supports Fine-grained traffic orchestration of application with Kubernetes Ingress and [Gateway API](https://gateway-api.sigs.k8s.io/). 13 | 14 | - **Flexibility**: 15 | - Handles both incremental and existing workloads with ease. 16 | - Be compatible with workload-referencing components like HPA, allowing for easy deployment and management of workloads. 17 | - Supports plug-and-play and hot-swapping, with immediate effect upon application, and the flexibility to be easily deleted at any stage, including during the rollout process. 18 | 19 | - **Extensibility**: 20 | - Extend to other workloads and traffic types easily with pluggable lua scripts. 21 | 22 | ## Quick Start 23 | - See [Getting Started](https://openkruise.io/rollouts/introduction/) documents in OpenKruise official website. 24 | 25 | ## Contributing 26 | You are warmly welcome to hack on Kruise Rollout. We have prepared a detailed guide [CONTRIBUTING.md](CONTRIBUTING.md). 27 | 28 | ## Community 29 | Active communication channels: 30 | 31 | - Slack: [OpenKruise channel](https://kubernetes.slack.com/channels/openkruise) (*English*) 32 | - DingTalk:Search GroupID `23330762` (*Chinese*) 33 | - WeChat: Search User `openkruise` and let the robot invite you (*Chinese*) 34 | - Bi-weekly Community Meeting (APAC, *Chinese*): 35 | - Thursday 19:00 GMT+8 (Asia/Shanghai), [Calendar](https://calendar.google.com/calendar/u/2?cid=MjdtbDZucXA2bjVpNTFyYTNpazV2dW8ybHNAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ) 36 | - [Meeting Link(zoom)](https://us02web.zoom.us/j/87059136652?pwd=NlI4UThFWXVRZkxIU0dtR1NINncrQT09) 37 | - [Notes and agenda](https://shimo.im/docs/gXqmeQOYBehZ4vqo) 38 | - Bi-weekly Community Meeting (*English*): TODO 39 | 40 | ## Acknowledge 41 | - The global idea is from both OpenKruise and KubeVela communities, and the basic code of rollout is inherited from the KubeVela Rollout. 42 | - This project is maintained by both contributors from [OpenKruise](https://openkruise.io/) and [KubeVela](https://kubevela.io). 43 | 44 | ## License 45 | Kruise Rollout is licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE.md) for the full license text. 46 | 47 | -------------------------------------------------------------------------------- /api/addtoscheme_apps_v1alpha1.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/api/v1alpha1" 21 | ) 22 | 23 | func init() { 24 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 25 | AddToSchemes = append(AddToSchemes, v1alpha1.SchemeBuilder.AddToScheme) 26 | } 27 | -------------------------------------------------------------------------------- /api/addtoscheme_apps_v1beta1.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/api/v1beta1" 21 | ) 22 | 23 | func init() { 24 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 25 | AddToSchemes = append(AddToSchemes, v1beta1.SchemeBuilder.AddToScheme) 26 | } 27 | -------------------------------------------------------------------------------- /api/apis.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package apis 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/runtime" 21 | ) 22 | 23 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 24 | var AddToSchemes runtime.SchemeBuilder 25 | 26 | // AddToScheme adds all Resources to the Scheme 27 | func AddToScheme(s *runtime.Scheme) error { 28 | return AddToSchemes.AddToScheme(s) 29 | } 30 | -------------------------------------------------------------------------------- /api/v1alpha1/batchrelease_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // +genclient 24 | // +k8s:openapi-gen=true 25 | // +kubebuilder:object:root=true 26 | // +kubebuilder:subresource:status 27 | // +kubebuilder:printcolumn:name="KIND",type=string,JSONPath=`.spec.targetReference.workloadRef.kind` 28 | // +kubebuilder:printcolumn:name="PHASE",type=string,JSONPath=`.status.phase` 29 | // +kubebuilder:printcolumn:name="BATCH",type=integer,JSONPath=`.status.canaryStatus.currentBatch` 30 | // +kubebuilder:printcolumn:name="BATCH-STATE",type=string,JSONPath=`.status.canaryStatus.batchState` 31 | // +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=".metadata.creationTimestamp" 32 | 33 | type BatchRelease struct { 34 | metav1.TypeMeta `json:",inline"` 35 | metav1.ObjectMeta `json:"metadata,omitempty"` 36 | 37 | Spec BatchReleaseSpec `json:"spec,omitempty"` 38 | Status BatchReleaseStatus `json:"status,omitempty"` 39 | } 40 | 41 | // BatchReleaseSpec defines how to describe an update between different compRevision 42 | type BatchReleaseSpec struct { 43 | // TargetRef contains the GVK and name of the workload that we need to upgrade to. 44 | TargetRef ObjectRef `json:"targetReference"` 45 | // ReleasePlan is the details on how to rollout the resources 46 | ReleasePlan ReleasePlan `json:"releasePlan"` 47 | } 48 | 49 | // BatchReleaseList contains a list of BatchRelease 50 | // +kubebuilder:object:root=true 51 | type BatchReleaseList struct { 52 | metav1.TypeMeta `json:",inline"` 53 | metav1.ListMeta `json:"metadata,omitempty"` 54 | Items []BatchRelease `json:"items"` 55 | } 56 | 57 | func init() { 58 | SchemeBuilder.Register(&BatchRelease{}, &BatchReleaseList{}) 59 | } 60 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1alpha1 contains API Schema definitions for the apps v1alpha1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=rollouts.kruise.io 20 | package v1alpha1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "rollouts.kruise.io", Version: "v1alpha1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | 37 | SchemeGroupVersion = GroupVersion 38 | ) 39 | 40 | // Resource is required by pkg/client/listers/... 41 | func Resource(resource string) schema.GroupResource { 42 | return SchemeGroupVersion.WithResource(resource).GroupResource() 43 | } 44 | -------------------------------------------------------------------------------- /api/v1beta1/batchrelease_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // +genclient 24 | // +k8s:openapi-gen=true 25 | // +kubebuilder:object:root=true 26 | // +kubebuilder:subresource:status 27 | // +kubebuilder:storageversion 28 | // +kubebuilder:printcolumn:name="KIND",type=string,JSONPath=`.spec.targetReference.workloadRef.kind` 29 | // +kubebuilder:printcolumn:name="PHASE",type=string,JSONPath=`.status.phase` 30 | // +kubebuilder:printcolumn:name="BATCH",type=integer,JSONPath=`.status.canaryStatus.currentBatch` 31 | // +kubebuilder:printcolumn:name="BATCH-STATE",type=string,JSONPath=`.status.canaryStatus.batchState` 32 | // +kubebuilder:printcolumn:name="AGE",type=date,JSONPath=".metadata.creationTimestamp" 33 | 34 | type BatchRelease struct { 35 | metav1.TypeMeta `json:",inline"` 36 | metav1.ObjectMeta `json:"metadata,omitempty"` 37 | 38 | Spec BatchReleaseSpec `json:"spec,omitempty"` 39 | Status BatchReleaseStatus `json:"status,omitempty"` 40 | } 41 | 42 | // BatchReleaseSpec defines how to describe an update between different compRevision 43 | type BatchReleaseSpec struct { 44 | // WorkloadRef contains enough information to let you identify a workload for Rollout 45 | // Batch release of the bypass 46 | WorkloadRef ObjectRef `json:"workloadRef,omitempty"` 47 | // ReleasePlan is the details on how to rollout the resources 48 | ReleasePlan ReleasePlan `json:"releasePlan"` 49 | } 50 | 51 | // BatchReleaseList contains a list of BatchRelease 52 | // +kubebuilder:object:root=true 53 | type BatchReleaseList struct { 54 | metav1.TypeMeta `json:",inline"` 55 | metav1.ListMeta `json:"metadata,omitempty"` 56 | Items []BatchRelease `json:"items"` 57 | } 58 | 59 | func init() { 60 | SchemeBuilder.Register(&BatchRelease{}, &BatchReleaseList{}) 61 | } 62 | -------------------------------------------------------------------------------- /api/v1beta1/convertion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | func (*Rollout) Hub() {} 20 | 21 | func (*BatchRelease) Hub() {} 22 | -------------------------------------------------------------------------------- /api/v1beta1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1beta1 contains API Schema definitions for the apps v1beta1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=rollouts.kruise.io 20 | package v1beta1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "rollouts.kruise.io", Version: "v1beta1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | 37 | SchemeGroupVersion = GroupVersion 38 | ) 39 | 40 | // Resource is required by pkg/client/listers/... 41 | func Resource(resource string) schema.GroupResource { 42 | return SchemeGroupVersion.WithResource(resource).GroupResource() 43 | } 44 | -------------------------------------------------------------------------------- /api/v1beta1/trafficrouting.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1beta1 18 | 19 | // TrafficRoutingRef hosts all the different configuration for supported service meshes to enable more fine-grained traffic routing 20 | type TrafficRoutingRef struct { 21 | // Service holds the name of a service which selects pods with stable version and don't select any pods with canary version. 22 | Service string `json:"service"` 23 | // Optional duration in seconds the traffic provider(e.g. nginx ingress controller) consumes the service, ingress configuration changes gracefully. 24 | // +kubebuilder:default=3 25 | GracePeriodSeconds int32 `json:"gracePeriodSeconds,omitempty"` 26 | // Ingress holds Ingress specific configuration to route traffic, e.g. Nginx, Alb. 27 | Ingress *IngressTrafficRouting `json:"ingress,omitempty"` 28 | // Gateway holds Gateway specific configuration to route traffic 29 | // Gateway configuration only supports >= v0.4.0 (v1alpha2). 30 | Gateway *GatewayTrafficRouting `json:"gateway,omitempty"` 31 | // CustomNetworkRefs hold a list of custom providers to route traffic 32 | CustomNetworkRefs []ObjectRef `json:"customNetworkRefs,omitempty"` 33 | } 34 | 35 | // IngressTrafficRouting configuration for ingress controller to control traffic routing 36 | type IngressTrafficRouting struct { 37 | // ClassType refers to the type of `Ingress`. 38 | // current support nginx, aliyun-alb. default is nginx. 39 | // +optional 40 | ClassType string `json:"classType,omitempty"` 41 | // Name refers to the name of an `Ingress` resource in the same namespace as the `Rollout` 42 | Name string `json:"name"` 43 | } 44 | 45 | // GatewayTrafficRouting configuration for gateway api 46 | type GatewayTrafficRouting struct { 47 | // HTTPRouteName refers to the name of an `HTTPRoute` resource in the same namespace as the `Rollout` 48 | HTTPRouteName *string `json:"httpRouteName,omitempty"` 49 | // TCPRouteName *string `json:"tcpRouteName,omitempty"` 50 | // UDPRouteName *string `json:"udpRouteName,omitempty"` 51 | } 52 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/rollouts.kruise.io_rollouts.yaml 6 | - bases/rollouts.kruise.io_batchreleases.yaml 7 | - bases/rollouts.kruise.io_rollouthistories.yaml 8 | - bases/rollouts.kruise.io_trafficroutings.yaml 9 | #+kubebuilder:scaffold:crdkustomizeresource 10 | 11 | patchesStrategicMerge: 12 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 13 | # patches here are for enabling the conversion webhook for each CRD 14 | - patches/webhook_in_rollouts.yaml 15 | - patches/webhook_in_batchreleases.yaml 16 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 17 | 18 | # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. 19 | # patches here are for enabling the CA injection for each CRD 20 | #- patches/cainjection_in_rollouts.yaml 21 | #- patches/cainjection_in_batchreleases.yaml 22 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 23 | 24 | # the following config is for teaching kustomize how to do kustomization for CRDs. 25 | configurations: 26 | - kustomizeconfig.yaml 27 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_batchreleases.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: batchreleases.rollouts.kruise.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_rollouts.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: rollouts.rollouts.kruise.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_batchreleases.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: batchreleases.rollouts.kruise.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1beta1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_rollouts.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: rollouts.rollouts.kruise.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1beta1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: kruise-rollout 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: kruise-rollout- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../manager 18 | - ../rbac 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: manager 13 | args: 14 | - "--health-probe-bind-address=:8081" 15 | - "--metrics-bind-address=127.0.0.1:8080" 16 | - "--leader-elect" 17 | - "--feature-gates=AdvancedDeployment=true" 18 | - "--v=5" 19 | env: 20 | - name: KUBE_CACHE_MUTATION_DETECTOR 21 | value: "true" 22 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | imagePullPolicy: Always 12 | args: 13 | - "--config=controller_manager_config.yaml" 14 | volumeMounts: 15 | - name: manager-config 16 | mountPath: /controller_manager_config.yaml 17 | subPath: controller_manager_config.yaml 18 | volumes: 19 | - name: manager-config 20 | configMap: 21 | name: manager-config 22 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 71ddec2c.kruise.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | securityContext: 26 | runAsNonRoot: true 27 | containers: 28 | - command: 29 | - /manager 30 | args: 31 | - --leader-elect 32 | - --feature-gates=AdvancedDeployment=true 33 | image: controller:latest 34 | name: manager 35 | securityContext: 36 | allowPrivilegeEscalation: false 37 | livenessProbe: 38 | httpGet: 39 | path: /healthz 40 | port: 8081 41 | initialDelaySeconds: 15 42 | periodSeconds: 20 43 | readinessProbe: 44 | httpGet: 45 | path: /readyz 46 | port: 8081 47 | initialDelaySeconds: 5 48 | periodSeconds: 10 49 | resources: 50 | limits: 51 | cpu: 100m 52 | memory: 100Mi 53 | requests: 54 | cpu: 100m 55 | memory: 100Mi 56 | serviceAccountName: controller-manager 57 | terminationGracePeriodSeconds: 10 58 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/batchrelease_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit batchreleases. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: batchrelease-editor-role 6 | rules: 7 | - apiGroups: 8 | - rollouts.kruise.io 9 | resources: 10 | - batchreleases 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - rollouts.kruise.io 21 | resources: 22 | - batchreleases/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/batchrelease_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view batchreleases. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: batchrelease-viewer-role 6 | rules: 7 | - apiGroups: 8 | - rollouts.kruise.io 9 | resources: 10 | - batchreleases 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - rollouts.kruise.io 17 | resources: 18 | - batchreleases/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | #- auth_proxy_service.yaml 16 | #- auth_proxy_role.yaml 17 | #- auth_proxy_role_binding.yaml 18 | #- auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | --- 14 | apiVersion: rbac.authorization.k8s.io/v1 15 | kind: RoleBinding 16 | metadata: 17 | name: manager-rolebinding 18 | namespace: system 19 | roleRef: 20 | apiGroup: rbac.authorization.k8s.io 21 | kind: Role 22 | name: manager-role 23 | subjects: 24 | - kind: ServiceAccount 25 | name: controller-manager 26 | namespace: system 27 | -------------------------------------------------------------------------------- /config/rbac/rollout_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit rollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: rollout-editor-role 6 | rules: 7 | - apiGroups: 8 | - rollouts.kruise.io 9 | resources: 10 | - rollouts 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - rollouts.kruise.io 21 | resources: 22 | - rollouts/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/rollout_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view rollouts. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: rollout-viewer-role 6 | rules: 7 | - apiGroups: 8 | - rollouts.kruise.io 9 | resources: 10 | - rollouts 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - rollouts.kruise.io 17 | resources: 18 | - rollouts/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/rollouts_v1alpha1_batchrelease.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: batchrelease-sample 5 | spec: 6 | # Add fields here 7 | foo: bar 8 | -------------------------------------------------------------------------------- /config/samples/rollouts_v1alpha1_rollout.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollout-sample 5 | spec: 6 | # Add fields here 7 | foo: bar 8 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | patchesStrategicMerge: 6 | - patch_manifests.yaml 7 | 8 | configurations: 9 | - kustomizeconfig.yaml 10 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | creationTimestamp: null 6 | name: mutating-webhook-configuration 7 | webhooks: 8 | - admissionReviewVersions: 9 | - v1 10 | - v1beta1 11 | clientConfig: 12 | service: 13 | name: webhook-service 14 | namespace: system 15 | path: /mutate-apps-kruise-io-v1alpha1-cloneset 16 | failurePolicy: Fail 17 | name: mcloneset.kb.io 18 | rules: 19 | - apiGroups: 20 | - apps.kruise.io 21 | apiVersions: 22 | - v1alpha1 23 | operations: 24 | - UPDATE 25 | resources: 26 | - clonesets 27 | sideEffects: None 28 | - admissionReviewVersions: 29 | - v1 30 | - v1beta1 31 | clientConfig: 32 | service: 33 | name: webhook-service 34 | namespace: system 35 | path: /mutate-apps-kruise-io-v1alpha1-daemonset 36 | failurePolicy: Fail 37 | name: mdaemonset.kb.io 38 | rules: 39 | - apiGroups: 40 | - apps.kruise.io 41 | apiVersions: 42 | - v1alpha1 43 | operations: 44 | - UPDATE 45 | resources: 46 | - daemonsets 47 | sideEffects: None 48 | - admissionReviewVersions: 49 | - v1 50 | - v1beta1 51 | clientConfig: 52 | service: 53 | name: webhook-service 54 | namespace: system 55 | path: /mutate-apps-v1-deployment 56 | failurePolicy: Fail 57 | name: mdeployment.kb.io 58 | rules: 59 | - apiGroups: 60 | - apps 61 | apiVersions: 62 | - v1 63 | operations: 64 | - UPDATE 65 | resources: 66 | - deployments 67 | sideEffects: None 68 | - admissionReviewVersions: 69 | - v1 70 | - v1beta1 71 | clientConfig: 72 | service: 73 | name: webhook-service 74 | namespace: system 75 | path: /mutate-unified-workload 76 | failurePolicy: Fail 77 | name: munifiedworload.kb.io 78 | rules: 79 | - apiGroups: 80 | - '*' 81 | apiVersions: 82 | - '*' 83 | operations: 84 | - CREATE 85 | - UPDATE 86 | resources: 87 | - '*' 88 | sideEffects: None 89 | --- 90 | apiVersion: admissionregistration.k8s.io/v1 91 | kind: ValidatingWebhookConfiguration 92 | metadata: 93 | creationTimestamp: null 94 | name: validating-webhook-configuration 95 | webhooks: 96 | - admissionReviewVersions: 97 | - v1 98 | - v1beta1 99 | clientConfig: 100 | service: 101 | name: webhook-service 102 | namespace: system 103 | path: /validate-rollouts-kruise-io-rollout 104 | failurePolicy: Fail 105 | name: vrollout.kb.io 106 | rules: 107 | - apiGroups: 108 | - rollouts.kruise.io 109 | apiVersions: 110 | - v1alpha1 111 | - v1beta1 112 | operations: 113 | - CREATE 114 | - UPDATE 115 | resources: 116 | - rollouts 117 | sideEffects: None 118 | -------------------------------------------------------------------------------- /config/webhook/patch_manifests.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: admissionregistration.k8s.io/v1 2 | kind: MutatingWebhookConfiguration 3 | metadata: 4 | name: mutating-webhook-configuration 5 | webhooks: 6 | - name: munifiedworload.kb.io 7 | objectSelector: 8 | matchExpressions: 9 | - key: rollouts.kruise.io/workload-type 10 | operator: Exists 11 | - name: mcloneset.kb.io 12 | objectSelector: 13 | matchExpressions: 14 | - key: rollouts.kruise.io/workload-type 15 | operator: Exists 16 | - name: mdaemonset.kb.io 17 | objectSelector: 18 | matchExpressions: 19 | - key: rollouts.kruise.io/workload-type 20 | operator: Exists 21 | # - name: mstatefulset.kb.io 22 | # objectSelector: 23 | # matchExpressions: 24 | # - key: rollouts.kruise.io/workload-type 25 | # operator: Exists 26 | # - name: madvancedstatefulset.kb.io 27 | # objectSelector: 28 | # matchExpressions: 29 | # - key: rollouts.kruise.io/workload-type 30 | # operator: Exists 31 | - name: mdeployment.kb.io 32 | objectSelector: 33 | matchExpressions: 34 | - key: control-plane 35 | operator: NotIn 36 | values: 37 | - controller-manager 38 | - key: rollouts.kruise.io/workload-type 39 | operator: Exists 40 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9876 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /docs/getting_started/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Requirements 4 | - Install Kubernetes Cluster, requires **Kubernetes version >= 1.19**. 5 | - (Optional, If Use CloneSet) Helm installation of OpenKruise, **Since v1.1.0**, Reference [Install OpenKruise](https://openkruise.io/docs/installation). 6 | 7 | ## Install with helm 8 | 9 | Kruise Rollout can be simply installed by helm v3.1+, which is a simple command-line tool and you can get it from [here](https://github.com/helm/helm/releases). 10 | 11 | ```bash 12 | # Firstly add openkruise charts repository if you haven't do this. 13 | $ helm repo add openkruise https://openkruise.github.io/charts/ 14 | 15 | # [Optional] 16 | $ helm repo update 17 | 18 | # Install the latest version. 19 | $ helm install kruise-rollout openkruise/kruise-rollout --version 0.1.0 20 | ``` 21 | 22 | ## Uninstall 23 | 24 | Note that this will lead to all resources created by Kruise Rollout, including webhook configurations, services, namespace, CRDs and CR instances Kruise Rollout controller, to be deleted! 25 | 26 | Please do this ONLY when you fully understand the consequence. 27 | 28 | To uninstall kruise rollout if it is installed with helm charts: 29 | 30 | ```bash 31 | $ helm uninstall kruise-rollout 32 | release "kruise-rollout" uninstalled 33 | ``` 34 | 35 | ## What's Next 36 | Here are some recommended next steps: 37 | - Learn Kruise Rollout's [Basic Usage](../tutorials/basic_usage.md). 38 | -------------------------------------------------------------------------------- /docs/getting_started/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | ## What is Kruise Rollout? 3 | Kruise Rollouts is **a Bypass component which provides advanced deployment capabilities such as canary, traffic routing, and progressive delivery features, for a series of Kubernetes workloads, such as Deployment and CloneSet**. 4 | 5 | Kruise Rollout integrates with ingress controllers and service meshes, leveraging their traffic shaping abilities to gradually shift traffic to the new version during an update. 6 | In addition, the business Pods metrics analysis can be used during rollout to determine whether the release will continue or be suspended. 7 | 8 | ![arch](../images/rollout-arch.png) 9 | 10 | ## Why is Kruise Rollout? 11 | The native Kubernetes Deployment Object supports the **RollingUpdate** strategy which provides a basic set of safety guarantees(maxUnavailable, maxSurge) during an update. However the rolling update strategy faces many limitations: 12 | - **The process of batch release cannot be strictly controlled**, e.g. 20%, 40% etc. Although maxUnavailable, maxSurge can control the release rate, it will release the next batch as soon as the previous batch has been released. 13 | - **Can't precisely control traffic flow during the release**, e.g. 20% traffic flow rate to the new version of Pods. 14 | - **No ability to query external metrics to verify whether the business indicators during the upgrade process are normal**. 15 | 16 | ## Features 17 | - **Functionality**: 18 | - Support multi-batch delivery for Deployment/CloneSet. 19 | - Support Nginx/ALB/Istio traffic routing control during rollout. 20 | 21 | - **Flexibility**: 22 | - Support scaling up/down to workloads during rollout. 23 | - Can be applied to newly-created or existing workload objects directly; 24 | - Can be ridden out of at any time when you needn't it without consideration of unavailable workloads and traffic problems. 25 | - Can cooperate with other native/third-part Kubernetes controllers/operators, such as HPA and WorkloadSpread. 26 | 27 | - **Non-Invasion**: 28 | - Does not invade native workload controllers. 29 | - Does not replace user-defined workload and traffic configurations. 30 | 31 | - **Extensibility**: 32 | - Easily extend to other traffic routing types, or workload types via plugin codes. 33 | 34 | - **Easy-integration**: 35 | - Easily integrate with classic or GitOps-style Kubernetes-based PaaS. 36 | 37 | ## What's Next 38 | Here are some recommended next steps: 39 | - Start to [Install Kruise Rollout](./installation.md). 40 | - Learn Kruise Rollout's [Basic Usage](../tutorials/basic_usage.md). 41 | - [Demo video](https://www.bilibili.com/video/BV1wT4y1Q7eD?spm_id_from=333.880.my_history.page.click) 42 | -------------------------------------------------------------------------------- /docs/images/approve_rollout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/approve_rollout.png -------------------------------------------------------------------------------- /docs/images/continuous_release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/continuous_release.png -------------------------------------------------------------------------------- /docs/images/deploy_rollout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/deploy_rollout.png -------------------------------------------------------------------------------- /docs/images/echoserver_application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/echoserver_application.png -------------------------------------------------------------------------------- /docs/images/rollback_echoserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/rollback_echoserver.png -------------------------------------------------------------------------------- /docs/images/rollout-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/rollout-arch.png -------------------------------------------------------------------------------- /docs/images/rollout_canary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/rollout_canary.png -------------------------------------------------------------------------------- /docs/images/rollout_intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/rollout_intro.png -------------------------------------------------------------------------------- /docs/images/upgrade_echoserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/upgrade_echoserver.png -------------------------------------------------------------------------------- /docs/images/upgrade_failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openkruise/rollouts/6554cfcf08abd3b2c959f7fa30a4e3509315ef8c/docs/images/upgrade_failed.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openkruise/rollouts 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/davecgh/go-spew v1.1.1 7 | github.com/evanphx/json-patch v4.12.0+incompatible 8 | github.com/onsi/ginkgo v1.16.5 9 | github.com/onsi/gomega v1.24.1 10 | github.com/openkruise/kruise-api v1.3.0 11 | github.com/spf13/pflag v1.0.5 12 | github.com/stretchr/testify v1.8.2 13 | github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 14 | golang.org/x/time v0.3.0 15 | gopkg.in/yaml.v2 v2.4.0 16 | k8s.io/api v0.26.3 17 | k8s.io/apiextensions-apiserver v0.26.3 18 | k8s.io/apimachinery v0.26.3 19 | k8s.io/apiserver v0.26.3 20 | k8s.io/client-go v0.26.3 21 | k8s.io/component-base v0.26.3 22 | k8s.io/klog/v2 v2.100.1 23 | k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 24 | layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf 25 | sigs.k8s.io/controller-runtime v0.14.6 26 | sigs.k8s.io/gateway-api v0.7.1 27 | sigs.k8s.io/yaml v1.3.0 28 | ) 29 | 30 | require ( 31 | github.com/beorn7/perks v1.0.1 // indirect 32 | github.com/blang/semver/v4 v4.0.0 // indirect 33 | github.com/cespare/xxhash/v2 v2.1.2 // indirect 34 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 35 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 36 | github.com/fsnotify/fsnotify v1.6.0 // indirect 37 | github.com/go-logr/logr v1.2.3 // indirect 38 | github.com/go-logr/zapr v1.2.3 // indirect 39 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 40 | github.com/go-openapi/jsonreference v0.20.0 // indirect 41 | github.com/go-openapi/swag v0.19.14 // indirect 42 | github.com/gogo/protobuf v1.3.2 // indirect 43 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 44 | github.com/golang/protobuf v1.5.2 // indirect 45 | github.com/google/gnostic v0.5.7-v3refs // indirect 46 | github.com/google/go-cmp v0.5.9 // indirect 47 | github.com/google/gofuzz v1.1.0 // indirect 48 | github.com/google/uuid v1.1.2 // indirect 49 | github.com/imdario/mergo v0.3.12 // indirect 50 | github.com/josharian/intern v1.0.0 // indirect 51 | github.com/json-iterator/go v1.1.12 // indirect 52 | github.com/mailru/easyjson v0.7.6 // indirect 53 | github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 55 | github.com/modern-go/reflect2 v1.0.2 // indirect 56 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 57 | github.com/nxadm/tail v1.4.8 // indirect 58 | github.com/pkg/errors v0.9.1 // indirect 59 | github.com/pmezard/go-difflib v1.0.0 // indirect 60 | github.com/prometheus/client_golang v1.14.0 // indirect 61 | github.com/prometheus/client_model v0.3.0 // indirect 62 | github.com/prometheus/common v0.37.0 // indirect 63 | github.com/prometheus/procfs v0.8.0 // indirect 64 | go.uber.org/atomic v1.7.0 // indirect 65 | go.uber.org/multierr v1.6.0 // indirect 66 | go.uber.org/zap v1.24.0 // indirect 67 | golang.org/x/net v0.7.0 // indirect 68 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 69 | golang.org/x/sys v0.5.0 // indirect 70 | golang.org/x/term v0.5.0 // indirect 71 | golang.org/x/text v0.7.0 // indirect 72 | gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect 73 | google.golang.org/appengine v1.6.7 // indirect 74 | google.golang.org/protobuf v1.28.1 // indirect 75 | gopkg.in/inf.v0 v0.9.1 // indirect 76 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 77 | gopkg.in/yaml.v3 v3.0.1 // indirect 78 | k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect 79 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 80 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 81 | ) 82 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /lua_configuration/networking.istio.io/DestinationRule/testdata/traffic_routing_with_a_match.yaml: -------------------------------------------------------------------------------- 1 | trafficRouting: 2 | apiVersion: rollouts.kruise.io/v1alpha1 3 | kind: TrafficRouting 4 | metadata: 5 | name: tr-demo 6 | spec: 7 | strategy: 8 | matches: 9 | - headers: 10 | - type: Exact 11 | name: version 12 | value: canary 13 | objectRef: 14 | - service: svc-demo 15 | customNetworkRefs: 16 | - apiVersion: networking.istio.io/v1beta1 17 | kind: DestinationRule 18 | name: ds-demo 19 | original: 20 | apiVersion: networking.istio.io/v1beta1 21 | kind: DestinationRule 22 | metadata: 23 | name: ds-demo 24 | spec: 25 | host: svc-demo 26 | trafficPolicy: 27 | loadBalancer: 28 | simple: ROUND_ROBIN 29 | subsets: 30 | - labels: 31 | version: base 32 | name: version-base 33 | expected: 34 | - apiVersion: networking.istio.io/v1beta1 35 | kind: DestinationRule 36 | metadata: 37 | name: ds-demo 38 | spec: 39 | host: svc-demo 40 | trafficPolicy: 41 | loadBalancer: 42 | simple: ROUND_ROBIN 43 | subsets: 44 | - labels: 45 | version: base 46 | name: version-base 47 | - labels: 48 | istio.service.tag: gray 49 | name: canary -------------------------------------------------------------------------------- /lua_configuration/networking.istio.io/DestinationRule/trafficRouting.lua: -------------------------------------------------------------------------------- 1 | local spec = obj.data.spec 2 | local canary = {} 3 | canary.labels = {} 4 | canary.name = "canary" 5 | local podLabelKey = "istio.service.tag" 6 | canary.labels[podLabelKey] = "gray" 7 | table.insert(spec.subsets, canary) 8 | return obj.data -------------------------------------------------------------------------------- /lua_configuration/networking.istio.io/VirtualService/testdata/rollout_with_three_steps.yaml: -------------------------------------------------------------------------------- 1 | rollout: 2 | apiVersion: rollouts.kruise.io/v1beta1 3 | kind: Rollout 4 | metadata: 5 | name: rollouts-demo 6 | spec: 7 | workloadRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: deploy-demo 11 | strategy: 12 | canary: 13 | steps: 14 | - matches: 15 | - headers: 16 | - type: Exact 17 | name: user-agent 18 | value: pc 19 | - type: RegularExpression 20 | name: name 21 | value: ".*demo" 22 | requestHeaderModifier: 23 | set: 24 | - name: "header-foo" 25 | value: "bar" 26 | - matches: 27 | - headers: 28 | - type: Exact 29 | name: user-agent 30 | value: pc 31 | - headers: 32 | - type: RegularExpression 33 | name: name 34 | value: ".*demo" 35 | - traffic: "50%" 36 | trafficRoutings: 37 | - service: svc-demo 38 | customNetworkRefs: 39 | - apiVersion: networking.istio.io/v1alpha3 40 | kind: VirtualService 41 | name: vs-demo 42 | original: 43 | apiVersion: networking.istio.io/v1alpha3 44 | kind: VirtualService 45 | metadata: 46 | name: vs-demo 47 | spec: 48 | hosts: 49 | - "*" 50 | gateways: 51 | - nginx-gateway 52 | http: 53 | - route: 54 | - destination: 55 | host: svc-demo 56 | expected: 57 | - apiVersion: networking.istio.io/v1alpha3 58 | kind: VirtualService 59 | metadata: 60 | name: vs-demo 61 | spec: 62 | hosts: 63 | - "*" 64 | gateways: 65 | - nginx-gateway 66 | http: 67 | - match: 68 | - headers: 69 | user-agent: 70 | exact: pc 71 | name: 72 | regex: .*demo 73 | headers: 74 | request: 75 | set: 76 | header-foo: bar 77 | route: 78 | - destination: 79 | host: svc-demo-canary 80 | - route: 81 | - destination: 82 | host: svc-demo 83 | - apiVersion: networking.istio.io/v1alpha3 84 | kind: VirtualService 85 | metadata: 86 | name: vs-demo 87 | spec: 88 | hosts: 89 | - "*" 90 | gateways: 91 | - nginx-gateway 92 | http: 93 | - match: 94 | - headers: 95 | name: 96 | regex: .*demo 97 | route: 98 | - destination: 99 | host: svc-demo-canary 100 | - match: 101 | - headers: 102 | user-agent: 103 | exact: pc 104 | route: 105 | - destination: 106 | host: svc-demo-canary 107 | - route: 108 | - destination: 109 | host: svc-demo 110 | - apiVersion: networking.istio.io/v1alpha3 111 | kind: VirtualService 112 | metadata: 113 | name: vs-demo 114 | spec: 115 | hosts: 116 | - "*" 117 | gateways: 118 | - nginx-gateway 119 | http: 120 | - route: 121 | - destination: 122 | host: svc-demo 123 | weight: 50 124 | - destination: 125 | host: svc-demo-canary 126 | weight: 50 127 | -------------------------------------------------------------------------------- /lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_a_match.yaml: -------------------------------------------------------------------------------- 1 | trafficRouting: 2 | apiVersion: rollouts.kruise.io/v1alpha1 3 | kind: TrafficRouting 4 | metadata: 5 | name: tr-demo 6 | spec: 7 | strategy: 8 | matches: 9 | - headers: 10 | - type: Exact 11 | name: user-agent 12 | value: pc 13 | - type: RegularExpression 14 | name: name 15 | value: ".*demo" 16 | requestHeaderModifier: 17 | set: 18 | - name: "header-foo" 19 | value: "bar" 20 | objectRef: 21 | - service: svc-demo 22 | customNetworkRefs: 23 | - apiVersion: networking.istio.io/v1alpha3 24 | kind: VirtualService 25 | name: vs-demo 26 | original: 27 | apiVersion: networking.istio.io/v1alpha3 28 | kind: VirtualService 29 | metadata: 30 | name: vs-demo 31 | spec: 32 | hosts: 33 | - "*" 34 | gateways: 35 | - nginx-gateway 36 | http: 37 | - route: 38 | - destination: 39 | host: svc-demo 40 | subset: base 41 | expected: 42 | - apiVersion: networking.istio.io/v1alpha3 43 | kind: VirtualService 44 | metadata: 45 | name: vs-demo 46 | spec: 47 | hosts: 48 | - "*" 49 | gateways: 50 | - nginx-gateway 51 | http: 52 | - match: 53 | - headers: 54 | user-agent: 55 | exact: pc 56 | name: 57 | regex: .*demo 58 | headers: 59 | request: 60 | set: 61 | header-foo: bar 62 | route: 63 | - destination: 64 | host: svc-demo 65 | subset: canary 66 | - route: 67 | - destination: 68 | host: svc-demo 69 | subset: base 70 | -------------------------------------------------------------------------------- /lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_matches.yaml: -------------------------------------------------------------------------------- 1 | trafficRouting: 2 | apiVersion: rollouts.kruise.io/v1alpha1 3 | kind: TrafficRouting 4 | metadata: 5 | name: tr-demo 6 | spec: 7 | strategy: 8 | matches: 9 | - headers: 10 | - type: Exact 11 | name: user-agent 12 | value: pc 13 | - headers: 14 | - type: RegularExpression 15 | name: name 16 | value: ".*demo" 17 | requestHeaderModifier: 18 | set: 19 | - name: "header-foo" 20 | value: "bar" 21 | objectRef: 22 | - service: svc-demo 23 | customNetworkRefs: 24 | - apiVersion: networking.istio.io/v1alpha3 25 | kind: VirtualService 26 | name: vs-demo 27 | original: 28 | apiVersion: networking.istio.io/v1alpha3 29 | kind: VirtualService 30 | metadata: 31 | name: vs-demo 32 | spec: 33 | hosts: 34 | - "*" 35 | gateways: 36 | - nginx-gateway 37 | http: 38 | - route: 39 | - destination: 40 | host: svc-demo 41 | subset: base 42 | expected: 43 | - apiVersion: networking.istio.io/v1alpha3 44 | kind: VirtualService 45 | metadata: 46 | name: vs-demo 47 | spec: 48 | hosts: 49 | - "*" 50 | gateways: 51 | - nginx-gateway 52 | http: 53 | - match: 54 | - headers: 55 | name: 56 | regex: .*demo 57 | headers: 58 | request: 59 | set: 60 | header-foo: bar 61 | route: 62 | - destination: 63 | host: svc-demo 64 | subset: canary 65 | - match: 66 | - headers: 67 | user-agent: 68 | exact: pc 69 | headers: 70 | request: 71 | set: 72 | header-foo: bar 73 | route: 74 | - destination: 75 | host: svc-demo 76 | subset: canary 77 | - route: 78 | - destination: 79 | host: svc-demo 80 | subset: base -------------------------------------------------------------------------------- /lua_configuration/networking.istio.io/VirtualService/testdata/traffic_routing_with_weight.yaml: -------------------------------------------------------------------------------- 1 | trafficRouting: 2 | apiVersion: rollouts.kruise.io/v1alpha1 3 | kind: TrafficRouting 4 | metadata: 5 | name: tr-demo 6 | spec: 7 | strategy: 8 | weight: 50 9 | objectRef: 10 | - service: svc-demo 11 | customNetworkRefs: 12 | - apiVersion: networking.istio.io/v1alpha3 13 | kind: VirtualService 14 | name: vs-demo 15 | original: 16 | apiVersion: networking.istio.io/v1alpha3 17 | kind: VirtualService 18 | metadata: 19 | name: vs-demo 20 | spec: 21 | hosts: 22 | - "*" 23 | gateways: 24 | - nginx-gateway 25 | http: 26 | - route: 27 | - destination: 28 | host: svc-demo 29 | subset: base 30 | expected: 31 | - apiVersion: networking.istio.io/v1alpha3 32 | kind: VirtualService 33 | metadata: 34 | name: nginx-vs 35 | namespace: demo 36 | spec: 37 | hosts: 38 | - "*" 39 | gateways: 40 | - nginx-gateway 41 | http: 42 | - route: 43 | - destination: 44 | host: svc-demo 45 | subset: base 46 | weight: 50 47 | - destination: 48 | host: svc-demo 49 | subset: canary 50 | weight: 50 51 | -------------------------------------------------------------------------------- /lua_configuration/trafficrouting_ingress/aliyun-alb.lua: -------------------------------------------------------------------------------- 1 | annotations = {} 2 | if ( obj.annotations ) 3 | then 4 | annotations = obj.annotations 5 | end 6 | annotations["alb.ingress.kubernetes.io/canary"] = "true" 7 | annotations["alb.ingress.kubernetes.io/canary-by-cookie"] = nil 8 | annotations["alb.ingress.kubernetes.io/canary-by-header"] = nil 9 | annotations["alb.ingress.kubernetes.io/canary-by-header-pattern"] = nil 10 | annotations["alb.ingress.kubernetes.io/canary-by-header-value"] = nil 11 | annotations["alb.ingress.kubernetes.io/canary-weight"] = nil 12 | annotations["alb.ingress.kubernetes.io/order"] = "1" 13 | if ( obj.weight ~= "-1" ) 14 | then 15 | annotations["alb.ingress.kubernetes.io/canary-weight"] = obj.weight 16 | end 17 | if ( not obj.matches ) 18 | then 19 | return annotations 20 | end 21 | for _,match in ipairs(obj.matches) do 22 | local header = match.headers[1] 23 | if ( header.name == "canary-by-cookie" ) 24 | then 25 | annotations["alb.ingress.kubernetes.io/canary-by-cookie"] = header.value 26 | else 27 | annotations["alb.ingress.kubernetes.io/canary-by-header"] = header.name 28 | if ( header.type == "RegularExpression" ) 29 | then 30 | annotations["alb.ingress.kubernetes.io/canary-by-header-pattern"] = header.value 31 | else 32 | annotations["alb.ingress.kubernetes.io/canary-by-header-value"] = header.value 33 | end 34 | end 35 | end 36 | return annotations 37 | -------------------------------------------------------------------------------- /lua_configuration/trafficrouting_ingress/higress.lua: -------------------------------------------------------------------------------- 1 | annotations = {} 2 | -- obj.annotations is ingress annotations, it is recommended not to remove the part of the lua script, it must be kept 3 | if ( obj.annotations ) 4 | then 5 | annotations = obj.annotations 6 | end 7 | -- indicates the ingress is nginx canary api 8 | annotations["nginx.ingress.kubernetes.io/canary"] = "true" 9 | -- First, set all canary api to nil 10 | annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = nil 11 | annotations["nginx.ingress.kubernetes.io/canary-by-header"] = nil 12 | annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = nil 13 | annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = nil 14 | annotations["nginx.ingress.kubernetes.io/canary-weight"] = nil 15 | -- if rollout.spec.strategy.canary.steps.weight is nil, obj.weight will be -1, 16 | -- then we need remove the canary-weight annotation 17 | if ( obj.weight ~= "-1" ) 18 | then 19 | annotations["nginx.ingress.kubernetes.io/canary-weight"] = obj.weight 20 | end 21 | -- if don't contains headers, immediate return annotations 22 | if ( not obj.matches ) 23 | then 24 | return annotations 25 | end 26 | -- headers & cookie apis 27 | -- traverse matches 28 | for _,match in ipairs(obj.matches) do 29 | local header = match.headers[1] 30 | -- cookie 31 | if ( header.name == "canary-by-cookie" ) 32 | then 33 | annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value 34 | else 35 | annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name 36 | -- if regular expression 37 | if ( header.type == "RegularExpression" ) 38 | then 39 | annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value 40 | else 41 | annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value 42 | end 43 | end 44 | end 45 | -- must be return annotations 46 | return annotations -------------------------------------------------------------------------------- /lua_configuration/trafficrouting_ingress/mse.lua: -------------------------------------------------------------------------------- 1 | function split(input, delimiter) 2 | local arr = {} 3 | string.gsub(input, '[^' .. delimiter ..']+', function(w) table.insert(arr, w) end) 4 | return arr 5 | end 6 | 7 | annotations = obj.annotations 8 | annotations["nginx.ingress.kubernetes.io/canary"] = "true" 9 | annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = nil 10 | annotations["nginx.ingress.kubernetes.io/canary-by-header"] = nil 11 | annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = nil 12 | annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = nil 13 | -- MSE extended annotations 14 | annotations["mse.ingress.kubernetes.io/canary-by-query"] = nil 15 | annotations["mse.ingress.kubernetes.io/canary-by-query-pattern"] = nil 16 | annotations["mse.ingress.kubernetes.io/canary-by-query-value"] = nil 17 | annotations["nginx.ingress.kubernetes.io/canary-weight"] = nil 18 | if ( obj.weight ~= "-1" ) 19 | then 20 | annotations["nginx.ingress.kubernetes.io/canary-weight"] = obj.weight 21 | end 22 | if ( annotations["mse.ingress.kubernetes.io/service-subset"] ) 23 | then 24 | annotations["mse.ingress.kubernetes.io/service-subset"] = "gray" 25 | end 26 | 27 | if ( obj.requestHeaderModifier ) 28 | then 29 | local str = '' 30 | for _,header in ipairs(obj.requestHeaderModifier.set) do 31 | str = str..string.format("%s %s", header.name, header.value) 32 | end 33 | annotations["mse.ingress.kubernetes.io/request-header-control-update"] = str 34 | end 35 | if ( not obj.matches ) 36 | then 37 | return annotations 38 | end 39 | for _,match in ipairs(obj.matches) do 40 | if match.headers and next(match.headers) ~= nil then 41 | header = match.headers[1] 42 | if ( header.name == "canary-by-cookie" ) 43 | then 44 | annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value 45 | else 46 | annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name 47 | if ( header.type == "RegularExpression" ) 48 | then 49 | annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value 50 | else 51 | annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value 52 | end 53 | end 54 | end 55 | if match.queryParams and next(match.queryParams) ~= nil then 56 | queryParam = match.queryParams[1] 57 | annotations["nginx.ingress.kubernetes.io/canary-by-query"] = queryParam.name 58 | if ( queryParam.type == "RegularExpression" ) 59 | then 60 | annotations["nginx.ingress.kubernetes.io/canary-by-query-pattern"] = queryParam.value 61 | else 62 | annotations["nginx.ingress.kubernetes.io/canary-by-query-value"] = queryParam.value 63 | end 64 | end 65 | end 66 | return annotations -------------------------------------------------------------------------------- /lua_configuration/trafficrouting_ingress/nginx.lua: -------------------------------------------------------------------------------- 1 | annotations = {} 2 | -- obj.annotations is ingress annotations, it is recommended not to remove the part of the lua script, it must be kept 3 | if ( obj.annotations ) 4 | then 5 | annotations = obj.annotations 6 | end 7 | -- indicates the ingress is nginx canary api 8 | annotations["nginx.ingress.kubernetes.io/canary"] = "true" 9 | -- First, set all canary api to nil 10 | annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = nil 11 | annotations["nginx.ingress.kubernetes.io/canary-by-header"] = nil 12 | annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = nil 13 | annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = nil 14 | annotations["nginx.ingress.kubernetes.io/canary-weight"] = nil 15 | -- if rollout.spec.strategy.canary.steps.weight is nil, obj.weight will be -1, 16 | -- then we need remove the canary-weight annotation 17 | if ( obj.weight ~= "-1" ) 18 | then 19 | annotations["nginx.ingress.kubernetes.io/canary-weight"] = obj.weight 20 | end 21 | -- if don't contains headers, immediate return annotations 22 | if ( not obj.matches ) 23 | then 24 | return annotations 25 | end 26 | -- headers & cookie apis 27 | -- traverse matches 28 | for _,match in ipairs(obj.matches) do 29 | if match.headers and next(match.headers) ~= nil then 30 | local header = match.headers[1] 31 | -- cookie 32 | if ( header.name == "canary-by-cookie" ) 33 | then 34 | annotations["nginx.ingress.kubernetes.io/canary-by-cookie"] = header.value 35 | else 36 | annotations["nginx.ingress.kubernetes.io/canary-by-header"] = header.name 37 | -- if regular expression 38 | if ( header.type == "RegularExpression" ) 39 | then 40 | annotations["nginx.ingress.kubernetes.io/canary-by-header-pattern"] = header.value 41 | else 42 | annotations["nginx.ingress.kubernetes.io/canary-by-header-value"] = header.value 43 | end 44 | end 45 | end 46 | end 47 | -- must be return annotations 48 | return annotations 49 | -------------------------------------------------------------------------------- /pkg/controller/batchrelease/control/apis.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package control 18 | 19 | import "k8s.io/apimachinery/pkg/util/intstr" 20 | 21 | // OriginalDeploymentStrategy stores part of the fileds of a workload, 22 | // so that it can be restored when finalizing. 23 | // It is only used for BlueGreen Release 24 | // Similar to DeploymentStrategy, it is an annotation used in workload 25 | // However, unlike DeploymentStrategy, it is only used to store and restore the user's strategy 26 | type OriginalDeploymentStrategy struct { 27 | // +optional 28 | MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,1,opt,name=maxUnavailable"` 29 | // +optional 30 | MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty" protobuf:"bytes,2,opt,name=maxSurge"` 31 | // Minimum number of seconds for which a newly created pod should be ready 32 | // without any of its container crashing, for it to be considered available. 33 | // Defaults to 0 (pod will be considered available as soon as it is ready) 34 | // +optional 35 | MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"` 36 | // The maximum time in seconds for a deployment to make progress before it 37 | // is considered to be failed. The deployment controller will continue to 38 | // process failed deployments and a condition with a ProgressDeadlineExceeded 39 | // reason will be surfaced in the deployment status. Note that progress will 40 | // not be estimated during the time a deployment is paused. Defaults to 600s. 41 | ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"` 42 | } 43 | -------------------------------------------------------------------------------- /pkg/controller/batchrelease/control/bluegreenstyle/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package bluegreenstyle 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/api/v1beta1" 21 | batchcontext "github.com/openkruise/rollouts/pkg/controller/batchrelease/context" 22 | "github.com/openkruise/rollouts/pkg/util" 23 | corev1 "k8s.io/api/core/v1" 24 | ) 25 | 26 | type Interface interface { 27 | // BuildController will get workload object and parse workload info, 28 | // and return a initialized controller for workload. 29 | BuildController() (Interface, error) 30 | // GetWorkloadInfo return workload information. 31 | GetWorkloadInfo() *util.WorkloadInfo 32 | // ListOwnedPods fetch the pods owned by the workload. 33 | // Note that we should list pod only if we really need it. 34 | // reserved for future use 35 | ListOwnedPods() ([]*corev1.Pod, error) 36 | // CalculateBatchContext calculate current batch context 37 | // according to release plan and current status of workload. 38 | CalculateBatchContext(release *v1beta1.BatchRelease) (*batchcontext.BatchContext, error) 39 | // Initialize do something before rolling out, for example: 40 | // - pause the workload 41 | // - update: MinReadySeconds, ProgressDeadlineSeconds, Strategy 42 | Initialize(release *v1beta1.BatchRelease) error 43 | // UpgradeBatch upgrade workload according current batch context. 44 | UpgradeBatch(ctx *batchcontext.BatchContext) error 45 | // Finalize do something after rolling out, for example: 46 | // - set pause to false, restore the original setting, delete annotation 47 | Finalize(release *v1beta1.BatchRelease) error 48 | } 49 | -------------------------------------------------------------------------------- /pkg/controller/batchrelease/control/canarystyle/deployment/stable.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package deployment 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/openkruise/rollouts/api/v1beta1" 24 | "github.com/openkruise/rollouts/pkg/controller/batchrelease/control" 25 | "github.com/openkruise/rollouts/pkg/util" 26 | apps "k8s.io/api/apps/v1" 27 | "k8s.io/apimachinery/pkg/types" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | ) 30 | 31 | type realStableController struct { 32 | stableInfo *util.WorkloadInfo 33 | stableObject *apps.Deployment 34 | stableClient client.Client 35 | stableKey types.NamespacedName 36 | } 37 | 38 | func newStable(cli client.Client, key types.NamespacedName) realStableController { 39 | return realStableController{stableClient: cli, stableKey: key} 40 | } 41 | 42 | func (rc *realStableController) GetStableInfo() *util.WorkloadInfo { 43 | return rc.stableInfo 44 | } 45 | 46 | func (rc *realStableController) Initialize(release *v1beta1.BatchRelease) error { 47 | if control.IsControlledByBatchRelease(release, rc.stableObject) { 48 | return nil 49 | } 50 | 51 | d := util.GetEmptyObjectWithKey(rc.stableObject) 52 | owner := control.BuildReleaseControlInfo(release) 53 | 54 | body := fmt.Sprintf(`{"metadata":{"annotations":{"%s":"%s"}}}`, util.BatchReleaseControlAnnotation, owner) 55 | return rc.stableClient.Patch(context.TODO(), d, client.RawPatch(types.StrategicMergePatchType, []byte(body))) 56 | } 57 | 58 | func (rc *realStableController) Finalize(release *v1beta1.BatchRelease) error { 59 | if rc.stableObject == nil { 60 | return nil // no need to process deleted object 61 | } 62 | 63 | // if batchPartition == nil, workload should be promoted; 64 | pause := release.Spec.ReleasePlan.BatchPartition != nil 65 | body := fmt.Sprintf(`{"metadata":{"annotations":{"%s":null}},"spec":{"paused":%v}}`, 66 | util.BatchReleaseControlAnnotation, pause) 67 | 68 | d := util.GetEmptyObjectWithKey(rc.stableObject) 69 | if err := rc.stableClient.Patch(context.TODO(), d, client.RawPatch(types.StrategicMergePatchType, []byte(body))); err != nil { 70 | return err 71 | } 72 | if control.ShouldWaitResume(release) { 73 | return waitAllUpdatedAndReady(d.(*apps.Deployment)) 74 | } 75 | return nil 76 | } 77 | 78 | func waitAllUpdatedAndReady(deployment *apps.Deployment) error { 79 | if deployment.Spec.Paused { 80 | return fmt.Errorf("promote error: deployment should not be paused") 81 | } 82 | 83 | createdReplicas := deployment.Status.Replicas 84 | updatedReplicas := deployment.Status.UpdatedReplicas 85 | if createdReplicas != updatedReplicas { 86 | return fmt.Errorf("promote error: all replicas should be upgraded") 87 | } 88 | 89 | availableReplicas := deployment.Status.AvailableReplicas 90 | allowedUnavailable := util.DeploymentMaxUnavailable(deployment) 91 | if allowedUnavailable+availableReplicas < createdReplicas { 92 | return fmt.Errorf("promote error: ready replicas should satisfy maxUnavailable") 93 | } 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /pkg/controller/batchrelease/control/canarystyle/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package canarystyle 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/api/v1beta1" 21 | batchcontext "github.com/openkruise/rollouts/pkg/controller/batchrelease/context" 22 | "github.com/openkruise/rollouts/pkg/util" 23 | ) 24 | 25 | type Interface interface { 26 | CanaryInterface 27 | StableInterface 28 | // BuildStableController will get stable workload object and parse 29 | // stable workload info, and return a controller for stable workload. 30 | BuildStableController() (StableInterface, error) 31 | // BuildCanaryController will get canary workload object and parse 32 | // canary workload info, and return a controller for canary workload. 33 | BuildCanaryController(release *v1beta1.BatchRelease) (CanaryInterface, error) 34 | // CalculateBatchContext calculate the current batch context according to 35 | // our release plan and the statues of stable workload and canary workload. 36 | CalculateBatchContext(release *v1beta1.BatchRelease) (*batchcontext.BatchContext, error) 37 | } 38 | 39 | // CanaryInterface contains the methods about canary workload 40 | type CanaryInterface interface { 41 | // GetCanaryInfo return the information about canary workload 42 | GetCanaryInfo() *util.WorkloadInfo 43 | // UpgradeBatch upgrade canary workload according to current batch context 44 | UpgradeBatch(*batchcontext.BatchContext) error 45 | // Create creates canary workload before rolling out 46 | Create(controller *v1beta1.BatchRelease) error 47 | // Delete deletes canary workload after rolling out 48 | Delete(controller *v1beta1.BatchRelease) error 49 | } 50 | 51 | // StableInterface contains the methods about stable workload 52 | type StableInterface interface { 53 | // GetStableInfo return the information about stable workload 54 | GetStableInfo() *util.WorkloadInfo 55 | // Initialize claim the stable workload is under rollout control 56 | Initialize(controller *v1beta1.BatchRelease) error 57 | // Finalize do something after rolling out, for example: 58 | // - free the stable workload from rollout control; 59 | // - resume stable workload and wait all pods updated if we need. 60 | Finalize(controller *v1beta1.BatchRelease) error 61 | } 62 | -------------------------------------------------------------------------------- /pkg/controller/batchrelease/control/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package control 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/pkg/util" 21 | ) 22 | 23 | type WorkloadEventType string 24 | 25 | const ( 26 | // WorkloadNormalState means workload is normal and event should be ignored. 27 | WorkloadNormalState WorkloadEventType = "workload-is-at-normal-state" 28 | // WorkloadUnknownState means workload state is unknown and should retry. 29 | WorkloadUnknownState WorkloadEventType = "workload-is-at-unknown-state" 30 | // WorkloadPodTemplateChanged means workload revision changed, should be stopped to execute batch release plan. 31 | WorkloadPodTemplateChanged WorkloadEventType = "workload-pod-template-changed" 32 | // WorkloadReplicasChanged means workload is scaling during rollout, should recalculate upgraded pods in current batch. 33 | WorkloadReplicasChanged WorkloadEventType = "workload-replicas-changed" 34 | // WorkloadStillReconciling means workload status is untrusted Untrustworthy, we should wait workload controller to reconcile. 35 | WorkloadStillReconciling WorkloadEventType = "workload-is-reconciling" 36 | // WorkloadHasGone means workload is deleted during rollout, we should do something finalizing works if this event occurs. 37 | WorkloadHasGone WorkloadEventType = "workload-has-gone" 38 | // WorkloadRollbackInBatch means workload is rollback according to BatchRelease batch plan. 39 | WorkloadRollbackInBatch WorkloadEventType = "workload-rollback-in-batch" 40 | ) 41 | 42 | // Interface is the interface that all type of control plane implements for rollout. 43 | type Interface interface { 44 | // Initialize make sure that the resource is ready to be progressed. 45 | // this function is tasked to do any initialization work on the resources. 46 | // it returns nil if the preparation is succeeded, else the preparation should retry. 47 | Initialize() error 48 | 49 | // UpgradeBatch tries to upgrade old replicas according to the release plan. 50 | // it will upgrade the old replicas as the release plan allows in the current batch. 51 | // this function is tasked to do any initialization work on the resources. 52 | // it returns nil if the preparation is succeeded, else the preparation should retry. 53 | UpgradeBatch() error 54 | 55 | // EnsureBatchPodsReadyAndLabeled checks how many replicas are ready to serve requests in the current batch. 56 | // this function is tasked to do any initialization work on the resources. 57 | // it returns nil if the preparation is succeeded, else the preparation should retry. 58 | EnsureBatchPodsReadyAndLabeled() error 59 | 60 | // Finalize makes sure the resources are in a good final state. 61 | // this function is tasked to do any initialization work on the resources. 62 | // it returns nil if the preparation is succeeded, else the preparation should retry. 63 | Finalize() error 64 | 65 | // SyncWorkloadInformation will watch and compare the status recorded in Status of BatchRelease 66 | // and the real-time workload information. If workload status is inconsistent with that recorded 67 | // in BatchRelease, it will return the corresponding WorkloadEventType and info. 68 | SyncWorkloadInformation() (WorkloadEventType, *util.WorkloadInfo, error) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/controller/batchrelease/control/partitionstyle/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package partitionstyle 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/api/v1beta1" 21 | batchcontext "github.com/openkruise/rollouts/pkg/controller/batchrelease/context" 22 | "github.com/openkruise/rollouts/pkg/util" 23 | corev1 "k8s.io/api/core/v1" 24 | ) 25 | 26 | type Interface interface { 27 | // BuildController will get workload object and parse workload info, 28 | // and return a initialized controller for workload. 29 | BuildController() (Interface, error) 30 | // GetWorkloadInfo return workload information. 31 | GetWorkloadInfo() *util.WorkloadInfo 32 | // ListOwnedPods fetch the pods owned by the workload. 33 | // Note that we should list pod only if we really need it. 34 | ListOwnedPods() ([]*corev1.Pod, error) 35 | // CalculateBatchContext calculate current batch context 36 | // according to release plan and current status of workload. 37 | CalculateBatchContext(release *v1beta1.BatchRelease) (*batchcontext.BatchContext, error) 38 | 39 | // Initialize do something before rolling out, for example: 40 | // - claim the workload is under our control; 41 | // - other things related with specific type of workload, such as 100% partition settings. 42 | Initialize(release *v1beta1.BatchRelease) error 43 | // UpgradeBatch upgrade workload according current batch context. 44 | UpgradeBatch(ctx *batchcontext.BatchContext) error 45 | // Finalize do something after rolling out, for example: 46 | // - free the stable workload from rollout control; 47 | // - resume workload if we need. 48 | Finalize(release *v1beta1.BatchRelease) error 49 | } 50 | -------------------------------------------------------------------------------- /pkg/controller/deployment/deployment_event_handler.go: -------------------------------------------------------------------------------- 1 | package deployment 2 | 3 | import ( 4 | "context" 5 | 6 | admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 7 | appsv1 "k8s.io/api/apps/v1" 8 | "k8s.io/client-go/util/workqueue" 9 | "k8s.io/klog/v2" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/event" 12 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 13 | 14 | "github.com/openkruise/rollouts/api/v1alpha1" 15 | "github.com/openkruise/rollouts/pkg/webhook/util/configuration" 16 | ) 17 | 18 | type MutatingWebhookEventHandler struct { 19 | client.Reader 20 | } 21 | 22 | func (m MutatingWebhookEventHandler) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { 23 | config, ok := evt.Object.(*admissionregistrationv1.MutatingWebhookConfiguration) 24 | if !ok || config == nil || !isKruiseRolloutMutatingConfiguration(config) || config.DeletionTimestamp.IsZero() { 25 | return 26 | } 27 | m.enqueue(q) 28 | } 29 | 30 | func (m MutatingWebhookEventHandler) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { 31 | config, ok := evt.Object.(*admissionregistrationv1.MutatingWebhookConfiguration) 32 | if !ok || config == nil || !isKruiseRolloutMutatingConfiguration(config) || config.DeletionTimestamp.IsZero() { 33 | return 34 | } 35 | m.enqueue(q) 36 | } 37 | 38 | func (m MutatingWebhookEventHandler) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { 39 | config, ok := evt.ObjectNew.(*admissionregistrationv1.MutatingWebhookConfiguration) 40 | if !ok || config == nil || !isKruiseRolloutMutatingConfiguration(config) || config.DeletionTimestamp.IsZero() { 41 | return 42 | } 43 | m.enqueue(q) 44 | } 45 | 46 | func (m MutatingWebhookEventHandler) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { 47 | config, ok := evt.Object.(*admissionregistrationv1.MutatingWebhookConfiguration) 48 | if !ok || config == nil || !isKruiseRolloutMutatingConfiguration(config) { 49 | return 50 | } 51 | m.enqueue(q) 52 | } 53 | 54 | func (m MutatingWebhookEventHandler) enqueue(q workqueue.RateLimitingInterface) { 55 | deploymentLister := appsv1.DeploymentList{} 56 | err := m.List(context.TODO(), &deploymentLister, client.MatchingLabels(map[string]string{v1alpha1.AdvancedDeploymentControlLabel: "true"})) 57 | if err != nil { 58 | klog.Errorf("Failed to list deployment, error: %v", err) 59 | } 60 | for index := range deploymentLister.Items { 61 | if deploymentLister.Items[index].Spec.Strategy.Type == appsv1.RollingUpdateDeploymentStrategyType { 62 | continue 63 | } 64 | q.Add(reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&deploymentLister.Items[index])}) 65 | } 66 | } 67 | 68 | func isKruiseRolloutMutatingConfiguration(object client.Object) bool { 69 | return object.GetName() == configuration.MutatingWebhookConfigurationName 70 | } 71 | -------------------------------------------------------------------------------- /pkg/controller/rollout/rollout_event_handler_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rollout 18 | 19 | import ( 20 | "context" 21 | "testing" 22 | 23 | "k8s.io/client-go/util/workqueue" 24 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 25 | "sigs.k8s.io/controller-runtime/pkg/event" 26 | ) 27 | 28 | func TestPodEventHandler(t *testing.T) { 29 | fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build() 30 | handler := enqueueRequestForWorkload{reader: fakeClient, scheme: scheme} 31 | 32 | err := fakeClient.Create(context.TODO(), rolloutDemo.DeepCopy()) 33 | if nil != err { 34 | t.Fatalf("unexpected create rollout %s failed: %v", rolloutDemo.Name, err) 35 | } 36 | 37 | // create 38 | createQ := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) 39 | createEvt := event.CreateEvent{ 40 | Object: deploymentDemo, 41 | } 42 | handler.Create(createEvt, createQ) 43 | if createQ.Len() != 1 { 44 | t.Errorf("unexpected create event handle queue size, expected 1 actual %d", createQ.Len()) 45 | } 46 | 47 | // other namespace 48 | demo1 := deploymentDemo.DeepCopy() 49 | demo1.Namespace = "other-ns" 50 | createEvt = event.CreateEvent{ 51 | Object: demo1, 52 | } 53 | handler.Create(createEvt, createQ) 54 | if createQ.Len() != 1 { 55 | t.Errorf("unexpected create event handle queue size, expected 1 actual %d", createQ.Len()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pkg/controller/rollouthistory/rollouthistory_event_handler.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package rollouthistory 18 | 19 | import ( 20 | rolloutv1alpha1 "github.com/openkruise/rollouts/api/v1alpha1" 21 | "k8s.io/apimachinery/pkg/types" 22 | "k8s.io/client-go/util/workqueue" 23 | "sigs.k8s.io/controller-runtime/pkg/client" 24 | "sigs.k8s.io/controller-runtime/pkg/event" 25 | "sigs.k8s.io/controller-runtime/pkg/handler" 26 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 27 | ) 28 | 29 | var _ handler.EventHandler = &enqueueRequestForRolloutHistory{} 30 | 31 | type enqueueRequestForRolloutHistory struct { 32 | } 33 | 34 | func (w *enqueueRequestForRolloutHistory) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { 35 | w.handleEvent(q, evt.Object) 36 | } 37 | 38 | func (w *enqueueRequestForRolloutHistory) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { 39 | } 40 | 41 | func (w *enqueueRequestForRolloutHistory) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { 42 | } 43 | 44 | func (w *enqueueRequestForRolloutHistory) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { 45 | w.handleEvent(q, evt.ObjectNew) 46 | } 47 | 48 | func (w *enqueueRequestForRolloutHistory) handleEvent(q workqueue.RateLimitingInterface, obj client.Object) { 49 | // In fact, rolloutHistory which is created by controller must have rolloutNameLabel and rolloutIDLabe 50 | rolloutName, ok1 := obj.(*rolloutv1alpha1.RolloutHistory).Labels[rolloutNameLabel] 51 | _, ok2 := obj.(*rolloutv1alpha1.RolloutHistory).Labels[rolloutIDLabel] 52 | if !ok1 || !ok2 { 53 | return 54 | } 55 | // add rollout which just creates a rolloutHistory to queue 56 | nsn := types.NamespacedName{Namespace: obj.GetNamespace(), Name: rolloutName} 57 | q.Add(reconcile.Request{NamespacedName: nsn}) 58 | } 59 | 60 | var _ handler.EventHandler = &enqueueRequestForRollout{} 61 | 62 | type enqueueRequestForRollout struct { 63 | } 64 | 65 | func (w *enqueueRequestForRollout) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { 66 | w.handleEvent(q, evt.Object) 67 | } 68 | 69 | func (w *enqueueRequestForRollout) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { 70 | } 71 | 72 | func (w *enqueueRequestForRollout) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { 73 | } 74 | 75 | func (w *enqueueRequestForRollout) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { 76 | w.handleEvent(q, evt.ObjectNew) 77 | } 78 | 79 | func (w *enqueueRequestForRollout) handleEvent(q workqueue.RateLimitingInterface, obj client.Object) { 80 | // RolloutID shouldn't be empty 81 | rollout := obj.(*rolloutv1alpha1.Rollout) 82 | if rollout.Status.CanaryStatus == nil || rollout.Status.CanaryStatus.ObservedRolloutID == "" { 83 | return 84 | } 85 | // add rollout with RolloutID to queue 86 | nsn := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} 87 | q.Add(reconcile.Request{NamespacedName: nsn}) 88 | } 89 | -------------------------------------------------------------------------------- /pkg/feature/rollout_features.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package feature 18 | 19 | import ( 20 | "k8s.io/apimachinery/pkg/util/runtime" 21 | "k8s.io/component-base/featuregate" 22 | 23 | utilfeature "github.com/openkruise/rollouts/pkg/util/feature" 24 | ) 25 | 26 | const ( 27 | // RolloutHistoryGate enable recording history for each rolling. 28 | RolloutHistoryGate featuregate.Feature = "RolloutHistory" 29 | // AdvancedDeploymentGate enable advanced deployment controller. 30 | AdvancedDeploymentGate featuregate.Feature = "AdvancedDeployment" 31 | // AppendServiceSelectorGate enable appending pod labels from PodTemplateMetadata to the canary service selector. 32 | AppendServiceSelectorGate featuregate.Feature = "AppendPodSelector" 33 | ) 34 | 35 | var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ 36 | RolloutHistoryGate: {Default: false, PreRelease: featuregate.Alpha}, 37 | AdvancedDeploymentGate: {Default: false, PreRelease: featuregate.Alpha}, 38 | AppendServiceSelectorGate: {Default: false, PreRelease: featuregate.Alpha}, 39 | } 40 | 41 | func init() { 42 | runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultFeatureGates)) 43 | } 44 | -------------------------------------------------------------------------------- /pkg/feature/switch.go: -------------------------------------------------------------------------------- 1 | package feature 2 | 3 | import "flag" 4 | 5 | var ( 6 | // If workloadTypeFilterSwitch is true, webhook and controllers 7 | // will filter out unsupported workload type. Currently, rollout 8 | // support Deployment, CloneSet, StatefulSet and Advanced StatefulSet. 9 | // Default to true. 10 | workloadTypeFilterSwitch bool 11 | ) 12 | 13 | func init() { 14 | flag.BoolVar(&workloadTypeFilterSwitch, "filter-workload-type", true, "filter known workload gvk for rollout controller") 15 | } 16 | 17 | func NeedFilterWorkloadType() bool { 18 | return workloadTypeFilterSwitch 19 | } 20 | -------------------------------------------------------------------------------- /pkg/trafficrouting/network/composite.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2024 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package network 18 | 19 | import ( 20 | "context" 21 | 22 | "k8s.io/apimachinery/pkg/util/validation/field" 23 | 24 | "github.com/openkruise/rollouts/api/v1beta1" 25 | ) 26 | 27 | var ( 28 | _ NetworkProvider = (CompositeController)(nil) 29 | ) 30 | 31 | // CompositeController is a set of NetworkProvider 32 | type CompositeController []NetworkProvider 33 | 34 | func (c CompositeController) Initialize(ctx context.Context) error { 35 | for _, provider := range c { 36 | if err := provider.Initialize(ctx); err != nil { 37 | return err 38 | } 39 | } 40 | return nil 41 | } 42 | 43 | func (c CompositeController) EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) { 44 | done := true 45 | for _, provider := range c { 46 | if innerDone, innerErr := provider.EnsureRoutes(ctx, strategy); innerErr != nil { 47 | return false, innerErr 48 | } else if !innerDone { 49 | done = false 50 | } 51 | } 52 | return done, nil 53 | } 54 | 55 | func (c CompositeController) Finalise(ctx context.Context) (bool, error) { 56 | modified := false 57 | errList := field.ErrorList{} 58 | for _, provider := range c { 59 | if updated, innerErr := provider.Finalise(ctx); innerErr != nil { 60 | errList = append(errList, field.InternalError(field.NewPath("FinalizeChildNetworkProvider"), innerErr)) 61 | } else if updated { 62 | modified = true 63 | } 64 | } 65 | return modified, errList.ToAggregate() 66 | } 67 | -------------------------------------------------------------------------------- /pkg/trafficrouting/network/customNetworkProvider/lua_configuration/networking.error.io/LuaError/trafficRouting.lua: -------------------------------------------------------------------------------- 1 | -- the lua script contains error 2 | local spec = obj.error 3 | return sepc.error -------------------------------------------------------------------------------- /pkg/trafficrouting/network/customNetworkProvider/lua_configuration/networking.istio.io/DestinationRule/trafficRouting.lua: -------------------------------------------------------------------------------- 1 | local spec = obj.data.spec 2 | local canary = {} 3 | canary.labels = {} 4 | canary.name = "canary" 5 | local podLabelKey = "istio.service.tag" 6 | canary.labels[podLabelKey] = "gray" 7 | table.insert(spec.subsets, canary) 8 | return obj.data -------------------------------------------------------------------------------- /pkg/trafficrouting/network/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package network 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/openkruise/rollouts/api/v1beta1" 23 | ) 24 | 25 | // NetworkProvider common function across all TrafficRouting implementation 26 | type NetworkProvider interface { 27 | // Initialize only determine if the network resources(ingress & gateway api) exist. 28 | // If error is nil, then the network resources exist. 29 | Initialize(ctx context.Context) error 30 | // EnsureRoutes check and set routes, e.g. canary weight and match conditions. 31 | // 1. Canary weight specifies the relative proportion of traffic to be forwarded to the canary service within the range of [0,100] 32 | // 2. Match conditions indicates rules to be satisfied for A/B testing scenarios, such as header, cookie, queryParams etc. 33 | // Return true if and only if the route resources have been correctly updated and does not change in this round of reconciliation. 34 | // Otherwise, return false to wait for the eventual consistency. 35 | EnsureRoutes(ctx context.Context, strategy *v1beta1.TrafficRoutingStrategy) (bool, error) 36 | // Finalise will do some cleanup work after the canary rollout complete, such as delete canary ingress. 37 | // if error is nil, the return bool value means if the resources are modified 38 | Finalise(ctx context.Context) (bool, error) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/util/client/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package client 18 | 19 | import ( 20 | "fmt" 21 | 22 | kruiseclientset "github.com/openkruise/kruise-api/client/clientset/versioned" 23 | "k8s.io/apimachinery/pkg/runtime" 24 | "k8s.io/client-go/discovery" 25 | kubeclientset "k8s.io/client-go/kubernetes" 26 | "k8s.io/client-go/rest" 27 | ) 28 | 29 | // GenericClientset defines a generic client 30 | type GenericClientset struct { 31 | DiscoveryClient discovery.DiscoveryInterface 32 | KubeClient kubeclientset.Interface 33 | KruiseClient kruiseclientset.Interface 34 | } 35 | 36 | var ( 37 | cfg *rest.Config 38 | defaultGenericClient *GenericClientset 39 | ) 40 | 41 | // newForConfig creates a new Clientset for the given config. 42 | func newForConfig(c *rest.Config) (*GenericClientset, error) { 43 | cWithProtobuf := rest.CopyConfig(c) 44 | cWithProtobuf.ContentType = runtime.ContentTypeProtobuf 45 | discoveryClient, err := discovery.NewDiscoveryClientForConfig(cWithProtobuf) 46 | if err != nil { 47 | return nil, err 48 | } 49 | kubeClient, err := kubeclientset.NewForConfig(cWithProtobuf) 50 | if err != nil { 51 | return nil, err 52 | } 53 | kruiseClient, err := kruiseclientset.NewForConfig(c) 54 | if err != nil { 55 | return nil, err 56 | } 57 | return &GenericClientset{ 58 | DiscoveryClient: discoveryClient, 59 | KubeClient: kubeClient, 60 | KruiseClient: kruiseClient, 61 | }, nil 62 | } 63 | 64 | // NewRegistry creates clientset by client-go 65 | func NewRegistry(c *rest.Config) error { 66 | var err error 67 | defaultGenericClient, err = newForConfig(c) 68 | if err != nil { 69 | return err 70 | } 71 | cfgCopy := *c 72 | cfg = &cfgCopy 73 | return nil 74 | } 75 | 76 | // GetGenericClient returns default clientset 77 | func GetGenericClient() *GenericClientset { 78 | return defaultGenericClient 79 | } 80 | 81 | // GetGenericClientWithName returns clientset with given name as user-agent 82 | func GetGenericClientWithName(name string) *GenericClientset { 83 | if cfg == nil { 84 | return nil 85 | } 86 | newCfg := *cfg 87 | newCfg.UserAgent = fmt.Sprintf("%s/%s", cfg.UserAgent, name) 88 | clientset, _ := newForConfig(cfg) 89 | return clientset 90 | } 91 | -------------------------------------------------------------------------------- /pkg/util/configuration/configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package configuration 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/openkruise/rollouts/pkg/util" 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/api/errors" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | ) 28 | 29 | const ( 30 | // kruise rollout configmap name 31 | RolloutConfigurationName = "kruise-rollout-configuration" 32 | 33 | LuaTrafficRoutingIngressTypePrefix = "lua.traffic.routing.ingress" 34 | LuaTrafficRoutingCustomTypePrefix = "lua.traffic.routing" 35 | ) 36 | 37 | func GetTrafficRoutingIngressLuaScript(client client.Client, iType string) (string, error) { 38 | data, err := getRolloutConfiguration(client) 39 | if err != nil { 40 | return "", err 41 | } else if len(data) == 0 { 42 | return "", nil 43 | } 44 | value, ok := data[fmt.Sprintf("%s.%s", LuaTrafficRoutingIngressTypePrefix, iType)] 45 | if !ok { 46 | return "", nil 47 | } 48 | return value, nil 49 | } 50 | 51 | func getRolloutConfiguration(c client.Client) (map[string]string, error) { 52 | cfg := &corev1.ConfigMap{} 53 | err := c.Get(context.TODO(), client.ObjectKey{Namespace: util.GetRolloutNamespace(), Name: RolloutConfigurationName}, cfg) 54 | if err != nil { 55 | if errors.IsNotFound(err) { 56 | return nil, nil 57 | } 58 | return nil, err 59 | } 60 | return cfg.Data, nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/util/constant.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/openkruise/rollouts/api/v1alpha1" 23 | ) 24 | 25 | // For Rollout and BatchRelease 26 | const ( 27 | // BatchReleaseControlAnnotation is controller info about batchRelease when rollout 28 | BatchReleaseControlAnnotation = "batchrelease.rollouts.kruise.io/control-info" 29 | // InRolloutProgressingAnnotation marks workload as entering the rollout progressing process 30 | // and does not allow paused=false during this process. However, blueGreen is an exception, 31 | // which allows paused=false during progressing. 32 | InRolloutProgressingAnnotation = "rollouts.kruise.io/in-progressing" 33 | // RolloutHashAnnotation record observed rollout spec hash 34 | RolloutHashAnnotation = "rollouts.kruise.io/hash" 35 | ) 36 | 37 | // For Workloads 38 | const ( 39 | // CanaryDeploymentLabel is to label canary deployment that is created by batchRelease controller 40 | CanaryDeploymentLabel = "rollouts.kruise.io/canary-deployment" 41 | // CanaryDeploymentFinalizer is a finalizer to resources patched by batchRelease controller 42 | CanaryDeploymentFinalizer = "finalizer.rollouts.kruise.io/batch-release" 43 | // KruiseRolloutFinalizer is a finalizer for deployment/service/ingress/gateway/etc 44 | KruiseRolloutFinalizer = "rollouts.kruise.io/rollout" 45 | // WorkloadTypeLabel is a label to identify workload type 46 | WorkloadTypeLabel = "rollouts.kruise.io/workload-type" 47 | // DeploymentRevisionAnnotation is the revision annotation of a deployment's replica sets which records its rollout sequence 48 | DeploymentRevisionAnnotation = "deployment.kubernetes.io/revision" 49 | ) 50 | 51 | const ( 52 | TrafficRoutingFinalizer = "rollouts.kruise.io/trafficrouting" 53 | ) 54 | 55 | // For Pods 56 | const ( 57 | // NoNeedUpdatePodLabel will be patched to pod when rollback in batches if the pods no need to rollback 58 | NoNeedUpdatePodLabel = "rollouts.kruise.io/no-need-update" 59 | ) 60 | 61 | // For Others 62 | const ( 63 | // We omit vowels from the set of available characters to reduce the chances 64 | // of "bad words" being formed. 65 | alphanums = "bcdfghjklmnpqrstvwxz2456789" 66 | 67 | // CloneSetType DeploymentType and StatefulSetType are values to WorkloadTypeLabel 68 | CloneSetType WorkloadType = "cloneset" 69 | DeploymentType WorkloadType = "deployment" 70 | StatefulSetType WorkloadType = "statefulset" 71 | DaemonSetType WorkloadType = "daemonset" 72 | 73 | AddFinalizerOpType FinalizerOpType = "Add" 74 | RemoveFinalizerOpType FinalizerOpType = "Remove" 75 | ) 76 | 77 | type WorkloadType string 78 | 79 | type FinalizerOpType string 80 | 81 | func ProgressingRolloutFinalizer(name string) string { 82 | return fmt.Sprintf("%s/%s", v1alpha1.ProgressingRolloutFinalizerPrefix, name) 83 | } 84 | -------------------------------------------------------------------------------- /pkg/util/errors/types.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // RetryError represents a benign error that can be handled or ignored by the caller. 9 | // It encapsulates information that is non-critical and does not require immediate attention. 10 | type RetryError struct { 11 | Err error 12 | } 13 | 14 | // Error implements the error interface for RetryError. 15 | // It returns the error message of the encapsulated error or a default message. 16 | func (e *RetryError) Error() string { 17 | if e.Err != nil { 18 | return fmt.Sprintf("[retry]: %s", e.Err.Error()) 19 | } 20 | return "retry error" 21 | } 22 | 23 | // NewRetryError creates a new instance of RetryError. 24 | func NewRetryError(err error) *RetryError { 25 | return &RetryError{Err: err} 26 | } 27 | 28 | func IsRetryError(err error) bool { 29 | var re *RetryError 30 | return errors.As(err, &re) 31 | } 32 | 33 | func AsRetryError(err error, target **RetryError) bool { 34 | return errors.As(err, target) 35 | } 36 | 37 | // BadRequestError represents a fatal error that requires special handling. 38 | // Such errors are critical and may necessitate logging, alerts, or even program termination. 39 | type BadRequestError struct { 40 | Err error 41 | } 42 | 43 | // Error implements the error interface for BadRequestError. 44 | // It returns the error message of the encapsulated error or a default message. 45 | func (e *BadRequestError) Error() string { 46 | if e.Err != nil { 47 | return e.Err.Error() 48 | } 49 | return "fatal error" 50 | } 51 | 52 | // NewBadRequestError creates a new instance of BadRequestError. 53 | // It encapsulates the provided error, marking it as critical. 54 | func NewBadRequestError(err error) *BadRequestError { 55 | return &BadRequestError{Err: err} 56 | } 57 | 58 | // IsBadRequest checks whether the provided error is of type BadRequestError. 59 | // It returns true if the error is a BadRequestError or wraps a BadRequestError, false otherwise. 60 | func IsBadRequest(err error) bool { 61 | var brErr *BadRequestError 62 | return AsBadRequest(err, &brErr) 63 | } 64 | 65 | // AsBadRequest attempts to cast the provided error to a BadRequestError. 66 | // It returns true if the casting is successful, allowing the caller to handle it accordingly. 67 | func AsBadRequest(err error, target **BadRequestError) bool { 68 | return errors.As(err, target) 69 | } 70 | -------------------------------------------------------------------------------- /pkg/util/expectation/resource_expectations_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package expectations 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | func TestResourceExpectations(t *testing.T) { 24 | e := NewResourceExpectations() 25 | controllerKey01 := "default/cs01" 26 | controllerKey02 := "default/cs02" 27 | pod01 := "pod01" 28 | pod02 := "pod02" 29 | 30 | e.Expect(controllerKey01, Create, pod01) 31 | e.Expect(controllerKey01, Create, pod02) 32 | e.Expect(controllerKey01, Delete, pod01) 33 | if ok, _, _ := e.SatisfiedExpectations(controllerKey01); ok { 34 | t.Fatalf("expected not satisfied") 35 | } 36 | 37 | e.Observe(controllerKey01, Create, pod02) 38 | e.Observe(controllerKey01, Create, pod01) 39 | if ok, _, _ := e.SatisfiedExpectations(controllerKey01); ok { 40 | t.Fatalf("expected not satisfied") 41 | } 42 | 43 | e.Observe(controllerKey02, Delete, pod01) 44 | if ok, _, _ := e.SatisfiedExpectations(controllerKey01); ok { 45 | t.Fatalf("expected not satisfied") 46 | } 47 | 48 | e.Observe(controllerKey01, Delete, pod01) 49 | if ok, _, _ := e.SatisfiedExpectations(controllerKey01); !ok { 50 | t.Fatalf("expected satisfied") 51 | } 52 | 53 | e.Observe(controllerKey01, Create, pod01) 54 | e.Observe(controllerKey01, Create, pod02) 55 | e.DeleteExpectations(controllerKey01) 56 | if ok, _, _ := e.SatisfiedExpectations(controllerKey01); !ok { 57 | t.Fatalf("expected satisfied") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/util/feature/feature_gate.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package feature 17 | 18 | import ( 19 | "k8s.io/component-base/featuregate" 20 | ) 21 | 22 | var ( 23 | // DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate. 24 | // Only top-level commands/options setup and the k8s.io/component-base/featuregate/testing package should make use of this. 25 | // Tests that need to modify feature gates for the duration of their test should use: 26 | // defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., )() 27 | DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() 28 | 29 | // DefaultFeatureGate is a shared global FeatureGate. 30 | // Top-level commands/options setup that needs to modify this feature gate should use DefaultMutableFeatureGate. 31 | DefaultFeatureGate featuregate.FeatureGate = DefaultMutableFeatureGate 32 | ) 33 | -------------------------------------------------------------------------------- /pkg/util/grace/grace_wrapper.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package grace 18 | 19 | import ( 20 | "time" 21 | 22 | "k8s.io/klog/v2" 23 | ) 24 | 25 | func RunWithGraceSeconds(key, action string, graceSeconds int32, f func() (bool, error)) (bool, time.Duration, error) { 26 | return runWithGraceSeconds(DefaultGraceExpectations, key, action, graceSeconds, f) 27 | } 28 | 29 | // Returns: 30 | // - error: If the passed function itself returns an error, the error is directly returned. 31 | // - bool: Tells the caller whether a retry is needed. 32 | // - time.Duration: The remaining time to wait. (only valid when error is nil) 33 | // The passed function `f` needs to be idempotent. 34 | // - The bool value returned by `f` indicates whether the related resources were updated in this call. 35 | // - If resources were updated, we need to wait for `graceSeconds`. 36 | func runWithGraceSeconds(e *realGraceExpectations, key, action string, graceSeconds int32, f func() (bool, error)) (bool, time.Duration, error) { 37 | modified, err := f() 38 | if err != nil { 39 | return true, 0, err 40 | } 41 | // if user specify 0, it means no need to wait 42 | if graceSeconds == 0 { 43 | e.Observe(key, Action(action)) 44 | return false, 0, nil 45 | } 46 | // if f return true, it means some resources are modified by f in this call 47 | // we need to wait a grace period 48 | if modified { 49 | e.Expect(key, Action(action)) 50 | klog.Infof("function return modified, expectation created, key: %s, action: %s, expect to wait %d seconds", key, action, graceSeconds) 51 | return true, time.Duration(graceSeconds) * time.Second, nil 52 | } 53 | if satisfied, remaining := e.SatisfiedExpectations(key, Action(action), graceSeconds); !satisfied { 54 | klog.Infof("expectation unsatisfied, key: %s, action: %s, remaining/graceSeconds: %.1f/%d", key, action, remaining.Seconds(), graceSeconds) 55 | return true, remaining, nil 56 | } 57 | e.Observe(key, Action(action)) 58 | klog.Infof("expectation satisfied, key: %s, action: %s", key, action) 59 | return false, 0, nil 60 | } 61 | 62 | // warning: only used for test 63 | func ResetExpectations() { 64 | DefaultGraceExpectations.resetExpectations() 65 | } 66 | -------------------------------------------------------------------------------- /pkg/util/lua_configuration.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "io/ioutil" 21 | "os" 22 | "path/filepath" 23 | 24 | "k8s.io/klog/v2" 25 | ) 26 | 27 | // patch -> file.Content, 28 | // for example: lua_configuration/trafficrouting_ingress/ingress.lua -> ingress.lua content 29 | var luaConfigurationList map[string]string 30 | 31 | func init() { 32 | luaConfigurationList = map[string]string{} 33 | _ = filepath.Walk("./lua_configuration", func(path string, f os.FileInfo, err error) error { 34 | if err != nil { 35 | klog.Warningf("filepath walk ./lua_configuration failed: %s", err.Error()) 36 | return err 37 | } 38 | if f.IsDir() { 39 | return nil 40 | } 41 | var data []byte 42 | data, err = ioutil.ReadFile(filepath.Clean(path)) 43 | if err != nil { 44 | klog.Errorf("Read file %s failed: %s", path, err.Error()) 45 | return err 46 | } 47 | luaConfigurationList[path] = string(data) 48 | return nil 49 | }) 50 | klog.Infof("Init Lua Configuration(%s)", DumpJSON(luaConfigurationList)) 51 | } 52 | 53 | func GetLuaConfigurationContent(key string) string { 54 | return luaConfigurationList[key] 55 | } 56 | -------------------------------------------------------------------------------- /pkg/util/luamanager/lua.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package luamanager 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "time" 23 | 24 | lua "github.com/yuin/gopher-lua" 25 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 26 | ) 27 | 28 | type LuaManager struct{} 29 | 30 | func (m *LuaManager) RunLuaScript(obj *unstructured.Unstructured, script string) (*lua.LState, error) { 31 | l := lua.NewState(lua.Options{SkipOpenLibs: true}) 32 | defer l.Close() 33 | for _, pair := range []struct { 34 | n string 35 | f lua.LGFunction 36 | }{ 37 | {lua.MathLibName, lua.OpenMath}, 38 | {lua.BaseLibName, lua.OpenBase}, 39 | {lua.TabLibName, lua.OpenTable}, 40 | {lua.StringLibName, lua.OpenString}, 41 | {JsonLibName, OpenJson}, 42 | } { 43 | if err := l.CallByParam(lua.P{ 44 | Fn: l.NewFunction(pair.f), 45 | NRet: 0, 46 | Protect: true, 47 | }, lua.LString(pair.n)); err != nil { 48 | return nil, err 49 | } 50 | } 51 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 52 | defer cancel() 53 | l.SetContext(ctx) 54 | objectValue := decodeValue(l, obj.Object) 55 | l.SetGlobal("obj", objectValue) 56 | err := l.DoString(script) 57 | return l, err 58 | } 59 | 60 | func decodeValue(L *lua.LState, value interface{}) lua.LValue { 61 | switch converted := value.(type) { 62 | case bool: 63 | return lua.LBool(converted) 64 | case float64: 65 | return lua.LNumber(converted) 66 | case string: 67 | return lua.LString(converted) 68 | case json.Number: 69 | return lua.LString(converted) 70 | case int: 71 | return lua.LNumber(converted) 72 | case int32: 73 | return lua.LNumber(converted) 74 | case int64: 75 | return lua.LNumber(converted) 76 | case []interface{}: 77 | arr := L.CreateTable(len(converted), 0) 78 | for _, item := range converted { 79 | arr.Append(decodeValue(L, item)) 80 | } 81 | return arr 82 | case map[string]interface{}: 83 | tbl := L.CreateTable(0, len(converted)) 84 | for key, item := range converted { 85 | tbl.RawSetH(lua.LString(key), decodeValue(L, item)) 86 | } 87 | return tbl 88 | case nil: 89 | return lua.LNil 90 | } 91 | 92 | return lua.LNil 93 | } 94 | -------------------------------------------------------------------------------- /pkg/util/meta.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import "os" 20 | 21 | func GetRolloutNamespace() string { 22 | if ns := os.Getenv("POD_NAMESPACE"); len(ns) > 0 { 23 | return ns 24 | } 25 | return "kruise-rollout" 26 | } 27 | -------------------------------------------------------------------------------- /pkg/util/patch/patch_utils_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package patch 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | "testing" 23 | 24 | "github.com/openkruise/rollouts/pkg/util" 25 | v1 "k8s.io/api/core/v1" 26 | ) 27 | 28 | func TestCommonPatch(t *testing.T) { 29 | condition := v1.PodCondition{Type: v1.ContainersReady, Status: v1.ConditionTrue, Message: "just for test"} 30 | patchReq := NewStrategicPatch(). 31 | AddFinalizer("new-finalizer"). 32 | RemoveFinalizer("old-finalizer"). 33 | InsertLabel("new-label", "foo1"). 34 | DeleteLabel("old-label"). 35 | InsertAnnotation("new-annotation", "foo2"). 36 | DeleteAnnotation("old-annotation"). 37 | UpdatePodCondition(condition) 38 | 39 | expectedPatchBody := fmt.Sprintf(`{"metadata":{"$deleteFromPrimitiveList/finalizers":["old-finalizer"],"annotations":{"new-annotation":"foo2","old-annotation":null},"finalizers":["new-finalizer"],"labels":{"new-label":"foo1","old-label":null}},"status":{"conditions":[%s]}}`, util.DumpJSON(condition)) 40 | 41 | if !reflect.DeepEqual(patchReq.String(), expectedPatchBody) { 42 | t.Fatalf("Not equal: \n%s \n%s", expectedPatchBody, patchReq.String()) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /pkg/util/ratelimiter/rate_limiter.go: -------------------------------------------------------------------------------- 1 | package ratelimiter 2 | 3 | import ( 4 | "flag" 5 | "time" 6 | 7 | "golang.org/x/time/rate" 8 | "k8s.io/client-go/util/workqueue" 9 | ) 10 | 11 | func init() { 12 | flag.DurationVar(&baseDelay, "rate-limiter-base-delay", time.Millisecond*5, "The base delay for rate limiter. Defaults 5ms") 13 | flag.DurationVar(&maxDelay, "rate-limiter-max-delay", time.Second*1000, "The max delay for rate limiter. Defaults 1000s") 14 | flag.IntVar(&qps, "rate-limiter-qps", 10, "The qps for rate limier. Defaults 10") 15 | flag.IntVar(&bucketSize, "rate-limiter-bucket-size", 100, "The bucket size for rate limier. Defaults 100") 16 | } 17 | 18 | var baseDelay, maxDelay time.Duration 19 | var qps, bucketSize int 20 | 21 | func DefaultControllerRateLimiter() workqueue.RateLimiter { 22 | return workqueue.NewMaxOfRateLimiter( 23 | workqueue.NewItemExponentialFailureRateLimiter(baseDelay, maxDelay), 24 | &workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(qps), bucketSize)}, 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /pkg/util/slices_utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package util 15 | 16 | import ( 17 | "github.com/openkruise/rollouts/api/v1beta1" 18 | ) 19 | 20 | func FilterHttpRouteMatch(s []v1beta1.HttpRouteMatch, f func(v1beta1.HttpRouteMatch) bool) []v1beta1.HttpRouteMatch { 21 | s2 := make([]v1beta1.HttpRouteMatch, 0, len(s)) 22 | for _, e := range s { 23 | if f(e) { 24 | s2 = append(s2, e) 25 | } 26 | } 27 | return s2 28 | } 29 | -------------------------------------------------------------------------------- /pkg/webhook/add_rollout.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhook 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/pkg/webhook/rollout/validating" 21 | ) 22 | 23 | func init() { 24 | addHandlers(validating.HandlerMap) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/webhook/add_workload.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package webhook 18 | 19 | import ( 20 | "github.com/openkruise/rollouts/pkg/webhook/workload/mutating" 21 | ) 22 | 23 | func init() { 24 | addHandlers(mutating.HandlerMap) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/webhook/rollout/validating/webhooks.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package validating 18 | 19 | import ( 20 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 21 | ) 22 | 23 | // +kubebuilder:webhook:path=/validate-rollouts-kruise-io-rollout,mutating=false,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups=rollouts.kruise.io,resources=rollouts,verbs=create;update,versions=v1alpha1;v1beta1,name=vrollout.kb.io 24 | 25 | var ( 26 | // HandlerMap contains admission webhook handlers 27 | HandlerMap = map[string]admission.Handler{ 28 | "validate-rollouts-kruise-io-rollout": &RolloutCreateUpdateHandler{}, 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /pkg/webhook/util/crd/crd.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package crd 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "reflect" 23 | 24 | rolloutapi "github.com/openkruise/rollouts/api" 25 | webhookutil "github.com/openkruise/rollouts/pkg/webhook/util" 26 | apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 27 | apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" 28 | apiextensionslisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1" 29 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 | "k8s.io/apimachinery/pkg/labels" 31 | "k8s.io/apimachinery/pkg/runtime" 32 | "k8s.io/apimachinery/pkg/runtime/schema" 33 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 34 | ) 35 | 36 | var ( 37 | scheme = runtime.NewScheme() 38 | ) 39 | 40 | func init() { 41 | utilruntime.Must(rolloutapi.AddToScheme(scheme)) 42 | } 43 | 44 | func Ensure(client apiextensionsclientset.Interface, lister apiextensionslisters.CustomResourceDefinitionLister, caBundle []byte) error { 45 | crdList, err := lister.List(labels.Everything()) 46 | if err != nil { 47 | return fmt.Errorf("failed to list crds: %v", err) 48 | } 49 | 50 | webhookConfig := apiextensionsv1.WebhookClientConfig{ 51 | CABundle: caBundle, 52 | } 53 | path := "/convert" 54 | if host := webhookutil.GetHost(); len(host) > 0 { 55 | url := fmt.Sprintf("https://%s:%d%s", host, webhookutil.GetPort(), path) 56 | webhookConfig.URL = &url 57 | } else { 58 | var port int32 = 443 59 | webhookConfig.Service = &apiextensionsv1.ServiceReference{ 60 | Namespace: webhookutil.GetNamespace(), 61 | Name: webhookutil.GetServiceName(), 62 | Port: &port, 63 | Path: &path, 64 | } 65 | } 66 | 67 | for _, crd := range crdList { 68 | if len(crd.Spec.Versions) == 0 || crd.Spec.Conversion == nil || crd.Spec.Conversion.Strategy != apiextensionsv1.WebhookConverter { 69 | continue 70 | } 71 | if !scheme.Recognizes(schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Versions[0].Name, Kind: crd.Spec.Names.Kind}) { 72 | continue 73 | } 74 | 75 | if crd.Spec.Conversion.Webhook == nil || !reflect.DeepEqual(crd.Spec.Conversion.Webhook.ClientConfig, webhookConfig) { 76 | newCRD := crd.DeepCopy() 77 | newCRD.Spec.Conversion.Webhook = &apiextensionsv1.WebhookConversion{ 78 | ClientConfig: webhookConfig.DeepCopy(), 79 | ConversionReviewVersions: []string{"v1", "v1beta1"}, 80 | } 81 | if _, err := client.ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), newCRD, metav1.UpdateOptions{}); err != nil { 82 | return fmt.Errorf("failed to update CRD %s: %v", newCRD.Name, err) 83 | } 84 | } 85 | } 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /pkg/webhook/util/generator/certgenerator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kruise Authors. 3 | Copyright 2018 The Kubernetes Authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package generator 19 | 20 | // Artifacts hosts a private key, its corresponding serving certificate and 21 | // the CA certificate that signs the serving certificate. 22 | type Artifacts struct { 23 | // PEM encoded private key 24 | Key []byte 25 | // PEM encoded serving certificate 26 | Cert []byte 27 | // PEM encoded CA private key 28 | CAKey []byte 29 | // PEM encoded CA certificate 30 | CACert []byte 31 | // Resource version of the certs 32 | ResourceVersion string 33 | } 34 | 35 | // CertGenerator is an interface to provision the serving certificate. 36 | type CertGenerator interface { 37 | // Generate returns a Artifacts struct. 38 | Generate(CommonName string) (*Artifacts, error) 39 | // SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert. 40 | SetCA(caKey, caCert []byte) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/webhook/util/generator/fake/certgenerator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kruise Authors. 3 | Copyright 2018 The Kubernetes Authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package fake 19 | 20 | import ( 21 | "bytes" 22 | "fmt" 23 | 24 | "github.com/openkruise/rollouts/pkg/webhook/util/generator" 25 | ) 26 | 27 | // CertGenerator is a certGenerator for testing. 28 | type CertGenerator struct { 29 | CAKey []byte 30 | CACert []byte 31 | DNSNameToCertArtifacts map[string]*generator.Artifacts 32 | } 33 | 34 | var _ generator.CertGenerator = &CertGenerator{} 35 | 36 | // SetCA sets the PEM-encoded CA private key and CA cert for signing the generated serving cert. 37 | func (cp *CertGenerator) SetCA(CAKey, CACert []byte) { 38 | cp.CAKey = CAKey 39 | cp.CACert = CACert 40 | } 41 | 42 | // Generate generates certificates by matching a common name. 43 | func (cp *CertGenerator) Generate(commonName string) (*generator.Artifacts, error) { 44 | certs, found := cp.DNSNameToCertArtifacts[commonName] 45 | if !found { 46 | return nil, fmt.Errorf("failed to find common name %q in the certGenerator", commonName) 47 | } 48 | if cp.CAKey != nil && cp.CACert != nil && 49 | !bytes.Contains(cp.CAKey, []byte("invalid")) && !bytes.Contains(cp.CACert, []byte("invalid")) { 50 | certs.CAKey = cp.CAKey 51 | certs.CACert = cp.CACert 52 | } 53 | return certs, nil 54 | } 55 | -------------------------------------------------------------------------------- /pkg/webhook/util/generator/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kruise Authors. 3 | Copyright 2018 The Kubernetes Authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package generator 19 | 20 | import ( 21 | "crypto/tls" 22 | "crypto/x509" 23 | "encoding/pem" 24 | "time" 25 | ) 26 | 27 | // ValidCACert think cert and key are valid if they meet the following requirements: 28 | // - key and cert are valid pair 29 | // - caCert is the root ca of cert 30 | // - cert is for dnsName 31 | // - cert won't expire before time 32 | func ValidCACert(key, cert, caCert []byte, dnsName string, time time.Time) bool { 33 | if len(key) == 0 || len(cert) == 0 || len(caCert) == 0 { 34 | return false 35 | } 36 | // Verify key and cert are valid pair 37 | _, err := tls.X509KeyPair(cert, key) 38 | if err != nil { 39 | return false 40 | } 41 | 42 | // Verify cert is valid for at least 1 year. 43 | pool := x509.NewCertPool() 44 | if !pool.AppendCertsFromPEM(caCert) { 45 | return false 46 | } 47 | block, _ := pem.Decode(cert) 48 | if block == nil { 49 | return false 50 | } 51 | c, err := x509.ParseCertificate(block.Bytes) 52 | if err != nil { 53 | return false 54 | } 55 | ops := x509.VerifyOptions{ 56 | DNSName: dnsName, 57 | Roots: pool, 58 | CurrentTime: time, 59 | } 60 | _, err = c.Verify(ops) 61 | return err == nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/webhook/util/matcher.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "strings" 21 | 22 | v1 "k8s.io/api/admissionregistration/v1" 23 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | "k8s.io/apimachinery/pkg/runtime/schema" 25 | "k8s.io/apiserver/pkg/admission" 26 | ) 27 | 28 | // Matcher determines if the Attr matches the Rule. 29 | type Matcher struct { 30 | Rule v1.RuleWithOperations 31 | Attr admission.Attributes 32 | } 33 | 34 | // Matches returns if the Attr matches the Rule. 35 | func (r *Matcher) Matches() bool { 36 | return r.scope() && 37 | r.operation() && 38 | r.group() && 39 | r.version() && 40 | r.resource() 41 | } 42 | 43 | func exactOrWildcard(items []string, requested string) bool { 44 | for _, item := range items { 45 | if item == "*" { 46 | return true 47 | } 48 | if item == requested { 49 | return true 50 | } 51 | } 52 | 53 | return false 54 | } 55 | 56 | var namespaceResource = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"} 57 | 58 | func (r *Matcher) scope() bool { 59 | if r.Rule.Scope == nil || *r.Rule.Scope == v1.AllScopes { 60 | return true 61 | } 62 | // attr.GetNamespace() is set to the name of the namespace for requests of the namespace object itself. 63 | switch *r.Rule.Scope { 64 | case v1.NamespacedScope: 65 | // first make sure that we are not requesting a namespace object (namespace objects are cluster-scoped) 66 | return r.Attr.GetResource() != namespaceResource && r.Attr.GetNamespace() != metav1.NamespaceNone 67 | case v1.ClusterScope: 68 | // also return true if the request is for a namespace object (namespace objects are cluster-scoped) 69 | return r.Attr.GetResource() == namespaceResource || r.Attr.GetNamespace() == metav1.NamespaceNone 70 | default: 71 | return false 72 | } 73 | } 74 | 75 | func (r *Matcher) group() bool { 76 | return exactOrWildcard(r.Rule.APIGroups, r.Attr.GetResource().Group) 77 | } 78 | 79 | func (r *Matcher) version() bool { 80 | return exactOrWildcard(r.Rule.APIVersions, r.Attr.GetResource().Version) 81 | } 82 | 83 | func (r *Matcher) operation() bool { 84 | attrOp := r.Attr.GetOperation() 85 | for _, op := range r.Rule.Operations { 86 | if op == v1.OperationAll { 87 | return true 88 | } 89 | // The constants are the same such that this is a valid cast (and this 90 | // is tested). 91 | if op == v1.OperationType(attrOp) { 92 | return true 93 | } 94 | } 95 | return false 96 | } 97 | 98 | func splitResource(resSub string) (res, sub string) { 99 | parts := strings.SplitN(resSub, "/", 2) 100 | if len(parts) == 2 { 101 | return parts[0], parts[1] 102 | } 103 | return parts[0], "" 104 | } 105 | 106 | func (r *Matcher) resource() bool { 107 | opRes, opSub := r.Attr.GetResource().Resource, r.Attr.GetSubresource() 108 | for _, res := range r.Rule.Resources { 109 | res, sub := splitResource(res) 110 | resMatch := res == "*" || res == opRes 111 | subMatch := sub == "*" || sub == opSub 112 | if resMatch && subMatch { 113 | return true 114 | } 115 | } 116 | return false 117 | } 118 | -------------------------------------------------------------------------------- /pkg/webhook/util/util.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The rollout Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "os" 21 | "strconv" 22 | 23 | "k8s.io/klog/v2" 24 | ) 25 | 26 | func GetHost() string { 27 | return os.Getenv("WEBHOOK_HOST") 28 | } 29 | 30 | func GetNamespace() string { 31 | if ns := os.Getenv("POD_NAMESPACE"); len(ns) > 0 { 32 | return ns 33 | } 34 | return "kruise-rollout" 35 | } 36 | 37 | func GetSecretName() string { 38 | if name := os.Getenv("SECRET_NAME"); len(name) > 0 { 39 | return name 40 | } 41 | return "kruise-rollout-webhook-certs" 42 | } 43 | 44 | func GetServiceName() string { 45 | if name := os.Getenv("SERVICE_NAME"); len(name) > 0 { 46 | return name 47 | } 48 | return "kruise-rollout-webhook-service" 49 | } 50 | 51 | func GetPort() int { 52 | port := 9876 53 | if p := os.Getenv("WEBHOOK_PORT"); len(p) > 0 { 54 | if p, err := strconv.ParseInt(p, 10, 32); err == nil { 55 | port = int(p) 56 | } else { 57 | klog.Fatalf("failed to convert WEBHOOK_PORT=%v in env: %v", p, err) 58 | } 59 | } 60 | return port 61 | } 62 | 63 | func GetCertDir() string { 64 | if p := os.Getenv("WEBHOOK_CERT_DIR"); len(p) > 0 { 65 | return p 66 | } 67 | return "/tmp/kruise-rollout-webhook-certs" 68 | } 69 | 70 | func GetCertWriter() string { 71 | return os.Getenv("WEBHOOK_CERT_WRITER") 72 | } 73 | -------------------------------------------------------------------------------- /pkg/webhook/util/writer/error.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kruise Authors. 3 | Copyright 2018 The Kubernetes Authors. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | 18 | package writer 19 | 20 | type notFoundError struct { 21 | err error 22 | } 23 | 24 | func (e notFoundError) Error() string { 25 | return e.err.Error() 26 | } 27 | 28 | func isNotFound(err error) bool { 29 | _, ok := err.(notFoundError) 30 | return ok 31 | } 32 | 33 | type alreadyExistError struct { 34 | err error 35 | } 36 | 37 | func (e alreadyExistError) Error() string { 38 | return e.err.Error() 39 | } 40 | 41 | func isAlreadyExists(err error) bool { 42 | _, ok := err.(alreadyExistError) 43 | return ok 44 | } 45 | -------------------------------------------------------------------------------- /pkg/webhook/workload/mutating/webhooks.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kruise Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package mutating 18 | 19 | import ( 20 | "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 21 | ) 22 | 23 | // +kubebuilder:webhook:path=/mutate-apps-kruise-io-v1alpha1-cloneset,mutating=true,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups=apps.kruise.io,resources=clonesets,verbs=update,versions=v1alpha1,name=mcloneset.kb.io 24 | // +kubebuilder:webhook:path=/mutate-apps-kruise-io-v1alpha1-daemonset,mutating=true,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups=apps.kruise.io,resources=daemonsets,verbs=update,versions=v1alpha1,name=mdaemonset.kb.io 25 | // +kubebuilder:webhook:path=/mutate-apps-v1-deployment,mutating=true,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups=apps,resources=deployments,verbs=update,versions=v1,name=mdeployment.kb.io 26 | // +kubebuilder:webhook:path=/mutate-unified-workload,mutating=true,failurePolicy=fail,sideEffects=None,admissionReviewVersions=v1;v1beta1,groups=*,resources=*,verbs=create;update,versions=*,name=munifiedworload.kb.io 27 | 28 | var ( 29 | // HandlerMap contains admission webhook handlers 30 | HandlerMap = map[string]admission.Handler{ 31 | "mutate-apps-kruise-io-v1alpha1-cloneset": &WorkloadHandler{}, 32 | "mutate-apps-v1-deployment": &WorkloadHandler{}, 33 | "mutate-apps-kruise-io-v1alpha1-daemonset": &WorkloadHandler{}, 34 | "mutate-unified-workload": &UnifiedWorkloadHandler{}, 35 | } 36 | ) 37 | -------------------------------------------------------------------------------- /rollouts.rollouts.kruise.io-rollouts-demo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | annotations: 5 | rollouts.kruise.io/hash: d8c8v2w74286d77c4925wdw58v65z8725z7z4cw4f6cc485cvv62vx9cdwfv7b76 6 | rollouts.kruise.io/rolling-style: canary 7 | creationTimestamp: "2023-09-25T13:49:00Z" 8 | deletionGracePeriodSeconds: 0 9 | deletionTimestamp: "2023-09-25T13:52:34Z" 10 | generation: 2 11 | name: rollouts-demo 12 | namespace: rollout-59e5b9cbeb2ed91f 13 | resourceVersion: "2699" 14 | uid: e861a61c-07f6-4064-8486-154bc6d86e29 15 | spec: 16 | disabled: false 17 | objectRef: 18 | workloadRef: 19 | apiVersion: apps/v1 20 | kind: Deployment 21 | name: echoserver 22 | strategy: 23 | canary: 24 | steps: 25 | - matches: 26 | - headers: 27 | - name: user-agent 28 | type: Exact 29 | value: pc 30 | pause: {} 31 | replicas: 1 32 | - pause: {} 33 | weight: 50 34 | trafficRoutings: 35 | - customNetworkRefs: 36 | - apiVersion: networking.istio.io/v1alpha3 37 | kind: VirtualService 38 | name: vs-demo 39 | service: echoserver 40 | status: 41 | canaryStatus: 42 | canaryReadyReplicas: 0 43 | canaryReplicas: 0 44 | canaryRevision: 684554996d 45 | currentStepIndex: 2 46 | currentStepState: Completed 47 | observedWorkloadGeneration: 1 48 | podTemplateHash: "" 49 | rolloutHash: d8c8v2w74286d77c4925wdw58v65z8725z7z4cw4f6cc485cvv62vx9cdwfv7b76 50 | stableRevision: 567694c97d 51 | conditions: 52 | - lastTransitionTime: "2023-09-25T13:50:00Z" 53 | lastUpdateTime: "2023-09-25T13:50:00Z" 54 | message: Rollout is in Progressing 55 | reason: Initializing 56 | status: "True" 57 | type: Progressing 58 | message: workload deployment is completed 59 | observedGeneration: 1 60 | phase: Progressing 61 | -------------------------------------------------------------------------------- /scripts/deploy_kind.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -z "$IMG" ]; then 4 | echo "no found IMG env" 5 | exit 1 6 | fi 7 | 8 | set -e 9 | 10 | make kustomize 11 | KUSTOMIZE=$(pwd)/bin/kustomize 12 | (cd config/manager && "${KUSTOMIZE}" edit set image controller="${IMG}") 13 | "${KUSTOMIZE}" build config/default | sed -e 's/imagePullPolicy: Always/imagePullPolicy: IfNotPresent/g' > /tmp/rollout-kustomization.yaml 14 | echo -e "resources:\n- manager.yaml" > config/manager/kustomization.yaml 15 | kubectl apply -f /tmp/rollout-kustomization.yaml 16 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/cloneset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kruise.io/v1alpha1 2 | kind: CloneSet 3 | metadata: 4 | labels: 5 | app: busybox 6 | name: sample 7 | spec: 8 | replicas: 5 9 | updateStrategy: 10 | maxUnavailable: 0 11 | maxSurge: 1 12 | selector: 13 | matchLabels: 14 | app: busybox 15 | template: 16 | metadata: 17 | labels: 18 | app: busybox 19 | spec: 20 | containers: 21 | - name: busybox 22 | image: busybox:1.32 23 | imagePullPolicy: IfNotPresent 24 | command: ["bin/sh", "-c", "sleep 10000000"] 25 | resources: 26 | limits: 27 | memory: "10Mi" 28 | cpu: "10m" 29 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/cloneset_number_100.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: release-cloneset-100 5 | spec: 6 | targetReference: 7 | type: workloadRef 8 | workloadRef: 9 | apiVersion: apps.kruise.io/v1alpha1 10 | kind: CloneSet 11 | name: sample 12 | releasePlan: 13 | batches: 14 | - canaryReplicas: 1 15 | pauseSeconds: 20 16 | - canaryReplicas: 3 17 | pauseSeconds: 20 18 | - canaryReplicas: 6 19 | pauseSeconds: 20 20 | - canaryReplicas: 10 21 | pauseSeconds: 20 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/cloneset_percentage_100.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: release-cloneset-100 5 | spec: 6 | targetReference: 7 | type: workloadRef 8 | workloadRef: 9 | apiVersion: apps.kruise.io/v1alpha1 10 | kind: CloneSet 11 | name: sample 12 | releasePlan: 13 | batches: 14 | - canaryReplicas: 20% 15 | pauseSeconds: 20 16 | - canaryReplicas: 40% 17 | pauseSeconds: 20 18 | - canaryReplicas: 60% 19 | pauseSeconds: 20 20 | - canaryReplicas: 100% 21 | pauseSeconds: 10 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/cloneset_percentage_50.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: release-cloneset-50 5 | spec: 6 | targetReference: 7 | type: workloadRef 8 | workloadRef: 9 | apiVersion: apps.kruise.io/v1alpha1 10 | kind: CloneSet 11 | name: sample 12 | releasePlan: 13 | batches: 14 | - canaryReplicas: 20% 15 | pauseSeconds: 30 16 | - canaryReplicas: 40% 17 | pauseSeconds: 60 18 | - canaryReplicas: 50% 19 | pauseSeconds: 30 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sample 5 | labels: 6 | app: busybox 7 | spec: 8 | replicas: 5 9 | strategy: 10 | type: RollingUpdate 11 | rollingUpdate: 12 | maxUnavailable: 0 13 | maxSurge: 1 14 | selector: 15 | matchLabels: 16 | app: busybox 17 | template: 18 | metadata: 19 | labels: 20 | app: busybox 21 | spec: 22 | containers: 23 | - name: busybox 24 | image: busybox:1.32 25 | imagePullPolicy: IfNotPresent 26 | command: ["/bin/sh", "-c", "sleep 10000"] 27 | resources: 28 | limits: 29 | memory: "10Mi" 30 | cpu: "10m" 31 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/deployment_number_100.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: release-deployment-number-100 5 | spec: 6 | targetReference: 7 | workloadRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: sample 11 | releasePlan: 12 | batches: 13 | - canaryReplicas: 1 14 | pauseSeconds: 20 15 | - canaryReplicas: 2 16 | pauseSeconds: 20 17 | - canaryReplicas: 3 18 | pauseSeconds: 20 19 | - canaryReplicas: 5 20 | pauseSeconds: 10 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/deployment_percentage_100.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: release-deployment-percentage-100 5 | spec: 6 | targetReference: 7 | type: workloadRef 8 | workloadRef: 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | name: sample 12 | releasePlan: 13 | batches: 14 | - canaryReplicas: 20% 15 | pauseSeconds: 20 16 | - canaryReplicas: 40% 17 | pauseSeconds: 20 18 | - canaryReplicas: 60% 19 | pauseSeconds: 20 20 | - canaryReplicas: 100% 21 | pauseSeconds: 10 22 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/deployment_percentage_50.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: BatchRelease 3 | metadata: 4 | name: release-deployment-percentage-50 5 | spec: 6 | targetReference: 7 | type: workloadRef 8 | workloadRef: 9 | apiVersion: apps/v1 10 | kind: Deployment 11 | name: sample 12 | releasePlan: 13 | batches: 14 | - canaryReplicas: 20% 15 | pauseSeconds: 30 16 | - canaryReplicas: 40% 17 | pauseSeconds: 60 18 | - canaryReplicas: 50% 19 | pauseSeconds: 30 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/e2e/test_data/batchrelease/sts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: sts-demo 5 | spec: 6 | objectRef: 7 | workloadRef: 8 | apiVersion: apps/v1 9 | kind: StatefulSet 10 | name: sts-demo 11 | strategy: 12 | canary: 13 | steps: 14 | - weight: 20 15 | pause: {} 16 | - weight: 40 17 | pause: {duration: 10} 18 | - weight: 60 19 | pause: {duration: 10} 20 | - weight: 80 21 | pause: {duration: 10} 22 | - weight: 100 23 | --- 24 | apiVersion: apps/v1 25 | kind: StatefulSet 26 | metadata: 27 | name: sts-demo 28 | spec: 29 | selector: 30 | matchLabels: 31 | app: sts-demo 32 | serviceName: sts-demo 33 | replicas: 5 34 | template: 35 | metadata: 36 | labels: 37 | app: sts-demo 38 | spec: 39 | containers: 40 | - name: myapp 41 | image: busybox:1.33 42 | imagePullPolicy: IfNotPresent 43 | command: ["/bin/sh", "-c", "sleep 100000000"] 44 | ports: 45 | - containerPort: 80 46 | name: web 47 | volumeMounts: 48 | - name: www 49 | mountPath: /usr/share/nginx/html 50 | volumeClaimTemplates: 51 | - metadata: 52 | name: www 53 | spec: 54 | accessModes: [ "ReadWriteOnce" ] 55 | resources: 56 | requests: 57 | storage: 20Mi 58 | --- 59 | apiVersion: v1 60 | kind: Service 61 | metadata: 62 | name: sts-demo 63 | labels: 64 | app: sts-demo 65 | spec: 66 | ports: 67 | - port: 80 68 | name: web 69 | clusterIP: None 70 | selector: 71 | app: myapp -------------------------------------------------------------------------------- /test/e2e/test_data/customNetworkProvider/destinationrule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: DestinationRule 3 | metadata: 4 | name: ds-demo 5 | spec: 6 | host: svc-demo 7 | trafficPolicy: 8 | loadBalancer: 9 | simple: ROUND_ROBIN 10 | subsets: 11 | - labels: 12 | version: base 13 | name: echoserver -------------------------------------------------------------------------------- /test/e2e/test_data/customNetworkProvider/rollout_with_multi_trafficrouting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1beta1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | disabled: false 7 | workloadRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: echoserver 11 | strategy: 12 | canary: 13 | enableExtraWorkloadForCanary: true 14 | steps: 15 | - replicas: 1 16 | matches: 17 | - headers: 18 | - type: Exact 19 | name: user-agent 20 | value: pc 21 | - queryParams: 22 | - type: Exact 23 | name: user-agent 24 | value: pc 25 | - path: 26 | value: /pc 27 | - replicas: "50%" 28 | traffic: "50%" 29 | trafficRoutings: 30 | - service: echoserver 31 | ingress: 32 | classType: nginx 33 | name: echoserver 34 | customNetworkRefs: 35 | - apiVersion: networking.istio.io/v1alpha3 36 | kind: VirtualService 37 | name: vs-demo 38 | -------------------------------------------------------------------------------- /test/e2e/test_data/customNetworkProvider/rollout_with_trafficrouting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1beta1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | disabled: false 7 | workloadRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: echoserver 11 | strategy: 12 | canary: 13 | enableExtraWorkloadForCanary: true 14 | steps: 15 | - replicas: 1 16 | matches: 17 | - headers: 18 | - type: Exact 19 | name: user-agent 20 | value: pc 21 | - queryParams: 22 | - type: Exact 23 | name: user-agent 24 | value: pc 25 | - path: 26 | value: /pc 27 | - replicas: "50%" 28 | traffic: "50%" 29 | trafficRoutings: 30 | - service: echoserver 31 | customNetworkRefs: 32 | - apiVersion: networking.istio.io/v1alpha3 33 | kind: VirtualService 34 | name: vs-demo -------------------------------------------------------------------------------- /test/e2e/test_data/customNetworkProvider/rollout_without_trafficrouting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | annotations: 6 | rollouts.kruise.io/rolling-style: canary 7 | rollouts.kruise.io/trafficrouting: tr-demo 8 | spec: 9 | disabled: false 10 | objectRef: 11 | workloadRef: 12 | apiVersion: apps/v1 13 | kind: Deployment 14 | name: echoserver 15 | strategy: 16 | canary: 17 | steps: 18 | - replicas: 1 -------------------------------------------------------------------------------- /test/e2e/test_data/customNetworkProvider/trafficrouting.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: TrafficRouting 3 | metadata: 4 | name: tr-demo 5 | spec: 6 | strategy: 7 | matches: 8 | - headers: 9 | - type: Exact 10 | name: user-agent 11 | value: pc 12 | objectRef: 13 | - service: echoserver 14 | customNetworkRefs: 15 | - apiVersion: networking.istio.io/v1alpha3 16 | kind: VirtualService 17 | name: vs-demo 18 | - apiVersion: networking.istio.io/v1alpha3 19 | kind: DestinationRule 20 | name: ds-demo -------------------------------------------------------------------------------- /test/e2e/test_data/customNetworkProvider/virtualservice_without_destinationrule.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: vs-demo 5 | spec: 6 | hosts: 7 | - "*" 8 | gateways: 9 | - nginx-gateway 10 | http: 11 | - route: 12 | - destination: 13 | host: echoserver -------------------------------------------------------------------------------- /test/e2e/test_data/deployment/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: sample 5 | labels: 6 | app: busybox 7 | Annotations: 8 | batchrelease.rollouts.kruise.io/control-info: '{"apiVersion":"rollouts.kruise.io/v1alpha1","kind":"BatchRelease","name":"rollouts-demo","uid":"45891961-8c29-4ea9-8e61-fd5a1fd19ffa","controller":true,"blockOwnerDeletion":true}' 9 | rollouts.kruise.io/deployment-strategy: '{"rollingUpdate":{"maxUnavailable":"25%","maxSurge":"25%"}}' 10 | spec: 11 | paused: true 12 | replicas: 5 13 | strategy: 14 | type: Recreate 15 | selector: 16 | matchLabels: 17 | app: busybox 18 | template: 19 | metadata: 20 | labels: 21 | app: busybox 22 | spec: 23 | containers: 24 | - name: busybox 25 | image: busybox:1.32 26 | imagePullPolicy: IfNotPresent 27 | command: ["/bin/sh", "-c", "sleep 10000"] 28 | env: 29 | - name: VERSION 30 | value: version1 31 | resources: 32 | limits: 33 | memory: "10Mi" 34 | cpu: "10m" -------------------------------------------------------------------------------- /test/e2e/test_data/gateway/httproute-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: gateway.networking.k8s.io/v1alpha2 2 | kind: HTTPRoute 3 | metadata: 4 | name: echoserver 5 | spec: 6 | hostnames: 7 | - test.app.domain 8 | rules: 9 | - backendRefs: 10 | - group: "" 11 | name: echoserver 12 | port: 80 13 | # - group: "" 14 | # kind: Service 15 | # name: echoserver-canary 16 | # port: 80 17 | # weight: 40 18 | matches: 19 | - path: 20 | type: PathPrefix 21 | value: /apis/echo 22 | -------------------------------------------------------------------------------- /test/e2e/test_data/gateway/rollout-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | objectRef: 7 | workloadRef: 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | name: echoserver 11 | strategy: 12 | canary: 13 | steps: 14 | - weight: 20 15 | pause: {} 16 | - weight: 40 17 | pause: {duration: 10} 18 | - weight: 60 19 | pause: {duration: 10} 20 | - weight: 80 21 | pause: {duration: 10} 22 | - weight: 100 23 | trafficRoutings: 24 | - service: echoserver 25 | gateway: 26 | httpRouteName: echoserver 27 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/advanced_statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kruise.io/v1beta1 2 | kind: StatefulSet 3 | metadata: 4 | name: echoserver 5 | labels: 6 | apps: echoserver 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: echoserver 11 | serviceName: headless-service 12 | replicas: 5 13 | template: 14 | metadata: 15 | labels: 16 | app: echoserver 17 | spec: 18 | containers: 19 | - name: echoserver 20 | image: cilium/echoserver:latest 21 | imagePullPolicy: IfNotPresent 22 | ports: 23 | - containerPort: 8080 24 | env: 25 | - name: PORT 26 | value: '8080' 27 | - name: POD_NAME 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.name 31 | - name: POD_NAMESPACE 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.namespace 35 | - name: POD_IP 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: status.podIP 39 | - name: NODE_NAME 40 | value: version1 41 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/cloneset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kruise.io/v1alpha1 2 | kind: CloneSet 3 | metadata: 4 | name: echoserver 5 | labels: 6 | app: echoserver 7 | annotations: 8 | rollouts.kruise.io/e2e-test-sample: "true" 9 | spec: 10 | replicas: 5 11 | updateStrategy: 12 | maxUnavailable: 0 13 | maxSurge: 1 14 | selector: 15 | matchLabels: 16 | app: echoserver 17 | template: 18 | metadata: 19 | labels: 20 | app: echoserver 21 | spec: 22 | containers: 23 | - name: echoserver 24 | image: cilium/echoserver:latest 25 | imagePullPolicy: IfNotPresent 26 | ports: 27 | - containerPort: 8080 28 | env: 29 | - name: PORT 30 | value: '8080' 31 | - name: POD_NAME 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.name 35 | - name: POD_NAMESPACE 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: metadata.namespace 39 | - name: POD_IP 40 | valueFrom: 41 | fieldRef: 42 | fieldPath: status.podIP 43 | - name: NODE_NAME 44 | value: version1 45 | 46 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/daemonset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kruise.io/v1alpha1 2 | kind: DaemonSet 3 | metadata: 4 | name: fluentd-elasticsearch 5 | namespace: kube-system 6 | labels: 7 | k8s-app: fluentd-logging 8 | spec: 9 | selector: 10 | matchLabels: 11 | name: fluentd-elasticsearch 12 | template: 13 | metadata: 14 | labels: 15 | name: fluentd-elasticsearch 16 | spec: 17 | tolerations: 18 | # these tolerations are to have the daemonset runnable on control plane nodes 19 | # remove them if your control plane nodes should not run pods 20 | - key: node-role.kubernetes.io/control-plane 21 | operator: Exists 22 | effect: NoSchedule 23 | - key: node-role.kubernetes.io/master 24 | operator: Exists 25 | effect: NoSchedule 26 | containers: 27 | - name: fluentd-elasticsearch 28 | image: quay.io/fluentd_elasticsearch/fluentd:v2.5.2 29 | resources: 30 | limits: 31 | memory: 200Mi 32 | requests: 33 | cpu: 100m 34 | memory: 200Mi 35 | volumeMounts: 36 | - name: varlog 37 | mountPath: /var/log 38 | terminationGracePeriodSeconds: 30 39 | volumes: 40 | - name: varlog 41 | hostPath: 42 | path: /var/log 43 | # updateStrategy: 44 | # type: RollingUpdate 45 | # rollingUpdate: 46 | # rollingUpdateType: Standard 47 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: echoserver 5 | labels: 6 | app: echoserver 7 | spec: 8 | replicas: 5 9 | strategy: 10 | type: RollingUpdate 11 | rollingUpdate: 12 | maxUnavailable: 0 13 | maxSurge: 1 14 | selector: 15 | matchLabels: 16 | app: echoserver 17 | template: 18 | metadata: 19 | labels: 20 | app: echoserver 21 | spec: 22 | containers: 23 | - name: echoserver 24 | image: cilium/echoserver:latest 25 | # For ARM-based env 26 | # image: jmalloc/echo-server:latest 27 | imagePullPolicy: IfNotPresent 28 | ports: 29 | - containerPort: 8080 30 | env: 31 | - name: PORT 32 | value: '8080' 33 | - name: POD_NAME 34 | valueFrom: 35 | fieldRef: 36 | fieldPath: metadata.name 37 | - name: POD_NAMESPACE 38 | valueFrom: 39 | fieldRef: 40 | fieldPath: metadata.namespace 41 | - name: POD_IP 42 | valueFrom: 43 | fieldRef: 44 | fieldPath: status.podIP 45 | - name: NODE_NAME 46 | value: version1 47 | 48 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/deployment_disabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: workload-demo 5 | namespace: default 6 | spec: 7 | replicas: 10 8 | selector: 9 | matchLabels: 10 | app: demo 11 | template: 12 | metadata: 13 | labels: 14 | app: demo 15 | spec: 16 | containers: 17 | - name: busybox 18 | image: busybox:latest 19 | imagePullPolicy: IfNotPresent 20 | command: ["/bin/sh", "-c", "sleep 100d"] 21 | env: 22 | - name: VERSION 23 | value: "version-1" -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/headless_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: headless-service 5 | labels: 6 | app: echoserver 7 | spec: 8 | ports: 9 | - port: 8080 10 | name: web 11 | clusterIP: None 12 | selector: 13 | app: echoserver -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/hpa_v1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: hpa-dp 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: echoserver 10 | minReplicas: 2 11 | maxReplicas: 6 12 | targetCPUUtilizationPercentage: 1 -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/hpa_v2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: hpa-dp 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: echoserver 10 | behavior: 11 | scaleDown: 12 | stabilizationWindowSeconds: 10 13 | # selectPolicy: Disabled 14 | # scaleUp: 15 | # selectPolicy: Disabled 16 | minReplicas: 2 17 | maxReplicas: 6 18 | metrics: 19 | - type: Resource 20 | resource: 21 | name: cpu 22 | target: 23 | type: AverageValue 24 | averageValue: '1m' 25 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/native_statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: echoserver 5 | labels: 6 | apps: echoserver 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: echoserver 11 | serviceName: headless-service 12 | replicas: 5 13 | template: 14 | metadata: 15 | labels: 16 | app: echoserver 17 | spec: 18 | containers: 19 | - name: echoserver 20 | image: cilium/echoserver:latest 21 | imagePullPolicy: IfNotPresent 22 | ports: 23 | - containerPort: 8080 24 | env: 25 | - name: PORT 26 | value: '8080' 27 | - name: POD_NAME 28 | valueFrom: 29 | fieldRef: 30 | fieldPath: metadata.name 31 | - name: POD_NAMESPACE 32 | valueFrom: 33 | fieldRef: 34 | fieldPath: metadata.namespace 35 | - name: POD_IP 36 | valueFrom: 37 | fieldRef: 38 | fieldPath: status.podIP 39 | - name: NODE_NAME 40 | value: version1 41 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/nginx_ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: echoserver 5 | annotations: 6 | kubernetes.io/ingress.class: nginx 7 | spec: 8 | rules: 9 | - host: echoserver.example.com 10 | http: 11 | paths: 12 | - backend: 13 | service: 14 | name: echoserver 15 | port: 16 | number: 80 17 | path: /apis/echo 18 | pathType: Exact 19 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: kruise-rollout-configuration 5 | namespace: kruise-rollout 6 | data: 7 | "lua.traffic.routing.ingress.aliyun-alb": | 8 | function split(input, delimiter) 9 | local arr = {} 10 | string.gsub(input, '[^' .. delimiter ..']+', function(w) table.insert(arr, w) end) 11 | return arr 12 | end 13 | annotations = {} 14 | if ( obj.annotations ) 15 | then 16 | annotations = obj.annotations 17 | end 18 | 19 | annotations["alb.ingress.kubernetes.io/canary"] = "true" 20 | annotations["alb.ingress.kubernetes.io/canary-by-cookie"] = nil 21 | annotations["alb.ingress.kubernetes.io/canary-by-header"] = nil 22 | annotations["alb.ingress.kubernetes.io/canary-by-header-pattern"] = nil 23 | annotations["alb.ingress.kubernetes.io/canary-by-header-value"] = nil 24 | annotations["alb.ingress.kubernetes.io/canary-weight"] = nil 25 | conditionKey = string.format("alb.ingress.kubernetes.io/conditions.%s", obj.canaryService) 26 | annotations[conditionKey] = nil 27 | if ( obj.weight ~= "-1" ) 28 | then 29 | annotations["alb.ingress.kubernetes.io/canary-weight"] = obj.weight 30 | end 31 | if ( not obj.matches ) 32 | then 33 | return annotations 34 | end 35 | conditions = {} 36 | match = obj.matches[1] 37 | for _,header in ipairs(match.headers) do 38 | local condition = {} 39 | if ( header.name == "Cookie" ) 40 | then 41 | condition.type = "Cookie" 42 | condition.cookieConfig = {} 43 | cookies = split(header.value, ";") 44 | values = {} 45 | for _,cookieStr in ipairs(cookies) do 46 | cookie = split(cookieStr, "=") 47 | value = {} 48 | value.key = cookie[1] 49 | value.value = cookie[2] 50 | table.insert(values, value) 51 | end 52 | condition.cookieConfig.values = values 53 | elseif ( header.name == "SourceIp" ) 54 | then 55 | condition.type = "SourceIp" 56 | condition.sourceIpConfig = {} 57 | ips = split(header.value, ";") 58 | values = {} 59 | for _,ip in ipairs(ips) do 60 | table.insert(values, ip) 61 | end 62 | condition.sourceIpConfig.values = values 63 | else 64 | condition.type = "Header" 65 | condition.headerConfig = {} 66 | condition.headerConfig.key = header.name 67 | vals = split(header.value, ";") 68 | values = {} 69 | for _,val in ipairs(vals) do 70 | table.insert(values, val) 71 | end 72 | condition.headerConfig.values = values 73 | end 74 | table.insert(conditions, condition) 75 | end 76 | annotations[conditionKey] = json.encode(conditions) 77 | return annotations 78 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_canary_base.yaml: -------------------------------------------------------------------------------- 1 | # we recommend that new test cases or modifications to existing test cases should 2 | # use v1beta1 Rollout, eg. use rollout_v1beta1_canary_base.yaml 3 | apiVersion: rollouts.kruise.io/v1alpha1 4 | kind: Rollout 5 | metadata: 6 | name: rollouts-demo 7 | spec: 8 | objectRef: 9 | workloadRef: 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | name: echoserver 13 | strategy: 14 | canary: 15 | steps: 16 | - weight: 20 17 | pause: {} 18 | - weight: 40 19 | pause: {duration: 10} 20 | - weight: 60 21 | pause: {duration: 10} 22 | - weight: 80 23 | pause: {duration: 10} 24 | - weight: 100 25 | pause: {duration: 0} 26 | trafficRoutings: 27 | - service: echoserver 28 | ingress: 29 | classType: nginx 30 | name: echoserver 31 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_canary_daemonset_base.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-test 5 | # The rollout resource needs to be in the same namespace as the corresponding workload 6 | namespace: kube-system 7 | # This annotation can help us upgrade the Deployment using partition, just like StatefulSet/CloneSet. 8 | annotations: 9 | rollouts.kruise.io/rolling-style: partition 10 | spec: 11 | objectRef: 12 | # rollout of published workloads, currently only supports Deployment, CloneSet, StatefulSet, Advanced StatefulSet 13 | workloadRef: 14 | apiVersion: apps.kruise.io/v1alpha1 15 | kind: DaemonSet 16 | name: fluentd-elasticsearch 17 | strategy: 18 | canary: 19 | steps: 20 | - replicas: 1 21 | - replicas: 100% 22 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_canary_daemonset_interrupt.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-test 5 | # The rollout resource needs to be in the same namespace as the corresponding workload 6 | namespace: kube-system 7 | # This annotation can help us upgrade the Deployment using partition, just like StatefulSet/CloneSet. 8 | annotations: 9 | rollouts.kruise.io/rolling-style: partition 10 | spec: 11 | objectRef: 12 | # rollout of published workloads, currently only supports Deployment, CloneSet, StatefulSet, Advanced StatefulSet 13 | workloadRef: 14 | apiVersion: apps.kruise.io/v1alpha1 15 | kind: DaemonSet 16 | name: fluentd-elasticsearch 17 | strategy: 18 | canary: 19 | steps: 20 | - replicas: 1 21 | - replicas: 2 22 | - replicas: 100% 23 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_disabled.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1alpha1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | namespace: default 6 | annotations: 7 | rollouts.kruise.io/rolling-style: partition 8 | spec: 9 | disabled: false 10 | objectRef: 11 | workloadRef: 12 | apiVersion: apps/v1 13 | kind: Deployment 14 | name: workload-demo 15 | strategy: 16 | canary: 17 | steps: 18 | - replicas: 2 19 | - replicas: 50% 20 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_v1beta1_bluegreen_base.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1beta1 # we use v1beta1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | workloadRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: echoserver 10 | strategy: 11 | blueGreen: 12 | steps: 13 | - replicas: 50% 14 | traffic: 0% 15 | pause: {} 16 | - replicas: 100% 17 | traffic: 0% 18 | - replicas: 100% 19 | traffic: 50% 20 | - replicas: 100% 21 | traffic: 100% 22 | trafficRoutings: 23 | - service: echoserver 24 | ingress: 25 | classType: nginx 26 | name: echoserver 27 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_v1beta1_bluegreen_cloneset_base.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1beta1 # we use v1beta1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | workloadRef: 7 | apiVersion: apps.kruise.io/v1alpha1 8 | kind: CloneSet 9 | name: echoserver 10 | strategy: 11 | blueGreen: 12 | steps: 13 | - replicas: 100% 14 | traffic: 0% 15 | pause: {} 16 | - replicas: 100% 17 | traffic: 50% 18 | - replicas: 100% 19 | traffic: 100% 20 | trafficRoutings: 21 | - service: echoserver 22 | ingress: 23 | classType: nginx 24 | name: echoserver -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_v1beta1_canary_base.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1beta1 # we use v1beta1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | workloadRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: echoserver 10 | strategy: 11 | canary: 12 | enableExtraWorkloadForCanary: true 13 | steps: 14 | - traffic: 20% 15 | replicas: 20% 16 | pause: {} 17 | - traffic: 40% 18 | replicas: 40% 19 | pause: {duration: 10} 20 | - traffic: 60% 21 | replicas: 60% 22 | pause: {duration: 10} 23 | - traffic: 80% 24 | replicas: 80% 25 | pause: {duration: 10} 26 | - traffic: 100% 27 | replicas: 100% 28 | pause: {duration: 0} 29 | trafficRoutings: 30 | - service: echoserver 31 | ingress: 32 | classType: nginx 33 | name: echoserver 34 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/rollout_v1beta1_partition_base.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rollouts.kruise.io/v1beta1 # we use v1beta1 2 | kind: Rollout 3 | metadata: 4 | name: rollouts-demo 5 | spec: 6 | workloadRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: echoserver 10 | strategy: 11 | canary: 12 | enableExtraWorkloadForCanary: false 13 | steps: 14 | - traffic: 20% 15 | replicas: 20% 16 | pause: {} 17 | - replicas: 40% 18 | pause: {duration: 10} 19 | - replicas: 60% 20 | pause: {duration: 10} 21 | - replicas: 80% 22 | pause: {duration: 10} 23 | - replicas: 100% 24 | pause: {duration: 0} 25 | trafficRoutings: 26 | - service: echoserver 27 | ingress: 28 | classType: nginx 29 | name: echoserver 30 | -------------------------------------------------------------------------------- /test/e2e/test_data/rollout/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: echoserver 5 | labels: 6 | app: echoserver 7 | spec: 8 | ports: 9 | - port: 80 10 | targetPort: 8080 11 | protocol: TCP 12 | name: http 13 | selector: 14 | app: echoserver 15 | -------------------------------------------------------------------------------- /test/kind-conf.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | - role: worker 6 | - role: worker 7 | featureGates: 8 | EphemeralContainers: true 9 | --------------------------------------------------------------------------------