了解容器与虚拟机
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.cshtml和Privacy.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。