听说你没法在JRE中使用arthas?不,你可以

本文是《容器中的Java》系列文章之 5/n ,欢迎关注后续连载 :) 。

之前经常遇到的问题是,排查问题需要挂arthas,但客户用的是JRE,没法挂载arthas。就只能让客户更换成JDK,再重新部署、排查问题。

很多有用的现场,在这个过程中也会丢失,最终导致问题排查效率降低。于是就探索了下如何在JRE环境中,使用artahs。

复现问题

如果一个Bug 没法复现,研发大概率是无法修复的。 —— by 网友

我们写一个Java例子和Dockerfile:

./src/main/java/Main.java
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("hello!");
Thread.sleep(30 * 1000);
}
}
}
./Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM openjdk:8-jdk-alpine as builder
COPY ./ /app
WORKDIR /app/src/main/java/
# 编译java文件
RUN javac Main.java

# 运行时容器使用JRE
FROM openjdk:8-jre-alpine
RUN apk add bash curl busybox-extras
WORKDIR /app/src/main/java/
# 将arthas copy 到容器中
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
COPY --from=builder /app/src/main/java/ /app/src/main/java/
CMD ["java", "Main"]

构建并正常启动应用,并尝试用arthas attach,此处为了便于了解原理,我们使用as.sh来执行:

阅读更多

为什么在容器中1号进程挂不上arthas?

本文是《容器中的Java》系列文章之 4/n ,欢迎关注后续连载 :) 。

最近在容器环境中,发现在Java进程是1号进程的情况下,无法使用arthas,提示AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread。具体操作和报错如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.5.6
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 1 com.alibabacloud.mse.demo.ZuulApplication
1
[INFO] arthas home: /home/admin/.opt/ArmsAgent/arthas
[INFO] Try to attach process 1
[ERROR] Start arthas failed, exception stack trace:
com.sun.tools.attach.AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:86)
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:78)
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:250)
at com.taobao.arthas.core.Arthas.attachAgent(Arthas.java:117)
at com.taobao.arthas.core.Arthas.<init>(Arthas.java:27)
at com.taobao.arthas.core.Arthas.main(Arthas.java:166)
[INFO] Attach process 1 success.

之前也遇到过,总是调整了下镜像,让Java进程不是1号进程就可以了。但这个不是长久之计,还是要抽时间看下这个问题。

复现问题

我们创建如下项目,来复现这个问题:

./src/main/java/Main.java
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("hello!");
Thread.sleep(30 * 1000);
}
}
}
./Dockerfile
1
2
3
4
5
FROM openjdk:8u212-jdk-alpine
COPY ./ /app
WORKDIR /app/src/main/java/
RUN javac Main.java
CMD ["java", "Main"]

然后正常启动应用,并尝试用arthas,或者jstack:

阅读更多

如何通过Kubernetes事件来报告错误

组内有维护一个Kubernetes Webhook,可以拦截pod的创建请求,并做一些修改(比如添加环境变量、添加init-container等)。

业务逻辑本身很简单,但是如果过程中产生错误,就很难处理。要不直接阻止pod创建,那么就有可能导致应用无法启动。要么忽略业务逻辑,那么就会导致静默失败,谁也不知道这儿出现了一个错误。

于是,朴素的想法就是接入告警系统,但这会导致当前组件和具体的告警系统耦合起来。

在Kubernetes中,有Event机制,可以做到把一些事件,比如警告、错误等信息记录下来,就比较适合这个场景。

什么是Kubernetes中的事件/Event?

事件(Event)是 Kubernetes 中众多资源对象中的一员,通常用来记录集群内发生的状态变更,大到集群节点异常,小到 Pod 启动、调度成功等等。

比如我们Describe一个pod,就能看到这个pod对应的事件:

kubectl describe pod sc-b-68867c5dcb-sf9hn

可以看到,从调度、到启动、再到这个pod最终拉取镜像失败,都会通过event的方式记录下来。

阅读更多

containerd/nerdctl - 一个开源、免费的Docker的替代品

2021年8月31日,Docker公司更改了收费策略,简而言之就是对个人和小公司不收费,对大公司收费。

那么,是时候开始试一下 containerd + lima这个开源、免费的Docker替代品。

什么是containerd?

containerd是一个标准化的容器运行时,可以被其他系统很方便的集成。正是由于这个原因,containerd也成为了Kubernetes的默认容器运行时。

但是在containerd的目标聚焦在和其他系统集成,所以它的默认命令行工具(crictl)也不是很好用,和docker也不兼容。

后来,nttlabs贡献了一个名为nerdctl的containerd客户端,可以兼容docker命令行工具。于是我们就可以使用nerdctl来作为docker的替代品了。

nerdctl不仅与docker兼容,而且还支持了更多的功能:

  • 支持containerd的命名空间查看,nerdctl不仅可以管理Docker容器,也可以直接管理本地的的Kubernetes pod
  • 支持将Docker Image Manifest镜像转换为OCI镜像、estargz镜像
  • 支持OCIcrypt(镜像加密)

什么是lima?

containerd要在macOS上使用,需要安装虚拟机、然后在虚拟机配置containerd,这个过程很浪费时间。

阅读更多

JVM如何获取当前容器的资源限制

本文是《容器中的Java》系列文章之 1/n ,欢迎关注后续连载 :) 。

最近同事说到Java的 ParallelGCThreads 参数,我翻了下jdk8的代码,发现 ParallelGCThreads 的参数默认值如下:

  • 如果cpu核心数目少于等于8,则GC线程数量和CPU数一致
  • 如果cpu核心数大于8,则前8个核,每个核心对应一个GC线;其他核,每8个核对应5个GC线程

但是被提醒,发现即使在分配4核的容器上,GC线程数也为38。然后就想到应该和容器的资源限制有关——jvm可能无法觉察到当前容器的资源限制。

翻了下代码,发现最新版本的java是能感知容器的资源限制的,就按照jdk版本再翻了下代码:

线上的jdk(jdk8u144)

写一个sleep 1000s的程序,用于查看JVM的线程数量:

1
./jdk1.8.0_144/bin/java -XX:+UseG1GC -XX:+ParallelRefProcEnabled Main 

然后查看GC线程数目:

1
2
$ jstack $pid | grep 'Parallel GC Threads' | wc -l
38
阅读更多