"在 Docker 中安装和使用 Rust nightly 版本"

一直关注 Rust 语言,最近一下发现了两个 web 框架,IronNickel.rs。先不说这两个框架成熟度如何,一般情况下,一个语言有了 web 框架,算是一个里程碑,说明他离靠谱也不远了。这样我决定跟一下 nightly 版本(新框架都是跟 nightly),另外也能感受一下 Yehuda Katz 的构建工具 Cargo。ArchLinux 的仓库里已经有 0.11 版本,再用脚本安装必然会有冲突。于是想到了最近半年最火的 Docker,可以轻松的创建多个环境,正是一个非常好的场景。

安装

安装 Docker, Arch Linux 仓库里很早就有,非常方便:sudo pacman -S docker。完成之后启动他:sudo systemctl start docker

之后我们拉一个 ubuntu 的镜像下来:docker pull ubuntu

完成之后,我们启动一个 container,做一些基本的 setup:docker run -i -t ubuntu:14.04 /bin/bash

这相当与运行在 ubuntu:14.04 这个镜像上运行一个 shell,接下来就进入了这个 shell 环境,和 ubuntu 安装版本完全一致,我们做一些基础的准备,安装一些必要的工具:apt-get install build-essentials git curl libssl-dev

之后,就可以下载 Rust 提供的脚本来安装 nightly 版本了:curl -s http://www.rust-lang.org/rustup.sh > rustup

这里有个问题,rustup 脚本判断64位系统时会出错导致安装失败:

# Detect 64 bit linux systems with 32 bit userland and force 32 bit compilation
if [ $CFG_OSTYPE = unknown-linux-gnu -a $CFG_CPUTYPE = x86_64 ]
then
    file -L "$SHELL" | grep -q "x86[_-]64"
    if [ $? != 0 ]; then
        CFG_CPUTYPE=i686
    fi
fi

因为在我的机器上已知系统是64位,就强行绕过了他的判断。

if [ $CFG_OSTYPE = unknown-linux-gnu -a $CFG_CPUTYPE = x86_64 ]
then
    file -L "$SHELL" | grep -q "x86[_-]64"
    if [ $? == 0 ]; then
        CFG_CPUTYPE=i686
    fi
fi

之后执行 rustup 就可以直接安装最近的 rustc 和 cargo 了。安装完成执行 rustc -vcargo --version (两个工具还不统一!)可以了解安装情况。

exit 退出 shell,commit 你的镜像,这样一个干净的镜像要好好保存:docker commit IMAGE_ID sunng/rust-nightly

Hello World

之后可以写点代码了,我们不在 docker 里写,我们在 host 机器上写,然后挂载到 docker 上,因此 emacs 什么的也不用配置了。

创建一个目录,比如在 $HOME/var/docker/helloworld下,最简单的 rust 项目只要两个文件: Cargo.tomlsrc/main.rs

#Cargo.toml
[package]

name = "hello-world"
version = "0.1.0"
authors = [ "sunng@about.me" ]
//main.rs

fn main() {
  println!("hello world");
}

构建项目不需要手动 rustc 了,那是上个世纪的东西,我们直接 cargo build 就可以:docker run -i -t -v $HOME/var/docker:/mnt/data -w /mnt/data/helloworld sunng/nightly cargo build

其中 -v 参数用于挂载目录,-w 参数指定执行的 pwd。

如果构建成功,就可以执行了,在 docker 中执行:docker run -i -t -v $HOME/var/docker:/mnt/data -w /mnt/data/helloworld sunng/nightly target/hello-world

其实可以直接在 host 系统里执行也是完全可以的:$HOME/var/docker/helloworld/target/hello-world

Web Hello World

前面说了 Rust 都有 web 框架了,我们就写一个 Web 版本的 Hello World 吧。这次用 Iron 框架,首先添加依赖到 Cargo 文件:

[package]

name = "hello-world"
version = "0.1.0"
authors = [ "sunng@about.me" ]

[dependencies.iron]

git = "https://github.com/iron/iron.git"

[dependencies.core]

git = "https://github.com/iron/core.git"

Cargo 目前还没有中央仓库,但是据说将来会有。目前还都是用 git 仓库来直接添加,所以构建环境里必须要有 git。

照着 Iron 的例子写一个最简单的 hello world 程序。

extern crate iron;
extern crate http;

use std::io::net::ip::Ipv4Addr;
use iron::{Iron, Server, Chain, Request, Response, Alloy, Status, Unwind, FromFn};
use http::status;

fn hello_world(_: &mut Request, res: &mut Response, _: &mut Alloy) -> Status {
    res.serve(status::Ok, "Hello, world!");
    Unwind
}

fn main() {
  let mut server: Server = Iron::new();
  server.chain.link(FromFn::new(hello_world));
  server.listen(Ipv4Addr(127, 0, 0, 1), 3000);
}

编译 docker run -i -t -v $HOME/var/docker:/mnt/data -w /mnt/data/helloworld sunng/nightly cargo build

运行 docker run -i -t -v $HOME/var/docker:/mnt/data -w /mnt/data/helloworld -p 3000:3000 sunng/nightly target/hello-world

新增的参数-p是把 docker 环境里的端口3000映射到 host 上的3000,这样我们才能在外面访问。

最后还有一个问题,因为程序听的是127.0.0.1,所以在 host 上是无法访问这个端口的,修改代码:

  server.listen(Ipv4Addr(0, 0, 0, 0), 3000);

就可以正常工作了。

Wrap up

总结一下上面用 docker 比虚拟机的好处:

  • 占用资源少,启动快
  • 与 host 共享网络、硬盘都非常方便,满足开发需要不成问题
  • 所有都是命令,与 host 系统上的进程集成也非常方便
  • 支持镜像的版本控制和仓库