第 05章 部署持續集成(5.7)

持續集成的基本概念

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

持續集成

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

快速發現錯誤。每完成一點更新,就集成到主幹,可以快速發現錯誤,定位錯誤也比較容易。
防止分支大幅偏離主幹。如果不是經常集成,主幹又在不斷更新,會導致以後集成的難度變大,甚至難以集成。
Martin Fowler 說過,”持續集成並不能消除 Bug,而是讓它們非常容易發現和改正。”
這裏寫圖片描述
持續集成強調開發人員提交了新代碼之後,立刻進行構建、(單元)測試。根據測試結果,我們可以確定新代碼和原有代碼能否正確地集成在一起。

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

持續交付

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

持續交付可以看作持續集成的下一步。它強調的是,不管怎麼更新,軟件是隨時隨地可以交付的。
這裏寫圖片描述
持續交付在持續集成的基礎上,將集成後的代碼部署到更貼近真實運行環境的「類生產環境」(production-like environments)中。比如,我們完成單元測試後,可以把代碼部署到連接數據庫的 Staging 環境中更多的測試。如果代碼沒有問題,可以繼續手動部署到生產環境中。

持續部署

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

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

持續部署的前提是能自動化完成測試、構建、部署等步驟。
這裏寫圖片描述

持續集成的操作流程

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

提交

流程的第一步,是開發者向代碼倉庫提交代碼。所有後面的步驟都始於本地代碼的一次提交(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 | |
| +———+ +———+ +———+ |
| |
+——————————————+

使用 GitLab Runner

簡介

理解了上面的基本概念之後,有沒有覺得少了些什麼東西 —— 由誰來執行這些構建任務呢?
答案就是 GitLab Runner 了!

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

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

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

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

安裝

在目標主機上安裝 GitLab Runner,這裏的目標主機指你要部署的服務器
Ubuntu 安裝腳本:

curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash
sudo apt-get update
sudo apt-get install gitlab-ci-multi-runner

註冊 Runner

安裝好 GitLab Runner 之後,我們只要啓動 Runner 然後和 GitLab CI 綁定:

[root@iZbp1fmnx8oyubksjdk7leZ gitbook]# gitlab-ci-multi-runner register
Running in system-mode.                            

Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
http://192.168.75.146:8080/
Please enter the gitlab-ci token for this runner:
1Lxq_f1NRfCfeNbE5WRh
Please enter the gitlab-ci description for this runner:
[iZbp1fmnx8oyubksjdk7leZ]: deploy-gaming
Please enter the gitlab-ci tags for this runner (comma separated):
deploy
Whether to run untagged builds [true/false]:
[false]: true
Whether to lock Runner to current project [true/false]:
[false]: 
Registering runner... succeeded                     runner=P_zfkhTb
Please enter the executor: virtualbox, docker+machine, parallels, shell, ssh, docker-ssh+machine, kubernetes, docker, docker-ssh:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded! 

說明:

gitlab-ci-multi-runner register:執行註冊命令
Please enter the gitlab-ci coordinator URL:輸入 ci 地址
Please enter the gitlab-ci token for this runner:輸入 ci token
Please enter the gitlab-ci description for this runner:輸入 runner 名稱
Please enter the gitlab-ci tags for this runner:設置 tag
Whether to run untagged builds:這裏選擇 true ,代碼上傳後會能夠直接執行
Whether to lock Runner to current project:直接回車,不用輸入任何口令
Please enter the executor:選擇 runner 類型,這裏我們選擇的是 shell
CI 的地址和令牌,在 項目 –> 設置 –> CI/CD –> Runner 設置:
這裏寫圖片描述
.gitlab-ci.yml

在項目工程下編寫 .gitlab-ci.yml 配置文件:

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 匹配
其它配置

爲保證能夠正常集成,我們還需要一些其它配置:

安裝完 GitLab Runner 後系統會增加一個 gitlab-runner 賬戶,我們將它加進 root 組:

gpasswd -a gitlab-runner root

配置需要操作目錄的權限,比如你的 runner 要在 gaming 目錄下操作:

chmod 775 gaming

由於我們的 shell 腳本中有執行 git pull 的命令,我們直接設置以 ssh 方式拉取代碼:

su gitlab-runner
ssh-keygen -t rsa -C "你在 GitLab 上的郵箱地址"
cd 
cd .ssh
cat id_rsa.pub

複製 id_rsa.pub 中的祕鑰到 GitLab:
這裏寫圖片描述
通過 ssh 的方式將代碼拉取到本地
測試集成效果

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

這裏寫圖片描述
passed 表示執行成功

其他命令

刪除註冊信息:

gitlab-ci-multi-runner unregister --name "名稱"

查看註冊列表:

gitlab-ci-multi-runner list

使用 GitLab Runner Docker

爲了配置方便,我們使用 docker 來部署 GitLab Runner

環境準備

創建工作目錄 /usr/local/docker/runner
創建構建目錄 /usr/local/docker/runner/environment
下載 jdk-8u152-linux-x64.tar.gz 並複製到 /usr/local/docker/runner/environment
Dockerfile

在 /usr/local/docker/runner/environment 目錄下創建 Dockerfile

FROM gitlab/gitlab-runner:v11.0.2
MAINTAINER Lusifer <topsale@vip.qq.com>

# 修改軟件源
RUN echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial main restricted universe multiverse' > /etc/apt/sources.list && \
    echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-security main restricted universe multiverse' >> /etc/apt/sources.list && \
    echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main restricted universe multiverse' >> /etc/apt/sources.list && \
    echo 'deb http://mirrors.aliyun.com/ubuntu/ xenial-backports main restricted universe multiverse' >> /etc/apt/sources.list && \
    apt-get update -y && \
    apt-get clean

# 安裝 Docker
RUN apt-get -y install apt-transport-https ca-certificates curl software-properties-common && \
    curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | apt-key add - && \
    add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" && \
    apt-get update -y && \
    apt-get install -y docker-ce
COPY daemon.json /etc/docker/daemon.json

# 安裝 Docker Compose
WORKDIR /usr/local/bin
RUN wget https://raw.githubusercontent.com/topsale/resources/master/docker/docker-compose
RUN chmod +x docker-compose

# 安裝 Java
RUN mkdir -p /usr/local/java
WORKDIR /usr/local/java
COPY jdk-8u152-linux-x64.tar.gz /usr/local/java
RUN tar -zxvf jdk-8u152-linux-x64.tar.gz && \
    rm -fr jdk-8u152-linux-x64.tar.gz

# 安裝 Maven
RUN mkdir -p /usr/local/maven
WORKDIR /usr/local/maven
RUN wget https://raw.githubusercontent.com/topsale/resources/master/maven/apache-maven-3.5.3-bin.tar.gz
# COPY apache-maven-3.5.3-bin.tar.gz /usr/local/maven
RUN tar -zxvf apache-maven-3.5.3-bin.tar.gz && \
    rm -fr apache-maven-3.5.3-bin.tar.gz
# COPY settings.xml /usr/local/maven/apache-maven-3.5.3/conf/settings.xml

# 配置環境變量
ENV JAVA_HOME /usr/local/java/jdk1.8.0_152
ENV MAVEN_HOME /usr/local/maven/apache-maven-3.5.3
ENV PATH $PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin

WORKDIR /

daemon.json

在 /usr/local/docker/runner/environment 目錄下創建 daemon.json,用於配置加速器和倉庫地址

{
  "registry-mirrors": [
    "https://registry.docker-cn.com"
  ],
  "insecure-registries": [
    "192.168.75.131:5000"
  ]
}

docker-compose.yml

在 /usr/local/docker/runner 目錄下創建 docker-compose.yml

version: '3.1'
services:
  gitlab-runner:
    build: environment
    restart: always
    container_name: gitlab-runner
    privileged: true
    volumes:
      - /usr/local/docker/runner/config:/etc/gitlab-runner
      - /var/run/docker.sock:/var/run/docker.sock

註冊 Runner

docker exec -it gitlab-runner gitlab-runner register

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

# 輸入 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

附:項目配置 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 itoken-eureka-$APP_VERSION.jar /app/app.jar
ENTRYPOINT ["dockerize", "-timeout", "5m", "-wait", "tcp://192.168.75.128:8888", "java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar", "--spring.profiles.active=prod"]

EXPOSE 8761
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章