Docker

Tổng quan về Docker

Docker là gì ?

Docker là một nền tảng cung cấp cho lập trình viên cách building, deploying và running ứng dụng một cách dễ dàng bằng cách đóng gói phần mềm vào các đơn vị tiêu chuẩn hóa được gọi là container (sẽ được tìm hiểu ở phần sau). Nó có mọi thứ mà phần mềm cần để chạy, như là thư viện, tool, code, ...

Một cách hiểu khác như sau: Docker chính là một platform ở tầng OS, nó có thể tinh chỉnh được và nó được phục vụ cho việc chạy ảo hóa các dịch vụ và ứng dụng một cách nhanh chóng hơn.

Khi nào ta cần dùng Docker?

  • Phát triển các ứng dụng, dịch vụ yêu cầu cài đặt quá nhiều thứ liên quan, hoặc có version không tương thích với máy chủ hiện tại.
  • Khi có nhu cầu thu nhỏ, mở rộng linh hoạt để đáp ứng nhanh. VD như bật/tắt nhanh các container để hỗ trợ tăng tải cho hệ thống của bạn.
  • Rất phù hợp với Microservices. Mình chắc chắn rằng bạn sẽ không muốn chạy từng service nhỏ lên và cấu hình chúng bằng tay.
  • Tăng tốc, hỗ trợ CI/CD tốt hơn. Vì lúc này automation server chỉ cần quan tâm Docker thay vì lại phải cài đặt đủ thứ vào.
  • Dễ thay đổi, di chuyển hơn vì mọi thứ ở trong container. Bản thân Docker vẫn có version control cho các Image, từ đó dễ dàng up/down version ứng dụng hơn.
  • An toàn hơn vì mỗi container là một môi trường hoàn toàn độc lập với bên ngoài.

Setup Docker.

Cài đặt Docker trên Windows 10

Tải file cài đặt doker cho windows tại https://www.docker.com/get-started

Sau khi đã tải về, các bạn tiến hành cài đặt thông qua file Docker Desktop Installer.exe vừa tải về. Như mình đã giới thiệu, để chạy được doker cần bật tính năng Hyper-V của windows và trong quá trình cài đặt docker nếu windows chưa được bật Hyper-V, sẽ có một checkbox hỏi xem có muốn bật Hyper-V luôn không, thì mình nên lựa chọn checkbox để bật Hyper-V luôn.

Sau khi cài đặt hoàn tất, cần khởi động lại máy để có thể chạy docker, sau khi khởi động chúng ta có thể thấy biểu tượng Docker ở Tray Icon

Ok vậy là bạn cài xong rồi, giờ chúng ta chỉ cần mở cửa sổ console để kiểm tra xem Docker chạy chưa nhé. Để kiểm tra thông tin chi tiết: chúng ta thực hiện command.

docker info

Để kiểm tra phiên bản Docker, chúng ta dùng command

Khi quá trình khởi chạy hoàn tất, chúng ta có một màn hình giới thiệu khi mở Docker Desktop.

Cài đặt Docker trên Ubuntu 20.4

Đầu tiên bạn phải update và upgrade hệ thống apt của mình trước bằng 2 câu lệnh sau nhé

sudo apt update
		sudo apt upgrade

Tiếp theo dùng lệnh sau để download và cài đặt docker

sudo apt install docker.io

Sau khi cài đặt xong, các bạn có thể kiểm tra thông tin docker bằng lệnh

docker info

và thông tin phiên bản bằng lệnh

docker --version

Các bạn có thể tham khảo thêm cách cài docker cho các phiên bản Linux khác tại https://docs.docker.com/engine/

Tài nguyên tham khảo khi học Docker

Kiến trúc ảo hóa và cách hoạt động của Docker

Sự khác biệt Docker và VMWare/VirtualBox

Comment

  • Lý do mình để mục này trước là vì đơn giản phỏng vấn intern mình từng bị hỏi câu này. Và vì cũng muốn có cái nhìn rõ ràng hơn về Docker.

Khác biệt

  • Hình dưới đây là cấu trúc khái quát từ trái sang phải của công nghệ ảo hóa của máy ảo (Hypervisor) và Docker

Bạn muốn test trên nhiều hệ điều hành, nhiều phiên bản (distributions) của Linux thì việc đầu tiên mà bạn đa số sẽ làm là tạo một máy ảo sau đó cài đặt hệ điều hành trên máy ảo đó, rồi cài tiếp môi trường, IDE các thứ,... Sau đó đối với các máy ảo khác thì bạn làm tương tự. Lúc này bạn có thể chạy các ứng dụng trên đó dễ dàng. Tuy nhiên, điều đó nảy sinh vấn đề: vì ảo hóa trên VirtualBox/VMWare là ảo hóa vật lý, tức là mô phỏng RAM, bộ nhớ, CPU cả hệ điều hành nên dung lượng tổng khá là ì ạch, và chạy khá là chậm.

Docker thì khác, cũng là công nghệ ảo hóa, nhưng mà là ảo hóa hệ điều hành. Container Engine đóng vai trò là mô phỏng hệ điều hành, và các ứng dụng chạy trên container đó, và dĩ nhiên là chạy trên phần lõi được phân phát, chia sẻ (shared kernel), dùng chung trên hệ điều hành với máy host cho nên là chạy nhanh hơn.

Một lý do để phân biệt Docker và VM đó chính là trên máy ảo phải thiết lập cố định tài nguyên, tức là set RAM bao nhiêu GB, bộ nhớ bao nhiêu, CPU mấy nhân và các thông số khác. Còn Docker thì do chạy trên các containers giống như là một ứng dụng độc lập nên là không cần phải làm điều đó.

Sơ lược về kiến trúc Docker

Docker sử dụng kiến trúc client-server (môn Mạng máy tính bạn sẽ được học và làm đồ án). Docker client giao tiếp với Docker daemon, nằm tại local hoặc tại remote (nhiệm vụ là build, thực thi và phân phối các containers). Cả hai giao tiếp với nhau thông qua REST API, thay vì UNIX socket (socket thuần) hay là giao tiếp card mạng. Có một thành phần là Docker Registry, với vai trò là lưu trữ các images (chi tiết về khái niệm sẽ được giải thích phần sau). Docker Hub là một nơi lưu trữ Docker images cho cộng đồng giống như Github (nơi lưu trữ các repositories). Ta vẫn có thể thiết lập image do mình tự tạo ra là “private", chỉ duy nhất bản thân mình sử dụng được.

Docker CLI

Các keywords cần lưu ý

Image

Là một read-only template dùng để tạo ra các container. Một image có thể được tạo ra dựa trên một image khác với một số tùy chỉnh bổ sung.

Nếu bạn đã quen với hướng đối tượng, chúng ta có thể hiểu images như những class còn cointaner là những object.

Container

Là đơn vị phần mềm cung cấp cơ chế đóng gói ứng dụng, mã nguồn, thiết lập, thư viện… vào một đối tượng duy nhất. Ứng dụng sau khi được đóng gói có thể hoạt động một cách nhanh chóng và hiệu quả trên các môi trường điện toán khác nhau. Từ đó nó có thể tạo ra một môi trường hoàn hảo nơi mà có mọi thứ để chương trình có thể hoạt động được, không chịu sự tác động từ môi trường của hệ thống cũng như không làm ảnh hưởng ngược lại về phía hệ thống chứa nó.

Dockerfile

một file dạng text không có phần đuôi mở rộng, chứa các đặc tả về một trường thực thi phần mềm, cấu trúc cho Docker image. Từ những câu lệnh đó, Docker sẽ build ra Docker image (thường có dung lượng nhỏ từ vài MB đến lớn vài GB).

Volume

Là cơ chế tạo và sử dụng dữ liệu của docker, có nhiệm vụ lưu trữ dữ liệu độc lập với vòng đời của container.

Có 3 trường hợp sử dụng Volume:

  • Giữ lại dữ liệu khi một Container bị xóa.
  • Để chia sẻ dữ liệu giữa máy chủ vật lý và Docker Container.
  • Chia sẻ dữ liệu giữa các Docker Container.

Docker CLI Cheatsheet

Command cơ bản

Command Chức năng
docker --version Kiểm tra phiên bản docker
docker info Thông tin hệ thống docker
docker inspect <name-or-id-of-image-container> Lấy thông tin về image hoặc container
docker help Hiển thị các chức năng của các command trong docker

Command thao tác với Image

Command Chức năng
docker image ls Xem toàn bộ image
docker image build <dockerfile-path> Build image từ docker file
docker pull <image-name>:<tag> Pull image từ Docker Huyb
docker push <image-name>:<tag> Push image lên Docker Hub
docker image prune Xoá những image không tên hoặc chưa bao giờ dùng
docker image rm <image-name-or-id> Xoá image
docker system prune sự kết hợp của docker image prunedocker container prune

Command thao tác với Container

Command Chức năng
docker container run <image> Chạy 1 container từ image
docker start <container> Chạy 1 container đã tồn tại
docker container stop <container> Dừng containter
docker container kill <container> Kill container
docker container rm <container> Xoá container
docker container prune Xoá toàn bộ container đã dừng
docker logs -f <container> Đọc log container
docker system prune sự kết hợp của docker image prunedocker container prune

Command thao tác với Network

Command Chức năng
docker network ls Liệt kê danh sách các mạng hiện có
docker network connect <name-network> <container> Nối container vào mạng

Một số flag thường dùng trong các command

Command Chức năng
--help Hiển thị toàn bộ các flag của command và chức năng của chúng
-name Tên
-d Chạy nền (run in background)
-t cho phép xuất ra terminal
-i cho phép người dùng thao tác bằng command
-f cho phép xuất ra file
-a tất cả
-v volume (tìm hiểu bên dưới)
--last <number> lọc n container gần nhất
-it cho phép thao tác vào và ra (nên dùng)

Chạy container hello-world

Để kiểm tra xem docker đã có thể hoạt động hay chưa, chúng ta sẽ thử tạo 1 container đơn giản nhất bằng lệnh

docker run -ls hello-world

Lệnh này sẽ yêu cầu Docker chạy image có tên là hello-world, còn tag -it là để gắn container vào process hiện tại của terminal, khi đó ta có thể dùng Ctrl+ C để tắt container.

Sau khi chạy lệnh trên, ta ngay lập tức sẽ nhận được kết quả là

Unable to find image 'hello-world:latest' locally

Điều này có nghĩa là hiện tại trên máy của chúng ta không có image "hello-world" vì đây là lần chạy đầu tiên, do đó, Docker sẽ phải kéo image "hello-world" về máy từ Docker hub. Và sau khi kéo image về thành công, ta sẽ nhận được thông báo như sau:

Status: Downloaded newer image for hello-world:latest

Và sau đó docker sẽ thực hiện chạy image "hello-world" vừa kéo về được. Kết quả ta có thể thấy bên dưới

$ docker run -ls hello-world
		Unable to find image 'hello-world:latest' locally
		latest: Pulling from library/hello-world
		2db29710123e: Pull complete
		Digest: sha256:37a0b92b08d4919615c3ee023f7ddb068d12b8387475d64c622ac30f45c29c51
		Status: Downloaded newer image for hello-world:latest
		
		Hello from Docker!
		This message shows that your installation appears to be working correctly.
		
		To generate this message, Docker took the following steps:
		 1. The Docker client contacted the Docker daemon.
		 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
			(amd64)
		 3. The Docker daemon created a new container from that image which runs the
			executable that produces the output you are currently reading.
		 4. The Docker daemon streamed that output to the Docker client, which sent it
			to your terminal.
		
		To try something more ambitious, you can run an Ubuntu container with:
		 $ docker run -it ubuntu bash
		
		Share images, automate workflows, and more with a free Docker ID:
		 https://hub.docker.com/
		
		For more examples and ideas, visit:
		 https://docs.docker.com/get-started/

Chi tiết Docker CLI

Download image

Bước đầu, để có image nào đó bạn tải về từ https://hub.docker.com/search?q=&type=image, tại đó có đủ các loại phù hợp với công việc của bạn!

Và các bạn lưu ý những image nào có dòng Docker Official Images thì đó là những image chính thức được Docker cung cấp, đảm bảo hạn chế rủi ro mã độc nhé

Để download image có sẵn từ DockerHub, ta dùng

docker pull [OPTIONS] NAME[:TAG|@DIGEST]

Ví dụ về 1 lệnh pull đơn giản

$ docker pull debian
		Using default tag: latest
		latest: Pulling from library/debian
		bb7d5a84853b: Pull complete
		Digest: sha256:4d6ab716de467aad58e91b1b720f0badd7478847ec7a18f66027d0f8a329a43c
		Status: Downloaded newer image for debian:latest
		docker.io/library/debian:latest

Ở ví dụ trên, bạn có thể thấy tag được dùng là latest .Đây là tag mặc định của docker khi mà không có đầu vào cho tag. Ta có thể pull image debian với tag khác là jessie bằng lệnh bên dưới

$ docker pull debian:jessie
		jessie: Pulling from library/debian
		b82b9923b08d: Pull complete
		Digest: sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
		Status: Downloaded newer image for debian:jessie
		docker.io/library/debian:jessie

Tuy vậy, tag là thứ có thể được sửa đổi dễ dàng. Chính vì vậy, mỗi phiên bản của image đều có Digest là 1 mã độc nhất cho mỗi phiên bản. Ta có thể tải image debian:jessie trên bằng lệnh sau sẽ cho kết quả tương tự

$ docker pull debian@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
		docker.io/library/debian@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0: Pulling from library/debian
		b82b9923b08d: Pull complete
		Digest: sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
		Status: Downloaded newer image for debian@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0
		docker.io/library/debian@sha256:32ad5050caffb2c7e969dac873bce2c370015c2256ff984b70c1c08b3a2816a0

Bên dưới là những options có thể dùng được trong câu lệnh này

Name Chức năng
--all-tags hoặc -a Tải xuống toàn bộ tag của image từ kho
--disable-content-trust Bỏ qua xác minh image
--platform Bổ sung từ API 1.32 trở lên Đặt platform nếu máy chủ là máy multi-platform
-quiet , -q Giảm những dòng output dài dòng, chỉ xuất hiện output khi đã download xong

Lệnh xem các image và container

Để xem danh sách các image đang có trên máy, ta dùng lệnh sau:

docker images [OPTIONS] [NAME[:TAG]] Để xem danh sách các container đang chạy trên máy, ta dùng:

docker ps [OPTIONS]

Hai câu lệnh trên có phần Options tương đối giống nhau, được liệt kê ở bảng bên dưới

Name Chức năng
--all hoặc -a Hiển thị toàn bộ image (mặc định image trung gian bị ẩn) hoặc toàn bộ container (mặc định chỉ hiển thị container đang chạy)
--filter hoặc -f Lọc kết quả dựa theo điều kiện cho sẵn, đọc thêm về nó trên trang chủ cho imagecho container
-quiet , -q Chỉ hiện thị image ID hoặc container ID
--format Xem thêm cho imagecho container
--no-trunc Hiển thị ID ở dạng đầy đủ
--digests (Chỉ có cho images) hiện digests
--size hoặc-s (Chỉ có cho container) hiển thị file size
--last <num> hoặc -n <num> (Chỉ có cho container) chỉ hiển thị num conterner vừa mới được tạo
--latest hoặc -l (Chỉ có cho container) tương tự flag -n 1

Xóa các containers và images

Để xoá 1 image, ta dùng lệnh docker rmi [OPTIONS] IMAGE [IMAGE...]

Còn với container, câu lệnh có cú pháp như sau docker rm [OPTIONS] CONTAINER [CONTAINER...]

Các tham số đó là:

  • [OPTIONS] các thiết lập khi chạy command, bạn hãy xem bảng bên dưới
  • IMAGE hoặc CONTAINER tên image/container hoặc ID của chúng. Mỗi command có thể xoá cùng lúc nhiều image hoặc nhiều container

Chúng cũng có những options tương tự nhau

Name Chức năng
--force hoặc -f Buộc hệ thống xoá image hoặc container dù có lỗi xảy ra
--no-prune (Chỉ dành cho image) không xoá những image mà image bị xoá phụ thuộc (không xoá image parent)
--link hoặc -l (Chỉ dành cho container) Xoá liên kết mạng được chỉ định (tìm hiểu thêm tại phần Docker Networking)
--volumes hoặc -v (Chỉ dành cho container) Xoá toàn bộ hoặc các volume được chỉ định có liên kết với container

Chạy container và dừng tiến trình chạy container.

Để tạo và chạy một container theo cú pháp:

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

Các tham số đó là:

  • [OPTIONS] các thiết lập khi tạo container, có rất nhiều thiết lập tùy mục đích tạo container, bảng bên dưới sẽ chỉ liệt kê các options thường dùng. Nếu muốn tìm hiểu sâu hơn, các bạn có thể dùng lênh docker run help hoặc tìm hiểu tại đây
  • IMAGE tên image hoặc ID của image từ nó sinh ra container.
  • [COMMAND] [ARG...] lệnh và tham số khi container chạy.

Các options thường dùng

Name Chức năng
-i Duy trì mở stdin để nhập lệnh.
-t Cho phép kết nối với terminal để tương tác
-it Là sự kết hợp của -i-t, và đây là option được khuyên dùng khi tạo container mới mà bạn cần truy cập vào terminal của nó
-d Chuyển container sang chế độ chạy trong background ngay khi vừa tạo
-v <host-path>:<containner-path> Ánh xạ một thự mục máy host vào một thư mục container, chia sẻ dữ liệu
-p <host-port>:<container-port> Ánh xạ 1 cổng từ máy host vào 1 cổng của container

Khi bạn đang ở trong terminal của 1 container, bạn có thể gõ Ctrl + C để thoát và dừng container đó, hoặc bạn gõ Ctrl + P, Ctrl + Q để thoát khỏi container và đứa nó vào chế độ chạy ngầm

Các container ở chế độ chạy ngầm có thể được kiểm tra bằng lệnh docker ps

Để quay trở lại 1 terminal của 1 container chạy ngầm, chúng ta dùng lệnh

docker container attach <container>

Vậy nếu chúng ta muốn chạy một container có sẵn nhưng đã dừng thì sao? Ta sẽ dùng lệnh

docker container start -i <container>

Dockerfile

Build Dockerfile

Mục tiêu đạt được trong phần này

  • Hiểu được cách hoạt động của Dockerfile
  • Đọc hiểu cách setup trên README.md của một vài thư viện trên Github hoặc ở trên các Package Manager khác.
  • Vai trò lệnh FROM, WORKDIR, RUN, CMD
  • Cách build một image bằng lệnh docker build
  • Cách copy một file nằm trong container sang thư mục làm việc (local).

Giới thiệu sơ lược về Dockerfile

Dockerfile là một file script hướng dẫn Docker cách build một image, bằng cách chỉ rõ ra sẽ bao gồm những lệnh nào. Theo cách hiểu ví von của Jeff Delaney (tác giả của video Docker in 100 Seconds) thì Dockerfile giống như DNA (ADN) - thành phần quan trọng để hình thành nên cơ thể con người.

Trong bài hướng dẫn này, ta sẽ tự build một image dùng để download video Youtube bất kì. Khi đó bạn chỉ cần gọi container và link video thì việc còn lại sẽ tự động hóa việc tải video của bạn.

Để làm quen với Dockerfile thì ta sẽ cho phép download một video có sẵn link.

Mở đầu

Để có thể download được video trên Youtube, ta cần sử dụng command youtube-dl trên Github: https://github.com/ytdl-org/youtube-dl

Ở phần Setup các bạn lưu ý duy nhất mục To install it right away for all UNIX users (Linux, macOS, etc.)... Tuy nhiên command này yêu cầu người dùng phải có Python phiên bản 2.6, 2.7, or 3.2+ và sử dụng curl. (nằm ở file README.md)

Để kiểm tra xem Ubuntu có sẵn curl và python hay không thì ta thực hiện bước dưới đây:

(base) potluck:~$ docker container run -it ubuntu
		root@ade9d55eced1:/# python
		bash: python: command not found
		root@ade9d55eced1:/# curl
		bash: curl: command not found

Điều này chứng tỏ Ubuntu không có sẵn curl và python. Lúc này ta sẽ dùng lệnh để cài đặt lần lượt như sau (lưu ý bỏ sudo đi):

#Curl install
		apt-get update && apt-get install -y curl
		#Python install
		apt-get install -y python
		#Install youtube-dl
		curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
		#Change mod that can execute command.
		chmod a+rx /usr/local/bin/youtube-dl

Việc sử dụng câu lệnh download video Youtube khá đơn giản: youtube-dl <tên youtube URL>. Tuy nhiên để gọi câu lệnh đó, ta cần chỉ ra thư mục dẫn tới youtube-dl

/usr/local/bin/youtube-dl https://www.youtube.com/watch?v=dQw4w9WgXcQ

Viết Dockerfile để tự động hóa việc download video trên Youtube

Lần này ta sẽ tự động hóa các lệnh trên bằng cách việc viết Dockerfile và build image dựa trên chỉ dẫn trên.

Lúc này ta sẽ xây dựng một image có thể hoạt động giống như image hello-world. Chúng ta cần phải lưu ý rằng image không thể chỉnh sửa được (immutable), thay vào đó ta phải dựa vào image gốc đó mà ta thêm vào các layers, các tập lệnh mà người dùng mong muốn để hình thành một image mới. Như vậy ta sẽ phải cần image gốc.

Lựa chọn môi trường phù hợp khi thực hiện các tập lệnh trên là một điều quan trọng. Khi vào trang Docker Hub để khám phá (chọn mục Operating System), thì vô vàn sự lựa chọn.

https://hub.docker.com/search?type=image&image_filter=official&category=os

Trong bài hướng dẫn này, ta sẽ sử dụng Ubuntu với tag là latest, vì Ubuntu có khá nhiều tools hỗ trợ cho đại đa số công việc.

Có một lưu ý quan trọng là bạn cần phải cẩn thận khi chọn tag version, nhất là bạn làm việc với Python thì vấn đề tương thích package trong phiên bản đó.

Ok, bắt tay vào làm thôi nào!!!

FROM ubuntu:latest
		
		WORKDIR /learn_docker
		
		RUN apt-get update && apt-get install -y curl
		
		RUN apt-get install -y python
		
		RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
		
		RUN chmod a+rx /usr/local/bin/youtube-dl
		
		CMD ["/usr/local/bin/youtube-dl","https://www.youtube.com/watch?v=dQw4w9WgXcQ"]

Chúng ta có thể các bước như sau:

  • Bắt đầu FROM một image: sử dụng image làm bản gốc cùng với :tag. Tag có thể tìm thấy trên Supported tags and respective Dockerfile links. Ta cứ hiểu Tag là một phiên bản.
  • WORKDIR tại thư mục: lúc này xác định nơi thực thi nằm ở đâu
  • RUN command: thực thi các lệnh trên command line, thường thì lệnh RUN được dùng để setup cấu hình, môi trường, ví dụ như yarn install trước khi chạy. Ta có thể dùng nhiều lệnh RUN vẫn được
  • CMD command: dùng để thực thi câu lệnh khi thực hiện container run. CMD được thực thi khi chạy docker container run, trừ khi ta ghi đè lên. Còn ghi đè như thế nào thì bạn đọc tham khảo ở phần copy một file từ local sang container.
    • CMD, RUN, ENTRYPOINT có 2 dạng, thực thi dạng shell và dạng exec.
    • Chú ý rằng nếu nhiều lệnh CMD thì sẽ chỉ lấy lệnh CMD cuối cùng để thực thi

Shell form và Exec form

  • Dạng shell là một chuỗi làm tham số để chạy trong /bin/sh -c. Shell form đơn giản là lệnh terminal mà ta sử dụng. Ví dụ "docker container ls". ⇒ Chạy trong shell.
  • Còn dạng exec là phân cách từng phần giữa các đoạn lệnh. Bản chất của exec là một mảng JSON được format. Ưu điểm của dạng này là sẽ ghi đè lên /bin/sh -c. ⇒ Không chạy trong shell mà chạy trực tiếp.

Nếu các bạn đã sử dụng python, các bạn sẽ gặp dạng exec form dưới đây, dạng này được phân tách từng phần. Câu lệnh dưới là để xuất địa chỉ MAC trong máy tính ở Windows.

import subprocess
		subprocess.check_output(["getmac", "/v","/fo","list"])

Tiếp tục phần demo...

Sau khi đã build xong, thì ta sẽ build image hello-world, với câu lệnh:

docker build . -t surprise

Trong đó: dấu chấm chính là nơi chứa Dockerfile và ta cần đặt tên image đó ( -t <name>):

Dưới đây vì log khá là dài nên đã bỏ bớt một số phần, chỉ chừa lại một vài phần thật sự quan trọng!!!!

$ docker build . -t surprise
		Sending build context to Docker daemon  2.048kB
		Step 1/7 : FROM ubuntu:latest
		latest: Pulling from library/ubuntu
		7b1a6ab2e44d: Pull complete 
		Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
		Status: Downloaded newer image for ubuntu:latest
		 ---> ba6acccedd29
		Step 2/7 : WORKDIR /learn_docker
		 ---> Running in 5fba3acb0753
		Removing intermediate container 5fba3acb0753
		 ---> d62babcb46a7
		Step 3/7 : RUN apt-get update && apt-get install -y curl
		 ---> Running in 230a06dc266b
		...
		Removing intermediate container 230a06dc266b
		 ---> 226d9af0dca8
		Step 4/7 : RUN apt-get install -y python
		 ---> Running in 069f6b81cffb
		...
		Step 5/7 : RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
		 ---> Running in 878c2014461f
		Removing intermediate container 878c2014461f
		 ---> 0a7471807a71
		Step 6/7 : RUN chmod a+rx /usr/local/bin/youtube-dl
		 ---> Running in 2291d1c1c15f
		Removing intermediate container 2291d1c1c15f
		 ---> 4ca8cdd2e3c1
		Step 7/7 : CMD ["/usr/local/bin/youtube-dl","https://www.youtube.com/watch?v=dQw4w9WgXcQ"]
		 ---> Running in cafb784e508e
		Removing intermediate container cafb784e508e
		 ---> cbda1a1f634c
		Successfully built cbda1a1f634c
		Successfully tagged surprise:latest

Trong quá trình build thì ta nhận ra có một vài bước liên quan đến hashes và sha256. Những bước này cho thấy các layer mới chồng lên các image. Layers đóng vai trò như là cache trong quá trình build. Nếu như có một sự thay đổi nhỏ trong Dockerfile thì các bước trước sự thay đổi đó sẽ được bỏ qua một cách nhanh chóng để tới bước thay đổi ấy.

Bây giờ ta chạy thử container dưới đây, khá là OK đúng không :)) ?

$ docker container run surprise
		WARNING: Assuming --restrict-filenames since file system encoding cannot encode all characters. Set the LC_ALL environment variable to fix this.
		[youtube] dQw4w9WgXcQ: Downloading webpage
		[youtube] dQw4w9WgXcQ: Downloading player 9216d1f7
		[download] Destination: Rick_Astley_-_Never_Gonna_Give_You_Up_Official_Music_Video-dQw4w9WgXcQ.mp4
		[download] 100% of 15.95MiB in 04:1266KiB/s ETA 00:003

Ta sẽ kiểm tra loại container của image surprise mà ta đã sử dụng (vì tên của container sẽ được sinh ra ngẫu nhiên). Ta nhận thấy image surprise có container name tên là nostalgic_bartik.

$ docker container ls -a
		CONTAINER ID   IMAGE         COMMAND                  CREATED             STATUS                      PORTS     NAMES
		6c35aa43288c   surprise      "/usr/local/bin/yout…"   5 minutes ago       Exited (0) 52 seconds ago             nostalgic_bartik
		bfdef9f59b1e   hello-world   "sh"                     About an hour ago   Up About an hour                      lucid_hamilton
		afdf4cc8a7a2   hello-world   "/bin/sh -c ./hello.…"   2 hours ago         Exited (0) 2 hours ago                youthful_cray

Sau khi đã download xong video, thì bạn sẽ copy file đó vào thư mục local hiện tại

$ docker cp "nostalgic_bartik://learn_docker/Rick_Astley_-_Never_Gonna_Give_You_Up_Official_Music_Video-dQw4w9WgXcQ.mp4" .

Build Dockerfile cải tiến

Mục tiêu đạt được trong phần này

  • Phân biệt RUN và CMD
  • Phân biệt được CMD và ENTRYPOINT
  • Quản lý image verison bằng Tag
  • Volume: Bind Mount

Phân biệt RUN và CMD

  • RUN: nằm ở giai đoạn build image, và kết quả đó sẽ nằm trong container image. Tức là ta chỉ cần cài 1 lần, những lần sau không cần cài lại (trừ khi gỡ image đó hoặc build Dockerfile khác). Và có thể dùng nhiều lệnh RUN
  • CMD: lệnh dùng để thực thi câu lệnh mặc định khi chạy container. CMD có thể bị override nếu chạy container với cú pháp "docker container run <image> <other_command>". Và CMD chỉ thực thi 1 lần thôi, nếu có nhiều CMD thì sẽ lấy lần cuối.

Các bạn lưu ý phần tô đen, vì trong bài hướng dẫn này ta sẽ đề cập một lần nữa.

Tiếp tục phần trước

Lúc này ta sẽ thay đổi 1 tí câu lệnh ở CMD ta chỉ cần download video trên Youtube với link bất kỳ. Ta sẽ quy định ở CMD là gọi youtube-dl ra và phần còn lại sau khi build xong, ta sẽ thực hiện chạy container với lệnh dưới đây:

docker container run surprise https://www.youtube.com/watch?v=dQw4w9WgXcQ

FROM ubuntu:latest
		
		WORKDIR /learn_docker
		
		RUN apt-get update && apt-get install -y curl
		
		RUN apt-get install -y python
		
		RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
		
		RUN chmod a+rx /usr/local/bin/youtube-dl
		
		CMD ["/usr/local/bin/youtube-dl"]
  • Vì ta cập nhật (thêm, xóa, sửa) trên Dockerfile và đã build sẵn image, nên thay vì ghi đè, ta sẽ quản lý phiên bản của image đó bằng cách sử dụng Tag. Đó là lý do vì sao Docker Hub có mục Supported Tags trong đó mỗi tag là một phiên bản.

Còn 1 cách khác là docker commit <tên container cũ> <tên image mới>. Nhưng cách này ít người sử dụng nên sẽ không khuyến khích dùng cách commit. Ví dụ ở câu lệnh này:

docker commit nostalgic_bartik surprise_additional

Thay vào đó ta sẽ dùng lệnh này để cập nhật tag mới:

docker build . -t surprise:v2

:Tag trong đó Tag là phiên bản mà ta đã chỉnh sửa img

Chạy lệnh trên và ta nhận thấy có các dòng lệnh sau:

$ docker build . -t surprise:v2
		Sending build context to Docker daemon  16.73MB
		Step 1/7 : FROM ubuntu:latest
		 ---> ba6acccedd29
		Step 2/7 : WORKDIR /learn_docker
		 ---> Using cache
		 ---> d62babcb46a7
		Step 3/7 : RUN apt-get update && apt-get install -y curl
		 ---> Using cache
		 ---> 226d9af0dca8
		Step 4/7 : RUN apt-get install -y python
		 ---> Using cache
		 ---> 3a86c830e3d0
		Step 5/7 : RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
		 ---> Using cache
		 ---> 0a7471807a71
		Step 6/7 : RUN chmod a+rx /usr/local/bin/youtube-dl
		 ---> Using cache
		 ---> 4ca8cdd2e3c1
		Step 7/7 : CMD ["/usr/local/bin/youtube-dl"]
		 ---> Running in c95b89810885
		Removing intermediate container c95b89810885
		 ---> 738c53ece616
		Successfully built 738c53ece616
		Successfully tagged surprise:v2

Nhìn vào dòng log của build thì ta thấy có chữ Using cache và so sánh với mã hash ở mục trên thì hầu như tương đương nhau, chính là các layers đó được dùng để lưu lại cache, giúp cho quá trình build nhanh hơn. Đây là một kỹ thuật tối ưu của Docker mà ta sẽ tìm hiểu ở phần 3.

Sau đó ta sẽ chạy container của surprise:v2

$ docker container run surprise:v2 https://www.youtube.com/watch?v=dQw4w9WgXcQ
		docker: Error response from daemon: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "https://www.youtube.com/watch?v=dQw4w9WgXcQ": stat https://www.youtube.com/watch?v=dQw4w9WgXcQ: no such file or directory: unknown.
		ERRO[0000] error waiting for container: context canceled

Ở đây ta thấy dòng xuất hiện lỗi. Đây là lý do starting container process caused: exec: "https://www.youtube.com/watch?v=dQw4w9WgXcQ". Tức là lệnh này đã bị ghi đè lên CMD khiến cho không thể thực thi được. Chính vì vậy ta cần tìm cách nào đó có thể truyền link như là một tham số. Lúc này ta nhận thấy có ENTRYPOINT. Vậy ENTRYPOINT là gì? Và có khác gì so với CMD?

Theo Document trên Docker, ENTRYPOINT cho phép việc cấu hình container như là một file thực thi lệnh, mà lúc này người dùng chỉ cần cung cấp tham số để hoạt động. Hay cách hiểu khác là cung cấp interface dưới dạng bash package để người dùng tương tác. Ví dụ yarn (Package Manager thay thế npm) có các lệnh yarn add, yarn start, yarn install,... Những từ khóa đó là người dùng truyền vào để thực thi lệnh.

Sau khi thay CMD thành ENTRYPOINT

...
		ENTRYPOINT ["/usr/local/bin/youtube-dl"]

Ta sẽ build lại image:

$ docker build . -t surprise
		Sending build context to Docker daemon  16.73MB
		Step 1/7 : FROM ubuntu:latest
		 ---> ba6acccedd29
		Step 2/7 : WORKDIR /learn_docker
		---> Using cache
		 ---> d62babcb46a7
		Step 3/7 : RUN apt-get update && apt-get install -y curl
		 ---> Using cache
		 ---> 226d9af0dca8
		Step 4/7 : RUN apt-get install -y python
		 ---> Using cache
		 ---> 3a86c830e3d0
		Step 5/7 : RUN curl -L https://yt-dl.org/downloads/latest/youtube-dl -o /usr/local/bin/youtube-dl
		 ---> Using cache
		 ---> 0a7471807a71
		Step 6/7 : RUN chmod a+rx /usr/local/bin/youtube-dl
		 ---> Using cache
		 ---> 4ca8cdd2e3c1
		Step 7/7 : ENTRYPOINT ["/usr/local/bin/youtube-dl"]
		 ---> Running in d138a3da1e56
		Removing intermediate container d138a3da1e56
		 ---> 1b58e25348d7
		Successfully built 1b58e25348d7
		Successfully tagged surprise:latest

Lúc này ta chạy lại câu lệnh và build thành công

$ docker container run surprise https://www.youtube.com/watch?v=dQw4w9WgXcQD
		WARNING: Assuming --restrict-filenames since file system encoding cannot encode all characters. Set the LC_ALL environment variable to fix this.
		[youtube] dQw4w9WgXcQ: Downloading webpage
		[youtube] dQw4w9WgXcQ: Downloading player f8cb7a3b
		[download] Destination: Rick_Astley_-_Never_Gonna_Give_You_Up_Official_Music_Video-dQw4w9WgXcQ.mp4
		[download] 100% of 15.95MiB in 04:1271KiB/s ETA 00:005

Sau khi đã download xong video, thì bạn sẽ copy file đó vào thư mục local hiện tại

$ docker cp "nostalgic_bartik://learn_docker/Rick_Astley_-_Never_Gonna_Give_You_Up_Official_Music_Video-dQw4w9WgXcQ.mp4" .

Như vậy đã hoàn thành!!!

So sánh CMD và ENTRYPOINT

  • Một đặc điểm trong Docker là ENTRYPOINT mặc định là /bin/sh -c nhưng không có COMMAND mặc định.

Lệnh docker run -it ubuntu bash: thì thực chất ta thực thi lệnh /bin/sh -c bash.

Sau này thì ENTRYPOINT, —entrypoint ra đời để có thể tự tùy biến đầu vào hoặc interface.

Ở bài hướng dẫn này, ta sẽ tổng kết CMD và ENTRYPOINT có khác gì nhau?

  • ENTRYPOINT:
    • Không bị ghi đè, thay vào đó thêm vào khúc sau của command trong ENTRYPOINT
  • CMD:
    • Bị override khi thực hiện "docker container run <image> <other_command>"
    • Dùng để thực thi lệnh mặc định

Trường hợp ENTRYPOINT và CMD tồn tại song song thì sẽ ra sao. Đây là bảng phân tích các trường hợp (sử dụng dạng exec form và shell form)

Các trường hợp

Lệnh Kết quả
ENTRYPOINT yarn add CMD react /bin/sh -c ‘yarn add’ /bin/sh -c react
ENTRYPOINT ["yarn", "add"] CMD react yarn add /bin/sh -c react
ENTRYPOINT yarn add CMD ["react"] bin/sh -c ‘yarn add’ react
ENTRYPOINT ["yarn", "add"] CMD ["react"] yarn add react

Volume: Bind mount

Đây là một trick thay vì bạn phải thao tác copy thêm một lần nữa sang thư mục hiện hành, thì bạn chỉ cần dẫn tới một đường dẫn file về thư mục local hiện tại. Cú pháp ở dưới như sau:

$ docker run -v "$(pwd):/learn_docker" surprise https://www.youtube.com/watch?v=U3ASj1L6_sY
  • Trong đó: -v "<đường dẫn local>:<đường dẫn trong container>". pwd là vị trí của thư mục hiện hành.
  • Volume được hiểu là folder hoặc file được chia sẻ giữa container và máy thật. Trong máy ảo (VMWare, VirtualBox) có khái niệm tương tự là Shared Folders, dùng để chia sẻ file, folder giữa máy ảo và máy thực, theo một phương thức trao đổi.
  • Một cách khác cho công dụng của bind mount đó là đồng bộ việc chỉnh sửa file trên máy sang trên container và ngược lại. -v "<đường dẫn>/<file trùng tên>:<đường dẫn container>/<file trùng tên>".

Cho phép kết nối bên ngoài vào trong Containers

Giới thiệu

Để có thể cho phép kết nối "public network" thì ta cần thực hiện 2 bước:

  1. EXPOSE <port>: Cho Docker container mở port tại thời điểm chạy. Bước này ta có thể cho phép giao thức TCP hay UDP, và mặc định là TCP. Lưu ý rằng bước này không có mở port cho các dịch vụ khác hay bên ngoài truy cập, mà chỉ là xác định loại port nào sẽ được xuất ra.
  1. PUBLISH <port> (Không có lệnh này trong Dockerfile): Mở port của Docker container cho các dịch vụ khác truy cập hay các Docker container khác truy cập.

Lần này ta sẽ chuyển sang ví dụ khác, đó là Dockerize hóa một trang web tĩnh sử dụng React framework.

Trước hết ta dùng dòng lệnh dưới đây. Lưu ý là chúng ta sẽ sử dụng Visual Studio Code nên có lệnh "code .".

git clone https://github.com/lenhatquang97/React_Learn.git
		cd React_Learn
		code .

Mô tả sơ lược:

Để có thể setup React project, thì điều đầu tiên ta cần làm là cài đặt Node.js, sau đó thiết lập biến môi trường (environment variables) để có thể chạy lệnh node <câu lệnh> toàn cục. Sau đó thì cài đặt các package như sau:

npm install --silent 
		npm install react-scripts -g --silent

Cuối cùng là npm start để chạy ứng dụng. Như vậy là ta đã hình dung cách thức setup dự án sử dụng React. Bây giờ đây là lúc chúng ta sẽ viết Dockerfile cho React Application. Bước này khá khó với các bạn, nên mình sẽ giải thích kĩ hơn ở phần mô tả.

FROM node:16.13
		
		WORKDIR /react_learn
		
		ENV PATH /app/node_modules/.bin:$PATH
		
		COPY package.json ./
		COPY package-lock.json ./
		
		RUN npm install --silent
		RUN npm install react-scripts -g --silent
		
		COPY . ./
		
		EXPOSE 3000
		
		CMD [ "npm","start" ]

Mô tả Dockerfile:

FROM node:16.13: lý do mình chọn bản 16.13 thay vì bản latest (17.0) là trong quá trình Setup gặp phải lỗi này: https://github.com/webpack/webpack/issues/14532. Do đó ta có thể thấy vì sao node có khá nhiều tag trên Docker Hub.

ENV PATH /app/node_modules/.bin:$PATH: Lệnh ENV dùng để thiết lập biến môi trường. Cú pháp ở đây là ENV <key> = <value> . Lệnh này dùng thiết lập cho Node

COPY package.json ./: lệnh này dùng để copy 1 file tới đường dẫn làm việc hiện hành trong container. Cú pháp ở đây là COPY <đường dẫn file> <đường dẫn thư mục của container>. Mục đích của việc copy 2 file package.json và package-lock.json là để chuẩn bị cho việc thực hiện lệnh npm install —silent . Vì lệnh npm install cài đặt dựa trên 2 file json nên phải đưa 2 file json trước. Tiếp đó ta cài đặt react-scripts.

COPY . ./ : Sao chép tất cả thư mục, tệp trừ những mục trong .dockerignore (phần này sẽ nói sau).

EXPOSE 3000: Cho phép Docker container mở port 3000.

CMD ["npm", "start"] : Chạy lệnh npm start thay vì chạy /bin/sh -c npm start.

.dockerignore

Bước tiếp theo ta cần thực hiện là liệt kê những thư mục hay tệp sẽ không đưa vào trong container. Tạo file .dockerignore (giống như .gitignore) và thực hiện như sau:

node_modules
		build
		.dockerignore
		Dockerfile
		Dockerfile.prod

Ta làm như vậy là để tăng tốc việc build một image, từ đó tiết kiệm được thời gian. node_modules dùng để lưu lại các package đã download trên NPM nhưng thực tế là Docker đã lưu lại cache cho việc đó.

Tiếp tục Dockerize React Application

Sau đó ta sẽ thực hiện việc build image tên là react_learn trên Dockerfile

$ docker build . -t learn_react
		Sending build context to Docker daemon  1.263MB
		Step 1/10 : FROM node:16.13
		...
		Step 2/10 : WORKDIR /react_learn
		 ---> Running in 1cec2b43be52
		Removing intermediate container 1cec2b43be52
		 ---> 25ac09c5b477
		Step 3/10 : ENV PATH /app/node_modules/.bin:$PATH
		 ---> Running in 6efc45d012e5
		Removing intermediate container 6efc45d012e5
		 ---> d19233389da8
		Step 4/10 : COPY package.json ./
		 ---> cbbfc5fddc20
		Step 5/10 : COPY package-lock.json ./
		 ---> 7919324b36ef
		Step 6/10 : RUN npm install --silent
		 ---> Running in 81d05dded36e
		...
		Removing intermediate container 81d05dded36e
		 ---> 6e0f0435d398
		Step 7/10 : RUN npm install react-scripts -g --silent
		 ---> Running in 8c7f3b2439c6
		Removing intermediate container 8c7f3b2439c6
		 ---> d4d5818fe8a8
		Step 8/10 : COPY . ./
		 ---> 9a54a4e37393
		Step 9/10 : EXPOSE 3000
		 ---> Running in 08496878700c
		Removing intermediate container 08496878700c
		 ---> 775d5b1692fc
		Step 10/10 : CMD [ "npm","start" ]
		 ---> Running in c36913be2d41
		Removing intermediate container c36913be2d41
		 ---> 599b6eb4b3e4
		Successfully built 599b6eb4b3e4
		Successfully tagged learn_react:latest

Cuối cùng ta sẽ thực hiện việc chạy container bằng cách dùng lệnh dưới đây, trong đó -p là port.

docker container run -p 3000 learn_react

Nếu trường hợp không sử dụng EXPOSE 3000 thì có một câu lệnh khác thay thế:

docker run -p 3000:3000 learn_react với -p <port của local>:<port của container>

$ docker container run -p 3000 learn_react
		
		> my-app@0.1.0 start
		> react-scripts start
		
		ℹ 「wds」: Project is running at http://172.17.0.2/
		ℹ 「wds」: webpack output is served from 
		ℹ 「wds」: Content not from webpack is served from /react_learn/public
		ℹ 「wds」: 404s will fallback to /
		Starting the development server...
		
		Compiled successfully!
		
		You can now view my-app in the browser.
		
		  Local:            http://localhost:3000
		  On Your Network:  http://172.17.0.2:3000
		
		Note that the development build is not optimized.
		To create a production build, use npm run build.

Kết quả thực hiện ở dưới đây:

Tuy nhiên, ta không thể stop được container bằng Ctrl + C. Lúc này ta sẽ mở cửa sổ terminal khác và dùng lệnh trong docker

$ docker container ls
		CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS         PORTS                                         NAMES
		7df505285d90   learn_react   "docker-entrypoint.s…"   7 minutes ago   Up 7 minutes   0.0.0.0:49161->3000/tcp, :::49161->3000/tcp   kind_almeida
		$ docker container stop kind_almeida
		kind_almeida

Và thế là bạn đã có thể tự setup React Application trong Docker.

Xuất bản image lên public repository

Bước cuối cùng là xuất bản project lên trên trang web hub.docker.com

  • Lần này ta sẽ xuất bản learn_react image. Ta sẽ chọn Create Repository, với tên project là learn_react
  • Chọn sang public và bấm nút Create
  • Trên terminal hiện có của VSCode, ta sẽ thực hiện: docker login → để có thể xác thực.
  • Sau đó ta thực hiện 2 câu lệnh với cú pháp sau
docker tag learn_react <docker_id>/learn_react
		docker push <docker_id>/learn_react

Đợi một khoảng thời gian và chờ Docker push trên Docker Hub thành công!!

Docker Compose

Docker Compose là gì?

Theo tài liệu của Docker:

Docker Compose là một công cụ để định nghĩa và chạy các ứng dụng trên nhiều Docker Container. Với Docker Compose, bạn sử dụng file YAML để cấu hình các dịch vụ của ứng dụng. Sau đó, với một lệnh duy nhất, bạn tạo và khởi động tất cả các dịch vụ từ cấu hinh của mình

Vậy khi nào ta cần dùng Docker Compose?

Một câu chuyện được đặt ra như sau:

Bạn muốn ứng dụng Docker cho một dự án mới hoặc một dự án đang phát triển, bạn phải làm sao? Qua sự tìm hiểu ở các phần trước, bạn hoàn toàn có thể sử dụng Dockerfile build cho mình 1 image và cài đặt tất cả những môi trường cần thiết (như mysql, redis, php,... ) lên một container duy nhất. Tuy nhiên:

  • Nếu như bạn muốn dùng kết hợp nhiều image có sẵn trên DockerHub thì sao ?
  • Nếu một cơ sở dữ liệu dùng chung cho nhiều project thì sẽ xử lý thế nào ?
  • Hơn nữa, với tư duy của OOP, 1 class thì không nên cõng nhiều nhiệm vụ.

Từ đó docker-compose được sinh ra để kết nối các container riêng lẻ với nhau.

Khi đó, chúng ta sẽ xây dựng nhiều container, khi nào cần tương tác với database thì gọi tới container mysql chẳng hạn, tương tác với redis thì gọi tới container redis, cần cái gì thì gọi tới container làm nhiệm vụ đó.

Cài đặt Docker Compose

Đối với Windows: Docker Compose đã được tích hợp vào Docker Desktop bạn đã cài đặt ở phần đầu tiên

Đối với Linux:

  1. Chạy command bên dưới để tải xuống bản phát hành ổn định mới nhất của Docker Compose
    sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
  1. Cấp quyền thực thi cho các file
    sudo chmod +x /usr/local/bin/docker-compose
  1. Kiểm tra Docker Compose đã được cài đặt hay chưa bằng cách kiểm tra phiên bản của nó:
    docker-compose --version

Sau khi kiểm tra thì phiên bản trên máy mình là Docker Compose version v2.0.0

Sử dụng Docker Compose với một project đơn giản

Trong phần này, bạn xây dựng một ứng dụng web Python đơn giản chạy trên Docker Compose. Ứng dụng sử dụng framework Flask và duy trì bộ đếm lượt truy cập trong Redis. Mặc dù ứng dụng mẫu này sử dụng Python, nhưng các khái niệm được trình bày ở đây sẽ dễ hiểu ngay cả khi bạn không quen thuộc với nó.

Setup

Đầu tiên, bạn tạo 1 thư mục chứa project

mkdir composetest
		cd composetest

Tiếp theo, tạo 1 file app.py trong thư mục mục project vừa tạo với nội dung như sau:

import time
		
		import redis
		from flask import Flask
		
		app = Flask(__name__)
		cache = redis.Redis(host='redis', port=6379)
		
		def get_hit_count():
			retries = 5
			while True:
				try:
					return cache.incr('hits')
				except redis.exceptions.ConnectionError as exc:
					if retries == 0:
						raise exc
					retries -= 1
					time.sleep(0.5)
		
		@app.route('/')
		def hello():
			count = get_hit_count()
			return 'Hello World! I have been seen {} times.\n'.format(count)

Trong ví dụ này, redis là hostname của container resdis nằm cùng 1 mạng. Chúng ta sử dụng cổng mặc định cho Redis là 637

Kế tiếp, tạo 1 file requirements.txt trong thư mục project của bạn, copy và dán nội dung bên dưới vào file:

flask
		redis

Tạo 1 Dockerfile

Trong bước này, bạn viết 1 Dockerfile dùng để build image chứa tất cả những thứ mà một ứng dụng Python cần. Dockerfile có nội dung như bên dưới

# syntax=docker/dockerfile:1
		FROM python:3.7-alpine
		WORKDIR /code
		ENV FLASK_APP=app.py
		ENV FLASK_RUN_HOST=0.0.0.0
		RUN apk add --no-cache gcc musl-dev linux-headers
		COPY requirements.txt requirements.txt
		RUN pip install -r requirements.txt
		EXPOSE 5000
		COPY . .
		CMD ["flask", "run"]

File trên cho Docker biết nó cần:

  • Build một image từ image Python 3.7.
  • Đặt thư mục làm việc là /code.
  • Cài đặt biến môi trường bằng flask command.
  • Cài đặt gcc và những thứ liên quan
  • Copy requirements.txt và cài đặt những thứ cần thiết.
  • Thêm metadata vào image để mô tả rằng container luôn lắng nghe cổng 5000
  • Copy thư mục hiện tại . vào thư mục làm việc . trong image.
  • Đặt command mặc định cho container thành flask run.

Định nghĩa các service trong Docker Compose

Tạo file docker-compose.yml và dán nội dung bên dưới vào:

version: "3.8"
		services:
		  web:
			build: .
				container_name: web
				restart: always
			ports:
			  - "5000:5000"
				volumes:
			  - .:/code
			environment:
			  FLASK_ENV: development
		  redis:
			image: "redis:alpine"
			  container_name: redis
				restart: always

Đây là file dùng để khai báo và điều phối hoạt động của các container trong project.

Mọi file docker-compose.yml đều phải bắt đầu bằng cách chỉ định phiên bản file. Bạn có thể tra cứu phiên bản mới nhất tại đây

Các khối trong file YAML được định nghĩa bằng cách thụt lề, bảng bên dưới sẽ giải thích cho bạn ý nghĩa của các lệnh trong file mẫu

Tên Chức năng
service Chứa các định nghĩa cho mỗi service (hoặc container) trong app. Ở ví dụ trên ta có 2 service là webredis
build Sử dụng khi chúng ta không xây dựng container từ image có sẵn nữa mà xây dựng nó từ Dockerfile.
container_name Chỉ định tên container tùy chỉnh, thay vì tên mặc định. do hệ thống tự đặt
ports Có thể chỉ định cả 2 cổng (HOST:CONTAINER) tức là (cổng ở máy thật: cổng ở máy ảo) hoặc chỉ định mình cổng cho máy ảo thôi. Ví dụ: "2222:3333" Khi bạn truy cập vào cổng 2222 ở máy thật thì sẽ được trỏ tới truy cập ở cổng 3333 của máy ảo.
restart Giá trị mặc định là no, còn nếu bạn đặt là always thì container sẽ khởi động lại nếu exit code cho biết lỗi không thành công.
environment Thêm các biến môi trường
volume Option này sẽ giúp copy thư mục từ máy host (trên ví dụ là . - thư mục gốc) vào thư mục trên container (trên ví dụ là thư mục code ), nhờ đó, ta có thể chỉnh sửa file code app.py mà không cần rebuild. Đồng thời, nhờ lệnh này, ta có thể backup lại dữ liệu từ container (vì mỗi lần container dừng dữ liệu sẽ bị xoá)
depends_on Option này giúp ta đánh dấu những container mà ta phụ thuộc vào, những container đó sẽ được tạo trước.

Mọi người có thể tham khảo thêm các option khác tại đây

Build và chạy app của bạn với Docker Compose

Từ thư mục project của bạn, chạy ứng dụng của bạn bằng command

docker-compose up

Màn hình của bạn sẽ hiển thị tương tự bên dưới

[+] Running 7/7
		 - redis Pulled                                                                                                   12.8s
		   - a0d0a0d46f8b Already exists                                                                                   0.0s
		   - a04b0375051e Pull complete                                                                                    2.3s
		   - cdc2bb0f9590 Pull complete                                                                                    2.5s
		   - 0aa2a8e7bd65 Pull complete                                                                                    2.9s
		   - f64034a16b58 Pull complete                                                                                    3.6s
		   - 7b9178a22893 Pull complete                                                                                    3.6s
		[+] Building 30.0s (17/17) FINISHED
		 => [internal] load build definition from dockerfile                                                               0.0s
		 => => transferring dockerfile: 329B                                                                               0.0s
		 => [internal] load .dockerignore                                                                                  0.0s
		 => => transferring context: 2B                                                                                    0.0s
		 => resolve image config for docker.io/docker/dockerfile:1                                                         5.5s
		 => [auth] docker/dockerfile:pull token for registry-1.docker.io                                                   0.0s
		 => docker-image://docker.io/docker/dockerfile:1@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1  1.7s
		 => => resolve docker.io/docker/dockerfile:1@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b  0.0s
		 => => sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2 2.00kB / 2.00kB                     0.0s
		 => => sha256:93f32bd6dd9004897fed4703191f48924975081860667932a4df35ba567d7426 528B / 528B                         0.0s
		 => => sha256:e532695ddd93ca7c85a816c67afdb352e91052fab7ac19a675088f80915779a7 1.21kB / 1.21kB                     0.0s
		 => => sha256:24a639a53085eb680e1d11618ac62f3977a3926fedf5b8471ace519b8c778030 9.67MB / 9.67MB                     1.3s
		 => => extracting sha256:24a639a53085eb680e1d11618ac62f3977a3926fedf5b8471ace519b8c778030                          0.2s
		 => [internal] load .dockerignore                                                                                  0.0s
		 => [internal] load build definition from dockerfile                                                               0.0s
		 => [internal] load metadata for docker.io/library/python:3.7-alpine                                               7.0s
		 => [auth] library/python:pull token for registry-1.docker.io                                                      0.0s
		 => [1/6] FROM docker.io/library/python:3.7-alpine@sha256:68dc2f52411f1071069a75c00029a0620d98139d638153cd33d029c  3.1s
		 => => resolve docker.io/library/python:3.7-alpine@sha256:68dc2f52411f1071069a75c00029a0620d98139d638153cd33d029c  0.0s
		 => => sha256:c11246b421beac7bdab2cd3f620a098af2a8bbbf8e608bef7bf056866e710734 281.50kB / 281.50kB                 0.9s
		 => => sha256:c5f7759615a9d583124f55e1df5aed6dda463b4f79867d2ea7ed78a8e3eb49b1 10.58MB / 10.58MB                   2.1s
		 => => sha256:6dc4dde3f226ebab24bd4e8fc69a7bd393bdce332980ef22cd962f5d884d16e8 233B / 233B                         0.4s
		 => => sha256:68dc2f52411f1071069a75c00029a0620d98139d638153cd33d029cf9810c8d6 1.65kB / 1.65kB                     0.0s
		 => => sha256:5d0900f6a439b7c28c508346b17cf5408bd6e670033e95d02c7a8ce81c69168a 1.37kB / 1.37kB                     0.0s
		 => => sha256:4d1c95b3db1c2a209efca1819a5c6015145799dbf47057082cd2266d13f27786 8.10kB / 8.10kB                     0.0s
		 => => sha256:9928d5d652ca3325f5a6558376ea5438008763664e06031998750e114659543c 2.35MB / 2.35MB                     2.3s
		 => => extracting sha256:c11246b421beac7bdab2cd3f620a098af2a8bbbf8e608bef7bf056866e710734                          0.1s
		 => => extracting sha256:c5f7759615a9d583124f55e1df5aed6dda463b4f79867d2ea7ed78a8e3eb49b1                          0.4s
		 => => extracting sha256:6dc4dde3f226ebab24bd4e8fc69a7bd393bdce332980ef22cd962f5d884d16e8                          0.0s
		 => => extracting sha256:9928d5d652ca3325f5a6558376ea5438008763664e06031998750e114659543c                          0.2s
		 => [internal] load build context                                                                                  0.0s
		 => => transferring context: 1.12kB                                                                                0.0s
		 => [2/6] WORKDIR /code                                                                                            0.1s
		 => [3/6] RUN apk add --no-cache gcc musl-dev linux-headers                                                        6.5s
		 => [4/6] COPY requirements.txt requirements.txt                                                                   0.0s
		 => [5/6] RUN pip install -r requirements.txt                                                                      4.9s
		 => [6/6] COPY . .                                                                                                 0.0s
		 => exporting to image                                                                                             0.7s
		 => => exporting layers                                                                                            0.7s
		 => => writing image sha256:e226c7105e104036d673e098ee4cf65d5d1765a491154c867e91f0993eb7f6ea                       0.0s
		 => => naming to docker.io/library/composetest_web                                                                 0.0s
		[+] Running 3/3
		 - Network composetest_default    Created                                                                          0.7s
		 - Container composetest-redis-1  Created                                                                          0.1s
		 - Container composetest-web-1    Created                                                                          0.1s
		Attaching to composetest-redis-1, composetest-web-1
		composetest-redis-1  | 1:C 09 Nov 2021 11:21:37.411 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
		composetest-redis-1  | 1:C 09 Nov 2021 11:21:37.412 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=1, just started
		composetest-redis-1  | 1:C 09 Nov 2021 11:21:37.412 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
		composetest-redis-1  | 1:M 09 Nov 2021 11:21:37.412 * monotonic clock: POSIX clock_gettime
		composetest-redis-1  | 1:M 09 Nov 2021 11:21:37.412 * Running mode=standalone, port=6379.
		composetest-redis-1  | 1:M 09 Nov 2021 11:21:37.412 # Server initialized
		composetest-redis-1  | 1:M 09 Nov 2021 11:21:37.412 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
		composetest-redis-1  | 1:M 09 Nov 2021 11:21:37.413 * Ready to accept connections
		composetest-web-1    |  * Serving Flask app 'app.py' (lazy loading)
		composetest-web-1    |  * Environment: production
		composetest-web-1    |    WARNING: This is a development server. Do not use it in a production deployment.
		composetest-web-1    |    Use a production WSGI server instead.
		composetest-web-1    |  * Debug mode: off
		composetest-web-1    |  * Running on all addresses.
		composetest-web-1    |    WARNING: This is a development server. Do not use it in a production deployment.
		composetest-web-1    |  * Running on http://172.18.0.3:5000/ (Press CTRL+C to quit)

Những dòng trên mô tả việc Compose tải image Redis về, và đồng thời build 1 image từ dockerfile của bạn, và sau đó bắt đầu những dịch vụ bạn đã định nghĩa. Trong trường hợp này, code của bạn được sao chép tĩnh (statically copied) vào image trong lúc build.

Truy cập vào http://localhost:5000/ để thấy ứng dụng đang chạy của bạn

Sau đó, bạn tải lại trang và sẽ thấy con số tăng lên

Chuyển sang một cửa sổ terminal (hoặc command prompt) khác, gõ lệnh docker images -a để liệt kê những image có trên máy.

images -a
		REPOSITORY        TAG       IMAGE ID       CREATED       SIZE
		composetest_web   latest    e226c7105e10   2 hours ago   183MB
		redis             alpine    e24d2b9deaec   4 weeks ago   32.3MB

Docker Network

Xem các mạng (network) trong Docker

Mục tiêu đạt được trong phần này

  • Liệt kê các network: docker network ls
  • Xem thành phần bên trong network: docker network inspect
  • Xem các thông tin driver về network mà ta cài đặt Docker: docker info

Chi tiết thực hiện

  • Để xem các kết nối trong Docker, ta sẽ thực hiện dưới đây, trong đó có các thông số về ID, tên, loại driver và phạm vi kết nối
$ docker network ls
		NETWORK ID     NAME      DRIVER    SCOPE
		7b533e835c4c   bridge    bridge    local
		6ee1c411176c   host      host      local
		98eda4bd78ae   none      null      local

Nhận thấy rằng bridge network đi liền với bridge driver. Lưu ý rằng cả hai đều kết nối với nhau, nhưng không đồng nhất với nhau.

Ngoài ra ta còn thấy phạm vi kết nối của bridge là local - tức là chỉ tồn tại trong Docker host (trong máy tính có cái gọi là localhost).

Mọi kết nối được tạo từ bridge driver đều dựa trên cơ chế hoạt động của Linux bridge, cách hoạt động giống như switch (https://developers.redhat.com/blog/2018/10/22/introduction-to-linux-interfaces-for-virtual-networking#bridge)

  • Để xem thông tin chi tiết của network đó thì ta thực hiện lệnh với cú pháp: docker network inspect <network name>. Thông tin được hiển thị dưới dạng một mảng chứa nhiều JSON object, thường thì ta sẽ xem IP Address, Subnet hoặc ConfigFrom.
$ docker network inspect bridge
		[
			{
				"Name": "bridge",
				"Id": "7b533e835c4ce2e4feb9bd8b5a14ccf560e84c75e25197e1ee190737320d473d",
				"Created": "2021-11-07T10:29:48.850215535+07:00",
				"Scope": "local",
				"Driver": "bridge",
				"EnableIPv6": false,
				"IPAM": {
					"Driver": "default",
					"Options": null,
					"Config": [
						{
							"Subnet": "172.17.0.0/16",
							"Gateway": "172.17.0.1"
						}
					]
				},
				"Internal": false,
				"Attachable": false,
				"Ingress": false,
				"ConfigFrom": {
					"Network": ""
				},
				"ConfigOnly": false,
				"Containers": {
					"a12d9e60bf31a8fa8e5627560b38eeb30b3652a393c51886fcef3a264263015d": {
						"Name": "client",
						"EndpointID": "3b7f9cb790d4b8331b276d7b0844ef3742f91060cd36dab97f8aba255b17fb9e",
						"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": {}
			}
		]
  • Để xem thông tin các plugins (extensions) về network được cài đặt khi bạn cài Docker, ta sử dụng lệnh: docker info. Ta sẽ xem plugín ở 2 mục: mục Client và mục Server. Đấy Docker hoạt động theo mô hình client-server !!!
$ docker info
		Client:
		 Context:    default
		 Debug Mode: false
		 Plugins:
		  app: Docker App (Docker Inc., v0.9.1-beta3)
		  buildx: Build with BuildKit (Docker Inc., v0.6.1-docker)
		
		Server:
		 Containers: 3
		  Running: 2
		  Paused: 0
		  Stopped: 1
		 Images: 12
		 Server Version: 20.10.8
			...
		 Plugins:
		  Volume: local
		  Network: bridge host ipvlan macvlan null overlay
		  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
		 Swarm: inactive
		 ....
		 Labels:
		 Experimental: false
		 Insecure Registries:
		  127.0.0.0/8
		 Live Restore Enabled: false

Mô phỏng kiến trúc client-server bằng Docker Network

Mục tiêu đạt được trong phần này

Trong bài hướng dẫn này ta sẽ làm việc với network trong Docker. Ta sẽ mô phỏng lại kiến trúc client-server dưới đây và đơn giản bằng 3 bước sau:

  • Tạo cầu nối (bridge network) ở đây là đường mạng
  • Tạo endpoint cho client (terminal đang tương tác) và mối liên kết client - bridge
  • Tạo endpoint cho server (là ứng dụng Web đã mở port sẵn) và mối liên kết server - bridge.

Và bây giờ là bắt tay vào làm thôi nào!!!

Lab Time

  • Trước hết ta sẽ dùng câu lệnh để xem các network đang tồn tại trong máy: docker network ls
$ docker network ls
		NETWORK ID     NAME      DRIVER    SCOPE
		7b533e835c4c   bridge    bridge    local
		6ee1c411176c   host      host      local
		98eda4bd78ae   none      null      local
  • Ta sẽ tạo bridge network (cầu nối). bridge có sẵn khi ta cài đặt Docker, và nằm trong Docker Engine. -d ở đây là --driver, chỉ ra rằng sẽ tạo loại kết nối nào nằm trong danh sách network của docker.
$ docker network create -d bridge my-bridge-network
  • Sau đó ta sẽ chạy container liên kết với cầu nối mạng đó với cổng là 8081
$ docker run -d -p 8081:8081 -e "port=8081" --name app --network=my-bridge-network selaworkshops/python-app:2.0
  • Để tìm địa chỉ IP của container app đó, ta cần:
$ docker inspect app

Ta thấy khá là nhiều tham số nhưng ta chỉ việc chú ý đến tham số IPAddress: 172.18.0.2 (mỗi máy sẽ có một địa chỉ IP khác nhau)

  • Chạy alphine container dưới dạng terminal (interactive mode)
$ docker run -it --name client alpine:latest
  • Sau đó ta sẽ cài đặt curl
# apk --no-cache add curl
  • Từ container đang làm việc tên là "client", ta sẽ kết nối tới container tên "app". Ở đây IP Address trong bài mình dùng là 172.18.0.2
# curl <IP Address>:8081 --connect-timeout 5
  • Tuy nhiên ta vẫn không thấy kết nối nào cả, do đã hết thời gian. Lúc này nguyên nhân là chưa có cầu nối nào để kết nối với "app" container.
curl: (28) Connection timeout after 5001 ms
  • Lúc này ta mở terminal mới và gắn cho "client" một cầu nối kết nối tới "app"
$ docker network connect my-bridge-network client
  • Sau đó ta thực hiện lại lệnh trước:
$ curl 172.18.0.2:8081 --connect-timeout 5
		<h1>Python App</h1>/
  • Lúc này ta kiểm tra mối liên kết đó
$ docker inspect my-bridge-network
  • Chú ý phần này, nhận thấy rằng 2 container cùng nằm trên 1 đường mạng là 172.18.0.0/16 và đường mạng này chứa 2 container, chứng tỏ là đã kết nối thành công.
"Containers": {
					"81d83e572df2fdcc8eb5a323b87b6ecd07fb34d3ed1f80c497965324a7bddef8": {
						"Name": "app",
						"EndpointID": "7ca0738981c352ab241fd928b02807f725c0596bd38e56c92ada98d2ffce4b81",
						"MacAddress": "02:42:ac:12:00:02",
						"IPv4Address": "172.18.0.2/16",
						"IPv6Address": ""
					},
					"a12d9e60bf31a8fa8e5627560b38eeb30b3652a393c51886fcef3a264263015d": {
						"Name": "client",
						"EndpointID": "83632bd41e2fad9bc7fdf1de6eb77c604283bd56448bffdc328d104ca2a78347",
						"MacAddress": "02:42:ac:12:00:03",
						"IPv4Address": "172.18.0.3/16",
						"IPv6Address": ""
					}
				},
  • Lúc này ta sẽ ngắt endpoint của cả hai container
$ docker network disconnect my-bridge-network app
		$ docker network disconnect my-bridge-network client
  • Xóa cầu nối mạng này
$ docker network rm my-bridge-network
  • Kiểm tra một lần nữa. Và không thấy sự xuất hiện của my-bridge-network
$ docker network ls
		NETWORK ID     NAME      DRIVER    SCOPE
		7b533e835c4c   bridge    bridge    local
		6ee1c411176c   host      host      local
		98eda4bd78ae   none      null      local

Bridge networking trong Docker

Mục tiêu đạt được trong phần này

  • Hiểu được NAT là gì và kỹ thuật port-mapping
  • Cách kiểm tra kết nối từ host sang container và từ container sang Internet.

Tạo bridge sử dụng tool và kiểm tra kết nối

  • Đầu tiên ta sẽ cài đặt tool có tên là "bridge-utils", sau đó ta sẽ xem các bridge có trong Docker host.
  • Lệnh brctl được sử dụng khi có nhiều mạng Ethernet trên máy chủ và bạn muốn kết hợp lại với nhau. Hay nói là cách khác là brctl là mô phỏng thiết bị switch

Tên bridge name có tên là docker0, ta nhận thấy có xuất hiện interfaces kết nối tới bridge (*)

$ sudo apt-get install bridge-utils
		$ brctl show
		bridge name	bridge id		      STP enabled	interfaces
		docker0		  8000.02427c679daf	no
  • Để xem chi tiết thông tin về docker0 ta dùng lệnh: ip a
$ ip a
		3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
			link/ether 02:42:7c:67:9d:af brd ff:ff:ff:ff:ff:ff
			inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
			   valid_lft forever preferred_lft forever
			inet6 fe80::42:7cff:fe67:9daf/64 scope link 
			   valid_lft forever preferred_lft forever
  • Tiếp đó ta sẽ tiến hành kết nối container. Đầu tiên ta sẽ tạo một container mới. Container này với nhiệm vụ là chạy ngầm
$ docker run -dt ubuntu sleep infinity
		9ed0cb731d5d88d49779ae2031e2f396f71e1fb5920bb7c47fc2237ceb3a1402
  • Sau đó kiểm tra các bridge. So sánh tại dòng (*) ta nhận thấy interface mới là veth93015a2 tức là container vửa mới tạo đã kết nối tới bridge
$ brctl show
		bridge name	  bridge id		       STP enabled	interfaces
		docker0		    8000.02427c679daf	 no		        veth93015a2
  • Sau đó ta sẽ kiểm tra bridge. Lưu ý là container vừa mới tạo có tên là musing_hermann với địa chỉ ip là 172.17.0.2/16.
docker network inspect bridge
		...
		"Containers": {
					"9ed0cb731d5d88d49779ae2031e2f396f71e1fb5920bb7c47fc2237ceb3a1402": {
						"Name": "musing_hermann",
						"EndpointID": "5554d3235a6372c3d638e8f125f97ed71e01c6753d5cd686cf80e100df8d1d7a",
						"MacAddress": "02:42:ac:11:00:02",
						"IPv4Address": "172.17.0.2/16",
						"IPv6Address": ""
					}
		},
  • Sau khi có được địa chỉ IP, bước tiếp theo là kiểm tra kết nối tới container đó với lệnh ping. Để ngắt ping thì ta sẽ ctrl + c. Tại đây thì thông số 0% loss, chứng tỏ là kết nối ổn định.
ping 172.17.0.2
		PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
		64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.177 ms
		64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.051 ms
		64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.072 ms
		64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.061 ms
		64 bytes from 172.17.0.2: icmp_seq=9 ttl=64 time=0.091 ms
		^C
		--- 172.17.0.2 ping statistics ---
		5 packets transmitted, 5 received, 0% packet loss, time 8200ms
		rtt min/avg/max/mdev = 0.045/0.088/0.177/0.037 ms
  • Lúc này ta vào trong container có tên là frosty_wilbur để tương tác với terminal. Sau đó ta sẽ cài đặt ping.
$ docker exec -it musing_hermann /bin/bash
		root@9ed0cb731d5d:/#
		root@9ed0cb731d5d:/# apt-get update && apt-get install -y iputils-ping
  • Bước tiếp theo là ta ping tới trang hcmus.edu.vn. Ta thấy được là container đã ping Internet và có cấu hình đúng. Cuối cùng ta sẽ thoát ra bằng lệnh exit.
root@9ed0cb731d5d:# ping -c5 hcmus.edu.vn
		PING hcmus.edu.vn (14.241.254.131) 56(84) bytes of data.
		64 bytes from 14.241.254.131: icmp_seq=1 ttl=56 time=7.67 ms
		64 bytes from 14.241.254.131: icmp_seq=2 ttl=56 time=6.84 ms
		64 bytes from 14.241.254.131: icmp_seq=3 ttl=56 time=8.73 ms
		64 bytes from 14.241.254.131: icmp_seq=4 ttl=56 time=6.85 ms
		64 bytes from 14.241.254.131: icmp_seq=5 ttl=56 time=8.66 ms
		
		--- hcmus.edu.vn ping statistics ---
		5 packets transmitted, 5 received, 0% packet loss, time 4006ms
		rtt min/avg/max/mdev = 6.848/7.755/8.735/0.827 ms

Cấu hình dịch vụ NAT

  • NAT là một dịch vụ cho phép chuyển đổi từ một địa chỉ IP này thành một địa chỉ IP khác. Thông thường, NAT được dùng phổ biến trong mạng sử dụng địa chỉ cục bộ, cần truy cập đến mạng Internet.
  • Tại bước này ta sẽ cấu hình NGINX container và map port 8080 của Docker host tới port 80 bên trong container. Điều này đồng nghĩa với việc khi kết nối tới Docker host tại cổng 8080 sẽ được map sang cổng 80 trong container.
  • Chạy container tên là web1 với interface ngoài là 8080 và bên trong container là 80
$ docker run --name web1 -d -p 8080:80 nginx
		Unable to find image 'nginx:latest' locally
		latest: Pulling from library/nginx
		b380bbd43752: Pull complete 
		fca7e12d1754: Pull complete 
		745ab57616cb: Pull complete 
		a4723e260b6f: Pull complete 
		1c84ebdff681: Pull complete 
		858292fd2e56: Pull complete 
		Digest: sha256:644a70516a26004c97d0d85c7fe1d0c3a67ea8ab7ddf4aff193d9f301670cf36
		Status: Downloaded newer image for nginx:latest
		c023f449a8ac32555d53b09fcf1897daf1ca25e0d0c442b3c672b974652742c1
  • Sau đó, ta sẽ check loại container. Ở đây, ta nhận thấy web1 container chạy NGINX. Ta nhận thấy đã 0.0.0.0:8080->80/tcp là tất cả địa chỉ IPv4 cục bộ với cổng 8080 ánh xạ tới cổng 80.
docker ps
		CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                                   NAMES
		c023f449a8ac   nginx          "/docker-entrypoint.…"   2 minutes ago    Up 2 minutes    0.0.0.0:8080->80/tcp, :::8080->80/tcp
  • Lúc này ta sẽ vào trang web có địa chỉ IP: <port>. Để lấy được địa chỉ IP ta cần tìm địa chỉ IP của Docker host đó. Có thể bạn tìm ifconfig và chọn 1 địa chỉ bất kì trong đó, miễn ta có thể vào được. Hình dưới đây là kết quả khi vào được.
  • Lưu ý là chỉ vào đúng port của nó là 8080 như ta đã đề ra

Resources

Reference

Kubernetes Cheat Sheet

Online Courses

Exercises

Docker CLI

  1. Bạn hãy dùng kiến thức đã học, tìm hiểu xem lệnh docker volume có những flag nào và chức năng của chúng là gì?
  1. Hãy chạy 1 container ubuntu ở chế độ chạy nền và kiểm tra các thông tin sau:
    • REPOSITORY
    • TAG
    • IMAGE ID ở dạng đầy đủ
    • SIZE
  1. Hãy dừng container Ubuntu đang chạy
  1. Hãy xoá tất cả image trong máy bằng 1 câu lệnh duy nhất

Dockerfile

Việc viết hướng dẫn cài đặt trong README là điều nên làm, và quan trọng!!! Khi được/bị điều sang dự án mới với ngôn ngữ mà mình chưa từng biết, việc đầu tiên ta cần tìm hiểu là đọc kỹ README, search google và setup theo hướng dẫn!!!

Trong bài tập về Dockerfile ta sẽ tiến hành Dockerize hóa project có sẵn:

  • Sau đó đọc kỹ những yêu cầu trong README (lưu ý không đọc phần connect to backend)
  • Viết Dockerfile và chạy container với port 5000. Lưu ý là nếu chạy container thì app sẽ bắt đầu kết nối (điều này tốn một vài giây).
  • Lưu ý là có một số thao tác ta không cần phải chạy!!! Những lệnh nào mà README đề cập không cần chạy ??

Docker Compose

Bạn hãy tạo 1 compose chạy wordpress có thể được truy cập từ trình duyệt bằng địa chỉlocalhost:8000 với các thông tin sau:

  • File docker-compose.yaml có các dòng đầu như sau:
version: "3.9"
		volumes:
		  db_data: {}
		  wordpress_data: {}
		services:
  • Gồm 2 container
  • Container chứa database gồm các thông tin sau:
    • Được tạo từ image mysql:5.7
    • Volume db_data được ánh xạ đến thư mục /var/lib/mysql của container
    • Port hoạt động mặc định trên container là 3306
    • Các biến môi trường với giá trị tuỳ chỉnh
      • MYSQL_ROOT_PASSWORD
      • MYSQL_DATABASE (tên database)
      • MYSQL_USER
      • MYSQL_PASSWORD
  • Container chứa wordpress gồm các thông tin sau:
    • Được tạo từ wordpress:latest
    • Phụ thuộc vào container chứa database
    • Port hoạt động mặc định trên container là 80
    • Các biến môi trường cần thiết:
      • WORDPRESS_DB_HOST: <tên container database> : <port hoạt động>
      • WORDPRESS_DB_USER
      • WORDPRESS_DB_PASSWORD
      • WORDPRESS_DB_NAME

Docker Network

Vì Docker Network trong bài tutorial khá là đơn giản (mức độ Beginner và Intermediate).

Do đó, bạn nên tự mô phỏng kiến trúc client-server và tự cấu hình NAT với Docker trong bài tutorial.