步骤/目录： 0.Pi Dashboard简介 1.LNP部署Pi Dashboard本文首发于个人博客https://lisper517.top/index.php/archives/36/，转载请注明出处。本文的目的是使用docker、docker-compose在树莓派上运行Pi Dashboard。本文实验日期为2022年8月27日。本文使用的是树莓派4B（内存8G版），系统为Pi OS 64位桌面版（2022年4月4日更新，镜像名 2022-04-04-raspios-bullseye-arm64.img ）。0.Pi Dashboard简介在windows系统中，可以用任务管理器便捷地查看电脑的CPU、显卡等使用情况；对于树莓派，这样的信息收集起来比较麻烦，好在有大神开发了Pi Dashboard，这是一个网页查看树莓派状态的工具，详细介绍在 https://make.quwj.com/project/10 。简单来说Pi Dashboard的部署和Typecho类似，也是放在nginx的文件夹里、搭配php使用，不同点是不需要mysql。1.LNP部署Pi Dashboard这里用docker-compose运行nginx、php服务，最后把Pi Dashboard放在nginx的网页根目录下即可。在树莓派上进行如下操作：mkdir -p /docker/pi_dashboard/conf/nginx mkdir /docker/pi_dashboard/www mkdir /docker/pi_dashboard/ssl_cert docker pull nginx:1.23.1 docker pull php:8.1-fpm nano /docker/pi_dashboard/conf/nginx/default.conf写入以下内容：server { listen 50443 ssl; server_name www.lisper517.top; ssl on; ssl_certificate /ssl_cert/.pem文件名; ssl_certificate_key /ssl_cert/.key文件名; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; location / { index index.html index.htm index.php default.html default.htm default.php; root /var/www/html; } location ~\.php$ { fastcgi_pass php:9000; fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name; include fastcgi_params; } error_page 497 https://$host:50443$request_uri; }这是使用https的配置，注意更改 www.lisper517.top 和ssl证书的两个文件名。如果只使用http，则写入如下内容：server { listen 50443; location / { index index.html index.htm index.php default.html default.htm default.php; root /var/www/html; } location ~\.php$ { fastcgi_pass php:9000; fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name; include fastcgi_params; } }但是要注意，无论使用http还是https，都尽量不要把这个页面开放到公网。然后编写compose模板：nano /docker/pi_dashboard/docker-compose.yml写入如下内容：version: "3.9" services: nginx: image: nginx:1.23.1 ports: - "50443:50443" volumes: - /docker/pi_dashboard/conf/nginx:/etc/nginx/conf.d - /docker/pi_dashboard/ssl_cert:/ssl_cert - /docker/pi_dashboard/www:/var/www/html logging: driver: syslog restart: always php: image: php:8.1-fpm volumes: - /docker/pi_dashboard/www:/var/www/html logging: driver: syslog restart: always无论http还是https都可以用这个compose模板。然后开启服务：cd /docker/pi_dashboard docker-compose config docker-compose up看一下日志是否正常。如果用https记得用WinSCP把证书放到正确的位置；然后到 Pi Dashboard 的github页 把压缩包下载下来（Code里面，Download ZIP），解压后用WinSCP放到树莓派的/docker/pi_dashboard/www目录下，最好不要放整个文件夹，而是把里面的文件放到该目录下；如果放整个文件夹的话，把这个文件夹改名为 pi-dashboard 。最后，浏览器访问 树莓派ip:50443 即可看到 Pi Dashboard ；如果放整个文件夹，则访问 树莓派ip:50443/pi-dashboard 。
步骤/目录： 1.背景介绍 2.方法一：wiki镜像站 3.方法二：离线wiki 4.方法三：香港服务器+V2Ray （0）V2Ray介绍 （1）服务器购买与初始化 （2）准备工作 （3）用docker安装V2Ray （4）安装V2Ray客户端及设置 （5）V2Ray的高级操作 a.网页伪装 b.服务器备份 c.VLESS + TCP + TLS + WS d.双服务器 （6）服务器被封禁的表现 5.方法四：购买服务 附录：不使用docker，直接安装V2Ray（不推荐）本文首发于个人博客https://lisper517.top/index.php/archives/31/，转载请注明出处。本文的目的是查阅wiki百科。本文实验日期为2022年8月12日。本文使用的云服务器为阿里云轻量应用服务器（香港），系统镜像 Docker 19.03 。1.背景介绍笔者使用gnuhealth（一款开源HIS软件，见 Docker实践——用于小诊所的开源医疗记录系统 ）时，其安装页面、树莓派镜像介绍都在wikibooks上（ https://en.wikibooks.org/wiki/GNU_Health/Installation 和 https://en.wikibooks.org/wiki/GNU_Health/Embedded ）。由于wikibooks没有对我国大陆提供服务，所以需要使用其他方法访问。另外，wiki实际上指的是一类浏览器访问、用户协同编辑其内容的网站，其中以wiki百科最为出名。本文中的wiki一般指代wiki百科或wikibooks。文中介绍的前两种方法仅能访问wiki百科，最后一种方法可访问wiki百科或wikibooks。2.方法一：wiki镜像站这是最简单的方法，主要依靠其他人搭建的wiki百科镜像网页来访问。这些网页一般是接到访问请求后转发给wikipedia，然后接收响应、把响应转发回来。目前（2022年8月）可用的镜像站有以下这些：英文wiki，有 https://en.jinzhao.wiki/wiki/Main_Page 中文wiki，有 https://chinois.jinzhao.wiki/wiki/Wikipedia:%E9%A6%96%E9%A1%B5 https://www.wikipedia.ahut.cf/ https://zh.wikipedia.hfut.cf/wiki/Wikipedia:%E9%A6%96%E9%A1%B5若镜像站停止服务，可随时bing或baidu搜索 “wiki镜像” ，查询新的镜像站。使用时，比如对 https://en.wikipedia.org/wiki/GNU_Health 这个原网址，把 /wiki/ 后面的部分复制就行，比如使用第一个镜像站时改成 https://en.jinzhao.wiki/wiki/GNU_Health 即可。3.方法二：离线wiki这一部分的内容主要参考了 这篇文章 。大致的方法是将wiki百科网站内容全部下载（全网站内容已被别人打包为 .zim文件 ，全部的英文wiki大概 95GB ，中文wiki 20GB ），然后通过特殊的阅读器（kiwix）浏览。第一步，在 kiwix官网下载页 下载阅读器。kiwix提供了Android、windows、linux、mac、ios、树莓派的软件安装，还有可用于chrome、firefox、edge三种浏览器的插件版kiwix。第二步，到 kiwix提供的wiki下载页面 下载zim文件。比如英文wiki百科、全站页面就是名称为 wikipedia (English) ， Number of articles/Flavour 这一列为 all maxi ，其大小目前为95.2GB（这个zim包是2022年5月打包的）。右边对应的有网页下载（Download）和BT种子下载（BitTorrent），BT下载要快很多。中文的wikipedia、 all maxi 只有20.32GB，打包时间是2022年4月。下载完成后，打开kiwix阅读器，用kiwix阅读器打开本地的zim文件即可。这种方法还可在局域网搭建服务器，使局域网各机器没有zim文件也能阅读wikipedia（但是要装kiwix），笔者未实践。4.方法三：香港服务器+V2Ray如果想拿到gnuhealth的一手资料，wikibooks是必须上的。为了达到这个目标，可使用类似wiki百科镜像站的原理，使用自己的云服务器，进行请求、响应的转发。另外，使用V2Ray访问wiki时务必注意低调，多看少说。要记住，看wiki是为了学习国外技术，师夷长技以制夷。（0）V2Ray介绍首先介绍一下V2Ray，以下涉及V2Ray的部分主要参考了 这篇文章 。另外，有一定基础的可以直接看原文（原文还包括其他相关文章的链接），本文适合略有基础的小白直接上手。未说明的软件或地方可自行搜索，或在评论留言询问。V2Ray是一个开源的工具，支持多种平台、多种协议，能同时支持多个出入站协议，而且其流量可伪装成网页流量。相比于传统的Shadowsocks(R)（即SSR或SS，俗称酸酸乳），区别主要在于V2Ray是一整个框架，功能强大、性能更好，SS(R)则是代理工具、配置简单。目前V2Ray的原开发者断更，社区自发组织开发了V2Fly，基本上可以认为二者相同。V2Fly的官网为 https://www.v2fly.org/ （不对我国大陆提供服务），github的项目地址则为 https://github.com/v2fly/v2ray-core 。V2Fly配置可参考 https://github.com/v2fly/v2ray-examples 。介绍完V2Ray，下面正式开始操作。需要注意的是，本文中使用了一些高级操作（购买域名，伪装网页流量，使用docker），如果想简单搭一个的可参考 原文章 ，但如果是小白或需要一定的稳定性，建议还是跟着本文的步骤走。另外，自己搭建是要花一些钱的，实验性质的话花费在100￥左右或更低（最低大概50￥）。（1）服务器购买与初始化首先需要购买云服务器。各种云服务器的比较可查看 这份排名 ，买的时候建议先买一个月或更短期的，因为服务器到手后需要测试能否访问wiki，能访问后再续费也不迟，不能访问就及时退款。笔者使用的为阿里云轻量应用服务器（香港），系统镜像为 Docker 19.03 ，配置是CPU2核，内存2GB，硬盘60GB，月流量2T，带宽30Mbps（3.75MB/s，实际一般比该速度略低），价格为34￥/月。购买完服务器后，先试验一下该服务器能否访问wiki，可使用：ping en.wikibooks.org进行测试，正常显示即可以访问wiki；若不能访问，可及时退货，更换新加坡、东京、韩国等地的服务器。再测试一下网速。这里使用speedtest（该py脚本适用于python2.4-3.7。先装python、pip，参考 runoob教程 ，尽量不要 apt install python3 ）：apt install python3-pip pip3 install speedtest-cli speedtest --bytes然后对服务器进行一些常规设置，比如设置一下root用密钥登录等。下面的操作有不懂的地方可参考 提升树莓派安全性 中的 3.-（3）设置长密码（定期更换）或仅用密钥登录 一节。具体操作如下：配置密钥登录（实际上香港或海外的服务器用putty连有时挺卡的，卡的时候可以上阿里云连）：sudo su root vim ~/.ssh/authorized_keys #填入密钥，试登录 vim /etc/ssh/sshd_config #PasswordAuthentication yes 改为 no ； PermitRootLogin yes 改为 prohibit-password systemctl reload sshd #分别用密码和密钥试登录，密码登录被拒安装docker-compose：curl -L https://github.com/docker/compose/releases/download/v1.29.2/docker-compose-`uname -s`-`uname -m` > /usr/bin/docker-compose chmod +x /usr/bin/docker-compose docker-compose -v运行docker-portainer（创建自制证书的方法可参考 Docker树莓派实践——portainer使用https 中的 1.准备工作 ），portainer是一个通过网页图形化管理docker的工具，也可以不装（因为笔者文中对docker的操作都写出了命令。但如果以后有用docker的打算，建议装上省心。）：mkdir -p /docker/portainer/ssl_certificate #将自制证书（即server.crt和server.key）放入上面的文件夹内。如果不用HTTPS的话也可不放 docker pull portainer/portainer-ce docker volume create portainer_data docker run -d --name portainer -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data -v /docker/portainer/ssl_certificate:/ssl_certificate -p 9443:9443 --restart always portainer/portainer-ce --sslcert /ssl_certificate/server.crt --sslkey /ssl_certificate/server.key然后在云服务器的防火墙（注意不是用ufw、iptables等工具，当然如果服务器预装了这些软件则也需要打开）开放9443端口，浏览器访问 https://服务器ip:9443 初始化portainer；如果不用HTTPS的话，命令改为docker run -d --name portainer -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data -p 9000:9000 --restart always portainer/portainer-ce然后打开云服务器的9000端口，浏览器访问 服务器ip:9000 初始化portainer。但是为了安全起见还是尽量走HTTPS。最后，如果对docker感兴趣，可阅读之前的文章 Docker入门（一） 与 Docker入门（二） ，以及笔者的 Docker实践系列 ；只是单纯想访问wiki百科的话，可继续阅读。（2）准备工作访问wiki百科等国外网站时，它们极有可能检测来源ip，如果发现来自我国大陆，就可能停止服务，非常危险，所以需要用nginx把所有数据流都伪装成网页流量来骗过wiki百科。这一步的准备工作中，需要购买域名，对域名进行认证、DNS解析，试配置nginx等，最后应当能用https正常访问购买的域名。首先要购买域名。这里主要参考了 这篇文章 。因为没有备案需求（如果确实有需求、想把自己的备案网站放到香港或其他非内地的服务器上，可在云服务器提供商处顺带购买域名、完成此步），笔者选择了Namesilo这家域名注册商，好处主要有：价格、续费较便宜；防止其他人查询域名所有者；支持支付宝。Namesilo官网为 https://www.namesilo.com/ ，可以看到 .com 后缀的域名一年是9.95$，约60￥。购买过程如下：进入 Namesilo官网 ，点击右上角的 Sign-up 注册一个账号，注册完毕后在搜索框中搜索一个心仪的域名，比如 wuhuqifei ，如果未被占用的话就选择一个合适的后缀。注意有些域名被划掉的红色价格是续费价，不要买贵了（也可以一年换一次域名，只买最低价）。选好后点旁边的 Add 添加到购物车，点击红色的 checkout 结账，这时可设置续费、时长等，还可在 Have a Coupon or Promotion Code? 里输入邀请码降价（原文给出的邀请码为 tlanyan2020 ，可-1$），但是注意不要购买其他的DNS等服务，只买域名。最后点击 Checkout 付钱，选择支付宝，在 PAY 上面打两个勾，点击 PAY ，用支付宝扫码即可。接下来设置DNS。Namesilo自带的DNS服务较难用，这里用的阿里云服务器，那么就用阿里云的DNS（其他厂商的云服务器和域名也可以使用阿里云的DNS服务）。打开云服务器的管理界面，在 站点设置-域名-添加域名 中，填入刚才的域名；根据 新手引导1 或 新手引导2 中的操作执行即可，但是引导2的方法要等48小时。使用引导1中的方法，最后要到Namesilo管理后台处把域名的解析服务器改成阿里的 ns1.alidns.com 和 ns2.alidns.com ，具体方法是进入Namesilo后台、点击 Manage My Domains ，在刚才买下的domain左边打勾，上面的 ACTIONS for Selected Domains 里选第二个 Change Nameservers ，删除原有的三条配置、填上阿里云的两个DNS服务器， Submit 后跳转回域名管理页面，该域名的 Status 变为 Processing Nameserver Update... ，等待1min刷新页面， Status 变为 Active 即可。然后可以打开CMD（windows机器上win+R，输入cmd后点确定）试一下，使用命令 ping www.自己的域名 应该会显示云服务器的ip，注意这里及下文提到 自己的域名 都是指 xxxxx.com ，com可能是自己选择的其他后缀。下一步是给购买的域名申请证书。在阿里云中搜索 SSL 证书 ，选择 SSL 证书-免费证书 ，选择digicert的免费证书。这个证书一般用于个人或小网站，一年能领20个，一张证书有效时长为一年、仅为一个域名提供证明。创建一张证书， 证书绑定域名 填 www.自己的域名 ，其他的都不用改。然后会出现一条验证信息，需要在DNS解析里添加一条记录，类型为TXT，主机记录为_dnsauth，记录值为一长串，可以复制下来。因为上文使用的是阿里云的DNS服务器，所以在阿里云DNS控制台会自动配置，如果用的其他厂商提供的DNS（如Nameliso自己的DNS），可自己手动添加该条解析配置。然后验证、提交审核即可，基本上是秒发证书。最后在 SSL 证书 这里下载域名的证书即可，把前6种类型的都可以下一份，本文教程只用到nginx版的证书，但是其他的最好还是下载备份。现在可以试着配置一下nginx。在云服务器上进行如下操作：mkdir -p /docker/v2ray/conf/nginx mkdir /docker/v2ray/www mkdir /docker/v2ray/ssl_cert docker pull nginx:1.23.1用WinSCP把nginx版的证书（.key和.pem文件）放到 /docker/v2ray/ssl_cert 目录下，然后配置nginx：vim /docker/v2ray/conf/nginx/default.conf写入以下内容：server { listen 80; server_name www.自己的域名; rewrite ^(.*)$ https://$host$1 permanent; } server { listen 443 ssl; server_name www.自己的域名; ssl_certificate /ssl_cert/xxxxx.pem; ssl_certificate_key /ssl_cert/xxxxx.key; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; location / { root /var/www/html; } }现在可以试启动nginx：docker run -d --name nginx_test -v /docker/v2ray/www:/var/www/html -v /docker/v2ray/conf/nginx:/etc/nginx/conf.d -v /docker/v2ray/ssl_cert:/ssl_cert -p 80:80 -p 443:443 nginx:1.23.1这时浏览器访问 自己的域名 或 https://www.自己的域名 ，都可看到nginx的403 Forbidden。如果看不到，可检查一下服务器的防火墙或ufw等，是否开放了80、443端口。然后写一个测试网页：vim /docker/v2ray/www/index.html写入以下内容： nginx test This is my first website, wuhu, qifei! I don't know what to put on... 建议把 This is my first website, wuhu, qifei! 和 I don't know what to put on... 改成其他语句。浏览器访问 自己的域名/index.html ，看到正确的页面（This is my first website, wuhu, qifei!大字，或者自己写的其他内容）。最后，nginx版证书下载后只有.key和.pem，还需要一个.crt文件。根据pem文件可生成crt文件（pem其实是crt和key合成的），具体方法是在云服务器上使用如下命令：openssl x509 -outform der -in /docker/v2ray/ssl_cert/xxxxx.pem -out /docker/v2ray/ssl_cert/xxxxx.crt上述命令中要把两个地方的 xxxxx 换成自己的pem文件名。转换完成后，可以把crt文件用WinSCP传回来备个份。到此为止，准备工作完成，进行一些清理：docker stop nginx_test docker container rm nginx_test（3）用docker安装V2Ray本文接下来将用docker-compose管理nginx和V2Ray，使用的是teddysun/v2ray这个镜像。如果不想用docker，可参考 附录 直接安装V2Ray，但不推荐。在云服务器上，进行如下操作：docker pull teddysun/v2ray:5.0.7 mkdir -p /docker/v2ray/conf/v2ray mkdir -p /docker/v2ray/log/nginx mkdir /docker/v2ray/log/v2ray vim /docker/v2ray/conf/v2ray/config.jsonconfig.json 就是v2ray的配置文件，需要写入如下内容：{ "log": { "loglevel": "info", "access": "/log/v2ray.access.log", "error": "/log/v2ray.error.log" }, "inbounds": [{ "port": 40000, "protocol": "vmess", "settings": { "clients": [ { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "level": 1, "alterId": 0 } ] }, "streamSettings": { "network": "ws", "wsSettings": { "path": "/mysecretpath" } }, "listen": "127.0.0.1" }], "outbounds": [{ "protocol": "freedom", "settings": {} },{ "protocol": "blackhole", "settings": {}, "tag": "blocked" }], "routing": { "rules": [ { "type": "field", "ip": ["geoip:private"], "outboundTag": "blocked" } ] } }其中，可以更改或值得注意的有以下几处："port": 40000, 是v2ray服务器端用于通信的端口，建议换成其他端口（20000~55000之间），记得打开端口 "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 是UUID，相当于frps.ini里的token，用于服务器端识别客户端。id的格式为 8-4-4-4-12 ，由数字或小写字母组成，UUID需要用V2Ray-core生成，等下会生成一个 "path": "/mysecretpath" 下文配置nginx会用到，可自行更改为其他路径 "alterId": 0 下文配置V2Ray客户端时会用到然后修改一下nginx的配置：rm /docker/v2ray/conf/nginx/default.conf vim /docker/v2ray/conf/nginx/default.conf写入以下内容：server { listen 80; server_name www.自己的域名; rewrite ^(.*)$ https://$host$1 permanent; } server { listen 443 ssl http2; server_name www.自己的域名; charset utf-8; ssl_certificate /ssl_cert/xxxxx.pem; ssl_certificate_key /ssl_cert/xxxxx.key; ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # TLSv1.3要求nginx 1.13.0及以上版本 ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; ssl_session_tickets off; access_log /log/nginx.access.log; error_log /log/nginx.error.log; location / { root /var/www/html; index index.html; } # 下面是和v2ray相关的部分，也即websocket相关配置 location /mysecretpath { proxy_redirect off; proxy_pass http://127.0.0.1:40000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }在和v2ray相关的部分中，要注意的有两点：location /mysecretpath { 这里要和V2Ray配置里的"path": "/mysecretpath"一致 proxy_pass http://127.0.0.1:40000; 40000要和v2ray的配置文件中的port一致接下来编辑compose模板：vim /docker/v2ray/docker-compose.yml写入如下内容：version: "3.9" services: nginx: image: nginx:1.23.1 network_mode: "host" volumes: - /docker/v2ray/conf/nginx:/etc/nginx/conf.d - /docker/v2ray/ssl_cert:/ssl_cert - /docker/v2ray/www:/var/www/html - /docker/v2ray/log/nginx:/log logging: driver: syslog restart: always v2ray: image: teddysun/v2ray:5.0.7 network_mode: "host" volumes: - /docker/v2ray/conf/v2ray:/etc/v2ray - /docker/v2ray/ssl_cert:/ssl_cert - /docker/v2ray/log/v2ray:/log logging: driver: syslog restart: always在最后运行之前，需要用V2Ray的客户端（其实V2Ray是客户端和服务器端用的是同样的文件，后面解释）生成一个UUID，即config.json里的id。以windows平台的客户端 V2rayN 为例，在自己的windows电脑上，到 V2rayN项目原址 下载最新的release，需要下载的是v2rayN-Core.zip，然后解压（这个客户端是解压即可用的，免安装。放到一个合适的位置），记下解压位置的盘符和路径。假设盘符和路径分别为 D 和 D:\v2rayN-Core ，用CMD（win+R，输入cmd后回车即可打开cmd）进行如下操作：D:\v2rayN-Core\v2ctl uuid #输出一个UUID复制这个UUID，在云服务器上用vim /docker/v2ray/conf/v2ray/config.json命令打开配置，复制到对应的位置。一切准备好后，现在可以开始运行：cd /docker/v2ray docker-compose config docker-compose up两项服务显示绿灯，正常启动，没有疯狂重启。输出的日志大约22行，V2Ray占2行。首先粗验证一下，浏览器访问 自己的域名 或 自己的域名/index.html ，正确显示大字体的 This is my first website, wuhu, qifei! ；访问 自己的域名/mysecretpath ，出现 Bad Request （注意，不应该是404），说明nginx把请求转发给了V2Ray，但是由于不是用V2Ray客户端访问，V2Ray端口就伪装成了报错网页。服务器端设置完成后，不要关闭ssh窗口，或者进行如下操作：^C # 按下ctrl+C关闭docker-v2ray docker-compose up -d这样docker-v2ray就在后台运行，可以关闭ssh窗口。但是建议测试阶段先在前台运行看下docker-v2ray的日志，方便排查问题。如果是运行阶段出问题，按本文的配置，nginx和V2Ray的日志在 /docker/v2ray/log 下。（4）安装V2Ray客户端及设置V2Ray是不区分客户端和服务端的，因为都用的同样的文件。但有很多社区人员二次开发了一些客户端，各种平台的各类客户端汇总可看 这篇文章 ，这里仅介绍一个windows平台客户端（V2rayN）和一个安卓平台客户端（v2rayNG），以及linux运行的v2rayA。另外，据说iOS的V2Ray客户端没有免费的。V2rayN的配置参考了 这篇文章 。前面实际上已经下载了V2rayN。在windows电脑上，进入解压后的文件夹，运行v2rayN.exe，在右下角的程序托盘里会出现一个蓝色V图标，左键单击后进入V2rayN的主界面。接着添加V2Ray节点，点击主界面左上方的服务器，添加一个VMess服务器（V2Ray的配置中有一行 "protocol": "vmess", ，即这里的服务器协议），需要填写的有：别名 填一个方便记忆的名字（如wiki-1），地址 填云服务器地址，端口 填443（不要填成40000了），用户ID 填刚才的UUID，额外ID 刚才的alterId（本文中为0）；下面的部分，传输协议选 ws，伪装域名填 www.自己的域名 ，路径填 /mysecretpath ，传输层安全选 tls ，alpn选 http/1.1 。填写完成后点击确定，在 所有服务器 里会出现新的一行记录（后面要更改该条记录可直接双击它）。客户端顺利连到服务器端的话下方的信息框中会显示：配置成功 [] [VMess] wiki-1(xx***xxx:443) 启动服务(2022/8/13 12:32:54)... Xray 1.5.6 (Xray, Penetrates Everything.) Custom (go1.18.2 windows/386) A unified platform for anti-censorship. 2022/08/13 12:32:54 [Info] infra/conf/serial: Reading config: D:\v2rayN-Core\config.json 2022/08/13 12:32:55 [Warning] core: Xray 1.5.6 started设置完服务器后还需要开启客户端的代理模式，即开始代理。对程序托盘里蓝色的V图标单击右键，点击 系统代理-自动配置系统代理 即可，图标会变成红色的V。这时已经可以访问wiki了，浏览器可访问 https://en.wikibooks.org ，如果不能正常访问的话可参考 这篇文章 。如果要停止代理，对程序托盘里红色的V图标单击右键，点击 系统代理-清除系统代理 即可。最后再设置一下代理范围。默认情况下，V2rayN是全局代理，即不管访问什么网站都要经过服务器代理，这其实没有必要，国内的网站直连更快。如果是V2rayN 4.0以后的版本（本文写作时最新的版本是5.32），可在主界面点击 设置-路由设置 ，把 启用路由高级功能 取消勾选，然后选择 基础功能-一键导入基础规则 ，这样就有了一些基本的规则，然后自己日常使用时慢慢添加规则。可以看到分了三个页面： 1.代理的Domain或IP （添加代理规则，符合规则的都会走代理）、 2.直连的Domain或IP （直连规则）、 阻止的Domain或IP （不允许访问的规则），比如想要访问 https://en.wikibooks.org/wiki/GNU_Health/Installation ，就在 代理的Domain或IP 中的左边一栏（Domain）中，将：geosite:google改为geosite:google, # 一定注意每个规则之间要有一个英文逗号分隔 domain:wikibooks.org点击确定保存。然后浏览器访问该网址，即可正确出现wiki百科网页；访问国内网页时则会直连。另外，如果使用爬虫时需要同时用V2rayN，可以在 设置-参数设置-系统代理设置 中，把 高级代理设置，协议选择（可选） 这里选择 http=http://{ip}:{http_port};https=http://{ip}:{http_port} ，就ok了，用requests库或者scrapy都可以。V2rayN的使用大致就是这样了。再次提醒，使用V2Ray访问wiki时务必注意低调，多看少说。要记住，看wiki是为了学习国外技术，师夷长技以制夷。接下来介绍v2rayNG的使用。v2rayNG的使用参考了 这篇文章 。v2rayNG也在github上开源，在github的发行版下载地址是 https://github.com/2dust/v2rayNG/releases ，点进最新的版本，下载apk包（比如 v2rayNG_1.7.18.apk ），把apk包传到手机上进行安装。安装完成后打开，点右上角的 + 号，手动输入配置（和刚才V2rayN的配置过程一样），点击右上角的勾保存，此时点击右下角灰色的V即可开启代理，但是最好先进行一些其他设置。点击左上角的三条横线，点击设置，勾上 启用速度显示 ，把域名策略改成 AsIs ，最后打开代理，访问 wikipedia.org 试试。另外，在设置的 自定义规则 里，也能设置代理、直连、阻止的Domain或IP，可以自行填写，或在右上角三个点处使用 设置默认路由规则 ，日常使用中慢慢添加自己需要的规则。v2rayA在github上的发行版下载地址为 https://github.com/v2rayA/v2rayA/releases ，而且v2rayA是有官方docker镜像的，这里就安装docker-v2rayA。运行如下命令：mkdir -p /docker/v2rayA/conf/v2rayA docker run -d --name v2raya --privileged --network=host -v /docker/v2rayA/conf/v2rayA:/etc/v2raya --restart=always mzz2017/v2raya然后访问 linux机器ip:2017 即可看到v2rayA界面，同样进行类似设置即可。v2rayA需要用到2017、20170~20172端口，有时可能用到53端口，记得打开。最后还需提醒，少说多看，务必低调。（5）V2Ray的高级操作为了避免被wiki百科检测到来源ip、禁止访问，可以进行一些高级配置来骗过wiki百科。但是要注意，服务器最终还是在阿里云手上，如果wiki百科非要查，其实是骗不过他们的火眼金睛的。还是那句话，少说多看，务必低调。a.网页伪装严格来说，这并不是高级操作，而是对前文方法的补全，但这一步是必不可少的。在之前的配置中，其实是把进出服务器的流量伪装成了访问服务器网页的流量。实际上你在使用服务器访问wiki，但从外表上看只是其他人在访问服务器上的网页。但是前文中，整个网站只有一个简单的网页，而如果服务器的流量较大，很容易看出流量和网站的复杂程度不匹配。因此需要找一些网页来充实网站，或者用nginx反向代理到其他大网站。如果是后一种方法，可以参考 这篇文章 ，介绍了如何搭建wiki百科的镜像站（但是不建议选wiki，可以选百度百科这种能从内地访问的网站）。如果是后一种方法，可以自己写爬虫爬取一些网页（笔者可能在之后推出爬虫系列教程），或者参考之前的文章 Docker树莓派实践——Typecho及其备份 ，用typecho等轻量级（一定要轻量级，太大的框架会消耗大量服务器资源）的框架搭建网站。另外，如果网页经常被爬虫访问，被wiki百科查到的概率会进一步降低，所以如果每个月流量有剩余的话，可以自己写点爬虫爬自己的网站。b.服务器备份这个其实不算高级操作，只是用crontab把docker-V2Ray备份一下，方便以后服务器恢复，或部署V2Ray到新的服务器上。在服务器上进行如下操作：mkdir -p /backup/v2ray crontab -e加上一行：15 0 * * * /usr/bin/zip -r /backup/v2ray/backup.zip /docker/v2ray这样每天0点15分就会备份整个docker-v2ray，每天早晨可以自己手动下载压缩包，或者在树莓派上用ssh命令自动下载（见 Docker树莓派实践——Typecho及其备份 一文最后）。恢复时，把压缩包放到/docker目录下解压缩，然后进入/docker/v2ray、直接执行 docker-compose up -d 即可一键恢复。c.VLESS + TCP + TLS + WS上文中的配置大致上使用了 VMESS + Websocket（WS） + TLS ，其中VMESS是V2Ray的一种数据传输协议；WS是通信协议，与HTTP协议的不同在于能从服务器端向请求端主动发送信息，前文中是配置了nginx、对WS进行了反向代理；TLS则是指将进出服务器的流量伪装成了用HTTPS访问服务器网页时的流量（用HTTP的话数据不加密，一眼就能看穿）。下面将演示使用 VLESS + TCP + TLS + WS 的配置，这也是 官方配置 中最推荐的（recommended）。首先介绍一下VLESS和TCP。VLESS也是V2Ray的一种数据传输协议，可以说是VMESS的进阶版，VLESS的特点有：不依赖系统时间（VMESS要对比客户端和服务器端的时间，两者在不同时区的话时间可能不同，那么就不能用V2Ray，但这个问题设置一下时区应该就能解决，只是比较难发现），不使用alterId；无加密（所以需要配合TLS等方式加密），因此速度更快；自带分流、回落（比如上文中，用浏览器直接访问 www.自己的域名/mysecretpath ，显示的是正常的网页即 Bad Request ，这就是一种回落；分流则是对多个代理请求分到不同的端口处理），可替代nginx的分流回落（但是最好还是用nginx开一个正常网页，只是有些情况下可以不用nginx配置WS）。TCP则是一种基础的通信协议，这里没有什么特殊的。另外笔者想介绍一下XTLS。XTLS从名字上看就和TLS有点关系，简单来说XTLS减少了一次加密，提高了性能。目前来说 VLESS + XTLS 是性能最好的搭配，但是XTLS好像不能搭配WS，笔者便没有深入了解。具体可参考 github XTLS介绍 和 VLESS + XTLS使用介绍 。但其实也不必过于纠结XTLS带来的性能提升，不用XTLS性能也差不了太多，VMESS换成VLESS已经提升不少性能了，安全性还是第一位的。前文中已经安装过所有需要的软件，这里改动一下服务器端和客户端的配置即可。在云服务器上进行如下操作：docker stop v2ray_nginx_1 docker stop v2ray_v2ray_1 mv /docker/v2ray/conf/v2ray/config.json /docker/v2ray/conf/v2ray/config.json_backup # 做备份，用不了时可改回原配置 vim /docker/v2ray/conf/v2ray/config.json写入如下内容（以下配置参考了 官方配置模板 ）：{ "log": { "loglevel": "info", "access": "/log/v2ray.access.log", # log文件 "error": "/log/v2ray.error.log" # log文件 }, "inbounds": [ { "port": 40000, # 因为nginx的TCP占用了443，所以这里从443改成40000端口 "protocol": "vless", "settings": { "clients": [ { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # 换成UUID "level": 0, "flow": "xtls-rprx-origin" # 此设置可加强性能 } ], "decryption": "none", "fallbacks": [ { "dest": 80 }, { "path": "/mysecretpath", # 分流路径 "dest": 40000, # V2Ray使用的监听端口 "xver": 0 } ] }, "streamSettings": { "network": "tcp", "security": "tls", "tlsSettings": { "alpn": [ "http/1.1" ], "certificates": [ { "certificateFile": "/ssl_cert/.crt", # .crt路径 "keyFile": "/ssl_cert/.key" # .key路径 } ] } } }, { "port": 40000, # V2Ray使用的监听端口，和上面应当一致 "listen": "127.0.0.1", "protocol": "vless", "settings": { "clients": [ { "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", # 换成UUID "level": 0 } ], "decryption": "none" }, "streamSettings": { "network": "ws", "security": "none", "wsSettings": { "path": "/mysecretpath" # 分流路径，和上面应当一致 } } } ], "outbounds": [ { "protocol": "freedom" } ] }复制时，记得把所有注释删除，即以 # 开头的内容。这里稍微对服务器端配置进行解释（很多地方是笔者猜测，有不对的地方请指出），不想看的可以继续进行后面的步骤。"inbounds" 后面是指定入站的请求如何处理。 "flow": "xtls-rprx-origin" 可以极大提升性能。比较重要的是 "fallbacks" ，这是用于分流和回落的设置， "fallbacks" 的一项内容最多可以有4种参数， "alpn" （一般忽略）、 "path" （回落/分流路径）、 "dest" （回落/分流地址，只填端口号则表示本机，也可以转到其他网站，比如填 www.baidu.com:443 ）、 "xver" （为1时表示传递真实ip，0表示不传递），就拿上面的配置来说，遇到一个请求，如果是V2Ray客户端发出的（请求格式正确）、UUID是对的、访问端口和路径也确实是代理用的，那么就会根据 "fallbacks" 里的配置被分流到服务器的40000端口，进一步流到下面的WS里（如果再添加几个 "path" 、 "dest" 、 "xver" 的组合，就更能显示出真正的分流）；而如果有一个地方不对，就会被扔回到HTTP端口，即服务器的80端口（如果 "dest": 80 改成 "dest": "www.baidu.com:443" 则会跳转到百度去），那么就会返回正常网页。然后重新开启服务：cd /docker/v2ray docker-compose config docker-compose up这时同样在浏览器访问 自己的域名 ，出现的是正常网页；访问 自己的域名/mysecretpath ，出现 Bad Request ，说明服务器端应该配置成功。然后配置客户端，在 V2rayN 和 v2rayNG 的服务器中都可选择VLESS服务器，根据自己服务器端的配置填写即可，注意端口还是填443而不是40000，流控（flow）选择和 "flow": "xtls-rprx-origin" 的一样，传输协议是ws。另外， 官方配置 中还可以用tcp而不是ws配置客户端，详见 config_client_tcp_tls.json 这个文件。d.双服务器即一台内地服务器+一台非内地服务器。由于香港的或其他非内地云服务器提供的网页服务经常在内地也能看到，能看到有被wiki百科查到的风险，比较坏的情况是被阻止访问（最坏的是被人肉搜索），这时该云服务器就无法从内地访问。在 阿里云的自检网站 可以查到一个域名或ip是否被列入工信部黑名单。对这种云服务器，并非没有办法拯救。之前也提到过，用putty连香港或其他非内地的服务器有时有点卡，但在阿里云控制台连从来不会卡（除非有时阿里云的服务器抽风），所以想要连接这台香港服务器，可以用一台内地服务器，从阿里云的内部网络访问，有时甚至还能加快访问速度。但是这种操作需要再买一台内地服务器，而内地服务器要稍微贵一点。如果不想担惊受怕就可以花这个钱，或者也可以再买其他非内地服务器（但是仍然有被封的风险）。在端口转发中，最关键的一个问题是香港服务器和内地服务器通过哪个ip能访问到对方。前面已经提到，香港服务器被列入工信部黑名单后，就不能从内地访问到香港服务器，但是对于香港服务器是否还能通过公网ip访问到内地服务器，或者内地服务器能否通过公网ip访问到香港服务器（即公网访问），笔者并无经验。如果要使用内网ip访问，对于阿里云服务器，点开服务器详情可以看到服务器除了一个公网ip，还有一个内网ip。购买一个内地服务器后，当香港服务器的nginx在运行中时，可以在内地服务器上 ping 自己的域名 ，能正常解析公网ip，但是直接ping香港服务器的内网ip则没有响应，这是因为不同地域服务器间还需要另外开通内网互通。在轻量应用服务器的控制台左侧点入 内网互通 ，可以看到对同账号下不同地域的服务器间进行内网互通要到企业网控制台操作，还要交一笔钱。理论上来说，进行内网互通后，香港服务器和内地服务器能通过各自的内网ip访问到对方，这种内网ip多半是127开头，也可能是172开头。但是不知道内网ip也没关系，可以先按本流程走，以后2个服务器之间无法互相连接时，即刻到阿里云开通内网互通，把香港服务器访问内地服务器的内网ip复制一次就行了。下面就介绍一下使用双服务器、端口转发的3种方法。双服务器的流量转发，可以使用firewalld/iptables转发（优点是性能高），或者配置nginx转发，还可以配置frp转发。这里笔者比较推荐frp，因为frp虽然第一次配置稍繁琐，但后续更改配置也算简单。更重要的是frp作为十分常用的内网穿透工具，用途更多样化，很有必要了解。这里也略微提一下前两种方法（参考了 这篇文章 ），但这两种方法笔者并未实践。第一种方法firewalld，是CentOS默认的防火墙软件，一般服务器镜像上都已预装。阿里云的Docker镜像是基于CentOS，所以也装了firewalld，可使用 firewalld -h 查看帮助。如果要进行转发，可在内地服务器上如下进行配置：vim /etc/sysctl.conf在这个文件最后里加一行 net.ipv4.ip_forward = 1 ，意为打开前向代理。然后用 sysctl -p 使配置生效，接下来就可以设置端口转发。在本文中，其他需要转发的端口分别是（内地服务器到香港服务器）：50022到22，80到80，443到443，40000到40000。在内地服务器上如下进行配置（记得替换自己的香港服务器ip）：systemctl enable firewalld systemctl start firewalld firewall-cmd --state firewall-cmd --permanent --add-masquerade firewall-cmd --permanent --add-port=443/tcp firewall-cmd --permanent --add-port=443/udp firewall-cmd --permanent --add-forward-port=port=443:proto=tcp:toaddr=香港服务器ip:toport=443 firewall-cmd --permanent --add-forward-port=port=443:proto=udp:toaddr=香港服务器ip:toport=443 firewall-cmd --permanent --add-port=50022 firewall-cmd --permanent --add-forward-port=port=50022:proto=tcp:toaddr=香港服务器ip:toport=22 firewall-cmd --permanent --add-port=80 firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toaddr=香港服务器ip:toport=80 firewall-cmd --permanent --add-port=40000 firewall-cmd --permanent --add-forward-port=port=40000:proto=tcp:toaddr=香港服务器ip:toport=40000 firewall-cmd --reload只有443端口需要配置tcp、udp协议。配置完成后，记得在两个服务器的防火墙上分别打开端口。这里稍微解释一下，22是香港服务器的ssh端口（用于putty连接），80是HTTP端口，443是HTTPS端口，40000是V2Ray监听的端口。另外再次提醒，这里的香港服务器ip暂时可填公网ip，后面有必要时可换为内网ip。删除转发规则只需要把 --add 改成 --remove 即可。关于firewalld或iptables的更多操作可自行搜索。第二种方法配置nginx转发。在内地服务器上（Docker镜像），可进行如下操作：mkdir -p /docker/nginx_forward/conf/nginx docker pull nginx:1.23.1 vim /docker/nginx_forward/docker-compose.yml写入如下内容：version: "3.9" services: nginx: image: nginx:1.23.1 network_mode: "host" volumes: - /docker/nginx_forward/conf/nginx:/etc/nginx/conf.d logging: driver: syslog restart: always然后配置nginx：vim /docker/nginx_forward/conf/nginx/forward.conf写入如下内容（记得替换自己的香港服务器ip）：#端口转发配置 stream { server { listen 50022; proxy_pass 香港服务器ip:22; } server { listen 80; proxy_pass 香港服务器ip:80; } server { listen 443; proxy_pass 香港服务器ip:443; } server { listen 40000; proxy_pass 香港服务器ip:40000; } }同样记得在防火墙打开端口。最后运行docker-nginx：cd /docker/nginx_forward docker-compose config docker-compose up -d下面开始介绍frp的使用方法。使用frp，基本的原理是，以内地服务器做frps，香港服务器做frpc。简单来说，frp是一种把frpc上的端口投射到frps上的工具，比如把香港服务器的80、443端口投射到内地服务器的80、443端口（或其他端口），那么在内地即使不能通过香港服务器的公网ip访问香港服务器，也可以通过访问内地服务器的公网ip访问到香港服务器。关于frp的更多介绍见 Docker树莓派实践——frp（附dashboard开启https、frp双向验证） 。frp中最关键的地方是，笔者实践时未开通内网互通，所以香港服务器是用公网ip访问内地服务器的，也能成功连接。最后，买服务器时要注意，2个服务器的每月流量额度最好能相等，这样不会出现一个服务器流量先用完的情况。首先进行证书的准备。为了安全，frp也应该使用tls证书，在香港服务器上进行如下操作：mkdir -p /docker/frpc/conf mkdir /docker/frpc/ssl_cert cd /docker/frpc/ssl_cert vim /docker/frpc/ssl_cert/my-openssl.cnf写入以下内容：[ ca ] default_ca = CA_default [ CA_default ] x509_extensions = usr_cert [ req ] default_bits = 2048 default_md = sha256 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca string_mask = utf8only [ req_distinguished_name ] [ req_attributes ] [ usr_cert ] basicConstraints = CA:FALSE nsComment = "OpenSSL Generated Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = CA:true然后输入下面六条命令：openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -subj "/CN=www.自己的域名" -days 5000 -out ca.crt #第三条命令 openssl genrsa -out server.key 2048 #第四条命令 openssl req -new -sha256 -key server.key \ -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \ -reqexts SAN \ -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:香港服务器公网ip,DNS:自己的域名")) \ -out server.csr #第五条命令 openssl x509 -req -days 365 \ -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -extfile <(printf "subjectAltName=DNS:localhost,IP:香港服务器公网ip,DNS:自己的域名") \ -out server.crt mv /docker/frpc/ssl_cert/ca.crt /docker/frpc/ssl_cert/server_ca.crt其中注意要改5处，3处 自己的域名 （勿加www.），2处 香港服务器公网ip 。然后，用WinSCP把 server.key、server.crt、server_ca.crt 这3个文件拷回来备份；/docker/frpc/ssl_cert这个文件夹里只留下 server_ca.crt 和 my-openssl.cnf 这2个文件，其他的文件删除。再输入如下的六条命令：openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -subj "/CN=www.自己的域名" -days 5000 -out ca.crt #第三条命令 openssl genrsa -out client.key 2048 #第四条命令 openssl req -new -sha256 -key client.key \ -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com" \ -reqexts SAN \ -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:自己的域名,DNS:www.自己的域名")) \ -out client.csr #第五条命令 openssl x509 -req -days 365 \ -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -extfile <(printf "subjectAltName=DNS:自己的域名,DNS:www.自己的域名") \ -out client.crt mv /docker/frpc/ssl_cert/ca.crt /docker/frpc/ssl_cert/client_ca.crt其中注意要改5处，5处都是 自己的域名 （勿乱加www.）。然后，用WinSCP把 client.key、client.crt、client_ca.crt 这3个文件拷回来备份；/docker/frpc/ssl_cert这个文件夹里只留下 client.key、client.crt、server_ca.crt 这3个文件，其他的文件删除（包括 my-openssl.cnf ）。剩下的 server.key、server.crt、client_ca.crt 这3个文件后面放到内地服务器上。做好证书后，购买一个阿里云的内地服务器，同样选择 Docker 镜像，像香港服务器一样进行初始化（设置密钥，下载docker-compose，安装portainer）。然后对内地服务器进行如下操作：docker pull snowdreamtech/frps mkdir -p /docker/frps/conf mkdir /docker/frps/ssl_cert vim /docker/frps/conf/frps.ini在frps.ini（frps的配置文件）中写入以下内容：[common] bind_port = 7000 #frpc上也要有相同的token值 token = 你的口令 #frps统计板 dashboard_port = 7500 dashboard_user = wuhuqifei # 用户名记得更改 dashboard_pwd = wuhuqifei # 密码记得更改 #dashboard使用tls的相关配置 dashboard_tls_mode = true dashboard_tls_cert_file = /ssl_cert/server.crt dashboard_tls_key_file = /ssl_cert/server.key #kcp据说是多消耗15%流量、降低tcp延迟，端口可和bind_port相同。流量用的多可以不加这个 kcp_bind_port = 7000 #双向认证相关配置 tls_only = true tls_cert_file = /ssl_cert/server.crt tls_key_file = /ssl_cert/server.key tls_trusted_ca_file = /ssl_cert/client_ca.crt下面对frps的配置进行一些说明。 bind_port 和 token 是最核心的设置，其他的设置可以不要但这两个必须有，前者是指定用于frps自己通讯的端口，后者相当于V2Ray的UUID（但是token可以自己随手输，UUID则要专门生成）。dashboard是一个网页统计版，就是说浏览器访问 内地服务器ip:7500 ，输入user和pwd后就能从网页UI观察frps的投射情况；dashboard不用tls的话是用HTTP通信、不加密，用上tls后就能从HTTPS访问、加密，访问时要用 https://内地服务器ip:7500 这种形式，浏览器会弹出警告，在高级选项里选择继续访问；双向认证是指frps和frpc互相通讯时，不止是frps要验证frpc的口令，而且frps和frpc相互之间（frps要用tls证书验证frpc的身份，frpc也要用tls证书验证frps的身份，所以叫双向）还要验证各自的证书，更加安全。设置完frps.ini后，把 server.key、server.crt、client_ca.crt 这3个文件放入/docker/frps/ssl_cert中，然后运行docker-frps：docker run -d --name frps -v /docker/frps/conf/frps.ini:/etc/frp/frps.ini -v /docker/frps/ssl_cert:/ssl_cert --network host --restart=always snowdreamtech/frps之后可以用浏览器访问 https://内地服务器ip:7500 （记得打开7500端口），输入用户名密码可看到frps运行情况。设置完内地服务器后，在香港服务器上进行如下操作（如果服务器无法连接，就从阿里云控制台连）：docker pull snowdreamtech/frpc vim /docker/frpc/conf/frpc.ini写入以下内容：[common] server_addr = 内地服务器ip地址 # 这里应该是办理内网互通后得到的内网ip，如果没有的话可以用内地服务器的公网ip试试 server_port = 7000 token = 你的口令 login_fail_exit = false # 一次连不上是否立即关闭frpc。为false时frpc会不断尝试，直到连上frps #双向验证相关配置 tls_enable = true tls_cert_file = /ssl_cert/client.crt tls_key_file = /ssl_cert/client.key tls_trusted_ca_file = /ssl_cert/server_ca.crt [HK-ssh-1] type = tcp local_ip = localhost local_port = 22 remote_port = 50022 [HK-http-1] type = tcp local_ip = localhost local_port = 80 remote_port = 80 [HK-https-1] type = tcp local_ip = localhost local_port = 443 remote_port = 443 [HK-v2ray-1] type = tcp local_ip = localhost local_port = 40000 remote_port = 40000 #如果想把香港服务器上的portainer网页也投射出来，就把下面四行的注释去掉 #[HK-portainer-1] #type = tcp #local_ip = localhost #local_port = 9443 # 如果portainer没用HTTPS，就改成9000 #remote_port = 9444再次检查，香港服务器的/docker/frpc/ssl_cert这个文件夹里应该只有 client.key、client.crt、server_ca.crt 这3个文件。frpc的配置比较简单， [common] 下面的八行设置是连接frps时的设置；后面的，每五行是一个端口投射，以第一个 [HK-ssh-1] 为例，这个中括号里面的是这项投射的名称（frpc成功连到frps后在dashboard上可以看到）， type 是投射的通讯协议； [HK-ssh-1] 这整个五行配置的意思是frpc在通过口令、双向认证成功连接到frps后，将会要求把frpc的22端口投射到frps的50022端口；22端口一般是用于ssh的，平常用putty时默认就是连接到22端口，把香港服务器的22端口投射到内地服务器的50022端口，那么用putty连接 内地服务器:50022 端口即可连接到香港服务器的ssh。之后就可以开启frpc了，但还要检查一下服务器端口的开放情况。内地服务器需要开放22、80、443、7000、7500、40000、50022端口，香港服务器需要开放22、80、443、40000端口；如果portainer也投射的话，内地服务器还需要开放9444端口，香港服务器开放9443或9000端口，之后浏览器访问 https://内地服务器ip:9444 就能看到香港服务器的portainer网页UI。然后，在香港服务器上开启docker-frpc：docker run -d --name frpc -v /docker/frpc/conf/frpc.ini:/etc/frp/frpc.ini -v /docker/frpc/ssl_cert:/ssl_cert --network host --restart=always snowdreamtech/frpc浏览器访问 https://内地服务器ip:7500 ，忽略警告（ 高级选项-继续访问 ），输入dashboard的用户名密码，如果frpc成功连接到frps，就可以看到tcp里新增了4或5项投射。最后，对V2Ray进行配置，这里只需要更改V2Ray客户端的设置。比如在V2rayN上，应该只需修改地址，把 地址 改成 内地服务器公网ip 即可，甚至都不需要改域名。笔者实验时，两个服务器没有内网互通，只改地址可成功连接；如果不能，可以试试把 域名 也改成 内地服务器公网ip 。另外，使用双服务器时，可以配置一下香港服务器，使得其他人扫描端口时，除了22、80、443端口开放，其他端口都关闭；只有ip是内地服务器时才开放其他端口。在香港服务器上如下操作：firewall-cmd --permanent --remove-port=40000/tcp firewall-cmd --permanent --zone=trusted --add-port=40000/tcp firewall-cmd --permanent --zone=trusted --add-source=内地服务器ip fireawll-cmd --reload用ufw或iptables也可达到相同的效果（ufw更简单），详情可自行搜索。（6）服务器被封禁的表现wiki对跨越者服务器的封禁表现为80、443端口无法访问，这两个端口的网页都看不了；但是在其它端口的网页服务都能访问。据说现在wiki正在训练AI对跨越流量智能识别+封禁，可能2022年中的一波就与此有关。5.方法四：购买服务方法三是主要是探讨了一些学术上的可行性。如果不想折腾，可以购买一些服务，常见的有 JustMySocks（5.88$/月） 、 ladder（2.99$/月） 、 express（6.67$/月） 、 nord（3.29$/月） 、 surfshark（2.3$/月） 、 pure（3.24$/月） 、 vypr（1.67$/月） 等。具体购买可参考 这篇文章 。附录：不使用docker，直接安装V2Ray（不推荐）如果不用docker的话也可以安装V2Ray，但是docker管理更方便、不会污染服务器的系统，且docker-V2Ray版本更新也挺勤快的，所以笔者并不推荐以下的方法。以下内容主要来自 这篇文章 。本来官方是有提供自动安装脚本的（在 https://github.com/v2fly/v2ray-core/blob/master/release/install-release.sh 或 https://install.direct/go.sh ），但是后来弃用了，目前可使用社区中其他人提供的脚本。使用如下命令即可安装V2Ray：bash <(curl -sL https://raw.githubusercontent.com/hijkpw/scripts/master/goV2.sh)输出如下：Installing V2Ray v4.45.2 on x86_64 Downloading V2Ray: https://github.com/v2fly/v2ray-core/releases/download/v4.45.2/v2ray-linux-64.zip % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 100 13.1M 100 13.1M 0 0 4168k 0 0:00:03 0:00:03 --:--:-- 3919k /dev/fd/63: line 152: unzip: command not found Updating software repo Installing unzip Archive: /tmp/v2ray/v2ray.zip inflating: /usr/bin/v2ray/geoip.dat inflating: /usr/bin/v2ray/geosite.dat inflating: /usr/bin/v2ray/v2ray inflating: /usr/bin/v2ray/v2ctl PORT:xxxxx UUID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Failed to execute operation: No such file or directory Created symlink from /etc/systemd/system/multi-user.target.wants/v2ray.service to /etc/systemd/system/v2ray.service. V2Ray v4.45.2 is installed.其中的PORT和UUID也可在V2Ray的配置文件中看到：vim /etc/v2ray/config.json另外，配置里的alterId也要记下。然后在云服务器的防火墙中打开PORT对应端口，验证一下V2Ray运行没有：ss -ntlp | grep v2ray若输出为空，应该是被SELinux限制了。可关闭SELinux并重启V2Ray：setenforce 0 systemctl restart v2ray ss -ntlp | grep v2ray这时可正确看到输出。之后使用时，更改 /etc/v2ray/config.json 这个配置文件即可，每次更改完配置文件后用 systemctl restart v2ray 重启V2Ray。最后，设置一下v2ray开机自启：systemctl enable v2ray
Docker实践——用于小诊所的开源医疗记录系统
步骤/目录： 0.准备工作 （1）购买并设置树莓派 （2）购买并设置服务器 （3）准备软件 1.方法一：docker + openmrs 2.方法二：gnuhealth 的树莓派系统 3.方法三：gnuhealth 直接安装（无法使用） 4.了解：新致“开源”医疗 附录：通过公网访问EMR或HIS本文首发于个人博客https://lisper517.top/index.php/archives/29/，转载请注明出处。本文的目的是搭建一个可用于小诊所的电子病历系统，省去一笔开支费用。有些方法中会用到docker、docker-compose。本文实验日期为2022年8月7日。本文使用的云服务器为阿里云轻量应用服务器（杭州），系统镜像为 Docker 19.03；树莓派为 4B 8GB 版，系统镜像是Pi OS的64位桌面版，名为 2022-04-04-raspios-bullseye-arm64.img 。笔者有医学背景，最近需要一个电子病历系统。医疗方面的电子化，可分EMR（electronic medical record）和HIS（hospital information system），EMR侧重于电子病历，而HIS侧重于使用计算机技术对整个医院进行管理，HIS包括EMR，HIS也难得多。但是如果只是想拥有一个私人的电子病历系统，或者开小诊所想使用电子病历，那么可以考虑使用一些开源的EMR或HIS（界面都是全英文的，但是一直用就熟练了，可以输入中文）。当然，这里的电子病历比较简陋，只是起到一个记录、方便查阅（相较于纸质病历）的作用，一般不应起法律效力。如果是单纯的医生、并不懂计算机技术，可以只看这篇文章，遇到中间的注释不懂的，如果没有实际影响可以跳过不看。如果对计算机技术感兴趣的，可从笔者的第一篇文章看起，这里用到的所有技术都能通过之前的文章学会。最后需要说明，下文中的所有方法，是让EMR或HIS在树莓派（一种微型电脑）上运行，服务器只提供一个公网ip、方便公网访问，如果只是在局域网中用（比如说自己开诊所，把树莓派放到诊所里面运行）就可以不买服务器；如果是上班时间摸鱼，想把病人记在自己的私人病历上，而且上班地方的电脑能连接外网，这种情况就可以买一个服务器。另外，以下的这些方法仅供娱乐，如果真的自己开诊所，建议找一些免费的病历系统用，付费的其实也不贵，1000￥/年差不多了。0.准备工作准备工作中，需要购买并设置树莓派；购买服务器，对服务器进行一些初始设置；在自己的电脑上准备相关软件。下文中出现的一个或多个X，一般指代不同的变化的数字。（1）购买并设置树莓派树莓派的详细介绍可参考 树莓派型号、参数与作用 （笔者之前的文章，赶时间的可以不看），粗略来说树莓派是一种便宜的微型电脑，可以用来运行一些任务。购买渠道上，建议到淘宝搜索 树莓派4B ，购买内存4GB或8GB版的，8GB版的目前（2022年8月）淘宝最便宜的是900￥左右，另外需要购买一个树莓派外壳（挑一个外观好看的）、树莓派官方电源、2张64GB的sd卡（2张以备不时之需）、1个读卡器（用于电脑读取sd卡），大概总成本在1100￥（8GB版）或900￥（4GB版）。另外还应该有一台自己的笔记本或台式机（这里需要是装windows系统，因为有些软件就是windows下运行的）。收到东西后，组装一下，把树莓派装进外壳，插上 type C 电源线，此时先不插sd卡。现在要准备一些软件。到 这个网址 下载树莓派的官方刻录工具（用于把树莓派的操作系统写入sd卡），下载的文件名为 imager-1.X.X.exe ，其中 1.X.X 是版本号，选最新的版本，下载完成后安装；点击 这个网址 下载树莓派的操作系统，下载的文件名为 20XX-XX-XX-raspios-bullseye-arm64.img.xz ，然后用7z或rar等软件把它解压成 .img 文件。sd卡插上读卡器、连接到自己的电脑，打开官方刻录工具，操作系统选择 使用自定义镜像 ，找到刚才下载的镜像；sd卡选择插上的sd卡（如果同时连了其他U盘，注意不要选到这些其他的设备），点击右下角的齿轮图标（设置），勾选 开启SSH服务 ，在下面的 Set username and password 里设置一个自己喜欢的用户名及密码（后续还要设置很多的密码，笔者建议所有的地方都用同一个密码，这个密码要8个字符以上，同时包含大小写字母和数字），勾选 配置WiFi ，输入自己的wifi名和密码（如果用网线直连树莓派，则可以不设置wifi。推荐用网线直连），点击保存，点击烧录，等待一会儿。之后弹出sd卡（烧录工具会自动弹出sd卡）并拔下读卡器、sd卡，把sd卡插到树莓派上（方向不要插反了，有金手指的一面朝向树莓派板），连上电源即开机（树莓派没有开机键，连上电开机，如果断电就gg）。接下来使用putty连接到树莓派。首先查找一下树莓派的局域网ip，比如你家里的wifi是路由器开的，那么浏览器登录到路由器的管理页面（详见自己路由器的说明），应该有一个叫 raspberrypi 的设备，记下它的ip地址，一般是 192.168.1.XXX 。然后下载putty，到 putty官网下载页 下载名为 putty-64bit-0.XX-installer.msi 的文件，安装到自己的电脑上。安装好后，打开文件夹内的 putty.exe ，在 Host Name (or IP address) 下面的框中输入树莓派的ip（192.168.1.XXX），右下角点击 Open ，输入烧录镜像时设置的用户名和密码（密码不会显示）即可连接到树莓派上，显示输入命令的界面。以后每次都是这样连接的。下面对树莓派进行一些初始设置。在设置之前，需要进行一些说明。下文中的命令，应当从浏览器中复制，左键点击putty界面，单击鼠标右键即可粘贴（不要自己手动输入），每次只粘贴一行命令，然后按 enter键 执行这行命令。确认自己的网速没问题，在putty界面输入以下命令：sudo su root passwd root raspi-config第二个命令是设置root用户的密码，需要输入。自己设置一个好记的密码，可以和刚才烧录镜像时设置的密码一样，然后会让你再输入确认一次。设置好root的密码后，以后用 sudo su root 可能需要输入root的密码。执行第三个命令时，会进入一个设置页面。按上下移动，到 Advanced Options ，按 enter ，选 Expand Filesystem 。回到一开始的界面，按左右移动，选择右下角的 Finish ，会询问是否现在重启，选择 Yes ，这时putty界面会断开连接，关掉即可。等待一会儿后，重新用putty连接，进行如下操作：sudo su root apt-get update apt-get -y install vim apt-get -y upgrade如果提示中看到了 packagekitd ，则等上10min后再执行上面的命令。第四个命令可能要执行一会儿，可以先看后面（（2）购买并设置服务器）。执行完后，再输入以下的命令：cd /tmp curl https://get.docker.com/ > ./docker_install.sh chmod +x docker_install.sh sh docker_install.sh –mirror Aliyun rm docker_install.sh curl -L https://github.com/docker/compose/releases/download/v2.10.0/docker-compose-`uname -s`-`uname -m` > /usr/bin/docker-compose chmod +x /usr/bin/docker-compose docker-compose -v rebootreboot 命令也是重启。putty会断开连接，关掉，等待1min左右，打开一个新的putty、重新连接。最后，需要设置一下root的登录（后面用WinSCP需要）：sudo su root vim /etc/ssh/sshd_config这里对vim命令进行说明。使用vim命令一般是要对文件内容进行更改，以这里为例，执行 vim /etc/ssh/sshd_config 后会进入展示文件内容的页面（这里是修改一个已存在的文件，所以会显示该文件的原有内容。后面涉及到新建文件，则会显示空的内容），键盘输入 i ，进入编辑模式，可以按方向键上下左右进行移动；然后对文件内容进行修改（这里的修改是复制内容到文件中，所以需要复制内容，点击putty界面，按 shift+鼠标右键 进行粘贴），修改完毕后，按 ESC ，再按 shift+: （这里的 + 表示按住shift键不放，然后按 : ，最后一起松开，后面同理），输入 wq （页面底部会显示 :wq），最后按 enter 即可保存文件并退出。在这个文件的第一行加上：PermitRootLogin yes按上面的方法保存，然后执行命令：service sshd reload rm /etc/localtime ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime date最后的 date 可输出当前时间，这里设置的是上海时间。另外，这里的第三条命令输入的路径较长，像这种路径在输入的时候可以不输入完，而是输入前几个字母后按 tab键 进行匹配，如果前几个字母没有重名的就会自动补全路径。至此，树莓派设置完毕。如果要关机，使用 shutdown now 命令，开机就是连上电源。最后，有时觉得命令行界面输出的内容太多了，可使用 clear 命令清屏。（2）购买并设置服务器购买服务器的主要目的是提供一个公网ip，这样就能从公网访问到自己的EMR或HIS了。如果确定自己只在局域网中用EMR或HIS，也可以只买树莓派。比较大的提供服务器的厂商有阿里云、腾讯云、百度云、华为云等，其中阿里云起步最早、技术最成熟、市场份额最大，价格要贵一些（又称套路云，因为第一年价格便宜、续费价格贵）；腾讯云起步晚一些、技术也不错，价格稍便宜；其他的厂商就不推荐了。总结一下，不差钱就买阿里云，否则就用腾讯云，但实际上两者相差不大。这里以阿里云为例，登录 阿里云官网 ，用支付宝、淘宝等阿里系（qq、微信就是腾讯系）的app扫码登录，在上方的 产品 中找到 热门产品 -> 轻量应用服务器 ，这里选择内地服务器（香港或海外服务器有时网络会有波动），选择一个配置的服务器，先买一个月的低配置服务器，以后再续费和升级。这里的服务器只是负责提供一个公网ip，所以服务器的配置主要看月流量、峰值带宽，一般来说小诊所，1T、5Mbps（原价100￥/月）应该够用了，仅限个人使用的话，400GB、3Mbps（原价60￥/月）也可以。购买之前还可以比比阿里云和腾讯云的价格，有时候赶上优惠活动还能便宜点。购买好服务器后，服务器的镜像选择 Docker ，确认后等待服务器初始化完成。在上方进入 控制台 -> 轻量应用服务器 ，这里是服务器的后台管理页面（或者直接访问 swas.console.aliyun.com ）。这里可以看到服务器的公网ip，就是下面的 47.xx.xx.xx （可能前两个数字会有不同），这个ip需要用到。点击这个服务器、进入详情页面，点击右上角的 远程连接 即可连接到服务器。连接到服务器后，需要进行一些初始设置。需要提前说明一些基础操作：如果远程连接后弹出来的是一整个黑色页面，则按 ctrl+C 、ctrl+V 可以正常进行复制粘贴；若弹出来一个小的黑色框，它和putty是一样的，即可以在自己的电脑上用 ctrl+C 复制文字，在黑色框中单击鼠标右键粘贴。另外，有时某些命令输入后需要确认（弹出一行英文，末尾一般是 [y/n] ），那么就输入 y ，然后按 enter ；有时也可能提示输入 yes ，那么就输入 yes 后 enter ；有些时候可能要输入密码、用户名之类的，也是输入后按enter，但注意这里输入密码时不会显示。下面对服务器初始设置，进行如下操作（有具体操作说明）：sudo su root passwd root第二条命令是设置root用户的密码，输入一个好记的密码，再输入一次确认密码。尽量确保这个密码不要丢失。然后进行如下操作：curl -L https://github.com/docker/compose/releases/download/v2.10.0/docker-compose-`uname -s`-`uname -m` > /usr/bin/docker-compose chmod +x /usr/bin/docker-compose docker-compose -v这一步的目的是安装docker-compose，成功的话会输出版本信息。最后需要开放服务器的8081端口。在云服务器的控制后台点进刚才购买的服务器，在左边的 安全 里有一个 防火墙 ，点击右上角的 添加规则 ，端口范围填 8081 ，然后确定即可。至此，服务器设置完成。最后，有时觉得命令行界面输出的内容太多了，可使用 clear 命令清屏。（3）准备软件这里准备的软件是安装在windows笔记本或台式机上的（用MAC的自求多福）。需要的软件有WinSCP、7z、SD Card Formatter。有时候需要在自己的电脑和服务器之间拷贝文件，对于大多数的windows系统用户，可以选择WinSCP。到 WinSCP的下载页 ，点击 DOWNLOAD WINSCP X.XX.X (XX MB) 下载并安装。在WinSCP的使用上，比如要连到自己的服务器，文件协议选 SCP ，主机名填 自己服务器的公网ip ，用户名为 root ，密码为刚才设置的root密码，点击登录即可；树莓派也选SCP，填其局域网ip，用户名也是root，进行登录。WinSCP连接后的默认界面，左边是自己电脑上的东西，右边是服务器上的东西，互相可以拖动文件拷贝；或者打开 我的电脑 也可往服务器上拷。7z则是一个解压或压缩文件的软件，下载地址在 https://www.7-zip.org/ ，选 Download .exe 32-bit x86 1.2 MB 这个即可，下载完后安装。SD Card Formatter是格式化sd卡的工具，在这篇教程中不会直接用到，但在日常使用中可能会用到。比如一张sd卡以前写入过操作系统，现在不需要了（可能是系统有问题），就用SD Card Formatter把sd卡格式化、写入其他操作系统。SD Card Formatter的下载地址在 https://www.sdcardformatter.com/download/ ，下载windows版的，安装即可。使用时选 quick format 。1.方法一：docker + openmrs这个方法应该要用树莓派。笔者实验时，试图把openmrs运行在云服务器上，但是2核服务器性能不够、跑不了openmrs，表现为卡在 Create OpenMRS tables 中的某一步；4核的服务器说不定可以，但是太贵了。openmrs是一个应用比较广泛的EMR了。openmrs提供了一个演示网页，访问 演示网页 即可试用openmrs。登录用户名密码为admin和Admin123，选择 Outpatient Clinic （门诊），注册病人（Register a patient）、点进病人详情后写一个 Visit Note ，即可体验写简单的门诊病历。下面开始在自己的树莓派上布置一个openmrs。另外，为了节约时间，建议从笔者这里下载后面会用到的东西，下载地址为 https://lisper517.top/resources/OpenMRS/all.zip ，将其下载后解压，里面文件的用途见下文。在 openmrs的下载页 ，提供了四种下载选择，其中 Reference Application - 2.12 - Standalone Edition 和 Platform 2.5.0 - Standalone Edition 是傻瓜包，安装即用（安装在windows系统上的），安装好后要自己安装扩展，不推荐用于实际环境； Reference Application 2.12 - Advanced Installation 和 Platform 2.5.0 - WAR (for Enterprise) 是推荐用于实际环境的，但是需要自己另外搭建数据库（mysql）、Java应用容器（指Apache Tomcat，其一般也可简称为Tomcat），搭建完成后也要自己安装扩展，不得不说openmrs搭建比较复杂。这里笔者使用的就是 Platform 2.5.0 - WAR (for Enterprise) 这个包，其下载速度非常慢，笔者提供的文件里的 openmrs.war 就是下载好的包，其大小为122MB。之后开始搭建，这里使用docker搭建运行openmrs。在树莓派上运行：mkdir -p /docker/openmrs/data/mysql mkdir -p /docker/openmrs/conf/mysql docker pull tomcat:7.0.109 docker pull mysql:8.0.29笔者试用两个Standalone Edition时发现都用的是tomcat:7.0.50，但是这个版本docker官方仓库没有，就用的tomcat:7里最新的7.0.109。不要用8~10版本的tomcat，可能有些小问题。另外需要说明，tomcat的/usr/local/tomcat目录大概可以理解为nginx的/var/www/html，之后将在tomcat容器的/usr/local/tomcat/webapps里放上刚才下载的war包。第一次运行时需要开一次容器复制一下/usr/local/tomcat里的文件，进行如下操作：docker run -itd --name tomcat --rm tomcat:7.0.109 docker cp tomcat:/usr/local/tomcat /docker/openmrs docker container stop tomcat mv /docker/openmrs/tomcat /docker/openmrs/www rmdir /docker/openmrs/www/webapps mv /docker/openmrs/www/webapps.dist /docker/openmrs/www/webapps注意要用webapps.dist代替webapps，否则看不到webapps里的网页。然后编写compose模板（这里对vim开头的命令做一个说明，不会的可以看）：vim /docker/openmrs/docker-compose.yml写入以下内容version: "3.9" services: tomcat: image: tomcat:7.0.109 ports: - "8081:8080" volumes: - /docker/openmrs/www:/usr/local/tomcat depends_on: - mysql logging: driver: syslog mysql: image: mysql:8.0.29 command: "mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci" environment: MYSQL_ROOT_PASSWORD: 123456 volumes: - /docker/openmrs/data/mysql:/var/lib/mysql - /docker/openmrs/conf/mysql:/etc/mysql/conf.d restart: always logging: driver: syslog其中 MYSQL_ROOT_PASSWORD: 123456 是指定数据库的root用户密码，可以把这个密码改的复杂一点，但是没必要记住，因为写在文件中了。另外，这里最好是mysql开启后再开启tomcat，笔者在tomcat服务里使用了 depends_on 来限定，并且tomcat没有设置 restart: always （这样树莓派开机的时候就只有mysql打开，而tomcat还没有打开），这是因为如果tomcat在mysql之前打开的话，openmrs可能会因为找不到数据库而以为自己没有初始化过，后面访问的时候会变成初始的页面，那么之前存储的病人数据虽然没丢，但是找回来也是有些麻烦的。保存完这个文件后，开启服务：cd /docker/openmrs docker-compose config docker-compose up第一次运行需要等待一两分钟，当没有新的日志输出时，浏览器访问 树莓派ip:8081 即可看到tomcat介绍页面（左上角粗体黑字 Apache Tomcat/7.0.109）。接下来把刚才下载的openmrs.war这个文件用WinSCP放到树莓派的/docker/openmrs/www/webapps里，然后tomcat日志会输出以下内容：tomcat_1 | Aug 09, 2022 1:29:00 PM org.apache.catalina.startup.HostConfig deployWAR tomcat_1 | INFO: Deploying web application archive [/usr/local/tomcat/webapps/openmrs.war] tomcat_1 | Aug 09, 2022 1:29:14 PM org.apache.catalina.startup.TldConfig execute tomcat_1 | INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time. tomcat_1 | WARN - OpenmrsUtil.getRuntimePropertiesFilePathName(2128) |2022-08-09T13:29:17,146| Unable to find a runtime properties file at /usr/local/tomcat/openmrs-runtime.properties tomcat_1 | WARN - OpenmrsUtil.getRuntimePropertiesFilePathName(2156) |2022-08-09T13:29:17,225| Unable to find properties file: /root/.OpenMRS/openmrs-runtime.properties tomcat_1 | WARN - OpenmrsUtil.getRuntimeProperties(2089) |2022-08-09T13:29:17,227| Unable to find a runtime properties file. Initial setup is needed. View the webapp to run the setup wizard. tomcat_1 | WARN - OpenmrsUtil.getRuntimePropertiesFilePathName(2128) |2022-08-09T13:29:17,901| Unable to find a runtime properties file at /usr/local/tomcat/openmrs-runtime.properties tomcat_1 | WARN - OpenmrsUtil.getRuntimePropertiesFilePathName(2156) |2022-08-09T13:29:17,903| Unable to find properties file: /root/.OpenMRS/openmrs-runtime.properties tomcat_1 | WARN - OpenmrsUtil.getRuntimeProperties(2089) |2022-08-09T13:29:17,904| Unable to find a runtime properties file. Initial setup is needed. View the webapp to run the setup wizard. tomcat_1 | Aug 09, 2022 1:29:17 PM org.apache.catalina.startup.HostConfig deployWAR tomcat_1 | INFO: Deployment of web application archive [/usr/local/tomcat/webapps/openmrs.war] has finished in [17,598] ms最后一行说明openmrs应用已成功部署，这时浏览器访问 树莓派ip:8081/openmrs 即可看到openmrs初始化界面（版本2.5.0），这里讲一下初始设置：第一个页面语言选择 English （没有中文），第二个页面（选择安装模式）选 Advanced ，第三个页面（数据库相关设置）在 Database connection 里将 localhost 改为 mysql ，修改前：jdbc:mysql://localhost:3306/@DBNAME@?autoReconnect=true&sessionVariables=default_storage_engine=InnoDB&useUnicode=true&characterEncoding=UTF-8修改后：jdbc:mysql://mysql:3306/@DBNAME@?autoReconnect=true&sessionVariables=default_storage_engine=InnoDB&useUnicode=true&characterEncoding=UTF-8这里是因为用容器运行mysql，所以ip填容器名；另外下面选择No，Password填123456（或者自己改的其他密码）。第四个页面（数据库相关设置）第三个问题是填openmrs使用的数据库用户，选No、Password填123456（自建用户总是有一些错误，不如直接把数据库的root给openmrs，反正是容器），第五个页面不改动，第六个页面填两次Admin123（或者自己设置密码，这个密码需要记住，满足下面的4条要求即可，建议所有涉及密码的地方都用一个统一的密码），第七个页面不用填，第八个页面确认一下设置即可开始初始化（没什么好确认的），第九个页面显示初始化进度，需要等待大约6~10分钟。初始化完成后，网页显示登录页面，输入刚才设置的admin、Admin123（或其他密码），会进入Add On Manager页面，这里是管理扩展（modules，也称mod）的页面（用openmrs:2.4.3版本时第一次admin登录说不是admin、无权限管理扩展，这是自带扩展的一个bug；笔者教程中使用的是2.5.0版本，不会遇到这个问题）。现在还不能进入EMR，因为刚安装好的openmrs只是内核、还没有交互界面。openmrs官方mod站在 http://addons.openmrs.org ，上面的mod比较多。官方mod站中间上面点击RefApp 2.11，这里列出的扩展大概是功能比较齐全的，都装上就差不多了，但是这里下载也是太慢了，在笔者提供的压缩文件中，modules文件夹里面就是下载好的扩展。这里要安装的扩展比较多，先在putty里按一下 ctrl+C 停止openmrs，然后把这些扩展（omod文件）用WinSCP移动到 /docker/openmrs/www/webapps/openmrs/WEB-INF/bundledModules 目录下（这个目录下不应该出现文件夹，而只应该有omod文件），另外要注意该目录下已经有fhir2、owa、webservices.rest三个扩展，把它们3个删掉。移动完毕后，再次启动openmrs：cd /docker/openmrs docker-compose up等待大约8分钟后，不再有新日志输出，最后一行日志为：INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.浏览器访问 树莓派ip:8081/openmrs ，可看到页面UI比较完善了。以用户密码admin、Admin123（或自己设置的密码），选择 Outpatient Clinic （门诊）登录系统并试用。这里讲一下openmrs的基础操作，主要是门诊病历书写：书写病历首先需要一个病人。如果是以前看过的病人，在 Find Patient Record 里可以找到；如果是新病人则需要注册，选择 Register a patient，按步骤填入病人信息（推荐middle name写中文，其他两个写拼音），注意有些信息可以不填。在 Find Patient Record 里找到病人后点击该病人，或者注册病人后自动跳转，都会来到这个病人的详情页，点击右侧 General Actions 里的 Start Visit 即可开始接诊，新建 Visit Note 即可开始写病历。这个病历比较简陋，只有诊断和病历主体；诊断方面，比如输入 pulp ，有一个牙髓根尖周疾病的诊断，可以添加为主诊断，然后添加第二、第三诊断，就是背这些疾病的英文名比较麻烦，也可以自己写中文诊断，诊断字典里没有的也可以；病历主体方面，需要自己准备病历模板。由于这个病历主要是自己能看懂，所以写一些关键的就行了，比如做根管时几个根管的长度；病人有没有全身疾病等。另外需要注意，openmrs里使用的时区和中国时区不同，笔者暂时没找到改时区的方法。一般openmrs里的时间比中国上海时间早8个小时。现在已经完成了openmrs的搭建。如果想在实际环境中使用这种方法，为了安全，可对openmrs进行备份，树莓派上输入：mkdir -p /backup/openmrs crontab -e加上一行：0 6-20/1 * * * /usr/bin/zip -r /backup/openmrs/backup.zip /docker/openmrs这样就在6点到20点之间，每小时对openmrs备份一次。如果某天openmrs不能进入，可用WinSCP把/backup/openmrs目录下的这个backup.zip备份文件复制到自己的电脑上后解压（建议每过几天就把这个文件转移到自己的电脑上一次），按前面 0.准备工作-（1）购买并设置树莓派 的方法重新准备一张写入树莓派系统的sd卡，在新的树莓派系统中使用 mkdir /docker 命令，然后把解压后的文件夹用WinSCP放到/docker目录下，最后输入下面两行命令：cd /docker/openmrs docker-compose start mysql docker-compose up即可，非常的简单。另外需要提醒，如果在实际环境中使用此方法，放到公网上的话（通过公网访问树莓派的方法见附录），建议晚上不开，需要写病历的时候再开（每次打开需要等待10min左右）：#这两行是关闭openmrs的命令 cd /docker/openmrs docker-compose stop #这三行是打开openmrs的命令 cd /docker/openmrs docker-compose start mysql docker-compose up -d注意打开的命令稍微有所变化， docker-compose start mysql 输入后建议等待至少2min后再输入 docker-compose up -d ，保证mysql服务在tomcat之前打开；使用 docker-compose up -d 时不会输出日志，也可以安全关闭putty。而且openmrs自带了4个其他用户，为了安全，建议在 System Administration - Manage Accounts 里把这4个用户 retire 掉。最后，其实openmrs在github上提供了 官方compose模板 ，但是比较难用，因为maven（一种包管理器）在国内下载太慢，感兴趣的可以通过 v2ray + v2rayA 连接到国外源试试（见 Docker实践——如何查阅wiki百科及V2Ray ）。官方compose的使用方法是点击 Code ，在下拉菜单中点击 Download ZIP ，下载后把zip包解压，改名为openmrs，在树莓派上用 mkdir /docker 创建一个目录，把这个openmrs文件夹放到/docker目录下，进行如下操作：cd /docker/openmrs docker-compose build docker-compose up即可安装openmrs，同样在浏览器中访问，只是把 树莓派ip:8081/openmrs 改成 树莓派ip:8080/openmrs 。但由于网络问题，多半无法用官方模板构建，笔者即使成功构建，也无法正常访问openmrs。到这里已经完成openmrs的介绍。如果想试试后面的方法，可使用如下命令把树莓派系统恢复到初始状态（先按 ctrl+C 再执行下列命令）：cd /docker/openmrs docker-compose stop docker container rm openmrs-tomcat-1 docker container rm openmrs-mysql-1 docker image rm tomcat:7.0.109 docker image rm mysql:8.0.29 docker network rm openmrs_default cd / rm -r /docker/openmrs若提示 Error: No such container: openmrs-tomcat-1 ，则把所有 - 换成 _ 试试。总结一下，openmrs比较简洁，可以根据需要自己安装不同的扩展、满足多种需要。但是openmrs需要自己下扩展，算是一个扣分项（不过笔者提供的扩展已经够用了）。可根据自己的情况决定是否要用openmrs。2.方法二：gnuhealth 的树莓派系统gnuhealth是一个开源的HIS系统，2008年由一个西班牙医生 Luis Falcón 创立。gnuhealth的安装可分为两种方法，一是系统镜像法，二是正常安装。gnuhealth也提供了一个试用的网页，地址为 http://federation.gnuhealth.org:8000/ ，用户名是admin，密码为gnusolidario。gnuhealth的使用将在后面说明。下面这两个网页提供给懂linux的读者，gnuhealth的安装说明在 https://en.wikibooks.org/wiki/GNU_Health/Installation ，embedded版的初步说明则在 https://en.wikibooks.org/wiki/GNU_Health/Embedded （这个基本没用）。若无法访问，可从笔者的网页备份查看，即 安装说明 和 embedded版说明 。方法二、三都主要参照了第一个网页。在 gnuhealth官网 上提供了树莓派版本的embedded系统镜像，可以将树莓派打造成一个HIS的服务器（调度中心）。它的用法和写入树莓派系统镜像是一样的，也是用烧录工具把一个img文件写入sd卡中，然后树莓派插上sd卡就完事。镜像的官网下载页面在 https://gnuhealth.org/embedded.html （这个网页可以不看），实际的树莓派4系统镜像下载页面在 https://www.gnuhealth.org/downloads/embedded/raspberry/rpi4/ ，下载.gz结尾的文件，解压得到img镜像（本文写作时，镜像名为 gnuhealth-3.6.4-rpi4-SD-opensuse-leap15.2-xfce.img ），用树莓派官方烧录工具写到sd卡（这一次只选镜像和sd卡，不用改设置）、插到树莓派上，树莓派插上网线开机。注意一定要插上网线，因为这里写入操作系统时没法设置wifi。开机大约3min后，同样是在路由器里找到树莓派的局域网ip，用putty连接（如果 connetion refused 就再等一会儿）。系统有两个用户，分别是root和gnuhealth，密码都是 freedom ，用root进入系统（后面每次重启都是用root进系统），进行以下操作：passwd root passwd gnuhealth vim /etc/sysconfig/network/config前两行命令是更改root和gnuhealth用户的密码，把密码改成自己的。第三行命令会打开/etc/sysconfig/network/config这个文件，其中有一行是 NETCONFIG_DNS_STATIC_SERVERS="" （按ESC，按 / 后粘贴内容，然后按enter，可以查找），把它改成：NETCONFIG_DNS_STATIC_SERVERS="8.8.8.8 8.8.4.4"这是因为gnuhealth没有设置DNS服务器（DNS可以把域名解析成公网ip），所以添加了google的两个DNS服务器。然后进行以下操作：reboot apt-get update apt-get upgrade -y reboot第三个命令可能要执行一会儿。重启、重新用putty连接后，输入以下命令：rm /etc/localtime ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime date apt-get install -y xrdp systemctl enable xrdp systemctl start xrdp前三条命令用于更换时区为上海，使用 date 命令可显示正确的时间。后三条命令是安装xrdp（远程桌面相关），接下来试试用远程桌面连接到树莓派，gnuhealth用的OpenSUSE，自带tigervnc，也就是Xvnc，开启时先设置一个密码：vncpasswd这个是tigervnc的密码，建议可以和root、gnuhealth用户的密码一样；需要输入两次，另外还会提示是否输入一个只能看的密码（用这个密码连接远程桌面时只能看不能动），这个可以不设置。然后在自己的笔记本或台式机上，按 win+R ，输入 mstsc 后执行，打开windows的远程桌面（或者在搜索框中，就是关机键旁边的放大镜图标中搜索远程桌面），输入树莓派的局域网ip，连接，选择 Xvnc ，用户名和密码就是gnuhealth（这里不要用root登录）和刚才设置的密码。桌面左下角第四个图标是 xterm ，打开后执行：/home/gnuhealth/.local/bin/gnuhealth-client就会打开gnuhealth的客户端，可以直接连入试用（大概看一下界面即可），密码是gnusolidario。这里的数据库是health36rpi，用户名是admin，需要记住。然后在windows机器上用对应的tryton客户端5.0.23版本查看，访问 tryton客户端5.0.23下载地址 ，安装后，使用方法和刚才一样，Host填 树莓派ip:8000，Database是health36rpi，User name为admin，输入密码登录。这里的gnuhealth版本比较老（ ls -al /home/gnuhealth 可以看到有个 gnuhealth-3.6.4 ，说明这个版本是3.6.4；另外这里用的tryton是5.0.23版，最新的已经到6.0.17版了），可以升级一下（下面的操作不建议跟做，因为最后有一些问题）。根据 gnuhealth升级教程 （或 升级教程备份网页），进行下面的操作：su root systemctl stop gnuhealth mkdir -p /backup/gnuhealth chown -R gnuhealth:users /backup/gnuhealth su gnuhealth ~/gnuhealth/tryton/server/util/gnuhealth-control backup --backdir /backup/gnuhealth --database health36rpi mv ~/gnuhealth ~/gnuhealth_3.6.4如果下载版本不同，可能 health36rpi 这里要改动。到这里为止就把老版本的gnuhealth进行了备份，接下来安装新版本的gnuhealth：cd ~ wget https://ftp.gnu.org/gnu/health/gnuhealth-latest.tar.gz tar -xzf gnuhealth-latest.tar.gz rm gnuhealth-latest.tar.gz ls -al笔者下载时，最新版本为 gnuhealth-4.0.4 ：cd gnuhealth-4.0.4 wget -qO- https://ftp.gnu.org/gnu/health/gnuhealth-setup-latest.tar.gz | tar -xzvf - mkdir ~/.pip vim ~/.pip/pip.conf在 pip.conf 中写入以下内容：[global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple [install] trusted-host=mirrors.aliyun.com这步操作是把python源换成国内的。然后继续执行：pip install --upgrade pip bash ./gnuhealth-setup install cd .. source ~/.gnuhealthrc createdb test cdexe python3 ./trytond-admin --all --database=test本来这里的 --database= 应该用 health36rpi 或者 ghdemo40 的，由于笔者在使用原来的demo或者后来新的demo数据库时有些问题（缺各种module），所以这里用 createdb test 命令新建了一个新的test库（由于是空数据库，后面登录查看是完全没有数据的）。最后一个命令要运行几分钟，之后会要求输入一个邮箱，可以填自己的邮箱（也可以随便填）；设置两次密码，还是建议用通用密码。最后开启服务：su root systemctl start gnuhealth然后就可以用最新的tryton客户端查看了，比如在windows机器上访问 tryton客户端下载地址 ，安装后，使用方法和刚才一样，Host填 树莓派ip:8000，Database是test，User name为admin，输入密码登录，但是数据库是空的。gnuhealth的树莓派系统差不多就是这样使用，它的优点是使用较简单；缺点是扩展性差，比如不能装docker，因为这个发行版是OpenSUSE；但是docker对HIS功能完全没有影响，如果只用树莓派跑gnuhealth还是完全ok的；还有就是gnuhealth的升级（迁移旧的数据库）有些问题，笔者不知道如何解决，如果自己新建一个数据库又不知道怎么把它打造成和官方的demo一样；如果新的tryton客户端能支持访问老版本也好，可惜不能，只能用老版本的tryton客户端；或者好像也可以不用下tryton客户端、直接用网页访问gnuhealth。可惜gnuhealth在这三个方面都介绍的语焉不详，感觉并不是很方便。所以在下一种方法中，笔者试着从树莓派的官方镜像开始，搭建gnuhealth，可惜遇到了同样的问题。3.方法三：gnuhealth 直接安装（无法使用）按 （1）购买并设置树莓派 中的方法准备一个树莓派的操作系统，然后开始安装。根据 安装说明 ，gnuhealth需要的环境有：Operating system: GNU/Linux or FreeBSD for the server. RDBMS Database: PostgreSQL >= 10.x Document-oriented Database for Health Information System / Person Master Index: PostgreSQL :>= 10.x Python: >= 3.6 Gunicorn : 19.9 # WSGI HTTP服务器，用pip安装 Flask : 1.0 # python写的Web应用框架 Tryton 6.0 # python写的，提供登录和界面，以及客户端 Bash shell PIP for Python 3 # 要求用pip命令时是pip3但这里面只有postgresql是需要自己准备的，其他的东西要不就是不重要，要不就是树莓派系统自带，要不就是gnuhealth提供了一个安装脚本、可自动安装。唯一一个例外是Gunicorn，根据gnuhealth的wiki手册，gnuhealth默认使用的HTTP服务器是Werkzeug、使用时要下载tryton客户端，如果换成Gunicorn就可以从浏览器打开、不用装tryton客户端，可惜介绍的并不是很详细，笔者也没有兴趣继续探索（openmrs已经能完全满足笔者需要了），希望懂行的朋友可以在评论不吝赐教。用root用户登录树莓派，在树莓派上进行如下操作：adduser gnuhealth apt-get install -y postgresql postgresql-client service postgresql start su - postgres -c "psql -t -P format=unaligned -c 'show hba_file'"注意第二个命令执行时需要设置密码，另外还有 Full Name 这些东西（这些可直接按enter跳过）。最后一个命令会显示 /etc/postgresql/XX/main/pg_hba.conf （或者是 /var/lib/pgsql/data/pg_hba.conf ），记住数字（笔者实验时为13），备份并修改这个文件：su postgres cp /etc/postgresql/XX/main/pg_hba.conf /etc/postgresql/XX/main/pg_hba.conf_backup vim /etc/postgresql/XX/main/pg_hba.conf对没有注释的部分，改成下面这样：# TYPE DATABASE USER ADDRESS METHOD # "local" is for Unix domain socket connections only #local all all peer local all all trust # IPv4 local connections: host all all 127.0.0.1/32 trust # IPv6 local connections: host all all ::1/128 trust # Allow replication connections from localhost, by a user with the # replication privilege. local replication all peer host replication all 127.0.0.1/32 ident host replication all ::1/128 ident然后进行以下操作：su root service postgresql restart su - postgres -c "createuser --createdb --no-createrole --no-superuser gnuhealth" su gnuhealth cd ~ wget https://ftp.gnu.org/gnu/health/gnuhealth-latest.tar.gz tar -xzf gnuhealth-latest.tar.gz rm gnuhealth-latest.tar.gz ls -al这时应该会显示一些文件，其中有一个的名字为 gnuhealth-X.X.X ，笔者实验时的版本为 gnuhealth-4.0.4 ，下面以 gnuhealth-X.X.X 指代：cd gnuhealth-X.X.X ./scripts/demo/install_demo_database.sh 40 wget -qO- https://ftp.gnu.org/gnu/health/gnuhealth-setup-latest.tar.gz | tar -xzvf - mkdir ~/.pip vim ~/.pip/pip.conf这里的第二行命令是用gnuhealth自带的脚本下载demo数据库，其数据库下载网页为 这个网页 ，如果要下载其他的就把 40 改成 32 、 34 等。最后一行命令，也是修改一下python源，写入：[global] index-url = https://pypi.tuna.tsinghua.edu.cn/simple [install] trusted-host=mirrors.aliyun.com然后安装gnuhealthpip install --upgrade pip bash ./gnuhealth-setup install cd .. source ~/.gnuhealthrc createdb test cdexe python3 ./trytond-admin --all --database=test本来应该 --database=ghdemo40 ，但是demo数据库有些问题。同样用 createdb test 创建test数据库代替。这里也会要求输入一个邮箱，可以填自己的邮箱（也可以随便填）；设置两次密码，还是用通用密码。然后继续执行：su root vim /etc/systemd/system/gnuhealth.service写入：[Unit] Description=GNU Health Server After=network.target [Service] Type=simple User=gnuhealth WorkingDirectory=/home/gnuhealth ExecStart=/home/gnuhealth/start_gnuhealth.sh Restart=on-abort [Install] WantedBy=multi-user.target然后修改一下 start_gnuhealth.sh ：su gnuhealth vim /home/gnuhealth/start_gnuhealth.sh将最后一行的：python3 ./trytond $@ || bailout改成：python3 ./trytond $@ --logconf /home/gnuhealth/gnuhealth/tryton/server/config/gnuhealth_log.conf || bailout这是设置gnuhealth的log格式，在上一个方法中其实也可以这样写。最后开启gnuhealth：su root systemctl enable gnuhealth systemctl start gnuhealth同样在windows机器上访问 tryton客户端下载地址 下载tryton客户端，安装后，使用方法和刚才一样，Host填 树莓派ip:8000，Database是test，User name为admin，输入密码登录，但是数据库是空的。从树莓派系统搭建的gnuhealth扩展性强了，可以装docker，但是笔者同样不知道如何安装demo数据库、用gunicorn从网页访问gnuhealth。4.了解：新致“开源”医疗新致“开源”医疗的官网为 http://code.tntlinking.com/#/detail/1 ，打着开源的噱头，其实是商业公司开发的产品。它的官网是面对企业开发者的，需要关注微信公众号，手机号注册签约，并同意签约协议才能下载源码，协议内容如下：新致开源医疗项目商业合作协议 更新日期：2022年6月6日 新致开源医疗项目商业合作协议（下称“本协议”）包含您在经销新致开源医疗平台的商品或服务时所应遵守的条款和条件。本协议由武汉新致医卫信息技术有限公司（下称“本公司”或“我们”）与您或您所代表的实体（下称“用户”或“您”）签订。如果您代表某一实体签订本协议，您向我们声明并保证，您具有合法代表该实体之权限且有可约束该实体遵守本协议条款与条件之合法权限。除非我们另有通知，本协议自您接受之日起生效。您与本我们在下文中单独提及时会被称作一方，一同提及时会被称作双方。 根据《中华人民共和国民法典》的有关规定，经双方友好协商，本着长期平等合作，互利互惠的原则，为实现“新致开源医疗产品（HIS）商业发行版”（以下简称“发行版”）的商业推广和联合运营，创造良好的经济效益和社会效益，达成以下协议： 一、合作宗旨 促进国内普惠医疗数字化的发展，利用您广泛的市场资源优势，发挥我们医疗软件的研发能力，合作实现发行版的技术升级与市场营运。 二、合作范围 1、市场推广 1.1、双方共同对发行版进行推广、销售，包括但不限于直接或间接参加医院系统的招投标、代理招募和中小规模诊疗机构推广； 1.2、双方共同对发行版进行品牌力提升，包括但不限于参加国内各类重大医疗行业大会。 2、产品研发 2.1、推动发行版和其他系统进行兼容、集成； 2.2、对发行版进行迭代开发，遵循GPL开源协议（《GNU GENERAL PUBLIC LICENSE》）。 三、合作方式 1、您以现有的市场营销网络及社会资源为基础，进一步的开发市场潜力，逐步形成一个规范化且有区域影响力的营销网络； 2、您根据市场客群需求，推广发行版，收集反馈用户需求； 3、我们通过天天数链平台（www.tntlinking.com）提供经过确权的发行版源代码及相关资料库。 四、权力及义务 1、您的义务 1.1、您承诺并同意在发行版合作期内，必须遵守国家相关法律、正规合法运营及缴纳税收，如发生任何违法违纪行为所产生的一切法律责任及赔偿均由您独立承担； 1.2、您严格在我们许可下使用发行版，需在系统中保留“武汉新致医卫信息技术有限公司@2017开源授权”的字样； 1.3、根据商业化推广要求，您在与客户完成签约后10日之内，向我们报备客户相关信息，提供签章合同扫描件的关键页。 2、您的权利 2.1、我们授权您对发行版进行销售、修改，修改过程中需遵守本协议第四条4.1.2款约定； 2.2、您在发行版销售过程中，有权自主决定目标客户、销售价格、服务条款等相关内容。 3、我们的义务 3.1、提供给您的发行版拥有原始且完整的著作权； 3.2、对发行版产品进行持续研发和升级； 3.3、在合作过程中，对涉及到您的有关机密数据进行严格保密。 4、我们的权利 4.1、我们对您推广发行版的市场情况具有知情权； 4.2、我们对您使用发行版运营收益情况具有知情权； 4.3、我们可书面询问您上述两款的情况，您应于5个工作日内进行答复并对我们提出的疑义提供材料证明。 五、利益分配 1、分配规则 1.1、若报价以产品加实施的方式成交，则我们收取产品成交价的40%； 1.2、除上述5.1.1所述的报价方式外成交的，我们收取项目成交价的15%。 2、支付方式 2.1、您应在收到客户第一笔款后的7日内告知我们，在收到我们按照同比例额度开具的增值税专用发票（税率：6%）后30日内支付给我们； 2.2、您应在收到客户最后一笔款后的7日内告知我们，在收到我们按照剩余额度开具的增值税专用发票（税率：6%）后30日内支付给我们； 2.3、我们须保证所提供发票的真实有效合法性。您逾期告知或逾期付款的，每逾期一天，应按逾期款的1%（百分之一）向我们支付违约金，直至支付完毕之日止。逾期若达30日，我们有权单方面解除本协议。 六、其它 1、适用法律及管辖 本协议适用法律为中华人民共和国法律。您和平台同意，本协议签订地为中国上海市浦东新区。在合同履行过程中双方如发生纠纷，应协商解决；协商不成的，双方应向协议签订地有管辖权的人民法院以诉讼的方式解决。 2、声明 您在使用本公司提供的签约下载服务之前，请您务必审慎阅读、充分理解本协议各条款内容，包括但不限于免除或者限制责任的条款。如您不同意本服务协议及/或随时对其的修改，您可以主动停止使用新致公司提供的签约下载服务；您一旦使用本公司提供的签约下载服务，即视为您已了解并完全同意本服务协议各项内容，包括本公司对服务协议随时所做的任何修改，并成为我们的用户。该协议有几个关键点：（1）使用GPL开源协议。GPL要求必须开放源码，不能做成闭源的商业软件发布，但是可以做成开源的商业软件收钱。（2）如果开发者二次开发的产品卖出钱了，该公司收取40%或15%。（3）一旦同意该协议，公司后续若对该协议进行更改，也视为自动同意。或者也可以选择解约。光同意该协议还没完，后面需要输入公司信息等，所以这个“开源”系统不是面对个人开发者的，主要是卖钱的。这其实也是一个缩影。在github或sourceforge上可以找到国外真正开源的HIS，虽然别人打着援助落后地区的旗号、可能不适用于国内医院，但确实是真开源；国内的市场太小，也没有这种需求，难以找到真正免费开源的HIS。附录：通过公网访问EMR或HIS这里用到了frp，并且是通过docker运行frp。更详细的内容见 Docker树莓派实践——frp（附dashboard开启https、frp双向验证） 。在方法二中，系统镜像为OpenSUSE、装不了docker，则也可以在树莓派上直接安装frp，其下载地址为 https://github.com/fatedier/frp/releases 。在方法一中，openmrs使用的是树莓派的8081端口；在方法二、三中，gnuhealth使用的是8000端口。在购买的云服务器上进行如下操作（云服务器使用Docker镜像）：sudo su root mkdir -p /docker/frps/conf vim /docker/frps/conf/frps.ini写入如下内容：[common] bind_port = 7000 token = 你的密码 dashboard_port = 7500 dashboard_user = wuhuqifei dashboard_pwd = wuhuqifei kcp_bind_port = 7000token即密码，要设置的复杂一点；dashboard_user和dashboard_pwd也可自行更改。然后在云服务器的防火墙中打开7000、7500端口。注意，是在云服务器详情的 安全-防火墙 中新增两个个规则，打开这两个端口；另外，在刚才云服务的设置里，应该也打开了8081端口。然后在云服务器上继续操作：docker run -d --name frps -v /docker/frps/conf/frps.ini:/etc/frp/frps.ini --network host --restart=always snowdreamtech/frps之后可以查看 云服务器公网ip:7500 ，输入dashboard_user和dashboard_pwd即用户名和密码，查看frps的运行情况。在树莓派上进行如下操作：mkdir -p /docker/frpc/conf nano /docker/frpc/conf/frpc.ini写入如下内容：[common] server_addr = 云服务器公网ip地址 server_port = 7000 token = 你的密码 login_fail_exit = false [pi4B-http-1] type = tcp local_ip = localhost local_port = 8081 #如果是gnuhealth，则改成8000 remote_port = 8081然后执行：docker run -d --name frpc -v /docker/frpc/conf/frpc.ini:/etc/frp/frpc.ini --network host --restart=always snowdreamtech/frpc最后访问 云服务器公网ip:8081/openmrs ，即可从公网访问到自己的openmrs；或者在tryton客户端的Host填 云服务器公网ip:8081 ，可通过公网访问gnuhealth。
步骤/目录： 1.编写compose模板 2.运行并验证本文首发于个人博客https://lisper517.top/index.php/archives/25/，转载请注明出处。本文的目的是使用docker、docker-compose在树莓派上运行qbittorrent。本文实验日期为2022年8月1日。本文使用的是树莓派4B（内存8G版），系统为Pi OS 64位桌面版（2022年4月4日更新，镜像名 2022-04-04-raspios-bullseye-arm64.img ）。之前的文章中曾提到过qbittorrent，详见 树莓派+Samba+frp搭建NAS（网络云存储） 。简单来说，qbittorrent的作用是离线下载，一般搭配NAS使用。在docker官方仓库网页进行搜索，最后笔者选用的是linuxserver/qbittorrent镜像，其下载量为100M+，一个月前更新。1.编写compose模板如前所述，笔者使用树莓派+samba搭建了NAS，挂载一块硬盘到分享目录，路径为/home/samba_user/shared。如果没有NAS也无所谓，但由于树莓派本身存储较小，最好还是外接存储。另外，samba目前在官方仓库没有下载量大的官方镜像，所以仍然是直接在本地跑。在树莓派上进行如下操作：docker pull linuxserver/qbittorrent mkdir -p /docker/qbittorrent/conf mkdir /docker/qbittorrent/ssl_certificate id samba_user #这里的samba_user是为samba创建的系统用户 #输出uid=1002、gid=1003 nano /docker/qbittorrent/docker-compose.yml写入以下内容：version: "3.9" services: qbittorrent: image: linuxserver/qbittorrent environment: - PUID=1002 - PGID=1003 - TZ=Asia/Shanghai - WEBUI_PORT=8080 volumes: - /docker/qbittorrent/conf:/config - /home/samba_user/shared/DLs:/downloads - /docker/qbittorrent/ssl_certificate:/ssl_certificate ports: - "8080:8080" - "6881:6881" - "6881:6881/udp" restart: unless-stopped这里需要解释一下docker-qbittorrent的注意事项。PUID、PGID用于指定一位宿主机系统用户，该用户需要拥有宿主机的下载目录（本例中为/home/samba_user/shared），这样才不会有读写权限的问题，使用 id 用户名 可查看uid、gid。2.运行并验证cd /docker/qbittorrent docker-compose config docker-compose up若要后台运行docker-frpc，可使用docker-compose up -d命令。这时使用ufw打开8080、6881端口，即可浏览器登录到 树莓派ip:8080 ，出现qbittorrent网页UI，初始用户名密码为admin和adminadmin。在顶部工具栏下方，右边的齿轮图标为设置。接下来对qbittorrent进行一些初始设置：在 Web UI 中将语言改为中文；在 下载 中，将 默认Torrent管理模式 改为自动，勾选 保存未完成的torrent到 、 复制.torrent文件到（这两个目录自选），下面的 下载完成时发送电子邮件到 用的也是SMTP、有需要的可以打开， Torrent完成时运行外部程序 也可根据自身需要打开；在 速度 中，可以选择下载速度限制；在 BitTorrent 中，可调整最大同时下载数；在 Web UI 中，可设置使用HTTPS，修改用户名密码。
步骤/目录： 1.准备工作 2.编写compose模板 3.运行并验证 4.gogs使用https 5.gogs备份本文首发于个人博客https://lisper517.top/index.php/archives/24/，转载请注明出处。本文的目的是使用docker、docker-compose在云服务器上运行gogs。本文实验日期为2022年7月28日。本文使用的云服务器为阿里云轻量应用服务器，系统镜像 Docker 19.03 。之前的文章中曾介绍过gogs，详见 树莓派+云服务器，gogs+frp搭建私有git 。简单来说，使用gogs可以搭建一个私有的轻量级的git服务器，一般gogs用于个人或小微企业。在docker官方仓库网页可以搜索到官方gogs镜像即 gogs/gogs ，其下载量为50M+，3天前更新。下面就使用该镜像+mysql镜像搭建gogs服务。1.准备工作和frp的部署一样，笔者准备了一台云服务器，如果只打算在局域网用gogs也可以在本地机器上运行。在服务器上准备目录，拉取镜像：mkdir -p /docker/gogs/data/gogs mkdir /docker/gogs/data/mysql mkdir -p /docker/gogs/conf/mysql docker pull gogs/gogs docker pull mysql:8.0.29这里需要简单说明一下docker-gogs的使用。容器内的22端口用于git命令相关操作，3000用于网页访问；容器内的/data目录包含了仓库、gogs的个性化设置。2.编写compose模板编写compose模板：vim /docker/gogs/docker-compose.yml写入以下内容：version: "3.9" services: gogs: image: gogs/gogs ports: - "50023:22" - "9444:3000" volumes: - /docker/gogs/data/gogs:/data depends_on: - mysql logging: driver: syslog restart: always mysql: image: mysql:8.0.29 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: gogs MYSQL_USER: gogs MYSQL_PASSWORD: 234567 command: - mysqld - --character-set-server=utf8 - --default-authentication-plugin=mysql_native_password volumes: - /docker/gogs/data/mysql:/var/lib/mysql - /docker/gogs/conf/mysql:/etc/mysql/conf.d logging: driver: syslog restart: always根据 docker-mysql 的介绍，通过 MYSQL_USER 、 MYSQL_PASSWORD 创建的用户，对通过 MYSQL_DATABASE 创建的数据库享有所有权限。3.运行并验证cd /docker/gogs docker-compose config docker-compose up记得在云服务器的设置里打开9444、50023端口。这时可以访问 云服务器ip:9444 ，出现gogs初始页面。这里谈一点gogs的初始设置：数据库选mysql，数据库主机填mysql:3306，用户密码就是gogs@234567。基本设置里的域名，笔者使用云服务器就填服务器ip，应用URL则不影响实际、只是展示用。SMPT设置和typecho的CommentNotifier插件差不多，见 Docker树莓派实践——Typecho及其备份 。如果是私人或小微企业，可以在 禁止用户自主注册 、 启用登录访问限制 里打勾。具体配置细节还是参考 gogs/gogs镜像原址 。另外，若要后台运行docker-frpc，可使用docker-compose up -d命令。4.gogs使用httpsgogs可以使用https，这里笔者使用的是自己域名的证书，也可以用自签证书。mkdir /docker/gogs/ssl_certificate cp /docker/gogs/data/gogs/gogs/conf/app.ini /docker/gogs/data/gogs/gogs/conf/app.ini_backup vim /docker/gogs/data/gogs/gogs/conf/app.ini将自己的证书放到/docker/gogs/ssl_certificate中后，对gogs的配置文件app.ini进行以下修改：[server] ...... ...... #新增4行 PROTOCOL = https ROOT_URL = https://lisper517.top:3000 CERT_FILE = /ssl_certificate/www.lisper517.top.pem KEY_FILE = /ssl_certificate/www.lisper517.top.key修改gogs配置文件主要参考了 这篇文章 ，其中还介绍了使用nginx反向代理。然后修改compose模板，新增一个挂载：- /docker/gogs/ssl_certificate:/ssl_certificate最后删除容器、重新生成：docker stop gogs_mysql_1 docker stop gogs_gogs_1 docker container rm gogs_mysql_1 docker container rm gogs_gogs_1 docker network rm gogs_default docker volume ls docker volume prune #gogs用了一个匿名卷也删掉 cd /docker/gogs docker-compose config docker-compose up最后在浏览器访问 https://lisper517.top:9444 即可看到使用了https的gogs页面。5.gogs备份为了定时备份，同 Docker树莓派实践——Typecho及其备份 中最后一部分一样，在服务器上进行如下操作：mkdir -p /backup/gogs crontab -e加上一行：15 0 * * * /usr/bin/zip -r /backup/gogs/backup.zip /docker/gogszip命令写绝对路径是怕cron运行时的env里PATH不包括/usr/bin，实际上应该尽量都写绝对路径。用whereis zip可查找zip的路径。这样每天0点15分就会备份整个docker-gogs。最后，可以在树莓派上也设置每天1点15分拷贝服务器上的/backup/gogs/backup.zip备份文件，具体操作如下：mkdir -p /backup/gogs mkdir /backup/key #存放密钥 crontab -e加上如下内容：15 1 * * * /usr/bin/scp -i /backup/key/alicloud-backup root@lisper517.top:/backup/gogs/backup.zip /backup/gogs关于打开树莓派的crontab日志功能、设置正确的树莓派系统时间见 Docker树莓派实践——Typecho及其备份 。另外，在gogs后台可以看到有备份仓库的功能，笔者这里为了方便镜像更新后数据迁移，就直接备份文件夹了。
步骤/目录： 1.方法一：独立镜像、network连接 （1）准备工作 （2）运行容器与测试 （3）收尾与总结 2.方法二：docker-compose （1）准备工作 （2）编写文件并运行项目 （3）收尾与总结 3.实战：编写易移植的typecho博客并使用https （1）准备工作 （2）编写nginx配置及compose模板 （3）运行项目并测试 （4）问题及其他 a.博客迁移 b.安装完成后无法登录 c.typecho镜像选择 d.typecho插件及其他 （5）后续 （6）为typecho添加其它功能 a.在文章页面添加最新文章 b.添加侧边栏目录索引 c.添加Sitemap d.添加网站图标 （7）使用其它主题 （8）升级版本本文首发于个人博客https://lisper517.top/index.php/archives/22/，转载请注明出处。本文的目的是使用docker、docker-compose在树莓派上运行Typecho。本文实验日期为2022年7月26日。本文使用的是树莓派4B（内存8G版），系统为Pi OS 64位桌面版（2022年4月4日更新，镜像名 2022-04-04-raspios-bullseye-arm64.img ）。之前的文章中曾介绍过Typecho，详见 树莓派搭建个人博客 。简单来说，Typecho是LNMP环境下运行的个人博客。学习Docker后，发现docker官网有typecho官方维护的镜像，于是试着用docker运行typecho。另外，这个镜像的下载量为100K+，但更新时间为3年前。官方镜像虽然只有50K下载，但最近更新比较勤（据说之前typecho五年没更新，docker镜像就更不会更新了），所以下面还是使用官方镜像。在typecho官方镜像页可以看到后缀不同的镜像，这些镜像的命名格式是：nightly-php版本号-后缀。php的版本主要有7.3、7.4、8.0，后缀主要有 空后缀、cli、fpm、apache、alpine 。本文选用的镜像是nightly-php8.0-apache。1.方法一：独立镜像、network连接主要思路是使用nightly-php8.0-apache镜像（apache和nginx作用一样，也是网页服务器），搭配docker-mysql。两个容器独立运行，使用docker network连接。需要注意typecho设置时如何填写数据库地址。在这种方式中，可以不用Dockerfile或docker-compose。（1）准备工作准备文件夹并拉取镜像：mkdir -p /test/mysql/data mkdir /test/mysql/conf docker network create -d bridge net_test docker pull mysql:8.0.29 docker pull joyqi/typecho:nightly-php8.0-apache拉取typecho镜像时比较慢，需要耐心等待。感觉白天镜像下的快一点。（2）运行容器与测试进行如下操作：docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0.29 docker run -d --name typecho_test -p 80:80 joyqi/typecho:nightly-php8.0-apache docker network connect net_test mysql_test docker network connect net_test typecho_test笔者实验时宿主机的80、3306端口没有被占用，有占用的话需自行更改。另外，typecho初始化时使用的数据库需要提前创建。进行如下操作：docker exec -it mysql_test bash mysql -uroot -p #这里连不上库可以参考 Docker树莓派实践——LNMP 中的附录 create database test; show databases; exit exit顺便也可以查看mysql容器是否运行正常。如果不想进入mysql容器，也可在mysql容器启动命令中指定MYSQL_DATABASE：docker run -itd --name mysql_test -v /test/mysql/data:/var/lib/mysql -v /test/mysql/conf:/etc/mysql/conf.d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_DATABASE=test mysql:8.0.29下面进入typecho页面进行第一次访问的初始化。浏览器访问 树莓派ip ，出现typecho初始化页面。跟着引导走即可，需要注意的是数据库地址要填容器名，本例中为mysql_test，容器id、容器ip（使用docker network inspect net_test查看，注意不要带/后面的数字）也可以。另外，数据库名称填刚才创建的test。设置完成后即可正常访问。（3）收尾与总结进行删除等收尾工作：docker stop mysql_test docker stop typecho_test docker container ls -a docker container rm mysql_test docker container rm typecho_test docker container ls -a docker images docker image rm mysql:8.0.29 docker image rm joyqi/typecho:nightly-php8.0-apache docker images docker network rm net_test cd / rm -r /test两个镜像如果后续需要的话，也可以不删。总结一下，这种方法使用了两个独立的镜像，typecho镜像自带了php、apache，另开了一个mysql容器。两个容器通过docker network连接，能够正常工作。这种方法较为简单易行。2.方法二：docker-compose刚才的方法两个容器需要分别配置，移植性不够好，下面使用compose进行配置。这里还是使用nightly-php8.0-apache这个镜像，搭配nginx和mysql，其中nginx用来反向代理。（1）准备工作准备文件夹并拉取镜像：mkdir -p /test/data/mysql mkdir -p /test/conf/mysql mkdir /test/conf/nginx mkdir /test/conf/typecho docker pull mysql:8.0.29 docker pull nginx:1.23.1 docker pull joyqi/typecho:nightly-php8.0-apachetypecho的个性化设置目录在容器/app/usr下，等下准备将/test/conf/typecho挂载到该目录。/app/usr目录下主要是插件、主题等自定义博客的一些配置。（2）编写文件并运行项目编写compose模板：nano /test/docker-compose.yml写入以下内容：version: "3.9" services: nginx: image: nginx:1.23.1 ports: - "443:443/tcp" - "443:443/udp" - "80:80" volumes: - /test/conf/nginx:/etc/nginx/conf.d depends_on: - typecho logging: driver: syslog restart: always mysql: image: mysql:8.0.29 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: typecho command: - mysqld - --character-set-server=utf8mb4 - --default-authentication-plugin=mysql_native_password volumes: - /test/data/mysql:/var/lib/mysql - /test/conf/mysql:/etc/mysql/conf.d logging: driver: syslog restart: always typecho: image: joyqi/typecho:nightly-php8.0-apache volumes: - /test/conf/typecho:/app/usr logging: driver: syslog restart: always然后编写nginx配置：nano /test/conf/nginx/default.conf写入以下内容：server { listen 80; server_name localhost; location / { proxy_pass http://typecho:80; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }nginx配置参考了这个问题。然后就可以运行项目：cd /test docker-compose config docker-compose up浏览器访问 树莓派ip ，出现typecho初始化页面。同样注意数据库地址填服务容器名或容器全名，本例中为mysql或test_mysql_1，容器id、容器ip应该也可以。另外，数据库名这里填compose模板里指定创建的typecho。设置完成后即可正常访问。（3）收尾与总结进行删除等收尾工作：docker stop test_nginx_1 docker stop test_mysql_1 docker stop test_typecho_1 docker container ls -a docker container rm test_nginx_1 docker container rm test_mysql_1 docker container rm test_typecho_1 docker container ls -a docker images docker image rm nginx:1.23.1 docker image rm mysql:8.0.29 docker image rm joyqi/typecho:nightly-php8.0-apache docker images docker network rm test_default cd / rm -r /test这三个镜像如果后续需要的话，也可以不删。总结一下，这种方法使用了三个镜像并使用docker-compose管理，typecho镜像自带了php、apache，另开了mysql容器和nginx容器。这种方法加以修改，就能很方便地在更换服务器时移植博客。最后，除了上面使用的两种方法，还有很多其他方式搭建，这里就不介绍了，下面进行实战。3.实战：编写易移植的typecho博客并使用https有时候树莓派因更换或其他原因需要从头配置，想移植博客到新的环境中。使用docker-compose可以简化这个过程。下面还是使用nightly-php8.0-apache这个镜像，搭配nginx和mysql，nginx用于反向代理。另外还需要一台云服务器、一个域名、https认证证书，并且在服务器运行frps、在树莓派运行frpc。在树莓派运行frpc详见 Docker树莓派实践——frpc 或 在树莓派上设置frp穿透。在服务器运行frps详见 在树莓派上设置frp穿透 。域名、https认证证书等可自行搜索资料，或参考 树莓派搭建个人博客 （少量提及）。（1）准备工作这里将树莓派系统上所有docker项目相关的文件都放在/docker中：mkdir -p /docker/typecho/data/mysql mkdir -p /docker/typecho/conf/mysql mkdir /docker/typecho/conf/nginx mkdir /docker/typecho/conf/typecho mkdir /docker/typecho/ssl_certificate docker pull mysql:8.0.29 docker pull nginx:1.23.1 docker pull joyqi/typecho:nightly-php8.0-apache/docker/typecho/ssl_certificate用来存放https相关的证书、密钥，可以使用WinSCP等工具上传到树莓派。另外再次提醒，typecho的个性化设置目录在容器/app/usr下，等下会将/docker/typecho/conf/typecho挂载到该目录。/app/usr目录下主要是插件、主题等自定义博客的一些配置。然后修改一下frpc.ini：[common] server_addr = 云服务器ip地址 server_port = 7000 token = 你的密码 login_fail_exit = false [pi4B-blog-http-1] type = tcp local_ip = localhost local_port = 80 remote_port = 80 [pi4B-blog-https-1] type = tcp local_ip = localhost local_port = 443 remote_port = 443（2）编写nginx配置及compose模板nano /docker/typecho/conf/nginx/default.conf配置中写入以下内容（配置参考了这个问题）：server { listen 80; server_name www.lisper517.top; rewrite ^(.*)$ https://$host$1 permanent; } server { listen 443 ssl; server_name www.lisper517.top; ssl_certificate .pem文件路径; ssl_certificate_key .key文件路径; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_prefer_server_ciphers on; location / { proxy_pass http://typecho:80; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-NginX-Proxy true; proxy_redirect off; } }记得把 www.lisper517.top 换成自己的域名。该配置将http请求改写成https后再处理。或者也可写入：server { listen 80; listen 443 ssl; server_name www.lisper517.top; ssl on; ssl_certificate .pem文件路径; ssl_certificate_key .key文件路径; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; location / { proxy_pass http://typecho:80; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-Ip $remote_addr; proxy_set_header X-NginX-Proxy true; #proxy_redirect off; } error_page 497 https://$host$request_uri; }同样注意把 www.lisper517.top 换成自己的域名。第二种配置的原理是http请求发到https端口后nginx会产生497错误，通过改写错误页面实现http请求返回https协议。两种配置通过不同的方法，使得即使用 http://博客地址 访问typecho博客，也会跳转到https页面。需要注意两种配置中的 proxy_pass http://typecho:80; ，这里同样是容器名。然后是compose模板：nano /docker/typecho/docker-compose.yml写入以下内容：version: "3.9" services: nginx: image: nginx:1.23.1 ports: - "443:443/tcp" - "443:443/udp" - "80:80" volumes: - /docker/typecho/conf/nginx:/etc/nginx/conf.d - /docker/typecho/ssl_certificate:/ssl_certificate depends_on: - typecho logging: driver: syslog restart: always mysql: image: mysql:8.0.29 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: typecho command: - mysqld - --character-set-server=utf8mb4 - --default-authentication-plugin=mysql_native_password volumes: - /docker/typecho/data/mysql:/var/lib/mysql - /docker/typecho/conf/mysql:/etc/mysql/conf.d logging: driver: syslog restart: always typecho: image: joyqi/typecho:nightly-php8.0-apache volumes: - /docker/typecho/conf/typecho:/app/usr logging: driver: syslog restart: always最后运行项目：cd /docker/typecho docker-compose config docker-compose up（3）运行项目并测试在浏览器访问自己的域名（如果没有绑定域名就访问服务器ip），即可看到typecho初始页面。跟随引导对typecho进行初始化设置，在本例中，数据库地址填服务容器名或容器全名（mysql或typecho_mysql_1），数据库用户名root、密码123456，数据库名typecho（数据库需要提前创建。本例中通过MYSQL_DATABASE: typecho创建了该库）。如果在本过程中遇到问题，可以参考下一条 （4）问题及其他 。（4）问题及其他第三次提醒，typecho的个性化设置（比如主题、插件等）在typecho根目录的/usr下，joyqi/typecho:nightly-php8.0-apache容器中则是在/app/usr（/app就是typecho在容器中的根目录，不同typecho镜像可能有所差别）。另外，typecho的配置在typecho根目录下的config.inc.php中，里面存有数据库用户密码和其他设置。下面整理了一些常见的问题。a.博客迁移方法一是登入后台，在 控制台-备份 中操作，适用于文章较少时。这样生成的.dat文件只是文章、评论、注册的用户等，/app/usr目录、config.inc.php也可以备份一下。方法二是从mysql备份typecho使用的数据库，可用于文章较多时。同样，也可以备份一下/app/usr目录和config.inc.php。使用mysql导出、导入数据见 MySQL 导出数据 、 MySQL 导入数据 。另外，joyqi/typecho镜像支持通过docker-compose里的environment（或Dockerfile的ENV命令）设置一些环境变量，来达到第一次登录typecho时无需手动初始化的效果。支持的环境变量名可参考 镜像详情页 。或者也可以直接备份config.inc.php文件。最后，如果需要，可以写自动备份typecho几个目录的脚本、使用crontab等定时备份。这部分可自行探索，或参考本文最后。b.安装完成后无法登录笔者按照 3.实战：编写易移植的typecho并使用https 中的方法完成安装，可以从 www.lisper517.top 访问到博客，进行初始化后却无法登录，表现为在登录页面输入正确的用户密码后该登录页面会刷新、密码一栏清空，打开的其他页面也还是未登录状态；输入错误的用户密码后页面上方则会显示 用户名或密码错误 ，说明typecho能识别正确的用户密码，但无法跳转到登录后的页面。除了不能正常登录，评论、查看文章的功能都正常。后来笔者修改了nginx配置、去掉了https相关设置，如下：server { listen 80; server_name www.lisper517.top; location / { proxy_pass http://typecho:80; } }重启nginx容器后可以正常登录。然后把nginx配置改回使用https的配置，登录状态保持，但无法退出。这时可以正常使用备份、撰写/修改文章等用户功能。期间参考 该镜像github下的提问 ，在 config.inc.php 最后加上一行 define('__TYPECHO_SECURE__',true); ，没有解决问题。该设置大概是让typecho使用https协议。后来树莓派断电并使用其他Pi OS系统（换sd卡），大概3小时后重新使用该系统，又可以从https登录typecho后台了。用新的树莓派系统按本文配置从头装docker-typecho也没有复现问题，笔者便没有深入研究。估计可能是frps和云服务器的问题。遇到同样的问题或其他难以解决的问题，推荐重启云服务器，或使用docker-LNMP加树莓派本地运行typecho，把typecho根目录挂载到nginx的网页根目录。可参考笔者之前的 Docker树莓派实践——LNMP 和 树莓派搭建个人博客 ，或 这篇文章 。c.typecho镜像选择之前提到过，joyqi/typecho镜像命名规则为nightly-php版本号-后缀，后缀主要有 空后缀、cli、fpm、apache、alpine 。由于笔者并非CS出身，对php、nginx实际上不太了解，只会用apache镜像+nginx反向代理。有会使用fpm或其他镜像的希望在下方评论不吝赐教。d.typecho插件及其他在 树莓派搭建个人博客 中最后提到了添加备案号、修改允许上传的附件大小。这里再推荐两种插件。一是文章浏览次数统计插件，如ViewsCounter、PageViews，其使用可参考 ViewsCounter项目地址 、 PageViews使用参考 。二是可通过邮件提醒评论回复的插件，如Comment2Mail，安装后，如果用户或游客在文章下方的评论有新的回复，会自动发邮件提醒，当然该插件需要游客留下邮箱。但是需要提醒，由于typecho突然诈尸更新了1.2.0，语法大改，目前以前的插件有些用不了，Comment2Mail就是其中之一。如果用的是typecho 1.2.0之前的版本，使用Comment2Mail可参考 Comment2Mail项目地址 或 typecho官网 。若使用typecho 1.2.0，笔者在 typecho官方论坛 找到一款替代插件CommentNotifier，该插件基于Comment2Mail但按照typecho 1.2.0的新语法打造。详见 CommentNotifier项目地址 。使用CommentNotifier，以SMTP、qq邮箱为例， SMTP服务器地址 为 smtp.qq.com ， SMTP登录用户 和 SMTP邮箱地址 都填 自己的qq邮箱 ， SMTP登录密码 则是 qq邮箱SMTP授权码（登录qq邮箱，在 设置-账户 里选择开启IMAP/SMTP服务，经过验证后可得到一个授权码）， SMTP 加密模式 选 SSL加密 ， SMTP服务端口 为 465 。另外CommentNotifier自带的邮件模板比较简陋，可自行编写，或使用以下配置（参考 这篇文章 中的配置稍微修改）： 亲爱的：{Pname} ！ > 您在 《{title}》 下的评论有了新回复呢！ 您的评论： {Ptext} {author} 给您的回复： {commentText} 不战斗便无法存活，不战斗便无法生存！ ——石森章太郎 《假面骑士龙骑》 点击查看完整回复內容 本邮件为系统自动发送，请勿直接回复哦！ Copyright © {siteUrl}. All Rights Reserved. 替换插件根目录里的 theme/reply.html （Comment2Mail）或 template/default/guest.html （CommentNotifier）即可。注意笔者这里修改了一些变量名，该模板只能用于CommentNotifier。有需要的可自行更改，或到 参考文章 中复制原配置。（5）后续docker-typecho的typecho根目录就是/app，只挂载/app/usr的话会为/app创建匿名卷。为了尽量减少匿名卷的生成，把typecho的挂载中 - /docker/typecho/conf/typecho:/app/usr 改成了 - /docker/typecho/conf/typecho/app:/app 。并且为了保证博客24h运行，笔者取消了使用树莓派运行typecho+frp内网穿透，而是直接把typecho运行在了云服务器上。如果以后云服务器涨价，也许会考虑申请动态公网ip+DDNS或其他方式。另外，为了保证博客及时备份（对/docker/typecho文件夹备份），笔者使用crontab定时备份，操作如下：mkdir -p /backup/typecho crontab -e输入如下内容：0 0 * * * /usr/bin/zip -r /backup/typecho/backup.zip /docker/typechozip命令写绝对路径是怕cron运行时的env里PATH不包括/usr/bin，实际上应该尽量都写绝对路径。用whereis zip可查找zip的路径。这样每天0点0分就会备份整个docker-typecho。笔者实验时backup.zip仅16MB，解压后的文件夹则有209MB，还是很节约空间的。最后，可以在树莓派上也设置每天1点拷贝服务器上的/backup/typecho/backup.zip备份文件，具体操作如下：mkdir -p /backup/typecho mkdir /backup/key #存放密钥 crontab -e输入如下内容：0 1 * * * /usr/bin/scp -i /backup/key/alicloud-backup root@lisper517.top:/backup/typecho/backup.zip /backup/typecho另外记得打开树莓派的crontab日志功能：nano /etc/rsyslog.conf将其中的#cron.* /var/log/cron.log前的#去掉，之后重启树莓派。在笔者的云服务器上cron日志则是/var/log/cron，默认打开。最后，笔者使用时发现云服务器上的crontab日志里的时间和本地时间一致，但树莓派上则不一致，比如现在是本地时间8:00，crontab里有8点的定时任务，但查看树莓派的cron.log发现crontab的时间可能还是凌晨2点。可以查看一下树莓派系统用的是哪里的时间：date #输出时间和本地时间不一致 ls -l /etc/localtime #输出结果 ... /etc/localtime -> /usr/share/zoneinfo/Europe/London把树莓派的时区改成本地：rm /etc/localtime ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime date #输出时间和本地时间一致即可。最后重启rsyslog和cron，再看一下cron.log，时间就是本地时间：service restart rsyslog service restart cron tail -f /var/log/cron.log（6）为typecho添加其它功能可以自己修改主题的php，达到美观的功能。如果typecho容器运行时修改php，可能会卡死，重启一下容器就行了。a.在文章页面添加最新文章这个其实是上面提到的 ViewsCounter 的一种用法。如下操作：vim /docker/typecho/conf/typecho/app/usr/themes/default/sidebar.php在合适的地方加入以下内容： <?php _e('热门文章'); ?> <?php foreach (ViewsCounter_Plugin::getMostViewed() as $post): ?> <a href="<?php echo $post['permalink'] ?>"><?php echo $post['title'] ?> <?php endforeach; ?> 即可在侧边栏显示浏览数最多的文章。这个显示的个数也可在插件里设置。b.添加侧边栏目录索引参考了 这篇文章 。vim /docker/typecho/conf/typecho/app/usr/themes/default/functions.php添加3个函数：/* 文章目录开始 */ function createCatalog($obj) { //为文章标题添加锚点 global $catalog; global $catalog_count; $catalog = array(); $catalog_count = 0; $obj = preg_replace_callback('/<h([1-6])(.*?)>(.*?)<\/h\1>/i', function ($obj) { global $catalog; global $catalog_count; $catalog_count++; $catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count); return '<h' . $obj[1] . $obj[2] . '>' . $obj[3] . '</h' . $obj[1] . '>'; }, $obj); return $obj; } function getCatalog() { //输出文章目录容器 global $catalog; $index = ''; if ($catalog) { $index = ' ' . "\n"; $prev_depth = ''; $to_depth = 0; foreach ($catalog as $catalog_item) { $catalog_depth = $catalog_item['depth']; if ($prev_depth) { if ($catalog_depth == $prev_depth) { $index .= ' ' . "\n"; } elseif ($catalog_depth > $prev_depth) { $to_depth++; $index .= ' ' . "\n"; } else { $to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth; if ($to_depth2) { for ($i = 0; $i < $to_depth2; $i++) { $index .= ' ' . "\n" . ' ' . "\n"; $to_depth--; } } $index .= ' '; } } $index .= ' ' . $catalog_item['text'] . ''; $prev_depth = $catalog_item['depth']; } for ($i = 0; $i <= $to_depth; $i++) { $index .= ' ' . "\n" . ' ' . "\n"; } $index = ' ' . ' ' . "\n" . $index . ' ' . "\n"; } echo $index; } function themeInit($archive) { if ($archive->is('single')) { $archive->content = createCatalog($archive->content); } } /* 文章目录结束 */然后在页面内添加相应的代码，比如也加在侧边栏：vim /docker/typecho/conf/typecho/app/usr/themes/default/sidebar.php添加一行即可：<? php getCatalog(); ?>这样就实现了在侧边栏悬浮固定的目录索引，还为标题添加了超链，会前端的可以再精修一下。笔者使用的是如下配置： <?php getCatalog();?> 固定索引的大小、位置，添加了滑动条。c.添加Sitemap目的是生成一个 你的博客域名/sitemap.xml ，对博客网页进行归类整理，便于谷歌、bing、百度的爬虫访问。你可以自己写一个爬虫，或者使用typecho插件。笔者找到了一款适用typecho 1.2的插件： Sitemap-For-Typecho ，使用方法如下（参考 插件作者文章一 、 插件作者文章二 ）：首先设置typecho的伪静态，在typecho的后台， 设置-永久链接 里，启用 地址重写功能 。如果出现红字 重写功能检测失败，请检查你的服务器设置 ，则勾选下面的 如果你仍想启用此功能，请勾选这里 。如果还是不行，就放弃从typecho设置伪静态，而是在nginx中添加配置： if (!-e $request_filename) { rewrite ^(.*)$ /index.php$1 last; }如果用apache则添加：RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1]设置完伪静态，就可以添加sitemap插件。在 github 的 <> Code - Download ZIP 中下载zip文件，解压后把里面的Sitemap文件夹放到typecho的 app/usr/plugins 目录下，并且用 chmod 777 -R Sitemap 把该插件目录改成777（不改的话，启用插件时会出现一个好像以 fwrite() 开头的错误报告）。重启docker-typecho，在后台管理插件处打开该插件设置，推荐把 自动更新Sitemap 打开（这样会在每次有新文章时更新Sitemap，但是发布新文章时可能会卡一下，因为要先运行这个插件）；也可以在Sitemap更新时自动推送到baidu，但是笔者觉得没有这个必要，因为从后台数据来看，根本没有人从baidu跳转到本博客。如果不打开 自动更新Sitemap ，那么每次发布新文章后都要到该插件后台管理处手动更新Sitemap。刚安装好插件后可以手动更新一下，访问 你的博客域名/sitemap.xml 看看效果。另外可以在robots.txt中收录一下，添加一行： Sitemap: 你的域名/sitemap.xml 。d.添加网站图标根据 官方文档 以及其它网络资料，首先用一些网页工具生成 favicon.ico 文件，上传到下面的文件夹：/usr/themes/你的主题名然后修改下面的文件：/usr/themes/你的主题名/header.php写入三行： 刷新页面即可。（7）使用其它主题typecho自带的主题其实已经足够使用，笔者一向认为内容大于形式，但是看久了总会审美疲劳。很多人为typecho开发了各种主题，本文介绍一下 Joe ，搭建后的效果参考 这个演示站点 ，比较花里胡哨。到 Joe 的项目地址下载zip包，放到 /docker/typecho/conf/typecho/app/usr/themes 目录，然后就可以使用了。需要注意的是，由于采用了Joe的那一套php页面，所以需要再次手动添加目录索引、备案号。（8）升级版本根据 官方文档 和 这篇文章 ，首先备份并删除服务器上的这四个目录与文件：/admin/ /var/ /index.php /install.php然后下载最新版本的typecho（ 官方下载地址 ），把同样的四个目录与文件上传到服务器，最后刷新页面、登录后台即可升级。但升级后有些插件可能需要重新设置。
步骤/目录： 1.准备工作 2.编写frpc.ini和compose模板 3.运行并验证 附录： 1.让frps的dashboard使用https （1）tls介绍 （2）生成证书并运行 （3）访问的注意事项 2.frp双向验证 （1）介绍 （2）准备工作 （3）修改配置并运行本文首发于个人博客https://lisper517.top/index.php/archives/21/，转载请注明出处。本文的目的是使用docker、docker-compose在云服务器上运行frps、在树莓派上运行frpc。本文实验日期为2022年7月27日。本文使用的是树莓派4B（内存8G版），系统为Pi OS 64位桌面版（2022年4月4日更新，镜像名 2022-04-04-raspios-bullseye-arm64.img ）。之前的文章中曾介绍过frp，详见 在树莓派上设置frp穿透 。简单来说，frp的运行需要一台有公网ip的机器和另一台机器（比如没有公网ip的内网机器）；frp的作用是把另一台机器的端口映射到公网机器，使得从公网机器也能访问到另一台机器的端口（即内网穿透）。在docker官方仓库网页可以搜索到很多frp镜像，这里frpc选用的是snowdreamtech/frpc镜像，其下载量为10M+，两个月前更新。frps镜像也是该作者的。1.准备工作首先准备云服务器。笔者这里使用的是阿里云，镜像为Docker 19.03。在云服务器上进行如下操作：docker pull snowdreamtech/frps mkdir -p /docker/frps/conf mkdir /docker/frps/ssl_certificate #存放tls相关证书 #安装docker-compose。使用env命令发现PATH里没有/usr/local/bin，就放在/usr/bin里了 curl -L https://github.com/docker/compose/releases/download/v1.29.2/docker-compose-`uname -s`-`uname -m` > /usr/bin/docker-compose chmod +x /usr/bin/docker-compose docker-compose -v #编辑frps配置 vim /docker/frps/conf/frps.ini笔者使用的初始frps配置如下：[common] bind_port = 7000 #frpc上也要有相同的token值 token = 你的密码 #frps统计板 dashboard_port = 7500 dashboard_user = wuhuqifei dashboard_pwd = wuhuqifei #kcp据说是多消耗15%流量、降低tcp延迟。端口可和bind_port相同 kcp_bind_port = 7000使用初始配置时的运行命令：docker run -d --name frps -v /docker/frps/conf/frps.ini:/etc/frp/frps.ini --network host --restart=always snowdreamtech/frps为了让dashboard使用tls协议加密，笔者将frps.ini修改为：[common] bind_port = 7000 #frpc上也要有相同的token值 token = 你的密码 #frps统计板 dashboard_port = 7500 dashboard_user = wuhuqifei dashboard_pwd = wuhuqifei #kcp据说是多消耗15%流量、降低tcp延迟。端口可和bind_port相同 kcp_bind_port = 7000 #dashboard使用tls的相关配置 dashboard_tls_mode = true dashboard_tls_cert_file = /ssl_certificate/server.crt dashboard_tls_key_file = /ssl_certificate/server.key关于.crt、.key、ca.crt文件的介绍与自己生成的方法见 附录1. ，或者参考 gofrp文档 。然后更改命令，加上挂载证书存放目录的部分：docker run -d --name frps -v /docker/frps/conf/frps.ini:/etc/frp/frps.ini -v /docker/frps/ssl_certificate:/ssl_certificate --network host --restart=always snowdreamtech/frps记得把.crt（这里不是指ca.crt）、.key文件放到云服务器的/docker/frps/ssl_certificate目录，至此云服务器设置完毕。接下来对树莓派进行操作。在树莓派上准备目录，拉取镜像：mkdir -p /docker/frpc/conf docker pull snowdreamtech/frpc2.编写frpc.ini和compose模板nano /docker/frpc/conf/frpc.ini写入以下内容：[common] server_addr = 云服务器ip地址 server_port = 7000 token = 你的密码 login_fail_exit = false [pi4B-ssh-1] type = tcp local_ip = localhost local_port = 22 remote_port = 50022其中pi4B-ssh-1这一项映射是为了之后验证。另外由于这项映射要使用到服务器的50022端口，还需要登录到云服务器主页、在防火墙设置里打开该端口（勿用ufw或iptables打开）。再为树莓派编写compose模板：nano /docker/frpc/docker-compose.yml写入以下内容：version: "3.9" services: frpc: image: snowdreamtech/frpc network_mode: host volumes: - /docker/frpc/conf/frpc.ini:/etc/frp/frpc.ini logging: driver: syslog restart: always3.运行并验证cd /docker/frpc docker-compose config docker-compose up #输出结果 frpc_1 | 2022/07/27 03:23:33 [I] [service.go:301] [50829e63df489f3f] login to server success, get run id [50829e63df489f3f], server udp port [0] frpc_1 | 2022/07/27 03:23:33 [I] [proxy_manager.go:144] [50829e63df489f3f] proxy added: [pi4B-ssh-1] frpc_1 | 2022/07/27 03:23:33 [I] [control.go:180] [50829e63df489f3f] [pi4B-ssh-1] start proxy success这时可以登录到frps的dashboard（云服务器ip:7500/），输入设置的用户名密码，在 Proxies-TCP 中，可看到一项名为 pi4B-ssh-1 的服务，说明成功设置了frp。想进一步实验，可以试着用putty连接到 云服务器ip:50022 ，即通过公网ip连接到树莓派ssh。再次提醒，记得在云服务器的设置里打开50022端口。最后，若要后台运行docker-frpc，可使用docker-compose up -d命令。最后提醒一下，关于ufw的使用。用frp把树莓派端口映射到服务器，或者使用docker把容器端口映射到宿主机树莓派时，即使没有用ufw打开端口，也能从树莓派本地以外访问到这些端口。所以为了安全起见，请谨慎设置端口映射。另外，关于dashboard开启https的进一步内容，可阅读 附录1. 。附录：1.让frps的dashboard使用https在云服务器上开启frps的dashboard是挺简单的，但是由于云服务器有公网ip，暴露dashboard端口其实不太安全。笔者之后了解到，frps的dashboard可以通过一些设置来使用tls协议、走https，于是查找了一些资料后配置成功。（1）tls介绍SSL/TLS是一种密码通信框架，其中SSL出现较早，TLS出现较晚，TLS可以说是SSL的后续版本。在 Docker树莓派实践——Typecho 一文中，nginx配置其实就出现了SSL/TLS：ssl_protocols TLSv1 TLSv1.1 TLSv1.2;实际上，HTTPS、SMTP服务都可由SSL/TLS承载。SSL/TLS的具体原理就不介绍了。从使用上来说，使用TLS，可以身份验证、信息加密，使得信息传输更加安全，这样访问服务器端口、使用frp或其他软件时就更加保险。（2）生成证书并运行这里主要参考了 gofrp文档 。笔者使用的阿里云，虽然为自己的域名申请了一个免费证书，但该证书下载下来后，其apache版本的crt文件分为chain和public，nginx版则只有pem文件，而且都没有ca.crt。实际上，dashboard使用的证书最好还是自己生成。在树莓派上使用如下命令：mkdir ~/ssl_cert cd ~/ssl_cert find / -name "openssl.cnf" #查找openssl.cnfopenssl.cnf是openssl的配置文件，需要拷贝到~/ssl_cert下。在树莓派上找到了/usr/lib/ssl/openssl.cnf和/etc/ssl/openssl.cnf，但内容与 gofrp文档 有些差异。于是笔者直接复制了 gofrp文档 中的配置：nano ~/ssl_cert/my-openssl.cnf写入以下内容：[ ca ] default_ca = CA_default [ CA_default ] x509_extensions = usr_cert [ req ] default_bits = 2048 default_md = sha256 default_keyfile = privkey.pem distinguished_name = req_distinguished_name attributes = req_attributes x509_extensions = v3_ca string_mask = utf8only [ req_distinguished_name ] [ req_attributes ] [ usr_cert ] basicConstraints = CA:FALSE nsComment = "OpenSSL Generated Certificate" subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer [ v3_ca ] subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer basicConstraints = CA:true接下来生成默认ca（CA即Certification Authority，颁发证书的机构）：openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -subj "/CN=www.lisper517.top" -days 5000 -out ca.crt其中可以把www.lisper517.top改成自己的域名，或者也可以乱写。然后使用以下命令生成证书：#第一条命令 openssl genrsa -out server.key 2048 #第二条命令 openssl req -new -sha256 -key server.key \ -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \ -reqexts SAN \ -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:47.98.39.236,DNS:lisper517.top")) \ -out server.csr #第三条命令 openssl x509 -req -days 365 \ -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -extfile <(printf "subjectAltName=DNS:localhost,IP:47.98.39.236,DNS:lisper517.top") \ -out server.crt注意把第二、三条命令中的IP:47.98.39.236,DNS:lisper517.top修改一下，IP改成自己云服务器的ip，DNS改成自己的域名。这些文件里需要的是server.crt和server.key，而ca.crt在 附录2. 中需要用到，并且可以用于关闭浏览器警告。可以使用如下命令压缩并转移：zip cert.zip ca.crt server.crt server.key cd .. mv ssl_cert /home/pi #或者其他用户，只要能用WinSCP登录。如果设置了root可登录，也可以不mv因为用WinSCP把server.key转移到win10机器上时出现Permisson denied，所以先压缩一下才能转移。之后解压一下，还是通过WinSCP放到云服务器上（笔者放到了/docker/frps/ssl_certificate目录下，ca.crt可以不放到云服务器上）。另外，如果想通过配置使得win10或linux接受自己颁发的证书，还要记得转移一下ca.crt，这个文件用于确认颁发证书的机构。最后记得删除临时文件夹：rm -r /home/pi/ssl_cert至此就生成并转移了需要的三个文件。然后在浏览器访问 https://域名:7500 ，就会发现dashboard已经使用了https。但是由于证书是自己生成的，浏览器会发出警告。关于警告的解决方法见下一节 （3）访问的注意事项 。（3）访问的注意事项对自己生成的证书，浏览器会认为证书颁发机构不可信任。笔者一般是强行访问，但也可以通过设置来添加到可信任的颁发机构。在linux上，笔者并未实操，但据说将ca.crt放到/usr/local/share/ca-certificates目录下，然后update-ca-certificates即可。在win10上，可按win+r键，输入mmc并运行，在 文件-添加/删除管理单元 中，在左侧单击 证书 ，点击 添加 ，然后可以为本用户或计算机所有用户添加，点击完成、确定，然后点击 证书 - 当前用户 （或 证书 - 计算机用户） 的下拉菜单，点击 受信任的根证书颁发机构-证书 ，在中间会出现许多颁发机构。在中间空白处右键单击，然后选择 所有任务-导入 ，最后把自己的ca.crt导入即可。最后关闭，将设置存入 控制台1.msc 即可。之后重启电脑，就不会出现安全警告了。2.frp双向验证（1）介绍详见 gofrp文档 。简单来说，frp可以使用use_encryption或STCP等功能让传输过程安全，但无法判断对方身份是否合法。而使用tls、双向验证，不仅能加密，还能验证身份。（2）准备工作刚才介绍dashboard使用https时提到过，ca.crt代表了证书颁发机构，如果server.crt和server.key是ca签发或在ca的信任链中，则server.crt和server.key是合法的。也可以理解为，ca.crt为server.crt和server.key提供合法性，只要验证了ca.crt颁发了这两个文件，那么这两个文件就可以信任。frp双向验证的双向，指的就是frpc和frps对彼此的身份都要验证。在前文中笔者演示了自己颁发证书，生成了ca.crt、server.crt和server.key，这里我们称为server_ca.crt、server.crt和server.key；使用类似的方法生成client_ca.crt、client.crt和client.key，那么frpc端上存放server_ca.crt、client.crt和client.key，其中server_ca.crt用来对frps端进行验证，client.crt和client.key则提供给frps端以使frps端对frpc端进行验证；对称地，frps端上存放client_ca.crt、server.crt和server.key，其中client_ca.crt用来对frpc端进行验证，server.crt和server.key则提供给frpc端以使frpc端对frps端进行验证。前文已经生成了server_ca.crt、server.crt和server.key，虽然是给dashboard用的，但也能用于双向验证。接下来只演示生成client_ca.crt、client.crt和client.key。之前的步骤见前文，这里只有最后三条命令有改动：#第一条命令 openssl genrsa -out client.key 2048 #第二条命令 openssl req -new -sha256 -key client.key \ -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com" \ -reqexts SAN \ -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:lisper517.top,DNS:www.lisper517.top")) \ -out client.csr #第三条命令 openssl x509 -req -days 365 \ -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \ -extfile <(printf "subjectAltName=DNS:lisper517.top,DNS:www.lisper517.top") \ -out client.crt同样注意把DNS:lisper517.top,DNS:www.lisper517.top改成自己的域名，或者乱写也可以。同样，转移client.key的时候提示Permisson denied，也用zip压缩后转移。另外，转移ca.crt的时候务必分清，最好是像下文中一样改个名字。最后，记得删除临时文件夹。（3）修改配置并运行vim /docker/frps/conf/frps.inifrps配置新增四行：[common] bind_port = 7000 token = 你的密码 dashboard_port = 7500 dashboard_user = wuhuqifei dashboard_pwd = wuhuqifei kcp_bind_port = 7000 dashboard_tls_mode = true dashboard_tls_cert_file = /ssl_certificate/server.crt dashboard_tls_key_file = /ssl_certificate/server.key #新增内容 tls_only = true tls_cert_file = /ssl_certificate/server.crt tls_key_file = /ssl_certificate/server.key tls_trusted_ca_file = /ssl_certificate/client_ca.crt然后把三个文件放到/docker/frps/ssl_certificate文件夹中。注意这里的ca.crt是生成client.crt、client.key时生成的，即client_ca.crt。最后重启docker-frps，服务器端配置完毕：docker restart frps然后对树莓派进行如下修改：mkdir /docker/frpc/ssl_certificate nano /docker/frpc/conf/frpc.ini同样增加四行内容：tls_enable = true tls_cert_file = /ssl_certificate/client.crt tls_key_file = /ssl_certificate/client.key tls_trusted_ca_file = /ssl_certificate/server_ca.crt同样把三个文件放到文件夹中，同样注意这里的ca.crt是生成server.crt、server.key时生成的，即server_ca.crt。然后挂载/docker/frpc/ssl_certificate：nano /docker/frpc/docker-compose.yml在volumes下新增：- /docker/frpc/ssl_certificate:/ssl_certificate由于新增了挂载，需要删除容器、重新生成：docker stop frpc_frpc_1 docker container rm frpc_frpc_1 cd /docker/frpc docker-compose up查看一下日志，是否成功连接。成功连接后使用-d后台运行：docker stop frpc_frpc_1 docker-compose up -d
步骤/目录： 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写入文件内容如下： nginx test wuhu,qifei 浏览器访问 树莓派ip/test.html ，出现对应页面。（3）安装php并配置nginxapt-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）安装mariadbmariadb和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.135.方法三：瘦容器法如前所述，这种方法是最推荐的，各个镜像换版本很方便，能做到数秒内切换版本；由于镜像较多，管理稍微麻烦一点，但可通过docker compose进行方便快捷的管理。下面的内容，不涉及docker-compose的部分主要参考了这篇文章和这个网页；涉及docker-compose的部分参考了这篇文章。最后需要说明，前三步都是测试环境、配置。赶时间可直接看第四、五步。（1）创建环境，配置nginxmkdir -p /test/nginx/conf.d mkdir /test/www nano /test/nginx/conf.d/default.confdefault.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 nginx443端口用于HTTPS协议，这里也可以不加。进行测试：nano /test/www/index.html nano /test/www/index.php分别写入： nginx test wuhu,qifei 和：<?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/confphp镜像需要安装扩展、新增配置，所以第一次运行时不要挂载宿主机目录到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.confnginx的配置文件中写入以下内容：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/Dockerfilephp的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.ymlcompose模板文件内容如下，其编写参考了这篇文章和这个项目。：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分别写入： nginx test wuhu,qifei 和：<?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/Dockerfiletest.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.sqlcompose模板文件中写入以下内容：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.sqlcompose模板文件中写入以下内容：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.sqlcompose模板文件中写入以下内容：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
本文较长，属于系统性总结，可作为使用手册查阅。本文主要介绍了docker-compose的使用。步骤/目录： 1.Docker Compose （1）安装compose与卸载 （2）compose的使用例一 （3）docker-compose命令 a.build b.config c.down d.exec e.images f.kill g.logs h.pause i.port j.ps k.pull l.push m.restart n.rm o.run p.scale q.start r.stop s.top t.unpause u.up v.help w.version （4）docker-compose.yml模板文件 a.build b.cap_add，cap_drop c.command d.configs e.cgroup_parent f.container_name g.deploy h.devices i.depends_on j.dns k.dns_search l.tmpfs m.env_file n.environment o.expose p.external_links q.extra_hosts r.healthcheck s.image t.label u.logging v.network_mode w.networks x.pid y.ports z.secrets a1.security_opt b1.stop_signal c1.sysctls d1.ulimits e1.volumes f1.其他指令 g1.读取变量值 （5）docker-compose实战 2.其他进阶内容 3.使用docker-compose更新容器 4.docker使用心得 （1）docker网络-单机 （2）docker与ufw （3）docker网络-多机本文首发于个人博客https://lisper517.top/index.php/archives/19/，转载请注明出处。本文的目的是对docker-compose相关内容做总结归纳，使用本文从零学习docker将有一定难度。本文主要总结自菜鸟教程和Docker——从入门到实践和一些其他零散资料，有不明白的地方请参考原文。本文写作日期为2022年2月-7月。本文涉及的主要是docker的进阶内容，初学者可以先只看Docker Compose，再使用一段时间docker后，有需要再学习其他的。1.Docker Compose更详细的说明见Docker Compose，这里只做一个快速手册式的总结。在以下compose的讲解中，服务（service）指一个镜像实例化的一个或多个容器（为了便于理解，笔者一般都称服务容器），项目（project）指为了完成某种功能而相互协作的多个容器构成的整体。Docker Compose是一种管理多个容器的工具，只需要配置docker-compose.yml（compose模板）文件就能打包多个容器为一个项目（project）。YAML格式（.yml文件）类似html、json格式，是一种组织内容表达的格式，规则类似json的键值对，形式上不同而已。最后，原来的compose是用python写的，后来的compose v2则是官方用Go重写的。（1）安装compose与卸载compose支持linux、mac、windows平台；可通过pip或二进制包下载两种方式安装。对于mac、windows平台，其docker桌面版自带compose；对于linux平台，x86_64架构的机器，可使用以下方式安装（该命令会自动选择合适的平台）：curl -L https://github.com/docker/compose/releases/download/v1.27.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose #国内用户可替换为以下命令加快下载，笔者实验的时候这个网页好像已经失效了 curl -L https://download.fastgit.org/docker/compose/releases/download/v1.27.4/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose #至此就算安装完成对于linux平台，arm架构的机器（如树莓派），可使用pip安装：pip install -U docker-compose另外还可以使用以下命令进行bash命令补全（估计是一个包的命令不全，需要下面这个包补充）：curl -L https://raw.githubusercontent.com/docker/compose/1.27.4/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose chmod +x /etc/bash_completion.d/docker-compose笔者使用该命令时出现curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL错误，于是去掉了域名前的https协议才成功下载：curl -L raw.githubusercontent.com/docker/compose/1.27.4/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose但是，笔者实验的时候这两个网页已经404了（github上的compose项目网页不太安定），于是就通过pip安装了。有需要的可以自行找下载页。安装完成后，可运行docker-compose --version测试是否成功安装。需要卸载的话，如果使用二进制包安装，删除包即可：rm /usr/local/bin/docker-compose rm /etc/bash_completion.d/docker-compose如果通过pip安装，就用pip卸载：pip uninstall docker-compose（2）compose的使用例一最常见需要多个容器协作的场景应该就是web网页了。这里使用python搭建一个镜像，需要达到的目标是每访问一次5000端口，网页显示的访问次数就+1。本项目一共使用了2个容器。进行如下操作：mkdir -p /test/compose_test nano /test/compose_test/app.py写入以下内容：from flask import Flask from redis import Redis app = Flask(__name__) redis = Redis(host='redis', port=6379) @app.route('/') def hello(): count = redis.incr('hits') return 'Hello World! 该页面已被访问 {} 次。\n'.format(count) if __name__ == "__main__": app.run(host="0.0.0.0", debug=True)如果不会python，上面这个文件的内容不算重要。然后编写Dockerfile：nano /test/compose_test/Dockerfile写入以下内容FROM python:3.6-alpine ADD . /code #不知道为什么这里用ADD，和前面的原则冲突 WORKDIR /code RUN pip install redis flask CMD ["python", "app.py"]这里的alpine是指极简版（不仅是python），据说学界对是否应使用alpine版素有争论。最后编写docker-compose.yml文件：nano /test/compose_test/docker-compose.yml写入以下内容：version: '3' services: web: build: . ports: - "5000:5000" redis: image: "redis:alpine"上面的compose模板看不懂，可先继续往下读，后面再回头看这里。最后运行compose项目：cd /test/compose_test docker-compose up运行完后，访问 树莓派ip:5000 ，每次刷新后访问次数都会+1。运行完后进行删除：^C docker container ls -a docker container rm compose_test_web_1 #注意这里容器的命名方式 docker container rm compose_test_redis_1 docker container ls -a docker images docker image rm compose_test_web:latest docker image rm redis:alpine docker image rm python:3.6-alpine docker images docker network ls docker network rm compose_test_default docker network ls rm -r /test从本示例可以看出compose的使用方式。一般来说，compose和使用Dockerfile时相比，只多了编写docker-compose.yml文件这一步，并且把docker build换成了docker-compose up。docker-compose up也是十分强大的docker-compose命令。（3）docker-compose命令原文内容较长，这里也是仅做总结。详情可阅读原网页或官方网页。如同刚才接触的docker-compose up，docker-compose还有许多其他命令。命令的基本格式为：docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]使用docker-compose help [COMMAND]可以查看某个命令的详细介绍。首先介绍格式中的[options]。常见的命令选项如下：-f, --file FILE 指定使用的 Compose 模板文件，默认为 docker-compose.yml，可以多次指定。 -p, --project-name NAME 指定项目名称，默认将使用所在目录名称作为项目名。 --verbose 输出更多调试信息。 -v, --version 打印版本并退出。然后介绍[COMMAND]部分。常用的命令有：a.build格式为docker-compose build [options] [SERVICE...]，用途是构建或重新构建项目里的服务容器。它的选项包括：--force-rm 删除构建过程中的临时容器。 --no-cache 构建镜像过程中不使用 cache（这将加长构建过程）。 --pull 始终尝试通过 pull 来获取更新版本的镜像。b.config验证 Compose 文件格式是否正确，若正确则显示配置，若格式错误显示错误原因。c.down停止up命令所启动的所有服务容器，并移除网络。d.exec进入指定的服务容器。这个命令可用docker exec代替。e.images列出 Compose 文件中包含的镜像。Compose 文件有时可能不包含镜像（image），全用build。f.kill格式为docker-compose kill [options] [SERVICE...]，用途是发送 SIGKILL 信号来强制停止服务容器。支持通过-s参数来指定发送的信号，例如通过如下指令发送 SIGINT 信号：docker-compose kill -s SIGINTg.logs格式为docker-compose logs [options] [SERVICE...]，可查看服务容器的输出。该命令在调试问题的时候十分有用。默认情况下，docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color 来关闭颜色。h.pause格式为docker-compose pause [SERVICE...]，用途是暂停一个服务容器。相对的是unpause。i.port格式为docker-compose port [options] SERVICE PRIVATE_PORT，可打印某个服务容器端口所映射的公共端口。选项：--protocol=proto 指定端口协议，tcp（默认值）或者 udp。 --index=index 如果同一服务存在多个容器，指定命令对象容器的序号（默认为 1）。j.ps格式为docker-compose ps [options] [SERVICE...]，可列出项目中目前的所有容器。选项：-q 只打印容器的 ID 信息。k.pull格式为docker-compose pull [options] [SERVICE...]，可拉取服务依赖的镜像。选项：--ignore-pull-failures 忽略拉取镜像过程中的错误。l.push推送服务依赖的镜像到 Docker 镜像仓库。m.restart格式为docker-compose restart [options] [SERVICE...]，可重启项目中的服务。选项：-t, --timeout TIMEOUT 指定重启前停止容器的超时（默认为 10 秒）。n.rm格式为docker-compose rm [options] [SERVICE...]，用途是删除所有（停止状态的）服务容器。推荐先执行 docker-compose stop 命令来停止容器。选项：-f, --force 强制直接删除，包括非停止状态的容器。不推荐使用该选项。 -v 同时删除容器所挂载的数据卷。o.run格式为docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]，用途是在指定服务上执行一个命令，例如：docker-compose run ubuntu ping docker.com将会启动一个 ubuntu 服务容器，并执行 ping docker.com 命令。默认情况下，如果存在关联，则所有关联的服务将会自动被启动，除非这些服务已在运行中。该命令类似启动容器后运行指定的命令，相关卷、链接等等都将会按照配置自动创建。两个不同点：给定命令将会覆盖原有的自动运行命令；不会自动创建端口，以避免冲突。如果不希望自动启动关联的容器，可以使用 --no-deps 选项，例如：docker-compose run --no-deps web python manage.py shell将不会启动 web 容器所关联的其它容器。选项：-d 后台运行容器。 --name NAME 为容器指定一个名字。 --entrypoint CMD 覆盖默认的容器启动指令。 -e KEY=VAL 设置环境变量值，可多次使用选项来设置多个环境变量。 -u, --user="" 指定运行容器的用户名或者 uid。 --no-deps 不自动启动关联的服务容器。 --rm 运行命令后自动删除容器，d 模式下将忽略。 -p, --publish=[] 映射容器端口到本地主机。 --service-ports 配置服务端口并映射到本地主机。 -T 不分配伪 tty，意味着依赖 tty 的指令将无法运行。p.scale格式为docker-compose scale [options] [SERVICE=NUM...]，用于设置指定服务运行的容器个数。通过 service=num 的参数来设置数量。例如：docker-compose scale web=3 db=2将启动 3 个容器运行 web 服务，2 个容器运行 db 服务。一般的，当指定数目多于该服务当前实际运行容器，将新创建并启动容器；反之，将停止容器。选项：-t, --timeout TIMEOUT 停止容器时候的超时（默认为 10 秒）。q.start格式为docker-compose start [SERVICE...]，可启动已经存在的服务容器。r.stop格式为docker-compose stop [options] [SERVICE...]，可停止已经处于运行状态的容器，但不删除它。通过docker-compose start可以再次启动这些容器。选项：-t, --timeout TIMEOUT 停止容器时候的超时（默认为 10 秒）。s.top查看各个服务容器内运行的进程。t.unpause格式为docker-compose unpause [SERVICE...]，可恢复处于暂停状态中的服务。相对的是pause。u.up格式为docker-compose up [options] [SERVICE...]。该命令十分强大，它将尝试自动完成包括构建镜像，（重新）创建服务，启动服务，并关联服务相关容器的一系列操作。可以说，大部分时候都可以直接通过该命令来启动一个项目。链接的服务都将会被自动启动，除非已经处于运行状态。默认情况，docker-compose up启动的容器都在前台，控制台将会同时打印所有容器的输出信息，方便进行调试。前台模式下，通过Ctrl-C停止命令时，所有容器将会停止。如果使用docker-compose up -d，将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。默认情况，如果服务容器已经存在，docker-compose up将会尝试停止容器，然后重新创建（保持使用 volumes 挂载的卷），以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建，可以使用docker-compose up --no-recreate。这样将只会启动处于停止状态的容器，而忽略已经运行的服务。如果用户只想重新部署某个服务，可以使用docker-compose up --no-deps -d <SERVICE_NAME>来重新创建服务并后台停止旧服务、启动新服务，这种方式不会影响到其所依赖的服务。选项：-d 在后台运行服务容器。 --no-color 不使用颜色来区分不同的服务的控制台输出。 --no-deps 不启动服务所链接的容器。 --force-recreate 强制重新创建容器，不能与 --no-recreate 同时使用。 --no-recreate 如果容器已经存在了，则不重新创建，不能与 --force-recreate 同时使用。 --no-build 不自动构建缺失的服务镜像。 -t, --timeout TIMEOUT 停止容器时候的超时（默认为 10 秒）。v.help格式为docker-compose help [COMMAND]，可输出命令的详细信息。w.version格式为docker-compose version，可打印版本信息。（4）docker-compose.yml模板文件模板文件是docker-compose的核心，其使用的YAML格式有些类似json的键值对套娃。下面是一份模板：version: "3" services: webapp: image: examples/web ports: - "80:80" volumes: - "/data"每个服务容器都必须通过image: 指定镜像，或用build指定构建方式。另外，version: "3"指定compose模板文件使用的docker-compose版本，不同的版本对应不同版本的docker引擎，可向下兼容，所以一般可以写高点。下面介绍各指令的用法。a.build用于指定 Dockerfile 所在文件夹的路径（可以是绝对路径，或者相对 docker-compose.yml 文件的路径，为了部署方便一般都写相对路径）。Compose 将会利用它自动构建这个镜像，然后使用这个镜像。示例：version: '3' services: webapp: build: ./dirbuild下可使用context指定Dockerfile的路径，使用dockerfile指令指定 Dockerfile 文件名，使用arg指令指定构建镜像时的变量：version: '3' services: webapp: build: context: ./dir dockerfile: Dockerfile-alternate args: buildno: 1environment则类似ENV，但是放在build外面，后面也有介绍。还可使用cache_from指定构建镜像的缓存：build: context: . cache_from: - alpine:latest - corp/web_app:3.14b.cap_add，cap_drop指定容器的内核能力（capacity）分配，比如让容器拥有所有能力可以指定为：cap_add: - ALL去掉 NET_ADMIN 能力可以指定为：cap_drop: - NET_ADMINc.command覆盖容器启动后默认执行的命令（覆盖镜像的CMD），例如：command: echo "hello world"d.configs仅用于 Swarm mode（虫群模式）。关于Swarm mode可查看此网页（新手可以不看）。e.cgroup_parent指定父 cgroup 组，意味着将继承该组的资源限制。例如创建一个 cgroup 组名称为 cgroups_1：cgroup_parent: cgroups_1f.container_name指定容器名称。不指定时默认将会使用项目名称_服务容器名称_序号这样的格式。示例：container_name: docker-web-container注意，指定容器名称后，该服务将无法进行扩展（scale），因为 Docker 不允许多个容器具有相同的名称。g.deploy也是仅用于 Swarm mode（虫群模式）。关于Swarm mode可查看此网页（新手可以不看）。h.devices指定设备映射关系。示例：devices: - "/dev/ttyUSB1:/dev/ttyUSB0"i.depends_on解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web：version: '3' services: web: build: . depends_on: - db - redis redis: image: redis db: image: postgres注意，上例中 web 服务不会等待 redis db 「完全启动」之后才启动。j.dns自定义 DNS 服务器。可以是一个值或一个列表：dns: 8.8.8.8或：dns: - 8.8.8.8 - 114.114.114.114k.dns_search配置 DNS 搜索域。同样可以是一个值或一个列表：dns_search: example.com或：dns_search: - domain1.example.com - domain2.example.coml.tmpfs挂载一个 tmpfs 文件系统到容器。示例：tmpfs: /run或：tmpfs: - /run - /tmpm.env_file从文件中获取环境变量，可以为单独的文件路径或列表。如果通过docker-compose -f FILE方式来指定 Compose 模板文件，则env_file中变量的路径会基于模板文件路径。如果有变量名称与environment指令冲突，则按照惯例，以位置靠后的为准。格式示例：env_file: .env或：env_file: - ./common.env - ./apps/web.env - /opt/secrets.env环境变量文件中每一行必须符合格式，支持 # 开头的注释行，如：# common.env: Set development environment PROG_ENV=developmentn.environment设置环境变量，可以使用数组或字典两种格式。只给定名称的变量会自动获取运行 Compose 主机上对应变量的值，可以用来防止泄露不必要的数据。示例：environment: RACK_ENV: development SESSION_SECRET:或：environment: - RACK_ENV=development - SESSION_SECRET如果变量名称或者值中用到 true|false，yes|no 等表达 布尔 含义的词汇，最好放到引号里，避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇，包括：y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF或者也可以全部用引号。如果运行时又指定（docker run -e），那么应该是docker run时指定的会覆盖environment指定的（未实验）。o.expose暴露端口，但不映射到宿主机、只被连接的服务访问，仅可以指定内部端口为参数。示例：expose: - "3000" - "8000"p.external_links注意：不建议使用该指令。链接到 docker-compose.yml 外部的容器，甚至并非 Compose 管理的外部容器。示例：external_links: - redis_1 - project_db_1:mysql - project_db_1:postgresqlq.extra_hosts类似docker run中的--add-host参数，指定额外的 host 名称映射信息。示例：extra_hosts: - "googledns:8.8.8.8" - "dockerhub:52.1.157.61"会在启动后的服务容器中 /etc/hosts 文件中添加如下两条条目：8.8.8.8 googledns 52.1.157.61 dockerhubr.healthcheck通过命令检查容器是否健康运行。示例：healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 1m30s timeout: 10s retries: 3s.image指定为镜像名称或镜像 ID。如果镜像在本地不存在，Compose 将会尝试拉取这个镜像。示例：image: ubuntu image: orchardup/postgresql image: a4bc65fdt.label为容器添加 Docker 元数据（metadata）信息，比如可以为容器添加辅助说明信息。示例：labels: com.startupteam.description: "webapp for a startup team" com.startupteam.department: "devops department" com.startupteam.release: "rc3 for v1.0"u.logging配置日志选项。示例：logging: driver: syslog options: syslog-address: "tcp://192.168.0.42:123"目前支持三种日志驱动类型：driver: "json-file" driver: "syslog" driver: "none"options 配置日志驱动的相关参数：options: max-size: "200k" max-file: "10"v.network_mode设置网络模式，使用和docker run的--network参数一样的值。示例：network_mode: "bridge" network_mode: "host" network_mode: "none" network_mode: "service:[service name]" network_mode: "container:[container name/id]"w.networks配置容器连接的网络。示例：version: "3" services: some-service: networks: - some-network - other-network networks: some-network: other-network:在服务容器里写成数组形式，在服务容器外总结时写成字典形式。后面也有一些指令需要在服务容器内外都写一次。x.pid跟主机系统共享进程命名空间。打开该选项的容器之间，以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。示例：pid: "host"y.ports暴露端口信息。使用宿主端口：容器端口 (HOST:CONTAINER) 格式，或者仅仅指定容器的端口（宿主将会随机选择端口）都可以。示例：ports: - "3000" - "8000:8000" - "49100:22" - "127.0.0.1:8001:8001"注意：当使用 HOST:CONTAINER 格式来映射端口时，如果你使用的容器端口小于 60 并且没放到引号里，可能会得到错误结果，因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题，建议数字串都采用引号包括起来的字符串格式。z.secrets存储敏感数据，例如一些密码。示例：version: "3.1" services: mysql: image: mysql environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password secrets: - db_root_password - my_other_secret secrets: my_secret: file: ./my_secret.txt my_other_secret: external: true服务容器内外各写一次。a1.security_opt指定容器模板标签（label）机制的默认属性（用户、角色、类型、级别等），比如配置标签的用户名和角色名。示例：security_opt: - label:user:USER - label:role:ROLEb1.stop_signal设置另一个信号来停止容器，不指定时默认使用的是SIGTERM停止容器。示例：stop_signal: SIGUSR1c1.sysctls配置容器内核参数。示例：sysctls: net.core.somaxconn: 1024 net.ipv4.tcp_syncookies: 0或：sysctls: - net.core.somaxconn=1024 - net.ipv4.tcp_syncookies=0d1.ulimits指定容器的 ulimits 限制值。例如，指定最大进程数为 65535，指定文件句柄数为 20000（软限制，应用可以随时修改，不能超过硬限制） 和 40000（系统硬限制，只能 root 用户提高）： ulimits: nproc: 65535 nofile: soft: 20000 hard: 40000e1.volumes设置数据卷挂载路径。可以设置为宿主机路径(HOST:CONTAINER)或者数据卷名称(VOLUME:CONTAINER)，并且可以设置访问模式 （HOST:CONTAINER:ro）。该指令中路径支持相对路径。示例：volumes: - /var/lib/mysql - cache/:/tmp/cache - ~/configs:/etc/configs/:ro如果路径为数据卷名称，必须在文件中配置数据卷（就是服务容器内外各写一次）：version: "3" services: my_src: image: mysql:8.0 volumes: - mysql_data:/var/lib/mysql volumes: mysql_data: f1.其他指令此外，还有包括domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令，基本跟docker run中对应参数的功能一致。指定服务容器启动后执行的入口文件：entrypoint: /code/entrypoint.sh指定容器中运行应用的用户名：user: nginx指定容器中工作目录：working_dir: /code指定容器中搜索域名、主机名、mac 地址等：domainname: your_website.com hostname: test mac_address: 08-00-27-00-0C-0A允许容器中运行一些特权命令：privileged: true指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效，在生产环境中推荐配置为always或者unless-stopped：restart: always以只读模式挂载容器的 root 文件系统，意味着不能对容器内容进行修改：read_only: true打开标准输入，可以接受外部输入：stdin_open: true模拟一个伪终端：tty: trueg1.读取变量值Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。例如，下面的 Compose 文件将从运行它的环境中读取变量 ${MONGO_VERSION} 的值，并写入执行的指令中：version: "3" services: db: image: "mongo:${MONGO_VERSION}"如果执行MONGO_VERSION=3.2 docker-compose up则会启动一个 mongo:3.2 镜像的容器；如果执行MONGO_VERSION=2.8 docker-compose up则会启动一个 mongo:2.8 镜像的容器。若当前目录存在 .env 文件，执行docker-compose命令时将从该文件中读取变量，比如在当前目录新建 .env 文件并写入以下内容：# 支持 # 号注释 MONGO_VERSION=3.6执行docker-compose up则会启动一个 mongo:3.6 镜像的容器。h1.其他参考网页官方文档awesome-compose（5）docker-compose实战https://yeasy.gitbook.io/上提供了一些项目实战：实战 Django实战 Rails实战 WordPress实战 LNMP笔者在 Docker树莓派实践——LNMP 中使用三种方法搭建了LNMP环境，值得参考。2.其他进阶内容docker buildx：新一代构建镜像工具，详见Docker Buildx。swarm mode：集群管理容器，详见Swarm mode。以上内容菜鸟教程也有相应介绍。3.使用docker-compose更新容器只需要3步。进入docker-compose.yml所在的文件夹，进行如下操作：docker-compose pull docker-compose up -d --remove-orphans更新完所有容器后，删除旧的镜像：docker image prune4.docker使用心得（1）docker网络-单机如果涉及到多个容器组（stacks）的互相协作，可能新手不太容易搞清互相怎么写ip。笔者经过一些实验，稍微搞清楚了。a.单个容器自己联系自己，写localhost即可；b.容器组a内部，或者容器组a、容器组b都加入了同一个docker网络，那么写容器名就可以（或者用inspect查看内部的ip，但是这样移植性不好）；c.容器组a没有使用 network_mode:host ，容器组b则用了 network_mode:host ，或者宿主机上其它的服务c，这时a要联络到b、c，需要使用 ip addr show docker0 获得宿主机上docker0的ip，填写之，且此时需要注意b、c的端口冲突。从定义域上说这个ip范围是 容器外 ∩ 宿主机内 。d.在一些特殊情况下，可能镜像有bug，docker0的ip不行，就需要 ifconfig 找到eth0的inet的ip（网卡0，默认网卡），填写之。比如0.53.2版本的frps的webServer.addr（旧称dashboard）。（2）docker与ufwdocker与ufw等防火墙有一些冲突，这主要是因为docker和ufw都是通过操纵底层的iptables来实现防火墙功能的，而docker修改iptables后无法通过ufw查看。a.端口自动开放参考 这个问题 。使用某些容器时（比如一些数据库），用docker把容器的端口映射后，即使没有用ufw把该端口打开，也能从非localhost访问端口。解决方法是在yml中写 127.0.0.1:8080:8080 ，即在宿主机端口前添加 127.0.0.1: ，限制只能从本机访问端口。需要注意的是，用frp这种反向代理时来源ip也会是localhost。笔者实践时没有遇到这种问题，可能后来版本的docker已经解决了这个问题。b.端口必须开放在后面的文章中，笔者使用NPM时，遇到了这样的情况：NPM映射部分端口，加入docker网络（容器a）；其它一些docker容器使用 network_mode:host （容器b。docker网络和host模式应该是冲突的，以host模式优先）；宿主机上一些没有使用docker的其它服务（服务c）。这时容器a要联络到容器b、服务c，要通过docker0的ip（ ip addr show docker0 ），同时需在ufw中打开该端口。如果你的宿主机不对公网开放，这没什么问题，但是对公网开放时这些端口经常被人扫。笔者目前的解决方法是在ufw中指定来源ip，比如：ufw allow from 192.168.1.0/24 to any port 22允许 192.168.1.* 来源的ip访问22端口（ 24 是掩码，一种特定的写法）。这样端口虽然开放了，但外网无法访问。更多ufw的使用方法，请自行搜索，比如参考 这个问题 。（3）docker网络-多机设想以下的场景：机器A上运行容器a，机器B需要访问容器a。一般的场景下将容器a端口映射或者用host模式即可，但在某些情况下（比如端口冲突且不能更改时），可以选择将容器a虚拟化、分配一个独立的局域网ip，相当于一台机器C。macvlan、overlay技术都可以达到该效果，其中macvlan技术的示例见 自建DNS与广告过滤-adguardhome与pihole 中的附录。