Giới Thiệu
Docker đã trở thành một công cụ không thể thiếu trong phát triển phần mềm hiện đại, giúp các nhà phát triển dễ dàng triển khai và quản lý ứng dụng trong môi trường container. Bài viết này sẽ giúp bạn hiểu rõ hơn về kiến trúc, cách hoạt động và những khía cạnh thú vị của Docker mà không phải ai cũng biết đến. Hãy cùng khám phá!
Kiến Trúc Docker
Docker hoạt động theo mô hình client-server, trong đó CLI (Command Line Interface) của Docker là client gửi lệnh đến tiến trình daemon Docker, được gọi là dockerd. Daemon này là động cơ chính chịu trách nhiệm quản lý tất cả các đối tượng của Docker (containers, images, volumes, networks).
Khi bạn chạy các lệnh như docker build .
hoặc docker run nginx
, CLI sẽ phân tích đầu vào của bạn và giao tiếp qua API REST với daemon. Việc này thường diễn ra qua một socket Unix (ví dụ: /var/run/docker.sock
) trên máy cục bộ của bạn, nhưng cũng có thể hoạt động từ xa qua TLS hoặc SSH.
Các Hệ Thống Con Của Daemon
- Containerd: Quản lý vòng đời container, bao gồm khởi động, dừng và tạm dừng containers.
- Tính Năng Kernel Linux: Sử dụng các tính năng như namespaces (để cách ly), cgroups (quản lý tài nguyên).
- Bảo Mật: Sử dụng các mô-đun bảo mật như seccomp và AppArmor để đảm bảo các container chạy một cách an toàn và hiệu quả.
- Mạng Lưới và Volume: Đảm bảo kết nối mạng và mount volumes để lưu trữ dữ liệu.
Daemon luôn chạy như một dịch vụ, quản lý containers một cách liên tục, ngay cả sau khi khởi động lại hệ thống, đảm bảo rằng các container của bạn tiếp tục chạy hoặc có thể được khởi động lại theo cấu hình.
Lớp Trong Docker
Mỗi khi bạn xây dựng, kéo hoặc đẩy một image, Docker làm việc với các lớp. Các lớp này tương ứng với từng lệnh trong Dockerfile của bạn. Khi xây dựng lại, chỉ các lớp đã thay đổi mới được xây dựng lại, điều này làm tăng tốc độ.
Cách Thức Hoạt Động Của Lớp
Các lớp được lưu trữ bằng cách sử dụng lưu trữ theo địa chỉ nội dung, nghĩa là mỗi lớp được xác định và tham chiếu bởi một hash mã hóa của nội dung, không chỉ bởi một số hoặc tên tuần tự. Điều này đảm bảo rằng hai lớp giống nhau, ngay cả từ các image khác nhau, được lưu trữ chỉ một lần trên đĩa, làm cho việc lưu trữ và chuyển giao image trở nên cực kỳ hiệu quả.
Khi kéo một image, Docker tải các lớp theo hash của chúng, sau đó giải nén mỗi lớp vào thư mục riêng của nó trên hệ thống tệp máy chủ. Khi chạy một container, Docker sử dụng union filesystem (như OverlayFS) để xếp chồng các lớp bất biến này lên nhau và trình bày một cái nhìn hệ thống tệp thống nhất bên trong container. Lớp container có thể ghi sẽ ở trên cùng, ghi lại mọi thay đổi bạn thực hiện trong thời gian chạy.
Cách Ly Trong Docker - Namespaces và Cgroups
Bạn có bao giờ tự hỏi làm thế nào Docker quản lý các container của mình với sự cách ly hoàn toàn? Docker sử dụng một công nghệ được gọi là namespaces để cung cấp không gian làm việc cách ly gọi là container. Khi bạn chạy một container, Docker tạo ra một tập hợp các namespaces cho container đó.
Các Loại Namespace
- PID: Cách ly quy trình.
- NET: Cách ly mạng.
- MNT: Cách ly hệ thống tệp.
- UTS: Cách ly tên miền.
- IPC: Cách ly giao tiếp giữa các quy trình.
- USER: Cách ly người dùng/nhóm.
Cgroups Trong Linux
Control groups (cgroups) là một tính năng của kernel Linux cho phép quản lý và giới hạn mức sử dụng tài nguyên, chẳng hạn như CPU, bộ nhớ, I/O đĩa và băng thông mạng, ở mức độ chi tiết. Cgroups như một quy tắc phân cấp cho việc kiểm soát tài nguyên.
Khi bạn thiết lập giới hạn tài nguyên cho một dịch vụ, bạn viết các ràng buộc vào các tệp cgroup cùng với ID quy trình (PID) của dịch vụ đó. Các cgroup con sẽ thừa hưởng các hạn chế tài nguyên từ cha của chúng, nghĩa là bạn không thể cho phép một cgroup con vượt quá giới hạn của cha. Tuy nhiên, các cgroup con có thể áp đặt giới hạn chặt chẽ hơn trong các ranh giới do cha đặt.
Docker và Kubernetes phụ thuộc nhiều vào cgroups để cách ly các container và áp đặt giới hạn tài nguyên. Khi bạn chỉ định yêu cầu hoặc giới hạn tài nguyên trong các deployment của Kubernetes, chúng cuối cùng sẽ được chuyển đổi thành các cài đặt cgroup được áp dụng bởi runtime container.
SHA Trong Docker
Bạn đã bao giờ gặp phải tình huống mà một trong những dịch vụ của bạn bắt đầu hoạt động sai, vì image gốc mà toàn bộ ứng dụng được xây dựng đã được cập nhật và làm hỏng mã ứng dụng chưa? Điều này xảy ra khi đôi khi có các bản cập nhật cho cùng một tag image, và khi dịch vụ khởi động lại, nếu ImagePullPolicy được thiết lập là Always, nó giả định kéo một image của cùng một tag, nhưng image không còn giống nhau nữa.
Giải Pháp
Mỗi image cũng được liên kết với một digest SHA — giống như một commit git. Bằng cách chỉ định SHA thay vì tag phiên bản, bạn có thể kéo một phiên bản cụ thể của image. Ví dụ: docker pull ubuntu@sha256:sha_digest_here
. Để tránh đau đầu, bạn chỉ cần gán image của mình vào phiên bản SHA như sau:
yaml
build:
image: ubuntu@sha256:3235326357dfb65f1781dbc4df3b834546d8bf914e82cce58e6e6b676e23ce8f
commands:
- build_my_wonderful_app.sh
Bạn có thể tìm SHA bằng cách kéo image (nó hiển thị digest một khi image đã được kéo) hoặc bằng cách kiểm tra image.
Tất Cả Trong Chi Tiết Khi Bạn Chạy Lệnh Docker
Docker hoạt động trên mô hình client-server. Lệnh bạn nhập trên terminal là Docker CLI client giao tiếp với Docker daemon (dockerd), là bộ não chạy trên máy của bạn hoặc máy chủ từ xa.
Các Bước Khi Chạy Một Lệnh Docker
- Phân Tích Lệnh: Docker CLI phân tích lệnh và các tham số của bạn, kiểm tra cú pháp và xây dựng yêu cầu có cấu trúc để gửi tới API của Docker daemon.
- Gửi Yêu Cầu: Yêu cầu này được gửi đến daemon Docker, dù là trên máy cục bộ hay máy chủ từ xa.
- Xử Lý Yêu Cầu: Tùy thuộc vào lệnh, dockerd sẽ thực hiện các hành động khác nhau.
- Tương Tác Với Container Runtime: Docker daemon dựa vào một runtime container (mặc định là containerd) để xử lý vòng đời container và các tính năng ở cấp độ OS.
- Khởi Động và Chạy Container: Khi mọi thiết lập hoàn tất, hệ thống tệp gốc của container là một lớp có thể ghi chồng lên các lớp image bên dưới.
Thực Hành Tốt Nhất Khi Sử Dụng Docker
- Sử Dụng Tags Cụ Thể: Luôn xác định image bằng SHA để tránh sự cố không mong muốn.
- Tối Ưu Hóa Dockerfile: Tổ chức các lệnh trong Dockerfile để giảm thiểu số lượng lớp và kích thước image.
- Giới Hạn Tài Nguyên: Sử dụng cgroups để giới hạn tài nguyên cho các container, tránh việc sử dụng quá mức.
Những Cạm Bẫy Thường Gặp
- Không Giới Hạn Tài Nguyên: Dễ dẫn đến các vấn đề về hiệu suất và không ổn định cho hệ thống.
- Không Sử Dụng Image Đã Được Kiểm Tra: Cần đảm bảo rằng các image bạn sử dụng đã được kiểm tra và ổn định để tránh rủi ro bảo mật.
Kết Luận
Docker là một công cụ mạnh mẽ giúp bạn quản lý ứng dụng hiệu quả hơn. Hy vọng bài viết này đã giúp bạn hiểu rõ hơn về Docker và cách nó hoạt động. Hãy áp dụng những kiến thức này vào dự án của bạn để tối ưu hóa quy trình phát triển. Nếu bạn có câu hỏi nào, đừng ngần ngại liên hệ với tôi qua các kênh xã hội của mình để thảo luận thêm về DevOps hay bất kỳ điều gì thú vị khác nhé!
Cảm ơn bạn đã đọc!