YAML là gì? Ngôn ngữ dữ liệu thân thiện với con người
YAML theo cách đơn giản nhất
Tên đầy đủ của YAML là 'YAML Ain't Markup Language' — một cách chơi chữ đệ quy để nói cho bạn biết nó không phải là gì. Nó không dùng để đánh dấu tài liệu như HTML. Thay vào đó, YAML là một định dạng tuần tự hóa dữ liệu được thiết kế để con người có thể đọc và chỉnh sửa mà không cần mở một tài liệu hướng dẫn ở tab khác. Nó xuất hiện lần đầu vào năm 2001, là sản phẩm của Clark Evans, Ingy döt Net và Oren Ben-Kiki. Kể từ đó, nó đã trở thành ngôn ngữ cấu hình mặc định cho các công cụ thiết yếu như Kubernetes, Ansible, GitHub Actions, Docker Compose và Ruby on Rails. Về bản chất, YAML sử dụng thụt lề để biểu diễn các cấu trúc dữ liệu như cặp khóa-giá trị (key-value pairs) và danh sách (lists). Bạn sẽ không tìm thấy dấu ngoặc nhọn (< >) hay dấu ngoặc xoắn ({ }) ở đây. Một file Docker Compose đơn giản sẽ minh họa rõ điều này: version: '3.9' services: web: image: nginx:latest ports: - '80:80' Đặt nó cạnh một file JSON tương đương và sự hấp dẫn của YAML là rõ ràng. Không có dấu phẩy, không có dấu ngoặc kép cho khóa, và không có dấu ngoặc đóng bị lệch. Đánh đổi là thụt lề trở nên rất quan trọng. Chỉ cần sai một khoảng trắng, file có thể không phân tích cú pháp được (parse) hoặc tệ hơn, ngầm hiểu thành một điều gì đó hoàn toàn khác. Sự căng thẳng giữa việc dễ đọc và cấu trúc nghiêm ngặt này định nghĩa trải nghiệm với YAML. Làm quen với nó là chìa khóa để sử dụng định dạng này một cách hiệu quả.
Cách YAML cấu trúc dữ liệu: Ba khối xây dựng cơ bản
Mọi tài liệu YAML đều được cấu thành từ các phần cơ bản giống nhau: scalars, sequences và mappings. Scalar chỉ là một giá trị đơn lẻ, như một chuỗi (string), một số, một giá trị boolean, hoặc null. YAML rất thông minh trong việc suy luận kiểu dữ liệu, điều này chủ yếu hữu ích nhưng đôi khi có thể gây ra vấn đề. Chuỗi 'yes' được phân tích cú pháp thành boolean `true` trong tiêu chuẩn YAML 1.1 cũ hơn, vẫn được PyYAML và các công cụ cũ khác sử dụng. Tuy nhiên, YAML 1.2 xử lý nó một cách chính xác như một chuỗi thông thường. Khoảng cách phiên bản đó đã gây ra các lỗi sản xuất thực tế, vì vậy bạn hoàn toàn cần biết công cụ của mình đang sử dụng trình phân tích cú pháp (parser) nào. Sequence chỉ đơn giản là một danh sách có thứ tự. Bạn viết nó với một dấu gạch ngang và một khoảng trắng ở đầu: fruits: - apple - banana - mango Mapping là một tập hợp các cặp khóa-giá trị, điều mà bạn sẽ thấy thường xuyên nhất trong các file cấu hình. Bạn có thể lồng các mapping ở bất kỳ độ sâu nào, và cấu trúc được định nghĩa hoàn toàn bằng cách thụt lề nhất quán. Đặc tả YAML rất rõ ràng về điều này: bạn phải sử dụng khoảng trắng (spaces), không bao giờ dùng tab. YAML cũng cung cấp các tính năng mạnh mẽ để giảm sự lặp lại. Anchors (&) và aliases (*) cho phép bạn định nghĩa một khối dữ liệu một lần và tái sử dụng nó ở nơi khác. Đây là một cứu cánh trong các pipeline CI/CD, nơi nhiều job có thể chia sẻ cùng một khối biến môi trường. Đối với các chuỗi dài, YAML có hai kiểu khối: literal block scalar (|) bảo toàn chính xác các dòng mới, trong khi folded block scalar (>) sẽ gộp chúng thành các khoảng trắng. Điều này hoàn hảo cho các lệnh shell dài mà bạn muốn giữ cho dễ đọc trong file mà không cần thêm ngắt dòng thực tế vào chính lệnh đó.
YAML thực sự được sử dụng ở đâu
YAML phát triển mạnh trong thế giới cơ sở hạ tầng và công cụ dành cho nhà phát triển. Các con số không biết nói dối. Tính đến năm 2024, GitHub Actions xử lý hơn 100 triệu lượt chạy workflow mỗi ngày, mỗi lượt đều được điều khiển bởi một file .yml trong thư mục .github/workflows của dự án. Kubernetes, công cụ đứng sau hầu hết các ứng dụng cloud-native, dựa vào YAML để định nghĩa mọi tài nguyên: Deployments, Services, ConfigMaps, v.v. Một ứng dụng microservices điển hình có thể dễ dàng tích lũy hàng trăm file YAML. Ansible, công cụ tự động hóa IT được hơn 25.000 tổ chức sử dụng (theo Red Hat), dùng YAML cho tất cả các playbook của mình. Một tác vụ tiêu chuẩn trong playbook của Ansible trông như thế này: - name: Install nginx ansible.builtin.package: name: nginx state: present Ngoài lĩnh vực DevOps, bạn sẽ tìm thấy YAML trong các công cụ tạo trang web tĩnh như Jekyll, nơi sử dụng YAML front matter để lưu trữ siêu dữ liệu trong các file Markdown. Các công cụ kiểm thử API như Hoppscotch và Insomnia sử dụng nó cho cấu hình môi trường. Ngay cả các pipeline khoa học dữ liệu cũng dùng nó; các công cụ như DVC (Data Version Control) theo dõi các tham số thử nghiệm trong các file YAML. Một nơi bạn sẽ không thấy nhiều YAML là trong việc trao đổi dữ liệu giữa các dịch vụ web. Các REST API gần như phổ biến sử dụng JSON cho phần thân yêu cầu và phản hồi. Tại sao? Bởi vì các trình phân tích cú pháp JSON được tích hợp sẵn trong mọi trình duyệt và sự chặt chẽ của định dạng này không để lại chỗ cho sự mơ hồ. Đây là điểm khác biệt chính: YAML dành cho con người chỉnh sửa file trên ổ đĩa; JSON dành cho máy móc truyền dữ liệu qua mạng. Ghi nhớ quy tắc đơn giản đó sẽ giúp bạn tránh được nhiều bối rối về việc chọn định dạng nào.
YAML so với JSON so với TOML: Chọn định dạng phù hợp
Khi chọn một định dạng cấu hình, bạn thường phải lựa chọn giữa YAML, JSON và TOML. Mỗi loại đều có một "tính cách" riêng biệt. JSON (JavaScript Object Notation) là định dạng nghiêm ngặt nhất. Mọi chuỗi phải được đặt trong dấu ngoặc kép, mọi danh sách và đối tượng phải được đóng rõ ràng, và nó không hỗ trợ chú thích (comments). Điểm cuối cùng này là một nguồn gây khó chịu lớn cho các nhà phát triển muốn chú thích cấu hình. Nhưng sức mạnh của JSON nằm ở khả năng phân tích cú pháp cứng nhắc, không mơ hồ; bất kỳ hai trình phân tích cú pháp tuân thủ nào cũng sẽ tạo ra cấu trúc dữ liệu giống hệt nhau từ cùng một đầu vào. Kích thước file của nó thường tương đương với YAML đối với các cấu hình thông thường. TOML (Tom's Obvious, Minimal Language) được tạo ra để khắc phục việc JSON thiếu chú thích và các quy tắc khoảng trắng phức tạp của YAML. Nó sử dụng cú pháp kiểu INI và đã được Cargo (trình quản lý gói của Rust) và tiêu chuẩn pyproject.toml của Python áp dụng. TOML rất tuyệt vời cho các cấu hình phẳng hoặc lồng nông, nhưng nó trở nên cồng kềnh và dài dòng khi bạn cần biểu diễn dữ liệu lồng sâu. Vậy YAML phù hợp ở đâu? YAML là lựa chọn thắng thế rõ ràng khi cấu hình của bạn có độ sâu lồng đáng kể, khi bạn có thể sử dụng anchors và aliases để loại bỏ sự lặp lại, hoặc khi những người không chuyên về kỹ thuật cần chỉnh sửa file. Nó là lựa chọn sai lầm khi nhóm của bạn liên tục gặp lỗi thụt lề hoặc khi suy luận kiểu dữ liệu tạo ra những bất ngờ (như vấn đề Norway nổi tiếng, trong đó mã quốc gia 'NO' được phân tích cú pháp thành boolean `false`). Nếu bạn cần chuyển đổi giữa các định dạng, CocoConvert cung cấp các công cụ chuyển đổi YAML sang JSON và JSON sang YAML đáng tin cậy. Tuy nhiên, nó không hỗ trợ TOML làm định dạng đầu ra. Nếu bạn cần chuyển từ YAML sang TOML, bạn sẽ phải dùng đến một công cụ dòng lệnh như `yq`. Tốt hơn hết là biết điều đó ngay từ đầu.
Những lỗi YAML thường gặp và cách tránh
Nguồn gốc phổ biến nhất của các lỗi YAML là việc thụt lề không nhất quán. Bất kỳ ai đã dành hàng giờ gỡ lỗi một pipeline CI chỉ để rồi phát hiện ra một khoảng trắng đặt sai vị trí đều hiểu nỗi đau này. Không như Python, vốn chấp nhận bất kỳ độ rộng thụt lề nhất quán nào, YAML yêu cầu các khóa ngang hàng ở cùng cấp phải có thụt lề giống hệt nhau. Việc trộn lẫn thụt lề hai khoảng trắng và bốn khoảng trắng trong một file sẽ gây ra lỗi phân tích cú pháp hoặc, tệ hơn nhiều, âm thầm cấu trúc lại dữ liệu của bạn. Cách an toàn duy nhất để làm việc là cấu hình trình soạn thảo của bạn để sử dụng khoảng trắng thay cho tab và áp dụng một kích thước thụt lề nhất quán. Hai khoảng trắng là tiêu chuẩn thực tế cho Kubernetes và GitHub Actions. Các ký tự đặc biệt không được đặt trong dấu ngoặc kép là một cái bẫy khác. Dấu hai chấm theo sau là một khoảng trắng là dấu phân cách cặp khóa-giá trị, vì vậy một chuỗi như 'http://example.com:8080' phải được đặt trong dấu ngoặc kép. Quên dấu ngoặc kép, và bạn sẽ gặp lỗi phân tích cú pháp. Tương tự, các giá trị bắt đầu bằng `{`, `[`, hoặc `%` cần được đặt trong dấu ngoặc kép vì chúng có ý nghĩa đặc biệt trong cú pháp YAML. Sau đó là những bất ngờ về việc ép kiểu dữ liệu. Chúng ta đã đề cập đến việc 'no' có thể trở thành `false` như thế nào. Nhưng bạn có biết rằng các số có số 0 ở đầu có thể được phân tích cú pháp thành số nguyên bát phân không? Giá trị 0755, một quyền file Unix phổ biến, trở thành số thập phân 493 trừ khi bạn đặt nó trong dấu ngoặc kép. Ngày tháng là một "bãi mìn" khác; 2024-01-01 không có dấu ngoặc kép sẽ trở thành một đối tượng ngày tháng, chứ không phải chuỗi, điều này có thể làm hỏng các công cụ mong đợi một chuỗi. Cách phòng thủ tốt nhất là kiểm tra cú pháp (lint) file YAML của bạn trước khi commit. `yamllint` là một công cụ dòng lệnh thiết yếu giúp phát hiện lỗi thụt lề, khoảng trắng thừa ở cuối dòng và các vấn đề phổ biến khác. Pipeline CI của bạn hoàn toàn nên bao gồm một bước `yamllint`. Để kiểm tra nhanh một lần, việc dán file của bạn vào công cụ chuyển đổi YAML sang JSON của CocoConvert là một bài kiểm tra hợp lý tuyệt vời. Nếu cấu trúc JSON kết quả không giống như bạn mong muốn, file YAML của bạn đang có vấn đề.
Chuyển đổi file YAML: CocoConvert có thể làm gì
CocoConvert cung cấp các công cụ cho hai nhu cầu chuyển đổi phổ biến nhất: YAML sang JSON và JSON sang YAML. Quy trình rất đơn giản: dán nội dung của bạn hoặc tải lên một file .yaml, chọn định dạng đích và tải về kết quả. Công cụ chuyển đổi bảo toàn chính xác cấu trúc lồng nhau của dữ liệu của bạn. Nó cũng xử lý đúng các file YAML đa tài liệu (nơi các tài liệu được phân tách bằng ---), chuyển đổi mỗi tài liệu thành một đối tượng JSON riêng biệt trong một mảng lớn hơn. Khi chuyển đổi YAML sang JSON, đầu ra được định dạng với thụt lề hai khoảng trắng tiêu chuẩn, giúp nó dễ đọc và tương thích với gần như tất cả các công cụ JSON. Nếu bạn cần một chuỗi JSON nhỏ gọn, một dòng duy nhất — có thể cho một biến môi trường hoặc để giảm kích thước tải trọng — tùy chọn rút gọn (minify) có sẵn trên trang kết quả. Khi chuyển từ JSON sang YAML, công cụ chuyển đổi sử dụng thụt lề hai khoảng trắng và bỏ qua dấu hiệu bắt đầu tài liệu (---) đối với các tài liệu đơn lẻ. Nếu đầu vào chứa nhiều đối tượng JSON, mỗi đối tượng sẽ trở thành một tài liệu YAML riêng biệt được phân tách bằng dấu `---`. Quan trọng là, nó tự động đặt dấu ngoặc kép cho các giá trị chuỗi có thể bị hiểu sai thành boolean, null hoặc số, vì vậy bạn không phải lo lắng về một chuỗi như 'true' hoặc '1.0' gây ra vấn đề. Hãy thẳng thắn về những hạn chế. CocoConvert không bảo toàn các chú thích YAML trong quá trình chuyển đổi. Đây không phải là thiếu sót của công cụ; các chú thích không phải là một phần của mô hình dữ liệu chính thức của YAML, vì vậy chúng bị loại bỏ bởi trình phân tích cú pháp. Anchors và aliases cũng được giải quyết, nghĩa là đầu ra cuối cùng sẽ chứa các giá trị lặp lại thay vì các tham chiếu. Cuối cùng, các file rất lớn (trên 10 MB) có thể hết thời gian chờ (time out) trên gói miễn phí. Đối với những công việc lớn đó, một công cụ dòng lệnh như `yq` là lựa chọn tốt hơn.
Khi nào nên dùng YAML và khi nào nên tránh
YAML là công cụ phù hợp khi một vài điều kiện được đáp ứng. File sẽ được con người đọc và chỉnh sửa, dữ liệu có cấu trúc lồng nhau có ý nghĩa, và hệ sinh thái xung quanh đã sử dụng YAML. Các manifest của Kubernetes, pipeline CI/CD và playbook của Ansible là những ví dụ điển hình cho điều này. Cố gắng sử dụng JSON trong những trường hợp này chỉ làm cho mọi thứ khó khăn hơn mà không mang lại lợi ích thực sự nào. Ngược lại, YAML là lựa chọn sai lầm khi một file chỉ được máy móc chạm vào. Đối với giao tiếp giữa các máy, hãy sử dụng JSON hoặc một định dạng nhị phân hiệu quả hơn như MessagePack hoặc Protocol Buffers. Nó cũng là một lựa chọn kém nếu nhóm của bạn liên tục gặp khó khăn với lỗi thụt lề và thiếu kỷ luật để sử dụng một công cụ kiểm tra cú pháp (linter). Trong tình huống đó, cú pháp đơn giản hơn của TOML, hoặc thậm chí một file JSON đã được xử lý trước, sẽ dẫn đến ít sự cố sản xuất hơn. Nếu bạn đang sử dụng YAML trong các dự án Python, có một rủi ro bảo mật nghiêm trọng mà bạn phải hiểu. Hàm `yaml.load()` mặc định của PyYAML có thể thực thi mã tùy ý được nhúng trong một file YAML. Nếu bạn đang phân tích cú pháp YAML từ một nguồn không đáng tin cậy, bạn phải luôn sử dụng `yaml.safe_load()`. Đây không phải là một lỗ hổng lý thuyết; nó đã bị khai thác trong các cuộc tấn công chuỗi cung ứng thực tế. Định dạng này đã đồng hành cùng chúng ta hơn hai thập kỷ và sẽ không biến mất. Đặc tả YAML 1.2, đã khắc phục hầu hết các vấn đề ép kiểu dữ liệu gây khó chịu từ phiên bản 1.1, hiện được hỗ trợ rộng rãi bởi các trình phân tích cú pháp hiện đại như PyYAML 6.0+, js-yaml 4.x và yaml.v3 của Go. Khuyến nghị mạnh mẽ nhất của tôi: nếu bạn đang làm việc trên một dự án phụ thuộc nhiều vào YAML, việc chuyển sang một trình phân tích cú pháp tuân thủ 1.2 là nâng cấp có tác động lớn nhất mà bạn có thể thực hiện. Nó sẽ loại bỏ toàn bộ một loại lỗi tinh vi mà bạn không cần phải thay đổi một dòng cấu hình nào.