环境部署一直是一个很大的问题,不管是开发环境仍是生产环境,可是 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 容器中的目录,因此这里使用硬编码指定。固然,也可让两个容器共享同一个数据卷,可是笔者认为,这只是为了方便容器编排,其余彻底没有好处。