{"id":21497197,"url":"https://github.com/financial-times/ec2-powercycle","last_synced_at":"2025-07-15T20:30:33.428Z","repository":{"id":9064680,"uuid":"59582963","full_name":"Financial-Times/ec2-powercycle","owner":"Financial-Times","description":"Lambda function to stop and start EC2 instances based on tag","archived":false,"fork":false,"pushed_at":"2023-05-23T15:51:24.000Z","size":192,"stargazers_count":22,"open_issues_count":7,"forks_count":18,"subscribers_count":28,"default_branch":"master","last_synced_at":"2024-04-08T16:48:00.392Z","etag":null,"topics":["universal-publishing"],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Financial-Times.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2016-05-24T15:00:34.000Z","updated_at":"2022-10-18T16:01:23.000Z","dependencies_parsed_at":"2023-01-13T15:08:49.518Z","dependency_job_id":null,"html_url":"https://github.com/Financial-Times/ec2-powercycle","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fec2-powercycle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fec2-powercycle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fec2-powercycle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Financial-Times%2Fec2-powercycle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Financial-Times","download_url":"https://codeload.github.com/Financial-Times/ec2-powercycle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226065800,"owners_count":17568269,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["universal-publishing"],"created_at":"2024-11-23T16:22:07.669Z","updated_at":"2024-11-23T16:22:08.353Z","avatar_url":"https://github.com/Financial-Times.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EC2-POWERCYCLE\n\n[![Circle CI](https://circleci.com/gh/Financial-Times/ec2-powercycle/tree/master.png?style=shield)](https://circleci.com/gh/Financial-Times/ec2-powercycle/tree/master)\n\nAWS Lambda function to stop and start EC2 instances based on resource tag using crontab-like expressions\n\n## Table of Contents\n\n**[Usage](#usage)**  \n**[Testing and development](#testing-and-development)**  \n**[Creating a Lambda Deployment Package](#creating-a-lambda-deployment-package)**  \n**[Build environment](#build-environment)**  \n**[Serverless build pipeline](#serverless-build-pipeline)**  \n**[Identity and Access Management policy](#identity-and-access-management-policy)**  \n**[Creating and scheduling Lambda function](#creating-and-scheduling-lambda-function)**\n\n## Usage\n\nLambda function looks for EC2 instances and Auto Scaling Groups that has a resource tag _ec2Powewrcycle_ attached to it.\n\nTag value is simple JSON document that describes start and stop schedule in [crontab-like expressions](http://en.wikipedia.org/wiki/Cron).  \nIn case of ASGs, the tag may also contain information about the scaling state of the group (min and desired instances in the group).\nIf it doesn't, then the min and the desired instances are defaulted both to 1.\n\n### Examples\n\n1. EC2 instance stop/start schedule: Mon - Fri, 8.00am - 5.55pm\n\n    ```json\n    asgLifecycle: { \"start\": \"0 8 * * 1-5\", \"stop\": \"55 17 * * 1-5\" }\n    ```\n\n1. Auto Scaling Group stop/start schedule: Mon-Fri, 9:00am - 11:00pm. Min no of instances in ASG is 2 and desired no is 3\n\n    ```json\n    asgLifecycle: { \"start\": \"0 9 * * 1-5\", \"stop\": \"00 23 * * 1-5\", \"min\": 2, \"desired\": 3 }\n    ```\n\n1. Auto Scaling Group without scaling state specified. This will default to Min no of instance in ASG to 1 and the desired no to 1\n\n    ```json\n    asgLifecycle: { \"start\": \"0 8 * * 1-5\", \"stop\": \"55 17 * * 1-5\" }\n    ```\n\n1. Auto Scaling Group without min state specified. This will default to Min no of instance in ASG to 1.\n\n    ```json\n    asgLifecycle: { \"start\": \"0 8 * * 1-5\", \"stop\": \"55 17 * * 1-5\", \"desired\": 3 }\n    ```\n\nAs of [commit 00389de](https://github.com/Financial-Times/ec2-powercycle/commit/00389defafe30d1a85627a35713640a6e150e7e7) the stop/start schedule can be defined as an URL to publicly accessible JSON document. This feature can be handy when managing schedule for large number of nodes.\n\n```yml\nec2Powercycle: https://raw.githubusercontent.com/Financial-Times/ec2-powercycle/master/json/dev-schedule.json\n```\n\n## Testing and development\n\nTo run ec2Powercycle job local dev environment you need to install all dependencies such as boto3 and croniter.\nFull list of dependencies can be found in the file [ec2_powercycle.py](https://github.com/Financial-Times/ec2-powercycle/blob/master/ec2_powercycle.py)\n\nYou also need to set up AWS credetials (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION) in order to interact with AWS API.\n\nTo run the job first change to _python_ directory inside the repository, then call the hander() function.\n\n```sh\ncd /path/to/repository\npython -c \"from ec2_powercycle import * ; handler()\"\n```\n\nFunction can be executed in so-called dryrun mode with the following command.\n\n```sh\npython -c \"from ec2_powercycle import * ; handler({ \\\"DryRun\\\": \\\"True\\\" })\"\n```\n\nIn dryrun mode function doesn't stop/start instances.\n\n## Creating a Lambda Deployment Package\n\nEC2-POWERCYCLE uses 3rd party library called [Croniter](https://github.com/kiorky/croniter) which must be installed before deployment package is created.\n\n### Installing Croniter and Requests into lib/ directory\n\n```sh\npip3 install croniter requests -t lib/\n```\n\n### Creating zip archive\n\nThe following command is run in the root of the ec2-powercycle repository.\nThe command bundles ec2-powercycle business logic, its dependencies and the README.md which can be uploaded to Lambda or S3 bucket.\n\n```sh\nzip -r ../ec2-powercycle-0.0.1.zip ./*.py lib/ README.md\n```\n\n## Build environment\n\nThis repository ships with [Dockerfile](https://github.com/Financial-Times/ec2-powercycle/blob/master/Dockerfile) that can be used for packaging and deployment automation.\n\n### Building Docker image\n\nThe following command is run in the root of the repository and it creates a Docker container called ec2-powercycle with tag value 1.\n\n```sh\n sudo docker build -t ec2powercycle .\n```\n\n### Launching Docker image\n\nWhen Docker image is running it first executes the packaging script [package.sh](https://github.com/Financial-Times/ec2-powercycle/blob/master/package.sh), then deployment script [lambda-deploy-latest.sh](https://github.com/Financial-Times/ec2-powercycle/blob/master/lambda-deploy-latest.sh) that pushes ec2-powercycle.zip package into Lambda.\n\nTo run [lambda-deploy-latest.sh](https://github.com/Financial-Times/ec2-powercycle/blob/master/lambda-deploy-latest.sh) in __headless__ mode you can provide AWS credentials as Docker environment variables.\n\n```sh\nsudo docker run --env \"AWS_ACCESS_KEY_ID=\u003caccess_key_id\u003e\" \\\n--env \"AWS_SECRET_ACCESS_KEY=\u003caccess_key_secret\u003e\" \\\n--env \"AWS_DEFAULT_REGION=\u003caws_region_for_s3_bucket\u003e\" \\\n--env \"AWS_LAMBDA_FUNCTION=\u003clambda_function_name\u003e\" \\\n-it ec2powercycle\n```\n\nLaunching Docker image without environment variable will run [post-to-lambda.sh](https://github.com/Financial-Times/ec2-powercycle/blob/master/post-to-lambda.sh) in interactive mode that prompts user for AWS credentials.\n\n```sh\nsudo docker run -it ec2-powercycle\n```\n\n## Serverless build pipeline\n\n[Circleci](https://circleci.com) is a hosted CI service that integrates nicely with [Github](https://github.com) and [AWS](http://console.aws.amazon.com/).\n\n### Release process\n\nBuild pipeline currently has a single workflow with three possible tasks: `deploy-test`, `deploy-master` and `deploy-release`. \n\n```mermaid\nsequenceDiagram\n    autonumber\n    Note right of developer: deploy-test\n    developer--\u003e\u003egithub: git push feature/branch\n    Note right of CircleCI: Dev account\n    CircleCI--\u003e\u003egithub: git clone feature/branch\n    CircleCI--\u003e\u003eAWS: Upload the function\n    CircleCI--\u003e\u003eAWS: Invoke the function in \"dryRun\" mode\n\n    Note right of developer: deploy-master\n    developer--\u003e\u003egithub: merge pull request\n    CircleCI--\u003e\u003eAWS: Create version for the last uploaded function\n    CircleCI--\u003e\u003eAWS: Create alias(named LIVE) for that version\n\n    Note right of developer: deploy-release\n    developer--\u003e\u003egithub: git push tag release-*\n    Note right of CircleCI: Prod account\n    CircleCI--\u003e\u003egithub: git clone release-*\n    CircleCI--\u003e\u003eAWS: Upload the function\n    CircleCI--\u003e\u003eAWS: Create version for the last uploaded function\n    CircleCI--\u003e\u003eAWS: Create alias(named LIVE) for that version\n```\n\nThe Development task is run every time the _master_ branch is updated. Development task creates a deployment package, deploys it to Lambda and invokes the function against DEV alias.\n\nOnce you have completed Development work and wish to \"promote\" your code to Production you can trigger Production task by creating a Git tag with prefix _release-_ and pushing the tag to repository.\n\nUse the following commands to create a tag and push it to repository.\n\n```sh\ngit tag -a release-12 -m \"Repoint LIVE alias to release-12 tag\"\ngit push origin release-12\n```\n\n### Adding AWS credentials into Circleci\n\nAWS credentials are already added to two different contexts _ec2-powercycle-test_ and _ec2-powercycle-prod_ as environment variables. The workflow invokes the corresponding context with its AWS credentials in order to deploy deployment package to Lambda.\n\nTo change credentials: \n* Go to [Circleci Contexts](https://app.circleci.com/settings/organization/github/Financial-Times/contexts) and click on _ec2-powercycle-test_ or _ec2-powercycle-prod_\n* Delete the old environment variable\n* Create new environment variable\n\n## Identity and Access Management policy\n\nWhen creating Lambda function you will be asked to associate IAM role with the function.\n\n### IAM policy for Lambda function\n  \nThe following policy example enables Lambda function to access the following AWS services:\n\n* __CloudWatch__ - Full access to Amazon CloudWatch for logging and job scheduling\n* __EC2__ - Access to query status and stop/start instances when resource tag ec2Powercycle is attached to the instance and environment tag does not equal __p__ (p=production)\n  \n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"logs:CreateLogGroup\",\n        \"logs:CreateLogStream\",\n        \"logs:PutLogEvents\",\n        \"logs:DescribeLogStreams\"\n      ],\n      \"Resource\": \"arn:aws:logs:*:*:*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ec2:Describe*\",\n        \"autoscaling:Describe*\"\n      ],\n      \"Resource\": \"*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"ec2:StartInstances\",\n        \"ec2:StopInstances\"\n      ],\n      \"Condition\": {\n        \"StringLike\": {\n          \"ec2:ResourceTag/ec2Powercycle\": \"*\"\n        },\n        \"StringNotEqualsIgnoreCase\": {\n          \"ec2:ResourceTag/environment\": \"p\"\n        }\n      },\n      \"Resource\": \"arn:aws:ec2:*:*:instance/*\"\n    },\n    {\n      \"Effect\": \"Allow\",\n      \"Action\": [\n        \"autoscaling:UpdateAutoScalingGroup\"\n      ],\n      \"Condition\": {\n        \"StringLike\": {\n          \"autoscaling:ResourceTag/ec2Powercycle\": \"*\"\n        },\n        \"StringNotEqualsIgnoreCase\": {\n          \"autoscaling:ResourceTag/environment\": \"p\"\n        }\n      },\n      \"Resource\": \"*\"\n    }\n  ]\n}\n```\n\n### IAM policy for build pipeline\n\nThe following policy enables build and deployment job to update Lambda function, invoke it and update aliases.\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"lambda:CreateAlias\",\n                \"lambda:GetFunction\",\n                \"lambda:InvokeFunction\",\n                \"lambda:List*\",\n                \"lambda:PublishVersion\",\n                \"lambda:UpdateAlias\",\n                \"lambda:UpdateFunctionCode\"\n            ],\n            \"Resource\": [\n                \"arn:aws:lambda:*:*:function:ec2-powercycle\"\n            ]\n        }\n    ]\n}\n```\n\n## Creating and scheduling Lambda function\n\nOnce deployment package has been created we can create a Lambda function and use CloudWatch to set the function to run periodically.\n\n### Creating Lambda function\n\n 1. Log on to AWS console and go to Lambda configuration menu\n 1. Click _Create a Lambda function_\n 1. In _Select blueprint_ menu choose one of the blueprints (e.g. _s3-get-object-python_) click _Remove_ button on the next screen to remove _triggers_. Then click _Next_.\n 1. on _Configure function_ page provide the following details:\n\n    * Name*: ec2-powercycle\n    * Description: Optional description of the function\n    * Runtime*: Python 3.7\n\n 1. In _Lambda function code_ section select _Upload a .ZIP file_ to upload ec2powercycle.zip package to Lambda\n 1. In _Lambda function handler and role_ section set handler name _ec2_powercycle.handler_\n 1. Select the role that has the above IAM policy attached to it\n 1. Set _Timeout_ value 1 min\n 1. Click _Next_ and _Create function_\n  \n### Scheduling Lambda function\n\n 1. In Lambda configuration menu open the ec2-powercycle Lambda job\n 2. Go to _Triggers_ tab\n 3. Click _Add trigger_\n 4. Select _Event source type: CloudWatchEvents - Schedule_ and provide the following details\n    * Rule name: _whatever unique name_\n    * Rule description:_optional description of the rule_\n    * Schedule expression: _rate(15 minutes)_\n 5. Click _Submit_ to create schedule\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffinancial-times%2Fec2-powercycle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffinancial-times%2Fec2-powercycle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffinancial-times%2Fec2-powercycle/lists"}