Hiểu về Docker và Tiêu chuẩn OCI

Bức Tranh Toàn Cảnh: Docker và Sự Cần Thiết của Tiêu chuẩn

Trong hành trình “Roadmap Docker” của chúng ta, chúng ta đã cùng nhau tìm hiểu về khái niệm Container Là Gì và Vì Sao Mỗi Lập Trình Viên Nên Tìm Hiểu. Container đã thay đổi cách chúng ta đóng gói, phân phối và chạy ứng dụng, mang lại sự nhất quán từ môi trường phát triển đến sản xuất. Docker, với sự đơn giản và mạnh mẽ của mình, đã trở thành cái tên đồng nghĩa với công nghệ container, mở ra kỷ nguyên mới cho DevOps và phát triển phần mềm.

Sự bùng nổ của container do Docker khởi xướng đã tạo ra một hệ sinh thái phong phú. Tuy nhiên, khi ngày càng có nhiều công cụ và nền tảng khác nhau xuất hiện (như CoreOS rkt, CRI-O, Buildah, Podman, v.v.), một thách thức lớn đặt ra: làm thế nào để đảm bảo tất cả các công cụ này có thể hoạt động cùng nhau một cách mượt mà? Làm thế nào để một image được tạo bằng công cụ này có thể chạy trên runtime khác, hoặc một runtime có thể chạy image từ nguồn khác?

Sự phân mảnh này có thể dẫn đến tình trạng “khóa nhà cung cấp” (vendor lock-in), cản trở sự đổi mới và gây khó khăn cho người dùng khi muốn chuyển đổi hoặc kết hợp các công cụ khác nhau. Đây chính là lúc nhu cầu về một bộ tiêu chuẩn chung trở nên cấp thiết.

Giải Pháp: Liên Minh Công Nghiệp Mở (OCI)

Để giải quyết vấn đề phân mảnh và thúc đẩy sự phát triển lành mạnh của hệ sinh thái container, Open Container Initiative (OCI) đã ra đời vào tháng 6 năm 2015. OCI là một dự án dưới sự bảo trợ của Linux Foundation, với mục tiêu tạo ra các tiêu chuẩn mở cho các định dạng image container và runtime container. Các thành viên sáng lập bao gồm những tên tuổi lớn trong ngành như Docker, CoreOS, Google, Microsoft, Amazon, Facebook, IBM, Red Hat, Oracle, VMware và nhiều công ty khác.

Mục tiêu chính của OCI là thiết lập một bộ tiêu chuẩn kỹ thuật tối thiểu, cho phép các nhà phát triển xây dựng các công cụ có thể tương tác với nhau mà không bị ràng buộc vào một công nghệ cụ thể nào. Nói cách khác, OCI muốn đảm bảo rằng một “container” được hiểu và thực thi giống nhau bởi bất kỳ công cụ nào tuân thủ tiêu chuẩn OCI.

OCI hiện đang duy trì hai đặc tả kỹ thuật chính:

  1. Runtime Specification (runtime-spec): Định nghĩa cách thực thi container.
  2. Image Specification (image-spec): Định nghĩa cách tạo, đóng gói và ký các image container.

Chúng ta hãy cùng đi sâu hơn vào hai đặc tả này.

Đi Sâu vào Tiêu chuẩn OCI Runtime Specification

Runtime Specification (runtime-spec) của OCI mô tả chi tiết cách một phần mềm “runtime” (như `runc`, `crun`) nên thực thi một container. Đặc tả này không định nghĩa cách xây dựng image hay cách tải image, mà chỉ tập trung vào giai đoạn “chạy” container.

Một trong những khái niệm cốt lõi của runtime-spec là “Filesystem Bundle”. Đây là một thư mục chứa tất cả các thành phần cần thiết để chạy một container, bao gồm:

  • Root Filesystem: Hệ thống tệp gốc mà container sẽ sử dụng.
  • Configuration File (`config.json`): Một tệp JSON mô tả chi tiết các thiết lập runtime cho container.

Tệp `config.json` là trái tim của OCI Runtime Specification. Nó chứa tất cả thông tin mà runtime cần để tạo và chạy container, bao gồm:

  • Thông tin về hệ thống tệp gốc (đường dẫn).
  • Tiến trình chính sẽ chạy trong container (lệnh, đối số, thư mục làm việc).
  • Các biến môi trường.
  • Thiết lập người dùng và nhóm (UID/GID).
  • Các mount points (gắn kết volume).
  • Cài đặt mạng (namespaces, giao diện mạng).
  • Cài đặt tài nguyên (CPU, bộ nhớ, I/O – sử dụng cgroups trên Linux).
  • Cài đặt bảo mật (Linux capabilities, SELinux, AppArmor, seccomp).
  • Các hook lifecycle (lệnh chạy trước/sau khi container tạo/chạy/stop).

Ví dụ đơn giản về cấu trúc của tệp `config.json` (đã rút gọn):

{
  "ociVersion": "1.0.2",
  "process": {
    "terminal": true,
    "user": {
      "uid": 1000,
      "gid": 1000
    },
    "args": [
      "sh"
    ],
    "env": [
      "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
    ],
    "cwd": "/"
  },
  "root": {
    "path": "rootfs"
  },
  "hostname": "my-container",
  "mounts": [
    {
      "destination": "/proc",
      "type": "proc",
      "source": "proc"
    },
    // ... other mounts ...
  ],
  "linux": {
    "namespaces": [
      {
        "type": "pid"
      },
      {
        "type": "net"
      },
      // ... other namespaces ...
    ],
    "resources": {
      // ... cgroups settings ...
    }
  }
}

Runtime-spec cũng định nghĩa các hành động (actions) tiêu chuẩn mà một runtime phải hỗ trợ:

  • `state`: Lấy trạng thái của container.
  • `create`: Tạo container từ bundle, nhưng chưa chạy tiến trình chính.
  • `start`: Chạy tiến trình chính trong container đã tạo.
  • `kill`: Gửi tín hiệu tới tiến trình chính.
  • `delete`: Xóa container (sau khi đã stop).
  • `exec`: Chạy một tiến trình mới bên trong container đang chạy.

Các runtime tuân thủ OCI Runtime Specification bao gồm `runc` (runtime ban đầu của Docker, sau này được đóng góp cho OCI), `crun` (một runtime nhẹ hơn được viết bằng C), `Kata Containers` (sử dụng máy ảo nhẹ để tăng cường bảo mật), và nhiều loại khác. Sự tồn tại của spec này cho phép bạn thay thế runtime này bằng runtime khác mà không làm thay đổi cách các công cụ khác (như Docker, Kubernetes, Podman) tương tác với container ở tầng runtime.

Khám Phá Tiêu chuẩn OCI Image Specification

Image Specification (image-spec) của OCI định nghĩa cách một container image được xây dựng, đóng gói và ký. Nó mô tả cấu trúc của các tệp tạo nên một image và cách các thành phần này được tham chiếu. Mục tiêu là đảm bảo rằng bất kỳ công cụ xây dựng image nào tuân thủ spec này sẽ tạo ra các image có thể được hiểu và chạy bởi bất kỳ OCI-compliant runtime nào (thông qua một tầng trung gian).

Một OCI image về cơ bản là một tập hợp các tệp được tổ chức theo một cấu trúc cụ thể, thường được phân phối dưới dạng các lớp (layers) nén. Các thành phần chính của một OCI image bao gồm:

  • Manifest: Đây là tệp “mục lục” của image. Nó mô tả các lớp (layers) tạo nên filesystem của image và tham chiếu đến tệp cấu hình (config). Manifest cũng chứa thông tin về kiến trúc (architecture) và hệ điều hành (OS) mà image hỗ trợ. Có nhiều loại manifest, phổ biến nhất là `application/vnd.oci.image.manifest.v1+json`. Đối với image đa kiến trúc, có một loại manifest đặc biệt gọi là `index` hoặc `manifest list` (`application/vnd.oci.image.index.v1+json`) tham chiếu đến các manifest riêng lẻ cho từng kiến trúc.
  • Image Configuration: Tệp JSON này chứa các thiết lập cấu hình chi tiết cho image, tương tự như thông tin cấu hình trong Dockerfile như lệnh entrypoint/cmd, biến môi trường, cổng được expose, volume, user, working directory, v.v. Thông tin này được sử dụng bởi runtime khi chạy container. Định dạng là `application/vnd.oci.image.config.v1+json`.
  • Filesystem Layers: Đây là các tệp lưu trữ (tarball) của từng lớp trong hệ thống tệp của image. Các lớp này được xếp chồng lên nhau theo thứ tự xác định trong manifest để tạo ra hệ thống tệp gốc cuối cùng cho container. Hệ thống tệp này thường sử dụng định dạng OverlayFS hoặc tương tự để quản lý các lớp chỉ đọc (read-only) và một lớp ghi (writable layer). Định dạng phổ biến cho các lớp là `application/vnd.oci.image.layer.v1.tar+gzip`.

Khi bạn tải một image từ registry (ví dụ: Docker Hub), thực chất bạn đang tải manifest, tệp cấu hình, và các lớp filesystem tương ứng. Công cụ runtime hoặc tầng trung gian (như containerd) sẽ sử dụng thông tin này để tạo ra “Filesystem Bundle” tuân thủ OCI Runtime Specification, và sau đó chuyển bundle này cho OCI runtime (như `runc`) để thực thi.

Ví dụ, Docker sử dụng OCI Image Spec khi bạn chạy lệnh `docker build` (tạo image theo spec) và `docker push`/`docker pull` (phân phối image theo spec).

Docker và OCI: Mối Quan Hệ Cộng Sinh

Docker không chỉ là người tiên phong mà còn là một trong những động lực chính thúc đẩy sự ra đời và phát triển của OCI. Ban đầu, Docker có định dạng image và runtime riêng. Tuy nhiên, nhận thấy lợi ích của việc chuẩn hóa cho toàn bộ hệ sinh thái, Docker đã đóng góp công nghệ cốt lõi của mình (bao gồm thư viện `libcontainer` sau này trở thành `runc`) cho OCI và tích cực tham gia vào việc định hình các tiêu chuẩn.

Ngày nay, Docker Engine (daemon) hoạt động như một lớp trừu tượng cấp cao. Nó không trực tiếp chạy container nữa. Thay vào đó, Docker Engine sử dụng một runtime container cấp cao hơn gọi là containerd.

containerd là một daemon quản lý vòng đời đầy đủ của container. Nó có khả năng quản lý việc kéo (pull) image từ registry, quản lý lưu trữ image, quản lý network attachment, và tương tác với các OCI-compliant runtimes để chạy container.

Và chính `containerd` lại sử dụng các OCI-compliant runtimes cấp thấp như `runc` để thực sự tạo và chạy các tiến trình container theo đúng OCI Runtime Specification.

Mối quan hệ này có thể được hình dung như sau:

Docker CLI -> Docker Daemon (Engine) -> containerd -> OCI Runtime (runc, crun, v.v.) -> Container

Khi bạn gõ lệnh `docker run ubuntu sh`:

  1. Docker CLI gửi lệnh tới Docker Daemon.
  2. Docker Daemon xử lý lệnh, kiểm tra xem image `ubuntu` có tồn tại cục bộ không. Nếu không, nó yêu cầu `containerd` kéo image từ registry (sử dụng OCI Image Spec).
  3. `containerd` kéo image, giải nén các lớp, và chuẩn bị “Filesystem Bundle” cùng với tệp `config.json` (tuân thủ OCI Runtime Spec).
  4. Docker Daemon yêu cầu `containerd` tạo và chạy container từ bundle đó.
  5. `containerd` sử dụng một OCI runtime mặc định (thường là `runc`) để thực thi container dựa trên `config.json` và root filesystem đã chuẩn bị.
  6. `runc` sử dụng các tính năng của kernel Linux (namespaces, cgroups, chroot/pivot_root) để tạo và chạy tiến trình `sh` trong môi trường container biệt lập.

Mối quan hệ này cho thấy Docker đã tích hợp sâu các tiêu chuẩn OCI vào kiến trúc của mình. Điều này không chỉ làm cho Docker trở nên mạnh mẽ và linh hoạt hơn mà còn mang lại lợi ích to lớn cho người dùng.

Bạn có thể đã đọc về sự khác biệt giữa Container, Máy ảo (VM) và Bare Metal trong bài viết Roadmap Docker: Container, Máy ảo (VM) và Bare Metal – Đâu là lựa chọn phù hợp cho DevOps?. Trong ngữ cảnh này, việc Docker tuân thủ OCI củng cố vị thế của container như một đơn vị triển khai tiêu chuẩn, độc lập với tầng runtime bên dưới và các công cụ xây dựng image bên trên, tăng cường tính di động và khả năng tương tác mà VM khó lòng sánh kịp.

Lợi Ích Khi Docker Tuân Thủ Tiêu chuẩn OCI

Việc Docker tích hợp và tuân thủ các tiêu chuẩn của OCI mang lại nhiều lợi ích thiết thực cho người dùng và toàn bộ hệ sinh thái container:

  • Tính Tương Tác (Interoperability): Đây là lợi ích lớn nhất. Image được tạo bằng Docker có thể chạy trên các nền tảng orchestration khác như Kubernetes (thường sử dụng CRI-O hoặc containerd làm container runtime interface) hoặc chạy bằng các công cụ độc lập như Podman. Ngược lại, image được xây dựng bằng các công cụ OCI-compliant khác (như Buildah) có thể chạy bằng Docker. Điều này phá vỡ rào cản giữa các nền tảng và công cụ.
  • Tính Di Động (Portability): Container image của bạn trở nên thực sự di động. Bạn có thể xây dựng nó trên máy dev của mình, đẩy lên bất kỳ registry OCI-compliant nào, và chạy nó trên bất kỳ server nào có OCI-compliant runtime, bất kể bạn dùng Docker, Podman, hay CRI-O.
  • Thúc Đẩy Đổi Mới: Các tiêu chuẩn mở cho phép các nhà cung cấp và cộng đồng tập trung vào việc đổi mới ở các lớp khác nhau của stack container (công cụ build, runtime, công cụ quản lý, orchestration) mà không cần lo lắng về việc tương thích định dạng cơ bản. Bạn có thể kết hợp các công cụ tốt nhất cho từng công việc.
  • Giảm Rủi Ro Khóa Nhà Cung Cấp (Reduced Vendor Lock-in): Bằng cách dựa vào các tiêu chuẩn mở, bạn không còn bị phụ thuộc vào một công cụ hay nhà cung cấp duy nhất. Điều này mang lại sự linh hoạt và tự do lựa chọn.
  • Tính Dự Đoán và Nhất Quán: Các tiêu chuẩn cung cấp một bộ quy tắc rõ ràng về cách container nên hoạt động. Điều này giúp các nhà phát triển và kỹ sư DevOps dễ dàng dự đoán hành vi của container trên các môi trường khác nhau.

Nhờ OCI, hệ sinh thái container ngày càng trở nên mạnh mẽ, đa dạng và cởi mở hơn. Docker vẫn là một công cụ phổ biến và mạnh mẽ để làm việc với container, và việc nó tuân thủ OCI chỉ làm tăng thêm giá trị của nó trong bối cảnh hệ sinh thái container ngày càng phát triển.

Tổng Kết và Bảng So Sánh

Docker đã đóng vai trò lịch sử trong việc đưa công nghệ container đến với cộng đồng rộng lớn. Tuy nhiên, sự phát triển bền vững của hệ sinh thái đòi hỏi sự chuẩn hóa. OCI ra đời để đáp ứng nhu cầu này, cung cấp các đặc tả kỹ thuật mở cho runtime và image container.

Docker đã khéo léo tích hợp các tiêu chuẩn OCI vào kiến trúc của mình, sử dụng các thành phần như `containerd` và `runc` để tương tác với OCI Runtime Specification, đồng thời tạo và phân phối image theo OCI Image Specification. Điều này không chỉ đảm bảo sự tương thích ngược với các công cụ Docker cũ hơn mà còn mở ra cánh cửa cho sự tương tác với toàn bộ hệ sinh thái container tuân thủ OCI.

Với vai trò là một DevOps Engineer, hiểu rõ về OCI và cách Docker tương tác với nó là cực kỳ quan trọng. Nó giúp bạn:

  • Hiểu sâu hơn về cách container thực sự hoạt động bên dưới lớp vỏ Docker.
  • Đưa ra quyết định tốt hơn khi lựa chọn công cụ build image, runtime container, và nền tảng orchestration.
  • Chẩn đoán sự cố liên quan đến container hiệu quả hơn.
  • Tận dụng tối đa các lợi ích của hệ sinh thái container mở.

Để tóm tắt mối quan hệ giữa Docker và OCI, chúng ta có thể xem xét bảng so sánh dưới đây:

Khái niệm Docker Tương ứng/Quan hệ với OCI Mô tả
Dockerfile Công cụ để xây dựng OCI Image Tệp chứa các chỉ thị để xây dựng image. Quá trình build theo các chỉ thị này tạo ra các lớp filesystem và cấu hình image theo OCI Image Specification.
Docker Image OCI Image Format Định dạng đóng gói ứng dụng và môi trường của nó, bao gồm các lớp filesystem và cấu hình. Docker sử dụng cấu trúc và định dạng tệp theo OCI Image Specification.
Docker Container Kết quả chạy từ OCI Runtime Specification Bundle Một instance đang chạy được tạo từ một image. Docker Engine chuẩn bị Filesystem Bundle và config.json theo OCI Runtime Spec và giao cho runtime (thường là runc thông qua containerd) để thực thi.
Docker Daemon / Docker Engine Lớp trừu tượng cấp cao / Sử dụng containerd và OCI Runtimes Thành phần chính quản lý các đối tượng Docker (images, containers, volumes, networks). Nó tương tác với containerd và các OCI-compliant runtimes để thực hiện các tác vụ cấp thấp.
containerd OCI Container Runtime (High-level) Một daemon quản lý vòng đời container đầy đủ, bao gồm image transfer, storage, execution, supervision. Nó sử dụng các OCI Runtime (low-level) như runc.
runc OCI Container Runtime (Low-level) Một công cụ dòng lệnh nhẹ để chạy container theo OCI Runtime Specification. Được đóng góp bởi Docker cho OCI. Là runtime mặc định được containerd (và do đó là Docker) sử dụng.

Qua bài viết này, hy vọng bạn đã có cái nhìn sâu sắc hơn về mối liên hệ giữa Docker và các tiêu chuẩn OCI. Việc hiểu rõ nền tảng này là bước đệm quan trọng trên con đường làm chủ công nghệ container và các công cụ hiện đại trong hệ sinh thái DevOps.

Trong các bài viết tiếp theo của series “Roadmap Docker”, chúng ta sẽ tiếp tục khám phá những khía cạnh khác của Docker và hệ sinh thái container rộng lớn. Hãy tiếp tục theo dõi nhé!

Chỉ mục