背景
在測試環境部署這塊,經歷過以下幾個階段:
-
階段一
有一臺測試服務器把項目放上面測試,當初也沒有什么相關的經驗,每次改完代碼本地打包上傳到服務器上,然后一頓命令啟動項目,完成了最原始的部署。
這種方式構建和部署全靠人肉,項目簡單的時候還好說,項目一多配置一多( 比如微服務 )中間哪個環節粗心出點錯那簡直就是災難。
同時還要專門有人對運維相關的技術比較了解,不然我不在的時候測試工作就完全停滯了。
-
階段二
既然都是重復工作,那就整理下步驟寫個腳本:
從 SVN/Git 拉代碼
Maven 構建打包
重啟 Tomcat
每次執行下腳本就搞定了。看著挺不錯的,不過實際執行時的情況總會復雜許多( 服務器賬號權限、測試人員對 Linux 的熟悉程度、項目啟動依賴復雜等等問題 )。
-
階段三
了解到 Jenkins 是個不錯的工具,那就把腳本的內容遷移到 Jenkins 上,不管是開發還是測試只要在 web 界面上點擊一下按鈕即可完成構建部署,很 easy 。
-
階段四
容器化:使用 Docker 來部署項目,這樣就可以干掉原來服務器上散落各地參差不齊的 Tomcat ( 不同項目依賴不同 ),利用 Docker Compose 對項目進行編排,提供一種規范的構建配置( 同時也是一份文檔 ),大大減小了后期維護和交接的成本。
-
階段五
上面的階段已經能解決日常需求了,但是還有一點問題就是每次提交完代碼還要手動去 Jenkins 上發布,能更自動點就更好了( 嗯,就是懶 )。
于是就引出了本文的目標 —— 自動持續構建,不需要人工操作 ( 留人工操作用于處理特殊情況 )。
方案流程
開發提交代碼。
開發對需要發布的版本打上 Tag 。
觸發 GitLab 的 tag push 事件,調用 Webhook 。
Webhook 觸發 Jenkins 的構建任務。
Jenkins 構建完項目可以按版本號上傳到倉庫、部署、通知相關人員等等。
安裝 GitLab
GitLab 官方文檔 已經介紹的比較詳細了,這里不再贅述,下面給出最終調整過的 Docker Compose 配置( 參考 ):
gitlab:
image: "twang2218/gitlab-ce-zh:11.0.2"
restart: always
hostname: 'gitlab'
ports:
- "10022:10022"
- "10086:10086"
# postgresql 端口
- "5432:5432"
volumes:
- ./gitlab/data:/var/opt/gitlab
- ./gitlab/log:/var/log/gitlab
- ./gitlab/config:/etc/gitlab
environment:
GITLAB_OMNIBUS_CONFIG: |
# 倉庫路徑,填寫宿主機的域名或 IP
external_url 'http://192.168.xxx.xxx:10086'
# ssh 連接端口
gitlab_rails['gitlab_shell_ssh_port'] = 10022
# 調整工作進程數減小內存占用,最小為 2
unicorn['worker_processes'] = 2
# 設置時區
gitlab_rails['time_zone'] = 'Asia/Shanghai'
# 郵箱配置
gitlab_rails['gitlab_email_from'] = '<your_email>'
gitlab_rails['gitlab_email_display_name'] = '<your_email_name>'
gitlab_rails['smtp_enable'] = true
gitlab_rails['smtp_address'] = 'smtp.163.com'
gitlab_rails['smtp_port'] = 25
gitlab_rails['smtp_user_name'] = "<your_email_account>"
gitlab_rails['smtp_password'] = "<your_email_password>"
gitlab_rails['smtp_domain'] = 'smtp.163.com'
gitlab_rails['smtp_tls'] = false
gitlab_rails['smtp_openssl_verify_mode'] = 'none'
gitlab_rails['smtp_enable_starttls_auto'] = false
gitlab_rails['smtp_ssl'] = false
gitlab_rails['smtp_force_ssl'] = false
# 數據庫配置
gitlab_rails['db_host'] = '127.0.0.1'
gitlab_rails['db_port'] = 5432
gitlab_rails['db_username'] = "gitlab"
gitlab_rails['db_password'] = "gitlab"
postgresql['listen_address'] = '0.0.0.0'
postgresql['port'] = 5432
postgresql['md5_auth_cidr_addresses'] = %w()
postgresql['trust_auth_cidr_addresses'] = %w(0.0.0.0/0)
postgresql['sql_user'] = "gitlab"
postgresql['sql_user_password'] = Digest::MD5.hexdigest "gitlab" << postgresql['sql_user']
# 備份設置-保留7天
gitlab_rails['backup_keep_time'] = 604800
GITLAB_BACKUPS: "daily"
GITLAB_SIGNUP: "true"
GITLAB_ROOT_PASSWORD: "lb80h&85"
GITLAB_GRAVATAR_ENABLED: "true"
說明:
這里使用 漢化版 鏡像,如果不適應可以換回 官方原版 鏡像
gitlab/gitlab-ce:11.0.2-ce.0
。項目初始配置 + 啟動很慢,需要一段時間,日志中出現 Reconfigured 時表示啟動成功。
192.168.xxx.xxx 替換為宿主機的 IP 地址。
初始管理員賬號密碼:
root
/lb80h&85
( 自行修改配置文件中的密碼 )。該配置為 乞丐版 ,內存占用 2G+ ( worker_processes 越多內存占用越大,默認為 8G )。
postgresql 為容器中內置的數據庫( 賬號密碼:
gitlab
/gitlab
),非必要情況就不要暴露端口了。郵箱填寫用于 發送找回密碼和通知 的發件人賬號( 收不到郵件? ),不想配郵箱可以刪掉相關配置,不影響正常使用( 注冊賬號時郵箱可以隨便填 )。
-
如果指定了
external_url
,那么其中的端口號就是用于 NGINX 監聽的端口號( 如果nginx['listen_port']
沒有顯式配置 ),所以上面配置中的端口映射是10086:10086
而非10086:80
。( 參考 ) 由于安全問題,新版本瀏覽器禁用了
10080
端口的訪問,可以換成其他端口。( 參考 )
安裝 Jenkins
為了測試方便,本文中使用 Docker 化的 Jenkins ,如果需要調用一些特殊的命令或腳本就不是很方便,實際使用過程中可以換成普通版的。
Docker Compose 配置如下:
version: '3'
services:
jenkins:
image: jenkins/jenkins:2.456
container_name: jenkins
networks:
- net
user: "root"
restart: always
ports:
- 9000:8080
environment:
- JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
volumes:
- /etc/localtime:/etc/localtime:ro
- ./data:/var/jenkins_home:rw
- ./backup:/var/jenkins_backup:rw
# 網絡配置
networks:
net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.23.0.0/16
說明:
-
初次啟動請打印日志,日志中有管理員賬號的初始密碼,第一次登錄的時候需要用到。
Jenkins initial setup is required. An admin user has been created and a password generated. Please use the following password to proceed to installation: db7bb60324dc4331bc0dd3e79cc499a5 This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
為了方便,使用了 root 賬號啟動 Docker 容器,如果使用默認賬號啟動還需要修改本地目錄( data、backup )的讀寫權限。
初始化過程中有些插件可能搜不到,可以在賬號初始化結束后再進入設置頁面安裝插件。
配置 GitLab
注冊賬號什么的就不贅述了,建一個測試項目 test ,隨便 commit 幾條內容。
按下圖步驟創建賬號的 access token ,用于 Jenkins 調用 GitLab 的 API 。
記下這里生成的 access token ( gRCtwVWU8cxwHdxxVZJD
),后面要用到。
配置 Jenkins
-
安裝插件
安裝過程可能會失敗,多試幾次就好了,可以嘗試更新 Jenkins 。
插件 說明 Git Parameter 用于參數化構建中動態獲取項目分支。 Generic Webhook Trigger 用于解析 Webhook 傳過來的參數。 GitLab 用于推送構建結果給 GitLab 。 -
添加 GitLab 憑據
憑據 -> 系統 -> 全局憑據 -> 添加憑據,把上面 GitLab 中生成的 access token 填進去。
Jenkins - 添加憑據 -
配置 GitLab 連接
系統管理 -> 系統設置 -> GitLab 配置項,填入 GitLab 相關的配置,后面配置項目時用到。
Jenkins - 配置 GitLab 連接 -
新建項目 test
Jenkins - 項目完整配置新建一個 Job ( 構建一個多配置項目 ),大部分配置可以按上圖照抄:
勾選 參數化構建過程,添加 Git Parameter 類型的參數 ref ,這樣手動點擊構建按鈕(
Build with Parameters
)的時候就可以指定分支進行構建。Source Code Management 選擇 Git ,添加項目地址和授權方式( 賬號密碼 或者 ssh key ,不能選 GitLab API token ),分支填寫構建參數 $ref 。
-
Build Triggers 選擇 Generic Webhook Trigger 方式用于解析 GitLab 推過來的詳細參數,其中 Token 用 GUID / UUID 生成一個。
用法說明: 在 GitLab 的 Webhook 編輯頁面中點擊 View details 查看調用詳情,其中 Request body 就是傳遞給 Jenkins 的參數,利用 JSONPath 語法( 在線測試 )將這個大的 JSON 解析為一個個小的自定義參數用于后面的構建流程。
其他觸發方式中: Trigger builds remotely 是 Jenkins 自帶的, Build when a change is pushed to GitLab 是 GitLab 插件 提供的,都屬于簡單的觸發構建,無法做復雜的處理。
雖然 Generic Webhook Trigger 提供了 Token 參數進行鑒權,但為了避免不同項目進行混調( 比如 A 項目提交代碼卻觸發了 B 項目的構建 ),還要對請求做下過濾。 Optional filter 中 Text 填寫需要校驗的內容( 可使用變量 ), Expression 使用正則表達式對 Text 進行匹配,匹配成功才允許觸發構建。
Build 內容按自己實際的項目類型進行調整,使用 Maven 插件 或 腳本 等等。
GitLab Connection 選擇上面添加的 GitLab 連接(
Jenkins
) , Post-build Actions 添加Publish build status to GitLab
動作,實現構建結束后反饋構建結果給 GitLab 。
-
回到 GitLab 的項目頁面中,添加一個 Webhook :
http://JENKINS_URL/generic-webhook-trigger/invoke?token=<上面 Jenkins 項目配置中的 token>
觸發器選擇 標簽推送事件 。因為日常開發中 push 操作比較頻繁而且不是每個版本都需要構建,所以只針對需要構建的版本打上 Tag 就好了。
GitLab - 添加 Webhook創建完使用 test 按鈕 先測試下,可能會出現下面的錯誤:
Hook execution failed: URL 'http://192.168.xxx.xxx:9000/generic-webhook-trigger/invoke?token=d63ad84eb18cb04d4459ec347a196dce' is blocked: Requests to the local network are not allowed
解決辦法: 允許 GitLab 本地網絡發送 Webhook 請求 。
測試效果
可以在 GitLab 上直接添加 Tag ,不過我覺得用 IDEA 操作更方便點,就把代碼拉下來在本地操作。
使用快捷鍵 Ctrl + Shift + K
調出 Push 窗口 ,把 Tag 推送到 GitLab 中。
回到 GitLab 頁面可以看到觸發了 Webhook , View details 查看請求詳情, Response body 中 triggered
字段值為 true
則表示成功觸發了 Jenkins 的構建。
如果 triggered
字段值為 false
很可能是 Optional filter 中實際的 Text 值和正則表達式 Expression 不匹配( 見 Response body 中 regexpFilterText
和 regexpFilterExpression
的具體值 ),比如刪除 Tag 的時候沒有 commitsId 就不會匹配上。
再看下構建結果:
見上面的
Publish build status to GitLab
。
注意: 每添加一個 Tag 就會觸發一次事件,不管是不是一起 push 的。所以一次 push 多個 Tag 會觸發 Jenkins 進行多次構建。不過 Jenkins 已經做了處理,默認串行執行任務( 一個任務結束后再執行下一個 ),而且在構建前有一個 pending 狀態,此時被多次觸發會進行合并,并取首次觸發的參數,如下圖所示:
測試發現新版 Jenkins 似乎不再合并構建了。
關于 Tag 的幾點說明
推送 Tag 到遠端的時候,遠端已存在( 同名 )的 Tag 不會被添加到遠端。
拉取遠端的 Tag 時,本地已存在( 同名 )的 Tag 不會添加到本地。
拉取遠端的 Tag 時,本地不會刪除遠端已刪除的 Tag ,需要同步遠端的 Tag 可以先刪除本地所有 Tag 再進行 pull 。
刪除 Tag 也會推送事件,要做好過濾( 上面配置中已使用 commitsId 字段進行過濾 )。
后續
通過上面的步驟已經初步實現了想要的效果,還有幾個點后續可以再考慮下:
上文只包含自動構建的內容,對于項目的部署可以考慮幾種方式:手動選擇指定的版本進行發布、構建任務結束后直接觸發部署任務、定時部署最新版本( 根據實際需求調整 )。
測試發版的頻率會比較高,會生成大量的 Tag ,可以約定 Tag 的格式,比如用
test 0.0.1
表示觸發測試環境的項目構建,用online 1.0.0
表示觸發正式版本構建,隔離之后可以方便后續的維護和清理。構建部分可以整合 Docker ,把構建結果打包到 Docker 鏡像中( 代碼版本庫的 Tag 正好可以作為鏡像的 Tag ),再上傳到 Docker 鏡像倉庫( 私服 或者第三方倉庫 )中,后續部署就可以直接從鏡像倉庫拉取鏡像直接運行了。
集成自動化測試 ,比如 這個 。
嘗試配置 GitLab 自帶的 CI / CD 。
相關
總結
以上就是對曾經踩過的一些坑進行的整合,也沒什么好總結的。總之,合理地利用現有工具來解放雙手,就能有更多時間做其他想做的事!
時間有限一些基礎的步驟就不細講直接一筆帶過了,方案上可能有些細節方面也沒考慮全,歡迎評論留言。