Repository: kubernetes-sigs/cluster-api-provider-aws
Pull request #3741 (2022-09-23 10:02:47)

Suspend and Resume ASG Processes
<!-- Thanks for this PR! If this is your first PR please read the [contributing guide](../CONTRIBUTING.md) -->
<!-- If this PR is still work-in-progress and is being open for visibility please prefix the title with `WIP:` -->

**What type of PR is this?**

/kind feature


**What this PR does / why we need it**:

<!-- Enter a description of the change and why this change is needed -->

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*:
Fixes https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues/3712

Suspend or Resume ASG processes.

**Special notes for your reviewer**:

**Checklist**:
<!-- Put an "X" character inside the brackets of each completed task. Some may be optional depending on the PR in which case these can be deleted -->

- [x] squashed commits
- [x] includes documentation
- [x] adds unit tests
- [ ] adds or updates e2e tests


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
**Once this PR has been reviewed and has the lgtm label**, please assign enxebre for approval by writing `/assign @enxebre` in a comment. For more information see:[The Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fcluster-api-provider-aws).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/cluster-api-provider-aws/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["enxebre"]} -->
k8s-ci-robot: @Skarlso: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-build | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-build/1573251526334353408) | true | `/test pull-cluster-api-provider-aws-build`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-build | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-build/1573251526334353408) | true | `/test pull-cluster-api-provider-aws-build`
pull-cluster-api-provider-aws-apidiff-main | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573251526296604672) | false | `/test pull-cluster-api-provider-aws-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Skarlso: Ops, forgot to add the conversion for to autoscaling group. 🤦 
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-build | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-build/1573251526334353408) | true | `/test pull-cluster-api-provider-aws-build`
pull-cluster-api-provider-aws-apidiff-main | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573251526296604672) | false | `/test pull-cluster-api-provider-aws-apidiff-main`
pull-cluster-api-provider-aws-test | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573251526263050240) | true | `/test pull-cluster-api-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
Skarlso: Oh, this isn't easy at all... AutoScalingGroup is not a kube object, so unmarshalling it doesn't work.
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-build | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-build/1573251526334353408) | true | `/test pull-cluster-api-provider-aws-build`
pull-cluster-api-provider-aws-test | 4dd3662bbf51c5987148dc3e99491f3d04d17cf0 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573251526263050240) | true | `/test pull-cluster-api-provider-aws-test`
pull-cluster-api-provider-aws-apidiff-main | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573259929047797760) | false | `/test pull-cluster-api-provider-aws-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-apidiff-main | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573259929047797760) | false | `/test pull-cluster-api-provider-aws-apidiff-main`
pull-cluster-api-provider-aws-test | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573259929010049024) | true | `/test pull-cluster-api-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-apidiff-main | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573259929047797760) | false | `/test pull-cluster-api-provider-aws-apidiff-main`
pull-cluster-api-provider-aws-test | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573259929010049024) | true | `/test pull-cluster-api-provider-aws-test`
pull-cluster-api-provider-aws-verify | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-verify/1573259929131683840) | true | `/test pull-cluster-api-provider-aws-verify`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-test | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573259929010049024) | true | `/test pull-cluster-api-provider-aws-test`
pull-cluster-api-provider-aws-verify | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-verify/1573259929131683840) | true | `/test pull-cluster-api-provider-aws-verify`
pull-cluster-api-provider-aws-apidiff-main | 1b40f27101f4127e9302fb2757c7577803ec568d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573263602507321344) | false | `/test pull-cluster-api-provider-aws-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-verify | 64caf16f3a377af8ccdb4a2109a86ad9d5bed0f2 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-verify/1573259929131683840) | true | `/test pull-cluster-api-provider-aws-verify`
pull-cluster-api-provider-aws-apidiff-main | 1b40f27101f4127e9302fb2757c7577803ec568d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573263602507321344) | false | `/test pull-cluster-api-provider-aws-apidiff-main`
pull-cluster-api-provider-aws-test | 1b40f27101f4127e9302fb2757c7577803ec568d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573263602473766912) | true | `/test pull-cluster-api-provider-aws-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
k8s-ci-robot: @Skarlso: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
pull-cluster-api-provider-aws-test | 1b40f27101f4127e9302fb2757c7577803ec568d | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-test/1573263602473766912) | true | `/test pull-cluster-api-provider-aws-test`
pull-cluster-api-provider-aws-apidiff-main | 79682e5a45b6f7b50cd8b3956d6da1632be9f420 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_cluster-api-provider-aws/3741/pull-cluster-api-provider-aws-apidiff-main/1573268757990608896) | false | `/test pull-cluster-api-provider-aws-apidiff-main`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=cluster-api-provider-aws&pr=3741). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3ASkarlso). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/cluster-api-provider-aws/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
index e205254fe..44ee9d150 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinepools.yaml
@@ -846,6 +846,37 @@ spec:
                       type: string
                   type: object
                 type: array
+              suspendProcesses:
+                description: SuspendProcesses defines a list of processes to suspend
+                  for the given ASG. This is constantly reconciled. If a process is
+                  removed from this list it will automatically be resumed.
+                properties:
+                  all:
+                    type: boolean
+                  processes:
+                    description: Processes defines the processes which can be enabled
+                      or disabled individually.
+                    properties:
+                      addToLoadBalancer:
+                        type: boolean
+                      alarmNotification:
+                        type: boolean
+                      azRebalance:
+                        type: boolean
+                      healthCheck:
+                        type: boolean
+                      instanceRefresh:
+                        type: boolean
+                      launch:
+                        type: boolean
+                      replaceUnhealthy:
+                        type: boolean
+                      scheduledActions:
+                        type: boolean
+                      terminate:
+                        type: boolean
+                    type: object
+                type: object
             required:
             - awsLaunchTemplate
             - maxSize
diff --git a/docs/book/src/topics/suspend-asg-processes.md b/docs/book/src/topics/suspend-asg-processes.md
new file mode 100644
index 000000000..d60111e31
--- /dev/null
+++ b/docs/book/src/topics/suspend-asg-processes.md
@@ -0,0 +1,105 @@
+# Suspend ASG Processes
+
+- **Feature status:** Experimental
+- **Feature gate:** MachinePool=true
+
+MachinePool allows users to manage many machines as a single entity. Infrastructure providers implement a separate CRD that handles infrastructure side of the feature.
+
+## Suspend Processes
+
+It's possible to suspend certain processes for ASG. The list of processes can be found [here](https://docs.aws.amazon.com/autoscaling/ec2/APIReference/API_SuspendProcesses.html).
+
+To utilize this feature, simply denote the list of processes that are desired to be suspended.
+
+```yaml
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
+kind: AWSMachinePool
+metadata:
+  name: capa-mp-0
+spec:
+  minSize: 1
+  maxSize: 10
+  availabilityZones:
+    - "${AWS_AVAILABILITY_ZONE}"
+  awsLaunchTemplate:
+    instanceType: "${AWS_CONTROL_PLANE_MACHINE_TYPE}"
+    sshKeyName: "${AWS_SSH_KEY_NAME}"  
+  suspendProcesses:
+    processes:
+        launch: true
+        alarmNotification: true
+        azRebalance: true
+---
+```
+
+## Resume Processes
+
+If a service is desired to be resumed, simply remove it from the list of suspended processes. The reconciler will then
+resume any process that is not part of the desired suspended processes list.
+
+```yaml
+---
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
+kind: AWSMachinePool
+metadata:
+  name: capa-mp-0
+spec:
+  minSize: 1
+  maxSize: 10
+  availabilityZones:
+    - "${AWS_AVAILABILITY_ZONE}"
+  awsLaunchTemplate:
+    instanceType: "${AWS_CONTROL_PLANE_MACHINE_TYPE}"
+    sshKeyName: "${AWS_SSH_KEY_NAME}"  
+  suspendProcesses:
+    processes:
+      launch: true
+---
+```
+
+_Note_ that now `AlarmNotification` and `AZRebalance` will be resumed, but the reconciler will not try to suspend
+`Launch` again. So it doesn't incur additional expensive, redundant API calls.
+
+## Optional `All`
+
+An option is also provided to suspend all processes without having to set each of them to `true`. Simply use `all` like
+this:
+
+```yaml
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
+kind: AWSMachinePool
+metadata:
+  name: capa-mp-0
+spec:
+  minSize: 1
+  maxSize: 10
+  availabilityZones:
+    - "${AWS_AVAILABILITY_ZONE}"
+  awsLaunchTemplate:
+    instanceType: "${AWS_CONTROL_PLANE_MACHINE_TYPE}"
+    sshKeyName: "${AWS_SSH_KEY_NAME}"  
+  suspendProcesses:
+    all: true
+```
+
+To exclude individual processes from `all` simply add them with value `false`:
+
+```yaml
+apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
+kind: AWSMachinePool
+metadata:
+  name: capa-mp-0
+spec:
+  minSize: 1
+  maxSize: 10
+  availabilityZones:
+    - "${AWS_AVAILABILITY_ZONE}"
+  awsLaunchTemplate:
+    instanceType: "${AWS_CONTROL_PLANE_MACHINE_TYPE}"
+    sshKeyName: "${AWS_SSH_KEY_NAME}"  
+  suspendProcesses:
+    all: true
+    processes:
+      launch: false
+```
diff --git a/exp/api/v1beta1/conversion.go b/exp/api/v1beta1/conversion.go
index d3d32c7c6..61c9a857f 100644
--- a/exp/api/v1beta1/conversion.go
+++ b/exp/api/v1beta1/conversion.go
@@ -21,6 +21,7 @@ import (
 	infrav1beta1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
 	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta2"
 	infrav1exp "sigs.k8s.io/cluster-api-provider-aws/exp/api/v1beta2"
+	utilconversion "sigs.k8s.io/cluster-api/util/conversion"
 	"sigs.k8s.io/controller-runtime/pkg/conversion"
 )
 
@@ -30,7 +31,17 @@ func (src *AWSMachinePool) ConvertTo(dstRaw conversion.Hub) error {
 	if err := Convert_v1beta1_AWSMachinePool_To_v1beta2_AWSMachinePool(src, dst, nil); err != nil {
 		return err
 	}
-	
+
+	// Manually restore data.
+	restored := &infrav1exp.AWSMachinePool{}
+	if ok, err := utilconversion.UnmarshalData(src, restored); err != nil || !ok {
+		return err
+	}
+
+	if restored.Spec.SuspendProcesses != nil {
+		dst.Spec.SuspendProcesses = restored.Spec.SuspendProcesses
+	}
+
 	return nil
 }
 
@@ -41,8 +52,8 @@ func (r *AWSMachinePool) ConvertFrom(srcRaw conversion.Hub) error {
 	if err := Convert_v1beta2_AWSMachinePool_To_v1beta1_AWSMachinePool(src, r, nil); err != nil {
 		return err
 	}
-	
-	return nil
+
+	return utilconversion.MarshalData(src, r)
 }
 
 // ConvertTo converts the v1beta1 AWSMachinePoolList receiver to a v1beta2 AWSMachinePoolList.
@@ -75,7 +86,7 @@ func (r *AWSManagedMachinePool) ConvertFrom(srcRaw conversion.Hub) error {
 	if err := Convert_v1beta2_AWSManagedMachinePool_To_v1beta1_AWSManagedMachinePool(src, r, nil); err != nil {
 		return err
 	}
-	
+
 	return nil
 }
 
@@ -147,3 +158,20 @@ func Convert_v1beta1_Instance_To_v1beta2_Instance(in *infrav1beta1.Instance, out
 func Convert_v1beta2_AWSLaunchTemplate_To_v1beta1_AWSLaunchTemplate(in *infrav1exp.AWSLaunchTemplate, out *AWSLaunchTemplate, s apiconversion.Scope) error {
 	return autoConvert_v1beta2_AWSLaunchTemplate_To_v1beta1_AWSLaunchTemplate(in, out, s)
 }
+
+func Convert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(in *AWSMachinePoolSpec, out *infrav1exp.AWSMachinePoolSpec, s apiconversion.Scope) error {
+	return autoConvert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(in, out, s)
+}
+
+func Convert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(in *infrav1exp.AWSMachinePoolSpec, out *AWSMachinePoolSpec, s apiconversion.Scope) error {
+	return autoConvert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(in, out, s)
+}
+
+func Convert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(in *AutoScalingGroup, out *infrav1exp.AutoScalingGroup, s apiconversion.Scope) error {
+	return autoConvert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(in, out, s)
+}
+
+func Convert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(in *infrav1exp.AutoScalingGroup, out *AutoScalingGroup, s apiconversion.Scope) error {
+	// explicitly ignore CurrentlySuspended.
+	return autoConvert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(in, out, s)
+}
diff --git a/exp/api/v1beta1/zz_generated.conversion.go b/exp/api/v1beta1/zz_generated.conversion.go
index d21842339..0af7cdb05 100644
--- a/exp/api/v1beta1/zz_generated.conversion.go
+++ b/exp/api/v1beta1/zz_generated.conversion.go
@@ -95,16 +95,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddGeneratedConversionFunc((*AWSMachinePoolSpec)(nil), (*v1beta2.AWSMachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(a.(*AWSMachinePoolSpec), b.(*v1beta2.AWSMachinePoolSpec), scope)
-	}); err != nil {
-		return err
-	}
-	if err := s.AddGeneratedConversionFunc((*v1beta2.AWSMachinePoolSpec)(nil), (*AWSMachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(a.(*v1beta2.AWSMachinePoolSpec), b.(*AWSMachinePoolSpec), scope)
-	}); err != nil {
-		return err
-	}
 	if err := s.AddGeneratedConversionFunc((*AWSMachinePoolStatus)(nil), (*v1beta2.AWSMachinePoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta1_AWSMachinePoolStatus_To_v1beta2_AWSMachinePoolStatus(a.(*AWSMachinePoolStatus), b.(*v1beta2.AWSMachinePoolStatus), scope)
 	}); err != nil {
@@ -150,16 +140,6 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
-	if err := s.AddGeneratedConversionFunc((*AutoScalingGroup)(nil), (*v1beta2.AutoScalingGroup)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(a.(*AutoScalingGroup), b.(*v1beta2.AutoScalingGroup), scope)
-	}); err != nil {
-		return err
-	}
-	if err := s.AddGeneratedConversionFunc((*v1beta2.AutoScalingGroup)(nil), (*AutoScalingGroup)(nil), func(a, b interface{}, scope conversion.Scope) error {
-		return Convert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(a.(*v1beta2.AutoScalingGroup), b.(*AutoScalingGroup), scope)
-	}); err != nil {
-		return err
-	}
 	if err := s.AddGeneratedConversionFunc((*BlockDeviceMapping)(nil), (*v1beta2.BlockDeviceMapping)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta1_BlockDeviceMapping_To_v1beta2_BlockDeviceMapping(a.(*BlockDeviceMapping), b.(*v1beta2.BlockDeviceMapping), scope)
 	}); err != nil {
@@ -295,6 +275,16 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddConversionFunc((*AWSMachinePoolSpec)(nil), (*v1beta2.AWSMachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(a.(*AWSMachinePoolSpec), b.(*v1beta2.AWSMachinePoolSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddConversionFunc((*AutoScalingGroup)(nil), (*v1beta2.AutoScalingGroup)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(a.(*AutoScalingGroup), b.(*v1beta2.AutoScalingGroup), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*apiv1beta1.Instance)(nil), (*apiv1beta2.Instance)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta1_Instance_To_v1beta2_Instance(a.(*apiv1beta1.Instance), b.(*apiv1beta2.Instance), scope)
 	}); err != nil {
@@ -310,11 +300,21 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddConversionFunc((*v1beta2.AWSMachinePoolSpec)(nil), (*AWSMachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(a.(*v1beta2.AWSMachinePoolSpec), b.(*AWSMachinePoolSpec), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*v1beta2.AWSManagedMachinePoolSpec)(nil), (*AWSManagedMachinePoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta2_AWSManagedMachinePoolSpec_To_v1beta1_AWSManagedMachinePoolSpec(a.(*v1beta2.AWSManagedMachinePoolSpec), b.(*AWSManagedMachinePoolSpec), scope)
 	}); err != nil {
 		return err
 	}
+	if err := s.AddConversionFunc((*v1beta2.AutoScalingGroup)(nil), (*AutoScalingGroup)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(a.(*v1beta2.AutoScalingGroup), b.(*AutoScalingGroup), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddConversionFunc((*apiv1beta2.Instance)(nil), (*apiv1beta1.Instance)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1beta2_Instance_To_v1beta1_Instance(a.(*apiv1beta2.Instance), b.(*apiv1beta1.Instance), scope)
 	}); err != nil {
@@ -528,11 +528,6 @@ func autoConvert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(in *AW
 	return nil
 }
 
-// Convert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec is an autogenerated conversion function.
-func Convert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(in *AWSMachinePoolSpec, out *v1beta2.AWSMachinePoolSpec, s conversion.Scope) error {
-	return autoConvert_v1beta1_AWSMachinePoolSpec_To_v1beta2_AWSMachinePoolSpec(in, out, s)
-}
-
 func autoConvert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(in *v1beta2.AWSMachinePoolSpec, out *AWSMachinePoolSpec, s conversion.Scope) error {
 	out.ProviderID = in.ProviderID
 	out.MinSize = in.MinSize
@@ -548,14 +543,10 @@ func autoConvert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(in *v1
 	out.DefaultCoolDown = in.DefaultCoolDown
 	out.RefreshPreferences = (*RefreshPreferences)(unsafe.Pointer(in.RefreshPreferences))
 	out.CapacityRebalance = in.CapacityRebalance
+	// WARNING: in.SuspendProcesses requires manual conversion: does not exist in peer-type
 	return nil
 }
 
-// Convert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec is an autogenerated conversion function.
-func Convert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(in *v1beta2.AWSMachinePoolSpec, out *AWSMachinePoolSpec, s conversion.Scope) error {
-	return autoConvert_v1beta2_AWSMachinePoolSpec_To_v1beta1_AWSMachinePoolSpec(in, out, s)
-}
-
 func autoConvert_v1beta1_AWSMachinePoolStatus_To_v1beta2_AWSMachinePoolStatus(in *AWSMachinePoolStatus, out *v1beta2.AWSMachinePoolStatus, s conversion.Scope) error {
 	out.Ready = in.Ready
 	out.Replicas = in.Replicas
@@ -780,11 +771,6 @@ func autoConvert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(in *AutoSc
 	return nil
 }
 
-// Convert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup is an autogenerated conversion function.
-func Convert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(in *AutoScalingGroup, out *v1beta2.AutoScalingGroup, s conversion.Scope) error {
-	return autoConvert_v1beta1_AutoScalingGroup_To_v1beta2_AutoScalingGroup(in, out, s)
-}
-
 func autoConvert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(in *v1beta2.AutoScalingGroup, out *AutoScalingGroup, s conversion.Scope) error {
 	out.ID = in.ID
 	out.Tags = *(*apiv1beta2.Tags)(unsafe.Pointer(&in.Tags))
@@ -799,14 +785,10 @@ func autoConvert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(in *v1beta
 	out.MixedInstancesPolicy = (*MixedInstancesPolicy)(unsafe.Pointer(in.MixedInstancesPolicy))
 	out.Status = ASGStatus(in.Status)
 	out.Instances = *(*[]apiv1beta2.Instance)(unsafe.Pointer(&in.Instances))
+	// WARNING: in.CurrentlySuspendProcesses requires manual conversion: does not exist in peer-type
 	return nil
 }
 
-// Convert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup is an autogenerated conversion function.
-func Convert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(in *v1beta2.AutoScalingGroup, out *AutoScalingGroup, s conversion.Scope) error {
-	return autoConvert_v1beta2_AutoScalingGroup_To_v1beta1_AutoScalingGroup(in, out, s)
-}
-
 func autoConvert_v1beta1_BlockDeviceMapping_To_v1beta2_BlockDeviceMapping(in *BlockDeviceMapping, out *v1beta2.BlockDeviceMapping, s conversion.Scope) error {
 	out.DeviceName = in.DeviceName
 	if err := Convert_v1beta1_EBS_To_v1beta2_EBS(&in.Ebs, &out.Ebs, s); err != nil {
diff --git a/exp/api/v1beta2/awsmachinepool_types.go b/exp/api/v1beta2/awsmachinepool_types.go
index ba6eb4b03..ea7558a7a 100644
--- a/exp/api/v1beta2/awsmachinepool_types.go
+++ b/exp/api/v1beta2/awsmachinepool_types.go
@@ -17,6 +17,8 @@ limitations under the License.
 package v1beta2
 
 import (
+	"reflect"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
@@ -83,6 +85,56 @@ type AWSMachinePoolSpec struct {
 	// Enable or disable the capacity rebalance autoscaling group feature
 	// +optional
 	CapacityRebalance bool `json:"capacityRebalance,omitempty"`
+
+	// SuspendProcesses defines a list of processes to suspend for the given ASG. This is constantly reconciled.
+	// If a process is removed from this list it will automatically be resumed.
+	SuspendProcesses *SuspendProcessesTypes `json:"suspendProcesses,omitempty"`
+}
+
+// SuspendProcessesTypes contains user friendly auto-completable values for suspended process names.
+type SuspendProcessesTypes struct {
+	All       bool       `json:"all,omitempty"`
+	Processes *Processes `json:"processes,omitempty"`
+}
+
+// Processes defines the processes which can be enabled or disabled individually.
+type Processes struct {
+	Launch            *bool `json:"launch,omitempty"`
+	Terminate         *bool `json:"terminate,omitempty"`
+	AddToLoadBalancer *bool `json:"addToLoadBalancer,omitempty"`
+	AlarmNotification *bool `json:"alarmNotification,omitempty"`
+	AZRebalance       *bool `json:"azRebalance,omitempty"`
+	HealthCheck       *bool `json:"healthCheck,omitempty"`
+	InstanceRefresh   *bool `json:"instanceRefresh,omitempty"`
+	ReplaceUnhealthy  *bool `json:"replaceUnhealthy,omitempty"`
+	ScheduledActions  *bool `json:"scheduledActions,omitempty"`
+}
+
+// ConvertSetValuesToStringSlice converts all the values that are set into a string slice for further processing.
+func (s *SuspendProcessesTypes) ConvertSetValuesToStringSlice() []string {
+	if s == nil {
+		return nil
+	}
+
+	if s.Processes == nil {
+		s.Processes = &Processes{}
+	}
+
+	e := reflect.ValueOf(s.Processes).Elem()
+	var result []string
+	for i := 0; i < e.NumField(); i++ {
+		if s.All {
+			if !e.Field(i).IsNil() && !*e.Field(i).Interface().(*bool) {
+				// don't enable if explicitly set to false.
+				continue
+			}
+			result = append(result, e.Type().Field(i).Name)
+		} else if !e.Field(i).IsNil() && *e.Field(i).Interface().(*bool) {
+			result = append(result, e.Type().Field(i).Name)
+		}
+	}
+
+	return result
 }
 
 // RefreshPreferences defines the specs for instance refreshing.
diff --git a/exp/api/v1beta2/types.go b/exp/api/v1beta2/types.go
index c891e1055..c047c40d5 100644
--- a/exp/api/v1beta2/types.go
+++ b/exp/api/v1beta2/types.go
@@ -193,9 +193,10 @@ type AutoScalingGroup struct {
 	DefaultCoolDown   metav1.Duration `json:"defaultCoolDown,omitempty"`
 	CapacityRebalance bool            `json:"capacityRebalance,omitempty"`
 
-	MixedInstancesPolicy *MixedInstancesPolicy `json:"mixedInstancesPolicy,omitempty"`
-	Status               ASGStatus
-	Instances            []infrav1.Instance `json:"instances,omitempty"`
+	MixedInstancesPolicy      *MixedInstancesPolicy `json:"mixedInstancesPolicy,omitempty"`
+	Status                    ASGStatus
+	Instances                 []infrav1.Instance `json:"instances,omitempty"`
+	CurrentlySuspendProcesses []string           `json:"currentlySuspendProcesses,omitempty"`
 }
 
 // ASGStatus is a status string returned by the autoscaling API.
diff --git a/exp/api/v1beta2/zz_generated.deepcopy.go b/exp/api/v1beta2/zz_generated.deepcopy.go
index 5086e4075..c0b20c40c 100644
--- a/exp/api/v1beta2/zz_generated.deepcopy.go
+++ b/exp/api/v1beta2/zz_generated.deepcopy.go
@@ -248,6 +248,11 @@ func (in *AWSMachinePoolSpec) DeepCopyInto(out *AWSMachinePoolSpec) {
 		*out = new(RefreshPreferences)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.SuspendProcesses != nil {
+		in, out := &in.SuspendProcesses, &out.SuspendProcesses
+		*out = new(SuspendProcessesTypes)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSMachinePoolSpec.
@@ -542,6 +547,11 @@ func (in *AutoScalingGroup) DeepCopyInto(out *AutoScalingGroup) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
+	if in.CurrentlySuspendProcesses != nil {
+		in, out := &in.CurrentlySuspendProcesses, &out.CurrentlySuspendProcesses
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoScalingGroup.
@@ -788,6 +798,66 @@ func (in *Overrides) DeepCopy() *Overrides {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Processes) DeepCopyInto(out *Processes) {
+	*out = *in
+	if in.Launch != nil {
+		in, out := &in.Launch, &out.Launch
+		*out = new(bool)
+		**out = **in
+	}
+	if in.Terminate != nil {
+		in, out := &in.Terminate, &out.Terminate
+		*out = new(bool)
+		**out = **in
+	}
+	if in.AddToLoadBalancer != nil {
+		in, out := &in.AddToLoadBalancer, &out.AddToLoadBalancer
+		*out = new(bool)
+		**out = **in
+	}
+	if in.AlarmNotification != nil {
+		in, out := &in.AlarmNotification, &out.AlarmNotification
+		*out = new(bool)
+		**out = **in
+	}
+	if in.AZRebalance != nil {
+		in, out := &in.AZRebalance, &out.AZRebalance
+		*out = new(bool)
+		**out = **in
+	}
+	if in.HealthCheck != nil {
+		in, out := &in.HealthCheck, &out.HealthCheck
+		*out = new(bool)
+		**out = **in
+	}
+	if in.InstanceRefresh != nil {
+		in, out := &in.InstanceRefresh, &out.InstanceRefresh
+		*out = new(bool)
+		**out = **in
+	}
+	if in.ReplaceUnhealthy != nil {
+		in, out := &in.ReplaceUnhealthy, &out.ReplaceUnhealthy
+		*out = new(bool)
+		**out = **in
+	}
+	if in.ScheduledActions != nil {
+		in, out := &in.ScheduledActions, &out.ScheduledActions
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Processes.
+func (in *Processes) DeepCopy() *Processes {
+	if in == nil {
+		return nil
+	}
+	out := new(Processes)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *RefreshPreferences) DeepCopyInto(out *RefreshPreferences) {
 	*out = *in
@@ -818,6 +888,26 @@ func (in *RefreshPreferences) DeepCopy() *RefreshPreferences {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SuspendProcessesTypes) DeepCopyInto(out *SuspendProcessesTypes) {
+	*out = *in
+	if in.Processes != nil {
+		in, out := &in.Processes, &out.Processes
+		*out = new(Processes)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SuspendProcessesTypes.
+func (in *SuspendProcessesTypes) DeepCopy() *SuspendProcessesTypes {
+	if in == nil {
+		return nil
+	}
+	out := new(SuspendProcessesTypes)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in Tags) DeepCopyInto(out *Tags) {
 	{
diff --git a/exp/controllers/awsmachinepool_controller.go b/exp/controllers/awsmachinepool_controller.go
index e514b9601..cc88861f6 100644
--- a/exp/controllers/awsmachinepool_controller.go
+++ b/exp/controllers/awsmachinepool_controller.go
@@ -374,9 +374,9 @@ func (r *AWSMachinePoolReconciler) reconcileDelete(machinePoolScope *scope.Machi
 }
 
 func (r *AWSMachinePoolReconciler) updatePool(machinePoolScope *scope.MachinePoolScope, clusterScope cloud.ClusterScoper, existingASG *expinfrav1.AutoScalingGroup) error {
+	asgSvc := r.getASGService(clusterScope)
 	if asgNeedsUpdates(machinePoolScope, existingASG) {
 		machinePoolScope.Info("updating AutoScalingGroup")
-		asgSvc := r.getASGService(clusterScope)
 
 		if err := asgSvc.UpdateASG(machinePoolScope); err != nil {
 			r.Recorder.Eventf(machinePoolScope.AWSMachinePool, corev1.EventTypeWarning, "FailedUpdate", "Failed to update ASG: %v", err)
@@ -384,6 +384,55 @@ func (r *AWSMachinePoolReconciler) updatePool(machinePoolScope *scope.MachinePoo
 		}
 	}
 
+	suspendedProcessesSlice := machinePoolScope.AWSMachinePool.Spec.SuspendProcesses.ConvertSetValuesToStringSlice()
+	if !cmp.Equal(existingASG.CurrentlySuspendProcesses, suspendedProcessesSlice) {
+		var (
+			toBeSuspended []string
+			toBeResumed   []string
+
+			currentlySuspended = make(map[string]struct{})
+			desiredSuspended   = make(map[string]struct{})
+		)
+
+		// Convert the items to a map, so it's easy to create an effective diff from these two slices.
+		for _, p := range existingASG.CurrentlySuspendProcesses {
+			currentlySuspended[p] = struct{}{}
+		}
+
+		for _, p := range suspendedProcessesSlice {
+			desiredSuspended[p] = struct{}{}
+		}
+
+		// Anything that remains in the desired items is not currently suspended so must be suspended.
+		// Anything that remains in the currentlySuspended list must be resumed since they were not part of
+		// desiredSuspended.
+		for k := range desiredSuspended {
+			if _, ok := currentlySuspended[k]; ok {
+				delete(desiredSuspended, k)
+			}
+			delete(currentlySuspended, k)
+		}
+
+		// Convert them back into lists so
+		for k := range desiredSuspended {
+			toBeSuspended = append(toBeSuspended, k)
+		}
+
+		for k := range currentlySuspended {
+			toBeResumed = append(toBeResumed, k)
+		}
+
+		if len(toBeSuspended) > 0 {
+			if err := asgSvc.SuspendProcesses(existingASG.Name, toBeSuspended); err != nil {
+				return errors.Wrapf(err, "failed to suspend processes while trying update pool")
+			}
+		}
+		if len(toBeResumed) > 0 {
+			if err := asgSvc.ResumeProcesses(existingASG.Name, toBeResumed); err != nil {
+				return errors.Wrapf(err, "failed to resume processes while trying update pool")
+			}
+		}
+	}
 	return nil
 }
 
@@ -397,7 +446,10 @@ func (r *AWSMachinePoolReconciler) createPool(machinePoolScope *scope.MachinePoo
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to create AWSMachinePool")
 	}
-
+	suspendedProcessesSlice := machinePoolScope.AWSMachinePool.Spec.SuspendProcesses.ConvertSetValuesToStringSlice()
+	if err := asgsvc.SuspendProcesses(asg.Name, suspendedProcessesSlice); err != nil {
+		return nil, errors.Wrapf(err, "failed to suspend processes while trying to create Pool")
+	}
 	return asg, nil
 }
 
diff --git a/pkg/cloud/services/autoscaling/autoscalinggroup.go b/pkg/cloud/services/autoscaling/autoscalinggroup.go
index c61535c88..19cf064a2 100644
--- a/pkg/cloud/services/autoscaling/autoscalinggroup.go
+++ b/pkg/cloud/services/autoscaling/autoscalinggroup.go
@@ -91,6 +91,14 @@ func (s *Service) SDKToAutoScalingGroup(v *autoscaling.Group) (*expinfrav1.AutoS
 		}
 	}
 
+	if len(v.SuspendedProcesses) > 0 {
+		currentlySuspendedProcesses := make([]string, len(v.SuspendedProcesses))
+		for i, service := range v.SuspendedProcesses {
+			currentlySuspendedProcesses[i] = aws.StringValue(service.ProcessName)
+		}
+		i.CurrentlySuspendProcesses = currentlySuspendedProcesses
+	}
+
 	return i, nil
 }
 
@@ -448,6 +456,28 @@ func (s *Service) UpdateResourceTags(resourceID *string, create, remove map[stri
 	return nil
 }
 
+func (s *Service) SuspendProcesses(name string, processes []string) error {
+	input := autoscaling.ScalingProcessQuery{
+		AutoScalingGroupName: aws.String(name),
+		ScalingProcesses:     aws.StringSlice(processes),
+	}
+	if _, err := s.ASGClient.SuspendProcesses(&input); err != nil {
+		return errors.Wrapf(err, "failed to suspend processes for AutoScalingGroup: %q", name)
+	}
+	return nil
+}
+
+func (s *Service) ResumeProcesses(name string, processes []string) error {
+	input := autoscaling.ScalingProcessQuery{
+		AutoScalingGroupName: aws.String(name),
+		ScalingProcesses:     aws.StringSlice(processes),
+	}
+	if _, err := s.ASGClient.ResumeProcesses(&input); err != nil {
+		return errors.Wrapf(err, "failed to resume processes for AutoScalingGroup: %q", name)
+	}
+	return nil
+}
+
 func mapToTags(input map[string]string, resourceID *string) []*autoscaling.Tag {
 	tags := make([]*autoscaling.Tag, 0)
 	for k, v := range input {
diff --git a/pkg/cloud/services/interfaces.go b/pkg/cloud/services/interfaces.go
index 4c90b1908..1d3ad6316 100644
--- a/pkg/cloud/services/interfaces.go
+++ b/pkg/cloud/services/interfaces.go
@@ -42,6 +42,8 @@ type ASGInterface interface {
 	CanStartASGInstanceRefresh(scope *scope.MachinePoolScope) (bool, error)
 	UpdateResourceTags(resourceID *string, create, remove map[string]string) error
 	DeleteASGAndWait(id string) error
+	SuspendProcesses(name string, processes []string) error
+	ResumeProcesses(name string, processes []string) error
 }
 
 // EC2Interface encapsulates the methods exposed to the machine
diff --git a/pkg/cloud/services/mock_services/autoscaling_interface_mock.go b/pkg/cloud/services/mock_services/autoscaling_interface_mock.go
index f78f2be69..4419eff10 100644
--- a/pkg/cloud/services/mock_services/autoscaling_interface_mock.go
+++ b/pkg/cloud/services/mock_services/autoscaling_interface_mock.go
@@ -125,6 +125,20 @@ func (mr *MockASGInterfaceMockRecorder) GetASGByName(arg0 interface{}) *gomock.C
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetASGByName", reflect.TypeOf((*MockASGInterface)(nil).GetASGByName), arg0)
 }
 
+// ResumeProcesses mocks base method.
+func (m *MockASGInterface) ResumeProcesses(arg0 string, arg1 []string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ResumeProcesses", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ResumeProcesses indicates an expected call of ResumeProcesses.
+func (mr *MockASGInterfaceMockRecorder) ResumeProcesses(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResumeProcesses", reflect.TypeOf((*MockASGInterface)(nil).ResumeProcesses), arg0, arg1)
+}
+
 // StartASGInstanceRefresh mocks base method.
 func (m *MockASGInterface) StartASGInstanceRefresh(arg0 *scope.MachinePoolScope) error {
 	m.ctrl.T.Helper()
@@ -139,6 +153,20 @@ func (mr *MockASGInterfaceMockRecorder) StartASGInstanceRefresh(arg0 interface{}
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartASGInstanceRefresh", reflect.TypeOf((*MockASGInterface)(nil).StartASGInstanceRefresh), arg0)
 }
 
+// SuspendProcesses mocks base method.
+func (m *MockASGInterface) SuspendProcesses(arg0 string, arg1 []string) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SuspendProcesses", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// SuspendProcesses indicates an expected call of SuspendProcesses.
+func (mr *MockASGInterfaceMockRecorder) SuspendProcesses(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SuspendProcesses", reflect.TypeOf((*MockASGInterface)(nil).SuspendProcesses), arg0, arg1)
+}
+
 // UpdateASG mocks base method.
 func (m *MockASGInterface) UpdateASG(arg0 *scope.MachinePoolScope) error {
 	m.ctrl.T.Helper()


Test patch:
diff --git a/exp/controllers/awsmachinepool_controller_test.go b/exp/controllers/awsmachinepool_controller_test.go
index 152522326..b6752be3b 100644
--- a/exp/controllers/awsmachinepool_controller_test.go
+++ b/exp/controllers/awsmachinepool_controller_test.go
@@ -249,6 +249,126 @@ func TestAWSMachinePoolReconciler(t *testing.T) {
 				g.Expect(errors.Cause(err)).To(MatchError(expectedErr))
 			})
 		})
+		t.Run("there's suspended processes provided during ASG creation", func(t *testing.T) {
+			setSuspendedProcesses := func(t *testing.T, g *WithT) {
+				t.Helper()
+				ms.AWSMachinePool.Spec.SuspendProcesses = &expinfrav1.SuspendProcessesTypes{
+					Processes: &expinfrav1.Processes{
+						Launch:    pointer.Bool(true),
+						Terminate: pointer.Bool(true),
+					},
+				}
+			}
+			t.Run("it should suspend these processes", func(t *testing.T) {
+				g := NewWithT(t)
+				setup(t, g)
+				defer teardown(t, g)
+				setSuspendedProcesses(t, g)
+
+				ec2Svc.EXPECT().ReconcileLaunchTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
+				asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(nil, nil)
+				asgSvc.EXPECT().CreateASG(gomock.Any()).Return(&expinfrav1.AutoScalingGroup{
+					Name: "name",
+				}, nil)
+				asgSvc.EXPECT().SuspendProcesses("name", []string{"Launch", "Terminate"}).Return(nil).AnyTimes()
+
+				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs)
+				g.Expect(err).To(Succeed())
+			})
+		})
+		t.Run("all processes are suspended", func(t *testing.T) {
+			setSuspendedProcesses := func(t *testing.T, g *WithT) {
+				t.Helper()
+				ms.AWSMachinePool.Spec.SuspendProcesses = &expinfrav1.SuspendProcessesTypes{
+					All: true,
+				}
+			}
+			t.Run("it should result in all processes being included except the value all", func(t *testing.T) {
+				g := NewWithT(t)
+				setup(t, g)
+				defer teardown(t, g)
+				setSuspendedProcesses(t, g)
+
+				ec2Svc.EXPECT().ReconcileLaunchTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
+				asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(nil, nil)
+				asgSvc.EXPECT().CreateASG(gomock.Any()).Return(&expinfrav1.AutoScalingGroup{
+					Name: "name",
+				}, nil)
+				asgSvc.EXPECT().SuspendProcesses("name", []string{
+					"Launch",
+					"Terminate",
+					"AddToLoadBalancer",
+					"AlarmNotification",
+					"AZRebalance",
+					"HealthCheck",
+					"InstanceRefresh",
+					"ReplaceUnhealthy",
+					"ScheduledActions",
+				}).Return(nil).AnyTimes()
+
+				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs)
+				g.Expect(err).To(Succeed())
+			})
+			t.Run("and one or more processes are disabled, it should not list those", func(t *testing.T) {
+				g := NewWithT(t)
+				setup(t, g)
+				defer teardown(t, g)
+				setSuspendedProcesses(t, g)
+				ms.AWSMachinePool.Spec.SuspendProcesses.Processes = &expinfrav1.Processes{
+					Launch:      pointer.Bool(false),
+					AZRebalance: pointer.Bool(true), // this should still be included but not twice...
+				}
+				ec2Svc.EXPECT().ReconcileLaunchTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
+				asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(nil, nil)
+				asgSvc.EXPECT().CreateASG(gomock.Any()).Return(&expinfrav1.AutoScalingGroup{
+					Name: "name",
+				}, nil)
+				asgSvc.EXPECT().SuspendProcesses("name", []string{
+					"Terminate",
+					"AddToLoadBalancer",
+					"AlarmNotification",
+					"AZRebalance",
+					"HealthCheck",
+					"InstanceRefresh",
+					"ReplaceUnhealthy",
+					"ScheduledActions",
+				}).Return(nil).AnyTimes()
+
+				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs)
+				g.Expect(err).To(Succeed())
+			})
+		})
+		t.Run("there are existing processes already suspended", func(t *testing.T) {
+			setSuspendedProcesses := func(t *testing.T, g *WithT) {
+				t.Helper()
+
+				ms.AWSMachinePool.Spec.SuspendProcesses = &expinfrav1.SuspendProcessesTypes{
+					Processes: &expinfrav1.Processes{
+						Launch:    pointer.Bool(true),
+						Terminate: pointer.Bool(true),
+					},
+				}
+			}
+			t.Run("it should suspend and resume processes that are desired to be suspended and desired to be resumed", func(t *testing.T) {
+				g := NewWithT(t)
+				setup(t, g)
+				defer teardown(t, g)
+				setSuspendedProcesses(t, g)
+
+				ec2Svc.EXPECT().ReconcileLaunchTemplate(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
+				ec2Svc.EXPECT().ReconcileTags(gomock.Any(), gomock.Any()).Return(nil)
+				asgSvc.EXPECT().GetASGByName(gomock.Any()).Return(&expinfrav1.AutoScalingGroup{
+					Name:                      "name",
+					CurrentlySuspendProcesses: []string{"Launch", "process3"},
+				}, nil)
+				asgSvc.EXPECT().UpdateASG(gomock.Any()).Return(nil).AnyTimes()
+				asgSvc.EXPECT().SuspendProcesses("name", []string{"Terminate"}).Return(nil).AnyTimes()
+				asgSvc.EXPECT().ResumeProcesses("name", []string{"process3"}).Return(nil).AnyTimes()
+
+				_, err := reconciler.reconcileNormal(context.Background(), ms, cs, cs)
+				g.Expect(err).To(Succeed())
+			})
+		})
 	})
 
 	t.Run("Deleting an AWSMachinePool", func(t *testing.T) {
@@ -351,7 +471,7 @@ func setupCluster(clusterName string) (*scope.ClusterScope, error) {
 	})
 }
 
-func Test_asgNeedsUpdates(t *testing.T) {
+func TestASGNeedsUpdates(t *testing.T) {
 	type args struct {
 		machinePoolScope *scope.MachinePoolScope
 		existingASG      *expinfrav1.AutoScalingGroup
@@ -515,6 +635,47 @@ func Test_asgNeedsUpdates(t *testing.T) {
 			},
 			want: true,
 		},
+		{
+			name: "SuspendProcesses != asg.SuspendProcesses",
+			args: args{
+				machinePoolScope: &scope.MachinePoolScope{
+					MachinePool: &expclusterv1.MachinePool{
+						Spec: expclusterv1.MachinePoolSpec{
+							Replicas: pointer.Int32(1),
+						},
+					},
+					AWSMachinePool: &expinfrav1.AWSMachinePool{
+						Spec: expinfrav1.AWSMachinePoolSpec{
+							MaxSize:           2,
+							MinSize:           0,
+							CapacityRebalance: true,
+							MixedInstancesPolicy: &expinfrav1.MixedInstancesPolicy{
+								InstancesDistribution: &expinfrav1.InstancesDistribution{
+									OnDemandAllocationStrategy: expinfrav1.OnDemandAllocationStrategyPrioritized,
+								},
+								Overrides: nil,
+							},
+							SuspendProcesses: &expinfrav1.SuspendProcessesTypes{
+								Processes: &expinfrav1.Processes{
+									Launch:    pointer.Bool(true),
+									Terminate: pointer.Bool(true),
+								},
+							},
+						},
+					},
+					Logger: logr.Discard(),
+				},
+				existingASG: &expinfrav1.AutoScalingGroup{
+					DesiredCapacity:           pointer.Int32(1),
+					MaxSize:                   2,
+					MinSize:                   0,
+					CapacityRebalance:         true,
+					MixedInstancesPolicy:      &expinfrav1.MixedInstancesPolicy{},
+					CurrentlySuspendProcesses: []string{"Launch", "Terminate"},
+				},
+			},
+			want: true,
+		},
 		{
 			name: "all matches",
 			args: args{
diff --git a/pkg/cloud/services/autoscaling/autoscalinggroup_test.go b/pkg/cloud/services/autoscaling/autoscalinggroup_test.go
index 33d2d16fd..60742a159 100644
--- a/pkg/cloud/services/autoscaling/autoscalinggroup_test.go
+++ b/pkg/cloud/services/autoscaling/autoscalinggroup_test.go
@@ -184,6 +184,27 @@ func TestServiceSDKToAutoScalingGroup(t *testing.T) {
 			},
 			wantErr: false,
 		},
+		{
+			name: "valid input - suspended processes",
+			input: &autoscaling.Group{
+				DesiredCapacity: aws.Int64(1234),
+				MaxSize:         aws.Int64(1234),
+				MinSize:         aws.Int64(1234),
+				SuspendedProcesses: []*autoscaling.SuspendedProcess{
+					{
+						ProcessName:      aws.String("process1"),
+						SuspensionReason: aws.String("not relevant"),
+					},
+				},
+			},
+			want: &expinfrav1.AutoScalingGroup{
+				DesiredCapacity:           aws.Int32(1234),
+				MaxSize:                   int32(1234),
+				MinSize:                   int32(1234),
+				CurrentlySuspendProcesses: []string{"process1"},
+			},
+			wantErr: false,
+		},
 		{
 			name: "valid input - all fields filled",
 			input: &autoscaling.Group{