Golang 程序 crash 的时候自动 core dump

前段时间遇到一个问题,程序莫名其妙 crash 了,stack 也没看出什么端倪来。今天改了一个参数,让 golang 程序在崩溃的时候 core dump。

其实核心就是加一个环境变量就可以了,GOTRACEBACK=1. 但是还有一些其他跟系统相关的问题,这篇文章简单记录一下。

Golang 1.6 之后,这个环境的变量可选值有了一些变化,新的值如下:

  • GOTRACEBACK=none will suppress all tracebacks, you only get the panic message.
  • GOTRACEBACK=single is the new default behaviour that prints only the goroutine believed to have caused the panic.
  • GOTRACEBACK=all causes stack traces for all goroutines to be shown, but stack frames related to the runtime are suppressed.
  • GOTRACEBACK=system is the same as the previous value, but frames related to the runtime are also shown, this will reveal goroutines started by the runtime itself.
  • GOTRACEBACK=crash is unchanged from Go 1.5.

一些要注意的点:

首先,介绍下除了这个 GOTRACEBACK 参数,还有其他一些很有用的能控制 golang 运行时的环境变量,这篇文章总结的很好。

然后这个参数在 macOS 上是无效的,就不要在 MAC 上白费力气了。

Linux 上还受到 ulimit 的限制。可以用 ulimit -c 查看对 Core dump 的大小限制。如果是 0 是 dump 不出来了,也不建议设置成 ulimited。我改成了 50G。如果程序使用 systemd 启动的,可以设置 service unit 文件中的 LimitCORE= 参数,效果等同。

产生的 core dump 存放在哪里了呢?

可以通过这里查看:

这里定义了 core dump 文件的命名方式。

但是在 ubuntu 里面,会看到这样的输出:

意思是通过 pipe 定向到了 apport. apport 是 ubuntu 发行版选择使用的 core dump 管理服务。

默认情况下,用户程序是不会有 core dump 的。然后我们有两个解决办法:

  1. 关闭 apport,使用系统的 core dump 直接写在磁盘上
  2. 配置 apport,让它也写用户的 core dump 文件

第一种方法比较简单,直接修改上文中的 /proc/sys/kernel/core_pattern 文件即可:

注意这里有一个小小的问题要注意一下:这个配置是全局的,只有 root 账户才能编辑。如果在普通用户下执行 sudo echo "kernel.core_pattern=/tmp/%e.%t.%p.%s.core" > /proc/sys/kernel/core_pattern 是不行的,因为在这行命令中,echo 是用 sudo 执行的,但是重定向确实 shell (bash) 来完成的,重定向,即真正的写入工作,实际上没有在 root 下,所以你会得到错误:Permission denied, 或者 Destination /proc/sys/kernel not writable. 解决办法是用这个命令:sudo bash -c 'echo "kernel.core_pattern=/tmp/%e.%t.%p.%s.core" > /proc/sys/kernel/core_pattern'.

然后可以 disable apport:

 

第二种方法,首先要确保 apport 在运行。可以通过 systemctl status apport 查看。也可以看下 apport 日志:

触发一次 core dump,会看到:

意思是 core dump 不是来自于 ubuntu 打包的软件,忽略。

配置方法是,修改 ~/.config/apport/settings (如果没有,手动创建)文件,写入:

再触发一次 core dump,这次日志里就会有写入的信息了:

还要注意的是,这个文件不是 core dump 文件,而是 apport 打包的 debug 文件,可以使用 apport-unpack 解包:

解包出来的 CoreDump 就可以用 gdb 分析了。其他的文件记录了一些系统相关的信息。(感觉是 Ubuntu 用来让用户报告 bug 的)

 

最后,如果进程的 workdir 下没有生成 core dump 的话,可以看下是不是在 /var/lib/systemd/coredump/,网上说用 systemd 的系统会存放在这里,不过我没遇到。

 

用油猴制作一个 Jenkins 日志窗口

上次介绍了油猴脚本的基本使用方法,这篇文章简单记录一下今天用油猴提高 Quality of Life 的一个脚本。

先描述一下我要解决的问题:

我们平时很多线上操作是通过 Jenkins 执行的,要在大规模的机器上运行任务,有时候,这些任务要运行很长时间。我一般会关注着这些自动化操作,一般进行其他的工作。我想让日志一直出现在屏幕上,但是又不影响我其他工作。

解决方法是,我在 Jenkins 页面上加了一个按钮,通过这个按钮可以打开一个最小化的窗口,效果如下:

这是新添加的按钮,点击这里,可以弹出一个日志窗口

 

弹出来的日志窗口位于左上角,没有菜单栏,没有书签栏,也没有 Extentions,基本上所有的空间都用来显示日志了。

这样做操作的时候,有实时的日志一直在滚动着,放心多了。


源代码如下:

直接粘贴到自己的油猴就能用。

核心逻辑是,如果当前页面是 Jenkins 原生的 URL(Params 没有 view_window=minimized),就在页面上插入一个链接 <a>,目标是 当前的 URL + 参数view_window=minimized 。通过代码,设置打开这个 URL 的时候关闭 menubar,toolbar,以及设置好窗口大小,位置等等。 打开一个基本上只有日志的窗口,一致放在屏幕旁边。可以用 Mac 上的 Rectangle 软件,将这个窗口固定在 Top。从这个窗口打开链接(按住 Cmd),还是用 Chrome 正常的窗口打开的,很方便。

如果监测到 URL 中有 view_window=minimized  这个参数,就删除页面内 sidebar,footbar 等,让所有的空间都用来展示日志。

操作的时候最需要的按钮是停止键,但是默认的 Jenkins 把这个按钮放到了页面的最上面,这样在底部看滚动日志,如果需要停止的话,还要拖到页面顶部去找按钮,太慢了。我用 JQuery 把它放到日志滚动下面了。

本来想做成一个 Jenkins 插件直接把公司的 Jenkins 给改了,但是看了下 Jenkins 发布插件还是挺复杂的,还得写点 Java 和 XML,可能要花上一两天。所以就直接用油猴实现了,花了半小时。

 

MTU 和 UDP (以及基于 UDP 的协议)

上次在写了之后《有关 MTU 和 MSS 的一切》之后,最近又有了一个问题,苦苦思索了一个周,终于得到了答案。现在一想问题的答案简单而有效,但是中午吃饭的时候和几个同事讨论,我们都没有很快想到这个,所以还是觉得值得记录一下。

首先我要花一些篇幅来描述一下这个问题。因为和同事交流的时候发现大家会以为我在问另一个问题。

我们知道如果 IP 包的 size 整个大于 MTU 的话,那么 3 层就会负责 fragmentation,即讲一个大包拆成多个小包单独发送。那么我的问题是,三层在将数据从自己这边传给下一个 hop 的时候,只知道自己的 MTU,而不知道对方的 MTU,那么如果对方的 MTU 小于自己的时候,怎么拆包发给它呢?

如何将数据发给 MTU 比自己小的另一侧?

在之前的文章中,我们知道,TCP 因为是有连接的协议,连接在建立的时候,就有 MSS 的协商,如果中间设备的 MTU 比较小,就会 MSS clamping,这样就能保证两端都不会发送超过 MTU 的数据。

但是对于面向无连接的协议,比如 UDP,怎么处理这个问题呢?

首先,不处理肯定是不行的,因为理论上收到了 MTU 比自己能接受的 MTU 还要大的包,就会被丢弃。UDP 有没有重传机制,那么就一直发,一直丢,发送端也不知道发生了什么事情。

然后想到了了 PMTUD,那篇文章也提到过。但是 PMTUD 的目的是:避免进行 IP fragmentation,先通过 PMTUD 得知链路上的 MTU 是多少,然后在后续的通讯中保证不发送大于 MTU 大小的包。这不是我问的问题,我的问题是,如果对方的 MTU 比较小,这时候 Don’t Fragmentation 又没有设置,三层是怎么拆包的。并且,向 UDP 这种协议,有一些场景也不现实,难道 DNS 一次请求之前我都要发送多个包去探测 MTU 吗?效率也太低了。我还实际去抓了包,确实是没有 PMTUD 的。

下一个得到的答案是:对方会把包丢弃,然后发送一个 ICMP 回来,Type=3 (Destination Unreachable) and code=4, packet too big and DF is set. 表示我收到一个包,大的我无法转发,但是这个包又设置了 DF,让我不要拆包,没办法,只好丢了,你要知道。这个答案我也不是很满意,因为按照语义,这个 Code=4 的意思是 DF is set 我才丢的。而我想问的是,DF 没有 set,你随便拆,你要怎么知道对方的 MTU 然后拆包呢?

这些答案是我和同事们讨论过的,好像都可以解决问题,但是又好像都不太合理。

其中搜索了一些资料,感觉都没有直接回答这个问题,大部分文章提到这部分的时候好像都直接略过了,只是介绍如何根据自己这一端的 MTU 进行 fragmentation。有一些感觉比较离谱,比如这里,说路由器知道对方的 MTU。我就好奇了,它怎么知道的?IP 协议没有任何机制协商 MTU 呀。

我还自己做了一个实验,进行验证。搞了两个虚拟机,A MTU=1000,B MTU=500,然后用 A 去 ping B,size=800,结果发现 A 到 B 没有 fragmentation,B 到 A 有 fragmentation。但是两边都能收到包(我猜这个是实验环境的问题,因为两个 VM 中间的网络比较简单,所以网卡都能处理这种不合理的包?)至少,我们证明了在 IP 这一层,它不会去关心对方的 MTU 是什么,只会根据自己这边的 MTU 去 fragmentation.

某天有同事从深圳来新加坡出差,我们一起吃饭,又提起这个话题,他直接说:UDP 不管这个问题呀!

对哦,这就是我为什么在 UDP 相关的资料中都没发现和 MTU 有关的东西。这个协议太简单了,不处理这个问题。如果你要基于 UDP 实现一个协议,就要自己处理超过 MTU 的问题。

这是我基于自己读了一些 RFC 之后认为的答案,如果有错误,欢迎指出。

比如:

DNS 协议规定:RFC 1035 DNS 响应不能超过 512 bytes(UDP message),如果超过 512 bytes,在 512 bytes 之后的内容就会被截断。512 bytes 的内容是安全的吗?(链路上所有的节点都能正常接受这个 size?),我们来算一下:2 层 Ethernet 最小的 Frame 是 576 bytes, IP header 20 bytes + IP option 0-40 bytes, UDP header 8 bytes, 所以在 IP option =0 的时候,512 bytes 的 UDP message 最终的 Ethernet Frame 是: 512 bytes + 20 bytes + 0 + 8 = 540 bytes, 小于 576 bytes。是安全的。IP option 在小于 576 – 540 = 36 bytes 的时候是安全的,可以说,在绝大部分情况,这个大小是安全的。

这是 DNS 对 MTU 问题的解决办法:我只发送全世界最小的二层包,总没问题了吧?

与之类似解决方法的是 TFTP 协议(RFC 1350),默认是 512 bytes,但是可以配置。不过用户要自己对配置负责,配置不当就直接丢包

KCP 也是有一个默认值 1400 bytes,但是支持通过函数 ikcp_setmtu 来设置。因为本质上这个是 “Pure algorithm protocol”,你可以有自己的 MTU 探测实现。

最后是 QUIC,这个最具有代表性。它的处理方法是:

  1. QUIC 的实现应该(RFC 用的是 SHOULD)使用 PMTUD,并且应该记录每一个 source ip + dest ip 的 MTU
  2. 但是如果没有 PMTUD 的话,也可以认为 MTU=1280,协议设置 max_udp_payload_size = 1200 bytes,如此,按照上面的算法的话,IPv4 的 header 最多可以有 52 bytes,IPv6 的 header 可以有 32 bytes,正常情况下也够用
  3. 如果链路上连 1280 的 PDU 都支持不了,QUIC 就会这个 UDP 无法使用(和端口连不上等同),然后会 fallback 到 TCP

对于3,还有一个问题,就是 QUIC 如何知道 1280 的 MTU 能不能传呢?我发现了这个协议一个很神奇的设置,就是它的每一个 IP 包大小都是一样的,比如 MTU=1280,那么发送的每一个二层包都是 1280 bytes,不够的就 padding 到 1280,如果传不过去,那么握手包也传不过去,一开始就被丢弃了。

QUIC 所有的包都一样大

很绝妙,不过我觉得有一点要注意的是,中间 overlay 协议在设计的时候可能要注意这一点:比如 Overlay 要在中间插入 100 bytes 的数据,MTU 设置为 1400,那么就不应该接收 1450 的包。即,即使有时候没有 100 bytes 的数据要插入的时候,也应该 padding 100 bytes 进去。否则的话,像 QUIC 这种协议,就可能握手阶段没问题,让它过去了,协议认为 MTU=1450,但是后面可能会频繁丢包。

最后,重申一下我对 QUIC 不是很了解,只是浅读了一些资料。如果读者发现本文错误,欢迎指出。

 

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

现在公司多多少少都会用一些开源的软件,我在工作中也部署和维护了很多开源软件。这篇文章就讲讲维护这些软件的一些经验。我主要想说的,是那些需要部署的服务,比如 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
    • 高反药
    • 创可贴
  • 防晒霜(物理防晒也可以,冰袖,遮阳帽)
  • 登山杖(主要是冲顶,踩着火山灰如果没有登山杖很难走)
  • 登山鞋(必须)

我能走下来吗?

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

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