Hiện đại hóa các ứng dụng cho Kubernetes
Các ứng dụng không trạng thái hiện đại được xây dựng và thiết kế để chạy trong các containers phần mềm như Docker và được quản lý bởi các cụm containers như Kubernetes. Chúng được phát triển bằng cách sử dụng các nguyên tắc và mẫu Cloud Native và Twelve Factor , để giảm thiểu sự can thiệp thủ công và tối đa hóa tính di động và dự phòng. Việc di chuyển máy ảo hoặc các ứng dụng dựa trên kim loại trần vào các container (được gọi là “container”) và triển khai chúng bên trong các cụm thường liên quan đến những thay đổi đáng kể trong cách các ứng dụng này được xây dựng, đóng gói và phân phối.Xây dựng dựa trênỨng dụng kiến trúc cho Kubernetes , trong hướng dẫn khái niệm này, ta sẽ thảo luận về các bước cấp cao để hiện đại hóa các ứng dụng của bạn, với mục tiêu cuối cùng là chạy và quản lý chúng trong một cụm Kubernetes. Mặc dù bạn có thể chạy các ứng dụng trạng thái như database trên Kubernetes, hướng dẫn này tập trung vào việc di chuyển và hiện đại hóa các ứng dụng không trạng thái, với dữ liệu liên tục được download repodata bên ngoài. Kubernetes cung cấp chức năng nâng cao để quản lý và mở rộng hiệu quả các ứng dụng không trạng thái và ta sẽ khám phá các thay đổi về ứng dụng và cơ sở hạ tầng cần thiết để chạy các ứng dụng có thể mở rộng, có thể quan sát và di động trên Kubernetes.
Chuẩn bị Đơn xin Di cư
Trước khi chứa ứng dụng của bạn hoặc viết file cấu hình Kubernetes Pod và Deployment, bạn nên áp dụng các thay đổi ở cấp ứng dụng để tối đa hóa tính di động và khả năng quan sát của ứng dụng trong Kubernetes. Kubernetes là một môi trường tự động hóa cao, có thể tự động triển khai và khởi động lại các containers ứng dụng bị lỗi, vì vậy, điều quan trọng là phải xây dựng logic ứng dụng thích hợp để giao tiếp với bộ điều phối containers và cho phép nó tự động mở rộng ứng dụng của bạn khi cần thiết.
Extract dữ liệu cấu hình
Một trong những thay đổi cấp ứng dụng đầu tiên cần thực hiện là extract cấu hình ứng dụng từ mã ứng dụng. Cấu hình bao gồm bất kỳ thông tin nào khác nhau giữa các triển khai và môi trường, chẳng hạn như điểm cuối dịch vụ, địa chỉ database , thông tin đăng nhập cũng như các tham số và tùy chọn khác nhau. Ví dụ: nếu bạn có hai môi trường, chẳng hạn như staging
và production
, và mỗi môi trường chứa một database riêng biệt, ứng dụng của bạn sẽ không có điểm cuối database và thông tin đăng nhập được khai báo rõ ràng trong mã, nhưng được lưu trữ ở một vị trí riêng biệt, hoặc dưới dạng các biến môi trường, file local hoặc kho key-value bên ngoài, từ đó các giá trị được đọc vào ứng dụng.
Việc mã hóa cứng các thông số này vào mã của bạn gây ra rủi ro bảo mật vì dữ liệu cấu hình này thường bao gồm thông tin nhạy cảm, sau đó bạn sẽ kiểm tra hệ thống kiểm soát version của bạn . Nó cũng làm tăng độ phức tạp khi bạn phải duy trì nhiều version ứng dụng của bạn , mỗi version bao gồm cùng một logic ứng dụng cốt lõi, nhưng thay đổi một chút về cấu hình. Khi các ứng dụng và dữ liệu cấu hình của chúng phát triển, việc mã hóa cấu hình cứng thành mã ứng dụng nhanh chóng trở nên khó sử dụng.
Bằng cách extract các giá trị cấu hình từ mã ứng dụng của bạn và thay vào đó nhập chúng từ môi trường đang chạy hoặc các file local , ứng dụng của bạn sẽ trở thành một gói chung, di động có thể được triển khai vào bất kỳ môi trường nào, miễn là bạn cung cấp cho nó dữ liệu cấu hình đi kèm. Phần mềm containers như Docker và phần mềm cụm như Kubernetes đã được thiết kế xung quanh mô hình này, xây dựng các tính năng để quản lý dữ liệu cấu hình và đưa nó vào containers ứng dụng. Các tính năng này sẽ được đề cập chi tiết hơn trong phần Containerizing và Kubernetes .
Dưới đây là một ví dụ nhanh minh họa cách ngoại hóa hai giá trị cấu hình DB_HOST
và DB_USER
từ mã của ứng dụng Python Flask đơn giản. Ta sẽ cung cấp chúng trong môi trường đang chạy của ứng dụng dưới dạng env vars, từ đó ứng dụng sẽ đọc chúng:
from flask import Flask DB_HOST = 'mydb.mycloud.com' DB_USER = 'sammy' app = Flask(__name__) @app.route('/') def print_config(): output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER) return output
Chạy ứng dụng đơn giản này (tham khảo Flask Quickstart để tìm hiểu cách thực hiện) và truy cập điểm cuối web của nó sẽ hiển thị một trang chứa hai giá trị cấu hình này.
Bây giờ, đây là ví dụ tương tự với các giá trị cấu hình được bên ngoài môi trường chạy của ứng dụng:
import os from flask import Flask DB_HOST = os.environ.get('APP_DB_HOST') DB_USER = os.environ.get('APP_DB_USER') app = Flask(__name__) @app.route('/') def print_config(): output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER) return output
Trước khi chạy ứng dụng, ta đặt các biến cấu hình cần thiết trong môi trường local :
- export APP_DB_HOST=mydb.mycloud.com
- export APP_DB_USER=sammy
- flask run
Trang web được hiển thị phải chứa văn bản giống như trong ví dụ đầu tiên, nhưng cấu hình của ứng dụng hiện có thể được sửa đổi độc lập với mã ứng dụng. Bạn có thể sử dụng cách tiếp cận tương tự để đọc các tham số cấu hình từ file local .
Trong phần tiếp theo, ta sẽ thảo luận về việc di chuyển trạng thái ứng dụng ra bên ngoài containers .
Trạng thái ứng dụng giảm tải
Các ứng dụng Cloud Native chạy trong containers và được điều phối động bởi phần mềm cụm như Kubernetes hoặc Docker Swarm. Một ứng dụng hoặc dịch vụ nhất định có thể được cân bằng tải trên nhiều bản sao và bất kỳ containers ứng dụng riêng lẻ nào cũng có thể bị lỗi, với mức độ gián đoạn dịch vụ tối thiểu hoặc không cho khách hàng. Để kích hoạt tính năng chia tỷ lệ theo chiều ngang, dự phòng này, các ứng dụng phải được thiết kế theo kiểu không trạng thái. Điều này nghĩa là chúng phản hồi các yêu cầu của khách hàng mà không lưu trữ local dữ liệu ứng dụng và ứng dụng client liên tục và tại bất kỳ thời điểm nào nếu containers ứng dụng đang chạy bị phá hủy hoặc khởi động lại, dữ liệu quan trọng sẽ không bị mất.
Ví dụ: nếu bạn đang chạy ứng dụng sổ địa chỉ và ứng dụng của bạn thêm, xóa và sửa đổi địa chỉ liên hệ khỏi sổ địa chỉ, thì repodata sổ địa chỉ phải là database bên ngoài hoặc repodata khác và dữ liệu duy nhất được lưu trong bộ nhớ containers phải là về bản chất ngắn hạn và dùng một lần mà không làm mất thông tin nghiêm trọng. Dữ liệu tồn tại qua các lượt truy cập của user như phiên cũng sẽ được chuyển đến các cửa hàng dữ liệu bên ngoài như Redis. Khi nào có thể, bạn nên tải mọi trạng thái từ ứng dụng của bạn xuống các dịch vụ như database hoặc bộ nhớ đệm được quản lý.
Đối với các ứng dụng trạng thái yêu cầu lưu trữ dữ liệu liên tục (như database MySQL được sao chép), Kubernetes xây dựng các tính năng để gắn dung lượng lưu trữ khối liên tục vào các containers và Pod. Để đảm bảo Pod có thể duy trì trạng thái và truy cập cùng một ổ đĩa liên tục sau khi khởi động lại, dung lượng công việc StatefulSet phải được sử dụng. StatefulSets lý tưởng để triển khai database và các repodata lâu dài khác cho Kubernetes.
Vùng chứa không trạng thái cho phép khả năng di động tối đa và sử dụng đầy đủ các tài nguyên cloud có sẵn, cho phép trình lập lịch Kubernetes nhanh chóng mở rộng ứng dụng của bạn lên và xuống và chạy Pod ở bất kỳ nơi nào có sẵn tài nguyên. Nếu bạn không yêu cầu sự ổn định và đảm bảo thứ tự do dung lượng công việc StatefulSet cung cấp, bạn nên sử dụng dung lượng công việc Triển khai để quản lý và mở rộng quy mô và các ứng dụng của bạn .
Để tìm hiểu thêm về thiết kế và kiến trúc của microservices Cloud Native, không trạng thái, hãy tham khảo Sách trắng Kubernetes của ta .
Thực hiện kiểm tra sức khỏe
Trong mô hình Kubernetes, máy bay điều khiển cụm có thể được dựa vào để sửa chữa một ứng dụng hoặc dịch vụ bị hỏng. Nó thực hiện điều này bằng cách kiểm tra tình trạng của các Group ứng dụng và khởi động lại hoặc lên lịch lại các containers không tốt hoặc không phản hồi. Theo mặc định, nếu containers ứng dụng của bạn đang chạy, Kubernetes sẽ coi Pod của bạn là “khỏe mạnh”. Trong nhiều trường hợp, đây là một chỉ báo tin cậy cho tình trạng của một ứng dụng đang chạy. Tuy nhiên, nếu ứng dụng của bạn bị bế tắc và không thực hiện bất kỳ công việc có ý nghĩa nào, thì quá trình ứng dụng và containers sẽ tiếp tục chạy vô thời hạn và theo mặc định Kubernetes sẽ giữ cho containers bị ngừng hoạt động.
Để giao tiếp đúng tình trạng ứng dụng với mặt phẳng điều khiển Kubernetes, bạn nên thực hiện kiểm tra tình trạng ứng dụng tùy chỉnh cho biết khi nào một ứng dụng đang chạy và sẵn sàng nhận lưu lượng.Loại kiểm tra tình trạng đầu tiên được gọi là thăm dò mức độ sẵn sàng và cho Kubernetes biết khi nào ứng dụng của bạn sẵn sàng nhận lưu lượng truy cập. Loại kiểm tra thứ hai được gọi là thăm dò độ sống động và cho phép Kubernetes biết khi nào ứng dụng của bạn hoạt động tốt và đang chạy. Tác nhân Kubelet Node có thể thực hiện các thăm dò này trên các Pod đang chạy bằng 3 phương pháp khác nhau:
- HTTP: Đầu dò Kubelet thực hiện yêu cầu HTTP GET đối với một điểm cuối (like
/health
) và thành công nếu trạng thái phản hồi nằm trong repository ảng từ 200 đến 399 - Lệnh containers : Đầu dò Kubelet thực hiện lệnh bên trong containers đang chạy. Nếu mã thoát là 0, thì thăm dò thành công.
- TCP: Đầu dò Kubelet cố gắng kết nối với containers của bạn trên một cổng được chỉ định. Nếu nó có thể cài đặt kết nối TCP, thì thăm dò thành công.
Bạn nên chọn phương pháp thích hợp tùy thuộc vào (các) ứng dụng đang chạy, ngôn ngữ lập trình và khuôn khổ. Cả hai đầu dò mức độ sẵn sàng và khả năng sống đều có thể sử dụng cùng một phương pháp thăm dò và thực hiện cùng một kiểm tra, nhưng việc đưa vào một đầu dò mức độ sẵn sàng sẽ đảm bảo Pod không nhận được lưu lượng truy cập cho đến khi đầu dò bắt đầu thành công.
Khi lập kế hoạch và suy nghĩ về việc chứa ứng dụng của bạn và chạy nó trên Kubernetes, bạn nên phân bổ thời gian lập kế hoạch để xác định nghĩa "khỏe mạnh" và "sẵn sàng" cho ứng dụng cụ thể của bạn cũng như thời gian phát triển để triển khai và thử nghiệm các điểm cuối và / hoặc kiểm tra lệnh.
Đây là một điểm cuối sức khỏe tối thiểu cho ví dụ Flask được tham chiếu ở trên:
. . . @app.route('/') def print_config(): output = 'DB_HOST: {} -- DB_USER: {}'.format(DB_HOST, DB_USER) return output @app.route('/health') def return_ok(): return 'Ok!', 200
Một đầu dò độ sống của Kubernetes kiểm tra đường dẫn này sau đó sẽ trông giống như sau:
. . . livenessProbe: httpGet: path: /health port: 80 initialDelaySeconds: 5 periodSeconds: 2
Các initialDelaySeconds
quy định cụ thể lĩnh vực mà Kubernetes (đặc biệt là Node Kubelet) nên thăm dò /health
endpoint sau khi chờ đợi 5 giây, và periodSeconds
kể Kubelet để thăm dò /health
mỗi 2 giây.
Để tìm hiểu thêm về các đầu dò sống và sẵn sàng, hãy tham khảo tài liệu Kubernetes .
Mã công cụ để ghi log và giám sát
Khi chạy ứng dụng được chứa trong một môi trường như Kubernetes, điều quan trọng là phải xuất bản dữ liệu đo từ xa và ghi log để theo dõi và gỡ lỗi hiệu suất ứng dụng của bạn. Tích hợp các tính năng để xuất bản các số liệu hiệu suất như thời lượng phản hồi và tỷ lệ lỗi sẽ giúp bạn theo dõi ứng dụng của bạn và cảnh báo khi ứng dụng của bạn không lành mạnh.
Một công cụ bạn có thể sử dụng để giám sát các dịch vụ của bạn là Prometheus , bộ công cụ giám sát và cảnh báo hệ thống open-souce , được tổ chức bởi Cloud Native Computing Foundation (CNCF). Prometheus cung cấp một số thư viện ứng dụng client để hỗ trợ mã của bạn với nhiều loại số liệu khác nhau để đếm các sự kiện và thời lượng của chúng. Ví dụ: nếu bạn đang sử dụng khung Flask Python, bạn có thể sử dụng ứng dụng khách Prometheus Python để thêm trình trang trí vào các hàm xử lý yêu cầu của bạn để theo dõi thời gian xử lý yêu cầu. Các chỉ số này sau đó có thể được Prometheus loại bỏ tại một điểm cuối HTTP như /metrics
.
Một phương pháp hữu ích để sử dụng khi thiết kế thiết bị đo đạc ứng dụng của bạn là phương pháp RED. Nó bao gồm ba chỉ số yêu cầu chính sau:
- Xếp hạng: Số lượng yêu cầu mà ứng dụng của bạn nhận được
- Lỗi: Số lỗi do ứng dụng của bạn phát ra
- Thời lượng: Lượng thời gian cần để ứng dụng của bạn gửi phản hồi
Bộ chỉ số tối thiểu này sẽ cung cấp cho bạn đủ dữ liệu để cảnh báo khi hiệu suất ứng dụng của bạn giảm sút. Việc triển khai thiết bị này cùng với các kiểm tra sức khỏe được thảo luận ở trên sẽ cho phép bạn nhanh chóng phát hiện và phục hồi sau một ứng dụng bị lỗi.
Để tìm hiểu thêm về các tín hiệu cần đo khi giám sát các ứng dụng của bạn, hãy tham khảo Giám sát Hệ thống Phân tán từ sách Kỹ thuật về độ tin cậy của Trang web của Google.
Ngoài việc suy nghĩ và thiết kế các tính năng để xuất bản dữ liệu đo từ xa, bạn cũng nên lập kế hoạch cách ứng dụng của bạn sẽ đăng nhập trong môi trường dựa trên cụm phân tán. Lý tưởng nhất là bạn nên xóa các tham chiếu cấu hình được mã hóa cứng đến các file log local và folder log , thay vào đó đăng nhập trực tiếp vào stdout và stderr. Bạn nên coi log là một dòng sự kiện liên tục hoặc chuỗi các sự kiện được sắp xếp theo thời gian. Sau đó, stream kết quả này sẽ được ghi lại bởi containers bao quanh ứng dụng của bạn, từ đó nó có thể được chuyển tiếp đến một lớp ghi log như ngăn xếp EFK (Elasticsearch, Fluentd và Kibana). Kubernetes cung cấp rất nhiều sự linh hoạt trong việc thiết kế kiến trúc ghi log của bạn, ta sẽ khám phá chi tiết hơn bên dưới.
Xây dựng Logic quản trị thành API
Sau khi ứng dụng của bạn được chứa và cài đặt và chạy trong môi trường cụm như Kubernetes, bạn có thể không còn quyền truy cập shell vào containers đang chạy ứng dụng của bạn. Nếu bạn đã triển khai kiểm tra tình trạng, ghi log và theo dõi đầy đủ, bạn có thể nhanh chóng được cảnh báo và gỡ lỗi các vấn đề về production , nhưng việc thực hiện ngoài việc khởi động lại và triển khai lại containers có thể khó khăn. Cho nhanh chóng sửa lỗi vận hành và bảo trì như đỏ bừng mặt hàng đợi hoặc xóa một bộ nhớ cache, bạn nên thực hiện các điểm cuối API thích hợp để bạn có thể thực hiện các hoạt động mà không cần phải khởi động lại container hoặc exec
vào container chạy và thực hiện hàng loạt các lệnh.Các vật chứa phải được coi là vật bất biến, và nên tránh sử dụng thủ công trong môi trường production . Nếu bạn phải thực hiện các việc quản trị một lần, như xóa bộ nhớ đệm, bạn nên hiển thị chức năng này qua API.
Tóm lược
Trong các phần này, ta đã thảo luận về những thay đổi cấp ứng dụng mà bạn có thể cần thực hiện trước khi chứa ứng dụng của bạn và chuyển nó sang Kubernetes. Để có hướng dẫn chuyên sâu hơn về cách xây dựng ứng dụng Cloud Native, hãy tham khảoỨng dụng kiến trúc cho Kubernetes .
Bây giờ ta sẽ thảo luận về một số cân nhắc cần lưu ý khi xây dựng containers cho ứng dụng của bạn.
Giữ lại đơn đăng ký của bạn
Đến đây bạn đã triển khai logic ứng dụng để tối đa hóa tính di động và khả năng quan sát của nó trong môi trường dựa trên cloud , đã đến lúc đóng gói ứng dụng của bạn bên trong containers . Theo mục đích của hướng dẫn này, ta sẽ sử dụng containers Docker, nhưng bạn nên sử dụng bất kỳ triển khai containers nào phù hợp nhất với nhu cầu production của bạn.
Khai báo rõ ràng các phụ thuộc
Trước khi tạo Dockerfile cho ứng dụng của bạn, một trong những bước đầu tiên là kiểm tra phần mềm và hệ điều hành phụ thuộc vào ứng dụng của bạn để chạy chính xác. Dockerfiles cho phép bạn version rõ ràng mọi phần mềm được cài đặt vào hình ảnh và bạn nên tận dụng tính năng này bằng cách khai báo rõ ràng hình ảnh root , thư viện phần mềm và các version ngôn ngữ lập trình.
Tránh các thẻ latest
và các gói chưa được version càng nhiều càng tốt, vì chúng có thể thay đổi, có khả năng phá vỡ ứng dụng của bạn. Bạn có thể cần tạo register riêng hoặc bản sao riêng của register công khai để kiểm soát nhiều hơn việc lập version hình ảnh và ngăn các thay đổi ngược dòng vô tình phá vỡ bản dựng hình ảnh của bạn.
Để tìm hiểu thêm về cách cài đặt register hình ảnh riêng tư, hãy tham khảo Triển khai server đăng ký từ tài liệu chính thức của Docker và phần Sổ đăng ký bên dưới.
Giữ kích thước hình ảnh nhỏ
Khi triển khai và kéo containers images , hình ảnh lớn có thể làm chậm mọi thứ đáng kể và làm tăng chi phí băng thông của bạn. Đóng gói một bộ công cụ và file ứng dụng tối thiểu vào một hình ảnh mang lại một số lợi ích:
- Giảm kích thước hình ảnh
- Tăng tốc độ xây dựng hình ảnh
- Giảm độ trễ khi bắt đầu containers
- Tăng tốc thời gian truyền hình ảnh
- Cải thiện bảo mật bằng cách giảm bề mặt tấn công
Một số bước bạn có thể cân nhắc khi xây dựng hình ảnh của bạn :
- Sử dụng hình ảnh hệ điều hành cơ sở tối thiểu như
alpine
hoặc xây dựng từscratch
thay vì hệ điều hành đầy đủ tính năng nhưubuntu
- Dọn dẹp các file và đồ tạo tác không cần thiết sau khi cài đặt
- Sử dụng các container “xây dựng” và “thời gian chạy” riêng biệt để giữ cho các container ứng dụng production nhỏ
- Bỏ qua các tạo tác và file xây dựng không cần thiết khi sao chép trong các folder lớn
Để có hướng dẫn đầy đủ về cách tối ưu hóa containers Docker, bao gồm nhiều ví dụ minh họa, hãy tham khảo Xây dựng containers được tối ưu hóa cho Kubernetes .
Cấu hình tiêm
Docker cung cấp một số tính năng hữu ích để đưa dữ liệu cấu hình vào môi trường chạy ứng dụng của bạn.
Một tùy chọn để thực hiện việc này là chỉ định các biến môi trường và giá trị của chúng trong Dockerfile bằng cách sử dụng câu lệnh ENV
, để dữ liệu cấu hình được tích hợp vào hình ảnh:
... ENV MYSQL_USER=my_db_user ...
Sau đó, ứng dụng của bạn có thể phân tích cú pháp các giá trị này từ môi trường đang chạy và cấu hình cài đặt của ứng dụng một cách thích hợp.
Bạn cũng có thể chuyển các biến môi trường làm tham số khi khởi động containers bằng cách sử dụng docker run
và cờ -e
:
- docker run -e MYSQL_USER='my_db_user' IMAGE[:TAG]
Cuối cùng, bạn có thể sử dụng file env, chứa danh sách các biến môi trường và giá trị của chúng. Để thực hiện việc này, hãy tạo file và sử dụng tham số --env-file
để chuyển nó vào lệnh:
- docker run --env-file var_list IMAGE[:TAG]
Nếu bạn đang hiện đại hóa ứng dụng của bạn để chạy nó bằng cách sử dụng trình quản lý cụm như Kubernetes, bạn nên mở rộng thêm cấu hình của bạn từ hình ảnh và quản lý cấu hình bằng các đối tượng Bản đồ bí mật và Bản đồ bí mật tích hợp của Kubernetes . Điều này cho phép bạn tách cấu hình khỏi file kê khai hình ảnh để bạn có thể quản lý và tạo version riêng cho ứng dụng của bạn . Để tìm hiểu cách cấu hình bên ngoài bằng ConfigMaps và Secrets, hãy tham khảo phần ConfigMaps và Secrets bên dưới.
Xuất bản hình ảnh lên register
Khi bạn đã xây dựng hình ảnh ứng dụng của bạn , để cung cấp chúng cho Kubernetes, bạn nên tải chúng lên register containers images . Các cơ quan đăng ký công khai như Docker Hub lưu trữ các Docker image mới nhất cho các dự án nguồn mở phổ biến như Node.js và nginx . Cơ quan đăng ký riêng tư cho phép bạn xuất bản hình ảnh ứng dụng nội bộ của bạn , cung cấp chúng cho các nhà phát triển và cơ sở hạ tầng, nhưng không phải thế giới rộng lớn hơn.
Bạn có thể triển khai register riêng bằng cách sử dụng cơ sở hạ tầng hiện có của bạn (ví dụ: trên đầu lưu trữ đối tượng cloud ) hoặc tùy chọn sử dụng một trong một số sản phẩm đăng ký Docker như Quay.io hoặc các gói Docker Hub trả phí. Các cơ quan đăng ký này có thể tích hợp với các dịch vụ kiểm soát version được lưu trữ như GitHub để khi một Dockerfile được cập nhật và đẩy, dịch vụ đăng ký sẽ tự động kéo Dockerfile mới, xây dựng containers images và cung cấp hình ảnh cập nhật cho các dịch vụ của bạn.
Để kiểm soát nhiều hơn việc xây dựng và thử nghiệm containers images cũng như gắn thẻ và xuất bản chúng, bạn có thể triển khai quy trình tích hợp liên tục (CI).
Triển khai một đường ống xây dựng
Việc xây dựng, thử nghiệm, xuất bản và triển khai hình ảnh của bạn vào production theo cách thủ công có thể dễ xảy ra lỗi và không mở rộng quy mô tốt. Để quản lý các bản dựng và liên tục xuất bản các containers chứa các thay đổi mã mới nhất đối với register hình ảnh của bạn, bạn nên sử dụng một đường dẫn xây dựng.
Hầu hết các đường ống xây dựng thực hiện các chức năng cốt lõi sau:
- Xem kho mã nguồn để biết các thay đổi
- Chạy thử nghiệm khói và đơn vị trên mã đã sửa đổi
- Tạo containers images có chứa mã đã sửa đổi
- Chạy thử nghiệm tích hợp thêm bằng cách sử dụng containers images được xây dựng
- Nếu các bài kiểm tra vượt qua, hãy gắn thẻ và xuất bản hình ảnh lên register
- (Tùy chọn, trong cài đặt triển khai liên tục) Cập nhật Các triển khai Kubernetes và triển khai hình ảnh cho các cụm dàn / production
Có nhiều sản phẩm tích hợp liên tục trả phí có tích hợp sẵn với các dịch vụ kiểm soát version phổ biến như GitHub và các cơ quan đăng ký hình ảnh như Docker Hub. Một thay thế cho các sản phẩm này là Jenkins , một server tự động hóa xây dựng open-souce miễn phí có thể được cấu hình để thực hiện tất cả các chức năng được mô tả ở trên. Để tìm hiểu cách cài đặt đường ống tích hợp liên tục Jenkins, hãy tham khảo Cách cài đặt đường ống tích hợp liên tục trong Jenkins trên Ubuntu 16.04 .
Thực hiện ghi log và giám sát container
Khi làm việc với containers , điều quan trọng là phải nghĩ về cơ sở hạ tầng ghi log mà bạn sẽ sử dụng để quản lý và lưu trữ log cho tất cả các containers đang chạy và đã dừng của bạn. Có nhiều mẫu cấp containers mà bạn có thể sử dụng để ghi log và cũng có nhiều mẫu cấp Kubernetes.
Trong Kubernetes, các containers mặc định sử dụng trình điều khiển ghi log json-file
Docker, trình điều khiển ghi lại các stream stdout và stderr và ghi chúng vào các file JSON trên Node nơi containers đang chạy. Đôi khi việc ghi log trực tiếp vào stderr và stdout có thể không đủ cho containers ứng dụng của bạn và bạn có thể cần ghép nối containers ứng dụng với containers sidecar ghi log trong Kubernetes Pod.Sau đó, containers sidecar này có thể lấy log từ hệ thống file , socket local hoặc tạp chí systemd, giúp bạn linh hoạt hơn một chút so với việc chỉ sử dụng các stream stderr và stdout. Vùng chứa này cũng có thể thực hiện một số xử lý và sau đó truyền trực tuyến các bản ghi đã được bổ sung chi tiết tới stdout / stderr hoặc trực tiếp tới một chương trình backend ghi log . Để tìm hiểu thêm về các mẫu ghi log Kubernetes, hãy tham khảo phần theo dõi và ghi log Kubernetes của hướng dẫn này.
Cách ứng dụng của bạn ghi log ở cấp containers sẽ phụ thuộc vào độ phức tạp của nó. Đối với các microservices đơn giản, mục đích duy nhất, đăng nhập trực tiếp vào stdout / stderr và cho phép Kubernetes nhận các stream này là cách tiếp cận được khuyến khích , vì sau đó bạn có thể tận dụng lệnh kubectl logs
để truy cập các stream log từ các containers được triển khai Kubernetes của bạn .
Tương tự như ghi log , bạn nên bắt đầu nghĩ đến việc giám sát trong môi trường dựa trên containers và cụm. Docker cung cấp lệnh docker stats
hữu ích để lấy các chỉ số tiêu chuẩn như mức sử dụng CPU và bộ nhớ để chạy các containers trên server và hiển thị nhiều số liệu hơn nữa thông qua API REST từ xa . Ngoài ra, công cụ open-souce cCity (được cài đặt trên Kubernetes Nodes theo mặc định) cung cấp chức năng nâng cao hơn như thu thập số liệu lịch sử, xuất dữ liệu số liệu và giao diện user web hữu ích để phân loại dữ liệu.
Tuy nhiên, trong môi trường production nhiều nút, nhiều containers , các ngăn xếp số liệu phức tạp hơn như Prometheus và Grafana có thể giúp tổ chức và giám sát dữ liệu hiệu suất của containers của bạn.
Tóm lược
Trong các phần này, ta đã thảo luận ngắn gọn về một số phương pháp hay nhất để xây dựng containers , cài đặt đường dẫn CI / CD và đăng ký hình ảnh, cũng như một số cân nhắc để tăng khả năng quan sát vào containers của bạn.
- Để tìm hiểu thêm về cách tối ưu hóa containers cho Kubernetes, hãy tham khảo Xây dựng containers được tối ưu hóa cho Kubernetes .
- Để tìm hiểu thêm về CI / CD, hãy tham khảo Giới thiệu về Tích hợp, Phân phối và Triển khai Liên tục và Giới thiệu về Các phương pháp hay nhất về CI / CD .
Trong phần tiếp theo, ta sẽ khám phá các tính năng của Kubernetes cho phép bạn chạy và mở rộng ứng dụng được chứa trong một cụm.
Triển khai trên Kubernetes
Đến đây, bạn đã chứa ứng dụng của bạn và triển khai logic để tối đa hóa tính di động và khả năng quan sát của ứng dụng trong môi trường Cloud Native. Bây giờ ta sẽ khám phá các tính năng Kubernetes cung cấp các giao diện đơn giản để quản lý và mở rộng ứng dụng của bạn trong một cụm Kubernetes.
Viết file triển khai và cấu hình Pod
Khi bạn đã chứa ứng dụng của bạn và xuất bản lên register , bây giờ bạn có thể triển khai nó vào một cụm Kubernetes bằng cách sử dụng dung lượng công việc Pod. Đơn vị nhỏ nhất có thể triển khai trong một cụm Kubernetes không phải là một container mà là một Pod. Các group thường bao gồm một containers ứng dụng (như ứng dụng web Flask được chứa trong containers ) hoặc một containers ứng dụng và bất kỳ containers “ backend ” nào thực hiện một số chức năng trợ giúp như giám sát hoặc ghi log . Các containers trong Pod chia sẻ tài nguyên lưu trữ, không gian tên mạng và không gian cổng. Họ có thể giao tiếp với nhau bằng cách sử dụng localhost
và có thể chia sẻ dữ liệu bằng cách sử dụng các ổ đĩa được mount . Ngoài ra, dung lượng công việc Pod cho phép bạn xác định Vùng chứa Init chạy các tập lệnh hoặc tiện ích cài đặt trước khi containers ứng dụng chính bắt đầu chạy.
Các group thường được triển khai bằng cách sử dụng Triển khai, là Bộ điều khiển được xác định bởi file YAML khai báo trạng thái mong muốn cụ thể.Ví dụ: một trạng thái ứng dụng có thể đang chạy ba bản sao của containers ứng dụng web Flask và hiển thị cổng 8080. Sau khi được tạo, mặt phẳng điều khiển dần dần đưa trạng thái thực tế của cụm trùng với trạng thái mong muốn được khai báo trong Triển khai bằng cách lập lịch các containers trên các node theo yêu cầu. Để chia tỷ lệ số lượng bản sao ứng dụng đang chạy trong cụm, chẳng hạn từ 3 đến 5, bạn cập nhật trường replicas
của file cấu hình Triển khai, sau đó kubectl apply
file cấu hình mới. Sử dụng các file cấu hình này, tất cả các hoạt động mở rộng và triển khai đều có thể được theo dõi và tạo version bằng cách sử dụng các dịch vụ và tích hợp kiểm soát nguồn hiện có của bạn.
Đây là file cấu hình Triển khai Kubernetes mẫu cho ứng dụng Flask:
apiVersion: apps/v1 kind: Deployment metadata: name: flask-app labels: app: flask-app spec: replicas: 3 selector: matchLabels: app: flask-app template: metadata: labels: app: flask-app spec: containers: - name: flask image: sammy/flask_app:1.0 ports: - containerPort: 8080
Triển khai này chạy 3 Pod chạy một containers có tên là flask
bằng cách sử dụng sammy/flask_app
image (phiên bản 1.0
) với cổng 8080
mở. Triển khai được gọi là flask-app
.
Để tìm hiểu thêm về Kubernetes Pods and Deployments, hãy tham khảo các phần Pods và Deployments trong tài liệu Kubernetes chính thức.
Cấu hình bộ nhớ Pod
Kubernetes quản lý việc lưu trữ Pod bằng cách sử dụng Dung lượng , Dung lượng liên tục (PV) và Xác nhận quyền sở hữu dung lượng liên tục (PVC). Dung lượng là phần trừu tượng Kubernetes được sử dụng để quản lý lưu trữ Pod và hỗ trợ hầu hết các dịch vụ lưu trữ khối của nhà cung cấp cloud , cũng như lưu trữ local trên các Node lưu trữ các Pod đang chạy. Để xem danh sách đầy đủ các loại Dung lượng được hỗ trợ, hãy tham khảo tài liệu Kubernetes.
Ví dụ: nếu Pod của bạn chứa hai containers NGINX cần chia sẻ dữ liệu giữa chúng (giả sử containers thứ nhất, được gọi là nginx
phân phát các trang web và vùng thứ hai, được gọi là nginx-sync
tìm nạp các trang từ một vị trí bên ngoài và cập nhật các trang được cung cấp bởi nginx
container), thông số Pod của bạn sẽ trông giống như thế này (ở đây ta sử dụng loại Dung lượng trống emptyDir
):
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx volumeMounts: - name: nginx-web mountPath: /usr/share/nginx/html - name: nginx-sync image: nginx-sync volumeMounts: - name: nginx-web mountPath: /web-data volumes: - name: nginx-web emptyDir: {}
Ta sử dụng volumeMount
cho mỗi containers , cho biết rằng ta muốn mount file nginx-web
chứa các file trang web tại /usr/share/nginx/html
trong containers nginx
và tại /web-data
trong nginx-sync
thùng đựng hàng. Ta cũng xác định một volume
được gọi là nginx-web
thuộc loại emptyDir
.
Theo cách tương tự, bạn có thể cấu hình lưu trữ Pod bằng cách sử dụng các sản phẩm lưu trữ khối cloud bằng cách sửa đổi loại volume
từ emptyDir
thành loại dung lượng lưu trữ cloud có liên quan.
Vòng đời của Dung lượng được gắn với vòng đời của Pod, nhưng không gắn với vòng đời của containers . Nếu một containers trong Pod chết, Vùng chứa vẫn tồn tại và containers mới chạy sẽ có thể gắn cùng một Vùng chứa và truy cập dữ liệu của nó. Khi một Pod được khởi động lại hoặc chết, các Tập của nó cũng vậy, mặc dù nếu các Tập bao gồm lưu trữ khối cloud , chúng sẽ đơn giản được ngắt kết nối với dữ liệu mà các Pod trong tương lai vẫn có thể truy cập được.
Để bảo toàn dữ liệu trên các bản cập nhật và khởi động lại Pod, các đối tượng PersisteVolume (PV) và PersisteVolumeClaim (PVC) phải được sử dụng.
PersentlyVolumes là những bản tóm tắt đại diện cho các phần lưu trữ liên tục như dung lượng lưu trữ khối cloud hoặc lưu trữ NFS. Chúng được tạo riêng biệt với PersentlyVolumeClaims, vốn là những yêu cầu về bộ nhớ của các nhà phát triển. Trong cấu hình Pod của họ, các nhà phát triển yêu cầu lưu trữ liên tục bằng cách sử dụng PVC, Kubernetes trùng với các Dung lượng PV có sẵn (nếu sử dụng lưu trữ khối cloud , Kubernetes có thể tạo động các PersisteVolumes khi PersisteVolumeClaims được tạo).
Nếu ứng dụng của bạn yêu cầu một ổ đĩa liên tục cho mỗi bản sao, đây là trường hợp của nhiều database , bạn không nên sử dụng Deployments mà hãy sử dụng bộ điều khiển StatefulSet, được thiết kế cho các ứng dụng yêu cầu số nhận dạng mạng ổn định, bộ nhớ ổn định liên tục và đảm bảo đặt hàng. Các triển khai nên được sử dụng cho các ứng dụng không trạng thái và nếu bạn xác định PersisteVolumeClaim để sử dụng trong cấu hình Triển khai, thì PVC đó sẽ được chia sẻ bởi tất cả các bản sao của Triển khai.
Để tìm hiểu thêm về bộ điều khiển StatefulSet, hãy tham khảo tài liệu Kubernetes. Để tìm hiểu thêm về các tuyên bố của PersentlyVolumes và PersentlyVolume, hãy tham khảo tài liệu lưu trữ Kubernetes.
Đưa dữ liệu cấu hình với Kubernetes
Tương tự như Docker, Kubernetes cung cấp các trường env
và envFrom
để cài đặt các biến môi trường trong file cấu hình Pod. Đây là đoạn mã mẫu từ file cấu hình Pod đặt biến môi trường HOSTNAME
trong Pod đang chạy thành my_hostname
:
... spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 env: - name: HOSTNAME value: my_hostname ...
Điều này cho phép bạn di chuyển cấu hình ra khỏi Dockerfiles và vào các file cấu hình Pod và Triển khai. Ưu điểm chính của việc bổ sung cấu hình bên ngoài từ Dockerfiles của bạn là giờ đây bạn có thể sửa đổi các cấu hình dung lượng công việc Kubernetes này (giả sử bằng cách thay đổi giá trị HOSTNAME
thành my_hostname_2
) riêng biệt với các định nghĩa containers ứng dụng của bạn. Khi bạn sửa đổi file cấu hình Pod, sau đó bạn có thể triển khai lại Pod bằng cách sử dụng môi trường mới của nó, trong khi containers images bên dưới (được xác định thông qua Dockerfile của nó) không cần phải được xây dựng lại, thử nghiệm và đẩy vào repository . Bạn cũng có thể version các cấu hình Pod và Triển khai này riêng biệt với Dockerfiles của bạn , cho phép bạn nhanh chóng phát hiện các thay đổi vi phạm và tách biệt thêm các vấn đề cấu hình khỏi lỗi ứng dụng.
Kubernetes cung cấp một cấu trúc khác để tiếp tục mở rộng và quản lý dữ liệu cấu hình: Bản đồ cấu hình và Bí mật.
ConfigMaps và Secrets
ConfigMaps cho phép bạn lưu dữ liệu cấu hình dưới dạng các đối tượng mà sau đó bạn tham chiếu trong các file cấu hình Pod và Deployment của bạn , để bạn có thể tránh mã hóa cứng dữ liệu cấu hình và sử dụng lại nó trên các Pod và Deployments.
Đây là một ví dụ, sử dụng cấu hình Pod ở trên. Trước tiên, ta sẽ lưu biến môi trường HOSTNAME
dưới dạng Bản đồ cấu hình, sau đó tham chiếu nó trong cấu hình Pod:
- kubectl create configmap hostname --from-literal=HOSTNAME=my_host_name
Để tham chiếu nó từ file cấu hình Pod, ta sử dụng các cấu valueFrom
và configMapKeyRef
:
... spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 env: - name: HOSTNAME valueFrom: configMapKeyRef: name: hostname key: HOSTNAME ...
Vì vậy, giá trị của biến môi trường HOSTNAME
đã được bên ngoài hoàn toàn khỏi file cấu hình. Sau đó, ta có thể cập nhật các biến này trên tất cả các Triển khai và Group tham chiếu đến chúng và khởi động lại Group để các thay đổi có hiệu lực.
Nếu ứng dụng của bạn sử dụng file cấu hình, ConfigMaps cũng cho phép bạn lưu trữ các file này dưới dạng đối tượng ConfigMap (sử dụng --from-file
), sau đó bạn có thể mount vào containers dưới dạng file cấu hình.
Secrets cung cấp chức năng thiết yếu tương tự như ConfigMaps, nhưng nên được sử dụng cho dữ liệu nhạy cảm như thông tin xác thực database vì các giá trị được mã hóa base64.
Để tìm hiểu thêm về ConfigMaps và Secrets, hãy tham khảo tài liệu Kubernetes.
Tạo dịch vụ
Khi bạn đã cài đặt và chạy ứng dụng của bạn trong Kubernetes, mọi Pod sẽ được gán một địa chỉ IP (nội bộ), được chia sẻ bởi các containers của nó. Nếu một trong các Group này bị xóa hoặc chết, các Group mới bắt đầu sẽ được gán các địa chỉ IP khác nhau.
Đối với các dịch vụ hoạt động lâu dài có chức năng hiển thị cho các client nội bộ và / hoặc bên ngoài, bạn có thể cần cấp cho một group Group thực hiện cùng một chức năng (hoặc Triển khai) một địa chỉ IP ổn định để tải cân bằng các yêu cầu trên các containers của nó. Bạn có thể thực hiện việc này bằng Dịch vụ Kubernetes.
Dịch vụ Kubernetes có 4 loại, được chỉ định bởi trường type
trong file cấu hình Dịch vụ:
-
ClusterIP
: Đây là loại mặc định, cấp cho Dịch vụ một IP nội bộ ổn định có thể truy cập từ bất kỳ đâu bên trong cụm. -
NodePort
: Điều này sẽ hiển thị Dịch vụ của bạn trên mỗi Node tại một cổng tĩnh, trong repository ảng 30000-32767 theo mặc định. Khi một yêu cầu truy cập vào một Node tại địa chỉ IP Node của nó vàNodePort
cho dịch vụ của bạn, yêu cầu sẽ được cân bằng tải và được chuyển đến các containers ứng dụng cho dịch vụ của bạn. -
LoadBalancer
: Điều này sẽ tạo ra một cân bằng tải sử dụng tải sản phẩm cân bằng cung cấp dịch vụ cloud của bạn, và cấu hình mộtNodePort
vàClusterIP
cho dịch vụ của bạn mà yêu cầu bên ngoài sẽ được chuyển. -
ExternalName
: Loại Dịch vụ này cho phép bạn ánh xạ một Dịch vụ Kubernetes với một bản ghi DNS. Nó được dùng để truy cập các dịch vụ bên ngoài từ Pods của bạn bằng Kubernetes DNS.
Lưu ý việc tạo một Dịch vụ loại LoadBalancer
cho mỗi Triển khai đang chạy trong cụm của bạn sẽ tạo một bộ cân bằng tải cloud mới cho mỗi Dịch vụ, điều này có thể trở nên tốn kém. Để quản lý việc định tuyến các yêu cầu bên ngoài tới nhiều dịch vụ bằng một bộ cân bằng tải, bạn có thể sử dụng Bộ điều khiển Ingress. Bộ điều khiển Ingress nằm ngoài phạm vi của bài viết này, nhưng để tìm hiểu thêm về chúng, bạn có thể tham khảo tài liệu Kubernetes. Bộ điều khiển Ingress đơn giản phổ biến là NGINX Ingress Controller .
Đây là một file cấu hình dịch vụ đơn giản cho ví dụ Flask được sử dụng trong Pods và Triển khai phần của hướng dẫn này:
apiVersion: v1 kind: Service metadata: name: flask-svc spec: ports: - port: 80 targetPort: 8080 selector: app: flask-app type: LoadBalancer
Ở đây, ta chọn để hiển thị Triển khai flask-app
dụng flask-svc
Dịch vụ flask-svc
. Ta tạo bộ cân bằng tải trên cloud để định tuyến lưu lượng truy cập từ cổng bộ cân bằng tải 80
đến cổng containers 8080
tiếp xúc.
Để tìm hiểu thêm về Dịch vụ của Kubernetes, hãy tham khảo phần Dịch vụ của tài liệu Kubernetes.
Ghi log và giám sát
Phân tích cú pháp thông qua container cá nhân và Pod bản ghi sử dụng kubectl logs
và docker logs
có thể nhận được tẻ nhạt như số lượng ứng dụng đang chạy phát triển. Để giúp bạn gỡ lỗi các vấn đề về ứng dụng hoặc cụm, bạn nên triển khai ghi log tập trung. Ở cấp độ cao, điều này bao gồm các tác nhân chạy trên tất cả các node công nhân xử lý stream và file log Pod, làm giàu chúng bằng metadata và chuyển tiếp log tới một chương trình backend như Elasticsearch . Từ đó, dữ liệu log có thể được trực quan hóa, lọc và tổ chức bằng công cụ trực quan hóa như Kibana .
Trong phần ghi log cấp containers , ta đã thảo luận về cách tiếp cận Kubernetes được đề xuất để các ứng dụng trong containers ghi log vào các stream stdout / stderr. Ta cũng đã thảo luận ngắn gọn về việc ghi log các containers sidecar có thể giúp bạn linh hoạt hơn khi đăng nhập từ ứng dụng của bạn . Bạn cũng có thể chạy các tác nhân ghi log trực tiếp trong Pod của bạn để thu thập dữ liệu log local và chuyển tiếp chúng trực tiếp đến chương trình backend ghi log của bạn. Mỗi cách tiếp cận đều có ưu và nhược điểm và sự cân bằng việc sử dụng tài nguyên (ví dụ: chạy một containers tác nhân ghi log bên trong mỗi Pod có thể trở nên tiêu tốn nhiều tài nguyên và nhanh chóng áp đảo chương trình backend ghi log của bạn). Để tìm hiểu thêm về các kiến trúc ghi log khác nhau và sự cân bằng của chúng, hãy tham khảo tài liệu Kubernetes.
Trong cài đặt tiêu chuẩn, mỗi Node chạy một tác nhân ghi log như Filebeat hoặc Fluentd để thu thập log containers do Kubernetes tạo ra. Nhớ lại rằng Kubernetes tạo các file log JSON cho các containers trên Node (trong hầu hết các cài đặt, chúng có thể được tìm thấy tại /var/lib/docker/containers/
). Chúng nên được xoay vòng bằng một công cụ như logrotate. Tác nhân ghi log Node phải được chạy dưới dạng Bộ điều khiển DaemonSet , một loại Dung lượng công việc Kubernetes đảm bảo mọi Node chạy một bản sao của DaemonSet Pod. Trong trường hợp này, Pod sẽ chứa tác nhân ghi log và cấu hình của nó, xử lý log từ các file và folder được gắn vào DaemonSet Pod ghi log .
Tương tự như nút thắt cổ chai trong việc sử dụng kubectl logs
để gỡ lỗi các vấn đề về containers , cuối cùng bạn có thể cần phải xem xét một tùy chọn mạnh mẽ hơn là chỉ sử dụng kubectl top
và Kubernetes Dashboard để theo dõi việc sử dụng tài nguyên Pod trên cụm của bạn. Giám sát mức độ ứng dụng và cụm có thể được cài đặt bằng cách sử dụng hệ thống giám sát Prometheus và database chuỗi thời gian cũng như console số liệu Grafana . Prometheus hoạt động bằng cách sử dụng mô hình “pull”, mô hình này sẽ quét các điểm cuối HTTP (như /metrics/cadvisor
trên Nodes hoặc các điểm cuối REST API của ứng dụng /metrics
) định kỳ cho dữ liệu số liệu, sau đó nó xử lý và lưu trữ. Dữ liệu này sau đó có thể được phân tích và hiển thị trực quan bằng console Grafana. Prometheus và Grafana có thể được chạy vào một cụm Kubernetes giống như bất kỳ Triển khai và Dịch vụ nào khác.
Để có thêm khả năng phục hồi, bạn có thể cần chạy cơ sở hạ tầng ghi log và giám sát của bạn trên một cụm Kubernetes riêng biệt hoặc sử dụng các dịch vụ đo lường và ghi log bên ngoài.
Kết luận
Việc di chuyển và hiện đại hóa một ứng dụng để nó có thể chạy một cách hiệu quả trong một cụm Kubernetes thường liên quan đến việc lập kế hoạch và lưu trữ những thay đổi về phần mềm và cơ sở hạ tầng.Sau khi được triển khai, những thay đổi này cho phép chủ sở hữu dịch vụ liên tục triển khai các version ứng dụng mới của họ và dễ dàng mở rộng quy mô khi cần thiết mà không cần can thiệp thủ công ở mức tối thiểu. Các bước như cấu hình bên ngoài từ ứng dụng của bạn, cài đặt ghi log và xuất bản chỉ số thích hợp cũng như cấu hình kiểm tra tình trạng cho phép bạn tận dụng tối đa mô hình Cloud Native mà Kubernetes đã thiết kế. Bằng cách xây dựng các container di động và quản lý chúng bằng các đối tượng Kubernetes như Triển khai và Dịch vụ, bạn có thể sử dụng đầy đủ cơ sở hạ tầng máy tính và tài nguyên phát triển sẵn có của bạn .
Các tin liên quan