GitLab 持續集成

互聯網軟件的開發和發布,已經形成了一套標準流程,最重要的組成部分就是持續集成(Continuous integration,簡稱CI)。

持續集成

持續集成指的是,頻繁地(一天多次)將代碼集成到主干。它的好處主要有兩個:

  • 快速發現錯誤。每完成一點更新,就集成到主干,可以快速發現錯誤,定位錯誤也比較容易。
  • 防止分支大幅偏離主干。如果不是經常集成,主干又在不斷更新,會導致以后集成的難度變大,甚至難以集成。

Martin Fowler 說過,"持續集成并不能消除 Bug,而是讓它們非常容易發現和改正。"

image

持續集成強調開發人員提交了新代碼之后,立刻進行構建、(單元)測試。根據測試結果,我們可以確定新代碼和原有代碼能否正確地集成在一起。

與持續集成相關的,還有兩個概念,分別是持續交付和持續部署。

持續交付

持續交付(Continuous delivery)指的是,頻繁地將軟件的新版本,交付給質量團隊或者用戶,以供評審。如果評審通過,代碼就進入生產階段。

持續交付可以看作持續集成的下一步。它強調的是,不管怎么更新,軟件是隨時隨地可以交付的。

image

持續交付在持續集成的基礎上,將集成后的代碼部署到更貼近真實運行環境的「類生產環境」(production-like environments)中。比如,我們完成單元測試后,可以把代碼部署到連接數據庫的 Staging 環境中更多的測試。如果代碼沒有問題,可以繼續手動部署到生產環境中。

持續部署

持續部署(continuous deployment)是持續交付的下一步,指的是代碼通過評審以后,自動部署到生產環境。

持續部署的目標是,代碼在任何時刻都是可部署的,可以進入生產階段。

持續部署的前提是能自動化完成測試、構建、部署等步驟。

image

持續集成的操作流程

根據持續集成的設計,代碼從提交到生產,整個過程有以下幾步。

提交

流程的第一步,是開發者向代碼倉庫提交代碼。所有后面的步驟都始于本地代碼的一次提交(commit)。

測試(第一輪)

代碼倉庫對 commit 操作配置了鉤子(hook),只要提交代碼或者合并進主干,就會跑自動化測試。

測試的種類:

  • 單元測試:針對函數或模塊的測試
  • 集成測試:針對整體產品的某個功能的測試,又稱功能測試
  • 端對端測試:從用戶界面直達數據庫的全鏈路測試

第一輪至少要跑單元測試。

構建

通過第一輪測試,代碼就可以合并進主干,就算可以交付了。

交付后,就先進行構建(build),再進入第二輪測試。所謂構建,指的是將源碼轉換為可以運行的實際代碼,比如安裝依賴,配置各種資源(樣式表、JS腳本、圖片)等等。

常用的構建工具如下:

  • Jenkins
  • Travis
  • Codeship
  • Strider

Jenkins 和 Strider 是開源軟件,Travis 和 Codeship 對于開源項目可以免費使用。它們都會將構建和測試,在一次運行中執行完成。

測試(第二輪)

構建完成,就要進行第二輪測試。如果第一輪已經涵蓋了所有測試內容,第二輪可以省略,當然,這時構建步驟也要移到第一輪測試前面。

第二輪是全面測試,單元測試和集成測試都會跑,有條件的話,也要做端對端測試。所有測試以自動化為主,少數無法自動化的測試用例,就要人工跑。

需要強調的是,新版本的每一個更新點都必須測試到。如果測試的覆蓋率不高,進入后面的部署階段后,很可能會出現嚴重的問題。

部署

通過了第二輪測試,當前代碼就是一個可以直接部署的版本(artifact)。將這個版本的所有文件打包( tar filename.tar * )存檔,發到生產服務器。

生產服務器將打包文件,解包成本地的一個目錄,再將運行路徑的符號鏈接(symlink)指向這個目錄,然后重新啟動應用。這方面的部署工具有Ansible,Chef,Puppet等。

回滾

一旦當前版本發生問題,就要回滾到上一個版本的構建結果。最簡單的做法就是修改一下符號鏈接,指向上一個版本的目錄。

使用 GitLab 持續集成

從 GitLab 8.0 開始,GitLab CI 就已經集成在 GitLab 中,我們只要在項目中添加一個.gitlab-ci.yml文件,然后添加一個 Runner,即可進行持續集成。 而且隨著 GitLab 的升級,GitLab CI 變得越來越強大。

概念

Pipeline

一次 Pipeline 其實相當于一次構建任務,里面可以包含多個流程,如安裝依賴、運行測試、編譯、部署測試服務器、部署生產服務器等流程。

任何提交或者 Merge Request 的合并都可以觸發 Pipeline,如下圖所示:

+------------------+           +----------------+
|                  |  trigger  |                |
|   Commit / MR    +---------->+    Pipeline    |
|                  |           |                |
+------------------+           +----------------+

Stages

Stages 表示構建階段,說白了就是上面提到的流程。我們可以在一次 Pipeline 中定義多個 Stages,這些 Stages 會有以下特點:

  • 所有 Stages 會按照順序運行,即當一個 Stage 完成后,下一個 Stage 才會開始
  • 只有當所有 Stages 完成后,該構建任務 (Pipeline) 才會成功
  • 如果任何一個 Stage 失敗,那么后面的 Stages 不會執行,該構建任務 (Pipeline) 失敗

因此,Stages 和 Pipeline 的關系就是:

+--------------------------------------------------------+
|                                                        |
|  Pipeline                                              |
|                                                        |
|  +-----------+     +------------+      +------------+  |
|  |  Stage 1  |---->|   Stage 2  |----->|   Stage 3  |  |
|  +-----------+     +------------+      +------------+  |
|                                                        |
+--------------------------------------------------------+

Jobs

Jobs 表示構建工作,表示某個 Stage 里面執行的工作。我們可以在 Stages 里面定義多個 Jobs,這些 Jobs 會有以下特點:

  • 相同 Stage 中的 Jobs 會并行執行
  • 相同 Stage 中的 Jobs 都執行成功時,該 Stage 才會成功
  • 如果任何一個 Job 失敗,那么該 Stage 失敗,即該構建任務 (Pipeline) 失敗

所以,Jobs 和 Stage 的關系圖就是:

+------------------------------------------+
|                                          |
|  Stage 1                                 |
|                                          |
|  +---------+  +---------+  +---------+   |
|  |  Job 1  |  |  Job 2  |  |  Job 3  |   |
|  +---------+  +---------+  +---------+   |
|                                          |
+------------------------------------------+

基于 Docker 安裝 GitLab Runner

GitLab Runner 簡介

理解了上面的基本概念之后,有沒有覺得少了些什么東西 —— 由誰來執行這些構建任務呢?

答案就是 GitLab Runner 了!

想問為什么不是 GitLab CI 來運行那些構建任務?

一般來說,構建任務都會占用很多的系統資源 (譬如編譯代碼),而 GitLab CI 又是 GitLab 的一部分,如果由 GitLab CI 來運行構建任務的話,在執行構建任務的時候,GitLab 的性能會大幅下降。

GitLab CI 最大的作用是管理各個項目的構建狀態,因此,運行構建任務這種浪費資源的事情就交給 GitLab Runner 來做拉!

因為 GitLab Runner 可以安裝到不同的機器上,所以在構建任務運行期間并不會影響到 GitLab 的性能

基于 Docker 安裝 GitLab Runner

準備目錄結構

image

docker-compose.yml

version: '2' 
services:
    gitlab:
        image: twang2218/gitlab-ce-zh:10.5
        restart: always
        hostname: '10.3.50.160'
        container_name: gitlab
        environment:
          TZ: 'Asia/Shanghai'
          GITLAB_OMNIBUS_CONFIG: |
            external_url 'http://10.3.50.160:8080'
            gitlab_rails['gitlab_shell_ssh_port'] = 2222
            unicorn['port'] = 8888
            nginx['listen_port'] = 8080
        ports:
          - '8080:8080'
          - '8443:443'
          - '2222:22'
        volumes:
            - /etc/localtime:/etc/localtime
            - ./conf:/etc/gitlab
            - ./data/logs:/var/log/gitlab
            - ./data/data:/var/opt/gitlab
    gitlab-runner:
        image: gitlab/gitlab-runner
        restart: always
        hostname: gitlab-runner
        container_name: gitlab-runner
        extra_hosts:
            - git.imlcs.top:10.3.50.160
        depends_on:
            - gitlab
        volumes:
            - /etc/localtime:/etc/localtime
            - ./runner:/etc/gitlab-runner
            - /var/run/docker.sock:/var/run/docker.sock

GitLab CI 地址與令牌參數

項目–>設置–>CI/CD–>Runner 設置

image

注冊 Runner

方法一

docker exec -it gitlab-runner  gitlab-runner register -n \
  --url http://10.3.50.160:8080/ \
  --registration-token cpR4sgBCsZ-TJUpJVz9t \
  --description "dockersock" \
  --docker-privileged=true \
  --docker-pull-policy="if-not-present" \
  --docker-image "docker:latest" \
  --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
  --docker-volumes /root/m2:/root/.m2 \
  --executor docker 

方法二

docker exec -it gitlab-runner gitlab-runner register

# 輸入 GitLab 地址
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://10.3.50.160:8888/

# 輸入 GitLab Token
Please enter the gitlab-ci token for this runner:
1Lxq_f1NRfCfeNbE5WRh

# 輸入 Runner 的說明
Please enter the gitlab-ci description for this runner:
可以為空

# 設置 Tag,可以用于指定在構建規定的 tag 時觸發 ci
Please enter the gitlab-ci tags for this runner (comma separated):
deploy

# 這里選擇 true ,可以用于代碼上傳后直接執行
Whether to run untagged builds [true/false]:
true

# 這里選擇 false,可以直接回車,默認為 false
Whether to lock Runner to current project [true/false]:
false

# 選擇 runner 執行器,這里我們選擇的是 shell
Please enter the executor: virtualbox, docker+machine, parallels, shell, ssh, docker-ssh+machine, kubernetes, docker, docker-ssh:
#shell
docker                                 # 使用 docker 作為輸出模式
Please enter the default Docker image (e.g. ruby:2.1):
alpine:latest                          # 使用的基礎鏡像

使用 Runner

.gitlab-ci.yml

在項目工程下編寫.gitlab-ci.yml配置文件:
示例一,找一個springboot的簡單項目

image: docker-maven:alpine
services:
  - redis:3-alpine

#Maven 阿里云鏡像
#before_script:
#  - echo -e "<?xml version=\""1.0\"" encoding=\""UTF-8\""?><settings xmlns=\""http://maven.apache.org/SETTINGS/1.0.0\"" xmlns:xsi=\""http://www.w3.org/2001/XMLSchema-instance\"" xsi:schemaLocation=\""http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd\""><mirrors><mirror><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><mirrorOf>central</mirrorOf></mirror></mirrors></settings>" > ~/.m2/settings.xml

# 定義 stages
stages:
#  - test
  - build

# 定義 jobs
#test app:
#  stage: test
#  script:
#    - echo "I am test job"
#    - mvn test

# 定義 job
build app:
  stage: build
  script:
    - mvn  -Dmaven.test.skip=true clean package docker:build

示例二

stages:
  - install_deps
  - test
  - build
  - deploy_test
  - deploy_production

cache:
  key: ${CI_BUILD_REF_NAME}
  paths:
    - node_modules/
    - dist/

# 安裝依賴
install_deps:
  stage: install_deps
  only:
    - develop
    - master
  script:
    - npm install

# 運行測試用例
test:
  stage: test
  only:
    - develop
    - master
  script:
    - npm run test

# 編譯
build:
  stage: build
  only:
    - develop
    - master
  script:
    - npm run clean
    - npm run build:client
    - npm run build:server

# 部署測試服務器
deploy_test:
  stage: deploy_test
  only:
    - develop
  script:
    - pm2 delete app || true
    - pm2 start app.js --name app

# 部署生產服務器
deploy_production:
  stage: deploy_production
  only:
    - master
  script:
    - bash scripts/deploy/deploy.sh

上面的配置把一次 Pipeline 分成五個階段:

  • 安裝依賴(install_deps)
  • 運行測試(test)
  • 編譯(build)
  • 部署測試服務器(deploy_test)
  • 部署生產服務器(deploy_production)

注意:設置 Job.only 后,只有當 develop 分支和 master 分支有提交的時候才會觸發相關的 Jobs。

節點說明:

  • stages:定義構建階段,這里只有一個階段 deploy
  • deploy:構建階段 deploy 的詳細配置也就是任務配置
  • script:需要執行的 shell 腳本
  • only:這里的 master 指在提交到 master 時執行
  • tags:與注冊 runner 時的 tag 匹配

測試集成效果

所有操作完成后 push 代碼到服務器,查看是否成功:

image

passed 表示執行成功

其它命令

  • 刪除注冊信息
gitlab-ci-multi-runner unregister --name "名稱"

  • 查看注冊列表
gitlab-ci-multi-runner list

附:項目配置 Dockerfile 案例

FROM openjdk:8-jre

MAINTAINER Lusifer <topsale@vip.qq.com>

ENV APP_VERSION 1.0.0-SNAPSHOT
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

RUN mkdir /app

COPY myshop-service-user-provider-$APP_VERSION.jar /app/app.jar
ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.10.131:3306", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar"]

EXPOSE 8501

持續集成實戰用戶管理服務

本節視頻

部署通用模塊項目

先將所有被依賴項目(通用模塊項目)部署到 Nexus,為項目創建一個deploy.bat文件,示例代碼如下:

cd ..
cd myshop-dependencies
call mvn deploy

cd ..
cd myshop-commons
call mvn deploy

cd ..
cd myshop-commons-domain
call mvn deploy

cd ..
cd myshop-commons-mapper
call mvn deploy

cd ..
cd myshop-commons-dubbo
call mvn deploy

cd ..
cd myshop-static-backend
call mvn deploy

cd ..
cd myshop-service-user-api
call mvn deploy

持續集成依賴管理項目

由于我們所有項目的父工程都是依賴于myshop-dependencies,所以我們持續集成的第一步是將該項目進行持續集成,在項目目錄創建.gitlab-ci.yml文件,代碼如下:

stages:
  - deploy

deploy:
  stage: deploy
  script:
    - /usr/local/maven/apache-maven-3.5.3/bin/mvn clean install

持續集成服務提供者

gitlab-ci.yml

# 定義階段
stages:
  - build
  - push
  - run
  - clean

build:
  stage: build
  script:
    - /usr/local/maven/apache-maven-3.5.3/bin/mvn clean package
    - cp target/myshop-service-user-provider-1.0.0-SNAPSHOT.jar docker
    - cd docker
    - docker build -t 192.168.10.133:5000/myshop-service-user-provider:v1.0.0 .

push:
  stage: push
  script:
    - docker push 192.168.10.133:5000/myshop-service-user-provider:v1.0.0

run:
  stage: run
  script:
    - cd docker
    - docker-compose down
    - docker-compose up -d

clean:
  stage: clean
  script:
    - docker image prune -f

Dockerfile

FROM openjdk:8-jre
MAINTAINER Lusifer <topsale@vip.qq.com>

ENV APP_VERSION 1.0.0-SNAPSHOT
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

RUN mkdir /app

COPY myshop-service-user-provider-$APP_VERSION.jar /app/app.jar
ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.10.131:3306", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar"]

EXPOSE 8501 22222 20881

docker-compose.yml

version: '3.1'
services:
  myshop-service-user-provider:
    image: 192.168.10.133:5000/myshop-service-user-provider:v1.0.0
    container_name: myshop-service-user-provider
    ports:
      - 8501:8501
      - 22222:22222
      - 20881:20881

networks:
  default:
    external:
      name: dubbo

持續集成服務消費者

gitlab-ci.yml

stages:
  - build
  - push
  - run
  - clean

build:
  stage: build
  script:
    - /usr/local/maven/apache-maven-3.5.3/bin/mvn clean package
    - cp target/myshop-service-user-consumer-1.0.0-SNAPSHOT.jar docker
    - cd docker
    - docker build -t 192.168.10.133:5000/myshop-service-user-consumer:v1.0.0 .

push:
  stage: push
  script:
    - docker push 192.168.10.133:5000/myshop-service-user-consumer:v1.0.0

run:
  stage: run
  script:
    - cd docker
    - docker-compose down
    - docker-compose up -d

clean:
  stage: clean
  script:
    - docker image prune -f

Dockerfile

FROM openjdk:8-jre
MAINTAINER Lusifer <topsale@vip.qq.com>

ENV APP_VERSION 1.0.0-SNAPSHOT
ENV DOCKERIZE_VERSION v0.6.1
RUN wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && tar -C /usr/local/bin -xzvf dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz \
    && rm dockerize-linux-amd64-$DOCKERIZE_VERSION.tar.gz

RUN mkdir /app

COPY myshop-service-user-consumer-$APP_VERSION.jar /app/app.jar
ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.10.131:20881", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar"]

EXPOSE 8601 8701

docker-compose.yml

version: '3.1'
services:
  myshop-service-user-consumer:
    image: 192.168.10.133:5000/myshop-service-user-consumer:v1.0.0
    container_name: myshop-service-user-consumer
    ports:
      - 8601:8601
      - 8701:8701

networks:
  default:
    external:
      name: my_net

附:參考資料

Gitlab-ci與RUNNER的安裝與使用
spring boot項目基于docker、gitlab持續集成配置

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容