继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

基于 GitLab 的 CI 实践

慕标5832272
关注TA
已关注
手记 1228
粉丝 229
获赞 1001

为何选择 GitLab CI

认识 GitLab CI

什么是 GitLab CI

GitLab CI 是 GitLab 为了提升其在软件开发工程中作用,完善 DevOPS 理念所加入的 CI/CD 基础功能。可以便捷的融入软件开发环节中。通过 GitLab CI 可以定义完善的 CI/CD Pipeline。

优势

  • GitLab CI 是默认包含在 GitLab 中的,我们的代码使用 GitLab 进行托管,这样可以很容易的进行集成

  • GitLab CI 的前端界面比较美观,容易被人接受

  • 包含实时构建日志,容易追踪

  • 采用 C/S 的架构,可方面的进行横向扩展,性能上不会有影响

  • 使用 YAML 进行配置,任何人都可以很方便的使用。

重点概念

Pipeline

Pipeline 相当于一个构建任务,里面可以包含多个流程,如依赖安装、编译、测试、部署等。
任何提交或者 Merge Request 的合并都可以触发 Pipeline

Stages

Stage 表示构建的阶段,即上面提到的流程.

  • 所有 Stages 按顺序执行,即当一个 Stage 完成后,下一个 Stage 才会开始

  • 任一 Stage 失败,后面的 Stages 将永不会执行,Pipeline 失败

  • 只有当所有 Stages 完成后,Pipeline 才会成功

Jobs

Job 是 Stage 中的任务.

  • 相同 Stage 中的 Jobs 会并行执行

  • 任一 Job 失败,那么 Stage 失败,Pipeline 失败

  • 相同 Stage 中的 Jobs 都执行成功时,该 Stage 成功

好的,基本的概念已经和大家介绍了, 大家可以发现,上面说的概念,没有提到任务的实际执行者. 那任务在哪里执行呢?

GitLab runner

Runner 是任务的实际执行者, 可以在 MacOS/Linux/Windows 等系统上运行。使用 golang 进行开发。 同时也可部署在 k8s 上

注册

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 = 1
check_interval = 0

这两个。 比较需要关注的是下面几个

全局配置
  • concurrent: 并发数, 0 为无限制

  • sentry_dsn:与 Sentry 联动,可以将异常等收集至 Sentry 中。

  • listen_address: 暴露出 metrics 供 Prometheus 监控

Executor

  • Shell

  • Docker (本次的分享内容)

  • Docker Machine and Docker Machine SSH (autoscaling)

  • Parallels

  • VirtualBox

  • SSH

  • Kubernetes (推荐)

详解 Docker In Docker

概述

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 端口

简单做法

可以直接使用 Docker 官方镜像仓库中的 docker:dind 镜像, 但是在运行时, 需要指定 --privileged 选项

CI 实践

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/docker

variables:
  DOCKER_DRIVER: overlay2    # overlay2 is best bug need kernel >= 4.2services:
    - name: registry.docker-cn.com/taobeier/docker:stable-dind      alias: docker

stages:
  - build
  - deploy


build_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.py

deploy:
  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 容器.

私有镜像源

用户认证需要 GitLab Runner 1.8 或更高版本,在 0.6 ~ 1.8 版本之间的 Runner 需要自行去 Runner 的机器上手动执行。

默认情况下,如果访问的镜像仓库需要认真的话, GitLab Runner 会使用 DOCKER_AUTH_CONFIG 变量的作为认证的凭证。

注意:DOCKER_AUTH_CONFIG 是完成的 docker auth 凭证,也就是说,它应该和我们 ~/.docker/config.json 中的内容一致,例如:

{    "auths": {        "registry.example.com": {            "auth": "5oiR5piv5byg5pmL5rab"
        }
    }
}

简单的做法就是,我们在本地/服务器上执行 docker login 私有镜像源 登录成功后,将 ~/.docker/config.json 的文件内容直接复制,作为我们的变量的值, 或者是  echo -n '用户名:密码' | base64 以这样的方式来获得 auth 的内容,组装成对应的格式,写入 GitLab 的 value 配置中。

生产环境中的 CI 性能优化

  1. 使用国内源对容器镜像进行加速 例如:使用 Docker 中国官方镜像加速服务 https://registry.docker-cn.com 当然各家公司其实也有提供镜像加速的服务。

  2. 使用私有镜像仓库。例如 Docker Registry, 或者 Harbor, 我们是在使用 Harbor 作为私有镜像仓库的。

因为网络的原因, 如果默认使用官方镜像, 1. 官方镜像拉不下来;2. 在官方镜像中安装包耗时长;3. 如果换源,需要每个 Dockerfile 都要做相同的事情。 这我们当然是不能同意的。 所以,我们构建了自己的私有镜像。 从 BusyBox 开始, 构建 Alpine Linux 使用私有源, 以此为基础 构建我们所需要的其他镜像。 用户不再需要自行换源。

这个操作完成后, 原先我们需要在 CI 执行的过程中安装 py-pip(为了安装 docker-compose 和我们的服务依赖)耗时从 3min30s 减少到了 18s。

这里,需要说下为何我们是从头开始构建镜像,而不是基于官方镜像。 主要是为了减少镜像体积 以及为了更快的适用于我们的需求。

同样的,我们构建了基础的 Docker 镜像,Python/Maven 等镜像,都是默认使用了我们的私有源,并且,用户在使用时, 并不需要关注换源的事情, 减少用户的心智负担。

  1. 规范 Dockerfile, 减少不必要的依赖安装, 减少镜像体积。其实结合上面的部分,我们做的事情是直接构建了我们的基础镜像 docker/alpine/maven之类的基础镜像,默认直接都换了源。这样既方便使用,还可以减少镜像层数。

  2. 拆分 job, 通过 tag 的方式可指定runner, 由不同的 runner 来并行执行无强依赖的一些动作。 便于分摊压力。

  3. 使用 Cache, CI 的构建中,大多数的镜像,其实变化不大,所以使用cache 可以成倍的提升 CI 的速度。

  4. 可能遇到的坑

前面提到了 service 中可以使用各种各样的服务, 无论是 dind 还是 mysql redis 等。 但是 如果我们全部做到了优化,都使用我们的私有源, 那便会发现问题。

因为 gitlab ci 默认对于 docker:dind 的 service  其实会选择连名为 docker 的 host ,以及 2375 端口。 当使用私有镜像源的时候,  比如

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 。 这个方法目前很少人在用, 毕竟网络上查到的都是第一种 ,但是这个方式却是最简单的。



作者:TaoBeier
链接:https://www.jianshu.com/p/bfb342624db2


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP