Skip to main content

3.5. Operations Deployment: Container với Docker và máy ảo với KVM

Mục lục​


1. Vì sao dùng container?​

Dựng một database server theo cách cổ điển sẽ ra một hình dung rõ về chỗ container có ích. Cài nó bằng:

sudo apt install mariadb-server

Rồi cấu hình các thiết lập bằng cách sửa file trong thư mục /etc/mysql. Rồi tạo vài database, cấu hình database user, gán privilege và nhiều việc khác.

Giả sử setup đó đã ổn và giờ cần chuyển sang một máy mạnh hơn, ở đâu đó trên cloud. Rắc rối là các thành phần của nó nằm rải rác khắp nơi: thiết lập của MariaDB ở một thư mục, database ở một thư mục khác, log ở thư mục khác nữa. Gom hết và di dời được đống đó là một việc nhức đầu.

Nhưng nếu setup MariaDB đó được tạo trong một Docker container thì mọi thứ tồn tại bên trong container đó: daemon, file cấu hình, log, và cả database. Việc duy nhất phải làm là copy container này sang một máy khác, và mọi thứ chạy y như trước.

💡 Hình dung: container giống một chiếc vali đã đóng sẵn: toàn bộ đồ cho chuyến đi nằm trong đó, xách lên là đi. Cách cài cổ điển thì giống đồ đạc bày rải khắp nhà, mỗi lần chuyển nhà lại phải đi gom từng món, và luôn sót một vài thứ.

Container bao gói ứng dụng lại, nhờ vậy nó di động và dễ mang đi, dễ như copy rồi paste một file từ chỗ này sang chỗ khác. Khi đã hài lòng với một ứng dụng đã container hóa, triển khai nó ra hàng chục server khác nhau ở quy mô lớn là việc làm được, cũng chính là việc Kubernetes làm, và là một phần lý do Kubernetes phổ biến như vậy.

Phần dưới đây đi theo đúng phạm vi cần cho kỳ thi LFCS: chạy một web server NGINX trong container. Ai cần đi sâu hơn về Docker thì có loạt bài riêng, bắt đầu từ Docker Images và Docker Containers.

Một ghi chú trước khi vào thực hành. Bình thường thì chạy được lệnh Docker không cần sudo nếu user Linux của mình thuộc một group tên docker. Trong một số môi trường sẽ gặp lỗi permission denied khi chạy lệnh Docker. Gặp vấn đề đó thì thêm sudo phía trước các lệnh Docker, hoặc nếu được thì thêm user Linux của mình vào group docker, log out rồi log in lại.

The same MariaDB setup, two waysInstalled the classic wayetc mysql holds settingsanother dir holds databasesa third dir holds the logsmoving all this to a new boxmeans hunting every pieceInside a Docker containerone container holdsthe daemonthe configuration filesthe logsthe databasescopy the container and itworks the same elsewhereContainers encapsulate the application, which makes it portable.

2. Image, tag và registry​

Giả sử mới bắt đầu với Docker và chưa biết có những lệnh nào. Tổng quan nhanh về cách dùng utility Docker lấy bằng:

docker --help

Trong danh sách đó có subcommand search, và nó giúp tìm image NGINX cần dùng:

docker search nginx

Kết quả ra rất nhiều. Một image cung cấp mọi thứ cần thiết để tạo và khởi chạy một container; ở đây nó gồm daemon NGINX, các software library mà nó cần, file cấu hình, script và nhiều thứ khác.

Image đầu tiên trong danh sách là kết quả khớp nhất cho trường hợp này: nhiều người đã thêm nó vào danh sách yêu thích nên nó có rất nhiều star, và nó cũng được đánh dấu là official image. Giờ tải image đó về máy, dùng đúng tên ở cột Name trong danh sách kết quả:

docker pull nginx
docker pull ubuntu/nginx

Dòng thứ hai tải một image khác ở dưới trong danh sách, image từ Ubuntu. Ở cả hai dòng đều thấy phần nginx:latest. Phần cuối, latest, gọi là image tag.

💡 Hình dung: tag giống nhãn size trên áo thun. Trong cửa hàng, cùng một mẫu áo có nhiều size nên nhãn ghi S, M hay L. Container image cũng vậy: image NGINX có nhiều biến thể, vẫn là NGINX nhưng nhiều hình dạng và kích thước khác nhau.

Ví dụ có nhiều phiên bản cho bản build NGINX. Tag latest trỏ tới phiên bản mới nhất có sẵn, còn tag kiểu 1.24.0 trỏ tới phiên bản 1.24.0. Khai đúng tag là chọn được chính xác bản build NGINX muốn dùng. Cú pháp dùng tag trong lệnh:

docker pull nginx:1.22.1

Không khai tag như hai lệnh trước thì Docker mặc định là lấy phiên bản mới nhất, chính xác hơn là lấy image được tag là latest.

Xóa image​

Tới đây đã có vài image không cần nữa. Trước khi xóa thì xem đang có những image nào:

docker images

Để xóa image ubuntu/nginx, dùng lệnh rmi, tức Remove Image:

docker rmi ubuntu/nginx
docker images

Còn lại hai image. Để xóa image cuối thì không đưa riêng tên của nó được, vì cả hai cùng tên nginx và Docker không biết phải xóa cái nào. Vậy phải khai cả tag:

docker rmi nginx:1.22.1

3. Tạo và chạy container​

Tạo và chạy container khá dễ, với lệnh docker run kèm tên image, còn tag thì tùy chọn:

docker run nginx

Lệnh này tạo container và cũng chạy daemon NGINX bên trong nó. Nhưng để ý vấn đề: terminal giờ bị kẹt trong container này. Các thông báo trạng thái sinh ra trong container thì thấy được, nhưng không gõ lệnh khác được nữa. Đây là một chỗ mắc khá thường gặp với người mới dùng Docker, trước khi họ quen với workflow.

Nhấn Ctrl-C để ra khỏi môi trường của container này. Nhưng Ctrl-C không phải là detach: nó gửi tín hiệu vào tiến trình đang chạy trong container, nên container bị stop theo.

Tổ hợp để detach thật mà vẫn giữ container chạy là Ctrl-P rồi Ctrl-Q, và nó chỉ hoạt động khi container được khởi động với cả -i và -t. Bài giảng gốc gọi Ctrl-C là detach rồi nhận xét rằng nó stop luôn container; đúng hệ quả nhưng sai tên thao tác. Cách sạch nhất vẫn là --detach ngay từ đầu, như phần dưới.

Tạo và chạy container khác, lần này làm cho đúng, với vài option:

docker run --detach --publish 8080:80 --name myWebServer nginx

Option --detach làm Docker detach khỏi container, nên terminal không bị kẹt trong đó nữa. Option --publish 8080:80 làm daemon NGINX chạy trong container này truy cập được từ bên ngoài. Tham số 8080:80 thoạt nhìn hơi khó hiểu, nhưng nó bảo Docker chuyển hướng network traffic: bất cứ gì đi vào port 8080 trên máy này đều được chuyển sang port 80 bên trong container. Cách nhớ: đọc từ trái sang phải, đi từ máy thật vào container.

Option cuối là --name, chọn một cái tên cho container. Không khai thì Docker tự sinh tên cho mình, kiểu relaxed_lederberg, và loại tên đó làm việc nhận ra container nào là container nào khó hơn khi có nhiều container chạy trên hệ thống. Cuối cùng, ở cuối lệnh là image muốn dùng, ở đây là nginx. Chạy lệnh thì Docker sinh ra một định danh duy nhất cho container này, chính là thứ thấy trong output.

Liệt kê, start và stop container​

docker ps

Nhưng container đầu tiên, cái bị ép thoát bằng Ctrl-C, lại không có trong danh sách. Lý do: docker ps chỉ liệt kê container đang chạy, không liệt kê container đã stop. Trợ giúp của chính lệnh đó cho biết cách liệt kê tất cả:

docker ps --help
docker ps --all

Option -a, tên dài --all, là thứ cần tìm. Giờ danh sách có cả container đang chạy và vài container không thành công. Cột STATUS cho thấy một cái đang Up, tức đang chạy, còn những cái khác có status Exited, tức đã stop.

Để khởi động lại container cũ, dùng docker start, theo sau là tên container ở cột cuối hoặc container ID ở cột đầu:

docker start <container-ID>
docker ps

Giờ cả hai đều đang chạy. Ở cột Ports có một cái ghi 80/tcp, và đó là container không được publish port, nên web server NGINX trong nó không truy cập được dễ dàng từ ngoài. Stop nó lại bằng docker stop kèm tên container:

docker stop <container-name>

Nhắc tới port đã publish, thử kiểm tra xem port 8080 có được chuyển sang port 80 trên container hay không, bằng lệnh nc, tức netcat:

nc localhost 8080
GET /

Lệnh nc kết nối tới port 8080 trên máy local, và Docker sẽ chuyển hướng sang port 80 trên container, nên về mặt thực tế là đã kết nối tới NGINX chạy trong đó. Gõ GET / để gửi lệnh GET tới web server, và nó trả về trang index ở dạng text. Output đó xác nhận NGINX trong container chạy đúng và port đã được publish ra host. Nhấn Ctrl-D để thoát session netcat.

docker run so với docker start​

Với người mới học Docker, khác biệt giữa docker run và docker start hơi khó phân biệt, vì cả hai trông như đều chỉ khởi động container. Nhưng docker run thực chất là build một container từ một image, cấu hình nó theo một cách nhất định, rồi sau đó mới khởi động nó. docker start không build gì cả; nó chỉ khởi động một container đã được tạo trước đó bởi lệnh docker run ban đầu.

Cách nhớ khác biệt này: nếu container chưa tồn tại thì cần docker run để khởi tạo một cái; nếu container đã tồn tại thì cần docker start.

Xóa container và image​

Liệt kê lại toàn bộ container, kể cả cái đã stop, rồi xóa container đã stop bằng docker rm:

docker ps --all
docker rm <container-name>

rm viết tắt của remove, và đừng lẫn nó với lệnh docker rmi ở mục trước, lệnh xóa image chứ không xóa container.

Nhắc tới image thì có một trường hợp biên hay gặp trong thực tế. Giả sử cần xóa image nginx:

docker rmi nginx

# Output (minh họa):
Error response from daemon: conflict: unable to remove repository reference "nginx" (must force) - container 3f2a1b is using its referenced image

Image này không xóa được vì vẫn còn một container đang dùng nó. Phải xóa container, hoặc các container, đang phụ thuộc nó trước. Ở đây là container tên myWebServer đã tạo ở trên:

docker rm myWebServer

# Output (minh họa):
Error response from daemon: cannot remove container "myWebServer": container is running: stop the container before removing

Lại lỗi nữa: không xóa được container đang chạy. Giờ thì đã rõ cả bức tranh. Để xóa image thì phải đi qua đủ các bước sau:

docker stop myWebServer
docker rm myWebServer
docker rmi nginx
docker images

Đầu tiên stop container phụ thuộc image nginx, rồi xóa container, rồi mới xóa image. Chạy docker images ở cuối sẽ thấy trống, xác nhận image đã bị xóa.

Docker tự pull image, và restart policy​

Ở phần trên, image được pull trước rồi mới chạy container dựa trên image đó. Nhưng Docker tự lo được việc này: chạy docker run với một image chưa có trên máy thì Docker tự động pull image cần thiết để khởi động container, không cần pull tay trước.

Giờ thì vấn đề với container NGINX hiện tại là gì? Muốn nó làm việc như một web server thì phải đảm bảo nó luôn sẵn sàng. Ở kịch bản hiện tại, container gặp lỗi là nó vào trạng thái stopped, và tệ hơn, reboot server một cái là phải đăng nhập vào khởi động container bằng tay mỗi lần.

Cách bảo Docker luôn giữ container này chạy nằm trong manual:

man docker run

Gõ dấu / để bắt đầu tìm, nhập restart policy rồi xem đoạn đó. Trong đó có một option đáng chú ý với giá trị always. Nhấn q để thoát manual, rồi chèn option đó vào lệnh docker run trước đây:

docker run --detach --publish 8080:80 --name myWebServer --restart always nginx

Container NGINX này sẽ luôn restart khi gặp sự cố, và cũng khởi động mỗi lần hệ thống boot. Vì image nginx:latest không còn trên máy, chú ý là Docker tự động ra ngoài tải nó về.

docker run builds it, docker start only wakes itnginx imagepulled from a registrydocker runcontainer createdand then startedrunsstatus Updocker ps shows itdocker stopstatus Exitedonly ps --all shows itdocker startnothing is rebuilt--publish 8080:80 reads left to right, host then containertraffic arriving on host port 8080 is redirected to port 80 inside the container--detach frees the terminal. --restart always survives a reboot.

4. Build image riêng bằng Dockerfile​

Tới đây mọi thứ đều dùng container image có sẵn công khai do người khác build. Nhưng trong thực tế, các image chung chung đó không đủ: hoặc phải sửa chúng cho khớp nhu cầu riêng, hoặc phải tự build image của mình.

Image build được từ đầu, nhưng cho dễ thì lấy image nginx làm nền rồi xây lên trên. Việc đầu tiên là tạo một thư mục cho project nhỏ này, đặt tên myimage:

mkdir myimage
cd myimage

Ở đây thêm được mọi file muốn đưa vào image cuối cùng. Với mục đích demo thì chỉ cần một file:

vim index.html

Thêm vào đó một dòng text đơn giản. Nó không phải HTML hợp lệ, nhưng đủ cho demo, nên lưu index.html lại rồi đi tiếp.

Đã có đủ file cần thiết, bước tiếp theo là nói cho Docker biết muốn build image này thế nào. Các chỉ dẫn phải nằm trong một file có tên rất cụ thể là Dockerfile, với chữ D viết hoa:

vim Dockerfile
FROM nginx
COPY index.html /usr/share/nginx/html/index.html

Dòng đầu chỉ thị Docker dùng NGINX làm base image, tức điểm khởi đầu, và các thay đổi mong muốn sẽ được thêm lên trên image NGINX đó. Dòng sau bảo Docker copy một file từ vị trí này sang vị trí khác: cụ thể là index.html từ thư mục myimage hiện tại sẽ được copy sang /usr/share/nginx/html/index.html bên trong container. Việc này thay thế file có sẵn ở vị trí đó trong container bằng file tự tạo, và Dockerfile được lưu lại bằng Escape rồi :wq.

File này rõ ràng là rất đơn giản với các chỉ dẫn rất đơn giản. Những chỉ dẫn phổ biến khác gồm:

  • RUN để nói cho Docker biết các lệnh nào cần chạy để chuẩn bị image này, ví dụ chỉ thị Docker cài thêm vài utility vào image.
  • CMD và ENTRYPOINT để nói cho Docker biết lệnh nào cần chạy bên trong container này khi nó khởi động, cùng các tham số lệnh có thể cần tới. Khác biệt giữa hai cái: lệnh khởi động khai ở CMD là tùy chọn, nó có thể bị bỏ qua trong một số trường hợp, ví dụ khi người dùng đưa ra một cách khởi động container khác trong lệnh docker run. Còn lệnh khai ở ENTRYPOINT thì không phải tùy chọn, nó sẽ được thực thi khi container được khởi tạo.

Lệnh build​

Quay về home directory rồi build image. Lệnh thông dụng là docker build --tag, theo sau là repository, tên image và tag, rồi cuối cùng là đường dẫn tới thư mục chứa Dockerfile:

cd
docker build --tag jeremy/customnginx:1.0 myimage

Ở đây jeremy là tên repository. Nếu muốn publish image này lên Docker Hub và username ở đó là jeremy, thì mọi project sẽ được lưu dưới repository tên jeremy; có thể coi nó như thư mục cá nhân của mình trên Docker Hub. customnginx là tên tự chọn cho image, và 1.0 là tag cho phiên bản 1.0 của image này. Cuối cùng, myimage là thư mục chứa file project và Dockerfile.

Cần nhắc nhanh về cách dùng lệnh thì luôn có man docker build, cuộn xuống mục examples. Tên repository cũng bỏ đi được nếu không cần tới:

docker build --tag custom-nginx:1.0 myimage

5. Máy ảo với QEMU-KVM​

Trong software, tạo được một máy tính ảo bên trong một máy tính thật. Những máy mô phỏng này gọi là virtual machine, và chúng rất hữu ích trên server.

Hình dung một server mạnh với 64 CPU core và 1024 gigabyte RAM. Tạo 32 virtual machine trên đó thì mỗi máy được 2 virtual CPU, cũng gọi là vCPU, và 32 gigabyte RAM. Vậy thay vì cho thuê một server vật lý lớn cho đúng một khách, có thể cho thuê 32 server ảo nhỏ hơn cho 32 khách. Các công ty cloud compute như DigitalOcean, Amazon Web Services và Google Cloud bán một phần dịch vụ của họ cho hàng nghìn khách hàng theo đúng cách này.

Có nhiều giải pháp software để dựng loại setup này, nhưng trên Linux thì phổ biến nhất ở thời điểm hiện tại là QEMU-KVM. Đây thực chất là hai phần mềm nối với nhau. Tên QEMU đến từ quick emulator, phần này emulate, tức mô phỏng, một máy tính ảo. KVM đến từ Kernel-based Virtual Machine, đây là phần code nằm trong kernel Linux, dùng công nghệ phần cứng để tăng tốc máy ảo.

Phần dưới đây tập trung vào một tool tên virsh. Đây là chương trình cho phép quản lý máy ảo từ command line. Ai từng dùng VirtualBox thì quen với giao diện đồ họa, bấm nút để tạo máy ảo, cấu hình nó, khởi động nó. virsh làm đúng những việc đó nhưng qua câu lệnh thay vì qua giao diện đồ họa.

Để bắt đầu nhanh, cài package software virt-manager:

sudo apt install virt-manager

Bình thường virt-manager dành cho máy Linux có giao diện đồ họa. Nhưng nó cài được cả trên máy Linux chỉ có giao diện text. Bản thân ứng dụng virt-manager thì không cần dùng tới; cài nó chỉ để package manager kéo vào rất nhiều package dependency khác, trong đó có các utility cần cho phần máy ảo.

Tiếp tục với demo. Tạo một file cấu hình:

vim testmachine.xml

Nội dung file này giúp tạo một máy ảo tên testmachine, được virtualize qua QEMU, với 1 gigabyte RAM, 1 virtual CPU, và kiến trúc 64-bit. Trong file có giá trị hvm, viết tắt của hardware virtual machine.

Đây là một ví dụ làm cho nhanh, nên bước qua khá vội. Một máy ảo thật còn cần thêm storage, network card, một hệ điều hành và nhiều thứ khác nữa. Để tạo máy ảo này, đưa file định nghĩa vào cho virsh:

virsh define testmachine.xml

6. Quản lý domain bằng virsh​

Có thể đào sâu hơn về lệnh này. Trước hết, virsh có một trang trợ giúp rất gọn gàng:

virsh help

Nội dung khá nhiều, nhưng các lệnh được nhóm lại đẹp. Cuộn lên sẽ thấy nhóm domain management. Trong nhóm đó có phần lớn các lệnh cần quan tâm. Một domain về cơ bản là một máy ảo, nên các lệnh ở đây dùng để tương tác với máy ảo.

Trước tiên cần xem các domain đang có:

virsh list
virsh list --all

Dòng đầu không thấy dấu vết của thứ vừa định nghĩa, vì nó chỉ hiện các domain đang active, còn cái vừa định nghĩa thì chưa active. Thêm --all vào cuối là thấy domain máy ảo tên testmachine, và thấy nó đang ở trạng thái shut off.

Khởi động, reboot và tắt​

virsh start testmachine
virsh list

Nếu tên máy có khoảng trắng thì bọc nó trong cặp nháy kép. Để reboot máy ảo:

virsh reboot testmachine

Lệnh này gửi một tín hiệu reboot tới hệ điều hành đang chạy trong máy ảo, nên việc reboot diễn ra nhẹ nhàng: process được đóng bình thường, ứng dụng có cơ hội lưu dữ liệu. Giống như khởi động lại một máy tính thật. Nhưng nếu một máy không phản hồi và bị treo vì lý do nào đó thì có thể force-reset nó, tương đương việc bấm nút reset vật lý, hoặc rút dây nguồn ra rồi cắm lại.

Để tắt máy ảo theo cách bình thường:

virsh shutdown testmachine

Lệnh này gửi tín hiệu shutdown tới hệ điều hành bên trong máy ảo. Giống việc reboot, nó thực hiện shutdown nhẹ nhàng và cho ứng dụng đủ thời gian đóng lại bình thường và lưu dữ liệu. Nhưng máy ảo đơn giản trong demo này không có hệ điều hành, nên lệnh sẽ không hoạt động như trông đợi; trong một setup thật có hệ điều hành thì nó hoạt động bình thường.

Nếu máy ảo bị kẹt thì có thể ép tắt nguồn cứng:

virsh destroy testmachine
virsh list --all

Phần lớn lệnh virsh đều dễ đoán, nhưng lệnh này là ngoại lệ. Tên nó nghe như đang phá hủy hay xóa máy đi, nhưng không phải. Lệnh này tương đương việc rút nguồn khỏi máy tính ở nhà rồi không cắm lại.

Xóa domain​

Để xóa hẳn một domain hay máy ảo, phải tắt nguồn nó rồi dùng lệnh:

virsh undefine testmachine

Lệnh trên xóa máy ảo đã định nghĩa, nhưng không xóa dữ liệu đã lưu, ví dụ các virtual disk của nó. Thường thì giữ lại dữ liệu là điều mong muốn. Nhưng nếu lần này muốn xóa luôn cả dữ liệu thì tra các use case nâng cao của từng lệnh bằng virsh help kèm tên lệnh:

virsh help undefine

Trong đó thấy có option xóa toàn bộ dữ liệu liên quan tới domain. Chạy lệnh với option đó rồi kiểm tra lại là thấy mọi thứ đã biến mất.

Autostart và tài nguyên của domain​

Khi server thật khởi động, các máy ảo trên nó phải được khởi động bằng tay. Muốn một máy ảo tự khởi động mỗi lần máy thật, tức server, boot lên, và muốn tắt hành vi đó đi:

virsh autostart testmachine
virsh autostart --disable testmachine

Để xem các tài nguyên ảo đã gán cho một máy ảo, dùng dominfo, viết tắt của domain info:

virsh dominfo testmachine

# Output (minh họa):
Id: 3
Name: testmachine
State: running
CPU(s): 1
Max memory: 1048576 KiB
Used memory: 1048576 KiB

Chỉ một CPU core. Giả sử cần nâng lên hai CPU core. Lệnh này cần tra cứu chút, bắt đầu bằng cách gõ virsh set rồi nhấn Tab vài lần để lấy gợi ý, sẽ nhớ ra đúng lệnh là virsh setvcpus. Chú ý là setvcpus có chữ s ở cuối, không phải setvcpu. Để biết cách dùng:

virsh help setvcpus

Cú pháp là setvcpus domain count kèm option --maximum. Và option --config đặt số vCPU cho lần boot kế tiếp của máy:

virsh setvcpus testmachine 2 --config

# Output (minh họa):
error: invalid argument: requested vcpus is greater than max allowable vcpus for the live domain: 2 > 1

Lỗi cho biết maximum đang được đặt là 1. Nên phải đổi giá trị đó trước:

virsh setvcpus testmachine 2 --config --maximum
virsh setvcpus testmachine 2 --config

Dòng đầu đặt số vCPU tối đa cho máy ảo này thành hai core. Dòng sau mới là dòng gán hai virtual core vào máy. Nhớ rằng việc này không đổi vCPU khi máy ảo đang chạy, nên phải tắt nguồn nó đi. Ở trường hợp demo không có hệ điều hành bên trong thì ép tắt nguồn, rồi khởi động lại để thay đổi có hiệu lực:

virsh destroy testmachine
virsh start testmachine
virsh dominfo testmachine

Kiểm tra lại domain info là thấy nó đã có tác dụng.

Để đổi dung lượng bộ nhớ thì trình tự tương tự, chạy theo hai bước: trước hết đổi giá trị maximum thành 2048 megabyte, rồi đặt mức cấp phát bộ nhớ thành 2048 megabyte. Sau đó shutdown máy nhẹ nhàng, hoặc ép tắt nguồn nếu cần, rồi khởi động lại. dominfo sẽ cho thấy các thay đổi đã có hiệu lực.

virsh domain lifecycletestmachine.xmlthe definition filedefineshut offonly list --all shows itstartrunningplain list shows itshutdowndestroyforced offlike pulling thepower cord outundefinedeletes the domaindata kept unless askedautostart and autostart --disable control boot behaviourdominfo reads vCPUs and memory, setvcpus needs --config and --maximumdestroy deletes nothing. undefine is the one that deletes.

7. Máy ảo có OS thật với virt-install​

Máy ảo ở mục trên rất tối giản: nó không có virtual disk nào gắn vào nên không chứa được hệ điều hành. Nó phục vụ tốt mục đích minh họa cách quản lý bằng lệnh virsh, nhưng giờ cần một máy ảo hoàn chỉnh hơn, có hệ điều hành chạy bên trong.

Cách làm ở đây dùng cloud image. Tải về một disk image, ví dụ Ubuntu 24.04, bản stable mới nhất của Ubuntu ở thời điểm ghi bài giảng nguồn. File đó gồm một hệ điều hành Ubuntu đã được cấu hình sẵn để dùng trong môi trường cloud; Google Cloud dùng những image tương tự khi khởi tạo một virtual private server trên nền tảng của họ.

Image này kiểm tra được bằng QEMU Disk Utility:

qemu-img info <tên-image>

# Output (minh họa):
image: ubuntu-24.04-server-cloudimg-amd64.img
file format: qcow2
virtual size: 3.5 GiB
disk size: 585 MiB

Điểm đáng chú ý ở đây là virtual size 3.5 gigabyte. Khi image này được gắn vào một máy ảo, nó đóng vai trò virtual disk, và virtual size cho biết disk đó được cấu hình với tổng dung lượng 3.5 gigabyte. Trong nhiều trường hợp thì như vậy không đủ: cần cài thêm software vào máy ảo nên cần virtual disk lớn hơn. Dung lượng virtual disk này mở rộng lên khoảng 10 gigabyte được bằng một lệnh nữa của cùng utility QEMU đó, và kiểm tra lại file image sau đó sẽ thấy virtual disk size đã được cập nhật.

Storage pool​

Các tool virtualization cần truy cập vào thứ chúng gọi là storage pool. Trong pool này chúng lưu disk image, snapshot và dữ liệu khác. Theo mặc định, chúng dùng thư mục /var/lib/libvirt cho việc này. Tạo thêm storage pool khác được nếu muốn, nhưng đường đơn giản là dùng luôn vị trí mặc định:

ls /var/lib/libvirt

Trong thư mục đó có một subdirectory images. Copy file disk image vào subdirectory đó.

Tìm option của virt-install​

Giờ tới việc phức tạp đầu tiên: dùng lệnh gì để tạo một máy ảo và bảo nó dùng file image này làm virtual disk? Việc đó dùng utility virt-install. Nhưng utility này hỗ trợ hàng trăm tổ hợp command-line option, nên tìm ra đúng các option cần cho từng mục đích khá rối. Cách định hướng nhẹ nhàng cho người mới hoàn toàn là như sau.

Bắt đầu bằng cách gõ thẳng lệnh rồi nhấn Enter:

virt-install

Nó báo lỗi, nhưng lỗi đó giúp phát hiện các option đầu tiên cần tới: output cho biết virt-install cần biết loại hệ điều hành dự định dùng trên máy ảo, và nó còn đưa luôn ví dụ về lệnh dùng để liệt kê các hệ điều hành được hỗ trợ:

virt-install --osinfo list

Trong danh sách đó có cả một loạt distribution của Linux, gồm cả bản 24.04. Tiếp theo, lấy thêm trợ giúp từ manual:

man virt-install

Trong đó có rất nhiều option về cách kết nối, cách đặt bộ nhớ, và đủ loại cấu hình khác. Option cần để ý là --import. Option này bảo utility bỏ qua quá trình cài hệ điều hành, đúng thứ cần ở đây, vì bình thường virt-install sẽ cố cài thật một hệ điều hành lên trên disk image; đó là lý do utility này tên là virt-install, chế độ hoạt động mặc định của nó là cài. Với --import, nó được bảo bỏ qua bước đó, vì disk image ở đây đã có Ubuntu cài sẵn. Cũng theo manual, option này bảo virt-install đặt image làm thiết bị dùng để boot máy ảo này.

Thoát manual rồi dùng thêm một mẹo nữa để tìm hết các option cần thiết. Lần này dạng trợ giúp cô đọng hơn sẽ hữu ích:

virt-install --help

Giờ bắt đầu chọn lọc các option cần dùng. Cuộn lên sẽ thấy ngay ba cái đáng quan tâm đầu tiên là --name, --memory và --vcpus. Với chúng, đặt được tên cho máy ảo để dễ nhận ra về sau, đặt được dung lượng bộ nhớ cấp cho máy này theo megabyte (1024 megabyte tương đương 1 gigabyte), và chọn được số virtual CPU core muốn cấp cho máy ảo.


8. Cài OS từ ISO hoặc từ network​

Mục trước làm việc với cloud image đã có hệ điều hành cài sẵn nên không phải cài gì. Một kịch bản khác là khi muốn tự tay cài một hệ điều hành, tức không dùng disk image cấu hình sẵn mà muốn bắt đầu từ đầu và tạo một bản cài sạch.

Trường hợp đó thì tạo một máy ảo, gắn vào nó một virtual CD-ROM hoặc DVD-ROM, và tạo một disk image trống. Sau đó virt-install boot từ CD-ROM hoặc DVD-ROM và cài hệ điều hành lên trên disk image đó.

Có vài khác biệt quan trọng trong lệnh cho mục đích này.

Thứ nhất, không dùng option --import nữa. Không có disk image dựng sẵn, không có hệ điều hành cài sẵn, nên không có gì để import.

Thứ hai, option --disk không trỏ tới đường dẫn của một file image nữa, vì không có file nào. Thay vào đó dùng size=10 để bảo virt-install tạo một image mới với 10 gigabyte dung lượng.

Thứ ba, option --location trỏ tới một file ISO, tức file image của một đĩa cài CD hoặc DVD-ROM. Và vẫn dùng --graphics none như trước.

Cuối cùng, ở cuối lệnh cần thêm một option nữa:

--extra-args console=ttyS0

Với option này, kernel Linux boot trong máy ảo được bảo expose ra thứ gọi là serial console, được biểu diễn bằng ttyS0 ở đây. Về bản chất, nó cho một đường để kết nối vào máy ảo đó và tương tác với nó trong môi trường chỉ có text. Không có option này thì máy ảo boot theo cách khiến không có đường nào kết nối vào console nếu hệ thống không có giao diện đồ họa.

Lấy file cài trực tiếp từ internet​

Lệnh ở trên không được chạy trong bài giảng nguồn vì không có file ISO cài Debian 12 ở đó, và cũng không cần tải một file như vậy về, bởi có cách hiệu quả hơn để làm việc này: bảo virt-install lấy các file nó cần trực tiếp từ internet, và nó chỉ tải về đúng những gì cần, nhờ vậy giảm thời gian cho cả quá trình.

Việc duy nhất phải làm là sửa tham số của option --location. Thay vì trỏ tới một file ISO, trỏ nó tới một địa chỉ web. Địa chỉ web đó phải cung cấp một cấu trúc thư mục và file nhất định mà virt-install hiểu và dùng được; mở địa chỉ đó trên web browser là xem được cấu trúc file và thư mục ấy.

Đổi tham số --location từ đường dẫn ISO sang địa chỉ web rồi nhấn Enter, sẽ thấy nó bắt đầu tải về các file cần thiết cho quá trình cài. Sau một chút output từ kernel Linux thì màn hình installer hiện ra.

Tùy terminal đang dùng, màn hình này có thể hiển thị hơi lộn xộn. Môi trường cài đã boot lên là một môi trường rất tối giản, nên các ngôn ngữ bổ sung không có sẵn. Nhấn Enter và chọn giá trị mặc định cho hai bước đầu là thấy quá trình chạy đúng như thiết kế. Các bước sau đó bắt đầu tải thêm thứ về nên demo dừng ở đây. Nhấn Ctrl cùng dấu ngoặc vuông đóng để thoát ra khỏi terminal đó. Một ghi chú nhỏ: output sau đó có thể hơi lộn xộn, và lệnh clear không phải lúc nào cũng sửa được.

Phần lớn thời gian thì nên dùng cloud image như ở mục 7, nhưng biết cả phương pháp này cũng có ích.

virt-install: import an image or install freshCloud imagedownload the disk imageqemu-img info reads itvirtual size 3.5 GB to 10 GBcopy it into var lib libvirt images--import skips the OS install--name, --memory, --vcpusthe OS is already insidethe usual choice in practiceFresh installno --import option at all--disk size=10 makes the disk--location points at an ISOor at a web address instead--graphics none--extra-args console=ttyS0gives a serial console to useCtrl and closing bracket exitsvirt-install --osinfo list names every supported guest OS.man virt-install and virt-install --help narrow the options down.

Nguồn tham khảo​

Nguồn gốc: Khóa "Prep Course - Linux Foundation Certified System Administrator (LFCS) Certification" — phần "Operations Deployment" (quản lý và cấu hình container với Docker, gồm image, tag, port publishing và Dockerfile; tạo và vận hành máy ảo với QEMU-KVM qua virsh; tạo máy ảo từ cloud image và cài hệ điều hành bằng virt-install), nền tảng KodeKloud. Giảng viên: Jeremy Morgan và Alexandru Andrei.

Fact-check:

  • Tổ hợp detach mặc định của Docker là CTRL-p CTRL-q, và chỉ dùng được khi container chạy với -i và -t; Ctrl-C thì proxy tín hiệu vào container nên làm container dừng. Nguồn: Docker CLI reference — docker container attach, 2026-10-25.