Docker + Kubernetes 部署.Net Core3 WebAPI

概要

1. 什么是Docker,Kubernetes?
2. 如何在Docker容器中运行.Net Core3 WebAPI?
3. 如何使用K8s管理Docker容器并使用负载均衡服务?

1.什么是Docker,Kubernetes?

Docker

这里推荐一篇Docker结构相关文章
1. 为什么要使用Docker,为什么要在容器中运行app?

使用Docker,我们可以在不同的操作系统中对应用完成更快,更方便的部署。
使用容器,可以为应用创建独立,安全的运行环境。

2. 什么是container,image,repository?

container是一个应用的运行实例,包括所有的依赖以及运行库。
image是container的标准副本。
repository是image的托管库。

3. 如何将应用转化为image,从而可以在Docker中作为一个容器运行?

首先在本地构建完成应用并成功运行,在项目中新建文件Dockerfile(一般为根目录),从Docker.io中找到所需的运行环境,sdk,runtime等。在Dockerfile写入用于创建image的流程,引用运行环境,拷贝项目代码至上下文,最后build。

使用Docker cli 创建image,并上传至repo。

Kubernetes

1. 什么是kubernetes,为什么还要使用kubernetes来管理容器?

Kubernetes是一个用于发布,管理容器的系统。

使用Kubernetes来管理容器,可以实现更加高效,安全的应用管理,例如应用的always restart,load balance,auto scaling,反向代理,不间断服务的更换版本等。

2. 什么是cluster,pod,service,ingress,node?

cluster是整个系统的总称,包含kubernetes运行的所有单元
cluster中包含两种node,唯一的master节点和多个子节点
master节点中运行整个kubernetes的管理系统,系统api
Ingress为所有service提供反向代理,分发流量
service为选中的deployment提供访问入口,也可以包含负载均衡的功能
pod是node中的运行单元,pod中可以运行单个或多个容器(一般为单个,可能在容器间互相通信或存在代理服务等情况下运行多个)。通常每个pod都是分布式系统中的一个主机


2.如何在Docker容器中运行.Net Core3 WebAPI?

.Net Core3 WebAPI

由于关系到容器创建过程,这里先简单介绍后端工程结构
1. 新建 .net core3 + ef core 工程,简单创建三个layer,最底层Model,中间层Data,以及最上层WebAPI
2. model中使用efcore脚手架生成数据库表的实体类,编写自定义的异常,枚举量等
3. Data中为每个数据库封装一个repo类存放相关逻辑方法
4. 配置跨域,配置数据库连接字符串等

构建Docker镜像

进入项目的根目录下新建dockerfile,填入以下代码

#首先使用FROM AS 的指令从docker官方的镜像仓库中下载得到编译工程的SDK
FROM mcr.microsoft.com/dotnet/core/sdk:3.0 AS build
#暴露容器的80端口
EXPOSE 80
#设置/app为根目录
WORKDIR /app
#COPY方法 第一个参数为当前目录 其次是容器构建的目标目录
#将.Net工程的sln解决方案文件,以及各个依赖工程的csproj项目文件拷如容器对应目录为后面build做准备
COPY \*.sln .
COPY BackEndDemo/\*.csproj ./BackEndDemo/
COPY BackEndDemo.Data/\*.csproj ./BackEndDemo.Data/
COPY BackEndDemo.Model/\*.csproj ./BackEndDemo.Model/
#运行detnet指令还原项目所有的nuget依赖
RUN dotnet restore
#将所有的代码等文件拷入容器中
COPY BackEndDemo/. ./BackEndDemo/
COPY BackEndDemo.Data/. ./BackEndDemo.Data/
COPY BackEndDemo.Model/. ./BackEndDemo.Model/
WORKDIR /app/BackEndDemo
#打包发布整个工程
RUN dotnet publish -c Release -o out
#从官方仓库引入aspnetcore3的运行环境
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0 AS runtime
WORKDIR /app
#将打包发布得到的dll等文件拷入 /out./ 目录
COPY --from=build /app/BackEndDemo/out ./
#设置入口dll为BackEndDemo(即最上层的webapi)
ENTRYPOINT ["dotnet", "BackEndDemo.dll"]

打开cmd,使用docker命令构建镜像,填入名称以及标签,然后上传镜像至仓库

docker build -t <repoName/imageName>:<tagName> .
docker push <repoName/imageName>:<tagName>

发布镜像并运行容器

使用run指令本地运行工程

docker run -it --rm -p 44353:80 --name parainstance1 ymhvic/para

44353:80 表示宿主机的44353端口映射至容器的80端口(即在dockfile中暴露的端口),填入运行实例名称以及要运行的镜像名称

容器在本地运行,访问宿主机ip即localhost:44353即可以访问到接口


3.如何使用K8s管理Docker容器并使用负载均衡服务?

搭建MiniKube

前面已经实现了后端程序至docker容器的打包,现在要将容器放入minkube master节点,并为每一个容器分配一个pod。

首先为了在本地运行kubernetes,选用minkube运行hyperV虚拟机,minkube的作用即配置并为虚拟机注入了K8s的运行环境

开始构建minikube,cmd运行以下命令

minikube start --vm-driver=hyperv --image-repository=registry.cn-hangzhou.aliyuncs.com/google\_containers

由于网络原因,这里手动配置了镜像库到aliyun的节点上,构建完成后看到以下log,说明kubectl指令已经被配置到minikube中运行。

Done! kubectl is now configured to use "minikube"

之后使用kubectl查看log,pod,service即返回minikube中运行信息

发布对象至Kuberbetes

使用kubectl apply命令来上传yaml json等文件以达到部署pod,service,ingress的目的,以下是一个用于发布pod的deployment.yaml实例。

#部署对象类型: Ingress, Service, deployment..
kind: Deployment
#Kubernetes API 版本号
apiVersion: apps/v1
#信息数据
metadata:
  name: para-deployment
  #分配到cluster中的某个命名空间下
  namespace: default
spec:
  selector:
  matchLabels:
    app: para
#重复的实例数
  replicas: 2
  template:
metadata:
  labels:
    app: para
spec:
#docker容器的相关
  containers:
  - name: para
    image: 'ymhvic/para:0.12'
    ports:
    - containerPort: 80
  #重启策略
  restartPolicy: Always
  #部署目标node名称
  nodeName: minikube

deployment代表一个发布计划,包括此次发布的所有详细配置,
也可以使用kubectl create deployment 发布pod。

Service与Ingress

相应的,也需要部署一个service来访问刚刚部署的pod。一般在云服务平台中,平台将提供一个外网ip来访问创建的Kuber service,而在开发环境使用minikube时,则需要运行额外的命令来暴露创建的service。

这里在部署负载均衡类型的服务之后,需要使用以下指令来暴露服务。

minikube service <serviceName>

类型还有NodePort即为某个node提供静态的端口,ClusterIP即只暴露给同个cluster下其他的service使用的控制服务。因为使用了minikube,为了访问K8s暴露的service,需要使用

开发中我们可以直接暴露服务到外部,但进入线上还是建议使用Ingress来访问服务,Ingress即一个Ingress Controller,用于处理流量并分发,映射外部请求到级群内部资源,通常我们可以使用Nginx, Traefik, HAproxy来代替,也可以自己编写

运行中Scaling

一旦将此文件apply至k8s,则k8s将一直”维护”此文件。K8s将执行操作pod直到node中的pod运行情况与该文件描述一致,例如replicas参数,若容器运行报错或人为将pod删除等等情况使得pod数量减少,则K8s将自动增加pod数量至replicas值。

关于replicas,也可通过scale指令来调整(从而无需修改yaml),也可以设置autoscale,由k8s来根据运行情况创建或删除pod。

若需要更改,则修改deployment.yaml 并重新apply即可。

负载均衡

为了测试K8s的负载均衡类型的服务,笔者在后端写了一个接口来返回当前运行环境的IP,并在返回结果前sleep 5s,来模拟服务器长时间处理请求的情况。

由于每个pod的内网ip都不同,可以从返回结果中分辨是由哪个pod处理的请求。为了模拟接口处理请求繁忙的情况,这里使用了Thread.Sleep(5000)来挂起当前线程5s。

public async Task<ActionResult<object>> GetServerInfo()
{
    Thread.Sleep(5000);
    return Ok(System.Net.Dns.GetHostAddresses(System.Net.Dns.GetHostName())
              .Select(
                  ip => ip.ToString()
              ));
}

在1个pod以及5个pod的情况测试了同时发起100次请求的平均返回时间,发现多个pod的情况下响应速度更快,即根据运行情况分配流量到了每个pod。

可以看到单pod的100次请求平均返回时间为35.7s,5个pod带负载均衡服务下的返回时间为8.5s

版本升级

使用K8s,也可以实不中断服务的版本升级。通常情况下,更新应用版本后发布新的镜像至Dock repo。

此时,我们修改deployment.yaml中的image值,将其设为目标的镜像版本(通常通过标签区分)。保存并重新apply,如果本地不存在缓存,此时K8s会从repo pull得到镜像。创建一个使用了新版本的pod并运行,然后删去node中的某一pod,将其替换为新版本pod,此时其他pod当前一直还在处理请求,直到每个pod都被更新。

由此,就完成了一次无down time的版本更新。

发表评论

电子邮件地址不会被公开。 必填项已用*标注