背景

由于最近的lnmp一键包投毒事件,不太敢再继续用lnmp一键包来装lnmp环境了。个人不太喜欢用面板,不如乘着这个机会把环境移植到docker去。用docker最大的好处就是全部东西都是官方的,不太担心会被投毒。但是用了这么多年lnmp一键包,还是有点感情的(很多config不想自己手动再搓一遍)。于是决定同时尽量使用来自一键包的各种配置文件。

Docker这边,目标是做出一个lnmp最小环境并全部使用nignx,mysql,php-fpm官方镜像。

太长不看版

https://github.com/anshi233/lnmp-docker

这个repo里包含了理论上走完全文能得到的一个完整lnmp环境。使用方法请参考README。

Docker架构

经过一段时间研究后,于是决定参考https://lnmp.org/faq/lnmp-software-list.html把关键的配置文件全部映射到docker环境里面去。

Docker大致架构决定如下,主要使用docker compose来达成类似“一键”包的效果。

├── docker-compose.yml       (docker compose 配置文件)
├── dockerfiles              (自定义的dockerfile存放文件夹)
│   ├── nginx.Dockerfile     (nginx 的dockerfile) 
│   └── php-fpm.Dockerfile   (php-fpm 的dockerfile)
├── mysql                    (mysql相关文件存放文件夹)
│   ├── conf.d               (.cnf配置文件存放文件夹)
│   └── mysql                (mysql主要数据库文件存放文件夹)
├── nginx                    (nginx相关文件存放文件夹)
│   ├── conf                 (nginx配置文件存放文件夹)
│   ├── logs                 (nginx logs文件夹)
│   └── wwwlogs              (nginx 网站访问log文件夹)
├── php                      (php相关文件存放文件夹)
│   ├── etc                  (php配置文件夹)
│   │   ├── php-fpm.conf     (php-fpm主要配置文件)
│   │   ├── php-fpm.d        (php-fpm额外配置文件存放文件夹)
│   │   └── php.ini          (php.ini)
│   └── var                  
│       └── log              (php log存放文件夹)
├── ssl                      (https ssl证书存放文件夹)
├── www                      (www用户文件夹,主要存放网站)
└── wwwroot                  (lnmp默认的网站存放文件夹)

现在,我们可以尝试把lnmp一键包搭建的环境的配置文件复制到以上设计好的docker的目录里去了。

lnmp的环境的配置文件来源很自由,你可以从你已经搭了环境的机器复制过来。或者单开一台临时vps装上lnmp2.0然后复制到其他机器。这里只复制nginx,php,www的文件夹。mysql打算重新创建文件夹并手动导入sql来迁移。

#原目录                                     docker目录
#nginx
/usr/local/nginx/conf         -->          [你的docker文件夹根目录]/nginx/conf
/usr/local/nginx/logs         -->          [你的docker文件夹根目录]/nginx/logs
/usr/local/nginx/wwwlogs      -->          [你的docker文件夹根目录]/nginx/wwwlogs
#php
/usr/local/php/etc            -->          [你的docker文件夹根目录]/php/etc
#如果没有/usr/local/php/var/log就创建一个
/usr/local/php/var            -->          [你的docker文件夹根目录]/php/var
#旧版lnmp 有/home/www lnmp2.0没有这个
#也可以复制到其他地方然后软链接过来
/home/www                     -->          [你的docker文件夹根目录]/www
/home/wwwroot                 -->          [你的docker文件夹根目录]/wwwroot

mysql和dockerfile就手动创建

mkdir -p [你的docker文件夹根目录]/dockerfiles
mkdir -p [你的docker文件夹根目录]/mysql/conf.d
mkdir -p [你的docker文件夹根目录]/mysql/mysql

运行权限方面,我强烈不推荐使用root权限去运行任何公开服务。所以计划使用www用户来运行nginx和php-fpm。mysql用户运行mysql。

如果是新机器,我们需要手动在host主机里创建www和mysql用户。推荐创建的同时指定一个uid。具体的创建命令每个发行版都可能不太一样,请google。

如果是已经有www mysql用户的老机器,我们可以通过查看/etc/passwd来查询www用户的uid。稍后会用到。

配置目录的权限

chown -R www:www     [你的docker文件夹根目录]/nginx
chown -R www:www     [你的docker文件夹根目录]/php
chown -R www:www     [你的docker文件夹根目录]/www
chown -R www:www     [你的docker文件夹根目录]/wwwroot
chown -R mysql:mysql [你的docker文件夹根目录]/mysql

nginx

nginx的配置文件默认情况下是在/usr/local/nginx/conf 里面。理论上只要把这个文件映射到docker nginx的配置文件目录里就可以了。但是实际上,lnmp.org提供的配置文件模板里有些绝对路径也需要根据实际情况去修改。

首先理清下原设置应该被映射到docker容器里面的哪个路径

原设置

config: /usr/local/nginx/

ssl: /some_ssl_folder

web: /home/www or /home/wwwroot

logs: /usr/local/nginx/logs(原本是用来存放pid文件的的,可忽略) and /home/wwwlogs

而他们在官方nginx docker容器里的默认对应位置是

config: /etc/nginx

ssl: N/A (我们自己创建)

web: 我们自己创建

logs: 我们自己创建

于是只有config需要映射到/etc/nginx里面去,其他文件夹都是可以很灵活地放到其他地方。

于是我们在docker compose里可以创建以下卷映射:

- ./nginx/wwwlogs:/home/wwwlogs
- ./nginx/logs:/usr/local/nginx/logs
- ./www:/home/www
- ./wwwroot:/home/wwwroot
- ./nginx/conf:/etc/nginx
- ./ssl:/ssl

同时,我们可以把已经安装了lnmp2.0的机器上的/usr/local/nginx/conf文件夹全部复制到docker文件夹里面的nginx/conf里面去了。

同时,原本的www用户也要加上。由于容器内的用户名和主机并不互通。为确保一致我们需要用uid。

接下来就是修改nginx的配置文件了。我们只需要修改一些用绝对路径的选项。

nginx.conf:
修改以下地方
//使用/tmp作为pid文件存放位置。
pid        /tmp/nginx.pid;   

http {
    //在docker container里,tmp文件夹是才是所有用户可写的
    client_body_temp_path /tmp/client_temp;
    proxy_temp_path       /tmp/proxy_temp_path;
    fastcgi_temp_path     /tmp/fastcgi_temp;
    uwsgi_temp_path       /tmp/uwsgi_temp;
    scgi_temp_path        /tmp/scgi_temp;

.....
}

enable-php.conf 和 enable-php-pathinfo.conf也需要修改成使用端口来访问php-fpm服务。
这里我们留到php部分再修改。

以上为nginx.conf需要修改的地方。

为了让容器运行在www用户下。我的方法为在docker compose文件里明文指定www用户的uid和gid。比如在nginx service下面加上这一行:

user: "1001:1001"  #www用户的uid和gid

同时,我还想给nginx加上lua支持,需要添加插件。这里有点坑,由于alpine apk能安装的nginx-lua插件不一定是最新版本的。在写这个文档时,nginx docker官方最新版本是1.25.2,apk上最新的版本是1.24.0,nginx运行时会因错误版本而报错。比较稳妥的方法就是让官方image手动编译我们要的模块。参考:docker-nginx/modules at master · nginxinc/docker-nginx (github.com)

创建dockerfiles/nginx.Dockerfile

FROM nginx:alpine
ARG ENABLED_MODULES="ndk lua"

这里使用alpine版容器因为感觉会省点内存?

(可选)如果要启动lua支持,还需要在nginx.conf里面加上

pcre_jit on;

到这里,nginx配置部分就大致准备完毕。接下来再把wwwroot里的默认网页复制到docker文件夹里并创建以下docker-compose.yml文件看看能不能工作。

services:
  nginx:
    build:
      dockerfile: dockerfiles/nginx.Dockerfile
    container_name: nginx
    user: "1001:1001"  #your www user uid:gid
    volumes:
      - ./nginx/wwwlogs:/home/wwwlogs
      - ./nginx/logs:/usr/local/nginx/logs
      - ./www:/home/www
      - ./wwwroot:/home/wwwroot
      - ./nginx/conf:/etc/nginx
      - ./ssl:/ssl
    ports:
      - 80:80
      - 443:443

    restart: unless-stopped

保存完docker compose文件后使用以下命令启动nginx容器

docker compose up nginx -d
# 查看 nginx 的输出
docker compose logs nginx

如果所有东西都配置完毕,访问host主机,我们应该能看见熟悉的lnmp模板网站了。

但是现在还没有结束,探针一打开就报502错误。我们还需要配置php部分。

https://pic.cosmiccat.net/uploads/2023/10/24/1.jpg

PHP

根据lnmp.org官方文档和php-fpm docker的文档,我们需要映射以下文件进到我们的php-fpm容器。

#ro是只读的意思
      - ./php/etc/php-fpm.conf:/usr/local/etc/php-fpm.conf:ro
      - ./php/etc/php.ini:/usr/local/etc/php/php.ini:ro
      - ./php/var:/usr/local/php/var
#我们的网站目录也许要能够访问到
      - ./www:/home/www
      - ./wwwroot:/home/wwwroot

同时,php-fpm的配置文件也需要根据需求进行修改

Global块的修改:

[global]
;修改pid文件的位置到容器内可写的临时目录里
pid=/tmp/php-fpm.pid
;默认情况下php-fpm是后台运行的。为了在docker里运行,我们得让它跑在前台。
daemonize = no

www块的修改

原本php-fpm.conf的内容

[www]
listen = /tmp/php-cgi.sock
listen.backlog = -1
listen.allowed_clients = 127.0.0.1
listen.owner = www
listen.group = www
listen.mode = 0666

user = www
group = www

需要改成监听端口模式

[www]
;只留下这两行
;监听9000端口
listen = 0.0.0.0:9000
listen.backlog = -1

因为在docker里面,容器之间的交流推荐使用网络来解决。于是最好将php-fpm配置成端口监听模式。

到这里,php-fpm配置文件就修改完了。

nginx这边的php配置模板也需要更新。因为在容器里,我们需要拿端口来和php-fpm连接。

修改./nginx/conf/enable-php.conf 和 ./nginx/conf/enable-php-pathinfo.conf

#            fastcgi_pass  unix:/tmp/php-cgi.sock;
            #php-fpm容器host名和容器名一样
            fastcgi_pass  php-fpm:9000;

但是目前还有一个问题,实际跑网站时我们需要很多额外的扩展(比如mysqli,gd,ioncube等等)。

可以通过dockefile里额外运行https://github.com/mlocati/docker-php-extension-installer这个项目来帮我们安装。

创建dockerfiles/php-fpm.Dockerfile

#这里使用php7.4 alpine版本,可以自己指定版本或者直接用最新版本
FROM php:7.4.32-fpm-alpine
#下载docker-php-extension-installer
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
#运行并安装gd xdebug mysqli。也可以加上其他你想要的模块名。
RUN chmod +x /usr/local/bin/install-php-extensions && \
    install-php-extensions gd xdebug mysqli

接下来在 compose file nginx容器的后面加上php的部分

php-fpm:
    build:
      dockerfile: dockerfiles/php-fpm.Dockerfile
    user: "1001:1001" #你的www uid和gid
    container_name: php-fpm
    volumes:
      - ./php/etc/php-fpm.conf:/usr/local/etc/php-fpm.conf:ro
      - ./php/etc/php.ini:/usr/local/etc/php/php.ini:ro
      - ./php/var:/usr/local/php/var
      - ./www:/home/www
      - ./wwwroot:/home/wwwroot

    restart: unless-stopped

然后运行php-fpm容器

docker compose php-fpm up -d
# 查看php-fpm的输出,如果正常运行的话应该什么都不输出
docker compose logs php-fpm

不出意外,我们就可以访问探针了

https://pic.cosmiccat.net/uploads/2023/10/24/2.jpg

MYSQL

最后,我们来迁移mysql

由于一些原因,lnmp2.0创建的mysql数据库无法直接在docker版上运行。由于mysql可以很方便地通过mysqldump来导出整个数据库。所以采用了先导出整个数据库到sql文件,稍后再使用phpmyadmin之类的程序导入回去。

接下来准备mysql的docker compose部分。这里我使用了mysql容器官方的配置模板。

mysql:
    container_name: mysql
    image: mysql:8
    # NOTE: use of "mysql_native_password" is not recommended: https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-pas>    # (this is just an example, not intended to be a production configuration)
    # 有一些旧的程序还是只支持native password的方式
    command: --default-authentication-plugin=mysql_native_password
    # mysql用户的user id
    user: "1002:1002"
    environment:
      #mysql新数据库的root默认密码,请改成你自己想要的密码。
      #创建完数据库后请删掉这两段
      MYSQL_ROOT_PASSWORD: CHANGE_TO_YOUR_PASSWORD
      #让root用户可以被除了127.0.0.1以外的ip访问
      MYSQL_ROOT_HOST: '%'
    volumes:
      #额外.cnf配置文件目录
      - ./mysql/conf.d:/etc/mysql/conf.d
      #mysql主要的数据库数据目录
      - ./mysql/mysql:/var/lib/mysql
    restart: unless-stopped

如果路径权限配置正确的话,mysql容器是能直接跑起来的。

其中,./mysql/conf.d是用来存放mysql额外配置文件的。(可选)这里可以创建一个网上复制来的低内存占用参数

low-mem.cnf

[mysqld]
#### These optimize the memory use of MySQL
#### http://www.tocker.ca/2014/03/10/configuring-mysql-to-use-minimal-memory.html
innodb_buffer_pool_size=5M
innodb_log_buffer_size=256K
#query_cache_size=0 #it has been removed in 8.0.3
max_connections=10
key_buffer_size=8
thread_cache_size=0
host_cache_size=0
innodb_ft_cache_size=1600000
innodb_ft_total_cache_size=32000000

# per thread or per operation settings
#thread_stack=131072
sort_buffer_size=32K
read_buffer_size=8200
read_rnd_buffer_size=8200
max_heap_table_size=16K
tmp_table_size=1K
bulk_insert_buffer_size=0
join_buffer_size=128
net_buffer_length=1K
innodb_sort_buffer_size=64K

#settings that relate to the binary log (if enabled)
binlog_cache_size=4K
binlog_stmt_cache_size=4K

#### from https://mariadb.com/de/node/579
performance_schema = off

为了让phpmyadmin能够连接到我们的数据库,我们还需要修改一些phpmyadmin的参数。

找到./wwwroot/default/phpmyadmin/config.inc.php

找到

$cfg[Servers][$i][host] = localhost;

改成

$cfg[Servers][$i][host] = mysql;

最后,运行mysql容器

docker compose up mysql -d
# 查看mysql的输出
docker compose logs mysql

至此,我们应该可以得到一个的使用docker搭建的最小lnmp2.0环境了。

如果要关闭服务的话可以使用

# 关闭 nginx
docker compose down nginx
# 关闭 php-fpm
docker compose down php-fpm
# 关闭 mysql
docker compose down mysql

SSL

SSL证书获取部分,由于SSL证书获取的方法多种多样,所以略过了。如果想让容器也能读取到SSL证书,本文使用的例子是把SSL文件放到主机里的”./ssl”文件夹里去。

并在nginx的配置文件里指定被映射的”/ssl”文件夹为ssl保存目录。

你也可以自由地映射自己的ssl目录到nignx容器的任何地方。只需要确定nginx配置文件里的位置是正确的就可以了。