feat(ansible,awx): clone ec2 instances

This commit is contained in:
Michele Cereda
2024-08-16 19:54:24 +02:00
parent 36b28f4d32
commit 7c2f24966c
7 changed files with 229 additions and 4 deletions

View File

@@ -0,0 +1,130 @@
---
- name: Properly clone a running EC2 instance
hosts: localhost
gather_facts: false
vars:
original_instance_name_tag: "Use ME!!"
pre_tasks:
- name: Get information from the original instance
tags:
- gather_information
- pre_task
amazon.aws.ec2_instance_info:
filters:
"tag:Name": "{{ original_instance_name_tag }}"
"instance-state-name": [ "running" ]
register: original_instance_information
- name: Check a running instance with Name tag '{{ original_instance_name_tag }}' has been found
tags:
- gather_information
- pre_task
ansible.builtin.assert:
that: original_instance_information.instances | length > 0
fail_msg: No running instances found with Name tag '{{ original_instance_name_tag }}'
success_msg: At least one running instance has been found with Name tag '{{ original_instance_name_tag }}'
tasks:
- name: Create a Security Group with only the connections required for testing
tags: security_group
amazon.aws.ec2_security_group:
name: Clone EC2 Instance SG
description: Temporary SG for cloning EC2 Instances
rules_egress:
- cidr_ip: 0.0.0.0/0
ports: 443
rule_desc: Required by SSM, but could be stricter
register: clone_instance_security_group_information
- name: Create snapshots of the instance's volumes
# Allows for more control over the snapshot, namely to avoid recreating snapshot of massive volumes and lose hours
tags: snapshot
amazon.aws.ec2_snapshot:
volume_id: "{{ item }}"
description: Temporary snapshot for cloning EC2 Instances
last_snapshot_min_age: 1440 # 1d
wait_timeout: 7200 # 2h might still be not enough for big boi volumes
loop: "{{ original_instance_information.instances[0].block_device_mappings | map(attribute='ebs.volume_id') }}"
register: original_instance_snapshots
- name: Create an AMI from the snapshot
tags: ami
amazon.aws.ec2_ami:
# no_reboot: false # set to true if one does *not* want to have the original instance shut down
wait: true
name: temp-{{ original_instance_name_tag | regex_replace(' ', '-') | lower }}-ami
description: Temporary AMI for cloning EC2 Instances
tags:
Name: Clone EC2 Instance AMI
root_device_name: "{{ original_instance_information.instances[0].root_device_name }}"
device_mapping: >-
{%- set devices_list = [] -%}
{%- for result in original_instance_snapshots.results -%}
{%- for device in original_instance_information.instances[0].block_device_mappings
| selectattr('ebs.volume_id', 'equalto', result.volume_id) -%}
{{-
devices_list.append({
'device_name': device.device_name,
'snapshot_id': result.snapshots | sort(attribute='start_time') | last | json_query('snapshot_id'),
'volume_type': 'gp3',
'delete_on_termination': true,
})
-}}
{%- endfor -%}
{%- endfor -%}
{{ devices_list }}
register: original_instance_ami
- name: Use the AMI to launch a clone
tags:
- clone
- instance
when: original_instance_ami.image_id is defined
amazon.aws.ec2_instance:
name: Clone EC2 Instance
vpc_subnet_id: "{{ original_instance_information.instances[0].subnet_id }}"
instance_type: "{{ original_instance_information.instances[0].instance_type }}"
image:
id: "{{ original_instance_ami.image_id }}"
security_group: "{{ clone_instance_security_group_information.group_id }}"
iam_instance_profile: "{{ original_instance_information.instances[0].iam_instance_profile.arn }}"
register: clone_instance_information
- name: Wait for the instance to be ready
tags:
- clone
- instance
- check
when: clone_instance_information.instance_ids is defined
block:
- name: Just pause enough for the instance to initialize
# Because of course there seems to be no effing way to distinguish between just running and ready, and of
# course the SSM connection plugin crashes badly instead of just erroring the task out (ノಠ益ಠ)ノ彡┻━┻
ansible.builtin.pause:
minutes: 3
- name: Try connecting with SSM
delegate_to: "{{ clone_instance_information.instances[0].instance_id }}"
vars:
ansible_connection: community.aws.aws_ssm
ansible_aws_ssm_bucket_name: some-bucket
ansible_aws_ssm_region: eu-west-1
ansible_aws_ssm_timeout: 300
ansible.builtin.ping:
- name: Ready!
ansible.builtin.debug:
msg: The clone instance is ready!
post_tasks:
- name: Remove the clone
tags:
- clone
- instance
- cleanup
- post_task
when: clone_instance_information.instance_ids is defined
amazon.aws.ec2_instance:
instance_ids: "{{ clone_instance_information.instance_ids }}"
state: absent
- name: Remove the AMI
tags:
- always # stupid ami module fails if already existing
- ami
- cleanup
- post_task
when: original_instance_ami.image_id is defined
amazon.aws.ec2_ami:
image_id: "{{ original_instance_ami.image_id }}"
state: absent

View File

@@ -70,6 +70,8 @@ One can can rapidly remapping addresses to other instances in one's account and
| [Security Hub] | Aggregator for security findings | | [Security Hub] | Aggregator for security findings |
[Service icons][aws icons] are publicly available for diagrams and such. [Service icons][aws icons] are publicly available for diagrams and such.
Public service IP address ranges are [available in JSON form][aws public ip address ranges now available in json form]
at <https://ip-ranges.amazonaws.com/ip-ranges.json>.
### Billing and Cost Management ### Billing and Cost Management
@@ -254,6 +256,7 @@ Refer [IAM].
- [Rotating AWS KMS keys] - [Rotating AWS KMS keys]
- [Image baking in AWS using Packer and Image builder] - [Image baking in AWS using Packer and Image builder]
- [Using AWS KMS via the CLI with a Symmetric Key] - [Using AWS KMS via the CLI with a Symmetric Key]
- [AWS Public IP Address Ranges Now Available in JSON Form]
<!-- <!--
Reference Reference
@@ -301,6 +304,7 @@ Refer [IAM].
[what is amazon vpc?]: https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html [what is amazon vpc?]: https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html
[what is aws config?]: https://docs.aws.amazon.com/config/latest/developerguide/WhatIsConfig.html [what is aws config?]: https://docs.aws.amazon.com/config/latest/developerguide/WhatIsConfig.html
[what is cloudwatch]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html [what is cloudwatch]: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html
[aws public ip address ranges now available in json form]: https://aws.amazon.com/blogs/aws/aws-ip-ranges-json/
<!-- Others --> <!-- Others -->
[automating dns-challenge based letsencrypt certificates with aws route 53]: https://johnrix.medium.com/automating-dns-challenge-based-letsencrypt-certificates-with-aws-route-53-8ba799dd207b [automating dns-challenge based letsencrypt certificates with aws route 53]: https://johnrix.medium.com/automating-dns-challenge-based-letsencrypt-certificates-with-aws-route-53-8ba799dd207b

View File

@@ -28,9 +28,13 @@ Apparently, Linux machines are able to do that automatically with a reboot.
## Snapshots ## Snapshots
When created, snapshots are **incremental**.<br/> The first snapshot is **complete**, with all the volume's blocks being copied. All successive snapshots of the same
volume are **incremental**, with only the changes being copied.<br/>
Incremental snapshots are stored in EBS' standard tier. Incremental snapshots are stored in EBS' standard tier.
Snapshots can be unbearably slow depending on the amount of data needing to be copied.<br/>
For comparison, the first snapshot of a 200 GiB volume took about 2h to complete.
Snapshots can be [archived][archive amazon ebs snapshots] to save money should they **not** need frequent nor fast Snapshots can be [archived][archive amazon ebs snapshots] to save money should they **not** need frequent nor fast
retrieval.<br/> retrieval.<br/>
When archived, incremental snapshots are converted to **full snapshots** and moved to EBS' archive tier. When archived, incremental snapshots are converted to **full snapshots** and moved to EBS' archive tier.

View File

@@ -15,6 +15,13 @@ Use an instance profile to pass an IAM role to an EC2 instance.
The instance type [_can_ be changed][change the instance type]. The procedure depends on the root volume, but does The instance type [_can_ be changed][change the instance type]. The procedure depends on the root volume, but does
require downtime. require downtime.
Clone EC2 instances by:
1. Creating an AMI from the original instance.
Mind the default behaviour of the AMI creator is to **shutdown** the instance, take a snapshot, and boot it again
[to guarantee the image's filesystem integrity][create an ami from an amazon ec2 instance].
1. Using that AMI to launch clones identical to the original.
<details> <details>
<summary>Real world use cases</summary> <summary>Real world use cases</summary>
@@ -122,6 +129,8 @@ See [EBS].
- [Retrieve instance metadata] - [Retrieve instance metadata]
- [Burstable performance instances] - [Burstable performance instances]
- [Change the instance type] - [Change the instance type]
- [How to Clone instance EC2]
- [Create an AMI from an Amazon EC2 Instance]
<!-- <!--
Reference Reference
@@ -137,16 +146,18 @@ See [EBS].
<!-- Upstream --> <!-- Upstream -->
[best practices for handling ec2 spot instance interruptions]: https://aws.amazon.com/blogs/compute/best-practices-for-handling-ec2-spot-instance-interruptions/ [best practices for handling ec2 spot instance interruptions]: https://aws.amazon.com/blogs/compute/best-practices-for-handling-ec2-spot-instance-interruptions/
[burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html [burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances.html
[change the instance type]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-resize.html
[connect to your instances without requiring a public ipv4 address using ec2 instance connect endpoint]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html [connect to your instances without requiring a public ipv4 address using ec2 instance connect endpoint]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html
[create an ami from an amazon ec2 instance]: https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide//tkv-create-ami-from-instance.html
[describe-images]: https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html [describe-images]: https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html
[describeimages]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html [describeimages]: https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeImages.html
[how to clone instance ec2]: https://repost.aws/questions/QUOrWudF3vRL2Vqtrv0M9lfQ/how-to-clone-instance-ec2
[iam roles for amazon ec2]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html [iam roles for amazon ec2]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
[key concepts and definitions for burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-credits-baseline-concepts.html [key concepts and definitions for burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-credits-baseline-concepts.html
[retrieve instance metadata]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html [retrieve instance metadata]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
[standard mode for burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-standard-mode.html [standard mode for burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-standard-mode.html
[unlimited mode for burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-unlimited-mode.html [unlimited mode for burstable performance instances]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/burstable-performance-instances-unlimited-mode.html
[using instance profiles]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html [using instance profiles]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html
[change the instance type]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-resize.html
<!-- Others --> <!-- Others -->
[aws ec2 instance pricing comparison]: https://ec2instances.github.io/ [aws ec2 instance pricing comparison]: https://ec2instances.github.io/

View File

@@ -51,7 +51,7 @@ ansible-playbook 'path/to/playbook.yml' --syntax-check
# Ad-hoc commands. # Ad-hoc commands.
ansible -i 'hosts.yml' -m 'ping' 'all' ansible -i 'hosts.yml' -m 'ping' 'all'
ansible -i 'host-1,host-n,' 'hostRegex' -m 'ansible.builtin.shell' -a 'echo $TERM' ansible -i 'host-1,host-n,' 'hostRegex' -m 'ansible.builtin.shell' -a 'echo $TERM'
ansible -i 'localhost,' -c 'local' -m 'ansible.builtin.copy' -a 'src=/tmp/src' -a 'dest=/tmp/dest' 'localhost' ansible -i 'localhost ansible_python_interpreter=venv/bin/python3,' -c 'local' -m 'ansible.builtin.copy' -a 'src=/tmp/src' -a 'dest=/tmp/dest' 'localhost'
ansible-vault encrypt_string --name 'command_output' 'somethingNobodyShouldKnow' ansible-vault encrypt_string --name 'command_output' 'somethingNobodyShouldKnow'
ansible-vault encrypt --output 'ssh.key' '.ssh/id_rsa' ansible-vault encrypt --output 'ssh.key' '.ssh/id_rsa'

View File

@@ -19,6 +19,14 @@
content: | content: |
- name: Show input data type
set_fact:
should_be_string: "{{ 'this' | type_debug }}"
- name: Run locally
delegate_to: 127.0.0.1 # 'localhost' works too
command: hostname
- name: Import tasks - name: Import tasks
block: block:
- name: By using absolute paths and special variables (preferred) - name: By using absolute paths and special variables (preferred)
@@ -135,6 +143,7 @@
set_fact: set_fact:
random_item: "{{ ['a','b','c'] | random }}" random_item: "{{ ['a','b','c'] | random }}"
- name: Sort dict elements in list by attribute - name: Sort dict elements in list by attribute
tags: order_by
vars: vars:
snapshots: snapshots:
- name: sales - name: sales
@@ -156,6 +165,7 @@
set_fact: set_fact:
vpc_security_group_ids: >- vpc_security_group_ids: >-
{{ instance_information.vpc_security_groups | map(attribute='vpc_security_group_id') }} {{ instance_information.vpc_security_groups | map(attribute='vpc_security_group_id') }}
volume_ids: "{{ instances_information.instances[0].block_device_mappings | map(attribute='ebs.volume_id') }}"
- name: Return only elements with specific attributes matching a filter - name: Return only elements with specific attributes matching a filter
set_fact: set_fact:
available_rds_snapshots: snapshots_list | selectattr("status", "equalto", "available") available_rds_snapshots: snapshots_list | selectattr("status", "equalto", "available")
@@ -243,6 +253,28 @@
{%- endfor -%} {%- endfor -%}
{%- endfor -%} {%- endfor -%}
{{- output -}} {{- output -}}
- name: Get the device name and last snapshot id for all block devices in an EC2 instance
# Useful to create AMIs from instance snapshots
tags:
- aws
- ec2
- snapshot
- ami
ansible.builtin.set_fact:
last_snap_for_device: >-
{%- set devices_list = [] -%}
{%- for result in current_instance_snapshots.results -%}
{%- for device in current_instance_information.instances[0].block_device_mappings
| selectattr('ebs.volume_id', 'equalto', result.volume_id) -%}
{{-
devices_list.append({
'device_name': device.device_name,
'snapshot_id': result.snapshots | sort(attribute='start_time') | last | json_query('snapshot_id'),
})
-}}
{%- endfor -%}
{%- endfor -%}
{{ devices_list }}
- name: "Use the users' home directory for something" - name: "Use the users' home directory for something"
block: block:
@@ -261,6 +293,7 @@
- ec2-user - ec2-user
register: users_homedir_retrieve register: users_homedir_retrieve
- name: Compute and register the results - name: Compute and register the results
tags: AnsibleUnsafeText_to_Dict
ansible.builtin.set_fact: ansible.builtin.set_fact:
users_homedir: >- users_homedir: >-
{{ {{
@@ -354,7 +387,18 @@
msg: I always execute msg: I always execute
- name: AWS - name: AWS
tags: aws
block: block:
- name: Get current IP ranges
# too many to be put into security group rules
set_fact:
ip_ranges: >-
lookup('url', 'https://ip-ranges.amazonaws.com/ip-ranges.json', split_lines=False)
| from_json
| json_query('prefixes')
| selectattr('region', 'equalto', 'eu-west-1')
| selectattr('service', 'equalto', 'AMAZON')
| map(attribute='ip_prefix')
- name: Assume roles - name: Assume roles
block: block:
- name: Get session tokens - name: Get session tokens
@@ -374,6 +418,38 @@
resource: i-xyzxyz01 resource: i-xyzxyz01
tags: tags:
MyNewTag: value MyNewTag: value
- name: EC2
block:
- name: Get running instances with 'K8S' as the 'Application' tag
amazon.aws.ec2_instance_info:
filters:
"tag:Application": K8S
"instance-state-name": [ "running" ]
- name: Clone EC2 instances
vars:
source_instance_id: i-0123456789abcdef0
block:
- name: Get instance information from the original instance
amazon.aws.ec2_instance_info:
instance_ids:
- "{{ source_instance_id }}"
register: source_instance_info
- name: Create an AMI of the original instance
amazon.aws.ec2_ami:
instance_id: "{{ source_instance_id }}"
no_reboot: true # remove if the instance rebooting upon AMI creation is no biggie
wait: true
wait_timeout: 3600 # big volumes call for bit wait times (a 200GiB volume took )
name: ami-source
register: source_ami
- name: Use the AMI to launch clones identical to the original
when: source_ami.image_id is defined
amazon.aws.ec2_instance:
name: clone
vpc_subnet_id: "{{ source_instance_info.instances[0].subnet_id }}"
instance_type: "{{ source_instance_info.instances[0].instance_type }}"
image:
id: "{{ source_ami.image_id }}"
- name: RDS - name: RDS
block: block:
- name: Create an instance's snapshot - name: Create an instance's snapshot

View File

@@ -49,7 +49,7 @@ aws iam list-instance-profiles | grep -i 'ssm'
sudo ssm-cli get-diagnostics --output 'table' sudo ssm-cli get-diagnostics --output 'table'
# Check instances are available # Check instances are available for use with SSM
aws ssm get-connection-status --query "Status=='connected'" --output 'text' --target "i-0915612ff82914822" aws ssm get-connection-status --query "Status=='connected'" --output 'text' --target "i-0915612ff82914822"
# Connect to instances if they are available # Connect to instances if they are available