1. Tìm hiểu về Service
1.1. Tại sao cần Service?
- Mỗi Pod đều có địa chỉ IP riêng biệt, tuy nhiên khi Pod bị xóa và tạo lại, địa chỉ IP của Pod mới sẽ thay đổi, điều này gây khó khăn trong việc truy cập
- Giải pháp đặt ra là tạo một proxy phía trước các Pod, khi client truy cập sẽ thông qua Service rồi mới chuyển tiếp đến Pod được quản lý
- Service lựa chọn Pod thông qua label selector
- Service là tập hợp logic của các Pod (có thể thay đổi được), chỉ phụ thuộc vào label
- Service có địa chỉ IP và cổng riêng, các cổng này đều là ảo. Khi map Service port ra node thì gọi là NodePort
- Thông tin được lưu trữ trong ipvsadm -Ln
- kube-proxy theo dõi việc tạo Service và liên kết IP của Pod với IP của Service
- coredns thực hiện phân giải domain, chuyển đổi tên domain của Service thành địa chỉ IP
1.2. Quy trình Service proxy Pod
- Khi tạo Service, label selector sẽ tìm các Pod liên quan, endpoint sẽ lưu trữ IP của các Pod đó
- Khi Pod thay đổi, endpoint cũng cập nhật tương ứng
- Khi có request, endpoint chuyển tiếp đến Pod phù hợp, còn kube-proxy quyết định node nào sẽ xử lý
1.3. Bốn loại Service
ExternalName
- Cho phép container trong cluster K8s truy cập resource bên ngoài, không có selector và endpoint
- Ánh xạ Service với một domain, khi truy cập Service sẽ trả về domain tương ứng
- Tên domain đầy đủ: <service_name>.<namespace>.svc.cluster.local
ClusterIP
- Chỉ expose IP nội bộ trong cluster, dịch vụ chỉ có thể truy cập từ bên trong cluster
- Đây là loại Service mặc định
NodePort
- Expose dịch vụ qua IP của node và một cổng tĩnh
- Luồng request: node:nodeport → svc:svcport → pod:containerport
LoadBalancer
- Sử dụng load balancer của cloud provider để expose dịch vụ ra ngoài
- Có thể route đến NodePort và ClusterIP service
1.4. Thực hành
Tạo Service loại ClusterIP
#Tạo deployment và để Service proxy các Pod
[root@master ~]# cat service-clusterip.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deploy
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- name: web-container
image: docker.io/library/nginx
imagePullPolicy: IfNotPresent
---
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: production
spec:
type: ClusterIP
ports:
- targetPort: 80
port: 80
selector:
app: webapp
#Kiểm tra Service
[root@master ~]# kubectl get svc -n production
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web-service ClusterIP 10.108.45.201 <none> 80/TCP 2h15m
#Kiểm tra endpoints
[root@master ~]# kubectl get endpoints -n production
NAME ENDPOINTS AGE
web-service 10.244.125.8:80,10.244.167.19:80 2h15m
#Kiểm tra Pod
[root@master ~]# kubectl get pod -n production -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-deploy-8f7cd4-rnkdm 1/1 Running 0 2h 10.244.167.19 node1 <none> <none>
web-deploy-8f7cd4-xklpt 1/1 Running 0 2h 10.244.125.8 node2 <none> <none>
#Truy cập vào Pod để test
[root@master ~]# kubectl exec -it -n production web-deploy-8f7cd4-rnkdm -- /bin/bash
curl web-service.production.svc.cluster.local
Tạo Service loại NodePort
#ClusterIP chỉ truy cập được từ trong cluster, còn NodePort cho phép truy cập từ bên ngoài
[root@master ~]# cat service-nodeport.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-deploy
namespace: production
spec:
replicas: 2
selector:
matchLabels:
api: apiserver
template:
metadata:
labels:
api: apiserver
spec:
containers:
- name: api-container
image: docker.io/library/nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: api-service
namespace: production
spec:
type: NodePort
ports:
- targetPort: 80
port: 80
nodePort: 30080
selector:
api: apiserver
#Request sẽ được chuyển từ node:30080 → service:80 → Pod
[root@master ~]# kubectl get svc -n production
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
api-service NodePort 10.99.147.221 <none> 80:30080/TCP 1h30m
Tạo Service loại ExternalName
#Truy cập cross-namespace: client trong namespace default truy cập nginx trong namespace backend
[root@master ~]# cat service-external.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: client-deploy
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: clientapp
template:
metadata:
labels:
app: clientapp
spec:
containers:
- name: client-container
image: docker.io/library/busybox:1.28
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","sleep 3600"]
---
apiVersion: v1
kind: Service
metadata:
name: client-service
namespace: default
spec:
type: ExternalName
externalName: nginx-service.backend.svc.cluster.local
ports:
- targetPort: 80
port: 80
name: http
#Khi truy cập client-service sẽ được ánh xạ đến nginx-service
[root@master ~]# kubectl get svc -n default
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
client-service ExternalName <none> nginx-service.backend.svc.cluster.local 80/TCP 2m10s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d2h
#Vào Pod trong namespace default, truy cập domain service, request sẽ được forward sang Pod trong namespace backend
[root@master ~]# kubectl exec -it -n default client-deploy-5f8d9-abcd1 -- /bin/sh
/ # cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5
/ # wget -q -O - client-service.default.svc.cluster.local
Loại Service ExternalName cho phép truy cập cross-namespace. Khi ánh xạ Service bên ngoài vào Service nội bộ, khi truy cấp domain của Service sẽ được chuyển tiếp đến Service bên ngoại. Domain mặc định của Service có dạng: servicename.namespace.svc.cluster.local