使用 Rust 进行 Web 开发——02/x:部署你的第一个应用程序
你可以在这里找到Rust Web 编程入门教程。关注我的Twitter,即可随时获取 Rust Web 开发的最新资讯。也欢迎查看该系列教程的GitHub 代码库。
更新于 2019 年 7 月 8 日:潮汐 0.2.0
本系列共有 3 个目标
- 展示新开发者在使用 Rust 编程时将面临的障碍,以及 Rust 在特定主题(这次是部署)方面的优势。
- 展示Rust中Web开发的不同方案。
- 之后务必确保生产环境中的应用程序是最新版本。
第三点对我来说至关重要。这就是为什么我们在第二部分(02/x)开头会提出一些你可以构建的东西,以及为什么本教程会是现在这个样子。每当你学习新东西时,请记住这个思维模型:
永远不要为了做事本身而做事
换句话说:千万不要仅仅因为想学Rust就去学它。这是你学习一门新语言(或者说,学习生活中任何事情)失败的最大原因。你必须要有目标,有做事的理由。
“那我应该如何学习 Rust 呢?”
- 心中已有想要实现的应用程序或想法。它可以是计算质数的服务、追踪编程目标的网页应用、获取你在 GitHub 上最新点赞并追踪其活动的服务等等。你想构建什么,由你决定。
- 要对自己负责。告诉你的朋友、同事或伴侣,你承诺六个月后他们就能使用这项服务。每隔几天或几周,你都要让他们了解你的最新进展。
它不需要是一个完美无瑕的想法,也不需要是一项能与其他应用竞争的服务。它必须是你想要拥有的东西。它能帮助你度过最初的艰难时期,也能帮助你度过蜜月期结束后,意识到 Rust 有时也会很棘手的阶段。
我也会公开我的信息:
在接下来的 6 个月结束时,我将拥有一个运行中的网络服务,其前端将用于我的 MeetUp “Rust and Tell Berlin”,以便演讲者可以提交提案,并且可以观看之前活动中举办的演讲的幻灯片和视频。
我将通过这套教程来督促自己。
我们继续。在本系列文章的这一部分,我们将部署我们的第一个应用程序。如果您熟悉 NodeJS,那么部署生命周期如下所示:
使用 NodeJS,您可以将任何代码部署到生产服务器。但您必须编写完善的测试用例,并使用 ESLint 和其他工具来捕获 undefined 和类型错误。
理想情况下,我们的开发周期如下所示:
所以我们希望尽早、尽可能靠近代码源(你的本地机器)的地方发现问题。一旦我们找到了一个可行的代码库,我们就希望将这个可行的解决方案部署到服务器上。由于 Rust 的类型系统和强大的编译器,我们可以打包出一个可用的二进制文件并将其部署到生产环境。测试将覆盖其余的错误。
Rust 将可能出现的错误移到了更靠近编码环境的位置。
a) Rust 编译器会发现很多问题,几乎所有问题。
b) 您可以通过良好的测试来发现其余问题(在我们的例子中:接收到错误参数时的错误处理)。
c) 编译 Rust 代码后,您将得到一个二进制文件,该文件可以通过多种不同的方式分发。
本地代码和生产就绪代码之间的区别
谈到部署,我们必须确保代码能够:
- 根据运行环境随机分配端口
- 优雅地处理错误
- 使用正确的返回代码和消息来响应非预期输入。
- 通过适当的测试设置,在部署流程早期发现问题并进行测试。
- 记录事件以便追踪错误
本文将介绍第一个必备条件(随机分配端口)。本系列文章将逐一介绍其余要求。
四种不同的部署方案
我们通常提供不同的部署和托管方案。有些方案更适合大型应用,有些方案则更适合私有项目,以及需要快速启动且复杂度较低的项目。我们的方案包括:
- 托管部署/主机托管(Heroku)
- 通过 Docker 和 Docker 镜像仓库进行自我管理
- 通过 Docker 和 Git 镜像仓库进行自我管理
- 托管式无服务器 Lambda 函数(AWS Lambda,现为 ZEIT)
本文将介绍这些选项,并探讨它们的优点、缺点以及如何准备 Rust 代码以便(以最佳方式)进行部署。
构建应用的第一个版本
正如我们开头所说,我们需要一个想法,以及我们想要构建什么。即使我们将在下一篇文章(03/x)中描绘出应用程序的更宏大的蓝图,我们也可以先着手选择一个我们想要用来构建它的框架:
如第一篇文章所述,如果你愿意,你也可以选择更低的级别:
我们将选择一个框架用于本文的文字版。我将选择Tide,因为我计划未来对其进行更多贡献。我会在本系列文章的GitHub 代码库中列出 Rocket 和 Actix 的解决方案。
设置我们的应用程序
我们希望确保使用异步代码,而 Rust 稳定版目前尚未支持异步代码。因此,我们需要安装并设置Rust 的nightly 版本:
$ rustup install nightly-2019-02-25
$ rustup default nightly
这将生成我们的第一个文件夹结构。一个运行中的 Web 应用程序的基本框架如下所示:
货物.toml
[package]
name = "my-cool-web-app"
version = "0.1.0"
authors = ["YOUR NAME + EMAIL"]
edition = "2018"
[dependencies]
tide = "0.2.0"
main.rs
#![feature(async_await)]
fn main() {
let mut app = tide::App::new(());
app.at("/").get(async move |_| "Hello, world!");
app.serve();
}
正如我们之前所说,我们需要让托管环境有机会为我们的应用程序分配一个端口。
我们的main.rs 文件必须满足以下要求:
#![feature(async_await)]
extern crate tide;
use tide::App;
use std::{env, net::SocketAddr};
fn main() {
let mut app = App::new(());
let address = SocketAddr::from(([127, 0, 0, 1], get_server_port()));
app.at("/").get(async move |_| "hello world");
app.serve(address).expect("Start server");
}
fn get_server_port() -> u16 {
env::var("PORT")
.ok()
.and_then(|port| port.parse().ok())
.unwrap_or_else(|| 8186)
}
设置完成后,我们可以逐一介绍每种部署方案。
通过 Heroku 进行托管部署
托管环境在很大程度上只是一种抽象。它们内部的工作原理与你自己的流水线相同:将代码推送到 Git 仓库。一个“钩子”会监视这个仓库,并在代码发生更改时开始编译最新版本并运行它。但对你来说,它只是一个
git push heroku master……
首先,您需要一个Heroku 帐户(免费)。使用您的新帐户登录并创建一个新应用:
点击“创建应用”后,Heroku 会在“部署”选项卡下说明如何将代码推送到他们的服务器:
准备你的代码
首先,我们需要将代码库推送到远程位置(Heroku)。因此,请安装Heroku 工具链。之后,我们可以将远程位置添加到我们的 Git 仓库中:
$ cd my-cool-web-app
$ heroku login
$ heroku git:remote -a my-cool-web-app
接下来,我们需要告诉 Heroku 应用程序构建完成后如何运行它。Heroku 需要一个名为Procfile的文件,其中包含启动命令:
$ touch Procfile
然后把下面这行代码放进去:
web ./target/release/my-cool-web-app
我们还需要告诉 Heroku 我们使用的是哪个版本的 Rust。由于我们想使用 nightly 版本,所以我们在根目录下创建一个名为RustConfig的文件:
$ touch RustConfig
使用以下这行代码:
VERSION=nightly
注意
Rust 语言非常新,Heroku 目前还不支持它。我们需要安装并激活一个 Rust 的“构建包”。因此,请在应用程序的根目录中执行以下命令:
$ heroku create --buildpack emk/rust
$ heroku buildbpacks:set emk/rust
这将启用对 Rust 语言的支持。
现在我们可以:
$ git add .
$ git commit -m "Init"
$ git push heroku master
成功后,我们返回浏览器中的 Heroku 控制面板,然后点击生成的域名(位于“设置”下方)。浏览器窗口应该会打开并显示“Hello, World!”。
概括
- Heroku 让部署应用程序变得轻松便捷。
- 不到 5 分钟,你的应用就能上线运行。
- 您可以分配自己的域名并启用 HTTPS(如果您付费的话)。
- 对于本教程和启动副业项目而言,Heroku 是最佳选择:价格低廉、易于使用,并且消除了部署的开销,尤其是在初期阶段。
Docker
使用 Docker 的最大优势在于可以自由选择流水线和环境。您可以先在本地构建镜像,然后直接将其推送到 Docker 镜像仓库。服务器可以从仓库下载并执行
docker run该镜像。或者,您也可以创建一个蓝图(Dockerfile),供其他服务在其服务器上使用。
如果您使用 Docker 进行部署,则有两种选择。第一种是将您的代码(使用 Dockerfile)推送到 Git 注册表(例如 GitHub 或 Bitbucket),然后配置一个部署服务器,该服务器监听更改,通过 SSH 连接到 Git 注册表,获取代码,进行部署和运行。
你的第二个选择是使用 Docker 镜像仓库。这样做的好处是你可以预先构建容器并直接发布。这有时可以加快部署速度,并且需要发布的代码更少(尤其是在使用 Rust 的情况下)。
我们可以利用 Rust 能够编译成二进制文件的特性。我们甚至可以更进一步,编译一个不包含任何外部依赖的静态 Rust 二进制文件。为此,我们需要:
- 构建 Rust 二进制文件
- 已将所需的 C 库静态链接到其中,以便它可以独立运行。
最终目标是得到一个甚至不需要 Rust 就能运行的二进制文件。感谢开源社区和 Erik Kidd,现在已经有现成的解决方案可以帮助我们实现这一点。
最终生成一个体积超小、没有任何外部依赖的 Docker 镜像。这就是rust-musl-builder。它是一个 Docker 镜像,可以帮助你构建静态 Rust 二进制文件。它会在首次执行后立即下载整个镜像。
我们输入和创建的所有内容都来自应用程序的根目录。
$ cd my-cool-web-app
在创建 Dockerfile 之前,让我们先看看我们实际要做什么。我们使用 rust-musl-builder 将 musl-libc 库静态链接到我们的二进制文件中。
$ docker run --rm -it -v "$(pwd)":/home/rust/src ekidd/rust-musl-builder cargo build --release
这将生成我们超小的二进制文件。您可以像这样查看它:
$ ls -lh target/x86_64-unknown-linux-musl/release/my-cool-web-app
它只有几兆字节(在我的例子中是 4.4MB)。为了能够反复执行此过程,不仅在本地机器上,而且在不同服务器的部署管道中,我们创建了一个多阶段 Dockerfile。
FROM ekidd/rust-musl-builder:nightly AS build
COPY . ./
RUN sudo chown -R rust:rust .
RUN cargo build --release
FROM scratch
COPY --from=build /home/rust/src/target/x86_64-unknown-linux-musl/release/my-cool-web-app /
ENV PORT 8181
EXPOSE ${PORT}
CMD ["/my-cool-web-app"]
您现在可以通过以下方式构建镜像:
$ docker build -t my-cool-web-app:latest .
然后运行它:
$ docker run -d --rm -P --name heroku heroku:latest
现在您可以通过以下方式打开浏览器(在 macOS 系统中):
$ open http://$(docker container port my-cool-web-app 8181)
我们刚刚创建了一个极其精简的 Docker 镜像,其中包含我们的二进制文件,没有任何外部依赖项。您可以通过以下方式查看刚刚创建的镜像:
$ docker image ls my-cool-web-app
概括
- Docker 功能强大,但如果使用得当,也能发挥很大的作用。
- 尤其是使用 Rust:你可以创建静态链接的二进制文件,这些文件非常小,甚至不需要 Rust 环境就能运行。
- 选择 Docker 时,您还可以拥有更多托管和运行应用程序的选项。
- 然而,像 Heroku 这样的托管环境不允许将 Docker 镜像推送到其环境中。
无服务器运行时 — ZEIT/now
无服务器架构与前两种方案的思路截然不同。无服务器架构也意味着无状态,因此你构建的不是 Web 应用程序,而是函数。你无需在应用程序中内置 API 接口,只需拥有这些 API 接口(在无服务器架构中称为“处理程序”)。像 Rocket 和 Actix 这样的 Web 框架在这里可能有点过于复杂。目前,ZEIT 在其新的无服务器环境中不支持 Rust nightly 构建。
因此,我们不创建二进制文件(使用),而是cargo new web-app创建一个库:
$ cargo new now-service --lib
$ cd now-service
这里我们需要创建一个名为“now.json
{
"name": "now-service",
"version": 2,
"builds": [
{
"src": "src/index.rs",
"use": "@now/rust"
}
]
}
我们的src/lib.rs例子如下所示:
use http::{Request, Response, StatusCode, header};
fn handler(request: Request<()>) -> http::Result<Response<String>> {
let response = Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "text/html")
.body("<!doctype html><html><head><title>A simple deployment with Now!</title></head><body><h1>Welcome to Rust on Now</h1></body></html>".to_string())
.expect("failed to render response");
Ok(response)
}
与 Heroku 一样,您需要安装ZEIT 工具链,它现在名为“now”。有多种安装方式。如果您使用的是 macOS,可以通过以下方式安装:
$ brew cask install now
这将安装 Now 应用程序。请在您的“应用程序”文件夹中找到并打开它。您可以通过输入您的电子邮件地址来完成安装。此操作还会安装命令行工具链。
基本就是这样。你可以输入:
$ now
然后按回车键。这将开始上传您的申请。登录您的ZEIT 控制面板,然后点击提供的链接。
概括
- 无服务器架构可以帮助您节省成本:服务仅在需要时才运行。
- 这会导致启动时间延长,这一点需要考虑。
- 无服务器的理念让你重新思考状态,以及你是否真的需要为某些用例构建一个功能齐全的 Web 应用程序。
- 现在使用 AWS Lambda 或 ZEITs 进行部署可能需要更长时间。
文章来源:https://dev.to/gruberb/web-programming-in-rust-02x-deploy-your-first-app-1k05关注我的推特账号,即可随时获取 Rust Web 开发的最新资讯。也欢迎查看他所编写的系列代码的GitHub 代码库。








