2.4. Core Concepts - Services and Namespaces
Mục lục
- 1. Kubernetes Services là gì
- 2. NodePort Service
- 3. ClusterIP Service
- 4. LoadBalancer Service
- 5. Lab: giải bài thực hành về Services
- 6. Namespaces
- 7. Lab: giải bài thực hành về Namespaces
1. Kubernetes Services là gì
Kubernetes services giúp kích hoạt giao tiếp giữa các thành phần khác nhau, bên trong và bên ngoài ứng dụng. Kubernetes services giúp kết nối các ứng dụng lại với nhau, hoặc kết nối với người dùng. Ví dụ, ứng dụng có các nhóm Pod chạy các phần khác nhau, như một nhóm phục vụ tải front-end cho người dùng, một nhóm khác chạy các tiến trình back-end, và nhóm thứ ba kết nối tới một nguồn dữ liệu bên ngoài. Chính Services giúp kích hoạt kết nối giữa các nhóm Pod này. Services giúp ứng dụng front-end trở nên khả dụng cho người dùng cuối, giúp giao tiếp giữa Pod back-end và front-end, và giúp thiết lập kết nối tới nguồn dữ liệu bên ngoài. Nhờ vậy, services tạo ra sự kết nối lỏng lẻo (loose coupling) giữa các microservice trong ứng dụng.
Giao tiếp ra bên ngoài
Xét một use case của Services: đã bàn về việc Pod giao tiếp với nhau qua networking nội bộ. Giờ xem một khía cạnh khác — giao tiếp bên ngoài. Đã deploy một Pod chạy web application. Làm sao, với vai trò người dùng bên ngoài, truy cập trang web đó?
Trước tiên, xem thiết lập hiện có: Kubernetes node có một IP, ví dụ 192.168.1.2. Laptop cũng nằm trên cùng mạng, có IP 192.168.1.10. Mạng Pod nội bộ nằm trong dải 10.244.0.0, và Pod có IP 10.244.0.2. Rõ ràng không thể ping hay truy cập trực tiếp Pod ở địa chỉ 10.244.0.2 vì nó nằm trên một mạng riêng biệt. Vậy có những lựa chọn nào để xem trang web?
Trước tiên, nếu SSH vào Kubernetes node tại 192.168.1.2, từ node, có thể truy cập trang web của Pod bằng cách curl. Hoặc nếu node có GUI, có thể mở trình duyệt và xem trang web tại địa chỉ http://10.244.0.2. Nhưng đây là truy cập từ bên trong Kubernetes node — không phải điều thực sự muốn. Muốn có khả năng truy cập web server từ chính laptop, mà không cần SSH vào node, chỉ bằng cách truy cập IP của Kubernetes node.
Vì vậy cần một thứ ở giữa, giúp map request từ laptop, qua node, tới Pod đang chạy web container. Đây là lúc Kubernetes Service vào cuộc. Kubernetes Service là một object, giống như Pods, ReplicaSets, hay Deployments đã làm việc trước đó. Một trong các use case của nó là lắng nghe trên một port của node và forward request trên port đó tới một port trên Pod đang chạy web application. Loại service này gọi là NodePort Service, vì service lắng nghe trên một port của node và forward request tới các Pod.
Các loại Service
Loại thứ nhất là NodePort, vừa bàn ở trên, nơi service làm cho một port nội bộ truy cập được trên một port của node.
Loại thứ hai là ClusterIP. Trong trường hợp này, service tạo một virtual IP bên trong cluster để kích hoạt giao tiếp giữa các service khác nhau, ví dụ giữa một tập front-end server và một tập back-end server.
Loại thứ ba là LoadBalancer, nơi nó cấp phát (provision) một load balancer cho ứng dụng trên các nhà cung cấp cloud được hỗ trợ. Một ví dụ tốt là phân phối tải giữa các web server khác nhau ở tầng front-end.
2. NodePort Service
Quay lại NodePort: vài slide trước đã bàn về việc truy cập bên ngoài vào ứng dụng. Đã nói một Service có thể giúp bằng cách map một port trên node tới một port trên Pod. Nhìn kỹ hơn vào Service: có 3 port liên quan.
Port trên Pod, nơi web server thực sự chạy, là 80, và nó được gọi là targetPort, vì đó là nơi service forward request tới. Port thứ hai là port trên chính service — chỉ đơn giản gọi là port. (Ghi nhớ, các thuật ngữ này đều nhìn từ góc độ của Service.) Service thực chất giống một virtual server bên trong cluster — bên trong cluster, nó có địa chỉ IP riêng, và địa chỉ IP đó gọi là ClusterIP của Service. Cuối cùng, có port trên chính node, dùng để truy cập web server từ bên ngoài — gọi là NodePort. Trong ví dụ này, nó được set là 30008. Đó là vì NodePort chỉ có thể nằm trong một dải hợp lệ, mặc định từ 30000 đến 32767.
Tạo Service
Cũng giống như cách đã tạo Deployment, ReplicaSet, hay Pod trước đó, dùng một definition file để tạo Service. Cấu trúc cấp cao của file vẫn giữ nguyên như trước: có apiVersion, kind, metadata, và spec.
apiVersion sẽ là v1, kind tất nhiên là Service. metadata sẽ có name — tên của Service. Nó có thể có label, nhưng chưa cần dùng ở đây. Tiếp theo là spec — như luôn luôn, đây là phần quan trọng nhất của file, vì đây là nơi định nghĩa thực sự cho Service, và đây là phần khác nhau giữa các loại object.
Trong phần spec của Service, có type và ports. type là loại service đang tạo — như đã bàn, có thể là ClusterIP, NodePort, hoặc LoadBalancer. Trong trường hợp này, vì đang tạo một NodePort, set là NodePort.
Phần tiếp theo của spec là ports. Đây là nơi cung cấp thông tin đã bàn ở trên. Loại port đầu tiên là targetPort, set là 80. Cái tiếp theo đơn giản là port, một port trên đối tượng Service, cũng set là 80. Thứ ba là nodePort, set là 30008 hoặc bất kỳ số nào trong dải hợp lệ.
Lưu ý, trong các field này, chỉ có port là field bắt buộc. Nếu không cung cấp targetPort, nó được giả định giống với port. Nếu không cung cấp nodePort, một port trống trong dải hợp lệ từ 30000 đến 32767 sẽ được tự động cấp phát. Cũng lưu ý ports là một array — chú ý dấu gạch ngang dưới phần ports chỉ ra phần tử đầu tiên. Về lý thuyết, có thể có nhiều port mapping như vậy trong cùng một service.
Vậy đã có đủ thông tin, nhưng còn thiếu một thứ quan trọng: chưa có gì trong definition file kết nối Service với Pod. Mới chỉ chỉ định targetPort, nhưng chưa nhắc tới Pod đích — có thể có hàng trăm Pod khác chạy web service trên port 80. Vậy làm sao làm điều đó?
Như đã làm với ReplicaSet trước đây — một kỹ thuật sẽ thấy rất thường xuyên trong Kubernetes — dùng label và selector để liên kết chúng lại với nhau. Đã biết Pod được tạo với một label — cần đưa label đó vào trong Service definition file. Có một property mới trong phần spec, gọi là selector. Dưới selector, cung cấp danh sách label để xác định Pod. Tham khảo pod definition file dùng để tạo Pod, lấy label từ đó và đặt dưới phần selector. Việc này liên kết Service với Pod.
Xong, tạo service bằng lệnh kubectl create, input service definition file:
kubectl create -f service-definition.yaml
Và service đã được tạo. Để xem service vừa tạo:
kubectl get services
Lệnh này liệt kê service, ClusterIP, và các port được map. type là NodePort như đã tạo, và port trên node được set là 30008, vì đó là port chỉ định trong definition file. Giờ có thể dùng port này để truy cập web service, qua curl hay trình duyệt.
Ví dụ, curl tới 192.168.1.2 — IP của node — trên port 30008 để truy cập web server.
Service với nhiều Pod
Tới đây mới bàn về Service map tới một Pod duy nhất. Nhưng không phải lúc nào cũng vậy. Trong môi trường production, thường có nhiều instance của web application chạy để có tính sẵn sàng cao và cân bằng tải. Trong trường hợp này, có nhiều Pod tương tự chạy web application. Tất cả đều có cùng label — key app, giá trị my-app. Cùng label này được dùng làm selector khi tạo Service.
Vậy khi Service được tạo, nó tìm các Pod khớp với label và tìm thấy 3 Pod. Service sau đó tự động chọn cả 3 Pod làm endpoint để forward request bên ngoài từ người dùng — không cần thực hiện thêm cấu hình gì để đạt được điều này. Nếu tò mò về thuật toán nó dùng để cân bằng tải giữa 3 Pod khác nhau — nó dùng một thuật toán ngẫu nhiên (random algorithm). Nhờ vậy, Service hoạt động như một built-in load balancer để phân phối tải giữa các Pod khác nhau.
Service khi Pod trải trên nhiều node
Cuối cùng, xem điều gì xảy ra khi các Pod trải trên nhiều node. Trong trường hợp này, có web application trên các Pod nằm ở các node riêng biệt trong cluster. Khi tạo một service, không cần thực hiện thêm cấu hình gì, Kubernetes tự động tạo một service trải rộng trên toàn bộ node trong cluster, và map target port tới cùng một NodePort trên mọi node trong cluster. Nhờ vậy, có thể truy cập ứng dụng bằng IP của bất kỳ node nào trong cluster, dùng cùng một số port — trong trường hợp này là 30008.
Tóm lại, dù là một Pod duy nhất trên một node duy nhất, nhiều Pod trên một node duy nhất, hay nhiều Pod trên nhiều node, Service được tạo theo hệt cùng một cách mà không cần thêm bước nào khi tạo Service. Khi Pod bị xóa hoặc thêm vào, Service tự động được cập nhật, giúp nó rất linh hoạt và thích ứng. Một khi đã tạo, thường sẽ không cần thực hiện thêm thay đổi cấu hình nào nữa.
3. ClusterIP Service
Một ứng dụng web full-stack thường có nhiều loại Pod host các phần khác nhau của ứng dụng. Có thể có một tập Pod chạy front-end web server, một tập Pod khác chạy back-end server, một tập Pod chạy key-value store như Redis, và một tập Pod khác chạy database bền vững (persistent database) như MySQL. Web front-end server cần giao tiếp với back-end server, và back-end server cần giao tiếp với database cũng như service Redis, v.v. Vậy đâu là cách đúng để thiết lập kết nối giữa các service hay tầng khác nhau của ứng dụng?
Tất cả các Pod đều được gán một địa chỉ IP. Nhưng các IP này, như đã biết, không cố định. Các Pod có thể sập bất cứ lúc nào, và Pod mới liên tục được tạo. Vì vậy không thể dựa vào các địa chỉ IP này để giao tiếp nội bộ giữa các thành phần ứng dụng. Ngoài ra, nếu Pod front-end đầu tiên tại 10.244.0.3 cần kết nối tới một back-end service, nó sẽ chọn cái nào trong 3 cái? Và ai quyết định việc đó?
Một Kubernetes Service có thể giúp group các Pod lại với nhau và cung cấp một interface duy nhất để truy cập các Pod trong một nhóm. Ví dụ, một service được tạo cho các Pod back-end sẽ giúp group toàn bộ Pod back-end lại, và cung cấp một interface duy nhất để các Pod khác truy cập service. Các request được forward tới một trong các Pod dưới service đó một cách ngẫu nhiên. Tương tự, tạo thêm các service cho Redis, và cho phép Pod back-end truy cập hệ thống Redis thông qua service. Nhờ vậy, có thể dễ dàng và hiệu quả deploy một ứng dụng theo kiến trúc microservices trên Kubernetes cluster — mỗi tầng giờ có thể scale hoặc di chuyển tùy ý mà không ảnh hưởng giao tiếp giữa các service khác nhau.
Mỗi service được gán một IP và tên bên trong cluster, và đó là tên các Pod khác dùng để truy cập service. Loại service này gọi là ClusterIP.
Tạo ClusterIP Service
Để tạo loại service này, như luôn luôn, dùng một definition file. Trong service definition file, dùng template mặc định có apiVersion, kind, metadata, và spec. apiVersion là v1, kind là Service, và đặt tên cho service — trong trường hợp này gọi là back-end. Dưới đặc tả (specification), có type và ports; type là ClusterIP. Thực tế, ClusterIP là type mặc định — dù không chỉ định, nó tự động được coi là type ClusterIP.
Dưới ports, có targetPort và port. targetPort là port nơi back-end được expose, trong trường hợp này là 80, và port là nơi service được expose, cũng là 80. Để liên kết service với một tập Pod, dùng selector. Tham khảo pod definition file và copy label từ đó, đặt dưới selector. Vậy là xong.
Giờ có thể tạo service bằng lệnh kubectl create, rồi kiểm tra trạng thái bằng lệnh kubectl get services:
kubectl create -f service-definition.yaml
kubectl get services
Service có thể được các Pod khác truy cập bằng ClusterIP hoặc tên của service.
4. LoadBalancer Service
Giờ xem một loại service khác gọi là LoadBalancer. Đã thấy NodePort Service, giúp làm cho một ứng dụng hướng ra bên ngoài (external-facing) khả dụng trên một port của các worker node.
Xét các ứng dụng front-end — voting app và result app. Các Pod này được host trên các worker node trong cluster — giả sử có một cluster 4 node. Để các ứng dụng này truy cập được bởi người dùng bên ngoài, tạo các Service loại NodePort.
Các service loại NodePort giúp nhận traffic trên các port của node và route traffic tới các Pod tương ứng. Nhưng nên đưa URL nào cho end user để truy cập ứng dụng? Có thể truy cập cả 2 ứng dụng bằng IP của bất kỳ node nào và high port mà service được expose — tức là 4 tổ hợp IP-port cho voting app và 4 tổ hợp IP-port cho result app. Ngay cả khi Pod chỉ được host trên 2 trong số các node, chúng vẫn truy cập được trên IP của tất cả node trong cluster. Ví dụ, nếu Pod của voting app chỉ deploy trên các node có IP 70 và 71, chúng vẫn truy cập được trên port của mọi node trong cluster. Đó là cách một Service được cấu hình — nên sẽ phải chia sẻ các URL này cho người dùng để truy cập ứng dụng.
Nhưng đó không phải điều end user muốn. Họ cần một URL duy nhất, như example-votingapp.com hoặc example-resultapp.com, để truy cập ứng dụng. Làm sao đạt được điều đó?
Một cách là tạo một VM mới cho mục đích load balancer, và cài đặt, cấu hình một load balancer phù hợp trên đó, như HAProxy hay NGINX, v.v. Rồi cấu hình load balancer route traffic tới các node bên dưới. Việc thiết lập external load balancing như vậy, rồi duy trì và quản lý nó, có thể là một công việc khá tốn công.
Tuy nhiên, nếu đang chạy trên một cloud platform được hỗ trợ như Google Cloud, AWS, hay Azure, có thể tận dụng load balancer gốc (native) của cloud platform đó. Kubernetes hỗ trợ tích hợp với load balancer gốc của một số cloud provider nhất định, và tự cấu hình cho mình. Chỉ cần set type của service cho front-end service là LoadBalancer thay vì NodePort.
Lưu ý, cách này chỉ hoạt động với các cloud platform được hỗ trợ — GCP, AWS, và Azure chắc chắn được hỗ trợ. Nếu set type của service là LoadBalancer trong một môi trường không được hỗ trợ như VirtualBox hay môi trường khác, nó sẽ có tác dụng y hệt như set NodePort — service vẫn được expose trên một port cao (high-end port) trên các node. Nó chỉ đơn giản không thực hiện bất kỳ cấu hình external load balancer nào. Về sau, khi đi qua các demo deploy ứng dụng trên cloud platform, sẽ thấy điều này trong thực tế.
5. Lab: giải bài thực hành về Services
Câu hỏi 1 — Có bao nhiêu service tồn tại trên hệ thống?
kubectl get svc
(svc là dạng viết tắt của service.) Thấy có đúng một service — đây là service mặc định được Kubernetes tự tạo. Xóa nó.
Câu hỏi — Type của service mặc định của Kubernetes là gì?
Từ output, type là ClusterIP.
Câu hỏi — targetPort được cấu hình trên Kubernetes service là gì?
Xem chi tiết service:
kubectl describe svc kubernetes
Thấy port là 443, targetPort là 6443.
Câu hỏi — Có bao nhiêu label được cấu hình trên Kubernetes service?
Có 2 label: component: kube-apiserver và provider: kubernetes. Service này chính là API server của Kubernetes.
Câu hỏi — Có bao nhiêu endpoint được gắn trên Kubernetes service?
Xem phần Endpoints trong output — có một IP và port, tức là một endpoint.
(Giải thích thêm về Endpoints: khi tạo một Service, dùng label làm selector — Service tự động xác định các Pod có cùng label, và đó chính là các endpoint của Service. Có thể có trường hợp một Pod khác vô tình được tạo với cùng label, khiến số endpoint hiển thị nhiều hơn dự kiến — hoặc ngược lại, nếu selector đặt sai (ví dụ đặt FR thay vì FE), Service sẽ có 0 endpoint và không route được traffic tới bất kỳ Pod nào. Đây là cách để xác định nguyên nhân gốc rễ khi debug việc Service không route được traffic.)
Câu hỏi — Có bao nhiêu deployment tồn tại trên hệ thống hiện tại, trong namespace default?
Có một deployment.
Câu hỏi — Image nào được dùng để tạo Pod trong deployment?
Xem chi tiết deployment, image là kodekloud/simple-webapp-red.
Câu hỏi — Có truy cập được web app UI không?
Thử truy cập bằng tab ở trên cùng — nhận lỗi Bad Gateway. Đáp án: không, vì chưa có Service nào để truy cập ứng dụng.
Tạo Service mới để truy cập web application
Dùng file service-definition.yaml có sẵn tại root làm khung sườn cơ bản, cập nhật các chi tiết cần thiết. (Nếu không chắc cách viết từ đầu, có thể vào trang tài liệu Kubernetes, tìm "Service", và lấy một ví dụ mẫu để copy và dùng.)
Điền các giá trị:
- name:
web-app-service - type:
NodePort - targetPort:
8080 - port:
8080 - nodePort:
30080 - selector:
simple-webapp
Tạo service, kiểm tra, và thử truy cập lại — lần này thành công.
6. Namespaces
Bắt đầu với một analogy. Có 2 cậu bé cùng tên Mark. Để phân biệt chúng, gọi bằng họ của chúng — Smith và Williams. Chúng đến từ 2 nhà khác nhau, tất nhiên — nhà Smith và nhà Williams. Có các thành viên khác trong nhà. Các thành viên bên trong nhà gọi nhau đơn giản bằng tên (first name) — ví dụ người cha gọi Mark đơn giản là Mark. Tuy nhiên, nếu người cha muốn gọi tới Mark ở nhà kia, họ sẽ dùng tên đầy đủ. Ai đó bên ngoài 2 nhà này cũng sẽ dùng tên đầy đủ để nhắc tới 2 cậu bé hay bất kỳ ai trong 2 nhà.
Mỗi nhà này có bộ quy tắc riêng, định nghĩa ai được làm gì. Mỗi nhà cũng có tập tài nguyên riêng mà họ được phép tiêu thụ.
Quay lại Kubernetes: các "nhà" này tương ứng với namespace trong Kubernetes. Trong khóa học, tới lúc này đã tạo các object như Pod, Deployment, và Service trong cluster — mọi thứ đã làm đều nằm trong một namespace. Đã ở "trong một nhà" suốt thời gian qua. Namespace này gọi là namespace default, được tạo tự động bởi Kubernetes khi cluster được setup lần đầu.
Kubernetes tạo một tập Pod và Service cho mục đích nội bộ, như những gì cần cho giải pháp networking, DNS service, v.v. Để cô lập chúng khỏi người dùng và tránh việc vô tình xóa hay sửa các service này, Kubernetes tạo chúng dưới một namespace khác, được tạo lúc cluster khởi động, tên là kube-system.
Namespace thứ ba được Kubernetes tự động tạo là kube-public. Đây là nơi các resource cần được cung cấp cho tất cả người dùng được tạo.
Nếu môi trường nhỏ, hoặc đang học và thử nghiệm với một cluster nhỏ, sẽ không thực sự cần lo về namespace — có thể tiếp tục làm việc trong namespace default. Tuy nhiên, khi phát triển và dùng Kubernetes cluster cho mục đích doanh nghiệp hoặc production, có thể muốn cân nhắc dùng namespace. Cũng có thể tự tạo namespace riêng.
Ví dụ, nếu muốn dùng cùng cluster cho cả môi trường dev và production, nhưng đồng thời muốn cô lập tài nguyên giữa chúng, có thể tạo một namespace riêng cho mỗi cái. Nhờ vậy, khi làm việc trong môi trường dev, sẽ không vô tình sửa tài nguyên ở production. Mỗi namespace có thể có bộ policy riêng, định nghĩa ai được làm gì. Cũng có thể gán quota tài nguyên cho mỗi namespace — nhờ đó, mỗi namespace được đảm bảo một lượng nhất định và không dùng vượt quá giới hạn cho phép.
Giao tiếp giữa các resource trong và ngoài namespace
Quay lại namespace default đang làm việc, giống như các thành viên trong một nhà gọi nhau bằng tên đầu, các resource trong một namespace có thể tham chiếu tới nhau đơn giản bằng tên. Trong trường hợp này, Pod web app có thể reach tới service DB đơn giản bằng hostname db-service.
Khi cần, Pod web app cũng có thể reach tới một service ở namespace khác. Để làm được, phải thêm tên của namespace vào tên của service. Ví dụ, để Pod web ở namespace default kết nối tới database trong môi trường (namespace) dev, dùng định dạng tên: service-name.namespace.svc.cluster.local. Cụ thể sẽ là db-service.dev.svc.cluster.local. Có thể làm được điều này vì khi Service được tạo, một entry DNS được thêm tự động theo đúng định dạng này.
Nhìn kỹ vào tên DNS của service: phần cuối, cluster.local, là tên miền mặc định của Kubernetes cluster. svc là subdomain cho service, theo sau bởi namespace, rồi tới tên của chính service.
Các thao tác vận hành với namespace
Xem một số khía cạnh vận hành của namespace — bắt đầu với các lệnh kubectl.
Lệnh sau dùng để list tất cả các Pod:
kubectl get pods
Nhưng nó chỉ list các Pod trong namespace default. Để list Pod trong một namespace khác, dùng option --namespace kèm tên namespace, ví dụ kube-system:
kubectl get pods --namespace=kube-system
Với một pod definition file có sẵn, khi tạo Pod dùng file này, Pod được tạo trong namespace default. Để tạo Pod trong một namespace khác, dùng option --namespace:
kubectl create -f pod-definition.yaml --namespace=dev
Nếu muốn đảm bảo Pod này luôn được tạo trong môi trường dev, ngay cả khi không chỉ định namespace trên command line, có thể di chuyển định nghĩa namespace vào chính pod definition file, dưới phần metadata — đây là cách tốt để đảm bảo tài nguyên luôn được tạo trong cùng một namespace.
Vậy làm sao tạo một namespace mới? Giống như bất kỳ object nào khác, dùng một namespace definition file.
7. Lab: giải bài thực hành về Namespaces
Câu hỏi 1 — Có bao nhiêu namespace tồn tại trên hệ thống?
kubectl get namespaces
(hoặc dạng ngắn kubectl get ns). Đếm được 10 namespace.
Câu hỏi 2 — Có bao nhiêu Pod tồn tại trong namespace research?
kubectl get pods --namespace=research
Có 2 Pod. Cách khác, nếu không muốn gõ đầy đủ option, dùng dạng ngắn -n:
kubectl get pods -n research
Câu hỏi 3 — Tạo một Pod trong namespace finance theo spec cho trước
Để tạo Pod, dùng lệnh kubectl run, cung cấp tên (redis) và image. Nhưng không muốn tạo nó trong namespace default — nếu chạy lệnh bình thường, nó sẽ tạo trong default. Vì vậy dùng option --namespace và chỉ định finance:
kubectl run redis --image=redis --namespace=finance
Sau đó chạy lại lệnh get pods trong namespace finance để xác nhận Pod đã được tạo:
kubectl get pods --namespace=finance
Nguồn tham khảo
Nguồn gốc: Khóa "Certified Kubernetes Administrator (CKA)" — phần "Core Concepts" (Kubernetes Services: NodePort, ClusterIP, LoadBalancer, lab Services; Namespaces: analogy nhà Smith/Williams, default/kube-system/kube-public, DNS cross-namespace, lab Namespaces), 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.