使用Docker搭建开发环境

20

我正在为我的团队设置开发环境,但在 docker 的设置方面遇到了一些问题。目前的情况是:

  1. I used a base image to start a container

    docker run -t -i ubuntu:latest "/bin/bash"
    
  2. I installed all the compile and build tools in it

  3. I committed that image and pushed that to our local docker server

    docker commit e239ab... 192.168.10.100:5000/team-dev:beta
    
到目前为止一切都很好。现在,作为团队成员:
  1. I pull the dev environment image on my computer

    docker pull 192.168.10.100:5000/team-dev:beta
    
  2. I start a container:

    docker run -t -i 5cca4... "/bin/bash"
    
此时,我将我的容器视为一种远程机器,可以通过SSH进行工作。我尝试在容器内部执行git clone命令,但由于公钥问题而失败。我手动将id_rsa*文件复制到docker中,然后克隆成功了。然后我尝试编辑一些源文件,但我的vim配置、bash配置以及其他所有配置都被扔掉了,因为这是一个全新的操作系统环境。真正有效的是我的整个依赖版本化构建环境。
以下是我考虑用来解决问题的可能方案:
  1. 从基本镜像开始,使用Dockerfile将主机的所有环境变量添加到docker中。

    缺点:每次主机环境更改bash/vim/git时,都需要更新Dockerfile。

  2. 使用主机到容器的卷。在主机上进行Git克隆和文件编辑。在docker内部运行构建脚本和编译。

    缺点:数据卷中的内容如果需要更新镜像,则无法使用。我不知道这是否是我应该关心的事情。

还是我方法有误?

1
哈哈,我曾经在一个以Docker为中心的开发环境中工作过...就可维护性而言,那是一场噩梦。希望你没有这样的经历... - rcheuk
2
我编写了 Docker Shell 解决了 SSH 密钥授权的问题 - 请参见 http://dockershell.io/。 - mzedeler
5个回答

37
作为一个年轻的Docker用户,我将试着解释我的使用方式。我主要用它来完成两件事:隔离服务和容器化复杂环境。

1. 隔离服务

概述

就像关注点分离原则一样。

为什么?因为这样可以实现可重用性可扩展性(顺便也有助于调试和维护)。例如,对于PHP Laravel网站的开发环境,我会运行几个容器:

  • Mysql
  • Apache-PHP
  • Redis
  • ...

每个容器(服务)都会链接到彼此以便协同工作。例如:

Apache <== Mysql (3306 port).

Apache容器现在可以通过暴露的3306端口与Mysql容器建立TCP连接。

类似的项目将依赖于一个单独的Docker镜像,但会在不同的容器中运行。 但是为了团队合作,应该将应用程序所需的每个工具都容器化。

源代码管理

我从不直接将源代码放入容器中。我更喜欢通过(docker run -v选项)将它挂载到容器中。

当我想要运行命令以更改我的源代码时,例如构建、运行测试或npm更新,我会根据这个工具需要多少配置来决定是从主机还是容器中运行。
越复杂或应用程序特定的工具,我越倾向于在容器中完成。

运行容器

按照上面的例子,我会运行一个名为myapp和一个名为mysql的容器。

$ cd myapp
$ docker run -d \    # run as daemon (detached)
 -p 82:80 \          # bind container's port 80 to host's 82
 -p 22001:22 \       # bind container's port 22 to host's 22001
 --expose 3306       # exposed to other container linked with it
 --name mysql
 author/mysqlimage  
$ docker run -d \    # run as daemon (detached)
 -p 81:80 \          # bind container's port 80 to host's 81
 -p 22001:22 \       # bind container's port 22 to host's 22001
 -v $(pwd):/var/www  # mount current host's directory to container's /var/www
 --link mysql:db     # give access to mysql container (ports) as db alias
 --name myapp
 author/apacheimage

myapp容器将能够与mysql容器进行通信。测试方法:在终端中键入$ telnet db 3306

使用Fig运行容器

正如我所说,Docker的命令对我来说是一场噩梦,因此我找到了另一个很棒的工具Fig,这使我最终得到了一个清晰的yaml文件,位于我的项目根目录下:

web:
    image: lighta971/laravel-apache
    links:
        - db
    ports: 
        - "81:80"
        - "22001:22"
    volumes:
        - .:/app
db:
    image: lighta971/phpmyadmin-apache
    ports:
        - "82:80"
        - "22002:22"
    expose:
        - "3306"

然后,$ cd myapp && fig up 命令的结果与下面的命令相同:)

2. 将复杂环境容器化

我也在使用Docker进行Android开发。一个基本的Android/Cordova设置很大,像几GB的下载,并需要时间来设置环境。
这就是为什么我将所有组件放入单个“瑞士军刀”容器中的原因:

  • Android SDK
  • Android NDK
  • Apache Ant
  • Android工具
  • Java
  • ...

这样便生成了一个包含我所需的Cordova环境的镜像:

$ docker run -i 
 --privileged                  # special permission for usb
 -v /dev/bus/usb:/dev/bus/usb  # mount usb volume
 -v $(pwd):/app                # mount current folder
 -p 8001:8000
 lighta971/cordova-env

我在cvd中设置了别名:

$ alias cdv='docker run -i --privileged -v /dev/bus/usb:/dev/bus/usb -v $(pwd):/app -p 8001:8000 lighta971/cordova-env'

现在我可以透明地使用容器内的所有程序,就像它们安装在我的系统上一样。例如:
$ cdv adb devices            # List usb devices
$ cdv cordova build android  # Build an app
$ cdv cordova run android    # Run an app on a device
$ cdv npm update             # Update an app dependencies

由于卷挂载选项 $(pwd):/app,所有这些命令将在当前目录中工作。 Dockerfile 尽管如此,还有其他需要了解的事情,例如:
  • 理解构建过程以制作高效的镜像
  • 处理持久数据的需求
  • 保持镜像包更新
  • 等等。
希望这对你清楚明白 :)

请问您如何使用Docker处理配置文件?例如,我必须用自己的Apache配置替换默认配置。有什么最佳实践吗? - Mond Wan
@MondWan 在你的 Dockerfile 中使用 ADD 关键字来替换默认的 httpd.conf 文件为你自己的文件。它应该与 Dockerfile 放在同一个文件夹中。如果容器中发生了更改,就提交容器以更新你的镜像或创建新版本。我认为我也看到过使用 puppet 或 chef 来处理这个问题的 Dockerfile。 - Aurel
谢谢。还有一个问题。Docker能否引导HOST机器?假设有一个开发良好的HOST机器,是否有一种方法指示docker构建另一个应用程序实例。但是如果我想设置HOST机器怎么办?(例如:安装Eclipse、vim等) - Mond Wan
@MonWan,Docker不是一个构建工具,至少是一种虚拟化的方式。Docker不知道主机文件系统,所以我认为容器无法在其主机上安装软件。但是,如果您想在容器中查找GUI应用程序,请访问http://fabiorehm.com/blog/2014/09/11/running-gui-apps-with-docker/。 - Aurel
你如何处理主机和容器之间的文件权限问题?我想在位于 /home/my_user/project 的主机上开发代码。目前,我将所有权设置为 my_user:www-data 770,并使用卷与容器共享。为了使其工作,我需要在容器中创建 my_user 组。但这种解决方案依赖于主机,无法与团队的其他成员共享。 - Jakub Filipczyk

8
我尝试了很多不同的Docker开发环境配置方式。我的团队最终采用的是一个预定义的目录,将代码放置其中,并将其映射到Docker容器作为目录使用。必要的环境变量被放置在Dockerfile中,并编写了一系列bash脚本来启动具有必要路径映射的容器。我们将Dockerfile存储在Git中,每当添加新的依赖时,我们都需要更新Dockerfile,可能需要重新构建镜像(这主要是由于我们如何处理依赖管理造成的,这并不理想,但取决于技术栈)。
我们的脚本是针对技术而定的,因为我们使用了许多不同的技术。所有容器都会映射到特定技术的文件夹,其中存储了所有的配置信息。例如,/opt/目录成为了我们的代码所在的主目录。当运行docker run命令时,它会将本地的/opt/目录映射到Docker容器中的/opt/目录。
这通常是有效的。然而,设置本地开发环境也是一项挑战。我首先使用Windows机器从Git上获取代码。我将其映射到运行Docker的Ubuntu VM中。
在Ubuntu VM中,我有一个可以启动所有所需Docker容器的bash脚本。
./start-apache.sh
./start-mysql.sh
./start-mongodb.sh ... and so on

当我使用Windows作为主机时,我发现由于无法创建项目依赖的符号链接,这种方法最终停止了工作。因此,我转而在Ubuntu虚拟机中使用git,并在Ubuntu中启动所有内容,使用相同的bash脚本。

这种方法的缺点是我基本上是在虚拟机中编码,无法使用我喜欢的IDE进行Windows开发。虽然并不是太糟糕,但在VM中工作并不理想,我个人认为。

这种设置留下了很多问题。我们小型开发团队花了几周时间将其变得可维护。工作流程可以改进,但是既没有时间也没有知识...

我很感兴趣听听其他人为使用docker而开发的工作流程。


Windows 支持符号链接,为什么它对你没起作用? - Janus Troelsen
这与共享文件夹内的节点库有关。我们正在使用node/npm,并且那些库中的符号链接无法创建。我记不清是权限问题还是其他原因,但我在其他VM实例中使用node/npm时遇到了类似的问题(即最近更多地是在Windows主机上使用vagrant)。 - rcheuk

5

这是我最终定下来的工作流程。

建立开发环境的关键点:

  • 编写代码必须在docker之外进行
  • VCS必须在docker之外
  • 所有编译和执行都应该在docker内部进行
  • Docker容器应该是无状态的,这样修改和创建新的Docker容器只需要重新编译和执行即可

如lighta所建议的,有两个选择:

  1. 每个服务都有一个Docker
  2. 所有服务在一个Docker中

我更喜欢后者的方式,因为我正在处理多个项目,并且拥有m x n个Docker比拥有m个Docker更糟糕。

设置环境:

  • phusion_baseimage开始
  • 安装mysql、phpmyadmin、nodejs、grunt及其伙伴、apache、django
  • 设置init服务和日志记录(使用基础镜像附带的runitsvlogd非常容易),以便在启动时启动apache、mysql等服务。因此,docker start将重新启动这些服务
  • 暴露所需端口(80端口,通常还有8000端口用于运行测试服务器)
  • 现在,我按以下方式定义我的挂载点
    • ~/host/projectDocker/src > /root/project
    • ~/host/projectDocker/dbData/mysql_data > /var/lib/mysql
    • ~/host/projectDocker/apache_conf > /etc/apache/sites-enabled/
这个方法到目前为止非常成功。每当我们需要安装一些特定的库或依赖项(尤其是Haskell),我们只需设置一个新镜像,要求所有开发人员拉取最新的镜像并重新构建他们的容器。完成!万岁Docker。

你如何处理主机和容器之间的文件权限?默认情况下,卷是以主机所有者和组的身份挂载的,因此您必须在两端定义相同的用户... - Jakub Filipczyk

1
这是我为我的Node.js应用程序进行操作的方式。
首先,将Dockerfile作为源文件的一部分,它看起来像这样:
FROM node:0.10-onbuild
EXPOSE 8080
COPY . /src

这是用来构建图片的:

sudo docker build -t myapp .

构建完成后,我会开发我的代码,并使用以下方法查看容器中的更改:

sudo docker run --rm -it -p 8080:8080 --name="myapp" -v '/path/to/app/src:/usr/src/app' myapp:latest

注意这里的 -v,我将我的本地目录挂载到容器中,这样每次进行代码更改时就不需要重新构建了。
代码由github管理,因此开发流程是相同的。创建分支,在本地工作,准备好后合并回主分支。
如果我的应用程序依赖于另一个服务,比如rabbitmq,我会将其添加为单独的docker容器,并为我的应用程序使用配置文件。
希望这有所帮助。

0
我创建了一个镜像,其中包含了Cordova、Java、JDK、SDK管理器、Gradle和Maven、Angular CLI、Android接受的许可证等等,并在Docker容器中使用。https://hub.docker.com/r/walterwhites/docker-cordova/builds/ 构建镜像。
sudo docker build . -t walterwhites/cordova

运行容器:

docker run -ti --rm --name=cordova -v /Users/username/workspace/gusto-coffee-mobile-app:/workspace walterwhites/cordova

选项 -t 是以终端运行容器 选项 -i 是以交互模式运行 选项 -rm 是在退出容器时停止容器 选项 -v 是创建卷以在主机(本地计算机)和容器之间共享

注意:在 Cordova 上运行 Android 应用:

添加 Android 平台:

cordova platform add android

构建:

cordova build android

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接