分类 其他 下的文章

原文

I did a bit more research and seem to have used better key words because I found my solution now. I wanted to share the solution with everyone, in case someone else may ever need it.

Create folder for configuring docker service through systemd

mkdir /etc/systemd/system/docker.service.d

Create service configuration file at /etc/systemd/system/docker.service.d/http-proxy.conf and put the following in the newly created file

[Service]
# NO_PROXY is optional and can be removed if not needed
# Change proxy_url to your proxy IP or FQDN and proxy_port to your proxy port
# For Proxy server which require username and password authentication, just add the proper username and password to the URL. (see example below)

# Example without authentication
Environment="HTTP_PROXY=http://proxy_url:proxy_port" "NO_PROXY=localhost,127.0.0.0/8"

# Example with authentication
Environment="HTTP_PROXY=http://username:password@proxy_url:proxy_port" "NO_PROXY=localhost,127.0.0.0/8"
Reload systemctl so that new settings are read

sudo systemctl daemon-reload

Verify that docker service Environment is properly set

sudo systemctl show docker --property Environment

Restart docker service so that it uses updated Environment settings

sudo systemctl restart docker

Now you can execute the docker-compose command on your machine without getting any connection refused error messages.

背景

某 spring 服务使用 nginx 反向代理(下文称为 nginx-a),对外提供服务,并且在 nginx 上配置了 gzip on

后来由于某些原因,需要在 nginx 前面再加一层 nginx 做反向代理(下文称为 nginx-b),并且没有开启 gzip,因为已经在 nginx-a 开启了 gzip,无需开启两次

问题

加上 nginx-b 之后发现,浏览器访 nginx-b 问的时 gzip 失效了,于是又把域名指向 nginx-a,发现 gzip 却是生效的。

结论

排查之后发现是由于 nginx 作为反向代理服务器(nginx-b),会给后端服务发送一个 header Via: nginx,并且 nginx 作为 upstream(nginx-a)回检查 header 中的 Via,默认配置下如果设置了这个 header,nginx 返回的内容就不做 gzip 压缩,所以访问 nginx-b 的时候 gzip 失效了。

解决办法

只需要在 nginx-b 的配置里加上 gzip_proxied: any,表示任何时候返回的内容都做 gzip,具体介绍见 nginx 官网文档

参考文献:

不要情绪化

有问题就陈述具体事项和带来的影响,而不是描述自己的感受和可能做出的应对策略

示例一

错误示例:你这么搞我没法干了(描述应对策略)

正确示例:你这么搞导致我做这件事情的成本太高了(陈述问题和带来的影响)

示例二

错误示例:各种流程太多了,让人觉得很烦(描述自身感受)

正确示例:各种流程太多了,导致做事情效率太低(陈述问题和带来的影响)

问题描述

生产环境有一个场景是,a.com 会加载 b.com 下的一个静态资源 b.com/b.js,并且需要验证登陆才能拿到。通过如下代码获取

addScript('b.com/b.js')

function addScript(src, callback) {
  var script = document.createElement('script')
  script.src = src
  script.onload = script.onerror = function() {
    if (script.parentNode) {
      script.parentNode.removeChild(script)
    }
    if (callback) {
      callback()
      callback = null
    }
  }
  document.head.appendChild(script)
}

也就是我们常用的 jsonp 的形式

我们通常会手动创建 script 标签并设置 src 属性,来实现 jsonp 跨域请求

之前一直没问题,突然有一天有同事发现这个资源加载不到了,并且只是个别人会有这个问题。

查看之后发现,在出问题的浏览器中addScript('b.com/b.js')发出请求里没带 cookie,所以获取静态资源的请求直接 302 到了登陆页。经过一番查找,发现问题出在 cookie 的 SameSite 属性上。

在没有这个问题的浏览器中,通过手动设置 Chrome 的 chrome://flags/#same-site-by-default-cookies 为 Enabled 也能重现这个问题,至此问题确认。

image-20200804171245697.png

下面对 SameSite 做简单介绍。

SameSite 属性(引用自阮一峰博客

Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。

Cookie 的SameSite属性用来限制第三方 Cookie,从而减少安全风险。

它可以设置三个值。

  • Strict
  • Lax
  • None

2.1 Strict

Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

Set-Cookie: CookieName=CookieValue; SameSite=Strict;

这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。

2.2 Lax

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。

Set-Cookie: CookieName=CookieValue; SameSite=Lax;

导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。

请求类型示例正常情况Lax
链接<a href="..."></a>发送 Cookie发送 Cookie
预加载<link rel="prerender" href="..."/>发送 Cookie发送 Cookie
GET 表单<form method="GET" action="...">发送 Cookie发送 Cookie
POST 表单<form method="POST" action="...">发送 Cookie不发送
iframe<iframe src="..."></iframe>发送 Cookie不发送
AJAX$.get("...")发送 Cookie不发送
Image<img src="...">发送 Cookie不发送

设置了StrictLax以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。

2.3 None

Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

下面的设置无效。

Set-Cookie: widget_session=abc123; SameSite=None

下面的设置有效。

Set-Cookie: widget_session=abc123; SameSite=None; Secure

解决方案

解决方案就是显式设置 cookie 的 SameSite 属性为 None。一定是显式设置,response 中的 Set-Cookie 头里一定要包含 SameSite: None 字样。

参考文献

备注:文中提到 jsonp 以及 scrip 标签的 src 属性,主要是为了能让搜这几个关键字的人有可能搜索到这篇文章

关键词:groovyshell evaluate 内存泄露

grovvy 版本 2.4.10

1. 起因

代码在线上运行一段时间后,容器会自动重启,查看容器监控发现是内存溢出导致了自动重启。

查看 jvm 堆情况。堆内存的使用实际不大,但是 docker 实例的内存却又占满了,又因为 docker 实例并没有安装 agent 进程,说明很大可能是 JVM 堆外内存导致。

使用 jstat -gc pid 查看,发现 Metaspace 占用很大

回想该应用出现状况前的改动,主要是新增了 groovy 脚本执行任务。查看代码发现每次执行都会创建对象

public Object evaluate(String name, String script, Binding binding) {
    binding.setVariable("logger", LoggerFactory.getLogger("script-" + name));
    binding.setVariable("builder", new Builder());
    binding.setVariable("selector", this.selector);
    GroovyShell shell = new GroovyShell(binding);
    return shell.evaluate(script);
}

看监控类加载总量很大,猜测是因为生成很多临时 class 对象,无法 gc

2. 排查之后的具体原因

new GroovyShell() 未指定 patentClassloader

所以 GroovyShell 初始化的时候会自动 new 一个 classLoader 并设置 GroovyShell.class.getClassLoader() 为父 classloader

所以最终加载脚本文件的时候是交给 GroovyShell.class.getClassLoader() 处理的

执行 evaluate 方法,只传入了脚本字符串,直接 evaluate 字符串不会缓存脚本,所以每次执行都会重新加载一次,生成一个全新的 class 对象

但是 JVM 中的 Class 只有满足以下三个条件,才能被回收(也就是该 Class 被卸载 unload)

  • 类所有的实例都已经被 GC,也就是 JVM 中不存在该 Class 的任何实例。
  • 加载该类的 ClassLoader 已经被 GC。
  • 该类的 java.lang.Class 对象没有在任何地方被引用

由于实际负责加载类的 classloader 不会回收,导致 class 对象一直存在于 Metaspace 并且随着时间推移越来越多,并且启动时没有设置 Metaspace 最大值,导致 Metaspace 空间无限增长,最终容器自身 oom,自动重启

-XX:MetaspaceSize 表示触发 full gc 的容量。对于64位JVM来说,MetaspaceSize 默认为 20.75MB,-XX:MaxMetaspaceSize 默认值是无限大