Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a playbook to deploy a simple flask web app into high availability architecture #97

Merged
merged 20 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
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
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`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking_change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This modification should have no impact on current users of this playbook. I don't consider it a breaking change. I believed that setting a default value, rather than making it a required parameter, was a more practical approach.

* **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
Copy link
Contributor

@alinabuzachis alinabuzachis Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this deployment deploy_flask_app_workers_instance_type uses a t3.micro and cannot see where it is documented. It will be probably better to create a dedicated README for the high availability deployment especially if some default values from the webapp deployment are not retained. I personally find it clearer to to have separate READMEs, you can also reference the other rather than duplicating information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have referenced the other variables used for this playbook here - https://github.com/redhat-cop/cloud.aws_ops/pull/97/files#diff-9ac3987075e323c6dbca1ada8d6a37a9768cbc9f22ae5168b3d84c49556422e1R164. I can add a link.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the usage of t3.micro , based on this comment, I am retaining the default value - 't2.xlarge'. I will remove t3.micro from the playbooks and use it only for our testing.


`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
Loading