Repository: yankurniawan/ansible-for-aws Branch: master Commit: 99b6d82fbbd4 Files: 102 Total size: 134.4 KB Directory structure: gitextract_brojytnn/ ├── .gitignore ├── ami_create.yml ├── ami_delete.yml ├── dbsg_create.yml ├── dbsg_delete.yml ├── dhcp_options.yml ├── ec2.ini ├── ec2.py ├── ec2_check_name.yml ├── ec2_profile.yml ├── ec2_start.yml ├── ec2_start_1.yml ├── ec2_stop.yml ├── ec2_vol_1.yml ├── ec2_vol_2.yml ├── ec2_vpc_db_create.yml ├── ec2_vpc_jumpbox.yml ├── ec2_vpc_openvpn.yml ├── ec2_vpc_web_create.yml ├── group_vars/ │ ├── all │ └── tag_class_wordpress ├── host_vars/ │ └── localhost ├── hosts ├── iam_group.yml ├── iam_policy.yml ├── iam_policy_admin.json ├── iam_policy_app1.yml ├── iam_policy_s3_read.json ├── iam_role.yml ├── iam_user.yml ├── install_ansible.yml ├── keypair.yml ├── launch_ec2.yml ├── launch_ec2_eip.yml ├── launch_ec2_iteration.yml ├── launch_ec2_tags.yml ├── library/ │ ├── instance_lookup │ └── vpc_lookup ├── mysql_pg_create.yml ├── mysql_pg_delete.yml ├── mysql_rds_create.yml ├── mysql_rds_delete.yml ├── nat_launch.yml ├── roles/ │ ├── ansible/ │ │ └── tasks/ │ │ └── main.yml │ ├── apache/ │ │ └── tasks/ │ │ └── main.yml │ ├── common/ │ │ ├── handlers/ │ │ │ └── main.yml │ │ ├── tasks/ │ │ │ └── main.yml │ │ └── templates/ │ │ └── ntp.conf.j2 │ └── mysql/ │ └── tasks/ │ └── main.yml ├── route53.yml ├── s3_create_bucket.yml ├── s3_create_dir.yml ├── s3_delete_bucket.yml ├── s3_download_file.yml ├── s3_share_file.yml ├── s3_upload_file.yml ├── sg_database.yml ├── sg_delete.yml ├── sg_empty.yml ├── sg_jumpbox.yml ├── sg_modify.yml ├── sg_openvpn.yml ├── sg_webserver.yml ├── site.yml ├── terminate_ec2.yml ├── test.txt ├── test1.txt ├── vpc_create.yml ├── vpc_create_multi_az.yml ├── vpc_delete.yml ├── vpc_delete1.yml ├── vpc_info.yml ├── wordpress/ │ ├── backup.yml │ ├── delete_backup.yml │ ├── ec2.ini │ ├── ec2.py │ ├── group_vars/ │ │ └── all │ ├── hosts │ ├── provisioning.yml │ ├── restore.yml │ ├── roles/ │ │ ├── common/ │ │ │ └── tasks/ │ │ │ └── main.yml │ │ ├── mysql/ │ │ │ ├── handlers/ │ │ │ │ └── main.yml │ │ │ ├── tasks/ │ │ │ │ └── main.yml │ │ │ └── templates/ │ │ │ └── my.cnf.j2 │ │ ├── web/ │ │ │ └── tasks/ │ │ │ └── main.yml │ │ └── wordpress/ │ │ ├── tasks/ │ │ │ └── main.yml │ │ └── templates/ │ │ └── wp-config.php │ └── site.yml └── wordpress_ha/ ├── ec2.ini ├── ec2.py ├── group_vars/ │ └── all ├── hosts ├── provisioning_asg.yml ├── provisioning_rds.yml ├── provisioning_sg.yml ├── provisioning_vpc.yml ├── provisioning_wp.yml ├── roles/ │ ├── web/ │ │ └── tasks/ │ │ └── main.yml │ └── wordpress/ │ ├── tasks/ │ │ └── main.yml │ └── templates/ │ └── wp-config.php ├── site.yml └── vars/ └── staging.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ansible4aws-book.* .DS_Store ================================================ FILE: ami_create.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 ins_name: wordpress_master ami_name: wordpress tasks: - name: get instance id command: "aws ec2 describe-instances --filters Name=tag:Name,Values={{ ins_name }} --query 'Reservations[0].Instances[0].InstanceId' --output text" register: instanceid - name: create ami ec2_ami: instance_id: "{{ instanceid.stdout }}" region: "{{ region }}" wait: yes name: "{{ ami_name }}" register: ami when: instanceid.stdout!="None" - debug: var=ami ================================================ FILE: ami_delete.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 ami_name: wordpress tasks: - name: get ami id command: "aws ec2 describe-images --filters Name=name,Values={{ ami_name }} --query 'Images[0].ImageId' --output text" register: imageid - name: delete ami ec2_ami: region: "{{ region }}" image_id: "{{ imageid.stdout }}" delete_snapshot: yes state: absent when: imageid.stdout!="None" ================================================ FILE: dbsg_create.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 vars_files: - staging_vpc_info tasks: - name: create Multi-AZ DB subnet group rds_subnet_group: name: dbsg2 state: present region: "{{ region }}" description: DB Subnet Group 2 subnets: - "{{ staging_subnet_private_0 }}" - "{{ staging_subnet_private_1 }}" ================================================ FILE: dbsg_delete.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 tasks: - name: delete DB subnet group rds_subnet_group: name: dbsg2 state: absent region: "{{ region }}" ================================================ FILE: dhcp_options.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 name: test-vpc tasks: - name: create dhcp options set command: aws ec2 create-dhcp-options --dhcp-configuration "Key=domain-name,Values=example.com" "Key=domain-name-servers,Values=10.0.0.7,10.0.0.8" --query 'DhcpOptions.DhcpOptionsId' --output text register: dopt - name: get vpc id command: "aws ec2 describe-vpcs --filters Name=tag:Name,Values={{ name }} --query 'Vpcs[0].VpcId' --output text" register: vpcid - name: associate vpc with dhcp options set command: aws ec2 associate-dhcp-options --dhcp-options-id {{ dopt.stdout }} --vpc-id {{ vpcid.stdout }} ================================================ FILE: ec2.ini ================================================ # Ansible EC2 external inventory script settings # [ec2] # to talk to a private eucalyptus instance uncomment these lines # and edit edit eucalyptus_host to be the host name of your cloud controller #eucalyptus = True #eucalyptus_host = clc.cloud.domain.org # AWS regions to make calls to. Set this to 'all' to make request to all regions # in AWS and merge the results together. Alternatively, set this to a comma # separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2' regions = all regions_exclude = us-gov-west-1,cn-north-1 # When generating inventory, Ansible needs to know how to address a server. # Each EC2 instance has a lot of variables associated with it. Here is the list: # http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance # Below are 2 variables that are used as the address of a server: # - destination_variable # - vpc_destination_variable # This is the normal destination variable to use. If you are running Ansible # from outside EC2, then 'public_dns_name' makes the most sense. If you are # running Ansible from within EC2, then perhaps you want to use the internal # address, and should set this to 'private_dns_name'. destination_variable = public_dns_name # For server inside a VPC, using DNS names may not make sense. When an instance # has 'subnet_id' set, this variable is used. If the subnet is public, setting # this to 'ip_address' will return the public IP address. For instances in a # private subnet, this should be set to 'private_ip_address', and Ansible must # be run from with EC2. vpc_destination_variable = ip_address # To tag instances on EC2 with the resource records that point to them from # Route53, uncomment and set 'route53' to True. route53 = False # Additionally, you can specify the list of zones to exclude looking up in # 'route53_excluded_zones' as a comma-separated list. # route53_excluded_zones = samplezone1.com, samplezone2.com # API calls to EC2 are slow. For this reason, we cache the results of an API # call. Set this to the path you want cache files to be written to. Two files # will be written to this directory: # - ansible-ec2.cache # - ansible-ec2.index cache_path = ~/.ansible/tmp # The number of seconds a cache file is considered valid. After this many # seconds, a new API call will be made, and the cache file will be updated. # To disable the cache, set this value to 0 cache_max_age = 300 ================================================ FILE: ec2.py ================================================ #!/usr/bin/env python ''' EC2 external inventory script ================================= Generates inventory that Ansible can understand by making API request to AWS EC2 using the Boto library. NOTE: This script assumes Ansible is being executed where the environment variables needed for Boto have already been set: export AWS_ACCESS_KEY_ID='AK123' export AWS_SECRET_ACCESS_KEY='abc123' This script also assumes there is an ec2.ini file alongside it. To specify a different path to ec2.ini, define the EC2_INI_PATH environment variable: export EC2_INI_PATH=/path/to/my_ec2.ini If you're using eucalyptus you need to set the above variables and you need to define: export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html When run against a specific host, this script returns the following variables: - ec2_ami_launch_index - ec2_architecture - ec2_association - ec2_attachTime - ec2_attachment - ec2_attachmentId - ec2_client_token - ec2_deleteOnTermination - ec2_description - ec2_deviceIndex - ec2_dns_name - ec2_eventsSet - ec2_group_name - ec2_hypervisor - ec2_id - ec2_image_id - ec2_instanceState - ec2_instance_type - ec2_ipOwnerId - ec2_ip_address - ec2_item - ec2_kernel - ec2_key_name - ec2_launch_time - ec2_monitored - ec2_monitoring - ec2_networkInterfaceId - ec2_ownerId - ec2_persistent - ec2_placement - ec2_platform - ec2_previous_state - ec2_private_dns_name - ec2_private_ip_address - ec2_publicIp - ec2_public_dns_name - ec2_ramdisk - ec2_reason - ec2_region - ec2_requester_id - ec2_root_device_name - ec2_root_device_type - ec2_security_group_ids - ec2_security_group_names - ec2_shutdown_state - ec2_sourceDestCheck - ec2_spot_instance_request_id - ec2_state - ec2_state_code - ec2_state_reason - ec2_status - ec2_subnet_id - ec2_tenancy - ec2_virtualization_type - ec2_vpc_id These variables are pulled out of a boto.ec2.instance object. There is a lack of consistency with variable spellings (camelCase and underscores) since this just loops through all variables the object exposes. It is preferred to use the ones with underscores when multiple exist. In addition, if an instance has AWS Tags associated with it, each tag is a new variable named: - ec2_tag_[Key] = [Value] Security groups are comma-separated in 'ec2_security_group_ids' and 'ec2_security_group_names'. ''' # (c) 2012, Peter Sankauskas # # This file is part of Ansible, # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . ###################################################################### import sys import os import argparse import re from time import time import boto from boto import ec2 from boto import rds from boto import route53 import ConfigParser try: import json except ImportError: import simplejson as json class Ec2Inventory(object): def _empty_inventory(self): return {"_meta" : {"hostvars" : {}}} def __init__(self): ''' Main execution path ''' # Inventory grouped by instance IDs, tags, security groups, regions, # and availability zones self.inventory = self._empty_inventory() # Index of hostname (address) to instance ID self.index = {} # Read settings and parse CLI arguments self.read_settings() self.parse_cli_args() # Cache if self.args.refresh_cache: self.do_api_calls_update_cache() elif not self.is_cache_valid(): self.do_api_calls_update_cache() # Data to print if self.args.host: data_to_print = self.get_host_info() elif self.args.list: # Display list of instances for inventory if self.inventory == self._empty_inventory(): data_to_print = self.get_inventory_from_cache() else: data_to_print = self.json_format_dict(self.inventory, True) print data_to_print def is_cache_valid(self): ''' Determines if the cache files have expired, or if it is still valid ''' if os.path.isfile(self.cache_path_cache): mod_time = os.path.getmtime(self.cache_path_cache) current_time = time() if (mod_time + self.cache_max_age) > current_time: if os.path.isfile(self.cache_path_index): return True return False def read_settings(self): ''' Reads the settings from the ec2.ini file ''' config = ConfigParser.SafeConfigParser() ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini') ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path) config.read(ec2_ini_path) # is eucalyptus? self.eucalyptus_host = None self.eucalyptus = False if config.has_option('ec2', 'eucalyptus'): self.eucalyptus = config.getboolean('ec2', 'eucalyptus') if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'): self.eucalyptus_host = config.get('ec2', 'eucalyptus_host') # Regions self.regions = [] configRegions = config.get('ec2', 'regions') configRegions_exclude = config.get('ec2', 'regions_exclude') if (configRegions == 'all'): if self.eucalyptus_host: self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name) else: for regionInfo in ec2.regions(): if regionInfo.name not in configRegions_exclude: self.regions.append(regionInfo.name) else: self.regions = configRegions.split(",") # Destination addresses self.destination_variable = config.get('ec2', 'destination_variable') self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable') # Route53 self.route53_enabled = config.getboolean('ec2', 'route53') self.route53_excluded_zones = [] if config.has_option('ec2', 'route53_excluded_zones'): self.route53_excluded_zones.extend( config.get('ec2', 'route53_excluded_zones', '').split(',')) # Cache related cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) if not os.path.exists(cache_dir): os.makedirs(cache_dir) self.cache_path_cache = cache_dir + "/ansible-ec2.cache" self.cache_path_index = cache_dir + "/ansible-ec2.index" self.cache_max_age = config.getint('ec2', 'cache_max_age') def parse_cli_args(self): ''' Command line argument processing ''' parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') parser.add_argument('--refresh-cache', action='store_true', default=False, help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') self.args = parser.parse_args() def do_api_calls_update_cache(self): ''' Do API calls to each region, and save data in cache files ''' if self.route53_enabled: self.get_route53_records() for region in self.regions: self.get_instances_by_region(region) self.get_rds_instances_by_region(region) self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) def get_instances_by_region(self, region): ''' Makes an AWS EC2 API call to the list of instances in a particular region ''' try: if self.eucalyptus: conn = boto.connect_euca(host=self.eucalyptus_host) conn.APIVersion = '2010-08-31' else: conn = ec2.connect_to_region(region) # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported if conn is None: print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) sys.exit(1) reservations = conn.get_all_instances() for reservation in reservations: for instance in reservation.instances: self.add_instance(instance, region) except boto.exception.BotoServerError, e: if not self.eucalyptus: print "Looks like AWS is down again:" print e sys.exit(1) def get_rds_instances_by_region(self, region): ''' Makes an AWS API call to the list of RDS instances in a particular region ''' try: conn = rds.connect_to_region(region) if conn: instances = conn.get_all_dbinstances() for instance in instances: self.add_rds_instance(instance, region) except boto.exception.BotoServerError, e: if not e.reason == "Forbidden": print "Looks like AWS RDS is down: " print e sys.exit(1) def get_instance(self, region, instance_id): ''' Gets details about a specific instance ''' if self.eucalyptus: conn = boto.connect_euca(self.eucalyptus_host) conn.APIVersion = '2010-08-31' else: conn = ec2.connect_to_region(region) # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported if conn is None: print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) sys.exit(1) reservations = conn.get_all_instances([instance_id]) for reservation in reservations: for instance in reservation.instances: return instance def add_instance(self, instance, region): ''' Adds an instance to the inventory and index, as long as it is addressable ''' # Only want running instances if instance.state != 'running': return # Select the best destination address if instance.subnet_id: dest = getattr(instance, self.vpc_destination_variable) else: dest = getattr(instance, self.destination_variable) if not dest: # Skip instances we cannot address (e.g. private VPC subnet) return # Add to index self.index[dest] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) self.inventory[instance.id] = [dest] # Inventory: Group by region self.push(self.inventory, region, dest) # Inventory: Group by availability zone self.push(self.inventory, instance.placement, dest) # Inventory: Group by instance type self.push(self.inventory, self.to_safe('type_' + instance.instance_type), dest) # Inventory: Group by key pair if instance.key_name: self.push(self.inventory, self.to_safe('key_' + instance.key_name), dest) # Inventory: Group by security group try: for group in instance.groups: key = self.to_safe("security_group_" + group.name) self.push(self.inventory, key, dest) except AttributeError: print 'Package boto seems a bit older.' print 'Please upgrade boto >= 2.3.0.' sys.exit(1) # Inventory: Group by tag keys for k, v in instance.tags.iteritems(): key = self.to_safe("tag_" + k + "=" + v) self.push(self.inventory, key, dest) # Inventory: Group by Route53 domain names if enabled if self.route53_enabled: route53_names = self.get_instance_route53_names(instance) for name in route53_names: self.push(self.inventory, name, dest) # Global Tag: tag all EC2 instances self.push(self.inventory, 'ec2', dest) self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) def add_rds_instance(self, instance, region): ''' Adds an RDS instance to the inventory and index, as long as it is addressable ''' # Only want available instances if instance.status != 'available': return # Select the best destination address #if instance.subnet_id: #dest = getattr(instance, self.vpc_destination_variable) #else: #dest = getattr(instance, self.destination_variable) dest = instance.endpoint[0] if not dest: # Skip instances we cannot address (e.g. private VPC subnet) return # Add to index self.index[dest] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) self.inventory[instance.id] = [dest] # Inventory: Group by region self.push(self.inventory, region, dest) # Inventory: Group by availability zone self.push(self.inventory, instance.availability_zone, dest) # Inventory: Group by instance type self.push(self.inventory, self.to_safe('type_' + instance.instance_class), dest) # Inventory: Group by security group try: if instance.security_group: key = self.to_safe("security_group_" + instance.security_group.name) self.push(self.inventory, key, dest) except AttributeError: print 'Package boto seems a bit older.' print 'Please upgrade boto >= 2.3.0.' sys.exit(1) # Inventory: Group by engine self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest) # Inventory: Group by parameter group self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest) # Global Tag: all RDS instances self.push(self.inventory, 'rds', dest) def get_route53_records(self): ''' Get and store the map of resource records to domain names that point to them. ''' r53_conn = route53.Route53Connection() all_zones = r53_conn.get_zones() route53_zones = [ zone for zone in all_zones if zone.name[:-1] not in self.route53_excluded_zones ] self.route53_records = {} for zone in route53_zones: rrsets = r53_conn.get_all_rrsets(zone.id) for record_set in rrsets: record_name = record_set.name if record_name.endswith('.'): record_name = record_name[:-1] for resource in record_set.resource_records: self.route53_records.setdefault(resource, set()) self.route53_records[resource].add(record_name) def get_instance_route53_names(self, instance): ''' Check if an instance is referenced in the records we have from Route53. If it is, return the list of domain names pointing to said instance. If nothing points to it, return an empty list. ''' instance_attributes = [ 'public_dns_name', 'private_dns_name', 'ip_address', 'private_ip_address' ] name_list = set() for attrib in instance_attributes: try: value = getattr(instance, attrib) except AttributeError: continue if value in self.route53_records: name_list.update(self.route53_records[value]) return list(name_list) def get_host_info_dict_from_instance(self, instance): instance_vars = {} for key in vars(instance): value = getattr(instance, key) key = self.to_safe('ec2_' + key) # Handle complex types # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518 if key == 'ec2__state': instance_vars['ec2_state'] = instance.state or '' instance_vars['ec2_state_code'] = instance.state_code elif key == 'ec2__previous_state': instance_vars['ec2_previous_state'] = instance.previous_state or '' instance_vars['ec2_previous_state_code'] = instance.previous_state_code elif type(value) in [int, bool]: instance_vars[key] = value elif type(value) in [str, unicode]: instance_vars[key] = value.strip() elif type(value) == type(None): instance_vars[key] = '' elif key == 'ec2_region': instance_vars[key] = value.name elif key == 'ec2__placement': instance_vars['ec2_placement'] = value.zone elif key == 'ec2_tags': for k, v in value.iteritems(): key = self.to_safe('ec2_tag_' + k) instance_vars[key] = v elif key == 'ec2_groups': group_ids = [] group_names = [] for group in value: group_ids.append(group.id) group_names.append(group.name) instance_vars["ec2_security_group_ids"] = ','.join(group_ids) instance_vars["ec2_security_group_names"] = ','.join(group_names) else: pass # TODO Product codes if someone finds them useful #print key #print type(value) #print value return instance_vars def get_host_info(self): ''' Get variables about a specific host ''' if len(self.index) == 0: # Need to load index from cache self.load_index_from_cache() if not self.args.host in self.index: # try updating the cache self.do_api_calls_update_cache() if not self.args.host in self.index: # host migh not exist anymore return self.json_format_dict({}, True) (region, instance_id) = self.index[self.args.host] instance = self.get_instance(region, instance_id) return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True) def push(self, my_dict, key, element): ''' Pushed an element onto an array that may not have been defined in the dict ''' if key in my_dict: my_dict[key].append(element); else: my_dict[key] = [element] def get_inventory_from_cache(self): ''' Reads the inventory from the cache file and returns it as a JSON object ''' cache = open(self.cache_path_cache, 'r') json_inventory = cache.read() return json_inventory def load_index_from_cache(self): ''' Reads the index from the cache file sets self.index ''' cache = open(self.cache_path_index, 'r') json_index = cache.read() self.index = json.loads(json_index) def write_to_cache(self, data, filename): ''' Writes data in JSON format to a file ''' json_data = self.json_format_dict(data, True) cache = open(filename, 'w') cache.write(json_data) cache.close() def to_safe(self, word): ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' return re.sub("[^A-Za-z0-9\-]", "_", word) def json_format_dict(self, data, pretty=False): ''' Converts a dict to a JSON object and dumps it as a formatted string ''' if pretty: return json.dumps(data, sort_keys=True, indent=2) else: return json.dumps(data) # Run the script Ec2Inventory() ================================================ FILE: ec2_check_name.yml ================================================ --- - hosts: localhost gather_facts: no vars: region: ap-southeast-2 key: yan-key-pair-apsydney type: t2.micro image: ami-d9fe9be3 sg: sg_webserver_apsydney name: test-01 tasks: - name: check if instance with name tag exists command: aws ec2 describe-instances --filter Name=tag:Name,Values={{ name }} --query 'Reservations[0].Instances[0].InstanceId' --output text register: instanceid - name: create EC2 if not exists ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ type }}" image: "{{ image }}" group: "{{ sg }}" instance_tags: Name: "{{ name }}" wait: yes when: instanceid.stdout=="None" ================================================ FILE: ec2_profile.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: #your region region: ap-southeast-2 tasks: - name: EC2 provisioning with instance profile ec2: region: "{{ region }}" key_name: yan-key-pair-apsydney instance_type: t2.micro image: ami-dc361ebf group: sg_webserver_apsydney instance_profile_name: app1 ================================================ FILE: ec2_start.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 name: test-01 tasks: - name: get instance id command: aws ec2 describe-instances --filter Name=tag:Name,Values={{ name }} --query 'Reservations[0].Instances[0].InstanceId' --output text register: instanceid - name: start instance ec2: region: "{{ region }}" instance_ids: "{{ instanceid.stdout }}" state: running wait: yes when: instanceid.stdout!="None" ================================================ FILE: ec2_start_1.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 name: bamboo-1 tasks: - name: get instance id instance_lookup: region: "{{ region }}" tags: Name: "{{ name }}" register: instanceid - debug: var=instanceid.instance_ids - name: start instance ec2: region: "{{ region }}" instance_ids: "{{ instanceid.instance_ids }}" state: running wait: yes when: instanceid is defined ================================================ FILE: ec2_stop.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 name: test-01 tasks: - name: get instance id command: aws ec2 describe-instances --filter Name=tag:Name,Values={{ name }} --query 'Reservations[0].Instances[0].InstanceId' --output text register: instanceid - name: stop instance ec2: region: "{{ region }}" instance_ids: "{{ instanceid.stdout }}" state: stopped wait: yes when: instanceid.stdout!="None" ================================================ FILE: ec2_vol_1.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: #your region region: ap-southeast-2 tasks: - name: EC2 provisioning with general purpose EBS volume ec2: region: "{{ region }}" key_name: yan-key-pair-apsydney instance_type: t2.micro image: ami-dc361ebf group: sg_webserver_apsydney volumes: - device_name: /dev/sda1 device_type: gp2 volume_size: 100 delete_on_termination: true ================================================ FILE: ec2_vol_2.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: #your region region: ap-southeast-2 tasks: - name: EC2 provisioning with provisioned IOPS EBS volume ec2: region: "{{ region }}" key_name: yan-key-pair-apsydney instance_type: t2.micro image: ami-dc361ebf group: sg_webserver_apsydney volumes: - device_name: /dev/sda1 device_type: io1 iops: 1000 volume_size: 500 delete_on_termination: true ================================================ FILE: ec2_vpc_db_create.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars_files: - staging_vpc_info vars: region: ap-southeast-2 key: yan-key-pair-apsydney instance_type: t2.micro image: ami-dc361ebf prefix: staging tasks: - name: database instance provisioning ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: "{{ prefix }}_sg_database" instance_tags: Name: "{{ prefix }}_database" class: database environment: staging vpc_subnet_id: "{{ staging_subnet_private }}" assign_public_ip: no ================================================ FILE: ec2_vpc_jumpbox.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars_files: - staging_vpc_info vars: region: ap-southeast-2 key: yan-key-pair-apsydney instance_type: t2.micro image: ami-dc361ebf prefix: staging vpc_subnet_id: "{{ staging_subnet_public_0 }}" tasks: - name: jump box instance provisioning ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: "{{ prefix }}_sg_jumpbox" instance_tags: Name: "{{ prefix }}_jumpbox" class: jumpbox environment: "{{ prefix }}" vpc_subnet_id: "{{ vpc_subnet_id }}" register: ec2 - name: associate new EIP for the instance ec2_eip: region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances ================================================ FILE: ec2_vpc_openvpn.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars_files: - staging_vpc_info vars: region: ap-southeast-2 key: yan-key-pair-apsydney instance_type: t2.micro image: ami-a17f199b prefix: staging vpc_subnet_id: "{{ staging_subnet_public_0 }}" tasks: - name: openvpn server instance provisioning ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" source_dest_check: no wait: yes group: "{{ prefix }}_sg_openvpn" instance_tags: Name: "{{ prefix }}_openvpn" class: openvpn environment: "{{ prefix }}" vpc_subnet_id: "{{ vpc_subnet_id }}" register: ec2 - name: associate new EIP for the instance ec2_eip: region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances ================================================ FILE: ec2_vpc_web_create.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars_files: - staging_vpc_info vars: region: ap-southeast-2 key: yan-key-pair-apsydney instance_type: t2.micro image: ami-dc361ebf prefix: staging tasks: - name: web instance provisioning ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: "{{ prefix }}_sg_web" instance_tags: Name: "{{ prefix }}_web" class: web environment: staging vpc_subnet_id: "{{ staging_subnet_public }}" register: ec2 - name: associate new EIP for the instance ec2_eip: region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances ================================================ FILE: group_vars/all ================================================ --- # Variables here are applicable to all host groups ntpserver: 0.au.pool.ntp.org ansible_user: ec2-user ansible_ssh_private_key_file: ~/.ssh/yan-key-pair-apsydney.pem ================================================ FILE: group_vars/tag_class_wordpress ================================================ ansible_ssh_user: ec2-user ansible_ssh_private_key_file: ~/.ssh/wordpress-apsydney.pem ================================================ FILE: host_vars/localhost ================================================ ansible_ssh_user: ec2-user ansible_ssh_private_key_file: ~/.ssh/wordpress-apsydney.pem ================================================ FILE: hosts ================================================ [local] localhost #[webservers] #54.79.109.14 ansible_ssh_user=ec2-user ansible_ssh_private_key_file=~/.ssh/yan-key-pair-apsydney.pem ================================================ FILE: iam_group.yml ================================================ --- - hosts: localhost gather_facts: no connection: local tasks: - name: create IAM group admin iam: iam_type: group name: admin state: present ================================================ FILE: iam_policy.yml ================================================ --- - hosts: localhost gather_facts: no connection: local tasks: - name: Assign a policy called Administrator to the admin group iam_policy: iam_type: group iam_name: admin policy_name: Administrator state: present policy_document: iam_policy_admin.json ================================================ FILE: iam_policy_admin.json ================================================ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "*", "Resource": "*" } ] } ================================================ FILE: iam_policy_app1.yml ================================================ --- - hosts: localhost gather_facts: no connection: local tasks: - name: Assign a policy called S3ReadOnly to the app1 role iam_policy: iam_type: role iam_name: app1 policy_name: S3ReadOnly state: present policy_document: iam_policy_s3_read.json ================================================ FILE: iam_policy_s3_read.json ================================================ { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:Get*", "s3:List*" ], "Resource": "*" } ] } ================================================ FILE: iam_role.yml ================================================ --- - hosts: localhost gather_facts: no connection: local tasks: - name: create IAM role app1 iam: iam_type: role name: app1 state: present ================================================ FILE: iam_user.yml ================================================ --- - hosts: localhost gather_facts: no connection: local tasks: - name: create IAM user yan iam: iam_type: user name: yan state: present groups: admin ================================================ FILE: install_ansible.yml ================================================ --- - hosts: tag_class_jumpbox become: yes roles: - ansible ================================================ FILE: keypair.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 keyname: yan1 tasks: - name: create key pair ec2_key: region: "{{ region }}" name: "{{ keyname }}" register: mykey - name: write to file copy: content="{{ mykey.key.private_key }}" dest="~/.ssh/{{ keyname }}.pem" mode=0600 when: mykey.changed ================================================ FILE: launch_ec2.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 tasks: - name: EC2 basic provisioning ec2: region: "{{ region }}" key_name: yan-key-pair-apsydney instance_type: t1.micro image: ami-6bf99c51 wait: yes group: sg_webserver_apsydney instance_tags: group: webserver exact_count: 3 count_tag: group: webserver ================================================ FILE: launch_ec2_eip.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 tasks: - name: launch instance ec2: region: "{{ region }}" key_name: yan-key-pair-apsydney instance_type: t1.micro image: ami-6bf99c51 wait: yes group: sg_webserver_apsydney register: ec2 - name: associate new EIP for the instance ec2_eip: region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances ================================================ FILE: launch_ec2_iteration.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 tasks: - name: EC2 basic provisioning ec2: region: "{{ region }}" key_name: yan-key-pair-apsydney instance_type: t1.micro image: ami-6bf99c51 group: sg_webserver_apsydney instance_tags: Name: "web{{ item }}" with_sequence: count=5 ================================================ FILE: launch_ec2_tags.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 instance_type: t1.micro image: ami-6bf99c51 key: yan-key-pair-apsydney tasks: - name: launch ec2 with tags webserver staging ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: sg_webserver_apsydney instance_tags: Name: staging-webserver-1 class: webserver environment: staging - name: launch ec2 with tags webserver production ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: sg_webserver_apsydney instance_tags: Name: production-webserver-1 class: webserver environment: production - name: launch ec2 with tags database staging ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: sg_database_apsydney instance_tags: Name: staging-database-1 class: database environment: staging ================================================ FILE: library/instance_lookup ================================================ #!/usr/bin/python #author: Yan Kurniawan import sys AWS_REGIONS = ['ap-northeast-1', 'ap-southeast-1', 'ap-southeast-2', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2'] try: from boto.ec2 import connect_to_region except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) def main(): module=AnsibleModule( argument_spec=dict( region=dict(choices=AWS_REGIONS), tags=dict(default=None, type='dict'), ) ) params = module.params tags = params['tags'] region = params['region'] if region: try: ec2 = connect_to_region(region) except boto.exception.NoAuthHandlerFound, e: module.fail_json(msg=str(e)) else: module.fail_json(msg="region must be specified") instance_ids = [] for tag, value in tags.iteritems(): for instance in ec2.get_only_instances(filters={"tag:" + tag: value}): instance_ids.append(instance.id) module.exit_json(changed=False, instance_ids=instance_ids) from ansible.module_utils.basic import * main() ================================================ FILE: library/vpc_lookup ================================================ #!/usr/bin/python # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . DOCUMENTATION = ''' --- module: vpc_lookup short_description: returns a list of subnet Ids using tags as criteria description: - Returns a list of subnet Ids for a given set of tags that identify one or more VPCs version_added: "1.5" options: region: description: - The AWS region to use. Must be specified if ec2_url is not used. If not specified then the value of the EC2_REGION environment variable, if any, is used. required: false default: null aliases: [ 'aws_region', 'ec2_region' ] aws_secret_key: description: - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used. required: false default: null aliases: [ 'ec2_secret_key', 'secret_key' ] aws_access_key: description: - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used. required: false default: null aliases: [ 'ec2_access_key', 'access_key' ] tags: desription: - tags to lookup required: false default: null type: dict aliases: [] requirements: [ "boto" ] author: John Jarvis ''' EXAMPLES = ''' # Note: None of these examples set aws_access_key, aws_secret_key, or region. # It is assumed that their matching environment variables are set. # Return all instances that match the tag "Name: foo" - local_action: module: vpc_lookup tags: Name: foo ''' import sys AWS_REGIONS = ['ap-northeast-1', 'ap-southeast-1', 'ap-southeast-2', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2'] try: from boto.vpc import VPCConnection from boto.vpc import connect_to_region except ImportError: print "failed=True msg='boto required for this module'" sys.exit(1) def main(): module=AnsibleModule( argument_spec=dict( region=dict(choices=AWS_REGIONS), aws_secret_key=dict(aliases=['ec2_secret_key', 'secret_key'], no_log=True), aws_access_key=dict(aliases=['ec2_access_key', 'access_key']), tags=dict(default=None, type='dict'), ) ) tags = module.params.get('tags') aws_secret_key = module.params.get('aws_secret_key') aws_access_key = module.params.get('aws_access_key') region = module.params.get('region') # If we have a region specified, connect to its endpoint. if region: try: vpc = connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key) except boto.exception.NoAuthHandlerFound, e: module.fail_json(msg=str(e)) else: module.fail_json(msg="region must be specified") subnet_ids = [] for tag, value in tags.iteritems(): for subnet in vpc.get_all_subnets(filters={"tag:" + tag: value}): subnet_ids.append(subnet.id) vpc_ids = [] for tag, value in tags.iteritems(): for vpc in vpc.get_all_vpcs(filters={"tag:" + tag: value}): vpc_ids.append(vpc.id) module.exit_json(changed=False, vpc_ids=vpc_ids, subnet_ids=subnet_ids) # this is magic, see lib/ansible/module_common.py #<> main() ================================================ FILE: mysql_pg_create.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 tasks: - name: create mysql parameter group rds_param_group: name: mysqlpg1 state: present region: "{{ region }}" description: MySQL Parameter Group 1 engine: mysql5.6 params: innodb_lock_wait_timeout: 3600 max_allowed_packet: 512M net_write_timeout: 300 ================================================ FILE: mysql_pg_delete.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 tasks: - name: delete mysql parameter group rds_param_group: name: mysqlpg1 state: absent region: "{{ region }}" ================================================ FILE: mysql_rds_create.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 size: 100 instance_type: db.m1.small db_engine: MySQL engine_version: 5.6.22 subnet: dbsg2 parameter_group: dbpg1 # staging_sg_database security group ID security_groups: sg-xxxxxxxx iops: 1000 db_name: mydb username: dbadmin password: mypassword tasks: - name: create mysql RDS instance rds: command: create instance_name: staging-mysql-1 region: "{{ region }}" size: "{{ size }}" instance_type: "{{ instance_type }}" db_engine: "{{ db_engine }}" engine_version: "{{ engine_version }}" subnet: "{{ subnet }}" parameter_group: "{{ parameter_group }}" multi_zone: yes db_name: "{{ db_name }}" username: "{{ username }}" password: "{{ password }}" vpc_security_groups: "{{ security_groups }}" iops: "{{ iops }}" ================================================ FILE: mysql_rds_delete.yml ================================================ - hosts: localhost gather_facts: no connection: local vars: region: ap-southeast-2 tasks: - name: delete mysql RDS instance rds: command: delete region: "{{ region }}" instance_name: staging-mysql-1 ================================================ FILE: nat_launch.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars_files: - staging_vpc_info vars: region: ap-southeast-2 key: yan-key-pair-apsydney instance_type: t1.micro image: ami-3bae3201 prefix: staging tasks: - name: NAT instance provisioning ec2: region: "{{ region }}" key_name: "{{ key }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: "{{ prefix }}_sg_nat" instance_tags: Name: "{{ prefix }}_nat" class: nat environment: staging id: nat_launch_02 vpc_subnet_id: "{{ staging_subnet_public }}" source_dest_check: no wait: yes register: ec2 - name: associate new EIP for the instance tags: eip ec2_eip: region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances when: item.id is defined ================================================ FILE: roles/ansible/tasks/main.yml ================================================ --- - name: upgrade all packages yum: name=* state=latest - name: install the 'Development tools' package group yum: name="@Development tools" state=present - name: install required packages yum: name={{ item }} state=present with_items: - epel-release.noarch - python-pip - python-devel - name: install setuptools pip: name=setuptools extra_args='--upgrade' - name: install ansible pip: name=ansible ================================================ FILE: roles/apache/tasks/main.yml ================================================ --- - name: install apache yum: name=httpd state=present tags: apache - name: start the httpd service service: name=httpd state=started enabled=true tags: apache ================================================ FILE: roles/common/handlers/main.yml ================================================ --- - name: restart ntp service: name=ntpd state=restarted ================================================ FILE: roles/common/tasks/main.yml ================================================ --- - name: install ntp yum: name=ntp state=present tags: ntp - name: configure ntp file template: src=ntp.conf.j2 dest=/etc/ntp.conf tags: ntp notify: restart ntp - name: start the ntp service service: name=ntpd state=started enabled=true tags: ntp ================================================ FILE: roles/common/templates/ntp.conf.j2 ================================================ driftfile /var/lib/ntp/drift restrict 127.0.0.1 restrict -6 ::1 server {{ ntpserver }} includefile /etc/ntp/crypto/pw keys /etc/ntp/keys ================================================ FILE: roles/mysql/tasks/main.yml ================================================ --- - name: install mysql server yum: name=mysql-server state=present tags: mysql - name: start the mysql service service: name=mysqld state=started enabled=true tags: mysql ================================================ FILE: route53.yml ================================================ --- - hosts: localhost connection: local gather_facts: no tasks: - name: create record route53: command: create zone: yankurniawan.com. record: yankurniawan.com. type: A value: 54.79.34.239 ================================================ FILE: s3_create_bucket.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: bucketname: yan001 tasks: - name: create an S3 bucket s3: bucket: "{{ bucketname }}" mode: create ================================================ FILE: s3_create_dir.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: bucketname: yan001 tasks: - name: create virtual directory s3: bucket: "{{ bucketname }}" object: /backup/database/ mode: create ================================================ FILE: s3_delete_bucket.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: bucketname: yan001 tasks: - name: delete an S3 bucket and all of its contents s3: bucket: "{{ bucketname }}" mode: delete ================================================ FILE: s3_download_file.yml ================================================ --- - hosts: localhost gather_facts: no connection: local sudo: yes vars: bucketname: yan001 tasks: - name: download file s3: bucket: "{{ bucketname }}" object: /backup/database/test.txt dest: test.txt mode: get ================================================ FILE: s3_share_file.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: bucketname: yan001 tasks: - name: share file s3: bucket: "{{ bucketname }}" object: /backup/database/test.txt expiration: 3600 mode: geturl ================================================ FILE: s3_upload_file.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars: bucketname: yan001 tasks: - name: upload file s3: bucket: "{{ bucketname }}" object: /backup/database/test.txt src: test.txt overwrite: no mode: put ================================================ FILE: sg_database.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 #your ip address allowed_ip: 123.243.16.53/32 tasks: - name: create database security group ec2_group: region: "{{ region }}" name: sg_database_apsydney description: security group for apsydney database host rules: # allow ssh access from your ip address - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" # allow mysql access from webserver group - proto: tcp from_port: 3306 to_port: 3306 group_name: sg_webserver_apsydney rules_egress: - proto: all cidr_ip: 0.0.0.0/0 ================================================ FILE: sg_delete.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars_files: - staging_vpc_info vars: #your region region: ap-southeast-2 #prefix for naming prefix: staging vpc_id: "{{ staging_vpc }}" tasks: - name: delete {{ prefix }}_sg_web ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_web" description: security group for webservers state: absent - name: delete {{ prefix }}_sg_database ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_database" description: security group for databases state: absent - name: delete {{ prefix }}_sg_nat ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_nat" description: security group for nat state: absent ================================================ FILE: sg_empty.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars_files: - staging_vpc_info vars: #your region region: ap-southeast-2 #prefix for naming prefix: staging vpc_id: "{{ staging_vpc }}" tasks: - name: create empty security group for webservers local_action: module: ec2_group region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_web" description: security group for webservers - name: create empty security group for databases local_action: module: ec2_group region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_database" description: security group for databases - name: create empty security group for nat local_action: module: ec2_group region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_nat" description: security group for nat ================================================ FILE: sg_jumpbox.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars_files: - staging_vpc_info vars: #your region region: ap-southeast-2 #your ip address allowed_ip: 123.243.16.53/32 #prefix for naming prefix: staging vpc_id: "{{ staging_vpc }}" tasks: - name: create security group for jump box instance ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" #your security group name name: "{{ prefix }}_sg_jumpbox" description: security group for jump box rules: # allow ssh access from your ip address - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" rules_egress: - proto: all cidr_ip: 0.0.0.0/0 ================================================ FILE: sg_modify.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars_files: - staging_vpc_info vars: #your region region: ap-southeast-2 #your ip address allowed_ip: 54.79.34.239/32 #prefix for naming prefix: staging vpc_id: "{{ staging_vpc }}" private_subnet: 10.0.1.0/24 tasks: - name: modify sg_web rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" #your security group name name: "{{ prefix }}_sg_web" description: security group for webservers rules: # allow ssh access from your ip address - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" # allow http access from anywhere - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 # allow https access from anywhere - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 rules_egress: - proto: tcp from_port: 3306 to_port: 3306 group_name: "{{ prefix }}_sg_database" # allow http outbound - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 # allow https outbound - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 - name: modify sg_database rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_database" description: security group for databases rules: - proto: tcp from_port: 3306 to_port: 3306 group_name: "{{ prefix }}_sg_web" rules_egress: - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 - name: modify sg_nat rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" name: "{{ prefix }}_sg_nat" description: security group for nat rules: # allow ssh access from your ip address - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" # allow http access from private subnet - proto: tcp from_port: 80 to_port: 80 cidr_ip: "{{ private_subnet }}" # allow https access from private subnet - proto: tcp from_port: 443 to_port: 443 cidr_ip: "{{ private_subnet }}" rules_egress: - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 ================================================ FILE: sg_openvpn.yml ================================================ --- - hosts: localhost gather_facts: no connection: local vars_files: - staging_vpc_info vars: #your region region: ap-southeast-2 #your ip address allowed_ip: 123.243.16.53/32 #prefix for naming prefix: staging vpc_id: "{{ staging_vpc }}" tasks: - name: create security group for openvpn instance ec2_group: region: "{{ region }}" vpc_id: "{{ vpc_id }}" #your security group name name: "{{ prefix }}_sg_openvpn" description: security group for openvpn rules: - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 - proto: tcp from_port: 943 to_port: 943 cidr_ip: 0.0.0.0/0 - proto: udp from_port: 1194 to_port: 1194 cidr_ip: 0.0.0.0/0 rules_egress: - proto: all cidr_ip: 0.0.0.0/0 ================================================ FILE: sg_webserver.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 #your ip address allowed_ip: 123.243.16.53/32 tasks: - name: create security group ec2_group: region: "{{ region }}" name: sg_webserver_apsydney description: security group for apsydney webserver host rules: # allow ssh access from your ip address - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" # allow http access from anywhere - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 rules_egress: - proto: all cidr_ip: 0.0.0.0/0 ================================================ FILE: site.yml ================================================ --- # install, configure, and start ntp on all ec2 instances - hosts: ec2 sudo: yes roles: - common # install and start mysql server on instance with tags class=database - hosts: tag_class_database sudo: yes roles: - mysql # install and start apache on instance with tags class=webserver and environment=staging - hosts: tag_class_webserver:&tag_environment_staging sudo: yes roles: - apache ================================================ FILE: terminate_ec2.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 tasks: - name: terminate instances ec2: region: "{{ region }}" wait: yes instance_ids: ['i-9e1e18a1', 'i-f61b1dc9', 'i-36272109', 'i-0b272134', 'i-0a272135'] state: absent ================================================ FILE: test.txt ================================================ Hello World. This is a test. ================================================ FILE: test1.txt ================================================ Hello World. This is a test. ================================================ FILE: vpc_create.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 # prefix for naming prefix: staging # availability zone az: ap-southeast-2a tasks: - name: create vpc local_action: module: ec2_vpc region: "{{ region }}" cidr_block: 10.0.0.0/16 resource_tags: '{"Name":"{{ prefix }}_vpc"}' subnets: - cidr: 10.0.0.0/24 az: "{{ az }}" resource_tags: '{"Name":"{{ prefix }}_subnet_public"}' - cidr: 10.0.1.0/24 az: "{{ az }}" resource_tags: '{"Name":"{{ prefix }}_subnet_private"}' internet_gateway: yes route_tables: - subnets: - 10.0.0.0/24 routes: - dest: 0.0.0.0/0 gw: igw register: vpc - name: write vpc id to {{ prefix }}_vpc_info file sudo: yes local_action: shell echo "{{ prefix }}"_vpc":" "{{ vpc.vpc_id }}" > "{{ prefix }}"_vpc_info - name: write subnets id to {{ prefix }}_vpc_info file sudo: yes local_action: shell echo "{{ item.resource_tags.Name }}"":" "{{ item.id }}" >> "{{ prefix }}"_vpc_info with_items: vpc.subnets ================================================ FILE: vpc_create_multi_az.yml ================================================ --- - hosts: localhost gather_facts: no vars: region: ap-southeast-2 # prefix for naming prefix: staging # availability zones az0: ap-southeast-2a az1: ap-southeast-2b tasks: - name: create vpc with multi-az subnets local_action: module: ec2_vpc region: "{{ region }}" cidr_block: 10.0.0.0/16 resource_tags: '{"Name":"{{ prefix }}_vpc"}' subnets: - cidr: 10.0.0.0/24 az: "{{ az0 }}" resource_tags: '{"Name":"{{ prefix }}_subnet_public_0"}' - cidr: 10.0.1.0/24 az: "{{ az0 }}" resource_tags: '{"Name":"{{ prefix }}_subnet_private_0"}' - cidr: 10.0.2.0/24 az: "{{ az1 }}" resource_tags: '{"Name":"{{ prefix }}_subnet_public_1"}' - cidr: 10.0.3.0/24 az: "{{ az1 }}" resource_tags: '{"Name":"{{ prefix }}_subnet_private_1"}' internet_gateway: yes route_tables: - subnets: - 10.0.0.0/24 - 10.0.2.0/24 routes: - dest: 0.0.0.0/0 gw: igw register: vpc - name: write vpc id to {{ prefix }}_vpc_info file sudo: yes local_action: shell echo "{{ prefix }}"_vpc":" "{{ vpc.vpc_id }}" > "{{ prefix }}"_vpc_info - name: write subnets id to {{ prefix }}_vpc_info file sudo: yes local_action: shell echo "{{ item.resource_tags.Name }}"":" "{{ item.id }}" >> "{{ prefix }}"_vpc_info with_items: vpc.subnets ================================================ FILE: vpc_delete.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 tasks: - name: get vpc id local_action: module: vpc_lookup region: "{{ region }}" tags: Name: test-vpc register: vpc - name: delete vpc local_action: module: ec2_vpc region: "{{ region }}" state: absent vpc_id: "{{ item }}" wait: yes with_items: vpc.vpc_ids ================================================ FILE: vpc_delete1.yml ================================================ - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 name: test-vpc tasks: - name: get vpc id command: "aws ec2 describe-vpcs --filters Name=tag:Name,Values={{ name }} --query 'Vpcs[0].VpcId' --output text" register: vpcid - debug: var=vpcid.stdout - name: delete vpc local_action: module: ec2_vpc region: "{{ region }}" state: absent vpc_id: "{{ vpcid.stdout }}" wait: yes ================================================ FILE: vpc_info.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 tasks: - name: get subnet local_action: module: vpc_lookup region: "{{ region }}" tags: Name: staging_vpc register: vpc_subnet ================================================ FILE: wordpress/backup.yml ================================================ --- - name: backup database and store in S3 hosts: tag_class_wordpress gather_facts: no vars: bucketname: yan_wordpress tasks: - name: get date shell: date +%Y%m%d register: date - name: create mysql backup shell: mysqldump -u {{ wp_db_user }} -p{{ wp_db_password }} {{ wp_db_name }} > /tmp/{{ date.stdout }}_backup.sql - name: archive backup shell: tar -czf {{ date.stdout }}_backup.tar.gz {{ date.stdout }}_backup.sql && rm -f {{ date.stdout }}_backup.sql chdir=/tmp - name: create s3 bucket local_action: module: s3 bucket: "{{ bucketname }}" mode: create - name: upload backup archive local_action: module: s3 bucket: "{{ bucketname }}" object: /backup/database/{{ date.stdout }}_backup.tar.gz src: /tmp/{{ date.stdout }}_backup.tar.gz mode: put ================================================ FILE: wordpress/delete_backup.yml ================================================ --- - name: delete object from S3 bucket hosts: localhost gather_facts: no vars: bucketname: yan_wordpress date: 20141031 tasks: - name: delete backup file command: s3cmd del s3://{{ bucketname }}/backup/database/{{ date }}_backup.tar.gz ignore_errors: yes ================================================ FILE: wordpress/ec2.ini ================================================ # Ansible EC2 external inventory script settings # [ec2] # to talk to a private eucalyptus instance uncomment these lines # and edit edit eucalyptus_host to be the host name of your cloud controller #eucalyptus = True #eucalyptus_host = clc.cloud.domain.org # AWS regions to make calls to. Set this to 'all' to make request to all regions # in AWS and merge the results together. Alternatively, set this to a comma # separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2' #regions = all regions = ap-southeast-2 regions_exclude = us-gov-west-1,cn-north-1 # When generating inventory, Ansible needs to know how to address a server. # Each EC2 instance has a lot of variables associated with it. Here is the list: # http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance # Below are 2 variables that are used as the address of a server: # - destination_variable # - vpc_destination_variable # This is the normal destination variable to use. If you are running Ansible # from outside EC2, then 'public_dns_name' makes the most sense. If you are # running Ansible from within EC2, then perhaps you want to use the internal # address, and should set this to 'private_dns_name'. destination_variable = public_dns_name # For server inside a VPC, using DNS names may not make sense. When an instance # has 'subnet_id' set, this variable is used. If the subnet is public, setting # this to 'ip_address' will return the public IP address. For instances in a # private subnet, this should be set to 'private_ip_address', and Ansible must # be run from with EC2. vpc_destination_variable = ip_address # To tag instances on EC2 with the resource records that point to them from # Route53, uncomment and set 'route53' to True. route53 = False # Additionally, you can specify the list of zones to exclude looking up in # 'route53_excluded_zones' as a comma-separated list. # route53_excluded_zones = samplezone1.com, samplezone2.com # API calls to EC2 are slow. For this reason, we cache the results of an API # call. Set this to the path you want cache files to be written to. Two files # will be written to this directory: # - ansible-ec2.cache # - ansible-ec2.index cache_path = ~/.ansible/tmp # The number of seconds a cache file is considered valid. After this many # seconds, a new API call will be made, and the cache file will be updated. # To disable the cache, set this value to 0 cache_max_age = 300 ================================================ FILE: wordpress/ec2.py ================================================ #!/usr/bin/env python ''' EC2 external inventory script ================================= Generates inventory that Ansible can understand by making API request to AWS EC2 using the Boto library. NOTE: This script assumes Ansible is being executed where the environment variables needed for Boto have already been set: export AWS_ACCESS_KEY_ID='AK123' export AWS_SECRET_ACCESS_KEY='abc123' This script also assumes there is an ec2.ini file alongside it. To specify a different path to ec2.ini, define the EC2_INI_PATH environment variable: export EC2_INI_PATH=/path/to/my_ec2.ini If you're using eucalyptus you need to set the above variables and you need to define: export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html When run against a specific host, this script returns the following variables: - ec2_ami_launch_index - ec2_architecture - ec2_association - ec2_attachTime - ec2_attachment - ec2_attachmentId - ec2_client_token - ec2_deleteOnTermination - ec2_description - ec2_deviceIndex - ec2_dns_name - ec2_eventsSet - ec2_group_name - ec2_hypervisor - ec2_id - ec2_image_id - ec2_instanceState - ec2_instance_type - ec2_ipOwnerId - ec2_ip_address - ec2_item - ec2_kernel - ec2_key_name - ec2_launch_time - ec2_monitored - ec2_monitoring - ec2_networkInterfaceId - ec2_ownerId - ec2_persistent - ec2_placement - ec2_platform - ec2_previous_state - ec2_private_dns_name - ec2_private_ip_address - ec2_publicIp - ec2_public_dns_name - ec2_ramdisk - ec2_reason - ec2_region - ec2_requester_id - ec2_root_device_name - ec2_root_device_type - ec2_security_group_ids - ec2_security_group_names - ec2_shutdown_state - ec2_sourceDestCheck - ec2_spot_instance_request_id - ec2_state - ec2_state_code - ec2_state_reason - ec2_status - ec2_subnet_id - ec2_tenancy - ec2_virtualization_type - ec2_vpc_id These variables are pulled out of a boto.ec2.instance object. There is a lack of consistency with variable spellings (camelCase and underscores) since this just loops through all variables the object exposes. It is preferred to use the ones with underscores when multiple exist. In addition, if an instance has AWS Tags associated with it, each tag is a new variable named: - ec2_tag_[Key] = [Value] Security groups are comma-separated in 'ec2_security_group_ids' and 'ec2_security_group_names'. ''' # (c) 2012, Peter Sankauskas # # This file is part of Ansible, # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . ###################################################################### import sys import os import argparse import re from time import time import boto from boto import ec2 from boto import rds from boto import route53 import ConfigParser try: import json except ImportError: import simplejson as json class Ec2Inventory(object): def _empty_inventory(self): return {"_meta" : {"hostvars" : {}}} def __init__(self): ''' Main execution path ''' # Inventory grouped by instance IDs, tags, security groups, regions, # and availability zones self.inventory = self._empty_inventory() # Index of hostname (address) to instance ID self.index = {} # Read settings and parse CLI arguments self.read_settings() self.parse_cli_args() # Cache if self.args.refresh_cache: self.do_api_calls_update_cache() elif not self.is_cache_valid(): self.do_api_calls_update_cache() # Data to print if self.args.host: data_to_print = self.get_host_info() elif self.args.list: # Display list of instances for inventory if self.inventory == self._empty_inventory(): data_to_print = self.get_inventory_from_cache() else: data_to_print = self.json_format_dict(self.inventory, True) print data_to_print def is_cache_valid(self): ''' Determines if the cache files have expired, or if it is still valid ''' if os.path.isfile(self.cache_path_cache): mod_time = os.path.getmtime(self.cache_path_cache) current_time = time() if (mod_time + self.cache_max_age) > current_time: if os.path.isfile(self.cache_path_index): return True return False def read_settings(self): ''' Reads the settings from the ec2.ini file ''' config = ConfigParser.SafeConfigParser() ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini') ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path) config.read(ec2_ini_path) # is eucalyptus? self.eucalyptus_host = None self.eucalyptus = False if config.has_option('ec2', 'eucalyptus'): self.eucalyptus = config.getboolean('ec2', 'eucalyptus') if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'): self.eucalyptus_host = config.get('ec2', 'eucalyptus_host') # Regions self.regions = [] configRegions = config.get('ec2', 'regions') configRegions_exclude = config.get('ec2', 'regions_exclude') if (configRegions == 'all'): if self.eucalyptus_host: self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name) else: for regionInfo in ec2.regions(): if regionInfo.name not in configRegions_exclude: self.regions.append(regionInfo.name) else: self.regions = configRegions.split(",") # Destination addresses self.destination_variable = config.get('ec2', 'destination_variable') self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable') # Route53 self.route53_enabled = config.getboolean('ec2', 'route53') self.route53_excluded_zones = [] if config.has_option('ec2', 'route53_excluded_zones'): self.route53_excluded_zones.extend( config.get('ec2', 'route53_excluded_zones', '').split(',')) # Cache related cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) if not os.path.exists(cache_dir): os.makedirs(cache_dir) self.cache_path_cache = cache_dir + "/ansible-ec2.cache" self.cache_path_index = cache_dir + "/ansible-ec2.index" self.cache_max_age = config.getint('ec2', 'cache_max_age') def parse_cli_args(self): ''' Command line argument processing ''' parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') parser.add_argument('--refresh-cache', action='store_true', default=False, help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') self.args = parser.parse_args() def do_api_calls_update_cache(self): ''' Do API calls to each region, and save data in cache files ''' if self.route53_enabled: self.get_route53_records() for region in self.regions: self.get_instances_by_region(region) self.get_rds_instances_by_region(region) self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) def get_instances_by_region(self, region): ''' Makes an AWS EC2 API call to the list of instances in a particular region ''' try: if self.eucalyptus: conn = boto.connect_euca(host=self.eucalyptus_host) conn.APIVersion = '2010-08-31' else: conn = ec2.connect_to_region(region) # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported if conn is None: print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) sys.exit(1) reservations = conn.get_all_instances() for reservation in reservations: for instance in reservation.instances: self.add_instance(instance, region) except boto.exception.BotoServerError, e: if not self.eucalyptus: print "Looks like AWS is down again:" print e sys.exit(1) def get_rds_instances_by_region(self, region): ''' Makes an AWS API call to the list of RDS instances in a particular region ''' try: conn = rds.connect_to_region(region) if conn: instances = conn.get_all_dbinstances() for instance in instances: self.add_rds_instance(instance, region) except boto.exception.BotoServerError, e: if not e.reason == "Forbidden": print "Looks like AWS RDS is down: " print e sys.exit(1) def get_instance(self, region, instance_id): ''' Gets details about a specific instance ''' if self.eucalyptus: conn = boto.connect_euca(self.eucalyptus_host) conn.APIVersion = '2010-08-31' else: conn = ec2.connect_to_region(region) # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported if conn is None: print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) sys.exit(1) reservations = conn.get_all_instances([instance_id]) for reservation in reservations: for instance in reservation.instances: return instance def add_instance(self, instance, region): ''' Adds an instance to the inventory and index, as long as it is addressable ''' # Only want running instances if instance.state != 'running': return # Select the best destination address if instance.subnet_id: dest = getattr(instance, self.vpc_destination_variable) else: dest = getattr(instance, self.destination_variable) if not dest: # Skip instances we cannot address (e.g. private VPC subnet) return # Add to index self.index[dest] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) self.inventory[instance.id] = [dest] # Inventory: Group by region self.push(self.inventory, region, dest) # Inventory: Group by availability zone self.push(self.inventory, instance.placement, dest) # Inventory: Group by instance type self.push(self.inventory, self.to_safe('type_' + instance.instance_type), dest) # Inventory: Group by key pair if instance.key_name: self.push(self.inventory, self.to_safe('key_' + instance.key_name), dest) # Inventory: Group by security group try: for group in instance.groups: key = self.to_safe("security_group_" + group.name) self.push(self.inventory, key, dest) except AttributeError: print 'Package boto seems a bit older.' print 'Please upgrade boto >= 2.3.0.' sys.exit(1) # Inventory: Group by tag keys for k, v in instance.tags.iteritems(): key = self.to_safe("tag_" + k + "=" + v) self.push(self.inventory, key, dest) # Inventory: Group by Route53 domain names if enabled if self.route53_enabled: route53_names = self.get_instance_route53_names(instance) for name in route53_names: self.push(self.inventory, name, dest) # Global Tag: tag all EC2 instances self.push(self.inventory, 'ec2', dest) self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) def add_rds_instance(self, instance, region): ''' Adds an RDS instance to the inventory and index, as long as it is addressable ''' # Only want available instances if instance.status != 'available': return # Select the best destination address #if instance.subnet_id: #dest = getattr(instance, self.vpc_destination_variable) #else: #dest = getattr(instance, self.destination_variable) dest = instance.endpoint[0] if not dest: # Skip instances we cannot address (e.g. private VPC subnet) return # Add to index self.index[dest] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) self.inventory[instance.id] = [dest] # Inventory: Group by region self.push(self.inventory, region, dest) # Inventory: Group by availability zone self.push(self.inventory, instance.availability_zone, dest) # Inventory: Group by instance type self.push(self.inventory, self.to_safe('type_' + instance.instance_class), dest) # Inventory: Group by security group try: if instance.security_group: key = self.to_safe("security_group_" + instance.security_group.name) self.push(self.inventory, key, dest) except AttributeError: print 'Package boto seems a bit older.' print 'Please upgrade boto >= 2.3.0.' sys.exit(1) # Inventory: Group by engine self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest) # Inventory: Group by parameter group self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest) # Global Tag: all RDS instances self.push(self.inventory, 'rds', dest) def get_route53_records(self): ''' Get and store the map of resource records to domain names that point to them. ''' r53_conn = route53.Route53Connection() all_zones = r53_conn.get_zones() route53_zones = [ zone for zone in all_zones if zone.name[:-1] not in self.route53_excluded_zones ] self.route53_records = {} for zone in route53_zones: rrsets = r53_conn.get_all_rrsets(zone.id) for record_set in rrsets: record_name = record_set.name if record_name.endswith('.'): record_name = record_name[:-1] for resource in record_set.resource_records: self.route53_records.setdefault(resource, set()) self.route53_records[resource].add(record_name) def get_instance_route53_names(self, instance): ''' Check if an instance is referenced in the records we have from Route53. If it is, return the list of domain names pointing to said instance. If nothing points to it, return an empty list. ''' instance_attributes = [ 'public_dns_name', 'private_dns_name', 'ip_address', 'private_ip_address' ] name_list = set() for attrib in instance_attributes: try: value = getattr(instance, attrib) except AttributeError: continue if value in self.route53_records: name_list.update(self.route53_records[value]) return list(name_list) def get_host_info_dict_from_instance(self, instance): instance_vars = {} for key in vars(instance): value = getattr(instance, key) key = self.to_safe('ec2_' + key) # Handle complex types # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518 if key == 'ec2__state': instance_vars['ec2_state'] = instance.state or '' instance_vars['ec2_state_code'] = instance.state_code elif key == 'ec2__previous_state': instance_vars['ec2_previous_state'] = instance.previous_state or '' instance_vars['ec2_previous_state_code'] = instance.previous_state_code elif type(value) in [int, bool]: instance_vars[key] = value elif type(value) in [str, unicode]: instance_vars[key] = value.strip() elif type(value) == type(None): instance_vars[key] = '' elif key == 'ec2_region': instance_vars[key] = value.name elif key == 'ec2__placement': instance_vars['ec2_placement'] = value.zone elif key == 'ec2_tags': for k, v in value.iteritems(): key = self.to_safe('ec2_tag_' + k) instance_vars[key] = v elif key == 'ec2_groups': group_ids = [] group_names = [] for group in value: group_ids.append(group.id) group_names.append(group.name) instance_vars["ec2_security_group_ids"] = ','.join(group_ids) instance_vars["ec2_security_group_names"] = ','.join(group_names) else: pass # TODO Product codes if someone finds them useful #print key #print type(value) #print value return instance_vars def get_host_info(self): ''' Get variables about a specific host ''' if len(self.index) == 0: # Need to load index from cache self.load_index_from_cache() if not self.args.host in self.index: # try updating the cache self.do_api_calls_update_cache() if not self.args.host in self.index: # host migh not exist anymore return self.json_format_dict({}, True) (region, instance_id) = self.index[self.args.host] instance = self.get_instance(region, instance_id) return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True) def push(self, my_dict, key, element): ''' Pushed an element onto an array that may not have been defined in the dict ''' if key in my_dict: my_dict[key].append(element); else: my_dict[key] = [element] def get_inventory_from_cache(self): ''' Reads the inventory from the cache file and returns it as a JSON object ''' cache = open(self.cache_path_cache, 'r') json_inventory = cache.read() return json_inventory def load_index_from_cache(self): ''' Reads the index from the cache file sets self.index ''' cache = open(self.cache_path_index, 'r') json_index = cache.read() self.index = json.loads(json_index) def write_to_cache(self, data, filename): ''' Writes data in JSON format to a file ''' json_data = self.json_format_dict(data, True) cache = open(filename, 'w') cache.write(json_data) cache.close() def to_safe(self, word): ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' return re.sub("[^A-Za-z0-9\-]", "_", word) def json_format_dict(self, data, pretty=False): ''' Converts a dict to a JSON object and dumps it as a formatted string ''' if pretty: return json.dumps(data, sort_keys=True, indent=2) else: return json.dumps(data) # Run the script Ec2Inventory() ================================================ FILE: wordpress/group_vars/all ================================================ ansible_ssh_user: ec2-user ansible_ssh_private_key_file: ~/.ssh/wordpress-apsydney.pem # Which version of WordPress to deploy wp_version: 4.5.3 #wp_sha256sum: 73c21224d42156150b948ca645a296a2431f1dd6a19350e0d8a72e465adde56d # These are the WordPress database settings wp_db_name: wordpress wp_db_user: wordpress # Set your database password here wp_db_password: secret # WordPress settings # Disable All Updates # By default automatic updates are enabled, set this value to true to disable all automatic updates auto_up_disable: false #Define Core Update Level #true = Development, minor, and major updates are all enabled #false = Development, minor, and major updates are all disabled #minor = Minor updates are enabled, development, and major updates are disabled core_update_level: true ================================================ FILE: wordpress/hosts ================================================ [local] localhost ================================================ FILE: wordpress/provisioning.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 keyname: wordpress-apsydney #your ip address allowed_ip: 203.87.79.2/32 instance_type: t2.micro image: ami-dc361ebf tasks: - name: create key pair tags: keypair ec2_key: region: "{{ region }}" name: "{{ keyname }}" register: mykey - name: write the private key to file copy: content="{{ mykey.key.private_key }}" dest="~/.ssh/{{ keyname }}.pem" mode=0600 when: mykey.changed - name: create security group tags: sg ec2_group: region: "{{ region }}" #your security group name name: sg_wordpress_apsydney description: security group for apsydney webserver host rules: # allow ssh access from your ip address - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" # allow http access from anywhere - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 # allow https access from anywhere - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 rules_egress: - proto: all cidr_ip: 0.0.0.0/0 - name: launch ec2 instance tags: ec2 local_action: module: ec2 region: "{{ region }}" key_name: "{{ keyname }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: sg_wordpress_apsydney instance_tags: Name: wordpress-1 class: wordpress register: ec2 - name: associate new EIP for the instance tags: eip local_action: module: ec2_eip region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances when: item.id is defined ================================================ FILE: wordpress/restore.yml ================================================ --- - name: download backup from S3 and restore hosts: tag_class_wordpress gather_facts: no vars: bucketname: yan_wordpress date: 20140920 tasks: - name: download backup archive local_action: module: s3 bucket: "{{ bucketname }}" object: /backup/database/{{ date }}_backup.tar.gz dest: /tmp/{{ date }}_backup.tar.gz mode: get - name: extract archive and restore mysql backup shell: tar -xzf {{ date }}_backup.tar.gz && mysql -u {{ wp_db_user }} -p{{ wp_db_password }} {{ wp_db_name }} < {{ date }}_backup.sql chdir=/tmp ================================================ FILE: wordpress/roles/common/tasks/main.yml ================================================ --- - name: install the 'Development tools' package group yum: name="@Development tools" state=present update_cache=yes ================================================ FILE: wordpress/roles/mysql/handlers/main.yml ================================================ --- - name: restart mysql service: name=mysqld state=restarted ================================================ FILE: wordpress/roles/mysql/tasks/main.yml ================================================ --- - name: install mysql server yum: name={{ item }} state=present with_items: - mysql-devel - mysql-server - name: install mysql-python pip: name=mysql-python state=present - name: create mysql configuration file template: src=my.cnf.j2 dest=/etc/my.cnf notify: restart mysql - name: start mysql service service: name=mysqld state=started enabled=true ================================================ FILE: wordpress/roles/mysql/templates/my.cnf.j2 ================================================ # You can customize your mysql server configuration here [mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock user=mysql # Disabling symbolic-links is recommended to prevent assorted security risks symbolic-links=0 port=3306 [mysqld_safe] log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid ================================================ FILE: wordpress/roles/web/tasks/main.yml ================================================ --- - name: install apache, php, and php-mysql yum: name={{ item }} state=present with_items: - httpd - php - php-mysql - name: start and enable httpd service: name=httpd state=started enabled=yes ================================================ FILE: wordpress/roles/wordpress/tasks/main.yml ================================================ --- - name: download wordpress get_url: url=http://wordpress.org/wordpress-{{ wp_version }}.tar.gz dest=~/wordpress-{{ wp_version }}.tar.gz # sha256sum="{{ wp_sha256sum }}" - name: extract wordpress archive command: chdir=~ /bin/tar xvf wordpress-{{ wp_version }}.tar.gz creates=~/wordpress - name: copy wordpress to apache root directory shell: cp -r ~/wordpress/* /var/www/html - name: fetch random salts for wordpress config command: curl https://api.wordpress.org/secret-key/1.1/salt/ register: "wp_salt" - name: create wordpress database mysql_db: name={{ wp_db_name }} state=present - name: create wordpress database user mysql_user: name={{ wp_db_user }} password={{ wp_db_password }} priv={{ wp_db_name }}.*:ALL host='localhost' state=present - name: copy wordpress config file template: src=wp-config.php dest=/var/www/html/ - name: change ownership of wordpress installation file: path=/var/www/html/ owner=apache group=apache state=directory recurse=yes ================================================ FILE: wordpress/roles/wordpress/templates/wp-config.php ================================================ . ###################################################################### import sys import os import argparse import re from time import time import boto from boto import ec2 from boto import rds from boto import route53 import ConfigParser try: import json except ImportError: import simplejson as json class Ec2Inventory(object): def _empty_inventory(self): return {"_meta" : {"hostvars" : {}}} def __init__(self): ''' Main execution path ''' # Inventory grouped by instance IDs, tags, security groups, regions, # and availability zones self.inventory = self._empty_inventory() # Index of hostname (address) to instance ID self.index = {} # Read settings and parse CLI arguments self.read_settings() self.parse_cli_args() # Cache if self.args.refresh_cache: self.do_api_calls_update_cache() elif not self.is_cache_valid(): self.do_api_calls_update_cache() # Data to print if self.args.host: data_to_print = self.get_host_info() elif self.args.list: # Display list of instances for inventory if self.inventory == self._empty_inventory(): data_to_print = self.get_inventory_from_cache() else: data_to_print = self.json_format_dict(self.inventory, True) print data_to_print def is_cache_valid(self): ''' Determines if the cache files have expired, or if it is still valid ''' if os.path.isfile(self.cache_path_cache): mod_time = os.path.getmtime(self.cache_path_cache) current_time = time() if (mod_time + self.cache_max_age) > current_time: if os.path.isfile(self.cache_path_index): return True return False def read_settings(self): ''' Reads the settings from the ec2.ini file ''' config = ConfigParser.SafeConfigParser() ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini') ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path) config.read(ec2_ini_path) # is eucalyptus? self.eucalyptus_host = None self.eucalyptus = False if config.has_option('ec2', 'eucalyptus'): self.eucalyptus = config.getboolean('ec2', 'eucalyptus') if self.eucalyptus and config.has_option('ec2', 'eucalyptus_host'): self.eucalyptus_host = config.get('ec2', 'eucalyptus_host') # Regions self.regions = [] configRegions = config.get('ec2', 'regions') configRegions_exclude = config.get('ec2', 'regions_exclude') if (configRegions == 'all'): if self.eucalyptus_host: self.regions.append(boto.connect_euca(host=self.eucalyptus_host).region.name) else: for regionInfo in ec2.regions(): if regionInfo.name not in configRegions_exclude: self.regions.append(regionInfo.name) else: self.regions = configRegions.split(",") # Destination addresses self.destination_variable = config.get('ec2', 'destination_variable') self.vpc_destination_variable = config.get('ec2', 'vpc_destination_variable') # Route53 self.route53_enabled = config.getboolean('ec2', 'route53') self.route53_excluded_zones = [] if config.has_option('ec2', 'route53_excluded_zones'): self.route53_excluded_zones.extend( config.get('ec2', 'route53_excluded_zones', '').split(',')) # Cache related cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) if not os.path.exists(cache_dir): os.makedirs(cache_dir) self.cache_path_cache = cache_dir + "/ansible-ec2.cache" self.cache_path_index = cache_dir + "/ansible-ec2.index" self.cache_max_age = config.getint('ec2', 'cache_max_age') def parse_cli_args(self): ''' Command line argument processing ''' parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on EC2') parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)') parser.add_argument('--host', action='store', help='Get all the variables about a specific instance') parser.add_argument('--refresh-cache', action='store_true', default=False, help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') self.args = parser.parse_args() def do_api_calls_update_cache(self): ''' Do API calls to each region, and save data in cache files ''' if self.route53_enabled: self.get_route53_records() for region in self.regions: self.get_instances_by_region(region) self.get_rds_instances_by_region(region) self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.index, self.cache_path_index) def get_instances_by_region(self, region): ''' Makes an AWS EC2 API call to the list of instances in a particular region ''' try: if self.eucalyptus: conn = boto.connect_euca(host=self.eucalyptus_host) conn.APIVersion = '2010-08-31' else: conn = ec2.connect_to_region(region) # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported if conn is None: print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) sys.exit(1) reservations = conn.get_all_instances() for reservation in reservations: for instance in reservation.instances: self.add_instance(instance, region) except boto.exception.BotoServerError, e: if not self.eucalyptus: print "Looks like AWS is down again:" print e sys.exit(1) def get_rds_instances_by_region(self, region): ''' Makes an AWS API call to the list of RDS instances in a particular region ''' try: conn = rds.connect_to_region(region) if conn: instances = conn.get_all_dbinstances() for instance in instances: self.add_rds_instance(instance, region) except boto.exception.BotoServerError, e: if not e.reason == "Forbidden": print "Looks like AWS RDS is down: " print e sys.exit(1) def get_instance(self, region, instance_id): ''' Gets details about a specific instance ''' if self.eucalyptus: conn = boto.connect_euca(self.eucalyptus_host) conn.APIVersion = '2010-08-31' else: conn = ec2.connect_to_region(region) # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported if conn is None: print("region name: %s likely not supported, or AWS is down. connection to region failed." % region) sys.exit(1) reservations = conn.get_all_instances([instance_id]) for reservation in reservations: for instance in reservation.instances: return instance def add_instance(self, instance, region): ''' Adds an instance to the inventory and index, as long as it is addressable ''' # Only want running instances if instance.state != 'running': return # Select the best destination address if instance.subnet_id: dest = getattr(instance, self.vpc_destination_variable) else: dest = getattr(instance, self.destination_variable) if not dest: # Skip instances we cannot address (e.g. private VPC subnet) return # Add to index self.index[dest] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) self.inventory[instance.id] = [dest] # Inventory: Group by region self.push(self.inventory, region, dest) # Inventory: Group by availability zone self.push(self.inventory, instance.placement, dest) # Inventory: Group by instance type self.push(self.inventory, self.to_safe('type_' + instance.instance_type), dest) # Inventory: Group by key pair if instance.key_name: self.push(self.inventory, self.to_safe('key_' + instance.key_name), dest) # Inventory: Group by security group try: for group in instance.groups: key = self.to_safe("security_group_" + group.name) self.push(self.inventory, key, dest) except AttributeError: print 'Package boto seems a bit older.' print 'Please upgrade boto >= 2.3.0.' sys.exit(1) # Inventory: Group by tag keys for k, v in instance.tags.iteritems(): key = self.to_safe("tag_" + k + "=" + v) self.push(self.inventory, key, dest) # Inventory: Group by Route53 domain names if enabled if self.route53_enabled: route53_names = self.get_instance_route53_names(instance) for name in route53_names: self.push(self.inventory, name, dest) # Global Tag: tag all EC2 instances self.push(self.inventory, 'ec2', dest) self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) def add_rds_instance(self, instance, region): ''' Adds an RDS instance to the inventory and index, as long as it is addressable ''' # Only want available instances if instance.status != 'available': return # Select the best destination address #if instance.subnet_id: #dest = getattr(instance, self.vpc_destination_variable) #else: #dest = getattr(instance, self.destination_variable) dest = instance.endpoint[0] if not dest: # Skip instances we cannot address (e.g. private VPC subnet) return # Add to index self.index[dest] = [region, instance.id] # Inventory: Group by instance ID (always a group of 1) self.inventory[instance.id] = [dest] # Inventory: Group by region self.push(self.inventory, region, dest) # Inventory: Group by availability zone self.push(self.inventory, instance.availability_zone, dest) # Inventory: Group by instance type self.push(self.inventory, self.to_safe('type_' + instance.instance_class), dest) # Inventory: Group by security group try: if instance.security_group: key = self.to_safe("security_group_" + instance.security_group.name) self.push(self.inventory, key, dest) except AttributeError: print 'Package boto seems a bit older.' print 'Please upgrade boto >= 2.3.0.' sys.exit(1) # Inventory: Group by engine self.push(self.inventory, self.to_safe("rds_" + instance.engine), dest) # Inventory: Group by parameter group self.push(self.inventory, self.to_safe("rds_parameter_group_" + instance.parameter_group.name), dest) # Global Tag: all RDS instances self.push(self.inventory, 'rds', dest) def get_route53_records(self): ''' Get and store the map of resource records to domain names that point to them. ''' r53_conn = route53.Route53Connection() all_zones = r53_conn.get_zones() route53_zones = [ zone for zone in all_zones if zone.name[:-1] not in self.route53_excluded_zones ] self.route53_records = {} for zone in route53_zones: rrsets = r53_conn.get_all_rrsets(zone.id) for record_set in rrsets: record_name = record_set.name if record_name.endswith('.'): record_name = record_name[:-1] for resource in record_set.resource_records: self.route53_records.setdefault(resource, set()) self.route53_records[resource].add(record_name) def get_instance_route53_names(self, instance): ''' Check if an instance is referenced in the records we have from Route53. If it is, return the list of domain names pointing to said instance. If nothing points to it, return an empty list. ''' instance_attributes = [ 'public_dns_name', 'private_dns_name', 'ip_address', 'private_ip_address' ] name_list = set() for attrib in instance_attributes: try: value = getattr(instance, attrib) except AttributeError: continue if value in self.route53_records: name_list.update(self.route53_records[value]) return list(name_list) def get_host_info_dict_from_instance(self, instance): instance_vars = {} for key in vars(instance): value = getattr(instance, key) key = self.to_safe('ec2_' + key) # Handle complex types # state/previous_state changed to properties in boto in https://github.com/boto/boto/commit/a23c379837f698212252720d2af8dec0325c9518 if key == 'ec2__state': instance_vars['ec2_state'] = instance.state or '' instance_vars['ec2_state_code'] = instance.state_code elif key == 'ec2__previous_state': instance_vars['ec2_previous_state'] = instance.previous_state or '' instance_vars['ec2_previous_state_code'] = instance.previous_state_code elif type(value) in [int, bool]: instance_vars[key] = value elif type(value) in [str, unicode]: instance_vars[key] = value.strip() elif type(value) == type(None): instance_vars[key] = '' elif key == 'ec2_region': instance_vars[key] = value.name elif key == 'ec2__placement': instance_vars['ec2_placement'] = value.zone elif key == 'ec2_tags': for k, v in value.iteritems(): key = self.to_safe('ec2_tag_' + k) instance_vars[key] = v elif key == 'ec2_groups': group_ids = [] group_names = [] for group in value: group_ids.append(group.id) group_names.append(group.name) instance_vars["ec2_security_group_ids"] = ','.join(group_ids) instance_vars["ec2_security_group_names"] = ','.join(group_names) else: pass # TODO Product codes if someone finds them useful #print key #print type(value) #print value return instance_vars def get_host_info(self): ''' Get variables about a specific host ''' if len(self.index) == 0: # Need to load index from cache self.load_index_from_cache() if not self.args.host in self.index: # try updating the cache self.do_api_calls_update_cache() if not self.args.host in self.index: # host migh not exist anymore return self.json_format_dict({}, True) (region, instance_id) = self.index[self.args.host] instance = self.get_instance(region, instance_id) return self.json_format_dict(self.get_host_info_dict_from_instance(instance), True) def push(self, my_dict, key, element): ''' Pushed an element onto an array that may not have been defined in the dict ''' if key in my_dict: my_dict[key].append(element); else: my_dict[key] = [element] def get_inventory_from_cache(self): ''' Reads the inventory from the cache file and returns it as a JSON object ''' cache = open(self.cache_path_cache, 'r') json_inventory = cache.read() return json_inventory def load_index_from_cache(self): ''' Reads the index from the cache file sets self.index ''' cache = open(self.cache_path_index, 'r') json_index = cache.read() self.index = json.loads(json_index) def write_to_cache(self, data, filename): ''' Writes data in JSON format to a file ''' json_data = self.json_format_dict(data, True) cache = open(filename, 'w') cache.write(json_data) cache.close() def to_safe(self, word): ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups ''' return re.sub("[^A-Za-z0-9\-]", "_", word) def json_format_dict(self, data, pretty=False): ''' Converts a dict to a JSON object and dumps it as a formatted string ''' if pretty: return json.dumps(data, sort_keys=True, indent=2) else: return json.dumps(data) # Run the script Ec2Inventory() ================================================ FILE: wordpress_ha/group_vars/all ================================================ --- db_name: wordpress username: dbadmin password: mypassword dbhost: staging-wordpress-rds.cxzxl961nonk.ap-southeast-2.rds.amazonaws.com ansible_ssh_user: ec2-user ansible_ssh_private_key_file: ~/.ssh/wordpress-apsydney.pem wp_version: 4.1 # WordPress settings # Disable All Updates # By default automatic updates are enabled, set this value to true to disable all automatic updates auto_up_disable: false #Define Core Update Level #true = Development, minor, and major updates are all enabled #false = Development, minor, and major updates are all disabled #minor = Minor updates are enabled, development, and major updates are disabled core_update_level: true ================================================ FILE: wordpress_ha/hosts ================================================ [local] localhost ================================================ FILE: wordpress_ha/provisioning_asg.yml ================================================ --- - hosts: localhost connection: local gather_facts: no tasks: - include_vars: "{{ env }}.yml" - set_fact: timestamp: "{{ lookup('pipe', 'date +%g%m%d%H%M%S') }}" - name: Create public ELB ec2_elb_lb: region: "{{ region }}" name: "{{ asg_name }}-{{ env }}" state: present cross_az_load_balancing: yes security_group_ids: "{{ elb_group_ids }}" subnets: "{{ elb_subnets }}" listeners: - protocol: http load_balancer_port: 80 instance_port: 80 health_check: ping_protocol: http ping_port: 80 ping_path: "/index.php" response_timeout: 2 interval: 10 unhealthy_threshold: 2 healthy_threshold: 2 connection_draining_timeout: 60 register: elb - debug: var=elb - name: Create Launch Configuration ec2_lc: region: "{{ region }}" name: "{{ asg_name }}-{{ env }}-{{ timestamp }}" image_id: "{{ image_id }}" key_name: "{{ keypair }}" instance_type: "{{ instance_type }}" security_groups: "{{ security_groups }}" instance_monitoring: yes register: lc - debug: var=lc - name: Configure Auto Scaling Group ec2_asg: region: "{{ region }}" name: "{{ asg_name }}-{{ env }}-{{ timestamp }}" vpc_zone_identifier: "{{ asg_subnet_ids }}" launch_config_name: "{{ lc.name }}" availability_zones: "{{ zones }}" health_check_type: EC2 health_check_period: 300 desired_capacity: "{{ asg_min }}" min_size: "{{ asg_min }}" max_size: "{{ asg_max }}" tags: - Name: "{{ asg_name }}-{{ env }}" load_balancers: "{{ elb.elb.name }}" state: present register: asg - debug: var=asg - name: Configure Scaling Policies ec2_scaling_policy: region: "{{ region }}" name: "{{ item.name }}" asg_name: "{{ asg_name }}-{{ env }}-{{ timestamp }}" state: present adjustment_type: "{{ item.adjustment_type }}" min_adjustment_step: "{{ item.min_adjustment_step }}" scaling_adjustment: "{{ item.scaling_adjustment }}" cooldown: "{{ item.cooldown }}" with_items: - name: "Increase Group Size" adjustment_type: "ChangeInCapacity" scaling_adjustment: +1 min_adjustment_step: 1 cooldown: 180 - name: "Decrease Group Size" adjustment_type: "ChangeInCapacity" scaling_adjustment: -1 min_adjustment_step: 1 cooldown: 300 register: scaling_policy - debug: var=scaling_policy - name: Define Metric Alarms configuration set_fact: metric_alarms: - name: "{{ asg.name }}-ScaleUp" comparison: ">=" threshold: 70.0 alarm_actions: - "{{ scaling_policy.results[0].arn }}" - name: "{{ asg.name }}-ScaleDown" comparison: "<=" threshold: 30.0 alarm_actions: - "{{ scaling_policy.results[1].arn }}" - name: Configure Metric Alarms ec2_metric_alarm: region: "{{ region }}" name: "{{ item.name }}" state: present metric: "CPUUtilization" namespace: "AWS/EC2" statistic: "Average" comparison: "{{ item.comparison }}" threshold: "{{ item.threshold }}" period: 60 evaluation_periods: 5 unit: "Percent" dimensions: AutoScalingGroupName: "{{ asg.name }}" alarm_actions: "{{ item.alarm_actions }}" with_items: "{{ metric_alarms }}" when: "{{ asg.max_size }} > 1" register: alarms - debug: var=alarms ================================================ FILE: wordpress_ha/provisioning_rds.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 env: staging size: 5 instance_type: db.t2.micro db_engine: MySQL engine_version: 5.6.23 subnet_group: dbsg_wordpress param_group: wordpress # staging_sg_database security group ID security_groups: sg-eeb14e8b tasks: - name: "get {{ env }}_subnet_private_0 subnet id" command: "aws ec2 describe-subnets --filters Name=tag:Name,Values={{ env }}_subnet_private_0 --region {{ region }} --query 'Subnets[0].SubnetId' --output text" register: subnet0 - debug: var=subnet0.stdout - name: "get {{ env }}_subnet_private_1 subnet id" command: "aws ec2 describe-subnets --filters Name=tag:Name,Values={{ env }}_subnet_private_1 --region {{ region }} --query 'Subnets[0].SubnetId' --output text" register: subnet1 - debug: var=subnet1.stdout - name: create Multi-AZ DB subnet group rds_subnet_group: name: "{{ subnet_group }}" state: present region: "{{ region }}" description: DB Subnet Group for WordPress HA subnets: - "{{ subnet0.stdout }}" - "{{ subnet1.stdout }}" - name: create mysql parameter group rds_param_group: name: "{{ param_group }}" state: present region: "{{ region }}" description: MySQL Parameter Group for WordPress HA engine: mysql5.6 params: innodb_lock_wait_timeout: 3600 max_allowed_packet: 512M net_write_timeout: 300 - name: create mysql RDS instance rds: command: create instance_name: "{{ env }}-wordpress-rds" region: "{{ region }}" size: "{{ size }}" instance_type: "{{ instance_type }}" db_engine: "{{ db_engine }}" engine_version: "{{ engine_version }}" subnet: "{{ subnet_group }}" parameter_group: "{{ param_group }}" multi_zone: yes db_name: "{{ db_name }}" username: "{{ username }}" password: "{{ password }}" vpc_security_groups: "{{ security_groups }}" ================================================ FILE: wordpress_ha/provisioning_sg.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 allowed_ip: xx.xx.xx.xx/32 vpc_cidr: 10.0.0.0/16 env: staging tasks: - name: get vpc id command: "aws ec2 describe-vpcs --filters Name=tag:Name,Values={{ env }}_vpc --query 'Vpcs[0].VpcId' --output text" register: vpcid - name: create sg_web rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpcid.stdout }}" name: "{{ env }}_sg_web" description: security group for public web rules: # allow ssh access from ansible group - proto: tcp from_port: 22 to_port: 22 group_name: "{{ env }}_sg_ansible" group_desc: security group for ansible # allow http access from anywhere - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 # allow https access from anywhere - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 - name: create sg_wordpress rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpcid.stdout }}" name: "{{ env }}_sg_wordpress" description: security group for wordpress servers rules: # allow ssh access from ansible group - proto: tcp from_port: 22 to_port: 22 group_name: "{{ env }}_sg_ansible" group_desc: security group for ansible # allow http access from vpc cidr - proto: tcp from_port: 80 to_port: 80 group_name: "{{ env }}_sg_wordpress_lb" group_desc: security group for wordpress load balancer # allow https access from vpc cidr - proto: tcp from_port: 443 to_port: 443 group_name: "{{ env }}_sg_wordpress_lb" group_desc: security group for wordpress load balancer - name: create sg_database rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpcid.stdout }}" name: "{{ env }}_sg_database" description: security group for database rules: - proto: tcp from_port: 3306 to_port: 3306 group_name: "{{ env }}_sg_web" - proto: tcp from_port: 3306 to_port: 3306 group_name: "{{ env }}_sg_wordpress" - name: create sg_ansible rules ec2_group: region: "{{ region }}" vpc_id: "{{ vpcid.stdout }}" name: "{{ env }}_sg_ansible" description: security group for ansible rules: - proto: tcp from_port: 22 to_port: 22 cidr_ip: "{{ allowed_ip }}" - name: create sg_wordpress_lb ec2_group: region: "{{ region }}" vpc_id: "{{ vpcid.stdout }}" name: "{{ env }}_sg_wordpress_lb" description: security group for wordpress load balancer rules: # allow http access from anywhere - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0.0.0/0 # allow https access from anywhere - proto: tcp from_port: 443 to_port: 443 cidr_ip: 0.0.0.0/0 ================================================ FILE: wordpress_ha/provisioning_vpc.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: region: ap-southeast-2 env: staging az0: ap-southeast-2a az1: ap-southeast-2b tasks: - name: create vpc with multi-az subnets ec2_vpc: region: "{{ region }}" cidr_block: 10.0.0.0/16 resource_tags: '{"Name":"{{ env }}_vpc"}' subnets: - cidr: 10.0.0.0/24 az: "{{ az0 }}" resource_tags: '{"Name":"{{ env }}_subnet_public_0"}' - cidr: 10.0.1.0/24 az: "{{ az0 }}" resource_tags: '{"Name":"{{ env }}_subnet_private_0"}' - cidr: 10.0.2.0/24 az: "{{ az1 }}" resource_tags: '{"Name":"{{ env }}_subnet_public_1"}' - cidr: 10.0.3.0/24 az: "{{ az1 }}" resource_tags: '{"Name":"{{ env }}_subnet_private_1"}' - cidr: 10.0.4.0/24 az: "{{ az0 }}" resource_tags: '{"Name":"{{ env }}_subnet_private_2"}' - cidr: 10.0.5.0/24 az: "{{ az1 }}" resource_tags: '{"Name":"{{ env }}_subnet_private_3"}' internet_gateway: yes route_tables: - subnets: - 10.0.0.0/24 - 10.0.2.0/24 routes: - dest: 0.0.0.0/0 gw: igw ================================================ FILE: wordpress_ha/provisioning_wp.yml ================================================ --- - hosts: localhost connection: local gather_facts: no vars: #your region region: ap-southeast-2 keyname: wordpress-apsydney instance_type: t2.micro env: staging image: ami-d9fe9be3 ins_name: wordpress_master tasks: - name: get {{ env }}_subnet_public_0 subnet id command: "aws ec2 describe-subnets --region {{ region }} --filters Name=tag:Name,Values={{ env }}_subnet_public_0 --query 'Subnets[0].SubnetId' --output text" register: subnet0 - name: launch ec2 instance ec2: region: "{{ region }}" key_name: "{{ keyname }}" instance_type: "{{ instance_type }}" image: "{{ image }}" wait: yes group: "{{ env }}_sg_web" id: wordpress_ha_1 instance_tags: Name: "{{ ins_name }}" class: wordpress_ha vpc_subnet_id: "{{ subnet0.stdout }}" register: ec2 when: subnet0.stdout!="None" - name: check EIP association command: "aws ec2 describe-instances --region {{ region }} --filters Name=tag:Name,Values={{ ins_name }} --query 'Reservations[0].Instances[0].NetworkInterfaces[0].Association' --output text" register: eip - name: associate new EIP for the instance ec2_eip: region: "{{ region }}" instance_id: "{{ item.id }}" with_items: ec2.instances when: item.id is defined and eip.stdout=="None" ================================================ FILE: wordpress_ha/roles/web/tasks/main.yml ================================================ --- - name: install apache, php, and php-mysql yum: name={{ item }} state=present with_items: - httpd - php - php-mysql - name: start and enable httpd service: name=httpd state=started enabled=yes ================================================ FILE: wordpress_ha/roles/wordpress/tasks/main.yml ================================================ --- - name: download wordpress get_url: url=http://wordpress.org/wordpress-{{ wp_version }}.tar.gz dest=~/wordpress-{{ wp_version }}.tar.gz - name: extract wordpress archive command: chdir=~ /bin/tar xvf wordpress-{{ wp_version }}.tar.gz creates=~/wordpress - name: copy wordpress to apache root directory shell: cp -r ~/wordpress/* /var/www/html - name: fetch random salts for wordpress config local_action: command curl https://api.wordpress.org/secret-key/1.1/salt/ register: "wp_salt" - name: copy wordpress config file template: src=wp-config.php dest=/var/www/html/ - name: change ownership of wordpress installation file: path=/var/www/html/ owner=apache group=apache state=directory recurse=yes ================================================ FILE: wordpress_ha/roles/wordpress/templates/wp-config.php ================================================