Docker树莓派实践——LNMP
步骤/目录:
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/bahs
或bash
,会导致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 容器名 bash
或docker 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