6. Microservices Architecture
Mục lục
- 1. Microservices Architecture: ứng dụng Voting App là gì?
- 2. Chạy Voting App bằng Docker
- 3. Lên kế hoạch deploy Voting App lên Kubernetes
- 4. Demo: Deploy Voting App bằng Pod và Service
- 5. Demo: Chuyển từ Pod sang Deployment
Microservices Architecture
1. Microservices Architecture: ứng dụng Voting App là gì?
Phần này tìm hiểu microservices architecture bằng 1 ứng dụng web đơn giản, sau đó deploy ứng dụng này lên nhiều nền tảng Kubernetes khác nhau, ví dụ Google Cloud Platform. Ứng dụng dùng để minh họa là 1 ứng dụng mẫu do Docker phát triển — cùng 1 ứng dụng này sẽ được dùng xuyên suốt các phần sau của khóa học.
Đây là 1 ứng dụng voting (bỏ phiếu) mẫu, cung cấp 1 giao diện để user vote và 1 giao diện khác hiển thị kết quả. Ứng dụng gồm nhiều thành phần:
- Voting app — web application viết bằng Python, cho user chọn giữa 2 lựa chọn (cat và dog). Khi chọn xong, vote được lưu vào Redis.
- Redis — đóng vai trò in-memory database trong ví dụ này.
- Worker — ứng dụng viết bằng .NET, xử lý vote mới và cập nhật vào persistent database — trong trường hợp này là PostgreSQL. PostgreSQL có 1 bảng lưu số vote cho mỗi lựa chọn (cat, dog).
- Result app — web application viết bằng Node.js, đọc số lượng vote từ PostgreSQL và hiển thị cho user trên giao diện web.
Đó là kiến trúc và luồng dữ liệu (data flow) của ứng dụng voting mẫu này. Ứng dụng được xây dựng từ nhiều service khác nhau, nhiều công cụ phát triển khác nhau, và nhiều nền tảng phát triển khác nhau — Python, Node.js, .NET, v.v. Ứng dụng mẫu này dùng để minh họa việc dựng cả 1 stack ứng dụng gồm nhiều thành phần đa dạng trên Docker dễ dàng tới mức nào.
2. Chạy Voting App bằng Docker
Thử dựng ứng dụng stack này trên 1 Docker engine duy nhất bằng lệnh docker run, giả sử toàn bộ image đã được build sẵn và có trên Docker repository.
Bước 1 — data layer. Chạy Redis và PostgreSQL ở chế độ nền (-d), đặt tên container rõ ràng (redis, db) — tên này sẽ cần dùng lại ngay ở bước link phía dưới.
docker run -d --name=redis redis
# Output minh họa
7e6c9d2a4f1b
docker run -d --name=db postgres
# Output minh họa
a3f81c05e9d7
Bước 2 — application service. Deploy front-end voting app (vote) và result app (result) — cả 2 đều là web server chạy trên port 80 bên trong container, nên publish (-p) ra 2 port khác nhau trên host để không đụng nhau: 5000 cho vote, 5001 cho result. Cuối cùng chạy worker — worker không có web UI nên không cần publish port.
docker run -d --name=vote -p 5000:80 voting-app
# Output minh họa
1b4d7e93a2c6
docker run -d --name=result -p 5001:80 result-app
# Output minh họa
9f2a6b8d0e14
docker run -d --name=worker worker
# Output minh họa
5c0e3f7a1d92
docker ps cho thấy đủ 5 container đang chạy, nhưng mở browser vào localhost:5000 thì trang vote không hoạt động. Lý do: mới chạy container riêng lẻ, chưa link chúng lại — voting app chưa biết Redis nào để nói chuyện, worker và result app chưa biết PostgreSQL nào để nói chuyện.
Cách giải quyết: --link. Trong code, voting app tìm 1 host tên redis; worker và result app tìm 1 host tên db. Cờ --link <container>:<host-alias> chèn thêm 1 dòng vào file hosts của container đang chạy, trỏ <host-alias> đó tới internal IP của <container> — đây chính là lý do đã đặt tên container y hệt host mà code đang tìm ngay từ bước 1.
docker run -d --name=vote -p 5000:80 --link redis:redis voting-app
docker run -d --name=result -p 5001:80 --link db:db result-app
docker run -d --name=worker --link redis:redis --link db:db worker
Worker cần truy cập cả Redis lẫn PostgreSQL nên có 2 --link. Chạy lại đúng thứ tự trên, localhost:5000 và localhost:5001 giờ hoạt động bình thường.
⚠️ Lưu ý: dùng
--linktheo cách này đã bị deprecated, và hỗ trợ có thể bị gỡ bỏ trong tương lai ở Docker. Lý do là các khái niệm mới hơn và nâng cao hơn trong Docker Swarm và networking hỗ trợ cách làm tốt hơn để đạt được điều tương tự — nhưng vẫn đáng học qua cách này để nắm khái niệm từ những điều cơ bản nhất.
3. Lên kế hoạch deploy Voting App lên Kubernetes
Đã thấy ứng dụng voting hoạt động trên Docker, giờ xem cách deploy nó lên Kubernetes. Trước khi bắt tay vào làm, cần có ý tưởng rõ ràng về mục tiêu và lên kế hoạch trước.
Mục tiêu: deploy các ứng dụng này dưới dạng container trên 1 Kubernetes cluster, thiết lập kết nối (connectivity) giữa các container để chúng truy cập được lẫn nhau và truy cập được database, và cho phép truy cập từ bên ngoài (external access) cho các ứng dụng hướng ra ngoài — voting app và result app — để user truy cập được qua trình duyệt web.
Không thể deploy container trực tiếp lên Kubernetes — object nhỏ nhất có thể tạo trên 1 Kubernetes cluster là Pod. Vậy phải deploy các ứng dụng này dưới dạng Pod trên cluster trước (hoặc dưới dạng ReplicaSet hay Deployment) — nhưng để đơn giản, phần này chỉ dùng Pod; sau đó sẽ xem cách chuyển đổi dễ dàng sang Deployment.
Sau khi Pod đã deploy, bước tiếp theo là thiết lập kết nối giữa các service — cần biết rõ yêu cầu kết nối (connectivity requirements): ứng dụng nào cần truy cập tới service nào.
- Redis database được truy cập bởi voting app (lưu vote vào Redis) và worker app (đọc vote từ Redis).
- PostgreSQL database được truy cập bởi worker app (cập nhật tổng số vote) và result app (đọc tổng số vote để hiển thị).
- Voting app được truy cập bởi external user (người vote).
- Result app cũng được truy cập bởi external user (xem kết quả).
Hầu hết các thành phần đều được 1 thành phần khác truy cập — ngoại trừ worker app. Không có mũi tên nào đi vào worker — nghĩa là không thành phần nào khác, cũng không user bên ngoài nào, truy cập worker app. Worker app chỉ đơn giản đọc số vote từ Redis rồi cập nhật tổng số vote lên PostgreSQL — không thành phần nào khác hay user bên ngoài nào truy cập worker app.
Về port: voting app (Python web server) lắng nghe trên port 80, result app (Node.js server) cũng lắng nghe trên port 80, Redis database có service lắng nghe trên port 6379, PostgreSQL database có service lắng nghe trên port 5432, còn worker app không có service nào — vì nó chỉ là 1 worker process, không được truy cập bởi bất kỳ service hay external user nào.
vote → redis: ghi vote.worker → redis: đọc vote.worker → db: ghi tổng số vote.result → db: đọc tổng số vote.
Vậy làm sao để 1 thành phần truy cập được thành phần khác — ví dụ làm sao để Redis database truy cập được từ voting app? Voting app có nên dùng IP address của Redis Pod không? Không — vì IP đó có thể thay đổi, IP của Pod có thể đổi nếu Pod restart, và cũng có thể gặp vấn đề khi scale ứng dụng sau này. Cách đúng là dùng 1 Service.
Sẽ tạo 1 Service cho Redis Pod để voting app và worker app truy cập được, và đặt tên nó là Redis service — service này sẽ truy cập được ở bất kỳ đâu trong cluster bằng tên của service, redis. Vì sao tên này quan trọng? Source code bên trong voting app và worker app đã hard-code sẵn để trỏ tới 1 Redis database chạy trên host tên redis — nên cần đặt tên Service là redis để các ứng dụng này kết nối được tới Redis database. (Việc hard-code như vậy không phải best practice — lẽ ra nên dùng environment variable hoặc cách khác, nhưng để đơn giản, phần này giữ nguyên theo đúng cách ứng dụng đã được phát triển.)
💡 Hình dung như thế này: Service giống danh bạ điện thoại nội bộ công ty. Ứng dụng chỉ cần biết đúng tên phòng ban cần gọi (
redis,db) — không cần nhớ số máy lẻ cụ thể (IP của Pod), và danh bạ tự động cập nhật ngay nếu nhân viên đổi chỗ ngồi (Pod bị xóa/tạo lại ở IP khác). Đây cũng là lý do source code hard-code sẵn tênredis/dbvẫn hoạt động được — miễn đặt đúng tên Service khớp với tên đã hard-code, "danh bạ" sẽ tự nối máy tới đúng Pod đang chạy dịch vụ đó.
Các Service này không cần truy cập từ bên ngoài cluster, nên chỉ cần loại ClusterIP. Áp dụng tương tự khi tạo Service cho PostgreSQL Pod, để PostgreSQL DB truy cập được từ worker và result app. Vậy nên đặt tên Service PostgreSQL là gì? Nhìn vào source code của result app và worker app, chúng tìm database ở địa chỉ db — nên Service tạo cho PostgreSQL phải đặt tên là db. Cũng lưu ý: khi kết nối tới database, worker và result app truyền vào username và password để kết nối, cả 2 đều set là postgres — nên khi deploy PostgreSQL DB Pod, phải đảm bảo set đúng các credential này làm bộ credential khởi tạo (initial set of credentials) khi tạo database.
Tiếp theo là thiết lập truy cập từ bên ngoài (external access) — dùng Service với type set là NodePort. Tạo Service cho voting app và result app, set type là NodePort. Có thể tự quyết định port nào sẽ expose ra — sẽ là 1 port cao (high port), số port lớn hơn 30000.
Tóm tắt kế hoạch: sẽ deploy tổng cộng 5 Pod (vote, result, redis, db, worker) và có 4 Service — 1 cho Redis, 1 cho PostgreSQL (cả 2 loại ClusterIP, internal), và Service hướng ra ngoài cho voting app và result app (loại NodePort). Riêng worker Pod không có Service, vì nó không chạy bất kỳ process/service nào cần được truy cập bởi ứng dụng khác hay user bên ngoài — nó chỉ là 1 worker process đọc từ 1 database và cập nhật database khác, nên không cần Service. (1 Service chỉ cần thiết khi ứng dụng có process/database service/web service nào đó cần được expose, cần được truy cập bởi thành phần khác — điều này không đúng với worker app.)
Trước khi bắt tay deploy, danh sách Docker image sẽ dùng cho từng thành phần — image được build từ 1 fork của repository gốc Docker Samples, riêng database dùng bản release chính thức có sẵn:
| Thành phần | Image | Tag |
|---|---|---|
| vote | KodeKloud/example-voting-app_vote | v1 |
| result | result | v1 |
| worker | worker | v1 |
| redis | image Redis chính thức | — |
| db | image PostgreSQL chính thức | — |
(Lưu ý: ở phần demo tiếp theo, file Pod thực tế lại dùng image dockersamples/examplevotingapp_vote, dockersamples/example-voting-app-result, dockersamples/examplevotingapp_worker.)
4. Demo: Deploy Voting App bằng Pod và Service
Bước đầu tiên là tạo file định nghĩa Pod cho mỗi service trong ứng dụng.
Pod cho voting app — tạo file voting-app-pod.yaml:
apiVersion: v1,kind: Pod.metadata—name: vote,labels: theo conventionapp: <tên service>— ở đâyapp: vote.spec.containers— 1 container,name: vote,image: dockersamples/examplevotingapp_vote,containerPort: 80(voting app lắng nghe trên port 80), có thể đặt tên cho port này, ở đây gọi làvote.
# voting-app-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: vote
labels:
app: vote
spec:
containers:
- name: vote
image: dockersamples/examplevotingapp_vote
ports:
- containerPort: 80
name: vote
Pod cho result app — tạo file result-app-pod.yaml, copy từ file voting app và đổi vài giá trị: Pod spec gần như giống 100%, đổi tên thành result, đổi label thành result, đổi tên container thành result, image là dockersamples/example-voting-app-result, cũng lắng nghe trên port 80, đổi tên Pod thành result.
# result-app-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: result
labels:
app: result
spec:
containers:
- name: result
image: dockersamples/example-voting-app-result
ports:
- containerPort: 80
name: result
Pod cho Redis — tạo file redis-pod.yaml, copy lại, đổi mọi chỗ có chữ "vote" thành "redis" (tên, label, tên container, containerPort), đổi image thành Redis Alpine image, và đổi containerPort — Redis image mặc định lắng nghe trên port 6379.
# redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis
labels:
app: redis
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
name: redis
Pod cho PostgreSQL — tạo file db-pod.yaml, copy config Redis, đổi tên thành db, label thành app: db, dùng Postgres image, đặt tên là postgres, tag image là 15-alpine, containerPort là 5432 (port mặc định Postgres lắng nghe), đổi tên port thành postgres. Để cấu hình Postgres, cần cung cấp thông tin username/password mặc định qua environment variable — dùng field env, cung cấp danh sách cặp key-value (name và value). Theo convention của Postgres, key là POSTGRES_USER (username mặc định, set là postgres) và POSTGRES_PASSWORD (password mặc định, cũng set là postgres) — để đơn giản cho demo này.
# db-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: db
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: postgres
Pod cho worker — copy voting-app pod làm template, đổi các giá trị thành worker, image là dockersamples/examplevotingapp_worker. Worker Pod không có Service đi kèm, nên không cần khai port — xóa phần đó đi.
# worker-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: worker
labels:
app: worker
spec:
containers:
- name: worker
image: dockersamples/examplevotingapp_worker
Sau khi có đủ pod spec cho các thành phần, cần tạo Service để cho phép giao tiếp giữa các thành phần.
Service cho Redis — tạo file redis-service.yaml: apiVersion: v1, kind: Service, metadata.name: redis (quan trọng — vì khi tạo Service, nó sẽ có 1 IP address, và ứng dụng cũng có thể trỏ tới Service qua IP đó, nhưng quan trọng hơn, Service còn tạo 1 DNS entry, nên thay vì phải nhớ IP, có thể gửi request tới domain redis — hostname đó; ứng dụng đã được cấu hình sẵn để tới Redis database qua domain redis, nên tên Service phải là redis). Có thể thêm labels (tùy chọn, không bắt buộc) — set là redis. Dưới spec, cần chỉ định loại (type) Service — có 3 loại: ClusterIP, NodePort, load balancer. Redis database không nên expose ra bên ngoài — chỉ các thành phần nội bộ (đã có sẵn trong Kubernetes cluster) mới cần truy cập được, không phải bên ngoài — nên dùng loại ClusterIP. Tuy nhiên đây là type mặc định, nên thực ra có thể xóa field type này đi, nó vẫn tự mặc định là ClusterIP.
Tiếp theo khai ports — đặt tên cho port, gọi là redis-service. Cần cung cấp 2 giá trị, port và targetPort — targetPort là port sẽ forward request tới trên container, dựa theo port mà Redis Pod đang lắng nghe, tức 6379 — copy giá trị đó và set targetPort là 6379. Port mà service lắng nghe, nói chung nên giữ giống targetPort cho đơn giản — cũng set là 6379. Cuối cùng cần khai selector — nói cho Service biết nên forward traffic tới Pod nào — lấy label của Redis Pod (app: redis), paste vào Service — nghĩa là bất kỳ request nào tới Service sẽ được forward tới bất kỳ Pod nào mang label app: redis.
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
spec:
type: ClusterIP
ports:
- name: redis-service
port: 6379
targetPort: 6379
selector:
app: redis
Service cho PostgreSQL — tạo file db-service.yaml, copy Redis service và đổi giá trị tương ứng: tên Service quan trọng vì tự động set DNS entry — ứng dụng đã cấu hình sẵn để tới database qua hostname db, nên Service phải đặt tên db, label cũng đổi thành db. Type cũng chỉ cần internal, tương tự Redis database — có thể set ClusterIP (tùy chọn vì là type mặc định, nhưng vẫn giữ tường minh trong file). targetPort là port database lắng nghe, 5432, port cũng set giống, 5432. Cuối cùng khai selector trỏ tới label của database Pod.
# db-service.yaml
apiVersion: v1
kind: Service
metadata:
name: db
labels:
app: db
spec:
type: ClusterIP
ports:
- name: db-service
port: 5432
targetPort: 5432
selector:
app: db
Service cho voting app — copy DB service, đổi giá trị: tên Service là vote, label cũng vote. Về type — vote service cần truy cập được từ bên ngoài Kubernetes cluster (đây gần như là entry point vào ứng dụng) — ClusterIP không cung cấp khả năng đó, nên cần dùng NodePort hoặc load balancer — demo này dùng NodePort. Đặt tên port là vote-service. targetPort — xem lại voting app, nó lắng nghe trên port 80, nên set targetPort: 80. Và để minh họa rằng port của Service không nhất thiết phải giống targetPort, đổi port (port của Service) thành 8080 — nghĩa là bất kỳ ai muốn nói chuyện với service sẽ gửi request trên port 8080 tới Service IP, rồi được forward sang port 80 trên container. Vì dùng type NodePort, cần thêm 1 field nữa là nodePort — nói cho các node trong Kubernetes cluster biết nên lắng nghe trên port nào để forward traffic tới vote service. Port này phải nằm trong dải hợp lệ, ở đây set là 31000 — khi user gửi request tới IP address của 1 node, gửi tới port này, nó sẽ được forward tới vote Service, rồi vote Service forward tiếp tới vote Pod. Cuối cùng cần selector trỏ tới app: vote.
# vote-service.yaml
apiVersion: v1
kind: Service
metadata:
name: vote
labels:
app: vote
spec:
type: NodePort
ports:
- name: vote-service
port: 8080
targetPort: 80
nodePort: 31000
selector:
app: vote
Service cho result app — copy vote service, đổi tên thành result, label cũng result. Vì result service cũng cần expose ra bên ngoài, cũng cần loại NodePort hoặc load balancer — demo dùng NodePort. Đặt tên port là result-service, dùng nodePort khác — 31001. targetPort là port result app lắng nghe, cũng là 80. Có thể đổi port (port của Service) thành giá trị khác nữa, ví dụ 8081 (thay vì giữ giống targetPort 80, hoặc giống 8080 như vote service). Cuối cùng đổi selector trỏ tới result.
# result-service.yaml
apiVersion: v1
kind: Service
metadata:
name: result
labels:
app: result
spec:
type: NodePort
ports:
- name: result-service
port: 8081
targetPort: 80
nodePort: 31001
selector:
app: result
Với đầy đủ specification và manifest, giờ deploy lên cluster. Xác nhận trạng thái ban đầu:
kubectl get nodes
# Output minh họa
NAME STATUS ROLES AGE VERSION
master Ready control-plane 10d v1.30.0
kubectl get pods
# Output minh họa
No resources found in default namespace.
kubectl get service
# Output minh họa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10d
Cluster này là single-node, chưa có Pod nào, và kubectl get service chỉ thấy Kubernetes service mặc định — xác nhận chưa deploy gì cả. Giờ deploy toàn bộ, cách nhanh nhất là trỏ -f vào thư mục hiện tại (thay vì phải deploy thủ công từng file một, ví dụ pod.yaml, db-service.yaml,...):
kubectl apply -f .
# Output minh họa
pod/vote created
pod/result created
pod/redis created
pod/db created
pod/worker created
service/redis created
service/db created
service/vote created
service/result created
Kiểm tra lại những gì đã tạo:
kubectl get pods
# Output minh họa
NAME READY STATUS RESTARTS AGE
vote 1/1 Running 0 8s
result 1/1 Running 0 8s
redis 1/1 Running 0 8s
db 1/1 Running 0 8s
worker 1/1 Running 0 8s
Đủ 5 Pod — vote, result, redis, db, worker — đều 1/1 và Running.
kubectl get service
# Output minh họa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis ClusterIP 10.101.32.10 <none> 6379/TCP 12s
db ClusterIP 10.104.55.21 <none> 5432/TCP 12s
vote NodePort 10.107.12.44 <none> 8080:31000/TCP 12s
result NodePort 10.99.201.6 <none> 8081:31001/TCP 12s
(có thể gõ tắt svc thay vì service) — cột PORT(S) cho vote/result hiện đúng cặp port:nodePort (8080:31000, 8081:31001) đã khai trong file cấu hình, còn redis/db là ClusterIP — chỉ truy cập được nội bộ, không có EXTERNAL-IP.
Giờ thử truy cập ứng dụng thật:
kubectl get nodes -o wide
# Output minh họa
NAME STATUS ROLES AGE VERSION INTERNAL-IP
master Ready control-plane 10d v1.30.0 192.168.1.2
Cờ -o wide cho ra IP address của node (192.168.1.2) — copy IP đó, mở trình duyệt, dán IP vào, rồi truy cập vote service bằng cách thêm đúng nodePort (31000) vào sau IP — sẽ thấy giao diện vote service, có thể chọn cats và cast vote thành công. Truy cập result service ở nodePort 31001 — thấy kết quả cats 100%, dogs 0%, xác nhận truy cập được các NodePort service, các ứng dụng đang giao tiếp đúng với nhau, lưu và truy xuất dữ liệu đúng vào database — mọi service đã hoạt động và deploy thành công lên Kubernetes cluster.
5. Demo: Chuyển từ Pod sang Deployment
Demo trước đã đi qua các bước cần thiết để deploy đúng ứng dụng voting lên Kubernetes cluster — deploy toàn bộ Pod specification cũng như specification cho các Service khác nhau, để chúng giao tiếp được với nhau, và expose voting app cùng result app ra bên ngoài bằng NodePort service.
Vì deploy Pod trực tiếp, cần biết có 1 số hạn chế khi deploy Pod trực tiếp:
- Không dễ scale. Không có cách dễ dàng để scale số lượng Pod lên/xuống — ví dụ muốn có 5 instance voting app thay vì 1, không có cơ chế để scale up/down dễ dàng.
- Không tự phục hồi. Nếu 1 trong các ứng dụng bị down, không có gì theo dõi để restart nó hay deploy 1 instance mới — không có chức năng đó chỉ với Pod thuần.
- Không dễ upgrade. Không có cách đơn giản để upgrade ứng dụng — muốn dùng version 2 của voting/result app, phải xóa Pod cũ, deploy 1 pod spec hoàn toàn mới với version mới — điều này có thể gây ảnh hưởng tới user, vì service sẽ down trong khoảng thời gian đó.
Giải pháp: thay vì deploy Pod trực tiếp, dùng các Kubernetes primitive khác như ReplicaSet và Deployment — cụ thể tập trung dùng Deployment, vì Deployment giải quyết được nhiều vấn đề trên. Thay vì deploy mỗi thành phần dưới dạng Pod, deploy nó dưới dạng Deployment — mang lại nhiều lợi ích: dễ scale up/down; có khả năng theo dõi (monitor/watch) Pod — nếu 1 Pod down, Deployment sẽ tạo Pod mới; dễ deploy version mới của ứng dụng nhờ có sẵn chức năng rolling update, nên user không bị ảnh hưởng. Dùng Deployment vì Deployment thực chất dùng ReplicaSet bên dưới — Deployment là 1 lớp abstraction (trừu tượng hóa) trên cả Pod lẫn ReplicaSet.
Bước 1 — Xóa mọi thứ đã deploy:
kubectl delete -f .
# Output minh họa
pod "vote" deleted
pod "result" deleted
pod "redis" deleted
pod "db" deleted
pod "worker" deleted
service "redis" deleted
service "db" deleted
service "vote" deleted
service "result" deleted
Bước 2 — Tổ chức lại thư mục: di chuyển các file pod configuration (cho demo Pod) vào 1 thư mục riêng, tạo 1 thư mục mới riêng cho cấu hình Deployment mới dùng Kubernetes Deployment.
Bước 3 — Tạo file Deployment, mở song song (split-screen) với file Pod spec tương ứng để dễ copy.
vote-deployment.yaml:
apiVersion: apps/v1,kind: Deployment.metadata—name: vote, có thể thêmlabels(tùy chọn, không bắt buộc thay đổi gì nhiều).spec.template— báo cho Deployment biết cách tạo Pod: copy toàn bộ phần từmetadatatrở xuống của file voting app Pod, dán vào dướitemplate.spec.replicas— số instance voting app muốn có, set là1.spec.selector.matchLabels— báo cho Deployment biết sau khi tạo Pod theo template, cần theo dõi Pod nào — dùngmatchLabels, chỉ định đúng label đã gắn cho Pod (app: vote) — nghĩa là bất cứ khi nào thấy Pod mang labelapp: vote, Deployment phải theo dõi nó, đảm bảo có đúng 1 và chỉ 1 Pod như vậy, không nhiều hơn không ít hơn.
# vote-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vote
spec:
replicas: 1
selector:
matchLabels:
app: vote
template:
metadata:
labels:
app: vote
spec:
containers:
- name: vote
image: dockersamples/examplevotingapp_vote
ports:
- containerPort: 80
name: vote
result-deployment.yaml: copy vote deployment, đổi tên thành result, đổi label, xóa và copy lại template từ result app Pod, sửa lại phần spec, và cuối cùng đổi selector (label vote trước đó) thành result.
# result-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: result
spec:
replicas: 1
selector:
matchLabels:
app: result
template:
metadata:
labels:
app: result
spec:
containers:
- name: result
image: dockersamples/example-voting-app-result
ports:
- containerPort: 80
name: result
redis-deployment.yaml: copy result deployment, đổi tên thành Redis, đổi label thành Redis, cập nhật template, và đảm bảo selector trỏ đúng label Redis.
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:alpine
ports:
- containerPort: 6379
name: redis
db-deployment.yaml: copy config Redis, đổi tên thành db, đổi label Deployment thành db, đổi template thành DB, và đảm bảo cập nhật selector.
# db-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_USER
value: postgres
- name: POSTGRES_PASSWORD
value: postgres
worker-deployment.yaml: copy Redis deployment, đổi giá trị: tên là worker, label cũng worker, template đổi tương ứng, và đảm bảo cập nhật selector thành worker.
# worker-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 1
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: worker
image: dockersamples/examplevotingapp_worker
Về Service — không cần đổi gì cả. Miễn label khớp đúng, Service hoạt động y hệt dù chuyển từ Pod sang Deployment — chỉ cần copy nguyên các Service đã có và dán lại.
Deploy và kiểm tra: vào thư mục deployments, xác nhận chưa có gì được deploy, rồi deploy toàn bộ thư mục. Sau khi deploy:
kubectl get services
# Output minh họa
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis ClusterIP 10.101.32.11 <none> 6379/TCP 5s
db ClusterIP 10.104.55.22 <none> 5432/TCP 5s
vote NodePort 10.107.12.45 <none> 8080:31000/TCP 5s
result NodePort 10.99.201.7 <none> 8081:31001/TCP 5s
Vẫn là những Service y hệt đã thấy trước đó. Phần thú vị hơn:
kubectl get deployments
# Output minh họa
NAME READY UP-TO-DATE AVAILABLE AGE
vote 1/1 1 1 10s
result 1/1 1 1 10s
redis 1/1 1 1 10s
db 1/1 1 1 10s
worker 1/1 1 1 10s
1 Deployment cho mỗi ứng dụng — mỗi Deployment hiện 1 of 1 ready, 1 up-to-date, 1 available — đúng như mong muốn: mọi thứ ready, up to date (đang chạy đúng version mới nhất), và available (sẵn sàng xử lý traffic).
Bên dưới, Deployment dùng ReplicaSet:
kubectl get replicaset
# Output minh họa
NAME DESIRED CURRENT READY AGE
vote-7d9f8c6b57 1 1 1 12s
result-6f8d9c7b5 1 1 1 12s
redis-58b6c9d4f8 1 1 1 12s
db-b7c8d9e6f5 1 1 1 12s
worker-5c6d7e8f9 1 1 1 12s
1 ReplicaSet cho mỗi Deployment, hiện các cột DESIRED, CURRENT, READY.
kubectl get all
# Output minh họa (rút gọn)
NAME READY STATUS RESTARTS AGE
pod/vote-7d9f8c6b57-a1b2c 1/1 Running 0 15s
pod/result-6f8d9c7b5-d3e4f 1/1 Running 0 15s
pod/redis-58b6c9d4f8-g5h6i 1/1 Running 0 15s
pod/db-b7c8d9e6f5-j7k8l 1/1 Running 0 15s
pod/worker-5c6d7e8f9-m9n0o 1/1 Running 0 15s
NAME TYPE CLUSTER-IP PORT(S)
service/redis ClusterIP 10.101.32.11 6379/TCP
service/db ClusterIP 10.104.55.22 5432/TCP
service/vote NodePort 10.107.12.45 8080:31000/TCP
service/result NodePort 10.99.201.7 8081:31001/TCP
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/vote 1/1 1 1
deployment.apps/result 1/1 1 1
deployment.apps/redis 1/1 1 1
deployment.apps/db 1/1 1 1
deployment.apps/worker 1/1 1 1
Lệnh này hiện toàn bộ — Pod, Service, Deployment, và ReplicaSet — trong 1 lần chạy duy nhất.
Truy cập ứng dụng:
kubectl get node -o wide
# Output minh họa
NAME STATUS ROLES AGE VERSION INTERNAL-IP
master Ready control-plane 10d v1.30.0 192.168.1.2
Lấy IP address của node, truy cập vote service qua IP đó. Ứng dụng còn hiển thị luôn container nào đang xử lý request — ví dụ tên Pod kết thúc bằng nfw9f:
kubectl get pod
# Output minh họa
NAME READY STATUS RESTARTS AGE
vote-7d9f8c6b57-nfw9f 1/1 Running 0 2m
Xác nhận đúng Pod nfw9f đang xử lý request, nên có thể vote được. Kiểm tra result service — kết quả hiển thị "100% for Python".
Scale ứng dụng: giả sử muốn vote service có 5 instance thay vì 1:
kubectl scale deployment vote --replicas=5
# Output minh họa
deployment.apps/vote scaled
Cung cấp tên Deployment cần scale (vote), thêm --replicas= kèm số lượng mong muốn — set 5 sẽ tạo 5 Pod cho vote Deployment.
kubectl get deployment
# Output minh họa
NAME READY UP-TO-DATE AVAILABLE AGE
vote 4/5 5 4 8s
Tại thời điểm này, 4 of 5 ready, 5 up-to-date, 4 available — chạy lại lệnh:
kubectl get deployment
# Output minh họa
NAME READY UP-TO-DATE AVAILABLE AGE
vote 5/5 5 5 12s
Đủ cả 5 đã được nâng cấp lên đúng cấu hình mới nhất.
kubectl get pod
# Output minh họa
NAME READY STATUS RESTARTS AGE
vote-7d9f8c6b57-a1b2c 1/1 Running 0 3m
vote-7d9f8c6b57-d3e4f 1/1 Running 0 12s
vote-7d9f8c6b57-g5h6i 1/1 Running 0 12s
vote-7d9f8c6b57-gbbv9 1/1 Running 0 12s
vote-7d9f8c6b57-nfw9f 1/1 Running 0 3m
Xác nhận có 5 Pod vote, mỗi Pod mang 1 tên riêng biệt. Quay lại ứng dụng, mỗi lần refresh sẽ thấy 1 container khác xử lý request — ví dụ lần này là Pod gbbv9. Đó là minh chứng scale ứng dụng dễ dàng thế nào khi dùng Deployment.
Nguồn tham khảo
Nguồn gốc: Khóa "Kubernetes for the Absolute Beginners - Hands-on Tutorial" — module "Microservices Architecture", bài "Microservices Architecture", "Deploying the Voting Application on Docker", "Deploy the Voting Application on Kubernetes", "Demo - Deploying the Voting Application using Pods", và "Demo - Migrating from Pods to Deployments", nền tảng KodeKloud.
Đính chính/cập nhật (fact-check):
docker run --linkvẫn hoạt động nhưng đã deprecated từ Docker v1.10; từ Docker 29.6 có hẳn deprecation warning khi dùng trên default bridge network; mục tiêu gỡ bỏ hoàn toàn là Docker v30.0 — thay thế khuyến nghị là user-defined network (có DNS resolution tích hợp sẵn) — 2026, moby/moby PR #47427, Docker Docs: Deprecated Engine features