使用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和最终部署都有很大关系。

将要做的事

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

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

[root@localhost docker]# tree

.

├── docker-compose.yml

├── getInDocker.sh

├── jenkins_modified

│   ├── Dockerfile

│   └── etc

│       ├── localtime

│       └── timezone

├── nginx_modified

│   ├── conf

│   │   ├── conf.d

│   │   │   └── default.conf

│   │   ├── fastcgi.conf

│   │   ├── mime.types

│   │   └── nginx.conf

│   └── Dockerfile

├── php-fpm_modified

│   ├── conf

│   │   └── www.conf

│   └── Dockerfile

├── project-build.sh

└── www-data

    ├── Dockerfile

    └── web

        ├── a.txt

        └── index.php

我们需要准备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

 

1

2

3

4

5

6

7

8

9

10

11

FROM nginx:1.10.2

 

MAINTAINER catscarlet

 

ENV NGINX_VERSION 1.10.2-1~jessie

 

COPY conf/ /etc/nginx

 

EXPOSE 80 443

 

CMD ["nginx", "-g", "daemon off;"]

逐条解释下:

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

 

1

2

3

4

5

6

7

8

9

10

11

12

server {

    listen       80;

    root         /var/www/html;

    index        index.php index.html;

 

    charset utf-8;

 

    location ~ \.php$ {

        fastcgi_pass   php-fpm:9000;

        include        fastcgi.conf;

    }

}

生成镜像:

 

1

docker build -t 'nginx_app' nginx_modified/

测试启动:

 

1

# docker run -d -p 10080:80 nginx_app

因为我们的设备上还跑着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,我们只指定以下版本就好了

 

1

2

3

FROM php:7.0.13-fpm

 

MAINTAINER catscarlet

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

代码镜像

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

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

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

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

Dockerfile

 

1

2

3

4

5

6

7

FROM debian:jessie

 

MAINTAINER catscarlet

 

COPY web/ /var/www/html/

 

VOLUME '/var/www/html/'

web/a.txt

 

1

This is a text file.

web/index.php

 

1

2

3

4

5

6

7

8

9

10

<?php

 

ini_set('date.timezone', 'Asia/Shanghai');

header('Content-Type:text/plain; charset=utf-8');

 

$hostname = file_get_contents('/etc/hostname');

$date = date('r', time());

$rst = 'This is Server: '.$hostname."\n".'Now is '.$date;

 

echo $rst;

Docker Compose

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

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

安装

执行命令如:

 

1

2

curl -L https://github.com/docker/compose/releases/download/1.9.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

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

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

配置

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

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

www-data:

    build: ./www-data

 

php-fpm:

    build: ./php-fpm_modified

    expose:

        - 9000

    volumes_from:

        - www-data

 

nginx_app:

    build: ./nginx_modified

    expose:

        - 80

    links:

        - php-fpm

    volumes_from:

        - www-data

    ports:

        - "9080:80"

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值