背景

今天服务器 Redis 服务突然报错

MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.

网上搜索到解决方案是修改 redis.conf 配置文件,把 stop-writes-on-bgsave-error 设置为 no 即可。结果在保存配置文件的时候报错,提示磁盘已满。

后来发现罪魁祸首原来是 Docker 日志,有一个日志文件居然占了 23GB!

$ du -h $(find /var/lib/docker/containers/ -name *-json.log)
164M    /var/lib/docker/containers/1111/1111-json.log
60M     /var/lib/docker/containers/2222/2222-json.log
100M    /var/lib/docker/containers/3333/3333-json.log
12M     /var/lib/docker/containers/4444/4444-json.log
52K     /var/lib/docker/containers/5555/5555-json.log
23G     /var/lib/docker/containers/6666/6666-json.log

解决

1、清空日志文件

$ cat /dev/null > /var/lib/docker/containers/xxx/xxx-json.log
直接删除日志文件不能立即释放磁盘空间

2、全局日志设置

Docker 支持全局日志设置,但只对之后新建的容器有效。

$ vim /etc/docker/daemon.json
{
    .
    .
    .
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "100m",
        "max-file": "3"
    }
}

背景

Docker 是一个非常棒的工具,能够让我们的开发工作更简单、更快、更高效。它的优点就不多说了,但是凡事都有两面性,对于 macOS 和 Windows 系统的用户来说,Docker 还是有一些问题的。最大的问题还是它在这两大操作系统上的网络和 I/O 性能瓶颈。这里我们就讲讲数据卷缓存方法,它们可以加速我们的 Docker 运行速度。

在使用 Docker 时,我们经常会用到数据卷,如共享代码、链接资源、提供备份等。数据卷有很多使用场景,它的 I/O 操作有时很轻,有时很重很密集。在 Linux 系统上,因为可以使用原生内核和系统资源,运行情况比在 macOS 和 Windows 下好很多。在默认配置中,数据卷始终确保主机和容器之间的数据一致性:如果您在主机或容器上保存一些内容,更改会立即反映在另一端,这样可以防止数据丢失。

Docker 提供了两种方法来缓存和加速数据卷,它们都牺牲了一些一致性,但是可以大幅提升运行速度。这两种方法就是:cached 和 delegated。下面是 Docker 官方文档介绍。

delegated

delegated 模式提供最弱保障。对于这类文件夹,容器对文件系统具有权威性。由容器执行的写操作可能不会立即反映在主机文件系统上。在一些场景下,如 NFS 异步模式,如果挂载了 delegated 模式数据卷的容器在运行中崩溃,写入的数据可能会丢失。

cached

cached 配置包含 delegated 配置的所有保障,并且还提供了容器写操作可见性的额外保障。因此,cached 模式通常可以提升频繁读操作场景时的性能,代价是在容器和主机之间牺牲一些临时的不一致性。

它们使用起来非常简单:

  • delegated-volume:/var/volume1:delegated
  • cached-volume:/var/volume2:cached

区别和选择

这两种模式的 I/O 操作都会比默认模式快很多,并且不会影响容器和主机之间的性能。问题是我们该使用哪一种?那当然要看场景了。文档虽然介绍的很清楚,但是没有提供任何实际应用案例。那我们就举例子说明一下。

案例一

我们有一个数据库容器(MySQL、Mongo 或者 PostgreSQL),我们把数据存储在数据卷上,这样容器重启的时候数据不会丢失,同时我们可以备份数据,这些主机操作都是只读的,因为我们不会在主机环境下去改变数据,所以我们不需要把主机的写操作同步到容器中。那么最好的选择就是 delegated 模式。容器能够写操作并且立刻看到结果,而主机方面就没那么重要了,因为它不会进行写操作,忽略它就好了。

案例二

当我们频繁的在主机上的修改代码,然后在容器里读取这些更改。在这个场景下,最好的选择是 cached。为什么?因为在容器方面大部分都是只读操作,而我们在主机上进行写操作。

总结

delegated 和 cached 的区别其实不是特别大,但是也需要注意。他们的性能都比 default 模式要好,但是 default 模式却是最安全的。鱼与熊掌不可兼得,就看怎么取舍了。如果你有什么更好的建议,可以在评论区留言。

PS:在 PHP 开发时,xdebug 也会影响性能,如果你觉得 Docker 很慢,不妨试试关闭 xdebug。

问题

Laravel Sail 环境下,运行 sail composer update 时报错:

  - Upgrading laravel/sail (v1.14.8 => v1.14.11): Extracting archive
    Update of laravel/sail failed

解决

删除 .env 里下面两行:

WWWGROUP=1000
WWWUSER=1000

重启容器后运行 sail composer update 就可以了。

更新成功以后别忘了把这两行再加回去。

分析

这两行是安装 sail 时自动加上去的,用来设置容器内执行权限。sail composer update 的时候因为权限不够导致更新失败。删除这两行以后获得 root 权限就可以正常更新了。

我的阿里云账号下有三台服务器:

深圳 可用区 D
A:Ubuntu 服务器,内网:172.28.119.31
B:Windows 服务器,内网:172.28.119.32

深圳 可用区 E
C:Ubuntu 服务器,内网:172.17.230.3

三台服务器在同一个 VPC 专有网络下,也在同一个安全组下。按照 阿里云官方说明,三台服务器是默认内网互通的。

问题

A 和 C 之间内网无法 ping 通,但是 A 和 C 内网都能 ping 通 B。

检查

查看 A 服务器路由表:

root@ubuntu-20220304:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.28.127.253  0.0.0.0         UG    100    0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.21.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-72f5d0a4f9cd
172.22.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-bf5ce375897e
172.28.112.0    0.0.0.0         255.255.240.0   U     0      0        0 eth0
172.28.127.253  0.0.0.0         255.255.255.255 UH    100    0        0 eth0

查看 C 服务器路由表:

root@ubuntu-20220616:~# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.239.253  0.0.0.0         UG    100    0        0 eth0
172.17.224.0    0.0.0.0         255.255.240.0   U     0      0        0 eth0
172.17.239.253  0.0.0.0         255.255.255.255 UH    100    0        0 eth0

发现 A 服务器路由表里 172.17.0.0 被 docker 占用,而 C 服务器 IP 正好是 172.17.230.3

查看 docker bridge 网络配置:

{
    "Name": "bridge",
    "Id": "fdd7ac1fee6e82486d42e53d8a3cce1164411b65de6a963bf5342aab019f7f7e",
    "Created": "2022-06-16T13:22:29.147640187+08:00",
    "Scope": "local",
    "Driver": "bridge",
    "EnableIPv6": false,
    "IPAM": {
        "Driver": "default",
        "Options": null,
        "Config": [
            {
                "Subnet": "172.17.0.0/16",
                "Gateway": "172.17.0.1"
            }
        ]
    },
    "Internal": false,
    "Attachable": false,
    "Ingress": false,
    "ConfigFrom": {
        "Network": ""
    },
    "ConfigOnly": false,
    "Containers": {},
    "Options": {
        "com.docker.network.bridge.default_bridge": "true",
        "com.docker.network.bridge.enable_icc": "true",
        "com.docker.network.bridge.enable_ip_masquerade": "true",
        "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
        "com.docker.network.bridge.name": "docker0",
        "com.docker.network.driver.mtu": "1500"
    },
    "Labels": {},
    "CreatedTime": 1655356949147
}

解决

方案一

添加 A 服务器路由:

$ route add -net 172.17.230.0/24 gw 172.28.127.253 dev eth0

这是临时方案,重启服务器会失效。

方案二

更换 C 服务器虚拟交换机,使用其他内网网段

方案三

更换 A 服务器 docker 的默认网段

编辑 /etc/docker/daemon.json(不存在则创建一个)

$ vim /etc/docker/daemon.json

添加以下代码:

{
    "bip": "172.20.0.1/16"
}

重启 docker 服务:

$ systemctl restart docker

参考:Configure the default bridge network

前言

作为程序员,一直以来的开发环境都是在虚拟机下。既可以标准化、又方便迁移,还有一点是不会弄脏弄乱原生系统。特别是用 Laravel 框架的时候,Homestead + Vagrant + VirtualBox 非常好用,一个 vagrant up 命令把所有依赖都装好,开箱即用。让我们把时间和精力都放在开发上。

不过用的时间长了,Homestead 也是有一定的弊端的。比如启动时间长、吃硬盘、耗资源,当时就想着能不能用 Docker 替代虚拟机。早期的时候 Laravel 官方还没有基于 Docker 的解决方案,不过有一些第三方的,怕不靠谱就懒得折腾了。而且 Homestead 本身确实比较优秀,用起来还是比较方便的,就一直拖下来了。现在官方出了 Sail,正好把之前几个项目都转到 Sail 环境下,把我的 MBP 的资源都释放出来。

网上很多类似迁移教程都是针对新项目的。本篇文章记录的是老项目从 Homestead 迁移到 Sail。老项目比新项目要复杂一些,如果你正好有这方面需求,希望这篇文章能够帮助到你。

我在迁移不同的项目过程中都会踩到不同的坑,解决方案我会补充在文章里。如果你碰到了问题,仔细看看文章里面有没有解决方案。如果没有,欢迎给我留言。

1. 安装 Docker

这个就不说了,很简单,网上教程一大堆。

2. 关闭 .env 里面的 redis 和 memcached 配置

FILESYSTEM_DRIVER=public
BROADCAST_DRIVER=log
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=memcached
SESSION_LIFETIME=120

改为:

FILESYSTEM_DRIVER=public
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

不关闭 redis 配置的话,后面执行 composer 命令的时候会因为没有 redis 服务而报错。

3. 安装 Sail

$ cd ./project
$ docker run --rm \
    -u "$(id -u):$(id -g)" \
    -v $(pwd):/var/www/html \
    -w /var/www/html \
    laravelsail/php81-composer:latest \
    composer require laravel/sail --dev

离开了 Homestead 环境,我们假设原生系统下没有安装 php 和 composer「至少我的是这样」。所以要通过 composer 去安装 Sail 就成了难题了。难不成为了装 Sail 再装一套 php + composer?那就与 Docker 精神背道而驰了。细心的 Laravel 团队肯定也想到了这一点,所以提供了一个只有 php + composer 的小容器 laravelsail/phpXX-composer 给我们来安装 Sail。

这个 phpXX 是该项目下对应的 php 版本,比如 74、80、81。例子里我们就用 81 来演示。

当然,你可以在以前的 Homestead 环境下执行 composer require laravel/sail --dev,完全没有问题,效果是一样的。

如果在执行命令的时候有什么组件因为 php 扩展的问题报错,可以先在 composer.json 里把报错的组件删掉,等 Sail 搭好了再重新安装。因为毕竟这是一个简化版的镜像,不会象 Sail 开发环境那样启用很多扩展,它的目的只是安装 Sail。

4. 生成 docker-compose.yml

$ docker run --rm \
    -u "$(id -u):$(id -g)" \
    -v $(pwd):/var/www/html \
    -w /var/www/html \
    laravelsail/php81-composer:latest \
    php artisan sail:install --with=mysql,redis,memcached

上一步已经把 Sail 安装好了,现在就是要生成 docker-compose.yml 编排文件。--with 后面跟的是这个项目下面需要的组件。支持的有:

  • mysql
  • pgsql
  • mariadb
  • redis
  • memcached
  • meilisearch
  • minio
  • selenium
  • mailhog

点击参阅官方说明

只要把需要的组件加在 with 后面,就会自动编排到配置文件里面。演示中我添加了 mysqlredismemcached

如果用到了 VSCodeRemote - Containers 来管理开发环境,可以在 --with=XXX 后面加上 --devcontainer,这样还会额外生成 VSCode 需要的 devcontainer.json 文件。

完成这步操作以后就可以恢复 .env 里的 redis 和 memcached 了。

其实到这一步 Sail 就算安装好了,可以运行 ./vendor/bin/sail up。后面的操作是一些优化步骤。

5. 修改 Dockerfile

$ cp -r ./vendor/laravel/sail/runtimes ./docker

因为一些众所周知的原因,我们需要对 Dockerfile 进行一些修改和优化,而 Dockerfile 文件原本在 ./vendor/laravel/sail/runtimes/X.X 下面「又是 XX」。Laravel 的约定是不能直接修改 ./vendor/ 下的任何文件,因为一更新就没啦,所以我们需要把它复制出来。

./vendor/laravel/sail/runtimes 整个文件夹复制一份放在项目根目录下,文件夹名字改为 dockerdocker 下面有 7.48.08.1 三个子文件夹,里面的文件都一样,用哪个版本的 php 就修改相应文件夹下面的文件。咱们还是用 8.1 来演示。

Dockerfile 用来创建一个容器
docker-compose.yml 用来创建一堆容器,并且按照一定的网络和依赖关系组合起来
$ vim ./docker/8.1/Dockerfile
FROM ubuntu:22.04

LABEL maintainer="Taylor Otwell"

ARG WWWGROUP
ARG NODE_VERSION=16
ARG POSTGRES_VERSION=14

WORKDIR /var/www/html

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=UTC

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN apt-get update \
.
.
.

改为:

FROM ubuntu:20.04

LABEL maintainer="Taylor Otwell"

ARG WWWGROUP
ARG NODE_VERSION=16
ARG POSTGRES_VERSION=14

WORKDIR /var/www/html

ENV DEBIAN_FRONTEND noninteractive
ENV TZ=Asia/Shanghai

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

ADD sources.list /etc/apt/

RUN apt-get update \
.
.
.
  • FROM ubuntu:22.04 => FROM ubuntu:20.04 我比较习惯 20.04,这个改不改随个人喜好。改成 20.04 以后,该文件中的 jammy 全部要改成 focal
  • ENV TZ=UTC => ENV TZ=Asia/Shanghai 我是中国人🇨🇳
  • ADD sources.list /etc/apt/ 给容器启动的时候添加国内镜像源

6. 设置镜像源

$ vim ./docker/8.1/sources.list
# 默认注释了源码仓库,如有需要可自行取消注释
deb http://mirrors.ustc.edu.cn/ubuntu/ focal main restricted universe multiverse
# deb-src http://mirrors.ustc.edu.cn/ubuntu/ focal main restricted universe multiverse

deb http://mirrors.ustc.edu.cn/ubuntu/ focal-security main restricted universe multiverse
# deb-src http://mirrors.ustc.edu.cn/ubuntu/ focal-security main restricted universe multiverse

deb http://mirrors.ustc.edu.cn/ubuntu/ focal-updates main restricted universe multiverse
# deb-src http://mirrors.ustc.edu.cn/ubuntu/ focal-updates main restricted universe multiverse

deb http://mirrors.ustc.edu.cn/ubuntu/ focal-backports main restricted universe multiverse
# deb-src http://mirrors.ustc.edu.cn/ubuntu/ focal-backports main restricted universe multiverse

# 预发布软件源,不建议启用
# deb http://mirrors.ustc.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse
# deb-src http://mirrors.ustc.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse

几点说明:

  • 这里我用了中科大的源,用什么源也是个人喜好,只不过我一开始用阿里云的源的时候一直超时,换中科大的就没问题。
  • 不管什么源,只能用 http,不能用 https,会报下面的错误:

    Certificate verification failed: The certificate is NOT trusted. The certificate issuer is unknown.  Could not handshake: Error in the certificate verification.
  • 重复一遍,20.04focal22.04jammy

7. 修改 docker-compose.yml

$ vim ./docker-compose.yml
# For more information: https://laravel.com/docs/sail
version: '3'
services:
    laravel.test:
        build:
            context: ./vendor/laravel/sail/runtimes/8.1
.
.
.

改为:

# For more information: https://laravel.com/docs/sail
version: '3'
services:
    laravel.test:
        build:
            context: ./docker/8.1
.
.
.

前面选了 8.1,这里就写 8.1

8. 启航!

$ ./vendor/bin/sail up

后记

第一次 sail up 生成镜像的时候还是挺耗时的,需要耐心等待一段时间。这个时候网络环境特别重要,这就是为什么要那么费劲添加国内镜像源了。

如果卡在下面这个地方一直不动:

#0 192.1 gpg: keybox '/root/.gnupg/pubring.kbx' created

解决步骤:

  1. 打开第 5 步里的 Dockerfile
  2. 找到 hkp://keyserver.ubuntu.com:80
  3. 改为 hkp://keyserver.ubuntu.com