写在前面

这里会分享什么呢?

依旧是实用性相对比较强的技术内容,用于解决一些具体的场景问题。

更新计划和频率

在文档目录树的左侧可以看到暂定的一些内容,稍后我会逐步添加。

当前的内容更新规划由内网中的 Phabricator 管理,暂时不好公开,晚些时候转换为 Roadmap。

发布方式暂时为 CI 从静态文档中构建,计划随后修改为由 Outline 中抽取合适内容即时生成。

自动申请 HTTPS 证书

本站当前使用的配置如下,修改配置中的变量即可直接使用,我使用的是 Cloudflare 作为 DNS 服务商,如果你使用其他服务商,可以查阅 Traefik 官方文档,替换变量名称即可。

docker-compose.yml

version: "3"
services:
  traefik:
    container_name: traefik
    image: traefik:v2.4.11
    restart: always
    ports:
      - 80:80
      - 443:443
    networks:
      - traefik
    environment:
      - CF_API_EMAIL=你的邮箱
      - CLOUDFLARE_DNS_API_TOKEN=你的API TOKEN
      - CLOUDFLARE_ZONE_API_TOKEN=你的API TOKEN
    command:
      - "--global.sendanonymoususage=false"
      - "--global.checknewversion=false"
      - "--entrypoints.http.address=:80"
      - "--entrypoints.https.address=:443"
      - "--entryPoints.http.forwardedHeaders.trustedIPs=127.0.0.1/32,172.18.0.1/24"
      - "--entryPoints.https.forwardedHeaders.trustedIPs=127.0.0.1/32,172.18.0.1/24"
      - "--api=true"
      - "--api.insecure=true"
      - "--api.dashboard=true"
      - "--api.debug=false"
      - "--ping=true"
      - "--log.level=trace"
      - "--log.format=common"
      - "--accesslog=false"
      - "--providers.docker=true"
      - "--providers.docker.watch=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.swarmMode=false"
      - "--providers.docker.useBindPortIP=false"
      - "--providers.docker.network=traefik"
      - "--providers.file=true"
      - "--providers.file.watch=true"
      - "--providers.file.directory=/etc/traefik/config"
      - "--providers.file.debugloggeneratedtemplate=true"
      - "--certificatesresolvers.le.acme.email=你的邮箱"
      - "--certificatesresolvers.le.acme.storage=/data/ssl/acme.json"
      - "--certificatesresolvers.le.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
      - "--certificatesresolvers.le.acme.dnsChallenge.provider=cloudflare"
      - "--certificatesresolvers.le.acme.dnsChallenge.delayBeforeCheck=30"
    volumes:
      # 仅限标准的 Linux 环境使用
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config/:/etc/traefik/config/:ro
      - ./ssl/:/data/ssl/
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"

      # 处理网页
      - "traefik.http.routers.traefik-dash-web.tls.certresolver=le"
      - "traefik.http.routers.traefik-dash-web.tls.domains[0].main=suyang.wiki"
      - "traefik.http.routers.traefik-dash-web.tls.domains[0].sans=*.suyang.wiki,*.console.suyang.wiki,*.demo.suyang.wiki"

      - "traefik.http.routers.traefik-dash-web.tls=true"
      - "traefik.http.routers.traefik-dash-web.middlewares=common-auth@file"
      - "traefik.http.routers.traefik-dash-web.entrypoints=https"
      - "traefik.http.routers.traefik-dash-web.rule=Host(`traefik.suyang.wiki`) && PathPrefix(`/`)"
      - "traefik.http.routers.traefik-dash-web.service=dashboard@internal"
      # 处理接口
      - "traefik.http.routers.traefik-dash-api.middlewares=common-auth@file"
      - "traefik.http.routers.traefik-dash-api.entrypoints=https"
      - "traefik.http.routers.traefik-dash-api.rule=Host(`traefik.suyang.wiki`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
      - "traefik.http.routers.traefik-dash-api.tls=true"
      - "traefik.http.routers.traefik-dash-api.service=api@internal"
    healthcheck:
      test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:8080/ping || exit 1"]
      interval: 3s
      retries: 12
    logging:
      driver: "json-file"
      options:
        max-size: "1m"

networks:
  traefik:
    external: true

config/default.toml

一些常用的中间件声明配置,可以不进行配置。

# 提供 Gzip 压缩
[http.middlewares.gzip.compress]

# 独立协议跳转规则
[http.middlewares.redir-https.redirectScheme]
  scheme = "https"
# 兼容一些旧的配置,确认没有使用则可以删除
[http.middlewares.https-redirect.redirectScheme]
  scheme = "https"

# 定义一个空服务,用于一些特殊场景
[http.services]
  [http.services.noop.LoadBalancer]
     [[http.services.noop.LoadBalancer.servers]]
        url = "" # or url = "localhost"

# 定义一个简单的 BA 验证
[http.middlewares.common-auth.basicAuth]
  users = [
    # htpasswd -nb your-user-name your-pass-word
    "your-user-name:$shdsdfiuysdiufywiuhreiwhf.",
  ]
  removeheader = true

config/tls.toml

相对比较宽容的 A+ 评分的配置。

[tls]
  [tls.options]
    [tls.options.default]
      minVersion = "VersionTLS12"
      sniStrict = true
      cipherSuites = [
        "TLS_AES_128_GCM_SHA256",
        "TLS_AES_256_GCM_SHA384",
        "TLS_CHACHA20_POLY1305_SHA256",
        "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
      ]

Refs

使用 Traefik 和 Nginx 快速搭建静态网站

有的时候仅需要将一些静态文件和域名进行“绑定”展示,相比直接使用 Nginx,配合 Traefik 可以更加灵活。

version: "3"
services:
  nginx:
    image: nginx:1.21.1-alpine
    restart: always
    expose:
      - 80
    networks:
      - traefik
    volumes:
      - ./public:/usr/share/nginx/html:ro
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.docs-homepage-0.middlewares=redir-https@file"
      - "traefik.http.routers.docs-homepage-0.entrypoints=http"
      - "traefik.http.routers.docs-homepage-0.rule=Host(`suyang.wiki`, `www.suyang.wiki`)"
      - "traefik.http.routers.docs-homepage-1.middlewares=gzip@file"
      - "traefik.http.routers.docs-homepage-1.tls=true"
      - "traefik.http.routers.docs-homepage-1.entrypoints=https"
      - "traefik.http.routers.docs-homepage-1.rule=Host(`suyang.wiki`, `www.suyang.wiki`)"
      - "traefik.http.services.docs-homepage-backend.loadbalancer.server.scheme=http"
      - "traefik.http.services.docs-homepage-backend.loadbalancer.server.port=80"
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
networks:
  traefik:
    external: true

域名复用其实也很简单,只需要单独设置 Prefix 字段即可。

version: "3"
services:
  nginx:
    image: nginx:1.21.1-alpine
    restart: always
    expose:
      - 80
    networks:
      - traefik
    volumes:
      - ./public:/usr/share/nginx/html:ro
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik"
      - "traefik.http.routers.runbook.middlewares=gzip@file"
      - "traefik.http.routers.runbook.entrypoints=https"
      - "traefik.http.routers.runbook.tls=true"
      - "traefik.http.routers.runbook.rule=Host(`suyang.wiki`) && PathPrefix(`/runbook`)"
      - "traefik.http.services.runbook-backend.loadbalancer.server.scheme=http"
      - "traefik.http.services.runbook-backend.loadbalancer.server.port=80"
    logging:
      driver: "json-file"
      options:
        max-size: "1m"
networks:
  traefik:
    external: true

私有化部署

关于 Outline 的私有化,我已经写过两篇简单的文章来介绍如何使用,根据文章“一路Next”,就可以啦。

上面两篇文章默认读者已经配置了 Traefik ,所以并没有展开介绍如何配置使用 Traefik,如果你还不会使用 Traefik,可以参考下面的文章进行搭建使用。

为镜像打网卡驱动拾遗

因为 ESXi 6.x 与 7.x 的网卡 API 版本不一致,所以这两个版本的网卡驱动不能够混用。

然而,有一些网卡驱动目前暂时没有 7.x 版本的适配文件,所以我们如果想在一些老设备上使用 ESXi ,可能不得不使用 6.x 版本的软件。

我选择的基础镜像版本是:ESXi-6.7.0-20191204001-standard

镜像下载地址:

https://customerconnect.vmware.com/cn/downloads/details?downloadGroup=ESXI67U3B&productId=742

驱动下载可以从 v-front.de 下载,也可以到厂商的下载中心进行下载(比如 dell、lenovo 等),但是不建议使用社区远古版本的驱动(尤其是 issue 反馈了大量未解决 bug 的项目)

https://vibsdepot.v-front.de/wiki/index.php/Net55-r8168

《NUC 折腾笔记 - 安装 ESXi 7》中,我基本对 7.x 版本的镜像打驱动补丁的方式做了完整介绍。

但是在 6.x 中,命令需要一些变动,多一步修改系统允许加载的外部驱动等级,将默认等级降低至“接受社区驱动软件”

New-EsxImageProfile -CloneProfile "ESXi-6.7.0-20191204001-standard" -name "ESXi-6.7.0-20191204001-nic" -vendor "soulteary"
Set-EsxImageProfile -Name "ESXi-6.7.0-20191204001-nic" -AcceptanceLevel CommunitySupported
Add-EsxSoftwarePackage -ImageProfile  "ESXi-6.7.0-20191204001-nic" -SoftwarePackage "net55-r8168"
Export-EsxImageProfile -ImageProfile "ESXi-6.7.0-20191204001-nic" -ExportToISO -filepath .\exsi6.7.0.iso

笔记本使用细节

虽然使用笔记本安装 PVE 能够默认拥有双网卡、自带键盘和显示器的终端。但是默认情,操作键盘进行输入会不时得到刺耳的警告蜂鸣声音。

触发这个声音的原理是终端使用类似 Tab 进行补全的时候,触发类似下面的行为:

echo -e '\a'

解决问题需要编辑 /etc/inputrc 文件,将以下配置禁用掉:

# do not bell on tab-completion
# set bell-style none
# set bell-style visible

PHP

偷懒小技巧

有一些时候,我们需要使用临时的 PHP 仿真环境调试需要输出交互的代码,命令行直接执行代码,配合 curl 来搞太低效了,可以使用下面的方式,快速创建一个容器环境:

(默认环境缺少不少组件,所以你也可以换成你封装好的镜像来玩)

docker run -d -p 11080:80 -v "$(pwd)/index.php":/var/www/html/index.php php:7.4-apache

Docker

alpine 小技巧

偶尔使用 alpine 进行封装或者 shell 进行调试开发的时候,安装软件特别慢,可以考虑使用下面的方式快速切换软件源。

sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories

MySQL小技巧

批量移除一定相同命名前缀的数据表

不知道哪个有才的脚本,在数据库里加了一堆无意义前缀的表。

删除方式很多,然而最终都是殊途同归,比如读出所有表名称,然后输出删表语句。

这里可以选择直接用 SQL 解决问题:

SELECT CONCAT('drop table ', table_name, ';') 
FROM information_schema.tables
WHERE table_schema= 'YourDatabase'
AND table_name LIKE 'prefix_%' ;

手动转换表数据默认字符集

有一些云控制台实现的非常粗糙,创建的时候选项比较单一,选择不到我们所需要的字符集,只能靠创建后手动调整了:

ALTER DATABASE dbname CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
ALTER TABLE `tbname` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
ALTER TABLE `tbname` DEFAULT CHARACTER SET=utf8mb4, COLLATE=utf8mb4_bin;

另外,为了避免转换过程中失败,可以调整 innodb_large_prefix on 参数

这里可以借助灵活的 js 来快速解决战斗,批量生成转换语句:

tpl = (tblName)=>`ALTER TABLE \`${tblName}\` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;`;

tables = `history
...
...
...
user_relation
usercontent_relation
users`.split('\n').filter(n=>n).map(n=>tpl(n))
console.log(tables.join('\n'))

快速查阅 Ubuntu 变更日志

Ubuntu 有一个使用 Nginx AutoIndex 提供服务的站点,里面提供了各个版本的 changelogs 速查的方式。

  • https://changelogs.ubuntu.com/