Azure App Service 多容器运行以及代码直接部署

概要

1. 多容器以及代码直接部署
2. 部署过程

多容器以及代码直接部署

Azure App Service中同时运行多个容器

在之前的博客中我们提到过,App Service本质上以容器的方式运行在一个Azure VM中,只是根据选择的Service Plan不同在网络环境以及硬件上有所区别。由此,一个App Service理所当然可以同时运行多个容器。主要的实现方式为使用Docker Compose构建规划多容器的运行。

什么是Docker Compose

Docker Compose是一个用于定义和运行多容器 Docker 的应用程序工具,在目录中添加yaml文件,列出所有需要运行的容器及其相关信息如版本,运行命令等。最后使用docker-compose up即可开始运行多个容器。

Azure App Service中Code直接部署

Code直接部署即直接由本地开环境的代码一步直接部署至Azure。相较于其他部署方式,Code直接部署更快更方便。真正实现了无需任何前置操作的“一键部署”。

部署过程

Code直接部署

Code直接部署这里以C#为例,本文使用了一个.Net Core 3.1的WebAPI工程,Visual Studio中简单构建代码后,直接打开需要发布工程的Publish发布标签,选择App Service为目标。

在VS中登陆需要发布到目标的Azure账号,在创建发布模板时即可通过选择账号下的Subscription订阅、Resource Group资源组,找到对应的App Service(笔者已经先行在Azure中创建了一个App Service实例)。

点击发布,即可以看到Output中的开始输出的发布过程,首先在本地生成DLL等文件,在App Service中创建目录,传输至对应的目录。

笔者由于需要在K8s中测试负载均衡的原因,在WebAPI中添加了一个接口/api/ServerInfo, 线程挂起5s后返回当前主机IP。
使用POSTMAN 请求接口 https://vicdemocode.azurewebsites.net/api/serverinfo

这里返回的是App Service实例在资源组中的内网地址。

到此,就实现了一个WebAPI至App Service的Code方式部署。

单实例运行多容器

与单容器部署类似,多容器的部署只是选择了以Docker Compose的方式完成对实例的设置。在Container Setting中选择Docker Compose,选择上传yaml文件或直接键入文件内容,运行后即可在下方日志看到实例的运行情况。

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的版本更新。