服务总览
这里记录了所有需要维护的服务、维护这些服务的原因,以及一些做法的考量,以供后续维护参考(为什么要做、为什么这样做、后续改进/替代方案可行性分析),更新可能有延迟。
记录顺序总体按照「假如爆炸了,理想的恢复顺序」进行,注意,部分服务不直接影响面向用户的部分,但可能导致可用性降级。
本页只记录 *.lab.tiankaima.cn 相关基础设施;*.tiankaima.dev 的个人服务不在此处维护。
用户侧入口
监控
代理
DNS
正如名字所暗示的,是我自己个人的域名,因此目前是在我的 CloudFlare 上,交替使用了两个 TLD 的原因是,我最开始注册了 .dev 域名,后面因为合规原因又注册了 .cn(我自己备案了这个域名),不过事实上我们也没办法使用 :443,所以也许区别不大。
Caddy
目标:
Caddy :易用的反向代理,自带 TLS 证书申请工具
注意:
校内服务器的 HTTP/HTTPS 设置为 8000+80/443=8080/8443,因为 :80 & :443 都会被查水表。
使用 ghcr.io/tiankaima/caddy-cf:latest 替换了默认的 caddy:alpine 镜像,这里面打包 了一些 Caddy 插件方便使用。
tls { dns cloudflare } 的用法是通过 DNS Challenge 签发 TLS 证书(默认行为是 HTTP Challenge,会更加透明一些,但是因为 :80 端口无法使用,所以只能这样设置),然后为了集中这个设置(不再重复这段代码),使用了 Handler 的语法。如果新增反代的话就照猫画虎一下就好。
上游文档:http://caddyserver.com/
Note
修改 Caddyfile 后,按照顺序执行:
docker exec caddy caddy validate -c /etc/caddy/Caddyfile
docker exec caddy caddy fmt -c /etc/caddy/Caddyfile -w
docker exec caddy caddy reload -c /etc/caddy/Caddyfile
Note
在维护结束后,开放给用户之前,可以先在内网进行测试,只需要取消下面的注释:
/srv/docker/caddy/conf/Caddyfile handle @coder {
#respond @public-ipv4 "Under Maintainance"
}
然后在本地通过修改 /etc/hosts 的方式将服务直接解析到内网地址即可访问(注意浏览器中 DoH/DoT 设置)。
cls1-gateway(历史配置)
https://201.ustclug.org/advanced/caddy/
/srv/docker/caddy .
├── conf
│ └── Caddyfile
├── data
├── fmt.sh
├── reload.sh
├── run.sh
└── validate.sh
修改 Caddyfile 后可按 ./validate.sh -> ./fmt.sh -> ./reload.sh 执行,保证 0-downtime。
/srv/docker/caddy/conf/Caddyfile grafana.lab.tiankaima.cn,
coder.lab.tiankaima.cn,
*.coder.lab.tiankaima.cn {
tls {
dns cloudflare ***
}
@public-ipv4 not client_ip 100.100.0.0/16 192.168.48.0/22 10.0.0.0/8
@grafana host grafana.lab.tiankaima.cn
handle @grafana {
reverse_proxy :3000 {
header_down -X-Frame-Options
header_down +X-Frame-Options "SAMEORIGIN"
}
}
@coder host coder.lab.tiankaima.cn *.coder.lab.tiankaima.cn
handle @coder {
# respond @public-ipv4 "Under Maintainance"
@websockets `header({'Connection':'*Upgrade*','Upgrade':'websocket'}) || header({':protocol': 'websocket'})`
reverse_proxy :3001 {
header_up -Accept-Encoding
header_up Accept-Encoding identity
header_down -content-security-policy
}
}
}
Coder(历史)
Warning
Coder 服务已暂停使用,本节仅用于保留历史部署信息。
https://coder.lab.tiankaima.cn:8443
/srv/docker/coder/docker-compose.yaml services :
coder :
image : ghcr.io/coder/coder:latest
container_name : coder
restart : unless-stopped
network_mode : "host"
volumes :
- /var/run/docker.sock:/var/run/docker.sock
- /var/run/docker/:/var/run/docker/
env_file :
- ./coder.env
environment :
CODER_PG_CONNECTION_URL : "postgresql://${POSTGRES_USER:-coder}:${POSTGRES_PASSWORD:-***}@127.0.0.1/${POSTGRES_DB:-coder}?sslmode=disable"
group_add :
- 988 # docker group
depends_on :
database :
condition : service_healthy
database :
image : postgres:16
container_name : coder-database
restart : unless-stopped
network_mode : "host"
environment :
POSTGRES_USER : ${POSTGRES_USER:-coder}
POSTGRES_PASSWORD : ${POSTGRES_PASSWORD:-***}
POSTGRES_DB : ${POSTGRES_DB:-coder}
POSTGRES_PORT : ${POSTGRES_PORT:-tcp://127.0.0.1:5432}
volumes :
- ./pg-data:/var/lib/postgresql/data
healthcheck :
test :
- "CMD-SHELL"
- "pg_isready -U ${POSTGRES_USER:-coder} -d ${POSTGRES_DB:-coder}"
interval : 5s
timeout : 5s
retries : 5
代理设置请参考 代理使用说明 。
Docker Socket
为在一台机器上同时控制多台 Docker Daemon,我们使用 systemd 挂载 ssh socket forward 到 /var/run/docker/$srv.sock:
/etc/systemd/system/docker-tunnel@$.service [Unit]
Description = SSH Tunnel for Docker Socket on %i
After = network.target
[Service]
ExecStartPre = /usr/bin/rm -f /var/run/docker.%i.sock
ExecStart = /usr/bin/ssh -nNT -L /var/run/docker/%i.sock:/var/run/docker.sock coder@%i
#ExecStartPost=chown :docker /var/run/docker.%i.sock
#ExecStartPost=chmod g+rw /var/run/docker.%i.sock
Restart = always
RestartSec = 10
User = root
[Install]
WantedBy = multi-user.target
为正确设置权限,调整 sudo crontab -e:
*/1 * * * * chown :docker /var/run/docker.*.sock; chmod g+rw /var/run/docker.*.sock
这不是一个 best practice,这甚至都不能称作解决办法……
LLDAP
目标:
LLDAP :轻量 LDAP 服务,作为统一账号目录,提供 LDAP/LDAPS 与 Web 管理界面。
注意:
部署在 jp-2 上,Web 管理界面只在本机 127.0.0.1:17170 监听,通过 Caddy 暴露 lldap.lab.tiankaima.cn。
LDAP/LDAPS 端口对内网服务可见(389 / 636),如无必要不要直接对公网暴露。
Headscale
目标:
Tailscale :跨集群建立点对点连接(俗称打洞),替代之前手工配置的 Wireguard 树状拓扑,改为网状拓扑;同时有 DERP 兜底,更适合复杂网络环境。
Headscale :是一个开源的 Tailscale 控制节点方案,自建的目标是解决可能的 Tailscale 被 GFW 屏蔽问题、合规问题。
注意:
部署在 jp-2 上:Headscale 节点需要公开的 :443 访问,校园内部无法提供对应环境,同时由于节点间的通讯是点对点的,放在境外不影响延迟。
使用 Docker 进行部署:参考文档 。
GitHub:https://github.com/juanfont/headscale
上游文档:https://headscale.net/
Note
在 jp-2 中 /root/.bashrc 中预留了如下的 alias:
alias headscale = 'docker exec -it headscale headscale'
这样,所有官方文档 提供的代码都可直接用 headscale ... 来执行。
cls1-gateway DERP
参考文档 。
Tailscale 接入
请在 lab 用户下注册新设备,首先通过命令行/网页获取 lab 用户的 $PREAUTH_KEY。拥有这个密钥=拥有(以该用户的身份)加入新设备的能力。
headscale users list
headscale preauthkey create -u $UID --reusable
/srv/network/tailscale.sh #!/bin/sh
set -xe
tailscale up \
--accept-routes \
--advertise-exit-node \
--login-server= https://headscale.lab.tiankaima.cn \
--authkey $FILL_PRE_AUTH_KEY_HERE
headscale-ui
https://headscale.lab.tiankaima.cn/web/ ,基于 Web 的 Headscale 控制面板。
使用前需要获取 API key,在 jp-2 上执行命令:
监控
有了统一挂载的 Docker Socket,可以很方便地在一处配置所有服务器的监控。
配置了 Prometheus + Grafana 来做监控:
CPU、内存、硬盘、网络流量:node-exporter
GPU 监控:dcgm-exporter
监控本体:prometheus
可视化:grafana
配置文件参考:https://gist.github.com/tiankaima/9c31f36435af0c5093704b366d43eea2
分别提供了以下目录的 docker-compose.yml:
/srv/docker/monitor/(有 Grafana)
/srv/docker/monitor.slave/(无 Grafana)
/srv/docker/monitor.slave.nogpu/(无 GPU 监控)
得益于 Docker 的前后端分离设计,Slave 上 Docker 容器开启、关闭、日志只需要设置环境变量即可:
On target slave sudo mkdir -p /srv/docker/monitor/proetheus/config/
sudo mkdir -p /srv/docker/monitor/proetheus/data/
sudo vim /srv/docker/monitor/proetheus/config/proetheus.yml
On cls1-gateway export DOCKER_HOST = unix:///var/log/docker.$HOSTNAME .sock
当前 node-exporter, dcgm-exporter, prometheus 直接使用 network: host 模式
Prometheus 分体式记录
每台服务器上都有着自己的 Prometheus 实例,分别记录各自的监控数据。
同时 cls1-gateway 上还额外添加了一个 prometheus.merged 实例来采集所有服务器的监控数据。
jp-2
cls1-gateway
docker-compose.yml prometheus.yml
/srv/docker/monitor/docker-compose.yml services :
node-exporter :
image : prom/node-exporter
container_name : monitor-node-exporter
restart : always
pid : "host"
network_mode : "host"
volumes :
- "/:/host:ro,rslave"
command :
- "--path.rootfs=/host"
prometheus :
image : prom/prometheus
container_name : monitor-prometheus
restart : always
network_mode : "host"
volumes :
- /srv/docker/monitor/prometheus/conf:/etc/prometheus
- /srv/docker/monitor/prometheus/data:/prometheus
depends_on :
- node-exporter
prometheus-merged :
image : prom/prometheus
container_name : monitor-prometheus-merged
restart : always
network_mode : "host"
volumes :
- /srv/docker/monitor/prometheus.merged/config:/etc/prometheus
- /srv/docker/monitor/prometheus.merged/data:/prometheus
command : --web.listen-address=:9091 --config.file=/etc/prometheus/prometheus.yml
depends_on :
- node-exporter
grafana :
image : grafana/grafana-oss
container_name : monitor-grafana
restart : always
volumes :
- /srv/docker/monitor/grafana/grafana.ini:/etc/grafana/grafana.ini
- /srv/docker/monitor/grafana/data:/var/lib/grafana
- /srv/docker/monitor/grafana/log:/var/log/grafana
depends_on :
- prometheus
environment :
- https_proxy=http://grafana.proxy.lab.tiankaima.cn:7890
- http_proxy=http://grafana.proxy.lab.tiankaima.cn:7890
- HTTPS_PROXY=http://grafana.proxy.lab.tiankaima.cn:7890
- HTTP_PROXY=http://grafana.proxy.lab.tiankaima.cn:7890
- no_proxy=192.168.48.1/22,cls1-srv1,cls1-srv2,cls1-srv3,cls1-srv4,cls1-srv5,cls2-srv1,cls2-srv2,cls2-srv3,cls2-srv4,cls2-srv5,cls2-srv6,cls2-srv7
network_mode : "host"
/srv/docker/monitor/prometheus/conf/prometheus.yml # my global config
global :
scrape_interval : 5s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval : 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting :
alertmanagers :
- static_configs :
- targets :
# - alertmanager:9093
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files :
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs :
- job_name : "prometheus"
static_configs :
- targets : [ "localhost:9090" ]
- job_name : "node"
static_configs :
- targets :
- "localhost:9100"
- "localhost:9400"