«

»

Dec 04 2016

使用GitLab、Jenkins、Docker建立快速持续化集成交付部署方案(二)

 

上一文中我们完成了基础环境的安装。

本文将要学习 Docker Image 的自定义,及 使用Docker Compose进行环境部署的方法。

文章索引

  1. GitLab、Jenkins、Docker 初始环境安装
  2. 制作 Docker镜像 及 Docker Compose 的使用(本文)
  3. 使用 Webhook 自动触发 Jenkins 进行 Docker镜像制作并保存到私有镜像仓库,以及目的机部署

 

提示:本文是基于 Docker 1.11 编写的,从 2016 年 4月 至今,Docker 已经改变了许多。基本用法没有改变,只不过增加了很多新特性,并移除了部分不再使用的标签。至2018年为止,本文依旧适用于入门学习,但部分新特性的改变(比如 aufs->overlays ),以及部分标签的移除(比如 MAINTAINER->LABEL ),请参照 Docker 官方文档为准。

Docker一些概念

国内各个厂家对Docker概念都翻译不一样,我这里先写一下我自己的概念理解

  • 镜像(image)镜像
  • 容器(container):由单一服务构成的针对最基本功能实现的 服务(service)(比如Nginx)
  • 项目(project):多个服务构成的 应用(application)(比如由 Nginx + PHP + Mysql + WordPress组成的博客)

单个容器只能提供非常基本的服务,比如单纯的Mysql数据库服务,单纯的Redis缓存服务。

需要由多个容器协同工作,才能组成一个复杂且强大的项目,实现产品功能。

这里鄙视以下各个厂家为圈地,拉拢用户,使用了非常多的混淆概念,比如引入微服务(microservice)。

什么是他妈的微服务,微博微信朋友圈玩多了吧。不论按照Windows还是按照Linux来讲,服务(service)指的都是由单一应用程序完成的最基本的功能实现。还他妈的微,是不是还要把HTTP服务拆成 HyperText Transfer Protocol 之后分三段去实现啊。

维基百科上有针对微服务的定义:微服务 (Microservices) 是一种软件架构风格 (Software Architecture Style),它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模组化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic) 的 API 集相互通讯。微服务是由以单一应用程序构成的小服务,自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通讯。同时服务会使用最小的规模的集中管理 (例如 Docker) 能力,服务可以用不同的编程语言与数据库等元件实作。

很明显这里的微服务指的与我们所说的服务是相同的,只不过更强调其单一性,即甚至要把Apache+PHP这种组合也剥离开(传统模式下,PHP是以模块形式与Apache共同工作的,而Nginx与PHP是靠PHP-FPM共同工作,后者更颗粒化)

然而国内不少网站把微服务当做微信、微博一个级别的概念对待,以为微服务就是产品功能级的服务,订单微服务,通知微服务,狗屎微服务。

使用Docker实现一个项目

我们在这里,要基于nginx、php-fpm,实现一个PHP应用。这个项目将作为一个基础学习项目,对接下来的Jenkins和最终部署都有很大关系。

将要做的事

首先先给大家看一下我的目录结构,如果你对我写的整个结构都很一目了然的话,证明你对这一部分都很熟悉,你可以跳过这一章节了。

我们需要准备nginx和php-fpm的Dockerfile,从Docker Hub上获取。编写一个静态文件和一个php文件,并为自己的代码制作一个Dockerfile用于生成数据卷容器。

没人说过容器里不能放纯代码的。纯代码,满足最基本颗粒化要求(存储),无状态或状态不重要(随用随建,不用即删,临时文件不重要)。

至于为什么要专门为php等代码建立数据卷容器,而不是直接使用git或其他代码管理工具同步到硬盘上的原因,将在后面介绍。

nginx

Let’s make a nginx_modified

确认配置方法

首先我们要知道nginx的镜像内部到底是怎样的。有两种方法:

第一种,生成一个默认容器,并进入容器内部观察结构。这是一种不推荐的做法,因为除非你对整个服务有及其充分的了解,否则你永远摸不清它里面到底是怎样的。这就相当于你作为一个人类活了一辈子,除非你是专业的医生,否则你是没办法拿手术刀给别人做手术的。

第二种,根据Docker Hub上的描述来修改相应的配置。这是推荐的办法,多数项目都会对自己的docker项目有比较详细的描述。而且多数项目可能会针对Docker做一些调整,他们的配置与传统的编译安装配置也是不同的。使用推荐的办法修改配置是最安全的办法。

除非他们自身的描述写的相当渣,或者完全就没有描述,否则不要使用第一种方法。

Dockerfile

Dockerfile是用于构建镜像的文件,其内包含了多指令,每一条指令构建一层。

这是为自定义nginx服务而编写的dockerfile

逐条解释下:

FROM:基于哪个版本的镜像生成新的镜像,我们这里选择stable版的1.10.2。如果这里不指定版本号,docker会默认使用latest版本(2016年11月24日为1.11.5)

MAINTAINER:这个镜像的作者名

ENV:指定环境变量,这里声明了nginx的版本

COPY:将本地conf/中的文件复制到镜像的/etc/nginx目录下。使用这个办法我们替换了nginx的配置文件。包括默认网站、对php文件的处理等。

EXPOSE:可映射的端口

CMD:容器启动时要执行的命令

VOLUME:定义匿名卷

nginx的default.conf

生成镜像:

测试启动:

因为我们的设备上还跑着GitLab和Jenkins呢,所以要避开许多端口,这里选择使用10080。

使用浏览器打开本机的10080端口,这个时候/var/www/html并没有映射到任何数据卷,所以看到的应该是nginx的默认欢迎界面。

底层执行docker ps查看这个容器,使用docker down {container_id}停止这个容器,使用docker rm {container_id}删除这个容器。你也可以在容器未停止时,直接使用docker rm -f {container_id}强制删除这个容器。

提示:你可以使用docker rm $(docker ps -a -q)删除所有已停止的容器

底层执行docker image查看生成的镜像信息,使用docker rmi {image_id}删除这个镜像。

提示:你可以使用docker rmi $(docker images -q -f "dangling=true")删除所有无tag的镜像。

PHP-FPM

接下来我们制作PHP-FPM的镜像。

其实完全没有必要,PHP-FPM不仅是真正的无状态的,而且几乎没有修改配置的必要。

Dockerfile,我们只指定以下版本就好了

默认情况下,会继承原镜像的端口映射9000。

代码镜像

这里再次强调一下,Docker的镜像是分层文件系统,我们的自定义nginx镜像并不会占用一个整个nginx镜像所需的硬盘空间,只占用我们添加的配置文件的空间大小,而所需的nginx则继续使用FROM中的镜像。

那么对于没有基础依赖的代码镜像,应该FROM什么镜像呢?

网上有不少办法是基于一个最小化的Linux操作系统来做,比如Busybox。Bullshit!

注意一下现在所有的Docker镜像,生成他们的Dockerfile都是有FROM的,而且一般都是 debian:jessie。这意味着多数镜像都是依赖debian:jessie的,debian:jessie已经存在于我们的本地镜像库中。我们也可以继续基于debian:jessie制作镜像。

Dockerfile

web/a.txt

web/index.php

Docker Compose

Docker的每个容器只完成最基本的服务,而想要实现一个产品功能,往往需要多个基本服务共同完成。对于每个服务来讲他们是无依赖的,但对于项目来讲,他们就是需要紧密结合的。

Docker Compose就提供了这样一个功能,允许定义一组容器,组成一个项目。

安装

执行命令如:

官方也提供pip安装方式,但依赖和局限性比较多,在不同的系统上有不同的问题,不建议用这种方式安装

相关文档:https://docs.docker.com/compose/

配置

Docker Compose使用YAML格式的配置文件:docker-compose.yml

主要使用方法:直接在docker-compose.yml的路径下,执行以下命令:

  • docker-compose build 新建(或重建)项目中所有需要build的镜像。此步非必须。
  • docker-compose up 新建(或重建)项目中所有需要build的镜像,并启动对应项目。这是一个前台操作,在所有容器内所有进程退出之前不会退出,使用Ctrl-C会停止容器,但不会删除容器。使用 -d 参数使项目在后台启动。
  • docker-compose start 启动一个已经存在的项目。
  • docker-compose stop 停止一个已经运行的项目。
  • docker-compose down 停止一个已经运行的项目,并删除所有相关联容器。

启动项目

在docker-compose.yml的路径下执行docker-compose up -d。

现在访问你的服务器http://your-server:9080/a.txt应该可以打开这个静态文件 a.txt,http://your-server:9080/index.php可以打开执行后的php结果了。

一些问题

代码位置问题

有很多开发者会把要运行的代码与服务打包到同一个镜像中。

这些开发者一般多为JAVA开发者。对于JAVA开发者来讲,他们的项目结构可能是这样的:TOMCAT + JAVA包 + 数据存储。对于 TOMCAT + JAVA包 这种情况便是强依赖环境,同样符合无状态和最小颗粒的特性,或许并没有问题。

然而对于其他语言的开发者,项目结构一般为 HTTP服务 + 语言解析器 + 代码 + 数据存储。

以PHP开发者举例,可以理解为 NGINX + PHP-FPM + 代码 + Mysql。而代码中不仅包含php代码,也会包含html、js、css等代码。

将代码单独的放入 NGINX 镜像或者 PHP-FPM 镜像,都会导致运行过程中,容器之间无法获取代码,解析时出现404错误。

另外在日常开发时,经常变动的也只有代码部分,其他镜像一般多为万年不变的。将代码独立到单独容器中也会减小部署压力。

(目前还没见到过PHP开发者使用Docker和做持续化部署的)

配置文件问题

我们对标准的nginx镜像进行了修改,添加和修改了配置文件。对于应可以随时替换的标准服务来讲这并不完美。于是有一种思路,就是把配置文件像代码一样也做成数据卷容器。这样做未尝不可,但难度会很大。因为挂在数据卷的话,会导致配置文件下整个目录覆盖,可能会影响到我们并不想更改的默认参数。

2 comments

  1. 王庭威
    Google Chrome 71.0.3578.80 Google Chrome 71.0.3578.80 Windows 10 x64 Edition Windows 10 x64 Edition
    Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36

    我在使用docker-compose时候,就遇到了这个疑惑。给nginx或者php 配置volumes挂载路径时候犯了愁,因为的nginx.conf还有站点路径都是现成的,是要做迁移的,而下载镜像的路径都是和这个不匹配的(类似yum安装的路径),我曾试图下一个镜像看看内部结构,再去挂载。太傻了。
    博主的说明给了我很大的帮助。可以去docker-hub去找资源,并看他的使用说明。之前有个朋友也提到过。但是一直没特意去hub上找,多谢!

    1. 石樱灯笼
      Google Chrome 71.0.3578.99 Google Chrome 71.0.3578.99 Android 8.0.0 Android 8.0.0
      Mozilla/5.0 (Linux; Android 8.0.0; G8342) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36

      「下一个镜像看看内部结构,再去挂载」,这其实也是不错的方法,在docker的学习阶段很有助于对其实现的理解。推荐的方法更着重于对现有服务的复用,以及大规模合作部署的兼容。当你开始着重于部署自己编译的应用(C语言等)时,就会感觉受益匪浅了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据