diff --git a/Makefile b/Makefile index f3788ca9..e67b1fbf 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,9 @@ setup: deps: docker pull containersol/mesos-agent:1.0.0-0.1.0 docker pull containersol/mesos-master:1.0.0-0.1.0 - docker pull mesosphere/mesos-dns:v0.6.0 + docker pull gliderlabs/registrator:v6 + docker pull consul:0.7.1 + docker pull xebia/mesos-dns:v0.0.5 docker pull mesosphere/marathon:v1.3.5 docker pull jplock/zookeeper:3.4.6 docker pull containersol/alpine3.3-java8-jre:v1 diff --git a/cli/src/integration-test/java/com/containersol/minimesos/main/CommandInitTest.java b/cli/src/integration-test/java/com/containersol/minimesos/main/CommandInitTest.java index 7e6b4c8b..9aea73e6 100644 --- a/cli/src/integration-test/java/com/containersol/minimesos/main/CommandInitTest.java +++ b/cli/src/integration-test/java/com/containersol/minimesos/main/CommandInitTest.java @@ -30,6 +30,8 @@ public void testFileContent() throws IOException { assertTrue("agent section is not found", fileContent.contains("agent {")); assertTrue("agent resources section is not found", fileContent.contains("resources {")); assertTrue("zookeeper section is not found", fileContent.contains("zookeeper {")); + assertTrue("consul section is not found", fileContent.contains("consul {")); + assertTrue("registrator section is not found", fileContent.contains("registrator {")); assertTrue("mesosdns section is not found", fileContent.contains("mesosdns {")); } diff --git a/cli/src/integration-test/java/com/containersol/minimesos/main/CommandTest.java b/cli/src/integration-test/java/com/containersol/minimesos/main/CommandTest.java index 074274dd..139db313 100644 --- a/cli/src/integration-test/java/com/containersol/minimesos/main/CommandTest.java +++ b/cli/src/integration-test/java/com/containersol/minimesos/main/CommandTest.java @@ -45,7 +45,7 @@ public void testUpAndDestroy() { assertTrue("Minimesos file at " + minimesosFile + " should exist", minimesosFile.exists()); - assertEquals(5, cluster.getMemberProcesses().size()); + assertEquals(7, cluster.getMemberProcesses().size()); cluster.destroy(new MesosClusterContainersFactory()); diff --git a/cli/src/integration-test/resources/configFiles/complete-minimesosFile b/cli/src/integration-test/resources/configFiles/complete-minimesosFile index 2816d86c..56ff3930 100644 --- a/cli/src/integration-test/resources/configFiles/complete-minimesosFile +++ b/cli/src/integration-test/resources/configFiles/complete-minimesosFile @@ -36,6 +36,11 @@ minimesos { } } + consul { + imageName = "consul" + imageTag = "0.7.1" + } + marathon { imageName = "mesosphere/marathon" imageTag = "v1.3.5" @@ -49,6 +54,11 @@ minimesos { loggingLevel = "# INHERIT FROM CLUSTER" } + registrator { + imageName = "gliderlabs/registrator" + imageTag = "v6" + } + mesosdns { imageName = "xebia/mesos-dns" imageTag = "0.0.5" diff --git a/cli/src/integration-test/resources/configFiles/marathonAppConfig-minimesosFile b/cli/src/integration-test/resources/configFiles/marathonAppConfig-minimesosFile index 026e3ba8..8ab1b0a1 100644 --- a/cli/src/integration-test/resources/configFiles/marathonAppConfig-minimesosFile +++ b/cli/src/integration-test/resources/configFiles/marathonAppConfig-minimesosFile @@ -35,6 +35,11 @@ minimesos { } } + consul { + imageName = "consul" + imageTag = "0.7.1" + } + marathon { imageName = "mesosphere/marathon" imageTag = "v1.3.5" @@ -50,6 +55,11 @@ minimesos { imageTag = "1.0.0-0.1.0" } + registrator { + imageName = "gliderlabs/registrator" + imageTag = "v6" + } + mesosdns { imageName = "xebia/mesos-dns" imageTag = "0.0.5" diff --git a/cli/src/main/java/com/containersol/minimesos/main/CommandInit.java b/cli/src/main/java/com/containersol/minimesos/main/CommandInit.java index 0d288a16..a49e27e6 100644 --- a/cli/src/main/java/com/containersol/minimesos/main/CommandInit.java +++ b/cli/src/main/java/com/containersol/minimesos/main/CommandInit.java @@ -15,10 +15,12 @@ import com.containersol.minimesos.config.AppConfig; import com.containersol.minimesos.config.ClusterConfig; import com.containersol.minimesos.config.ConfigParser; +import com.containersol.minimesos.config.ConsulConfig; import com.containersol.minimesos.config.MarathonConfig; import com.containersol.minimesos.config.MesosAgentConfig; -import com.containersol.minimesos.config.MesosMasterConfig; import com.containersol.minimesos.config.MesosDNSConfig; +import com.containersol.minimesos.config.MesosMasterConfig; +import com.containersol.minimesos.config.RegistratorConfig; import com.containersol.minimesos.config.ZooKeeperConfig; import org.slf4j.Logger; @@ -84,6 +86,8 @@ public String getConfigFileContent() { config.setMaster(new MesosMasterConfig(ClusterConfig.DEFAULT_MESOS_VERSION)); config.setZookeeper(new ZooKeeperConfig()); config.getAgents().add(new MesosAgentConfig(ClusterConfig.DEFAULT_MESOS_VERSION)); + config.setConsul(new ConsulConfig()); + config.setRegistrator(new RegistratorConfig()); config.setMesosdns(new MesosDNSConfig()); AppConfig weaveConfig = new AppConfig(); diff --git a/docs/index.md b/docs/index.md index e90e244d..d7fc7686 100644 --- a/docs/index.md +++ b/docs/index.md @@ -128,6 +128,10 @@ Scalar values are simple key-value strings. | agent resources mem | Block | Describes memory resources | | agent resources ports | Block | Describes network ports resources | +## Consul and registrator + +By default, minimesos starts consul and registrator containers giving you ability to configure service discovery. + ## Mesos DNS Mesos DNS registers Mesos processes and frameworks in its DNS server @@ -229,6 +233,7 @@ export MINIMESOS_NETWORK_GATEWAY=172.17.0.1 export MINIMESOS_AGENT=http://172.17.0.5:5051; export MINIMESOS_AGENT_IP=172.17.0.5 export MINIMESOS_ZOOKEEPER=zk://172.17.0.3:2181/mesos; export MINIMESOS_ZOOKEEPER_IP=172.17.0.3 export MINIMESOS_MARATHON=http://172.17.0.6:8080; export MINIMESOS_MARATHON_IP=172.17.0.6 +export MINIMESOS_CONSUL=http://172.17.0.7:8500; export MINIMESOS_CONSUL_IP=172.17.0.7 export MINIMESOS_MASTER=http://172.17.0.4:5050; export MINIMESOS_MASTER_IP=172.17.0.4 $ minimesos state | jq ".version" diff --git a/minimesos/src/main/groovy/com/containersol/minimesos/config/ClusterConfig.groovy b/minimesos/src/main/groovy/com/containersol/minimesos/config/ClusterConfig.groovy index e24d483c..33ca77d8 100644 --- a/minimesos/src/main/groovy/com/containersol/minimesos/config/ClusterConfig.groovy +++ b/minimesos/src/main/groovy/com/containersol/minimesos/config/ClusterConfig.groovy @@ -32,6 +32,8 @@ class ClusterConfig extends GroovyBlock { ZooKeeperConfig zookeeper = null MarathonConfig marathon = null MesosDNSConfig mesosdns = null + ConsulConfig consul = null + RegistratorConfig registrator = null def master(@DelegatesTo(MesosMasterConfig) Closure cl) { if (master != null) { @@ -71,6 +73,22 @@ class ClusterConfig extends GroovyBlock { delegateTo(mesosdns, cl) } + def consul(@DelegatesTo(ConsulConfig) Closure cl) { + if (consul != null) { + throw new RuntimeException("Cannot have more than 1 Consul server") + } + consul = new ConsulConfig() + delegateTo(consul, cl) + } + + def registrator(@DelegatesTo(RegistratorConfig) Closure cl) { + if (registrator != null) { + throw new RuntimeException("Cannot have more than 1 registrator") + } + registrator = new RegistratorConfig() + delegateTo(registrator, cl) + } + void setLoggingLevel(String loggingLevel) { if (!StringUtils.equalsIgnoreCase(loggingLevel, "WARNING") && !StringUtils.equalsIgnoreCase(loggingLevel, "INFO") && !StringUtils.equalsIgnoreCase(loggingLevel, "ERROR")) { throw new RuntimeException("Property 'loggingLevel' can only have the values INFO, WARNING or ERROR. Got '" + loggingLevel + "'") diff --git a/minimesos/src/main/groovy/com/containersol/minimesos/config/ConsulConfig.groovy b/minimesos/src/main/groovy/com/containersol/minimesos/config/ConsulConfig.groovy new file mode 100644 index 00000000..dff363c7 --- /dev/null +++ b/minimesos/src/main/groovy/com/containersol/minimesos/config/ConsulConfig.groovy @@ -0,0 +1,16 @@ +package com.containersol.minimesos.config; + +public class ConsulConfig extends ContainerConfigBlock implements ContainerConfig { + + public static final String CONSUL_IMAGE_NAME = "consul" + public static final String CONSUL_TAG_NAME = "0.7.1" + + public static final int CONSUL_HTTP_PORT = 8500 + public static final int CONSUL_DNS_PORT = 8600 + + public ConsulConfig() { + imageName = CONSUL_IMAGE_NAME + imageTag = CONSUL_TAG_NAME + } + +} diff --git a/minimesos/src/main/groovy/com/containersol/minimesos/config/RegistratorConfig.groovy b/minimesos/src/main/groovy/com/containersol/minimesos/config/RegistratorConfig.groovy new file mode 100644 index 00000000..cb74547a --- /dev/null +++ b/minimesos/src/main/groovy/com/containersol/minimesos/config/RegistratorConfig.groovy @@ -0,0 +1,13 @@ +package com.containersol.minimesos.config; + +public class RegistratorConfig extends ContainerConfigBlock implements ContainerConfig { + + public static final String REGISTRATOR_IMAGE_NAME = "gliderlabs/registrator" + public static final String REGISTRATOR_TAG_NAME = "v6" + + public RegistratorConfig() { + imageName = REGISTRATOR_IMAGE_NAME + imageTag = REGISTRATOR_TAG_NAME + } + +} diff --git a/minimesos/src/main/java/com/containersol/minimesos/cluster/ClusterUtil.java b/minimesos/src/main/java/com/containersol/minimesos/cluster/ClusterUtil.java index ca9295bd..0d9941ba 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/cluster/ClusterUtil.java +++ b/minimesos/src/main/java/com/containersol/minimesos/cluster/ClusterUtil.java @@ -38,7 +38,7 @@ public static List getDistinctRoleProcesses(List } for (Map.Entry role : roles.entrySet() ) { - if (role.getValue() >= 1) { + if (role.getValue() == 1) { Optional process = processes.stream().filter(withRole(role.getKey())).findFirst(); distinct.add(process.get()); } diff --git a/minimesos/src/main/java/com/containersol/minimesos/cluster/Consul.java b/minimesos/src/main/java/com/containersol/minimesos/cluster/Consul.java new file mode 100644 index 00000000..58ae1a05 --- /dev/null +++ b/minimesos/src/main/java/com/containersol/minimesos/cluster/Consul.java @@ -0,0 +1,7 @@ +package com.containersol.minimesos.cluster; + +/** + * Consul functionality + */ +public interface Consul extends ClusterProcess { +} diff --git a/minimesos/src/main/java/com/containersol/minimesos/cluster/Filter.java b/minimesos/src/main/java/com/containersol/minimesos/cluster/Filter.java index b6b58a52..50636545 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/cluster/Filter.java +++ b/minimesos/src/main/java/com/containersol/minimesos/cluster/Filter.java @@ -11,6 +11,10 @@ public static Predicate zooKeeper() { return process -> process instanceof ZooKeeper; } + public static Predicate consul() { + return process -> process instanceof Consul; + } + public static Predicate mesosMaster() { return process -> process instanceof MesosMaster; } @@ -27,4 +31,5 @@ public static Predicate withRole(String role) { return process -> role.equals(process.getRole()); } + public static Predicate registrator() { return process -> process instanceof Registrator; } } diff --git a/minimesos/src/main/java/com/containersol/minimesos/cluster/MesosCluster.java b/minimesos/src/main/java/com/containersol/minimesos/cluster/MesosCluster.java index 77a221ab..e38311e8 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/cluster/MesosCluster.java +++ b/minimesos/src/main/java/com/containersol/minimesos/cluster/MesosCluster.java @@ -310,6 +310,11 @@ public Marathon getMarathon() { return marathon.isPresent() ? marathon.get() : null; } + public Consul getConsul() { + Optional container = getOne(Filter.consul()); + return container.isPresent() ? container.get() : null; + } + /** * Optionally get one of a certain type of type T. Note, this cast will always work because we are filtering on that type. * If it doesn't find that type, the optional is empty so the cast doesn't need to be performed. diff --git a/minimesos/src/main/java/com/containersol/minimesos/cluster/Registrator.java b/minimesos/src/main/java/com/containersol/minimesos/cluster/Registrator.java new file mode 100644 index 00000000..b8bbd47c --- /dev/null +++ b/minimesos/src/main/java/com/containersol/minimesos/cluster/Registrator.java @@ -0,0 +1,9 @@ +package com.containersol.minimesos.cluster; + +/** + * Consul functionality + */ +public interface Registrator extends ClusterProcess { + + void setConsul(Consul consul); +} diff --git a/minimesos/src/main/java/com/containersol/minimesos/mesos/MesosClusterContainersFactory.java b/minimesos/src/main/java/com/containersol/minimesos/mesos/MesosClusterContainersFactory.java index f6d0325a..e9661868 100644 --- a/minimesos/src/main/java/com/containersol/minimesos/mesos/MesosClusterContainersFactory.java +++ b/minimesos/src/main/java/com/containersol/minimesos/mesos/MesosClusterContainersFactory.java @@ -9,13 +9,14 @@ import com.containersol.minimesos.MinimesosException; import com.containersol.minimesos.cluster.ClusterProcess; +import com.containersol.minimesos.cluster.Consul; import com.containersol.minimesos.cluster.Filter; import com.containersol.minimesos.cluster.Marathon; import com.containersol.minimesos.cluster.MesosAgent; import com.containersol.minimesos.cluster.MesosCluster; import com.containersol.minimesos.cluster.MesosClusterFactory; import com.containersol.minimesos.cluster.MesosMaster; -import com.containersol.minimesos.cluster.MesosDNS; +import com.containersol.minimesos.cluster.Registrator; import com.containersol.minimesos.cluster.ZooKeeper; import com.containersol.minimesos.config.ClusterConfig; import com.containersol.minimesos.config.ConfigParser; @@ -53,8 +54,12 @@ public Marathon createMarathon(MesosCluster mesosCluster, String uuid, String co return new MarathonContainer(mesosCluster, uuid, containerId); } - public MesosDNS createMesosDNS(MesosCluster mesosCluster, String uuid, String containerId) { - return new MesosDNSContainer(mesosCluster, uuid, containerId); + public Consul createConsul(MesosCluster mesosCluster, String uuid, String containerId) { + return new ConsulContainer(mesosCluster, uuid, containerId); + } + + public Registrator createRegistrator(MesosCluster mesosCluster, String uuid, String containerId) { + return new RegistratorContainer(mesosCluster, uuid, containerId); } @Override @@ -93,8 +98,11 @@ public void loadRunningCluster(MesosCluster cluster) { case "marathon": containers.add(createMarathon(cluster, uuid, containerId)); break; - case "mesosDNS": - containers.add(createMesosDNS(cluster, uuid, containerId)); + case "consul": + containers.add(createConsul(cluster, uuid, containerId)); + break; + case "registrator": + containers.add(createRegistrator(cluster, uuid, containerId)); break; } } @@ -167,8 +175,12 @@ private static ClusterContainers createProcesses(ClusterConfig clusterConfig) { clusterContainers.add(new MarathonContainer(clusterConfig.getMarathon())); } - if (clusterConfig.getMesosdns() != null) { - clusterContainers.add(new MesosDNSContainer(clusterConfig.getMesosdns())); + if (clusterConfig.getConsul() != null) { + clusterContainers.add(new ConsulContainer(clusterConfig.getConsul())); + } + + if (clusterConfig.getRegistrator() != null) { + clusterContainers.add(new RegistratorContainer(clusterConfig.getRegistrator())); } return clusterContainers; @@ -188,6 +200,10 @@ private static void validateProcesses(ClusterContainers clusterContainers) { if (!isPresent(clusterContainers, Filter.mesosAgent())) { throw new MinimesosException("Cluster requires at least 1 Mesos Agent. Please add one in the minimesosFile."); } + + if (isPresent(clusterContainers, Filter.registrator()) && !isPresent(clusterContainers, Filter.consul())) { + throw new MinimesosException("Registrator requires a single Consul. Please add consul in the minimesosFile."); + } } private static void connectProcesses(ClusterContainers clusterContainers) { @@ -206,6 +222,12 @@ private static void connectProcesses(ClusterContainers clusterContainers) { MesosAgent agent = (MesosAgent) a; agent.setZooKeeper(zookeeper); }); + + if (clusterContainers.getOne(Filter.registrator()).isPresent()) { + Consul consul = (Consul) clusterContainers.getOne(Filter.consul()).get(); + Registrator registrator = (Registrator) clusterContainers.getOne(Filter.registrator()).get(); + registrator.setConsul(consul); + } } private static Boolean isPresent(ClusterContainers clusterContainers, Predicate filter) { diff --git a/minimesos/src/main/java/com/containersol/minimesos/mesos/RegistratorContainer.java b/minimesos/src/main/java/com/containersol/minimesos/mesos/RegistratorContainer.java new file mode 100644 index 00000000..b910d436 --- /dev/null +++ b/minimesos/src/main/java/com/containersol/minimesos/mesos/RegistratorContainer.java @@ -0,0 +1,58 @@ +package com.containersol.minimesos.mesos; + +import com.containersol.minimesos.cluster.Consul; +import com.containersol.minimesos.cluster.MesosCluster; +import com.containersol.minimesos.cluster.Registrator; +import com.containersol.minimesos.config.ConsulConfig; +import com.containersol.minimesos.config.RegistratorConfig; +import com.containersol.minimesos.integrationtest.container.AbstractContainer; +import com.containersol.minimesos.docker.DockerClientFactory; +import com.github.dockerjava.api.command.CreateContainerCmd; +import com.github.dockerjava.api.model.Bind; + +/** + * Registrator automatically registers and deregisters services for any Docker container by inspecting containers as they come online. + */ +public class RegistratorContainer extends AbstractContainer implements Registrator { + + private RegistratorConfig config; + + private Consul consul; + + public RegistratorContainer(MesosCluster cluster, String uuid, String containerId) { + this(cluster, uuid, containerId, new RegistratorConfig()); + } + + private RegistratorContainer(MesosCluster cluster, String uuid, String containerId, RegistratorConfig config) { + super(cluster, uuid, containerId, config); + this.config = config; + } + + public RegistratorContainer(RegistratorConfig registrator) { + super(registrator); + this.config = registrator; + } + + @Override + public String getRole() { + return "registrator"; + } + + @Override + protected CreateContainerCmd dockerCommand() { + return DockerClientFactory.build().createContainerCmd(config.getImageName() + ":" + config.getImageTag()) + .withNetworkMode("host") + .withBinds(Bind.parse("/var/run/docker.sock:/tmp/docker.sock")) + .withCmd("-internal", String.format("consul://%s:%d", consul.getIpAddress(), ConsulConfig.CONSUL_HTTP_PORT)) + .withName(getName()); + } + + public void setConsul(ConsulContainer consul) { + this.consul = consul; + } + + @Override + public void setConsul(Consul consul) { + this.consul = consul; + } +} diff --git a/minimesos/src/main/resources/marathon/mesos-consul.json b/minimesos/src/main/resources/marathon/mesos-consul.json new file mode 100644 index 00000000..6aa3ea0b --- /dev/null +++ b/minimesos/src/main/resources/marathon/mesos-consul.json @@ -0,0 +1,18 @@ +{ + "args": [ + "--zk={{MINIMESOS_ZOOKEEPER}}", + "--consul=1", + "--consul-ip={{MINIMESOS_CONSUL_IP}}" + ], + "container": { + "type": "DOCKER", + "docker": { + "network": "BRIDGE", + "image": "containersol/mesos-consul:latest" + } + }, + "id": "mesos-consul", + "instances": 1, + "cpus": 0.1, + "mem": 256 +} diff --git a/minimesos/src/test/groovy/com/containersol/minimesos/config/ConfigWriterTest.groovy b/minimesos/src/test/groovy/com/containersol/minimesos/config/ConfigWriterTest.groovy index 6eefc1f7..d43c8597 100644 --- a/minimesos/src/test/groovy/com/containersol/minimesos/config/ConfigWriterTest.groovy +++ b/minimesos/src/test/groovy/com/containersol/minimesos/config/ConfigWriterTest.groovy @@ -31,6 +31,8 @@ public class ConfigWriterTest { config.zookeeper = new ZooKeeperConfig() config.marathon = new MarathonConfig() config.agents.add(new MesosAgentConfig()) + config.consul = new ConsulConfig() + config.registrator = new RegistratorConfig() config.mesosdns = new MesosDNSConfig() AppConfig appConfig = new AppConfig() @@ -62,6 +64,8 @@ public class ConfigWriterTest { compareContainers(first.marathon, second.marathon) compareContainers(first.zookeeper, second.zookeeper) + compareContainers(first.consul, second.consul) + compareContainers(first.registrator, second.registrator) compareContainers(first.mesosdns, second.mesosdns) compareMesosContainers(first.master, second.master) diff --git a/minimesos/src/test/java/com/containersol/minimesos/mesos/ClusterUtilTest.java b/minimesos/src/test/java/com/containersol/minimesos/mesos/ClusterUtilTest.java index e4239b88..51851844 100644 --- a/minimesos/src/test/java/com/containersol/minimesos/mesos/ClusterUtilTest.java +++ b/minimesos/src/test/java/com/containersol/minimesos/mesos/ClusterUtilTest.java @@ -3,6 +3,7 @@ import com.containersol.minimesos.cluster.ClusterProcess; import com.containersol.minimesos.cluster.ClusterUtil; import com.containersol.minimesos.config.ClusterConfig; +import com.containersol.minimesos.config.ConsulConfig; import com.containersol.minimesos.config.MesosAgentConfig; import com.containersol.minimesos.config.MesosMasterConfig; import com.containersol.minimesos.integrationtest.container.AbstractContainer; @@ -34,6 +35,17 @@ public String getRole() { return "master"; } }; + ClusterProcess consul = new AbstractContainer(new ConsulConfig()) { + @Override + protected CreateContainerCmd dockerCommand() { + return null; + } + + @Override + public String getRole() { + return "consul"; + } + }; ClusterProcess agent1 = new AbstractContainer(new MesosAgentConfig(ClusterConfig.DEFAULT_MESOS_VERSION)) { @Override protected CreateContainerCmd dockerCommand() { @@ -57,11 +69,12 @@ public String getRole() { } }; - List processes = Arrays.asList(master, agent1, agent2); + List processes = Arrays.asList(master, consul, agent1, agent2); List distinct = ClusterUtil.getDistinctRoleProcesses(processes); assertEquals(2, distinct.size()); assertTrue("master has a distinct role", distinct.contains(master)); + assertTrue("consul has a distinct role", distinct.contains(consul)); } } diff --git a/minimesos/src/test/resources/configFiles/minimesosFile-mesosClusterTest b/minimesos/src/test/resources/configFiles/minimesosFile-mesosClusterTest index b9bdb607..e3278c67 100644 --- a/minimesos/src/test/resources/configFiles/minimesosFile-mesosClusterTest +++ b/minimesos/src/test/resources/configFiles/minimesosFile-mesosClusterTest @@ -96,6 +96,11 @@ minimesos { } } + consul { + imageName = "consul" + imageTag = "0.7.1" + } + master { imageName = "containersol/mesos-master" imageTag = "1.0.0-0.1.0"