網(wǎng)站首頁 編程語言 正文
介紹
我們已經(jīng)知道,Service對集群之外暴露服務(wù)的主要方式有兩種:NodePort和LoadBalancer,但是這兩種方式,都有一定的缺點(diǎn):
- NodePort方式的缺點(diǎn)是會(huì)占用很多集群機(jī)器的端口,那么當(dāng)集群服務(wù)變多的時(shí)候,這個(gè)缺點(diǎn)就愈發(fā)明顯。
- LoadBalancer的缺點(diǎn)是每個(gè)Service都需要一個(gè)LB,浪費(fèi),麻煩,并且需要Kubernetes之外的設(shè)備的支持。
基于這種現(xiàn)狀,Kubernetes提供了Ingress資源對象,Ingress只需要一個(gè)NodePort或者一個(gè)LB就可以滿足暴露多個(gè)Service的需求。
客戶端首先對 域名 執(zhí)行 DNS 解析,得到 Ingress Controller 所在節(jié)點(diǎn)的 IP,然后客戶端向 Ingress Controller 發(fā)送 HTTP 請求,然后根據(jù) Ingress 對象里面的描述匹配域名,找到對應(yīng)的 Service 對象,并獲取關(guān)聯(lián)的 Endpoints 列表,將客戶端的請求轉(zhuǎn)發(fā)給其中一個(gè) Pod。
本文我們來使用client-go實(shí)現(xiàn)一個(gè)自定義控制器,通過判斷service
的Annotations
屬性是否包含ingress/http
,如果包含則創(chuàng)建ingress
,如果不包含則不創(chuàng)建。而且如果存在ingress
則進(jìn)行刪除。
具體實(shí)現(xiàn)
首先我們創(chuàng)建項(xiàng)目。
$ mkdir ingress-manager && cd ingress-manager $ go mod init ingress-manager # 由于控制器部分的內(nèi)容比較多,將它們單獨(dú)放到pkg目錄下 $ mkdir pkg # 最終項(xiàng)目目錄結(jié)構(gòu)如下 . ├── go.mod ├── go.sum ├── main.go └── pkg └── controller.go
接著我們來實(shí)現(xiàn)controller部分:
package pkg import ( "context" apiCoreV1 "k8s.io/api/core/v1" netV1 "k8s.io/api/networking/v1" "k8s.io/apimachinery/pkg/api/errors" metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" informersCoreV1 "k8s.io/client-go/informers/core/v1" informersNetV1 "k8s.io/client-go/informers/networking/v1" "k8s.io/client-go/kubernetes" coreV1 "k8s.io/client-go/listers/core/v1" v1 "k8s.io/client-go/listers/networking/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" "reflect" "time" ) const ( workNum = 5 // 工作的節(jié)點(diǎn)數(shù) maxRetry = 10 // 最大重試次數(shù) ) // 定義控制器 type Controller struct { client kubernetes.Interface ingressLister v1.IngressLister serviceLister coreV1.ServiceLister queue workqueue.RateLimitingInterface } // 初始化控制器 func NewController(client kubernetes.Interface, serviceInformer informersCoreV1.ServiceInformer, ingressInformer informersNetV1.IngressInformer) Controller { c := Controller{ client: client, ingressLister: ingressInformer.Lister(), serviceLister: serviceInformer.Lister(), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ingressManager"), } // 添加事件處理函數(shù) serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: c.addService, UpdateFunc: c.updateService, }) ingressInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ DeleteFunc: c.deleteIngress, }) return c } // 入隊(duì) func (c *Controller) enqueue(obj interface{}) { key, err := cache.MetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) } c.queue.Add(key) } func (c *Controller) addService(obj interface{}) { c.enqueue(obj) } func (c *Controller) updateService(oldObj, newObj interface{}) { // todo 比較annotation // 這里只是比較了對象是否相同,如果相同,直接返回 if reflect.DeepEqual(oldObj, newObj) { return } c.enqueue(newObj) } func (c *Controller) deleteIngress(obj interface{}) { ingress := obj.(*netV1.Ingress) ownerReference := metaV1.GetControllerOf(ingress) if ownerReference == nil { return } // 判斷是否為真的service if ownerReference.Kind != "Service" { return } c.queue.Add(ingress.Namespace + "/" + ingress.Name) } // 啟動(dòng)控制器,可以看到開了五個(gè)協(xié)程,真正干活的是worker func (c *Controller) Run(stopCh chan struct{}) { for i := 0; i < workNum; i++ { go wait.Until(c.worker, time.Minute, stopCh) } <-stopCh } func (c *Controller) worker() { for c.processNextItem() { } } // 業(yè)務(wù)真正處理的地方 func (c *Controller) processNextItem() bool { // 獲取key item, shutdown := c.queue.Get() if shutdown { return false } defer c.queue.Done(item) // 調(diào)用業(yè)務(wù)邏輯 err := c.syncService(item.(string)) if err != nil { // 對錯(cuò)誤進(jìn)行處理 c.handlerError(item.(string), err) return false } return true } func (c *Controller) syncService(item string) error { namespace, name, err := cache.SplitMetaNamespaceKey(item) if err != nil { return err } // 獲取service service, err := c.serviceLister.Services(namespace).Get(name) if err != nil { if errors.IsNotFound(err) { return nil } return err } // 新增和刪除 _, ok := service.GetAnnotations()["ingress/http"] ingress, err := c.ingressLister.Ingresses(namespace).Get(name) if err != nil && !errors.IsNotFound(err) { return err } if ok && errors.IsNotFound(err) { // 創(chuàng)建ingress ig := c.constructIngress(service) _, err := c.client.NetworkingV1().Ingresses(namespace).Create(context.TODO(), ig, metaV1.CreateOptions{}) if err != nil { return err } } else if !ok && ingress != nil { // 刪除ingress err := c.client.NetworkingV1().Ingresses(namespace).Delete(context.TODO(), name, metaV1.DeleteOptions{}) if err != nil { return err } } return nil } func (c *Controller) handlerError(key string, err error) { // 如果出現(xiàn)錯(cuò)誤,重新加入隊(duì)列,最大處理10次 if c.queue.NumRequeues(key) <= maxRetry { c.queue.AddRateLimited(key) return } runtime.HandleError(err) c.queue.Forget(key) } func (c *Controller) constructIngress(service *apiCoreV1.Service) *netV1.Ingress { // 構(gòu)造ingress pathType := netV1.PathTypePrefix ingress := netV1.Ingress{} ingress.ObjectMeta.OwnerReferences = []metaV1.OwnerReference{ *metaV1.NewControllerRef(service, apiCoreV1.SchemeGroupVersion.WithKind("Service")), } ingress.Namespace = service.Namespace ingress.Name = service.Name ingress.Spec = netV1.IngressSpec{ Rules: []netV1.IngressRule{ { Host: "example.com", IngressRuleValue: netV1.IngressRuleValue{ HTTP: &netV1.HTTPIngressRuleValue{ Paths: []netV1.HTTPIngressPath{ { Path: "/", PathType: &pathType, Backend: netV1.IngressBackend{ Service: &netV1.IngressServiceBackend{ Name: service.Name, Port: netV1.ServiceBackendPort{ Number: 80, }, }, }, }, }, }, }, }, }, } return &ingress }
接下來我們來實(shí)現(xiàn)main:
package main import ( "ingress-manager/pkg" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" ) func main() { // 獲取config // 先嘗試從集群外部獲取,獲取不到則從集群內(nèi)部獲取 var config, err = clientcmd.BuildConfigFromFlags("", clientcmd.RecommendedHomeFile) if err != nil { clusterConfig, err := rest.InClusterConfig() if err != nil { panic(err) } config = clusterConfig } // 通過config創(chuàng)建 clientSet clientSet, err := kubernetes.NewForConfig(config) if err != nil { panic(err) } // 通過 client 創(chuàng)建 informer,添加事件處理函數(shù) factory := informers.NewSharedInformerFactory(clientSet, 0) serviceInformer := factory.Core().V1().Services() ingressInformer := factory.Networking().V1().Ingresses() newController := pkg.NewController(clientSet, serviceInformer, ingressInformer) // 啟動(dòng) informer stopCh := make(chan struct{}) factory.Start(stopCh) factory.WaitForCacheSync(stopCh) newController.Run(stopCh) }
測試
首先創(chuàng)建deploy和service:
apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: app: my-nginx template: metadata: labels: app: my-nginx spec: containers: - name: my-nginx image: nginx:1.17.1 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: my-nginx labels: app: my-nginx spec: ports: - port: 80 protocol: TCP name: http selector: app: my-nginx
創(chuàng)建完成后進(jìn)行查看:
$ kubectl get deploy,service,ingress NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/my-nginx 1/1 1 1 7m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 78d service/my-nginx ClusterIP 10.105.32.46 <none> 80/TCP 7m
上面的命令我分別獲取deploy
,service
,ingress
,但是只獲取到了deploy
和service
,這符合我們的預(yù)期。接著我們給service/m-nginx中的annotations
添加ingress/http: nginx
:
$ kubectl edit service/my-nginx apiVersion: v1 kind: Service metadata: annotations: ingress/http: nginx kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"my-nginx"},"name":"my-nginx","namespace":"default"},"spec":{"ports":[{"name":"http","port":80,"protocol":"TCP"}],"selector":{"app":"my-nginx"}}} ...... service/my-nginx edited
重新進(jìn)行查看:
$ kubectl get deploy,service,ingress NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/demo-deployment 1/1 1 1 41d deployment.apps/my-nginx 1/1 1 1 11m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 78d service/my-nginx ClusterIP 10.105.32.46 <none> 80/TCP 11m NAME CLASS HOSTS ADDRESS PORTS AGE ingress.networking.k8s.io/my-nginx <none> example.com 80 19s
接著我們再來測試下,將ingress/http: nginx
注釋掉,看看ingress是否會(huì)自動(dòng)刪除:
$ kubectl get deploy,service,ingress NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/demo-deployment 1/1 1 1 41d deployment.apps/my-nginx 1/1 1 1 19m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 78d service/my-nginx ClusterIP 10.105.32.46 <none> 80/TCP 19m
我們發(fā)現(xiàn)和我們預(yù)期的效果一樣。
如果service被刪除了,ingress肯定也是不會(huì)存在的。這個(gè)這里就不多演示了。有興趣可以自行測試下。
原文鏈接:https://juejin.cn/post/7096297114812514318
相關(guān)推薦
- 2022-05-08 Python?轉(zhuǎn)換數(shù)據(jù)類型函數(shù)和轉(zhuǎn)換數(shù)據(jù)類型的作用_python
- 2022-06-12 Unity?使用tiledmap解析地圖的詳細(xì)過程_C#教程
- 2022-10-23 C#中const,readonly和static關(guān)鍵字的用法介紹_C#教程
- 2022-09-25 創(chuàng)建的對象如何在堆區(qū)分配內(nèi)存
- 2023-03-19 C語言利用goto語句設(shè)計(jì)實(shí)現(xiàn)一個(gè)關(guān)機(jī)程序_C 語言
- 2022-09-20 go語言VScode?see?'go?help?modules'?(exit?status?1)問題
- 2022-06-02 OnZoom基于Apache?Hudi的一體架構(gòu)實(shí)踐解析_服務(wù)器其它
- 2022-04-26 Python中Enum使用的幾點(diǎn)注意事項(xiàng)_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支