构建php镜像(docker)

背景

最近着手进行公司内项目的容器化改造,因此需要构建一个php的docker镜像

说实话,php由于本身的原因,在安装扩展方面确实没有其他语言那么顺畅,注定是要采坑的
经历了各种各样的错误信息以后,最终是构建完成了

构建的版本

  • php8.0-fpm
  • alpine

ps: 构建镜像的时候,我们应该尽可能的选择alpine系统,来保证镜像的大小,因为在实际的运行过程中,我们会有成千上万个contiainer在运行,镜像每多100M的容量,在运行过程中的消耗都是不可估量的

这里给出一份Dockerfile

FROM php:8.0-fpm-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk update
RUN apk add --no-cache  freetype-dev \
        libjpeg-turbo-dev \
        libmcrypt-dev \
        libpng-dev \
        zlib-dev

RUN apk add gcc g++ make libffi-dev openssl-dev m4 autoconf

RUN docker-php-ext-install pdo_mysql pcntl opcache bcmath \
    && docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd

RUN pecl install  grpc \
     && docker-php-ext-enable grpc  
RUN printf "no\nno\nno\n" | pecl install -o -f redis \
    && docker-php-ext-enable redis \
    && rm -rf /tmp/pear

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
        && composer config -g repo.packagist composer https://packagist.phpcomposer.com 

这期间遇到了挺多问题的 稍微列举一下吧

  1. ERROR: `phpize' failed

    原因是没有安装m4 autoconf 使用命令apk add m4 autoconf 解决

  2. configure: error: C compiler cannot create executables

    原因是用的镜像是alpine版的 很多的工具和依赖在原始的系统内并不存在
    使用命令apk add gcc g++ make libffi-dev openssl-dev解决

踩一次vagrant网络的坑

问题描述
使用vagrant启用虚拟机后,局域网内可以访问,但是局域网的无线wifi无法访问

环境描述

主机:

  • ip: 192.168.1.189
  • netmask: 255.255.255.0
  • gateway: 192.168.1.254

虚拟机

  • ip: 192.168.1.188
  • netmask: 255.255.255.0
  • gateway: 此处有坑 下文详细介绍

验证过程

最开始发现这个问题的时候,就已经猜测是网络的问题了
于是我启动另一台windows虚拟机,使用手动配置ip的方式 发现网络是可达的
因此这个地方就可以确定是网关配置的问题了
那么翻一下vagrant的官方文档
找到了这样一个配置

Vagrant.configure("2") do |config|
  config.vm.network "public_network", ip: "192.168.0.17"

  # default router
  config.vm.provision "shell",
    run: "always",
    inline: "route add default gw 192.168.0.1"

  # default router ipv6
  config.vm.provision "shell",
    run: "always",
    inline: "route -A inet6 add default gw fc00::1 eth1"

  # delete default gw on eth0
  config.vm.provision "shell",
    run: "always",
    inline: "eval `route -n | awk '{ if ($8 ==\"eth0\" && $2 != \"0.0.0.0\") print \"route del default gw \" $2; }'`"
end

仔细看看这里有一个这样的配置

inline: "route add default gw 192.168.0.1"

那么解决方法就来了

解决问题

首先在虚拟机中ifconfig查看自己的网卡
我的ubuntu中是enp0s8 (centos中一般是eth1)

修改VagrantFIle如下

 config.vm.network "public_network", ip:"192.168.110.191", bridge:"enp4s0"
 config.vm.provision "shell", run: "always", inline: "route add default gw 192.168.110.254  enp0s8"

最后执行命令,搞定

vagrnat provision

随记

我一直在反思一件事情,我们现在的开发模式真的能够适应我们现在的环境么?

我们真的能仅仅以做一个项目的角度来考虑我们现在做的事情么?

出现一个需求 前后端排期 分一下工 做完这个需求 真的就结束了这一次任务么

隐藏在这个任务更加深层的逻辑 究竟要由谁 在什么时间点来完成

有没有这样一个角色担当 来为整个项目团队负责

也许在大多数人看来 做业务才是有价值的吧

包括我自己也是一样

很多时候都会被表面的短期价值所蒙蔽

总任务我今天写的两行代码 明天修复了两个bug 就能产生收益

但这其实就是一个谬论吧

只有取到成果以后才有论功行赏的过程

因此我们应该坚定一件事情

只要我们的出发点不是仅基于自身的利益为出发点的

那么这些行动就存在它的价值

不计较某一行代码

某一个功能的得失

未来的路很长

也许我们现在的行动 并不一定能最终都产生积极的意义

但是只要方向正确

我们总有抵达彼岸的那天

shell中子shell的妙用

shell脚本子shell的妙用

目录结构

├── a
├── b
├── c
├── d
└── test.sh

现有这样一段脚本(此处代码加上行号是为了方便说明)

 1 #!/bin/bash
  2 
  3 set -e
  4 
  5 pwd=`pwd`
  6 
  7 for file in  `ls $pwd`
  8 do
  9    if [ -d $file ]
 10    then
 11        cd $pwd/$file
 12        echo $file
 13    fi
 14 done

执行该脚本后,得出的结果是

a

这里很有意思的地方是,for循环仅仅执行了一次就退出了

内建的cd 命令切换了目录以后 居然会导致当前的循环中断

为了避免这种情况的发生,我们可以借鉴一下子shell的概念 把cd 命令当成子shell作为独立的进程来运行 这样就不会对当前的脚本产生影响了

# 子shell的格式是用括号把一系列的命令包裹起来
(
    command1
    command2
    command3
)

因此经过一番修改,我得到了下面的脚本(在11和14行代码处加上了括号)

  1 #!/bin/bash
  2 
  3 set -e
  4 
  5 pwd=`pwd`
  6 
  7 for file in  `ls $pwd`
  8 do
  9    if [ -d $file ]
 10    then
 11     (
 12        cd $pwd/$file
 13        echo $file
 14     )
 15    fi
 16 done

这样就能得出正常的结果了

a
b
c
d

我理解的优秀

如何定义一个候选人是否优秀? 这个问题在我这段时间参与面试这么长时间来,始终萦绕在我的脑海里。

在如此艰难的生活压力之下,或许对于大多数人来说,工作仅仅是一种获得收入来源的方式而已。但是我想说的是,不可以没有梦想,因为有趣的灵魂往往就体现在此处。

工作绝对是一个双向的选择,企业需要员工来产生效益,员工需要平台来展现自我价值。如果在每一次求职过程中,只是在盲目的寻找,抱着一副无所谓的态度,那么一定需要摆正自己的态度了。人以类聚物以群分,抱着无所谓的态度最后唯一的选择一定也是一个没有任何发展潜力的企业,这样的组合是没有未来的。

我非常喜欢一句话:"一个连企业文化都没有的企业,一定是没有办法活过今天晚上的"。

在人生历程中,不管你是否选择创业还是选择一份工作,那么首先你自己一定要变的优秀,然后去接触更多优秀的伙伴,只有这样才会有一个优秀的未来。

那么如何定义一个人是否优秀呢? 也就是怎样去定义一个人的价值?

优秀的人一生都在开挂,所以要想办法让自己的变得优秀

自我认知

如果连自己的无法认清,那么该怎样去认清这个世界?

说实话该如何认清自己,这个答案其实很难进行判断,每个人都会有自己的定义,但是从我的角度进行解析,我觉得可以做到这些事情

明确自己的喜恶

这一点或许可以叫爱憎分明吧,在孩童时代我们我们总是盲目的接受这个世界的知识,对于任何事情都是言听计从,但是随着年龄的增长以后,你就会有自己喜欢和不喜欢的东西,所以明确他们。对于自己不喜欢的人或事,勇敢一点,大声的say no。

确定人生的目标

没有目标的人生都是在虚度自己的光阴,给自己做一个人生的规划吧,短期规划或者长期规划都可以,前进的方向必须要有,哪怕是一个错误的方向也一定比没有方向来的要好。

展现个人特点

为什么是个人特点,而不是自己的优缺点呢?因为在我的定义中,优点和缺点都不是绝对的,所以每当有人问我有什么优点或者缺点的时候,我的回答都是同一个: "我略带一些完美主义倾向和强迫症"。

没错,优点和缺点是一样的,因为我不觉得自己有什么优点,当然也不会有什么缺点,那些都是我个人固有的人格特点。这个世界上,能够承担你本人的责任的只有你自己,因此你的优缺点应该由你自己来定义,没有任何人可以对你的优点和缺点下定义。因此,我把这个地方叫做个人特点,一定要勇敢一些,贯彻自己的特点,不要人云亦云。或许你觉得自己没有什么特点,但是不要着急,它一定会出现的。

思维方式

自我认知是对自己的认知, 那么思维方式就是用来认识外界的手段了,试着尝试以不同的角度去理解这个世界,你会有不一样的收获。

换位思考

说起来是很简单,但是做起来真的好难,对于没有经历过的事情去凭空想象,那是需要极大的想象力的天才才能做到。虽然只有真正处在对方的那个位置,才能明白对方在想什么,但是去尝试想象一下,或许有意外收获呢

以求职的过程来做个说明,假设你是一名求职者,切换一下自己的位置,你会录用你自己么?

非常简单浅显的一个道理,如果你自己都不敢相信自己,如何能指望他人来相信你呢。 借用编程界的一句经典话语: “建立在不可靠服务的基础上构建的服务,都是不可靠的(TCP协议除外)

墨守成规 or 勇于创新

思维方式没有对错之分,但是你要有自己的思维方式。没错,正如上面所说的个人特点和人生目标一样,你必须要有自己的思维方式。

自我驱动

潜力代表你的过去,能力代表你的现在,只有学习才能创造决定自己的未来

自主学习能力

主动学习和被动学习带来的效果一定是不一样的,在任何情况,去主动的学习你想要知道的知识

虽然在我们国家,大多数的人总是跟随着教师学习一些书本上的知识,但是这一点让我深深的感受到了悲伤

我多么的期望我的每一个老师能够教会我的不仅仅是知识,而是学习.学会学习一定比学会知识要来的重要.

虽然我明白这件事情经过了很长的时间,但是任何时候你要你明白了,那就永远都不晚

时间管理

没有目标的人生是虚度,那么没有管理到的时间那就是浪费了。有了时间管理才有挽救你的拖延症。

有时间一定要多看看时间管理之类的书脊,养成良好的规划,分清事情的轻重缓急,多做你觉得重要的事情。

性格品德

性格品德就没有什么好说的了,生活在天朝这个美好的国度,请认同这个最大的核心价值观,这是作为一个中国人最基本的骄傲。

富强、民主、文明、和谐,自由、平等、公正、法治,爱国、敬业、诚信、友善

解决dia在ubuntu下无法输入注释的问题

系统环境介绍

  • 操作系统: ubuntu16.0.4
  • dia版本: 0.97.3

遇到的问题

在图表 数据库以及uml图中 对应的字段注释无法填写

解决方案

/usr/bin/dia 文件中的启动环境变量修改一下


# 打开dia启动文件 sudo vim /usr/bin/dia # 把 dia-normal --integrated "$@" 修改为下面这种形式 LIBOVERLAY_SCROLLBAR=0 dia-normal --integrated "$@"

laravel源码浅析

  • laravel版本5.8

入口

当你想要弄清楚任何一件事物的内部构造时,一定需要先找到入口,因此第一件要做的事情就是找到入口文件 public/index.php

<?php
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

精简一下,只有9行代码,非常的简洁,包括<?php在内只有10行代码

  1. 定义常量 表示laravel运行的开始时间
  2. 包含composer的自动加载 曾经有psr0 和psr4规范 但是在我写下这个文档的时间(2019-06-04)已经废弃psr0很久了
  3. 加载laravel引导 (把laravel想象成一个操作系统 此处的引导就是操作系统的那512M字节的数据),并得到laravel的应用变量
  4. 使用$app变量,制作一个Http内核$kernel (没错 就是操作系统的那个内核)
  5. 使用内核($kernel)处理 $request 并得到一个$response
  6. 发送$response
  7. $kernel内核终止请求与响应

至此一次完整的laravel的生命周期就结束了,同时思考一下http协议,一个完整的HTTP事务应该是这个样子

客户端发送请求->服务器处理请求并响应->客户端收到响应

对于一个http请求来说,只有请求和响应两个过程,而laravel的入口恰好完美的诠释了这个神圣而又复杂的过程.

写代码虽然很简单,但是能把代码写的像散文一样一定是非常困难的,而这个入口文件就让人心旷心神

引导

$app = require_once DIR.'/../bootstrap/app.php';

<?php

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

return $app;

大多数的其他框架都倾向于把引导放在入口里,但是laravel是单独放了一个文件,并且这个文件也非常的简洁

以代码量计算只有17行,以功能计算只有5个

  1. 根据路径new出一个Application类
  2. 绑定一个Http内核单例
  3. 绑定一个Console内核单例
  4. 绑定一个异常处理类单例
  5. 返回$app实例

不需要知道Application内部做了什么 也不需要知道singleton内部做了什么,仅仅只阅读这几行代码,就能窥豹一斑

laravel的app实例仅仅只有三个功能

  • http请求处理 由http内核提供
  • 控制台命令处理 由Console内核提供
  • app异常处理 由Exceptions\Handler提供

最后返回一个app实例,这个app实例是继承于Container类的,这里运用反射技术来提供了一个根据类名生成类实例的功能,具体实现可以查看laravel/framework/src/illuminate/Container/Container.php的第790行代码

Container容器的具体用法在后面的篇章中会详细的进行解释,此处不多做介绍

http 内核

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

使用$app实例制作一个实现了Http/Kernel契约的内核实例

还记得引导文件中的这一行代码么

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

没错,这个地方返回的内核就是绑定的App\Http\Kernel::class 实例,并且仅初始化了基本的中间件而已

这里make的是一个imterface,因此只要你的Kernel实现了Illuminate\Contracts\Http\Kernel::class这个接口,那么在上面的文件中你完全可以自定义属于自己的内核,虽然没有什么必要

处理请求

$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);

先看这一行代码 ,捕获http请求

$request = Illuminate\Http\Request::capture()

暂时不深入剖析源代码,根据文档中的描述来看以及经验来看,都应该想到这里的request就是

$_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER 中携带的信息,虽然会做一些处理,但是一个基本的http请求中都必须包含这些信息,这是Http协议决定了

接着是内核处理这个请求

$response = $kernel->handle($request);

这个地方就勉为其难的看一下源代码好了,目光定位到Illuminate\Foundation\Http\Kernel 的111行

顺便我们再精简一下,把异常处理去掉,得到代码如下

public function handle($request)
{
    $request->enableHttpMethodParameterOverride();
    $response = $this->sendRequestThroughRouter($request);
    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );
    return $response;
}

做了三件事情

  1. 开启http 方法重写 (这个地方其实仅仅是为了支持 传统表单页面中 用_method=put 来代替PUT请求这样的功能的)

    具体代码可以查看Symfony\Component\HttpFoundation\Request 1229行

  2. sendRequestThroughRouter发送请求到路由器并得到响应$response

  3. 分发事件处理

发送请求到路由器

这里是发送请求到路由器 那么接下来肯定是路由分发了

目光定位到Illuminate\Foundation\Http\Kernel 149行代码

protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
        ->send($request)
        ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
        ->then($this->dispatchToRouter());
}

只需要看最后一行代码就可以了,这里有两个关键词

  • Pipeline 管道
  • dispatchToRouter 路由分发

记得最开始make内核的时候,在App\Http\Kernel 文件里有许多的中间件数组配置,那么这里的Pipeline做的事情只有一件,让这些左右的中间件变成一个队列一样,这些请求依次穿过(through)这些中间件, 注意这里的用词是through 穿过,然后then 路由分发dispatchToRouter , 读起来很有感觉

dispatchToRouter路由分发就更简单了,在laravel中所有的路由都是预先配置好的,通过request请求中的path找到配置好的route,如果命中则交给route中定义的控制器或者闭包来进行处理,如果没有命中,那么则抛出异常

当然 抛出异常这个过程是在上一步处理请求中的 handle中执行的,因为我精简掉了异常处理代码 所以看不到

最后route的dispatch过程,可以把代码定位到Illuminate\Routing\Roouter 730行

经过了一些列的操作,甚至包括中间件

在laravel中 请求和响应是都要经过中间件的 代码分别在

请求: Illuminate\Foundation\Http\Kernel 149行代码

响应: Illuminate\Routing\Roouter 656行

public static function toResponse($request, $response)
{
    if ($response instanceof Responsable) {
        $response = $response->toResponse($request);

    }

    if ($response instanceof PsrResponseInterface) {
        $response = (new HttpFoundationFactory)->createResponse($response);

    } elseif ($response instanceof Model && $response->wasRecentlyCreated) {
        $response = new JsonResponse($response, 201);

    } elseif (! $response instanceof SymfonyResponse &&
        ($response instanceof Arrayable ||
        $response instanceof Jsonable ||
        $response instanceof ArrayObject ||
        $response instanceof JsonSerializable ||
        is_array($response))) {
        $response = new JsonResponse($response);

    } elseif (! $response instanceof SymfonyResponse) {
        $response = new Response($response);

    }

    if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
        $response->setNotModified();
    }

    return $response->prepare($request);
}

这里最后一定会得到一个Illuminate\Http\Response的实例

发送响应

$response->send();

根据上面所有的流程,我们最终得出, 返回了一个Response实例,那么接下来我们一起看看laravel 是怎么把这个发送这个response的

注意看这里 这个响应是自己发送的 并不是内核发送的 内核仅仅是根据请求 并且通过了管道内的中间件 最后得到了一个Response 实例

目光定位到Symfony\Component\HttpFoundation 的 373行代码

精简一下变成这样

public function send()
{
    $this->sendHeaders();
    $this->sendContent();
}
  • $this->sendHeaders(); 这行代码输出头信息 Symfony\Component\HttpFoundation 330行代码
header($name.': '.$value, $replace, $this->statusCode);
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

这个地方仅仅只是利用header 输出了一些头部信息

  • $this->sendContent(); 这行代码输出文本
public function sendContent()
{
    echo $this->content;
    return $this;
}

和预想中的一样,这个地方仅仅是echo了一些东西而已 没有特殊的地方

通过代码分析 我们可以知道 $response->send();只做了两件事情 输出头信息以及内容, 和输出所有的网页一样,头部和内容

内核终止请求与响应

$kernel->terminate($request, $response);

目光定位到Illuminate\Foundation\Application 中的1006行代码

public function terminate()
{
    foreach ($this->terminatingCallbacks as $terminating) {
        $this->call($terminating);
    }
}

这里的代码很简单,在响应成功之后,执行一些回调函数

总结

抛开所有的类库 框架 mvc等等各种各样的概念来看,laravel其实是一个非常简单的东西,当然http协议也是一样

创建应用实例->接受请求->得到响应->发送响应(header 一些东西 echo 一些东西)->结束

gitbook配置

gitbook配置



{ "title": "标题", "author": "crisen", "description": "描述", "language": "zh-cn", "gitbook": "3.2.3", "structure": { "readme": "README.md" }, "links": { "sidebar": { "crisen": "https://www.crisen.org" } }, "plugins": [ "-lunr", "-search", "search-plus", "expandable-chapters-small", "edit-link", "advanced-emoji", "anchors", "sitemap-general", "copy-code-button", "alerts", "ace", "splitter", "-sharing", "anchor-navigation-ex-toc", "theme-api" ], "pluginsConfig": { "edit-link": { "base": "http://github.com/crisenchou/docs/edit/master", "label": "编辑" }, "sitemap-general": { "prefix": "http://www.crisen.org" }, "theme-api": { "theme": "light" } } }

gitlab-ci-yml配置

.gitlab-ci.yml

.gitlab-ci.yml是从7.12版本开始启用的项目CI配置文件,本文件应该放在项目的根目录中,里边描述了你的项目应该如何构建。

一个yaml文件定义了一组各不相同的job,并定义了它们应该怎么运行。这组jobs会被定义为yaml文件的顶级元素,并且每个job的子元素中总有一个名为script的节点。

A set of jobs,强调是Set所以名称必须不同。

job1:
    script: "job1的执行脚本命令(shell)“

job2:
    script:
        - "job2的脚本命令1(shell)"
        - “job2的脚本命令2(shell)”

上述的文件是最简单的CI配置例子,每个job都执行了不同的命令,其中job1只执行了1条命令,job2通过数组的定义按顺序执行了两条命令。

当然,每个命令可以直接执行代码(./configure;make;make install)或者在仓库目中运行另一个脚本(test.sh)。

Job配置会被Runner读取并用于构建项目,并且在Runner的环境中被执行。很重要的一点是,每个job都会独立的运行,相互间并不依赖。

Each job is run independently from each other.

下边是一个比较复杂的CI配置文件例子:

image: ruby:2.1
services:
  - postgres

before_script:
  - bundle install

after_script:
  - rm secrets

stages:
  - build
  - test
  - deploy

job1:
  stage: build
  script:
    - execute-script-for-job1
  only:
    - master
  tags:
    - docker

以下是一些保留字,这些单词不能被用于命名job:

保留字 必填 介绍
image 构建使用的Docker镜像名称,使用Docker )作为Excutor时有效}
services 使用的Docker服务,使用Docker 作为Excutor时有效
stages 定义构建的stages
types stages的别名
before_script 定义所有job执行之前需要执行的脚本命令
after_script 定义所有job执行完成后需要执行的脚本命令
variables 定义构建变量
cache 定义一组文件,该组文件会在运行时被缓存,下次运行仍然可以使用

image和services

这两个关键字允许用户自定义运行时使用的Docker image,及一组可以在构建时使用的Service。这个特性的详细说明在 Docker integration – GitLab Documentation中。

before_script

被用于定义所有job被执行之前的命令,包括部署构建环境。它可以是一个数组元素或者一个多行文本元素。

after_script

Gitlab8.7及GitlabRunner1.2以上才支持本关键字

被用于定义所有job被执行完之后要执行的命令。同样可以是一个数组或者一个多行文本。

stages

用于定义可以被job使用的stage. 定义stages可以实现柔性的多stage执行管道。

The specification of stages allows for having flexible multi stage pipelines.

stages定义的元素顺序决定了构建的执行顺序:

  1. 同样stage的job是并行执行的。
  2. 下一个stage的jobs是当上一个stage的josb全部执行成功后才会执行。

让我们看一个简单的例子,以下有3个stage:

stages:
    - build
    - test
    - deploy
  1. 首先,所有stage属性为build的job会被并行执行.
  2. 如果所有stage属性为build的job都执行成功了,stage为test的job会被并行执行。
  3. 如果所有stage为test的job都执行成功了,则stage为deploy的job会被并行执行。
  4. 如果所有stage为deploy的job都执行成功了,则提交被标记为success。
  5. 如果任何一个前置job失败了,则提交被标记为failed并且任何下一个stage的job都不会被执行。

两个有价值的提示:

  1. 如果配置文件中没有定义stages,那么默认情况下的stages属性为build、test和deploy。
  2. 如果一个job没有定义stage属性,则它的stage属性默认为test。

types

stages的别名。

variables

从Runner0.5.0开始支持

GitlabCI允许你在.gitlab-ci.yml文件中设置构建环境的环境变量。这些变量会被存储在git仓库中并用于记录不敏感的项目配置信息,例如:

variables:
  DATABASE_URL: "postgres://postgres@postgres/my_database"

这些变量会在之后被用于执行所有的命令和脚本。Yaml配置的变量同样会被设置于所有被建立的service容器中,这可以让使用更加方便。variables同样可以设置为job级别的属性。

除了用户自定义的变量外,同样有Runner自动设置的变量。例如说CI_BUILD_REF_NAME,这个变量定义了正在构建的git仓库的branch或者tag的名称。

除了在.gitlab-ci.yml中设置的非敏感变量外,Gitlab的UI中还提供了设置敏感变量的功能。

更多关于变量的说明

cache

Runner0.7.0之后被介绍

用于定义一系列需要在构建时被缓存的文件或者目录。只能定义在项目工作环境中的目录或者文件。

cache is used to specify a list of files and directories which should be cached between builds.

默认情况下缓存功能是对每个job和每个branc都开启的。

如果cache在job元素之外被蒂尼,这意味着全局设置,并且所有的job会使用这个设置。以下是一些例子:

  • 缓存所有在binaries目录中的文件和.config文件:
rspec:
  script: test
  cache:
    paths:
    - binaries/
    - .config
  • 缓存所有git未追踪的文件
rspec:
  script: test
  cache:
    untracked: true
  • 缓存所有git未追踪的文件和在binaries目录下的文件
rspec:
  script: test
  cache:
    untracked: true
    paths:
    - binaries/
  • job级别定义的cache设置会负载全局级别的cache配置。该例子将仅缓存目录binaries
cache:
  paths:
  - my/files

rspec:
  script: test
  cache:
    paths:
    - binaries/

cache功能仅提供最努力的支持,但不要指望它总能生效。更多的实现细节,可以参阅GitlabRunner。

The cache is provided on a best-effort basis, so don’t expect that the cache will be always present.

cache:key

Runner1.0.0中被介绍

key允许你在不同的job之间定义cache的种类,例如所有job共享的cache单例、一个job一个的cache、一个branch一个的cache等等。

这允许你更便利的使用缓存,允许你在不同的job甚至不同的branche间共享缓存。

cache:key变量可以使用任何之前定义的变量。

例子

  • 缓存每个job
cache:
  key: "$CI_BUILD_NAME"
  untracked: true

缓存每个branch

cache:
  key: "$CI_BUILD_REF_NAME"
  untracked: true

缓存每个job和branch

cache:
  key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME"
  untracked: true

缓存每个branch和每个stage

cache:
  key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME"
  untracked: true

这段感觉怎么翻译都啰嗦,就这样吧

如果是在Windows环境下开发,需要使用%代替$标识环境变量:

cache:
  key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%"
  untracked: true

Jobs

.gitlab-ci.yml允许配置无限个job。每个job必须有一个唯一的名称,并且不能是前文所说的任何一个保留字。一个job由一系列定义构建行为的参数组成。

job_name:
  script:
    - rake spec
    - coverage
  stage: test
  only:
    - master
  except:
    - develop
  tags:
    - ruby
    - postgres
  allow_failure: true
关键字 必要性 介绍
script 定义了Runner会执行的脚本命令
image 使用Docker镜像,多内容参考使用Docker镜像
services 使用Docker服务,更多内容参考 使用Docker镜像
stage 定义构建的stage(默认:test
type stage的别名
variables 定义job级别的环境变量
only 定义一组构建会创建的git refs
except 定义一组构建不会创建的git refs
tags 定义一组tags用于选择合适的Runner
allow_failure 允许构建失败。失败的构建不会影响提交状态。
when 定义什么时候执行构建。可选:on_successon_failurealwaysmanual
dependencies 定义当前构建依赖的其他构建,然后你可以在他们之间传递artifacts
artifacts 定义一组构建artifact。
cache 定义一组可以缓存以在随后的工作中共享的文件
before_script 覆写全局的before_script命令
after_script 覆写全局的after_script命令
environment 定义当前构建完成后的运行环境的名称

script

这是一个或一组会被Runner执行的shell脚本。例如:

jobA:
  script: "bundle exec rspec"

jobB:
  script:
    - uname -a
    - bundle exec rspec

有时候,script命令需要被包在双引号或者单引号之间。例如,包含符号(:)的命令需要写在引号中,这样yaml的解析器才能正确的解析,而不会误以为这是一组键值对。在使用包含以下符号的命令时要特别小心:
:{}[],&*#?|-<>=!%@、```

stage

stage 允许分组构建不同的stage。构建相同的stage时是并行进行的。更多关于stages的说明可以参阅stages.

only and except

onlyexcept 是管理job在被构建时的refs策略的的参数。

  1. only 设置了需要被构建的branches和tags的名称。
  2. except 设置了不需要被构建的branches和tags的名称。

这里有一些使用refs策略的规则:

  • onlyexcept 是可以相互包含的。如果一个job中 onlyexcept都被定义了,ref会同时被 only 过滤 except
  • onlyexcept 支持正则表达式。
  • onlyexcept 可以使用这几个关键字: branches, tagstriggers
  • onlyexcept 允许通过定义仓库路径的方式来过滤要fork的job。

在以下的例子中,job将只会启动以issue-开头的refs,并且跳过所有的branches。

job:
    # 使用正则
    only:
        - /^issue-.*$/
  # 使用关键字
    except:
        - branches

在下边的例子中,job将仅对被打了tag的refs,或者是被API触发器触发时
才执行:

job:
  # 使用关键字
  only:
    - tags
    - triggers

仓库路径可以用于让job仅为父仓库执行并且不fork:

The repository path can be used to have jobs executed only for the parent repository and not forks:

job:
  only:
    - branches@gitlab-org/gitlab-ce
  except:
    - master@gitlab-org/gitlab-ce

上述的例子将会为除了master以外的所有的branches运行job

job variables

可以通过使用variables定义job级别的构建变量。