# Docker Micro services The idea behind microservices is that some types of applications become easier to build and maintain when they are broken down into smaller, composable pieces which work together. Each component is developed separately, and the application is then simply the sum of its constituent components. Adding new features becomes easier - Small processes that communicate over the network Docker, Docker Docker File -> Docker Hub -> Build -> Docker, Docker (Dev Machine) (Git Repo) (Jenkins) Host Machine ## Docker Components - **Docker Registry:** Storage component for docker images - We can either store the images publically or privately - Docker hub is docker's own cloud repository - Advantages - Control where images are being stored - Integrate image storage with your in-house dev - **Docker Images and Containers:** - Read only template used to create containers - Built by docuer users - Stored in Docker Hub or your Local registry - Contain all dependencies to create containers - Isolated Application platform - Contains everything needed to run the application - Containers can be built from more than one image - Docker Compose: - Docker compose makes it easier to configure and run applications made up of multiple containers. For Ex: LAMP stack. All of them can be deployed by using one YAML file. - You can execute all containers in a single command - Most Used Commands: - docker help `$docker --help` - login: `$docker login` - check version `$docker --version` - execute hello world `$docker run hello-world` - pull image: `$sudo docker pull centos` - start image: `$sudo docker run -it centos` - check all images `$docker images` - check all running containers `$docker ps -a` - access container command prompt `$docker exec -it <container_id> bash` - stop container: `$docker stop <cont_id>` - commit image to docker registry: `$docker commit <cont_id> <duckerhub_name>/<image_name>` - push image to docker registry: `$docker push <duckerhub_name>/<image_name>` - remove container: `$docker rm <cont_id>` - remove image: `$docker rmi <image_id>` - compose containers of full application: `$docker-compose up` - Sample flask application with redis - path of instructor: `C:\Windows\system32\webapplication` - Files Shown with exact name - webapp.py - requirements.txt - Dockerfile - docker-compose.yml - execute compose-up - Note: THE DEMO SHOWS KITEMATIC but according to Docker, this is a legacy solution and better to use docker desktop. UI is pretty much similar to proxmox and simple enough to understand #### Writing a DockerFile to build a container: - Dockerfile syntax consists of two types of code blocks 1. Comments: - Syntax: "# \<text\>" 2. Commands and argument - Syntax: "\<command_name\> \<arg1\> \<arg2\> \.\.\." - Example: ```yaml # Print "Welcome to Edureka!" RUN echo "Welcome to Edureka!" ### Important commands (NOTE ALL CAPS) - **FROM:** defines base image to start the process Example: ```yaml # Usage: From \<image_name\> FROM centos ``` - **RUN:** executes a command example a shell command, but can also do complex tasks like building docker images Example: ```yaml # Usage: RUN [command] RUN apt-get install -y vim ``` - **CMD:** exactly as **RUN** but cannot do complex tasks (mostly recommeneded to use CMD) - **ENTRYPOINT:** creates an entry point to a container i.e. the first ergument to be executed whe the container runs Example: ```yaml CMD "Welcome to Edureka" ENTRYPOINT echo ``` - **ADD:** Copy files from home to container Example: ```yaml ADD /foo/bar/src /foo1/bar1/dest ``` - **ENV:** Add **ENV VARIABLE TO A DOCKER CONTAINER** Example: ```yaml # Usage ENV key value ENV SERVER_WORKS 4 ``` - **WORKDIR:** Set **WORKDIR** before you go to execute your **CMD** command Example: ```yaml WORKDIR /path/foo/bar CMD sed -i <><> ``` - **EXPOSE:** Exposes a port inside a container to communicate with outside Example: ```yaml # Usage Expose [port] EXPOSE 443 EXPOSE 80 ``` - **MAINTAINER:** Adds an author and a maintainer to the image in case you want to maintain your own hub or use docker community hub. **Note:** This will get added in the **FROM** command ```yaml # Usage Maintainer [name] MAINTAINER foo_bar # Note FROM foo_bar/gitlab-runner ... ``` - **USER:** Set username or UID of container ```yaml # Usage USER [UID] USER 751 ``` - **VOLUME:** USed to specify a directory on the host machine to the container i.e. mount it ```yaml # USAGE VOLUME ["/dir1", "/dir2", ...] VOLUME ["/my_files"] ``` Use Docker Build to build your image, install packages and have packages ready with UID. ### Using Docker Compose.yml Use Docker Compose.yml for deploying multiple image based apps ## Best Practices ### Caching in Docker - Every command you execute on a docker file create a new temporary image that docker calls a **`layer`** - Caching layers is really helpful if you think there have not been any major changes in the container for execution and speed ### Storing application code - By default, a container's data is stored in a portion called the writable layer of the container - This layer is slow and has a lot of overhead as it runs in the kernel space of the HOST OS - To avoid this bottleneck, docker provides a few solutions - **Volumes**: Stored in the part of the host system that is controlled by docker **`/var/lib/docker/volumes/`** - **Bind mounts**: Can be stored anywhere on Host OS and can be modified by processes in host OS - **tempfs mounts**: Mounts stored on Host System's memory only and are never written to host file system - Named Pipe (only Windows) - **Volumes** have an advantage over adding your code to the container - Application code dependency is removed from container - Can be shared among multiple containers - Smaller container image - Better IO according to Docker - When app requires full control over file system, example DBs - **Bind Mounts** are used mostly when - Application needs configs from host OS - Application creates or uses articats ex `--target` for a build on host OS - Application requires a certain guaranteed file system structure - **Tempfs** are best used for temporary data use cases ### Using Multistage build - Dont use a base image and build on that every time, within the docker file use multiple build statements ![](https://i.imgur.com/4B5rkMZ.png) In the above image - Line 1 to 13 -> Stage 1 - Line 15 to 21 -> Stage 2 Stage 1 performs - Take the node code - create artifact of the code and packages using `$npm run build` Stage 2 performs - from an nginx container - add the node code artifact - expose port 80 - execute the command ## Docker Networking #### Presumptions - Master / Worker nodes have to be on static IP - Admin connecting from client machine #### Seven Networking Methods - Default bridge - User defined bridge - Host network - MAC VLAN - Mac VLAN Bridge Mode - Mac VLAN 802.1q Mode - Check network by `$ sudo docker network ls` ### 1. Default Bridge network -> Like Switch on Host - Type Bridge, default on docker - Not Pref - Install only network `$ sudo apt-get install docker.io` - Like a switch in docker - All containers created without specifying network are in this - Also performs DHCP performs - Copies /etc/resolv.conf from host to containexr - All containers can ping each other - All containers can ping internet through host (by Nat Masquerading) - Traffic from internet to container can be routed by port forwarding - Ex: `sudo docker run -itd -rm -p 80:80 --name stormbreaker nginx` ### 2. User defined Custom Bridge network - Exactly like bridge, but created by user - ***Preferred*** way to deploy images in docker - Created by `$ sudo docker network create asguad` - **Containers cannot ping containers inside other bridges** - **DNS management between containers is managed by the hostnames too as IPs can change if contains start and shutdown** ### 3. Host Network - Runs as a regular application on the host - **No isolation on host** - Done by passing option `--network host` while starting - Ip address and ports are shared by host - ***Not preferred*** ### 4. Mac VLAN TWO Methods #### 1. MAC VLAN Bridge mode - Connected to gateway as hosts (VMs/bare metals) - No need to expose ports of host - Connection happens in **promescious mode** - Connected **Logically on different lines** - Connected **Physically on same line** - causes confusion because routers choose mac address to identify device connections to switch - Need to allow multiple mac addresses on same cable - - Turn on promescious mode `sudo ip link set <interface like eth0> promisc on` - **Have to pass IP address and gateway** - Restart Network - Create Network `$ sudo docker network create -d macvlan --subnet <host_subnet>/24 --gateway <host_gateway> -o parent=<eth0> <name>` - Create Container on network `$ sudo docker run -itd --rm --network <name> --ip <ip> --name <container_names>` #### 2. 802.1q mode - Creates separate logical interfaces instead and attach logical vlans to logical interfaces - Example nginx connected to `eth0.30` - To create network in vlan 802.1q mode `$ sudo docker network create -d macvlan --subnet <host_subnet>/24 --gateway <host_gateway> -o parent=<eth0.20> <name>` ### 5. IP VLAN - Removes promescious mode as a requirement TWO Methods again #### 1. L2 Method - **One mac address (Host) shared by many IP Addresses** - All containers get IP address manually assigned from command `$ sudo docker network create -d ipvlan --subnet <host_subnet> --gateway <host_gateway> - parent=enp0s3 <name>` #### 2. L3 Method - All about layer 3 -> Routing only, not like a switch - **We connect containers on host like host is router** - **We create subnets inside host networks that can be assigned to containers** - When we want to talk to container groups inside host, the switch between client and host just forwards packets to host, host then routes them to client - Groups inside host are isolated but can talk to each other `$sudo docker network create -d ipvlan --subnet <subnet created inside host, non clashing> -o parent=enp0s3 -o ipvlan_mode=l3 <name>` - you can create static routes inside host and host switch ### 6. Docker Overlay Networks - Pro Networks that can be fine tuned for Pro requirements, - Out of scope for homelab use ### 7. None - None or No Network :-) ## Docker Compose - File that describes what docker needs to start a service - A service is a collection of containers starting with one - This file also describes what are the ports to be open, what volumes to bind and if you need to use any tags - This file also creates the docker container as needed and can be substituted for CLI and can integrate multiple Dockerfiles ### Features - Docker compose can also use an override file `docker-compose.override.yml` - can combine both files into one like inheritence - Can create variables in the file to reduce duplication - Can create env variables in the container from a `.env` file in the folder - Create env variables for dev and prod - Exopse ports in the following manner only, do not use common system on line 62 - Entire internet will be able to access example.com:8000 if you are using nginx on same host ![](https://i.imgur.com/Zc6JLvL.png) ## Docker Swarm mode - Integrated into docker engine, but goes above and beyond what docker can provide - A docker host can be both manager, worker, or both - When you want a container **service** in a swarm, you define an optimal state to the swarm and it takes care of creating and maintaining it - Required settings - Number of replicas - network - storage - ports, etc - A **task** is a running container which is part of a swarm service and managed by the swarm manager - Containers in a swarm service do not have servivce losses during updates - You can still run containers in standalone mode - Typical docker production swarms are distributed across multiple physical and cloud machines ### Deployment - A service is the submitted to a manager node - The manager node dispatches units of work to other nodes ## Docker Storage #### default inside container By default all files inside of a contaner are stored on a writable container layer. That means - data does not persist - writable layer is tightly coupled to host - requires an internal storage system that would reduce performance of system ### Storage Types provided by docker - Volumes: Stored in a part of the host file system, managed by docker (`/var/lib/docker/volumes/` on linux) - Best way to persist data in docker - Bets performance - Binds: Stored anywhere on host system - May or may not be important - Non docker processes and hosts can modify at any time - tempfs from host use host system memory and never written to host file system **All data gets exposed as files or folder inside to container, and looks the same from inside** #### Volumes - Created and managed by docker - You can create volumes by `docker volume create <>` - Isolated from the core functionality of the host machine - A given volume can be simultaneously monted on multiple docker hosts - Dont increase size of container as this data is stored outside the container ![](https://i.imgur.com/RSmS7m1.png) **Attaching volumes** - can be done using either `--mount` or `--volume` - -v syntax requires 3 things separated by `:` - Sample `targetpath:spurcepath:ro` - When you start a service in swarm mode, each service container gets and uses its local copy of the volume - **By default none of the containers can share this data** except: - if you use special storage drivers, ex: volume driver - Use S3 like cloud storage outside the realm of containers **Volume Driver** - Like networking, when you create a drive, you can specify which type of driver to use - To use, you need to install plugin and then specify driver ```bash $ docker plugin install --grant-all-permissions vieux/sshfs $ docker volume create --driver vieux/sshfs \ -o sshcmd=test@node2:/home/test \ -o password=testpassword \ # Note the additional options passed sshvolume ``` You can create NFS and Samba Services through docker ```bash $ docker service create -d \ --name nfs-service \ --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \ nginx:latest $ docker service create -d \ --name nfs-service \ --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \ nginx:latest $ docker volume create \ --driver local \ --opt type=cifs \ --opt device=//uxxxxx.your-server.de/backup \ --opt o=addr=uxxxxx.your-server.de,username=uxxxxxxx,password=*****,file_mode=0777,dir_mode=0777 \ --name cif-volume ``` **Mounting Block Storage as Volume Driver** - Under the hood `--mount` flag works as it does in linux kernel - Docker does not implement any additional functionality **- You cannot execute the `--mount` command inside the container because the container is unable to access `/dev/loop5` unless container was started with it** ```bash # Sample mount in linux $ mount -t <mount.volume-opt.type> <mount.volume-opt.device> <mount.dst> -o <mount.volume-opts.o> $ mount -t ext4 /dev/loop5 /external-drive # Sample same execution in docker docker run \ --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4' ``` **You can allocate some space on host and mount this as block device on container, Example:** ```bash # Create a file and allocate some space to it: $ fallocate -f 1G disk.raw # Build a file system onto the disk.raw file: $ mkfs.ext4 disk.raw # Create a loop device: $ losetup -f --show disk.raw # Note this device is removed after system reboot /dev/loop5 # Run a container that mounts the loop device as a volume $ docker run -it --rm \ --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4' \ ubuntu bash # Remove losetup $ losetup -d /dev/loop5 ``` **There are more options like backup and restore volumes** - #TODO #### Bind Mounts (skipping) - Been around since docker started - Limited functionality compared to volumes - Used from file systems directly inside host - You cannot use CLI to Manage bind mounts #### tempfs - Volumes and bind mounts let you persist data on host after the container dies (Note losetup is lost after host dies) - If you want a temp space to use, **on linux hosts** you have a third option `tempfs` - Uses host system memory, only 2 configurabe options -> size and octal file mode (default is 1777 => drwxrwxrwx) ### Using the ZFS Storage Driver - # TODO - # Prereq needs a working ZFS setup ## Overlay Networking Tutorial Reference from https://docs.docker.com/network/network-tutorial-overlay/ - Specially focused on swarm mode operation - Prereq: - A client laptop (windows or linux/mac) - Separate cluster running in swarm mode - Topics covered - Default overlay network - User defined overlay network - Overlay network for standalone containers - Communicate between a container and a swarm service #### Using the default overlay network - Created when swarm is created - Creates 2 individual networks `ingress` and `docker_gwbridge` - `docker_gwbridge` connects with `ingress` to connect to host network - If you create swarm services and do not specify a network, the default is used **- It is recommended that you use separate overlay networks for each service** ```bash docker network ls NETWORK ID NAME DRIVER SCOPE 495c570066be bridge bridge local 961c6cae9945 docker_gwbridge bridge local ff35ceda3643 host host local trtnl4tqnc3n ingress overlay swarm c8357deec9cb none null local ``` #### User defined overlay - Created by `$ docker network create -d overlay my-overlay` - You can inspect the network by `$docker inspect my-overlay` - You can remove overlay by `$docker network rm my-overlay` #### Using an overlay network for standalone containers This demonstrates DNS Container discovery -- specifically, how to communicate between standalone containers on different Docker daemons using an overlay network. Steps are: * On host1, initialize the node as a swarm (manager) * On host2, join the node to the swarm (worker) * On host1, create an attachable overlay network (test-net) * `docker network create --driver=overlay --attachable test-net` * On host1, run an interactive alpine container (alpine1) on test-net * `docker run -it --name alpine1 --network test-net alpine` * On host2, run an interactive, and detached, alpine container (alpine2) on test-net * `docker run -dit --name alpine2 --network test-net alpine` * Verify on host2 ```bash $ docker network ls NETWORK ID NAME DRIVER SCOPE ... uqsof8phj3ak test-net overlay swarm ``` * On host1, from within a session of alpine1, ping alpine2 ```bash $ ping -c 2 alpine2 PING alpine2 (10.0.0.5): 56 data bytes 64 bytes from 10.0.0.5: seq=0 ttl=64 time=0.600 ms 64 bytes from 10.0.0.5: seq=1 ttl=64 time=0.555 ms --- alpine2 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.555/0.577/0.600 ms # Cleanup $ docker container stop alpine2 $ docker network ls $ docker container rm alpine2 $ docker container rm alpine1 $ docker network rm test-net ``` #### Communicate between a container and a swarm service In this example you start two different alpine containers on the same Docker host and do some tests to understand how they communicate with each other. Steps: - List current networks - The default `bridge` network is listed, along with `host` and `none` - Later are not fully fledged networks, but are used to start a container connected to Docker daemon of host ```bash docker network ls NETWORK ID NAME DRIVER SCOPE 17e324f45964 bridge bridge local 6ed54d316334 host host local 7092879f2cc8 none null local ``` - Start 2 containers with detached, interactive with `ash` (alpine shell, default for alpine) ```bash $ docker run -dit --name alpine1 alpine ash $ docker run -dit --name alpine2 alpine ash ``` - Inspect bridge network ```bash docker network inspect bridge [ { "Name": "bridge", "Id": "17e324f459648a9baaea32b248d3884da102dde19396c25b30ec800068ce6b10", "Created": "2017-06-22T20:27:43.826654485Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { # <- Gateway Info "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16", "Gateway": "172.17.0.1" } ] }, "Internal": false, "Attachable": false, "Containers": { # <- Container Info "602dbf1edc81813304b6cf0a647e65333dc6fe6ee6ed572dc0f686a3307c6a2c": { "Name": "alpine2", "EndpointID": "03b6aafb7ca4d7e531e292901b43719c0e34cc7eef565b38a6bf84acf50f38cd", "MacAddress": "02:42:ac:11:00:03", "IPv4Address": "172.17.0.3/16", "IPv6Address": "" }, "da33b7aa74b0bf3bda3ebd502d404320ca112a268aafe05b4851d1e3312ed168": { "Name": "alpine1", "EndpointID": "46c044a645d6afc42ddd7857d19e9dcfb89ad790afb5c239a35ac0af5e8a5bc5", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" } }, "Options": { "com.docker.network.bridge.default_bridge": "true", "com.docker.network.bridge.enable_icc": "true", "com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", "com.docker.network.bridge.name": "docker0", "com.docker.network.driver.mtu": "1500" }, "Labels": {} } ] ``` - Now you can ping alpine1 froom alpine2 ```bash # ping -c 2 172.17.0.3 PING 172.17.0.3 (172.17.0.3): 56 data bytes 64 bytes from 172.17.0.3: seq=0 ttl=64 time=0.086 ms 64 bytes from 172.17.0.3: seq=1 ttl=64 time=0.094 ms --- 172.17.0.3 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.086/0.090/0.094 ms ``` ## Security 4 areas to consider - INtrinsic security of the kernel its support for namespaces and cgroups - Attach seurface of docker daemon - loopholes in container config - hardening security features of kernel #### kernel namespaces - docker containers are very similar to LXC containers - When you start a docker container, it creates a set of namespaces and control groups for the container - Namespaces provide the first and most straight forward form of isolation - Each network container also gets its own network stack - If set up iproperly, containers can interact with the host and other containers - They can ping each other, send and receive UDP packets and create TCP connections - From an architecture point of view, all containers are sitting on brisge interfaces, that means they are just like physical interfaces sitting on a network switch #### Control Groups - Implement resource accounting and limiting - Manage disk IO, CPU and memory - Essential to fend off DDoS attacks #### docker daemon attack surface - Working with docker Daemon requires root previleges - Unless you use rootless mode - HENCEonly truested users should get access to Docker - Docker lets you create a connection between the host and the container anywhere on the container, hence you shoudl make sure that you create the shared location in a non sensitive location - Which is why Docker switched to a unix socket instead of TCP socket for REST API endpoint ## Enabling GPU with Docker Compose - compose Services can define GPU device reservations if the docker host containes such devices and the daemon is set accordingly ## Practical design patterns https://www.youtube.com/watch?v=PpyPa92r44s **AGENDA: Take a physical application and break it down into simple components and build it back from the ground ** History 1. Physical Machines - Scenario where networking was simple - HA was guaranteed by hardware redundancy ![](https://i.imgur.com/BzmCG3q.png) 2. Virtual Machines - Scenario whre networking became more complex as more VMs were introduced - now you needed logical grouping that is isolated from physical hardware ![](https://i.imgur.com/1PwQL8v.png) 3. Containers - Container networks - 7 types as we know ![](https://i.imgur.com/fnWc7GC.png) - Use **null networks for IoT** -> No need to send packets outside but are reachable ### Host and Bridge Single host only - Host gets ip from the host network - Shares host namespace for networking - Bridge - Default in docker - All containers get an internal IP address - All containers inside bridge can talkto each other - Outside cannot talk to containers unless **Ports are shared** - Example: `docker run -p <host_post>:<container_port>` ![](https://i.imgur.com/ENLUIup.png) ### Overlay Used only on swarm mode - can be created by CLI by chooing swarm mode - Or by creating stack - Makes use of VXLAN - Every container that is part of an overlay network can speak to one another - By default overlay is encrypted 2 IP address given to each container - IP on overlay network -> 10.0.0.0/24 - IP on docker engine -> 172.18.0.0/24 ![](https://i.imgur.com/zJmlBb5.png) ### MAC VLAN - hardware mac address printed into the card ![](https://i.imgur.com/BsgpsrZ.png)