2 điểm bởi GN⁺ 2024-01-24 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong các môi trường có số lượng đối tượng cấu hình ngày càng tăng như Kubernetes, cách trực tiếp tạo thêm file YAML sẽ sớm chạm giới hạn, và cách tiếp cận tạo dữ liệu cấu hình trở nên phù hợp hơn template YAML
  • Helm chart chèn giá trị bằng values.yaml và Go template, nhưng ngay khi xuất hiện trường tùy chọn, mảng và map, gánh nặng về câu điều kiện và thụt lề tăng lên
  • YAML có quy tắc khoảng trắng nghiêm ngặt, trong khi trình phân tích Helm template không hiểu cấu trúc YAML, nên việc kết hợp toYamlindent rất dễ dẫn đến tạo cấu hình mong manh
  • YAML là siêu tập của JSON nên việc chuyển đổi qua lại đơn giản; Jsonnet xử lý việc tạo đối tượng cấu hình như viết code bằng biến bên ngoài, trường có điều kiện, kết hợp map và gộp đối tượng
  • kr8 dùng luồng dựa trên Jsonnet để tạo và thao tác cấu hình cho nhiều cụm Kubernetes, chọn cách trực tiếp tạo và biến đổi đối tượng thay vì lắp ghép chuỗi YAML phức tạp

Độ phức tạp cấu hình bắt đầu khi số lượng file YAML tăng lên

  • Khi ứng dụng và hạ tầng vượt qua một quy mô nhất định, độ phức tạp cấu hình tăng rất nhanh
  • Nếu chỉ có 1–2 mục tiêu triển khai, viết trực tiếp các file cấu hình YAML là đủ, nhưng khi số lượng tăng hơn nữa thì cần quản lý cấu hình một cách có hệ thống
  • Lý do cần nhiều file cấu hình thường là vì ngay cả cùng một đối tượng cũng có một số giá trị khác nhau
    • Triển khai theo môi trường như dev, stg, prod
    • Triển khai theo khu vực như Europe, North America
  • Không phải mọi cấu hình đều khác nhau, nhưng nếu khác biệt đủ lớn thì cần tách phần chung và phần khác nhau để quản lý
  • Lĩnh vực quản lý cấu hình đã xử lý vấn đề này từ lâu, và nhiều công cụ đã tận dụng YAML theo cách riêng của mình
  • hiera trong Puppet cho phép tra cứu biến theo phân cấp, mạnh mẽ và linh hoạt, đồng thời giảm đáng kể nhu cầu template hóa chính YAML

Vấn đề của template YAML bộc lộ trong Helm chart

  • Khi cloud computing và Kubernetes mở rộng đối tượng cấu hình lên các tầng phía trên hệ điều hành, các công cụ như CloudFormationHelm đã xuất hiện
  • Helm chart có thể nhận các tham số bên ngoài được định nghĩa trong values.yaml để render
  • Giá trị chuỗi đơn giản thì tương đối dễ xử lý
image: "{{ .Values.image }}"
  • Nếu chỉ định giá trị image trong values.yaml, giá trị đó sẽ được đưa vào template
  • Vấn đề trở nên lớn hơn khi bắt đầu xử lý cấu hình phức tạp hơn, chẳng hạn trường tùy chọn
{{- with .resourceGroup  }}
    resourceGroup: {{ .  }}
{{- end }}
  • Vì giá trị tùy chọn không thể để trống, cần câu điều kiện và vòng lặp, khiến template dễ trở nên lộn xộn
  • Khi đưa mảng hoặc map vào, cần kết hợp toYamlindent
{{- with .Values.podAnnotations  }}
      annotations:
{{ toYaml . | indent 8  }}
{{- end  }}
  • Việc gọi hàm dùng toYaml để chuyển YAML lại thành YAML đã khá gượng gạo, nhưng vấn đề lớn hơn là xử lý khoảng trắng

Xung đột giữa quy tắc khoảng trắng của YAML và template engine

  • YAML có quy tắc nghiêm ngặt về thụt lề và khoảng trắng
  • Ví dụ sau không phải YAML hợp lệ hoặc hoàn chỉnh
something: nothing
  hello: goodbye
  • Nếu con người viết trực tiếp, có thể nhấn backspace vài lần để sửa, nhưng khi tạo YAML bằng hệ thống template thì không đơn giản như vậy
  • Nếu số lượng file cấu hình vượt quá mức 5–10 file, cần tạo cấu hình thay vì viết trực tiếp
  • Để đưa giá trị .Values.podAnnotations vào dưới annotations vốn đã được thụt lề, bản thân giá trị đó cũng phải được thụt lề chính xác ở đúng mức
  • Vì trình phân tích Go template không hiểu YAML, cố thụt lề cú pháp template cho dễ nhìn cũng có thể gây vấn đề
{{- with .Values.podAnnotations }}
      annotations:
      {{ toYaml . | indent 6 }}
{{- end  }}
  • Khi hệ thống template không biết cấu trúc YAML mà vẫn phải xử lý khoảng trắng cùng câu điều kiện, việc tạo cấu hình phức tạp ngày càng khó hơn
  • Cách viết trực tiếp JSON cũng không phù hợp vì thiếu chú thích và dễ thiếu dấu phẩy; chính những bất tiện này khiến YAML được sử dụng

Jsonnet là ngôn ngữ template dữ liệu để tạo cấu hình JSON

  • YAML là siêu tập của JSON, nên việc chuyển đổi giữa JSON và YAML rất đơn giản
  • Nhiều ứng dụng và ngôn ngữ lập trình có thể phân tích hoặc chuyển đổi JSON và YAML một cách mặc định
  • Trong Python cũng có thể đọc YAML và xuất ra JSON
python -c 'import json, sys, yaml ; y=yaml.safe_load(sys.stdin.read()) ; print(json.dumps(y))'
  • Jsonnet tự gọi mình là ngôn ngữ template dữ liệu, với mục đích cốt lõi là tạo cấu hình JSON
  • Có thể xem bối cảnh thiết kế của Jsonnet trong design rationale

Xử lý biến bên ngoài và trường tùy chọn

  • Jsonnet có thể dùng biến bên ngoài để chèn giá trị cấu hình
{

  image: std.extVar('image'),

}
  • Khi truyền biến bên ngoài từ CLI, kết quả JSON sẽ được tạo ra
jsonnet image.jsonnet -V image="my-image"
{
   "image": "my-image"
}
  • Trường tùy chọn có thể được biểu diễn bằng biểu thức điều kiện trong code, thay vì nhét câu điều kiện template vào trong chuỗi
// define a variable - yes, jsonnet also has comments
local rg = null;
{

  image: std.extVar('image'),
  // if the variable is null, this will be blank
  [if rg != null then 'resourceGroup']: rg,

}
  • Nếu rgnull, trường resourceGroup sẽ không được đưa vào kết quả
  • Nếu chỉ định giá trị, trường đó sẽ được xuất ra

Thao tác với map và đối tượng đơn giản hơn thụt lề YAML

  • Khi đưa map vào cấu hình, như annotation của Kubernetes pod, trong Jsonnet có thể định nghĩa giá trị dưới dạng biến rồi đặt vào đối tượng
local annotations = {
  'nginx.ingress.kubernetes.io/app-root': '/',
  'nginx.ingress.kubernetes.io/enable-cors': true,
};

{
  metadata: { // annotations are nested under the metadata of a pod
    annotations: annotations,
  },

}
  • Cách này đơn giản hơn nhiều so với căn thụt lề trong template YAML
  • Kết quả được tạo là một đối tượng JSON có map annotation nằm dưới metadata.annotations
{
   "metadata": {
      "annotations": {
         "nginx.ingress.kubernetes.io/app-root": "/",
         "nginx.ingress.kubernetes.io/enable-cors": true
      }
   }
}
  • Việc thêm annotation vào đối tượng hiện có cũng có thể xử lý bằng toán tử + trong Jsonnet
local annotations = {
  'nginx.ingress.kubernetes.io/app-root': '/',
  'nginx.ingress.kubernetes.io/enable-cors': true,
};

{
  metadata: {
    annotations: annotations,
  },
} + { // this adds another JSON object
  metadata+: { // I'm using the + operator, so we'll append to the existing metadata
    annotations+: { // same as above
      something: 'nothing',
    },
  },
}
  • Trong đối tượng kết quả, something: "nothing" được thêm vào các annotation hiện có
{
   "metadata": {
      "annotations": {
         "nginx.ingress.kubernetes.io/app-root": "/",
         "nginx.ingress.kubernetes.io/enable-cors": true,
         "something": "nothing"
      }
   }
}
  • Trong ví dụ đơn giản, code có thể trông dài hơn, nhưng khi cấu hình trở nên phức tạp, khả năng thao tác đối tượng theo cách này sẽ rất hữu ích

kr8 xử lý cấu hình Kubernetes theo cách của Jsonnet

  • kr8 sử dụng các phương pháp này để dễ dàng và đơn giản tạo cũng như thao tác cấu hình của nhiều cụm Kubernetes
  • Luồng cốt lõi là tạo đối tượng cấu hình JSON rồi biến đổi theo cách cần thiết, thay vì lắp ghép template YAML bằng khoảng trắng và câu điều kiện

1 bình luận

 
GN⁺ 2024-01-24
Ý kiến trên Hacker News
  • Tôi giờ đã chán ngấy cấu hình viết bằng YAML. Đây là phần tôi ghét nhất ở GitHub Actions, thậm chí còn tệ hơn cả vấn đề độ ổn định
    Mỗi khi thấy một công cụ hay nào đó yêu cầu file YAML cho cấu hình là tôi lập tức thấy bất an. Các ngôn ngữ cấu hình độc quyền như HCL của Terraform hay ASL của AWS Step Functions cũng vậy
    Muốn có API khai báo thì không sao, nhưng tôi ước họ cho phép tạo phần khai báo đó bằng chương trình. Trải nghiệm cấu hình được khai báo và sinh ra từ mã tốt hơn rất nhiều, và AWS CDK làm điều này cực kỳ tốt
    Bạn có thể viết định nghĩa hạ tầng đám mây bằng ngôn ngữ an toàn kiểu dữ liệu với hỗ trợ IDE tốt, mà không phải phụ thuộc vào plugin đã 2 năm rồi không được cập nhật

    • Đến mức này thì tôi thấy JSON thuần còn tốt hơn YAML. Cú chốt hạ là deno fmt có bộ định dạng cho JSON nhưng không có cho YAML
      Trình định dạng JSON là một binary duy nhất chạy trong vài mili giây, còn tự động định dạng YAML thì thực tế gần như phải dùng Prettier, mà Prettier lại phụ thuộc vào nửa hệ sinh thái NPM và mất khoảng 2 giây để khởi động và chạy
      Vì thế, trong kho mã của công ty, mọi file YAML có thể đổi sang JSON đều đã được tôi chuyển hết sang JSON, và ít nhất với tôi thì hài lòng hơn nhiều. Không ai phàn nàn cả
      Nhiều trình soạn thảo cũng hỗ trợ thẻ $schema của JSON. Tôi đã thêm tính năng này vào sản phẩm, và thật tuyệt khi có thể chỉ cần bấm tab là tạo được file cấu hình mà không cần đọc tài liệu
      YAML cũng làm được với YAML language server, nhưng vì phím tab còn cần cho thụt lề nên tính tiện dụng không tốt lắm. JSON cũng không hoàn hảo, nhưng ít nhất thì văn bản "no" không bị hiểu là giá trị true
    • Tôi thường nghe người ta nói một ưu điểm của YAML là JSON không có chú thích, nhưng tôi không hiểu vì sao lại phải chuyển hẳn sang một ngôn ngữ khác hoàn toàn
      Chỉ cần thêm một bộ lọc để xóa chú thích trước khi đọc cấu hình là được mà? Chắc chắn không thể khó hơn việc đổi sang YAML
      Tôi cũng không thật sự hiểu luận điểm rằng YAML dễ đọc hơn. Nỗi khổ khi xử lý cấu hình không đến từ việc đỡ phải phân tích vài dấu ngoặc nhọn hay ngoặc vuông trong vài giây, mà đến từ chuyện rất khó biết có gì sai trong hàng trăm dòng cấu hình chỉ vì thiếu khoảng trắng hoặc tab
    • GitHub Actions dù được cấu hình bằng gì thì có lẽ vẫn sẽ dở thôi. Vì nó đang cố mô tả chương trình bằng cấu trúc dữ liệu
      Ansible cũng mắc cùng sai lầm đó, và vô số công cụ khác cũng vậy
    • Tôi đã nghĩ tương tự từ thời AWS CloudFormation. 4 năm trước tôi từng tạo một trình tạo CloudFormation mang tính thử nghiệm, nó sinh ra mọi tài nguyên và Python type hint từ file JSON do AWS công bố, và hoạt động khá tốt: https://github.com/weberc2/nimbus/blob/master/examples/src/n...
      Tôi không rõ CDK có hoạt động theo kiểu đó không. Khi thử dùng một chút thì nó khá khác với trải nghiệm “sinh CloudFormation” mà tôi đã tạo, nên tôi không thật sự cảm nhận được ưu điểm của CDK
      Nó giống như biến vấn đề YAML/template thành vấn đề kế thừa/phép thuật. Tôi muốn nghe thêm trải nghiệm từ những người đã dùng AWS CDK, Terraform CDK và Pulumi
    • Trong GitHub Actions còn đau đầu hơn vì nó không hỗ trợ YAML anchor. Ít nhất thứ đó cũng đã mang lại một mức độ kết hợp tối thiểu, nên khá đáng tiếc
      https://github.com/actions/runner/issues/1182
  • Tôi đồng ý rằng việc tạo template cho YAML khá là điên rồ, nhưng tôi vẫn luôn không hiểu vì sao không ngừng dùng các ngôn ngữ giả và chuyển sang ngôn ngữ lập trình thực thụ
    Nếu cần logic phức tạp thì cứ dùng ngôn ngữ lập trình để sinh ra YAML/JSON/bất cứ thứ gì. Ruby, Python, hay bất kỳ ngôn ngữ nào khác đều cung cấp thứ bạn cần mà không cần đến các ngôn ngữ na ná kỳ quặc như Jsonnet hay Go template
    Chỉ cần viết code thì sẽ ít mắc vào những vấn đề kỳ quái, khó nhìn thấu của engine template hơn rất nhiều. Dùng bất kỳ ngôn ngữ thực thụ nào cũng tốt hơn hẳn

    • Trước đây tôi từng phụ trách quản lý các Ansible playbook được chạy định kỳ để bootstrap và vá lỗi trên số lượng máy chủ khổng lồ
      Tôi cũng từng dùng Chef cho công việc tương tự, và vì đó là Ruby nên rất tiện để định nghĩa logic mong muốn cũng như dùng vòng lặp và biến đúng nghĩa
      Tôi hiểu Ansible được thiết kế cho người không phải lập trình viên, nhưng với những ai đã quen lập trình cơ bản thì chẳng có địa ngục nào tệ hơn việc nhốt các playbook Ansible đầy thao tác điều kiện và lặp vào cú pháp dài dòng của template Jinja
    • Trong stack ngày nay, kiến trúc gọi là nhúng ngôn ngữ dường như gần như đã bị lãng quên. Ngày trước, với ứng dụng đủ phức tạp, phần lõi thường được làm bằng C/C++/Java hoặc tương tự, và nếu cần scripting thì người ta nhúng thêm thứ như LISP hay Lua ở bên trên
      Nhưng giờ thì cách làm phổ biến là mang parser JSON/TOML/YAML vào rồi tạo một hàm readConfig, kể cả ở những chỗ mà một interpreter nhúng sẽ phù hợp hơn
      Từ góc nhìn nhà phát triển, việc tăng độ phức tạp cho format cấu hình dễ hơn là cung cấp khả năng nhúng ngôn ngữ hoàn chỉnh cùng binding cho ứng dụng. Vì thế có vẻ như người ta либо đã quên mất cách làm đó, либо thậm chí không nghĩ rằng nó khả thi
    • Pulumi hấp dẫn vì cho phép viết bằng ngôn ngữ ưa thích và bỏ HCL đi, nhưng cá nhân tôi thấy nó rõ ràng là tệ hơn. Hạ tầng dưới dạng mã nên mang tính khai báo thì mới tăng được tính dự đoán, khả năng tái lập và khả năng bảo trì
      Từ thời Chef/Puppet, nhiều nơi đã bắt đầu nhét logic vào IaC rồi cuối cùng biến thành những bãi hỗn độn khổng lồ không thể nâng cấp cũng không thể bảo trì. Cách tiếp cận Chef/Pulumi cũng làm được, nhưng cần những người cực kỳ nghiêm khắc về phong cách và bảo trì
      Với đội ngũ lớn và bảo trì lâu dài, tôi cho rằng mô hình Terraform/Puppet tốt hơn. HCL có thể khó chịu và việc dùng Python/TypeScript v.v. có thể đem lại cảm giác giải phóng, nhưng code thuần khai báo giúp ngăn được rất nhiều mớ spaghetti
    • Vấn đề là những người mê ngôn ngữ lại tạo ra ngôn ngữ cho những người mê ngôn ngữ khác
      Họ muốn nhét vào các yếu tố thiết kế ngôn ngữ đang thịnh hành, muốn tự hosting, lại còn muốn nó có thể dùng để viết web server đa luồng tốc độ cao, nên mọi thứ trở nên phức tạp về mặt khái niệm
      Điều cần thiết là một ngôn ngữ đồ chơi đơn giản kiểu Logo dành cho kỹ sư hệ thống/DevOps. Lý tưởng nhất là phải có thể giải thích bằng một cuốn sách chỉ dày cỡ sách K&R C
      Nó nên có kiểu động, các cấu trúc điều khiển có thể học trong một cuối tuần, không có threading hay concurrency, không có hướng đối tượng hay kế thừa, có thiết kế hàm/mô-đun, và có mô hình FFI để dễ gọi sang cũng như được gọi từ ngôn ngữ và framework khác
      Vấn đề là những người mê ngôn ngữ không tự kiềm chế được nên cứ tiếp tục thêm tính năng, rồi những thứ đó đi vào thư viện cốt lõi và style guide, khiến cả người mới bắt đầu cũng phải học hết
      Chính tôi cũng sẽ muốn thêm các hàm kiểu each/map vào mảng/hash map và đưa vào hàm hạng nhất cùng closure, nhưng đó có thể là một sai lầm. Đã có các ngôn ngữ hàm thuần bất biến cho cấu hình, nhưng hơn 95% số người dùng YAML template không muốn học lập trình theo kiểu đó, nên chúng khó phổ biến rộng rãi
    • Tôi muốn nhấn mạnh điểm sinh ra file cấu hình. Bản thân cấu hình nên được giới hạn vào dạng có thể nằm trong file JSON hoặc tương tự, điều đó cực kỳ hữu ích
      Cấu hình sẽ đơn giản hơn, dễ tiêu thụ hơn và cũng dễ tài liệu hóa hơn. Nhưng khi viết file cấu hình thì nên dùng ngôn ngữ lập trình, và nếu có thể thì nên là ngôn ngữ kiểu tĩnh có kiểm tra lỗi, tự động hoàn thành và tài liệu inline
      AWS CDK là ví dụ điển hình. Việc viết CloudFormation thuần túy rất đau đớn, nhưng CDK không phải là chồng thêm khả năng lập trình lên CloudFormation mà là sinh ra CloudFormation. Đầu vào mà AWS tiêu thụ vẫn là CloudFormation tương đối đơn giản và ổn định
  • Vừa nhìn tiêu đề là tôi tưởng sẽ nói về Kubernetes.
    Kubernetes API khá trực quan và có lược đồ JSON được định nghĩa tốt. Phần lớn thời gian học k8s lẽ ra nên dành để hiểu cách dùng API, nhưng trên thực tế lại bị dùng để mò cách sử dụng Helm chart.
    Tôi không nghĩ Jsonnet, Ksonnet, Nu hay CUE đã đạt được mức độ phổ biến lớn đến vậy. Có vẻ đa số dùng Kustomize hơn, vì nó tương đối trực quan và được tích hợp sẵn trong kubectl.
    Công cụ mong muốn là thứ có thể cung cấp kiểm tra kiểu theo lược đồ k8s, kiểm định và cảnh báo ngừng hỗ trợ phiên bản cho người viết định nghĩa, đưa cho người dùng một đầu ra duy nhất dễ kiểm tra, thất bại theo kiểu nguyên tử nếu cụm không hỗ trợ bất kỳ đối tượng/phiên bản nào, và phải được tích hợp vào chuỗi công cụ mặc định.
    Các script TypeScript chạy bằng Bun hay Deno, xuất ra một hàm nhận đối số và trả về danh sách định nghĩa, có vẻ sẽ khá hợp khi đi cùng deno compile và tương tự, nhưng lại vi phạm điều kiện phải tích hợp vào chuỗi công cụ mặc định.

    • Đây là một kiểu mẫu xuất hiện trên toàn bộ ngành phần mềm. Thay vì học các nguyên thủy và nền tảng cơ bản mà hệ thống dựa vào, người ta lại học cả đống lớp trừu tượng đặt lên trên vì cho rằng phần gốc quá khó.
      Bạn được che chắn khỏi các chi tiết cấp thấp, nhưng khi có sự cố thì phải đối mặt với một chồng trừu tượng khổng lồ khiến việc chẩn đoán và gỡ lỗi khó hơn nhiều.
      Việc tìm ra chính xác điều gì đang diễn ra trở nên khó hơn rất nhiều, và bạn bị phụ thuộc vào các lớp trừu tượng, đồng thời phải gánh cả những bản cập nhật do nhà cung cấp tung ra lẫn các vấn đề khác trong đồ thị phụ thuộc.
    • Trong hệ thống của chúng tôi, chúng tôi dùng jsonnet và hoàn toàn không liên quan gì tới k8s. Nó không hẳn là công cụ quá phổ biến mà là một công cụ ngách cho cấu hình phức tạp, và cũng không phải loại được quảng bá rầm rộ.
      Nó gần như làm được điều chúng tôi cần mà không gặp vấn đề gì, chạy đa nền tảng và có thể dùng xuyên qua nhiều ngôn ngữ. Tôi đã nhúng nó vào các file thực thi C++, .NET và JVM.
      Cấu hình JSON đầu ra có thể đi cùng một hệ công cụ rất phong phú mà các lựa chọn thay thế như toml/yaml/hocon/ini v.v. khó mà có được. Tôi từng thử dùng HOCON ở các ngôn ngữ không phải JVM, nhưng luôn vướng phải một trường hợp biên nào đó.
    • Có lẽ nó không đáp ứng được yêu cầu thứ hai, và chắc chắn không đáp ứng được yêu cầu thứ ba: https://cdk8s.io/docs/latest/
    • Ý tưởng giữ mọi thứ đơn giản là tốt, và về mặt triển khai tôi cũng cố dùng kustomize hoặc yaml thuần bất cứ khi nào có thể.
      Nhưng khi thực sự phải quản lý một hệ thống lớn, cuối cùng bạn cũng không thể tránh được lợi ích của template.
    • kustomize và đặc biệt là helm quá rối rắm, trong khi các file YAML của Kubernetes lại rất dễ viết và dễ hiểu.
  • Nhìn cách các lập trình viên ít suy nghĩ đến mức nào về việc xử lý cấu hình cho đúng thật buồn cười.
    Nó trông như chỉ là một tập hợp khóa và giá trị được lưu trong file hoặc được sinh ra bằng mã, nhưng thật ra đó là tất cả mọi thứ. Đó chính là lập trình.
    Mọi thứ đều là cấu hình, và mọi đối số hàm cũng là một dạng cấu hình. Mọi cấu hình trong file bên ngoài rồi cũng sẽ trở thành đối số hàm theo cách nào đó.
    Vấn đề nằm ở cách biểu diễn bằng văn bản thuần của mã. File cấu hình khai báo trông có vẻ tốt vì bạn có thể nhìn mọi thứ ở một chỗ, nhưng khi biến cấu hình thành chương trình thì rất khó tìm ra cần thay đổi ở đâu.
    Nếu mã có thể chạy trực tiếp để hiển thị biểu diễn của cấu hình cuối cùng, và có thể lần vết mỗi giá trị cấu hình cuối cùng được tạo ra như thế nào, thì sẽ không thành vấn đề. Nhưng dù tính năng này khá đơn giản, lại không có hệ thống nào được thiết kế theo cách đó. Cấu hình lúc nào cũng là thứ được nghĩ đến sau cùng.
    Nếu mở rộng ý tưởng này ra toàn bộ lập trình, bạn sẽ phải có khả năng nhìn thấy toàn bộ mã phụ thuộc vào một giá trị cấu hình, cùng với các phép biến đổi của nó.
    Ngoài ra, phần lớn cấu hình mang tính quan hệ/đồ thị, nên có lẽ tốt hơn là đặt chúng trong một cơ sở dữ liệu trung tâm. Các giá trị cấu hình khác nhau có liên hệ với nhau. Vì vậy, cấu hình nên được xem trong một trình chỉnh sửa cơ sở dữ liệu/đồ thị.
    Một khi rời khỏi văn bản thuần, mọi thứ bắt đầu đơn giản hơn rất nhiều, nhưng các tính năng ngôn ngữ đã nói ở trên vẫn cần thiết.

    • Một khách hàng từng làm việc tương tự đang cố gắng rất nghiêm túc. Họ có những file “cấu hình” hơn 1500 dòng cho mỗi sản phẩm, và dùng chúng để tạo bản vẽ kỹ thuật cùng các file sản xuất.
      Các cấu hình đang cố dùng quy ước đặt tên để gom các biến có liên quan lại với nhau.
      Họ muốn chuyển sang cấu trúc dữ liệu lồng nhau thực sự, có lẽ là JSON, nhưng các kỹ sư thì tuyệt đối không muốn viết code, nên cấu hình dưới dạng mã là điều không thể, mà nó cũng có các nhược điểm đã nói ở trên.
      Ý tưởng tiếp theo là cần có cách hiển thị và chỉnh sửa cấu hình tốt hơn. Tôi đã nghĩ đến một UI trực quan cho phép khám phá biểu diễn của sản phẩm cuối cùng, chọn các bộ phận và chỉnh sửa tham số theo cách đó.
      Tôi muốn biết liệu đây có phải hướng đi đúng không. Nếu không, mong được giải thích thêm một chút. Trọng tâm của ứng dụng này là cấu hình.
  • Tệ hơn nữa là ở những nơi như CI/CD, YAML gần như trở thành một ngôn ngữ lập trình. Mà lại là một thứ rất dài dòng, không trực quan, đặc tả kém và còn khác nhau tùy nhà cung cấp.

    • Gần như là lặp lại sai lầm của Java đầu thập niên 2010. Khi đó, cả ứng dụng thường được gắn lại với nhau bằng một khối XML khổng lồ dùng để cấu hình dependency injection.
      Dù có DTD và kiểm định XML, nó vẫn có đặc tính quen thuộc là chỉ nổ muộn và đưa ra các thông báo lỗi khó hiểu.
      Khi đó rất nhiều sự thất vọng đổ dồn lên XML, nhưng nhìn vào địa ngục YAML giữa thập niên 2020 thì vấn đề không nằm ở bản thân ngôn ngữ đánh dấu.
    • Chính xác. Chúng tôi dùng ytt[0], một “phiên bản được chỉnh sửa nhẹ của ngôn ngữ lập trình Starlark, vốn là một phương ngữ của Python”.
      Tôi thực sự ghét việc chôn logic ở đâu đó bên trong template YAML.
      [0] https://tanzu.vmware.com/developer/guides/ytt-gs/
    • Ở một số nơi làm việc với Kubernetes, người ta thực sự dùng nghiêm túc cụm từ kỹ sư YAML.
    • YAML giống như quả lê Bradford trong thế giới các định dạng tuần tự hóa: ban đầu trông có vẻ ổn, nhưng khi dự án tồn tại đủ lâu và YAML ngày càng phình to, nó sẽ sụp đổ dưới chính sức nặng của các nhánh của nó.
    • Tệ hơn nữa là mỗi thế hệ lại lặp lại sai lầm này. Tôi không biết liệu S-expression có phải câu trả lời hay không, nhưng Terraform HCL lẽ ra không nên được tạo ra ngay từ đầu.
  • Việc Helm thắng thế thật sự rất đáng buồn. Tôi làm công việc liên quan đến k8s mã nguồn mở ở công ty, và 100% người dùng đều yêu cầu tạo Helm chart, nên cuối cùng vẫn phải làm
    Làm việc với nó rất khổ sở. Tên file là kiểu foo.yaml nhưng thực ra không phải YAML, nên editor không thể hỗ trợ. Phải đẩy toàn bộ dữ liệu qua indent 4 để canh đúng thụt lề YAML
    Điều u ám nhất là phải phơi bày lại toàn bộ tính năng của Kubernetes theo cách riêng của nó. Nếu ai đó muốn thêm deployment.spec.template.spec.fooBars thì phải thêm deploymentFooBars vào values.yaml rồi nối lại. Tính năng nào cũng lặp lại như vậy
    Đây đúng là một ví dụ đi sai hướng của kiểu “cái tệ lại thành cái tốt”. Tôi cũng từng làm trò kinh khủng kiểu sed -e s/$FOO/foo/g để tự triển khai template, và có lẽ Helm cũng bắt đầu như thế. Kết quả là một mớ hỗn độn
    Cá nhân tôi đã dùng Kustomize từ trước khi nó được đưa vào kubectl, và luôn khá hài lòng. Nó có nhiều điểm kỳ quặc, nhưng ít nhất nó hiểu ý nghĩa của các đối tượng được tạo ra nên tiết kiệm thời gian
    Jsonnet tốt hơn nhiều. Chúng tôi phân phối kèm triển khai Envoy cho routing traffic phức tạp như một phần của ứng dụng k8s, và cấu hình Envoy thì dài dòng nhưng lại rất dễ xử lý bằng Jsonnet: https://github.com/pachyderm/pachyderm/blob/master/etc/gener...
    Tôi đang nghiêm túc cân nhắc transpile jsonnet sang ngôn ngữ template Go rồi triển khai mọi thứ bằng Jsonnet. Ít nhất cũng sẽ dễ bảo trì hơn phần nào, và helm install vẫn sẽ chạy bình thường nên chẳng ai nhận ra
    Nhưng có vẻ Helm sẽ là dấu chấm hết của Kubernetes. Nếu có công cụ cạnh tranh nào cho việc cấp phát tài nguyên tính toán/chạy container xuất hiện cùng một ngôn ngữ cấu hình tử tế, mọi người sẽ chuyển sang chỉ sau một đêm

    • Khi bạn bắt đầu muốn dùng sed -e s/$FOO/foo/g, có lẽ nên xem qua envsubst như một giải pháp chuẩn hơn và tốt hơn một chút
      Nếu đang nói đến chuyện template hóa hoặc chỉnh sửa Helm chart bằng jsonnet, Tanka cũng có thể hữu ích: https://tanka.dev/helm
    • Tôi muốn tin vào dự đoán rằng Helm sẽ là dấu chấm hết của Kubernetes
      Nhưng những nơi tôi từng làm vẫn sợ thay đổi nên cứ tiếp tục dùng tf/hcl và helm. Ít nhất trong dự án cá nhân thì còn thấy dễ thở hơn đôi chút
  • Tôi nghĩ ở đây có vấn đề. Nhưng tôi không chắc kiểu người chọn YAML làm ngôn ngữ cấu hình có xem đó là vấn đề hay không
    Có một xung đột trực tiếp giữa cách biểu diễn dữ liệu hướng con người và cách biểu diễn dữ liệu hướng máy tính. Máy tính thích thứ gì đó giống Lisp, còn con người thích thứ gì đó giống Python
    Nếu bạn là người muốn thao tác cấu hình Kubernetes bằng máy tính, việc Kubernetes dùng YAML hẳn sẽ khiến bạn khó chịu âm ỉ. Nhưng cộng đồng Kubernetes dường như chủ yếu là kiểu người thiên về YAML, nên họ cần gì phải quan tâm đến việc file cấu hình trở thành cơn ác mộng khi đưa logic lập trình vào
    Đây chính là nhược điểm của YAML trong tình huống này, và tôi nghĩ những người làm k8s nhìn chung đủ thông minh để lường trước điều đó
    Câu “YAML là tập cha của JSON” theo tôi không đúng trong thực tế, bất kể tác giả đặc tả có viết gì trong tài liệu. Nếu bạn đổi toàn bộ cấu hình YAML sang JSON thì đội DevOps sẽ nổi giận
    Hai định dạng dữ liệu có thể biểu diễn cùng một ngữ nghĩa, nhưng mọi ngôn ngữ cùng biên dịch xuống một kiến trúc CPU cũng vậy. JSON và YAML trong thực tế là hai thứ riêng biệt, và trộn chúng với nhau không phải ý hay

    • Trớ trêu là nếu tôi nhớ không nhầm, manifest k8s ban đầu vốn được giả định là để máy sinh ra, chứ không phải để con người trực tiếp viết
      Tất nhiên rồi mọi người vẫn viết bằng tay, và khi không chịu nổi nữa thì bắt đầu gắn thêm template. Có vẻ mọi chuyện lúc nào cũng diễn ra như vậy
      Văn bản viết tay không bị thay thế bởi văn bản tuần tự hóa cấu hình do máy sinh ra, mà thay vào đó bị thay bằng chính văn bản viết tay ban đầu nhưng được gắn thêm template
    • Câu “YAML là tập cha của JSON” chỉ có nghĩa là mọi tài liệu JSON đều là tài liệu YAML hợp lệ. Nó không có nghĩa YAML giống hệt JSON
  • Nguyên tắc cá nhân của tôi là không nên dùng nội suy chuỗi để sinh ra mã cho máy đọc. Ngôn ngữ template rốt cuộc cũng chỉ là nội suy chuỗi được làm cho đẹp hơn
    Tôi đã thấy hậu quả của cả SQL injection lẫn cross-site scripting. Chừng nào còn tiếp tục ném văn bản tùy ý vào trình thông dịch thì những chuyện này vẫn sẽ tiếp diễn
    Vì vậy tôi cũng cho rằng không nên dùng file template để tạo HTML
    Các lựa chọn thay thế cho ngôn ngữ template HTML có Ruby Haml và JavaScript Pug. Những ngôn ngữ này cung cấp một cách xác định rõ ràng toàn bộ cây gồm thẻ, thuộc tính và nút văn bản
    Nếu bạn không thích kiểu thụt lề có ý nghĩa của Python thì JavaScript có JSX. Phần trông như HTML trong JSX được biên dịch thành các biểu thức createElement để tạo cây tài liệu web, và cây đó có thể được xuất ra HTML nếu cần
    Haml, Pug và JSX không phải là ngôn ngữ template, dù chúng có thể xuất ra HTML. Tương tự, JSON.stringify(myObj) cũng không phải là ngôn ngữ template cho JSON
    Mã cho máy đọc, nếu có thể, nên được sinh ra bằng công cụ hiểu và tận dụng cấu trúc đã biết của ngôn ngữ đích

    • Nói rằng Haml, Pug và JSX không phải là ngôn ngữ template thì không hợp lý, trừ khi dùng định nghĩa mang tính cá nhân rằng ngôn ngữ template là “nội suy chuỗi được làm cho đẹp hơn”
      Haml là một hệ thống template để tránh mã nội tuyến trong tài liệu web và làm HTML gọn gàng hơn, còn Pug là một template engine giàu tính năng cho Node.js
      Tôi có thể đồng ý rằng xét nghiêm ngặt thì JSX không phải ngôn ngữ template
      Cuối cùng thì tất cả chúng đều được biên dịch thành HTML. Chỉ là thay vì nội suy chuỗi, chúng được parse thành cây cú pháp rồi render thành HTML dựa trên hiểu biết nội bộ về cấu trúc hợp lệ
      Template YAML là nội suy chuỗi được làm cho đẹp hơn, và không phải ngôn ngữ template, hoặc ít nhất là một ngôn ngữ template được hiện thực rất tệ
    • Không phải mọi ngôn ngữ template đều là ngôn ngữ template chuỗi. Ví dụ, nếu xem PHP là ngôn ngữ template cho văn bản, thì theo cùng logic, XQuery là ngôn ngữ template cho XML
    • Đây mới là cốt lõi của vấn đề. YAML và template chỉ là yếu tố gây xao nhãng. Cuối cùng thì nó quy về việc chuỗi là một kiểu quá chung chung và chúng ta dùng nó một cách lười biếng
      Quy tắc cá nhân của tôi là mỗi khi một giá trị đi vào chuỗi thì nó phải luôn được mã hóa cho đúng
      Trước đây tôi đã viết về chủ đề này: https://kevincox.ca/2022/02/08/escape-everything/
      Tóm lại, mọi chuỗi đều có một định dạng cần tuân theo, như HTML, SQL, hay đầu ra terminal cho con người đọc. Mỗi khi đưa một giá trị vào chuỗi, bạn phải mã hóa nó đúng theo định dạng đó, nhưng gần như chúng ta không bao giờ làm vậy
  • Chúng tôi đang chuyển sang cuelang [1]. Cá nhân tôi thấy nó có thiết kế tốt hơn Jsonette
    Kubernetes đã có sẵn cơ chế điều phối trạng thái, nên thứ còn thiếu trong cấu hình này chỉ là xóa, và giờ có thể giải quyết bằng tính năng prune [2]
    [1] https://cuelang.org/docs/integrations/k8s/
    [2] https://kubernetes.io/blog/2023/05/09/introducing-kubectl-ap...

    • Tôi có thể khuyên dùng cuelang. Công ty tôi bắt đầu dùng nó và nó thực sự rất tốt
      Một vài thông báo lỗi hơi khó diễn giải, nhưng bù lại nó bắt được rất nhiều lỗi từ trước nên hoàn toàn chấp nhận được. Những lần hiếm hoi giờ đây phải tự viết yaml thì so ra thấy chán kinh khủng
  • Trong những lúc thế này tôi thường chen vào bằng câu “bạn đã nghe về vị cứu tinh CUELang của chúng ta chưa?”: https://cuelang.org/
    Nó vẫn chưa Turing-complete, nhưng đủ biểu đạt để khử trùng lặp, và bạn có thể định nghĩa schema lẫn dữ liệu bằng cùng một ngôn ngữ trong cùng file hoặc file riêng, đồng thời còn có union type
    Nó có thể sinh YAML hoặc JSON, và có thể validate chính nó hoặc các file YAML/JSON
    Nhược điểm lớn nhất là hiện tại chỉ có implementation bằng Go, nên có thể cần subprocess hoặc FFI

    • Chúng tôi có một pipeline nhận vào các file cuelang cực kỳ ngắn gọn
      Sau đó nó tạo file JSON cho từng ứng dụng, rồi một công cụ nào đó tạo ra định nghĩa XML, định nghĩa đó được áp vào một file XLS do các kiến trúc sư sở hữu, và từ đó nó nhả ra YAML để áp vào Helm chart
      Các chart triển khai client k8s, và client đó tương tác với cluster chính bằng JSON thông qua API
      Mất kha khá thời gian, nhưng chúng tôi đang dùng công cụ tốt nhất cho từng công việc
    • So với dhall thì thế nào?