使用Elastic Beanstalk,我可以通过编程方式确定我是否在领导节点上?

时间:2014-02-20 19:27:34

标签: java amazon-web-services scheduled-tasks elastic-beanstalk

我在Tomcat上运行的Elastic Beanstalk Java应用程序中有一些内务处理任务,我需要经常运行它们。我希望这些任务只在领导节点上运行(或者更确切地说,在单个节点上运行,但领导者似乎是一个明显的选择)。

我正在考虑在Elastic Beanstalk中运行cron作业,但感觉这应该比我想出的更直接。理想情况下,我想在我的网络应用程序中选择以下两个选项之一:

  1. 在当前JRE中测试此服务器是否为领导节点的某种方式
  2. 某种方法可以点击特定的URL(wget?)来触发任务,但也可以将该URL限制为来自localhost的请求。
  3. 建议?

3 个答案:

答案 0 :(得分:2)

根据设计(leaders are only assigned during deployment,并且在其他情境中不需要)是不可能的。但是,您可以为此目的调整和使用EC2元数据。

这是一个关于如何实现此结果的工作示例(original source)。一旦你调用了getLeader,它就会找到 - 或者指定 - 一个要设置为领导者的实例:

package br.com.ingenieux.resource;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

import org.apache.commons.io.IOUtils;

import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.model.CreateTagsRequest;
import com.amazonaws.services.ec2.model.DeleteTagsRequest;
import com.amazonaws.services.ec2.model.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalk;
import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsRequest;

@Path("/admin/leader")
public class LeaderResource extends BaseResource {
    @Inject
    AmazonEC2 amazonEC2;

    @Inject
    AWSElasticBeanstalk elasticBeanstalk;

    @GET
    public String getLeader() throws Exception {
        /*
         * Avoid running if we're not in AWS after all
         */
        try {
            IOUtils.toString(new URL(
                    "http://169.254.169.254/latest/meta-data/instance-id")
                    .openStream());
        } catch (Exception exc) {
            return "i-FFFFFFFF/localhost";
        }

        String environmentName = getMyEnvironmentName();

        List<Instance> environmentInstances = getInstances(
                "tag:elasticbeanstalk:environment-name", environmentName,
                "tag:leader", "true");

        if (environmentInstances.isEmpty()) {
            environmentInstances = getInstances(
                    "tag:elasticbeanstalk:environment-name", environmentName);

            Collections.shuffle(environmentInstances);

            if (environmentInstances.size() > 1)
                environmentInstances.removeAll(environmentInstances.subList(1,
                        environmentInstances.size()));

            amazonEC2.createTags(new CreateTagsRequest().withResources(
                    environmentInstances.get(0).getInstanceId()).withTags(
                    new Tag("leader", "true")));
        } else if (environmentInstances.size() > 1) {
            DeleteTagsRequest deleteTagsRequest = new DeleteTagsRequest().withTags(new Tag().withKey("leader").withValue("true"));

            for (Instance i : environmentInstances.subList(1,
                        environmentInstances.size())) {
                deleteTagsRequest.getResources().add(i.getInstanceId());
            }

            amazonEC2.deleteTags(deleteTagsRequest);
        }

        return environmentInstances.get(0).getInstanceId() + "/" + environmentInstances.get(0).getPublicIpAddress();
    }

    @GET
    @Produces("text/plain")
    @Path("am-i-a-leader")
    public boolean isLeader() {
        /*
         * Avoid running if we're not in AWS after all
         */
        String myInstanceId = null;
        String environmentName = null;
        try {
            myInstanceId = IOUtils.toString(new URL(
                    "http://169.254.169.254/latest/meta-data/instance-id")
                    .openStream());

            environmentName = getMyEnvironmentName();
        } catch (Exception exc) {
            return false;
        }

        List<Instance> environmentInstances = getInstances(
                "tag:elasticbeanstalk:environment-name", environmentName,
                "tag:leader", "true", "instance-id", myInstanceId);

        return (1 == environmentInstances.size());
    }

    protected String getMyEnvironmentHost(String environmentName) {
        return elasticBeanstalk
                .describeEnvironments(
                        new DescribeEnvironmentsRequest()
                                .withEnvironmentNames(environmentName))
                .getEnvironments().get(0).getCNAME();
    }

    private String getMyEnvironmentName() throws IOException,
            MalformedURLException {
        String instanceId = IOUtils.toString(new URL(
                "http://169.254.169.254/latest/meta-data/instance-id"));

        /*
         * Grab the current environment name
         */
        DescribeInstancesRequest request = new DescribeInstancesRequest()
                .withInstanceIds(instanceId)
                .withFilters(
                        new Filter("instance-state-name").withValues("running"));

        for (Reservation r : amazonEC2.describeInstances(request)
                .getReservations()) {
            for (Instance i : r.getInstances()) {
                for (Tag t : i.getTags()) {
                    if ("elasticbeanstalk:environment-name".equals(t.getKey())) {
                        return t.getValue();
                    }
                }
            }
        }

        return null;
    }

    public List<Instance> getInstances(String... args) {
        Collection<Filter> filters = new ArrayList<Filter>();

        filters.add(new Filter("instance-state-name").withValues("running"));

        for (int i = 0; i < args.length; i += 2) {
            String key = args[i];
            String value = args[1 + i];

            filters.add(new Filter(key).withValues(value));
        }

        DescribeInstancesRequest req = new DescribeInstancesRequest()
                .withFilters(filters);
        List<Instance> result = new ArrayList<Instance>();

        for (Reservation r : amazonEC2.describeInstances(req).getReservations())
            result.addAll(r.getInstances());

        return result;
    }
}

答案 1 :(得分:1)

您可以保留一个秘密网址(长网址是不可猜测的,几乎和密码一样安全),从某个地方点击此网址。在此,您可以执行任务。

然而,一个问题是,如果任务花费的时间太长,那么在此期间您的服务器容量将受到限制。另一种方法是URL命中将消息发布到AWS SQS。 另一个 EC2可以有一个等待SQS并执行任务的代码。您还可以查看http://aws.amazon.com/swf/

答案 2 :(得分:0)

如果您在Linux类型的EC2实例上运行,则采用另一种方法:

  1. 编写执行(或触发)定期任务的shell脚本
  2. 利用.ebextensions功能customize your Elastic Beanstalk instance,创建一个container command,指定参数leader_only:true - 此命令仅在指定为Auto Scaling组中的领导者的实例上运行< / LI>
  3. 让容器命令将shell脚本复制到/etc/cron.hourly(或每天或其他)。
  4. 结果将是你的领导者&#34; EC2实例将每小时(或每天或其他)运行一个cron作业来执行您的定期任务,而Auto Scaling组中的其他实例则不会。