一、什么是微服務
在 Kubernetes 中,控制器負責維持業務副本,但真正把業務“暴露”出去的是 Service。
一句話理解:
Service = 一組 Pod 的穩定訪問入口 + 4 層負載均衡
Ingress = 7 層路由 + 統一入口 + 灰度 / 認證 / 重寫等高級能力
默認情況下,Service 僅具備 4 層(TCP/UDP)能力,如需 7 層(HTTP/HTTPS)請使用 Ingress。
二、微服務(Service)的四種類型總覽
類型 | 作用與適用場景 |
---|---|
ClusterIP | 默認值,集群內部虛擬 IP;僅供集群內部訪問,自動 DNS 與服務發現。 |
NodePort | 在每個節點打開固定端口(30000-32767),外部通過?節點IP:端口 ?即可訪問服務。 |
LoadBalancer | 基于 NodePort,再申請外部云負載均衡(或 MetalLB);適用于公有云或裸金屬+MetalLB。 |
ExternalName | 將集群內請求通過 CNAME 轉發到任意指定域名;常用于外部服務遷移或跨集群調用。 |
三、Service 的默認實現:iptables vs IPVS
iptables 模式(默認)
規則多、刷新慢,萬級 Pod 場景下 CPU 抖動明顯。
IPVS 模式(推薦生產)
內核級四層負載,支持 10w+ 連接,性能穩定。
切換后自動生成虛擬網卡
kube-ipvs0
,所有 ClusterIP 被綁定到該接口。
3.1 一鍵切換到 IPVS 模式
1.所有節點安裝工具
yum install ipvsadm -y
2.修改 kube-proxy 配置
kubectl -n kube-system edit cm kube-proxy
# 找到 mode 字段,改為 "ipvs"
如果什么都沒有,說明是默認的使用iptables,這里我們加上ipvs
修改配置后,要重啟,這里可以刪掉之前的網絡配置pod,重新刷新新的pod出來,此時就是新策略的pod
3.滾動重啟 kube-proxy Pod
kubectl -n kube-system get pods | awk '/kube-proxy/{system("kubectl -n kube-system delete pod "$1)
}'
4.驗證
ipvsadm -Ln | head
# 出現 10.96.x.x:xx rr 即成功
四、微服務類型詳解
4.1 ClusterIP —— 集群內默認訪問方式
4.1.1 標準 ClusterIP 示例
apiVersion: v1
kind: Service
metadata:labels:app: timingleename: timinglee
spec:ports:- port: 80 # Service 端口protocol: TCPtargetPort: 80 # Pod 端口selector:app: timinglee # 綁定到標簽一致的 Podtype: ClusterIP # 可省略,默認即 ClusterIP
kubectl apply -f clusterip.yml
kubectl get svc timinglee
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# timinglee ClusterIP 10.99.127.134 <none> 80/TCP 16s
clusterip模式只能在集群內訪問,并對集群內的pod提供健康檢測和自動發現功能
追加內容,此時微服務和控制器就在一個配置文件里了
只有集群內部的IP,集群外部的不暴露
4.1.2 DNS 自動解析驗證
# 集群內部可直接解析
dig timinglee.default.svc.cluster.local @10.96.0.10
# ;; ANSWER SECTION:
# timinglee.default.svc.cluster.local. 30 IN A 10.99.127.134
4.2 Headless —— 無 ClusterIP 的直連模式
適用場景:StatefulSet 的穩定網絡標識、客戶端自己做負載均衡、自定義 DNS 策略。
apiVersion: v1
kind: Service
metadata:name: timinglee
spec:clusterIP: None # 關鍵字段ports:- port: 80targetPort: 80selector:app: timinglee
之前有了無頭服務,要刪掉,不然影響實驗
沒有了IP以后,后端就沒有調度了
此時我們可以用dns來寫,把要訪問的server直接指定到后端的服務器中去
#開啟一個busyboxplus的pod測試
kubectl apply -f headless.yml
kubectl get svc timinglee
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# timinglee ClusterIP None <none> 80/TCP 6s
DNS 結果直接返回所有 Pod IP:
dig timinglee.default.svc.cluster.local @10.96.0.10
# ANSWER SECTION:
# timinglee.default.svc.cluster.local. 20 IN A 10.244.2.14
# timinglee.default.svc.cluster.local. 20 IN A 10.244.1.18
4.3 NodePort —— 節點端口暴露
4.3.1 快速示例
apiVersion: v1
kind: Service
metadata:name: timinglee-service
spec:type: NodePortports:- port: 80targetPort: 80nodePort: 31771 # 可省略,自動分配 30000-32767selector:app: timinglee
kubectl apply -f nodeport.yml
kubectl get svc timinglee-service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# timinglee NodePort 10.98.60.22 <none> 80:31771/TCP 8s
之前的服務設置了無頭服務,這里要刪除之前環境,重新運行
多了一個端口
這個端口用來直接對外暴露
外部訪問測試:
curl 172.25.254.100:31771/hostname.html
# timinglee-c56f584cf-fjxdk
nodeport在集群節點上綁定端口,一個端口對應一個服務
直接負載到下面兩個
用clusterip來訪問后端的
訪問模式
對應的端口是不固定的,但是我們可以直接指定,但是有范圍限制最大30000
但是想要超過限制也可以,修改配置文件就行。但是集群會掛掉,要等待自愈
加上這句話- --service-node-port-range=30000-40000
剛剛還不能超過的限制,現在就可以了
NodePort 默認端口范圍 30000-32767;如需自定義范圍,請修改 kube-apiserver 參數
--service-node-port-range=30000-40000
。
4.4 LoadBalancer —— 云或裸金屬的外部 VIP
4.4.1 公有云場景
apiVersion: v1
kind: Service
metadata:name: timinglee-service
spec:type: LoadBalancerports:- port: 80targetPort: 80selector:app: timinglee
在 云廠商(AWS/GCP/阿里云) 上,提交 YAML 后會自動為 Service 分配一個公網 VIP:
kubectl get svc timinglee-service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# timinglee LoadBalancer 10.107.23.134 203.0.113.10 80:32537/TCP 4s
4.4.2 裸金屬場景:MetalLB
MetalLB 為裸金屬或私有集群實現 LoadBalancer 功能。
① 安裝 MetalLB
# 1) 確保 kube-proxy 為 IPVS 模式(見第 3.1 節)
kubectl edit cm -n kube-system kube-proxy # mode: "ipvs"
kubectl -n kube-system get pods | awk '/kube-proxy/{system("kubectl -n kube-system delete pod "$1)}'# 2) 下載官方清單
wget https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml# 3) 修改鏡像地址(私有倉庫)
sed -i 's#quay.io/metallb/controller:v0.14.8#reg.timinglee.org/metallb/controller:v0.14.8#' \metallb-native.yaml
sed -i 's#quay.io/metallb/speaker:v0.14.8#reg.timinglee.org/metallb/speaker:v0.14.8#' \metallb-native.yaml# 4) 推送鏡像到 Harbor
docker pull quay.io/metallb/controller:v0.14.8
docker pull quay.io/metallb/speaker:v0.14.8
docker tag ... && docker push ...# 5) 部署
kubectl apply -f metallb-native.yaml
kubectl -n metallb-system wait --for=condition=ready pod -l app=metallb --timeout=120s
② 配置 IP 地址池
# configmap.yml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:name: first-poolnamespace: metallb-system
spec:addresses:- 172.25.254.50-172.25.254.99 # 與本地網絡同段
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:name: examplenamespace: metallb-system
spec:ipAddressPools:- first-pool
kubectl apply -f configmap.yml
再次查看 Service:
kubectl get svc timinglee-service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# timinglee LoadBalancer 10.109.36.123 172.25.254.50 80:31595/TCP 9m9s
集群外直接訪問 VIP:
curl 172.25.254.50
# Hello MyApp | Version: v1 | ...
部署安裝
必須要把集群做成ipvs的模式
并且重啟網絡方面的pod
這個文檔里的路徑已經修改好了,如果是未修改的,記得把路徑換成自己的軟件倉庫
這個生效了之后,才能改配置
之前這里還是正在生效,現在已經有了IP
#通過分配地址從集群外訪問服務
已經自動分配對外IP
4.5 ExternalName —— DNS CNAME 轉發
開啟services后,不會被分配IP,而是用dns解析CNAME固定域名來解決ip變化問題
一般應用于外部業務和pod溝通或外部業務遷移到pod內時
在應用向集群遷移過程中,externalname在過度階段就可以起作用了。
集群外的資源遷移到集群時,在遷移的過程中ip可能會變化,但是域名+dns解析能完美解決此問題
業務尚在集群外(如 RDS、COS、第三方 API)。
遷移過程中保持調用方 域名不變,僅改 DNS 指向。
apiVersion: v1
kind: Service
metadata:name: timinglee-service
spec:type: ExternalNameexternalName: www.timinglee.org # 目的域名
kubectl apply -f externalname.yml
kubectl get svc timinglee-service
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# timinglee ExternalName <none> www.timinglee.org <none> 2m58s
集群內 Pod 訪問 timinglee-service
時,DNS 會直接返回 www.timinglee.org
的地址,無需維護 IP 變化。
集群內部的IP在訪問時,做的是域名解析
把真實的微服務轉化成其他主機上
沒有IP時如何被訪問的,通過dns的域名解析
驗證 DNS 解析
這行命令測試 Kubernetes 集群內部 DNS(10.96.0.10是集群 DNS 服務的 IP)是否能正確解析ext-service對應的域名:
結果顯示ext-service.default.svc.cluster.local(集群內部服務域名)被解析為www.baidu.com
最終解析到百度的實際 IP 地址(103.235.46.115和103.235.46.102)
得到了集群內部的主機
微服務把集群外部的資源映射到集群內部,讓集群內部可以使用
五、Ingress-Nginx 全景實戰
Ingress = 7 層路由 + 多域名 + 灰度 + 認證 + TLS + 重寫
在service前面在加一個nginx
在集群暴露時,再加一個反向代理
一種全局的、為了代理不同后端 Service 而設置的負載均衡服務,支持7層
Ingress由兩部分組成:Ingress controller和Ingress服務
Ingress Controller 會根據你定義的 Ingress 對象,提供對應的代理能力。
業界常用的各種反向代理項目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已經為Kubernetes 專門維護了對應的 Ingress Controller。
5.1 部署 Ingress-Nginx(裸金屬)
# 1) 下載官方清單
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/baremetal/deploy.yaml# 2) 鏡像同步到私有倉庫
docker tag registry.k8s.io/ingress-nginx/controller:v1.11.2 \reg.timinglee.org/ingress-nginx/controller:v1.11.2
docker tag registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.3 \reg.timinglee.org/ingress-nginx/kube-webhook-certgen:v1.4.3
docker push ...# 3) 修改清單中的鏡像地址
sed -i 's#registry.k8s.io/ingress-nginx/controller#reg.timinglee.org/ingress-nginx/controller#' deploy.yaml
sed -i 's#registry.k8s.io/ingress-nginx/kube-webhook-certgen#reg.timinglee.org/ingress-nginx/kube-webhook-certgen#' deploy.yaml# 4) 部署 & 等待就緒
kubectl apply -f deploy.yaml
kubectl -n ingress-nginx wait --for=condition=ready pod -l app.kubernetes.io/name=ingress-nginx --timeout=120s
在部署文件里
上傳ingress所需鏡像到harbor
運行配置文件,并且查看是否建立了新的命名空間
此時查看還是沒有對外開放的ip的,因為微服還沒有修改,現在還是只能集群內部訪問
#修改微服務為loadbalancer
此時就有對外開放的IP了
在ingress-nginx-controller中看到的對外IP就是ingress最終對外開放的ip
測試ingress
生成一下模板
上面是控制器,下面是微服務
默認 Service 類型為 NodePort,如需 LoadBalancer:
kubectl -n ingress-nginx edit svc ingress-nginx-controller
# 把 type: NodePort 改為 type: LoadBalancer
kubectl -n ingress-nginx get svc
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# ingress-nginx-controller LoadBalancer 10.103.33.148 172.25.254.50 80:34512/TCP,443:34727/TCP
5.2 基于路徑的多版本分流
5.2.1 部署兩套版本業務
# myapp-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: myapp-v1
spec:replicas: 1selector:matchLabels: {app: myapp-v1}template:metadata:labels: {app: myapp-v1}spec:containers:- name: myappimage: myapp:v1
---
apiVersion: v1
kind: Service
metadata:name: myapp-v1
spec:selector:app: myapp-v1ports:- port: 80targetPort: 80
kubectl apply -f myapp-v1.yaml
# 同理再建 myapp-v2.yaml(鏡像改為 v2,service 名為 myapp-v2)
?
裝了兩臺主機,兩臺主機呈現不同web頁面
核心動作都是nginx完成的
調用nginx類,訪問微服務的80端口
當我們去訪問我們剛剛設立的對外IP時,它帶我們去看的時myappv1的80端口里的內容
5.2.2 創建基于路徑的 Ingress
# ingress-path.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-pathannotations:nginx.ingress.kubernetes.io/rewrite-target: /
spec:ingressClassName: nginxrules:- host: myapp.timinglee.orghttp:paths:- path: /v1pathType: Prefixbackend:service:name: myapp-v1port: {number: 80}- path: /v2pathType: Prefixbackend:service:name: myapp-v2port: {number: 80}
kubectl apply -f ingress-path.yamlcurl http://myapp.timinglee.org/v1 # Version: v1
curl http://myapp.timinglee.org/v2 # Version: v2
此時直接訪問是不行的,因為沒有設定默認發布目錄
可以設定一下
5.3 基于域名的多業務入口
# ingress-domain.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-domain
spec:ingressClassName: nginxrules:- host: myappv1.timinglee.orghttp:paths:- path: /pathType: Prefixbackend:service: {name: myapp-v1, port: {number: 80}}- host: myappv2.timinglee.orghttp:paths:- path: /pathType: Prefixbackend:service: {name: myapp-v2, port: {number: 80}}
kubectl apply -f ingress-domain.yamlcurl http://myappv1.timinglee.org # Version: v1
curl http://myappv2.timinglee.org # Version: v2
子集寫在最前面也行,寫最后面也行
此時我們
5.4 HTTPS(TLS)一鍵啟用
5.4.1 自簽證書 & Secret
openssl req -x509 -newkey rsa:2048 -nodes -keyout tls.key -out tls.crt -days 365 \-subj "/CN=myapp-tls.timinglee.org"kubectl create secret tls web-tls-secret --cert=tls.crt --key=tls.key
5.4.2 啟用 TLS 的 Ingress
# ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-tls
spec:ingressClassName: nginxtls:- hosts: [myapp-tls.timinglee.org]secretName: web-tls-secretrules:- host: myapp-tls.timinglee.orghttp:paths:- path: /pathType: Prefixbackend:service: {name: myapp-v1, port: {number: 80}}
kubectl apply -f ingress-tls.yamlcurl -k https://myapp-tls.timinglee.org # HTTPS 成功
生成證書去加密
設置非交互式輸入
此時生成的證書與集群無關
把證書變成資源,能被集群調用
里面有兩個文件
查看資源信息,它們以鍵值的方式保存了
要把加密的模式保存到配置文件中去
一次性可以給多個設備加密 host
最終要調用的資源里的證書
?
查看新建的ingress的詳細情況,是否加密成功
此時直接訪問已經不行了
https:// 表示使用 HTTPS 協議 訪問,符合 Ingress 配置中強制 HTTPS 的要求,因此不會被重定向。
-k 參數的作用是 跳過 SSL 證書驗證。如果你的 Ingress 使用的是自簽名證書(而非可信 CA 頒發的證書),curl 會默認驗證證書并報錯(如 SSL certificate problem)。加上 -k 后會忽略證書驗證,從而成功建立連接并獲取響應。
5.5 Basic Auth 用戶認證
5.5.1 生成密碼文件并寫入 Secret
dnf install -y httpd-tools
htpasswd -cm auth lee # 輸入兩次密碼
kubectl create secret generic auth-web --from-file=auth
5.5.2 在 Ingress 中開啟認證
# ingress-auth.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-authannotations:nginx.ingress.kubernetes.io/auth-type: basicnginx.ingress.kubernetes.io/auth-secret: auth-webnginx.ingress.kubernetes.io/auth-realm: "Please input username and password"
spec:ingressClassName: nginxtls:- hosts: [myapp-tls.timinglee.org]secretName: web-tls-secretrules:- host: myapp-tls.timinglee.orghttp:paths:- path: /pathType: Prefixbackend:service: {name: myapp-v1, port: {number: 80}}
用戶級別的訪問限制
此時的文件還是沒有關系和集群
要通過這個命令,把這個叫做htpasswd的文件抽象成集群中的資源
編輯配置文件,要用到參數
在調用時,也會驗證這些參數
錯誤情況:
這里會錯誤,是因為他默認調用auth這個名字,而之前創建用戶密碼時,儲存的文件名不是它,系統此時訪問不了
修改名字:
刪除之前儲存的資源
重新建立資源
刪除之前運行的配置文件
重新運行一次就行了
kubectl apply -f ingress-auth.yamlcurl -k https://myapp-tls.timinglee.org # 401 Unauthorized
curl -k -u lee:lee https://myapp-tls.timinglee.org # 200 OK
5.6 URL 重寫(Rewrite)與正則
5.6.1 根路徑重定向
nginx.ingress.kubernetes.io/app-root: /hostname.html
5.6.2 正則捕獲與重寫
# ingress-rewrite.yaml
metadata:annotations:nginx.ingress.kubernetes.io/use-regex: "true"nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:rules:- host: myapp-tls.timinglee.orghttp:paths:- path: /lee(/|$)(.*) # 匹配 /lee 或 /lee/xxxpathType: ImplementationSpecificbackend:service: {name: myapp-v1, port: {number: 80}}
curl -k -u lee:lee https://myapp-tls.timinglee.org/lee/hostname.html
# 實際返回 /hostname.html 內容
匹配正則表達式
測試:
六、金絲雀(Canary)發布
6.1 核心思路
先少量、后全量:降低新版本全量故障風險
Ingress-Nginx 支持 Header / Cookie / 權重 三種灰度策略
發布過程 只增不減:Pod 總數 ≥ 期望值,業務無中斷
6.2 基于 Header 的灰度
6.2.1 正式流量 Ingress(v1)
# myapp-v1-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: myapp-v1-ingress
spec:ingressClassName: nginxrules:- host: myapp.timinglee.orghttp:paths:- path: /pathType: Prefixbackend:service: {name: myapp-v1, port: {number: 80}}
部署老版本的
升級后的訪問是要基于什么情況下訪問
要寫參數來設置了
當攜帶timinglee的值是6時,就訪問new的
6.2.2 灰度流量 Ingress(v2)
# myapp-v2-canary-header.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: myapp-v2-canaryannotations:nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-by-header: "version"nginx.ingress.kubernetes.io/canary-by-header-value: "2"
spec:ingressClassName: nginxrules:- host: myapp.timinglee.orghttp:paths:- path: /pathType: Prefixbackend:service: {name: myapp-v2, port: {number: 80}}
kubectl apply -f myapp-v1-ingress.yaml
kubectl apply -f myapp-v2-canary-header.yaml# 測試
curl http://myapp.timinglee.org # v1
curl -H "version: 2" http://myapp.timinglee.org # v2
6.3 基于權重(Weight)的灰度
# myapp-v2-canary-weight.yaml
metadata:annotations:nginx.ingress.kubernetes.io/canary: "true"nginx.ingress.kubernetes.io/canary-weight: "10" # 10%nginx.ingress.kubernetes.io/canary-weight-total: "100"
kubectl apply -f myapp-v2-canary-weight.yaml# 100 次采樣腳本
for i in {1..100}; do curl -s myapp.timinglee.org | grep -c v2; done | awk '{v2+=$1} END{print "v2:"v2", v1:"100-v2}'
# v2:10, v1:90 # 符合 10% 權重
腳本測試:
測試沒有問題了
就修改權重
直到最后沒有問題,old的版本就可以刪除了
調整權重只需修改 annotation 后 kubectl apply
,即可實現 平滑全量滾動。
七、一鍵清理
kubectl delete ingress --all
kubectl delete svc --all -l app=myapp-v1,app=myapp-v2
kubectl delete deploy --all -l app=myapp-v1,app=myapp-v2