15. Other Topics - JSON PATH and Resources
Mục lục
- 1. JSON Path là gì?
- 2. Tại sao cần JSON Path?
- 3. Cách kubectl hoạt động với JSON
- 4. 4 bước sử dụng JSON Path trong kubectl
- 5. Các ví dụ JSON Path Queries
- 6. Sử dụng vòng lặp (Range)
- 7. Custom Columns
- 8. Sắp xếp với --sort-by
- Nguồn tham khảo
1. JSON Path là gì?
Sếp hỏi một câu tưởng như đơn giản: "Cluster mình đang có tổng cộng bao nhiêu CPU?". Bạn gõ kubectl get nodes -o wide, và nhận về một bảng gọn gàng gồm NAME, STATUS, ROLES, AGE, VERSION, INTERNAL-IP — không có cột nào tên CPU cả.
Dữ liệu đó có tồn tại không? Có. Nó nằm trong cluster, ngay lúc này, ở field status.capacity.cpu của từng node. Chỉ là kubectl không in nó ra, vì nếu in hết mọi thứ mà nó biết thì mỗi node sẽ chiếm khoảng 200 dòng màn hình.
Công cụ để với tới đúng con số đó, không phải bằng cách đọc 200 dòng mà bằng một dòng lệnh, gọi là JSON Path.
1.1. Các thuật ngữ dùng xuyên suốt tài liệu
Trước khi đi tiếp, cần thống nhất 5 từ sẽ xuất hiện liên tục. Không nắm những từ này thì phần sau sẽ rất khó theo.
- JSON là định dạng dữ liệu dạng lồng nhau, gồm các cặp
"key": value. Value có thể là chuỗi, số, một object khác ({...}), hoặc một array ([...]) chứa nhiều phần tử. - JSON Path (viết liền là JSONPath) là một ngôn ngữ truy vấn dùng để chỉ đường tới một chỗ cụ thể bên trong tài liệu JSON. Nó không sửa dữ liệu, chỉ trích ra.
- Expression là một đoạn đường dẫn nằm trong cặp ngoặc nhọn, ví dụ
{.metadata.name}. Một expression trả về một hoặc nhiều giá trị. - Query (hay template) là toàn bộ chuỗi bạn đưa cho
kubectl, có thể gồm nhiều expression ghép lại, ví dụ'{.metadata.name}{"\n"}'. - Array index là số thứ tự của phần tử trong một array, đếm từ 0.
items[0]là phần tử đầu tiên,items[1]là phần tử thứ hai.
💡 Hình dung: JSON giống một tủ hồ sơ khổng lồ. Mỗi ngăn có nhãn (key), bên trong ngăn có thể là giấy tờ (value) hoặc lại là một tủ nhỏ hơn. JSONPath là tờ giấy ghi đường đi tới đúng ngăn cần lấy: "tầng 2, ngăn thứ nhất, kẹp hồ sơ tên status, tờ tên cpu". Không phải mở cả tủ ra lục.
Ba câu hỏi cần trả lời được sau tài liệu này:
kubectltrả về JSON như thế nào, và tại sao phần lớn dữ liệu đó bị giấu đi?- Làm sao viết được một query lấy đúng field mình cần?
- Làm sao định dạng kết quả thành bảng đọc được, thay vì một dòng chữ dính liền?
2. Tại sao cần JSON Path?
2.1. Thách thức trong môi trường production
Trên một cluster học tập có 2 node, mắt thường đọc được hết. Trên production thì khác hẳn.
Ở đó bạn sẽ phải làm việc với:
- Hàng trăm node.
- Hàng nghìn object các loại: Deployment, Pod, ReplicaSet, Service, Secret, ConfigMap, PersistentVolume.
Và các yêu cầu sẽ đến dưới dạng những câu hỏi rất cụ thể.
- In ra bảng tóm tắt trạng thái của nhiều loại resource khác nhau.
- Xem một field cụ thể của toàn bộ resource cùng loại.
- Lọc resource theo tiêu chí, rồi chỉ lấy đúng vài cột.
2.2. Vì sao duyệt tay không phải là một lựa chọn
Mở từng object ra đọc trên một cluster nghìn object là công việc không có điểm dừng. Tệ hơn, nó không tái sử dụng được: lần sau cần lại đúng con số đó, bạn phải làm lại từ đầu.
Đây chính là lý do kubectl hỗ trợ JSON Path. Nó biến câu hỏi "node nào ít CPU nhất" từ một buổi chiều đọc YAML thành một dòng lệnh chạy trong nửa giây, và dòng lệnh đó dán được vào script.
3. Cách kubectl hoạt động với JSON
3.1. Luồng hoạt động
Trước khi lọc được dữ liệu, cần biết dữ liệu đó từ đâu tới. Đường đi chỉ gồm 4 chặng.
kubectllà CLI của Kubernetes, dùng để đọc và ghi các Kubernetes object.- Mỗi lần chạy một lệnh
kubectl, nó gọi tới Kubernetes API thông qua kube-apiserver. - kube-apiserver nói ngôn ngữ JSON, nên nó trả về thông tin ở định dạng JSON.
kubectlnhận JSON đó, rút gọn lại thành bảng human-readable, rồi in ra màn hình.
Điểm mấu chốt nằm ở chặng thứ 4.
3.2. Vấn đề: bảng đẹp là một bản rút gọn
Để output dễ đọc, kubectl bỏ đi phần lớn những gì nó nhận được.
kubectl get nodes -o wide
# Output minh họa
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
controlplane Ready control-plane 18d v1.37.0 192.168.1.10 <none> Ubuntu 24.04.3 LTS 6.8.0-79 containerd://2.1.4
node01 Ready <none> 18d v1.37.0 192.168.1.11 <none> Ubuntu 24.04.3 LTS 6.8.0-79 containerd://2.1.4
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Cờ -o wide đã là bản đầy đủ nhất mà kubectl in ra mặc định, vậy mà vẫn thiếu rất nhiều thứ quan trọng.
- Resource capacity của node: bao nhiêu CPU, bao nhiêu memory, chứa tối đa bao nhiêu Pod.
- Các taint đang đặt trên node.
- Các condition của node:
MemoryPressure,DiskPressure,PIDPressure. - Hardware architecture:
amd64hayarm64. - Danh sách image đã được cache sẵn trên node.
3.3. Nhìn vào JSON thật trước khi viết query
Muốn lấy được field, trước hết phải thấy nó nằm ở đâu. Xem bản JSON gốc của cùng lệnh trên.
kubectl get nodes -o json
# Output minh họa (rút gọn)
{
"apiVersion": "v1",
"kind": "List",
"items": [
{
"apiVersion": "v1",
"kind": "Node",
"metadata": {
"name": "controlplane",
"labels": {
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "controlplane"
}
},
"spec": {
"taints": [
{ "key": "node-role.kubernetes.io/control-plane", "effect": "NoSchedule" }
]
},
"status": {
"capacity": { "cpu": "4", "memory": "8127016Ki", "pods": "110" },
"nodeInfo": { "architecture": "amd64", "kubeletVersion": "v1.37.0" }
}
},
{
"kind": "Node",
"metadata": { "name": "node01" },
"status": {
"capacity": { "cpu": "2", "memory": "4039236Ki", "pods": "110" },
"nodeInfo": { "architecture": "arm64", "kubeletVersion": "v1.37.0" }
}
}
]
}
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Hai điều cần nhớ từ khối JSON này. Thứ nhất, khi lấy danh sách nhiều object, phần dữ liệu thật luôn nằm trong array items — kubectl gói chúng vào một object có "kind": "List". Thứ hai, con số CPU mà mục 1 đi tìm nằm ở status.capacity.cpu, và với node đầu tiên nó có giá trị "4".
Lưu ý: đó là chuỗi "4" chứ không phải số 4. Kubernetes biểu diễn mọi đại lượng tài nguyên (CPU, memory, storage) dưới dạng chuỗi, vì chúng có đơn vị đi kèm như 100m, 8127016Ki, 10Gi. Chi tiết này sẽ quay lại ở mục 8.
3.4. Giải pháp
Với JSON Path query, bạn tự quyết định lấy field nào và in ra theo bố cục nào — thay vì nhận lấy bảng mà kubectl chọn sẵn cho bạn.
Bảng bên trái là thứ kubectl chọn cho bạn. Bảng bên phải là thứ bạn chọn cho mình. Phần còn lại của tài liệu này chỉ làm một việc: dạy cách viết cái query tạo ra bảng bên phải.
4. 4 bước sử dụng JSON Path trong kubectl
Quy trình luôn giống nhau, bất kể truy vấn node, Pod hay PersistentVolume. Bốn bước, làm đúng thứ tự.
4.1. Bước 1: Xác định command cơ bản
Trước hết, chọn lệnh kubectl nào chứa sẵn dữ liệu bạn cần. JSONPath chỉ lọc thứ lệnh đó trả về — nó không đi lấy thêm dữ liệu ở đâu khác.
- Thông tin về node:
kubectl get nodes. - Thông tin về Pod:
kubectl get pods. - Thông tin về PersistentVolume:
kubectl get pv.
4.2. Bước 2: Xem output ở định dạng JSON
kubectl get nodes -o json
Đây chính là khối JSON đã xem ở mục 3.3. Với object lớn, -o json dài hàng trăm dòng, nên hãy dẫn nó qua less hoặc jq để cuộn cho dễ.
Kubernetes v1.37 bổ sung thêm
-o kyamlở mức stable — một dialect YAML chặt hơn, mọi chuỗi đều được đặt trong nháy kép nên không còn chuyệnNObị hiểu thànhfalse. Nó hữu ích khi đọc manifest, nhưng để dò đường cho JSONPath thì-o jsonvẫn trực quan hơn, vì cấu trúc{}và[]hiện rõ ngay trên màn hình.
4.3. Bước 3: Đọc cấu trúc JSON và viết query
Đây là bước thật sự cần suy nghĩ. Một JSONPath query không có gì huyền bí: nó là một chuỗi các bước đi, mỗi bước xuống sâu thêm một tầng trong cây JSON.
Lấy ví dụ cụ thể — lấy số CPU của node đầu tiên:
.items[0].status.capacity.cpu
Đọc từ trái sang phải, từng token một.
Thay [0] bằng [*] thì thay vì node đầu tiên, query đi vào mọi node — đó là toàn bộ khác biệt giữa "lấy một giá trị" và "lấy cả cột".
Không muốn đọc hết JSON để dò tên field? Có một cách nhanh hơn: kubectl explain hỏi thẳng API server xem một resource có những field nào.
kubectl explain node.status.capacity
# Output minh họa
KIND: Node
VERSION: v1
FIELD: capacity <map[string]Quantity>
DESCRIPTION:
Capacity represents the total resources of a node. More info:
https://kubernetes.io/docs/reference/node/node-status/#capacity
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Dòng FIELD: capacity <map[string]Quantity> xác nhận hai điều cùng lúc: field tồn tại, và nó là một map chứ không phải array — nên viết .capacity.cpu chứ không phải .capacity[0]. Thêm cờ -R (hay --recursive) để xem luôn toàn bộ cây field con, và --max-depth để giới hạn độ sâu cho khỏi tràn màn hình.
kubectl explain node.status --recursive --max-depth=2
4.4. Bước 4: Gắn query vào kubectl command
Query đã đúng thì việc còn lại chỉ là bọc nó trong cặp ngoặc nhọn và đưa cho -o jsonpath.
kubectl get nodes -o jsonpath='{.items[0].status.capacity.cpu}'
# Output minh họa
4
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Chú ý: kết quả là 4 trần trụi, không có header, không có dấu xuống dòng ở cuối — dấu nhắc shell sẽ hiện ngay sát sau con số. Đó là hành vi cố ý, vì output kiểu này sinh ra để gán vào biến trong script.
4.5. Nháy đơn hay nháy kép: chỗ sai nhiều nhất
Đây là lỗi làm mất nhiều thời gian nhất của người mới, và nó thậm chí không phải lỗi của Kubernetes — nó là lỗi của shell.
Trên bash, zsh và các shell Unix khác, hãy luôn bọc template bằng nháy đơn, rồi dùng nháy kép cho các literal bên trong.
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.startTime}{"\n"}{end}'
Lý do: bên trong nháy đơn, shell không đụng vào bất cứ ký tự nào. Còn nếu bọc ngoài bằng nháy kép, shell sẽ ăn mất $, backtick và dấu \ trước khi kubectl kịp nhìn thấy chúng.
Trên Windows thì ngược lại. Tài liệu chính thức nói rõ: template có khoảng trắng phải được bọc bằng nháy kép, và khi đó các literal bên trong phải dùng nháy đơn, hoặc nháy kép đã escape.
kubectl get pods -o=jsonpath="{range .items[*]}{.metadata.name}{'\t'}{.status.startTime}{'\n'}{end}"
kubectl get pods -o=jsonpath="{range .items[*]}{.metadata.name}{\"\t\"}{.status.startTime}{\"\n\"}{end}"
💡 Hình dung: shell giống nhân viên bưu điện mở thư ra đọc trước khi chuyển. Nháy đơn là chiếc phong bì niêm phong — nhân viên không được mở, thư tới tay
kubectlnguyên vẹn. Nháy kép là phong bì hở — nhân viên vừa đọc vừa sửa vài chữ, vàkubectlnhận được một lá thư đã bị chỉnh.
4.6. Khuyến nghị cho người mới bắt đầu
Nếu đây là lần đầu bạn viết JSONPath, đừng gõ thẳng vào kubectl. Vòng lặp thử-sai trên cluster rất chậm và khó biết mình sai ở đâu.
- Xem bản JSON của output trước bằng
-o json. - Copy JSON đó vào một JSONPath evaluator, ví dụ jsonpath.com.
- Thử query trên đó cho tới khi ra đúng kết quả mong muốn.
- Chuyển query đã đúng vào lệnh
kubectl, nhớ bọc trong'{...}'.
Làm vài lần theo đường này, đến lúc thi CKA bạn sẽ viết thẳng được, không cần evaluator nữa.
5. Các ví dụ JSON Path Queries
Cả 4 ví dụ dưới đây chạy trên cùng một cluster 2 node đã mô tả ở mục 3.3: controlplane (4 CPU, amd64) và node01 (2 CPU, arm64).
5.1. Ví dụ 1: Lấy tên các node
kubectl get nodes -o jsonpath='{.items[*].metadata.name}'
# Output minh họa
controlplane node01
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Dấu * là wildcard: nó nói "mọi phần tử của array items". Khi một expression trả về nhiều giá trị, kubectl nối chúng lại bằng một dấu cách.
5.2. Ví dụ 2: Lấy hardware architecture của các node
kubectl get nodes -o jsonpath='{.items[*].status.nodeInfo.architecture}'
# Output minh họa
amd64 arm64
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Đây đúng là thông tin mà kubectl get nodes -o wide không hề in ra. Trên cluster lẫn nhiều kiến trúc, arm64 ở đây giải thích được ngay vì sao một image chỉ build cho amd64 lại CrashLoopBackOff trên node đó.
5.3. Ví dụ 3: Lấy số CPU trên các node
kubectl get nodes -o jsonpath='{.items[*].status.capacity.cpu}'
# Output minh họa
4 2
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Đã trả lời được câu hỏi của sếp ở mục 1. Nhưng có một vấn đề: 4 2 thì con số nào thuộc node nào?
5.4. Ví dụ 4: Ghép nhiều expression trong một query
Ý tưởng đầu tiên ai cũng nghĩ ra là dán hai expression vào cạnh nhau.
kubectl get nodes -o jsonpath='{.items[*].metadata.name}{.items[*].status.capacity.cpu}'
# Output minh họa
controlplane node014 2
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Kết quả trông như bị lỗi, nhưng nó hoàn toàn đúng theo quy tắc. Hãy đọc kỹ: node014 thực ra là node01 dính liền với 4.
Quy tắc đó là: trong cùng một expression, nhiều giá trị được nối bằng dấu cách. Giữa hai expression khác nhau, kubectl không chèn gì cả — nó nối thẳng. Tên node chạy hết rồi mới tới lượt các con số CPU, và chỗ tiếp giáp dính vào nhau.
Muốn có khoảng trắng hay xuống dòng, bạn phải tự đặt vào.
5.5. Thêm newline và tab cho dễ đọc
Chèn một expression chứa chuỗi literal giữa hai expression kia là xong.
kubectl get nodes -o jsonpath='{.items[*].metadata.name}{"\n"}{.items[*].status.capacity.cpu}{"\n"}'
# Output minh họa
controlplane node01
4 2
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Hai ký tự escape cần nhớ, cả hai đều phải nằm trong nháy kép bên trong cặp ngoặc nhọn.
{"\n"}cho newline.{"\t"}cho tab.
Đọc được hơn hẳn. Nhưng vẫn chưa phải bảng: tên nằm một dòng, số nằm dòng khác, và mắt vẫn phải tự ghép cặp. Mục 6 sẽ sửa đúng chỗ này.
5.6. Bảng cú pháp kubectl thật sự hỗ trợ
JSONPath là một chuẩn rộng, và kubectl chỉ cài đặt một phần của nó, cộng thêm vài thứ riêng. Bảng dưới là tập hợp chính xác những gì dùng được.
| Cú pháp | Tên gọi | Ý nghĩa | Ví dụ |
|---|---|---|---|
{} | expression | Bọc một đường dẫn. Chữ nằm ngoài cặp ngoặc nhọn được in nguyên văn. | kind is {.kind} |
. hoặc [] | child operator | Đi xuống một field con. | {.metadata.name}, {['kind']} |
.. | recursive descent | Tìm mọi field trùng tên ở mọi độ sâu, không cần biết đường đi. | {..name} |
* | wildcard | Lấy toàn bộ phần tử của một array. | {.items[*].metadata.name} |
[start:end:step] | subscript / slice | Cắt một lát của array. Index âm đếm ngược từ cuối. | {.items[0]}, {.items[0:3]} |
[,] | union | Lấy nhiều field trong cùng một lần. | {.items[*]['metadata.name','status.capacity']} |
?() | filter | Chỉ giữ lại phần tử thỏa điều kiện. | {.items[?(@.status.phase=="Running")].metadata.name} |
@ | current object | Trỏ tới chính phần tử đang xét, dùng bên trong filter. | {@} |
range / end | vòng lặp | Lặp qua từng phần tử của array. | {range .items[*]}...{end} |
'' hoặc "" | quoted string | Chuỗi in ra nguyên văn, kể cả \n và \t. | {"\n"} |
\ | escape | Thoát dấu chấm nằm trong chính tên field. | {.metadata.labels.kubernetes\.io/hostname} |
Ba lưu ý đi kèm bảng này.
- Regular expression không được hỗ trợ. Một query kiểu
{.items[?(@.metadata.name=~/^test$/)].metadata.name}sẽ không chạy. Cần match bằng regex thì phải đẩy JSON quajq. - Toán tử
$là tùy chọn, vì expression luôn bắt đầu từ object gốc.{$.items[0]}và{.items[0]}là một. - Index âm không wrap around.
[-1]hợp lệ khi(-index + listLength) >= 0; vượt quá thì query trả về rỗng chứ không quay vòng về cuối list.
Hai toán tử ít người biết nhưng rất đáng nhớ cho kỳ thi là .. và ?().
Recursive descent .. lục tung mọi tầng để tìm field trùng tên, hữu ích khi bạn không nhớ đường đi chính xác.
kubectl get pods -o jsonpath='{..image}'
# Output minh họa
nginx:1.29 nginx:1.29 redis:8.2 redis:8.2
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Mỗi image xuất hiện 2 lần, và đó không phải bug. Field tên image tồn tại ở cả spec.containers[] (image bạn khai báo) lẫn status.containerStatuses[] (image đang thật sự chạy). Đó là bản chất của ..: nó là cái vợt, không phải cây kim. Dùng nó để dò đường, rồi thay bằng path chính xác khi đã tìm ra.
Filter ?() giữ lại phần tử thỏa điều kiện, với @ trỏ tới phần tử đang xét.
kubectl get pods -o jsonpath='{.items[?(@.status.phase=="Running")].metadata.name}'
# Output minh họa
web-5f7c9b8d4-2xvzq db-0
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Đọc câu lệnh thành tiếng Việt sẽ thấy nó rất tự nhiên: "trong items, giữ lại phần tử nào có status.phase bằng Running, rồi lấy metadata.name của chúng".
6. Sử dụng vòng lặp (Range)
6.1. Vấn đề với output một dòng
Nhìn lại kết quả của mục 5.5:
controlplane node01
4 2
Với 2 node còn ghép cặp bằng mắt được. Với 40 node thì không. Cái bạn thật sự muốn là một bảng, mỗi node một dòng:
- Cột thứ nhất là tên node.
- Cột thứ hai là số CPU.
Vấn đề nằm ở chỗ {.items[*].metadata.name} quét hết tên rồi mới tới lượt expression sau quét hết CPU. Thứ tự duyệt sai ngay từ gốc.
6.2. Giải pháp: đảo thứ tự duyệt bằng range
Range là từ khóa tạo vòng lặp trong JSONPath template. Nó đổi thứ tự duyệt từ "hết cột này rồi tới cột kia" thành "hết dòng này rồi tới dòng kia".
Với mỗi item trong items — tức mỗi node — bạn bảo kubectl làm 4 việc:
- In ra tên node.
- In một tab làm dấu phân cách.
- In ra số CPU.
- In một newline để kết thúc dòng.
Lặp xong node cuối cùng thì dừng. Đúng một bảng.
💡 Hình dung:
rangechính là vòng lặpforquen thuộc trong lập trình — "với MỖI phần tử, làm những việc sau". Không córange, template chạy đúng một lượt từ trên xuống. Córange, đoạn nằm giữarangevàendđược chạy lại một lần cho từng phần tử.
6.3. Cú pháp và giải thích
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.capacity.cpu}{"\n"}{end}'
# Output minh họa
controlplane 4
node01 2
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
| Thành phần | Ý nghĩa |
|---|---|
{range .items[*]} | Mở vòng lặp, chạy một lần cho mỗi phần tử của items. |
{.metadata.name} | Lấy tên node của phần tử đang xét. |
{"\t"} | In một ký tự tab. |
{.status.capacity.cpu} | Lấy số CPU của phần tử đang xét. |
{"\n"} | In một ký tự newline, kết thúc dòng hiện tại. |
{end} | Đóng vòng lặp. |
Chi tiết dễ bỏ sót: bên trong range, các path viết tương đối so với phần tử đang xét. Là {.metadata.name}, không phải {.items[*].metadata.name} — vì range đã bước vào trong items rồi.
Tab của terminal căn theo bội số 8 ký tự nên cột có thể lệch khi tên node dài ngắn khác nhau. Dẫn output qua column -t là thẳng hàng ngay.
6.4. Kết hợp range với filter
range và ?() ghép được với nhau, và đây là dạng query hay xuất hiện trong các bài lab thực tế nhất: lọc trước, rồi in bảng.
kubectl get pods -o jsonpath='{range .items[?(@.status.phase=="Running")]}{.metadata.name}{"\t"}{.spec.nodeName}{"\n"}{end}'
# Output minh họa
web-5f7c9b8d4-2xvzq node01
db-0 controlplane
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Một dòng lệnh trả lời được câu hỏi "Pod nào đang chạy, và đang chạy ở node nào" — thứ mà kubectl get pods -o wide cho bạn kèm theo 6 cột không cần tới.
Takeaway của mục này: range cho bạn toàn quyền với từng ký tự của output. Cái giá phải trả là query dài và dễ gõ sai dấu ngoặc. Mục tiếp theo giới thiệu một cách ngắn hơn nhiều cho trường hợp phổ biến nhất.
7. Custom Columns
7.1. Khi nào dùng custom-columns thay cho range
Nhìn lại query ở mục 6.3. Nó dài 95 ký tự, có 12 cặp ngoặc nhọn, và gõ sai một dấu là kubectl báo lỗi parse.
Nhưng để ý: 90% thời gian bạn chỉ muốn đúng một thứ — một bảng, mỗi object một dòng, mỗi field một cột. Cho trường hợp đó, kubectl có sẵn một cờ ngắn hơn nhiều là -o custom-columns.
Custom columns nghĩa là "các cột do người dùng tự định nghĩa": bạn khai tên cột và JSONPath tương ứng, kubectl lo phần còn lại.
7.2. Cú pháp
kubectl get nodes -o custom-columns=NODE:.metadata.name,CPU:.status.capacity.cpu
Cú pháp gồm đúng 3 quy tắc.
- Mỗi cột là một cặp
TÊN_CỘT:jsonpath, ngăn cách bởi dấu hai chấm đầu tiên. - Các cột ngăn cách nhau bằng dấu phẩy, không có khoảng trắng sau dấu phẩy.
- Tên cột được in ra y hệt cách bạn gõ. Quy ước chung là viết hoa toàn bộ, nhưng
kubectlkhông ép.
Điểm khác biệt quan trọng nhất so với -o jsonpath: path không cần phần .items[*]. custom-columns tự biết nó đang in một danh sách, nên nó tự lặp và áp path lên từng item. Bạn chỉ mô tả một dòng, nó nhân ra thành cả bảng.
Path ở đây còn dễ tính hơn: kubectl chấp nhận cả 4 cách viết metadata.name, .metadata.name, {metadata.name} và {.metadata.name}, rồi tự chuẩn hóa về dạng đầy đủ. Không phải nhớ dạng nào cũng được.
7.3. Ví dụ
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.capacity.cpu,ARCH:.status.nodeInfo.architecture
# Output minh họa
NAME CPU ARCH
controlplane 4 amd64
node01 2 arm64
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Đây chính xác là bảng mà kubectl get nodes -o wide không cho bạn, và nó ra trong một lệnh, có header, các cột đã tự căn thẳng hàng.
7.4. Field không tồn tại thì in ra gì?
Không phải object nào cũng có đủ mọi field. Pod chưa được schedule thì chưa có spec.nodeName; node không có taint thì không có spec.taints. custom-columns xử lý chuyện đó lặng lẽ, không báo lỗi.
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,CONTAINERS:.spec.containers[*].name
# Output minh họa
NAME NODE CONTAINERS
web-5f7c9b8d4-2xvzq node01 nginx,log-agent
db-0 controlplane mysql
queue-worker-7d9f <none> worker
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Hai hành vi cần nhớ từ output này.
- Path không khớp field nào thì in
<none>. Podqueue-worker-7d9fđangPending, chưa có node nào nhận nó. - Path khớp nhiều giá trị thì các giá trị được nối bằng dấu phẩy, gọn trong một ô. Pod
webcó 2 container nên cộtCONTAINERSlànginx,log-agent.
Chỗ này khác -o jsonpath: jsonpath nối nhiều giá trị bằng dấu cách, custom-columns nối bằng dấu phẩy.
7.5. custom-columns-file: tách template ra file riêng
Một bộ cột dùng đi dùng lại hàng ngày thì không nên gõ lại mỗi lần. kubectl cho phép để nó trong một file với -o custom-columns-file.
cat > node-cols.txt <<'EOF'
NAME CPU ARCH
metadata.name status.capacity.cpu status.nodeInfo.architecture
EOF
kubectl get nodes -o custom-columns-file=node-cols.txt
# Output minh họa
NAME CPU ARCH
controlplane 4 amd64
node01 2 arm64
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Format của file rất chặt, sai là kubectl từ chối chạy.
- Đúng 2 dòng: dòng đầu là tên các cột, dòng sau là các JSONPath tương ứng.
- Các mục trên mỗi dòng cách nhau bằng khoảng trắng. Căn cho thẳng cột chỉ để người đọc dễ nhìn,
kubectlkhông quan tâm. - Số lượng header phải bằng số lượng path. Lệch nhau là báo lỗi
number of headers and field specifications don't match.
Takeaway của mục này: cần một bảng thì dùng custom-columns, ngắn và khó sai. Cần một chuỗi đúng từng ký tự để gán vào biến shell thì mới quay lại -o jsonpath.
8. Sắp xếp với --sort-by
8.1. Vì sao cần sắp xếp
Có bảng rồi, câu hỏi tiếp theo gần như luôn là một câu so sánh. Node nào ít CPU nhất? Pod nào restart nhiều nhất? Volume nào to nhất?
kubectl trả về object theo thứ tự API server đưa xuống, thường là theo tên. Cờ --sort-by cho phép đổi thứ tự đó theo bất kỳ field nào, cũng bằng một JSONPath expression.
8.2. Cú pháp
kubectl get nodes --sort-by=.metadata.name
--sort-by dùng chung bộ parser "dễ tính" với custom-columns, nên cả .metadata.name, metadata.name và {.metadata.name} đều chạy. Và giống custom-columns, không cần phần .items[*] — nó đã biết đang sắp xếp một danh sách.
8.3. Ví dụ: sắp xếp node theo số CPU
--sort-by chỉ đổi thứ tự dòng, không đổi cột — nên ghép nó với custom-columns mới thấy được kết quả.
kubectl get nodes --sort-by=.status.capacity.cpu -o custom-columns=NAME:.metadata.name,CPU:.status.capacity.cpu
# Output minh họa
NAME CPU
node01 2
controlplane 4
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
node01 giờ đứng đầu vì nó là node ít CPU nhất. Thứ tự luôn là tăng dần, và kubectl không có cờ đảo ngược — muốn giảm dần thì dẫn qua tac.
8.4. Ví dụ: sắp xếp Pod theo số lần restart
Đây là lệnh đáng thuộc lòng nhất khi đi trực sự cố.
kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'
# Output minh họa
NAME READY STATUS RESTARTS AGE
db-0 1/1 Running 0 18d
web-5f7c9b8d4-2xvzq 1/1 Running 2 6d
queue-worker-7d9f 0/1 CrashLoopBackOff 47 3h
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Thủ phạm nằm ở dòng cuối: queue-worker-7d9f với 47 lần restart trong 3 tiếng. Trên một namespace có 200 Pod, đây là cách nhanh nhất để nó tự nổi lên.
Chú ý [0] trong path: nó chỉ đọc container đầu tiên của mỗi Pod. Với Pod nhiều container, một container phụ restart liên tục sẽ không được tính vào thứ tự sắp xếp.
8.5. Bốn điều cần biết trước khi tin vào --sort-by
--sort-by có vài hành vi không hiển nhiên. Biết trước sẽ tránh được những kết luận sai.
Field phải là số hoặc chuỗi. Tài liệu của kubectl get nói rõ field mà JSONPath trỏ tới phải là integer hoặc string. Trỏ vào một object hay một map thì kubectl báo unsortable type.
Object thiếu field được xếp lên đầu. Parser chạy ở chế độ cho phép thiếu key, nên Pod chưa có nodeName không làm lệnh crash — nó chỉ bị đẩy lên trên cùng. Nhưng nếu không object nào có field đó, kubectl dừng hẳn với lỗi couldn't find any field with path "..." in the list of objects. Đây là cách nhanh để phát hiện mình gõ sai tên field.
Hai object cùng thiếu field thì thứ tự không xác định. Ở Kubernetes v1.37, hàm so sánh của kubectl trả về "nhỏ hơn" theo cả hai chiều khi cả hai vế đều thiếu field, vi phạm hợp đồng của sort.Interface trong Go. Hệ quả là khi nhiều object cùng thiếu field sắp xếp, thứ tự giữa chúng có thể đổi giữa các lần chạy. Lỗi này đã có bản vá ở nhánh chính sau v1.37.
Đại lượng tài nguyên được so sánh theo giá trị số, không phải theo chuỗi. Đây là chỗ nhiều tài liệu trên mạng nói sai. Đúng là 2Gi và 10Gi được API lưu dưới dạng chuỗi, nhưng kubectl thử parse cả hai vế thành resource.Quantity trước khi so sánh; parse được thì nó so theo số thực sự.
kubectl get pv --sort-by=.spec.capacity.storage -o custom-columns=NAME:.metadata.name,SIZE:.spec.capacity.storage
# Output minh họa
NAME SIZE
pv-log 512Mi
pv-db 2Gi
pv-data 10Gi
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Nếu so sánh theo chuỗi thuần thì 10Gi đã phải đứng trước 2Gi, vì ký tự 0 nhỏ hơn ký tự G. Thứ tự thật là 512Mi, 2Gi, 10Gi — đúng theo dung lượng. Tin được con số này.
8.6. Chọn công cụ nào?
Ba cờ, ba công việc khác nhau, cùng chung một cú pháp JSONPath bên dưới.
💡 Hình dung: ba cờ này giống ba thao tác trên một bảng tính.
-o jsonpathlà gõ tay nội dung vào từng ô.-o custom-columnslà chọn xem hiện những cột nào.--sort-bylà bấm vào tiêu đề cột để đổi thứ tự dòng. Ba thao tác độc lập, và dùng chung được trong một lệnh.
Takeaway cuối cùng cho kỳ thi: trong đa số câu hỏi CKA, -o custom-columns kèm --sort-by là đủ và nhanh nhất. Chỉ khi đề yêu cầu ghi đúng một giá trị ra file thì mới cần tới -o jsonpath.
Nguồn tham khảo
Nguồn gốc: Khóa "Certified Kubernetes Administrator (CKA)" — phần "Other Topics" (Phần 15: JSON Path và Resources — cú pháp JSONPath, vòng lặp range, custom columns, sắp xếp với --sort-by), nền tảng KodeKloud. Giảng viên: Mumshad Mannambeth.
Repo ghi chú, link tài liệu, và đáp án các practice question của toàn bộ khóa học: kodekloudhub/certified-kubernetes-administrator-course.
Fact-check:
- kubectl hỗ trợ các toán tử
./[],..,*,[start:end:step],[,],?(),@,range/end, chuỗi trong nháy, và\để escape trong JSONPath template — 2026-09-23, JSONPath Support - kubectl không hỗ trợ regular expression trong JSONPath; muốn match bằng regex phải đẩy JSON qua
jq— 2026-09-23, JSONPath Support - Toán tử
$là tùy chọn vì expression luôn bắt đầu từ root object — 2026-09-23, JSONPath Support - Index âm trong slice không wrap around và chỉ hợp lệ khi
(-index + listLength) >= 0— 2026-09-23, JSONPath Support - Trên bash dùng nháy đơn bao template; trên Windows phải dùng nháy kép bao template có khoảng trắng và nháy đơn (hoặc nháy kép đã escape) cho literal bên trong — 2026-09-23, JSONPath Support
-o custom-columns=<spec>và-o custom-columns-file=<filename>; file template gồm đúng 2 dòng phân tách bằng khoảng trắng, dòng header và dòng field spec — 2026-09-23, kubectl Reference — Custom columns- Spec mỗi cột là
<header>:<jsonpath-field-spec>, tách theo dấu phẩy và cắt tại dấu hai chấm đầu tiên; số header phải bằng số field spec — 2026-09-23, kubectl customcolumn.go, nhánh release-1.37 RelaxedJSONPathExpressionchấp nhận cả 4 dạngmetadata.name,.metadata.name,{metadata.name},{.metadata.name}cho custom-columns và--sort-by— 2026-09-23, kubectl customcolumn.go, nhánh release-1.37- custom-columns in
<none>khi path không khớp field nào, và nối nhiều giá trị trong một ô bằng dấu phẩy — 2026-09-23, kubectl customcolumn.go, nhánh release-1.37 --sort-bynhận một JSONPath expression và field được trỏ tới phải là integer hoặc string — 2026-09-23, kubectl get--sort-bychạy parser ở chế độAllowMissingKeys(true), object thiếu field được xếp lên đầu, và kubectl báo lỗicouldn't find any field with pathkhi không object nào có field đó — 2026-09-23, kubectl sorter.go, nhánh release-1.37- Khi hai object cùng thiếu field sắp xếp, comparator của kubectl v1.37 trả về "nhỏ hơn" ở cả hai chiều nên thứ tự không xác định — 2026-09-23, kubernetes/kubectl issue #1875
- Khi hai giá trị đem so sánh là chuỗi lấy từ JSON, kubectl thử
resource.ParseQuantitytrên cả hai vế và so sánh theo giá trị số nếu cả hai parse được, ngược lại quay về so sánh chuỗi, nên10Gixếp sau2Gi— 2026-09-23, kubectl sorter.go, nhánh release-1.37 kubectl get pods --sort-by='.status.containerStatuses[0].restartCount'vàkubectl get pv --sort-by=.spec.capacity.storagelà ví dụ chính thức — 2026-09-23, kubectl Quick Referencekubectl explain TYPEcùng cờ-R/--recursivevà--max-depthvẫn hiện hành, output mặc định làplaintext— 2026-09-23, kubectl explain- Các output format của
kubectl getgồm json, yaml, kyaml, name, go-template, jsonpath, jsonpath-as-json, jsonpath-file, custom-columns, custom-columns-file, wide — 2026-09-23, kubectl get -o kyamlđạt mức stable từ Kubernetes v1.37 — 2026-09-23, Kubernetes v1.37: Garhwal