docker实践入门之六:举个栗子

最后一篇来举个栗子。

样本项目

  1. 一个基于bottle的python web项目
  2. 依赖mako
  3. 暂时只考虑部署一个版本的例子

项目有以下内容:

+-appname.py
+-config.json
+-requirements.txt
+-views
  +-index.html

requirements.txt的内容为:

bottle
mako

virtualenv部署方式

传统的python应用部署方式一般会推荐用virtualenv,它可以实现一个相对隔离的python环境供应用运行,避免因为不同的应用有不同的依赖导致不必要的冲突。

首先你需要建立一个基础环境,包括:

  • OS
  • 安装apache/nginx等webserver并配置
  • 安装相应版本的python
  • 安装virtualenv
  • 创建应用专用的virtualenv环境

之后通过hg/git更新应用代码,再用pip和requirements.txt更新依赖,再用gunicorn/uwsgi等wsgi server来启动应用(必要时辅以supervisord进行管理)

一个简单的fabric部署脚本类似这样:

with settings(warn_only=True):
    out = local("ps ax | grep appname.py | awk '{ print $1; }'")
if out and len(out) > 0:
    local("kill {0}".format(out[0]))
local("hg pull")
local("hg update")
local("pip install -r requirements.txt")
local("/path_to_virtualenv/appenv/bin/python appname.py")

功能就是检查是否已经运行了应用,如果运行则kill之,更新代码后重新运行。

这里为简单起见,没有使用wsgi server和supervisord。

这个做法存在几个问题:

首先是你需要自己手工准备一个基础环境。其次是不方便部署到多台机器。

接下来看看docker是怎么做的。

docker的部署方式

首先是docker image的准备。基础环境可以直接用前一篇文章里那个python3环境的image例子用的Dockerfile,这里不再重复。

应用环境同样可以用那个例子,区别无非是加上代码更新,而这个更新可以在build之前就在host上运行一次hg pull/hg update更新好(或者直接使用jenkins这种自动化部署工具来更新并部署),之后再build。

所以,需要做的工作只是写一个部署脚本:

def deploy_app(appname, skipbuild=False):
    if not skip_build:
        with settings(warn_only=True):
            local("docker tag {appname} {appname}:old".format(appname=appname))
            local("docker rmi {appname}".format(appname=appname))
        local("docker build -t {appname} ..".format(appname=appname))
        out = local("docker ps -a", capture=True)
        pat = re.compile("([0-9a-f]{{12}})\s+{0}".format(appname))
        m = pat.findall(out)
        if len(m) > 0:
            local("docker rm -f {0}".format(m[0]))
        with settings(warn_only=True):
            local("docker rmi {0}:old".format(appname))
    out = local("ip route | awk '/docker0/ { print $NF }'", capture=True)
    host_ip = "--add-host=dockerhost:{0}".format(out)
    local("docker run -d -p 8080:8080 --name={appname} {host_ip} -v /var/www/log:/var/www/log {appname}".format(appname=appname, host_ip=host_ip))

这样只要运行:

fab deploy_app:appname=appname

如果不需要重新构建image,还可以这样直接启动container:

fab deploy_app:appname=appname,skipbuild=True

本文毕竟号称入门实践,到这里基本算是入完门了,剩下就看你自己的。

思考

如果要部署多个版本分别用于开发、测试、生产,每个版本有各自不同的配置文件的情况要如何做?

推送到[go4pro.org]