This project helps with integration between Spring Cloud and Marathon framework for Apache Mesos
Add jcenter
repository:
repositories {
jcenter()
}
or for maven:
<repositories>
<repository>
<id>jcenter</id>
<url>http://jcenter.bintray.com/</url>
</repository>
</repositories>
And add dependency with latest version (or feel free to choose specific)
compile 'info.developerblog.spring.cloud:spring-cloud-marathon-starter:+'
or for maven:
<dependency>
<groupId>info.developerblog.spring.cloud</groupId>
<artifactId>spring-cloud-marathon-starter</artifactId>
<version>x.y.z</version>
</dependency>
Mesos and Marathon helps you to orchestrate microservices or other artifacts in distributed systems. In fact Marathon keeps information about current configuration including location of service, its healhchecks etc. So, do we need third-party system that keeps configuration and provides service discovery features? If you don't have any serious reason the answer will be "No".
DiscoveryClient
implementation (supports Ribbon, Feign and Zuul).
Client Load Balancing
with Ribbon and Hystrix.
Other distributed systems patterns from Spring Cloud.
All configuration should be provided in bootstrap.yml
spring.cloud.marathon.enabled: false
spring:
cloud:
marathon:
scheme: http #url scheme
host: marathon #marathon host
port: 8080 #marathon port
Authentication via Marathon, providing a list of marathon masters:
spring:
cloud:
marathon:
listOfServers: m1:8080,m2:8080,m3:8080 #list of marathon masters
token: <dcos_acs_token> #DC/OS HTTP API Token (optional)
username: marathon #username for basic auth (optional)
password: mesos #password for basic auth (optional)
or, provide a load balanced marathon endpoint:
spring:
cloud:
marathon:
endpoint: http://marathon.local:8080 #override scheme+host+port
token: <dcos_acs_token> #DC/OS HTTP API Token (optional)
username: marathon #username for basic auth (optional)
password: mesos #password for basic auth (optional)
Other configuration for services and discovery you can see in official documentation
Spring Cloud Marathon supports four methods of authentication:
- No Authentication (using Marathon Endpoint)
- Basic Authentication (using Marathon Endpoint)
- Token Authentication (using Marathon Endpoint)
Do not specify username, password, token or dcosPrivateKey Specify a load balanced endpoint or listOfServers that describe the Marathon Endpoint. e.g.
spring:
cloud:
marathon:
listOfServers: m1:8080,m2:8080,m3:8080 #list of marathon masters
Provide a username & password. Specify an endpoint or listOfServers that describe the Marathon Endpoint. e.g.
spring:
cloud:
marathon:
listOfServers: m1:8080,m2:8080,m3:8080 #list of marathon masters
username: marathon #username for basic auth (optional)
password: mesos #password for basic auth (optional)
Provide a HTTP API token (note: tokens expire after 5 days). Specify a load balanced endpoint or listOfServers that describe the Marathon Endpoint. e.g.
spring:
cloud:
marathon:
listOfServers: m1:8080,m2:8080,m3:8080 #list of marathon masters
token: <dcos_acs_token> #DC/OS HTTP API Token (optional)
There is one specific moment for services notation and their configuration. In Marathon service id has following pattern:
/group/path/app
and symbol /
is not allowed as a virtual host in Feign or RestTemplate. So we cannot use original service id as Spring Cloud service id. Instead of /
in this implementation other separator: .
is used. That means that service with id: /group/path/app
has internal presentation: group.path.app
.
And you should configure them like:
group.path.app:
ribbon:
<your settings are here>
If a specific service cannot be located by the id, then a second lookup is performed for services that contain the given id. e.g. A service has been deployed using three different Marathon service ids:
/group1/path/app
/group2/path/app
/group3/path/app
A client is configured for the service name only, excluding the group & path from the id:
app:
ribbon:
<your settings are here>
Service Tasks for all three services will be discovered & used by ribbon.
Sometimes it is useful discover services that are advertising a specific capability; by API version for example: Three versions of a service have been deployed, with the following Marathon service ids and labels:
/group1/path/app "labels":{ "API_VERSION" : "V1" }
/group2/path/app "labels":{ "API_VERSION" : "V2" }
/group3/path/app "labels":{ "API_VERSION" : "V2" }
A client is configured to expect the "V2" API Version:
app:
ribbon:
<your settings are here>
MetaDataFilter:
API_VERSION: V2
Only service instances that contain "app" in the service id and have matching labels will be discovered & used by ribbon.
Where multiple values are specified for MetaDataFilter, all values must match service labels before ribbon will use the service instance:
app:
ribbon:
<your settings are here>
MetaDataFilter:
API_VERSION: V2
ENVIRONMENT: UAT
APPLICATION_OWNER: fred.bloggs@company.com
Combining the loose service id matching with service label filtering permits us to get creative with service discovery:
group.path:
ribbon:
<your settings are here>
MetaDataFilter:
CUSTOMER_ENTITY: V1
i.e. select any service deployed to /group/path that supports Version 1 of the Customer Entity
Ignore the service id entirely and only match using service labels:
customer:
ribbon:
<your settings here>
IgnoreServiceId: true
MetaDataFilter:
CUSTOMER_ENTITY: V1
ENVIRONMENT: UAT
i.e. select any service in the UAT environment that supports Version 1 of the Customer Entity.
Note: The service id of 'customer' is ignored, so all services will be filtered by service label
Specify equality based selector on service labels
customer:
ribbon:
<your settings here>
MetaDataFilter:
API_VERSION: '!=V3' # not equal to V3; note: must be quoted to keep yaml happy
ENVIRONMENT: '==DEV' # equal to DEV; note: must be quoted to keep yaml happy
CUSTOMER_ENTITY: V1 # equal to V1; default is equals
Specify set based selector on service labels
customer:
ribbon:
<your settings here>
MetaDataFilter:
API_VERSION: in(V1,V2) # is V1 OR is V2
ENVIRONMENT: notin(UAT,PROD) # is not UAT AND is not PROD
Zones support is provided by fetching them from instance's hostname. For example, mesos slaves have hostname like following:
slave1.dc1
slave2.dc1
slave1.dc2
...
There is regexp pattern .+\.(.+)
for fetching zone (or datacenter) exists. So, you could define it in service configuration:
customer:
ribbon:
<your settings here>
ZonePattern: '.+\.(.+)'
Finally, you should define zone for app:
spring.cloud.marathon.discovery.zone: dc1
And then different zone-aware filters and rules will be applied.
Build sample application docker image:
./gradlew dockerBuild
Install native docker on Linux or docker for MacOS X or Windows and run docker-compose
for local environment deployment with zookeeper, mesos and marathon:
docker-compose up -d
Add following record into your /etc/hosts
file:
127.0.0.1 mesos-slave.dc1
127.0.0.1 mesos-slave.dc2
Then upload test-marathon-app-manifest.json
as application manifest:
curl -XPOST http://<marathon_host>:8080/v2/apps?force=true -H "Content-Type: application/json" --data-binary @test-marathon-app-manifest.json -v
and run the example application:
./gradlew bootRun
Now you may test application by curl:
curl localhost:9090/instances
curl localhost:9090/feign