1. 前言
1.1 為什麼要用 Docker ?
是否有這樣的場景,你搞了一個專案,在本地開發時需要搭建環境,放到線上時也需要搭建環境,到公司想暗戳戳玩一下要搭建環境,不搭還不行,因為你的環境依賴還挺多。這個時候如果有了Docker,只需要在機器上裝個Docker,放上寫好的Dickerfile,一行命令就自動完成這個事,方便又高效,豈不是很爽?
1.2 準備
接下來,本文介紹如何搭建一個PHP的開發環境,將用 zPhal-dockerfiles 做為例子,這是我為我的部落格系統準備的一套Dockerfile。
現在不管是windows,mac還是linux,docker都可以很好支援,包括Windows系統,在win10系統下Docker for Windows 其實還是挺不錯的,就是比較吃記憶體。
通過Docker命令列,我們可以做很多事情,拉取映象,執行容器,容器內執行命令等,但是現在,我們要用更加簡單粗暴的方式,編寫好dockerfiles檔案,然後通過docker-compose管理好這些檔案,簡化操作流程。
什麼是Dockerfile?
Dockerfile是由一系列命令和引數構成的指令碼,這些命令應用於拉取的基礎映象並最終建立一個新的映象,通過Dockerfile我們可以建立一個你需要的映象,裡面是包含了你要安裝的軟體,相當於是提前定製好要安裝的擴充,執行的命令等,然後一鍵執行,極大地簡化操作流程。
按照本文來搭建環境,你需要:
- 首先了解一下Docker以及Docker的一些基本操作,還有docker-compose是什麼。
- 然後需要安裝 Docker 和 docker-compose,我將使用 docker-compose 來管理我的 dockerfiles。
注意,編寫 dockerfile 是活的,不是死的,每個人寫出來的 dockerfile 都會不一樣,取決於你的需求。
Docker的官方文件非常清楚,雖然是英文,但是基本上什麼都有,有問題上文件翻是非常明智的:Docker Documentation
2. 開始編寫
接下來都是以 zPhal-dockerfiles 為例子,完整的可以點連結進去看,下面的只是片段。
2.1 預覽
首先,我們來看一下,我建立的這個dockerfile專案,我大概分成了下面的目錄(當然這個是自己定的,並不是要求這麼去排版你的檔案):
zPhal-dockerfiles
app/
index.php
phpinfo.php
data/
.gitignore
files/
mysql/
conf.d/
mysql-file.cnf
Dockerfile
nginx/
conf.d/
default.conf
zphal.conf
Dockerfile
nginx.conf
php/
pkg/
.gitignore
Dockerfile
php.ini
php-dev.ini
php-fpm.conf
redis/
Dockerfile
docker-compose.yml
logs/
.gitgnore
README.md
在這個專案裡,我用到PHP,MySQL,Nginx,Redis;以及Composer,Phalcon擴充等。
總的來說,我們做這件事有三個流程:編寫好各個軟體的dockerfile;編寫好配置檔案;通過docker-compose處理所有的dockerfile,包括將配置配置檔案扔進去 dockerfile 檔案將構建的映象中。
2.2 編寫 Dockerfile 檔案
2.2.1 PHP
下面是PHP的Dockerfile:
FROM php:7.2-fpm
MAINTAINER goozp "gzp@goozp.com"
# 設定時區
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 更新安裝依賴包和PHP核心擴充
RUN apt-get update && apt-get install -y
git
libfreetype6-dev
libjpeg62-turbo-dev
libpng-dev
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/
&& docker-php-ext-install -j$(nproc) gd
&& docker-php-ext-install zip
&& docker-php-ext-install pdo_mysql
&& docker-php-ext-install opcache
&& docker-php-ext-install mysqli
&& rm -r /var/lib/apt/lists/*
# 將預先下載好的擴充包從宿主機拷貝進去
COPY ./pkg/redis.tgz /home/redis.tgz
COPY ./pkg/cphalcon.tar.gz /home/cphalcon.tar.gz
# 安裝 PECL 擴充,這裡我們安裝的是Redis
RUN pecl install /home/redis.tgz && echo "extension=redis.so" > /usr/local/etc/php/conf.d/redis.ini
# 安裝第三方擴充,這裡是 Phalcon 擴充
RUN cd /home
&& tar -zxvf cphalcon.tar.gz
&& mv cphalcon-* phalcon
&& cd phalcon/build
&& ./install
&& echo "extension=phalcon.so" > /usr/local/etc/php/conf.d/phalcon.ini
# 安裝 Composer
ENV COMPOSER_HOME /root/composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
ENV PATH $COMPOSER_HOME/vendor/bin:$PATH
RUN rm -f /home/redis.tgz
rm -f /home/cphalcon.tar.gz
WORKDIR /data
# Write Permission
RUN usermod -u 1000 www-data
第一行定義了基礎映象,這裡我們用了PHP7.2的fpm版本,這裡第二行定義了一個維護者。
接下來定義了時區,在每一個dockerfile都定義了這一句,主要是為了使所有的容器的時間都與宿主機同步,其實我們可以在docker-composer.yml檔案中這麼定義:
services:
php-fpm:
volumes:
- /etc/localtime:/etc/localtime:ro
但是在非linux系統,比如Windows中執行時,我們不能取到/etc/localtime,為了更大相容所有平臺,我把時間同步寫到dockerfile中。
接下來安裝一些擴充,其實安裝擴充的過程類似於我們徒手在linux中安裝PHP擴充,值得一提的是composer。我將Composer直接安裝在了php-fpm的映象中,其實官方也提供了Composer的映象,拉取composer映象執行也可以達到目的,因為我們使用composer只是為了執行composer命令來管理我們的包,如果composer單獨是一個容器的話,我們在不用時,還可以將容器關掉;但是在這裡,我直接將composer裝進php-fpm映象中,主要是我的專案安裝了一些PHP擴充,在編寫composer.json檔案時,我定義了extension的依賴,這樣composer執行時會檢查環境是否安裝了這些依賴,所有如果我直接用composer映象的話,還需要把我用的擴充安裝到映象裡,就麻煩多了,所以我直接在php映象中就把這個事做了,其實沒什麼區別,取決於你怎麼用。
2.2.2 Nginx
下面是 Nginx 的 dockerfile:
FROM nginx:1.12
# set timezome
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
這個就簡單多了,我只設定了一個時間。因為我不需要安裝其它的東西,可以直接使用官方的映象。
當然,我們需要修改配置檔案,只要事先寫好配置檔案就行,最後在 docker-compose.yml 檔案中,將配置檔案扔進去,這個下面會講,包括PHP的配置檔案,MySQL的配置檔案,都是一樣的。
2.2.3 MySQL
下面是 MySQL 的 dockerfile:
FROM mysql:5.7
# set timezome
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
MySQL也沒有什麼特別之處,直接使用官方的映象。
2.2.4 Redis
下面是 Redis 的,也直接使用官方映象:
FROM redis:3.2
# set timezome
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
2.3 編寫配置檔案
如何處理配置檔案呢,我將配置檔案進行歸類,PHP的配置檔案放在PHP目錄下,Nginx的配置放在Nginx目錄下,至於要不要再新建一個子資料夾就看情況了,比如conf.d資料夾。
下面以Nginx配置檔案為例,首先nginx目錄是這樣的:
nginx/
conf.d/
default.conf
zphal.conf
Dockerfile
nginx.conf
除了nginx.conf外,還有一個子資料夾conf.d用來存放所有的域名配置檔案,在linux下搭建過php環境的應該都比較熟悉。這些配置檔案就是我們到時候要傳進去容器中的檔案,我們並不會在宿主機使用這些檔案。
所以需要注意的最重要一點就是,配置檔案中出現的路徑是容器內環境的路徑,而不是宿主機的路徑,每一個容器內都有一個執行環境,都是一臺微型小系統,這些路徑都是容器內的路徑。我們可以通過掛載與容器內通訊來同步檔案,在命令列啟動容器也需要掛載檔案路徑,而現在掛載這一步我們也用docker-compose來解決。
下面是一個配置檔案示例:
server {
listen 80 default;
index index.html index.htm;
server_name localhost docker;
root /data/www;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.html;
}
location ~ .php {
include fastcgi_params;
fastcgi_pass php-fpm:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /data/www/$fastcgi_script_name;
}
}
而root /data/www
中,/data/www
路徑,是到時候nginx容器的路徑,而不是當前在操作的宿主機的路徑,所以到時候我們要掛載web程式放的位置到這個路徑。
2.4 編寫 docker-compose.yml
在php,nginx等目錄的同級,我們建立一個 docker-compose.yml,我們在執行 docker-compose 相關命令時,會自動找到這個檔案,並根據裡面的內容來執行。
接上面 nginx 的例子,我們先談掛載,因為這是最重要的一步。在 docker-compose.yml 中,nginx 的部分:
nginx:
build: ./nginx
depends_on:
- php-fpm
links:
- php-fpm:php-fpm
volumes:
- ../app:/data/www:rw
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ../logs/nginx:/var/log/nginx
ports:
- "80:80"
- "8080:8080"
- "443:443"
restart: always
command: nginx -g `daemon off;`
有一個 volumes 引數,這裡就是我們要掛載的目錄的相關配置,第一條我們將../app
掛載到/data/www
之中,也是我們配置檔案中定義的預設監聽的root,而app目錄是我們宿主機中的一個目錄,通過這樣掛載我們可以直接將我們的專案檔案放到app中,docker會幫你傳輸到容器內的/data/www
目錄下。
其它的引數:
- build 定義了你的 dockerfile 在哪裡,如果沒有寫 dockerfile 可以不用 build,可以用 images引數定義官方映象,比如
image:mysql:5.7
; - depends_on表示將依賴其它映象,比如nginx依賴php-fpm,沒有它我nginx沒法玩;
- links定義連線,比如要連線到php-fpm容器,就是
php-fpm:php-fpm
,後面是別名; - ports表示埠對映,
80:80
表示將80埠對映到宿主機的80埠 - restart重啟,
restart: always
表示將自動重啟 - command是自動執行的命令
- ……
引數很多,更多的可以參考官方文件。
下面是一個完整的 docker-compose.yml 檔案:
version: `3.2`
services:
php-fpm:
build: ./php/
ports:
- "9000:9000"
links:
- mysql-db:mysql-db
- redis-db:redis-db
volumes:
- ../app:/data/www:rw
- ./php/php-dev.ini:/usr/local/etc/php/php.ini:ro
- ./php/php-fpm.conf:/usr/local/etc/php-fpm.conf:ro
- ../logs/php-fpm:/var/log/php-fpm:rw
restart: always
command: php-fpm
nginx:
build: ./nginx
depends_on:
- php-fpm
links:
- php-fpm:php-fpm
volumes:
- ../app:/data/www:rw
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ../logs/nginx:/var/log/nginx
ports:
- "80:80"
- "8080:8080"
- "443:443"
restart: always
command: nginx -g `daemon off;`
mysql-db:
build: ./mysql
ports:
- "3306:3306"
volumes:
- ../data/mysql:/var/lib/mysql:rw
- ../logs/mysql:/var/lib/mysql-logs:rw
- ./mysql/conf.d:/etc/mysql/conf.d:ro
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_DATABASE: zphaldb
MYSQL_USER: zphal
MYSQL_PASSWORD: zphal123
restart: always
command: "--character-set-server=utf8"
redis-db:
build: ./redis
ports:
- "6379:6379"
volumes:
- ../data/redis:/data
restart: always
3. 使用
這一套編寫下來,我們怎麼用呢?
3.1 使用搭建好的環境
-
首先,進入專案dockerfiles的目錄下,這裡是files目錄:
cd zPhal-dockerfiles/files wget https://pecl.php.net/get/redis-3.1.6.tgz -O php/pkg/redis.tgz wget https://codeload.github.com/phalcon/cphalcon/tar.gz/v3.3.1 -O php/pkg/cphalcon.tar.gz
然後下載我們會用到的PHP擴充包。
-
執行命令:
docker-compose up
docker會自動通過編寫好的docker-compose.yml內容構建映象,並且啟動容器。
如果沒問題,下次啟動時可以以守護模式啟用,所有容器將後臺執行:docker-compose up -d
-
關閉容器
可以這樣關閉容器並刪除服務:docker-compose down
使用 docker-compose 基本上就這麼簡單,用 stop,start 等這些命令來操縱容器服務。而更多的工作是在於編寫 dockerfile 和 docker-compose.yml 檔案。
3.2 使用 Composer
當我們要使用composer時怎麼做呢? 我們已經在php-fpm裡安裝了composer。
用 docker-compose 進行操作:
docker-compose run --rm -w /data/www/zPhal php-fpm composer update
-w /data/www/zPhal
為在php-fpm的工作區域,zPhal專案也是掛載在裡面,所有我們可以直接在容器裡執行composer。
或者進入宿主機app目錄下用docker命令:
cd zPhal-dockerfiles/app
docker run -it --rm -v `pwd`:/data/www/ -w /data/www/zPhal files_php-fpm composer update
4. 注意事項
- 注意掛載路徑
- 構建失敗時,注意容器內是否報錯
- 加速映象。如果過程下載映象很慢,可以使用國內的加速映象服務,比如阿里雲,Daocloud。
本文首發在我的部落格:徒手用 Docker 構建自己的 PHP 開發環境