利用 GitLab CI/CD 和 Envoy 自动化测试和部署 Laravel 项目

准备

  • 目标服务器(生产服务器),运行着测试 Laravel 项目,可 ssh 登陆
  • GitLab 服务器,包含了测试项目
  • 本地系统(Linux/Mac)一个测试用的 Laravel 项目

目标服务器准备工作

创建新用户

# 安装 acl 软件包便于进行文件和目录权限控制
sudo apt install acl
# 添加用户 deployer 用于执行发布任务
sudo adduser deployer
# 赋予 deployer 用户目录权限
sudo setfacl -R -m u:deployer:rwx /var/www

生成 ssh 密钥对

为刚刚的新用户生成 ssh 密钥对,方便 GitLab 发布或者本地测试时用密钥登陆生产服务器执行命令,注意密钥不要用密码(passphrase)保护:

# 在生产服务器上切换到 deployer 用户
sudo su deployer
# 生成 ssh 密钥对,邮箱换成自己的
ssh-keygen -t rsa -C "your.email@example.com" -b 4096
# 把公钥复制到 deployer 用户的 authorized_keys,后续可以让 GitLab 直接用私钥登陆
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

把私钥放到 GitLab 项目Settings | CI/CD | Variables 内,供后续使用:
ci_cd_variable

Envoy 使用

envoy 用来定义一些在远程服务器执行的任务,使用 blade 语法。Laravel 官方文档有介绍:Envoy Task Runner
以下 envoy 操作均在本地 Linux/Mac 机器上进行。

Envoy 安装

composer global require laravel/envoy
## 将 ~/.composer/vendor/bin 加入环境变量才能正常使用 envoy 命令
## 比如终端使用 zsh 的话,将 export PATH="$PATH:~/.composer/vendor/bin" 附加到 .zshrc 文件里
echo "export PATH=\"\$PATH:~/.composer/vendor/bin\"" >> .zshrc
source .zshrc
# 检查下环境变量
echo $PATH

Envoy 任务测试

在 Laravel 项目根目录添加 Envoy.blade.php 文件,内容如:

@servers(['web' => 'deployer@remote_host'])

@task('list', ['on' => 'web'])
    ls -l
@endtask

其中 @servers 定义了 ssh 登陆服务器的信息,'服务器名称=>值' 的形式,值里可以指定 ssh 参数,可以理解成一条完整的 ssh 命令除去开头的 ssh 后面的部分。
为了 envoy 命令的正常执行,需要本地机器的用户能正常 ssh 登陆到目标服务器,我们把本地的 ssh 公钥(~/.ssh/id_rsa.pub)加到目标服务器 deployer 用户的 authorized_keys 里,过程略。
此时在项目根目录执行 envoy run list,可以正常登陆到目标服务器并将命令ls -l 的执行结果返回:

➜  lowlog git:(master) ✗ envoy run list       
[deployer@low.bi]: total 0

Envory 编写发布任务脚本

直接上示例内容 Envoy.blade.php

@servers(['web' => 'deployer@192.168.1.1'])

@setup
    $repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
    $releases_dir = '/var/www/app/releases';
    $app_dir = '/var/www/app';
    $release = date('YmdHis');
    $new_release_dir = $releases_dir .'/'. $release;
@endsetup

@story('deploy')
    clone_repository
    run_composer
    update_symlinks
@endstory

@task('clone_repository')
    echo 'Cloning repository'
    [ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
    git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
@endtask

@task('run_composer')
    echo "Starting deployment ({{ $release }})"
    cd {{ $new_release_dir }}
    composer install --prefer-dist --no-scripts -q -o
@endtask

@task('update_symlinks')
    echo "Linking storage directory"
    rm -rf {{ $new_release_dir }}/storage
    ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage

    echo 'Linking .env file'
    ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env

    echo 'Linking current release'
    ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
@endtask

@setup 定义了一些变量,@story 将整个流程拆分为了几个单任务,@task 定义每个任务具体的操作。

分为三个步骤:

  1. clone_repository git 拉取最新代码,以时间为文件夹名,每次拉取的代码分开存放在 releases 文件夹内。
  2. run_composer composer 安装依赖。
  3. update_symlinks 更新软链接。storage 文件夹不变,每次发布通过软连接链接到最新版本下,.env同理,最后创建一个名为 current 的软链接,current 将作为项目根目录,总是链接到最新的代码。

更改 nginx 配置

需要将 nginx 里配置的 root 地址从 public 改为 current/public

root /var/www/lowlog/current/public;

更新 载入nginx 配置

sudo nginx -t
sudo systemctl reload nginx

到目前为止,已经可以在本地机器执行 envoy run deploy,手动将项目发布到目标服务器上了,下面开始配置 GitLab 实现自动发布。

此时目标服务器上只保留四个文件/文件夹:

  • storage:存储目录
  • releases:代码版本目录
  • current:软链接,链到最新版本的代码
  • .env:配置文件

GitLab CI

我们想要让 GitLab 建立并运行一个 Docker 镜像,镜像包含完整的运行环境,在镜像内执行单元测试,以及版本发布任务。

Dockerfile

在项目根目录下新建 Dockerfile,内容如下:

# Set the base image for subsequent instructions
FROM php:7.1

# Update packages
RUN apt-get update

# Install PHP and composer dependencies
RUN apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev

# Clear out the local repository of retrieved package files
RUN apt-get clean

# Install needed extensions
# Here you can install any other extension that you need during the test and deployment process
RUN docker-php-ext-install mcrypt pdo_mysql zip

# Install Composer
RUN curl --silent --show-error https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Install Laravel Envoy
RUN composer global require "laravel/envoy=~1.0"

这个 Docker 镜像以 php:7.1为基础,安装了一些软件包、PHP 扩展、composer 以及 Laravel Envoy。

Container Registry

GitLab 需要启用 Container Registry 用以保存 Docker 镜像,启用方法见: GitLab Container Registry administration
我的 GitLab 安装在内网机器,通过反向代理访问的,启用方法参考:GitLab + Container Registry 安装及反向代理配置

之后在项目设置里开启 Container Registry:
Container Registry 启用成功后,项目菜单会出现 Registry,里面有 Container Registry 的使用方法:

大致就是三条指令 docker logindocker builddocker push,在项目根目录下执行:

docker login registry.codegeass.cc
docker build -t registry.codegeass.cc/qqjt/lowlog .
docker push registry.codegeass.cc/qqjt/lowlog

这三条指令的执行需要事先在本地机器上 安装 了 docker,如果权限不够就在前面加上 sudo,成功之后就可以在 GitLab 看到镜像:

GitLab Runner 准备

GitLab Runner 是 GitLab CI 中的执行者,用来执行项目里的 .gitlab-ci.yml 文件中定义的指令。Runner 有三种类型:shared runnersspecific runnersgroup runners,更多介绍见: Configuring GitLab Runners
我这里是在公网服务器上安装了gitlab runner 并注册为specific runner

  • 安装 GitLab Runner
    参照 文档 进行包安装:

    # For Debian/Ubuntu/Mint
    curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
    sudo apt-get install gitlab-runner
  • 注册 Runner

    1. 打开 GitLab 中项目的Settings | CI/CD,获取注册 runner 所需要的 URLtoken
      specific runner
  1. 在安装了 Runner 的机器上执行 sudo gitlab-runner register,按照提示填入 URL 和 token 参数,其中 Executor 类型选择 docker。更多介绍见 GitLab Runner Executors

.gitlab-ci.yml

.gitlab-ci.yml 文件指定了 Runner 要执行的指令,我们在项目根目录下创建 .gitlab-ci.yml,内容如下:

image: registry.codegeass.cc/qqjt/lowlog:latest

services:
- mysql:5.7

variables:
  MYSQL_DATABASE: homestead
  MYSQL_ROOT_PASSWORD: secret
  DB_HOST: mysql
  DB_USERNAME: root

stages:
- test
- deploy

unit_test:
  stage: test
  script:
  - cp .env.example .env
  - composer install
  - php artisan key:generate
  - php artisan migrate
  - vendor/bin/phpunit

deploy_production:
  stage: deploy
  script:
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - eval $(ssh-agent -s)
  - ssh-add <(echo "$SSH_PRIVATE_KEY")
  - mkdir -p ~/.ssh
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  - ~/.composer/vendor/bin/envoy run deploy
  environment:
    name: production
    url: https://low.bi
  when: manual
  only:
  - master

里面使用我们之前 push 的 Docker 镜像,附加了一个 mysql 镜像作为数据库,定义了俩个步骤:unit_testdeploy_production,前者自动后者手动,其中 deploy_production 里用到了之前设置 ssh 时定义的变量 SSH_PRIVATE_KEY
.gitlab-ci.yml commit 并 push 到 GitLab 后,会触发 Pipelines
pipeline
点进 pipeline 可以看到具体的执行情况。
至此,利用 GitLab CI/CD 和 Envoy 自动化测试和部署 Laravel 项目全部步骤就完成了。

参考资料