日常项目开发过程中,一般都有多套环境,比如开发、测试和生产。各个环境部署的代码版本不一致,手动一个个来部署效率低且容易出错。如果项目采用了敏捷开发方式,可能每天需要部署几十次。手动方式更加不可行,因此必须要把多环境的部署工作自动化。本文将介绍在笔者参与项目中实际推行的一种方案,供大家参考。

整体方案

整体方案

当开发人员 push 代码到 develop、release 和 master 分支时将自动触发构建 Docker 镜像,然后部署到分支对应的开发环境。

Git 分支模型

每套环境部署的代码版本都不一样,还在开发中的功能只能部署到开发环境,已经开发完成的功能可以部署到测试环境,测试通过的功能才能部署到生产环境。依托于 Git 强大的分支功能,我们可以轻松地为每个环境维护一个分支,适当的时候在分支之间进行代码的合并。但分支多了容易混乱,开发人员可能搞不清什么时候应该提交到哪个分支。所以团队里需要制定一个分支管理规范,或者说确定一个分支模型。

Git分支模型

上图是 Git-flow 的分支模型。图中 develop 和 master 为长线分支,feature、release 和 hotfix 为短线分支。develop、release 和 master 分别部署到开发环境、测试环境和生产环境。develop 分支用来汇总下个版本功能的提交,在下个版本的 release 分支创建之前不允许提交非下个版本的功能到 develop 分支。当下个版本的所有功能开发完成后,从 develop 分支创建下个版本的 release 分支来测试,测试过程中 bug 修复需要提交到该 release 分支。测试完成后,将 release 分支合并到 master 分支去上线,同时还要把 release 分支上的 bug 修复合并回 develop 分支,最后删除 release 分支。

Git-flow 的安装请参考官方文档,安装完成后会给 git 命令增加一个子命令 flow,使用这个子命令可以简化分支操作。

初始化

为了能在 Git 项目里使用 git flow 命令,需要先初始化 Git-flow。

> git flow init
Initialized empty Git repository in /Users/jagger/tmp/git-flow/.git/
No branches exist yet. Base branches must be created now.
Branch name for production releases: [master]
Branch name for "next release" development: [develop]

How to name your supporting branch prefixes?
Feature branches? [feature/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? [] 

所有需要输入的地方直接回车,使用默认值就可以。

开发

对新功能的开发,如果功能较大建议创建一个 feature 分支来进行,小的话就直接在 develop 分支上进行。当 feature 分支功能开发完成后,合并到 develop 分支,然后删除该 feature 分支。

> git flow feature start rss-feed
Switched to a new branch 'feature/rss-feed'

Summary of actions:
- A new branch 'feature/rss-feed' was created, based on 'develop'
- You are now on branch 'feature/rss-feed'

Now, start committing on your feature. When done, use:

     git flow feature finish rss-feed

> git flow feature finish rss-feed
Switched to branch 'develop'
Already up-to-date.
Deleted branch feature/rss-feed (was 810a2a4).

Summary of actions:
- The feature branch 'feature/rss-feed' was merged into 'develop'
- Feature branch 'feature/rss-feed' has been removed
- You are now on branch 'develop'

通过简单的 git flow feature startgit flow feature finish 两条指令就能完成上述工作。

测试

当下个版本的功能都已开发完成,就可以从 develop 分支创建下个版本的 release 分支来测试。测试过程中的 bug 修复需要提交到该 release 分支。

> git flow release start 2.0
Switched to a new branch 'release/2.0'

Summary of actions:
- A new branch 'release/2.0' was created, based on 'develop'
- You are now on branch 'release/2.0'

Follow-up actions:
- Bump the version number now!
- Start committing last-minute fixes in preparing your release
- When done, run:

     git flow release finish '2.0'

发布

测试通过后,合并 release 分支到 master 和 develop 分支,然后删除 release 分支。

> git flow release finish 2.0
Switched to branch 'master'
Deleted branch release/2.0 (was 810a2a4).

Summary of actions:
- Latest objects have been fetched from 'origin'
- Release branch has been merged into 'master'
- The release was tagged '2.0'
- Release branch has been back-merged into 'develop'
- Release branch 'release/2.0' has been deleted

Docker 自动化部署

各个环境的分支代码都有了,但怎么快速可靠地把分支代码部署到对应环境了?使用 Docker 可以大大简化部署的复杂性,因为它解决了应用对系统环境的依赖问题,使得应用部署变成了一项十分简单的工作。借助于 GitLab Webhook 和 容器编排服务,可以实现当分支代码有 push 的时候,自动把该分支代码部署到对应环境。

这里以 DaoCloud 容器编排服务为例来讲解。当然大家可以选择任意其它第三方或自己搭建的容器编排服务,操作方式都类似。

创建项目

一个项目对应一个 Git 仓库,项目存在的目的是为了构建和部署应用镜像。DaoCloud 除了支持 GitHub、Bitbucket 这类公有服务,自己搭建的 GitLab 服务也支持。

-----2018-04-25-20.25.36

每次分支代码有 push 的时候,都会触发构建镜像。

-----2018-04-25-20.26.02

通过流程定义来配置要执行的操作和顺序,一般按照“测试 => 构建 => 部署”这样的流程。每个操作都可以单独配置触发条件,比如 push 到指定分支。

创建主机集群

主机集群由安装了 Docker Engine 的多台服务器构成,通过在服务器上安装 DaoCloud Monitor 来把它加入到某个主机集群。部署应用时需指定用来运行应用的主机集群,或者集群里的某台服务器。

-----2018-04-25-20.33.11

上面的 dev 集群只包含一台本地的 MacBook Pro。

编排应用

编排应用就是把应用编制排放到主机集群上去运行。大多数时候应用都依赖其它后端服务,比如 MySQL、Redis 等,启动应用时需要把它们一起启动起来,并且相互之间可互相访问。这样的一组应用在 Docker Compose 里称为 Stack。

下面我们创建了三个开发环境的 Stack,分别为应用服务 Stack zqc-dev,数据库服务 Stack db-dev,缓存服务 cache-dev。之所以不把数据库和缓存服务跟应用服务放在一个 Stack 里,是因为数据库和缓存服务属于公共服务,会被多个应用服务共享,放在某个应用服务里不合适。

Docker Compose 会为每个 Stack 创建一个默认的网络,以便 Stack 里的各个服务之间可以互相访问。那么 Stack 之间了?zqc-dev 需要访问 db-dev 里的数据库服务和 cache-dev 里的缓存服务。很简单,只要把 zqc-dev 里的应用服务加入到 db-dev 和 cache-dev 的网络里就可以了。具体可参考下面的应用服务 Docker Compose 配置文件。

-----2018-04-25-20.39.20

应用服务 Stack。

version: '2'
services:
  zqc-server:
    image: daocloud.io/jaggerwang/zqc-server:develop-6be5996
    environment:
      ZQC_DIR_DATA: /data
      ZQC_SERVER_DEBUG: 'true'
      ZQC_SESSION_SECRETKEY: xxxxxxxx
      ZQC_LOG_LEVEL: debug
      ZQC_MONGODB_ZQC_ADDRS: mongo:27017
      ZQC_REDIS_ZQC_ADDRESS: redis:6379
      ZQC_REDIS_ZQC_DB: 1
      ZQC_STORAGE_BUCKETS_ZQC_ENDPOINT: oss-cn-shanghai.aliyuncs.com
    ports:
    - 10200:1323
    volumes:
    - /Users/jagger/data/zqc/server:/data
    networks:
    - db-dev
    - cache-dev
networks:
  db-dev:
    external:
      name: db-dev_default
  cache-dev:
    external:
      name: cache-dev_default

应用服务 Docker Compose 配置文件。

-----2018-04-25-20.39.50

数据库服务 Stack。

version: '2'
services:
  mysql:
    image: index.docker.io/library/mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: xxxxxxxx
    ports:
    - 10000:3306
    volumes:
    - /Users/jagger/data/db/mysql:/var/lib/mysql
  mongo:
    image: index.docker.io/library/mongo:3.4
    ports:
    - 10001:27017
    volumes:
    - /Users/jagger/data/db/mongodb:/data/db

数据库服务 Docker Compose 配置文件。

-----2018-04-25-20.39.32

缓存服务 Stack。

version: '2'
services:
  redis:
    image: redis:4.0
    command:
    - redis-server
    - --appendonly
    - 'yes'
    ports:
    - 10010:6379
    volumes:
    - /Users/jagger/data/cache/redis:/data

缓存服务 Docker Compose 配置文件。

参考资料

  1. Git-flow
  2. Docker Compose