1. 程式人生 > >使用GitLab、Jenkins、Docker建立快速持續化整合交付部署方案(三)

使用GitLab、Jenkins、Docker建立快速持續化整合交付部署方案(三)

上一章節中我們完成了對Docker的使用和部署。

本文中我們將完成 GitLab → Jenkins → Docker 的環境,並完成基於GitLab、Jenkins、Docker的面向Web開發的快速部署方案。

 

我們最終得到的結果,將是部署人員只在部署環境下只敲一句命令,就完成整個部署工作。

最終的目的就是接近一鍵部署,燒鍋爐的大叔都會給伺服器部署服務了,開除工程師。

文章索引

  1. GitLab、Jenkins、Docker 初始環境安裝
  2. 製作 Docker映象 及 Docker Compose 的使用
  3. 使用 Webhook 自動觸發 Jenkins 進行 Docker映象製作並儲存到私有映象倉庫,以及目的機部署
    (本文)

 

私有Docker倉庫

為什麼要使用私有倉庫

我們最終要得到的交付成果,應是一個 Docker Image。

在某種程度上,不少公司在開發測試階段可能會考慮直接在開發伺服器上生成本地映象,或是使用 docker save 方式匯出tar映象包檔案,在不同機器上進行部署。這種方式對於公司網路環境比較好的情況下確實可以實行,而且對於線上生產環境,某種意義 上人工驗收後部署要比全自動部署更放心。

然而更多的企業,外部網路頻寬有限,內部網路優化更是渣的可憐。一個映象包一般都在百M左右,用網路複製可能需要幾十分鐘甚至半個小時,部署一次環境,等待網路複製的時間,要比 拿一個優盤抱著筆記本乘上幾十層電梯鑽進機房忍著低溫和轟鳴 蛋疼的多。

(已見過不止 3 家公司內部網路是這種情況。而且一般公司的IT部也都是所謂的網管,出問題後 重啟、重灌、換機 三步走的風格與一般小農村網咖網管也沒什麼區別。這種職位多數都是公司內部解決職工親屬就業問題內推產生的。神州泰嶽全公司都在用12.12.0.0/16這種公網地址當私網地址用,為伺服器申請一個靜態DHCP地址都會搞得吵架。神州數碼這種大公司的網管也在給新員工配置新電腦的時候,裝個盜版作業系統都要看手冊,還總出錯)

所以我們要在這裡,充分利用Docker映象的分層檔案系統:沒有更新的部分不會生成新的映象,不會浪費硬碟空間,拉取新映象時舊映象也不會消耗流量和頻寬。

建立私有倉庫

你可以直接執行:

 

1

docker run -d -p 5000:5000 -v /var/docker_registry:/var/lib/registry registry

來啟動一個倉庫容器,預設埠5000,資料將會保存於/var/docker_registry。

注意這是Version 2,對於其他版本請參考其它文件。

注意這是一個HTTP服務,沒有加密傳輸,對於安全性需求,建議自行解決。

相關文件:https://github.com/docker-library/docs/tree/master/registry

本地映象打tag

 

1

docker tag eea53635d0c6 your_registry_domain/private-docker-registry/www-data:0.0.1

這不是一個移動操作,而是一個複製軟連結誒操作,在docker images中會新增一個ID相同但tag不同的映象,不佔用硬碟空間。

推送本地映象到倉庫

 

1

docker push your_registry_domain/private-docker-registry/www-data:0.0.1

會將這個映象推送到your_registry_domain/private-docker-registry/

拉取倉庫映象到本地,或直接執行

pull 和 run,不解釋

 

配置GitLab

訪問許可權

注意,因為GitLab是我們自建的倉庫,擁有完全的使用權,所以對於倉庫的可視許可權將要比GitHub多出兩種選項。

  • Private 私有,只有所有者、組內成員或已分配的使用者有檢視許可權(同GitHub收費版的私有倉庫)
  • Internal 內部,擁有GitLab賬號的成員可以檢視,無賬號使用者無法訪問(適合於伺服器放在公網但不想公開程式碼的情景,GitHub沒有這個功能)
  • Public 公共,任何人都可以訪問(同GitHub免費版的公共倉庫)

建議公司內部的程式碼倉庫都設定為 Internal。

使用GitLat Webhook與Jenkins實現持續交付

GitLab等程式碼倉庫其實也支援CI/CD操作,然而我們將要執行的操作對於GitLab來講過於複雜了,所以這些操作要交給Jenkins這種專門的持續整合工具。為了能在需要時自動觸發Jenkins自動操作,我們要使用GitLab的Webhook進行操作。

Deploy Keys

你可以為Jenkins專門製作Deploy Keys,或者乾脆在GitLab上增加一個Jenkins賬戶,使用者Jenkins拉取程式碼。兩種方法都可以。

配置Webhook

根據我們之前的安裝部署,Jenkins的埠是8081。假設我們的主機域名(地址)為your-what-server,專案為catscarlet/test1.git,則url為

 

1

http://your-jenkins-server:8081/git/[email protected]:catscarlet/test1.git

觸發條件建議選擇 Merge Request event,或乾脆手動觸發。

這裡就有GitLab安裝在宿主機的好處了,埠很標準,不然改個ssh埠還要使用git的話,會變得很麻煩。至於https,相信搞得定Let’s encrypt的人很多,但搞不到公司域名的人更多。

 

配置Jenkins

安裝必要外掛

安裝這些外掛:

  • Credentials Plugin
  • Git plugin
  • Gitlab Hook Plugin
  • SSH plugin
  • Locale plugin

修改預設語言

Jenkins預設根據使用者系統來顯示語言,然而其自身的中文翻譯挺糟糕的,所以換成純英文更好一些。

在安裝 Locale plugin 外掛之後,點選 Manage Jenkins – Configure System,在Default Language處填寫en,儲存。

修改併發數

因為接下來我們會把專案程式碼複製到一個統一的臨時目錄下進行處理,如果這時候有其他專案也在處理,則這個臨時資料夾內可能就會有檔案混亂的問題。所以我們要把Jenkins的併發數改為1,引數為 # of executors 。對於需要併發處理的正式環境,您需要手動修改臨時目錄的位置。

新建Jenkins 專案

登入到Jenkins主頁,New Item 新建一個 Freestyle project ,命名為test1。

在 Source Code Management 這裡,選擇Git,並在Repositories填寫你的Git專案地址catscarlet/test1.git,Credentials選擇你的證書,Branch Specifier選擇對應Branch。在 Additional Behaviours 中增加 Clean before checkout。

Build Triggers選擇Poll SCM,啟用Webhook功能。內容留空,不定時Build,完全由Webhook觸發。

在 Build 這裡,Add build stemp 增加兩個專案:Execute shell 和 Execute shell script on remote host using ssh。

不能選擇直接在本地 Execute shell ,因為Jenkins目前是執行在Docker容器內的,與宿主機完全隔離,除了基本的Linux Shell和JAVA的SDK環境之外,容器內沒有任何其他東西了,不能使用Docker命令,沒有node和npm,不能編譯,幾乎什麼都做不了。

所以我們在這裡要分開做兩件事:

  • 準備程式碼,並複製到宿主機環境
  • 登入到宿主機,在宿主機上進行打包。你也可以選擇登入到其他主機上,比如一臺專用打包伺服器。

程式碼

更改Jenkins的啟動方式,額外繫結一個數據卷目錄。

 

1

docker run -d -v /var/jenkins_home:/var/jenkins_home -v /tmp:/tmp -p 8081:8081 -p 50000:50000 jenkins_modified

我們已經在Source Code Management選擇了Git,所以在執行Execute shell之前,Jenkins會先用Git把程式碼拉取到本地,不需要自己手動執行git pull。

Execute shell:

 

1

2

3

4

5

6

7

8

9

10

11

#!/bin/bash

echo '------------------------------'

echo '--- Execute shell ---'

touch BUILD_${BUILD_NUMBER}

echo ${BUILD_NUMBER} > BUILD_VERSION

tar zcvf tmp_archive.tgz ./*

rm -rf /tmp/tmp/*

mkdir -p /tmp/tmp/

cp tmp_archive.tgz /tmp/tmp/

echo '--- Execute shell finished ---'

echo '------------------------------'

Execute shell script on remote host using ssh:

需要先在 Jenkins – configuration – SSH remote hosts 中新增對應的伺服器登入方式。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

#!/bin/bash

echo '-----------------------------------------------------'

echo '--- Execute shell script on remote host using ssh ---'

cd /tmp/tmp/

tar zxf tmp_archive.tgz

echo '--- Finished tar zxf tmp_archive.tgz ---'

if [ ! -f project-build.sh ]; then

    echo "--- Error! project-build.sh not avaliable! ---"

    exit 1

fi

echo '--- Prepare to build ---'

bash project-build.sh test1 your_registry_domain:5000 private-docker-registry

echo '-----------------------------------------------------'

這裡建議呼叫從git倉庫上拉下來的指令碼,而不是把要執行的命令寫在Jenkins這裡。

project-build.sh

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

#!/bin/bash

BUILD_NUMBER=$1

REGISTRY_URL=$2

REGISTRY_NAME=$3

LOGFILE=build.log

touch $LOGFILE

 

echo "- BUILD_NUMBER:$1" >> $LOGFILE

echo "- REGISTRY_URL:$2" >> $LOGFILE

echo "- REGISTRY_NAME:$3" >> $LOGFILE

 

if !([ $1 -a $2 -a $3 ]); then

    echo '--- PRAMA ERROR ---' >> $LOGFILE

    exit 1

fi

 

if [ -f IMAGEIDS ]; then

    rm IMAGEIDS

fi

 

touch IMAGEIDS

 

echo -n "$REGISTRY_URL/$REGISTRY_NAME/test1-nginx_modified:" >> IMAGEIDS

docker build -q -t $REGISTRY_URL/$REGISTRY_NAME/test1-nginx_modified:$BUILD_NUMBER nginx_modified/ >> IMAGEIDS

echo -n "$REGISTRY_URL/$REGISTRY_NAME/test1-php-fpm_modified:" >> IMAGEIDS

docker build -q -t $REGISTRY_URL/$REGISTRY_NAME/test1-php-fpm_modified:$BUILD_NUMBER php-fpm_modified/ >> IMAGEIDS

echo -n "$REGISTRY_URL/$REGISTRY_NAME/test1-www-data:" >> IMAGEIDS

docker build -q -t $REGISTRY_URL/$REGISTRY_NAME/test1-www-data:$BUILD_NUMBER www-data/ >> IMAGEIDS

 

if [ -f tmp_shell.sh ]; then

    rm tmp_shell.sh

fi

 

while read line

do

    echo $line | sed "s/\(.*\):sha256:\(.*\)/docker tag \2 \1:latest/g" >> tmp_shell.sh

    echo $line | sed "s/\(.*\):sha256:\(.*\)/docker push \1/g" >> tmp_shell.sh

done < IMAGEIDS

 

if [ -f PUSH.log ]; then

    rm PUSH.log

fi

 

if [ ! -f tmp_shell.sh ]; then

    echo '--- ERROR! tmp_shell.sh is missing! ---'

    exit 1

fi

bash tmp_shell.sh >> docker_push.log

cat docker_push.log

這裡建議對每個映象都手動打包,而不是使用compose打包。一方面compose無法針對映象打tag(compose更關注於容器而非映象,容器是NAME而映象是TAG),另一方面最終部署所需的 docker-compose.yml 中需要編寫的是 帶倉庫路徑的 image 而非 build。

我們的理想情況是,部署人員從GitLab獲取 docker-compose.yml 之後,執行 docker-compose up,便完成容器部署操作。(面向傻瓜的程式設計,一鍵部署吔)

測試

GitLab端

在GitLab的Webhooks頁面有一個Test按鈕,點一下就會觸發Webhooks,如果提示 Hook executed successfully: HTTP 200 那麼GitLab這端就工作正常。

Jenkins端

Jenkins接到Webhooks後就會開始打包。

  • 灰色表示正在準備
  • 藍色表示打包中或打包完成
  • 紅色表示打包出錯

點選進入後可通過左側的 Console Output 檢視控制檯的情況,比如 Shell 執行出錯在哪裡。

注意Jenkins會在Git檢查程式碼變更情況,如果GitLab那段沒有新提交的話,Jenkins會認為程式碼相同,沒有必要,會忽略這次打包。所以如果要進行測試的話,可能需要刪除上一次的打包結果。你可以在 Polling Log 中檢視相關資訊。

部署

部署人員從GitLab獲取 docker-compose.yml 並複製到目的機,執行 docker-compose up

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

test1-www-data:

    image: your_registry_domain:5000/private-docker-registry/test1-www-data

 

test1-php-fpm:

    image: your_registry_domain:5000/private-docker-registry/test1-php-fpm_modified

    expose:

        - 9000

    volumes_from:

        - test1-www-data

 

test1-nginx_app:

    image: your_registry_domain:5000/private-docker-registry/test1-nginx_modified

    expose:

        - 80

    links:

        - test1-php-fpm

    volumes_from:

        - test1-www-data

    ports:

        - "9080:80"

這回燒鍋爐的大叔都會給伺服器部署服務了,終於可以把工程師都開除了。

 

總結

本系列文章簡單講述了基於GitLab、Jenkins、Docker的面向Web開發的快速部署方案。文章中沒有涉及Docker 1.12以及swarn等相關概念。

Docker 1.11中,基於compose的DAB方案,是以在同一伺服器上部署所有服務映象的方式實現的,這是一種容易簡單理解的實現方式。在叢集中,每個節點都執行相同的容器組合。多個節點執行相同的容器群,容器群之間隔離沒有互動,而容器間緊密結合。

但這是一種可能浪費資源的模式。負責某些服務的容器可能負載非常大,但上下游容器則比較空閒。這種情況下,更希望多個節點都能分擔壓力大的服務,而對比較閒的服務則減少數量。

另外同一節點上可能會有多個專案,而專案間可能會有相同的無擦別服務,比如php-fpm。更希望複用這一個容器,而不是啟用多個相同容器只佔記憶體不幹活。

Docker 1.12中實現了這種需求,將分散式的概念從專案分解到容器應用。但目前仍是試驗中,所以沒有仔細研究,也不建議使用。阿里雲在Docker還在1.11版本期間,自己也開發了一個阿里版Docker,實現了1.12中的這個功能,但是限制太多,指令碼不相容,不建議使用。