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

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

上一文中我們完成了基礎環境的安裝。

本文將要學習 Docker Image 的自定義,及 使用Docker Compose進行環境部署的方法。

 

文章索引

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

 

提示:本文是基於 Docker 1.11 編寫的,從 2016 年 4月 至今,Docker 已經改變了許多。基本用法沒有改變,只不過增加了很多新特性,並移除了部分不再使用的標籤。至2018年為止,本文依舊適用於入門學習,但部分新特性的改變(比如 aufs->overlays ),以及部分標籤的移除(比如 MAINTAINER->LABEL ),請參照 Docker 官方文件為準。

Docker一些概念

國內各個廠家對Docker概念都翻譯不一樣,我這裡先寫一下我自己的概念理解

  • 映象(image)映象
  • 容器(container):由單一服務構成的針對最基本功能實現的 服務(service)(比如Nginx)
  • 專案(project):多個服務構成的 應用(application)(比如由 Nginx + PHP + Mysql + WordPress組成的部落格)

單個容器只能提供非常基本的服務,比如單純的Mysql資料庫服務,單純的Redis快取服務。

需要由多個容器協同工作,才能組成一個複雜且強大的專案,實現產品功能。

這裡鄙視以下各個廠家為圈地,拉攏使用者,使用了非常多的混淆概念,比如引入微服務(microservice)。

什麼是他媽的微服務,微博微信朋友圈玩多了吧。不論按照Windows還是按照Linux來講,服務(service)指的都是由單一應用程式完成的最基本的功能實現。還他媽的微,是不是還要把HTTP服務拆成 HyperText Transfer Protocol 之後分三段去實現啊。

維基百科上有針對微服務的定義:微服務 (Microservices) 是一種軟體架構風格 (Software Architecture Style),它是以專注於單一責任與功能的小型功能區塊 (Small Building Blocks) 為基礎,利用模組化的方式組合出複雜的大型應用程式,各功能區塊使用與語言無關 (Language-Independent/Language agnostic) 的 API 集相互通訊。微服務是由以單一應用程式構成的小服務,自己擁有自己的行程與輕量化處理,服務依業務功能設計,以全自動的方式部署,與其他服務使用 HTTP API 通訊。同時服務會使用最小的規模的集中管理 (例如 Docker) 能力,服務可以用不同的程式語言與資料庫等元件實作。

很明顯這裡的微服務指的與我們所說的服務是相同的,只不過更強調其單一性,即甚至要把Apache+PHP這種組合也剝離開(傳統模式下,PHP是以模組形式與Apache共同工作的,而Nginx與PHP是靠PHP-FPM共同工作,後者更顆粒化)

然而國內不少網站把微服務當做微信、微博一個級別的概念對待,以為微服務就是產品功能級的服務,訂單微服務,通知微服務,狗屎微服務。

 

使用Docker實現一個專案

我們在這裡,要基於nginx、php-fpm,實現一個PHP應用。這個專案將作為一個基礎學習專案,對接下來的Jenkins和最終部署都有很大關係。

將要做的事

首先先給大家看一下我的目錄結構,如果你對我寫的整個結構都很一目瞭然的話,證明你對這一部分都很熟悉,你可以跳過這一章節了。

 

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

[[email protected] docker]# tree

.

├── docker-compose.yml

├── getInDocker.sh

├── jenkins_modified

│   ├── Dockerfile

│   └── etc

│       ├── localtime

│       └── timezone

├── nginx_modified

│   ├── conf

│   │   ├── conf.d

│   │   │   └── default.conf

│   │   ├── fastcgi.conf

│   │   ├── mime.types

│   │   └── nginx.conf

│   └── Dockerfile

├── php-fpm_modified

│   ├── conf

│   │   └── www.conf

│   └── Dockerfile

├── project-build.sh

└── www-data

    ├── Dockerfile

    └── web

        ├── a.txt

        └── index.php

我們需要準備nginx和php-fpm的Dockerfile,從Docker Hub上獲取。編寫一個靜態檔案和一個php檔案,併為自己的程式碼製作一個Dockerfile用於生成資料卷容器。

沒人說過容器裡不能放純程式碼的。純程式碼,滿足最基本顆粒化要求(儲存),無狀態或狀態不重要(隨用隨建,不用即刪,臨時檔案不重要)。

至於為什麼要專門為php等程式碼建立資料卷容器,而不是直接使用git或其他程式碼管理工具同步到硬碟上的原因,將在後面介紹。

nginx

Let’s make a nginx_modified

確認配置方法

首先我們要知道nginx的映象內部到底是怎樣的。有兩種方法:

第一種,生成一個預設容器,並進入容器內部觀察結構。這是一種不推薦的做法,因為除非你對整個服務有及其充分的瞭解,否則你永遠摸不清它裡面到底是怎樣的。這就相當於你作為一個人類活了一輩子,除非你是專業的醫生,否則你是沒辦法拿手術刀給別人做手術的。

第二種,根據Docker Hub上的描述來修改相應的配置。這是推薦的辦法,多數專案都會對自己的docker專案有比較詳細的描述。而且多數專案可能會針對Docker做一些調整,他們的配置與傳統的編譯安裝配置也是不同的。使用推薦的辦法修改配置是最安全的辦法。

除非他們自身的描述寫的相當渣,或者完全就沒有描述,否則不要使用第一種方法。

Dockerfile

Dockerfile是用於構建映象的檔案,其內包含了多指令,每一條指令構建一層。

這是為自定義nginx服務而編寫的dockerfile

 

1

2

3

4

5

6

7

8

9

10

11

FROM nginx:1.10.2

 

MAINTAINER catscarlet

 

ENV NGINX_VERSION 1.10.2-1~jessie

 

COPY conf/ /etc/nginx

 

EXPOSE 80 443

 

CMD ["nginx", "-g", "daemon off;"]

逐條解釋下:

FROM:基於哪個版本的映象生成新的映象,我們這裡選擇stable版的1.10.2。如果這裡不指定版本號,docker會預設使用latest版本(2016年11月24日為1.11.5)

MAINTAINER:這個映象的作者名

ENV:指定環境變數,這裡聲明瞭nginx的版本

COPY:將本地conf/中的檔案複製到映象的/etc/nginx目錄下。使用這個辦法我們替換了nginx的配置檔案。包括預設網站、對php檔案的處理等。

EXPOSE:可對映的埠

CMD:容器啟動時要執行的命令

VOLUME:定義匿名卷

nginx的default.conf

 

1

2

3

4

5

6

7

8

9

10

11

12

server {

    listen       80;

    root         /var/www/html;

    index        index.php index.html;

 

    charset utf-8;

 

    location ~ \.php$ {

        fastcgi_pass   php-fpm:9000;

        include        fastcgi.conf;

    }

}

生成映象:

 

1

docker build -t 'nginx_app' nginx_modified/

測試啟動:

 

1

# docker run -d -p 10080:80 nginx_app

因為我們的裝置上還跑著GitLab和Jenkins呢,所以要避開許多埠,這裡選擇使用10080。

使用瀏覽器開啟本機的10080埠,這個時候/var/www/html並沒有對映到任何資料卷,所以看到的應該是nginx的預設歡迎介面。

底層執行docker ps檢視這個容器,使用docker down {container_id}停止這個容器,使用docker rm {container_id}刪除這個容器。你也可以在容器未停止時,直接使用docker rm -f {container_id}強制刪除這個容器。

提示:你可以使用docker rm $(docker ps -a -q)刪除所有已停止的容器

底層執行docker image檢視生成的映象資訊,使用docker rmi {image_id}刪除這個映象。

提示:你可以使用docker rmi $(docker images -q -f "dangling=true")刪除所有無tag的映象。

PHP-FPM

接下來我們製作PHP-FPM的映象。

其實完全沒有必要,PHP-FPM不僅是真正的無狀態的,而且幾乎沒有修改配置的必要。

Dockerfile,我們只指定以下版本就好了

 

1

2

3

FROM php:7.0.13-fpm

 

MAINTAINER catscarlet

預設情況下,會繼承原映象的埠對映9000。

程式碼映象

這裡再次強調一下,Docker的映象是分層檔案系統,我們的自定義nginx映象並不會佔用一個整個nginx映象所需的硬碟空間,只佔用我們新增的配置檔案的空間大小,而所需的nginx則繼續使用FROM中的映象。

那麼對於沒有基礎依賴的程式碼映象,應該FROM什麼映象呢?

網上有不少辦法是基於一個最小化的Linux作業系統來做,比如Busybox。Bullshit!

注意一下現在所有的Docker映象,生成他們的Dockerfile都是有FROM的,而且一般都是 debian:jessie。這意味著多數映象都是依賴debian:jessie的,debian:jessie已經存在於我們的本地映象庫中。我們也可以繼續基於debian:jessie製作映象。

Dockerfile

 

1

2

3

4

5

6

7

FROM debian:jessie

 

MAINTAINER catscarlet

 

COPY web/ /var/www/html/

 

VOLUME '/var/www/html/'

web/a.txt

 

1

This is a text file.

web/index.php

 

1

2

3

4

5

6

7

8

9

10

<?php

 

ini_set('date.timezone', 'Asia/Shanghai');

header('Content-Type:text/plain; charset=utf-8');

 

$hostname = file_get_contents('/etc/hostname');

$date = date('r', time());

$rst = 'This is Server: '.$hostname."\n".'Now is '.$date;

 

echo $rst;

Docker Compose

Docker的每個容器只完成最基本的服務,而想要實現一個產品功能,往往需要多個基本服務共同完成。對於每個服務來講他們是無依賴的,但對於專案來講,他們就是需要緊密結合的。

Docker Compose就提供了這樣一個功能,允許定義一組容器,組成一個專案。

安裝

執行命令如:

 

1

2

curl -L https://github.com/docker/compose/releases/download/1.9.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

官方也提供pip安裝方式,但依賴和侷限性比較多,在不同的系統上有不同的問題,不建議用這種方式安裝

相關文件:https://docs.docker.com/compose/

配置

Docker Compose使用YAML格式的配置檔案:docker-compose.yml

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

www-data:

    build: ./www-data

 

php-fpm:

    build: ./php-fpm_modified

    expose:

        - 9000

    volumes_from:

        - www-data

 

nginx_app:

    build: ./nginx_modified

    expose:

        - 80

    links:

        - php-fpm

    volumes_from:

        - www-data

    ports:

        - "9080:80"

主要使用方法:直接在docker-compose.yml的路徑下,執行以下命令:

  • docker-compose build 新建(或重建)專案中所有需要build的映象。此步非必須。
  • docker-compose up 新建(或重建)專案中所有需要build的映象,並啟動對應專案。這是一個前臺操作,在所有容器內所有程序退出之前不會退出,使用Ctrl-C會停止容器,但不會刪除容器。使用 -d 引數使專案在後臺啟動。
  • docker-compose start 啟動一個已經存在的專案。
  • docker-compose stop 停止一個已經執行的專案。
  • docker-compose down 停止一個已經執行的專案,並刪除所有相關聯容器。

啟動專案

在docker-compose.yml的路徑下執行docker-compose up -d。

現在訪問你的伺服器http://your-server:9080/a.txt應該可以開啟這個靜態檔案 a.txt,http://your-server:9080/index.php可以開啟執行後的php結果了。

一些問題

程式碼位置問題

有很多開發者會把要執行的程式碼與服務打包到同一個映象中。

這些開發者一般多為JAVA開發者。對於JAVA開發者來講,他們的專案結構可能是這樣的:TOMCAT + JAVA包 + 資料儲存。對於 TOMCAT + JAVA包 這種情況便是強依賴環境,同樣符合無狀態和最小顆粒的特性,或許並沒有問題。

然而對於其他語言的開發者,專案結構一般為 HTTP服務 + 語言解析器 + 程式碼 + 資料儲存。

以PHP開發者舉例,可以理解為 NGINX + PHP-FPM + 程式碼 + Mysql。而程式碼中不僅包含php程式碼,也會包含html、js、css等程式碼。

將程式碼單獨的放入 NGINX 映象或者 PHP-FPM 映象,都會導致執行過程中,容器之間無法獲取程式碼,解析時出現404錯誤。

另外在日常開發時,經常變動的也只有程式碼部分,其他映象一般多為萬年不變的。將程式碼獨立到單獨容器中也會減小部署壓力。

(目前還沒見到過PHP開發者使用Docker和做持續化部署的)

配置檔案問題

我們對標準的nginx映象進行了修改,新增和修改了配置檔案。對於應可以隨時替換的標準服務來講這並不完美。於是有一種思路,就是把配置檔案像程式碼一樣也做成資料卷容器。這樣做未嘗不可,但難度會很大。因為掛在資料卷的話,會導致配置檔案下整個目錄覆蓋,可能會影響到我們並不想更改的預設引數。