Openwrt环境下某2ray `too many open files`问题解决

too many open files大部分情况下是由于配置错误引起的。如果配置没有问题,可以在某2ray的启动脚本里加上这一句:

1
2
ulimit -SHn 65535
/usr/bin/2ray -config config.json

启动后可以用这个命令来看是否生效:

1
cat /proc/{2AY进程id}/limits

参考:

Kubernetes Create User

Create User

kubectl apply -f eks-admin-service-account.yaml

1
2
3
4
5
apiVersion: v1
kind: ServiceAccount
metadata:
name: eks-admin
namespace: kube-system

kubectl apply -f eks-admin-cluster-role-binding.yaml

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: eks-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: eks-admin
namespace: kube-system

Get certificate

1
kubectl get secret default-token-cvn2d -o jsonpath="{['data']['ca\.crt']}" | base64 --decode

Get Token

1
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep eks-admin | awk '{print $1}')

Test If the role works

Setup credential

1
2
3
4
kubectl config set-cluster kubernetes --certificate-authority=ca.crt --server=$K8S_SERVER_URL
kubectl config set-credentials $K8S_USERNAME --token=$K8S_USER_TOKEN
kubectl config set-context aws --cluster=kubernetes --namespace=$K8S_NAMESPACE --user=$K8S_USERNAME
kubectl config use-context aws --user=$K8S_USERNAME

Test command

1
kubectl get pod

Ref

Openwrt下dnsmasq配置

配置文件

dnsmasq.conf

1
2
3
4
5
6
7
conf-dir=/root/dnsmasq.d/
//设置DNS缓存时间
min-cache-ttl=3600
//缓存数量
cache-size=1024
//重新加载后清空缓存
clear-on-reload

配置说明

转发域名解析

解析海外域名需要将dns解析请求转发到上游的无污染的dns服务器,配置文件 /root/dnsmas.d/not_china.conf

1
2
server=/google.com/127.0.0.1#5353
...

国内域名直接用国内的dns服务器,china.conf

1
2
server=/baidu.com/221.6.4.66
...

缓存域名解析

海外域名的解析时间一般会比较长,频繁的去请求上游DNS服务器既浪费时间又无意义,可以通过设置dnsmasq缓存来加快解析,减少请求上游DNS服务器的频率。

1
2
3
4
//设置DNS缓存时间
min-cache-ttl=3600
//缓存数量,最多10000
cache-size=9999

如果你不知道缓存数量应该设置为多少,可以通过下面命令查看dnsmasq的域名请求数作为参考:

killall -s USR1 dnsmasq

关于 no-resolv 配置

在不打开no-resolv的情况下,dnsmasq会使用ISP提供的dns服务器作为默认的服务器,比如 xx.com域名既不在 not_china.conf又不在china.conf中,dnsmasq就会用ISP的dns服务器来解析这个域名。

如果打开了no-resolv,同时又不设置resolv-file的话,dnsmasq就会找不到默认的dns服务器来解析xx.com域名,如果你的代理服务器正好属于这类域名,将导致你无法连接到你的服务器。

Openwrt下无法访问medium.com的解决办法

家里路由器刷了Openwrt系统,在访问medium.com ,nytimes.com等网站时,会时不时出现连接重置错误,今天花时间研究了下,终于解决了这个困扰我很久的问题,在此记录下。

问题描述

  • Chrome访问这些网站是会提示ERR_CONNECTION_RESET错误,反复刷新多次后又能打开网站。
  • wget -v命令显示返回了ipv6地址并且会提示无法创建SSL连接错误。

问题原因

问题就出在ipv6上,openwrt默认会打开ipv6地址分配,这会导致电脑被分配到了一个ipv6地址,而chrome,safari在本机有ipv6的情况下,会优先访问这些网站的ipv6的地址,而路由器上的iptables只会对ipv4包进行转发,故访问这些网站的ipv6地址是无法被代理的。

解决办法

方法1

关闭路由器ipv6地址分配,这样在没有ipv6地址的情况下会访问这些网站的ipv4地址,这样就可以被代理到了。

方法2:

添加ip6tables规则,对ipv6也进行转发(没有尝试过)

参考

Bitbucket pipeline传递文件内容变量

Bitbucket pipeline可以通过Repository variables来传递变量,但是如果变量包含一些特殊字符比如换行符,bitbucket就不能很好的处理,对于这种情况我们可以将变量用base64编码一下,在pipeline中再解码就可以解决这问题了。

1
2
3
4
cat file.txt | base64

// in pipeline file
echo ${YOUR_ENV} | base64 -d > file.txt

Java BufferedImage OutOfMemoryError

最近遇到了BufferedImage OutOfMemoryError的问题,在此记录一下。

事情的起因是项目中有个功能需要将多张图片合并为一张,流程如下:

  1. 有三个图片文件,File A,File B,File C
  2. 通过ImageIO.read(inputStream) 将ABC转换成BufferedImage类型
  3. 通过java.awt.image.BufferedImage#getWidth()获取这三张图片中的最大宽度maxWidth
  4. 计算另外两张图片等比例拉伸至maxWidth后的高度,将这三张图片的高度相加得到maxHeight
  5. 创建一张maxWidth * maxHeight的图片
  6. 通过画布将三张图片写入到这张图片里

主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
...
val images = mutableListOf<BufferedImage>()
...
images.add(ImageIO.read(inputStream))
...
joinImages(*images.toTypedArray())
...

fun joinImages(vararg imgs: BufferedImage): BufferedImage {
val offset = 20

val aggregateWidth = imgs.maxBy { it.width }!!.width
val aggregateHeight = imgs.sumBy {
(it.height * aggregateWidth) / it.width
} + (imgs.size - 1) * offset

val newImage = BufferedImage(aggregateWidth, aggregateHeight, BufferedImage.TYPE_INT_ARGB)
val g2 = newImage.createGraphics()
val oldColor = g2.color

g2.paint = Color.white
g2.fillRect(0, 0, aggregateWidth, aggregateHeight)
g2.color = oldColor

var y = 0

imgs.forEach {
val height = (aggregateWidth * it.height) / it.width
val scaled = it.getScaledInstance(aggregateWidth, height, Image.SCALE_DEFAULT)

g2.drawImage(toBufferedImage(scaled), null, 0, y)
y += height + offset
}
g2.dispose()

return newImage
}

运行过程中发现第417行代码时不时会抛出OutOfMemoryError错误,研究了下发现有两个问题:

  1. 对于jpeg格式的图片,java将其载入到内存时是不会对其进行压缩的,一个像素会占用3个字节的内存,如果图片的尺寸比较大,会占用非常大的内存,比如这张图片,实际文件大小为84kb,载入到内存里后的大小为11mb:
1
2
3
4
5
6
val file = File("/Downloads/fff.jpeg")
val image = ImageIO.read(FileInputStream(file))

println(image.width) //2000
println(image.height) //2000
println(ObjectSizeCalculator.getObjectSize(image)) //12000928 -> 11mb
  1. 合并图片前代码将这三张图片一次性全部加载到内存里,这也会占用比较大的内存。

对于问题1,目前只能规避这问题,加载图片前会预先判断下该图片会占用多少内存,对于会超出内存使用的jpeg图片不予合并。同时在代码第14行,我们对合并后的最大宽度进行限制,避免合并后的图片尺寸过大,占用的内存超出限制。

对于问题2,这三张图片可以按序加载,不用一次性全部加载到内存里,在需要合并时才加载到内存里,同时可以改进获取图片尺寸的代码,不用将图片加载到内存后再获取尺寸。

最终代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
fun getImageSize(file: File): Dimension {
ImageIO.createImageInputStream(file).use { `in` ->
val readers = ImageIO.getImageReaders(`in`)
if (readers.hasNext()) {
val reader = readers.next()
try {
reader.input = `in`
return Dimension(reader.getWidth(0), reader.getHeight(0))
} finally {
reader.dispose()
}
}
}

return Dimension(0, 0)
}


fun joinImages(vararg files: File): File {
...
val offset = 20
val dimensions = files.map { getImageSize(it) }
val aggregateWidth = min(dimensions.maxBy { it.width }!!.width, 800)
val aggregateHeight = dimensions.sumBy { (it.height * aggregateWidth) / it.width } + (files.size - 1) * offset
val newImage = try {
BufferedImage(aggregateWidth, aggregateHeight, BufferedImage.TYPE_INT_ARGB)
} catch (e: OutOfMemoryError) {
...
throw e
}
val g2 = newImage.createGraphics()
val oldColor = g2.color

g2.paint = Color.white
g2.fillRect(0, 0, aggregateWidth, aggregateHeight)
g2.color = oldColor

var y = 0

files.forEach {
var image = try {
ImageIO.read(it)
} catch (e: OutOfMemoryError) {
...
throw e
}
val height = (aggregateWidth * image.height) / image.width
val scaled = image.getScaledInstance(aggregateWidth, height, Image.SCALE_DEFAULT)

g2.drawImage(toBufferedImage(scaled), null, 0, y)

image.flush()
image = null

y += height + offset
}
g2.dispose()

val joinImageFile = File.createTempFile(UUID.randomUUID().toString(), ".png")

ImageIO.write(newImage, "png", joinImageFile)

return joinImageFile
}

按上述代码修改后,再也没有发生OutOfMemoryError。对于问题1,目前发现apache commons-imaging似乎可以解决这问题,有时间去尝试下,到时候再来更新本文。

参考:

https://coderanch.com/t/416485/java/Java-BufferedImage-OutOfMemoryError

Kubernetes环境配置JVM内存

我们知道JVM在docker容器环境中是无法正确检测到可用内存的,最近正好遇到了一个与之相关的问题,在此记录一下。

遇到问题的项目技术栈为JDK 8 + Spring Boot + Tomcat,部署在docker环境。项目运行过程中出现了java.lang.OutOfMemoryError: Java heap space异常,当时项目的部署文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: api-deployment
labels:
app: api
spec:
serviceName: api-app
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
terminationGracePeriodSeconds: 30
containers:
- image: ...
imagePullPolicy: "Always"
name: api
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 300
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
securityContext:
capabilities:
add:
- SYS_PTRACE
envFrom:
- secretRef:
name: secret

问题应该出在k8s内存设置与JVM的配置这边,网上查询资料后发现tomcat可以通过CATALINA_OPTS环境变量来设置JVM参数,UseCGroupMemoryLimitForHeap 可以让JVM自动检测容器的可用内存,MaxRAMFraction 为容器内存和堆内存的比例,比如容器内存为2G,MaxRAMFraction为2,则最大堆内存为2G/2=1G,这里将MaxRAMFraction设置为2比较安全,设置了这两个参数后,JVM就能通过检测容器的内存来自动调整堆内存大小,不用再显示设置堆内存了。

更新后的配置文件里加了如下代码:

1
2
3
4
5
6
7
...
env:
- name: CATALINA_OPTS
value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2"
resources:
requests:
memory: "512Mi"

项目运行一段时间后发现问题依旧,研究了下UseCGroupMemoryLimitForHeap参数,发现它是通过读取系统/sys/fs/cgroup/memory/memory.limit_in_bytes文件来检测内存的,登录到容器里查看了下该文件,发现里面是一个很大的值:9223372036854771712,等于没有内存限制,查了下资料发现这个字段是通过k8s文件中的resources->limits的这个属性来配置的,更新文件,加了如下代码:

1
2
limits:
memory: "2048Mi"

观察一段时间后内存就没再溢出,最终完整配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: api-deployment
labels:
app: api
spec:
serviceName: api-app
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
terminationGracePeriodSeconds: 30
containers:
- image: ...
imagePullPolicy: "Always"
name: api
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 300
periodSeconds: 5
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 60
periodSeconds: 5
securityContext:
capabilities:
add:
- SYS_PTRACE
env:
- name: CATALINA_OPTS
value: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2"
envFrom:
- secretRef:
name: secret
resources:
requests:
memory: "512Mi"
limits:
memory: "2048Mi"

参考

Google Cloud Pub Sub的一些研究

  • 多个客户端可以使用一个subscription,每个客户端只会收到一部分的消息:

Multiple subscribers can make pull calls to the same “shared” subscription. Each subscriber will receive a subset of the messages
出处:https://cloud.google.com/pubsub/docs/subscriber#push-subscription

  • 对于一些处理比较耗时的消息,客户端有后台任务会自动更新ack
    deadline,详情可见extendDeadlines方法。
  • 关于Message retention duration

By default, a message that cannot be delivered within the maximum retention time of 7 days is deleted and is no longer accessible. This typically happens when subscribers do not keep up with the flow of messages. Note that you can configure message retention duration (the range is from 10 minutes to 7 days).

意思就是如果消息在Message retention duration时间内没有被处理完成的话,这条消息就会删除并且再也访问不到。

比如程序的BUG导致消息一直无法被处理,经过 Message retention duration 后这条消息就再也收不到了,会导致数据丢失。