# Running our first Swarm service - How do we run services? Simplified version: `docker run` → `docker service create` .exercise[ - Create a service featuring an Alpine container pinging Google resolvers: ```bash docker service create --name pingpong alpine ping 8.8.8.8 ``` - Check the result: ```bash docker service ps pingpong ``` ] --- ## Checking service logs (New in Docker Engine 17.05) - Just like `docker logs` shows the output of a specific local container ... - ... `docker service logs` shows the output of all the containers of a specific service .exercise[ - Check the output of our ping command: ```bash docker service logs pingpong ``` ] Flags `--follow` and `--tail` are available, as well as a few others. Note: by default, when a container is destroyed (e.g. when scaling down), its logs are lost. --- class: extra-details ## Looking up where our container is running - The `docker service ps` command told us where our container was scheduled .exercise[ - Look up the `NODE` on which the container is running: ```bash docker service ps pingpong ``` - If you use Play-With-Docker, switch to that node's tab, or set `DOCKER_HOST` - Otherwise, `ssh` into that node or use `$(eval docker-machine env node...)` ] --- class: extra-details ## Viewing the logs of the container .exercise[ - See that the container is running and check its ID: ```bash docker ps ``` - View its logs: ```bash docker logs containerID ``` - Go back to `node1` afterwards ] --- ## Scale our service - Services can be scaled in a pinch with the `docker service update` command .exercise[ - Scale the service to ensure 2 copies per node: ```bash docker service update pingpong --replicas 6 ``` - Check that we have two containers on the current node: ```bash docker ps ``` ] --- ## Monitoring deployment progress with `--detach` (New in Docker Engine 17.10) - The CLI monitors commands that create/update/delete services - In effect, `--detach=false` is the default - synchronous operation - the CLI will monitor and display the progress of our request - it exits only when the operation is complete - Ctrl-C to detach at anytime - `--detach=true` - asynchronous operation - the CLI just submits our request - it exits as soon as the request is committed into Raft --- ## To `--detach` or not to `--detach` - `--detach=false` - great when experimenting, to see what's going on - also great when orchestrating complex deployments
(when you want to wait for a service to be ready before starting another) - `--detach=true` - great for independent operations that can be parallelized - great in headless scripts (where nobody's watching anyway) .warning[`--detach=true` does not complete *faster*. It just *doesn't wait* for completion.] --- class: extra-details ## `--detach` over time - Docker Engine 17.10 and later: the default is `--detach=false` - From Docker Engine 17.05 to 17.09: the default is `--detach=true` - Prior to Docker 17.05: `--detach` doesn't exist (You can watch progress with e.g. `watch docker service ps `) --- ## `--detach` in action .exercise[ - Scale the service to ensure 3 copies per node: ```bash docker service update pingpong --replicas 9 --detach=false ``` - And then to 4 copies per node: ```bash docker service update pingpong --replicas 12 --detach=true ``` ] --- ## Expose a service - Services can be exposed, with two special properties: - the public port is available on *every node of the Swarm*, - requests coming on the public port are load balanced across all instances. - This is achieved with option `-p/--publish`; as an approximation: `docker run -p → docker service create -p` - If you indicate a single port number, it will be mapped on a port starting at 30000
(vs. 32768 for single container mapping) - You can indicate two port numbers to set the public port number
(just like with `docker run -p`) --- ## Expose ElasticSearch on its default port .exercise[ - Create an ElasticSearch service (and give it a name while we're at it): ```bash docker service create --name search --publish 9200:9200 --replicas 5 \ elasticsearch`:2` ``` ] Note: don't forget the **:2**! The latest version of the ElasticSearch image won't start without mandatory configuration. --- ## Tasks lifecycle - During the deployment, you will be able to see multiple states: - assigned (the task has been assigned to a specific node) - preparing (this mostly means "pulling the image") - starting - running - When a task is terminated (stopped, killed...) it cannot be restarted (A replacement task will be created) --- class: extra-details, pic ![diagram showing what happens during docker service create, courtesy of @aluzzardi](images/docker-service-create.svg) --- ## Test our service - We mapped port 9200 on the nodes, to port 9200 in the containers - Let's try to reach that port! .exercise[ - Try the following command: ```bash curl localhost:9200 ``` ] (If you get `Connection refused`: congratulations, you are very fast indeed! Just try again.) ElasticSearch serves a little JSON document with some basic information about this instance; including a randomly-generated super-hero name. --- ## Test the load balancing - If we repeat our `curl` command multiple times, we will see different names .exercise[ - Send 10 requests, and see which instances serve them: ```bash for N in $(seq 1 10); do curl -s localhost:9200 | jq .name done ``` ] Note: if you don't have `jq` on your Play-With-Docker instance, just install it: ``` apk add --no-cache jq ``` --- ## Load balancing results Traffic is handled by our clusters [routing mesh]( https://docs.docker.com/engine/swarm/ingress/). Each request is served by one of the instances, in rotation. Note: if you try to access the service from your browser, you will probably see the same instance name over and over, because your browser (unlike curl) will try to re-use the same connection. --- class: pic ![routing mesh](images/ingress-routing-mesh.png) --- ## Under the hood of the routing mesh - Load balancing is done by IPVS - IPVS is a high-performance, in-kernel load balancer - It's been around for a long time (merged in the kernel since 2.4) - Each node runs a local load balancer (Allowing connections to be routed directly to the destination, without extra hops) --- ## Managing inbound traffic There are many ways to deal with inbound traffic on a Swarm cluster. - Put all (or a subset) of your nodes in a DNS `A` record (good for web clients) - Assign your nodes (or a subset) to an external load balancer (ELB, etc.) - Use a virtual IP and make sure that it is assigned to an "alive" node - etc. --- class: pic ![external LB](images/ingress-lb.png) --- ## Managing HTTP traffic - The TCP routing mesh doesn't parse HTTP headers - If you want to place multiple HTTP services on port 80/443, you need something more - You can set up NGINX or HAProxy on port 80/443 to route connections to the correct Service, but they need to be "Swarm aware" to dynamically update configs -- - Docker EE provides its own [Layer 7 routing](https://docs.docker.com/ee/ucp/interlock/) - Service labels like `com.docker.lb.hosts=` are detected automatically via Docker API and dynamically update the configuration -- - Two common open source options: - [Traefik](https://traefik.io/) - popular, many features, requires running on managers, needs key/value for HA - [Docker Flow Proxy](http://proxy.dockerflow.com/) - uses HAProxy, made for Swarm by Docker Captain [@vfarcic](https://twitter.com/vfarcic) --- class: btw-labels ## You should use labels - Labels are a great way to attach arbitrary information to services - Examples: - HTTP vhost of a web app or web service - backup schedule for a stateful service - owner of a service (for billing, paging...) - correlate Swarm objects together (services, volumes, configs, secrets, etc.) --- ## Pro-tip for ingress traffic management - It is possible to use *local* networks with Swarm services - This means that you can do something like this: ```bash docker service create --network host --mode global traefik ... ``` (This runs the `traefik` load balancer on each node of your cluster, in the `host` network) - This gives you native performance (no iptables, no proxy, no nothing!) - The load balancer will "see" the clients' IP addresses - But: a container cannot simultaneously be in the `host` network and another network (You will have to route traffic to containers using exposed ports or UNIX sockets) --- class: extra-details ## Using local networks (`host`, `macvlan` ...) - It is possible to connect services to local networks - Using the `host` network is fairly straightforward (With the caveats described on the previous slide) - Other network drivers are a bit more complicated (IP allocation may have to be coordinated between nodes) - See for instance [this guide]( https://docs.docker.com/engine/userguide/networking/get-started-macvlan/ ) to get started on `macvlan` - See [this PR](https://github.com/moby/moby/pull/32981) for more information about local network drivers in Swarm mode --- ## Visualize container placement - Let's leverage the Docker API! .exercise[ - Run this simple-yet-beautiful visualization app: ```bash cd ~/container.training/stacks docker-compose -f visualizer.yml up -d ``` ] --- ## Connect to the visualization webapp - It runs a web server on port 8080 .exercise[ - Point your browser to port 8080 of your node1's public ip (If you use Play-With-Docker, click on the (8080) badge) ] - The webapp updates the display automatically (you don't need to reload the page) - It only shows Swarm services (not standalone containers) - It shows when nodes go down - It has some glitches (it's not Carrier-Grade Enterprise-Compliant ISO-9001 software) --- ## Why This Is More Important Than You Think - The visualizer accesses the Docker API *from within a container* - This is a common pattern: run container management tools *in containers* - Instead of viewing your cluster, this could take care of logging, metrics, autoscaling ... - We can run it within a service, too! We won't do it yet, but the command would look like: ```bash docker service create \ --mount source=/var/run/docker.sock,type=bind,target=/var/run/docker.sock \ --name viz --constraint node.role==manager ... ``` .footnote[ Credits: the visualization code was written by [Francisco Miranda](https://github.com/maroshii). [Mano Marks](https://twitter.com/manomarks) adapted it to Swarm and maintains it. ] --- ## Terminate our services - Before moving on, we will remove those services - `docker service rm` can accept multiple services names or IDs - `docker service ls` can accept the `-q` flag - A Shell snippet a day keeps the cruft away .exercise[ - Remove all services with this one liner: ```bash docker service ls -q | xargs docker service rm ``` ]