

认识 GitLab CI 什么是 GitLab CI? GitLab CI 是 GitLab 为了提升其在软件开发工程中作用,完善 DevOps 理念所加入的 CI/CD 基础功能。可以便捷的融入软件开发环节中。通过 GitLab CI 可以定义完善的 CI/CD Pipeline。 优势
-
GitLab CI 是默认包含在 GitLab 中的,我们的代码使用 GitLab 进行托管,这样可以很容易的进行集成
-
GitLab CI 的前端界面比较美观,容易被人接受
-
包含实时构建日志,容易追踪
-
采用 C/S 的架构,可方面的进行横向扩展,性能上不会有影响
-
使用 YAML 进行配置,任何人都可以很方便的使用
-
所有 Stages 按顺序执行,即当一个 Stage 完成后,下一个 Stage 才会开始
-
任一 Stage 失败,后面的 Stages 将永不会执行,Pipeline 失败
-
只有当所有 Stages 完成后,Pipeline 才会成功
-
相同 Stage 中的 Jobs 会并行执行
-
任一 Job 失败,那么 Stage 失败,Pipeline 失败
-
相同 Stage 中的 Jobs 都执行成功时,该 Stage 成功
docker run --rm -t -i -v /path/to/config:/etc/gitlab-runner --name gitlab-runner gitlab/gitlab-runner register \ --executor "docker" \ --docker-image alpine:3 \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --description "docker-runner" \ --tag-list "dev" \ --run-untagged \ --locked="true"
上面的示例为将 Runner 注册为一个容器, 当然大家也可以直接在物理机上执行。 在物理机上的注册方式与注册为容器大致相同。
sudo gitlab-runner register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token "PROJECT_REGISTRATION_TOKEN" \ --executor "docker" \ --docker-image alpine:3 \ --description "docker-runner" \ --tag-list "docker,aws" \ --run-untagged \ --locked="false" \
(这段代码来自官方文档)
接下来,我们来看下 Runner 的类型, 以便在使用时进行区分。
类型
-
Shared:Runner runs jobs from all unassigned projects
-
Group:Runner runs jobs from all unassigned projects in its group
-
Specific:Runner runs jobs from assigned projects
-
Locked:Runner cannot be assigned to other projects
-
Paused:Runner will not receive any new jobs
concurrent = 1check_interval = 0
这两个。 比较需要关注的是下面几个:
全局配置
-
concurrent:并发数,0 为无限制。
-
sentry_dsn:与 Sentry 联动,可以将异常等收集至 Sentry 中。
-
listen_address:暴露出 metrics 供 Prometheus 监控。
-
Shell
-
Docker(本次的分享内容)
-
Docker Machine and Docker Machine SSH(autoscaling)
-
Parallels
-
VirtualBox
-
SSH
-
Kubernetes(推荐)

概述 Docker In Docker 简称 dind,在 GitLab CI 的使用中,可能会常被用于 Service 的部分。 dind 表示在 Docker 中实际运行了一个 Docker 容器,或 Docker daemon。 其实如果只是在 Docker 中执行 Docker 命令, 那装个二进制文件即可。但是如果想要运行 Docker daemon(比如需要执行 docker info)或者访问任意的设备都是不允许的。 Docker 在 run 命令中提供了两个很重要的选项 --privileged 和 --device , 另外的选项比如 --cap-add 和 --cap-drop 跟权限也很相关,不过不是今天的重点,按下不表。 --device 选项可以供我们在不使用 --privileged 选项时,访问到指定设备,比如 docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc 但是这也只是有限的权限, 我们知道 Docker 的技术实现其实是基于 CGroup 的资源隔离,而 --device 却不足于让我们在容器内有足够的权限来完成 Docker daemon 的启动。 在 2013年 左右, --privileged 选项被加入 Docker, 这让我们在容器内启动容器变成了可能。 虽然 --privileged 的初始想法是为了能让容器开发更加便利,不过有些人在使用的时候,其实可能有些误解。 有时候,我们可能只是想要能够在容器内正常的build 镜像,或者是与 Docker daemon 进行交互,例如 Docker images 等命令。 那么,我们其实不需要 dind, 我们需要的是 Docker Out Of Docker,即 dood,在使用的时候,其实是将 docker.sock 挂载入容器内。 例如, 使用如下命令:
sudo docker run --rm -ti -v /var/run/docker.sock:/var/run/docker.sock taobeier/docker /bin/sh
在容器内可进行正常的 Docker images 等操作, 同时需要注意,在容器内的动作,将影响到 宿主机上的 Docker daemon。
如何实现
-
创建组和用户,并将用户加入该组。 使用 groupadd 和 useradd 命令。
-
更新 subuid 和 subgid 文件, 将新用户和组配置到 /etc/subgid 和 /etc/subuid 文件中。 subuid 和 subgid 规定了允许用户使用的从属 ID。
-
接下来需要挂载 /sys/kernel/security 为 securityfs 类型可以使用 mountpoint 命令进行测试 mountpoint /sys/kernel/security 如果不是一个挂载点, 那么使用 mount -t securityfs none /sys/kernel/security 进行挂载。如果没有挂载成功的话, 可以检查是否是 SELinux 或者 AppArmor 阻止了这个行为。这里详细的安全问题,可以参考 Linux Security Modules (LSM)。
-
接下来允许 dockerd 命令启动 daemon 即可, dockerd --host=unix:///var/run/docker.sock --host=tcp://0.0.0.0:2375 即可将docker daemon 监听至 2375 端口。

Runner 实践 看 Runner 部分的配置:
[[runners]] name = "docker" url = "https://gitlab.example.com/" token = "TOKEN" limit = 0 executor = "docker" builds_dir = "" shell = "" environment = ["ENV=value", "LC_ALL=en_US.UTF-8"] clone_url = "http://172.17.0.4"
由于网络原因, clone_url 可以配置为可访问的地址,这样代码 clone 的时候,将会使用配置的这个地址。实际请求为 http://gitlab-ci-token:TOKEN@172.17.0.4/namespace/project.git。
再看一下 runners.docker 的配置,这部分将影响 Docker 的实际运行:
[runners.docker] host = "" hostname = "" tls_cert_path = "/home/tao/certs" image = "docker" dns = ["8.8.8.8"] privileged = false userns_mode = "host" devices = ["/dev/net/tun"] disable_cache = false wait_for_services_timeout = 30 cache_dir = "" volumes = ["/data", "/home/project/cache"] extra_hosts = ["other-host:127.0.0.1"] services = ["mongo", "redis:3"] allowed_images = ["go:*", "python:*", "java:*"]
DNS,Privileged,extra_hosts,Services 比较关键, 尤其是在生产中网络情况多种多样, 需要格外关注。 至于 Devices 配置 ,在今儿分享的一开始已经讲过了, allowed_images 的话, 是做了个限制。
上面几个配置项, 用过 Docker 的同学,应该很容易理解。 我们来看下 Services 这个配置项:
image: registry.docker-cn.com/taobeier/dockervariables: DOCKER_DRIVER: overlay2 # overlay2 is best bug need kernel >= 4.2services: - name: registry.docker-cn.com/taobeier/docker:stable-dind alias: dockerstages: - build - deploybuild_and_test: stage: build tags: - build script: # change repo #- sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories # 使用默认官方源 apk 耗时 7min 30s. 修改后 耗时 18s - ping -c 1 docker - ping -c 1 registry.docker-cn.com__taobeier__docker - ipaddr - apk add --no-cache py-pip # 使用默认耗时 1 min 15s. 修改后耗时 43s - pip install -i https://mirrors.ustc.edu.cn/pypi/web/simple docker-compose - docker-compose up -d - docker-compose run --rm web pytest -s -v tests/test_session.pydeploy: image: "registry.docker-cn.com/library/centos" stage: deploy tags: - deploy script: # install ssh client - 'ssh-agent || (yum install -y openssh-clients)' # run ssh-agent - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null # create ssh dir - mkdir -p ~/.ssh - chmod 700 ~/.ssh # use ssh-keyscan to get key - ssh-keyscan -p $SSH_PORT $DEPLOY_HOST >> ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts # - ssh -p $SSH_PORT $DEPLOY_USER@$DEPLOY_HOST ls - rm -rf .git - scp -r -P $SSH_PORT . $DEPLOY_USER@$DEPLOY_HOST:~/we/
Services 的本质其实是使用了 Docker 的 --link ,我们来看下它如何工作:
Docker Executor 如何工作
-
创建 service 容器(已经配置在 service 中的镜像)
-
创建 cache 容器(存储已经配置在 config.toml 的卷和构建镜像的 Dockerfile)
-
创建 build 容器 并且 link 所有的 service 容器
-
启动 build 容器 并且发送 job 脚本到该容器中
-
执行 job 的脚本
-
检出代码:/builds/group-name/project-name/
-
执行 .gitlab-ci.yml 中定义的步骤
-
检查脚本执行后的状态码,如果非 0 则构建失败
-
移除 build 和 service 容器
{ "auths": { "registry.example.com": { "auth": "5oiR5piv5byg5pmL5rab" } } }
简单的做法就是,我们在本地/服务器上执行 docker login 私有镜像源 登录成功后,将 ~/.docker/config.json 的文件内容直接复制,作为我们的变量的值。
或者是 echo -n '用户名:密码' |base64 以这样的方式来获得 auth 的内容,组装成对应的格式,写入 GitLab 的 value 配置中。
生产环境中的 CI 性能优化

services: - name: registry.docker-cn.com/taobeier/docker:stable-dind
那这个 service 的host 是什么呢?
这个 service 的 host 其实是会变成 registry.docker-cn.com__taobeier__docker,然后 GitLab Runner 便会找不到, job 就会执行失败。
有两种解决办法, 一种是加一个变量。
variables: DOCKER_HOST: "tcp://registry.docker-cn.com__taobeier__docker:2375"
但是这种方式很麻烦,没有人能完全记住遇到 / 会转换为 _ 难免会有问题。 那么就有了第二种办法:
services: - name: registry.docker-cn.com/taobeier/docker:stable-dind alias: docker
加一个 alias 。 这个方法目前很少人在用, 毕竟网络上查到的都是第一种 ,但是这个方式却是最简单的。
Q&A

