使用GitLab、Jenkins、Docker建立快速持續化整合交付部署方案(二)
上一文中我們完成了基礎環境的安裝。
本文將要學習 Docker Image 的自定義,及 使用Docker Compose進行環境部署的方法。
文章索引
- GitLab、Jenkins、Docker 初始環境安裝
- 製作 Docker映象 及 Docker Compose 的使用(本文)
- 使用 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映象進行了修改,新增和修改了配置檔案。對於應可以隨時替換的標準服務來講這並不完美。於是有一種思路,就是把配置檔案像程式碼一樣也做成資料卷容器。這樣做未嘗不可,但難度會很大。因為掛在資料卷的話,會導致配置檔案下整個目錄覆蓋,可能會影響到我們並不想更改的預設引數。