環境部署一直是一個很大的問題,不管是開發環境仍是生產環境,可是 Docker 將開發環境和生產環境以輕量級方式打包,提供了一致的環境。極大的提高了開發部署一致性。固然,實際狀況並無這麼簡單,由於生產環境和開發環境的配置是徹底不一樣的,好比日誌等的問題都須要單獨配置,可是至少比之前更加簡單方便了,這裏以 PHP 開發做爲例子講解 Docker 如何佈置開發環境。php
通常來講,一個 PHP 項目會須要如下工具:html
Web 服務器: Nginx/Tenginemysql
Web 程序: PHP-FPMlinux
數據庫: MySQL/PostgreSQLnginx
緩存服務: Redis/Memcachegit
這是最簡單的架構方式,在 Docker 發展早期,Docker 被大量的濫用,好比,一個鏡像內啓動多服務,日誌收集依舊是按照 Syslog 或者別的老方式,鏡像容量很是龐大,基礎鏡像就能達到 80M,這和 Docker 當初提出的思想徹底南轅北轍了,而 Alpine Linux 發行版做爲一個輕量級 Linux 環境,就很是適合做爲 Docker 基礎鏡像,Docker 官方也推薦使用 Alpine 而不是 Debian 做爲基礎鏡像,將來大量的現有官方鏡像也將會遷移到 Alpine 上。本文全部鏡像都將以 Alpine 做爲基礎鏡像。github
這部分筆者已經在另外一篇文章 Docker 容器的 Nginx 實踐中講解了 Tengine 的 Docker 實踐,而且給出了 Dockerfile,因爲比較偏好 Tengine,並且官方已經給出了 Nginx 的 alpine 鏡像,因此這裏就用 Tengine。筆者已經將鏡像上傳到官方 DockerHub,能夠經過sql
docker pull chasontang/tengine:2.1.2_f
獲取鏡像,具體請看 Dockerfile。docker
Docker 官方已經提供了 PHP 的 7.0.7-fpm-alpine 鏡像,Dockerfile 以下:數據庫
FROM alpine:3.4 # persistent / runtime deps ENV PHPIZE_DEPS \ autoconf \ file \ g++ \ gcc \ libc-dev \ make \ pkgconf \ re2c RUN apk add --no-cache --virtual .persistent-deps \ ca-certificates \ curl # ensure www-data user exists RUN set -x \ && addgroup -g 82 -S www-data \ && adduser -u 82 -D -S -G www-data www-data # 82 is the standard uid/gid for "www-data" in Alpine # http://git.alpinelinux.org/cgit/aports/tree/main/apache2/apache2.pre-install?h=v3.3.2 # http://git.alpinelinux.org/cgit/aports/tree/main/lighttpd/lighttpd.pre-install?h=v3.3.2 # http://git.alpinelinux.org/cgit/aports/tree/main/nginx-initscripts/nginx-initscripts.pre-install?h=v3.3.2 ENV PHP_INI_DIR /usr/local/etc/php RUN mkdir -p $PHP_INI_DIR/conf.d ##<autogenerated>## ENV PHP_EXTRA_CONFIGURE_ARGS --enable-fpm --with-fpm-user=www-data --with-fpm-group=www-data ##</autogenerated>## ENV GPG_KEYS 1A4E8B7277C42E53DBA9C7B9BCAA30EA9C0D5763 ENV PHP_VERSION 7.0.7 ENV PHP_FILENAME php-7.0.7.tar.xz ENV PHP_SHA256 9cc64a7459242c79c10e79d74feaf5bae3541f604966ceb600c3d2e8f5fe4794 RUN set -xe \ && apk add --no-cache --virtual .build-deps \ $PHPIZE_DEPS \ curl-dev \ gnupg \ libedit-dev \ libxml2-dev \ openssl-dev \ sqlite-dev \ && curl -fSL "http://php.net/get/$PHP_FILENAME/from/this/mirror" -o "$PHP_FILENAME" \ && echo "$PHP_SHA256 *$PHP_FILENAME" | sha256sum -c - \ && curl -fSL "http://php.net/get/$PHP_FILENAME.asc/from/this/mirror" -o "$PHP_FILENAME.asc" \ && export GNUPGHOME="$(mktemp -d)" \ && for key in $GPG_KEYS; do \ gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ done \ && gpg --batch --verify "$PHP_FILENAME.asc" "$PHP_FILENAME" \ && rm -r "$GNUPGHOME" "$PHP_FILENAME.asc" \ && mkdir -p /usr/src \ && tar -Jxf "$PHP_FILENAME" -C /usr/src \ && mv "/usr/src/php-$PHP_VERSION" /usr/src/php \ && rm "$PHP_FILENAME" \ && cd /usr/src/php \ && ./configure \ --with-config-file-path="$PHP_INI_DIR" \ --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \ $PHP_EXTRA_CONFIGURE_ARGS \ --disable-cgi \ # --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself) --enable-mysqlnd \ # --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195) --enable-mbstring \ --with-curl \ --with-libedit \ --with-openssl \ --with-zlib \ && make -j"$(getconf _NPROCESSORS_ONLN)" \ && make install \ && { find /usr/local/bin /usr/local/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; } \ && make clean \ && runDeps="$( \ scanelf --needed --nobanner --recursive /usr/local \ | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \ | sort -u \ | xargs -r apk info --installed \ | sort -u \ )" \ && apk add --no-cache --virtual .php-rundeps $runDeps \ && apk del .build-deps COPY docker-php-ext-* /usr/local/bin/ ##<autogenerated>## WORKDIR /var/www/html RUN set -ex \ && cd /usr/local/etc \ && if [ -d php-fpm.d ]; then \ # for some reason, upstream's php-fpm.conf.default has "include=NONE/etc/php-fpm.d/*.conf" sed 's!=NONE/!=!g' php-fpm.conf.default | tee php-fpm.conf > /dev/null; \ cp php-fpm.d/www.conf.default php-fpm.d/www.conf; \ else \ # PHP 5.x don't use "include=" by default, so we'll create our own simple config that mimics PHP 7+ for consistency mkdir php-fpm.d; \ cp php-fpm.conf.default php-fpm.d/www.conf; \ { \ echo '[global]'; \ echo 'include=etc/php-fpm.d/*.conf'; \ } | tee php-fpm.conf; \ fi \ && { \ echo '[global]'; \ echo 'error_log = /proc/self/fd/2'; \ echo; \ echo '[www]'; \ echo '; if we send this to /proc/self/fd/1, it never appears'; \ echo 'access.log = /proc/self/fd/2'; \ echo; \ echo 'clear_env = no'; \ echo; \ echo '; Ensure worker stdout and stderr are sent to the main error log.'; \ echo 'catch_workers_output = yes'; \ } | tee php-fpm.d/docker.conf \ && { \ echo '[global]'; \ echo 'daemonize = no'; \ echo; \ echo '[www]'; \ echo 'listen = [::]:9000'; \ } | tee php-fpm.d/zz-docker.conf EXPOSE 9000 CMD ["php-fpm"] ##</autogenerated>##
首先,鏡像繼承自 alpine:3.4
鏡像,使用 apk 命令安裝 php 最小依賴,同時添加 www-data 做爲 php-fpm 的運行用戶,將 php 的配置文件指定到 /usr/local/etc/php
,而後就是下載 php-src,編譯安裝,這裏能夠參考筆者以前寫的 php 編譯安裝文章。參數都中規中矩。安裝目錄被指定到 /usr/local
,而後使用 scanelf
得到所依賴的運行庫列表,而且將其餘安裝包刪除。將 docker-php-ext-configure
、docker-php-ext-enable
、docker-php-ext-install
複製到容器中,這三個文件用於後續安裝擴展。而後將 php-fpm.conf 複製到配置目錄,將 error_log 和 access_log 指定到終端標準輸出,daemonize = no
表示不以服務進程運行。EXPOSE 9000 端口用於和其餘容器通訊,而後就是 CMD ["php-fpm"]
運行 php-fpm。並且工做目錄被指定到 /var/www/html
。
已經搞定了基礎鏡像,咱們就可使用基礎鏡像來配置容器,可是經過手工 docker
命令啓動容器會很是麻煩。可是萬幸的是官方已經提供了 docker-compose
命令來編排容器,只須要寫一個 docker-compose.yaml
文件就行,具體能夠參考官方文檔。
version: '2' services: php-fpm: image: php:7.0.7-fpm-alpine volumes: - "./src:/var/www/html" restart: always tengine: depends_on: - php-fpm links: - php-fpm image: chasontang/tengine:2.1.2_f volumes: - "./nginx.vh.default.conf:/etc/nginx/conf.d/default.conf" ports: - "80:80" restart: always
很是容易理解,這裏定義了兩個服務,php-fpm 依賴 php:7.0.7-fpm-alpine 鏡像,而且將 src 文件夾映射爲 /var/www/html 文件夾,tengine 服務依賴 php-fpm 服務,而且 link php-fpm 服務,這樣就能經過網絡與 php-fpm 容器通訊,tengine 服務基於 chasontang/tengine:2.1.2_f 鏡像,並將 nginx.vh.default.conf 文件映射爲 /etc/nginx/conf.d/default.conf 文件。而後來看 nginx.vh.default.conf
server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} location ~ [^/]\.php(/|$) { fastcgi_split_path_info ^(.+?\.php)(/.*)$; fastcgi_pass php-fpm:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; include fastcgi_params; } # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }
tengine 鏡像實際上使用兩個配置文件,一個是 /etc/nginx/nginx.conf,還有就是 /etc/nginx/conf.d/ 目錄下的全部文件,由於 /etc/nginx/nginx.conf 中使用 include /etc/nginx/conf.d/*.conf;
包含了這個目錄,也就是說,能夠不須要去管 nginx 其餘配置,只須要用本身的 nginx 虛擬主機配置替代默認的虛擬主機配置,或者說增長虛擬主機配置就好了。
從上面能夠看到,default.conf 文件定義了一個 location 匹配包含 .php
的 URL,而後將其分割出 PATH_INFO 參數,將這些變量傳遞給 php-fpm:9000 的 php-fpm 服務。
這裏須要注意的是,因爲 Nginx 和 PHP-FPM 不在同一臺主機上,因此 Nginx 只作靜態文件處理和路由轉發,實際的 PHP 文件執行時在 PHP-FPM 容器中發生的。因此 SCRIPT_FILENAME 變量必需要使用 PHP-FPM 容器中的目錄,因此這裏使用硬編碼指定。固然,也可讓兩個容器共享同一個數據卷,可是筆者認爲,這只是爲了方便容器編排,其餘徹底沒有好處。