用Serverless Kubernetes为.NET (Core)应用保驾护航

前言

容器化对现在(0202年)来说,已经不算是什么新东西了,老黄最近也在公司推动这一块的发展,有幸落地了几个项目,有.NET Core的,也有.NET Framework的。

容器化现在主流的就是docker,说到docker,51%的概率是离不开kubernetes的。

当容器数量不多的时候,可以考虑人工+半自动化的方式维护。

当容器数量多了的话,不言而喻是需要引入容器编排的利器。

这也算是一个渐进的过程吧。

下面来看看基于 Serverless Kubernetes 的简单实践(不会介绍kubernetes的相关内容哈)。

为什么选择 Serverless Kubernetes

国内云产商基本都会有提供多个版本的Kubernetes让我们自己选择,有的公司可能能力强,一套打包带走。

老黄这边选择的是 Serverless 版的,Serverless 可以说是比较火的一个概念,也可以说是真正的云原生所应该有的基本形态。

当然老黄做出这个决定还有一个更重要的原因,不用自己维护服务器,可以更加专注自身的业务,只需要交付打包好的镜像即可。

毕竟老黄公司没有运维,已经充当半个运维了,不想让自己累趴。。

相信这个是大部分中小型公司一个比较ok的选择。

准备镜像

这里会写两个简单Web Api项目用来演示,一个.NET Core的,一个.NET Framework的。

其中.NET Core会暴露给集群外部访问,.NET Framework只在集群内部访问,同时.NET Core还会调用.NET Framework的接口。

.NET Framework

代码的话就是一个默认的ValuesController。

public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2", "nfx in k8s" };
    }
}

知道.NET Framework可以基于jexus跑在Linux下面的话,应该就知道要怎么打包制作镜像了。

可以直接用 beginor 做好的镜像 beginor/jexus-x64:6.2.1.12。

ps: 老黄这边是因为有不少项目需要用到图片,所以是自己单独弄了一个,加了libgdiplus 等一些必备的东西进去。

先准备一个jexus的配置文件,这里用的是最简单的。

port=80
root=/ /app
hosts=*

然后就是Dockerfile了

# 按需替换
FROM jexus-x64-img:6.1 AS base
# FROM beginor/jexus-x64:6.2.1.12 AS base

FROM mono:6.8 AS build
WORKDIR /src
COPY . .
# 还原nuget包
RUN nuget restore ServerlessNetApp.sln -Source https://api.nuget.org/v3/index.json
# 编译
RUN msbuild NfxApi/NfxApi.csproj /t:ReBuild /p:Configuration=Release /p:OutDir=/src/out /p:DeleteExistingFiles=True /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem

FROM base AS final
WORKDIR /app
# 把发布文件复制过来
COPY --from=build /src/out/_PublishedWebsites/NfxApi /app
COPY ./nfxweb /usr/jexus/siteconf/default
# 按需放开
# RUN sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf
CMD [ "jws", "restart"]

build一下,打包出一个镜像

docker build -t nfxapi:v1 -f ./Dockerfile.nfx .

还有重要的一步是,推送到镜像仓库,这样容器服务那边才可以拉取到。

.NET Core

代码也是很简单的,多了一个用HttpClient调用.NET Framework的接口就是了。

[ApiController]
[Route("")]
public class GwController : ControllerBase
{
    private readonly IHttpClientFactory _factory;

    public GwController(IHttpClientFactory factory)
    {
        _factory = factory;
    }

    [HttpGet]
    public string Get()
    {
        return $"gw-svc in k8s";
    }

    [HttpGet("svc")]
    public async Task<string> GetAsync()
    {
        var client = _factory.CreateClient();
        
        // 请求上面的.NET Framewore 项目
        // 用服务名的方式来处理服务发现
        var resp = await client.GetAsync("http://api-nfx-svc/api/values");
        resp.EnsureSuccessStatusCode();
        var res = await resp.Content.ReadAsStringAsync();

        return $"ok - {res}";
    }
}

下面这个Dockerfile应该好熟悉的了

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY . .
RUN dotnet restore "ServerlessNetApp.sln"
WORKDIR /src/GwApi
RUN dotnet build "GwApi.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "GwApi.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

# 按需放开
#RUN sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf
ENTRYPOINT ["dotnet", "GwApi.dll"]

同样的要把它打包成镜像推送到镜像仓库。

docker build -t ncapi:v1 -f ./Dockerfile.nc .

运行起来

运行起来的话就是准备一些yml文件了,这里就贴出部分内容,具体的可以去github看。

先来看看Deployment和Service。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: api-nfx-svc
  namespace: test
  labels:
    app: api-nfx-svc
spec:
  replicas: 1
  selector:
    matchLabels:
      app: api-nfx-svc
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: api-nfx-svc
    spec:
      containers:
           image: >-
            镜像仓库地址/api-nfx:v1
           imagePullPolicy: IfNotPresent
           name: api-nfx
           resources:
             requests:
               cpu: 250m
               memory: 512Mi
# 省略部分 ...
---
apiVersion: v1
kind: Service
metadata:
  name: api-nfx-svc
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: api-nfx-svc
  clusterIP: None
  sessionAffinity: None

因为要把.NET Core项目暴露出去,让外部访问,所以我们还要有一个Ingress。

这里的话是基于阿里云的负载均衡,没有用ingress-nginx或ingress-traefik,不那么通用。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    # 指定 SLB 的Id
    service.beta.kubernetes.io/alicloud-loadbalancer-id: lb-xxxxxxx
  name: gw-ingress
  namespace: test
spec:
  rules:
    - host: ncgw.xxx.com
      http:
        paths:
          - backend:
              serviceName: api-nc-svc
              servicePort: 80
            path: /
status:
  loadBalancer:
    ingress:
      - ip: xxx.xxx.xxx.xxxx

依次把服务,路由跑起来。

kubectl apply -f ../k8s/svc-nfx.yml

kubectl apply -f ../k8s/svc-nc.yml

kubectl apply -f ../k8s/ingress.yml

然后就可以看到在控制台的无状态中出现了我们刚才创建的两个应用。

ps: 老黄手抖,把应用名加了个-svc。。。

服务还有路由

最后就是负载均衡

最后通过DNS解析一下域名到这个负载均衡的IP就可以了。

这个时候应用已经跑起来了,现在是只暴露出了.NET Core的项目可以给外面的访问

访问.NET Core项目的这个地址, http://ncgw.domain.com/svc, 就是通过SLB->集群里的.NET Core->集群里面.NET Framework

一些额外细节

阿里云的serverless kubernetes是基于弹性容器实例(ECI)的,所以最终创建的pod是运行在ECI里面的。

下面是ECI的一个列表。

对比一下两者的监控,

先是.NET Core的

再看看.NET Framwork的

.NET Framework的项目确实是占用的资源多一点。

进去容器里面看看吧。

进来容器里面,其实就是进入了集群环境了。

我们同样可以通过服务名去访问到对应的服务了。

最后一个就是服务发现是基于DNS的。

写在最后

serverless kubernetes用起来确实比较方便,省了很多不必要的麻烦,不过也是踩着坑过来的,坑踩多了,也就可以轻车熟路了。

这里也只是演示了最简单的应用,还有水平伸缩(HPA),日志,监控等一系列的内容,这里是没有提及到的。

可能有人会问:

  1. 为什么还有.NET Framework,不直接.NET Core?

很多老业务不是说动就能动的那么干脆,毕竟还有数据库的限制。。。

  1. .NET Framework容器化有什么坑?

只要你代码没问题,可以在linux下面跑,那就没什么坑不坑的,要是用了一些不支持的特性,那谁也救不了的。

  1. serverless kubernetes的其他问题

可以参靠各大云产商的官方文档和提工单咨询。

最后就是这篇文章的代码可以在我的github查阅:

ServerlessNetApp

原文地址:https://www.cnblogs.com/catcher1994/p/13258780.html