Skip to content

Commit

Permalink
Merge pull request #97 from GomathiselviS/webapp_HA
Browse files Browse the repository at this point in the history
Create a playbook to deploy a simple flask web app into high availability architecture
  • Loading branch information
GomathiselviS authored Oct 25, 2023
2 parents 9451a70 + 93fce44 commit f99f858
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 72 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/webapp_in_HA.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- "Add a playbook to deploy a simple flask web app into high availability architecture (https://github.com/redhat-cop/cloud.aws_ops/pull/97)."
34 changes: 33 additions & 1 deletion playbooks/webapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ To delete the webapp:
### Common

* **operation** (str): Operation for the webapp playbook to perform, either `create` or `delete`. Default: `create`
* **resource_prefix** (str): (Required) A prefix to prepend to the name of all AWS resources created for the webapp
* **resource_prefix** (str): A prefix to prepend to the name of all AWS resources created for the webapp. Default: `ansible-test`
* **resource_tags** (dict, elements dict): Tags to apply to all AWS resources created for the webapp. Default: `prefix: "{{ resource_prefix }}"`
* **aws_access_key** (str): (Required) AWS access key ID for user account with the above permissions
* **aws_secret_key** (str): (Required) AWS secret access key for user account with the above permissions
Expand Down Expand Up @@ -157,6 +157,25 @@ To delete the webapp:
```
* **deploy_flask_app_force_init** (bool): Whether to drop existing tables and create new ones when deploying the webapp database. Default: `false`

### webapp deployment in HA architecture

`webapp_ha_aurora.yaml` playbook deploys the flask app to a cross region high availability architecture. The playbook replicates the app deployment to a second region. The backend is an Aurora global cluster. For adding the write forwarding feature, aurora-mysql can be used. Default db engine is aurora-postgresql. The app in each region is configured to access the associated Aurora cluster. In front of the two regions, route53 records are added to provide cross region DNS (failover scenario).

Along with the [above](https://github.com/redhat-cop/cloud.aws_ops/blob/main/playbooks/webapp/README.md#playbook-variables) variables, following variables are needed for this playbook:

* **rds_instance_class** (str): DB instance class for the aurora db instances. Default: `db.r5.large`
* **rds_global_cluster_name** (str): Name of the global cluster. Default: "{{ resource_prefix }}-global-cluster"
* **rds_primary_cluster_name** (str): Name of the primary cluster. Default: "{{ resource_prefix }}-primary-cluster"
* **rds_primary_cluster_region** (str): Primary Region. Default: `us-west-2`
* **rds_primary_cluster_instance_name** (str): Name of primary db instance. Default: "{{ resource_prefix }}-primary-instance"
* **rds_replica_cluster_name** (str): Name of the replica cluster. Default: "{{ resource_prefix }}-replica-cluster"
* **rds_replica_cluster_region** (str): Replica Region. Default: `us-east-2`
* **rds_replica_cluster_instance_name** (str): Name of the replica db instance. Default: "{{ resource_prefix }}-replica-instance"

#### vars for route53 records
* **route53_zone_name** (str): (required) Route53 Zone name.
* **route53_subdomain** (str): Sub domain name for the application url. Default: "flaskapp"

## Example Usage

Create a `credentials.yaml` file with the folling contents:
Expand Down Expand Up @@ -187,3 +206,16 @@ ansible-playbook migrate_webapp.yaml -e "@credentials.yaml" -e "dest_region=my-n
```

Note: migrating a webapp does not delete the app resources from the source region by default. To delete the source webapp, set var `delete_source: true`.

To deploy the app in a high availability architecture, run:

```bash
ansible-playbook webapp_ha_aurora.yaml -e "@credentials.yaml" -e "operation=create"
```

To delete the webapp resources created by the above playbook, run:

```bash
ansible-playbook webapp_ha_aurora.yaml -e "@credentials.yaml" -e "operation=delete"
```

3 changes: 3 additions & 0 deletions playbooks/webapp/files/run_app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
ansible.builtin.yum:
name:
- podman
sslverify: false
validate_certs: false
update_cache: true
state: present

- name: Pull image from private registry
Expand Down
1 change: 0 additions & 1 deletion playbooks/webapp/migrate_webapp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
- name: Migrate webapp
hosts: localhost
gather_facts: false

vars_files:
- vars/main.yaml

Expand Down
68 changes: 68 additions & 0 deletions playbooks/webapp/tasks/add_route53_records.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
- name: Add Route53 configurations
module_defaults:
group/aws:
aws_access_key: "{{ aws_access_key | default(omit) }}"
aws_secret_key: "{{ aws_secret_key | default(omit) }}"
security_token: "{{ security_token | default(omit) }}"
block:

- name: Add route53 health check for the load balancer in primary region
amazon.aws.route53_health_check:
health_check_name: "healthchk-lb-primary"
fqdn: "{{ primary_lb.elb.dns_name }}"
port: 5000
type: HTTP
use_unique_names: true
state: present
register: healthchk_primary_result

- name: Add route53 health check for the load balancer in replica region
amazon.aws.route53_health_check:
health_check_name: "healthchk-lb-replica"
fqdn: "{{ replica_lb.elb.dns_name }}"
port: 5000
type: HTTP
use_unique_names: true
state: present
register: healthchk_replica_result

- name: Pause for 30 secs for the health check status to be in sync
ansible.builtin.pause:
seconds: 30

- name: Add an alias record that points to an aws ELB in the primary region
amazon.aws.route53:
state: present
zone: "{{ route53_zone_name }}"
record: "{{ route53_subdomain }}.{{ route53_zone_name }}"
type: A
value: "{{ primary_lb.elb.dns_name }}"
alias: true
identifier: "primary-record"
failover: "PRIMARY"
health_check: "{{ healthchk_primary_result.health_check.id }}"
alias_hosted_zone_id: "{{ primary_lb.elb.hosted_zone_id }}"
register: alias_record_primary_result

- name: Add an alias record that points to an aws ELB in the replica region
amazon.aws.route53:
state: present
zone: "{{ route53_zone_name }}"
record: "{{ route53_subdomain }}.{{ route53_zone_name }}"
type: A
value: "{{ replica_lb.elb.dns_name }}"
alias: true
identifier: "replica-record"
failover: "SECONDARY"
health_check: "{{ healthchk_replica_result.health_check.id }}"
alias_hosted_zone_id: "{{ replica_lb.elb.hosted_zone_id }}"
register: alias_record_replica_result

- name: Pause for 30 secs for the alias records to be active
ansible.builtin.pause:
seconds: 30

- name: Get Application URL
ansible.builtin.debug:
msg: "Application url: {{ route53_subdomain }}.{{ route53_zone_name }}:{{ deploy_flask_app_listening_port }}"
123 changes: 66 additions & 57 deletions playbooks/webapp/tasks/create.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
---
- name: Set 'region' variable
ansible.builtin.set_fact:
region: "{{ region | default(aws_region) }}"

- name: Create resources playbook
module_defaults:
group/aws:
aws_access_key: "{{ aws_access_key | default(omit) }}"
aws_secret_key: "{{ aws_secret_key | default(omit) }}"
security_token: "{{ security_token | default(omit) }}"
region: "{{ region }}"
region: "{{ region | default(aws_region) }}"
block:
- name: Get image ID to create an instance
amazon.aws.ec2_ami_info:
Expand Down Expand Up @@ -147,61 +143,64 @@
state: present
register: rds_sg

- name: Get RDS instance info
amazon.aws.rds_instance_info:
db_instance_identifier: "{{ rds_identifier }}"
register: rds_result

- name: Create RDS instance
when: rds_result.instances | length == 0
- name: RDS creation
when: not "aurora" in rds_engine
block:
- name: Create RDS instance (PostGreSQL Database)
amazon.aws.rds_instance:
force_update_password: true
wait: true
allocated_storage: "{{ rds_allocated_storage_gb }}"
backup_retention_period: 0
db_instance_class: "{{ rds_instance_class }}"
- name: Get RDS instance info
amazon.aws.rds_instance_info:
db_instance_identifier: "{{ rds_identifier }}"
db_name: "{{ rds_instance_name }}"
engine: "{{ rds_engine }}"
engine_version: "{{ rds_engine_version }}"
master_user_password: "{{ deploy_flask_app_rds_master_password }}"
master_username: "{{ deploy_flask_app_rds_master_username }}"
monitoring_interval: 0
storage_type: standard
skip_final_snapshot: true
db_subnet_group_name: "{{ rds_subnet_group_name }}"
vpc_security_group_ids:
- "{{ rds_sg.group_id }}"
when: rds_snapshot_arn is not defined

- name: Create RDS instance from snapshot (PostGreSQL Database)
amazon.aws.rds_instance:
force_update_password: true
wait: true
allocated_storage: "{{ rds_allocated_storage_gb }}"
backup_retention_period: 0
db_instance_class: "{{ rds_instance_class }}"
register: rds_result

- name: Create RDS instance
when: rds_result.instances | length == 0
block:
- name: Create RDS instance (PostGreSQL Database)
amazon.aws.rds_instance:
force_update_password: true
wait: true
allocated_storage: "{{ rds_allocated_storage_gb }}"
backup_retention_period: 0
db_instance_class: "{{ rds_instance_class }}"
db_instance_identifier: "{{ rds_identifier }}"
db_name: "{{ rds_instance_name }}"
engine: "{{ rds_engine }}"
engine_version: "{{ rds_engine_version }}"
master_user_password: "{{ deploy_flask_app_rds_master_password }}"
master_username: "{{ deploy_flask_app_rds_master_username }}"
monitoring_interval: 0
storage_type: standard
skip_final_snapshot: true
db_subnet_group_name: "{{ rds_subnet_group_name }}"
vpc_security_group_ids:
- "{{ rds_sg.group_id }}"
when: rds_snapshot_arn is not defined

- name: Create RDS instance from snapshot (PostGreSQL Database)
amazon.aws.rds_instance:
force_update_password: true
wait: true
allocated_storage: "{{ rds_allocated_storage_gb }}"
backup_retention_period: 0
db_instance_class: "{{ rds_instance_class }}"
db_instance_identifier: "{{ rds_identifier }}"
engine: "{{ rds_engine }}"
engine_version: "{{ rds_engine_version }}"
master_user_password: "{{ deploy_flask_app_rds_master_password }}"
master_username: "{{ deploy_flask_app_rds_master_username }}"
monitoring_interval: 0
storage_type: standard
skip_final_snapshot: true
db_subnet_group_name: "{{ rds_subnet_group_name }}"
vpc_security_group_ids:
- "{{ rds_sg.group_id }}"
creation_source: snapshot
db_snapshot_identifier: "{{ rds_snapshot_arn }}"
when: rds_snapshot_arn is defined

- name: Get RDS instance info
amazon.aws.rds_instance_info:
db_instance_identifier: "{{ rds_identifier }}"
engine: "{{ rds_engine }}"
engine_version: "{{ rds_engine_version }}"
master_user_password: "{{ deploy_flask_app_rds_master_password }}"
master_username: "{{ deploy_flask_app_rds_master_username }}"
monitoring_interval: 0
storage_type: standard
skip_final_snapshot: true
db_subnet_group_name: "{{ rds_subnet_group_name }}"
vpc_security_group_ids:
- "{{ rds_sg.group_id }}"
creation_source: snapshot
db_snapshot_identifier: "{{ rds_snapshot_arn }}"
when: rds_snapshot_arn is defined

- name: Get RDS instance info
amazon.aws.rds_instance_info:
db_instance_identifier: "{{ rds_identifier }}"
register: rds_result
register: rds_result

- name: Set 'sshkey_file' variable
ansible.builtin.set_fact:
Expand All @@ -219,7 +218,17 @@
mode: 0400
when: rsa_key is changed

- name: Check if the vm exists
amazon.aws.ec2_instance_info:
filters:
instance-type: "{{ bastion_host_type }}"
key-name: "{{ deploy_flask_app_sshkey_pair_name }}"
vpc-id: "{{ vpc.vpc.id }}"
instance-state-name: running
register: vm_result

- name: Create a virtual machine
when: vm_result.instances | length == 0
amazon.aws.ec2_instance:
name: "{{ deploy_flask_app_bastion_host_name }}"
instance_type: "{{ bastion_host_type }}"
Expand Down
81 changes: 81 additions & 0 deletions playbooks/webapp/tasks/create_aurora_db_cluster.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
- name: Create resources playbook
module_defaults:
group/aws:
aws_access_key: "{{ aws_access_key | default(omit) }}"
aws_secret_key: "{{ aws_secret_key | default(omit) }}"
security_token: "{{ security_token | default(omit) }}"
block:
- name: Get security group id - primary region
amazon.aws.ec2_security_group_info:
filters:
group-name: "{{ rds_secgroup_name }}"
region: "{{ rds_primary_cluster_region }}"
register: rds_primary_sg

- name: Get security group id - replica region
amazon.aws.ec2_security_group_info:
filters:
group-name: "{{ rds_secgroup_name }}"
region: "{{ rds_replica_cluster_region }}"
register: rds_replica_sg

- name: Create Aurora db cluster
ansible.builtin.include_role:
name: cloud.aws_ops.create_rds_global_cluster
vars:
create_rds_global_cluster_operation: create
create_rds_global_cluster_engine: "{{ rds_engine }}"
create_rds_global_cluster_engine_version: "{{ rds_engine_version }}"
create_rds_global_cluster_instance_class: "{{ rds_instance_class }}"
create_rds_global_cluster_master_username: "{{ deploy_flask_app_rds_master_username }}"
create_rds_global_cluster_master_user_password: "{{ deploy_flask_app_rds_master_password }}"
create_rds_global_cluster_global_cluster_name: "{{ rds_global_cluster_name }}"
create_rds_global_cluster_primary_cluster_name: "{{ rds_primary_cluster_name }}"
create_rds_global_cluster_primary_cluster_region: "{{ rds_primary_cluster_region }}"
create_rds_global_cluster_primary_cluster_instance_name: "{{ rds_primary_cluster_instance_name }}"
create_rds_global_cluster_replica_cluster_name: "{{ rds_replica_cluster_name }}"
create_rds_global_cluster_replica_cluster_region: "{{ rds_replica_cluster_region }}"
create_rds_global_cluster_replica_cluster_instance_name: "{{ rds_replica_cluster_instance_name }}"
create_rds_global_cluster_db_subnet_group_name: "{{ rds_subnet_group_name }}"
create_rds_global_cluster_primary_cluster_db_name: "{{ rds_instance_name }}"
create_rds_global_cluster_primary_cluster_vpc_security_group_ids:
- "{{ rds_primary_sg.security_groups[0].group_id }}"
create_rds_global_cluster_replica_cluster_vpc_security_group_ids:
- "{{ rds_replica_sg.security_groups[0].group_id }}"

- name: Get primary instance info
amazon.aws.rds_instance_info:
db_instance_identifier: "{{ rds_primary_cluster_instance_name }}"
region: "{{ rds_primary_cluster_region }}"
register: primary_instance_info_result

- name: Get primary cluster info
amazon.aws.rds_cluster_info:
db_cluster_identifier: "{{ rds_primary_cluster_name }}"
region: "{{ rds_primary_cluster_region }}"
register: primary_cluster_info_result

- name: Get replica cluster info
amazon.aws.rds_cluster_info:
db_cluster_identifier: "{{ rds_replica_cluster_name }}"
region: "{{ rds_replica_cluster_region }}"
register: replica_cluster_info_result

- name: Get replica instance info
amazon.aws.rds_instance_info:
db_instance_identifier: "{{ rds_replica_cluster_instance_name }}"
region: "{{ rds_replica_cluster_region }}"
register: replica_instance_info_result

- name: Get global db info

Check failure on line 71 in playbooks/webapp/tasks/create_aurora_db_cluster.yaml

View workflow job for this annotation

GitHub Actions / ansible-lint

syntax-check[specific]

couldn't resolve module/action 'amazon.aws.rds_global_cluster_info'. This often indicates a misspelling, missing collection, or incorrect module path.
amazon.aws.rds_global_cluster_info:
global_cluster_identifier: "{{ rds_global_cluster_name }}"
region: "{{ rds_primary_cluster_region }}"
register: global_cluster_info

- name: Assert that primary and replica cluster are part of global db
ansible.builtin.assert:
that:
- global_cluster_info.global_clusters[0].global_cluster_members[0].db_cluster_arn == primary_cluster_info_result.clusters[0].db_cluster_arn
- global_cluster_info.global_clusters[0].global_cluster_members[1].db_cluster_arn == replica_cluster_info_result.clusters[0].db_cluster_arn
Loading

0 comments on commit f99f858

Please sign in to comment.