步骤/目录:
1.下载、烧录Pi OS最新镜像
2.安装docker并调试;备份系统镜像
3.方法一:假docker法
    (1)拉取并运行ubuntu镜像
    (2)安装vim、nginx
    (3)安装php并配置nginx
    (4)安装mariadb
    (5)收尾及总结
4.方法二:胖容器法
    (1)创建环境,运行容器
    (2)测试容器
    (3)收尾及总结
5.方法三:瘦容器法
    (1)创建环境,配置nginx
    (2)创建网络,开启php、nginx容器并测试
    (3)运行mysql容器并测试
    (4)准备环境并编写compose模板,运行并测试
    (5)收尾及总结
附录
1.docker连接mysql时显示"Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'"
    (1)docker run /bin/bash
    (2)Dockerfile CMD
    (3)docker run未挂载或映射端口
    (4)mysql配置问题
2.使用docker-compose创建mysql容器时的初始化
    (1)挂载配置文件和数据库
    (2)进入服务容器修改
    (3)挂载到/docker-entrypoint-initdb.d
    (4)Dockerfile COPY到/docker-entrypoint-initdb.d
    (5)--init-file选项

本文首发于个人博客https://lisper517.top/index.php/archives/20/,转载请注明出处。
本文的目的是使用docker、docker-compose在树莓派上搭建LNMP环境。
本文实验日期为2022年7月25日。本文使用的是树莓派4B(内存8G版),系统为Pi OS 64位桌面版(2022年4月4日更新,镜像名 2022-04-04-raspios-bullseye-arm64.img ),装上系统后仅做了一些基础配置(安装了ufw、watchdog,设置了ssh密钥,apt update了一下)。

LNMP环境指Linux、Nginx、Mysql、Php,LNMP常用于提供动态网页服务。一般来说使用docker有三种方式搭建LNMP环境:一是使用linux容器(如centos或ubuntu),docker run -it ubuntu bash,类似不使用docker时进行安装;二是使用Dockerfile,FROM ubuntu开始构建一个镜像,安装NMP;三是单独生成N、M、P的镜像,使用docker-compose或其他方法管理。方法一属于下策,但是最简单易行;方法二可称为fat container,只需要一个镜像、一个容器,缺点是不利于NMP的版本变更,但在一些情况下也可以使用;方法三为thin container,需要三个容器跑一个功能,容器多但是利于版本更迭、调整环境,而且使用docker-compose可以非常方便地管理多个容器。这里三种方法都演示一下。

另外,如果需要非docker搭建LNMP,可以参考之前的文章 树莓派搭建个人博客

1.下载、烧录Pi OS最新镜像

这里涉及的操作之前的文章已经介绍过了,详见 树莓派4B初始化
本次实验使用的是树莓派4B。在系统镜像下载网页下载最新的Pi OS 64位桌面版(下载文件名为2022-04-04-raspios-bullseye-arm64.img.xz,解压为.img文件后大小为3.95GB)。注意2022年4月后Pi OS取消了默认的pi用户,但可以自己创建:在boot下新建userconf文件,写入以下内容:

pi:$6$8WzhrUbnvxbJdS5n$p5acHHXaB02qI1eCJrSH4lSUOsXx9WnTvbNm2T9h9d/OmnFuf0qXGTAYF3GK5pqFONn7LiA4Khn4AHXcYG72D/

这样就手动创建了pi用户,密码还是raspberry。
装上系统后仅进行了apt-get upgrade,安装并设置了ufw、watchdog,调整了ssh设置。

2.安装docker并调试;备份系统镜像

安装、调试docker和docker-compose的过程在前面已经介绍,这里不再赘述(见 Docker入门(一) )。记得换docker源。
这里使用RaspberryBackup脚本备份系统,详细介绍见 树莓派备份-RaspberryBackup详解

3.方法一:假docker法

再次提醒,这种方法只是学术探讨,实际环境中不管是生成还是测试都不要使用。

(1)拉取并运行ubuntu镜像

docker pull ubuntu
docker run -itd -p 80:80 --name ubuntu_LNMP --restart always ubuntu /bin/bash #未映射宿主机文件夹
#输出容器id为 4a5
docker container ls -a
docker exec -it 4a5 /bin/bash

进入ubuntu命令行后,查看一下系统信息:

cat /proc/version
cat /etc/os-release

(2)安装vim、nginx

安装vim、nginx(未换apt源),开启nginx服务:

apt-get update
apt-get install -y vim nginx
service --status-all #使用systemctl还要安装systemd
service nginx start

这时访问 树莓派ip ,即可看到nginx初始页。
再测试一下:

cd /var/www/html
vim test.html

写入文件内容如下:

<!DOCTYPE html>
<html>
<head>
<title>nginx test</title>
</head>
<body>
<h1>wuhu,qifei</h1>
</body>
</html>

浏览器访问 树莓派ip/test.html ,出现对应页面。

(3)安装php并配置nginx

apt-get install -y php-fpm php-mysql
service --status-all
#这里安装的是php8.1-fpm

这里只配置nginx,php配置不变:

vim /etc/nginx/sites-available/default

配置文件共修改两处。首先将:

        index index.html index.htm index.nginx-debian.html;

改为:

        index index.php index.html index.htm index.nginx-debian.html;

其次在这个server的{}最后加上:

        location ~ .*\.php(\/.*)*$ {
                fastcgi_pass unix:/run/php/php8.1-fpm.sock; #注意自己的php-fpm版本
                include fastcgi_params;
                set $path_info "";
                set $real_script_name $fastcgi_script_name;
                if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
                        set $real_script_name $1;
                        set $path_info $2;
                }
                fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
                fastcgi_param SCRIPT_NAME $real_script_name;
                fastcgi_param PATH_INFO $path_info;
        }

接下来开始测试:

nginx -t
service nginx restart
vim /var/www/html/myweb.php

文件内容为:

<?php
        phpinfo();
?>

然后在浏览器输入 树莓派ip/myweb.php ,就会显示经典的php信息页面。

(4)安装mariadb

mariadb和mysql基本一样。

apt-get install -y mariadb-server
service --status-all
service mariadb start
mysql_secure_installation

这里进行一些基础设置,设置完root密码后全选y即可,但最好还是自己看一下。
之后进入mariadb看一下:

mysql -uroot -p
show databases;
exit

后续可以自己建库、表试试。另外,这里就不测试php和mariadb互联了。

(5)收尾及总结

使用以下命令删除镜像和容器:

docker stop ubuntu_LNMP
docker container rm ubuntu_LNMP
docker image rm ubuntu

总结一下,这种方法总体上比较简单,和不用docker时差不多。个人网页也许可以用这种方式,但还是不推荐,毕竟学习了docker还是应该整点狠活。

4.方法二:胖容器法

此处使用的镜像是这个镜像这里是该项目的github页面。为了方便,下文直接使用该镜像。感兴趣的可以看看该项目的Dockerfile。
如之前所述,这种方法的优点是只有一个镜像,管理方便;但是缺点是镜像内的几个软件更迭版本比较费时,不能做到数秒内切换版本。如果镜像搭建时各软件使用的都是已知比较稳定、交互时bug少的版本,那这种方法可能也可以用于生产环境。

(1)创建环境,运行容器

mkdir -p /test/www
mkdir -p /test/mysql/data
docker pull 2233466866/lnmp:1.13
docker images
docker run -itd --name=lnmp_test -v /test/www:/www -v /test/mysql/data:/data/mysql --privileged=true -p 80:80 2233466866/lnmp:1.13

这里使用的镜像使用了CentOS7 + Nginx + Node.js + MySQL + php-^7.4 + php5.6.40搭建。由于该镜像的平台架构是linux/amd64,而笔者用的树莓派是linux/arm64/v8,所以该镜像实际上是跑不起来的。后面测试容器、收尾部分并未实测。
如需要挂载各种配置文件,可去项目原址了解详情。

(2)测试容器

docker exec -it lnmp_test /bin/bash
cat /etc/os-release
cat /proc/version
nginx -t
ps aux | grep nginx
ps aux | grep mysql
ps aux | grep php7
ps aux | grep php5

之后在/test/www下创建一些测试网页,如前文一样,在浏览器访问 树莓派ip 进行测试。

(3)收尾及总结

使用以下命令删除镜像和容器:

rm -r /test
docker stop lnmp_test
docker container rm lnmp_test
docker image rm 2233466866/lnmp:1.13

5.方法三:瘦容器法

如前所述,这种方法是最推荐的,各个镜像换版本很方便,能做到数秒内切换版本;由于镜像较多,管理稍微麻烦一点,但可通过docker compose进行方便快捷的管理。
下面的内容,不涉及docker-compose的部分主要参考了这篇文章这个网页;涉及docker-compose的部分参考了这篇文章
最后需要说明,前三步都是测试环境、配置。赶时间可直接看第四、五步。

(1)创建环境,配置nginx

mkdir -p /test/nginx/conf.d
mkdir /test/www
nano /test/nginx/conf.d/default.conf

default.conf就是nginx的配置文件,其中写入以下内容:

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.php index.html index.htm;
    }

    location ~ \.php$ {
        fastcgi_pass   php_test:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/html/$fastcgi_script_name;
        include        fastcgi_params;
    }
}

其中fastcgi_pass php_test:9000;需要特别注意。这里的php_test是容器名,后面跑不起来的话可以试试换成容器id、容器在网络中的ip;9000是php提供服务的端口。

(2)创建网络,开启php、nginx容器并测试

创建网络,开启php-fpm容器、加入网络:

docker network create -d bridge net_test
docker pull php:8.1-fpm
docker stop portainer #之前这个容器也使用了9000端口,暂时停一下
docker run -d --name php_test -v /test/www:/var/www/html -p 9000:9000 --network net_test php:8.1-fpm

如果docker run时忘记加上--network net_test参数,可以使用如下命令补救:

docker network connect net_test php_test

开启nginx容器:

docker pull nginx
docker run -d --name nginx_test -v /test/www:/var/www/html -v /test/nginx/conf.d:/etc/nginx/conf.d -p 80:80 -p 443:443 --network net_test nginx

443端口用于HTTPS协议,这里也可以不加。

进行测试:

nano /test/www/index.html
nano /test/www/index.php

分别写入:

<!DOCTYPE html>
<html>
<head>
<title>nginx test</title>
</head>
<body>
<h1>wuhu,qifei</h1>
</body>
</html>

和:

<?php
        phpinfo();
?>

在浏览器输入 树莓派ip/index.html 或 树莓派ip/index.php ,出现对应界面,测试成功。

另外,为了让php使用mysql,还需要在php容器中安装pdo、pdo_mysql扩展,php镜像就有提供安装该扩展的快捷操作(这里可先不运行):

docker exec -it php_test bash
docker-php-ext-install pdo pdo_mysql
exit
docker restart php_test

(3)运行mysql容器并测试

先创建文件夹:

mkdir -p /test/mysql/data
mkdir /test/mysql/conf

然后运行容器并进入:

docker pull mysql
docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -p 3306:3306 --network net_test -e MYSQL_ROOT_PASSWORD=123456 mysql

这里拉取到的是mysql8.0.29,如果是老版本的话容器放配置、数据、日志的位置可能不一样。另外其中-e MYSQL_ROOT_PASSWORD=123456可设置mysql的root密码。
最后关于mysql的配置,本例中,在宿主机的/test/mysql/conf下任何以.cnf结尾的文件都会覆盖原有配置。
为了测试mysql,可以在mysql中建一张表。操作如下:

nano /test/mysql/test.sql

其内容如下:

CREATE TABLE student(
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
        name VARCHAR(20) NOT NULL COMMENT '姓名',
        age INT NOT NULL COMMENT '年龄'
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '学生数据表';
 
INSERT INTO student(name, age) VALUES('alice', 18);
INSERT INTO student(name, age) VALUES('bob', 28);

下面拷贝/test/mysql/test.sql到容器里,并进入mysql容器进行配置:

docker cp /test/mysql/test.sql mysql_test:/tmp/
docker exec -it mysql_test bash
mysql -uroot -p
#输入密码
CREATE DATABASE test;
SHOW DATABASES;
USE test;
SOURCE /tmp/test.sql;
SELECT * FROM student;
exit
exit

成功显示导入的表。

这里也不测试php和mysql的互联了。前面三步只是初步测试,下面开始动真格。

(4)准备环境并编写compose模板,运行并测试

前面运行的容器没有使用docker-compose管理、仅用于测试,现在运行的话php与mysql容器无法互联。下面删除测试容器、镜像与目录,另起炉灶:

cd /
docker container ls -a
docker stop mysql_test
docker stop nginx_test
docker stop php_test
docker container rm mysql_test
docker container rm nginx_test
docker container rm php_test
docker container ls -a
docker network ls
docker network rm net_test
docker network ls
docker images
docker image rm mysql
docker image rm nginx
docker image rm php:8.1-fpm
docker images
rm -r /test

然后创建测试目录:

mkdir -p /test/docker_compose/LNMP/nginx/conf.d
touch /test/docker_compose/LNMP/nginx/conf.d/default.conf
mkdir /test/docker_compose/LNMP/www
mkdir -p /test/docker_compose/LNMP/mysql/data
mkdir /test/docker_compose/LNMP/mysql/conf
mkdir /test/docker_compose/LNMP/php

下面准备拉取镜像、创建php配置目录(但本次不会挂载):

docker pull nginx:1.23.1
docker pull mysql:8.0.29
docker pull php:8.1-fpm
docker images
mkdir /test/docker_compose/LNMP/php/conf

php镜像需要安装扩展、新增配置,所以第一次运行时不要挂载宿主机目录到php服务容器的配置目录。先运行一次,后面再用docker cp拷贝,并挂载。
接下来创建Dockerfile。其中nginx需要修改配置文件,php需要安装pdo、pdo_mysql扩展,mysql需要建表、进行一些基础设置,所以nginx不用Dockerfile、只需修改一下/test/docker_compose/LNMP/nginx/conf.d/default.conf,而给php、mysql写Dockerfile即可:

nano /test/docker_compose/LNMP/nginx/conf.d/default.conf

nginx的配置文件中写入以下内容:

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.php index.html index.htm;
    }

    location ~ \.php$ {
        fastcgi_pass   php_test:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/html/$fastcgi_script_name;
        include        fastcgi_params;
    }
}

注意fastcgi_pass php_test:9000;,这里写的是服务容器名(php_test),写实际容器名(lnmp_php_test_1)也可以,据说还可以写项目内容器的ip或容器短id,笔者没有实验,毕竟这样通用性可能不强。
然后写一下php、mysql的Dockerfile:

mkdir -p /test/docker_compose/LNMP/dockerfiles/php
mkdir /test/docker_compose/LNMP/dockerfiles/mysql
nano /test/docker_compose/LNMP/dockerfiles/php/Dockerfile
nano /test/docker_compose/LNMP/dockerfiles/mysql/Dockerfile

php的Dockerfile写入以下内容:

FROM php:8.1-fpm
RUN docker-php-ext-install mysqli pdo pdo_mysql

后续的docker-compose up过程中显示3个php扩展都装了(这个版本的php自带PDO)。另外需要提醒一下,如果挂载了本地php配置目录到php_test服务容器,那么一定确保里面有这三个扩展的配置,否则装了和没装一样。严格按照本文流程是没问题的。
mysql的Dockerfile写入以下内容:

FROM mysql:8.0.29
COPY ./test.sql /docker-entrypoint-initdb.d/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld","--character-set-server=utf8mb4","--default-authentication-plugin=mysql_native_password"]

docker-mysql的/docker-entrypoint-initdb.d目录是其初始化目录,可放入.sh.sql.sql.gz文件来进行一些初始化操作,比如导入一些表。该目录里文件按字母顺序依次执行(.sql.gz压缩文件应该会解压成.sql后执行),若不指定库名,则要导入的库名默认为MYSQL_DATABASE指定的库。关于mysql初始化的内容详见附录2.。
再写入mysql构建时需要的test.sql:

nano /test/docker_compose/LNMP/dockerfiles/mysql/test.sql

文件内容如下:

CREATE DATABASE test;
USE test;
CREATE TABLE student( 
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 
        name VARCHAR(20) NOT NULL, 
        age INT NOT NULL
);
INSERT INTO student(name, age) VALUES('alice', 18);
INSERT INTO student(name, age) VALUES('bob', 28);

至此就写好了Dockerfile及其需要的文件。下面创建compose模板文件:

nano /test/docker_compose/LNMP/docker-compose.yml

compose模板文件内容如下,其编写参考了这篇文章这个项目。:

version: "3.9"

services:
  nginx_test:
    image: nginx:1.23.1
    ports:
      - "443:443/tcp"
      - "443:443/udp"
      - "80:80"
    volumes:
      - /test/docker_compose/LNMP/www:/var/www/html
      - /test/docker_compose/LNMP/nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - php_test
    logging: 
      driver: syslog
  
  mysql_test:
    build:
      context: ./dockerfiles/mysql/
      dockerfile: Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - /test/docker_compose/LNMP/mysql/data:/var/lib/mysql
      - /test/docker_compose/LNMP/mysql/conf:/etc/mysql/conf.d
    logging: 
      driver: syslog

  php_test:
    build:
      context: ./dockerfiles/php/
      dockerfile: Dockerfile
    volumes:
      - /test/docker_compose/LNMP/www:/var/www/html
      #- /test/docker_compose/LNMP/php/conf:/usr/local/etc/php
      #因为php镜像要安装3个扩展、会新增配置,所以第一次运行先不挂载php配置目录
    logging: 
      driver: syslog

再次提醒,第一次运行时,就算/test/docker_compose/LNMP/php/conf是空的,也不要挂载到php服务容器。像mysql容器,由于它在容器运行后才会运行初始化脚本、生成数据,所以第一次运行就把宿主机上空的数据目录/test/docker_compose/LNMP/mysql/data挂载了。但php在生成镜像时就会安装扩展、生成配置,如果第一次就挂载空的php配置目录,就会使用默认配置,结果找不到新扩展的相关配置、无法使用这些扩展。
然后验证一下格式,运行项目:

cd /test/docker_compose/LNMP/
docker-compose config
docker-compose up

输出结果:

…………
…………
Creating lnmp_mysql_test_1 ... done
Creating lnmp_phpl_test_1 ... done
Creating lnmp_nginx_test_1 ... done
…………
…………

现在另开一个ssh连接,测试一下LNMP项目。
首先测试一下nginx和php:

nano /test/docker_compose/LNMP/www/index.html
nano /test/docker_compose/LNMP/www/index.php

分别写入:

<!DOCTYPE html>
<html>
<head>
<title>nginx test</title>
</head>
<body>
<h1>wuhu,qifei</h1>
</body>
</html>

和:

<?php
        phpinfo();
?>

浏览器访问 树莓派ip 或 树莓派ip/index.php,显示php信息界面;访问 树莓派ip/index.html ,显示wuhu,qifei
接下来试试php与mysql是否正常协作。先看看mysql容器:

docker exec -it lnmp_mysql_test_1 bash
mysql -uroot -p
show databases;
use test;
show tables;
select * from student;
exit
exit

正确显示初始数据库。
为了测试LNMP环境(主要是php和mysql间的通信),新建一张网页LNMP_test.php

nano /test/docker_compose/LNMP/www/LNMP_test.php

其内容如下:

<?php
$dsn = 'mysql:dbname=test;host=mysql_test';
$user = 'root';
$password = '123456';

try {
    $dbh = new PDO($dsn, $user, $password);
    $sql = 'SELECT * FROM student WHERE id=?';
    $sth = $dbh->prepare($sql);
    $sth->execute([2]);
    $result = $sth->fetch(PDO::FETCH_ASSOC);
    var_dump($result);
} catch (PDOException $e) {
    echo 'Error: ' . $e->getMessage();
}
?>

特别注意$dsn = 'mysql:dbname=test;host=mysql_test';中的host,这里写的是服务容器的名字(在compose模板里的名字)。和nginx配置一样,这里也可以写成lnmp_mysql_test_1
然后在浏览器访问 树莓派ip/LNMP_test.php ,网页成功显示student表里的内容。

最后,如果想在宿主机保存php配置,可进行如下操作:

rmdir /test/docker_compose/LNMP/php/conf
docker cp lnmp_php_test_1:/usr/local/etc/php /test/docker_compose/LNMP/php
mv /test/docker_compose/LNMP/php/php /test/docker_compose/LNMP/php/conf
nano /test/docker_compose/LNMP/docker-compose.yml

再将compose模板里注释掉的挂载命令前的 # 去掉即可。然后删掉php_test容器,重新docker-compose up

docker stop lnmp_php_test_1
docker container rm lnmp_php_test_1
cd /test/docker_compose/LNMP/
docker-compose config
docker-compose up

这里没有改动镜像、只是改了启动时的选项(相当于docker run增加了一个-v参数),所以可以不删镜像。按理来说也可以不删容器。
这样就能同步php配置了。此时的镜像已经可以试用于生产环境,但是最好在compose模板里,每个服务容器下面再加上restart: always

(5)收尾及总结

使用如下命令彻底清理测试环境:

docker stop lnmp_mysql_test_1
docker stop lnmp_nginx_test_1
docker stop lnmp_php_test_1
docker container ls -a
docker container rm lnmp_mysql_test_1
docker container rm lnmp_nginx_test_1
docker container rm lnmp_php_test_1
docker container ls -a
docker images
docker image rm lnmp_mysql_test
docker image rm lnmp_php_test_1
docker image rm mysql:8.0.29
docker image rm nginx:1.23.1
docker image rm php:8.1-fpm
docker images
docker network ls
docker network rm lnmp_default
docker network ls
cd /
rm -r /test

总结一下,整个流程还是比较复杂的,其中有一些小坑。但是一旦配置完成,使用docker-compose就非常容易管理,分布式、Swarm mode等也手到擒来,可以说难度大、收益大。

附录:

1.docker连接mysql时显示"Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock'"

出现这个错误常常是因为mysql进程在容器里出现问题。

(1)docker run /bin/bash

docker run末尾加/bin/bahsbash,会导致mysql进程不运行。
进行如下操作:

mkdir -p /test/mysql/data
mkdir /test/mysql/conf
docker pull mysql
docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 mysql /bin/bash
docker exec -it mysql_test /bin/bash
mysql -uroot -p
#输入密码后输出结果
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
mysql -h localhost -uroot -p
#输出结果同上
mysql -h localhost -P 3306 -uroot -p
#输出结果同上

这是因为在docker run的末尾多加了一个/bin/bash。下面去掉/bin/bash

docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 mysql
docker exec -it mysql_test /bin/bash
mysql -uroot -p
#第一次输入密码后输出结果
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
mysql -uroot -p

第二次输入密码后才成功登录。如果想一次登录成功,需要加上-p参数:

docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 mysql

若要一次登录成功,需要同时加上-v-p参数。详见后面 (3)docker run未挂载或映射端口 。

(2)Dockerfile CMD

该情况来自于一个问答翻译网站,原问答地址多半在stackoverflow或github。
如果要用mysql官方镜像生成自己的镜像,并且在Dockerfile中写CMD tail -f /dev/null这样无关的命令,就会覆盖之前镜像的CMD,而正常镜像的CMD应该负责开启容器进程。比如去看官方的mysql镜像,最后是这样写的:

ENTRYPOINT ["docker-entrypoint.sh"]
EXPOSE 3306 33060
CMD ["mysqld"]

所以要写CMD也应写成以下形式:

ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"] #mysql镜像

或:

CMD ["nginx", "-g", "daemon off;"] #nginx镜像

另外,对于mysql镜像的自动导入表功能(/docker-entrypoint-initdb.d目录),据说也要用entrypoint脚本时才行。所以如果希望自己构建的mysql镜像附带初始表,可以这样写Dockerfile:

FROM mysql:8.0.29
ENV MYSQL_ROOT_PASSWORD=123456 MYSQL_DATABASE=test
COPY ./test.sql /docker-entrypoint-initdb.d/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"]

但是就算Dockerfile写的没错,之后docker run也可能出问题,详见下一条。

(3)docker run未挂载或映射端口

现有这样一个场景:从官方mysql镜像创建自己的镜像,要求该镜像启动时自动挂载一张表。
进行如下操作:

mkdir -p /test/mysql/data #数据文件夹,暂时先不挂载
mkdir /test/mysql/conf #配置文件夹,也暂时先不挂载
mkdir -p /test/dockerfiles/mysql
nano /test/dockerfiles/mysql/test.sql
nano /test/dockerfiles/mysql/Dockerfile

test.sql中写入以下内容:

CREATE TABLE student(
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
        name VARCHAR(20) NOT NULL COMMENT '姓名',
        age INT NOT NULL COMMENT '年龄'
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT '学生数据表';
 
INSERT INTO student(name, age) VALUES('alice', 18);
INSERT INTO student(name, age) VALUES('bob', 28);

Dockerfile中写入以下内容:

FROM mysql:8.0.29
ENV MYSQL_ROOT_PASSWORD=123456 MYSQL_DATABASE=test
COPY ./test.sql /docker-entrypoint-initdb.d/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld"]

看起来很完美,没有踩任何坑。Dockerfile通过/docker-entrypoint-initdb.d/目录会自动写入test.sql到test数据库中(默认写入MYSQL_DATABASE指定的库中),并且最后确保了这个自制镜像通过entrypoint脚本启动。接下来构建镜像、运行容器并测试一下:

cd /test/dockerfiles/mysql
docker build -t mysql:my .
docker run -itd --name mysql_test mysql:my
docker exec -it mysql_test /bin/bash
mysql -uroot -p=123456
#输出结果
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

又是同样的错误,但这里是由于docker run没有加-v-p参数。解决方法有二,一是修改mysql登录命令:

mysql -h localhost -P 3306 -uroot -p
#第一次输入密码后输出结果
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
mysql -h localhost -P 3306 -uroot -p
#第二次输入密码后才成功登录

二是修改docker run(加挂载和端口映射):

docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -p 3306:3306 mysql:my
docker exec -it mysql_test /bin/bash
mysql -uroot -p
#一次就成功登录

在方法二中,如果单独加-v或单独加-p参数,都不能彻底解决问题。如果单独加-v,共使用2次mysql -h localhost -P 3306 -uroot -p才进入mysql命令行;单独加-p时则用了4次。
不过这几种情况下,只要成功进入mysql命令行,会发现test库的创建、test.sql的导入都没有问题。

(4)mysql配置问题

配置问题就比较复杂了,可以先试试mysql -h localhost -P 3306 -uroot -p;或者如果之前运行过mysql的容器没删、这次运行出问题,可以拿以前的容器先顶上;最后如果没办法,可以用docker cp拷贝配置文件到宿主机,逐行比对自己的配置和mysql的默认配置。

mysql的登录问题告一段落,下面介绍篇mysql容器的初始化。

2.使用docker-compose创建mysql容器时的初始化

在使用docker-compose创建容器服务时,有时需要对初始的mysql做一些设置或存入一些初始数据。下面介绍5种方法。

(1)挂载配置文件和数据库

在服务容器中,一般mysql的数据文件在/var/lib/mysql,配置文件在/etc/mysql/conf.d。注意/etc/mysql/conf.d目录下本来是没有配置文件的,但如果在里面出现了以.cnf结尾的文件,就会覆盖mysql原有的配置文件。mysql原有的配置文件为/etc/my.cnf(linux)。
所以可以在docker run时使用-v参数挂载宿主机路径或数据卷到这些服务容器路径,就能轻松转移以前的mysql数据库配置和数据。

(2)进入服务容器修改

使用docker exec -it 容器名 bashdocker exec -it 容器名 /bin/bash进入容器服务,再登录到mysql命令行,就能用mysql命令行自带的命令对设置或数据进行修改,比如用source导入数据。

(3)挂载到/docker-entrypoint-initdb.d

如果希望在mysql镜像实例化时自动导入数据,可以试试/docker-entrypoint-initdb.d目录。根据docker-mysql官网的介绍,mysql镜像实例化时会以字母顺序执行/docker-entrypoint-initdb.d目录下的.sh.sql.sql.gz文件,其中sql.gz压缩文件应该会解压成.sql后执行。使用该目录有两种方法,其一是挂载宿主机路径到该目录。
下面进行实操,先创建测试目录、compose模板文件:

mkdir -p /test/mysql/data
mkdir /test/mysql/dockerfile
mkdir /test/mysql/init.d
nano /test/mysql/docker-compose.yml
nano /test/mysql/dockerfile/Dockerfile
nano /test/mysql/init.d/test.sql

compose模板文件中写入以下内容:

version: "3.9"

services:
  mysql_test:
    build:
      context: ./dockerfile/
      dockerfile: Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - /test/mysql/data:/var/lib/mysql
      - /test/mysql/init.d:/docker-entrypoint-initdb.d:ro
    logging: 
      driver: syslog

然后是Dockerfile:

FROM mysql:8.0.29
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld","--character-set-server=utf8mb4","--default-authentication-plugin=mysql_native_password"]

接着是test.sql:

CREATE DATABASE test;
USE test;
CREATE TABLE student( 
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 
        name VARCHAR(20) NOT NULL, 
        age INT NOT NULL
);
INSERT INTO student(name, age) VALUES('alice', 18);
INSERT INTO student(name, age) VALUES('bob', 28);

接下来开启项目,进行测试:

cd /test/mysql
docker-compose config
docker-compose up
#另开一个ssh界面
docker exec -it mysql_mysql_test_1 bash
mysql -uroot -p
use test;
select * from student;
exit
exit

成功显示导入的数据。
另外提醒一下,本例中在compose模板中mysql容器使用了syslog,正常执行/docker-entrypoint-initdb.d目录下文件时输出的log会在执行命令前后留下数行空行,非常显眼。

总结一下,这种方法的关键就是在容器服务的volumes下带上一个挂载:- 宿主机目录:/docker-entrypoint-initdb.d:ro,在宿主机目录中存放初始化文件。

测试完毕,进行收尾:

docker stop mysql_mysql_test_1
docker container ls -a
docker container rm mysql_mysql_test_1
docker container ls -a
docker network ls
docker network rm mysql_default
docker network ls
cd /
rm -r /test

(4)Dockerfile COPY到/docker-entrypoint-initdb.d

上面的方法是挂载,第二种方法就是用Dockerfile的COPY指令,在构建镜像的时候就把初始化文件放进去。下面进行实操,同样先创建测试目录、compose模板文件:

mkdir -p /test/mysql/data
mkdir /test/mysql/dockerfile
nano /test/mysql/docker-compose.yml
nano /test/mysql/dockerfile/Dockerfile
nano /test/mysql/dockerfile/test.sql

compose模板文件中写入以下内容:

version: "3.9"

services:
  mysql_test:
    build:
      context: ./dockerfile/
      dockerfile: Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - /test/mysql/data:/var/lib/mysql
    logging: 
      driver: syslog

然后是Dockerfile:

FROM mysql:8.0.29
COPY ./test.sql /docker-entrypoint-initdb.d/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld","--character-set-server=utf8mb4","--default-authentication-plugin=mysql_native_password"]

接着是test.sql:

CREATE DATABASE test;
USE test;
CREATE TABLE student( 
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 
        name VARCHAR(20) NOT NULL, 
        age INT NOT NULL
);
INSERT INTO student(name, age) VALUES('alice', 18);
INSERT INTO student(name, age) VALUES('bob', 28);

接下来开启项目,进行测试:

cd /test/mysql
docker-compose config
docker-compose up
#另开一个ssh界面
docker exec -it mysql_mysql_test_1 bash
mysql -uroot -p
use test;
select * from student;
exit
exit

成功显示导入的数据。
和上一种方法相比,这种方法的关键是在Dockerfile中COPY ./init.sql /docker-entrypoint-initdb.d/,而不是挂载初始化目录或文件到容器服务。
和上一种方法一样,本例中在compose模板中mysql容器使用了syslog,正常执行/docker-entrypoint-initdb.d目录下文件时输出的log会在执行命令前后留下数行空行,非常显眼。

测试完毕,进行收尾:

docker stop mysql_mysql_test_1
docker container ls -a
docker container rm mysql_mysql_test_1
docker container ls -a
docker network ls
docker network rm mysql_default
docker network ls
cd /
rm -r /test

(5)--init-file选项

前面两种方法的目标都是把初始化文件放到容器服务的/docker-entrypoint-initdb.d中。本方法则利用mysql数据库启动时,mysqld命令的--init-file达到同样的效果。不需要额外挂载,但同样需要使用Dockerfile的COPY指令。
下面进行实操,还是先创建测试目录、compose模板文件:

mkdir -p /test/mysql/data
mkdir /test/mysql/dockerfile
nano /test/mysql/docker-compose.yml
nano /test/mysql/dockerfile/Dockerfile
nano /test/mysql/dockerfile/test.sql

compose模板文件中写入以下内容:

version: "3.9"

services:
  mysql_test:
    build:
      context: ./dockerfile/
      dockerfile: Dockerfile
    environment:
      MYSQL_ROOT_PASSWORD: 123456
    volumes:
      - /test/mysql/data:/var/lib/mysql
    logging: 
      driver: syslog

然后是Dockerfile:

FROM mysql:8.0.29
COPY ./test.sql /init.sql
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["mysqld","--character-set-server=utf8mb4","--default-authentication-plugin=mysql_native_password","--init-file=/init.sql"]

接着是test.sql:

CREATE DATABASE test;
USE test;
CREATE TABLE student( 
        id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, 
        name VARCHAR(20) NOT NULL, 
        age INT NOT NULL
);
INSERT INTO student(name, age) VALUES('alice', 18);
INSERT INTO student(name, age) VALUES('bob', 28);

接下来开启项目,进行测试:

cd /test/mysql
docker-compose config
docker-compose up
#另开一个ssh界面
docker exec -it mysql_mysql_test_1 bash
mysql -uroot -p
use test;
select * from student;
exit
exit

成功显示导入的数据。
和前两种方法相比,这种方法的关键是在Dockerfile中COPY ./init.sql /init.sql,没有使用/docker-entrypoint-initdb.d目录。
另外提醒一下,据说--init-file参数提供的.sql文件,书写要求是不能有注释、一行写一条命令(估计意思是不能一行中写多条命令,但可以把一条命令拆成几行写,如果有条件还是尽量一行写一条命令)。

测试完毕,进行收尾:

docker stop mysql_mysql_test_1
docker container ls -a
docker container rm mysql_mysql_test_1
docker container ls -a
docker network ls
docker network rm mysql_default
docker network ls
cd /
rm -r /test

标签: 树莓派, docker, docker-compose, LNMP

添加新评论