带有Docker Compose的本机Commento服务器

注意:这是我的帖子 (英文)的译文,描述了在原始文档所在的同一站点上使用的评论服务器的实现。


TL; DR版本:我开发了Commento服务器配置,该配置可轻松,简单地以半自动模式进行部署。 从GitHub 将此存储库复制到您自己,并按照README中的说明进行操作。

不久前,我不可抗拒地想将Disqus(可能是向页面添加评论的最常见系统)更改为免费开放的Commento


为什么选择Commento?


像许多其他“免费”产品一样,Disqus的问题在于,这种情况下的产品是用户-即您。 此外,Disqus“丰富”了使用兆字节脚本和一百多个其他HTTP请求的每个页面。


另外,它的免费版本显示可以“每月仅$ 9”购买的广告(Plus计划)。 仅此一项就足以找到更好的东西。


在某个时候,我偶然发现了这篇文章,并发现了一个名为Commento的免费评论服务器。 碰巧的是,Commento最近才完全开放-在提供两个版本(免费的Community和Commercial Enterprise)之前,Commento完全开放了。 感谢其开发商Adhityaa Chandrasekar。


Commento比Disqus效率高几个数量级 ,附加负载的典型大小约为11 KB ,当然还有注释本身。 与所需的HTTP请求大致相同的情况。


Commento服务器的另一个优点是它非常快,因为它是用Go编写的。


好吧,作为蛋糕上的樱桃,他收到了来自Disqus的大量评论,他还能梦到什么?


Commento用例


对于非高级(技术上)用户,Commento在commento.io上提供了立即可用的云服务。 作者提供您自己选择月租费,但出于技术原因,不能低于$ 3。


Chandrasekar先生还慷慨地在Commento.io上提供了一个免费帐户,以换取该产品的“非重要补丁”。


好吧,我选择了第三个选项:自己提高Commento服务器。 在这种情况下,您不依赖任何人(当然,除了托管人以外),我喜欢独立性。


难点


我是Docker容器的忠实拥护者,并且经常使用Docker Compose (一种用于管理多个相关容器的组的工具)。 Commento在GitLab容器注册表中有一个现成的Docker映像。


因此,决定使用本身成熟的容器-但首先必须确定一些事情。


难题一:PostgreSQL


Commento需要一个相当新的PostgreSQL服务器版本,不幸的是不支持其他SQL服务器。


好吧,我们仍然在容器中运行所有内容,因此非常简单。


难度2:不支持HTTPS


Commento本身是一台Web服务器,但仅支持不安全的HTTP协议。


应当指出,这种做法在当今很普遍:在这种情况下,服务器隐藏在反向代理后面, 反向代理也执行SSL卸载。 事实是,在这种情况下,绝对需要SSL / HTTPS支持,毕竟在院子里2019年,寻找使用不安全的Internet协议授权用户的尝试非常麻烦。


我决定使用Nginx服务器,首先,我在使用它方面有丰富的经验,其次,它非常快速,经济且稳定。 并发布Docker映像的官方版本。


HTTPS配方中的第二个成分是域的SSL证书。 我永远感谢EFF和Mozilla创建了Let's Encrypt证书颁发机构 ,该机构每月颁发数百万个免费证书。


我们加密还提供了一个免费的命令行实用程序certbot ,该实用程序大大简化了获取和更新证书的过程。 好吧,而且-当然-给他的一个Docker映像!


难题3:Certbot鸡蛋问题


但是这个技巧比较棘手。


我们想在Nginx上的反向代理配置中引用SSL证书,这意味着如果没有证书,它只会拒绝启动。 同时,要获得 SSL证书,您需要一个正常工作的HTTP服务器,“加密”将证明您对该域的所有权。


我设法解决了这个问题,在我看来,这很优雅:


  1. 首先,生成一个虚拟的无效证书,其唯一目的是让Nginx启动。
  2. Nginx和certbot共同获得一个新的,现在有效的证书。
  3. 根据Let's Encrypt的建议,一旦收到证书,certbot就进入“待机模式”,每12个小时醒来一次,以检查是否需要更新。
  4. 片刻之后并且证书已被续订,certbot将通知Nginx重新启动。

困难四:必须保留一些东西


我强烈怀疑您要在重新引导或系统更新后保存用户注释。


另外,为了避免由于过于频繁的请求而让我们加密,最好在整个有效期内保留收到的证书。


在建议的配置中,使用Docker的卷解决了这两点,该卷是在Commento首次启动时由systemd自动创建的。 卷被标记为external卷,因此当使用docker-compose down -v删除容器时,Docker会跳过它们。


放在一起


现在您可以看到它们如何协同工作。


下图显示了四个容器之间的交互和流量:



我应用了内置的Docker depends_on选项,以确保容器以正确的顺序启动。


如果您只想启动自己的Commento服务器,则可以跳过本文的其余部分,直接转到GitHub上代码


好吧,稍后我将更详细地讨论此实现。


一切如何运作


撰写档案


如您在上图中所看到的,我的“组合”包含四个服务:


  1. certbot -EFF的certbot实用程序
  2. nginx反向代理实现SSL卸载
  3. app -评论服务器
  4. postgres -PostgreSQL数据库

docker-compose.yml包含其自己的Docker网络的声明(称为commento_network )和三个卷,其中两个是外部的(即必须在Compose外部创建):


  • commento_postgres_volume存储PostgreSQL服务器数据:用户,主持人,注释等。
  • certbot_etc_volume包含certbot接收的certbot

Nginx的


Nginx容器基于基于Alpine的轻量级官方映像,并使用以下脚本运行:


 #!/bin/sh trap exit TERM # Wait for the certificate file to arrive wait_for_certs() { echo 'Waiting for config files from certbot...' i=0 while [[ ! -f /etc/letsencrypt/options-ssl-nginx.conf ]]; do sleep 0.5 [[ $((i++)) -gt 20 ]] && echo 'No files after 10 seconds, aborting' && exit 2 done } # Watches for a "reload flag" (planted by certbot container) file and reloads nginx config once it's there watch_restart_flag() { while :; do [[ -f /var/www/certbot/.nginx-reload ]] && rm -f /var/www/certbot/.nginx-reload && echo 'Reloading nginx' && nginx -s reload sleep 10 done } # Wait for certbot wait_for_certs # Start "reload flag" watcher watch_restart_flag & # Run nginx in the foreground echo 'Starting nginx' exec nginx -g 'daemon off;' 

  • 3行ARRGHHH,Habr不支持在代码中显示行号-大约翻译)。注册了一个中断处理程序,以便Nginx和后台监视过程在容器停止时成功完成工作。
  • 27行调用了wait函数,该函数将暂停Nginx启动过程,直到certbot容器创建的SSL配置文件为止。 没有这个,Nginx将拒绝启动。
  • 30行创建了一个后台进程,该进程每十秒钟定期检查一次名为.nginx-reload的标志文件的存在,并在检测到该文件后立即指示Nginx重新加载配置。 证书更新时,此文件还会创建certbot。
  • 34行在正常模式下启动Nginx。 在这种情况下, exec表示当前的Shell进程已由 Nginx进程代替

此图中的另一个重要文件是Commento虚拟服务器配置,该配置强制Nginx将HTTPS请求转发到commento容器:


 server { listen [::]:443 ssl ipv6only=on; listen 443 ssl; server_tokens off; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name __DOMAIN__; location / { proxy_pass http://app:8080/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } ssl_certificate /etc/letsencrypt/live/__DOMAIN__/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/__DOMAIN__/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; } server { listen 80 default_server; listen [::]:80 default_server; server_tokens off; server_name __DOMAIN__; location /.well-known/acme-challenge/ { root /var/www/certbot; } # Redirect to HTTPS on port 80 location / { return 301 https://$host$request_uri; } } 

第一个服务器块(第1-21行)描述了如何使用HTTPS和转发规则。 这是提及“加密我们的”证书文件(或使用存根代替)的地方。


构建图像时,将服务器提供的域作为参数传递; 它替换了服务器配置中的__DOMAIN__行。


第二个块(第23-38行)是HTTP服务器的配置,certbot使用它来确认域所有权(所谓的“ ACME挑战”)。 所有其他请求都会导致通过HTTPS重定向到相应的地址。


证书机器人


我们的certbot映像基于带有以下脚本的官方构建


 #!/bin/sh trap exit TERM # Wait until nginx is up and running, up to 10 seconds wait_for_nginx() { echo 'Waiting for nginx...' i=0 while ! nc -z nginx 80 &>/dev/null; do sleep 0.5 [[ $((i++)) -gt 20 ]] && echo "nginx isn't online after 10 seconds, aborting" && exit 4 done echo 'nginx is up and running' } # Check vars [[ -z "$DOMAIN" ]] && echo "Environment variable 'DOMAIN' isn't defined" && exit 2 [[ -z "$EMAIL" ]] && echo "Environment variable 'EMAIL' isn't defined" && exit 2 TEST="${TEST:-false}" # Check external mounts data_dir='/etc/letsencrypt' www_dir='/var/www/certbot' [[ ! -d "$data_dir" ]] && echo "Directory $data_dir must be externally mounted" [[ ! -d "$www_dir" ]] && echo "Directory $www_dir must be externally mounted" # If the config/certificates haven't been initialised yet if [[ ! -e "$data_dir/options-ssl-nginx.conf" ]]; then # Copy config over from the initial location echo 'Initialising nginx config' cp /conf/options-ssl-nginx.conf /conf/ssl-dhparams.pem "$data_dir/" # Copy dummy certificates mkdir -p "$data_dir/live/$DOMAIN" cp /conf/privkey.pem /conf/fullchain.pem "$data_dir/live/$DOMAIN/" # Wait for nginx wait_for_nginx # Remove dummy certificates rm -rf "$data_dir/live/$DOMAIN/" # Run certbot to validate/renew certificate test_arg= $TEST && test_arg='--test-cert' certbot certonly --webroot -w /var/www/certbot -n -d "$DOMAIN" $test_arg -m "$EMAIL" --rsa-key-size 4096 --agree-tos --force-renewal # Reload nginx config touch /var/www/certbot/.nginx-reload # nginx config has been already initialised - just give nginx time to come up else wait_for_nginx fi # Run certbot in a loop for renewals while :; do certbot renew # Reload nginx config touch /var/www/certbot/.nginx-reload sleep 12h done 

简要介绍以下内容:


  • 如上一个脚本中一样,第3行是常规完成容器所必需的。
  • 17-19行检查所需的变量。
  • 在第22-25行中-正确安装了certbot工作所需的目录。
  • 叉子如下:
    • 仅在容器的第一个开始执行第30-50行:
      • 复制一个虚拟证书,使Nginx可以正常启动。
      • 同时,Nginx正在等待此过程结束,然后继续下载。
      • Nginx启动后,certbot将启动从Let's Encrypt获取有效证书的过程。
      • 最后,一旦收到证书,就会创建.nginx-reload文件,并向Nginx提示是时候重新加载配置了。
    • 54行等待Nginx启动-如果已经有完整的证书。
  • 完成所有这些操作后(第58-63行),他继续循环,每12小时检查一次是否需要续订证书,并通知Nginx重新启动。

Commento和PostgreSQL


apppostgres容器使用开发人员提供的原始图像,没有任何更改。


系统服务


这个难题的最后一部分是systemd commento.service 单元文件 ,您需要在该文件中/etc/systemd/system/commento.service创建一个符号链接,以便在系统启动时适时启动它:


 [Unit] Description=Commento server [Service] TimeoutStopSec=30 WorkingDirectory=/opt/commento ExecStartPre=-/usr/bin/docker volume create commento_postgres_volume ExecStartPre=-/usr/bin/docker volume create certbot_etc_volume ExecStartPre=-/usr/local/bin/docker-compose -p commento down -v ExecStart=/usr/local/bin/docker-compose -p commento up --abort-on-container-exit ExecStop=/usr/local/bin/docker-compose -p commento down -v [Install] WantedBy=multi-user.target 

行数:


  • 6行表示将项目代码克隆到/opt/commento目录-这要简单得多。
  • 7-8行创建外部卷(如果尚未创建)。
  • 在第9行中,删除了先前容器的可能剩余部分。 外部卷将保留。
  • 10行标志着Docker Compose的实际发布。 当容器中的任何一个--abort-on-container-exit--abort-on-container-exit标志将--abort-on-container-exit整个容器群。 因此,systemd将至少知道该服务已停止。
  • 11行再次清理和删除容器,网络和卷。

源代码


GitHub上提供一个完全正常的实现,只需要在docker-compose.yml配置变量docker-compose.yml 。 您只需要仔细按照README中描述的步骤进行操作即可。


该代码受MIT许可证的约束。


感谢您阅读此地方,非常欢迎发表评论!

Source: https://habr.com/ru/post/zh-CN464105/


All Articles