Docker for the Absolute Beginner - Hands On - DevOps

Docker Overview

Why do you need docker

  • Compatibility / Dependency
  • Long setup time
  • Different Dev/test/prod environment

What can it do?

  • Containerize application
  • Run each service with its own dependencies in separate containers.

What are containers?

Containers are completely isolated environments, as in they can have their own processes or services their own network interfaces their own mounts just like virtual machines except they all share the same OS kernel.

Sharing the kernel

The main purpose of Docker is to package and containerized applications and to ship them and to run them anywhere any times as many times as you want

Containers vs virtual machines

  • High Utilization on virtual machines
  • Consume more space in virtual machines
  • docker has less isolation as more resources are shared between the containers like the kernel.

Container vs image

  • an image is a package or a template just like a VM template that you might have worked with in the virtualization world, it is used to create more containers.
  • Containers are running instances of images that are isolated and have their own environments and set of processes.

Getting started

Docker Editions

  • Community Edition - set of free docker product
  • Enterprise edition - certified and supported container platform that comes with enterprise add-ons

Docker on windows

  1. Docker toolbox - contains a set of tools like oracle virtual box, docker engine, docker machine, docker compose, kitematic GUI.
  2. Docker desktop for windows - uses hyper-v

https://docs.docker.com/desktop/windows/install/

docker run nginx
docker run hello-world
docker run -it ubuntu bash

-it is short for --interactive + --tty. When you docker run with this command it takes you straight inside the container.

Basic Container Commands

// list containers
docker ps
docker ps -a // show current and previous running docker

// stop container
docker stop "name" / "container id"

// remove container
docker rm silly_sammet

// list images
docker images

// remove images
docker rmi nginx

// just download the image without running the image
docker pull nginx

//example 
docker run --init kodekloud/simple-webapp // use --init so we can use ctrl-c

docker run -d kodekloud/simple-webapp // run in background mode

docker attach 05eb79b3dfe4a2b7a

Docker Commands

docker pull centos
docker run -it centos bash # goes to bash
docker run -d centos sleep 20 # run and shutdown after 20 second
docker stop {name_container} / {container_id} #force kill
docker rm {container_id} / {name_container}

docker images # list all images
docker rmi {name} # remove docker images
docker exec {container_id} cat /etc/*release*

go to https://hub.docker.com/search?q=&source=verified&type=image to check all images available

https://drive.google.com/open?id=10vwiqRo8vW-LO88eQLvgy2V-Mp9clTFN&authuser=madindo%40gmail.com&usp=drive_fs

RUN - STDIN

Let's now look at inputs. I have a simple prompt application that when Run asks for my name. And on entering my name prints a welcome message. If I were to tokenize this application and run it as a Docker container like this, it wouldn't waitfor the prompt. It just prints whatever the application is supposed to print on standard out. That is because by default the Docker container does not listen to a standard input (non interactive).

but if you put -i (interative) before docker run it will wait for the input but still it will not prompt until we use -t (terminal)

RUN - port mapping

docker run kodekloud/webapp
# running on http://0.0.0.0:5000 -> it can be access from the docker but not outside docker
docker run -p 80:5000 kodekloud/webapp
# port 80 of localhost to port 5000

RUN - Volume mapping

#non persistent data
docker run mysql
docker stop mysql
docker rm mysql

# persistent data
docker run -v /opt/datadir:/var/lib/mysql

Inspect Container

docker inspect {container_name}

Container logs

docker logs {container_name}

Docker Commands Advanced Features

docker run ubuntu cat /etc/issue # check version ubuntu 22.04
docker run ubuntu:18.04 cat /etc/issue # using different version
docker run ubuntu sleep 15 # stay alive for 15 minutes
docker inspect {container_id}

Jenkins

docker run -p 8081:8080 -p 50000:50000 jenkins/jenkins # i use 8081 because mine conflicted

Demo Docker Image

docker run -it ubuntu bash
apt-get update
sudo apt install python3-pip
pip install flask
nano /opt/app.py

---
import os
from flask import Flask
app = Flask(__name__)

@app.route("/")
def main():
    return "Welcome!"

@app.route('/how are you')
def hello():
    return 'I am good, how about you?'

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)
--- 

FLASK_APP=/opt/app.py flask run --host=0.0.0.0
docker build . -t simpleapp
docker tag simpleapp {docker_username}/simpleapp
docker push {docker_username}/simpleapp

Demo Environment Variables

docker run -e APP_COLOR=blue -p 38282:8080 --name blue-app kodekloud/simple-webapp

docker run -e MYSQL_ROOT_PASSWORD=db_pass123 --name mysql-db mysql

Command Vs Entrypoint

CMD ["nginx"] # this is a command from DockerFile
ENTRYPOINT ["/entropoint.sh"] # this is an entrypoint

Docker Compose

docker run mmumshad/simple-webapp
docker run mongodb
docker run redis:alpine
docker run ansible

#docker-compose.yml
services:
  web:
    image: "mmumshad/simple-webapp"
  database:
  	image: "mongodb"
  messaging:
    image: "redis:alpine"
  orchestration:
    image: "ansible"
    
docker-compose up

Demo voting application

In this demo we are going to learn on putting it together but 1 by 1

git clone https://github.com/dockersamples/example-voting-app.git

#build voting app
cd vote
docker build . -t voting-app
#end build

docker run -p 8001:80 voting-app # if run well good now attach with redis

docker run -d --name=redis redis
docker run --name db -e POSTGRES_PASSWORD=postgres -d postgres:9.4
docker run -p 8001:80 -d --link redis:redis voting-app
docker run --link redis:redis --link db:db worker-app

#build result app on mac m1
cd result
docker build --platform linux/amd64 . -t result-app
docker run --platform linux/amd64 -p 5001:80 --link=db:db result-app
#end build

Docker compose

Docker compose for mac is different, use docker compose up when linux uses docker-composer up

version: '2'
services:
  redis:
    image: redis
  db:
    image: postgres:9.4
  vote:
    image: voting-app
    ports:
      - 8001:80
  worker:
    image: worker-app
    links:
      - db
      - redis
  result:
    image: result-app
    ports:
      - 5001:80

Docker Quiz

First create a redis database container called redis, image redis:alpine.
-> docker run --name redis -d redis:alpine

Next, create a simple container called clickcounter with the image kodekloud/click-counter, link it to the redis container that we created in the previous task and then expose it on the host port 8085
The clickcounter app run on port 5000.
-> docker run -p 8085:5000 --link redis:redis --name clickcounter -d kodekloud/click-counter

Create a docker-compose.yml file under the directory /root/clickcounter. Once done, run docker-compose up.
The compose file should have the exact specification as follows -
redis service specification - Image name should be redis:alpine.
clickcounter service specification - Image name should be kodekloud/click-counter, app is run on port 5000 and expose it on the host port 8085 in the compose file.
-> 
services:
  redis:
    image: redis:alpine
  clickcounter:
    image: kodekloud/click-counter
    ports:
    - 8085:5000
version: '3.0'

Docker Registry

Deploy Private registry

# tag image
docker image tag my-image localhost:5000/my-image
# push image
docker push localhost:5000/my-image
# pull image
docker pull localhost:5000/my-image
docker pull 192.168.56.100:5000/my-image

Docker quiz

Let practice deploying a registry server on our own.
Run a registry server with name equals to my-registry using registry:2 image with host port set to 5000, and restart policy set to always.

-> docker run -d -p 5000:5000 --restart=always --name my-registry registry:2

Now its time to push some images to our registry server. Let's push two images for now .i.e. nginx:latest and httpd:latest.
Note: Don't forget to pull them first.
To check the list of images pushed , use curl -X GET localhost:5000/v2/_catalog

->
Run: docker pull nginx:latest then docker image tag nginx:latest localhost:5000/nginx:latest and finally push it using docker push localhost:5000/nginx:latest.
We will use the same steps for the second image docker pull httpd:latest and then docker image tag httpd:latest localhost:5000/httpd:latest and finally push it using docker push localhost:5000/httpd:latest

docker image prune -a 
-> remove all images without at least one container associated to them


Docker Engine

Cgroups

docker run --cpu=.5 ubuntu # limit 50% of cpu
docker run --memory=100m # limit 100mb for memory
docker run --platform linux/amd64 -it --rm -d -p 8888:8080 tomcat:8.0
docker exec 5b20fb4b1820 ps -eaf # to see the processes

Docker Storage

There are 2 types of mounts, volume mounting, and bind mounting. Volume mounting will mount from the volume directory and the bind mounting will mount from anywhere on the docker host.

Storage drivers

  • AUFS
  • ZFS
  • BTRFS
  • Device Mapper
  • Overlay
  • Overlay2

Docker storage demo

docker images
docker history {container_id} # to see how it builds

docker system df # to see how much it takes on the system

docker system df -v # to see how many shared usage for each builds

Docker quiz

Run a mysql container named mysql-db using the mysql image. Set database password to db_pass123
Note: Remember to run it in the detached mode.
-> docker run -d --name mysql-db -e MYSQL_ROOT_PASSWORD=db_pass123 mysql

Run a mysql container again, but this time map a volume to the container so that the data stored by the container is stored at /opt/data on the host.
Use the same name : mysql-db and same password: db_pass123 as before. Mysql stores data at /var/lib/mysql inside the container.

-> docker run -v /opt/data:/var/lib/mysql -d --name mysql-db -e MYSQL_ROOT_PASSWORD=db_pass123 mysql

Docker Networking

Bridge -> docker run ubuntu
None -> docker run ubuntu --network=none
Host -> docker run ubuntu --network=host

docker inspect {container_name}

Docker quiz

Explore the current setup and identify the number of networks that exist on this system.
-> docker network ls 

-> docker inspect {container_name}

What is the subnet configured on bridge network?
-> docker network inspect bridge

Run a container named alpine-2 using the alpine image and attach it to the none network.
->docker run --name alpine-2 --network=none alpine

Create a new network named wp-mysql-network using the bridge driver. Allocate subnet 182.18.0.1/24. Configure Gateway 182.18.0.1

-> docker network create --driver bridge --subnet 182.18.0.1/24 --gateway 182.18.0.1 wp-mysql-network
-> docker network inspect wp-mysql-network

Deploy a mysql database using the mysql:5.6 image and name it mysql-db. Attach it to the newly created network wp-mysql-network
Set the database password to use db_pass123. The environment variable to set is MYSQL_ROOT_PASSWORD.
-> docker run -d -e MYSQL_ROOT_PASSWORD=db_pass123 --name mysql-db --network wp-mysql-network mysql:5.6

Deploy a web application named webapp using the kodekloud/simple-webapp-mysql image. Expose the port to 38080 on the host.
The application makes use of two environment variable:
1: DB_Host with the value mysql-db.
2: DB_Password with the value db_pass123.
Make sure to attach it to the newly created network called wp-mysql-network.
Also make sure to link the MySQL and the webapp container.
-> docker run --network=wp-mysql-network -e DB_Host=mysql-db -e DB_Password=db_pass123 -p 38080:8080 --name webapp --link mysql-db:mysql-db -d kodekloud/simple-webapp-mysql

Container Orchestration

docker service create --replicas=100 nodejs

docker swarm , kubernetes, mesos