计划启动一个EC2实例并在其中运行一个Python脚本

17

我想在AWS上安排我的Python脚本,但我不希望实例一直运行。因此,我想自动化以下过程:

  1. 在特定时间启动EC2实例
  2. 在其内部运行Python脚本
  3. 完成任务后停止EC2实例。

由于脚本需要更多RAM来执行并行处理,所以我不能将脚本直接运行为Lambda函数,因此选择了更大的AWS实例而不是编写为Lambda函数的方式。另外,由于费用昂贵,不希望该实例一直运行。

到目前为止,我按照Automatic starting and stopping of AWS EC2 instances with Lambda and CloudWatch · matoski.com 创建了一个Lambda函数,在特定时间启动和停止实例,但我找不到一种方法使Python脚本在实例启动后运行。

有人能指点我吗?


1
/etc/rc.local调用脚本。 - OneCricketeer
但是,当脚本完成后,我该如何停止实例呢? - ds_user
哦,这是新的。我们可以在脚本内部执行关闭实例吗?有示例吗?否则没问题,我会尝试找到它。但感谢您的意见。 - ds_user
2
这将需要 root 权限,但可以在这里看到各种解决方案(我自己不确定哪个是被接受的答案)https://dev59.com/m2Ag5IYBdhLWcg3w7erx - OneCricketeer
你说:“我不能直接将这个脚本作为Lambda函数运行,因为该脚本执行了一些需要更多RAM的并行处理操作”,但是Lambda函数可以分配大量RAM(这也增加了分配的计算资源)。 - John Rotenstein
显示剩余4条评论
3个回答

14

我的应用程序每天在13:39 UST运行一个实例,并在处理完成后自动关闭。它使用以下内容

  1. 使用Cloud Watch事件规则的定时Lambda函数

Cloud Watch事件/规则配置

  1. Lambda触发器将启动一个实例(具有硬编码ID)

import boto3
def lambda_handler(event, context):
    ec2 = boto3.client('ec2', region_name='ap-south-1')
    ec2.start_instances(InstanceIds=['i-xxxxxxx'])
    print('started your instances: ' + str('i-xxxxxx'))
    return

  1. 触发一个拥有定时执行 Python 脚本的 cron 实例

    @reboot python /home/Init.py

  2. 脚本完成后,Python 作业使用下面的片段自行关闭

import boto.ec2
import boto.utils
import logging
logger=logging.getLogger()
def stop_ec2():
    conn = boto.ec2.connect_to_region("ap-south-1") # or your region
    # Get the current instance's id
    my_id = boto.utils.get_instance_metadata()['instance-id']
    logger.info(' stopping EC2 :'+str(my_id))
    conn.stop_instances(instance_ids=[my_id])


2
如何使cron作业在实例启动时运行? - ds_user
1
@reboot是cron的特殊规范,而不是时间/日期字段。这确保脚本在实例启动/重启时运行。 我已经成功使用亚马逊Linux AMIs上述配置。 - Shambhurao Rane
谢谢。但是@reboot命令在哪里指定?那是一个shell脚本吗?您能解释一下吗? - ds_user
1
我启动了一个实例,复制了py脚本并添加了以下内容:sudo crontab -e @reboot /usr/bin/python /path/to/file/script.py然后停止了该实例。这是由Lambda使用实例ID启动的相同实例。 - Shambhurao Rane
我启动了一个实例,使用sudo crontab -e在Cron中添加了@reboot。然后复制了所有脚本,并停止了该实例。稍后,Lambda将使用实例ID触发相同实例的启动,由于@reboot,脚本将运行。 - Shambhurao Rane

4

针对未来前来查看这个问题的开发人员,现在有一种更新的方法:

  1. 创建包含 AmazonEC2RoleforSSM 策略的角色 EC2 实例
  2. 创建一个 Lambda 函数来执行唤醒、运行命令、关闭的操作
  3. 使用 CloudWatch 事件来触发 Lambda 函数

因此:

  1. 按照这里的步骤进行操作:https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html

  2. 使用以下 Lambda 函数模板:

import time
import boto3

REGION_NAME = 'us-east-1'

WORKING_DIRECTORY = '<YOUR WORKING DIRECTORY, IF ANY>'

COMMAND = """
    echo "Hello, world!"
    """

INSTANCE_ID = '<YOUR INSTANCE ID>'


def start_ec2():
    ec2 = boto3.client('ec2', region_name=REGION_NAME)
    ec2.start_instances(InstanceIds=[INSTANCE_ID])

    while True:
        response = ec2.describe_instance_status(InstanceIds=[INSTANCE_ID], IncludeAllInstances=True)
        state = response['InstanceStatuses'][0]['InstanceState']

        print(f"Status: {state['Code']} - {state['Name']}")

        # If status is 16 ('running'), then proceed, else, wait 5 seconds and try again
        if state['Code'] == 16:
            break
        else:
            time.sleep(5)

    print('EC2 started')


def stop_ec2():
    ec2 = boto3.client('ec2', region_name=REGION_NAME)
    ec2.stop_instances(InstanceIds=[INSTANCE_ID])

    while True:
        response = ec2.describe_instance_status(InstanceIds=[INSTANCE_ID], IncludeAllInstances=True)
        state = response['InstanceStatuses'][0]['InstanceState']

        print(f"Status: {state['Code']} - {state['Name']}")

        # If status is 80 ('stopped'), then proceed, else wait 5 seconds and try again
        if state['Code'] == 80:
            break
        else:
            time.sleep(5)

    print('Instance stopped')


def run_command():
    client = boto3.client('ssm', region_name=REGION_NAME)

    time.sleep(10)  # I had to wait 10 seconds to "send_command" find my instance 

    cmd_response = client.send_command(
        InstanceIds=[INSTANCE_ID],
        DocumentName='AWS-RunShellScript',
        DocumentVersion="1",
        TimeoutSeconds=300,
        MaxConcurrency="1",
        CloudWatchOutputConfig={'CloudWatchOutputEnabled': True},
        Parameters={
            'commands': [COMMAND],
            'executionTimeout': ["300"],
            'workingDirectory': [WORKING_DIRECTORY],
        },
    )

    command_id = cmd_response['Command']['CommandId']
    time.sleep(1)  # Again, I had to wait 1s to get_command_invocation recognises my command_id

    retcode = -1
    while True:
        output = client.get_command_invocation(
            CommandId=command_id,
            InstanceId=INSTANCE_ID,
        )

        # If the ResponseCode is -1, the command is still running, so wait 5 seconds and try again
        retcode = output['ResponseCode']
        if retcode != -1:
            print('Status: ', output['Status'])
            print('StdOut: ', output['StandardOutputContent'])
            print('StdErr: ', output['StandardErrorContent'])
            break

        print('Status: ', retcode)
        time.sleep(5)

    print('Command finished successfully') # Actually, 0 means success, anything else means a fail, but it didn't matter to me
    return retcode


def lambda_handler(event, context):
    retcode = -1
    try:
        start_ec2()
        retcode = run_command()
    finally:  # Independently of what happens, try to shutdown the EC2
        stop_ec2()

    return retcode

  1. 按照此处步骤操作:https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/RunLambdaSchedule.html

1
Lambda的最大运行时间不是15分钟吗?即使启动的EC2规格更高,如果运行时间超过15分钟,这个Lambda也会被强制退出,EC2将永远不会关闭,对吧? - Ranald Fong

3
我在使用此帖子中的解决方案启动和停止实例时遇到了问题。然后我按照 https://aws.amazon.com/premiumsupport/knowledge-center/start-stop-lambda-cloudwatch/ 上的说明操作,非常容易。基本上:
  1. 前往https://console.aws.amazon.com/iam/home#/home,在左侧单击策略并单击创建策略。 然后单击JSON选项卡。 然后复制粘贴以下内容以创建新策略:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Start*",
        "ec2:Stop*"
      ],
      "Resource": "*"
    }
  ]
}
  1. 转到https://console.aws.amazon.com/iam/home#/home,在左侧选择角色。确保您选择Lambda作为AWS服务,并附加在步骤1中创建的策略。

  2. 然后进入Lambda控制台,点击创建函数。选择Python 3.7,然后单击权限旁边的下拉菜单并使用现有角色,附加您在步骤2中创建的IAM角色。

  3. 将以下代码用作您的代码:

import boto3
region = 'us-west-1' # Dont use the specific, like instead of us-east-1d just write us-east-1
instances = ['i-xxxxxxxxxxxx']
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    ec2.start_instances(InstanceIds=instances)
    print('started your instances: ' + str(instances))
  1. 启动您的EC2实例,输入which python以查找Python路径,并将其写下来。然后,键入crontab -e以编辑您的CRON作业。不要使用sudo...因为有时候当您没有使用它来运行Python文件时,sudo会搞砸一些东西。在我的实例中,我有一个存储密码的pgpass文件,但是删除sudo就可以解决问题!
  2. 在注释行后的crontab编辑器中,键入@reboot /path/to/python /path/to/file.py。例如,对于我来说,这是@reboot /home/init/python /home/init/Notebooks/mypredictor.py
  3. 在Python文件的末尾,您需要停止实例。您可以像这样做:
import boto3
region = 'us-west-1' # Dont use the specific, like instead of us-east-1d just write us-east-1
instances = ['i-xxxxxxxxxxxx']
ec2 = boto3.client('ec2', region_name=region)

ec2.stop_instances(InstanceIds=instances)

你完成这个操作后,如何打开实例以进行修改? - Eric
@Eric 抱歉,我不理解你的问题。 - Corey Levinson
2
你设置了实例在重启后自动关闭。那么如果你需要打开它进行维护呢?一旦你打开它,它就会启动一个脚本来关闭自己。 - Eric
1
@Eric 我明白你的意思。是的,那很麻烦!我认为你需要从你的Python脚本中删除ec2.stop_instances这一行(或编辑你的crontab),保存并等待机器重新启动,然后再启动机器。可能的改进是:在调用你的Python脚本之前,你运行git pull来拉取任何代码更改,然后再运行你的Python脚本。 - Corey Levinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接