From d7d8d40d83855f6e9e08f0db2179b767d3848969 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 14:53:10 +0100 Subject: [PATCH 01/21] First pass at waiting for healthy only instances. --- roles/_init/defaults/main.yml | 5 +++- roles/_init/tasks/asg_target_health.yml | 26 ++++++++++++++++++ roles/_init/tasks/main.yml | 36 +++++++++++++++++++++---- 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 roles/_init/tasks/asg_target_health.yml diff --git a/roles/_init/defaults/main.yml b/roles/_init/defaults/main.yml index 00c14563..355f0cb9 100644 --- a/roles/_init/defaults/main.yml +++ b/roles/_init/defaults/main.yml @@ -15,7 +15,10 @@ provision_lock_file: /tmp/ce-provision-lock # must match _init.lock_file in ce-p 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 + suspend_processes: + - Launch + - Terminate + - HealthCheck # Application specific variables. drupal: drush_verbose_output: false diff --git a/roles/_init/tasks/asg_target_health.yml b/roles/_init/tasks/asg_target_health.yml new file mode 100644 index 00000000..f19e905c --- /dev/null +++ b/roles/_init/tasks/asg_target_health.yml @@ -0,0 +1,26 @@ +--- +- name: Empty the target list. + ansible.builtin.set_fact: + _target_health_list: [] + +- name: Fetch raw target health data with the AWS CLI. + ansible.builtin.command: >- + aws --region {{ _aws_region }} elbv2 describe-target-health + --target-group-arn {{ _target_group.target_groups[0].target_group_arn }} + register: _target_health + when: _target_group.target_groups | length > 0 + +- 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: 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: If the number of 'healthy' instances matches the total, set the flag so we can continue. + ansible.builtin.set_fact: + _target_health_flag: true + when: (_target_health_yaml.TargetHealthDescriptions | length) == (_target_health_list | length) diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index 34340018..cac63a40 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -149,12 +149,38 @@ - _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 + block: + # This task can be removed in the future. + - name: Handle legacy suspend_processes variables. + ansible.builtin.set_fact: + aws_asg: + suspend_processes: "{{ aws_asg.suspend_processes | split }}" + when: aws_asg.suspend_processes | type_debug == 'string' + + - name: Disable autoscale processes on ASG. + amazon.aws.autoscaling_group: + name: "{{ aws_asg.name }}" + region: "{{ aws_asg.region }}" + suspend_processes: "{{ aws_asg.suspend_processes }}" + delegate_to: localhost + run_once: true + + - name: Gather information about target group. + community.aws.elb_target_group_info: + region: "{{ _aws_region }}" + profile: "{{ _aws_profile }}" + names: + - "{{ aws_asg.name }}" + register: _target_group + + - name: Set a flag variable for instance states. + ansible.builtin.set_fact: + _target_health_flag: false + + - name: Loop over target instances until they are all 'healthy'. + ansible.builtin.import_tasks: asg_target_health.yml + until: _target_health_flag From ce3497c4924a5674a69f6e16b0b96be6f460a1c1 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 14:55:19 +0100 Subject: [PATCH 02/21] Moving the delegate_to and run_once to the block. --- roles/_init/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index cac63a40..e9f971ef 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -153,6 +153,8 @@ when: - aws_asg.name is defined - aws_asg.name | length > 0 + delegate_to: localhost + run_once: true block: # This task can be removed in the future. - name: Handle legacy suspend_processes variables. @@ -166,8 +168,6 @@ name: "{{ aws_asg.name }}" region: "{{ aws_asg.region }}" suspend_processes: "{{ aws_asg.suspend_processes }}" - delegate_to: localhost - run_once: true - name: Gather information about target group. community.aws.elb_target_group_info: From 0e9c2968c8f21a6c466dae9ce7e976a381339c3a Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 15:46:04 +0100 Subject: [PATCH 03/21] Adding AnsibleUnicode as a possible var type. --- roles/_init/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index e9f971ef..bedd2c54 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -161,7 +161,7 @@ ansible.builtin.set_fact: aws_asg: suspend_processes: "{{ aws_asg.suspend_processes | split }}" - when: aws_asg.suspend_processes | type_debug == 'string' + when: aws_asg.suspend_processes | type_debug == ('string' or 'AnsibleUnicode') - name: Disable autoscale processes on ASG. amazon.aws.autoscaling_group: From ee7ad57fa71893b72f15da90c270160548e2077a Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 16:07:36 +0100 Subject: [PATCH 04/21] Fixing or logic in when clause for legacy strings. --- roles/_init/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index bedd2c54..447df9f5 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -161,7 +161,7 @@ ansible.builtin.set_fact: aws_asg: suspend_processes: "{{ aws_asg.suspend_processes | split }}" - when: aws_asg.suspend_processes | type_debug == ('string' or 'AnsibleUnicode') + when: (aws_asg.suspend_processes | type_debug == 'string') or (aws_asg.suspend_processes | type_debug == 'AnsibleUnicode') - name: Disable autoscale processes on ASG. amazon.aws.autoscaling_group: From 26403671b91d06ce2ae5bc4b2ae81844a9065c40 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 16:13:48 +0100 Subject: [PATCH 05/21] Reverting to using command and CLI for ASG processes, simpler for IAM perms. --- roles/_init/defaults/main.yml | 5 +---- roles/_init/tasks/main.yml | 13 ++----------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/roles/_init/defaults/main.yml b/roles/_init/defaults/main.yml index 355f0cb9..90a0bf03 100644 --- a/roles/_init/defaults/main.yml +++ b/roles/_init/defaults/main.yml @@ -15,10 +15,7 @@ provision_lock_file: /tmp/ce-provision-lock # must match _init.lock_file in ce-p aws_asg: name: "" # if the deploy is on an ASG put the name here region: "eu-west-1" - suspend_processes: - - Launch - - Terminate - - HealthCheck + suspend_processes: "Launch Terminate HealthCheck" # 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 diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index 447df9f5..1cf62fe7 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -156,18 +156,9 @@ delegate_to: localhost run_once: true block: - # This task can be removed in the future. - - name: Handle legacy suspend_processes variables. - ansible.builtin.set_fact: - aws_asg: - suspend_processes: "{{ aws_asg.suspend_processes | split }}" - when: (aws_asg.suspend_processes | type_debug == 'string') or (aws_asg.suspend_processes | type_debug == 'AnsibleUnicode') - - name: Disable autoscale processes on ASG. - amazon.aws.autoscaling_group: - name: "{{ aws_asg.name }}" - region: "{{ aws_asg.region }}" - suspend_processes: "{{ aws_asg.suspend_processes }}" + ansible.builtin.command: > + aws autoscaling suspend-processes --auto-scaling-group-name {{ aws_asg.name }} --scaling-processes {{ aws_asg.suspend_processes }} --region {{ aws_asg.region }} - name: Gather information about target group. community.aws.elb_target_group_info: From 0c4bcfae5b01affcec8e11626723244d4533c374 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 16:18:50 +0100 Subject: [PATCH 06/21] Fixing region variable for AWS. --- roles/_init/tasks/asg_target_health.yml | 2 +- roles/_init/tasks/main.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/_init/tasks/asg_target_health.yml b/roles/_init/tasks/asg_target_health.yml index f19e905c..e49943b2 100644 --- a/roles/_init/tasks/asg_target_health.yml +++ b/roles/_init/tasks/asg_target_health.yml @@ -5,7 +5,7 @@ - name: Fetch raw target health data with the AWS CLI. ansible.builtin.command: >- - aws --region {{ _aws_region }} elbv2 describe-target-health + aws --region {{ aws_asg.region }} elbv2 describe-target-health --target-group-arn {{ _target_group.target_groups[0].target_group_arn }} register: _target_health when: _target_group.target_groups | length > 0 diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index 1cf62fe7..82e63ca9 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -162,8 +162,8 @@ - name: Gather information about target group. community.aws.elb_target_group_info: - region: "{{ _aws_region }}" - profile: "{{ _aws_profile }}" + region: "{{ aws_asg.region }}" + profile: "{{ aws_asg.profile | default(omit) }}" names: - "{{ aws_asg.name }}" register: _target_group From f0c52c927a2f247a78b825e6ddb911e0ff194b86 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Mon, 3 Mar 2025 16:28:33 +0100 Subject: [PATCH 07/21] Fine tuning when ASG manipulation occurs. --- roles/_exit/tasks/main.yml | 1 + roles/_init/tasks/main.yml | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/roles/_exit/tasks/main.yml b/roles/_exit/tasks/main.yml index 981986e0..ed486db7 100644 --- a/roles/_exit/tasks/main.yml +++ b/roles/_exit/tasks/main.yml @@ -8,6 +8,7 @@ when: - aws_asg.name is defined - aws_asg.name | length > 0 + - deploy_operation != 'deploy' - name: Delete the lock file. ansible.builtin.file: diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index 82e63ca9..cde8b6c0 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -70,6 +70,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: @@ -97,9 +100,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: @@ -153,6 +153,7 @@ when: - aws_asg.name is defined - aws_asg.name | length > 0 + - deploy_operation == 'deploy' delegate_to: localhost run_once: true block: From 430315db079cb4cf07b81158b688e3527600ec07 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 12:03:30 +0100 Subject: [PATCH 08/21] Moving ASG parts to another role so we can call them separately. --- roles/_exit/tasks/main.yml | 11 ---- roles/_init/README.md | 8 --- roles/_init/defaults/main.yml | 5 -- roles/_init/tasks/asg_target_health.yml | 26 -------- roles/_init/tasks/main.yml | 29 --------- roles/asg_management/README.md | 16 +++++ roles/asg_management/defaults/main.yml | 7 ++ .../tasks/asg_target_health.yml | 40 ++++++++++++ roles/asg_management/tasks/main.yml | 64 +++++++++++++++++++ 9 files changed, 127 insertions(+), 79 deletions(-) delete mode 100644 roles/_init/tasks/asg_target_health.yml create mode 100644 roles/asg_management/README.md create mode 100644 roles/asg_management/defaults/main.yml create mode 100644 roles/asg_management/tasks/asg_target_health.yml create mode 100644 roles/asg_management/tasks/main.yml diff --git a/roles/_exit/tasks/main.yml b/roles/_exit/tasks/main.yml index ed486db7..dce19c32 100644 --- a/roles/_exit/tasks/main.yml +++ b/roles/_exit/tasks/main.yml @@ -1,15 +1,4 @@ --- -# 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 - - deploy_operation != 'deploy' - - name: Delete the lock file. ansible.builtin.file: path: "{{ lock_file }}" diff --git a/roles/_init/README.md b/roles/_init/README.md index a1e37f95..7338e398 100644 --- a/roles/_init/README.md +++ b/roles/_init/README.md @@ -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. - diff --git a/roles/_init/defaults/main.yml b/roles/_init/defaults/main.yml index 90a0bf03..807ee9c6 100644 --- a/roles/_init/defaults/main.yml +++ b/roles/_init/defaults/main.yml @@ -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 HealthCheck" # 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 diff --git a/roles/_init/tasks/asg_target_health.yml b/roles/_init/tasks/asg_target_health.yml deleted file mode 100644 index e49943b2..00000000 --- a/roles/_init/tasks/asg_target_health.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -- name: Empty the target list. - ansible.builtin.set_fact: - _target_health_list: [] - -- name: Fetch raw target health data with the AWS CLI. - ansible.builtin.command: >- - aws --region {{ aws_asg.region }} elbv2 describe-target-health - --target-group-arn {{ _target_group.target_groups[0].target_group_arn }} - register: _target_health - when: _target_group.target_groups | length > 0 - -- 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: 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: If the number of 'healthy' instances matches the total, set the flag so we can continue. - ansible.builtin.set_fact: - _target_health_flag: true - when: (_target_health_yaml.TargetHealthDescriptions | length) == (_target_health_list | length) diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index cde8b6c0..09bf1182 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -147,32 +147,3 @@ 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. -- name: Disable autoscale processes on ASG. - when: - - aws_asg.name is defined - - aws_asg.name | length > 0 - - deploy_operation == 'deploy' - delegate_to: localhost - run_once: true - block: - - 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 }} - - - name: Gather information about target group. - community.aws.elb_target_group_info: - region: "{{ aws_asg.region }}" - profile: "{{ aws_asg.profile | default(omit) }}" - names: - - "{{ aws_asg.name }}" - register: _target_group - - - name: Set a flag variable for instance states. - ansible.builtin.set_fact: - _target_health_flag: false - - - name: Loop over target instances until they are all 'healthy'. - ansible.builtin.import_tasks: asg_target_health.yml - until: _target_health_flag diff --git a/roles/asg_management/README.md b/roles/asg_management/README.md new file mode 100644 index 00000000..0822134c --- /dev/null +++ b/roles/asg_management/README.md @@ -0,0 +1,16 @@ +# ASG Management +This role should be called in a separate playbook to the rest of the build with the host set as `localhost`, 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. + +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. + + + + + + diff --git a/roles/asg_management/defaults/main.yml b/roles/asg_management/defaults/main.yml new file mode 100644 index 00000000..33c11e2a --- /dev/null +++ b/roles/asg_management/defaults/main.yml @@ -0,0 +1,7 @@ +--- +# 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 diff --git a/roles/asg_management/tasks/asg_target_health.yml b/roles/asg_management/tasks/asg_target_health.yml new file mode 100644 index 00000000..a900b3a5 --- /dev/null +++ b/roles/asg_management/tasks/asg_target_health.yml @@ -0,0 +1,40 @@ +--- +- name: Empty the target list. + ansible.builtin.set_fact: + _target_health_list: [] + +- 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 {{ aws_asg.region }} + register: _target_health + when: + - _target_group.target_groups | length > 0 + - asg_management.profile is not defined + +- 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 {{ aws_asg.region }} + --profile {{ asg_management.profile }} + register: _target_health + when: + - _target_group.target_groups | length > 0 + - asg_management.profile is defined + - asg_management.profile | length > 0 + +- 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: 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) diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml new file mode 100644 index 00000000..5803e5cb --- /dev/null +++ b/roles/asg_management/tasks/main.yml @@ -0,0 +1,64 @@ +--- +- name: Suspend autoscale processes on ASG. + when: + - aws_asg.name | length > 0 + - deploy_operation == 'deploy' + delegate_to: localhost + run_once: true + block: + - 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' From 28996e433096f5e56bfed9cfa9dc5abf3b413ede Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 12:10:57 +0100 Subject: [PATCH 09/21] Adding a pause to not hammer the API. --- roles/asg_management/defaults/main.yml | 1 + roles/asg_management/tasks/asg_target_health.yml | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/roles/asg_management/defaults/main.yml b/roles/asg_management/defaults/main.yml index 33c11e2a..67371acd 100644 --- a/roles/asg_management/defaults/main.yml +++ b/roles/asg_management/defaults/main.yml @@ -5,3 +5,4 @@ asg_management: #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 diff --git a/roles/asg_management/tasks/asg_target_health.yml b/roles/asg_management/tasks/asg_target_health.yml index a900b3a5..3c7bd328 100644 --- a/roles/asg_management/tasks/asg_target_health.yml +++ b/roles/asg_management/tasks/asg_target_health.yml @@ -1,4 +1,8 @@ --- +- 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: [] @@ -7,7 +11,7 @@ ansible.builtin.command: >- aws elbv2 describe-target-health --target-group-arn {{ _target_group.target_groups[0].target_group_arn }} - --region {{ aws_asg.region }} + --region {{ asg_management.region }} register: _target_health when: - _target_group.target_groups | length > 0 @@ -17,7 +21,7 @@ ansible.builtin.command: >- aws elbv2 describe-target-health --target-group-arn {{ _target_group.target_groups[0].target_group_arn }} - --region {{ aws_asg.region }} + --region {{ asg_management.region }} --profile {{ asg_management.profile }} register: _target_health when: From c67ef1d601b5cedcd01d0c476c454b2a955c50f4 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 17:36:05 +0100 Subject: [PATCH 10/21] Missed a variable update. --- roles/asg_management/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index 5803e5cb..9c9cd967 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Suspend autoscale processes on ASG. when: - - aws_asg.name | length > 0 + - asg_management.name | length > 0 - deploy_operation == 'deploy' delegate_to: localhost run_once: true From 2b1946faed2590b8bffb23da11e1843da235f3c4 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 17:43:59 +0100 Subject: [PATCH 11/21] Adding some debug lines. --- roles/asg_management/tasks/asg_target_health.yml | 4 ++++ roles/asg_management/tasks/main.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/roles/asg_management/tasks/asg_target_health.yml b/roles/asg_management/tasks/asg_target_health.yml index 3c7bd328..743d59ba 100644 --- a/roles/asg_management/tasks/asg_target_health.yml +++ b/roles/asg_management/tasks/asg_target_health.yml @@ -29,6 +29,10 @@ - asg_management.profile is defined - asg_management.profile | length > 0 +- name: Check the targets variable. + ansible.builtin.debug: + msg: "{{ _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 }}" diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index 9c9cd967..0b5c313e 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -34,6 +34,10 @@ - "{{ asg_management.name }}" register: _target_group + - name: Check the target group ARN. + ansible.builtin.debug: + msg: "{{ _target_group }}" + - name: Loop over target instances until they are all 'healthy'. ansible.builtin.include_tasks: asg_target_health.yml From d8fefea75400bc9b1666da752fc9157d2760c05d Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 17:52:11 +0100 Subject: [PATCH 12/21] Fixing boto profile handling. --- .../tasks/asg_target_health.yml | 44 ++++++++++--------- roles/asg_management/tasks/main.yml | 4 -- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/roles/asg_management/tasks/asg_target_health.yml b/roles/asg_management/tasks/asg_target_health.yml index 743d59ba..78f3b21b 100644 --- a/roles/asg_management/tasks/asg_target_health.yml +++ b/roles/asg_management/tasks/asg_target_health.yml @@ -7,35 +7,39 @@ ansible.builtin.set_fact: _target_health_list: [] -- 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: 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: 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 + - 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: Check the targets variable. - ansible.builtin.debug: - msg: "{{ _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: 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: diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index 0b5c313e..9c9cd967 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -34,10 +34,6 @@ - "{{ asg_management.name }}" register: _target_group - - name: Check the target group ARN. - ansible.builtin.debug: - msg: "{{ _target_group }}" - - name: Loop over target instances until they are all 'healthy'. ansible.builtin.include_tasks: asg_target_health.yml From b3842eb24c63af8aac61ebfd96bf1f96d6e237b1 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 18:05:31 +0100 Subject: [PATCH 13/21] Adding extra build_completed flag to control when ASG processes are altered. --- roles/_exit/tasks/main.yml | 5 +++++ roles/_init/tasks/main.yml | 5 +++++ roles/asg_management/tasks/main.yml | 7 +++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/roles/_exit/tasks/main.yml b/roles/_exit/tasks/main.yml index dce19c32..7b7a3eb3 100644 --- a/roles/_exit/tasks/main.yml +++ b/roles/_exit/tasks/main.yml @@ -3,3 +3,8 @@ ansible.builtin.file: path: "{{ lock_file }}" state: absent + +- name: Signal to the ASG role the build is complete. + ansible.builtin.set_fact: + build_completed: true + when: deploy_operation != 'deploy' diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index 09bf1182..8bf9cc72 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -24,6 +24,11 @@ state: touch mode: 0644 +- name: Signal to the ASG role the build is beginning. + ansible.builtin.set_fact: + build_completed: false + when: deploy_operation == 'deploy' + # Ensure default values for common variables. - name: Define deploy user. ansible.builtin.set_fact: diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index 9c9cd967..97558fed 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -3,6 +3,7 @@ when: - asg_management.name | length > 0 - deploy_operation == 'deploy' + - build_completed is not defined delegate_to: localhost run_once: true block: @@ -47,7 +48,8 @@ when: - asg_management.name | length > 0 - asg_management.profile is not defined - - deploy_operation != 'deploy' + - build_completed is defined + - build_completed - name: Resume all autoscale processes on ASG via a specified boto profile. ansible.builtin.command: >- @@ -61,4 +63,5 @@ - asg_management.name | length > 0 - asg_management.profile is defined - asg_management.profile | length > 0 - - deploy_operation != 'deploy' + - build_completed is defined + - build_completed From 04f3f471cde7b2ab25ea604e2f64effe9f7a1057 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 18:33:37 +0100 Subject: [PATCH 14/21] Docs and switching to using a lock file instead of a variable, which doesn't traverse different plays. --- roles/_exit/tasks/main.yml | 4 --- roles/_init/tasks/drupal8.yml | 4 ++- roles/_init/tasks/main.yml | 6 +--- roles/asg_management/README.md | 52 +++++++++++++++++++++++++++++ roles/asg_management/tasks/main.yml | 13 +++++--- 5 files changed, 64 insertions(+), 15 deletions(-) diff --git a/roles/_exit/tasks/main.yml b/roles/_exit/tasks/main.yml index 7b7a3eb3..55676bad 100644 --- a/roles/_exit/tasks/main.yml +++ b/roles/_exit/tasks/main.yml @@ -3,8 +3,4 @@ ansible.builtin.file: path: "{{ lock_file }}" state: absent - -- name: Signal to the ASG role the build is complete. - ansible.builtin.set_fact: - build_completed: true when: deploy_operation != 'deploy' diff --git a/roles/_init/tasks/drupal8.yml b/roles/_init/tasks/drupal8.yml index eb3bdae9..ad15846f 100644 --- a/roles/_init/tasks/drupal8.yml +++ b/roles/_init/tasks/drupal8.yml @@ -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' diff --git a/roles/_init/tasks/main.yml b/roles/_init/tasks/main.yml index 8bf9cc72..b0c47a66 100644 --- a/roles/_init/tasks/main.yml +++ b/roles/_init/tasks/main.yml @@ -23,10 +23,6 @@ path: "{{ lock_file }}" state: touch mode: 0644 - -- name: Signal to the ASG role the build is beginning. - ansible.builtin.set_fact: - build_completed: false when: deploy_operation == 'deploy' # Ensure default values for common variables. @@ -145,7 +141,7 @@ 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. diff --git a/roles/asg_management/README.md b/roles/asg_management/README.md index 0822134c..a146ecdb 100644 --- a/roles/asg_management/README.md +++ b/roles/asg_management/README.md @@ -9,6 +9,58 @@ In order to manipulate an AWS Autoscaling Group (ASG) your `deploy` user must ha 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 three different playbooks. + +### `deploy-dev.yml` + +```yaml +--- +- name: Stop ASG processes. + ansible.builtin.import_playbook: asg-dev.yml + +- name: Build website. + ansible.builtin.import_playbook: build-dev.yml + +- name: Start ASG processes. + ansible.builtin.import_playbook: asg-dev.yml +``` + +### `asg-dev.yml` + +```yaml +--- +- hosts: localhost + vars_files: + - vars/common.yml + - vars/dev.yml + roles: + - _init + - asg_management + - _exit +``` + +### `build-dev.yml` + +```yaml +--- +- hosts: _dev_acme_com + vars_files: + - vars/common.yml + - vars/dev.yml + roles: + - _meta/deploy-drupal8 +``` + +### Process explained +Your CI will call `deploy-dev.yml`. This will run the ASG playbook, the `_init` role will set the ce-deploy lock file on localhost - the CI server itself - the `asg_management` role will check for that lock file and, if it exists, will suspend ASG processes. The `_exit` role will be executed, but will *not* remove the lock file because we are on the `deploy` operation. + +Then the build playbook is called and runs as normal on the ASG machines. + +Finally, the ASG playbook runs again. It will re-suspend the ASG for the same logical reason, which loses us a few seconds, but doesn't to any harm. Once again, we are still in the `deploy` operation so the lock file will not get deleted by `_exit` + +The bash script will then take us to either the `cleanup` or `revert` operation, depending on whether or not Ansible exited with an error code on the `deploy` operation. Either way, the ASG play will run and ASG processes will stay suspended, because the lock file is still there. At the end of the play the `_exit` role will delete the lock file on localhost (lock files on ASG servers still exist at this point). The build playbook will execute and run either cleanup or revert, depending on the operation, then the ASG playbook will run a final time. At this point the `asg_management` role will detect the lock file on localhost is no longer present and enable the ASG processes again. + diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index 97558fed..c2365463 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -1,9 +1,14 @@ --- +- name: Check for a ce-deploy lock file. + ansible.builtin.stat: + path: "{{ lock_file }}" + register: _ce_deploy_lock + - name: Suspend autoscale processes on ASG. when: - asg_management.name | length > 0 - deploy_operation == 'deploy' - - build_completed is not defined + - _ce_deploy_lock.stat.exists delegate_to: localhost run_once: true block: @@ -48,8 +53,7 @@ when: - asg_management.name | length > 0 - asg_management.profile is not defined - - build_completed is defined - - build_completed + - not _ce_deploy_lock.stat.exists - name: Resume all autoscale processes on ASG via a specified boto profile. ansible.builtin.command: >- @@ -63,5 +67,4 @@ - asg_management.name | length > 0 - asg_management.profile is defined - asg_management.profile | length > 0 - - build_completed is defined - - build_completed + - not _ce_deploy_lock.stat.exists From 773e74ae11f8ad16b148860bb7b6b2427974288c Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 18:55:06 +0100 Subject: [PATCH 15/21] Suppressing cachetool installation on deploy servers in example play. --- roles/asg_management/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/roles/asg_management/README.md b/roles/asg_management/README.md index a146ecdb..147e5397 100644 --- a/roles/asg_management/README.md +++ b/roles/asg_management/README.md @@ -18,12 +18,16 @@ To use this role the recommended approach is three different playbooks. --- - name: Stop ASG processes. ansible.builtin.import_playbook: asg-dev.yml + vars: + install_php_cachetool: false - name: Build website. ansible.builtin.import_playbook: build-dev.yml - name: Start ASG processes. ansible.builtin.import_playbook: asg-dev.yml + vars: + install_php_cachetool: false ``` ### `asg-dev.yml` From 2e1bf64aa9252db296ebb35cebd9b8fa3cc2adb7 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 19:16:21 +0100 Subject: [PATCH 16/21] Docs slight improvement. --- roles/asg_management/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/asg_management/README.md b/roles/asg_management/README.md index 147e5397..a08702d6 100644 --- a/roles/asg_management/README.md +++ b/roles/asg_management/README.md @@ -10,7 +10,7 @@ In order to manipulate an AWS Autoscaling Group (ASG) your `deploy` user must ha 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 three different playbooks. +To use this role the recommended approach is three different playbooks. Don't forget to add the `asg_management` variables to your variables file as well, see the defaults below for guidance. ### `deploy-dev.yml` From 08ac22bf87e91f4e9eb7e1959b5d12411015eb1a Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Tue, 4 Mar 2025 19:32:00 +0100 Subject: [PATCH 17/21] Slightly improved ASG handling, dynamic by build_type. --- roles/asg_management/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/roles/asg_management/README.md b/roles/asg_management/README.md index a08702d6..00e5393e 100644 --- a/roles/asg_management/README.md +++ b/roles/asg_management/README.md @@ -17,27 +17,29 @@ To use this role the recommended approach is three different playbooks. Don't fo ```yaml --- - name: Stop ASG processes. - ansible.builtin.import_playbook: asg-dev.yml + ansible.builtin.import_playbook: asg.yml vars: install_php_cachetool: false + build_type: dev - name: Build website. ansible.builtin.import_playbook: build-dev.yml - name: Start ASG processes. - ansible.builtin.import_playbook: asg-dev.yml + ansible.builtin.import_playbook: asg.yml vars: install_php_cachetool: false + build_type: dev ``` -### `asg-dev.yml` +### `asg.yml` ```yaml --- - hosts: localhost vars_files: - vars/common.yml - - vars/dev.yml + - "vars/{{ build_type }}.yml" roles: - _init - asg_management From e13cd99d55b728bd9dcda80b17a69bb3afba8a10 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Wed, 12 Mar 2025 16:58:44 +0100 Subject: [PATCH 18/21] Re-adding reminder to refactor. --- roles/asg_management/tasks/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index c2365463..b6111a02 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -12,6 +12,7 @@ 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 From 8ccafd896112b504b545bb70f1112fd40b09a300 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Wed, 12 Mar 2025 18:13:34 +0100 Subject: [PATCH 19/21] Adding verbose option for ASG role. --- roles/asg_management/defaults/main.yml | 1 + roles/asg_management/tasks/main.yml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/roles/asg_management/defaults/main.yml b/roles/asg_management/defaults/main.yml index 67371acd..289906cf 100644 --- a/roles/asg_management/defaults/main.yml +++ b/roles/asg_management/defaults/main.yml @@ -6,3 +6,4 @@ asg_management: 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 + verbose: false # set to true for debug output diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index b6111a02..97562324 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -4,6 +4,11 @@ path: "{{ lock_file }}" register: _ce_deploy_lock +- name: Show stat output. + ansible.builtin.debug: + msg: "{{ _ce_deploy_lock }}" + when: asg_management.verbose + - name: Suspend autoscale processes on ASG. when: - asg_management.name | length > 0 From 5680bb36f2ca3891e6a761c51ce3bfa567e35422 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Thu, 13 Mar 2025 11:12:31 +0100 Subject: [PATCH 20/21] Simplifying role by removing dependency on lock files. --- roles/asg_management/README.md | 46 +++++++++++------------------ roles/asg_management/tasks/main.yml | 15 ++-------- 2 files changed, 20 insertions(+), 41 deletions(-) diff --git a/roles/asg_management/README.md b/roles/asg_management/README.md index 00e5393e..10dc1a79 100644 --- a/roles/asg_management/README.md +++ b/roles/asg_management/README.md @@ -1,5 +1,5 @@ # ASG Management -This role should be called in a separate playbook to the rest of the build with the host set as `localhost`, 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 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` @@ -10,26 +10,24 @@ In order to manipulate an AWS Autoscaling Group (ASG) your `deploy` user must ha 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 three different playbooks. Don't forget to add the `asg_management` variables to your variables file as well, see the defaults below for guidance. +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. -### `deploy-dev.yml` +### `.gitlab-ci.yml` ```yaml --- -- name: Stop ASG processes. - ansible.builtin.import_playbook: asg.yml - vars: - install_php_cachetool: false - build_type: dev - -- name: Build website. - ansible.builtin.import_playbook: build-dev.yml - -- name: Start ASG processes. - ansible.builtin.import_playbook: asg.yml - vars: - install_php_cachetool: false - build_type: dev +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` @@ -41,16 +39,14 @@ To use this role the recommended approach is three different playbooks. Don't fo - vars/common.yml - "vars/{{ build_type }}.yml" roles: - - _init - asg_management - - _exit ``` -### `build-dev.yml` +### `deploy-dev.yml` ```yaml --- -- hosts: _dev_acme_com +- hosts: _ce_www_acme_codeenigma_net # ASG group name from EC2 discovery vars_files: - vars/common.yml - vars/dev.yml @@ -59,13 +55,7 @@ To use this role the recommended approach is three different playbooks. Don't fo ``` ### Process explained -Your CI will call `deploy-dev.yml`. This will run the ASG playbook, the `_init` role will set the ce-deploy lock file on localhost - the CI server itself - the `asg_management` role will check for that lock file and, if it exists, will suspend ASG processes. The `_exit` role will be executed, but will *not* remove the lock file because we are on the `deploy` operation. - -Then the build playbook is called and runs as normal on the ASG machines. - -Finally, the ASG playbook runs again. It will re-suspend the ASG for the same logical reason, which loses us a few seconds, but doesn't to any harm. Once again, we are still in the `deploy` operation so the lock file will not get deleted by `_exit` - -The bash script will then take us to either the `cleanup` or `revert` operation, depending on whether or not Ansible exited with an error code on the `deploy` operation. Either way, the ASG play will run and ASG processes will stay suspended, because the lock file is still there. At the end of the play the `_exit` role will delete the lock file on localhost (lock files on ASG servers still exist at this point). The build playbook will execute and run either cleanup or revert, depending on the operation, then the ASG playbook will run a final time. At this point the `asg_management` role will detect the lock file on localhost is no longer present and enable the ASG processes again. +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. diff --git a/roles/asg_management/tasks/main.yml b/roles/asg_management/tasks/main.yml index 97562324..3d034470 100644 --- a/roles/asg_management/tasks/main.yml +++ b/roles/asg_management/tasks/main.yml @@ -1,19 +1,8 @@ --- -- name: Check for a ce-deploy lock file. - ansible.builtin.stat: - path: "{{ lock_file }}" - register: _ce_deploy_lock - -- name: Show stat output. - ansible.builtin.debug: - msg: "{{ _ce_deploy_lock }}" - when: asg_management.verbose - - name: Suspend autoscale processes on ASG. when: - asg_management.name | length > 0 - deploy_operation == 'deploy' - - _ce_deploy_lock.stat.exists delegate_to: localhost run_once: true block: @@ -59,7 +48,7 @@ when: - asg_management.name | length > 0 - asg_management.profile is not defined - - not _ce_deploy_lock.stat.exists + - deploy_operation != 'deploy' - name: Resume all autoscale processes on ASG via a specified boto profile. ansible.builtin.command: >- @@ -73,4 +62,4 @@ - asg_management.name | length > 0 - asg_management.profile is defined - asg_management.profile | length > 0 - - not _ce_deploy_lock.stat.exists + - deploy_operation != 'deploy' From 3d31295e700f8bc90fe7ad27e46fffbea1d214c6 Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Thu, 13 Mar 2025 11:33:14 +0100 Subject: [PATCH 21/21] Verbose var now obsolete. --- roles/asg_management/defaults/main.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/roles/asg_management/defaults/main.yml b/roles/asg_management/defaults/main.yml index 289906cf..67371acd 100644 --- a/roles/asg_management/defaults/main.yml +++ b/roles/asg_management/defaults/main.yml @@ -6,4 +6,3 @@ asg_management: 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 - verbose: false # set to true for debug output