借助Docker,我们可以更容易地进行web应用部署,而同时不必头疼于项目依赖、环境变量以及各种配置问题,Docker可以快捷、高效地处理好这一切。

而这也是本教程所要实现的主要目的。

首先我们来学习使用Docker容器运行一个Python Flask应用,然后逐步介绍一套更酷的开发流程,其中涵盖了应用的持续集成与发布…

我(Michael Herman)最初是在2015年2月8日的PyTennessee上介绍了这一工作流程,如果感兴趣的话,你可以直接浏览我当时使用的幻灯片

流程

  1. 在本地功能分支上完成应用代码。2. 在Github上发起一个到master分支的Pull Request。3. 在Docker容器上运行自动测试。4. 如果测试通过,手动将这个PR merge进master分支。5. 一旦merge成功,再次运行自动测试。6. 如果第二次测试也通过,就在Docker Hub上对应用进行构建。7. 一旦构建完成,自动化地部署到生产环境。

本教程基于Mac OS X,在开始前需要保证以下工具已正确安装配置:Python v2.7.9, Flask v0.10.1, Docker v1.4.1, Docker Compose, v1.0.0, boot2docker 1.4.1, Redis v2.8.19

好了,让我们开始吧。

首先来介绍一些Docker中的基本概念:

  • Dockerfile中包括了一系列语句,用于对镜像的行为进行描述。
  • 镜像是一个模板,用来保存环境状态并创建容器。
  • 容器可以理解为实例化的镜像,并会在其中运行一系列进程。

如果对Dockerfile镜像容器的具体细节感兴趣,那么可以从Docker的官方文档获取更多详细信息。

为什么是Docker?

使用Docker意味着你能在开发机上完美地模拟生产环境,而不用再为任何由两者环境、配置差异所造成的问题而担心,除此之外Docker带给我们的还有:

  1. 良好的版本控制。2. 随时便捷地发布/重建整个开发环境。3. 一次构建,随处运行,就是这么神奇!

配置Docker

由于Darwin(OS X内核)缺少运行Docker容器的一些Linux内核功能,所以我们需要借助boot2docker,一个用于运行Docker的轻量级Linux发行版(启动一个专门为运行Docker定制过的小型虚拟机)。

首先为我们的Flask项目创建一个名为“fitter-happier-docker”的目录。

接下来遵照官方文档的步骤来完成Docker和boot2docker的安装。

我们可以通过以下命令来验证安装是否正确:

$ boot2docker version
Boot2Docker-cli version: v1.4.1
Git commit: 43241cb

Compose Up!

Docker Compose是官方提供的容器业务流程框架(译注:曾经的项目名称是Fig,甚至在本译文的初稿时依然是,进化速度之快可见一斑),只需通过简单的.yml配置文件,就能完成多个容器服务的构建和运行。

使用pip来安装Docker Compose,并通过如下命令来确认安装正确:

$ pip install docker-compose
$ docker-compose --version
docker-compose 1.1.0

现在来启动我们的Flask+Redis应用(你可以从这个repo来获取项目的全部源代码),首先在项目根目录下新建docker-compose.yml文件:

web:
    build: web
    volumes:
        - web:/code
    ports:
        - "80:5000"
    links:
        - redis
    command: python app.py
redis:
    image: redis:2.8.19
    ports:
        - "6379:6379"

可以看到我们对项目所含两个服务进行的操作:

  1. web:我们将在web目录下进行容器的构建,并且将其作为Volume挂载到容器的/code目录中,然后通过python app.py来启动Flask应用。最后将容器的5000端口暴露出来,并将其映射到主机的80端口上。2. redis:我们直接使用Docker Hub上的官方镜像来提供所需的Redis服务支持,将6379端口暴露并映射到主机上。

你一定注意到了位于web目录下的Dockerfile文件,它用于指导Docker如何构建我们的应用镜像(基于Ubuntu),并且保证了完备的依赖支持。

构建并运行

接下来只需要一行简单命令,就能轻松搞定一切(镜像的构建及容器的启动运行):

Ubuntu
$ docker-compose up

这会根据Dockerfile来构建Flask应用的镜像,从官方仓库拉取Redis镜像,然后将一切运行起来。

现在你可以去喝一杯咖啡,呃,也许是两杯:首次运行会花费相对较长的时间,事实上Docker会在构建过程中,将Dockerfile中的每一步操作(更正式的说法应该是layer)缓存下来,以后的构建过程会因此提速很多,因为只有发生改变的步骤才会被重新执行。

Docker Compose会并行地启动全部容器,每个容器都会被分配各自的名字,并且会为日志设置可读性更高的配色方案。

好了,那么准备好来测试了吗?

打开你的浏览器,输入主机DOCKER_HOST环境变量所对应的IP地址,例如在我这里是 http://192.168.59.103/ (运行boot2docker ip命令可以查询到IP地址)。

接下来你应该会在浏览器中看到以下文本“Hello! This page has been seen 1 times.”:

刷新页面,如果一切正常的话,计数器变量应该会进行累加。

通过Ctrl-C来终止我们的应用进程,然后通过以下命令让其改为在后台运行:

$ docker-compose up -d

想查看应用进程的运行状态?只需要输入以下命令就可以了:

$ docker-compose ps
            Name                          Command             State              Ports
--------------------------------------------------------------------------------------------------
fitterhappierdocker_redis_1     /entrypoint.sh redis-server   Up      0.0.0.0:6379->6379/tcp
fitterhappierdocker_web_1       python app.py                 Up      0.0.0.0:80->5000/tcp, 80/tcp

可以看到我们的两个进程运行在不同的容器中,而Docker Compose将它们组织在一起!

更进一步

在确定一切正常无误后,使用docker-compose stop命令来终止我们的应用,然后通过boot2docker down来安全地关闭虚拟机。接下来就可以向Git提交本地修改,并推送到Github了。

那么,我们刚才都完成了什么呢?

我们建立了本地环境,通过Dockerfile详尽描述了如何构建镜像,并基于该镜像启动了相应容器。我们使用Docker Compose来将这一切整合起来,包括构建和容器之间的关联、通信(在Flask和Redis进程之间)。

接下来,我们来看一个更酷的工作流程,即通过引入CircleCI来实现项目的持续集成。

同样的,你可以从此处获取源代码。

Docker Hub

到目前为止我们已经接触过Dockerfile、镜像以及容器(当然,借助了Docker Compose的帮助)。

如果你很熟悉Git的工作流程,那么可以把Docker镜像理解为Git的repo,而容器类似于该repo的clone,如果将这个比喻继续类推下去,那么Docker Hub也就相当于Github的地位了。

  1. 为了使用Docker Hub,你可以用Github账号来完成注册。2. 添加一个新的自动构建,将刚才完成的项目repo加入进来,一切按照默认选项即可,除了将“Dockerfile Location”改为“/web”。

一旦添加完毕,Docker Hub会进行一次初始化构建,请确保一切正常。

Docker Hub和CI

Docker Hub自身通过配置就可以充当持续集成服务,从而在每次推送Git提交后自动进行构建。

这意味着你不能直接将镜像推送到(通过docker push)Docker Hub上。Docker Hub会自己从repo进行拉取并构建镜像,从而保证整个过程中没有错误。在你的工作流程中请铭记这一点,因为在Docker文档中目前并没有对此进行详细说明。

让我们来试一下,加入以下测试用例:

self.assertNotEqual(four, 5)

提交并推送到Github,然后就可以看到Docker Hub如何开始一次新构建了。

由于这是项目部署上线前的最后一道防线,我们当然希望Docker Hub在构建完成之前,能够捕获所有的错误和异常。另外,你肯定也希望能将自己的单元测试和集成测试加入到持续集成流程中, 而这正是CircleCI的用武之地。

CircleCI

CircleCI是一个持续集成/发布平台,支持对Docker容器进行测试。你只需提供一个Dockerfile,CircleCI会据此构建镜像,并启动一个新容器,然后在其中运行你的测试。

还记得我们期望的工作流程吗?链接

现在来看看如何完成它。

安装

CIrcleCI官方提供了很好的入门指导

使用Github账号完成注册,然后将你的Github repo添加为一个新项目(成功后会收到邮件通知)。这会为该repo增加一个hook,每当你向其推送新的提交时,都会触发一次新的构建。

接下来需要向我们的repo添加一个配置文件,用以指导CircleCI完成构建。

circle.yml的文件内容如下:

machine:
  services:
    - docker

dependencies:
  override:
    - pip install -r requirements.txt

test:
  override:
    - docker-compose run -d --no-deps web
    - python web/tests.py

实际上,我们构建了一个新的镜像,并启动了一个新容器,然后进行测试:首先检查web应用是否正常启动运行,然后逐一进行单元测试。

你应该已经注意到我们在这里使用了命令docker-compose run -d --no-deps web而不是docker-compose up来启动应用,这是因为CircleCI已经集成了可用的Redis运行时环境,所以我们只需启动web应用进程就可以了。

当circle.yml文件修改完成,就可以推送到Github来启动一次新构建了。记住,这将同时在Docker Hub上启动一次构建。

一切正常?

在继续下去之前,需要对工作流程进行一些调整,因为我们通常不希望将提交直接推送到master分支上。

功能分支工作流程

如果不太熟悉这一工作流程,那么可以从此处获得准确生动的解释。

让我们来快速浏览一个示例:

创建功能分支

$ git checkout -b circle-test master
Switched to a new branch 'circle-test'

更新应用
并在texts.py里增加一个新的断言:

self.assertNotEqual(four, 6)

发起一个Pull Request

$ git add web/tests.py
$ git commit -m "circle-test"
$ git push origin circle-test

甚至在你真正发起PR之前,CircleCI就已经启动了构建。在PR创建完毕后,只需等待CircleCI通过所有测试,我们就可以点击Merge按钮来合并入master分支了。一旦merge成功,Docker Hub就会触发相应的构建过程。

重构工作流程

如果回到本文开头的工作流程处,你会发现我们实际上希望Docker Hub在master分支上再次进行测试后才启动构建,所以让我们来对现有流程进行以一些快速的调整:

  1. 打开你的Docker Hub仓库,在Settings下选择Automated Build。2. 取消对“When active we will build when new pushes occur”的选中状态。3. 保存。4. 选择位于Settings下的Build Triggers。5. 将status改为on。6. 复制以下curl命令:$ curl --data "build=true" -X POST https://registry.hub.docker.com/u/mjhea0/fitter-happier-docker/trigger/84957124-2b85-410d-b602-b48193853b66/

将以下代码加入到circle.yml文件末尾:

deployment:
  hub:
    branch: master
    commands:
      - $DEPLOY

现在我们会在merge到master分支并通过测试之后,执行$DEPLOY环境变量所代表的命令,我们需要将这个变量的值加入到CircleCi的环境变量中:

  1. 打开 Project Settings,选择 Environment variables。2. 添加一个名为“Deploy”的新变量,并且将刚才复制的curl命令粘贴进去作为该变量的值。

现在来检验一下成果:

$ git add circle.yml
$ git commit -m "circle-test"
$ git push origin circle-test

发起一个新的PR,一旦其通过CirecleCI测试,将其merge到master分支,这会触发另一次构建。一旦再次通过测试,之前设置的curl命令就会触发Docker Hub去启动一次新构建,一切都很完美。

结论

我们已经跑通了这个基于CircleCI的持续集成工作流程(步骤1-6):

  1. 在本地功能分支上完成应用代码。2. 在Github上发起一个到master分支的Pull Request。3. 在Docker容器上运行自动测试。4. 如果测试通过,手动将这个PR merge进master分支。5. 一旦merge成功,再次运行自动测试。6. 如果第二次测试也通过,就在Docker Hub上对应用进行构建。7. 一旦构建完成,自动化地部署到生产环境。

关于整个流程的最后一块拼图呢:及自动化地将应用发布到盛传环境(第7步),你可以在我的另一篇博客中得到答案。

翻译自:https://realpython.com/blog/python/docker-in-action-fitter-happier-more-productive/
译者:李明(armyiljfe@gmail.com,https://github.com/hydRAnger
原文:Docker实战:更轻松、更愉快、更高效

你可能感兴趣的文章

linjunzhugg · 2015年04月22日

咦,译文

回复

linjunzhugg · 2015年04月22日

点个赞

回复

载入中...
Aomine Aomine

2k 声望

发布于专栏

Aomine

37 人关注

系列文章