用 Docker 构建自己的 PHP 开发环境

博客分类: software 阅读次数: comments

用 Docker 构建自己的 PHP 开发环境

前言

为什么要用 Docker ?

是否有这样的场景,你搞了一个项目,在本地开发时需要搭建环境,放到线上时也需要搭建环境,到公司想暗戳戳玩一下要搭建环境,不搭还不行,因为你的环境依赖还挺多。这个时候如果有了Docker,只需要在机器上装个Docker,放上写好的Dickerfile,一行命令就自动完成这个事,方便又高效,岂不是很爽?

准备

接下来,本文介绍如何搭建一个PHP的开发环境,将用 php开发环境 做为例子。
现在不管是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

开始编写

接下来都是以 php开发环境 为例子,完整的可以点链接进去看,下面的只是片段。

预览

首先,我们来看一下,我创建的这个dockerfile项目,我大概分成了下面的目录(当然这个是自己定的,并不是要求这么去排版你的文件):

lnmp
	data/   # 数据文件夹
	logs/   # 日志文件夹
	files/
	    mysql/
	        Dockerfile
	        my.cnf
	    neo4j/
	        conf/
	            neo4j.conf
	        Dockerfile
	    openresty/ # 基于 NGINX 和 LuaJIT 的 Web 平台
	        conf.d/
	            default.conf
	        Dockerfile
	        nginx.conf
	    php72/
		    php-fpm.d/
			    docker.conf
			    www.conf
	        pkg/
	        Dockerfile
	        php.ini
	        php-dev.ini
	        php-fpm.conf
	    redis/
	        Dockerfile	
	        redis.conf
	    docker-compose.yml  # docker配置
	.env        # 配置文件
	Readme.md   # 帮助文档

在这个项目里,我用到PHP,MySQL,Nginx,Redis以及Neo4j。

总的来说,我们做这件事有三个流程:编写好各个软件的dockerfile;编写好配置文件;通过docker-compose处理所有的dockerfile,包括将配置配置文件扔进去 dockerfile 文件将构建的镜像中。

编写 Dockerfile 文件

PHP

下面是PHP的Dockerfile:

ARG LNMP_PHP72_VERSION

FROM php:$LNMP_PHP72_VERSION
MAINTAINER canren "bestsxf@gmail.com"

# 设置时区
ENV TZ=Asia/shanghai
RUN ln -snf /usr/share/zone/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 设置更新源,自带源不好用我换成了阿里的源
COPY ./apt/sources.list /etc/apt/sources.list

# 更新安装依赖包和php核心扩展
RUN apt-get update
RUN apt-get install -y git libfreetype6-dev libjpeg62-turbo-dev libpng-dev libmemcached-dev zlib1g zlib1g-dev
RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/
RUN docker-php-ext-install -j$(nproc) gd zip pdo_mysql opcache mysqli bcmath

# 安装进程管理工具supervisord
RUN apt-get install -y supervisor

# 安装redis,igbinary,swoole扩展
RUN pecl install redis && echo "extension=redis.so" > /usr/local/etc/php/conf.d/redis.ini
RUN pecl install igbinary && echo "extension=igbinary.so" > /usr/local/etc/php/conf.d/igbinary.ini
RUN pecl install swoole && echo "extension=swoole.so" > /usr/local/etc/php/conf.d/swoole.ini

# 安装swoole_loader解密插件
# RUN curl https://business.swoole.com/static/loader2.0.0/swoole_loader72.so > /usr/local/lib/php/extensions/no-debug-non-zts-20170718/swoole_loader72.so \
#     && { echo "extension=swoole_loader72.so"; \
#          echo "swoole_license_files=/data/www/xxx/license.zl"; } > /usr/local/etc/php/conf.d/swoole_loader.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 -r /var/lib/apt/lists/*

# 设置工作路径
WORKDIR /data/www

# 设置权限
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镜像中就把这个事做了,其实没什么区别,取决于你怎么用。

Openresty

下面是 Openresty 的 dockerfile,可直接使用官方镜像:

ARG LNMP_OPENRESTY_VERSION

FROM openresty/openresty:$LNMP_OPENRESTY_VERSION
MAINTAINER canren "bestsxf@gmail.com"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

WORKDIR /data/www

这个就简单多了,我只设置了一个时间。因为我不需要安装其它的东西,可以直接使用官方的镜像。
当然,我们需要修改配置文件,只要事先写好配置文件就行,最后在 docker-compose.yml 文件中,将配置文件扔进去,这个下面会讲,包括PHP的配置文件,MySQL的配置文件,都是一样的。

Mysql

下面是 MySQL 的 dockerfile,可直接使用官方镜像:

ARG LNMP_MYSQL_VERSION

FROM mysql:$LNMP_MYSQL_VERSION
MAINTAINER canren "bestsxf@gmail.com"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

MySQL也没有什么特别之处,直接使用官方的镜像。
如果本地机器要访问MYSQL的话,记得把mysql配置文件 lnmp/files/mysql/my.cnfbind-address = 127.0.0.1 修改为bind-address = 0.0.0.0

Redis

下面是 Redis 的 dockerfile,可直接使用官方镜像:

ARG REDIS_VERSION

FROM redis:$REDIS_VERSION
MAINTAINER canren "bestsxf@gmail.com"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

Neo4j

下面是 Neo4j 的 dockerfile,可直接使用官方镜像:

ARG NEO4J_VERSION

FROM neo4j:$NEO4J_VERSION
MAINTAINER canren "bestsxf@gmail.com"

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

编写配置文件

如何处理配置文件呢,我将配置文件进行归类,PHP的配置文件放在PHP目录下,Nginx的配置放在Nginx目录下,至于要不要再新建一个子文件夹就看情况了,比如conf.d文件夹。
下面以PHP配置文件为例,首先PHP目录是这样的:

php72/
    apt/
        sources.list        # 更新依赖包源地址
    php-fpm.d/              # php-fpm配置文件集
        docker.conf
        www.conf
    pkg/                    # 扩展包文件夹,一些需要通过make编译的扩展包
    worker/                 # job文件
        crontabs/
            root            # 定时任务的配置文件
        supervisor/         # 进程管理工具的文件夹
            conf.d/         # 进程配置文件夹,比如队列的守护进程
                .gitignore
        supervisord.conf
    Dockerfile
    php.ini                 # php配置文件
    php-dev.ini
    php-fpm.conf            # php-fpm配置文件

除了php-fom.conf外,还有一个若干子文件夹用来存放配置文件,在linux下搭建过php环境的应该都比较熟悉。这些配置文件就是我们到时候要传进去容器中的文件,我们并不会在宿主机使用这些文件。
所以需要注意的最重要一点就是,配置文件中出现的路径是容器内环境的路径,而不是宿主机的路径,每一个容器内都有一个运行环境,都是一台微型小系统,这些路径都是容器内的路径。我们可以通过挂载与容器内通讯来同步文件,在命令行启动容器也需要挂载文件路径,而现在挂载这一步我们也用docker-compose来解决。

编写 docker-compose.yml

在php,nginx等目录的同级,我们创建一个 docker-compose.yml,我们在执行 docker-compose 相关命令时,会自动找到这个文件,并根据里面的内容来执行。
接上面 php 的例子,我们先谈挂载,因为这是最重要的一步。在 docker-compose.yml 中,php 的部分:

  php-fpm72:
    build:
      context: php72/
      args:
        LNMP_PHP72_VERSION: $LNMP_PHP72_VERSION # 传递版本参数
    container_name: "canren-lnmp-php72"
    # privileged: true # 获取宿主机root权限
    ports:
      - "9000:9000" # 开启端口
    networks:
      - "lnmp" # 使用局域网
    volumes:
      - $WORKSPACES
      - ./php72/php.ini:/usr/local/etc/php/php.ini:rw
      - ./php72/php-fpm.conf:/usr/local/etc/php-fpm.conf:rw
      - ./php72/php-fpm.d:/usr/local/etc/php-fpm.d:cached
      - ./php72/worker/supervisor:/etc/supervisor:cached
      - ../logs/php-fpm72:/var/log/php-fpm:cached
    restart: always
    command: 
      - /bin/sh
      - -c
      - |
        /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
        /usr/local/sbin/php-fpm

有一个 volumes 参数,这里就是我们要挂载的目录的相关配置,第一条我们将../app挂载到/data/www之中,也是我们配置文件中定义的默认监听的root,而app目录是我们宿主机中的一个目录,通过这样挂载我们可以直接将我们的项目文件放到app中,docker会帮你传输到容器内的/data/www目录下。
其它的参数:

参数很多,更多的可以参考官方文档。
下面是一个完整的 docker-compose.yml 文件:

    version: '3.4'
    services:

    php-fpm72:
        build:
        context: php72/
        args:
            LNMP_PHP72_VERSION: $LNMP_PHP72_VERSION
        container_name: "canren-lnmp-php72"
        # privileged: true
        ports:
        - "9000:9000"
        networks:
        - "lnmp"
        volumes:
        - $WORKSPACES
        - ./php72/php.ini:/usr/local/etc/php/php.ini:rw
        - ./php72/php-fpm.conf:/usr/local/etc/php-fpm.conf:rw
        - ./php72/php-fpm.d:/usr/local/etc/php-fpm.d:cached
        - ./php72/worker/supervisor:/etc/supervisor:cached
        - ../logs/php-fpm72:/var/log/php-fpm:cached
        restart: always
        command: 
        - /bin/sh
        - -c
        - |
            /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
            /usr/local/sbin/php-fpm

    openresty:
        build:
        context: openresty/
        args:
            LNMP_OPENRESTY_VERSION: $LNMP_OPENRESTY_VERSION
        container_name: "canren-lnmp-openresty"
        # privileged: true
        ports:
        - "80:80"
        networks:
        - "lnmp"
        links:
        - php-fpm72
        depends_on:
        - php-fpm72
        volumes:
        - ./openresty/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:rw
        - ./openresty/conf.d:/etc/nginx/conf.d:cached
        - ../logs/openresty:/usr/local/openresty/nginx/logs:cached
        restart: always

    mysql-db:
        build:
        context: mysql/
        args:
            LNMP_MYSQL_VERSION: $LNMP_MYSQL_VERSION
        container_name: "canren-lnmp-mysql"
        ports:
        - "3306:3306"
        networks:
        - "lnmp"
        volumes:
        - ./mysql/my.cnf:/etc/my.cnf:rw
        - ../data/mysql:/var/lib/mysql:cached
        - ../logs/mysql:/var/lib/mysql-logs:cached
        environment:
        MYSQL_ROOT_PASSWORD: 123456
        MYSQL_DATABASE: db_test
        MYSQL_USER: test
        MYSQL_PASSWORD: test
        restart: always
        command: "--character-set-server=utf8"

    redis-db:
        build:
        context: redis/
        args:
            REDIS_VERSION: $REDIS_VERSION
        container_name: "canren-lnmp-redis"
        ports:
        - "6379:6379"
        networks:
        - "lnmp"
        volumes:
        - ./redis/redis.conf:/etc/redis.conf:rw
        - ../data/redis:/data:cached
        command: "/etc/redis.conf"
        restart: always

    neo4j-db: # 默认密码是neo4j
        build:
        context: neo4j/
        args:
            NEO4J_VERSION: $NEO4J_VERSION
        container_name: "canren-lnmp-neo4j"
        ports:
        - "7474:7474"
        - "7687:7687"
        networks:
        - "lnmp"
        volumes:
        - ./neo4j/conf:/var/lib/neo4j/conf:rw
        - ../data/neo4j:/data:cached
        - ../logs/neo4j:/logs:cached
        restart: always

    networks:
    lnmp:
        driver: bridge

使用

这一套编写下来,我们怎么用呢?

使用搭建好的环境

1.首先,进入项目dockerfiles的目录下,这里是files目录:

 cd lnmp/files

2.执行命令:

 docker-compose build

docker会自动通过编写好的docker-compose.yml内容构建镜像。如果Dockerfile文件有变动要执行此命令。

 docker-compose up

docker会自动通过编写好的docker-compose.yml启动容器。
如果没问题,下次启动时可以以守护模式 docker-compose up -d 启用,所有容器将后台运行。

组合命令,docker会自动通过编写好的docker-compose.yml内容构建镜像并以守护进程启动容器。

 docker-compose up -d --build

3.关闭容器 可以这样关闭容器并删除服务:

 docker-compose down

使用 docker-compose 基本上就这么简单,用 stop,start 等这些命令来操纵容器服务。而更多的工作是在于编写 dockerfile 和 docker-compose.yml 文件。

docker常用命令以及注意事项

命令

docker-compose build    # 每次构建一个新的镜像
docker-compose up       # 启动容器,如果没有镜像则尝试构建
docker-compose start    # 启动容器
docker-compose stop     # 关闭容器
docker-compose restart  # 重启容器
docker-compose down     # 关闭并且删除容器
docker exec -it ${NAMES} bash    # 进入docker镜像
docker ps # 展示所有启动的镜像

注意事项

相关链接

原文链接:https://www.goozp.com/article/77.html
lnmp地址:https://github.com/canren/lnmp