基于 docker 使用 nginx + phpfpm + typecho 搭建一个自己博客(中)

in Server with 0 comment

由于文章内容较长,在此分为三个部分:

上:基础工作和创建mysql容器
中:创建配置php-fpm和nginx容器(本文)
下:安装typecho及Lets Encrypt证书

创建配置php-fpm容器

在项目目录 ~/docker-blog 下创建 php-fpm 目录,用来存储 php-fpm 相关的文件。

本次由于使用了 mysql 数据库,需要安装 php 扩展,而且我们要自定义 php.ini 文件(本次只修改了时区信息⊙︿⊙),因此我们要自己构建一个基于 php:7.3.3-fpm 镜像的新镜像,在 ~/docker-blog/php-fpm 目录下创建 Dockerfile 文件,文件内容如下:

FROM php:7.3.3-fpm

RUN docker-php-ext-install pdo_mysql \
    && mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

这里说明一下 Dockerfile 中的 $PHP_INI_DIR 目录指的就是容器实例中的 /usr/local/etc/php 目录,在构建 php-fpm 新镜像时,我们通过 mv ... 命令生成了 /usr/local/etc/php/php.ini 文件。

对于 php 项目,如果没有设置 php.ini 文件,则采用默认配置。

接下来,我们要获取容器相关的配置文件,操作和 创建配置mysql容器 一样,阅读镜像页面的 DESCRIPTION 以及对应的 Dockerfile 文件,这里我们仅需要容器目录中的 /usr/local/etc/php/php.ini-production 文件,执行以下操作:

docker run --name test_php -d php:7.3.3-fpm
docker cp test_php:/usr/local/etc/php/php.ini-production ~/docker-blog/php-fpm/php.ini
docker stop test_php
docker rm test_php

现在,修改 ~/blog/php-fpm/php.ini 修改内容如下:

date.timezone = Asia/Shanghai

这里附录一份 php:7.3.3-fpm 镜像实例中相关的配置文件所在目录(/usr/local/etc):

/usr/local/etc/
    - pear.conf
    - php/
        - conf.d/
            - docker-php-ext-sodium.ini
        - php.ini-development
        - php.ini-production
    - php-fpm.conf
    - php-fpm.conf.default
    - php-fpm.d/
        - docker.conf
        - www.conf
        - www.conf.default

汇总一下,对于 php-fpm 容器,我们需要映射容器内的相关文件(夹):

这里,我们也为以后续步骤做一些铺垫,在项目目录 ~/docker-blog 下创建 website 文件夹,用来存所有站点。

对于 ~/docker-blog/docker-compose.yml 文件,内容修改后如下(这里还定义了 容器依赖 ):

version: '3.7'

services:

  mydb:
    image: mysql:5.7.25
    restart: on-failure
    volumes:
      - ./mysql/conf/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro
      - ./mysql/data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=blog123.
    networks:
      - blog-net

  php-fpm:
    build: ./php-fpm
    volumes:
      - ./website:/var/www/html
      - ./php-fpm/php.ini:/usr/local/etc/php/php.ini
    depends_on:
      - mydb
    networks:
      - blog-net

networks:
  blog-net:
这里的 depends_on 只是表示容器间的依赖关系,并不能决定容器的启动顺序,关于控制容器的启动顺序请参考:Control startup and shutdown order in Compose

现在我们回到项目目录下,重新运行 docker-compose,检查所有容器能否成功启动:

cd ~/docker-blog
docker-compose down
# 编译构建新的镜像
docker-compose build php-fpm
docker-compose up -d

至此,php-fpm 容器的配置告一段落。

创建配置nginx容器

nginx的基本配置

在项目目录 ~/docker-blog 下创建 nginx 文件夹,用于存储 nginx 相关的配置文件,接着再在 nginx 目录创建以下目录:

执行以下命令:

mkdir -p ~/docker-blog/nginx/log
mkdir ~/docker-blog/nginx/conf
mkdir ~/docker-blog/nginx/conf/vhost

通过阅读 nginx 镜像页面的 DESCRIPTION ,我们了解到下面这几个重要的容器内目录:

这里附录一份 nginx:1.15.9 容器中相关的配置文件:

/etc/nginx/
    - fastcgi_params
    - mime.types
    - nginx.conf
    - scgi_params
    - conf.d/
        - default.conf

本次将对 nginx 容器内的以下几个文件(夹)做数据卷映射:

在进行 nginx 之前,我们先创建 typecho 博客项目的站点目录,在项目目录 ~/docker-blog/website 下创建 blog 目录。

接下来在项目目录 ~/docker-blog/nginx/conf 下创建 nginx.conf 文件,文件内容如下(这里引用了 lnmp 软件里的配置):

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server_names_hash_bucket_size 128;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    client_max_body_size 1024m;
    client_body_buffer_size 10m;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile on;
    tcp_nopush on;

    keepalive_timeout 120;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss;
    gzip_vary on;
    gzip_proxied expired no-cache no-store private auth;
    gzip_disable "MSIE [1-6]\.";

    include /etc/nginx/conf.d/*.conf;
}

再在项目目录 ~/docker-blog/nginx/conf/vhost 下,创建 blog.conf 配置文件,文件内容如下:

server {
    listen  80;
    server_name www.demo.com;
    index index.html index.htm;
    root /usr/share/nginx/html/blog;

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 1h;
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
    }

    location ~ /\. {
        deny all;
    }
}

完成以上操作后,改写 ~/docker-blog/docker-compose.yml 文件,修改后内容如下:

version: '3.7'

services:

  mydb:
    image: mysql:5.7.25
    restart: on-failure
    volumes:
      - ./mysql/conf/my.cnf:/etc/mysql/conf.d/mysql.cnf:ro
      - ./mysql/data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=blog123.
    networks:
      - blog-net

  php-fpm:
    build: ./php-fpm
    volumes:
      - ./website:/var/www/html
      - ./php-fpm/php.ini:/usr/local/etc/php/php.ini:ro
    depends_on:
      - mydb
    networks:
      - blog-net

  nginx:
    image: nginx:1.15.9
    restart: on-failure
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf/vhost:/etc/nginx/conf.d:ro
      - ./nginx/log:/var/log/nginx
      - ./website:/usr/share/nginx/html
    environment:
      - "TZ=Asia/Shanghai"
    depends_on:
      - php-fpm
    networks:
      - blog-net

networks:
  blog-net:
上面的 docker-compose.yml 文件里,对 nginx 容器的时区做了调整,并开启了 443 这个为后续做铺垫。

现在我们创建一个测试文件 hello.html 放到项目目录 ~/docker-blog/website/blog 下,内容随便写,这里不附代码了,接着我们执行相应的 docker-compose 命令运行所有容器,并访问地址 *http://www.demo.com/hello.html* ,检查容器是否运行成功,命令如下:

cd ~/docker-blog
docker-compose down
docker-compose up -d

nginx与php-fpm的通信

nginx 与 [php-fpm] 的通信分为两种,一种是 unix socket,另一个种是 tcp socket。两者的主要区分在于 tcp socket 支持跨服务器,而 unix socket 则不支持,而且配置文件也不相同,这一点很重要。有关更多的详细信息,请参考:nginx与php-fpm通信

这里我就选择了 tcp socket 通信模式(理由是处于同一局域网下的不同服务器),同时决定了在 nginx 配置 fastcgi 通信的内容。

下面是这两个通信的配置,注意这是单机模式下的:

# tcp-socket 通信模式
location ~ [^/]\.php(/|$) {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

# unix-socket 通信模式
location ~ [^/]\.php(/|$) {
    fastcgi_pass unix:/tmp/php-cgi.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

下面讲一个不恰当的故事:一位顾客去银行办理业务,去了501房间,这时候501房间的经理发现自己解决不了顾客的业务,于是赶忙寻找了解相关业务的人员,帮忙处理业务,并告知业务人员顾客在501房间,业务人员去了501房间,处理完业务并告知了501房间的经理,经理随后送走了客户,整个业务流程结束。

其实上面的流程就是 nginxphp-fpm 的交互,浏览器请求 index.php 页面, nginx 在配置项 root 里配置的目录中找到了 index.php,但发现自己处理不了 php 文件,于是便告诉 php-fpm 文件在 $document_root 目录中,名称叫做 $fastcgi_script_name(即:index.php),php-fpm 便在 $document_root 目录中找到 index.php 文件,读取并处理完成后,返回给 nginx,由 nginx 返回浏览器,注意这上面讲的都是 nginxphp-fpm 在同一台机器上。

~/docker-blog/docker-compose.yml 文件里,已经对 nginxphp-fpm 指向了同一目录(nginx 转发的是相对路径),这样就保证了访问 nginx 容器的文件(请求),在 php-fpm 容器中的站点目录也能找到并处理(通过 SCRIPT_FILENAME 转换路径)。下面将对项目目录文件 ~/docker-blog/nginx/conf/nginx.conf~/docker-blog/nginx/conf/vhost/blog.conf 文件进行改进,达到与 php-fpm 容器进行交互。

修改 ~/docker-blog/nginx/conf/nginx.conf 文件(nginx主配置文件),修改后内容如下:

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    server_names_hash_bucket_size 128;
    client_header_buffer_size 32k;
    large_client_header_buffers 4 32k;
    client_max_body_size 1024m;
    client_body_buffer_size 10m;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile on;
    tcp_nopush on;

    keepalive_timeout 120;

    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 256k;

    gzip on;
    gzip_min_length 1k;
    gzip_buffers 4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 2;
    gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss;
    gzip_vary on;
    gzip_proxied expired no-cache no-store private auth;
    gzip_disable "MSIE [1-6]\.";

    include /etc/nginx/conf.d/*.conf;
}

修改 ~/docker-blog/nginx/conf/vhost/blog.conf 文件(blog站点配置文件),修改后内容如下:

server {
    listen  80;
    server_name www.demo.com;
    index index.html index.htm index.php;
    root /usr/share/nginx/html/blog;

    location ~ [^/]\.php(/|$) {
        fastcgi_pass php-fpm:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /var/www/html/blog/$fastcgi_script_name;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 1h;
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
    }

    location ~ /\. {
        deny all;
    }
}

注意上面配置中3项,这里分别说明一下:

现在我们在 ~/docker-blog/website/blog 目录下,新建 index.php 文件内容为下:

<?php
phpinfo();

执行 docker-compose 相应命令,重启 nginx 容器,检测是否能正常访问 http://www.demo.com/index.php

cd ~/docker-blog
docker-compose restart nginx

在正常访问的情况下,我们改进 ~/docker-blog/website/blog/index.php 让其支持访问 MySQL 数据库,修改后脚本如下:

<?php
$db_type = 'mysql';
$host = 'mydb';
$db_port = '3306';
$db_name = 'blog';
$user = 'blog';
$passwd = 'blog123.';
$dsn = "$db_type:host=$host;port=$db_port;dbname=$db_name";
try {
    $db_pdo = new PDO($dsn, $user, $passwd);
    echo 'successful<br/>';
} catch (PDOException $e) {
    die('Error!: '. $e->getMessage() . '<br/>');
}
关于配置中使用的容器名称,因为我们在部署应用之前无法确定IP,而且使用IP也相对麻烦。使用容器名称,实际内部是使用 docker 自带的 DNS 服务,注意要使用 dockerDNS 服务,我们必须自己新建网络,无法使用自带的 default 网络(注:DNS 域名解析服务,简单一句话:把域名解析为具体的IP地址)。
Comments are closed.