使用 Docker 打包 Rust Web 服务
引言
学习Rust四个多月了,打算用Rust重写我之前的web项目。Rust web中文资料非常少,学习之路很是坎坷,很多框架文档少的可怜,甚至有的框架直接没有文档只能依靠源代码里面的运行示例研究。但是不管多困难只要坚持下去就一定会有收获。这篇博客是我这一周学习的收获,它应该可以帮你解决以下几个问题。
1.搭建一个简单web服务运行程序。
2.使用cargo build --release 打包的时候速度很慢甚至出现网络异常的问题。
3.使用cargo build --release 打包的时候配置文件和静态资源找不到的问题。
4.使用debian发布Rust web服务。
5.使用alpine 发布Rust web服务。
搭建简单的web服务程序
这里我使用的是warp框架,warp 是基于hyper框架开发的,是一款简单灵活的web框架,不过有一点它没有文档。
1.使用 cargo new docker-rust-web 创建一个项目结构如下:
2.在控制台运行 cargo run 命令测试项目是否创建成功 正确结果如下图
3.添加依赖 cargo项目的依赖在Cargo.toml 文件中管理,因为只是为了演示docker发布web,所以这里只添加最基本的两个依赖分别是warp和tokio。完整配置如下:
# web 框架 warp = "0.3.1" tokio = { version = "1", features = ["full"] }
项目结构图如下:
4.创建一个web服务。完整代码如下:
use warp::{Filter, Rejection, Reply}; type Result<T> = std::result::Result<T, Rejection>; #[tokio::main] async fn main() { let user = warp::path!("user").and_then(user_handler); println!("Started server at localhost:8080"); warp::serve(user).run(([127, 0, 0, 1], 8080)).await; } async fn user_handler() -> Result<impl Reply> { Ok("ok") }
结构图如下:
5.控制台执行cargo run 测试一下结果。注意运行的时候会下载依赖包速度很慢,网络环境差的话很有可能会失败。正确运行结果如下图:
6.浏览器访问地址 http://127.0.0.1:8080/user 查看结果 正确结果如下
到这里一个简单的web应用就搭建完成了。
更换crates.io源
接下来我们来解决第二问题,编译代码的时候因为网络的问题经常失败。原因不用说了cargo默认的源是国外的,这个和npm、maven仓库一样的原因,更换成功国内的镜像就好了。找到你cargo的安装地址,我的电脑是mac 使用brew安装的cargo 默认地是 /Users/fuping/.cargo 目录下,如下图:
如果cargo安装路径下有config配置文件那么只需要将这段代码复制进去(注意如果你的机器默认带有config文件 那么一定不要覆盖掉 在最后面追加就可以了),如果没有config那就自己创建一个。代码如下:
[source.crates-io] replace-with = 'ustc' [source.ustc] registry = "https://mirrors.ustc.edu.cn/crates.io-index"
上面这个源是中科大的,还有一个是个人搭建的据说比较稳定,但是我没试过,感兴趣的可以试一下。代码如下
[source.crates-io] replace-with = "rustcc" [source.rustcc] registry = "https://code.aliyun.com/rustcc/crates.io-index.git"
配置好之后直接就生效了,我们来编译测试一下 控制台执行
CARGO_HTTP_MULTIPLEXING=false cargo fetch cargo build --release
如果配置的是第一个源会出现ustc的标记出现这个标记就说明配置成功了。
关于 cargo build 后配置文件和静态资源找不到
web项目我已经习惯了使用yml配置文件,为此我特意自己模仿springboot的配置方式写了一个切换配置文件的功能,运行起来没有任何问题,但是运行编译后的执行程序一直都是提示找不到文件。折腾很久发现把配置文件手动复制的到执行程序的同级目录发现程序能正常运行了。所以我猜想cargo 编译的时候不会把配置文件和静态资源也编译进去,所以我的建议是把配置文件和静态资源放到和src同级目录下这样编译后方便复制和读取配置文件。
debian发布Rust web服务
1.创建debian文件夹。debian文件夹和src同级目录 如下图:
2.创建Dockerfile文件。
FROM rust:1.55 as builder RUN USER=root cargo new --bin docker-rust-web WORKDIR ./docker-rust-web COPY ./Cargo.toml ./Cargo.toml COPY ./application.yml ./application.yml RUN cargo build --release \ && rm src/*.rs target/release/deps/docker-rust-web* ADD . ./ RUN cargo build --release FROM debian:buster-slim ARG APP=/usr/src/app RUN apt-get update \ && apt-get install -y ca-certificates tzdata \ && rm -rf /var/lib/apt/lists/* EXPOSE 8080 ENV TZ=Etc/UTC \ APP_USER=appuser RUN groupadd $APP_USER \ && useradd -g $APP_USER $APP_USER \ && mkdir -p ${APP} COPY --from=builder /docker-rust-web/target/release/docker-rust-web ${APP}/docker-rust-web COPY --from=builder /docker-rust-web/application.yml ${APP}/application.yml RUN chown -R $APP_USER:$APP_USER ${APP} USER $APP_USER WORKDIR ${APP} CMD ["./docker-rust-web"]
项目结构图:
注意 application.yml 是我特意添加的配置文件用来演示docker发布带有配置文件的项目 这里配置文件没有用到
3.创建.dockerignore文件。注意前面的点.dockerignore文件是用来标记忽略文件夹或文件的。如下图:
3.在控制台运行docker构建命令 如下:
sudo docker build -t rust-debian -f ./debian/Dockerfile .
构建失败了,看上面这张图使用的源还是默认的地址。rust和java不一样,java一次编译到处运行。我们可以把编译后的jar文件直接打包成docker镜像。而rust在不同的机器编译结果不一样所以我们需要把源代码复制然后在容器里面重新编译,容器默认安装cargo使用的源是默认地址。怎么解决呢?针对这个问题我折腾了很久,想到到能不能在创建容器的时候重新创建或者覆盖容器默认cargo安装路径下的配置文件,我创建一个只复制文件的容器然后登陆容器查看cargo的默认安装路径是 /usr/local/cargo。那么我们来尝试一下能不能实现我们所想的。
1.在debian文件夹下创建config文件内容如下:
[source.crates-io] replace-with = 'ustc' [source.ustc] registry = "https://mirrors.ustc.edu.cn/crates.io-index"
项目结构图如下:
2.更新Dockerfile 文件新增内容如下:
COPY ./debian/config /usr/local/cargo RUN CARGO_HTTP_MULTIPLEXING=false cargo fetch
项目结构图如下:
3.再次执行docker构建命令 sudo docker build -t rust-debian -f ./debian/Dockerfile . 结果如下:
出现了ustc的标记 但是还是编译失败了 如下图:
rust编译后会把- 自动转化为_ 所以失败了 我们更新一下Dockerfile 将- 改成_ 如下图:
再次执行构建命令 sudo docker build -t rust-debian -f ./debian/Dockerfile . 这次构建成功了结果如下图:
4.启动dockder镜像 执行命令如下:
docker run -p 8080:8080 rust-debian
结果如下图:
5.浏览器访问测试 结果如下
浏览器访问不到, 我登录容器在容器内部使用curl命令访问能访问到 排查很久发现是rust代码写的有问题:
在docker容器中使用127.0.0.1就会导致只能容器内部访问 。更新代码如下:
warp::serve(user).run(([0, 0, 0, 0], 8080)).await;
项目结构如下:
再次重新构建镜像然后启动 查看浏览器已经可以了
alpine发布Rust Web服务
alpine和debian发布流程基本一致 只是配置上稍有不同
1.创建alpine文件夹.
2.创建Dockerfile文件 内容如下:
FROM ekidd/rust-musl-builder:stable as builder RUN USER=root cargo new --bin docker-rust-web WORKDIR ./docker-rust-web COPY ./application.yml ./application.yml COPY ./Cargo.toml ./Cargo.toml COPY ./alpine/config /opt/rust/cargo RUN CARGO_HTTP_MULTIPLEXING=false cargo fetch RUN cargo build --release RUN rm src/*.rs ADD . ./ RUN rm ./target/x86_64-unknown-linux-musl/release/deps/docker_rust_web* RUN cargo build --release FROM alpine:latest ARG APP=/usr/src/app EXPOSE 8080 ENV TZ=Etc/UTC \ APP_USER=appuser RUN addgroup -S $APP_USER \ && adduser -S -g $APP_USER $APP_USER RUN apk update \ && apk add --no-cache ca-certificates tzdata \ && rm -rf /var/cache/apk/* COPY --from=builder /home/rust/src/docker-rust-web/target/x86_64-unknown-linux-musl/release/docker-rust-web ${APP}/docker-rust-web COPY --from=builder /home/rust/src/docker-rust-web/application.yml ${APP}/application.yml RUN chown -R $APP_USER:$APP_USER ${APP} USER $APP_USER WORKDIR ${APP} CMD ["./docker-rust-web"]
注意 alpine 容器默认的cargo 安装地址是 /opt/rust/cargo
3.创建config文件内容如下:
# more config [build] # Target musl-libc by default when running Cargo. target = "x86_64-unknown-linux-musl" [target.armv7-unknown-linux-musleabihf] linker = "arm-linux-gnueabihf-gcc" [source.crates-io] replace-with = 'ustc' [source.ustc] registry = "https://mirrors.ustc.edu.cn/crates.io-index"
注意 和debian的config相比多了两个 配置这个两个配置是alpine容器安装的cargo默认的配置
4.执行构建docker镜像命令
sudo docker build -t rust-alpine -f ./alpine/Dockerfile .
5.启动镜像。
docker run -p 8080:8080 rust-alpine
6.浏览器测试。
本文是在一位大佬的博客基础上完成 原文地址: https://blog.logrocket.com/packaging-a-rust-web-service-using-docker/