概要
1. 什么是Docker,Kubernetes?
2. 如何在Docker容器中运行.Net Core 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 Core WebAPI?
.Net Core 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的版本更新。