Skip to content
This repository was archived by the owner on Oct 8, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d7d8d40
First pass at waiting for healthy only instances.
gregharvey Mar 3, 2025
ce3497c
Moving the delegate_to and run_once to the block.
gregharvey Mar 3, 2025
0e9c296
Adding AnsibleUnicode as a possible var type.
gregharvey Mar 3, 2025
ee7ad57
Fixing or logic in when clause for legacy strings.
gregharvey Mar 3, 2025
2640367
Reverting to using command and CLI for ASG processes, simpler for IAM…
gregharvey Mar 3, 2025
0c4bcfa
Fixing region variable for AWS.
gregharvey Mar 3, 2025
f0c52c9
Fine tuning when ASG manipulation occurs.
gregharvey Mar 3, 2025
430315d
Moving ASG parts to another role so we can call them separately.
gregharvey Mar 4, 2025
abac4ab
Merge branch '1.x' into asg_target_checking
gregharvey Mar 4, 2025
28996e4
Adding a pause to not hammer the API.
gregharvey Mar 4, 2025
c67ef1d
Missed a variable update.
gregharvey Mar 4, 2025
2b1946f
Adding some debug lines.
gregharvey Mar 4, 2025
d8fefea
Fixing boto profile handling.
gregharvey Mar 4, 2025
b3842eb
Adding extra build_completed flag to control when ASG processes are a…
gregharvey Mar 4, 2025
04f3f47
Docs and switching to using a lock file instead of a variable, which …
gregharvey Mar 4, 2025
773e74a
Suppressing cachetool installation on deploy servers in example play.
gregharvey Mar 4, 2025
2e1bf64
Docs slight improvement.
gregharvey Mar 4, 2025
08ac22b
Slightly improved ASG handling, dynamic by build_type.
gregharvey Mar 4, 2025
e13cd99
Re-adding reminder to refactor.
gregharvey Mar 12, 2025
8ccafd8
Adding verbose option for ASG role.
gregharvey Mar 12, 2025
5680bb3
Simplifying role by removing dependency on lock files.
gregharvey Mar 13, 2025
3d31295
Verbose var now obsolete.
gregharvey Mar 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions roles/_exit/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
---
# If we are operating on an AWS ASG then resume autoscaling.
- name: Enable all autoscale processes on ASG.
ansible.builtin.command: >
aws autoscaling resume-processes --auto-scaling-group-name {{ aws_asg.name }} --region {{ aws_asg.region }}
delegate_to: localhost
run_once: true
when:
- aws_asg.name is defined
- aws_asg.name | length > 0

- name: Delete the lock file.
ansible.builtin.file:
path: "{{ lock_file }}"
state: absent
when: deploy_operation != 'deploy'
8 changes: 0 additions & 8 deletions roles/_init/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@ Mandatory role that must run before any other `ce-deploy` roles when executing a

These variables **must** be set in a common variables file if you do not wish to use defaults.

In order to manipulate an AWS Autoscaling Group (ASG) your `deploy` user must have an AWS CLI profile for a user with the following IAM permissions:
* `autoscaling:ResumeProcesses`
* `autoscaling:SuspendProcesses`
* `autoscaling:DescribeScalingProcessTypes`
* `autoscaling:DescribeAutoScalingGroups`

Set the `aws_asg.name` to the machine name of your ASG in order to automatically suspend and resume autoscaling on build.

<!--TOC-->
<!--ENDTOC-->

Expand Down
5 changes: 0 additions & 5 deletions roles/_init/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ install_php_cachetool: true # set to false if you don't need cachetool, e.g. for
ce_deploy_version: 1.x
lock_file: /tmp/ce-deploy-lock
provision_lock_file: /tmp/ce-provision-lock # must match _init.lock_file in ce-provision
# AWS ASG variables to allow for the suspension of autoscaling during a code deployment.
aws_asg:
name: "" # if the deploy is on an ASG put the name here
region: "eu-west-1"
suspend_processes: "Launch Terminate" # space separated string, see https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html
# Application specific variables.
drupal:
drush_verbose_output: false
Expand Down
4 changes: 3 additions & 1 deletion roles/_init/tasks/drupal8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
- name: Ensure we have a cachetool binary.
ansible.builtin.import_role:
name: cli/cachetool
when: install_php_cachetool
when:
- install_php_cachetool
- deploy_operation == 'deploy'
20 changes: 5 additions & 15 deletions roles/_init/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
path: "{{ lock_file }}"
state: touch
mode: 0644
when: deploy_operation == 'deploy'

# Ensure default values for common variables.
- name: Define deploy user.
Expand Down Expand Up @@ -70,6 +71,9 @@
- cache_clear_opcache.cachetool_bin | length > 0

- name: Manipulate variables for SquashFS builds.
when:
- deploy_code.mount_type is defined
- deploy_code.mount_type == "squashfs"
block:
- name: Define image builds base path.
ansible.builtin.set_fact:
Expand Down Expand Up @@ -97,9 +101,6 @@
ansible.builtin.file:
path: "{{ build_base_path }}"
state: directory
when:
- deploy_code.mount_type is defined
- deploy_code.mount_type == "squashfs"

- name: Set the previous deploy's path for later use where we need to manipulate the live site.
ansible.builtin.set_fact:
Expand Down Expand Up @@ -140,21 +141,10 @@
ansible.builtin.stat:
path: "{{ role_path }}/tasks/{{ project_type }}.yml"
register: _project_type_task_result
delegate_to: "localhost"
delegate_to: localhost

# Project specific init tasks.
- name: Include project init tasks.
ansible.builtin.include_tasks: "{{ project_type }}.yml"
when:
- _project_type_task_result.stat.exists

# If we are operating on an AWS ASG then pause autoscaling.
# @TODO - the autoscaling_group module can do this - https://docs.ansible.com/ansible/latest/collections/amazon/aws/autoscaling_group_module.html
- name: Disable autoscale processes on ASG.
ansible.builtin.command: >
aws autoscaling suspend-processes --auto-scaling-group-name {{ aws_asg.name }} --scaling-processes {{ aws_asg.suspend_processes }} --region {{ aws_asg.region }}
delegate_to: localhost
run_once: true
when:
- aws_asg.name is defined
- aws_asg.name | length > 0
64 changes: 64 additions & 0 deletions roles/asg_management/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# ASG Management
This role should be called in a separate playbook, e.g. a separate command in CI, to the rest of the build with the host set as `localhost` and not the target ASG group. The available hosts in the ASG group may change after it has run, so Ansible needs to interogate the ASG host group after this play has run and before building. This cannot be done unless Ansible stops and starts again.

In order to manipulate an AWS Autoscaling Group (ASG) your `deploy` user must have an AWS CLI profile for a user with the following IAM permissions:
* `autoscaling:ResumeProcesses`
* `autoscaling:SuspendProcesses`
* `autoscaling:DescribeScalingProcessTypes`
* `autoscaling:DescribeAutoScalingGroups`

Set the `asg_management.name` to the machine name of your ASG in order to automatically suspend and resume autoscaling on build.

## Recommended playbook setup
To use this role the recommended approach is two different playbooks and separate Ansible commands. Don't forget to add the `asg_management` variables to your variables file as well. Below you will find a GitLab CI example and suggested variables.

### `.gitlab-ci.yml`

```yaml
---
stages:
- deploy

deploy_dev:
stage: deploy
script:
- /bin/sh /home/deploy/ce-deploy/scripts/deploy.sh --workspace "$CI_PROJECT_DIR" --playbook deploy/asg.yml --ansible-extra-vars "{\"build_type\":\"$BUILD_TYPE\",\"install_php_cachetool\":false}" --build-number ${CI_PIPELINE_IID} --build-id acme-dev --boto-profile acme
- /bin/sh /home/deploy/ce-deploy/scripts/build.sh --workspace "$CI_PROJECT_DIR" --playbook deploy/deploy-dev.yml --build-number ${CI_PIPELINE_IID} --build-id acme-dev --boto-profile acme
- /bin/sh /home/deploy/ce-deploy/scripts/cleanup.sh --workspace "$CI_PROJECT_DIR" --playbook deploy/asg.yml --ansible-extra-vars "{\"build_type\":\"$BUILD_TYPE\",\"install_php_cachetool\":false}" --build-number ${CI_PIPELINE_IID} --build-id acme-dev --boto-profile acme
rules:
- if: '$CI_PIPELINE_SOURCE != "web" && $CI_COMMIT_BRANCH == "dev"'
- if: '$CI_PIPELINE_SOURCE == "web" && $SYNC == "no" && $BUILD_TYPE == "dev"'
```

### `asg.yml`

```yaml
---
- hosts: localhost
vars_files:
- vars/common.yml
- "vars/{{ build_type }}.yml"
roles:
- asg_management
```

### `deploy-dev.yml`

```yaml
---
- hosts: _ce_www_acme_codeenigma_net # ASG group name from EC2 discovery
vars_files:
- vars/common.yml
- vars/dev.yml
roles:
- _meta/deploy-drupal8
```

### Process explained
The example is a single development environment Drupal build. Your CI will call `asg.yml` with the `deploy.sh` script which will *only* run the `deploy` operation, therefore it will try to suspend ASG processes and wait until the ASG has settled down before continuing. After that we call a normal Drupal build, `deploy-dev.yml`, same as you would if it were a standalone server or a static cluster. Finally, we call `asg.yml` again but this time with the `cleanup.sh` script which will *only* run the `cleanup` operation, therefore it will re-enable the suspended ASG processes.

<!--TOC-->
<!--ENDTOC-->

<!--ROLEVARS-->
<!--ENDROLEVARS-->
8 changes: 8 additions & 0 deletions roles/asg_management/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# AWS ASG variables to allow for the suspension of autoscaling during a code deployment.
asg_management:
name: "" # if the deploy is on an ASG put the name here
#profile: "example" # optional, the boto profile name to use if not the system default
region: "eu-west-1"
suspend_processes: "Launch Terminate HealthCheck" # space separated string, see https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-suspend-resume-processes.html
pause: 5 # how long in seconds to wait before polling the AWS API again for instance statuses
52 changes: 52 additions & 0 deletions roles/asg_management/tasks/asg_target_health.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
- name: Pause so as to not hammer the AWS API.
ansible.builtin.pause:
seconds: "{{ asg_management.pause }}"

- name: Empty the target list.
ansible.builtin.set_fact:
_target_health_list: []

- name: Fetch target instance data.
when:
- _target_group.target_groups | length > 0
- asg_management.profile is not defined
block:
- name: Fetch raw target health data with the AWS CLI.
ansible.builtin.command: >-
aws elbv2 describe-target-health
--target-group-arn {{ _target_group.target_groups[0].target_group_arn }}
--region {{ asg_management.region }}
register: _target_health

- name: Convert raw JSON output from CLI to YAML for ease of use.
ansible.builtin.set_fact:
_target_health_yaml: "{{ _target_health.stdout | from_json }}"

- name: Fetch target instance data with a boto profile.
when:
- _target_group.target_groups | length > 0
- asg_management.profile is defined
- asg_management.profile | length > 0
block:
- name: Fetch raw target health data with the AWS CLI via a specified boto profile.
ansible.builtin.command: >-
aws elbv2 describe-target-health
--target-group-arn {{ _target_group.target_groups[0].target_group_arn }}
--region {{ asg_management.region }}
--profile {{ asg_management.profile }}
register: _target_health_boto

- name: Convert raw JSON output from CLI to YAML for ease of use.
ansible.builtin.set_fact:
_target_health_yaml: "{{ _target_health_boto.stdout | from_json }}"

- name: Make a list of all the 'healthy' instances.
ansible.builtin.set_fact:
_target_health_list: "{{ _target_health_list + [ item.TargetHealth.State ] }}"
with_items: "{{ _target_health_yaml.TargetHealthDescriptions }}"
when: item.TargetHealth.State == 'healthy'

- name: Run again if the number of 'healthy' instances does not match the total.
ansible.builtin.include_tasks: "./asg_target_health.yml"
when: (_target_health_yaml.TargetHealthDescriptions | length) != (_target_health_list | length)
65 changes: 65 additions & 0 deletions roles/asg_management/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
- name: Suspend autoscale processes on ASG.
when:
- asg_management.name | length > 0
- deploy_operation == 'deploy'
delegate_to: localhost
run_once: true
block:
# @TODO - the autoscaling_group module can do this - https://docs.ansible.com/ansible/latest/collections/amazon/aws/autoscaling_group_module.html
- name: Suspend autoscale processes on ASG via a specified boto profile.
ansible.builtin.command: >-
aws autoscaling suspend-processes
--auto-scaling-group-name {{ asg_management.name }}
--scaling-processes {{ asg_management.suspend_processes }}
--region {{ asg_management.region }}
--profile {{ asg_management.profile }}
when:
- asg_management.profile is defined
- asg_management.profile | length > 0

- name: Suspend autoscale processes on ASG.
ansible.builtin.command: >-
aws autoscaling suspend-processes
--auto-scaling-group-name {{ asg_management.name }}
--scaling-processes {{ asg_management.suspend_processes }}
--region {{ asg_management.region }}
when:
- asg_management.profile is not defined

- name: Gather information about target group.
community.aws.elb_target_group_info:
region: "{{ asg_management.region }}"
profile: "{{ asg_management.profile | default(omit) }}"
names:
- "{{ asg_management.name }}"
register: _target_group

- name: Loop over target instances until they are all 'healthy'.
ansible.builtin.include_tasks: asg_target_health.yml

- name: Resume all autoscale processes on ASG.
ansible.builtin.command: >-
aws autoscaling resume-processes
--auto-scaling-group-name {{ asg_management.name }}
--region {{ asg_management.region }}
delegate_to: localhost
run_once: true
when:
- asg_management.name | length > 0
- asg_management.profile is not defined
- deploy_operation != 'deploy'

- name: Resume all autoscale processes on ASG via a specified boto profile.
ansible.builtin.command: >-
aws autoscaling resume-processes
--auto-scaling-group-name {{ asg_management.name }}
--region {{ asg_management.region }}
--profile {{ asg_management.profile }}
delegate_to: localhost
run_once: true
when:
- asg_management.name | length > 0
- asg_management.profile is defined
- asg_management.profile | length > 0
- deploy_operation != 'deploy'