Managing Microservices in Docker Swarm
1. Introduction
In this article, we will take a look at Docker Swarm based Microservices. Docker Swarm has clustering and orchestration features in the platform.
2. Microservices in Docker Swarm
Docker is used as a container for packaging software. Swarm can have a group of Docker hosts in a cluster. Swarm can help as a packaging software tool to deploy the software on a cluster-based on the cloud.
2.1 Prerequisites
Docker software is required. Maven and Spring Boot framework is needed to build the microservices. Maven 3.6.1 is required for building java and web applications.
2.2 Download
You can download Docker from the site . Apache Maven 3.6.1 can be downloaded from Apache site. Spring framework latest releases are available from the spring website.
2.3 Setup for Docker
Below is the docker setup check commands after installing the docker package.
You can check the docker installation by running the hello-world docker repo which is located at the docker hub.
Docker Hello-World
docker run hello-world
The output of the above command will be as below:
Output
Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
2.4 Docker
Docker is a container for the execution of microservices. Each microservice can be running on a Docker container. Docker container has a docker file and composes file.
Let us start creating a microservice. A microservice which has customer information is shown below:
Customer Service
package org.javacodegeeks.controllers; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.javacodegeeks.model.Customer; @RestController public class ServiceController { @RequestMapping(value = "/customer", method = RequestMethod.GET) public Customer firstPage() { Customer customer = new Customer(); customer.setName("cust1"); customer.setCustId("12"); customer.setZipCode("50100"); return customer; } }
Spring Boot Application exposes the REST web service which has the customer information.
SpringBoot Application
package org.javacodegeeks; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.javacodegeeks.controllers.ServiceController; @SpringBootApplication public class SpringBootServiceApplication { public static void main(String[] args) { SpringApplication.run(SpringBootServiceApplication.class, args); } }
The below command is used for running the spring boot application
SpringBoot run
mvn spring-boot:run
The output when the above command executed is shown below:
Output
apples-MacBook-Air:customer-producer bhagvan.kommadi$ mvn spring-boot:run [INFO] Scanning for projects... [INFO] [INFO] --------------------------------- [INFO] Building customer-producer 0.0.1-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] >>> spring-boot-maven-plugin:1.4.1.RELEASE:run (default-cli) > test-compile @ customer-producer >>> [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ customer-producer --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 0 resource [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ customer-producer --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ customer-producer --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 0 resource [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ customer-producer --- [INFO] Nothing to compile - all classes are up to date [INFO] [INFO] <<< spring-boot-maven-plugin:1.4.1.RELEASE:run (default-cli) < test-compile @ customer-producer <<< [INFO] [INFO] [INFO] --- spring-boot-maven-plugin:1.4.1.RELEASE:run (default-cli) @ customer-producer --- . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.4.1.RELEASE) 2020-06-13 22:39:52.599 INFO 15260 --- [ main] o.j.SpringBootServiceApplication : Starting SpringBootServiceApplication on apples-MacBook-Air.local with PID 15260 (/Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/dockerswarm/customer-producer/target/classes started by bhagvan.kommadi in /Users/bhagvan.kommadi/Desktop/JavacodeGeeks/Code/dockerswarm/customer-producer) 2020-06-13 22:39:52.606 INFO 15260 --- [ main] o.j.SpringBootServiceApplication : No active profile set, falling back to default profiles: default 2020-06-13 22:39:52.707 INFO 15260 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@737e17e9: startup date [Sat Jun 13 22:39:52 IST 2020]; root of context hierarchy 2020-06-13 22:39:55.722 INFO 15260 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http) 2020-06-13 22:39:55.755 INFO 15260 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat 2020-06-13 22:39:55.757 INFO 15260 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.5 2020-06-13 22:39:55.899 INFO 15260 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2020-06-13 22:39:55.900 INFO 15260 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 3202 ms 2020-06-13 22:39:56.130 INFO 15260 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/] 2020-06-13 22:39:56.136 INFO 15260 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2020-06-13 22:39:56.137 INFO 15260 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2020-06-13 22:39:56.137 INFO 15260 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*] 2020-06-13 22:39:56.138 INFO 15260 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*] 2020-06-13 22:39:56.673 INFO 15260 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@737e17e9: startup date [Sat Jun 13 22:39:52 IST 2020]; root of context hierarchy 2020-06-13 22:39:56.834 INFO 15260 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/customer],methods=[GET]}" onto public org.javacodegeeks.model.Customer org.javacodegeeks.controllers.ServiceController.firstPage() 2020-06-13 22:39:56.839 INFO 15260 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2020-06-13 22:39:56.840 INFO 15260 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2020-06-13 22:39:56.931 INFO 15260 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-06-13 22:39:56.931 INFO 15260 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-06-13 22:39:57.019 INFO 15260 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-06-13 22:39:57.344 INFO 15260 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2020-06-13 22:39:57.475 INFO 15260 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2020-06-13 22:39:57.482 INFO 15260 --- [ main] o.j.SpringBootServiceApplication : Started SpringBootServiceApplication in 16.292 seconds (JVM running for 24.156) 2020-06-13 22:40:08.783 INFO 15260 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2020-06-13 22:40:08.786 INFO 15260 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2020-06-13 22:40:08.839 INFO 15260 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 52 ms
Docker image is created by having a docker file as shown below:
Dockerfile
From openjdk:8 copy ./target/customer-producer-0.0.1-SNAPSHOT.jar customer-producer-0.0.1-SNAPSHOT.jar CMD ["java","-jar","customer-producer-0.0.1-SNAPSHOT.jar"]
Docker container is run by executing the below command:
Docker Commands
docker image build -t customer-producer .
The output when the above command executed is shown below:
Docker Image Output
apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker image build -t customer-producer . Sending build context to Docker daemon 14.27MB Step 1/3 : From openjdk:8 ---> b190ad78b520 Step 2/3 : copy ./target/customer-producer-0.0.1-SNAPSHOT.jar customer-producer-0.0.1-SNAPSHOT.jar ---> 09eba5be07b2 Step 3/3 : CMD ["java","-jar","customer-producer-0.0.1-SNAPSHOT.jar"] ---> Running in 13314fb7c2db Removing intermediate container 13314fb7c2db ---> 455b83fa93a4 Successfully built 455b83fa93a4 Successfully tagged customer-producer:latest
Docker service is executed by using the command below:
Docker Service Execution Command
docker container run --name service-producer -p 8080:8080 -d customer-producer
The output of the above service running in the docker can be shown below:
Docker Service Execution Output
apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker container run --name service-producer -p 8080:8080 -d customer-producer b9171dfd91ccbda1c22d1af6fe2641bd53b8ef54099fdb8e1349e3bf1286acae
The service can be accessed at http://localhost:8080/customer. The screen shot attached below is shown below:
2.5 Docker Swarm
Docker Swarm can have a cluster of Docker hosts which can have a virtual host. The cluster can provide multi-host networking, scaling, load balancing, and security features.
Docker Swarm Node is part of the cluster running on Docker. The node can be of two types which are the manager and worker nodes. Docker Swarm node can have a service running on the worker node. Service can be deployed as replicated or global. Docker Swarm Tasks are executed on the docker container. Docker Swarm Manager Node starts the container having a docker image with a service on the worker node.
2.5 Microservices in Docker Swarm
Docker Swarm node can have microservices running on the worker node. Microservices based architecture is used for creating a large scale application.
Let us create a customer service client docker node that can access customer service running on service-producer. The service client code is as shown below:
Customer Service Client
package org.javacodegeeks.controllers; import java.io.IOException; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; public class ServiceClient { public void getCustomer() throws RestClientException, IOException { String baseUrl = "http://localhost:8080/customer"; RestTemplate restTemplate = new RestTemplate(); ResponseEntity response=null; try{ response=restTemplate.exchange(baseUrl, HttpMethod.GET, getHeaders(),String.class); }catch (Exception ex) { System.out.println(ex); ex.printStackTrace(); } System.out.println(response.getBody()); } private static HttpEntity getHeaders() throws IOException { HttpHeaders headers = new HttpHeaders(); headers.set("Accept", MediaType.APPLICATION_JSON_VALUE); return new HttpEntity(headers); } }
Dockerfile is created for service client as shown below:
Dockerfile
From openjdk:8 copy ./target/customer-consumer-0.0.1-SNAPSHOT.jar customer-consumer-0.0.1-SNAPSHOT.jar CMD ["java","-jar","customer-consumer-0.0.1-SNAPSHOT.jar"]
The command below is used for creating a docker image
Docker Commands
docker image build -t customer-consumer . docker container run --name service-consumer -p 8080:8080 -d customer-consumer
The output when the above command executed is shown below:
Docker Image Output
apples-MacBook-Air:customer-consumer bhagvan.kommadi$ docker image build -t customer-consumer . Sending build context to Docker daemon 14.27MB Step 1/3 : From openjdk:8 ---> b190ad78b520 Step 2/3 : copy ./target/customer-consumer-0.0.1-SNAPSHOT.jar customer-consumer-0.0.1-SNAPSHOT.jar ---> cc838150dac9 Step 3/3 : CMD ["java","-jar","customer-consumer-0.0.1-SNAPSHOT.jar"] ---> Running in cb60d21cdcb4 Removing intermediate container cb60d21cdcb4 ---> 5f3019bca3ca Successfully built 5f3019bca3ca Successfully tagged customer-consumer:latest
The command below is used to execute the service client:
Docker Commands
docker container run --name service-client -d customer-consumer
The output when the above command executed is shown below:
Docker Execution Output
apples-MacBook-Air:customer-consumer bhagvan.kommadi$ docker container run --name service-client -d customer-consumer d9071ac2bb7558c49da59a692a07502417a89ceb49e661f1c9c47905f6672642
We have the service client and service producer (created in section 2.4) created in two separate docker machines. Let us start the docker swarm initialization. The docker command is as shown below:
Docker Swarm Commands
docker swarm init
The output when the above command executed is shown below:
Docker Swarm Output
apples-MacBook-Air:employee-producer bhagvan.kommadi$ docker swarm init Swarm initialized: current node (5uqd5dh8ctuu3o8fsckiapuu7) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-20pwzx1p6jv55cx2xezbl090saaiupxbkldgz2iqgrnf8oh9ry-dll0go0p5g6uu30vvuer04ish 192.168.65.3:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
Let us create a network within the swarm by using the command below:
Docker Swarm Network Commands
docker network create --driver overlay server-client
Let us add two nodes to the network. The commands for the server and the client are shown below:
Docker Swarm Network Commands
docker network create --driver overlay server-client docker service create --network server-client --name server -p 8080:8080 customer-producer docker service create --network server-client --name client customer-consumer
The output when the above command executed is shown below:
Docker Swarm Network Node Output
apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker container stop service-producer service-producer apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker container rm service-producer service-producer apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker service create --network server-client --name server -p 8080:8080 customer-producer image customer-producer:latest could not be accessed on a registry to record its digest. Each node will access customer-producer:latest independently, possibly leading to different nodes running different versions of the image. olnfv5br41uxz59qqa4cs1769 overall progress: 1 out of 1 tasks 1/1: running verify: Service converged apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS olnfv5br41ux server replicated 1/1 customer-producer:latest *:8080->8080/tcp apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker service create --network server-client --name client customer-consumer image customer-consumer:latest could not be accessed on a registry to record its digest. Each node will access customer-consumer:latest independently, possibly leading to different nodes running different versions of the image. svqn1d1obm8eciluu2520ips4 overall progress: 1 out of 1 tasks 1/1: running verify: Service converged apples-MacBook-Air:customer-producer bhagvan.kommadi$ docker service ls ID NAME MODE REPLICAS IMAGE PORTS svqn1d1obm8e client replicated 1/1 customer-consumer:latest olnfv5br41ux server replicated 1/1 customer-producer:latest *:8080->8080/tcp apples-MacBook-Air:customer-producer bhagvan.kommadi$
The command below is used to check the client logs regarding access to the server – REST Customer service.
Docker Logs command
docker container ls docker container logs client.1.lfnmjdhlvup8wukbfegig0hjv
The output when the above command executed is shown below:
Docker Logs command
apples-MacBook-Air:customer-consumer bhagvan.kommadi$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES f22fa232ecb1 customer-consumer:latest "java -jar customer-…" 20 seconds ago Up 18 seconds client.1.lfnmjdhlvup8wukbfegig0hjv 9071f6e307d1 customer-producer:latest "java -jar customer-…" 23 minutes ago Up 23 minutes server.1.i2yfi864kj2p6rj0ofpomkobf apples-MacBook-Air:customer-consumer bhagvan.kommadi$ docker container logs client.1.lfnmjdhlvup8wukbfegig0hjv . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.4.1.RELEASE) 2020-06-13 18:28:33.175 INFO 1 --- [ main] o.j.SpringBootServiceApplication : Starting SpringBootServiceApplication v0.0.1-SNAPSHOT on f22fa232ecb1 with PID 1 (/customer-consumer-0.0.1-SNAPSHOT.jar started by root in /) 2020-06-13 18:28:33.197 INFO 1 --- [ main] o.j.SpringBootServiceApplication : No active profile set, falling back to default profiles: default 2020-06-13 18:28:33.576 INFO 1 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@14514713: startup date [Sat Jun 13 18:28:33 UTC 2020]; root of context hierarchy 2020-06-13 18:28:39.341 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8091 (http) 2020-06-13 18:28:39.407 INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat 2020-06-13 18:28:39.413 INFO 1 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.5.5 2020-06-13 18:28:39.793 INFO 1 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2020-06-13 18:28:39.793 INFO 1 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 6226 ms 2020-06-13 18:28:40.606 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/] 2020-06-13 18:28:40.621 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2020-06-13 18:28:40.623 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2020-06-13 18:28:40.624 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*] 2020-06-13 18:28:40.625 INFO 1 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*] 2020-06-13 18:28:41.730 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@14514713: startup date [Sat Jun 13 18:28:33 UTC 2020]; root of context hierarchy 2020-06-13 18:28:42.029 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2020-06-13 18:28:42.031 INFO 1 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2020-06-13 18:28:42.246 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-06-13 18:28:42.248 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-06-13 18:28:42.427 INFO 1 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2020-06-13 18:28:42.980 INFO 1 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2020-06-13 18:28:43.152 INFO 1 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8091 (http) 2020-06-13 18:28:43.176 INFO 1 --- [ main] o.j.SpringBootServiceApplication : Started SpringBootServiceApplication in 11.996 seconds (JVM running for 13.994) org.javacodegeeks.controllers.ServiceClient@1ee807c6 {"custId":"12","name":"cust1","zipCode":"50100"} apples-MacBook-Air:customer-consumer bhagvan.kommadi$
3. Download the Source Code
You can download the full source code of this example here: Managing Microservices in Docker Swarm