Trong Kubernetes, Service là một cách để thể hiện ứng dụng đang chạy trong một tập các Pods dưới dạng dịch vụ mạng.
Một mục tiêu chính của Service trong Kubernetes là bạn không cần sửa đổi ứng dụng hiện có để dùng một cơ chế service discovery xa lạ. Bạn có thể chạy code trong các Pod, dù đó là code được thiết kế cho thế giới cloud-native hay một ứng dụng cũ mà bạn đã container hóa. Bạn dùng Service để cung cấp tập Pod đó trên mạng, nhờ vậy các client có thể tương tác với nó.
Nếu bạn dùng một Deployment để chạy ứng dụng, Deployment đó có thể tạo và hủy các Pod một cách linh hoạt. Từ thời điểm này sang thời điểm khác, bạn không biết có bao nhiêu Pod trong số đó đang hoạt động và khỏe mạnh; thậm chí bạn có thể không biết tên của các Pod khỏe mạnh đó. Các Pod trong Kubernetes được tạo và hủy để khớp với trạng thái mong muốn của cluster. Pod là tài nguyên tạm thời (bạn không nên kỳ vọng rằng một Pod riêng lẻ là đáng tin cậy và bền vững).
Mỗi Pod có địa chỉ IP riêng (Kubernetes yêu cầu các plugin mạng đảm bảo điều này). Với một Deployment nhất định trong cluster, tập Pod đang chạy tại một thời điểm có thể khác với tập Pod chạy ứng dụng đó ngay sau đó.
Điều này dẫn đến một vấn đề: nếu một tập Pod (gọi là "backend") cung cấp chức năng cho các Pod khác (gọi là "frontend") bên trong cluster, làm thế nào để các frontend tìm ra và theo dõi được địa chỉ IP cần kết nối tới, để frontend có thể sử dụng phần backend của workload?
Đó là lúc Service xuất hiện.
Service API, một phần của Kubernetes, là một lớp trừu tượng giúp bạn cung cấp các nhóm Pod qua mạng. Mỗi đối tượng Service định nghĩa một tập logic các endpoint (thường các endpoint này là Pod) cùng với một chính sách về cách cho phép truy cập các pod đó.
Ví dụ, hãy xem xét một backend xử lý hình ảnh stateless đang chạy với 3 bản sao. Các bản sao đó có thể thay thế cho nhau—frontend không quan tâm chúng dùng backend nào. Mặc dù các Pod thực tế tạo nên tập backend có thể thay đổi, các client frontend không cần biết điều đó, cũng không cần tự theo dõi tập backend.
Lớp trừu tượng Service cho phép sự tách rời (decoupling) này.
Canary deployment là một trường hợp sử dụng phổ biến của Service. Để tìm hiểu những gì cần làm, hãy làm theo hướng dẫn Triển khai một bản phát hành bằng Canary Deployment.
Tập Pod mà một Service nhắm tới thường được xác định bởi một selector mà bạn định nghĩa. Để tìm hiểu các cách khác để định nghĩa endpoint cho Service, xem Service không có selector.
Nếu workload của bạn giao tiếp qua HTTP, bạn có thể chọn dùng một Ingress để kiểm soát cách lưu lượng web đến được workload đó. Ingress không phải là một loại Service, nhưng nó đóng vai trò điểm vào cho cluster của bạn. Ingress cho phép bạn gom các quy tắc định tuyến vào một tài nguyên duy nhất, để bạn có thể cung cấp nhiều thành phần của workload, chạy riêng biệt trong cluster, phía sau một listener duy nhất.
API Gateway dành cho Kubernetes cung cấp thêm các khả năng ngoài Ingress và Service. Bạn có thể thêm Gateway vào cluster - đây là một họ các API mở rộng, được triển khai bằng CustomResourceDefinition - rồi dùng chúng để cấu hình quyền truy cập tới các dịch vụ mạng đang chạy trong cluster.
Nếu ứng dụng của bạn có thể dùng Kubernetes API cho service discovery, bạn có thể truy vấn API server để lấy các EndpointSlice tương ứng. Kubernetes cập nhật các EndpointSlice của một Service mỗi khi tập Pod trong Service đó thay đổi.
Với các ứng dụng không phải native, Kubernetes cung cấp các cách để đặt một cổng mạng hoặc bộ cân bằng tải (load balancer) giữa ứng dụng của bạn và các Pod backend.
Dù bằng cách nào, workload của bạn đều có thể dùng các cơ chế service discovery này để tìm đích mà nó muốn kết nối tới.
Service là một đối tượng
(giống như Pod hay ConfigMap là đối tượng). Bạn có thể tạo,
xem hoặc sửa đổi định nghĩa Service bằng Kubernetes API. Thông thường
bạn dùng một công cụ như kubectl để thực hiện các lệnh gọi API đó thay bạn.
Ví dụ, giả sử bạn có một tập Pod, mỗi Pod lắng nghe trên cổng TCP 9376
và được gắn label app.kubernetes.io/name=MyApp. Bạn có thể định nghĩa một Service để
công bố listener TCP đó:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
Áp dụng manifest này sẽ tạo một Service mới tên là "my-service" với
loại Service mặc định là ClusterIP. Service này
nhắm tới cổng TCP 9376 trên bất kỳ Pod nào có label app.kubernetes.io/name: MyApp.
Kubernetes gán cho Service này một địa chỉ IP (cluster IP), mà cơ chế địa chỉ IP ảo sử dụng. Để biết thêm chi tiết về cơ chế đó, hãy đọc IP ảo và Service Proxy.
Controller của Service đó liên tục quét tìm các Pod khớp với selector của nó, rồi thực hiện mọi cập nhật cần thiết cho tập EndpointSlice của Service.
Tên của một đối tượng Service phải là một tên label RFC 1123 hợp lệ.
port đến nào tới một targetPort. Theo mặc định và
để thuận tiện, targetPort được đặt cùng giá trị với trường
port.Định nghĩa cổng trong Pod có tên, và bạn có thể tham chiếu các tên này trong
thuộc tính targetPort của một Service. Ví dụ, ta có thể gắn targetPort
của Service với cổng của Pod theo cách sau:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app.kubernetes.io/name: proxy
ports:
- name: name-of-service-port
protocol: TCP
port: 80
targetPort: http-web-svc
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app.kubernetes.io/name: proxy
spec:
containers:
- name: nginx
image: nginx:stable
ports:
- containerPort: 80
name: http-web-svc
Cách này vẫn hoạt động ngay cả khi Service có một tập Pod hỗn hợp dùng cùng một tên đã cấu hình, với cùng một giao thức mạng nhưng ở các số cổng khác nhau. Điều này mang lại nhiều sự linh hoạt khi triển khai và phát triển các Service của bạn. Ví dụ, bạn có thể thay đổi số cổng mà các Pod cung cấp trong phiên bản tiếp theo của phần mềm backend mà không làm gián đoạn client.
Giao thức mặc định của Service là TCP; bạn cũng có thể dùng bất kỳ giao thức được hỗ trợ nào khác.
Vì nhiều Service cần cung cấp nhiều hơn một cổng, Kubernetes hỗ trợ
nhiều định nghĩa cổng cho một Service.
Mỗi định nghĩa cổng có thể có cùng protocol, hoặc khác nhau.
Service thường trừu tượng hóa việc truy cập tới các Pod Kubernetes nhờ selector, nhưng khi được dùng cùng một tập đối tượng EndpointSlice tương ứng và không có selector, Service có thể trừu tượng hóa các loại backend khác, kể cả những backend chạy bên ngoài cluster.
Ví dụ:
Trong bất kỳ trường hợp nào ở trên, bạn đều có thể định nghĩa một Service mà không chỉ định selector để khớp với Pod. Ví dụ:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
Vì Service này không có selector, các đối tượng EndpointSlice tương ứng sẽ không được tạo tự động. Bạn có thể ánh xạ Service tới địa chỉ mạng và cổng nơi nó đang chạy bằng cách thêm thủ công một đối tượng EndpointSlice. Ví dụ:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: my-service-1 # theo quy ước, dùng tên của Service
# làm tiền tố cho tên của EndpointSlice
labels:
# Bạn nên đặt label "kubernetes.io/service-name".
# Đặt giá trị của nó khớp với tên của Service
kubernetes.io/service-name: my-service
addressType: IPv4
ports:
- name: http # nên khớp với tên của cổng Service đã định nghĩa ở trên
appProtocol: http
protocol: TCP
port: 9376
endpoints:
- addresses:
- "10.4.5.6"
- addresses:
- "10.1.2.3"
Khi bạn tạo một đối tượng EndpointSlice cho một Service, bạn có thể
dùng bất kỳ tên nào cho EndpointSlice đó. Mỗi EndpointSlice trong một namespace phải có
tên duy nhất. Bạn liên kết một EndpointSlice với một Service bằng cách đặt
label kubernetes.io/service-name
trên EndpointSlice đó.
IP của endpoint không được là: loopback (127.0.0.0/8 với IPv4, ::1/128 với IPv6), hoặc link-local (169.254.0.0/16 và 224.0.0.0/24 với IPv4, fe80::/64 với IPv6).
Địa chỉ IP của endpoint không thể là cluster IP của các Service Kubernetes khác, vì kube-proxy không hỗ trợ IP ảo làm đích đến.
Với một EndpointSlice do chính bạn tạo, hoặc được tạo trong code của bạn,
bạn cũng nên chọn một giá trị cho label
endpointslice.kubernetes.io/managed-by.
Nếu bạn tự viết code controller để quản lý EndpointSlice, hãy cân nhắc dùng một
giá trị tương tự như "my-domain.example/name-of-controller". Nếu bạn dùng một công cụ
bên thứ ba, hãy dùng tên của công cụ đó viết thường toàn bộ, và thay khoảng trắng cùng các dấu
câu khác bằng dấu gạch ngang (-).
Nếu mọi người trực tiếp dùng một công cụ như kubectl để quản lý EndpointSlice,
hãy dùng một tên mô tả việc quản lý thủ công này, chẳng hạn "staff" hoặc
"cluster-admins". Bạn nên
tránh dùng giá trị dành riêng "controller", vì giá trị này xác định các EndpointSlice
do chính control plane của Kubernetes quản lý.
Việc truy cập một Service không có selector hoạt động giống như khi nó có selector. Trong ví dụ về Service không có selector, lưu lượng được định tuyến tới một trong hai endpoint được định nghĩa trong manifest EndpointSlice: một kết nối TCP tới 10.1.2.3 hoặc 10.4.5.6, trên cổng 9376.
kubectl port-forward service/<service-name> forwardedPort:servicePort khi service không có
selector sẽ thất bại do ràng buộc này. Điều này ngăn Kubernetes API server
bị dùng làm proxy tới các endpoint mà bên gọi có thể không có quyền truy cập.Service ExternalName là một trường hợp đặc biệt của Service, không có
selector và dùng tên DNS thay thế. Để biết thêm thông tin, xem
phần ExternalName.
EndpointSlice là các đối tượng đại diện cho một tập con (một slice) của các endpoint mạng đứng sau một Service.
Cluster Kubernetes của bạn theo dõi số lượng endpoint mà mỗi EndpointSlice đại diện. Nếu một Service có nhiều endpoint đến mức chạm tới một ngưỡng, thì Kubernetes sẽ thêm một EndpointSlice trống khác và lưu thông tin endpoint mới vào đó. Theo mặc định, Kubernetes tạo một EndpointSlice mới khi tất cả các EndpointSlice hiện có đều chứa ít nhất 100 endpoint. Kubernetes không tạo EndpointSlice mới cho đến khi cần thêm một endpoint nữa.
Xem EndpointSlice để biết thêm thông tin về API này.
EndpointSlice API là bước phát triển tiếp theo của API Endpoints cũ hơn. Endpoints API không còn được hỗ trợ này có một số vấn đề so với EndpointSlice:
Vì vậy, tất cả client được khuyến nghị dùng EndpointSlice API thay vì Endpoints.
Kubernetes giới hạn số lượng endpoint có thể chứa trong một đối tượng Endpoints duy nhất. Khi một Service có hơn 1000 endpoint phía sau, Kubernetes cắt bớt dữ liệu trong đối tượng Endpoints. Vì một Service có thể được liên kết với nhiều hơn một EndpointSlice, giới hạn 1000 endpoint phía sau chỉ ảnh hưởng đến Endpoints API cũ (legacy).
Trong trường hợp đó, Kubernetes chọn tối đa 1000 endpoint backend khả dĩ để lưu
vào đối tượng Endpoints, và đặt một
chú thích (annotation) trên Endpoints:
endpoints.kubernetes.io/over-capacity: truncated.
Control plane cũng gỡ bỏ chú thích đó nếu số lượng Pod backend giảm xuống dưới 1000.
Lưu lượng vẫn được gửi tới các backend, nhưng mọi cơ chế cân bằng tải dựa vào Endpoints API cũ chỉ gửi lưu lượng tới tối đa 1000 endpoint trong số các endpoint phía sau hiện có.
Cũng vì giới hạn API đó, bạn không thể cập nhật thủ công một Endpoints để có hơn 1000 endpoint.
Trường appProtocol cung cấp một cách để chỉ định giao thức ứng dụng cho
từng cổng của Service. Trường này được dùng làm gợi ý để các bản triển khai (implementation) cung cấp
hành vi phong phú hơn cho các giao thức mà chúng hiểu.
Giá trị của trường này được phản ánh sang các đối tượng
Endpoints và EndpointSlice tương ứng.
Trường này tuân theo cú pháp label chuẩn của Kubernetes. Giá trị hợp lệ là một trong các loại sau:
Tên có tiền tố do bản triển khai định nghĩa, chẳng hạn mycompany.com/my-custom-protocol.
Tên có tiền tố do Kubernetes định nghĩa:
| Giao thức | Mô tả |
|---|---|
kubernetes.io/h2c | HTTP/2 qua kết nối không mã hóa (cleartext) như mô tả trong RFC 9113 |
kubernetes.io/ws | WebSocket qua kết nối không mã hóa như mô tả trong RFC 6455 |
kubernetes.io/wss | WebSocket qua TLS như mô tả trong RFC 6455 |
Với một số Service, bạn cần cung cấp nhiều hơn một cổng. Kubernetes cho phép bạn cấu hình nhiều định nghĩa cổng trên một đối tượng Service. Khi dùng nhiều cổng cho một Service, bạn phải đặt tên cho tất cả các cổng để chúng không bị nhầm lẫn. Ví dụ:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
Cũng như tên trong Kubernetes nói chung, tên cổng
chỉ được chứa ký tự chữ và số viết thường và -. Tên cổng cũng
phải bắt đầu và kết thúc bằng một ký tự chữ hoặc số.
Ví dụ, các tên 123-abc và web là hợp lệ, nhưng 123_abc và -web thì không.
Với một số phần của ứng dụng (ví dụ, frontend), bạn có thể muốn cung cấp Service trên một địa chỉ IP bên ngoài, có thể truy cập được từ bên ngoài cluster.
Các loại Service trong Kubernetes cho phép bạn chỉ định loại Service mà bạn muốn.
Các giá trị type hiện có và hành vi của chúng là:
ClusterIPtype cho Service.
Bạn có thể cung cấp Service ra internet công cộng bằng một
Ingress hoặc một
Gateway.NodePortNodePort).
Để node port sẵn sàng, Kubernetes thiết lập một địa chỉ cluster IP,
giống như khi bạn yêu cầu một Service có type: ClusterIP.LoadBalancerExternalNameexternalName (ví dụ,
tới hostname api.foo.bar.example). Việc ánh xạ này cấu hình máy chủ DNS của cluster
trả về một bản ghi CNAME với giá trị hostname bên ngoài đó.
Không có bất kỳ hình thức proxy nào được thiết lập.Trường type trong Service API được thiết kế theo dạng chức năng lồng nhau - mỗi cấp
bổ sung thêm vào cấp trước. Tuy nhiên có một ngoại lệ đối với thiết kế lồng nhau này. Bạn có thể
định nghĩa một Service LoadBalancer bằng cách
tắt việc cấp phát NodePort cho bộ cân bằng tải.
type: ClusterIPLoại Service mặc định này gán một địa chỉ IP từ một nhóm (pool) địa chỉ IP mà cluster của bạn đã dành riêng cho mục đích đó.
Một số loại Service khác được xây dựng dựa trên loại ClusterIP làm
nền tảng.
Nếu bạn định nghĩa một Service có .spec.clusterIP được đặt thành "None" thì
Kubernetes sẽ không gán địa chỉ IP. Xem headless Service
để biết thêm thông tin.
Bạn có thể chỉ định địa chỉ cluster IP của riêng mình như một phần của yêu cầu tạo
Service. Để làm điều này, hãy đặt trường .spec.clusterIP. Ví dụ, khi bạn
đã có sẵn một bản ghi DNS muốn tái sử dụng, hoặc có các hệ thống cũ (legacy)
được cấu hình cho một địa chỉ IP cụ thể và khó cấu hình lại.
Địa chỉ IP bạn chọn phải là một địa chỉ IPv4 hoặc IPv6 hợp lệ nằm trong
dải CIDR service-cluster-ip-range được cấu hình cho API server.
Nếu bạn cố tạo một Service với giá trị địa chỉ clusterIP không hợp lệ, API
server sẽ trả về mã trạng thái HTTP 422 để báo rằng có vấn đề.
Đọc tránh xung đột để tìm hiểu cách Kubernetes giúp giảm rủi ro và tác động khi hai Service khác nhau cùng cố dùng một địa chỉ IP.
type: NodePortNếu bạn đặt trường type thành NodePort, control plane của Kubernetes
cấp phát một cổng từ dải được chỉ định bởi cờ --service-node-port-range (mặc định: 30000-32767).
Mỗi node proxy cổng đó (cùng một số cổng trên mọi Node) vào Service của bạn.
Service của bạn báo cáo cổng đã được cấp phát trong trường .spec.ports[*].nodePort.
Dùng NodePort cho bạn sự tự do trong việc thiết lập giải pháp cân bằng tải của riêng mình, cấu hình các môi trường mà Kubernetes chưa hỗ trợ đầy đủ, hoặc thậm chí cung cấp trực tiếp địa chỉ IP của một hay nhiều node.
Với một Service node port, Kubernetes cấp phát thêm một cổng (TCP, UDP hoặc
SCTP, khớp với giao thức của Service). Mọi node trong cluster tự cấu hình
để lắng nghe trên cổng được gán đó và chuyển tiếp lưu lượng tới một trong các endpoint
sẵn sàng (ready) liên kết với Service đó. Bạn có thể kết nối tới Service type: NodePort
từ bên ngoài cluster bằng cách kết nối tới bất kỳ node nào với giao thức phù hợp
(ví dụ: TCP) và cổng phù hợp (cổng đã được gán cho Service đó).
Nếu bạn muốn một số cổng cụ thể, bạn có thể chỉ định giá trị trong trường nodePort.
Control plane sẽ cấp phát cổng đó cho bạn hoặc báo rằng
giao dịch API đã thất bại.
Điều này có nghĩa là bạn cần tự xử lý các xung đột cổng có thể xảy ra.
Bạn cũng phải dùng một số cổng hợp lệ, nằm trong dải được cấu hình
cho NodePort.
Dưới đây là một manifest mẫu cho Service có type: NodePort chỉ định
một giá trị NodePort (trong ví dụ này là 30007):
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app.kubernetes.io/name: MyApp
ports:
- port: 80
# Theo mặc định và để thuận tiện, `targetPort` được đặt cùng
# giá trị với trường `port`.
targetPort: 80
# Trường tùy chọn
# Theo mặc định và để thuận tiện, control plane của Kubernetes
# sẽ cấp phát một cổng từ một dải (mặc định: 30000-32767)
nodePort: 30007
Chính sách gán cổng cho các Service NodePort áp dụng cho cả trường hợp gán tự động và gán thủ công. Khi người dùng muốn tạo một Service NodePort dùng một cổng cụ thể, cổng đích có thể xung đột với một cổng khác đã được gán.
Để tránh vấn đề này, dải cổng cho các Service NodePort được chia thành hai băng (band). Việc gán cổng động mặc định dùng băng trên, và có thể dùng băng dưới khi băng trên đã hết. Khi đó người dùng có thể cấp phát từ băng dưới với rủi ro xung đột cổng thấp hơn.
Khi dùng dải NodePort mặc định 30000-32767, các băng được phân chia như sau:
Xem Tránh xung đột khi gán cổng cho Service NodePort để biết thêm chi tiết về cách tính các băng tĩnh và động.
type: NodePortKhi dùng kube-proxy ở chế độ
iptables, theo mặc định các Service NodePort
khả dụng trên tất cả IP của node. Khi dùng chế độ
nftables, theo mặc định chúng chỉ
khả dụng trên IP chính của node (hoặc các IP chính dual-stack).
Bạn có thể thay đổi tập IP của node mà các Service NodePort khả dụng bằng
cờ --nodeport-addresses của kube-proxy, hoặc trường nodePortAddresses tương đương
trong file cấu hình kube-proxy.
Cờ này chấp nhận một danh sách các khối IP phân tách bằng dấu phẩy (ví dụ 10.0.0.0/8, 192.0.2.0/25) hoặc một
hay nhiều từ khóa sau:
primary - địa chỉ IPv4 và/hoặc IPv6 chính của node, theo đối tượng Node.
(Đây là giá trị mặc định cho chế độ nftables.)localhost - các địa chỉ loopback của node (127.0.0.0/8, ::1/128).all - tất cả địa chỉ. (Đây là giá trị mặc định cho chế độ iptables và ipvs.)Ví dụ, nếu bạn khởi động kube-proxy với cờ --nodeport-addresses=192.168.0.0/24,
thì kube-proxy sẽ cố tìm một địa chỉ IP cục bộ thuộc subnet đó trên mỗi node, và chỉ phục vụ
các Service NodePort qua IP đó.
type: NodePort qua localhostCác cơ chế mà service proxy dùng để triển khai Service NodePort không phải lúc nào cũng hỗ trợ cung cấp Service NodePort trên localhost. Với kube-proxy:
Khi dùng chế độ iptables, với giá trị --nodeport-addresses có bao gồm
127.0.0.1, các Service NodePort sẽ khả dụng trên 127.0.0.1. Tuy nhiên, điều này
yêu cầu bật một sysctl của kernel (route_localnet) có thể gây ra tác dụng phụ
thiếu an toàn trong một số cluster. Có thể tắt NodePort localhost của IPTables bằng cách truyền
--iptables-localhost-nodeports false cho kube-proxy, hoặc bằng cách đặt
--nodeport-addresses thành một dải không bao gồm 127.0.0.1.
Khi dùng chế độ ipvs, hoặc chế độ iptables trong cluster IPv6 single-stack, Service
NodePort không khả dụng trên localhost.
To use this feature, you (or a cluster administrator) will need to enable the KubeProxyNFTablesLocalhostNodePorts feature gate for all relevant components in your cluster.
See Enable Or Disable Feature Gates for more information.
nftables, Service NodePort sẽ khả dụng trên localhost khi
feature gate KubeProxyNFTablesLocalhostNodePorts được bật, và
--nodeport-addresses được đặt thành một giá trị có bao gồm rõ ràng localhost.
(Chỉ đặt thành all sẽ không bật Service NodePort trên localhost.) Điều này
được triển khai bằng cách chuyển hướng các kết nối NodePort localhost qua một proxy userspace,
nên không hiệu quả bằng việc proxy service thông thường.Các plugin mạng bên thứ ba có bản triển khai service proxy riêng có thể hỗ trợ hoặc không hỗ trợ NodePort trên localhost; hãy tham khảo tài liệu của các plugin đó.
type: LoadBalancerTrên các nhà cung cấp dịch vụ đám mây hỗ trợ bộ cân bằng tải bên ngoài, việc đặt trường type
thành LoadBalancer sẽ cấp phát một bộ cân bằng tải cho Service của bạn.
Việc tạo bộ cân bằng tải thực tế diễn ra bất đồng bộ, và
thông tin về bộ cân bằng tải được cấp phát sẽ được công bố trong trường
.status.loadBalancer của Service.
Ví dụ:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
Lưu lượng từ bộ cân bằng tải bên ngoài được chuyển tới các Pod backend. Nhà cung cấp dịch vụ đám mây quyết định cách cân bằng tải.
Để triển khai một Service type: LoadBalancer, Kubernetes thường bắt đầu
bằng cách thực hiện các thay đổi tương đương với việc bạn yêu cầu một Service
type: NodePort. Sau đó, thành phần cloud-controller-manager cấu hình bộ cân bằng tải
bên ngoài để chuyển tiếp lưu lượng tới node port đã được gán đó.
Bạn có thể cấu hình một Service có cân bằng tải để bỏ qua việc gán node port, với điều kiện bản triển khai của nhà cung cấp dịch vụ đám mây hỗ trợ điều này.
Một số nhà cung cấp dịch vụ đám mây cho phép bạn chỉ định loadBalancerIP. Trong những trường hợp đó, bộ cân bằng tải được tạo
với loadBalancerIP do người dùng chỉ định. Nếu trường loadBalancerIP không được chỉ định,
bộ cân bằng tải sẽ được thiết lập với một địa chỉ IP tạm thời. Nếu bạn chỉ định loadBalancerIP
nhưng nhà cung cấp dịch vụ đám mây của bạn không hỗ trợ tính năng này, trường loadbalancerIP mà bạn
đặt sẽ bị bỏ qua.
Trường .spec.loadBalancerIP của Service đã không còn được hỗ trợ từ Kubernetes v1.24.
Trường này được đặc tả chưa đầy đủ và ý nghĩa của nó khác nhau giữa các bản triển khai. Nó cũng không thể hỗ trợ mạng dual-stack. Trường này có thể bị loại bỏ trong một phiên bản API tương lai.
Nếu bạn đang tích hợp với một nhà cung cấp hỗ trợ chỉ định (các) địa chỉ IP của bộ cân bằng tải cho Service thông qua một chú thích (riêng của nhà cung cấp), bạn nên chuyển sang cách đó.
Nếu bạn đang viết code tích hợp bộ cân bằng tải với Kubernetes, hãy tránh dùng trường này. Bạn có thể tích hợp với Gateway thay vì Service, hoặc bạn có thể tự định nghĩa các chú thích (riêng của nhà cung cấp) trên Service để chỉ định thông tin tương đương.
Kiểm tra sức khỏe (health check) của bộ cân bằng tải đóng vai trò then chốt đối với các ứng dụng hiện đại. Chúng được dùng để
xác định máy chủ nào (máy ảo, hoặc địa chỉ IP) mà bộ cân bằng tải nên
điều phối lưu lượng tới. Kubernetes API không định nghĩa cách triển khai kiểm tra sức khỏe
cho các bộ cân bằng tải do Kubernetes quản lý, thay vào đó các nhà cung cấp dịch vụ đám mây
(và những người viết code tích hợp) sẽ quyết định hành vi. Kiểm tra sức khỏe
của bộ cân bằng tải được sử dụng rộng rãi trong bối cảnh hỗ trợ trường
externalTrafficPolicy của Service.
This is a stable feature in Kubernetes, and has been since the 1.26 release. You can no longer toggle this feature (the associated feature gate has been removed).
Theo mặc định, với các Service loại LoadBalancer, khi có nhiều hơn một cổng được định nghĩa, tất cả các cổng phải có cùng giao thức, và giao thức đó phải được nhà cung cấp dịch vụ đám mây hỗ trợ.
Feature gate MixedProtocolLBService (được bật mặc định cho kube-apiserver kể từ v1.24) cho phép dùng
các giao thức khác nhau cho các Service loại LoadBalancer khi có nhiều hơn một cổng được định nghĩa.
Bạn có thể tùy chọn tắt việc cấp phát node port cho Service type: LoadBalancer bằng cách đặt
trường spec.allocateLoadBalancerNodePorts thành false. Chỉ nên dùng cách này cho các bản triển khai bộ cân bằng tải
định tuyến lưu lượng trực tiếp tới pod thay vì dùng node port. Theo mặc định, spec.allocateLoadBalancerNodePorts
là true và các Service loại LoadBalancer sẽ tiếp tục cấp phát node port. Nếu spec.allocateLoadBalancerNodePorts
được đặt thành false trên một Service hiện có đã được cấp phát node port, các node port đó sẽ không được tự động thu hồi.
Bạn phải xóa rõ ràng mục nodePorts trong từng cổng của Service để thu hồi các node port đó.
Với một Service có type được đặt thành LoadBalancer, trường .spec.loadBalancerClass
cho phép bạn dùng một bản triển khai bộ cân bằng tải khác với mặc định của nhà cung cấp dịch vụ đám mây.
Theo mặc định, .spec.loadBalancerClass không được đặt và Service loại LoadBalancer
sẽ dùng bản triển khai bộ cân bằng tải mặc định của nhà cung cấp dịch vụ đám mây nếu
cluster được cấu hình với một nhà cung cấp dịch vụ đám mây thông qua cờ
--cloud-provider của thành phần.
Nếu bạn chỉ định .spec.loadBalancerClass, hệ thống giả định rằng có một bản triển khai bộ cân bằng tải
khớp với class đã chỉ định đang theo dõi các Service.
Mọi bản triển khai bộ cân bằng tải mặc định (ví dụ, bản do
nhà cung cấp dịch vụ đám mây cung cấp) sẽ bỏ qua các Service có đặt trường này.
spec.loadBalancerClass chỉ có thể được đặt trên Service loại LoadBalancer.
Một khi đã đặt, giá trị này không thể thay đổi.
Giá trị của spec.loadBalancerClass phải là một định danh dạng label,
với tiền tố tùy chọn như "internal-vip" hoặc "example.com/internal-vip".
Các tên không có tiền tố được dành riêng cho người dùng cuối.
Với một Service type: LoadBalancer, controller có thể đặt .status.loadBalancer.ingress.ipMode.
.status.loadBalancer.ingress.ipMode chỉ định cách IP của bộ cân bằng tải hoạt động.
Trường này chỉ có thể được chỉ định khi trường .status.loadBalancer.ingress.ip cũng được chỉ định.
Có hai giá trị khả dĩ cho .status.loadBalancer.ingress.ipMode: "VIP" và "Proxy".
Giá trị mặc định là "VIP", nghĩa là lưu lượng được chuyển tới node
với đích được đặt là IP và cổng của bộ cân bằng tải.
Có hai trường hợp khi đặt giá trị này thành "Proxy", tùy thuộc vào cách bộ cân bằng tải
của nhà cung cấp dịch vụ đám mây chuyển lưu lượng:
Các bản triển khai Service có thể dùng thông tin này để điều chỉnh việc định tuyến lưu lượng.
Trong một môi trường hỗn hợp, đôi khi cần định tuyến lưu lượng từ các Service bên trong cùng một khối địa chỉ mạng (ảo).
Trong môi trường DNS split-horizon, bạn sẽ cần hai Service để có thể định tuyến cả lưu lượng bên ngoài lẫn bên trong tới các endpoint của mình.
Để thiết lập một bộ cân bằng tải nội bộ, hãy thêm một trong các chú thích sau vào Service tùy theo nhà cung cấp dịch vụ đám mây mà bạn đang dùng:
Chọn một trong các tab.
metadata:
name: my-service
annotations:
networking.gke.io/load-balancer-type: "Internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-scheme: "internal"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
metadata:
name: my-service
annotations:
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
metadata:
annotations:
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/oci-load-balancer-internal: true
type: ExternalNameService loại ExternalName ánh xạ một Service tới một tên DNS, chứ không phải tới một selector thông thường như
my-service hay cassandra. Bạn chỉ định các Service này bằng tham số spec.externalName.
Ví dụ, định nghĩa Service sau ánh xạ
Service my-service trong namespace prod tới my.database.example.com:
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
Service type: ExternalName chấp nhận một chuỗi địa chỉ IPv4,
nhưng xử lý chuỗi đó như một tên DNS gồm các chữ số,
chứ không phải như một địa chỉ IP (tuy nhiên internet không cho phép những tên như vậy trong DNS).
Các Service có tên bên ngoài trông giống địa chỉ
IPv4 sẽ không được các máy chủ DNS phân giải.
Nếu bạn muốn ánh xạ trực tiếp một Service tới một địa chỉ IP cụ thể, hãy cân nhắc dùng headless Service.
Khi tra cứu host my-service.prod.svc.cluster.local, DNS Service của cluster
trả về một bản ghi CNAME với giá trị my.database.example.com. Việc truy cập
my-service hoạt động giống như các Service khác nhưng có điểm khác biệt
quan trọng là việc chuyển hướng diễn ra ở mức DNS thay vì qua proxy hay
chuyển tiếp. Nếu sau này bạn quyết định chuyển cơ sở dữ liệu vào trong cluster, bạn
có thể khởi chạy các Pod của nó, thêm các selector hoặc endpoint phù hợp, và thay đổi
type của Service.
Bạn có thể gặp khó khăn khi dùng ExternalName với một số giao thức phổ biến, bao gồm HTTP và HTTPS. Nếu bạn dùng ExternalName thì hostname mà các client bên trong cluster sử dụng sẽ khác với tên mà ExternalName tham chiếu tới.
Với các giao thức sử dụng hostname, sự khác biệt này có thể dẫn đến lỗi hoặc phản hồi không mong muốn.
Các yêu cầu HTTP sẽ có header Host: mà máy chủ gốc không nhận ra;
các máy chủ TLS sẽ không thể cung cấp chứng chỉ khớp với hostname mà client đã kết nối tới.
Đôi khi bạn không cần cân bằng tải và một IP Service duy nhất. Trong
trường hợp này, bạn có thể tạo cái gọi là headless Service, bằng cách chỉ định rõ
"None" cho địa chỉ cluster IP (.spec.clusterIP).
Bạn có thể dùng headless Service để giao tiếp với các cơ chế service discovery khác mà không bị ràng buộc vào cách triển khai của Kubernetes.
Với headless Service, cluster IP không được cấp phát, kube-proxy không xử lý các Service này, và nền tảng không thực hiện cân bằng tải hay proxy cho chúng.
Headless Service cho phép client kết nối trực tiếp tới bất kỳ Pod nào mà nó muốn. Các headless Service không
cấu hình route và chuyển tiếp gói tin bằng
địa chỉ IP ảo và proxy; thay vào đó, headless Service báo cáo
địa chỉ IP endpoint của từng pod thông qua các bản ghi DNS nội bộ, được phục vụ qua
dịch vụ DNS của cluster.
Để định nghĩa một headless Service, bạn tạo một Service với .spec.type được đặt thành ClusterIP (cũng là giá trị mặc định của type),
và đặt thêm .spec.clusterIP thành None.
Giá trị chuỗi None là một trường hợp đặc biệt và không giống với việc để trống trường .spec.clusterIP.
Cách DNS được cấu hình tự động phụ thuộc vào việc Service có định nghĩa selector hay không:
Với các headless Service có định nghĩa selector, endpoints controller tạo các EndpointSlice trong Kubernetes API, và sửa đổi cấu hình DNS để trả về các bản ghi A hoặc AAAA (địa chỉ IPv4 hoặc IPv6) trỏ trực tiếp tới các Pod đứng sau Service.
Với các headless Service không định nghĩa selector, control plane không tạo các đối tượng EndpointSlice. Tuy nhiên, hệ thống DNS sẽ tìm và cấu hình một trong hai:
type: ExternalName.ExternalName.Khi bạn định nghĩa một headless Service không có selector, port phải
khớp với targetPort.
Với các client chạy bên trong cluster, Kubernetes hỗ trợ hai cách chính để tìm một Service: biến môi trường và DNS.
Khi một Pod chạy trên một Node, kubelet thêm một tập biến môi trường
cho mỗi Service đang hoạt động. Nó thêm các biến {SVCNAME}_SERVICE_HOST và {SVCNAME}_SERVICE_PORT,
trong đó tên Service được viết hoa và dấu gạch ngang được chuyển thành dấu gạch dưới.
Ví dụ, Service redis-primary cung cấp cổng TCP 6379 và đã được
cấp phát địa chỉ cluster IP 10.0.0.11 sẽ tạo ra các biến môi trường
sau:
REDIS_PRIMARY_SERVICE_HOST=10.0.0.11
REDIS_PRIMARY_SERVICE_PORT=6379
REDIS_PRIMARY_PORT=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_PRIMARY_PORT_6379_TCP_PROTO=tcp
REDIS_PRIMARY_PORT_6379_TCP_PORT=6379
REDIS_PRIMARY_PORT_6379_TCP_ADDR=10.0.0.11
Khi bạn có một Pod cần truy cập một Service, và bạn đang dùng phương pháp biến môi trường để công bố cổng và cluster IP cho các Pod client, bạn phải tạo Service trước khi các Pod client được tạo ra. Nếu không, các biến môi trường của những Pod client đó sẽ không được điền giá trị.
Nếu bạn chỉ dùng DNS để tìm cluster IP của một Service, bạn không cần lo lắng về vấn đề thứ tự này.
Kubernetes cũng hỗ trợ và cung cấp các biến tương thích với tính năng
"legacy container links" của Docker Engine.
Bạn có thể đọc makeLinkVariables
để xem cách tính năng này được triển khai trong Kubernetes.
Bạn có thể (và gần như luôn luôn nên) thiết lập một dịch vụ DNS cho cluster Kubernetes bằng một add-on.
Một máy chủ DNS nhận biết cluster (cluster-aware), chẳng hạn CoreDNS, theo dõi Kubernetes API để phát hiện các Service mới và tạo một tập bản ghi DNS cho mỗi Service. Nếu DNS đã được bật trên toàn bộ cluster thì tất cả các Pod sẽ tự động phân giải được Service theo tên DNS của chúng.
Ví dụ, nếu bạn có một Service tên là my-service trong namespace
Kubernetes my-ns, control plane và DNS Service cùng phối hợp
tạo một bản ghi DNS cho my-service.my-ns. Các Pod trong namespace my-ns
sẽ có thể tìm thấy service bằng cách tra cứu tên my-service
(my-service.my-ns cũng hoạt động).
Các Pod trong namespace khác phải dùng tên đầy đủ my-service.my-ns. Những tên này
sẽ được phân giải thành cluster IP được gán cho Service.
Kubernetes cũng hỗ trợ bản ghi DNS SRV (Service) cho các cổng có tên. Nếu
Service my-service.my-ns có một cổng tên là http với giao thức được đặt thành
TCP, bạn có thể thực hiện truy vấn DNS SRV cho _http._tcp.my-service.my-ns để tìm
số cổng của http, cũng như địa chỉ IP.
Máy chủ DNS của Kubernetes là cách duy nhất để truy cập các Service ExternalName.
Bạn có thể tìm thêm thông tin về việc phân giải ExternalName trong
DNS cho Service và Pod.
Trang IP ảo và Service Proxy giải thích cơ chế mà Kubernetes dùng để cung cấp một Service qua một địa chỉ IP ảo.
Bạn có thể đặt các trường .spec.internalTrafficPolicy và .spec.externalTrafficPolicy
để kiểm soát cách Kubernetes định tuyến lưu lượng tới các backend khỏe mạnh (“ready”).
Xem Chính sách lưu lượng để biết thêm chi tiết.
Trường .spec.trafficDistribution cung cấp một cách khác để tác động tới việc định tuyến
lưu lượng trong một Service Kubernetes. Trong khi chính sách lưu lượng tập trung vào các đảm bảo
ngữ nghĩa chặt chẽ, phân phối lưu lượng cho phép bạn thể hiện các ưu tiên
(chẳng hạn định tuyến tới các endpoint gần hơn về mặt topo mạng). Điều này có thể giúp tối ưu
hiệu năng, chi phí, hoặc độ tin cậy. Trong Kubernetes 1.37,
các giá trị sau được hỗ trợ:
PreferSameZonePreferSameNodePreferClose (không còn được hỗ trợ)PreferSameZone, có ngữ nghĩa
kém rõ ràng hơn.Nếu trường này không được đặt, bản triển khai sẽ áp dụng chiến lược định tuyến mặc định của nó.
Xem Phân phối lưu lượng để biết thêm chi tiết.
Nếu bạn muốn đảm bảo rằng các kết nối từ một client cụ thể luôn được chuyển tới cùng một Pod, bạn có thể cấu hình session affinity dựa trên địa chỉ IP của client. Đọc session affinity để tìm hiểu thêm.
Tất cả người dùng nên bắt đầu chuyển dần khỏi externalIPs.
Thay vào đó, hãy cân nhắc dùng một controller bộ cân bằng tải bên ngoài hoặc một bản triển khai
Gateway API.
Nếu có các IP bên ngoài định tuyến tới một hoặc nhiều node của cluster, các Service Kubernetes
có thể được cung cấp trên các externalIPs đó. Khi lưu lượng mạng đi vào cluster với
IP bên ngoài (là IP đích) và cổng khớp với Service đó, các quy tắc và route
mà Kubernetes đã cấu hình sẽ đảm bảo lưu lượng được định tuyến tới một trong các endpoint
của Service đó.
Khi định nghĩa một Service, bạn có thể chỉ định externalIPs cho bất kỳ
loại Service nào.
Trong ví dụ dưới đây, client có thể truy cập Service tên "my-service" qua TCP,
tại "198.51.100.32:80" (được tính từ .spec.externalIPs[] và .spec.ports[].port).
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 49152
externalIPs:
- 198.51.100.32
externalIPs; đây là trách nhiệm
của quản trị viên cluster.Service là một tài nguyên cấp cao nhất trong Kubernetes REST API. Bạn có thể tìm thêm chi tiết về đối tượng Service API.
Tìm hiểu thêm về Service và vai trò của chúng trong Kubernetes:
Để hiểu thêm bối cảnh, hãy đọc các tài liệu sau: