如何用AWS ECS Fargate部署一个.NET容器

内容纲要

了解容器与虚拟机

2013年,Docker发布,开始了我们对托管应用程序和管理基础设施的思考方式的转变。几十年来,基础设施团队和运营部门一直在利用虚拟机,并取得了巨大的成功,所以你可能会想,"这不就是一个虚拟机吗?"在精神上,是的;在应用上,不是。

要谈论容器,我们必须要谈论虚拟机。虚拟机(VM)是一台计算机的虚拟代表,从它的启动过程到加载整个操作系统(OS)的全部过程。这提供了很大的灵活性。你可以获得对整个虚拟计算机的细粒度控制,以及整个计算机的所有缺陷。操作系统必须被维护、修补和更新;磁盘必须被管理,所有的操作开销都会涉及到它们的护理和饲养。值得注意的是,虚拟机是巨大的,因为整个操作系统必须放在它们上面。因此,当你启动一个虚拟机时,你必须经历一个传统计算机的整个启动程序。

容器希望解决的主要问题有三个方面:管理的便利性、规模和启动速度。还有其他问题,但为了简单起见,我将专注于那些与业务流程最相关的问题。容器与传统的虚拟机有一个重要的不同之处。它们没有一个实际的操作系统。这对你来说可能是一个惊喜,即使你以前曾涉足过容器。相反,容器依赖于一个操作系统的抽象,它使用容器系统提供的标准和约定,提供进入主机操作系统的钩子。

Docker是所有实际用途的事实上的容器标准,但其他容器技术确实存在。标准化容器的概念来自于航运业。在航运业,没有标准化,这使得运输很困难,因为你必须弄清楚如何在船上、火车上或卡车上容纳每件物品。集装箱为移动货物提供了一个标准格式。"你可以在集装箱里放任何你想要的东西,只要它符合一套标准的尺寸,并能以统一的方式打开和栓住。"这简化了运输现实世界物品的物流过程。同样,将软件容器与主机操作系统互动的方式标准化,以委托执行任务的责任,简化了对最关键部分的管理,即应用程序。

用AWS ECS Fargate托管容器

现在我们已经对容器与虚拟机的不同之处有了更好的认识,那么这如何解决我们的三个问题呢?

首先,容器通过让我们编写构建脚本来创建一个稳定的、可重复的主机代表,使管理更加容易。这是做应用程序所需的工作主体所需要的。其次,由于一个操作系统最终托管了容器,该操作系统可以强制执行公司和安全策略。在最坏的情况下,这将是一个给定的容器能做的最多的事情。例如,如果主机操作系统只允许来自特定子网和端口的流量入站,那么容器就不能覆盖这一限制。容器最终会受到主机操作系统网络规则的约束。

这种管理的可靠性延续到数据管理、内存管理和任何其他需要强制执行的政策。最终,这里的结果是业务敏捷性。运营部门可以向开发者敞开大门,因为他们知道既定的指导原则已经到位。至于尺寸--我们的第二个关键问题领域--因为你不再需要整个操作系统,容器镜像通常小到几MB,而不是许多GB。关于速度问题--我们的第三个关键问题领域--因为没有一个完整的启动周期,所以容器可以有效地像托管应用程序一样快速启动。

容器的困难比虚拟机在抽象链上走得更远(正如这种东西的风格)。挑战归结为在许多资源上联网和管理容器镜像,这样你就可以把一组计算当作一个同质的单元。幸运的是,像Kubernetes、OpenShift、Mesos、Nomad和其他产品可以帮助你解决这些挑战。然而,你最终还是要回到管理一个计算机群以及相关的管理开销。

这就是AWS弹性容器服务(ECS)Fargate的优势所在。Fargate为你提供跨虚拟网络的网络抽象,称为VPC(虚拟私有云)。这种网络抽象是建立在AWS的核心中的,对于任何类型的工作负载,包括高安全性的政府工作负载,都是经过严格审核的。Fargate通过对机器管理的抽象化,使之更进一步。如果你愿意,你可以建立传统的集群并管理你的机器,但利用Fargate可以简化你的过程中的另一个部分。最终,使用云计算供应商的目标是让他们处理基础设施管理,这样你就可以专注于管理你的业务。

是时候跳进去,用AWS Fargate尝试一下.NET容器了!

你需要什么来开始

你所需要继续的是以下内容:

  • .NET的基本知识
  • .NET SDK 3.1
  • 一个AWS账户(你将使用一个免费级别的产品)
  • Okta CLI
  • AWS CLI V2
  • 用于Windows/MacOS的Docker桌面(如果你使用的是Linux,则不需要)。

本教程假定你已经设置并运行了Docker。

构建和dockerize一个.NET聊天应用程序

那么你想构建什么呢?为了保持简单,并看到使用标准HTTP协议以外的东西的价值,你将使用SignalR来构建一个非常基本的聊天应用程序。

  • 安全。只有登录的客户才能使用聊天功能
  • 聊天用户的名字必须来自于他们的验证身份
  • 实时性

为了实现这一目标,我将使用四种技术:

  • Okta用于身份管理
  • .NET主持应用程序
  • SignalR提供套接字管理的抽象概念
  • Vue.js提供前端的渲染。

为您的.NET聊天应用程序进行身份验证

认证在任何应用程序中都是至关重要的,但当你需要依赖某人的身份时就更重要了。Okta可以很容易地将访问权委托给需要的人,并在不良行为者获得一组凭证时关闭整个应用程序的访问。

在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register ,注册一个新的账户。如果你已经有一个账户,运行okta login 。然后,运行okta apps create 。选择默认的应用程序名称,或者根据你的需要进行更改。选择网络,然后按回车键

选择 "其他"。 然后,将重定向URI改为http://localhost:5000/authorization-code/callback ,并接受默认的注销重定向URI:http://localhost:5000 。

Okta CLI是做什么的?

Okta CLI将在您的Okta机构中创建一个OIDC网络应用。它将添加您指定的重定向URI,并授予Everyone组的访问权。当它完成后,您会看到如下输出。

Okta application configuration has been written to: /path/to/app/.okta.env

运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证。

export OKTA_OAUTH2_ISSUER="https://dev-133337.okta.com/oauth2/default"
export OKTA_OAUTH2_CLIENT_ID="0oab8eb55Kb9jdMIr5d6"
export OKTA_OAUTH2_CLIENT_SECRET="NEVER-SHOW-SECRETS"

您的Okta域是发行者的第一部分,在/oauth2/default 。

注意:您也可以使用Okta管理控制台来创建您的应用程序。

注意发行人URL客户ID。您将在下一步中需要这个。

注意:为了测试您的聊天应用程序,别忘了手动添加第二个用户到您的Okta组织。登录您的Okta机构,并导航到目录>人员页面。单击 "添加人员"按钮并填写表格。

设置一个.NET核心网络应用程序

让我们使用dotnet CLI创建一个新的ASP.NET Core Web应用程序。导航到你想创建项目的位置,运行以下命令。

dotnet new webapp -n Okta.Blog.Chat

让我们把这个项目称为Okta.Blog.Chat

现在,通过打开appsettings.json设置你的Okta应用程序凭证,并在AllowedHosts之后的JSON对象中添加以下内容。

"OktaSettings": {
  "OktaDomain": "{yourOktaDomain}",
  "ClientId": "{yourOktaClientID}",
  "ClientSecret": "{yourOktaClientSecret}"
}

注意:你可以从执行okta apps create 命令的文件夹中创建的 .okta.env 文件中找到所需的值。{yourOktaDomain} 的值应该是类似于https://dev-123456.okta.com 。请确保你的值中不包括-admin!

确保从Git中排除appsettings.json,这样你就不会意外地提交你的客户秘密。你可以通过运行命令dotnet new gitignore 来生成一个 .gitignore 文件。

现在你需要添加认证库。在Okta.Blog.Chat文件夹内运行以下命令。

dotnet add package Okta.AspNetCore

现在修改Startup.cs以使用Okta认证提供者。

修改方法ConfigureServices(IServiceCollection services) ,使其看起来像下面的代码。有一行是特意注释出来的。你将在后面使用它来进行授权。

public void ConfigureServices(IServiceCollection services)
{
    var oktaMvcOptions = new OktaMvcOptions()
    {
        OktaDomain = Configuration["OktaSettings:OktaDomain"],
        ClientId = Configuration["OktaSettings:ClientId"],
        ClientSecret = Configuration["OktaSettings:ClientSecret"],
        Scope = new List<string> { "openid", "profile", "email" },
    };

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OktaDefaults.MvcAuthenticationScheme;
    })
    .AddCookie()
    .AddOktaMvc(oktaMvcOptions);

    services.AddRazorPages()
    .AddRazorPagesOptions(options =>
    {
        //options.Conventions.AuthorizePage("/Chat");
    });
    services.AddSignalR();
}

请确保添加导入:

using Microsoft.AspNetCore.Authentication.Cookies;
using Okta.AspNetCore;
using Okta.Blog.Chat.Hubs;

这将添加认证提供者,将Chat页面设置为授权页面,并添加SignalR支持。

接下来,修改方法Configure(IApplicationBuilder app, IWebHostEnvironment env) ,使其看起来像下面的代码。有一行是特意注释出来的,你将在后面的SignalR设置中使用它。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseStaticFiles();

    app.UseRouting();

    app.UseAuthentication();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapRazorPages();
        //endpoints.MapHub<ChatHub>("/chathub");
    });
}

这里的关键区别是app.UseAuthentication(); 。

接下来,创建一个名为Hubs的新文件夹,并在该文件夹中创建一个名为ChatHub.cs的新类文件。这将提供我们的聊天后端。

using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace Okta.Blog.Chat.Hubs
{
    public class ChatHub : Hub
    {
        public async Task SendMessage(string message)
        {
            if (this.Context.User.Identity.IsAuthenticated)
                await Clients.All.SendAsync("ReceiveMessage", this.Context.User.Identity.Name, message);
        }
    }
}

你会看到在这个类中,你已经利用了用户的认证状态。这样,即使有人知道后端是SignalR,你也减轻了他们使用系统的能力,除非明确地进行认证。任何额外的授权逻辑也可以放在这里。

接下来,你需要确保你有你需要的页面。在Pages文件夹中,继续删除Privacy.cshtmlPrivacy.cshtml.cs页面,并在Pages文件夹中运行以下命令,添加一个名为Chat.cshtml的新页面。

dotnet new page -n Chat

编辑Pages/Shared/_Layout.cshtml并从中修改第二个导航项。

<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
复制代码

至此:

<a class="nav-link text-dark" asp-area="" asp-page="/Chat">Chat</a>

现在,如果你运行dotnet run 命令或从Visual Studio运行该应用程序,你应该能够导航到标题为 "Chat " 的聊天页面  在进入下一个步骤之前,确保它运行成功。

为您的.NET聊天应用程序添加Docker支持

在您添加聊天功能之前,先添加Docker支持。在根Okta.Blog.Chat文件夹下添加一个名为Dockerfile的文件,内容如下。

# build and publish the app
FROM mcr.microsoft.com/dotnet/sdk:3.1-bullseye AS build
WORKDIR /src
## copy csproj and restore as distinct layers
COPY Okta.Blog.Chat.csproj ./
RUN dotnet restore
## copy everything else and publish app
COPY . ./
RUN dotnet publish -c release -o /app --no-restore

# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:3.1-bullseye-slim AS base
WORKDIR /app
COPY --from=build /app ./
ENTRYPOINT ["dotnet", "Okta.Blog.Chat.dll"]

我们首先要构建和发布我们的应用程序。接下来,你将复制构建文件并调用ENTRYPOINT ,翻译为 "用我们发布的DLL运行dotnet"--没什么特别的,对吧?FROM 关键字使用其他构建的docker文件作为基础镜像来构建我们的镜像,所以安装.NET的工作已经完成。

现在添加一个包含以下内容的 .dockerignore 文件,这样它们就不会被复制到最终镜像中。

**/.dockerignore
**/.git
**/.vscode
**/bin
**/obj

如果你使用Visual Studio,你现在就可以在你的调试工具栏中直接调试到正在运行的容器中。如果你按下F5或点击播放按钮,你将在Docker容器中运行你的应用程序。

如果你没有使用Visual Studio,运行以下命令来构建和启动容器。

docker build . -t okta-chat
docker run -it --rm -p 5000:80 okta-chat

该应用程序应该可以在http://localhost:5000/ 。

授权您的聊天应用程序

修改Setup.cs并取消对以下行的注释。

options.Conventions.AuthorizePage("/Chat");

注意:在这一点上,如果你试图使用谷歌浏览器认证/Chat 路径,你会得到一个错误。这是因为你是在没有TLS的情况下运行应用程序的,当Secure 属性不存在时,Chrome会阻止带有SameSite=None 的set-cookie头(这将只为HTTPS请求预设)。

让我们确保我们的docker容器可以用HTTPS运行。

首先,你需要创建一个证书。你可以使用.NET CLI来创建用于开发的自签名证书。请注意,这只是为了开发目的,不应该在生产中使用。

运行下面的命令来创建一个自签名的证书。根据你的操作系统使用正确的命令,并使用一个适当的密码。

# clean existing certs if any
dotnet dev-certs https --clean
# create a new cert on macOS/Linux
dotnet dev-certs https -ep ${HOME}/.aspnet/https/aspnetapp.pfx -p mypass123
# create a new cert on Windows
dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p mypass123
# trust the cert
dotnet dev-certs https --trust

现在你可以使用下面的命令用HTTPS运行容器。你将使用你在上面创建的证书,并将其挂载为一个卷。

# macOS/Linux
docker run -it --rm -p 5000:80 -p 5001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=5001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="mypass123" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v ${HOME}/.aspnet/https:/https/ okta-chat

# Windows
docker run -it --rm -p 5000:80 -p 5001:443 -e ASPNETCORE_URLS="https://+;http://+" -e ASPNETCORE_HTTPS_PORT=5001 -e ASPNETCORE_Kestrel__Certificates__Default__Password="mypass123" -e ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx -v %USERPROFILE%\.aspnet\https:/https/ okta-chat

该应用程序现在应该可以在https://localhost:5001/ 。

在Okta开发者门户,进入应用程序>应用程序,点击你用CLI创建的应用程序名称。编辑常规设置,为签入重定向URI签出重定向URI添加URI,如下所示。

Sign-in redirect URI = https://localhost:5001/authorization-code/callback
Sign-out redirect URI = https://localhost:5001/

尝试运行该应用程序。您应该看到,要打开聊天页面,您会被重定向到Okta单点登录门户,然后被重定向回来。您现在已经成功认证了。

用Vue.js和SignalR添加聊天功能

你将使用Vue进行状态管理,因为你可以取少许或取多许。有了Vue,你。可以从一个CDN脚本标签开始,将其用于单个组件、单个页面,或在Node上使用强大的构建系统进行深度开发。对于这个练习,你将使用一个CDN托管的脚本。

但首先,你需要在后端完成一件事。

由于你已经创建了你的ChatHub.cs,打开Startup.cs并取消对以下行的注释。

endpoints.MapHub<ChatHub>("/chathub");

现在修改Chat.cshtml文件,使其看起来像这样。

@page

<div id="chatApp">
  <div class="container">
    <div class="row">
      <div class="col-2">Message</div>
      <div class="col-4">
        <input type="text" v-model="message" id="message" />
        <input type="button" v-on:click.stop.prevent="sendMessage" id="sendButton" value="Send Message" />
      </div>
    </div>
  </div>
  <div class="row">
    <div class="col-12">
      <hr />
    </div>
  </div>
  <div class="row">
    <div class="col-6">
      <ul id="messagesList">
        <li v-for="(item, index) in chatLog" :key="index">{{ item.User }} - {{ item.Message }}</li>
      </ul>
    </div>
  </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/aspnet-signalr/1.0.27/signalr.min.js"></script>
<script src="https://unpkg.com/vue@3.2.24/dist/vue.global.prod.js"></script>
<script>
  const connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();

  const VueChatApp = {
    data() {
      return {
        isConnected: false,
        message: "",
        chatLog: [],
      };
    },
    mounted() {
      connection.on("ReceiveMessage", (user, message) => {
        this.receiveMessage(user, message);
      });
      connection
        .start()
        .then(() => {
          this.isConnected = true;
        })
        .catch((err) => {
          return console.error(err.toString());
        });
    },
    methods: {
      receiveMessage(user, message) {
        this.chatLog.push({
          User: user,
          Message: message,
        });
      },
      sendMessage() {
        connection
          .invoke("SendMessage", this.message)
          .then(() => {
            this.message = "";
          })
          .catch((err) => {
            return console.error(err.toString());
          });
      },
    },
  };

  Vue.createApp(VueChatApp).mount("#chatApp");
</script>

我们使用CDN的SignalR客户端库,与ChatHub的连接使用你之前设置的端点。

const connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();

状态存储在我们Vue应用程序的数据部分。有三个属性在使用:一个让我们知道是否连接的标志,当前正在输入的信息,以及一个聊天记录。

data() {
  return {
    isConnected: false,
    message: "",
    chatLog: [],
  }
},

你的应用程序有两个方法:receiveMessage 和sendMessage 。

receiveMessage 被调用时,它将一个对象附加到聊天记录中,包括用户和消息。

sendMessage ,你使用SignalR连接来调用 "SendMessage "并传递我们的消息属性。一旦消息被发送,你就把它清空,以填充一个新的消息。

receiveMessage(user, message) {
  this.chatLog.push({
    User: user,
    Message: message,
  });
},
sendMessage() {
  connection
    .invoke("SendMessage", this.message)
    .then(() => {
      this.message = "";
    })
    .catch((err) => {
      return console.error(err.toString());
    });
},

当应用程序被创建时,为 "ReceiveMessage "添加一个钩子,调用前面描述的ViewModelreceiveMessage 方法。

然后开始连接到集线器,如果成功的话,isConnected 被设置为真。

connection.on("ReceiveMessage", (user, message) => {
  this.receiveMessage(user, message);
});
connection
  .start()
  .then(() => {
    this.isConnected = true;
  })
  .catch((err) => {
    return console.error(err.toString());
  });

如果你在这时运行你的应用程序,你将有一个安全的聊天应用程序在容器中运行

将您的.NET聊天应用程序部署到AWS上

现在,聊天应用程序已经完成,是时候部署了。首先,您需要构建docker镜像,并将其部署到AWS中使用。AWS通过其Elastic Container Registry(ECR)产品拥有私人容器库。

创建一个ECR存储库

首先,你需要对Docker文件做最后一次修改,以使TLS在Fargate上工作。

Docker文件中的dotnet publish 命令之后添加以下两行。

# build and publish the app
...
RUN dotnet publish -c release -o /app --no-restore

ARG CERT_PASSWORD
RUN dotnet dev-certs https -ep /app/aspnetapp.pfx -p ${CERT_PASSWORD}

# final stage/image
...

这将在docker镜像中创建一个自签名的开发证书,你可以用它在Fargate上用TLS运行应用程序,用于演示。

注意:这种类型的开发证书不建议在生产中使用。这里使用它是为了简化演示。对于生产设置,你应该使用一个由证书机构签署的证书。建议:要在ECS上运行一个生产的.NET应用程序,使用AWS应用负载平衡器(ALB),通过配置为使用由证书机构签署的证书的HTTPS监听器,将流量路由到反向代理(Nginx)。然后,反向代理将把流量路由到应用程序。反向代理和应用程序也应该被配置为使用有效的证书来使用TLS。

登录到你的AWS控制台并导航到ECR。

点击创建存储库

对于可见性设置,选择私有,并命名你的存储库。然后在向导的底部点击创建存储库

导航到okta-chat,点击查看推送命令。这里有所有你需要的步骤,以建立和推送你的图像到ECR。

按照步骤,将图像推送到ECR。请确保将--build-arg CERT_PASSWORD=mypass123 ,就像下面这样,传递给docker build 命令。

docker build --build-arg CERT_PASSWORD=mypass123 -t okta-chat .

在你的图像旁边的URI栏中选择复制按钮,并保存起来备用。这是你的图像的路径。

设置ECS Fargate集群

现在设置你的ECS集群。

在左边的菜单上,点击Amazon ECS下的集群,然后点击创建集群按钮。

点击选项中的Networking Only>Next step

我把我的集群命名为 "okta-test",你可以按照你认为合适的方式命名,然后在下面的屏幕上点击创建。然后点击查看集群

现在集群已经设置好了,你需要为你的图像创建一个任务。

点击左边菜单上的任务定义,然后点击创建新的任务定义

对于启动类型,选择FARGATE并点击下一步

对于任务定义的名称,我把我的命名为okta-chat

设置任务内存(GB)为0.5GB。 设置任务CPU为0.25vCPU。

.NET核心应用程序是非常高效的,容器也是如此。对于许多应用程序,你会发现你可以用比你想象的更小的盒子来满足许多请求。

其他选项不做改动。

现在你需要定义你要使用的镜像。点击添加容器

让我们给这个容器起一个相同的名字--okta-chat

对于镜像,你需要先前从ECR资源库中复制的路径。

设置软限制为512,并添加端口80和443。

向下滚动到ENVIRONMENT部分,添加以下键值对。

ASPNETCORE_URLS="https://+;http://+"
ASPNETCORE_HTTPS_PORT=443
ASPNETCORE_Kestrel__Certificates__Default__Password="mypass123"
ASPNETCORE_Kestrel__Certificates__Default__Path=./aspnetapp.pfx

在弹出窗口的底部点击添加。现在点击页面底部的创建。回到你的集群,点击任务标签,并点击运行新任务

注意:这里有一个建议。为生产创建服务而不是任务,以充分利用ECS的弹性扩展能力。为了演示的简单性,我直接使用了任务。当使用ALB(应用程序负载平衡器)来路由流量到应用程序时,也需要使用服务。

选择FARGATE作为启动类型

VPC和安全组下选择你的默认VPC和子网。

点击安全组旁边的 "编辑"按钮。点击添加规则,为HTTPS 443端口添加一个新的入站规则。在弹出的对话框中点击保存

现在,点击运行任务

你会被带回到你的集群。选择你创建的任务,点击它的ID。记下它的公共IP;你将需要它来调整你的Okta应用设置。

在Okta开发者门户,进入应用程序>应用程序,点击你用CLI创建的应用程序的名称。编辑常规设置,为登录重定向URI退出重定向URI添加URI,使用运行任务的IP地址,如下所示。

现在,如果你导航到https://YOUR_FARGET_TASK_PUBLIC_IP ,你会看到你是一个功能齐全的聊天应用程序的拥有者,用Okta保护,在ECS Fargate容器中运行。

回顾一下

哇,这真是个好机会!你的新聊天程序做得很好。我们能从中得到什么启示呢?

  • 无服务器技术适用于HTTP请求/响应,但其他协议需要不同的东西。
  • 容器比虚拟机更轻,但也有自己的挑战。
  • 一个docker文件包括构建应用程序和启动它的指令,以及要暴露哪些端口。
  • Fargate使容器的托管更容易,因为你不必管理主机的基础设施。
  • SignalR通过抽象出大部分繁重的工作,使实时通信变得更容易。
  • Vue可用于状态管理,而无需承担额外的构建和开发管道。
  • Okta使任何类型的.NET网络应用的安全更容易。
  • 没有理由拥有一个不安全的网站!
  • 使用AWS应用负载平衡器(ALB)+Nginx,配置为在生产中使用有效证书的TLS。

给TA打赏
共{{data.count}}人
人已打赏
.NET

记一次 .NET 某工控自动化控制系统 卡死分析

2022-8-3 16:59:13

.NET

.Net中的并行与异步

2022-8-7 19:58:09

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索