使用VirtualBox+Docker搭建可移植的Linux开发环境

0.279字数 3437阅读 5663

目的

做过Linux开发的工程师可能都会或多或少有过如下的经验:

  • 需要至少两台开发机器,一台Windows电脑用来运行IDE和各种工具,一台Linux电脑用来做编译和测试。
  • 两台机器同步代码很麻烦。拷贝容易出现错漏,samba共享又很慢(一个vimgrap运行半天)。
  • Linux机器下混杂着各种SDK,环境变量,临时包,不知道什么版本的工程软件……最后谁都不敢动这个神奇的环境,因为离开这个环境软件就跑不起来,甚至编译都编不过。
  • 测试团队想复制你的环境,或者你想复制测试的环境来复现bug?办不到……

这篇文章就是尝试来解决这个问题的。

  • 首先,我们只需要一台Windows电脑,并且宿主机和Linux环境共享数据。
  • 其次,它不慢,至少在主流的电脑配置上和你分开跑两台机器差不多。
  • 再次,这个环境可很容易的清理、复制、移植

这个方案不适合什么人

  • “老破小”(“老”电脑,“破”硬盘,“小”内存)
  • Windows或Mac等非Linux环境的开发。或者桌面系统原本就是Linux。
  • 喜欢折腾vagrant的,对虚拟机有偏见特别是性能和稳定性上的偏见的,或者开发环境很单纯很简单的。

对于直接上了docker Windows版的,可以忽略VirtualBox部分。Docker目前已经原生支持Windows,但是有两个条件:1是必须是Windows 10 Enterprise版本,Home版不带hyper-v;2是必须打开hyper-v选项。
我之所以采用Virtualbox方案,是为了兼容Windows10 home版,以及关掉hyper-v。因为我需要跑的一个android虚拟机跟hyper-v冲突。

再说说vagrant。vagrant其实就是在虚拟机(比如VirtualBox)的基础上再封装了一层管理脚本,以及提供了许多预配置的虚拟机镜像文件一个命令就可以下载+安装+启动。本身是很好的工具,批量部署很有优势,我也折腾过两天。但个人感受有几个缺点:比如引入了不必要的复杂性,比如几个流行的Linux发行版的镜像更新很不及时,比如自动化脚本出错还得手动分析问题等等。因此放弃。

思路

方案的大体思路如下:

  • 首先宿主机器是一台Windows。当然Mac甚至Linux也可以,但不在本文讨论范畴。
  • 用免费的Oracle VirtualBox 搭建虚拟机,安装Linux。
  • 把宿主机的一个目录共享到虚拟机中,然后再用这个目录创建一个所有容器共享的数据卷容器。以此在宿主机和各容器之间实现数据共享和同步。
  • 开发环境,版本管理环境,SDK环境,运行环境相互之间使用不同的容器将它们各自独立。并且这些容器可以快速的部署到测试甚至生产环境。

组件图

直接上图


组件图
  • 图中只有一个角色,就是开发者了。
  • 这个角色有两种主要用例,一是使用IDE等各种工具进行开发,二是使用特定端口访问运行环境的服务。在这里画的是80端口的web服务。
  • 将Windows下目录C:\foo\bar\workspace共享到虚拟机内
  • 虚拟机挂载该共享目录到/workspace
  • 使用docker创建一个workspace数据卷容器,卷挂载的是虚拟机系统的/workspace
  • 分别创建三个容器,包含运行环境,编译环境,版本管理环境。这三个容器都把workspace容器卷作为数据卷。
  • 版本管理容器负责和远端代码仓库同步代码
  • 编译环境负责把代码编译成可执行程序
  • 运行环境负责运行可执行程序
  • Docker会把运行环境的端口映射到虚拟机系统上
  • 由于虚拟机配置成网桥模式,所以宿主机可以直接访问该端口

详细步骤

安装配置VirtualBox

安装VirtualBox:先到官网下载最新版本的VirtualBox安装包和扩展包,下载地址。这里需要注意的是,扩展包的版本必须和安装包的版本一致。

安装扩展包:双击安装Virtualbox。装好Virtualbox后,打开主界面,点击:菜单->管理->全局设定->扩展->添加。然后选中扩展包。

下载Alpine Linux安装盘: 下载地址

这里多扯两句。Linux发行版多如牛毛,最流行的莫过于Ubuntu和CentOS。这两个发行版网上中英文资料,以及问题解答都很多,可能更适合初学者选择。我之所以选用AlpineLinux,是因为其轻量化的特点。有专门为虚拟机优化的版本,内核经过裁剪,安装盘只有几十MB。全部安装完不到300MB磁盘占用。启动也很快,占用资源少,在虚拟机中后台运行基本无感。我们基本上所有“功能”都基于Docker搭建,所以Linux宿主系统并不需要任何额外的功能,只要把Docker跑起来即可。另外,AlpineLinux是docker官方御用操作系统,很多镜像文件都基于Alpine构建。虽然宿主系统和镜像系统之间并无太多联系,但在学习曲线和操作习惯上,如果两者统一,效率会有有所提高。

另外提一下,目前Docker的宿主系统中,CoreOS也是一个很好的选择。CoreOS是专门为Docker量身打造的操作系统,拥有许多优秀特性。目前阿里云也支持该系统。有兴趣可以了解一下。

新建虚拟机:在VirtualBox新建一个虚拟机,类型选择“Linux 2.6 / 3.x / 4.x (64-bit)”。然后创建一个动态扩展的硬盘,大小可以选择100G,反正是动态扩展。创建完成后,手动修改如下选项:

  • 使能PAE/NX
  • 使能硬件加速
  • 网卡配置成“桥接网卡”
  • USB设置成3.0版本
  • 共享文件夹添加一个工作目录:C:\foo\bar\workspace,名称设为“workspace”
  • 光驱选择iso文件,然后找到我们刚才下载的AlpineLinux的iso

之所以选择基于“Linux 2.6 / 3.x / 4.x (64-bit)”进行配置,是因为VirtualBox没有默认的AlpineLinux的配置,而这个选项比较接近我们希望的配置。如果选择的是“Other Linux”,VirtualBox会给出一个老旧机型的配置。

网卡配置成“桥接网卡”是为了虚拟机和外面的物理网络能够相互访问。虚拟机能获得物理网络的IP。物理网络中的设备(包括Windows宿主机)可以像访问其他物理设备一样访问虚拟机(机器内部的Docker,后面会说)。

这里选择的共享文件夹,就是上面组件图中的共享文件夹,用于在宿主机,虚拟机和Docker之间双向共享数据用。

安装AlpineLinux

  • 启动虚拟机,用光盘启动,然后用root登录AlpineLinux,没有密码
  • 敲入命令
setup-alpine
  • 跟随命令行向导一步一步执行
  • 在选择安装源时,敲入“f”让系统自己寻找一个最快的源
  • 在选择安装目标盘时,敲入“sda”
  • 在选择分区类型时,敲入“sys”
  • 几分钟就装完了,退出光盘,重启虚拟机

配置AlpineLinux

把网络配置改成DHCP(非必须): 编辑/etc/network/interfaces,删除eth0的固定IP配置,然后添加一行:

iface eth0 inet dhcp

配置打开软件仓库:编辑/etc/apk/repositories,把community源前面的注释去掉。docker包是在community仓库里面的,所以这步必须做。

升级系统:执行如下命令

apk update
apk upgrade

配置sshd:AlpineLinux默认是关闭root用户ssh登录的。最安全的做法是创建一个非root用户。懒人可以直接打开root登录。编辑/etc/ssh/sshd_config,加入一行:

PermitRootLogin yes

安装Virtualbox扩展包:这步是为了使AlpineLinux能够支持VirtualBox的共享目录。命令:

apk add virtualbox-guest-additions virtualbox-guest-modules-virt virtualbox-guest-modules-vanilla

启动挂载共享目录:这步是为了让AlpineLinux每次启动都自动挂载VirtualBox的共享目录。编辑/etc/fstab,加入一行

workspace /workspace vboxsf defaults 0 0

安装Docker:如下命令安装docker,并使其服务端随系统启动:

apk add docker
ln -s /etc/init.d/docker /etc/runlevels/default/docker

Docker国内镜像加速:创建一个文件/etc/docker/daemon.json,内容为(如果有阿里云或者DaoCloud账号,也可以配置成私有的加速链接):

{
"registry-mirrors": ["https://registry.docker-cn.com"]
}

搞定,重启!

创建容器

下载镜像

首先下载一个Alpine的基础镜像。行文时Alpine最新版本为3.8,建议按版本装镜像而不是拉latest,这样以后好进行版本管理和迁移。

docker pull alpine:3.8

之所以再次选择AlpineLinux作为容器的基础镜像,还是因为其轻量化。Alpine3.8的基础镜像仅为5MB!

创建数据卷容器

docker run -it --name=workspace -v /workspace:/workspace:Z alpine:3.8

这个命令的意思是创建一个名字为workspace的容器,作为卷挂载本地(虚拟机Linux)的目录/workspace到容器的/workspace目录,使用alpine作为镜像,镜像版本3.8。那个大写的Z在这里没什么作用。但是如果虚拟机宿主Linux是CentOS这类启用了SELinux的系统,必须加这个参数,要不然容器是无法读写这个目录的!

创建SDK容器

这里以Golang为例,dockerhub上直接有官方的golang镜像,所以直接pull然后run即可

docker pull golang:1.10.3-alpine3.8
docker run -it --rm --volumes-from workspace golang:1.10.3-alpine3.8 [your commands...]

创建SCM容器

这里以git为例。git是个很轻量级的工具,我们可以自己制作一个镜像。
首先编写Dockerfile,这个文件的作用就跟C/C++的Makefile差不多。类容如下:

FROM alpine:3.8
MAINTAINER Cary Tan tx-cary@163.com
ENV PS1='[git@docker $PWD]\$ '
RUN echo "http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.8/main" > /etc/apk/repositories \
      && echo "http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.8/community" >> /etc/apk/repositories \
      && apk update \
      && apk add git git-doc \
      && mkdir -p /workspace
WORKDIR /workspace
CMD /bin/sh

然后执行如下命令构建并上传镜像到你的dockerhub。

docker build -t <your_name>/git:<tag> /path/to/your/Dockerfile/
docker push <your_name>/git:<tag>

用SCM镜像创建一个容器去下载某项目的代码:

docker run -it --rm --volumes-from workspace <your_name>/git:<tag> git clone ...

创建RUN容器

RUN镜像的环境根据项目不同,区别会很大。有时候可能还需要很多images/container一起协同工作。我在这里就举一个最简单的,用golang做的一个http hello world。

  1. 首先,在windows机器的workspace目录下创建一个叫gohttp的子目录,然后创建一个原文件main.go。源码如下:
package main
import (
  "fmt"
  "net/http"
)

const (
  host string = ":80"
)

func main() {
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello world!")
  })

  fmt.Println("Listening ", host)
  http.ListenAndServe(host, nil)
}
  1. 然后用golang的SDK镜像创建容器进行编译:
docker run -it --rm --volumes-from workspace golang:1.10.3-alpine3.8 go build -o /workspace/bin/gohttp /workspace/gohtt
p/main.go
  1. 用alpine镜像创建一个运行容器,也即我们使用原始alpine镜像作为我们的RUN镜像:
docker run -it -p 80:80 --volumes-from workspace alpine:3.8 /workspace/bin/gohttp

以上关键参数是“-p 80:80”。意思是将容器内的80端口映射到宿主机(虚拟机中的Linux系统)上。
此时,顺利的话,我们可以看到终端打印

Listening :80
  1. 最后,只需打开Windows上的浏览器,输入虚拟机IP地址,就可以验证我们的Hello World了!

自动启动并登录虚拟机

用过vagrant的都知道,这个工具最方便的地方就是只需要"vagrant up"和"vagrant ssh"两条命令就可以自动启动虚拟机并登录进去。但我们也知道,vagrant其实就是基于Virtualbox命令行封装的,所以我们只需要把关键命令找到,也可以在没有vagrant的情况下实现类似的便捷。

我们得选择一个趁手的终端软件。我最喜欢的是MobaXTerm,并且是付费用户。所以这里以MobaXTerm为例,直接使用cygwin,Win10自带bash或ubuntu等应该都能实现相同的功能。

新建一个Shell session,类型是“Bash”。在“Advanced Shell settings”中码入如下脚本

/drives/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe startvm "alpine" --type headless
sleep 30
ssh -o ConnectTimeout=30 root@<IP addr>
/drives/c/Program\ Files/Oracle/VirtualBox/VBoxManage.exe controlvm alpine acpipowerbutton

保存,双击。这个脚本会自动启动名为"alpine"的虚拟机,等待30秒,然后ssh登录到虚拟机IP地址。当用户退出ssh后,脚本自动关闭虚拟机。

安利一下MobaXTerm,这个终端软件是基于cygwin的,功能十分强大。不仅支持SSH、Telnet、串口,还支持CMD,Powershell,bash等终端。甚至还支持VNC,FTP,SFTP,远程桌面……不仅是客户端软件,它还内置了各种服务器软件,包括Xserver,TFTP,FTP,HTTP,SSH,Telnet,NFS,VNC……这款软件大部分功能都是免费的,免费版的限制一个是session的数量有限制,二是各种server启动时间有限制。

推荐阅读更多精彩内容