## Docker workshop ![](https://i.imgur.com/y3iifqT.png) --- ## About containers ![](https://i.imgur.com/ZnSQqlx.png) ---- ### A brief history about containers * During the unix development of Unix V7 in 1979, the chroot system call was introduced, changing the root directory of a process and its children to a new location in the filesystem. * Chroot was added to BSD in 1982. * 2000: FreeBSD Jails * 2001: Linux VServer * 2004: Solaris Containers ---- * 2005: Open VZ (Open Virtuzzo) * 2006: Process Containers * 2008: LXC * 2013: Docker * 2016: The Importance of Container Security Is Revealed * 2017: Container Tools Become Mature * Kubernetes Grows Up ---- ### How they work? With containers, instead of virtualizing the underlying computer like a virtual machine (VM), just the OS is virtualized. ![](https://i.imgur.com/N3ABScV.png) --- ## About images ---- > A Docker image is a file, comprised of multiple layers, used to execute code in a Docker container. An image is essentially built from the instructions for a complete and executable version of an application, which relies on the host OS kernel. When the Docker user runs an image, it becomes one or multiple instances of that container. --- ## The plan We'll explore most of the basic docker concepts creating a real microservice project consistent on: ---- * Rust program talking to kafka * CI compiling docker process using gitlab-ci * Kafka cluster using docker-compose * Containers monitored with telegraf and grafana * Load balancer with traefik At the end of the exercice you will have a working environment with those services. --- ## Requirements ---- Edit your hosts file with exercice name `docker.local`. This will be used to access services from load-balancer using Host header. ```ini 127.0.0.1 docker.local ``` Having docker and docker-compose installed and working in the system. --- ## Creating a rust application with docker Just create a rust project of hello world which will be the base of our rust kafka-client ```sh cargo new hello_world --bin ``` ---- Add a `Dockerfile` in the root folder of the application, this file is used to create the docker image of our hello-world. ---- ```Dockerfile FROM rust WORKDIR /app COPY . . RUN cargo install --path . CMD ["hello_world"] ``` ---- Test if the `Dockerfile` compiles properly our app ```sh docker build . ``` ---- If your image compiles properly; Congratulations!! you have your first docker image available. At this point you can use this image in your localhost or push it to some Docker registry to make it available to other docker hosts. But we're going to apply some CD here to automatize this process and be aligned with Continuous Delivery from the first step. ---- Just add the `gitlab-ci.yml` which is provided for the gitlab itself. ---- ```yaml # This file is a template, and might need editing before it works on your project. build-master: # Official docker image. image: docker:latest stage: build services: - docker:dind before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY script: - docker build --pull -t "$CI_REGISTRY_IMAGE" . - docker push "$CI_REGISTRY_IMAGE" only: - master build: # Official docker image. image: docker:latest stage: build services: - docker:dind before_script: - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY script: - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" . - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" except: - master ``` ---- This file work pretty similar to travis-ci or drone CI systems. It tells gitlab to compile your package and push it to the gitlab docker registry. ---- Commit and push this file (or use gitlab-ci add files template) and will be able to see the process of your building in gitlab-ci. After successfull build you will be able to pull your created image from any docker host machine. ---- You can test your new application by using `docker run` ```sh $ docker run registry.gitlab.com/gtrias/rust-example Hello, world! ``` This will download our previous built image (if it isn't available in localhost already) and run it as a container. ---- If you want to update local docker image with latest version you can run `docker pull` ```sh docker pull registry.gitlab.com/gtrias/rust-example ``` --- ## Creating kafka cluster using docker-compose ![](https://media.giphy.com/media/6AFldi5xJQYIo/giphy.gif) ---- Docker compose is a layer over docker API which allows you to manage collections of docker containers using a definition file. Since we don't want to remember all the time our docker commands we'll going to define our kafka cluster using a yml definition file. ---- We are going to base the cluster from already done job [here](https://github.com/wurstmeister/kafka-docker). So let's clone this project to follow. ---- The base docker-compose has this shape: ```yaml version: '2' services: zookeeper: image: wurstmeister/zookeeper ports: - "2181:2181" kafka: build: . ports: - "9092" environment: KAFKA_ADVERTISED_HOST_NAME: 172.17.0.1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 volumes: - /var/run/docker.sock:/var/run/docker.sock ``` ---- This specify two services, one zookeper and kafka itself. ---- You can pull, create containers and run them running: ```sh docker-compose up -d ``` ---- As `docker run` command this will download needed images, create containers and run them. Note the `-d` param refers to detached, it won't close the containers if you close the current shell. Additionally note the `build .` in kafka service, this tells docker-compose to build this image instead of looking one already created one. ---- After this process finishes you will have your running containers ready to work :) You can see the status running ```sh [genar@thinktravel kafka-docker]$ docker-compose ps Name Command State Ports ---------------------------------------------------------------------------------------------------------------------- kafka-docker_kafka_1 start-kafka.sh Up 0.0.0.0:32768->9092/tcp kafka-docker_zookeeper_1 /bin/sh -c /usr/sbin/sshd ... Up 0.0.0.0:2181->2181/tcp, 22/tcp, 2888/tcp, 3888/tcp ``` ---- Also you can chech their logs to ensure they are working as expected: ```sh genar@thinktravel kafka-docker]$ docker-compose logs -f --tail 20 Attaching to kafka-docker_zookeeper_1, kafka-docker_kafka_1 zookeeper_1 | 2019-06-24 19:38:29,608 [myid:] - INFO [main:Environment@100] - Server environment:os.name=Linux zookeeper_1 | 2019-06-24 19:38:29,608 [myid:] - INFO [main:Environment@100] - Server environment:os.arch=amd64 zookeeper_1 | 2019-06-24 19:38:29,608 [myid:] - INFO [main:Environment@100] - Server environment:os.version=5.1.5-arch1-2-ARCH zookeeper_1 | 2019-06-24 19:38:29,608 [myid:] - INFO [main:Environment@100] - Server environment:user.name=root zookeeper_1 | 2019-06-24 19:38:29,609 [myid:] - INFO [main:Environment@100] - Server environment:user.home=/root zookeeper_1 | 2019-06-24 19:38:29,609 [myid:] - INFO [main:Environment@100] - Server environment:user.dir=/opt/zookeeper-3.4.13 zookeeper_1 | 2019-06-24 19:38:29,612 [myid:] - INFO [main:ZooKeeperServer@836] - tickTime set to 2000 ``` ---- To stop all containers just type `docker-compose stop` or if you want to stop and also remove them run `docker-compose down` (if containers are destroyed all data not stored in volumes will be destroyed too) ---- Now you can scale kafka to 2 brokers running: ```sh docker-compose scale kafka=2 ``` --- ## Make our rust application talk to kafka ---- The proper implementation to listen kafka has been implemented [in this repository]( https://gitlab.com/gtrias/rust-example) --- Add our rust app to the kafka docker-compose.yml file: ```yaml version: '2' services: zookeeper: image: wurstmeister/zookeeper ports: - "2181:2181" kafka: build: . ports: - "9092" environment: KAFKA_ADVERTISED_HOST_NAME: 172.17.0.1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 volumes: - /var/run/docker.sock:/var/run/docker.sock rust-client: image: registry.gitlab.com/gtrias/rust-example ``` --- ## Add kafka-manager ```yaml version: '2' services: zookeeper: image: wurstmeister/zookeeper ports: - "2181:2181" kafka: build: . ports: - "9092" environment: KAFKA_ADVERTISED_HOST_NAME: 172.17.0.1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 volumes: - /var/run/docker.sock:/var/run/docker.sock kafka-manager: image: sheepkiller/kafka-manager ports: - 9000:9000 environment: APPLICATION_SECRET: letmein ZK_HOSTS: zookeeper:2181 rust-client: image: registry.gitlab.com/gtrias/rust-example ``` --- ### Generate some data to kafka to see if is received by our rust app You can use kafka-manager to generate the topics to be consumed by rust app. See examples here: https://wurstmeister.github.io/kafka-docker/ --- ## Docker API ---- Docker has a full rich API to manage containers, here I'm going to talk about the most basic and used (by me) to manage host containers: ---- ### List of images images in the docker host ```sh [genar@thinktravel orgmode]$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE factorial-backend_backend latest 06cf665f0d95 7 weeks ago 1.78GB factorial-backend_sidekiq latest 06cf665f0d95 7 weeks ago 1.78GB memcached latest 1941054e2bdf 8 weeks ago 62.2MB elixir latest c4eee7f74185 2 months ago 1.08GB ruby 2.5 e86557c9a8ab 2 months ago 873MB redis latest 0f55cf3661e9 4 months ago 95MB mysql 5.7 e47e309f72c8 4 months ago 372MB node 8 4f01e5319662 4 months ago 893MB node 8-alpine e8ae960eaa9e 4 months ago 66.3MB jwilder/nginx-proxy latest 60f01f8052f5 4 months ago 148MB phpmyadmin/phpmyadmin latest c6ba363e7c9b 4 months ago 166MB ruby 2.4.1 e7ca4a0b5b6d 21 months ago 684MB ``` ---- ### Get an image ```sh docker pull traefik ``` ---- ### Run a new container from an image ```sh docker run -d -p 8080:8080 -p 80:80 -v $PWD/traefik.toml:/etc/traefik/traefik.toml traefik 9e4a65ae2982832902c47e9429a8fbc81aee56de5aea414ab33d133da22d513e ``` This will get the image for you if its not already on the system. ---- ### Show running container logs ```sh docker logs -f --tail 20 e9c118b35981 ``` ---- ### Stop and remove a running container ```sh docker rm -f e9c118b35981 ``` ---- ### Run a command inside a running docker container ```sh docker exec -ti e9c118b35981 bin/rails console ``` Also you can enter to the container shell (if it has any installed inside which is the most habitual) ```sh docker exec -ti e9c118b35981 bash ``` ---- ### Pull images, create containers and run them ```sh docker-compose up -d ``` Note: -d is to run them detached, if not the process will be binded in your shell session and be lost if you close it. ---- ### Stop all containers ```sh docker-compose stop ``` ---- ### See all containers logs ```sh docker-compose logs -f --tail 20 ``` ---- ### See one container logs ```sh docker-compose logs -f --tail 20 traefik ``` ---- ### Stop and remove all containers (information non stored on volume will be lost) ```sh docker-compose down ``` --- ## Add traefik for easy access to our services Usually when you want to have a running container you also want to publish it to internet. A very nice way to achieve this is using a load-balancer able to reconfigure itself when some of the containers change. [traefik](https://traefik.io) does exact this job in a very neat way. ---- A picture is worth a thousand words ![](https://d33wubrfki0l68.cloudfront.net/7c5fd7d38c371e23cdff059e6cebb10292cd441c/7d420/assets/img/traefik-architecture.svg) ---- Add it to your project: ```yaml version: '2' services: zookeeper: image: wurstmeister/zookeeper ports: - "2181:2181" kafka: build: . ports: - 9092 environment: KAFKA_ADVERTISED_HOST_NAME: 172.17.0.1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 volumes: - /var/run/docker.sock:/var/run/docker.sock kafka-manager: image: sheepkiller/kafka-manager ports: - 9000:9000 environment: APPLICATION_SECRET: letmein ZK_HOSTS: zookeeper:2181 labels: - "traefik.port=9000" - "traefik.frontend.rule=Host:kafka-manager.docker.local" rust-client: image: registry.gitlab.com/gtrias/rust-example command: hello_world --topics my-topic --brokers kafka:9092 traefik: image: traefik command: --web --docker --docker.domain=docker.local --logLevel=DEBUG ports: # access this with the correct Host header to access the respective container - "80:80" # management UI - "8080:8080" volumes: # traefik does its magic by reading information about running containers from the docker socket - /var/run/docker.sock:/var/run/docker.sock - /dev/null:/traefik.toml ``` --- ## Links * [What’s the Diff: VMs vs Containers](https://www.backblaze.com/blog/vm-vs-containers/) * [A Brief History of Containers: From the 1970s to 2017](https://blog.aquasec.com/a-brief-history-of-containers-from-1970s-chroot-to-docker-2016) * [Digging into Docker layers](https://medium.com/@jessgreb01/digging-into-docker-layers-c22f948ed612) * https://github.com/wurstmeister/kafka-docker * https://github.com/spicavigo/kafka-rust * [traefik](https://traefik.io)
{"title":"Docker workshop","slideOptions":{"theme":"night"}}