Những điều cơ bản về Docker

Các hình ảnh và nội dung trình bày được lấy từ khoá học https://kodekloud.com/courses/docker-for-the-absolute-beginner
Docker là gì? Tại sao phải sử dụng docker?
Đặt vấn đề
- Mỗi ứng dụng, service sẽ có yêu cầu liên quan đến các package, dependency khác nhau. Nếu cài đặt nhiều ứng dụng, service trên một host sẽ dẫn đến việc khó quản lý.
- Mỗi khi muốn deploy lại một ứng dụng, service khác trên host khác phải lặp lại các công việc setup môi trường giống nhau, tốn nhiều thời gian.
- Vấn đề khác là sự khác nhau giữa môi trường Dev/Test/Prod, dẫn đến các bug có thể xảy ra ⇒ tốn thời gian fix lại cho phù hợp với môi trường.

Vấn đề mà Docker giải quyết
- Isolation: chạy ứng dụng, service trong container với set up môi trường riêng tách biệt.
- Easy setting: việc set up môi trường cho ứng dụng, service trở nên dễ dàng chỉ bằng một command, giúp tiết kiệm thời gian.
- Easy shipping: ứng dụng, service có thể chạy dựa trên môi trường khác nhau như CentOS, Ubuntu, Windows, RedHat,… nhưng miễn là có cùng kernel (Linux kernel như CentOS, Ubuntu,…) thì các ứng dụng, service được gói trong container có thể hoạt động bình thường.

Docker là gì?
Docker là một chương trình command-line, lưu ý rằng bản thân nó không tự tạo ra container. Khái niệm container đã có tại hệ điều hành UNIX từ lâu cũng với mục đích nhằm cách ly (isolation) các ứng dụng, service nhưng do việc cấu hình container cần có sự hiểu biết sâu về cách thành phần lõi của OS, liên quan đến kernel nên dễ dẫn đến sai. Do đó, Docker ra đời, giúp đơn giản hoá công việc cấu hình container.
Docker is a command-line program, a background daemon, and a set of remote services that take a logistical approach to solving common software problems and simplifying your experience installing, running, publishing, and removing software. It accomplishes this using a UNIX technology called containers.
Tuy nhiên, vì khái niệm container chỉ tồn tại trên hệ điều hành UNIX, vậy còn đối với các hệ điều hành khác như Windows, MAC, việc sử dụng docker sẽ như thế nào? Giải pháp chính là sử dụng máy ảo Linux làm docker host. Có 2 option cài đặt Docker cho Windows và MAC là Docker Toolbox và Docker Desktop.
Sự khác nhau giữa Docker và Virtual Machine:
- Virtual Machine: có cùng ý tưởng về tính chất cách ly (isolation) nhưng với mỗi máy ảo, cần cài đặt một OS, sau đó cài đặt các ứng dụng, service cần thiết. Do đó, chiếm dụng phần cứng nhiều hơn.
- Docker: khác với máy ảo, docker giao tiếp với kernel và hỗ trợ tạo nhiều container trên một host mà không cần cài đặt nguyên một OS dẫn đến việc chiếm dụng phần cứng ít hơn so với máy ảo.

Docker image vs Docker container
Docker Image giống như một template chứa các file cần thiết cho việc chạy một process.
Docker Container chỉ chạy khi có một process chạy trong nó, nếu như process dừng thì container cũng dừng.
Sử dụng Image có thể tạo ra nhiều container chạy độc lập với nhau.

Docker command cơ bản
Run - Chạy một container
#Cơ bản
docker run <image_name>
#Chạy image với tag (tham khảo các tag có sẵn với một image trên Docker Hub)
docker run <image_name>:<tag>
#Chạy docker mode attack cho phép nhập input
docker run –it <image_name>
- Port Mapping: theo mặc định, container sẽ được gắn vào briged network ảo có subnet là 172.17.0.0/24 trong docker host, tính chất của lớp mạng này là sẽ chỉ truy cập internal được. Do đó, trong trường hợp muốn public một web service ra bên ngoài và sử dụng IP của docker host thay vì IP internal, cần sử dụng port mapping, tham chiếu port container với port của host. Lưu ý rằng không sử dụng cùng một host port cho nhiều mapping container port.
docker run –p <host_port>:<container_port> <image_name>

- Volume Mapping: theo mặc định, tất cả dữ liệu của container sẽ biến mất nếu nó bị xoá. Để có thể đảm bảo dữ liệu không bị mất đi, docker sử dụng volume mapping, tham chiếu dữ liệu trong container đến một vị trí bộ nhớ cụ thể trong host.
docker run –v /opt/datadir:/var/lib/mysql mysql
PS - Liệt kê các container
#Các container đang chạy
docker ps
#Tất cả các container bao gồm đã dừng và đang chạy
docker ps -a
STOP - Dừng một container
docker stop <container_name_list>
RM - Xoá một container
<container_id> không nhất thiết là toàn bộ dãy string mà chỉ cần vài ký tự đầu nhưng là unique trong danh sách Container ID hiện tại.
docker rm <container_id_list>
RM - Xoá một image
#Liệt kê danh sách images đã pull
docker images
#Xoá images
docker rmi <image_list>
Pull - Tải xuống image
#Liệt kê danh sách images đã pull
docker pull nginx
Mở rộng command
docker run ubuntu sleep 5
Exec - chạy một command trong container
docker exec distracted_mcclintock cat /etc/hosts
Mode attack và detach
#Mode detach tạo một process chạy background
docker run –d <image_name>
#Mode attack gắn process vào terminal hiện tại
docker attach <container_id>
Inpect Container
docker inspect <container_id>
Container Logs
docker logs <container_id>
Docker enviroment variables
docker run-e APP_COLOR=blue -e <variable>=<value> simple-webapp-color
Docker Image
Cách tạo docker image
Docker Image sẽ đi cùng với file Dockerfile.
Dockerfile hiểu đơn giản là một file dạng script chứa tất cả các bước để setup, cài đặt và chạy ứng dụng hoặc service, concept tương tự việc viết script để chạy automation, tuy nhiên các công việc này thực hiện trong container riêng biệt và độc lập.

Với mỗi command dạng INSTRUCTION ARGUMENT trong Docker file sẽ tạo ra một layer, và khi kết hợp nhiều command lại, nó tạo thành một cấu trúc nhiều layer. Mỗi layer sẽ chiếm dung lượng bộ nhớ khác nhau, nhưng layer sau sẽ chỉ lưu dữ liệu mà layer trước chưa có, việc này giúp tối ưu hoá bộ nhớ lưu trữ.
Docker cũng hỗ trợ tính năng cache dữ liệu, đảm bảo khi dữ liệu của một layer build thất bại, dữ liệu các layer trước vẫn được giữ nguyên trong bộ nhớ cache, và khi build lại image, không cần phải tốn thời gian pull dữ liệu các layer này.

CMD và ENTRYPOINT
CMD và ENTRYPOINT đều là instruction cho phép chạy command/process ở thời điểm container bắt đầu chạy.
Lưu ý rằng cách instruction khác như RUN hay COPY trong ví dụ trên cũng đều thực hiện command nhưng là trong quá trình build image, sau khi build image và tạo container, các lệnh này sẽ không được thực thi nữa, thay vào đó là CMD và ENTRYPOINT.
CMD: các command và variable được định nghĩa có thể bị ghi đè trong quá trình chạy container, thường được sử dụng để định nghĩa mặc định. Trong ví dụ sau:
- Nếu chạy
docker run myimage, command thực thi sẽ làecho “Hello World” - Nếu chạy
docker run myimage ls, command thực thi sẽ làls
FROM ubuntu
CMD ["echo", "Hello World"]
ENTRYPOINT: các command và variable được định nghĩa không thể bị ghi đè trong quá trình chạy container.Trong ví dụ sau:
- Nếu chạy
docker run Hello, command thực thi sẽ làecho “Hello”
FROM ubuntu
ENTRYPOINT ["echo"]
Kết hợp việc sử dụng CMD và ENTRYPOINT:
FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["Hello, World!"]
Lưu ý, có hai cách để định nghĩa command cho CMD và ENTRYPOINT:

Docker Networking
Default networks
Có 3 dạng network chính trong docker host:
- Bridge: lớp mạng ảo internal 172.17.0.0/24 có default gateway là 172.17.0.1
- None: không có card mạng nào
- Host: sử dụng host:port của docker host.

User-defined networks
Tạo network mới:
docker network create –-driver bridge -–subnet 182.18.0.0/16 custom-isolated-network
Theo mặc định, các brigde network là cách ly hoàn toàn ( tức là không kết nối với nhau).
DNS server
Docker Host có một DNS server mặc định có IP 127.0.0.11 để diễn giải IP của các container. Việc sử dụng IP để kết nối đến các service chạy trong container khá bất tiện vì IP có thể thay đổi bất cứ lúc nào, do đó với DNS server, trong cùng một mạng, các container có thể kết nối với nhau thông qua container name thay vì IP.
Theo mặc định, tất cả user-defined bridge network sử dụng một DNS server duy nhất có IP là 127.0.0.11

Docker Storage
Cấu trúc thư mục của Docker
/var/lib/docker
|__aufs
|__containers
|__image
|__volumes
Storage driver và Layered architecture
Như đã trình bày ở phần [[#Cách tạo docker image]], Docker Image có kiến trúc dạng layer, tuy nhiên tuỳ thuộc vào cấu hình storage driver sẽ có cách sử dụng và các tính năng lưu trữ dữ liệu khác nhau giữa các layer khác nhau.
Các loại storage driver:
| Type | Mô tả |
|---|---|
| AUFS | • Kém hiệu quả với nhiều layer, hỗ trợ bởi các phiên bản Docker cũ |
| ZFS | • Cung cấp hiệu suất cao và các tính năng nâng cao như kiểm tra tính toàn vẹn dữ liệu, snapshot và replication. • Yêu cầu ZFS phải được cài đặt và cấu hình trên hệ thống máy chủ. |
| BTRFS | • Có thêm tính năng snapshot và subvolume • Khó quản lý |
| Device Mapper | • Sử dụng LVM (Logical Volume Manager) • Có thể chạy nhiều container nhưng không hiệu quả bằng Overlay2 và BTRFS |
| VFS | • Sử dụng cho testing và debugging • Không sử dụng trong môi trường production |
| Overlay2 | • Driver mặc định cho hầu hết các hệ điều hành Linux • Hiệu suất tốt và tối ưu hoá được việc sử dụng bộ nhớ • Cho phép chia phân vùng bộ nhớ read-only và write khác nhau • Cho phép sử dụng chung bộ nhớ cho nhiều layer |
Sau đây, sẽ trình bày cách hoạt động cơ bản của Overlay2 Driver:
Layered architecture: Overlay2 cho phép sử dụng lại dữ liệu của các layer được build sẵn trong bộ nhớ cache giữa các image. Ví dụ, Dockerfile dưới đây sẽ build image my-custom-app và có 5 layers, Dockerfile2 sẽ build image my-custom-app có 5 layers nhưng có 3 layers giống với my-custom-app. Nếu như ta build my-custom-app trước, sau đó build my-custom-app-2, thì thay vì Docker pull thêm dữ liệu của 3 layer giống nhau trước đó, nó sẽ sử dụng lại dữ liệu trong bộ nhớ cache. Từ đó, tiết kiệm được dung lượng bộ nhớ.

Mặc định các layer sau khi build image sẽ ở mode read-only. Và khi container được tạo từ image, Docker sẽ thêm một layer bên trên ở mode read-write, layer này sẽ lưu tất cả các dữ liệu phát sinh trong quá trình chạy của container. Nhưng nếu container bị xoá thì các dữ liệu ở layer read-write cũng sẽ biến mất.

Để ngăn các dữ liệu phát sinh trong quá trình chạy container không bị mất đi, cần tạo volume và mount nó vào container. Có hai kiểu mount chính là BIND và VOLUME.
- VOLUME: mặc định, sử dụng command
docker volume create <volume_name>sẽ tự động tạo một thư mục<volume_name>trong folder volumes của Docker. Thư mục này sau khi được mount vào container sẽ lưu trữ dữ liệu của Container.- Sử dụng cho dữ liệu database, application state, các file cấu hình cần được giữ lại.
docker volume create data_volume
docker run –-mount source=data_volume,target=/var/lib/mysql mysql
- BIND: thay vì sử dụng thư mục trong thư mục gốc của Docker, ta sử dụng folder nằm trên bộ nhớ khác.
- Sử dụng cho môi trường với thay đổi ở host sẽ ảnh hưởng đến container hay các file cấu hình bên ngoài Docker.
docker run –-mount type=bind,source=/data/mysql,target=/var/lib/mysql mysql
- Ngoài ra còn có TMPFS, tính năng này hữu ích trong những trường hợp cần truy cập nhanh vào các tệp tạm thời nhưng dữ liệu không cần phải tồn tại lâu (session data, cache)
Docker Compose
Docker Compose có 3 version:
- Version 1: đơn giản, không hỗ trợ volumes, networks và đa môi trường (đã loại bỏ).
- Version 2: hỗ trợ volumes, networks, cho phép tạo user-defined network và giao tiếp giữa các container, cho phép persistent storage.
- Version 3: giới thiệu Docker Swarm và môi trường cluster, hỗ trợ single-host và multi-host và các option scaling nâng cao hơn.
Trong ví dụ dưới đây, một ứng dụng voting đơn giản được xây dựng tại Github.
voting-appđược viết bằng Python cho phép người dùng đưa ra lựa chọn giữa CAT và DOGredis(Remote Dictionary Server) là open-source, in-memory data structure, nó lấy dữ liệu từ voting-app lưu trong memory thay vì disk, giúp tăng tốc độ xử lý.workernhận dữ liệu từ redis và thực hiện việc update dbdblà database lưu trữ dữ liệu voting, cụ thể với mỗi vote thì sẽ tăng giá trị cho CAT hoặc DOG.result-appđương viết bằng NodeJS, đọc thông tin từ db và hiển thị trên web interface về kết quả voting.
Các container trên được deploy trên một Docker Host duy nhất với hai network là front-end và back-end, front-end là nơi giao tiếp giữa user và web interface, back-end là nơi giao tiếp giữa các thành phần internal của ứng dụng.

Docker Registry
Ta có thể tạo các docker image bằng Dockerfile, sau đó build image dưới local và chạy container hoặc sử dụng image có sẵn trên Docker Hub. Vậy quá trình khi một image không có sẵn ở local và phải tiến hành pull từ Docker Hub diễn ra như thế nào? Việc này được thực hiện thông qua API server của Docker Hub, có thể giống như lệnh curl và URI path được gọi là Docker Registry.
Docker Hub chỉ là party lưu trữ image mặc định, chúng ta có thể pull image từ các party khác như Google, AWS,…

Sử dụng Private Registry trên Docker Hub
docker login private-registry.io
docker run private-registry.io/apps/internal-app
Deploy Private Registry
#Chạy container xây dựng một registry server local
docker run -d -p 5000:5000 --name registry registry:2
#Tag image muốn push
docker image tag my-image localhost:5000/my-image
#Push Image lên Registry Server
docker push localhost:5000/my-image
#Pull Image từ Registry Server
docker pull localhost:5000/my-image
Docker Engine
Khi một host có cài đặt Docker thì gọi là Docker Host hay Docker Engine, các thành phần của Docker Engine bao gồm:
- Docker Deamon: một process chạy liên tục dưới background, trong Linux gọi là deamon.
- REST API: có thể coi như một interface giao tiếp giữa Docker CLI và Docker deamon.
- Docker CLI: Docker command, có thể chạy remote được.

Containerization
Về mặt concept, container có tính chất cô lập, Docker sử dụng các Namespace như Process ID, Unix Timesharing, Network, Mount, InterProcess để định nghĩa tính chất này. Network, Mount đã được thể hiện trong các phần trước.

Process PID: Mỗi process trong hệ thống đều có Process ID (PID) riêng. Trong Docker, mỗi container chạy các process bên trong một không gian tên PID riêng biệt, nghĩa là PID trong container có thể khác với PID trên Docker Host. Các container Docker chạy dưới dạng các process bị cô lập, nhưng các tiến trình đó vẫn có các PID tương ứng trên Docker Host.

Cgroups (Control Groups) là một tính năng cơ bản trong Linux mà Docker và các công nghệ container khác sử dụng để quản lý và giới hạn tài nguyên hệ thống (như CPU, bộ nhớ, I/O đĩa và mạng) được phân bổ cho các process. Cgroups cho phép Docker cô lập và phân bổ tài nguyên cho các container theo cách được kiểm soát.

Container Orchestration
Đặt vấn đề
- Để đảm bảo tính sẵn có trong môi trường production, ta có thể sẽ phải deploy nhiều container chạy một service, cũng như có cơ chế cho phép phát hiện khi một container down thì sẽ sử dụng container nào tiếp theo hoặc cơ chế cân bằng tải phân bổ request đến container nào.
- Bên cạnh đó, có thể có hàng ngàn container khác nhau, cần có phương pháp để theo dõi hiện trạng của container để xử lý kịp thời.
- Việc deploy nhiều container chạy một service sẽ tốn thời gian và công sức, do phải vào từng Docker Host để set up container.
Các giải pháp Container Orchestration được sử dụng để giải quyết vấn đề này. Về mặt concept, các giải pháp này đều xoay quanh môi trường cluster, với một node là manager, các node còn lại là worker, nhận lệnh từ manager để deploy container một cách tự động.

Docker Swarm
Docker Swarm yêu cầu setup thủ công Manager Node để join các Docker Host vào Cluster.


Kubernetes (k8s)
Có thêm nhiều option scaling số lượng container dựa vào hiện trạng truy cập, lượng traffic, có thể deploy container với số lượng rất lớn.
