mirror of
https://github.com/jpetazzo/container.training.git
synced 2026-07-21 05:49:40 +00:00
524 lines
11 KiB
Markdown
524 lines
11 KiB
Markdown
# 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
|
|
```
|
|
|
|
<!-- ```wait No such container: 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
|
|
<br/>(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 <serviceID>`)
|
|
|
|
---
|
|
|
|
## `--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
|
|
<br/>(vs. 32768 for single container mapping)
|
|
|
|
- You can indicate two port numbers to set the public port number
|
|
<br/>(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
|
|
|
|

|
|
|
|
---
|
|
|
|
## Test our service
|
|
|
|
- We mapped port 9200 on the nodes, to port 9200 in the containers
|
|
|
|
- Let's try to reach that port!
|
|
|
|
.exercise[
|
|
|
|
<!-- Give it a few seconds to be ready ```bash sleep 5``` -->
|
|
|
|
- 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
|
|
|
|

|
|
|
|
---
|
|
|
|
## 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
|
|
|
|

|
|
|
|
---
|
|
|
|
## 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=<FQDN>` 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
|
|
```
|
|
|
|
<!-- ```longwait Creating dockerswarmvisualizer_viz_1``` -->
|
|
|
|
]
|
|
|
|
---
|
|
|
|
## 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)
|
|
|
|
<!-- ```open http://node1:8080``` -->
|
|
|
|
]
|
|
|
|
- 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
|
|
```
|
|
|
|
]
|