服务部署之k8s
微服务个人觉得是个非常复杂又庞大的体系,比如要有完备的监控平台、分布式日志收集系统、权限控制、服务治理,各服务应该高度自治、服务注册于发现、节点动态扩缩容、熔断降级,限流等等。标准的微服务架构涉及到这么多的技术点,如果没有巨人的肩膀依靠,我相信一般的公司都很难实施,好在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等补上。