Skip to main content

5.1. Application Lifecycle Management - Rolling Updates, Commands, ConfigMaps and Secrets

Mục lục


1. Giới thiệu phần Application Lifecycle Management

Phần này bàn về application lifecycle management. Bắt đầu với rolling updates và rollbacks, sau đó là các cách khác nhau để cấu hình application, scale application, trước khi xem xét các primitive của một self-healing application. Nhiều nội dung trong số này đã được đề cập trong khóa Certified Kubernetes Application Developer (CKAD) — nếu đã học khóa đó rồi, một số phần có thể sẽ lặp lại. Nếu các chủ đề này đã quen thuộc, có thể bỏ qua hoặc chuyển thẳng sang phần luyện tập (practice test).


2. Rolling Updates và Rollbacks

Khi lần đầu tạo một Deployment, nó sẽ trigger một rollout. Một rollout mới tạo ra một ReplicaSet mới, được ghi nhận là một deployment revision mới. Sau này, khi application được upgrade — nghĩa là khi phiên bản container được cập nhật sang phiên bản mới — một rollout mới lại được trigger và một deployment revision mới được tạo ra, đặt tên là Revision 2. Điều này giúp theo dõi các thay đổi trên deployment và cho phép rollback về một phiên bản deployment trước đó nếu cần.

Có thể xem trạng thái rollout bằng lệnh kubectl rollout status theo sau là tên deployment. Để xem các revision và lịch sử rollout, chạy lệnh kubectl rollout history theo sau là tên deployment — lệnh này hiển thị các revision và lịch sử của deployment.

kubectl rollout status deployment/myapp-deployment

# Output minh họa
Waiting for rollout to finish: 2 out of 4 new replicas have been updated...
Waiting for rollout to finish: 3 out of 4 new replicas have been updated...
deployment "myapp-deployment" successfully rolled out

(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.)

kubectl rollout history deployment/myapp-deployment

# Output minh họa
REVISION CHANGE-CAUSE
1 <none>
2 kubectl set image deployment/myapp-deployment myapp-container=myapp:v2

(Output minh họa — cấu trúc và tên cột đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.)

Có hai loại deployment strategy. Ví dụ, có 5 replica của một web application instance đang chạy. Một cách để upgrade chúng lên phiên bản mới là destroy tất cả rồi mới tạo các instance phiên bản mới — nghĩa là destroy 5 instance đang chạy trước, sau đó mới deploy 5 instance mới của phiên bản application mới. Vấn đề với cách này là trong khoảng thời gian sau khi các phiên bản cũ đã down và trước khi bất kỳ phiên bản mới nào lên, application sẽ down và không truy cập được. Chiến lược này gọi là Recreate strategy. Và may mắn là đây không phải deployment strategy mặc định.

Chiến lược thứ hai là không destroy tất cả pod cùng lúc. Thay vào đó, lấy phiên bản cũ xuống và đưa phiên bản mới lên từng cái một. Nhờ vậy application không bao giờ down, và việc upgrade diễn ra liền mạch. Nhớ rằng, nếu không chỉ định strategy khi tạo Deployment, nó sẽ mặc định là RollingUpdate. Nói cách khác, rolling update là deployment strategy mặc định.

💡 Hình dung: Recreate strategy giống việc đóng cửa toàn bộ một cửa hàng để thay mới hết kệ hàng cùng lúc — khách không vào mua được gì cho tới khi xong hết. RollingUpdate giống việc thay từng dãy kệ một ngay trong giờ mở cửa — khách vẫn mua sắm bình thường, chỉ một góc nhỏ tạm ngừng phục vụ tại một thời điểm, còn lại vẫn hoạt động.

Rolling Update vs Recreate — 4 pod theo thời gianBeforeDuringAfterRecreate — tất cả pod down cùng lúcv1v1v1v1v2v2v2v2Downtime — 0/4 pod đang chạyRollingUpdate — thay từng pod mộtv1v1v1v1v2v1v1v2v2v2v2Luôn còn pod chạy — chỉ 1/4 tạm down (25% maxUnavailable)v1 đang chạyđã xóav2 đang chạyv2 đang khởi độngRollingUpdate là strategy mặc định: application không bao giờ down hoàn toàn

Vậy update deployment như thế nào? "Update" ở đây có thể là nhiều thứ khác nhau, như update phiên bản application bằng cách update phiên bản Docker container được dùng, update labels, hay update số lượng replicas, v.v. Vì đã có deployment definition file, việc chỉnh sửa file này khá dễ dàng. Sau khi thực hiện các thay đổi cần thiết, chạy lệnh kubectl apply để áp dụng thay đổi. Một rollout mới được trigger và một revision mới của Deployment được tạo ra.

Còn một cách khác để làm điều tương tự — dùng lệnh kubectl set image để update image của application. Nhưng nhớ rằng, làm theo cách này sẽ khiến deployment definition file có một cấu hình khác đi so với thực tế trên cluster. Vậy nên phải cẩn thận khi dùng cùng definition file đó để thực hiện thay đổi trong tương lai.

Sự khác biệt giữa Recreate và Rolling Update strategy cũng có thể thấy khi xem chi tiết deployment. Từ lệnh kubectl describe deployment, có thể thấy thông tin chi tiết về deployment. Khi dùng Recreate strategy, các event cho thấy ReplicaSet cũ bị scale down về 0 trước, sau đó ReplicaSet mới mới được scale up lên 5. Còn khi dùng rolling update strategy, ReplicaSet cũ bị scale down từng cái một, đồng thời ReplicaSet mới được scale up từng cái một.

kubectl describe deployment myapp-deployment

# Output minh họa (rút gọn, strategy Recreate)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 30s deployment-controller Scaled down replica set myapp-old to 0
Normal ScalingReplicaSet 20s deployment-controller Scaled up replica set myapp-new to 5

# Output minh họa (rút gọn, strategy RollingUpdate)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 30s deployment-controller Scaled up replica set myapp-new to 1
Normal ScalingReplicaSet 28s deployment-controller Scaled down replica set myapp-old to 4
Normal ScalingReplicaSet 26s deployment-controller Scaled up replica set myapp-new to 2
Normal ScalingReplicaSet 24s deployment-controller Scaled down replica set myapp-old to 3

(Output minh họa — cấu trúc và message đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.) Điểm khác biệt cốt lõi nằm ở trình tự các event: Recreate scale-down-hết-rồi-mới-scale-up, còn RollingUpdate xen kẽ scale-up/scale-down từng bước nhỏ.

Hãy xem cách một deployment thực hiện upgrade phía sau hậu trường. Khi một deployment mới được tạo, giả sử để deploy 5 replica, nó đầu tiên tự động tạo một ReplicaSet, ReplicaSet này lại tạo ra số lượng pod cần thiết để đáp ứng số replicas. Khi upgrade application, Deployment object của Kubernetes tạo một ReplicaSet mới phía sau hậu trường và bắt đầu deploy container ở đó, đồng thời lấy các pod trong ReplicaSet cũ xuống theo chiến lược rolling update. Điều này có thể thấy khi liệt kê các replica set bằng lệnh kubectl get replicasets — sẽ thấy ReplicaSet cũ với 0 pod và ReplicaSet mới với 5 pod.

Giả sử, sau khi upgrade application, phát hiện có gì đó không ổn với phiên bản mới của build vừa dùng để upgrade. Vậy muốn rollback lại update. Kubernetes deployment cho phép rollback về một revision trước đó. Để undo một thay đổi, chạy lệnh kubectl rollout undo theo sau là tên deployment. Deployment khi đó sẽ destroy các pod trong ReplicaSet mới và đưa các pod cũ lên lại trong ReplicaSet cũ, application trở về định dạng cũ. Khi so sánh output của lệnh kubectl get replicasets trước và sau khi rollback, sẽ thấy sự khác biệt này — trước khi rollback, replica set đầu tiên có 0 pod, và replica set mới có 5 pod; điều này đảo ngược lại sau khi rollback hoàn tất.

Tóm tắt nhanh các lệnh: dùng lệnh kubectl create để tạo deployment, kubectl get deployments để liệt kê các deployment, kubectl applykubectl set image để update deployment, kubectl rollout status để xem trạng thái rollout, và kubectl rollout undo để rollback một deployment operation.


3. Lab: Thực hành Rolling Updates và Rollbacks

Trong bài lab này, thực hành với rolling updates. Trước tiên, thiết lập alias k cho kubectl (sẽ dùng alias này xuyên suốt bài lab).

Đã deploy sẵn một simple web application, kiểm tra pod và Service bằng kubectl get pods — có một tập hợp các pod frontend. Rất có thể có một Deployment tên frontend, deploy 4 pod. Chờ application deploy xong và xem application qua link "KodeKloud portal" — trang hiển thị "Hello frontend".

Màu hiện tại của web application là gì? Màu blue.

Chạy script tên curl test để gửi nhiều request tới web application — script này ping application liên tục và trả về màu (blue), chạy trong vài giây. Script này được dùng để mô phỏng việc nhiều user cùng truy cập application.

Kiểm tra deployment và xác định số pod được deploy. Deployment tạo 4 pod.

Image container nào được dùng để deploy application? Xem chi tiết Deployment frontend, dưới pod template của containers có image, giá trị là kodekloud/webapp-color:v1.

Kiểm tra Deployment và xác định strategy hiện tại. Strategy type là rolling update.

Nếu upgrade application ngay bây giờ thì điều gì sẽ xảy ra? Rolling update lấy từng pod xuống một lúc và đưa pod mới lên — không đợi lấy hết tất cả pod xuống mới đưa pod mới lên. Đáp án đúng: pod được upgrade từng vài cái một (không phải tất cả pod bị lấy xuống trước khi upgrade).

Upgrade application bằng cách set image trên deployment thành kodekloud/webapp-color:v2. Một cách là kubectl edit deployment frontend rồi sửa image trực tiếp. Cách khác là dùng lệnh kubectl set image. Xem help để biết cú pháp: kubectl set image deployment, tên deployment, sau đó tên container và image. Tên container được xác định qua kubectl describesimple-webapp. Lệnh chạy thực tế:

kubectl set image deploy frontend simple-webapp=kodekloud/webapp-color:v2

Xác nhận lại bằng describe — image đã được set thành kodekloud/webapp-color:v2. Chạy lại curl test — thấy chủ yếu vẫn là blue, nhưng thỉnh thoảng có green (green là phiên bản mới vừa deploy, blue là phiên bản cũ). Càng về sau, tỷ lệ green càng tăng dần, có lúc xen kẽ cả hai màu, rồi cuối cùng chỉ còn green — đó chính là cách rolling update hoạt động.

Sau khi upgrade, tối đa bao nhiêu pod có thể down cùng lúc? Xét theo cấu hình strategy hiện tại — strategy type là rolling update, với chi tiết bổ sung 25% max unavailable, nghĩa là chỉ 25% số pod bị lấy xuống cùng lúc trước khi đưa pod mới lên. Với 4 replica, 25% tương ứng với 1 pod. Cả maxUnavailable lẫn maxSurge (số pod tối đa được tạo thêm vượt quá số replica mong muốn) đều mặc định là 25% nếu không chỉ định trong deployment spec.

Đổi deployment strategy sang Recreate, xoá và tạo lại deployment nếu cần. Chạy kubectl edit deployment frontend, đổi strategy.type thành Recreate. Recreate không cần các tham số rolling update, nên xoá block đó đi. Xác nhận lại — strategy type đã đổi thành Recreate.

Upgrade application bằng cách set image trên deployment thành v3. Hiện đang ở v2, cần đổi sang v3, dùng cùng lệnh kubectl set image deploy frontend simple-webapp=kodekloud/webapp-color:v3 như trước. Chạy curl test lần này — thấy nhiều request lỗi, vì strategy Recreate lấy tất cả pod cũ xuống trước khi đưa pod mới lên. Nếu mở web application lúc này sẽ thấy lỗi "Bad Gateway". Sau một lúc, application phản hồi trở lại với màu red, và khi refresh sẽ thấy application đang chạy v3 với màu red.


4. Commands và Arguments trong Docker

Chủ đề này không nằm trong danh sách yêu cầu của chương trình thi chứng chỉ, nhưng vẫn quan trọng để giải thích vì đây là một chủ đề thường bị bỏ qua. Trước tiên ôn lại commands trong container và Docker, sau đó áp dụng vào pod ở phần tiếp theo — commands, arguments và ENTRYPOINT trong Docker.

Bắt đầu với một kịch bản đơn giản: chạy một Docker container từ image Ubuntu. Khi chạy docker run ubuntu, nó chạy một instance của image Ubuntu và thoát ngay lập tức. Nếu liệt kê các container đang chạy sẽ không thấy container này. Nếu liệt kê tất cả container (bao gồm cả những container đã dừng) sẽ thấy container vừa chạy đang ở trạng thái exited.

Vì sao vậy? Khác với máy ảo, container không được thiết kế để host một hệ điều hành. Container được thiết kế để chạy một task hoặc process cụ thể, ví dụ host một instance của web server, application server, database, hoặc chỉ đơn giản là thực hiện một phép tính toán/phân tích nào đó. Khi task hoàn tất, container thoát. Một container chỉ sống chừng nào process bên trong nó còn sống. Nếu web service bên trong container dừng hoặc crash, container thoát.

Vậy ai định nghĩa process nào được chạy bên trong container? Nếu xem Dockerfile của các image Docker phổ biến như nginx, sẽ thấy một instruction tên CMD (viết tắt của command), định nghĩa chương trình sẽ chạy bên trong container khi nó khởi động. Với image nginx, đó là lệnh nginx; với image MySQL, đó là lệnh mysqld.

Điều vừa thử ở trên là chạy một container chỉ với hệ điều hành Ubuntu thuần. Xem Dockerfile của image này sẽ thấy nó dùng bash làm command mặc định. bash không thực sự là một process như web server hay database server — nó là một shell lắng nghe input từ terminal. Nếu không tìm thấy terminal, nó thoát. Khi chạy container Ubuntu ở trên, Docker tạo container từ image Ubuntu và khởi chạy chương trình bash. Mặc định, Docker không attach terminal vào container khi chạy. Vậy nên chương trình bash không tìm thấy terminal, và nó thoát. Vì process khởi chạy khi container được tạo đã kết thúc, container cũng thoát theo.

Vậy làm sao chỉ định một command khác để khởi động container? Một cách là append thêm command vào lệnh docker run, qua đó ghi đè command mặc định được chỉ định trong image. Ví dụ, chạy lệnh docker run ubuntu-sleeper sleep 5 với sleep 5 là option thêm vào. Khi container khởi động, nó chạy chương trình sleep, đợi 5 giây rồi thoát.

Nhưng làm sao để thay đổi này trở nên vĩnh viễn? Giả sử muốn image luôn chạy command sleep mỗi khi khởi động, thì tạo image riêng từ base image Ubuntu và chỉ định một command mới. Có nhiều cách chỉ định command, hoặc đơn giản dưới dạng shell form, hoặc dạng JSON array như thế này. Nhưng nhớ rằng, khi chỉ định dưới dạng JSON array, phần tử đầu tiên trong array phải là executable — trong trường hợp này là chương trình sleep. Không được chỉ định command và tham số cùng nhau trong một chuỗi như vậy — command và các tham số của nó phải là các phần tử riêng biệt trong list.

Build image mới bằng lệnh docker build, đặt tên là ubuntu-sleeper. Giờ có thể đơn giản chạy docker run ubuntu-sleeper và nhận kết quả tương tự — nó luôn sleep 5 giây rồi thoát.

Nhưng nếu muốn thay đổi số giây nó sleep thì sao? Hiện tại nó đang hard-code là 5 giây. Như đã học, một cách là chạy lệnh docker run với command mới được append vào, trong trường hợp này là sleep 10. Command sẽ chạy lúc khởi động là sleep 10. Nhưng cách này không đẹp lắm — tên image ubuntu-sleeper bản thân nó đã ngụ ý container sẽ sleep, nên không cần chỉ định lại command sleep. Thay vào đó, muốn có được điều gì đó như thế này: docker run ubuntu-sleeper 10 — chỉ muốn truyền vào số giây container nên sleep, còn command sleep nên được tự động gọi.

Đó là lúc instruction ENTRYPOINT phát huy tác dụng. ENTRYPOINT định nghĩa executable command, CMD cung cấp default args — nghĩa là có thể chỉ định chương trình sẽ chạy khi container khởi động. Và bất cứ thứ gì chỉ định trên command line, trong trường hợp này là 10, sẽ được append vào ENTRYPOINT. Vậy command chạy khi container khởi động sẽ là sleep 10.

Đó là sự khác biệt giữa hai instruction: với CMD, các tham số command line truyền vào sẽ bị thay thế hoàn toàn. Còn với ENTRYPOINT, các tham số command line sẽ được append thêm vào. Bây giờ, nếu chạy image ubuntu-sleeper mà không append số giây? Command lúc khởi động sẽ chỉ là sleep, và sẽ gặp lỗi báo thiếu operand.

Vậy làm sao cấu hình một giá trị mặc định cho command nếu không chỉ định gì trên command line? Đó là lúc dùng cả ENTRYPOINT lẫn CMD instruction. Trong trường hợp này, CMD instruction sẽ được append vào ENTRYPOINT instruction. Vậy lúc khởi động, command sẽ là sleep 5 nếu không chỉ định tham số nào trên command line. Nếu có chỉ định, nó sẽ ghi đè CMD instruction. Và nhớ rằng, để điều này hoạt động, luôn phải chỉ định ENTRYPOINT và CMD instruction dưới dạng JSON format.

Cuối cùng, nếu thực sự muốn sửa đổi ENTRYPOINT lúc runtime, ví dụ từ sleep sang một command tưởng tượng sleep2.0? Có thể ghi đè bằng option --entrypoint trong lệnh docker run. Command cuối cùng lúc khởi động khi đó sẽ là sleep 20.


5. Commands và Arguments trong Kubernetes Pod

Ở phần trước đã tạo một image Docker đơn giản sleep trong một số giây cho trước, đặt tên ubuntu-sleeper, và chạy nó bằng lệnh Docker docker run ubuntu-sleeper. Mặc định, nó sleep 5 giây, nhưng có thể ghi đè bằng cách truyền command line argument. Giờ tạo một Pod dùng image này.

Bắt đầu từ một pod definition template trống, nhập tên Pod và chỉ định tên image. Khi Pod này được tạo, nó tạo một container từ image được chỉ định, và container sleep 5 giây trước khi thoát. Bây giờ, nếu cần container sleep 10 giây, tương tự command thứ hai ở trên, làm sao chỉ định thêm argument trong pod definition file? Bất cứ thứ gì được append vào lệnh docker run sẽ đi vào thuộc tính args của pod definition file, dưới dạng một array như sau.

Liên hệ điều này với Dockerfile đã tạo trước đó: Dockerfile có cả instruction ENTRYPOINT lẫn CMD. ENTRYPOINT là command chạy lúc khởi động. CMD là tham số mặc định được truyền cho command đó. Với option args trong pod definition file, ta ghi đè CMD instruction trong Dockerfile. Nhưng nếu cần ghi đè ENTRYPOINT, ví dụ từ sleep sang một command tưởng tượng sleep2.0? Trong thế giới Docker, sẽ chạy lệnh docker run với option ENTRYPOINT được set thành command mới. Mục tương ứng trong pod definition file là dùng field command. Field command tương ứng với instruction ENTRYPOINT trong Dockerfile.

Tóm lại, có hai field tương ứng với hai instruction trong Dockerfile: field command ghi đè ENTRYPOINT instruction, và field args ghi đè CMD instruction trong Dockerfile. Nhớ rằng, không phải field command ghi đè CMD instruction trong Dockerfile.

💡 Hình dung: ENTRYPOINT giống tên chương trình cố định gắn trên một cái máy — không đổi được trừ khi cố tình ép buộc (option --entrypoint). CMD giống giá trị mặc định đã điền sẵn trong ô nhập liệu của máy đó — người dùng có thể xóa đi gõ giá trị khác, nhưng không gõ gì thì máy tự dùng đúng giá trị mặc định ấy. Field command trong Pod ghi đè "tên máy" (ENTRYPOINT), còn args chỉ ghi đè "giá trị trong ô nhập liệu" (CMD).

ENTRYPOINT/CMD (Dockerfile) → command/args (Pod spec)DockerfilePod specENTRYPOINT["sleep"]command (ghi đè ENTRYPOINT)["sleep"]tương ứng vớiCMD (default arg)["5"]args (ghi đè CMD)["10"]tương ứng vớiLệnh chạy khi container khởi độngsleep 10

6. Lab: Thực hành Commands và Arguments

Có bao nhiêu pod tồn tại trên hệ thống? Chỉ có một pod.

Command dùng để chạy Pod là gì? File UbuntuSleeper3.yaml. Xem chi tiết pod, dưới containers có container Ubuntu, với commandsleep 800, không thấy argument nào — vậy command là sleep 800.

Tạo một Pod với image Ubuntu để chạy container sleep 5000 giây, chỉnh sửa file UbuntuSleeper2.yaml. File này đã có sẵn, là một pod definition file đơn giản, có containers, name, và image. Chỉnh sửa file này, set command để sleep 5000 giây. Nhớ rằng, khi chỉ định command, command luôn phải là một array, theo định dạng:

command: ["sleep", "5000"]

Đó là một cách chỉ định array. Cách khác là viết dưới dạng khác của array, ví dụ sleep rồi 5000 — cũng là hai form hợp lệ, có thể dùng bất kỳ form nào. Một dạng khác vẫn không đúng: ["sleep 5000"] — đây là một array chỉ có một item duy nhất, gộp cả sleep 5000 thành một chuỗi, trong khi đúng ra phải là hai phần tử riêng biệt. Một điều cần nhớ: khi chỉ định command, command luôn phải là phần tử đầu tiên trong array — sleep là command và 5000 là argument, nên sleep phải là một string/item riêng trong array.

Argument có thể được chỉ định hoặc cùng trong command dưới dạng item khác, hoặc cung cấp riêng như một argument thông qua field args:

command: ["sleep"]
args: ["5000"]

Cách này rõ ràng và dễ đọc hơn — command là sleep và argument là 5000. Nhưng dù chỉ định argument ở đâu, cuối cùng nó cũng được kết hợp lại khi chạy. Tạo file với command sleep — xác nhận pod chạy ở trạng thái Running, với command và argument set là 5 và 1000 (kết quả sau khi chỉnh sửa). Cách khác vẫn có thể dùng: command với 2 phần tử trong array ["sleep", "5000"].

Tạo Pod dùng file UbuntuSleeper3.yaml. File này có sẵn nhưng có lỗi, cần fix. Nhìn qua không thấy gì sai — có image, name, command, và argument cũng được chỉ định trong command (điều này vẫn ổn). Thử tạo pod — nhận lỗi BadRequest: cannot unmarshal number into Go struct field ContainerSpec.containers.command of type string. Mọi phần tử trong command phải là string — không được để một con số trần (number) trong đó. Bài tập này để đảm bảo không vô tình đặt một số thay vì string.

Update pod UbuntuSleeper3 để sleep 2000 giây. Thử kubectl edit pod UbuntuSleeper3, đổi command thành 2000 giây. Lưu lại — không được, báo lỗi forbidden: pod updates chỉ được thay đổi một số field nhất định (spec.containers[*].image, spec.initContainers[*].image, spec.activeDeadlineSeconds, và spec.tolerations — riêng tolerations chỉ được phép thêm mới, không sửa/xoá toleration đã có), field command không thuộc nhóm có thể sửa trực tiếp. Kubernetes đã tạo sẵn một file chứa các chỉnh sửa vừa thực hiện — dùng file đó với:

kubectl replace --force -f <file>

Lệnh này sẽ xoá pod và tạo lại pod mới dựa trên file đã chỉnh sửa. Đây là một cách tiếp cận có thể dùng trong kỳ thi khi cần sửa thứ gì đó không thể sửa trực tiếp. Cách khác là chỉnh sửa pod definition file gốc đang lưu local rồi update lại — đây mới là best practice. Việc chờ pod terminate mất một chút thời gian là hành vi bình thường: khi delete một Pod, một kill signal được gửi tới process đang chạy bên trong container, và tuỳ vào process cùng cách nó xử lý kill signal mà việc xử lý và terminate hoàn toàn có thể mất thời gian.

Kiểm tra Dockerfile trong thư mục root/webapp-color/Dockerfile, command nào chạy lúc container khởi động? ENTRYPOINT là nơi command sẽ chạy — command là python app.py.

Kiểm tra Dockerfile2, có cả ENTRYPOINT lẫn command argument, command nào chạy lúc khởi động?pythonapp.py là ENTRYPOINT, và các giá trị còn lại là argument cho ENTRYPOINT — command chạy sẽ là python app.py --color red.

Kiểm tra hai file dưới thư mục webapp-color-2. File đầu là Dockerfile tương tự, có ENTRYPOINT và tham số CMD. File thứ hai là Kubernetes manifest cho Pod, dùng image được build từ Dockerfile đó (giả định image đã được tạo từ Dockerfile này). Trong manifest, command được set là --color green, ghi đè command hiện có. Kết quả cuối cùng sẽ ra sao? Như đã học ở phần trước, command luôn ghi đè ENTRYPOINT. Và nếu có args, nó sẽ ghi đè CMD. Trong trường hợp này, command ghi đè ENTRYPOINT, nên command cuối cùng sẽ là --color green — không chạy app.py nữa, vì phần ENTRYPOINT (python app.py) đã bị ghi đè hoàn toàn.


7. Environment Variables trong Kubernetes

Cho một pod definition file dùng chung image với command Docker đã chạy ở lecture trước. Để set một environment variable, dùng thuộc tính env. env là một array, nên mỗi item dưới thuộc tính env bắt đầu bằng dấu gạch ngang, thể hiện một item trong array. Mỗi item có thuộc tính namevalue. name là tên environment variable được đưa vào trong container, và value là giá trị của nó.

apiVersion: v1
kind: Pod
metadata:
name: simple-webapp-color
spec:
containers:
- name: simple-webapp-color
image: simple-webapp-color
ports:
- containerPort: 8080
env:
- name: APP_COLOR
value: pink

Điều vừa thấy là cách trực tiếp để chỉ định environment variable, dùng định dạng key-value thuần. Tuy nhiên, còn có những cách khác để set environment variable, như dùng ConfigMaps và Secrets. Khác biệt ở đây là thay vì chỉ định value, ta chỉ định valueFrom, sau đó là một specification của ConfigMap hoặc Secret. Sẽ bàn chi tiết về ConfigMaps và Secrets ở các phần tiếp theo.


8. ConfigMaps

Ở phần trước đã thấy cách định nghĩa environment variable trong pod definition file. Khi có nhiều pod definition file, việc quản lý dữ liệu environment nằm rải rác trong các file sẽ trở nên khó khăn. Có thể tách thông tin này ra khỏi pod definition file và quản lý tập trung bằng ConfigMaps. ConfigMaps dùng để truyền dữ liệu cấu hình dưới dạng key-value pair trong Kubernetes. Khi một Pod được tạo, inject ConfigMap vào Pod để các key-value pair có sẵn dưới dạng environment variable cho application host trong container của Pod đó.

💡 Hình dung: Thay vì viết tay cùng một địa chỉ database vào hàng chục pod definition file khác nhau (sửa 1 chỗ phải sửa lại hết), ConfigMap giống một tờ ghi chú dán chung ở bảng thông báo — mọi pod cần thông tin đó chỉ việc "nhìn vào" đúng tờ ghi chú ấy. Đổi giá trị trên tờ ghi chú, mọi pod tham chiếu tới nó đều cập nhật theo (sau khi pod được tạo lại).

Có hai giai đoạn khi cấu hình configmaps: đầu tiên là tạo ConfigMap, sau đó là inject nó vào Pod. Giống như mọi Kubernetes object khác, có hai cách tạo config map: cách imperative, không dùng config map definition file; và cách declarative, dùng config map definition file.

Nếu không muốn tạo config map definition file, có thể dùng trực tiếp lệnh kubectl create configmap cùng các argument cần thiết. Với cách này, chỉ định trực tiếp key-value pair trên command line. Để tạo configmap với các giá trị cho trước, chạy lệnh kubectl create configmap, theo sau là tên config và option --from-literal. Option --from-literal dùng để chỉ định key-value pair ngay trong command. Ví dụ, tạo một config map tên app-config với key-value pair appColor=blue. Nếu muốn thêm nhiều key-value pair, chỉ cần chỉ định --from-literal nhiều lần. Tuy nhiên, cách này sẽ trở nên phức tạp khi có quá nhiều item cấu hình.

Một cách khác để nhập dữ liệu cấu hình là qua file. Dùng option --from-file để chỉ định đường dẫn tới file chứa dữ liệu cần thiết. Dữ liệu từ file này được đọc và lưu dưới tên của file đó.

Bây giờ xem cách tiếp cận declarative. Tạo một definition file giống như đã làm với Pod. File có apiVersion, kind, metadata, và thay vì spec thì có data. apiVersionv1. kindConfigMap. Dưới metadata, chỉ định tên cho ConfigMap — gọi là app-config. Dưới data, thêm dữ liệu cấu hình theo định dạng key-value. Chạy lệnh kubectl create configmap và chỉ định tên file cấu hình — lệnh này tạo ConfigMap app-config với các giá trị đã chỉ định.

Có thể tạo bao nhiêu configmap tuỳ ý cho các mục đích khác nhau theo cách tương tự — ví dụ một cái cho application, một cho MySQL, một cho Redis. Vì vậy việc đặt tên config map phù hợp rất quan trọng, vì sẽ dùng những tên này khi liên kết với các pod sau này. Để xem config map, chạy kubectl get configmaps — liệt kê configmap app-config vừa tạo. Lệnh kubectl describe configmaps liệt kê cả dữ liệu cấu hình dưới mục Data.

Giờ đã có ConfigMap, tiếp tục với bước 2: cấu hình nó với một Pod. Có một pod definition file đơn giản chạy web application. Để inject một environment variable, thêm một thuộc tính mới vào container gọi là envFrom. Thuộc tính envFrom là một list, nên có thể truyền nhiều environment variable tuỳ nhu cầu. Mỗi item trong list tương ứng với một configmap item. Chỉ định tên ConfigMap đã tạo trước đó — đây là cách inject một ConfigMap cụ thể từ những cái đã tạo. Tạo pod definition file lúc này sẽ tạo ra web application với nền màu blue.

Điều vừa thấy là dùng ConfigMaps để inject environment variable. Còn có những cách khác để inject dữ liệu cấu hình vào Pod — có thể inject dưới dạng một environment variable đơn lẻ, hoặc inject toàn bộ dữ liệu dưới dạng file trong một volume.


9. Lab: Thực hành Environment Variables và ConfigMaps

Có bao nhiêu pod tồn tại trên hệ thống? Chỉ có một pod.

Tên environment variable được set trên container trong pod là gì? Xem chi tiết pod — environment variable là APP_COLOR, set giá trị pink.

Giá trị của environment variable APP_COLOR là gì? pink.

Xem UI của web application bằng cách click vào link webapp-color — mở web application, đúng là màu pink.

Update environment variable trên pod để hiển thị nền màu green, xoá và tạo lại pod nếu cần. Chạy kubectl edit trên pod webapp-color. Biết trước là sẽ không cho phép edit trực tiếp trên pod, nhưng không sao vì chỉ cần lấy file manifest đã được chỉnh sửa để dùng thay thế pod. Set giá trị thành green và lưu — báo lỗi forbidden (như dự kiến), thoát ra. Nhưng file chứa các chỉnh sửa vừa thực hiện đã có sẵn giá trị green. Dùng file đó:

kubectl replace --force -f <file>

Lệnh này kill pod hiện tại và tạo pod mới. Kiểm tra lại pod, click vào link — thấy màu green.

Có bao nhiêu ConfigMap tồn tại trong namespace default? Chạy kubectl get configmap — có hai ConfigMap.

Xác định database host từ ConfigMap db-config. Chạy kubectl describe cm db-config (dùng short form cm cho ConfigMap) — thấy thông tin DB host, DB name, DB port. DB host là sql01.example.com.

Tạo một ConfigMap mới cho pod webapp-color theo spec cho trước. Chạy kubectl create configmap, xem --help để lấy đúng cú pháp. Dữ liệu cần đưa vào ConfigMap là APP_COLOR=dark blue. Vì không cần option --from-file, dùng option --from-literal:

kubectl create configmap webapp-config-map --from-literal=APP_COLOR="dark blue"

Xác nhận — ConfigMap đã được tạo, với dữ liệu APP_COLOR set là dark blue.

Update environment variable trên pod để dùng ConfigMap vừa tạo. Chạy kubectl edit pod cho pod APP_COLOR (webapp-color). Hiện tại environment variable đang set trực tiếp bằng namevalue. Cần bỏ phần này đi và lấy environment variable từ ConfigMap, dùng cú pháp envFrom. Tham khảo Kubernetes documentation, mục ConfigMap, xem ví dụ cấu hình pod dùng ConfigMap dưới dạng environment variable — có thể chỉ định từng environment variable một dùng valueFrom rồi ConfigMap, hoặc feed toàn bộ ConfigMap bằng envFrom:

envFrom:
- configMapRef:
name: webapp-config-map

Lưu lại — vẫn không được phép sửa trực tiếp, thoát ra và dùng file đã chỉnh sửa để force replace pod:

kubectl replace --force -f <file>

Chờ pod bị xoá và tạo lại. Xác nhận — thấy container webapp-color với environment variable được cấu hình từ ConfigMap. Kiểm tra lại web application — màu đã chuyển thành dark blue.


10. Secrets

Có một web application Python đơn giản kết nối tới một database MySQL. Khi thành công, application hiển thị một message thành công. Nếu xem kỹ code sẽ thấy hostname, username và password được hard-code. Đây tất nhiên không phải ý hay. Như đã học ở phần trước, một lựa chọn là chuyển các giá trị này vào một ConfigMap. Config map lưu dữ liệu cấu hình dưới dạng plain text. Vậy nên dù có thể chuyển hostname và username vào ConfigMap được, nhưng đây chắc chắn không phải nơi thích hợp để lưu password.

Đó là lúc Secrets phát huy tác dụng. Secrets dùng để lưu thông tin nhạy cảm như password hoặc key. Chúng tương tự ConfigMaps, ngoại trừ việc được lưu dưới định dạng đã encode hoặc hash.

💡 Hình dung: ConfigMap giống tấm bảng thông báo dán ở sảnh — ai đi ngang cũng đọc được ngay. Secret giống một phong bì dán kín cất trong ngăn kéo — không phải "không ai mở được" (chỉ cần có quyền vào ngăn kéo là bóc ra đọc được, vì bên trong chỉ là base64, xem thêm §13), nhưng ít nhất không vô tình lộ ra khi ai đó chỉ lướt qua bảng thông báo chung.

Cũng giống ConfigMaps, có hai bước khi làm việc với secrets: đầu tiên tạo secret, sau đó inject nó vào một pod. Có hai cách tạo Secret: cách imperative, không dùng Secret definition file; và cách declarative, dùng Secret definition file. Với cách imperative, có thể chỉ định trực tiếp key-value pair ngay trên command line. Để tạo secret với các giá trị cho trước, chạy lệnh kubectl create secret generic, theo sau là tên secret và option --from-literal. Ví dụ, tạo một secret tên app-secret với key-value pair DB_HOST=mysql. Nếu muốn thêm nhiều key-value pair, chỉ định --from-literal nhiều lần — có thể phức tạp khi có quá nhiều secret cần truyền vào.

Cách khác để nhập dữ liệu Secret là qua file, dùng option --from-file để chỉ định đường dẫn tới file chứa dữ liệu cần thiết — dữ liệu được đọc và lưu dưới tên file đó.

Với cách declarative, tạo một definition file giống như với ConfigMap. File có apiVersion, kind, metadata, và data. apiVersionv1, kindSecret. Dưới metadata, chỉ định tên Secret — gọi là app-secret. Dưới data, thêm dữ liệu secret theo định dạng key-value. Tuy nhiên, một điều đã bàn về Secrets là chúng dùng để lưu dữ liệu nhạy cảm và được lưu dưới định dạng đã encode. Nếu chỉ định dữ liệu ở dạng plain text sẽ không an toàn. Vậy nên khi tạo Secret theo cách declarative, phải chỉ định các giá trị secret ở định dạng đã encode. Để convert dữ liệu từ plain text sang định dạng encode, trên một Linux host, chạy lệnh echo -n theo sau là text cần convert (ví dụ mysql), rồi pipe qua utility base64.

Để xem secrets, chạy kubectl get secrets — liệt kê secret vừa tạo, cùng với một secret khác được Kubernetes tạo sẵn cho mục đích nội bộ. Để xem thêm thông tin về secret vừa tạo, chạy kubectl describe secret — hiển thị các attribute trong secret nhưng ẩn giá trị thực. Để xem cả giá trị, chạy kubectl get secret với output ở định dạng YAML bằng option -o — lúc này sẽ thấy các giá trị đã hash. Để decode các giá trị này, dùng cùng lệnh base64 như lúc encode, nhưng lần này thêm option decode (base64 --decode).

Đã có Secret, tiếp tục bước 2: cấu hình nó với một Pod. Có một pod definition file đơn giản chạy application. Inject một environment variable, thêm thuộc tính mới vào container gọi là envFrom. Thuộc tính envFrom là một list, nên có thể truyền nhiều environment variable tuỳ nhu cầu. Mỗi item trong list tương ứng với một secret item. Chỉ định tên secret đã tạo trước đó. Tạo pod definition file lúc này khiến dữ liệu trong secret có sẵn dưới dạng environment variable cho application.

Điều vừa thấy là inject secrets dưới dạng environment variable vào pod. Còn có những cách khác để inject secrets vào pod — có thể inject dưới dạng environment variable đơn lẻ, hoặc inject toàn bộ secret dưới dạng file trong một volume. Nếu mount secret dưới dạng volume trong pod, mỗi attribute trong secret sẽ được tạo thành một file, với giá trị của secret là nội dung file đó. Trong trường hợp có ba attribute trong secret, ba file sẽ được tạo ra. Và nếu xem nội dung file DB password sẽ thấy password bên trong đó.

Secret injection: environment variable vs volume mountSecret: db-secretenvFrom / secretRefvolumeMountPod (env var)DB_USER=rootDB_PASSWORD=***mỗi key → 1 environment variablePod (volume mount)Volume: /etc/db-secretDB_USERDB_PASSWORDmỗi key → 1 file trong volume

Tài liệu bổ sung: Khoá học có đính kèm một video giới thiệu thêm về Secret Store CSI Driver — "Dive deep into the world of Kubernetes security with our comprehensive guide to Secret Store CSI Driver": https://www.youtube.com/watch?v=MTnQW9MxnRI


11. Lab: Thực hành Secrets

Có bao nhiêu Secret tồn tại trên hệ thống trong namespace default? Chạy kubectl get secrets — chỉ có một. Đáp án: 1.

Có bao nhiêu Secret data được định nghĩa trong namespace default? Chạy kubectl describe secret — thấy thêm chi tiết, dưới mục data có nhiều item: ca.crt, namespace, và token (đây chính là token đã encode). Vậy có 3.

Type của default token Secret là gì? Type là kubernetes.io/service-account-token.

Trong các mục sau, mục nào KHÔNG phải Secret data được định nghĩa trong default token? Đã biết dữ liệu gồm ca.crt, namespace, và token — vậy type (Opaque data) không phải một item trong data.

Deploy một application theo kiến trúc cho sẵn — các pod và service đã được deploy sẵn. Kiểm tra pod và service, xem web application qua link Webapp MySQL. Kiểm tra deployment — không có deployment nào, kiểm tra pod — có hai pod: pod web app và pod MySQL. Kiểm tra service — có service mặc định của Kubernetes, service webapp, và service sql01. Rồi kiểm tra Secret — có default token Secret, nhưng đó không phải secret được dùng ở đây. Secret cần thiết để web application kết nối tới database MySQL.

Kiểm tra trạng thái application — application đang ở trạng thái failed, báo lỗi: failed connecting to the MySQL database. The database host is not set. The DB_USER is not set. And the password is not set.

Nguyên nhân application fail là do chưa tạo Secret object. Tạo một secret mới tên db-secret với dữ liệu cho trước. Chạy kubectl create secret, kiểm tra help — khi tạo secret phải chỉ định type: không tạo Docker registry hay TLS secret, mà tạo generic secret:

kubectl create secret generic db-secret \
--from-literal=DB_HOST=sql01 \
--from-literal=DB_USER=root \
--from-literal=DB_PASSWORD=password123

Nhớ các key này phân biệt hoa/thường (case-sensitive). Xác nhận — kubectl describe secret db-secret cho thấy secret đã tạo với data DB_HOST, DB_USER, DB_PASSWORD.

Cấu hình pod web app để load environment variable từ Secret vừa tạo. Kiểm tra pod webapp — hiện chưa có Secret nào. Trước khi edit, tham khảo Kubernetes documentation trang Secrets — như tài liệu có ghi, không nên tạo Secret như vừa làm để lưu database credentials hoặc password, vì dữ liệu secret được lưu trong etcd nguyên trạng, không hề encrypt — bất kỳ ai có quyền truy cập API server hoặc etcd database đều có thể đọc hoặc thậm chí sửa secret. Vì vậy nên bật encryption at rest hoặc xem xét cấu hình role-based access control.

Tìm thông tin cấu hình Secret cho Pod trong tài liệu — có nhiều lựa chọn, trong đó có dùng Secret như environment variable qua envFrom kết hợp secretKeyRef — toàn bộ dữ liệu lưu trong Secret sẽ được truyền qua dưới dạng environment variable:

envFrom:
- secretRef:
name: db-secret

Thêm block này vào phần containers của pod, lưu file, và thực hiện:

kubectl replace --force -f <file>

Chờ pod bị xoá và tạo lại. Xác nhận — environment variable được đọc từ db-secret. Quay lại xem web application — application giờ đã success, đọc được các environment variable đã truyền qua. (Lưu ý: đây chỉ là một sample application để minh hoạ cách secret được truyền qua environment variable — trong thực tế không nên đọc và hiển thị password như vậy, nhưng cách này giúp debug khi có gì đó không được truyền đúng.)


12. Mã hoá Secret Data at Rest

Bài này xem xét việc encrypt Secret data at rest. Theo trang tài liệu Kubernetes, mục Tasks → Administrate a cluster → Encrypt data at rest, có các bước sau (nội dung dưới đây bám theo tài liệu đó nhưng có giải thích thêm).

Bắt đầu một Kubernetes Playground — một cluster single-node, được build với tool kubeadm và dùng containerd.

Bước 1 — Tạo Secret object và xem cách nó được lưu. Tạo secret bằng kubectl create secret generic, một secret dạng literal, với key1: super-secret, đặt tên secret là mysecret2. Kiểm tra bằng kubectl get secretkubectl describe secret secret — thấy tên secret và data. Xem chi tiết hơn với kubectl get secret -o yaml — thấy dữ liệu key1, ở định dạng đã encode. Ai cũng có thể lấy chuỗi này rồi decode và xem được secret gốc. Kiểm tra công cụ base64, rồi echo giá trị đã encode, pipe qua base64 với option decode — thấy lại giá trị secret gốc.

Vậy điều cần nhớ: Secret object lưu trong cấu hình etcd chỉ là Base64 encode. Chỉ cần Base64 decode là thấy được nội dung. Vì vậy nhớ đừng tạo Secret definition file rồi push lên GitHub hay đâu đó — bất kỳ ai cũng có thể lấy và chạy lệnh này để xem secret.

Đó là bước đầu tiên, nhưng cần làm rõ: phạm vi bài này không thực sự liên quan tới encode ở đây. Sẽ tập trung vào dữ liệu được lưu bên trong etcd server. Sau khi hoàn tất, phần encode (Base64 trên API) vẫn giữ nguyên như cũ — không phải trọng tâm ở đây. Trọng tâm là cách dữ liệu được lưu trong etcd server.

Bước 2 — Xem cách dữ liệu được lưu trong etcd server. Ở cuối trang tài liệu có lệnh dùng etcdctl API version 3, chạy lệnh etcdctl, truyền các CA certificate để authenticate, và lấy đúng secret đó. Cách nó được lưu trong etcd là dưới đường dẫn registry/secrets/default/<tên-secret>.

Kiểm tra xem có sẵn command line etcdctl chưa — chưa có, nên bước đầu là cài etcdctl. Dùng apt install với gói etcd-client (tên package trên Ubuntu/Debian — có thể khác tên tuỳ distro). Nếu chưa có etcdctl, nhớ rằng etcd server đang chạy dưới dạng pod — có thể SSH vào control plane node, exec vào pod đó và chạy lệnh etcdctl từ bên trong, hoặc nếu muốn chạy local từ control plane node thì dùng etcdctl client utility. Nếu chưa có command line etcdctl, có nhiều lựa chọn để cài trên các hệ thống khác nhau. Sau khi cài xong bằng gói etcd-client, kiểm tra kubectl get pods -n kube-system — thấy etcd control plane pod, vì server vẫn chạy dưới dạng pod, còn etcdctl vừa cài chỉ là client.

Bước tiếp theo cần certificate file — kiểm tra dưới /etc/etcd, có đủ các certificate file cần để kết nối/authenticate tới etcd server. Copy lệnh, nhớ set ETCDCTL_API là 3, và tên Secret là mysecret (hoặc mysecret2 tuỳ tên đã tạo). Chạy lệnh này cho ra thông tin có phần lộn xộn nhưng vẫn thấy được secret dưới dạng text. Để xem đúng định dạng, chỉ cần append xxd -p vào lệnh, vì đôi khi không thấy đúng định dạng nếu không có nó. Đây chính là dữ liệu được lưu trong etcd. Nhìn kỹ sẽ thấy password hoặc bất cứ gì đã lưu hiển thị nguyên văn như vậy.

Vậy dữ liệu được lưu trong etcd ở định dạng chưa encrypt. Bất kỳ ai có quyền truy cập etcd đều có thể xem toàn bộ secret và thông tin nhạy cảm khác được lưu dưới dạng Secret object. Đó chính là vấn đề đang cần giải quyết bằng cách bật encryption at rest trong etcd.

💡 Hình dung: Secret chưa bật encryption at rest giống một cuốn sổ ghi mật khẩu để ngay trên bàn làm việc — ai bước vào phòng (truy cập được etcd) cũng đọc được ngay. Bật encryption at rest giống khóa cuốn sổ đó vào két sắt trước khi cất — cuốn sổ vẫn nằm nguyên chỗ cũ trong etcd, nhưng giờ phải có đúng chìa khóa (encryption key trong EncryptionConfiguration) mới đọc được nội dung.

Write path khi tạo Secret — trước và sau khi bật encryption at restChưa bật encryption at restkubectl createsecretkube-apiserveretcdplaintext (chỉ base64)Đã bật encryption at restkubectl createsecretkube-apiserverEncryptionConfig(provider: aescbc)etcd→ đã encryptedSecret cũ (tạo trước khi bật) không tự động re-encrypt — phải update lại để áp dụng

Bước 3 — Kiểm tra và bật encryption at rest. Theo tài liệu, đầu tiên cần xác định xem encryption at rest đã được bật hay chưa — việc này thực hiện qua thuộc tính encryption-provider-config. Kiểm tra process kube-apiserver đang chạy, lấy đầy đủ các option của nó, xem có option encryption-provider-config hay chưa — không thấy kết quả nào, nghĩa là option này chưa được cấu hình. Có thể xác nhận thêm vì đây là setup dùng kubeadm — dùng manifest tại /var/lib/kubelet/config.yaml (Kubernetes Manifest), xem file này để thấy toàn bộ option đã cấu hình cho kube-apiserver — cũng không thấy option encryption ở đây. Vậy nghĩa là encryption at rest chưa được bật, đã xác nhận điều này bằng cách tạo Secret object ở trên.

Bước tiếp theo là tạo một EncryptionConfiguration file rồi truyền nó vào qua option đó. Vậy đó là toàn bộ các bước cần để bật encryption: tạo một configuration file, rồi truyền nó vào như một option.

Nội dung file này gồm apiVersion (apiserver.config.k8s.io/v1), kindEncryptionConfiguration, và resources. Có thể chọn resource nào muốn encrypt — ví dụ Pods, Deployments, Secrets, Services. Không nhất thiết phải encrypt và lưu tất cả dữ liệu về Pods và Deployments, vì không phải mọi thứ đều nhạy cảm. Ở đây chỉ quan tâm tới Secrets. Dưới resources, chỉ định target là secrets — nghĩa là chỉ Secret object mới được encrypt.

Có thể encrypt secrets bằng một tập các provider. Provider mặc định gọi là identity, nghĩa là không có encryption nào cả — resource được ghi nguyên trạng, không encrypt. Còn có các provider khác như secretbox, aesgcm, aescbc, và kms (tích hợp với key management service bên ngoài như AWS KMS, HashiCorp Vault) — đây đều là các thuật toán/cơ chế encryption khác nhau, với chi tiết cách chúng encrypt được liệt kê trong tài liệu. Có thể chọn provider bất kỳ và cung cấp một key. Key này gắn với một secret — có thể cung cấp nhiều key name và secret, và secret này chính là thứ được dùng để encryption bởi thuật toán đã chọn.

Một điều cần lưu ý: thứ tựproviders là một list, có item thứ nhất, thứ hai, thứ ba, thứ tư. Thứ tự này quan trọng, vì khi encryption diễn ra, provider đầu tiên trong list sẽ được dùng để encrypt, sau đó bất kỳ provider nào trong list cũng có thể được dùng để decrypt. Vậy encryption luôn dùng provider đầu tiên. Nếu identity (không encrypt) là provider đầu tiên, thì không có encryption nào được bật cả — dù các provider khác có mặt phía sau.

Tạo một phiên bản đơn giản của file này: chỉ định tất cả Secret sẽ được encrypt, dùng provider aescbc, và đặt identity xuống cuối danh sách — vậy aescbc là provider đầu tiên, nên nó sẽ được dùng để encryption.

Provider này cần một Secret object — có thể generate một random key 32-byte. Lấy key này, copy vào, rồi tạo file EncryptionConfiguration, đặt tên enc.yaml, dán nội dung vào, và thay giá trị secret bằng key vừa generate. File enc.yaml hiện đang nằm trong home directory.

Bước tiếp theo là edit file kube-apiserver để trỏ tới file encryption vừa tạo — thêm dòng trỏ tới EncryptionConfiguration. Vì file được tạo local, cần mount nó vào bên trong pod, dùng volumesvolumeMounts. Ở đây có local directory trên control plane node, được map tới một path bên trong Pod — bất cứ gì có ở local directory sẽ có mặt ở path đó bên trong pod (trong trường hợp này, /etc/kubernetes/enc trên host được map tới /etc/kubernetes/enc bên trong pod).

Tạo local directory đó trước để đặt file vào, di chuyển file encryption từ home directory sang local directory đó. Sau đó edit manifest file kube-apiserver: thêm dòng chỉ ra vị trí file EncryptionConfiguration; dưới volumeMounts, chỉ định path bên trong pod nơi volume sẽ được mount; dưới volumes, chỉ định vị trí local directory. Lưu file lại — kube-apiserver sẽ restart. Chờ vài giây, kiểm tra bằng crictl ps (vì dùng containerd) để xem trạng thái — sau khi thấy pod Ready trở lại, chạy ps -aux và grep kube-apiserver để xác nhận option encryption provider đã được cấu hình.

Bước 4 — Kiểm chứng encryption hoạt động. Tạo thêm một Secret object mới, với key2: topsecret, cũng đặt tên mysecret2 (báo đã tồn tại, đổi tên). Giờ có hai secret: cái đầu tạo trước khi bật encryption, cái này tạo sau khi encryption đã bật. Chạy lại đúng lệnh kiểm tra etcd như trước cho secret mới — lần này không còn thấy giá trị topsecret ở dạng plain text nữa, nghĩa là encryption đã hoạt động. Kiểm tra lại secret cũ — vẫn còn thấy super-secret ở dạng plain text, vì sau khi bật encryption, chỉ những gì được tạo mới mới được encrypt — những gì đã tồn tại trước đó sẽ không tự động được re-encrypt. Nhưng nếu update một Secret đã tồn tại, nó sẽ được re-encrypt.

Để đảm bảo tất cả Secret đều được encrypt, một trong các việc cần làm là lấy các Secret rồi thay thế chúng bằng chính JSON file đó — về cơ bản là update lại các object với đúng dữ liệu cũ. Sau khi làm việc này và chạy lại lệnh kiểm tra, thấy không còn hiển thị super-secret nữa.

Vậy đó là cách encrypt Secret data at rest bằng một trong các thuật toán encryption này.


13. Lưu ý về tính an toàn của Secrets

Nhớ rằng secrets encode dữ liệu ở định dạng base64. Bất kỳ ai có secret đã encode base64 đều có thể dễ dàng decode nó. Vì vậy, có thể xem secrets là không thực sự an toàn theo nghĩa đó.

Khái niệm về độ an toàn của Secrets trong Kubernetes khá gây nhầm lẫn. Trang tài liệu Kubernetes và nhiều blog thường gọi secrets là một "lựa chọn an toàn hơn" để lưu dữ liệu nhạy cảm. Chúng an toàn hơn so với lưu ở dạng plain text vì giảm rủi ro vô tình để lộ password và dữ liệu nhạy cảm khác. Nhưng bản thân secret không "an toàn" theo nghĩa được encrypt — chính các best practice xung quanh việc sử dụng nó mới là thứ khiến nó an toàn hơn.

Secrets không được encrypt, nên xét theo nghĩa đó thì không an toàn hơn. Tuy nhiên, một số best practice khi dùng secrets giúp nó an toàn hơn, như:

  • Không check-in secret object definition file vào source code repository.
  • Bật Encryption at Rest cho Secrets để chúng được lưu ở dạng đã encrypt trong ETCD.

Ngoài ra, cách Kubernetes xử lý secrets cũng góp phần, cụ thể:

  • Một secret chỉ được gửi tới một node nếu có pod trên node đó cần nó.
  • Kubelet lưu secret vào tmpfs, để secret không bị ghi ra disk storage.
  • Khi Pod phụ thuộc vào secret bị xoá, kubelet sẽ xoá luôn bản copy local của dữ liệu secret đó.

Có những cách tốt hơn để xử lý dữ liệu nhạy cảm như password trong Kubernetes, ví dụ dùng các công cụ như Helm Secrets và HashiCorp Vault.


Nguồn tham khảo

Nguồn gốc: Khóa "Certified Kubernetes Administrator (CKA)" — phần "Application Lifecycle Management" (Rolling Updates và Rollbacks; Commands và Arguments trong Docker/Kubernetes; Environment Variables; ConfigMaps; Secrets; Mã hoá Secret Data at Rest; Lưu ý về tính an toàn của Secrets), 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: