部署和维护开源软件的经验

现在公司多多少少都会用一些开源的软件,我在工作中也部署和维护了很多开源软件。这篇文章就讲讲维护这些软件的一些经验。我主要想说的,是那些需要部署的服务,比如 Jenkins,Prometheus 这些。命令行工具,和库之类的,不在讨论的范围内(但是有一些经验是同样适用的)。

技术选型

如果能解决问题的方案有很多的话,选择哪一种来使用呢?

对开源软件技术选型的时候,可以参考的因素有:

口碑,被接受程度。如果软件的用户越多,那么存在的问题暴露的概率就越大,网上的资料也越多。需要去踩的坑也就越少。

已经存在的时间。同上,久经考验的软件相对可靠一些。

API 和 开放性。这一点容易被忽略。如果有 API 支持的话,在结合公司内部其他系统的时候就会简单很多。也可以做更多定制化的功能。存储使用的格式是什么?如果是公开的标准的话,比如 VictoriaMetrics 有针对 Prometheus 很方便的导入和导出,之后迁移会简单很多,就算以后不用这个软件了,也能很容易地换到其他的方案上。

做决策的支持一部分是知识,一部分是信息。信息就主要来自于项目文档。项目的 issue 页面,和社区讨论也值得参考。

项目文档建议仔细阅读一遍,能得到不少 insights。我经常在解决一个问题的时候,苦于不知道这个工具是否提供了某种方案可以解决这个问题。如果度过一遍文档的时候,遇到问题,你至少能想起某一个关键字。

读文档看似是挺花费时间的,但其实是节省时间。它至少可以加深你对软件的了解。(我发现我花时间最多的,就是用一个工具干不适合它干的事情……)

从 0 到 1 搭建

新部署一个开源软件,主要需要解决两个方面的问题。

第一个问题是如何将它部署到自家公司的基础设施中。常见的部署方法有:

  • 直接在机器上安装 Docker,然后一个 Docker 命令启动;
  • 写 ansible playbook 部署
  • 使用 k8s (一般公司内部都会有定制化)

对于合适的软件选择合适的部署方法非常重要。不要小看一行命令 Docker 部署这种方式,对于有些软件来说非常合适,比如 Jenkins,它的 Java 依赖比较难处理,又有很多系统依赖,但是本质是一个非常独立的单体应用,依赖的内容存储在一个单一的文件夹中,这种就非常适合用 Docker 部署了,升级和重启都是一行命令。虽然是手动操作,但这种方式几乎是效率最高的。

WordPress 是一个特殊的例子,这个软件有一些神奇的功能,比如在后台点一下,它可以去更新自己的代码,升级插件代码,甚至能升级自己本身。所以我将 wordpress 的运行环境整个都放在一个 Git 中,追踪代码的变化。

第二个问题是解决依赖,软件一般都有对其他服务的依赖,最常见的是存储。这部分选型的时候也可以考虑,一般依赖越少越好。比如 Jenkins,这个东西很神奇,基本上只依赖一个 $HOME_DIR, 给它一个文件夹,其他什么都不需要了,对升级和备份特别友好。其他依赖比如网关怎么接入,用户怎么登陆,后端的存储怎么维护,等等。

定制功能

官方的版本没有办法满足所有人的需求,我们经常需要对软件进行定制化。

需要强调的是,最好使用软件支持的一些插件的格式来做,比如 Jenkins, CoreDNS, K8s 等等,都支持插件。还有一些像是 Prometheus,可以通过写外部服务的方式做定制化的功能。

下下策就是直接 fork 修改软件的代码,这样就脱离主干了,后面很难跟进官方的升级。(不过要是能直接给官方提交 PR,是一个不错的方式)

升级

这其实是一个很重要的问题,就是要跟进官方的更新。

为什么说很重要呢?我见过很多故事,都是安装上一个软件版本,之后就再也不更新了,因为跑的好好的,没有人想去动(大部分软件都是这样,你通过它的版本就可以发现它是什么时候引入公司的,笑)。或者是因为 fork 修改了代码,升级升不动。然后从这个版本开始,存在的问题,可能你都要自己解决一遍。可能官方已经发布修复或者优化了,但是因为没有升级,就没办法享受到。甚至你都不知道这个更新。

所以建议订阅官方的发布记录,一般都会有提供 RSS 的订阅方式。

要阅读每一个版本的 changelog。

最好不要进行跨版本升级,因为有些不兼容修改可能会导致问题(还是以官方的升级指引为准)。

还可以订阅一些核心开发者的消息动态,看看大家都在关心什么问题,解决什么问题。

支持

最后,使用软件的过程中一定会遇到各种问题。如果当前没有解决方案,但是又有很多人有同样的问题,那么可以尝试自己去解决一下。(回馈社区!)

报告问题,在某种程度上也是一种支持。但是要把问题说清楚,提交可以复现的 Case。

如果没精力,那么捐点钱也是很大的支持。

 

去印尼爬火山 (Mt.Rinjani)

上回书说到,旅行刷新了去过的最南端。上周又去了印尼,这次去刷新了去过的最高点。我们去 Lombok 爬了 Rinjani 火山。

与其说是旅行,这次更像是一个修炼,体力和耐力的挑战,回来之后身心俱疲,跟之前去旅行度假完全不同。

Rinjani 是印尼第二高的火山,一共 3726 米。火山相比于其他的山,有很多奇特的地方,比如奇特的地貌,火山湖,路上的火山灰等等。

这也是我第一次爬火山(可能也是最后一次了)。这篇还是流水账地记一记这几天的行程吧,如果你也想去的话,我在本文最后写一些注意事项。

Day 0: 到达 Lombok,看瀑布

第一天坐飞机愉快地到达了龙母岛 (Lombok),此时开开心心,完全没有意识到事态的严重性。

从机场到酒店需要 3 个小时,比较远。我们是直接住在了山脚下,如果爬山那天长途坐车的话,实在是吃不消。

路基本上是沿着岛的海岸线修的,所以路上左边时常可以看到大海。

我们住的酒店是 Rinjani Light House, owner 是一个美国人,看得出来老板很喜欢咖啡,店里有很多和咖啡相关的书籍和张贴。

酒店很漂亮,是很有乡村气息的小屋。价格也很便宜(比在新加坡租房都要便宜)。

酒店很漂亮,天气凉爽

办好入住之后,我们就去附近的一个瀑布逛一下。没想到的是,去这个瀑布要下这么多楼梯。回来的时候,几乎已经累倒了。

瀑布,在瀑布下感受水汽

瀑布入口,可以看到 Rinjani 山

瀑布的门票 2万 Rp。

晚上就早早的吃过饭,爬山的公司(我们选择的是 Rinjani Dawn Adventures)来酒店坐 briefing。告诉我们大体的路线和答疑解惑。通行的游客也可以互相认识一下。和我们同一家公司的有一队新西兰年轻夫妇,两个加拿大阿姨。但是大家其实不是一起走的,我们两个人是单独一个团,包括了一个 Guide 和 2 个 Porter,应该是每两个人都是这么个配置。Porter 会帮我们带食物,3 天的引用水,帐篷睡袋等。我们自己要背其他所有的个人物品,包括衣服,工具,等等。几乎一直要背一瓶 1.5L 的水,喝完了再跟 Guide 要一瓶,自己喝的水自己背着。(第二天才知道,加拿大阿姨的团还挺高级,人家自己不需要背东西的,Guide 可以帮忙背,可能买的套餐更贵吧,笑)

团队过来坐 briefing

Day 1: 从 Sembalun 出发,下午到达营地

早上6点起床,车准时来接,加拿大阿姨跟我们住同一家酒店,我们一起上车,然后去接上另外两个人。到了他们公司,又做了一次 briefing。

在公司 briefing again

在这里如果自己缺什么东西的话,可以跟他们借,比如头灯,冲锋衣什么的。最好重新检查一遍自己的装备,因为要带的东西很多,难免有遗漏。

一切就绪就发车去起点。我们的路线是 Sembalun 上山,Senaru 下山。

去出发点之前先去了一个地方做 Medical Checkup. 其实就是填了个表,量了血压。然后再次坐车,终于到了出发点,我们的行程正式开始了。

到达山脚下

Porter 大队,很快就超过了我们,而且……都穿着人字拖。后面遇到了很多非常陡峭的路,近乎直上直下了,有些地方要借助绳子,梯子,不知道这些路 Porter 都是怎么过去的……

很快被 Porter 大队赶上了

今天的路线中,Pos 1 到 Pos 3 都很简单,路比较平缓,大概三个小时左右就可以了,但是 Pos 3 到营地非常陡,虽然路在地图上看起来很短,但是需要 3-4 小时。

路上的风景

POS1 路标

到 Pos 2,Porter 已经在等着我们了,我们到了之后他们就可以做午饭。

大家都在 POS2 吃饭

每一顿饭都有一个主食,软饮料,热饮料,水果盘。非常丰盛,每一顿都吃不完。

食物很赞

吃完饭之后继续出发。第一天感觉状态良好,在能力范围内,坚持一下就到目的地了。最后一段路上已经有一些火山灰了,比较滑,还摔了一跤。

从 Pos 3 往后,路上就是云雾缭绕的了,天气非常凉爽,虽然有些湿,但是因为不闷热,所以也还挺舒服。

路上云雾缭绕

在下午5点左右到达了营地。远处可以看到第二天要去的顶峰,此时还有一些云雾。不过过了一会就全散了,天空非常蓝,很纯净。几乎可以用风和日丽来形容,运气比较好。在这里欣赏了一个完整的日落。

到达营地

远处可以看到 Rinjani 山,来的比较早,看到了一个完整的云雾散去到日落的风景,很赞

无人机拍摄的营地

太阳下山之后,气温马上就降低了,今晚的饭正好是热汤,端在手里感觉非常幸福。哦对了,饭前吃了炸香蕉饼,也很好吃,只不过被猴子抢走了一个。

我们的帐篷

看着日落吃晚饭

观看日落

日落

吃过晚饭之后就钻到帐篷里面了,为第二天的冲顶养精蓄锐。

晚上睡觉还有一个小插曲,来了一只动物绕着我们的帐篷嗅了一圈,应该是野猪。

Day 2: 凌晨 2 点冲顶,下午徒步 6 小时到达第二个营地

凌晨两点准时起床,这顿饭是 extra breakfast,只是一碗泡面而已,事后证明根本不够。

冲顶的路分成三段,第一段是从营地走到山脊,非常难走,路上有梯子,绳子。大部分地形是在树林里面。路上可以看到远处一片亮闪闪的头灯。

第二段是沿着山脊上山,虽然坡度也不小,但是刚完成了第一段,会觉得这里好简单。

最后一段简直是让人绝望。整段路都是火山石,非常滑,走两步滑一步,又陡峭,两边的大斜坡看着像悬崖一样。

路海拔高,还比较陡

我的力气就在这一段感觉很快耗尽了,此时我们的海拔从营地的 2600m 上升到了 3000 多米,最后的 600m 尤为困难。此时的感觉就是迈不上去腿,无法使出力气,头感觉轻飘飘的,像随时要晕倒一样。(后来也有这个感觉,总结发现超过2000米就会这样,下降一段会好很多,应该是高原反应)。

时间已经到了6点,去山顶看日出是赶不上了,好在我们发现半山腰的日出也很美。就在这里看完了日出再继续。

在半山腰等日出

Guide 说他可以帮我,我可以拉着他的手。这时实在是一点力气都没有了,于是最后几百米大概就是这么上去的……

终于到达了山顶,其实说实话山顶的景色比较一般(可以对比一下后面下山的时候拍的照片)。在山顶稍作休息,我们就开始下山了。

山顶的风景 3726m

下山不需要什么力气,但是因为太阳出来了,马上就变得很晒,我们穿的又比较多,所以感觉被太阳 dry out 了。路上休息的不多,一路上想着感觉回去。

好像最近挺流行这么拍照

路上遇到了一只小猴子。

因为海拔比较高,所以路上都没有什么植物,一路顶着烈日,最后终于回到了营地,已经是筋疲力尽了。

这天中午的午饭又是油炸食品(感觉印尼的食物风格就是油炸,味精放的比较多),实在是没什么胃口,但是也强咽了几口。

时间已经是 10:45 了,回到营地的时候帐篷少了很多,一些团队已经开始下山了。因为我们下午还有六个小时的徒步(路上一想到这里就有一些绝望,笑,不过最后还是走下来了),所以吃完饭之后就抓紧收拾行李准备出发。

这里说一下行程:

  1. 最难的是我们这种 3D2N (三天两夜)的,第二天冲顶完成之后,下午要徒步去另一个营地,这天是最辛苦了;
  2. 次难的就是 2D1N 的,第一天到达营地,第二天冲顶然后接着下山。就是已经收掉的那些帐篷;
  3. 最轻松的还是 3D2N, 但是于 1 不同的是,第二天冲顶完成之后,选择放弃去另一个营地,下午就在原地休息,然后睡一晚上,第三天再下山。即 (2) 扩展成多一天休息;

路上 Guide 看到我的样子,跟我说如果我想放弃下午的徒步呆在这里,也是可以的,毕竟是私人的团。我想来都来了,还是坚持一下吧,所以还是选择了继续原来的行程。

帐篷经过上午太阳的暴晒已经非常闷热,在帐篷里收拾东西非常痛苦。打包好了,就开始了下午的徒步路线。出发的时候已经 12 点多了,计划是徒步 6 小时之后在营地 2 看日落,听说日落是很美的。但是以我的速度(拖团队的后腿了),可能赶不上。

下午的路程是三个小时下山,三个小时上山。基本上是环着火山湖(就是图片中的那个)走,先沿湖下山到湖边,然后绕着湖徒步半圈,最后上山爬升到另一个山上扎营。营地的位置其实就是第一天看日落(上文的图片就是)的时候,挡住太阳的那个山。所以今天的日落没有了山的遮挡,应该是很美的。

下山的路比我预期的要惊险很多,像是踩着长在悬崖上的石头上往下走,都跟攀岩差不多了。这里要感谢一下我的登山鞋,这是这次爬山我最感激买了的装备,火山灰,水,走石头路,泥泞路,都扛得住,非常耐造。

我们出发了一会,Porter 在后面收拾完帐篷等,就赶上来了。Porter 和我们走的路一模一样,没有近路。而且只穿着人字拖,感觉非常不可思议。

大约花了两个小时,石头路走完了,到了吃午饭的地方,还好午饭吃的不再是油炸食品,虽然胃口还是不好,但是也吃了不少。因为高反脑袋还有些晕,吃了一片 Panadol,好了很多。

午饭之后,是一个小时的路,较为平坦。现在的位置像是在一个山谷里面,离中间的火山湖已经很近了,路上云雾缭绕的,仙境一般。

被云雾笼罩的草地路

一个小时之后,来到了湖边。其实湖边有一个火山温泉的,但是我们已经顾不上去泡温泉了,只想着快点到目的地。

到了火山湖边

沿着湖边走了一段时间,来到了上山的路。

上山的路一路都很陡峭,就跟第一天最陡峭的那段一样。很多地方要借助绳子,梯子上去,梯子的斜度有点吓人。

一路上的景色还不错

从另一个方向看 Rinjani 山

在半路上看到的日落

最后在 7 点多到达了终点,日落已经没赶上了。吃过饭之后赶紧钻到帐篷里面去睡了。

Day 3: 下山

起床之后,对今天的行程就有信心多了,毕竟是一路下山,而且下山还不会有高反,哈哈。

早上起床看日出

下山走的是另一条路,一路上几乎都是丛林,没有太阳晒,好像没有啥好记录了,我们用四个办小时的时间完成了 5 个小时的路程,很顺利。

开始下山

回到旅馆冲了个凉水澡,好好休息了一下,第二天坐飞机回家了。

旅馆老板很有趣,给我们介绍咖啡,给我们看他的生豆(挑选过的,每一颗质量都不错),当晚给我们烘焙了一包,带了回家,第一次见到烘焙日期是当天的豆子。

以前以为印尼之后 Sumatra 这种浓烈的咖啡豆,没想到原来也有阿拉比卡,酸酸的,也挺好喝。

还买了一个手袋,这些周边产品老板说收入都会给本地的咖啡农和他们的孩子。

注意事项(仅供参考)

列了一些常见问题,如果你也想去的话(其实还蛮推荐的),可以参考一下:

山上有信号吗?

大部分地方没有,依照我的经验,超过 2000 米海拔的地方都没有信号,露营的地方没有。

小费给多少?

10新币(或100万Rp) * 参与人数 * 服务团队人数(包括 Guide 和 Porters)为最低标准。

如何购买?

参考上文链接。

我们是通过邮件和登山的公司交流的。另外注意每年的11月到次年的3月左右是雨季,封山。不要这个时间去。

总花费是多少?

我这次行程总共支出 4000 – 5000 人民币左右,包括:

  • 机票(从新加坡飞 Lombok)
  • 登山 Package,可以从我的连接看价格,3D2N 价格 240 USD 左右(3 个大哥带我们三天,伙食还跟饭店一样,感觉非常超值呀)
  • 接送机
  • 酒店(很便宜,一间可以住4个人,只要 250 CNY 左右……)
  • 其他话费……

需要带什么装备?

  • 衣服
    • 冲锋衣
    • 保暖层
    • 长裤
    • 速干衣(需要三件)
    • 除了冲顶的保暖衣服之外,其他的地方可以短袖短裤足够,但是,路上杂草很多,推荐降自己全包起来,比如压缩袜,瑜伽裤,冰袖之类的。会安全许多
  • 辅助配件
    • 护膝
    • 压缩袜
    • 羊毛袜(非常推荐,一双可以穿两天,脚不起水泡)
    • 水袋(比水瓶喝水方便)
    • 耳塞(晚上营地会比较吵)
  • 路餐(其实可以不带,Guide 带的已经足够)
  •  卫生用品
    • 卫生纸
    • 湿巾(用了很多,一路上都没有洗漱用的水)
    • 漱口水
  • 拍摄器材
    • 充电宝
    • 相机,Go Pro,无人机,自己选择,能拍的景色很多
  • 头灯
    • 这个很容易忽略,单独放出来,实在忘记了可以在出发前跟公司借
    • 一定要检查充好电了没有
    • 100 流明左右已经足够了
  • 药品
    • Panadol
    • 高反药
    • 创可贴
  • 防晒霜(物理防晒也可以,冰袖,遮阳帽)
  • 登山杖(主要是冲顶,踩着火山灰如果没有登山杖很难走)
  • 登山鞋(必须)

我能走下来吗?

除非是体能特别差,比如体重过高(其实我就属于胖子),一般都是没问题的。

我的经验:不要只担心后面有多少路,不要担心过多,要有一个信念,只要向前一步,就距离终点近了一点。

 

一个十万行的 trackback

之前写过一个服务发现系统,叫做 prometheus-http-sd,给 Prometheus 查找监控的目标使用。它的原理很简单,每当 Prometheus 请求 prometheus-http-sd 服务的时候,这个服务就执行用户的脚本,拿到 targets,然后返回。定位是作为一个中间系统,对接 Prometheus 和其他的 CMDB。用户通过写一个简单的脚本,可以将任何系统返回的结果,转换成 Prometheus 的 API 格式返回给 Prometheus.

于是就会有一个自然而然的需求:缓存。

如果部署了 100 个 Prometheus 采集端,那么每一个都需要来做服务发现,如果每 60s 请求一次,那么每分钟就有 100 次请求。监控目标的变更没有那么频繁,而且分钟级的不一致是可以接受的(Prometheus 原生的这个 HTTP 做服务发现的模式已经是分钟级不一致了)。所以我们可以把用户脚本的执行结果缓存起来,至少缓存1分钟,拿一次运行结果直接返回给这分钟内的其他请求。(考虑到内部系统一半写的比较差,这样可以节省巨大资源,笑)

以上是背景。这个缓存功能上线以后,我见到了10万行的一个错误 stack traceback:

这是异常日志的一部分,不知道读者是否能发现什么端倪。这个服务是开源的,存在 bug 的版本是这个

首先第一个比较明显的 bug 是,在用户脚本正常执行的时候才设置了缓存过期的时间,在发生异常的时候没有设置 expire 时间。这就导致了,一旦用户的脚本出发了 Exception,这个 Exception 就会进入到缓存中,并且不会过期,导致以后这个脚本永远会命中缓存返回 Exception。

但是为什么我会看到这么奇怪的 stack 呢?

看到这个 Exception,我的第一个想法是有什么地方拿到这个 Exception 之后,又作了修改,然后 set 回去,修改的时候又包上了当前的 stack,所以越包越长。但是在代码中看了好几遍,这个缓存都没有重新设置的地方。然后又用 PDB 去debug,跟着走了一遍代码,一切都是正常的。最后又用 watchpoint 这个库跟踪了一下,结果也是一样,cache 没有被重新修改过,只有第一次触发有 set。

那好吧,然后我又想到,会不会在 raise Exception 的时候,这个 stack 其实就是这样的了呢?又用 pdb 去跑了几遍代码,发现不存在这么深的 stack。(后来想了一下,Python 默认的 frame 最大好像是 1000,如果真是这样的 stack 的话肯定已经 stack overflow 了!)

想到这里已经没了思路,决定回家睡一觉。

第二天一醒,问题就解决了。

现在抛去一些无关的逻辑,最小化一个复现的 Case 是这样:

其中 d 是我们的缓存,上来就已经有了,然后,我们在代码中会 raise 这个 Exception 3 次。和 prometheus-http-sd 中是一样的逻辑。

看似无害,其实是有 bug 的。

它的输出如下:

可以看到,每一次这个 Exception 被 raise,它的 stack 都加深了。

到这里问题就明了了:raise Exception 这个操作本身就会修改这个 Exception。

PEP 3134 中发现,Python3 已经把和一个 Exception 有关的 traceback,局部变量等放到 Exception 对象中了。这里也说,你不能明确地指定一个 Python Exception 的 traceback。按照我们上面中代码的行为看,就是,Python 在 raise Exception 的时候,会把当前的 stack 写到 traceback 中,但是如果当前已经有 __traceback__ 了的话,就会把之前的 traceback 保留住。(但是没有找到相关资料和代码,欢迎读者补充)

所以,这里每次 raise 一次被缓存下来的 Exception,这个 stacktrace 就会加深一次。

那么问题如何解决呢?我觉得 Exception 不应该被缓存下来,从 Python 的机制可以看出,Exception 被 raise 的时候,应该是 Exception 真正发生的时候。而在我们的这个场景中,Exception 并没有发生,而只是我们返回了之前的一个缓存。缓存 Exception 对用户也有困扰,这个 Exception 看起来就像是:“我运行了你的代码,你的代码出错了”,而实际上用户的代码并没有执行过。所以我觉得这里应该缓存下来一个错误信息,string 就可以了。而不应该缓存 Exception 实例。


小插曲

在做以上实验的时候,我发现用这个代码,就会触发一个奇怪的错误:

错误如下:

说这个 e 没有定义,好奇怪。

然后在文档中找到了答案

原来 except 之后是有一个 clear 动作的。

想了一下,这应该是用来解决 GC 问题的。因为 Python3 把 trackback 放到 Exception 上,实际上是创建了一个环形的引用: err -> traceback -> stack frame -> err,引用计数无法清除,必须要等垃圾回收。而这部分环形的设计的对象又比较多,比如局部变量里面有已经关闭的文件的话,在 gc 之前,可能 file handle 会耗尽。所以这里解释器相当于做引用减 1 ,破掉 stack frame -> err 这个环。(不过感觉应该有办法区分开,当前的 e  不是 local,不要 del 吧?)

 

缅怀陈皓

今天听闻陈皓去世了,心情久久不能平静。周末的时候我还在看他在 Twitter 分享的一个观点,居然这一条就成了他最后发送的推文。

陈皓对我的帮助非常大,我的书签,博客引用,和笔记里面,都有很多对 coolshell.cn 的链接。可以说,他是我在很多领域的启蒙老师。

一些教程只是教会你用某一个工具,但是陈皓写的文章,是想教会你这个东西设计的逻辑,使用的核心思想是什么,举一反三,和其他优秀的设计有什么相通之处。

由于国内特殊的工作环境,在技术方面做到他这个级别的前辈很少了,又几十年如一日在网络上耐心地分享自己的知识的人就更少了。陈皓就像一盏明灯,不知道照亮了多少人的路。

陈皓这样光在网络上就有十几万的粉丝的“网红”,却很少见到有负面评价,今天在网络上大家都是清一色的表达自己的悲痛和思念,是因为他帮助过的人实在太多了,即使网络上跟别人有争论,也都是有理有据,讲逻辑,很有耐心。

我和他有过短暂的交流,沟通的过程非常舒服,他是一个很谦逊的人,即使对于我这样的后辈,也称可以“学到东西”。

由衷地感谢这位素未谋面的老师,愿在天堂安息。

让我们永远记住这位伟大的人。

 

去南半球的海岛

旅行的轨迹一直在往南走,不断突破“去过的最南边”,从上海,到杭州,厦门,广州,新加坡,最近去了巴厘岛,第一次约过了赤道。

我们从新加坡出发,先去雅加达转机,然后再去巴厘岛。

巴厘岛的时间让我怀疑人生:我们的轨迹一直向东飞,到雅加达,时间变了 -1,然后从雅加达到了巴厘岛,时间竟然又 +1,明明一直是向东飞,为啥会先减后加呢?

最后发现奇怪的并不是印度尼西亚,原来是新加坡,这个地方本来是 UTC+7, 现实却是 UTC+8, 导致我们只要出了新加坡就会先变成正常的 UTC +7, 然后向东,再经历一次正常的时区变更,变成 UTC +8.

巴厘岛所在的城市叫登巴萨,所以机票上是登巴萨而不是巴厘岛。因为巴厘岛的建筑限高,所以机场建在哪里都无所谓……于是就建在了市区,这是我去过的唯一一个在市区的机场。机场是填海造的,所以下降的时候有一种要掉进大海的感觉。

来之前还担心天气,天气预报说将来一周几乎天天有雨。来了之后发现这种担心几乎是多余的,雨很少下,即使下,也不会连续下一整天,太阳很快就出来了,不像新加坡一样,经常淅淅沥沥下好几天。也可能是刚过了雨季有关,巴厘岛的雨季是10月份到3月份。

Day 1

第一天到了酒店,然后在顶楼酒吧喝了一杯 welcome drink. 酒保很热情,跟我们介绍了很多印尼的文化。

酒店顶楼酒吧的景色很不错
welcome drink

之后去了一个比较近的海滩,有一些失望,沙滩上的垃圾也太多了…… 然后去了一家酒吧吃饭,吃的……汉堡。

最近刚去了普吉岛(咦?游记呢?),来巴厘岛之前以为海岛会大同小异,来了之后发现很不一样。普吉岛的海滩很干净,傍晚就看到很多工人在清理海滩。巴厘岛的公共海滩堆满了垃圾,大部分的度假村酒店都有自己的私有海滩。(感觉是不是政府不太行)

食物上也是,普吉岛到处都是泰国菜,我们旅行的几天,每天都随机走进一家泰国餐厅,完全没有翻车,怎么点都好吃。巴厘岛就不一样了,居然是西餐披萨汉堡居多。(后面就知道为什么了……)

Day 2

第二天直接报了个团出海了。上午去浮潜,下午去几个景点打卡。行程主要是在佩尼达岛附近。

发生了一个很有意思的事情,我这天穿着一双亚瑟士的鞋,居然有个英国小哥过来搭讪问我鞋子从哪里买的,我就告诉了他型号。原以为是开场白有其他话题,没想到他接着跟他爸爸在网上查了一下,还跟我对了一下英镑新币价格。第一次感受到一种“小红书集美要链接”的感觉。

上船,天气很好
海水是绿色的

打卡景点1,打卡就真的是卡个卡,来了,拍张照片,然后就走了……

著名景点1 所谓的精灵沙滩

第二个地方,其实挺危险的,拍照的地方向前一步的话就掉下去了,非常非常高。

景点2 好像是叫恶魔眼泪

第三个景点相比之下就有些垃跨了,我拍出来是这样的:

小红书用户拍出来是这样的:

晚上又坐船回到了巴厘岛,从 Trip Advisor 上找了一家评分最高的印尼菜餐厅。

印尼人民无比热情和友好,遇到一些服务行业的人员,比如酒店服务员,司机,餐厅的服务员,他们都会友好的打招呼,即使我打着另一家酒店的雨伞,明显不是同一家酒店的,在路上他们也会友好地问候你。

到了这家餐厅,发现这家餐厅是在一家酒店里,点菜的时候,服务员问我们房间号码,我们说我们不是住在这里的,单纯来这里吃饭。服务员听到之后感觉都两眼放光了,说谢谢我们,然后,我就体验到了几乎是这辈子吃过的服务最好的一顿饭!

这几乎是五星级酒店的服务,人均才150人民币左右。但是相对当地物价来说,可能是非常贵的水平了。服务员每次上菜都给我们介绍每一道菜的原料,制作方法,在印尼传统里面的历史。有一个菜有 10 种酱,她耐心地给我们花了有 10 几分钟给我们介绍每一种酱是什么。而且一共问了不下10遍“食物怎么样”,出于礼貌我们每次都说很好。很多手法也很专业,比如盘子都是温的,面包用篮子盛着。环境也很棒,酒店很漂亮。只有一点不好:就是不好吃。

餐厅环境
第一个菜,有10种酱和薯片,加开胃菜,和两个饮料
本地食物

可惜我的胃实在欣赏不了印尼食物,也没吃下多少。最后也掩盖不住了,只好说 “it’s good but not our taste”, 热情的服务员漏出了无法掩饰的失望,笑容逐渐消失…… 我拿了账单给了小费就赶紧溜了…… 并且决定接下里的行程就以西餐为主好了。

他们还让我填了 Feedback 卡片,后来发现很多餐厅吃完了都让我写这个,酒店也让我写。看来这里的人民很注重服务质量啊。

Day 3

包了个车,继续去各种景点打卡。

第一个景点:海神庙。

然后去了一个瀑布,说实话,感觉一般,和杭州九溪差不多……

我拍的跟小红书差距很大

最后是一个梯田,像是一个游乐园的综合体,有网红秋千(很高的那种),有梯田,有泳池 party。

门口放着咖啡豆,现做现卖
非常高的秋千
飞起来是这样子

还有一些雕像,比如这种:

这种:

还有这种:

我叫它“祖马”

晚上回酒店叫了个必胜客外卖,就结束了。

Day 4

换了一家酒店(其实是同一家,都是 IHG 开的),是一个度假村,在酒店躺了一天。

躺平的一天

然后下午去了牛角,吃了顿自助。只要 40 新币就可以随便吃,还可以看日落(记住这个日落,它还会出现,以不一样的形式)。新加坡的牛角要 90 新币。

牛角的环境

Day 5

本来这天没什么计划,后来又包了车去南边玩。(我很后悔)

清澈的海水

第一站是乌鲁瓦图,悬崖,大海,挺好看的。

高高的悬崖

下面这图叫情人崖:

进来之前,司机提醒我们注意眼镜,里面有猴子。庙里也有各种语言的牌子提醒游客看好自己的财物,注意猴子。我觉得挺搞笑的,太夸张了。

……下图是我的眼镜,和抢走它的猴子:

不知道为什么猴子想吃了我的眼镜。后来来了一个女人,给猴子几包豆子把我的眼镜拿回来了(虽然完全不能用了),然后伸手跟我们要钱……

现在感觉新加坡不让喂猴子(喂猴子违法)很有道理,喂多了它们就会抢你的东西,徒步的时候看到的猴子都比较老实。

最后一站去金巴兰海滩看日落,海滩日落……是不是很美?

到了之后发现是这样的:

堆满垃圾的海滩

如果不拍到垃圾的话还可以。

远处海天相接的地方正好有一只小船

第二天又在酒店泡了一下就回去工作了。