用Serverless Kubernetes為.NET (Core)應用保駕護航

Catcher8發表於2020-07-07

前言

容器化對現在(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

相關文章