Start, Run and Stop AWS EC2 instances

14th February, 2019

There will be at times you will like to start and run a task at one of your EC2 instances, however you will not prefer to have it running all the time. Most likely reason behind could be the aws costs. In general, you could save around 70% of the aws costs by effectively maintaining the run -time of your EC2 instances.

Here I will show you a simple Python script using Boto3 to control the state of your EC2 instances. Using this script, you will do the following steps in order:

  • Initiate your EC2 instance if its not found in "running" state.
  • Connect to your EC2 instance using the paramiko module.
  • Run your tasks.
  • Stop your EC2 instance.

In order to achieve the following you will need two more files in addition to the code displayed here:

  • aws_config.ini : In this file you need to specify your AWS credentials as well as other information regarding the instance and your ssh-access key path.
  • task.sh: A simple text (or shell script) file where you need to specify a list of shell commands which the instance will execute.

Following you will find the main script which uses the above mentioned files to start your instance and then run your tasks mentioned in the task.sh file and then finally stop your instance. 

import configparser
import boto3
from botocore.exceptions import ClientError
import time
import paramiko


class Ec2Instance:

    def __init__(self, config_file):
        self.config = configparser.ConfigParser(allow_no_value=True)
        self.config.read(config_file)

        ec2 = boto3.resource(
            'ec2',
            # Hard coded strings as credentials, not recommended.
            aws_access_key_id=self.config["aws"]["AWS_ACCESS_KEY_ID"],
            aws_secret_access_key=self.config["aws"]["AWS_SECRET_ACCESS_KEY"],
            region_name=self.config["aws"]["REGION"]
        )
        instance_id = self.config["aws"]["INSTANCE_ID"]
        self.instance = ec2.Instance(id=instance_id)  # instance id

    def start_instance(self):
        if self.instance.state["Name"] != "running":
            # DRY RUN to verify the permissions.
            try:
                self.instance.start(DryRun=True)
            except ClientError as e:
                if 'DryRunOperation' not in str(e):
                    raise

            # On DRY RUN success, initiate the instance
            print("Starting the instance.")
            self.instance.start(DryRun=False)
            self.instance.wait_until_running()
            self.instance.load()
            print("Waiting for status checks ..")
            time.sleep(45)

    def run_task(self, task_file):
        aws_key = paramiko.RSAKey.from_private_key_file(self.config["aws"]["KEY_PATH"])  # your private key for ssh
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        print("Connecting to shell using ssh")
        ssh_client.connect(
            hostname=self.instance.public_dns_name,
            username=self.config["aws"]["USER"],
            pkey=aws_key
        )
        print("Executing commands on EC2 instance")
        task_file = open(task_file, "r")
        tasks = task_file.read()
        stdin, stdout, stderr = ssh_client.exec_command(tasks)
        for line in iter(lambda: stdout.readline(2048), ""):
            print(line)
        print("All tasks have been executed.")

    def stop_instance(self):
        print("Stopping the instance.")
        self.instance.stop()

ec2_instance = Ec2Instance(config_file="aws_config.ini")
ec2_instance.start_instance()
ec2_instance.run_task(task_file="task.sh")
ec2_instance.stop_instance()

Hopefully this code could serve your pupose and most improtantly reduce your AWS costs.

More details and even easier approach can also be found in my GitHub repository  (i.e. by just using pip install ec2-task