搭建一個使用 GitLab CI 的項目

前言

產品需求評審後,各自拆分任務,從 master 分支切出一個 release 分支,根據各自任務狀況切出 updatefeature 的開發分支;html

開發調試或提測時,將代碼 push 到遠程分支,提 merge request(如下簡稱 mr)到 test 分支,GitLab CI 將項目代碼自動構建並部署到測試環境;node

測試完畢後提 mrrelease 分支,待本次需求的開發分支都 code review 併合並後,從 release 分支提 mrpre 分支,GitLab CI 將項目代碼自動構建並部署到預生產環境,而後進行迴歸測試,有問題再從 release 分支切出開發分支進行修改,重複以前的流程。linux

預生產環境沒問題後,從 release 分支提 mrmaster 分支,,而後打 tag 上線,GitLab CI 將項目代碼自動構建並部署到生產環境,而後進行迴歸測試,有問題再發版。webpack

至此一次需求的完整開發流程就告一段落了,其中構建/部署等一些重複工做都是 GitLab CI 幫咱們完成,對此一直很好奇,接下來咱們就來嘗試搭建一個使用 GitLab CI 的項目。git

搭建新項目

現有項目中使用 GitLab CI 能夠直接跳過這步,從這裏開始github

能夠按下面的步驟一步一步搭建,也能夠直接克隆這個倉庫:gitlab-ci-exampleweb

初始化項目

新建項目文件夾

mkdir gitlab-ci-example
cd gitlab-ci-example

初始化 git 和 npm

git init
npm init -y

新建項目文件

mkdir src build

新建 .gitignore 文件

gitlab-ci-example/.gitignore
dist
node_modules

新建 .editorconfig 文件

gitlab-ci-example/.editorconfig
# editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

新建 index.html 文件

gitlab-ci-example/src/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <h1>Learn Gitlab CI</h1>
    </div>
  </body>
</html>

新建 main.js 文件

gitlab-ci-example/src/main.js
function appendElementToAPP({ tag = "div", content = "" }) {
  const appEle = document.getElementById("app");
  const newEle = document.createElement(tag);
  newEle.innerHTML = content;
  appEle.append(newEle);
}

appendElementToAPP({
  tag: "div",
  content: `append content by js on ${new Date().toUTCString()}`,
});

新建 webpack.dev.js 文件

gitlab-ci-example/build/webpack.dev.js
"use strict";
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const resolve = (dir) => path.resolve(__dirname, "../", dir);

module.exports = {
  mode: "development",
  entry: {
    app: "./src/main.js",
  },
  output: {
    path: resolve("dist"),
    filename: "[name].[hash].js",
  },
  resolve: {
    extensions: [".js"],
  },
  devServer: {
    port: 8090,
    contentBase: resolve("dist"),
  },
  plugins: [
    new HtmlWebpackPlugin({
      filename: resolve("dist/index.html"),
      template: "src/index.html",
    }),
  ],
};

新建 webpack.prod.js 文件

gitlab-ci-example/build/webpack.prod.js
"use strict";
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

const resolve = (dir) => path.resolve(__dirname, "../", dir);

module.exports = {
  mode: "production",
  entry: {
    app: "./src/main.js",
  },
  output: {
    path: resolve("dist"),
    filename: "[name].[hash].js",
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      filename: resolve("dist/index.html"),
      template: "src/index.html",
    }),
  ],
};

新建 append-element.js 文件

gitlab-ci-example/build/append-element.js.sh
const path = require("path");
const fs = require("fs");
const cheerio = require("cheerio");

const htmlFilePath = path.resolve(__dirname, "../dist/index.html");

fs.readFile(htmlFilePath, (err, data) => {
  if (err) {
    return;
  }
  const $ = cheerio.load(data);
  $("#app").append(
    `<div style="color: red;">append content by build on ${new Date().toUTCString()}</div>`
  );
  fs.writeFileSync(htmlFilePath, $.html());
});

新建 deploy-test.sh 文件

gitlab-ci-example/build/deploy-test.sh
cp -rf dist/* /www/test/gitlab-ci-example

修改 package.json 文件

gitlab-ci-example/package.json
{
  "name": "gitlab-ci-example",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "deploy-test": "build/deploy-test.sh",
    "dev": "webpack-dev-server --config build/webpack.dev.js",
    "build": "webpack --config build/webpack.prod.js && node build/append-element.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

安裝項目依賴

npm i -D cheerio webpack webpack-cli webpack-dev-server clean-webpack-plugin html-webpack-plugin

運行項目

npm run dev

在瀏覽器中打開連接:http://localhost:8090/ ,你應該能看到:docker

1.jpg

打包項目

npm run build

在瀏覽器中打開 dist 目錄下的 index.html 文件,你應該能看到:shell

2.jpg

至此項目的基本功能搭建完成,接下來開始在項目中使用 GitLab CInpm


項目中使用 GitLab CI

使用 GitLab CI以前,你得先準備一下:

  • 一臺雲服務器
  • 一個 GitLab 倉庫

設置 GitLab Runner

在倉庫主頁,點擊側邊欄 - Settings - CI / CD,跳轉 CI / CD Settings 頁面,展開 Runners 選項,按步驟手動設置 GitLab Runner

3.jpeg

安裝 GitLab Runner

根據系統架構,下載並安裝對應的軟件包,查看詳情

# 下載(適用於amd64的軟件包)
curl -LJO https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_amd64.deb

# 若是下載太慢,建議在本地下載好以後,經過scp命令複製到遠程,相似這樣
# scp ~/gitlab-runner_amd64.deb yourUserName@yourIPAddress:/home/yourUserName

# 安裝
sudo dpkg -i gitlab-runner_amd64.deb
# 輸出
Selecting previously unselected package gitlab-runner.
(Reading database ... 67015 files and directories currently installed.)
Preparing to unpack gitlab-runner_amd64.deb ...
Unpacking gitlab-runner (13.0.1) ...
Setting up gitlab-runner (13.0.1) ...
GitLab Runner: detected user gitlab-runner
Runtime platform                                    arch=amd64 os=linux pid=28968 revision=21cb397c version=13.0.1
gitlab-runner: Service is not installed.
Runtime platform                                    arch=amd64 os=linux pid=28975 revision=21cb397c version=13.0.1
gitlab-ci-multi-runner: Service is not installed.
Runtime platform                                    arch=amd64 os=linux pid=28993 revision=21cb397c version=13.0.1
Runtime platform                                    arch=amd64 os=linux pid=29039 revision=21cb397c version=13.0.1

# 若是你收到相似上面的報錯,運行下面的命令,若是能輸出信息表示正常
sudo gitlab-runner status
# 輸出
Runtime platform                                    arch=amd64 os=linux pid=29971 revision=21cb397c version=13.0.1
gitlab-runner: Service is running!

對於上面的報錯信息,能夠看看這個 gitlab issue

註冊 GitLab Runnner

開始註冊,下面是 Linux 的例子,其餘系統請看這裏

# 註冊
sudo gitlab-runner register
# 輸出
Runtime platform                                    arch=amd64 os=linux pid=31237 revision=21cb397c version=13.0.1
Running in system-mode.

# 指定 GitLab 實例 URL
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.com/

# 輸入註冊令牌(從項目-設置-CI/CD 設置-Runners 那裏拷貝)
Please enter the gitlab-ci token for this runner:
JhXh7o********yDXATd

# 輸入描述
Please enter the gitlab-ci description for this runner:
[hostname]: runner-001

# 輸入關聯標籤
Please enter the gitlab-ci tags for this runner (comma separated):
runner-001-tag
# 輸出
Registering runner... succeeded                     runner=JhXh7oEx

# 選擇執行環境,這裏選擇的是 shell
Please enter the executor: virtualbox, docker-ssh+machine, kubernetes, parallels, shell, ssh, docker+machine, custom, docker, docker-ssh:
shell
# 輸出
Runner registered successfully. Feel free to start it, but if it\'s running already the config should be automatically reloaded!

下載安裝並註冊完 Runner 後,返回 CI / CD Settings 頁面,如今應該能看到項目關聯的 Runner

4.jpg

配置 GitLab CI

設置完 GitLab Runner 後,咱們就能夠開始配置 GitLab CI 了,新建 .gitlab-ci.yml 文件

gitlab-ci-example/.gitlab-ci.yml
# 工做名稱
job-test:
  # 階段
  stage: test
  # 觸發條件:test 分支更新時
  only:
    - test
  # 指定工做給具備特定標籤的 Runners
  tags:
    - runner-001-tag
  # 腳本
  script:
    - npm install
    - npm run build
    - npm run deploy-test

默認狀況下 GitLab Runner 不會運行沒有 tags 的工做,因此這裏咱們指定註冊 GitLab Runner 時候設置的標籤:runner-001-tag查看更多 GitLab CI/CD 配置選項

若是你不想設置 tags,能夠修改 GitLab Runner 的配置,勾選 Run untagged jobs,表示容許 GitLab Runner 運行沒有設置 tags 的任務。

6.jpg
7.jpg

保存 .gitlab-ci.yml 文件後,將改動 push 到遠程倉庫

觸發 GitLab CI

配置文件有了以後,咱們須要將其觸發,從包含上面改動的分支,切出一個 test 分支,提交到遠程,用於觸發 GitLab CI(新提交和合並 test 分支都會觸發 CI ),固然經過圖形化界面建立 test 分支也是能夠的

git checkout test
git push -u origin test

在倉庫主頁,點擊側邊欄 - CI / CD - Pipelines,就能看到當前倉庫全部的 CI 記錄,相似下面這樣:

8.jpg


遇到的問題

1. mkdir: cannot create directory ‘/home/gitlab-runner/builds/3-1Hb5zy’: Permission denied

Running with gitlab-runner 13.0.1 (21cb397c)
   on runner-001 3-1Hb5zy
Preparing the "shell" executor 00:00
 Using Shell executor...
Preparing environment 00:00
 Running on xx-ubuntu...
Getting source from Git repository 00:00
 mkdir: cannot create directory ‘/home/gitlab-runner/builds/3-1Hb5zy’: Permission denied
Uploading artifacts for failed job 00:00
 mkdir: cannot create directory ‘/home/gitlab-runner/builds/3-1Hb5zy’: Permission denied
 ERROR: Job failed: exit status 1

緣由:

將代碼 push 到遠程以後,構建出現了上面的報錯,GitLab Runner 構建時使用的是 gitlab-runner 用戶,建立目錄的時候提示權限不足,嘗試查看目錄信息:

# 查看文件和目錄信息
ls -alF /home/gitlab-runner
# drwxr-xr-x 4 root          root          4096 Jun  2 17:45 builds/

當前目錄的權限和權限組都是 rootgitlab-runner 用戶不在 root 權限組下,因此沒權限操做。

仔細想一想 🤔 發現不對勁,GitLab Runner 構建時使用的是 gitlab-runner 用戶,可是爲何 builds 目錄在 root 權限組下?回想一下在此以前作過哪些和 root 用戶相關的操做,通過確認和查閱資料後發現,原來是在此次構建以前,手動安裝服務(gitlab-runner install)的時候指定使用 root 用戶(--user root)致使的:

# 安裝服務,指定工做目錄,指定運行任務的用戶爲 root 用戶
sudo gitlab-runner install --working-directory /home/gitlab-runner --user root

如何解決:

刪除 builds 目錄、卸載重裝 gitlab-runner 服務,將服務關聯的用戶指回 gitlab-runner 用戶

# 中止服務
sudo gitlab-runner stop
# 卸載服務
sudo gitlab-runner uninstall
# 從新安裝服務,指定工做目錄和用戶
sudo gitlab-runner install --working-directory /home/gitlab-runner --user gitlab-runner
# 完整配置
# sudo gitlab-runner install --working-directory /home/gitlab-runner --config /etc/gitlab-runner/config.toml --service gitlab-runner --syslog --user gitlab-runner
# 校驗
sudo gitlab-runner verify
# 啓動服務
sudo gitlab-runner start
# 查看狀態
sudo gitlab-runner status
# 再次查看文件和目錄信息
ls -alF /home/gitlab-runner
# drwxrwxr-x 3 gitlab-runner gitlab-runner 4096 Jun  3 16:21 builds/

如今 builds 目錄的權限歸回 gitlab-runner 用戶全部了,在 gitlab 倉庫的 PipelinesJobs 頁面找到此次工做關聯的 retry 按鈕,點擊按鈕嘗試從新運行構建

2. bash: line 92: npm: command not found

Running with gitlab-runner 13.0.1 (21cb397c)
   on runner-001 3-1Hb5zy
Preparing the "shell" executor  00:00
 Using Shell executor...
Preparing environment 00:00
 Running on VM-0-5-ubuntu...
Getting source from Git repository  00:03
 Fetching changes with git depth set to 50...
 Reinitialized existing Git repository in /home/gitlab-runner/builds/3-1Hb5zy/0/Lsnsh/gitlab-ci-example/.git/
 Checking out 4e716630 as test...
 Skipping Git submodules setup
Restoring cache 00:00
Downloading artifacts 00:00
Running before_script and script  00:00
 $ npm install
 bash: line 92: npm: command not found
Running after_script  00:00
Uploading artifacts for failed job  00:00
 ERROR: Job failed: exit status 1

緣由:

重裝服務後 retry 構建後,出現了上面的報錯,緣由是由於 gitlab-runner 用戶所處的環境沒有安裝 node 致使的(默認狀況下在 root 或者其餘用戶上安裝的 nodegitlab-runner 用戶所處環境是訪問不到的)

如何解決:

登陸服務器,切換到 gitlab-runner 用戶,安裝 nvm,再安裝 node

# 切換到 root 用戶
sudo su
# 登陸 gitlab-runner 用戶
su -l gitlab-runner
# 安裝 nvm(https://github.com/nvm-sh/nvm),若是訪問腳本443,嘗試用其餘方式安裝 nvm 或者直接安裝 node
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash
# 查看 nvm
nvm ls
# 安裝 node(截止目前最新LTS版本爲:12.18.0,自行選擇版本安裝)
nvm install 12.16.2
# 查看 node 和 npm 版本
node -v
npm -v

如今 gitlab-runner 用戶所處環境已經安裝 node 了,高興的嘗試 retry 後,發現依然是 bash: line 92: npm: command not found,一度覺得是錯覺、是服務沒有檢測到 node 的存在,嘗試重裝、重啓 gitlab-runner 服務後 retry 依然是 failed

冷靜一番後,查閱大量相似案例,初步判斷多是環境變量沒加載,也就是 nvm 沒有加載致使的,嘗試在構建過程當中手動加載 ~/.bashrc 文件:

before_script:
  - source ~/.bashrc

從新 retry 後依然仍是 failed,最後仍是在 ~/.profile~/.bashrc 兩個配置文件頭部的一些註釋信息裏,找到了一些新的靈感:

~/.profile

# ~/.profile: executed by the command interpreter for login shells.
# ...

直譯過來:~/.profile: 由命令解釋器針對登陸 shell 執行。

~/.bashrc

# ~/.bashrc: executed by bash(1) for non-login shells.
# ...

直譯過來:~/.bashrc:由 bash(1) 對非登陸 shell 執行。

以上信息中提到了登陸與非登陸兩種狀態,配置文件在對應狀態下才會執行,經過添加調試信息發現,在 gitlab-runner 執行任務構建時,不會加載 ~/.bashrc 文件,只會加載 ~/.profile 文件;而經過 ssh 登陸服務器時,兩個文件都會加載,是否是有些疑惑 🤔,這是由於 ~/.profile 文件在開頭會根據環境(bash)決定是否要先加載 ~/.bashrc 文件,具體代碼以下:

# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
    fi
fi

要解決 npm 命令找不到這個問題,須要在 ~/.profile 配置文件添加上加載 nvm 的代碼:

# 編輯配置文件
vi ~/.profile
# 配置 nvm 加載,將下面的代碼添加到配置文件中(https://github.com/nvm-sh/nvm#installing-and-updating)
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
# 保存配置文件後,從新加載配置文件
source ~/.profile

3. sh: 1: build/deploy-test.sh: Permission denied

$ npm run deploy-test
> gitlab-ci-example@1.0.0 deploy-test /home/ubuntu/builds/3-1Hb5zy/0/Lsnsh/gitlab-ci-example
> build/deploy-test.sh
sh: 1: build/deploy-test.sh: Permission denied
npm ERR! code ELIFECYCLE
npm ERR! errno 126
npm ERR! gitlab-ci-example@1.0.0 deploy-test: `build/deploy-test.sh`
npm ERR! Exit status 126
npm ERR!
npm ERR! Failed at the gitlab-ci-example@1.0.0 deploy-test script.
# ...

緣由:

# 在項目構建目錄下(相似:/home/ubuntu/builds/3-1Hb5zy/0/Lsnsh/gitlab-ci-example),查看部署腳本的權限信息
ls -alF ./build/deploy-test.sh
# -rw-rw-r-- 1 ubuntu ubuntu   42 Jun  2 19:40 deploy-test.sh

經過以上命令發現和搜索相關問題,發現 deploy-test.sh 文件不具有可執行權限,因此沒法執行

如何解決:

  1. 直接更改 deploy-test.sh 文件權限
# 代表 deploy-test.sh 文件是可執行的
git update-index --chmod=+x ./build/deploy-test.sh
# 改動會直接進入暫存區,編輯器的 git state 可能代表還有新的更改,忽略後直接提交本次更改後,git state 的狀態會更新
git commit -m 'Make build.sh executable'
# 提交到遠程,master, test 或者其餘分支
git push
  1. 經過 sh 命名執行 deploy-test.sh 文件
# package.json
- "deploy-test": "build/deploy-test.sh",
+ "deploy-test": "sh build/deploy-test.sh",

4. /www/test/gitlab-ci-example: No such file or directory

$ npm run deploy-test
> gitlab-ci-example@1.0.0 deploy-test /home/gitlab-runner/builds/3-1Hb5zy/0/Lsnsh/gitlab-ci-example
> build/deploy-test.sh
/www/test/gitlab-ci-example: No such file or directory
Please create this directory and then assign the directory permissions to the gitlab-runner user.
You can execute the following command as root:
mkdir /www/test/gitlab-ci-example
chown gitlab-runner /www/test/gitlab-ci-example

緣由:

build/deploy-test.sh 腳本會將構建好的代碼,拷貝到 /www/test/gitlab-ci-example 目錄下,所以在構建以前須要先建立好這個目錄

如何解決:

參考 build/deploy-test.sh 腳本中打印出的提示信息,新建並分配目錄權限便可:

# 新建目錄(使用 root 用戶或者其餘 gitlab-runner 用戶覺得的用戶)
mkdir /www/test/gitlab-ci-example
# 分配 gitlab-runner 用戶文件夾權限
chown gitlab-runner /www/test/gitlab-ci-example

總結

至此,CI 終於能夠跑通了,部署後頁面的內容是這樣的,點擊查看

9.jpg

經過 .gitlab-ci.yml 配置文件,你能夠在構建的各個階段作處理,好比你能夠在 before_scriptafter_script 階段調用釘釘機器人接口,及時將部署狀態同步到我的/羣:

before_script:
  # 釘釘通知 釘釘羣
  - curl -X POST 'https://oapi.dingtalk.com/robot/send?access_token=xxx&xxxx'

通知相似下面這樣:

10.jpg

更多關於 .gitlab-ci.yml 文件的配置信息,請看官方文檔

好利用 CI / CD 這件工具,相信會大大提高團隊協做和開發效率。萬事開頭難,起初確定會有抵觸心理,邁過這道坎以後,還有下一道坎等着你[手動狗頭]

示例項目的倉庫連接以下,歡迎 star 🌟:

github 倉庫(template):https://github.com/Lsnsh/gitl...

gitlab 倉庫:https://gitlab.com/Lsnsh/gitl...


命令彙總

# 查看 gitlab-runner 相關進程
ps aux|grep gitlab-runner

# 註冊 gitlab-runner
sudo gitlab-runner register

# 重裝 gitlab-runner 服務
# 中止服務
sudo gitlab-runner stop
# 卸載服務
sudo gitlab-runner uninstall
# 從新安裝服務,指定工做目錄和用戶
sudo gitlab-runner install --working-directory /home/gitlab-runner --user gitlab-runner
# 完整配置
# sudo gitlab-runner install --working-directory /home/gitlab-runner --config /etc/gitlab-runner/config.toml --service gitlab-runner --syslog --user gitlab-runner
# 校驗
sudo gitlab-runner verify
# 啓動服務
sudo gitlab-runner start
# 查看狀態
sudo gitlab-runner status

# 拷貝文件到遠程主機
# scp ~/gitlab-runner_amd64.deb yourUserName@yourIPAddress:/home/yourUserName
# eg: (將文件 ~/gitlab-runner_amd64.deb 拷貝到遠程主機,公網 IP 爲 110.120.130 的 root 用戶目錄下)
scp ~/gitlab-runner_amd64.deb root@110.120.130:/home/root

# 查看文件和目錄信息
# eg: (查看 /home/gitlab-runner 目錄)
ls -alF /home/gitlab-runner

# 切換到 root 用戶
sudo su
# root 用戶登陸其餘用戶
# eg: (登陸 gitlab-runner 用戶)
su -l gitlab-runner

# 代表文件是可執行的
# git update-index --chmod=+x 文件路徑
# 代表文件是不可執行的
# git update-index --chmod=-x 文件路徑
# eg: (代表 ./build/deploy-test.sh 文件是可執行的)
git update-index --chmod=+x ./build/deploy-test.sh

# 給用戶分配文件或文件夾權限
# chown 用戶名 文件或文件夾路徑
# eg: (分配 gitlab-runner 用戶 /www/test/gitlab-ci-example 文件夾權限)
chown gitlab-runner /www/test/gitlab-ci-example

參考連接

相關文章
相關標籤/搜索