标签归档:docker

镜像操作神器 skopeo 用法总结

skopeo 是一个命令行工具,可对容器镜像和容器存储进行操作。 在没有dockerd的环境下,使用 skopeo 操作镜像是非常方便的。

安装

包管理器

# RHEL / CentOS Stream ≥ 8
sudo dnf install skopeo

# RHEL/CentOS ≤ 7.x
yum install skopeo

# openSUSE:
sudo zypper install skopeo

# alpine:
sudo apk add skopeo

# macOS:
brew install skopeo

# ArchLinux
sudo pacman -S skopeo

其他系统见 安装文档

编译安装

git clone https://github.com/containers/skopeo skopeo
cd !$
git checkout v1.0.0
make binary-static DISABLE_CGO=1
cp skopeo /usr/local/bin/

使用

在使用 skopeo 之前,我们首先要知道在命令行中镜像的格式

需要注意的是,这几种镜像的名字,对应着镜像存在的方式,不同存在的方式对镜像的 layer 处理的方式也不一样,比如 docker:// 这种方式是存在 registry 上的,docker-daemon: 是存在本地 docker pull 下来的,再比如 docker-archive 是通过 docker save 出来的镜像。同一个镜像有这几种存在的方式就像水有气体、液体、固体一样。可以这样去理解,他们表述的都是同一个镜像,只不过是存在的方式不一样而已。

IMAGE NAMES example
containers-storage: containers-storage:
dir: dir:/PATH
docker:// docker://k8s.gcr.io/kube-apiserver:v1.17.5
docker-daemon: docker-daemon:alpine:latest
docker-archive: docker-archive:alpine.tar (docker save)
oci: oci:alpine:latest

命令选项

# skopeo --help
NAME:
   skopeo - Various operations with container images and container image registries

USAGE:
   skopeo [global options] command [command options] [arguments...]

VERSION:
   0.1.37

COMMANDS:
     copy               Copy an IMAGE-NAME from one location to another
     inspect            Inspect image IMAGE-NAME
     delete             Delete image IMAGE-NAME
     manifest-digest    Compute a manifest digest of a file
     standalone-sign    Create a signature using local files
     standalone-verify  Verify a signature using local files
     help, h            Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --debug                  enable debug output
   --policy value           Path to a trust policy file
   --insecure-policy        run the tool without any policy check
   --registries.d DIR       use registry configuration files in DIR (e.g. for container signature storage)
   --override-arch ARCH     use ARCH instead of the architecture of the machine for choosing images
   --override-os OS         use OS instead of the running OS for choosing images
   --command-timeout value  timeout for the command execution (default: 0s)
   --help, -h               show help
   --version, -v            print the version

可以看到 skopeo 的功能很简单:

  • copy:复制一个镜像从 A 到 B,这里的 A 和 B 可以为本地 docker 镜像或者 registry 上的镜像。
  • inspect:查看一个镜像的 manifest 火车 image config 详细信息
  • delete:删除一个镜像,可以是本地 docker 镜像或者 registry 上的镜像
  • list-tags:列出一个 registry 上某个镜像的所有 tag
  • login:登录到某个 registry,和 docker login 类似
  • logout: 退出已经登录到某个 registry 的 auth 信息,和 docker logout 类似
  • manifest-digeststandalone-signstandalone-verify 这三个用的不多
  • sync:同步一个镜像从 A 到 B,感觉和 copy 一样,但 sync 支持的参数更多,功能更强大

获取镜像的信息

skopeo 可以在不用下载镜像的情况下,获取镜像信息

# skopeo inspect docker://docker.io/centos
{
    "Name": "docker.io/library/centos",
    "Digest": "sha256:fe8d824220415eed5477b63addf40fb06c3b049404242b31982106ac204f6700",
    "RepoTags": [
        "5.11",
        "5",
        "6.10",
        "6.6",
        "6.7",
        "6.8",
        "6.9",
        "6",
        "7.0.1406",
        "7.1.1503",
        "7.2.1511",
        "7.3.1611",
        "7.4.1708",
        "7.5.1804",
        "7.6.1810",
        "7.7.1908",
        "7",
        "8.1.1911",
        "8",
        "centos5.11",
        "centos5",
        "centos6.10",
        "centos6.6",
        "centos6.7",
        "centos6.8",
        "centos6.9",
        "centos6",
        "centos7.0.1406",
        "centos7.1.1503",
        "centos7.2.1511",
        "centos7.3.1611",
        "centos7.4.1708",
        "centos7.5.1804",
        "centos7.6.1810",
        "centos7.7.1908",
        "centos7",
        "centos8.1.1911",
        "centos8",
        "latest"
    ],
    "Created": "2020-01-18T00:26:46.850750902Z",
    "DockerVersion": "18.06.1-ce",
    "Labels": {
        "org.label-schema.build-date": "20200114",
        "org.label-schema.license": "GPLv2",
        "org.label-schema.name": "CentOS Base Image",
        "org.label-schema.schema-version": "1.0",
        "org.label-schema.vendor": "CentOS",
        "org.opencontainers.image.created": "2020-01-14 00:00:00-08:00",
        "org.opencontainers.image.licenses": "GPL-2.0-only",
        "org.opencontainers.image.title": "CentOS Base Image",
        "org.opencontainers.image.vendor": "CentOS"
    },
    "Architecture": "amd64",
    "Os": "linux",
    "Layers": [
        "sha256:8a29a15cefaeccf6545f7ecf11298f9672d2f0cdaf9e357a95133ac3ad3e1f07"
    ]
}

docker://: 是使用 Docker Registry HTTP API V2 进行连接远端 docker.io: 远程仓库 centos: 镜像名称

也可以获取本地dockerd的镜像信息

# skopeo inspect docker-daemon:ubuntu:latest
{
    "Name": "docker.io/library/ubuntu",
    "Digest": "sha256:004d05bd520c5c386fb8322d6081f4de567a9417e1ba8e5071663d7811992c88",
    "RepoTags": [],
    "Created": "2020-03-20T19:20:22.835345724Z",
    "DockerVersion": "18.09.7",
    "Labels": null,
    "Architecture": "amd64",
    "Os": "linux",
    "Layers": [
        "sha256:c8be1b8f4d60d99c281fc2db75e0f56df42a83ad2f0b091621ce19357e19d853",
        "sha256:977183d4e9995d9cd5ffdfc0f29e911ec9de777bcb0f507895daa1068477f76f",
        "sha256:6597da2e2e52f4d438ad49a14ca79324f130a9ea08745505aa174a8db51cb79d",
        "sha256:16542a8fc3be1bfaff6ed1daa7922e7c3b47b6c3a8d98b7fca58b9517bb99b75"
    ]
}

docker-daemon: docker守护镜像的镜像 ubuntu:latest: 本地镜像的名称

拷贝镜像

注意一下,这里的 location 就是指的上面提到的 IMAGE NAMES ,也就是说 skopeo copy src dest 可以有 6*6=36 种组合!比如我可以将一个镜像从一个 registry 复制到另一个 registry,skopeo copy docker://IMAGE_NAME docker://IMAGE_NAME,再强调一遍,一定要注意 IMAGE_NAME 的命名的格式。

在不使用 docker 的情况下从远端下载镜像

# skopeo --insecure-policy copy docker://nginx:1.17.6 docker-archive:/tmp/nginx.tar
Getting image source signatures
Copying blob 8ec398bc0356 done
Copying blob 465560073b6f done
Copying blob f473f9fd0a8c done
Copying config f7bb5701a3 done
Writing manifest to image destination
Storing signatures
# ls -alh  /tmp/nginx.tar 
-rw-r--r-- 1 root root 125M 4月  13 15:22 /tmp/nginx.tar

--insecure-policy: 用于忽略安全策略配置文件 docker://nginx:1.17.6: 该命令将会直接通过 http 下载目标镜像 docker-archive: 存储为 /tmp/nginx.tar,此文件可以直接通过 docker load 命令导入

相应的,可以将下载的文件导入到本地

# skopeo copy docker-archive:/tmp/nginx.tar docker-daemon:nginx:latest
Getting image source signatures
Copying blob 556c5fb0d91b done
Copying blob 49434cc20e95 done
Copying blob 75248c0d5438 done
Copying config f7bb5701a3 done
Writing manifest to image destination
Storing signatures

# docker images nginx
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx               latest              f7bb5701a33c        3 months ago        126MB

也可以将镜像下载到指定目录

# skopeo copy docker://busybox:latest dir:/tmp/busybox
Getting image source signatures
Copying blob 0669b0daf1fb done
Copying config 83aa35aa1c done
Writing manifest to image destination
Storing signatures

# ls -alh /tmp/busybox/
总用量 760K
drwxr-xr-x   2 root root  186 4月  13 15:26 .
drwxrwxrwt. 12 root root 4.0K 4月  13 15:25 ..
-rw-r--r--   1 root root 743K 4月  13 15:26 0669b0daf1fba90642d105f3bc2c94365c5282155a33cc65ac946347a90d90d1
-rw-r--r--   1 root root 1.5K 4月  13 15:26 83aa35aa1c79e4b6957e018da6e322bfca92bf3b4696a211b42502543c242d6f
-rw-r--r--   1 root root  527 4月  13 15:26 manifest.json
-rw-r--r--   1 root root   33 4月  13 15:25 version

或者从指定目录导入到本地

# skopeo copy dir:/tmp/busybox docker-daemon:busybox:latest
Getting image source signatures
Copying blob 0669b0daf1fb done
Copying config 83aa35aa1c done
Writing manifest to image destination
Storing signatures
# docker images busybox
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
busybox             latest              83aa35aa1c79        4 weeks ago         1.22MB

其他命令

skopeo --debug copy docker-archive:/dev/stdin containers-storage:foo < /tmp/nginx.tar

删除镜像

skopeo delete docker://localhost:5000/nginx:latest

认证文件

认证文件默认存放在 $HOME/.docker/config.json

文件内容

{
    "auths": {
        "myregistrydomain.com:5000": {
            "auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk",
            "email": "stuf@ex.cm"
        }
    }
}

其他参数

# 跳过源地址的 tls
skopeo copy docker://192.168.25.8:5000/library/registry:latest  oci-archive:registry-latest.tar --src-tls-verify=false

# 跳过验证 tls
skopeo list-tags --tls-verify=false docker://dockerio.vkvmsg1.skybyte.me/library/registry
# 注意这里 dockerio 的顶级镜像自行补全 library 前缀

References

#Docker #Containerd #Kubernetes

Ubuntu 安装指定版本 docker

如果你过去安装过 docker,先删掉:

sudo apt-get remove docker docker-engine docker.io containerd runc

首先安装依赖:

sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common

信任 Docker 的 GPG 公钥并添加仓库: 发行版

sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirror.nju.edu.cn/docker-ce/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

安装最新

sudo apt-get update
sudo apt-get install docker-ce

安装指定版本

首选查看都有哪些版本可选

apt-cache madison docker-ce | awk '{ print $3 }'

选择版本并安装

# 以 5:20.10.13~3-0~ubuntu-jammy 为例
VERSION_STRING=5:20.10.13~3-0~ubuntu-jammy

sudo apt-get install docker-ce=$VERSION_STRING docker-ce-cli=$VERSION_STRING containerd.io docker-buildx-plugin docker-compose-plugin

Reference

检查当前终端是不是 Docker

要确定当前运行的终端是否在 Docker 容器内,可以检查容器特定的文件和环境变量。以下是一种方法,通过查看 /proc/1/cgroup 文件来判断:

cat /proc/1/cgroup

观察输出的内容。如果您看到与 Docker 相关的内容(如 docker 或 containerd),则表示您当前正在 Docker 容器内运行。例如:

12:memory:/docker/9a3c3b3d3e3c413dc3f3e3g3h3i3j3k3l3m
11:devices:/docker/9a3c3b3d3e3c413dc3f3e3g3h3i3j3k3l3m
...

如果输出中没有提到 Docker 或类似的容器技术,那么您可能不在 Docker 容器内运行。

请注意,这种方法主要适用于 Docker 容器,而不一定适用于其他容器技术(如 Podman、LXC 等)。如果您使用的是其他容器技术,可能需要查找特定于该技术的文件或环境变量。

查看 Docker cp 进度

docker cp 命令本身不提供进度查看功能。但是,您可以使用第三方工具,如 pv(Pipe Viewer)在执行 docker cp 时查看进度。以下是使用 pv 查看 docker cp 进度的步骤:

首先,安装 pv。根据您的操作系统,使用相应的包管理器安装 pv。

# Ubuntu/Debian:
sudo apt-get install pv

# CentOS/RHEL:
sudo yum install pv

# macOS(使用 Homebrew):
brew install pv

容器到主机

要将文件从容器复制到主机并查看进度,请执行以下命令:

docker exec 
<container_name> tar -cf - <source_path_inside_container> | pv | tar -xf - -C <destination_path_on_host>

替换为容器名称, 替换为容器内的源文件/目录路径, 替换为主机上的目标文件/目录路径。 例如,从名为 my_container 的容器复制 /data 目录到主机上的 /tmp/data 目录: “` docker exec my_container tar -cf – /data | pv | tar -xf – -C /tmp/data “` pv 将显示一个进度条,以及已传输、剩余和总字节数。 ## 主机到容器 如果要从主机复制到容器并查看进度,可以使用以下命令: “` tar -cf – | pv | docker exec -i tar -xf – -C “` 将 ` ` 替换为主机上的源文件/目录路径,` ` 替换为容器名称,“ 替换为容器内的目标文件/目录路径。 例如,从主机上的 /tmp/data 目录复制到名为 my_container 的容器中的 /data 目录: “` tar -cf – /tmp/data | pv | docker exec -i my_container tar -xf – -C /data “` 这个命令会将主机上的文件/目录通过 tar 命令打包,然后通过 pv 查看进度,并通过管道将数据发送到容器中。在容器中,tar 命令会将数据解包到指定的目录。pv 将显示一个进度条,以及已传输、剩余和总字节数。

迁移 Docker volume 到本地挂载点

要将 Docker volume 迁移到本地挂载点,您需要执行以下步骤:

  1. 首先,确保已停止正在使用此 volume 的所有容器。您可以使用以下命令停止容器(将 替换为您的容器名称): “` docker stop
1. 使用 docker cp 命令将 volume 的内容复制到本地文件系统。首先,创建一个临时容器,将要迁移的 volume 挂载到该容器:    ```
docker run -d --name temp_container -v 
<volume_name>:/volume_data busybox tail -f /dev/null

替换为要迁移的 volume 的名称。 1. 使用 docker cp 命令将 volume 数据从临时容器复制到本地目录: “` docker cp temp_container:/volume_data “` 将 替换为要将数据复制到的本地目录。 1. 删除临时容器: “` docker rm -f temp_container “` 1. 更新您的 docker run 命令或 docker-compose.yml 文件,将原来的 volume 更改为本地挂载点。例如,如果您以前的 docker run 命令如下: “` docker run -d –name my_container -v :/data my_image “` 将其更改为: “` docker run -d –name my_container -v :/data my_image “` 将 替换为您在步骤 3 中使用的本地目录。 1. 启动更新后的容器,并验证数据是否已成功迁移到本地挂载点。 请注意,根据 volume 的大小和复杂性,迁移过程可能会花费一定时间。在迁移期间,请确保不要对 volume 进行修改以避免数据不一致。 “`

Dockerfile 禁止弹出 apt 安装交互视图

在 Dockerfile 中执行 apt 安装时,有些软件包可能会在安装过程中弹出交互式视图,例如询问用户是否接受软件包许可证或配置软件包参数等。在 Docker 构建过程中,由于无法进行交互式操作,这些视图可能会导致构建失败或出现不可预期的结果。为避免这些问题,可以通过以下方法避免在 Docker 构建过程中弹出交互式视图:

在 apt-get 命令中使用 -y 参数,该参数表示自动回答“yes”所有询问,例如:

RUN apt-get update && apt-get install -y package-name

在这个例子中,-y 参数告诉 apt-get 自动回答所有询问为“yes”,不需要手动操作确认。

在 Dockerfile 中设置环境变量 DEBIAN_FRONTEND 为 noninteractive,例如:

ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y package-name

在这个例子中,将 DEBIAN_FRONTEND 设置为 noninteractive,表示不需要交互式界面。

通过这些方法,可以在 Docker 构建过程中避免 apt 安装弹出交互式视图,从而确保构建过程的顺利进行。需要注意的是,自动回答所有询问可能会带来一定的安全风险,应该根据实际需求选择合适的方案。

部署 Prometheus 主机监控完全体

监控完全体 = Prometheus + Node Exporter + cadvisor + grafana

Prometheus 作为轮转数据库,从各个服务器采集数据; node-exporter 作为数据采集器,接受 prometheus 采集请求,上报主机各项参数; vadvisor 作为容器数据采集器,接受 prometheus 采集请求,上报主机 docker 各项参数; grafana 作为看板,从 prometheus 查询数据做数据可视化。

部署 Promoetheus

准备配置文件 /data/docker/prometheus/prometheus.yml

global:
  scrape_interval:     15s # By default, scrape targets every 15 seconds.

  # Attach these labels to any time series or alerts when communicating with
  # external systems (federation, remote storage, Alertmanager).
  external_labels:
    monitor: 'codelab-monitor'

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=
<job_name>` to any timeseries scraped from this config.
  - job_name: 'prometheus'

    # Override the global default and scrape targets from this job every 5 seconds.
    scrape_interval: 5s

    static_configs:
      - targets: ['localhost:9090']

  # 采集node exporter监控数据
  - job_name: 'vps2'
    static_configs:
      - targets: ['10.28.0.1:9100']

准备数据文件夹,并设置访问权限为 nobody

# 创建文件夹
mkdir -p /data/docker/prometheus/data

# 取得nobody的uid
docker run --rm quay.io/prometheus/busybox cat /etc/passwd
···
nobody:x:65534:65534:nobody:/home:/bin/false

# 配置文件夹权限
chown 65534:65534 -R /data/docker/prometheus/data

启动 prometheus

$ docker run -d \
    --name=prometheus \
    -p 9090:9090 \
    --network=myDefault \
    --restart always \
    --volume /data/docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
    --volume /data/docker/prometheus/data:/prometheus \
    prom/prometheus:v2.40.4 \
    --web.enable-lifecycle \
    --config.file=/etc/prometheus/prometheus.yml \
    --storage.tsdb.path=/prometheus \
    --storage.tsdb.retention=365d

$ docker run -d \
    --name=prometheus \
    -p 9090:9090 \
    --network=myDefault \
    --restart always \
    --volume /data/docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
    --volume /data/docker/prometheus/data:/prometheus \
    prom/prometheus:v2.40.4 \
    --web.enable-lifecycle \
    --config.file=/etc/prometheus/prometheus.yml \
    --storage.tsdb.path=/prometheus \
    --storage.tsdb.retention=365d
# --web.enable-lifecycle 允许热加载配置文件
# 注意放在镜像名后面的内容为映射到内部的配置标志

部署 Node Exporter 采集主机数据

可采用编译安装或包管理器直接安装。

编译安装

# x86
$ curl -OL https://github.com/prometheus/node_exporter/releases/download/v1.5.0/node_exporter-1.5.0.linux-amd64.tar.gz
$ tar -xzf node_exporter-1.5.0.linux-amd64.tar.gz
$ cp node_exporter-1.5.0.linux-amd64/node_exporter /usr/local/bin/
# arm64
$ curl -OL https://github.com/prometheus/node_exporter/releases/download/v1.5.0/node_exporter-1.5.0.linux-arm64.tar.gz
$ tar -xzf node_exporter-1.5.0.linux-arm64.tar.gz
$ cp node_exporter-1.5.0.linux-arm64/node_exporter /usr/local/bin/

$ sudo useradd -rs /bin/false nodeusr

$ echo "[Unit]
Description=Node Exporter
After=network.target

[Service]
User=nodeusr
Group=nodeusr
Type=simple
ExecStart=/usr/local/bin/node_exporter

[Install]
WantedBy=multi-user.target" > /etc/systemd/system/node_exporter.service

$ sudo systemctl daemon-reload
$ sudo systemctl enable node_exporter && systemctl start node_exporter

Ubuntu 等直接安装:

$ apt search node_exporter
Sorting... Done
Full Text Search... Done
prometheus-node-exporter-collectors/focal 0+git20200110.fc91c86-1 all
  Supplemental textfile collector scripts for Prometheus node_exporter

$ apt install -y prometheus-node-exporter-collectors

# 启动服务
$ systemctl enable prometheus-node-exporter.service && systemctl start prometheus-node-exporter.service
Synchronizing state of prometheus-node-exporter.service with SysV service script with /lib/systemd/systemd-sysv-install.
Executing: /lib/systemd/systemd-sysv-install enable prometheus-node-exporter

# OpenWRT
opkg update
opkg install prometheus-node-exporter-lua \
prometheus-node-exporter-lua-nat_traffic \
prometheus-node-exporter-lua-netstat \
prometheus-node-exporter-lua-openwrt \
prometheus-node-exporter-lua-wifi \
prometheus-node-exporter-lua-wifi_stations

curl localhost:9100/metrics

cat /etc/config/prometheus-node-exporter-lua
config prometheus-node-exporter-lua 'main'
        option listen_ipv6 '0'
        option listen_port '9100'
        option listen_interface 'lan'

/etc/init.d/prometheus-node-exporter-lua restart

重载 prometheus 配置文件

# 方法一  使用 SIGHUP 信号
kill -HUP $(pidof prometheus)
# 方法二 使用 POST 请求,需 --web.enable-lifecycle
curl -X POST http://localhost:9090/-/reload
# 实测两种方法在 Docker 下均不生效,猜测外部修改必须重启才能映射入容器,原因待查

部署 cadvisor

# 这里使用了我在 docker hub 镜像的官方 gcr 源镜像
# 避免无法拉取问题
# 普通 docker hub 镜像即可拉取
$ sudo docker run \
  --restart always \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  --privileged \
  --device=/dev/kmsg \
  --net=myDefault \
  songtianlun/cadvisor:v0.46.0
$ curl http://localhost:8080

部署 grafana

docker run -d \
    --name=grafana \
    -p 3000:3000 \
    --network=myDefault \
    --restart always \
    grafana/grafana

默认帐号: admin/admin

看板模版在这里找: https://grafana.com/grafana/dashboards/

参考文献

Juice FS 初探 | 一种为 VPS 提供无限磁盘空间的解决方案

JuiceFS 是一款面向云原生设计的高性能分布式文件系统,在 Apache 2.0 开源协议下发布。提供完备的 POSIX 兼容性,可将几乎所有对象存储接入本地作为海量本地磁盘使用,亦可同时在跨平台、跨地区的不同主机上挂载读写。

使用 JunicsFS 将云厂商的 S3 对象存储挂载到本地,就得到一个几乎无限容量的 VPS 空间了。目前 Juice 支持大部份主流厂商提供的 s3 服务,具体请查阅官方文档。

本文以 腾讯云 COS + 腾讯云轻量服务器,演示一下基本使用。

挂载 COS 到本地

使用以下命令即可创建一个基于 COS 的文件系统,下面演示基于 sqlite 和 redis 的创建、挂载、卸载命令。

# Jfs With Redis
juicefs format \
    --storage cos \
    --bucket jfs-redis-******** \
    --access-key ******** \
    --secret-key ******** \
    "redis://127.0.0.1:6379/1" \
    jfs-redis
# 挂载
juicefs mount -d "redis://127.0.0.1:6379/1" /mnt/jfs-redis/
# 卸载
juicefs umount /mnt/jfs-redis/

# Jfs With sqlite
juicefs format \
    --storage cos \
    --bucket jfs-******** \
    --access-key ******** \
    --secret-key ******** \
    "sqlite3:///opt/jfs/jfs.db" \
    jfs
# 挂载
juicefs mount -d "sqlite3:///opt/jfs/jfs.db" /mnt/jfs/
# 卸载
juicefs umount /mnt/jfs/

自动挂载

具体使用时,可以配置一下自动挂载,方法如下。

首先创建一个从 /sbin/mount.juicefsjuicefs 可执行文件的软链接,操作系统解析 fstab 时会调用 /sbin/mount.juicefs 命令。

$ which juicefs
/usr/local/bin/juicefs
$ ln -s /usr/local/bin/juicefs /sbin/mount.juicefs

新增以下内容到 /etc/fstab 使得开机自动挂载,这里以上文 sqlite 为例:

sqlite3:///opt/jfs/jfs.db    /mnt/jfs       juicefs     _netdev,max-uploads=50,writeback,cache-size=204800     0  0

使用 mount -a 使配置生效

限制容量和文件数

没有限制的行为可想而知,JuicsFS 的默认限制较高,可以手动限制一下文件系统的容量和文件数量。

# 限制文件系统容量 (GiB)
$ juicefs config "sqlite3:///opt/jfs/jfs.db" --capacity 102400
# 限制文件数量 (inode 数)
$ juicefs config "sqlite3:///opt/jfs/jfs.db" --inodes 100000

限制容量举例,可以看到设定前后可以看到挂载点容量的变化:

# 示例
$ df -h | grep jfs
Filesystem      Size  Used Avail Use% Mounted on
JuiceFS:jfs     1.0P  8.0K  1.0P   1% /mnt/jfs

# 设定容量上限为 128 GiB
$ juicefs config "sqlite3:///opt/jfs/jfs.db" --capacity 128
2022/11/20 21:07:08.832094 juicefs[2253158] 
<INFO>: Meta address: sqlite3:///opt/jfs/jfs.db [interface.go:402]
  capacity: 0 GiB -> 128 GiB

# 再次查看发现大小为 128GiB
$ df -h | grep jfs
JuiceFS:jfs     128G  8.0K  128G   1% /mnt/jfs

限制文件 inodes 数量举例,可以看到设定前后可以看到挂载点容量的变化:

# 示例
$ df -i
Filesystem       Inodes  IUsed    IFree IUse% Mounted on
/dev/vda2       3901440 367127  3534313   10% /
JuiceFS:jfs    10485762      2 10485760    1% /mnt/jfs

$ juicefs config "sqlite3:///opt/jfs/jfs.db" --inodes 3901440
2022/11/20 21:13:30.977616 juicefs[2255902] 
<INFO>: Meta address: sqlite3:///opt/jfs/jfs.db [interface.go:402]
    inodes: 0 -> 3901440
$ df -i
Filesystem       Inodes  IUsed    IFree IUse% Mounted on
/dev/vda2       3901440 367128  3534312   10% /
JuiceFS:jfs     3901440      2  3901438    1% /mnt/jfs

性能测试

文件系统怎么能没有性能测试呢,下面分别使用 dd 和自带 bench 演示性能。

dd 简单读写测试

# 本地文件系统 io 性能
$ sync; dd if=/dev/zero of=/tmp/tempfile-12138 bs=1M count=1024; sync
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 5.563 s, 193 MB/s

# JuicfFS sqlite 元数据驱动性能
$ sync; dd if=/dev/zero of=/mnt/jfs/tmpfile bs=1M count=1024; sync
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 6.97672 s, 154 MB/s

# JuicfFS redis 元数据驱动性能
$ sync; dd if=/dev/zero of=/mnt/jfs-redis/tmpfile-12138 bs=1M count=1024; sync
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 5.59675 s, 192 MB/s

juicefs bench 测试

本地文件系统成绩

# juicefs bench -p 4 /tmp/
  Write big blocks count: 4096 / 4096 [==============================================================]  done
   Read big blocks count: 4096 / 4096 [==============================================================]  done
Write small blocks count: 400 / 400 [==============================================================]  done
 Read small blocks count: 400 / 400 [==============================================================]  done
  Stat small files count: 400 / 400 [==============================================================]  done
Benchmark finished!
BlockSize: 1 MiB, BigFileSize: 1024 MiB, SmallFileSize: 128 KiB, SmallFileCount: 100, NumThreads: 4
+------------------+------------------+--------------+
|       ITEM       |       VALUE      |     COST     |
+------------------+------------------+--------------+
|   Write big file |     153.98 MiB/s | 26.60 s/file |
|    Read big file |     148.60 MiB/s | 27.56 s/file |
| Write small file |   2064.9 files/s | 1.94 ms/file |
|  Read small file |   3150.8 files/s | 1.27 ms/file |
|        Stat file | 111847.4 files/s | 0.04 ms/file |
+------------------+------------------+--------------+

juicefs + sqlite 成绩

$ juicefs bench -p 4 /mnt/jfs
  Write big blocks count: 4096 / 4096 [==============================================================]  done
   Read big blocks count: 4096 / 4096 [==============================================================]  done
Write small blocks count: 400 / 400 [==============================================================]  done
 Read small blocks count: 400 / 400 [==============================================================]  done
  Stat small files count: 400 / 400 [==============================================================]  done
Benchmark finished!
BlockSize: 1 MiB, BigFileSize: 1024 MiB, SmallFileSize: 128 KiB, SmallFileCount: 100, NumThreads: 4
Time used: 72.0 s, CPU: 35.5%, Memory: 704.1 MiB
+------------------+------------------+---------------+
|       ITEM       |       VALUE      |      COST     |
+------------------+------------------+---------------+
|   Write big file |     148.25 MiB/s |  27.63 s/file |
|    Read big file |     144.29 MiB/s |  28.39 s/file |
| Write small file |     40.5 files/s | 98.83 ms/file |
|  Read small file |    715.9 files/s |  5.59 ms/file |
|        Stat file |   3759.0 files/s |  1.06 ms/file |
|   FUSE operation | 71735 operations |    2.99 ms/op |
|      Update meta |  4773 operations |   27.18 ms/op |
|       Put object |  1424 operations |  443.33 ms/op |
|       Get object |     0 operations |    0.00 ms/op |
|    Delete object |     0 operations |    0.00 ms/op |
| Write into cache |  1424 operations |  281.82 ms/op |
|  Read from cache |  1428 operations |  556.68 ms/op |
+------------------+------------------+---------------+

juicefs + redis 成绩

试着跑了一次,结果跑崩了,想玩的自己跑一跑吧。

由于 redis 是内存数据库,跑这种没有上限的测试一定要谨慎。在实际使用中,也要根据自己的需要选择,否则机器很容易 gg。

垃圾清理

juicefs 默认有回收站机制,删除文件默认在回收站保留一天。

可以去挂载目录下执行这条命令彻底删除:

$ find .trash -name '*.tmp' | xargs rm -f

总结

本文介绍了 JuiceFS 的基本用法,为“大盘鸡”需求提供一种新的思路,展示了使用对象存储挂载到机器作为文件系统的基本效果。

目前看来是解决系统盘过小问题的好方案,但具体是不是采纳这种方案,等我明天看看账单再做决定。

第二天看了下账单,跑了大概 10 轮测试,账单 0.02¥ ,初步看还能接受:

至于元数据引擎的选择,在单节点服务器的需求上我还是偏向 sqlite 或 mysql 集群的方案,redis 虽然性能强劲,但实在有点吃不消。

最后,这一定是一个很棒的项目,在对接 docker、k8s 之类的容器设施非常方便,提供了插件,可以像操作默认存储卷一样使用,还可以直接使用挂载在本地的路径,总之,在一些方面 JuiceFS 做的已经很好了,下面就是等待时间的检验了。

参考文献

Nginx Proxy Manager – Docker 建站最佳伴侣

很长一段时间中,我都在思考容器建站的可行性。

容器有诸多益处,各类好处就不一一列举了。

在企业场景下,K8s 几乎一骑绝尘,可以完成大规模集群统一管理,完成几乎所有 Web 资源的自动调度。

但强大功能的代价,就是系统本身势必占用一部分资源,这就不适合大部分小网站、小企业去使用了。

传统建站一般是使用虚拟主机的形式,使用宝塔、AppNode、cPanel 之类的面板管理单节点站点,流量大了给服务器扩容、负载均衡、数据库外挂之类的也就解决了。

这种方案很适用于 LAMP 组合场景,可以让自己的想法快速成为现实。

在当代,各种新式编程语言大行其道,PHP 的时代似乎已经过去,容器成为更复杂运行环境的最佳载体。

作为个人站长,近几年部署应用都会优先考虑使用容器,还尝试过 k3s 组建轻量 k8s 集群的方案,使用近一年下来总有种大炮打蚊子的感觉。

说了这么多,就是想引出一种适用于当代 WEB 服务部署,容器化,但又不需要多台服务器的容器方案。

方案的关键技术为 Docker + Nginx 的组合。

上层采用 Portainer + Nginx Proxy Manager , 满足 UI 管理的需求。

部署方法

下面简单讲一下部署方法:

Docker

因为 Docker 的部署方法网上资源较多,这里就不列举了,下面默认已安装 docker .

Portainer

Portainer 是当下比较好用的一款 Docker Web GUI ,使用起来可以满足大部分需求了,部署方法也很简单,类似这样:

$ docker run -d
--network myDefault
--restart=always
--name="portainer" -p 9000:9000
-v /var/run/docker.sock:/var/run/docker.sock
-v portainer_data:/data 6053537/portainer-ce

其中的 myDefault 是自定义网桥,为兼容 docker-compose 等应用的外部访问,在这里不建议使用 Docker 默认网桥。

如果没有报错,就可以通过 9000 端口访问了,后面可以使用 nginx 反代此端口就可以关闭 9000 端口外部访问了。

Nginx Proxy Manager

这是一款 nginx web gui,使用下来体验不错,貌似内嵌了 openrusty,基本可以满足 Docker 反代、HTTPS 访问等的 GUI 配置需求了

使用如下 docker-compose 部署,可以直接在 portainer 操作:

version: '3'
services:
  app:
    hostname: nginx-proxy-manager
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
networks:
  default:
    external: true
    name: myDefault

打开后台:

http://127.0.0.1:81

默认管理账户:

Email:    admin@example.com
Password: changeme

效果展示

我在这里使用的是某位大佬汉化的 Portainer,为的是熟悉一下界面,简化操作,等熟悉了还是建议切回官方版本。

后面部署应用就在这里一站式搞定了,暴露外部访问时,就用 Nginx Proxy Manager:

反代配置界面如下:

以后在这里部署 Docker 应用,就可以很方便的暴露外部访问了,配了 Portainer 较好的生态和灵活的配置,几乎没有什么限制。

总结

本文介绍了一种轻量(1C2G 足矣)、容器化(Docker+Portainer)、自动化(Nginx Proxy Manager 自动 Https)的个人网站搭建方案,该方案几乎可以适应所有场景,更重要的是一台 1C2G 的服务器就足矣支撑数十个小应用。

自己使用或是外部访问都极其方便,再也不用纠结使用哪家面板,再也不用担心面板安全问题了。

我自己的图床、博客、Miniflux、网站统计、数据库以及一些自己开源的应用未来都计划部署在这里。

这就是今天分享的内容了,希望对你有用。

最后提醒一句,本文涉及的部署命令和配置文件随时有可能过时,请以官方文档为准。

参考文献

深挖 docker 默认网络 | 为什么 docker 默认网络能上外网

为什么默认配置创建出来的 docker 容器可以访问外网,为什么监听对应端口就能对外暴露docker服务,一张图搞清楚。

首先 Docker 有四中网络模式, 分别是 Bridge、Host、Container、None,默认使用 Bridge,今天就来讲讲 Bridge。

https://imagehost-cdn.frytea.com/images/2021/12/28/drawf92673ecda5420ba.jpg

创建的容器默认使用 bridge 的方式联网,因此默认就可以docker间互通,该网桥名叫 bridge0,通过 nat 的方式与物理网卡相连,每创建一个该模式下的容器,就自动创建一对 veth-pair 挂上去。

# brctl show 
bridge name bridge id       STP enabled interfaces
br-1061a9012b5a     8000.0242998bddbb   no      
docker0     8000.0242f9c2fd4f   no      veth4fc1b46
                            veth86c0817
                            vethe510127
vmbr0       8000.b496916544ad   no      enp59s0f1

通过 nat 的方式,docker可以自由的通过宿主机网卡访问外网,如果映射端口,也是通过 nat 的方式将对应流量送入docker:

# docker ps
CONTAINER ID   IMAGE          COMMAND              CREATED        STATUS        PORTS                                       NAMES
398907d00b97   001b9ea10452   "/sbin/init start"   5 hours ago    Up 5 hours    192.168.226.140:8206->8006/tcp              hci-nos3
475b07e8faa4   001b9ea10452   "/sbin/init start"   5 hours ago    Up 5 hours    192.168.226.139:8206->8006/tcp              hci-nos2
e9955db2132c   001b9ea10452   "/sbin/init"         29 hours ago   Up 29 hours   0.0.0.0:8106->8006/tcp, :::8106->8006/tcp   hci-nos
# iptables -n -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0           
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0           
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:8006
MASQUERADE  tcp  --  172.17.0.3           172.17.0.3           tcp dpt:8006
MASQUERADE  tcp  --  172.17.0.4           172.17.0.4           tcp dpt:8006

Chain DOCKER (2 references)
target     prot opt source               destination         
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
RETURN     all  --  0.0.0.0/0            0.0.0.0/0           
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:8106 to:172.17.0.2:8006
DNAT       tcp  --  0.0.0.0/0            192.168.226.139      tcp dpt:8206 to:172.17.0.3:8006
DNAT       tcp  --  0.0.0.0/0            192.168.226.140      tcp dpt:8206 to:172.17.0.4:8006

以上面几个docker为例,分别直接监听和指定ip,会发现创建了对应的规则在 iptables 中。

至此,为什么docker访问外网及访问docker的原理讲解完毕,有问题欢迎留言。

参考文献