Kitura deployment notes
背景
Ric前些阵子提出想部署一个纯Swift的开发环境,即macOS/iOS客户端用Swift写,后台服务器也用Swift起。这样的好处在于开发者只用掌握一门语言,即可完成“全栈”——整个应用的开发流程。
事不宜迟,我马上去调研有哪些较为成熟的后端方案。首先映入眼帘的是Kitura/Vapor/Perfect三驾马车。其中Kitura是IBM牵头的,其他二者为第三方为主导。更新频率方面,Vapor最快最勤,亲身践行了Swift官方跨大版本该API的“优良传统”,每次升级有很多部分推倒重来;Kitura有如IBM的一贯作风,稳健迟缓,据说后向兼容性能够保持到初代;Perfect虽说有一点中文支持,但更新频率赶不上二者,直到Swift5.1发布时依旧只停留在4.1的支持。
最开始我是选择了Kitura的,无它,相信大品牌名字读起来带感。它的文档也是比较全面而克制的,简洁而富有表达力,花了半小时就配好了环境。一切顺利,我想先试试官方提到的几个亮点功能吧,结果随便安装了个Markdown,发现渲染效果不堪直视——连表格和任务清单都无法渲染。这让我这个GitLab中度用户非常不爽,遂弃而转向Vapor。
Vapor的安装稍微多花一点时间,原因是其初始的“内置电池”,自带功能比较多,在3409这台Core Duo E8400的老机器上编译花了不少时间。加上后端Swift暂时还通过一个叫做SPM的工具管理,原理类似Carthage,不如CocoaPods可以直接引入二进制文件那么快。在等的过程中,我便熟悉了一下Vapor的文档。
作为一个只用过Python Web后端的菜鸟选手,我对服务器的认识主要来源于Django。Django的文档既清晰又极具操作性,每次忘记一些概念,开个项目一试就弄清了,非常适合学生做点自用的小服务器。上次Livid还说Django是“重量级”的服务,我倒不觉得。较之LAMP、Nginx,Django的部署可谓相当轻松了。
不过在看文档的过程中,还是有些隐忧。相比于Kitura,Vapor的“跑路”可能性更大。项目的姊妹云服务标价没啥竞争力,整个网站也有点买模板的感觉,Ubuntu的文档不如Kitura详细。语法也没有其那么标准易于理解。无论如何,还是先部署一个试试。
安装Swift环境
从官网下载,检查签名然后解压。如果不直接放进系统的bin的话,需要设置环境变量:
$ export PATH=/path/to/usr/bin:"${PATH}"
执行或者将其加入~/.bashrc
即可。
$ swift --version
以检查环境是否正确,Swift程序可以找到。
安装Vapor环境
目前2019年6月19日最新版本的环境是Vapor4,但仅支持Swift5.1.由于Swift官网发布版还在5.0.1版,因此就退而求其次选了Vapor3。等秋季开始时再看是否需要升级。
$ eval "$(curl -sL https://apt.vapor.sh)"
$ sudo apt-get install vapor
$ vapor --help
检查vapor环境正确。
然后通过
$ vapor new <your-project-name>
创建一个APP。与Django的startproject过程一样,都会创建一个包含设置、路由以及MVC的项目。不同的是,Vapor把文件夹都默认好了,只要把对应模块放进去即可。
$ vapor build
$ vapor run
或直接运行run也会增量编译。
默认编译是Debug的scheme,可通过vapor run--env production
设为禁用debug输出等等。
最后编译运行,即可在http://localhost:8080
看到It works的字样。
配置Nginx代理
本以为应该没啥问题,结果栽了大跟头。先说说桌子下这台Core Duo的主机。估计有年头了,CPU E8400,至少也是08或09年的机器了。内存2G、硬盘20G,由工院IT支持部门的小哥维护,通过netID登录,网卡注册到devices页面。这网络配置跟散落在学校各处的iMac估计很类似。然而最初我并未意识到这点,仍以为这是台空白的,完全由自己掌控的电脑。
于是就一步步来呗,按照一个教程安装了个Supervisor,后来觉得还是直接用screen留着会话,每次连上去看log比较简单,于是又改了回来。
$ supervisorctl status
的效果与
$ screen -ls
并不一样,一个是监控进程状态,另一个是监控会话状态。
然后第二部分教程让改防火墙。这也很正常,毕竟Windows每次做些跟网络有关的实验也需要关。
$ sudo ufw enable
$ sudo ufw allow ssh
$ sudo ufw allow http
$ sudo ufw status
此处ufw是Uncomplicated Firewall
的简称,是Ubuntu的简化版iptables,也就是这段网上看来的描述坑了我两三个小时的时间。
把ufw打开,允许22和80端口,这都很容易理解。然后配置nginx,因为暂时没有域名,所以直接搞
$ sudo nano /etc/nginx/sites-available/default
即可,指定80端口(上面防火墙允许了的),然后映射到本地的8080端口。
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
proxy_pass http://127.0.0.1:8080;
}
}
一切都是简单直观,不应该有任何问题。然而一刷新我傻了:Connection Refused.
不死心,换了两台设备测,结果一台是timeout另一台也是refused。这让我顿生疑虑。一般而言我见过的refused都因为服务器配置了规则禁止连接,但怎么又会timeout呢?难道是nginx根本没起来?
花了半小时了解如何获取nginx运行状态,以及常见nginx操作:
$ service nginx status
$ systemctl status nginx.service
$ sudo lsof -i TCP:80
$ systemctl -q is-active nginx && echo "It is active"
$ systemctl restart nginx
检查了一圈发现,似乎问题不在它身上。当时还在用Supervisor,又花了半小时研究如何检查它的状态,发现除了写了个typo以外并没有什么问题。题外话:自己打字不好速度快容易错的毛病,在开始学编程后被急剧放大,经常因为打错字怀疑人生,打错字挥泪重启服务器的举动不知一次出现——循环条件错死循环、内核变量地址错kernel panic,如此种种,累觉不爱。吭哧吭哧又浪费半小时,排除了它的嫌疑。
冷静下来一想,本地既然能够访问,那说明跟Swift和Vapor都没有关系。但是localhost能够访问127.0.0.1却不能,这又是为什么呢?感觉自己逐渐抓住了重点,于是去看hosts,看ufw的设置,C(4,2)一番穷举测试发现并没有什么用。时间不知不觉地流逝,只好一边吃差强人意的速冻盒饭一边调试。怎么设置ufw都没用,真的有些气馁了。还能有啥原因呢?
最后只能硬着头皮用关键字搜,搜到了关于iptables的操作。先是看到了个类似的问题,然后开始现场学如何改iptables。最后打开这台主机的iptables一看,唉呀妈呀这是turd mountain啊!
130多行的规则,清晰地划分了学院各个子网的访问权限,其中INPUT部分的-A INPUT -j drop
赫然在目。就是这句话浪费了我人生中宝贵的两个半小时。之前一直以为这是台人畜无害的干净主机,甚至连git和nginx都没装过。谁曾想,没装过nginx却把防火墙规则配了一溜够,真是披着羊皮的狼!
试了试发现靠前的条目优先级高于靠后的条目,于是加了一句-A INPUT -s 10.0.0.0/8 -p tcp -m tcp --dport 80 -j ACCEPT
解决问题。按理说,我是不应该配置这台主机的网络的,但阴差阳错又折腾得够呛。除了新知道一个词iptables以外,大半天时间全无收获,非常烦躁。
后记
越多地经历这样的情景,越让我觉得刷题有些可取之处。至少刷题时大脑能一直保持活动,时间不算浪费。而这种环境问题自己去找答案除了浪费时间毫无意义。最开始本想找个docker直接配好,结果这种小众的东西并没有人做高度定制化的docker,只有官方的两个镜像孤零零地挂在hub上。
又想到可能会要用CUDA,干脆别折腾了,花点钱买AWS算了。
吐槽完了,接着说Vapor,看它文档的功能还挺多挺全,而且Swift的Foundation并不比Python自带的库弱。尤其是宣传的性能Swift甚至能追上Go,令人很心动。之前看了几行go的代码,真的是难受啊,比C还难看。
自己没想过成为一个程序员大牛,只希望能用计算机语言在最短的时间创造最大的快捷、便利、快乐与幸福,这便是我在计算机领域的理想。Swift、Python、MATLAB、Verilog加之MSVC家族,自下而上,由内到外,基本已经能满足我能想到的大部分设想。或许没必要再纠结于编译原理,本身就是门外汉,何必与专业人士比拼他们的强项呢?
看着时长横跨17-19年的人生“或许是第一篇也是最后一篇”论文,从科研的急先锋跌至吊车尾,从编程的门外汉闯进疯人院……今天在和Millie录像时突然回首这两年,变化实在不小。或许在法国的平行世界中,我的变化更大?驾驶帆船乘风破浪在芒什海峡?摇车登上阿尔普迪埃?……
兜兜转转多年后,还是踏进了编程的世界,不知压箱底的辛文送的Java教材会作何感想。
满纸荒唐言,一把辛酸泪。😢
190623 补充
- 关于 WebSocket 的设置
环境搭好了以后主要通过两个项目来入门学习后端的配置与编写服务。一个是Kitura-Sample,也就是Kitura官方教程汇总的一个repo。其中有一些问题已经通过issue反馈给了项目管理员。不得不说,正规军的响应速度、解答的耐心程度都值得称道,让我对IBM这家公司路转粉。
在当前的项目中,有一个基于 WebSocket 的在线聊天室的实现。后端代码虽然简单但不失为一个好的脚手架,况且Swift原生的类似程序很少,值得继续探究。但当我想登入聊天室测试一下时,奇怪的事情发生了:WebSocket connection failed: Error during WebSocket handshake: Unexpected response code: 302
按理说302并非一个错误代码,可为什么无法建立连接呢?分别试了防火墙端口、路由路径以及npm依赖重装以后仍旧没有解决。在SO的帖子上却看到了真正的原因。似乎是因为 Nginx 需要对 WebSocket 的 Upgrade header 做出具体设置以实现长时间连接不断线的功能。
因此,上面的有关Nginx的配置需要增添为:
$ sudo nano /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
}
}
当然,最后两条和 WebSocket 无关,主要是把请求头补齐信息更全……
Leave a Comment