微服務個人覺得是個非常復雜又龐大的體系,比如要有完備的監控平台、分布式日志收集系統、權限控制、服務治理,各服務應該高度自治、服務注冊於發現、節點動態擴縮容、熔斷降級,限流等等。標准的微服務架構涉及到這么多的技術點,如果沒有巨人的肩膀依靠,我相信一般的公司都很難實施,好在google這個巨人幫我們實現了,它就是k8s。官網介紹,k8s源自Google15年生產環境的運維經驗提供的最佳實踐,可見一斑。概念性的東西就不介紹了(主要是我懂的也不多,太龐大),優勢就是上面說到的標准微服務架構涉及到的技術點,這哥們本身就基於標准微服務架構實現的。我這里簡單說下它和docker的關系。k8s容器集群管理系統,當然docker社區也提供類似的集群管理系統,docker三劍客之一docker-swarm,這個哥們設計有缺陷(大起大落上不了規模),所以后面就不了了之了。說到三劍客我們還接觸到一個容器編排docker-compose,三劍客現在還在用的應該就只有它了,集成測試環境的編排系統,它只能單機,同一個docker-compose里面的容器在一個docker網絡里面(當然可以通過其他手段實現),它跟docker的關系,個人覺得就是套娃的關系。閑聊就到這。下面看下整個服務部署的實驗環境。
通信主機所有通信主機都是基於vm虛擬機實現,看圖


簡單解說一下:
1.所有虛擬主機基於ubuntu-server18.4這個版本,好像最新版是20.4;
2.截圖里面有4台虛擬主機,其中org是對安裝k8s准備的公共系統環境,比如配置網絡、安裝k8s工具、docker等;
3.master和node節點都是基於org完整克隆出來的,這樣可以減少一定的工作量吧;
4.搭建k8s集群最少需要10個g的內存,這里還不包括master高可用,master2g,node4g,這是常規練手部署,也可以不按套路出牌,master和slave交叉部署,也是可以的;
下面我們通過命令看看node信息。
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get node 2 NAME STATUS ROLES AGE VERSION 3 kubernetes-master Ready master 3d15h v1.18.2 4 kubernetes-node01 Ready <none> 3d15h v1.18.2 5 kubernetes-node02 Ready <none> 3d15h v1.18.2 6 root@kubernetes-master:/usr/local/k8s-test01/product#
服務部署結構圖:


簡單介紹下,
1.在最前端的是網關也是代理(ingress-nginx-controller),它是k8s的網關入口實現,除了ingress-nginx還有很多,可以參考
https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/ ,它的高可用可通過HA或者keepalived實現,在這個實驗里面我沒有去實現,其實配置起來很簡單,通過shell腳本檢查nginx是否可用,對外提供vip地址,基於vrrp路由冗余協議實現,當然這個網關也是在k8s這個集群里面的,圖沒有顯示層級出來。
2.網關后面就是我們的service服務,我這里部署的是產品服務ProductService,整個service的網絡模型我部署成了ClusterIp類型,這是k8s對內訪問的三種模型之一。這個模型有個特點外面的服務不能直接訪問里面pod之上的容器,所以我們可以把它理解為一個封閉的網絡,由ingress-nginx代理,而它們的網絡是在k8s這個大型子網里面,通過coredns(k8s里面的組件)組件解析(當然這里面還有CNI的實現者網橋的介入,甚至還有docker網橋)。下面我們直接部署吧。
部署
我們着重看ProductService的部署吧,其他的k8s集群環境准備就不贅述了,分兩種,adm鏡像和二進制,adm比較簡單。我簡單說下,部署productservice我的一個思路,先是通過工程dockerfile構建鏡像,當然這些可以通過自動構建和部署,然后通過deployment資源管理器創建pod資源,最后部署service代理。這里簡單畫個圖理解一下。


這里簡單介紹下這里面涉及到的幾個資源:
1.在k8s集群里面master主要負責控制和管理node節點,node為工作負載節點,干活就是它,也就是容器的執行是在它里面的pod之上。
2.deployment是資源控制管理器,比如動態擴縮容,滾動更新,回滾等等操作,在k8s里面有多種資源控制器,比如rs、rc、ds、job等,rc已經被rs替代,然而deployment的功能又包含rs,所以我這里用dep。
3.service服務代理,代理誰?pod,通過label標簽匹配,為什么需要它?如果沒有它的話,pod暴露的是一個個ip,如果中間一個pod掛了,dep為了滿足我們的期望值,會重新創建一個pod,這時候出問題了,剛好service就是為了解決這個問題而誕生的,它通過標簽匹配到集群里面對應的標簽pod,監聽它們的狀態,然后把pod信息同步到service里面,提供外部服務。service代理模式分三種iptables、ipvs等。
圖片解說到這,看代碼。
1.構建鏡像 productservice
1 FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base 2 WORKDIR /app 3 EXPOSE 80 4 EXPOSE 443 5 6 FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build 7 WORKDIR /src 8 9 COPY "EventBus/EventBus/EventBus.csproj" "EventBus/EventBus/EventBus.csproj" 10 COPY "EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" "EventBus/EventBusRabbitMQ/EventBusRabbitMQ.csproj" 11 COPY "EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj" "EventBus/IntegrationEventLogEF/IntegrationEventLogEF.csproj" 12 COPY "Services/ProductService/Api/Product.Api.csproj" "Services/ProductService/Api/Product.Api.csproj" 13 COPY "Services/ProductService/Application/Product.Application.csproj" "Services/ProductService/Application/Product.Application.csproj" 14 COPY "Services/ProductService/Domain/Product.Domain.csproj" "Services/ProductService/Domain/Product.Domain.csproj" 15 COPY "Services/ProductService/Infrastructure/Product.Infrastructure.csproj" "Services/ProductService/Infrastructure/Product.Infrastructure.csproj" 16 17 COPY "NuGet.config" "NuGet.config" 18 WORKDIR /src/Services/ProductService/Api 19 RUN dotnet restore "Product.Api.csproj" 20 WORKDIR /src 21 COPY . . 22 WORKDIR /src/Services/ProductService/Api 23 RUN dotnet publish --no-restore -c Release -o /app 24 25 FROM build AS publish 26 27 FROM base AS final 28 WORKDIR /app 29 COPY --from=publish /app . 30 COPY --from=build /src/Services/ProductService/Proto /app/Proto 31 32 ENTRYPOINT ["dotnet", "Product.Api.dll"]
先通過dockerfire構建一份鏡像,記得打標簽。我這里把編譯也做了,當然你也可以先在vs里面發布好,然后再打包鏡像,會快一些,docker鏡像下載很蛋疼,一般有三種處理方式吧。1.配置加速地址 2.搭建鏡像私服 3.docker save和load命令。
2.通過deployment創建pod資源,今天這篇文章我不打算用helm包管理器來做,這樣里面的細節會多一些,后續下次k8s部署會全部基於helm來做,看代碼
1 apiVersion: apps/v1 2 kind: Deployment # 資源類型 3 metadata: 4 name: product-server # 名稱 5 spec: 6 replicas: 2 # pod實例數 7 selector: 8 matchLabels: 9 app: product-server 標簽 10 template: # 模板 11 metadata: 12 labels: 13 app: product-server 標簽 上下兩個標簽需要對應,表示template里面創建的這個pod屬於這個dep資源里面的 14 spec: #詳細信息 15 containers: # 容器 16 - name: product-server 17 image: myproduct-test-01:2.0 # image鏡像 18 imagePullPolicy: IfNotPresent # image拉取策略,如果本地有就不拉取,這里注意啊,這個本地不光是master節點,node節點也都需要有這個鏡像 19 ports: 20 - containerPort: 80 # 容器端口 21 name: site 22 protocol: TCP 23 - containerPort: 81 24 name: grpc 25 protocol: TCP 26 27 env: # 環境變量,也可以通過configmap實現,其實就是配置文件 28 - name: ASPNETCORE_ENVIRONMENT 29 value: "Development" 30 - name: ConnectionString 31 value: "Server=mssql;Database=product_db;User Id=sa;Password=Pass@word"
執行命令 kubectl create -f 文件名稱.yaml,接着我們通過查看命令,看看deployment信息, 在實際操作的時候,可能會因為pull鏡像導致狀態有問題,我一般是先把鏡像處理好,再在本地拉取鏡像。這里需要注意一點小知識點,pod的生命周期和探針,探針分兩種,就緒和存活探測兩種,這里我為什么要提這個呢?因為在微服務里面,個人覺得健康檢查很重要。
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get deployment 2 NAME READY UP-TO-DATE AVAILABLE AGE 3 mssql 1/1 1 1 27h 4 product-server 2/2 2 2 6h13m 5 rabmq 2/2 2 2 13h 6 root@kubernetes-master:/usr/local/k8s-test01/product#
3.部署service代理
1 apiVersion: v1 2 kind: Service # 資源類型 3 metadata: 4 name: product-server # 名稱,這個名稱類似dockercompose里面定義的服務名 5 spec: 6 ports: 7 - name: psvc 8 port: 80 # 服務端口,提供給集群內部訪問的端口,外部訪問不了 9 targetPort: 80 # 容器端口 10 - name: grpc 11 port: 81 12 targetPort: 81 13 selector: 14 app: product-server # 標簽,與之對應的是deployment里面的pod標簽,它們是多對多的關系 15 type: ClusterIP # 內部網絡
執行命令 kubectl create -f 文件名稱.yaml,接下來我們可以查看service信息。
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get service 2 NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 3 kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d15h 4 mssql LoadBalancer 10.104.37.195 <pending> 1433:31227/TCP 26h 5 product-server ClusterIP 10.107.6.57 <none> 80/TCP,81/TCP 6h10m 6 rabmq NodePort 10.102.48.159 <none> 15672:31567/TCP,5672:30567/TCP 13h 7 root@kubernetes-master:/usr/local/k8s-test01/product#
deployment和service配置文件可以放在一個yaml文件里面,通過---分開就行,這里分開是為了看起來有層次。
好了,productservice部署完畢了,我們的服務是部署完畢了,但是我們在外部訪問不了,需要入口網關,這里我們先使用ingress-nginx-controller。
部署ingress
1.安裝ingress-nginx,這里我用的是最新版0.30.0,很是郁悶,下載地址被牆了,最后在github開源代碼里面找到安裝資源,其實就是一份yaml文件。
1 # 其他... 2 apiVersion: apps/v1 3 kind: Deployment 4 metadata: 5 name: nginx-ingress-controller 6 namespace: ingress-nginx 7 labels: 8 app.kubernetes.io/name: ingress-nginx 9 app.kubernetes.io/part-of: ingress-nginx 10 spec: 11 replicas: 1 12 selector: 13 matchLabels: 14 app.kubernetes.io/name: ingress-nginx 15 app.kubernetes.io/part-of: ingress-nginx 16 template: 17 metadata: 18 labels: 19 app.kubernetes.io/name: ingress-nginx 20 app.kubernetes.io/part-of: ingress-nginx 21 annotations: 22 prometheus.io/port: "10254" 23 prometheus.io/scrape: "true" 24 spec: 25 # wait up to five minutes for the drain of connections 26 terminationGracePeriodSeconds: 300 27 serviceAccountName: nginx-ingress-serviceaccount 28 nodeSelector: 29 kubernetes.io/os: linux 30 containers: 31 - name: nginx-ingress-controller 32 image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.30.0 33 args: 34 - /nginx-ingress-controller 35 - --configmap=$(POD_NAMESPACE)/nginx-configuration 36 - --tcp-services-configmap=$(POD_NAMESPACE)/tcp-services 37 - --udp-services-configmap=$(POD_NAMESPACE)/udp-services 38 - --publish-service=$(POD_NAMESPACE)/ingress-nginx 39 - --annotations-prefix=nginx.ingress.kubernetes.io 40 # ......
安裝ingress-nginx ,執行命令 kubectl create -f ingress-nginx.yaml,隨后我們通過命令查看。
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get pods -n ingress-nginx 2 NAME READY STATUS RESTARTS AGE 3 nginx-ingress-controller-77db54fc46-tx6pf 1/1 Running 5 40h 4 root@kubernetes-master:/usr/local/k8s-test01/product#
接下來配置並發布ingress-nginx網關服務。
1 apiVersion: networking.k8s.io/v1beta1 2 kind: Ingress 3 metadata: 4 name: nginx-web 5 annotations: # 擴展信息,這里可以配置鑒權、認證信息 6 nginx.ingress.kubernetes.io/rewrite-target: / 7 spec: 8 # 路由規則 9 rules: 10 # 主機名,只能是域名,需要在宿主機配置hosts映射 11 - host: product.com 12 http: 13 paths: 14 - path: / 15 backend: 16 # 后台部署的 Service Name,與上面部署的service對應 17 serviceName: product-server 18 # 后台部署的 Service Port,與上面部署的service對應 19 servicePort: 80 20 - path: /grpc 21 backend: 22 # 后台部署的 Service Name,與上面部署的service對應 23 serviceName: product-server 24 # 后台部署的 Service Port,與上面部署的service對應 25 servicePort: 81
執行kubectl create -f productservice-ingress.yaml部署。接下來我們查看網關服務信息。
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get ingress 2 NAME CLASS HOSTS ADDRESS PORTS AGE 3 nginx-web <none> product.com 80 8h
mssql和rabbitmq的部署這里就貼代碼了,接下來我們訪問product.com這個域名,看看是否部署成功。


我們測試一個get接口試試,看看能不能通,看圖。


服務部署就到這,接下來我們簡單總結一下
1.這是測試環境所以master沒做高可用,ingress也沒做高可用,有時間再做順便補充一下;
2.外部網絡請求到ingress-nginx域名,線上環境這個域名肯定是公網地址,ingress做認證鑒權,合法的請求通過path路由到對應的后台service,如果其中一台ingress掛掉了,keepalived會把vip游離到其他slave節點,這樣就完成了ingress的高可用;
3.service代理會把請求隨機轉發到標簽匹配的pod里面的容器處理,如果其中一台node掛了或者pod異常退出了(也就是返回值不等於0),deployment會重新啟動一個pod,我們下面做個實驗,刪掉其中一個pod,看看效果怎么樣。
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get pods 2 NAME READY STATUS RESTARTS AGE 3 mssql-59bd4dc6df-xzxc2 1/1 Running 5 27h 4 product-server-599cfd85cc-2q7zx 1/1 Running 0 6s 5 product-server-599cfd85cc-ppmhx 1/1 Running 0 6h51m 6 rabmq-7c9748f876-9msjg 1/1 Running 0 14h 7 rabmq-7c9748f876-lggh6 1/1 Running 0 14h
我們先通過命令顯示所有default命名空間下面的所有pod,然后delete一個pod看看,它會不會重新啟動。執行刪除命令
1 kubectl delete pods product-server-599cfd85cc-ppmhx
接着馬上查看pods信息,要快知道嗎?
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl get pods 2 NAME READY STATUS RESTARTS AGE 3 mssql-59bd4dc6df-xzxc2 1/1 Running 5 27h 4 product-server-599cfd85cc-2q7zx 1/1 Running 0 2m8s 5 product-server-599cfd85cc-9s497 1/1 Running 0 13s 6 product-server-599cfd85cc-ppmhx 0/1 Terminating 0 6h53m 7 rabmq-7c9748f876-9msjg 1/1 Running 0 14h 8 rabmq-7c9748f876-lggh6 1/1 Running 0 14h
看到沒?ppmhx這個pod正在終止,馬上就創建了新的pod。牛逼吧,強大吧。這只是它的冰山一角,我們還可以通過HPA實現動態擴縮容,比如cpu達到多少負載,水平擴展pod,或者刪除pod。
最后提一下,碰到坑如何處理?
我一般是先通過命令查看大概信息
1 root@kubernetes-master:/usr/local/k8s-test01/product# kubectl describe pods product-server-599cfd85cc-2q7zx 2 Name: product-server-599cfd85cc-2q7zx 3 Namespace: default 4 Priority: 0 5 Node: kubernetes-node01/192.168.44.210 6 Start Time: Fri, 08 May 2020 00:31:31 +0800 7 Labels: app=product-server 8 pod-template-hash=599cfd85cc 9 Annotations: cni.projectcalico.org/podIP: 10.244.185.145/32 10 cni.projectcalico.org/podIPs: 10.244.185.145/32 11 Status: Running 12 IP: 10.244.185.145 13 IPs: 14 IP: 10.244.185.145 15 Controlled By: ReplicaSet/product-server-599cfd85cc 16 Containers: 17 product-server: 18 Container ID: docker://bfc6ab23a5e228b85960322d3ea57321ed4c51cd4ad413a3db1a8452e4f52bea 19 Image: myproduct-test-01:2.0 20 Image ID: docker://sha256:eab1c9f80b5f64adb368f1e3d3f2955597f90c0badd2ba81fc714a6774a0e473 21 Ports: 80/TCP, 81/TCP 22 Host Ports: 0/TCP, 0/TCP 23 State: Running 24 Started: Fri, 08 May 2020 00:31:33 +0800 25 Ready: True 26 Restart Count: 0 27 其他 ....
這里面描述了很多信息,如果遇到錯誤,也會有大概的信息提示,如果通過這里的信息提示不能排錯,我一般就會去看容器的日志信息,通過命令kubectl logs pod名稱,這個就不演示。
最后簡單總結一下吧,關於k8s站在我們應用的角度來說,不是太難,但是非運維人員要把它玩好,我覺得有點勉強,不是我們接受能力差,還是我們開發人員沒時間玩它。太龐大,知識面太多,比如你要完k8s你先的熟悉一種容器化技術吧,網絡,等等,還有坑太多,還可能要FQ啥的,最讓我吐槽的是各種鏡像下載超時,有時候被牆了,還要各種想辦法拿資源,這里哪位朋友方便介紹給我一個FQ軟件,最好是收費的。謝謝,就這樣把。下次把監控、ELK、PVC等補上。