本文讲的是Docker Workflow(一):一个可用于生产环境的Docker工作流,【编者的话】作者工作于墨西哥IIIEPE研究院,他将通过一系列文章,为我们逐一讲述他们在Docker实际应用过程中的经验与教训,给后来者提供一些参考。本文主要介绍了他基于Docker的开发工作流,包括GitLab、Jenkins、Registry、Nginx。
Docker现在已经两岁了(译者注:Docker于2013年3月13日首次发布),IIIEPE已经在生产环境中使用Docker3个来月。在此,我分享一些我们的经验和设计的工作流。
我们运行着多个使用Drupal、PHP和Node.js的网站,我们的目标是使用Docker运行所有的应用,因此我们设计了以下工作流:
- 所有开发人员使用Docker来创建应用。
- 我们的GitLab实例配置了Webhook,当检测到一个新的推送时,它将命令Jenkins运行一个任务。
- 每个Jenkins任务都包含相同的布置:从GitLab中克隆最新代码,运行测试,登录到我们的私有Docker Registry,使用最新代码构建一个新的镜像,然后将镜像推送上去。
- 最后,我们的编排软件Maestro-NG将部署新版本的镜像。
- 我们的负载均衡器将检测这些变化,并重载新的配置。
每一个步骤都需要几天的规划、测试和工作来设计基本准则。
我们做的第一件事是构建满足自己要求的基础镜像。镜像发布到Docker Hub中,它们包含了除了应用本身之外所有用于运行应用的东西。每次修改其中一个基础镜像,我们就会运行一个Jenkins任务来拉取新镜像,并触发后续任务来重新构建依赖此基础镜像的所有镜像。
创建完镜像之后,我们需要为所有应用定义一个标准结构。我们所有的应用都使用以下结构来组织:
/application /logs /files Dockerfile fig.yml.example docker-compose.yml.example Makefile
/application目录是应用的根目录。
/logs和 /files目录用于开发,以便应用可以写入日志和文件。这两个目录会被Git忽略,并在生产环境中完全排除。
Dockerfile是Jenkins用于构建镜像的文件,开发人员几乎不需要接触这个文件,后面详述。
fig.yml.example和docker-compose-yml.example是开发人员用于启动应用的文件。这二者均不用于生产环境,当开发人员克隆一个项目时,他需要复制这个example文件并填入他/她的值。
Makefile是拼图的最后一块,通过它我们可以拥有一个标准的命令集用于所有应用,并对开发人员隐藏各种各样的复杂性。
Dockerfile
每个应用的Dockerfile与其它应用非常类似,这个文件的最重要工作就是构建包含所有待部署代码的最终镜像。我们来看一个例子:
FROM iiiepe/nginx-drupal6 ENV MYSQL_ENV_MYSQL_DATABASE somedb ENV MYSQL_ENV_MYSQL_USER root ENV MYSQL_ENV_MYSQL_PASSWORD 123 ENV MYSQL_PORT_3306_TCP_ADDR localhost ENV MYSQL_PORT_3306_TCP_PORT 3306 ENV BASE_URL http://example.com ENV DRUPAL_ENVIRONMENT production EXPOSE 80 RUN usermod -u 1000 www-data RUN usermod -a -G users www-data ADD ./application /var/www RUN chown -R www-data:www-data /var/www
Dockerfile依赖于我们构建的基础镜像,在此之上,它只是设置了一些环境变量默认值、声明要暴露的端口,并将应用代码添加到/var/www
。
正因为我们构建镜像的这种方式,在Jenkins和开发人员之间唯一的差别是,Jenkins将添加整个应用目录到/var/www
中,而开发人员只是映射一下目录。
接下来这个是Docker Compose,他非常酷。
mysql: image: mysql:latest expose: - "3306" ports: - "3307:3306" environment: MYSQL_DATABASE: database MYSQL_USER: root MYSQL_PASSWORD: admin123 MYSQL_ROOT_PASSWORD: admin123 web: image: iiiepe/nginx-drupal6 volumes: - application:/var/www - logs:/var/log/supervisor - files:/var/www/sites/default/files ports: - "80:80" links: - mysql:mysql environment: BASE_URL: http://local.iiiepe.net DRUPAL_ENVIRONMENT: development
Docker Compose用此文件来初始化。在本例中,我们定义了一个应用,它包括两个容器:一个MySQL容器和一个Web容器。
MySQL容器定义了mysql镜像要使用的环境变量。同时将宿主的3307端口映射到窗口的3306端口上。这允许我们使用任何客户端访问MySQL服务器。
web容器使用了与Jenkins构建最终镜像时使用的相同镜像(见上述Dockerfile),但它同时共享了一些数据卷。在宿主和容器间共享的卷有应用、文件和日志。这实际是开发环境和生产环境间最大的改变:在生产环境中,容器的代码是在镜像中的,这允许我们在任何服务器上启动容器;而在开发环境中,目录只是共享的,因此在应用目录里,任何新的文件或对文件的修改都将即时地反映到容器里。
BASE_URL变量指向了http://local.iiiepe.net,这不是个真实的地址,只是用于标准化应用访问的一个方式。因为我们有些人使用Mac和Boot2Docker,我们需要一个标准的地址以便所有人可以将其写入到/etc/hosts文件中。
我的Mac上的/etc/hosts是这样的:
127.0.0.1 localhost 192.168.59.103 local.iiiepe.net
在一台Linux机器上,看起来是这样的:
127.0.0.1 localhost local.iiiepe.net
最后,我们定义了两个环境变量来决定应用的配置文件的一些设置。
自定义设置
Drupal需要一个settings.php来存储数据库信息,包括密码。该文件将被Git忽略,以免将你的密码提交上去,我们决定修改这个文件,让它使用环境变量并将其提交。
以下是一个Drupal 6网站的settings.php里的重要部分:
$username = getenv("MYSQL_ENV_MYSQL_USER"); $password = getenv("MYSQL_ENV_MYSQL_PASSWORD"); $host = getenv("MYSQL_PORT_3306_TCP_ADDR"); $port = getenv("MYSQL_PORT_3306_TCP_PORT"); $database = getenv("MYSQL_ENV_MYSQL_DATABASE"); $db_url = 'mysql://' . $username . ':' . $password . '@' . $host . '/' . $database;
正如你所看到的,没有密码会被提交。密码和其它敏感值将通过ENV变量注入。
有些网站使用Apache Solr作为搜索引擎,但在开发时,我们不希望能写入到Apache Solr,因此需要一个类似DRUPAL_ENVIRONMENT的ENV变量,完成类似下面的settings.php文件的事情:
$conf = array(); if(getenv("DRUPAL_ENVIRONMENT") === "development") { // Disable apache solr writting $conf["apachesolr_read_only"] = 1; }
Makefile
由于命令很长,使用Docker非常不便,因此Docker Compose(fig)对此很有帮助。我们更进一步尝试让事情变得更简单一些。
这是我们使用在一个Drupal网站上的Makefile:
CURRENT_DIRECTORY := $(shell pwd) start: @fig up -d clean: @fig rm --force stop: @fig stop status: @fig ps cli: @fig run --rm web bash log: @tail -f logs/nginx-error.log cc: @fig run --rm web drush cc all restart: @fig stop web @fig start web @tail -f logs/nginx-error.log .PHONY: clean start stop status cli log cc restart
使用Makefile比使用Docker Compose或Fig简单得多,因为我们可以创建类似make cc
的快捷方式来运行类似drush cc all
这样频繁使用的命令。
有关Makefile的最后一点说明是:我们依然使用Fig。因为在我们设计这个工作流时,Docker Compose还不可用,而且我们团队里的一些开发人员还在使用它,我们决定为Docker Compose建立名为Fig的符号连接,名称更短且更实用。安装Docker Compose后,你可以删除fig并创建符号连接:
sudo rm /usr/local/bin/fig sudo ln -s /usr/local/bin/docker-compose /usr/local/bin/fig
走的弯路
我们走过一些弯路,我将在别的文章中做介绍,不过有一个我想特别说一下。使用Docker最大的好处是,开发人员可以在与生产环境相同的环境上运行应用,且只损失一点点性能。
我看过一些文章,说他们在Docker之外做开发,然后在需要部署时构建镜像并发送到生产环境。如果你这么做,那你就错了,因为你开发所用的环境与生产机上运行的不一致。不要每次都构建镜像,相反的,在宿主和容器间共享卷,让别人在你每次推送时构建镜像。
未完待续……
文章还远未结束,不过它已经太长了。我们依然需要说明我们是如何整合Maestro-NG、配置Jenkins以及负载均衡器是如何工作的。咱们回见!
原文链接:A production ready Docker workflow(翻译:梁晓勇 校对:李颖杰)
原文发布时间为:2015-03-26
本文作者:sean
本文来自合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:Docker Workflow(一):一个可用于生产环境的Docker工作流