基於 GitLab 的 CI 實踐

來源:互聯網
上載者:User

上個月受 DockOne 社區邀請,做了一次 CI 實踐方面的線上分享,在此記錄下。
本文講述 GitLab CI 的架構及其能力特性,分析它在 DevOps 實踐中的作用。 通過分析 Docker In Docker 的技術細節,詳細講述 CI 實踐以及在生產環境中的所做的最佳化,包括但不限於鏡像倉庫等,以達到數倍的效能提升。
本次分享內容以 GitLab Community Edition 11.0.4 edb037c 為例。

為何選擇 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 = 1check_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/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 容器.

私人鏡像源

使用者認證需要 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 。 這個方法目前很少人在用, 畢竟網路上查到的都是第一種 ,但是這個方式卻是最簡單的。

Q&A

Q:您提到把各種依賴都以 Service 的提供,請問是以哪種方式呢? 比如Python的依賴,怎麼做成Service呢?

A:Service 化的依賴,主要是指類似 DB / MySQL/ Reids 之類的。 或者是 dind 其實它提供的是 2375 連接埠的TCP服務。 Python 的依賴,我推薦的做法是, 構建一個換了源的 Python 鏡像。 安裝依賴的時候,耗時會少很多。 或者說, 可以在定義 Pipeline 的時候, 將虛擬環境的 venv 檔案夾作為 cache ,之後的安裝也會檢查這個,避免不必要的安裝。

Q:請問,你們為什麼不用Jenkins Pipeline,而使用GitLab CI?

A:主要原因是我提到的那幾個方面。 整合較好, 介面美觀優雅, 使用簡單(所有有倉庫寫入權限的人 都可以使用, 只要建立 .gitlab-ci.yml 並且配置了 Runner 即可使用) 。換個角度,我們來看下使用Jenkins 的問題, Jenkins 對於項目的配置其實和 GitLab 的代碼是分離的, 兩部分的, 使用者(或者說我們的開發人員)在使用的時候, 需要有兩個平台, 並且,大多數時候, Jenkins 的許可權是不放開的。 對使用者來講, 那相當於是個黑盒。 那可能的問題是什麼呢? 遇到構建失敗了, 但是只有營運知道發生了什麼,但是研發無能為力,因為沒有許可權。 使用GItLab的好處,這個時候就更加突出了, 配置就在代碼倉庫裡面,並且使用 YAML 的配置,很簡單。 有啥問題,直接查,直接改。

Q:關於 Runner 的清理的問題,在長時間使用後,Runner 機器上回產生很多的 Cache 容器,如何清理呢。能夠在任務中自動清除嗎?

A:這個就相對簡單了,首先, 如果你的 Cache 容器確認沒用了, 每個 Cache 容器其實都有名字的, 直接按 Cache 的名字過略, 批量刪掉。 如果你不確定它是否有用,那你直接刪掉也是不影響的, 因為 Docker Excutor 的執行機制是建立完 Service 容器後, 建立 Cache 容器。 要是刪掉了,它只是會再建立一次。 如果你想在任務中清除, 目前還沒做相關的實踐,待我實踐後,看看有沒有很優雅的方式。

Q:請問下Maven的settings.xml怎麼處理?本地Maven倉庫呢?

A:我們構建了私人的 Maven 鏡像, 私人鏡像中是預設使用了我們的私人源。 對於項目中使用者無需關注 settings.xml 中是否配置repo。

Q:在GitLab的CD方案中,在部署的時候,需要在變數中配置跳板機的私密金鑰,如果這個項目是對公司整部門開發,那麼如何保護這個私密金鑰呢?

A:可以使用 secret variable 將私密金鑰寫入其中, (但是項目的管理員,具備查看該 variable 的許可權)開發一個 web server (其實只要暴露 IP 連接埠之類的就可以) 在 CI 執行的過程中去請求, server 對來源做判斷 (比如 執行CI 的時候,會有一些特定的變數,以此來判斷,是否真的是 CI 在請求)然後返回私密金鑰。

Q:GitLab CI適合什麼類型的項目呢?國內目前還比較小眾吧?

A:國內目前還較為小眾(相比 Jenkins 來說)其實只要需要 CI 的項目,它都適合。

可以通過下面二維碼訂閱我的文章公眾號【MoeLove】

TheMoeLove
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.