关注小众语言、AI,记录、分享技术点滴!

0%

你有没有在使用k8s过程中遇到过这种情况: 通过kubectl delete指令删除一些资源时,一直处于Terminating状态。 这是为什么呢?

本文将介绍当你执行kubectl delete语句时,K8s内部都执行了哪些操作。 以及为何有些资源’删除不掉’(具体表现为一直Terminating,删除namespace时很容易遇到这种情况)

接下来,我们聚焦讨论以下四个方面:

1)资源的哪些属性会对删除操作产生影响?
2)finalizers与owner references属性是如何影响删除操作的?
3)如何利用Propagation Policy(分发策略)更改删除顺序?
4)删除操作的工作原理?

1. 词汇表

1)资源: k8s的资源对象(如configmap, secret, pod…)
2)finalizers: 终结器,存放键的列表。列表内的键为空时资源才可被删除
3)owner references: 所有者引用(归谁管理/父资源对象是谁)
4)kubectl: K8s客户端工具

2. 强制删除pod

每当删除namespace或pod等一些Kubernetes资源时,有时资源状态会卡在terminating,很长时间无法删除,甚至有时增加–force flag(强制删除)之后还是无法正常删除。这时就需要edit该资源,将字段finalizers设置为null,之后Kubernetes资源就正常删除了。

当删除pod时有时会卡住,pod状态变为terminating,无法删除pod

1)强制删除pod

1
$ kubectl delete pod xxx -n xxx --force --grace-period=0

2)如果强制删除还不行,设置finalizers为空
如果一个容器已经在运行,这时需要对一些容器属性进行修改,又不想删除容器,或不方便通过replace的方式进行更新。kubernetes还提供了一种在容器运行时,直接对容器进行修改的方式,就是patch命令。

1
$ kubectl patch pod xxx -n xxx -p '{"metadata":{"finalizers":null}}'

这样pod就可以删除了。

3. k8s删除流程

删除操作看似简单,但是有很多因素可能会干扰删除,包括finalizers与owner references属性

4. Finalizers是什么?
上面我们提到了两个属性:finalizers与owner references可能会干扰删除操作,导致删除阻塞或失败。 那Finalizers是什么?会对删除有何影响呢?

当要理解Kubernetes中的资源删除原理时,了解finalizers(以下我们称finalizers为终结器)的工作原理是很有帮助的, 可以帮助您理解为什么有些对象无法被删除。

终结器是资源发出预删除操作信号的属性, 控制着资源的垃圾收集,并用于提示控制器在删除资源之前执行哪些清理操作。

finalizers本质是包含键的列表,不具有实际意义。与annotations(注释)类似,finalizers是可以被操作的(增删改)。

以下终结器您可能遇到过:

  • kubernetes.io/pv-protection
  • kubernetes.io/pvc-protection

这两个终结器作用于卷,以防止卷被意外删除。

类似地,一些终结器可用于防止资源被删除,但不由任何控制器管理。 下面是一个自定义的configmap,它没有具体值,但包含一个终结器:

1
2
3
4
5
6
7
8
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap
finalizers:
- kubernetes
EOF

终结器通常用于名称空间(namespace),而管理configmap资源的控制器不知道该如何处理finalizers字段。 下面我们尝试删除这个configmap对象:

1
2
3
4
$ kubectl delete configmap/mymap &
configmap "mymap" deleted
$ jobs
[1]+ Running kubectl delete configmap/mymap

Kubernetes返回该对象已被删除,然而它并没有真正意义上被删除,而是在删除的过程中。 当我们试图再次获取该对象时,我们发现该对象多了个deletionTimestamp(删除时间戳)字段。

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
$ kubectl get cm mymap -o yaml
apiVersion: v1
kind: ConfigMap
metadata:
creationTimestamp: "2021-09-29T11:04:40Z"
deletionGracePeriodSeconds: 0
deletionTimestamp: "2021-09-29T11:04:55Z"
finalizers:
- kubernetes
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:finalizers:
.: {}
v:"kubernetes": {}
manager: kubectl
operation: Update
time: "2021-09-29T11:04:40Z"
name: mymap
namespace: default
resourceVersion: "1378430"
selfLink: /api/v1/namespaces/default/configmaps/mymap
uid: 8d6ca0b1-4840-4597-8164-a63b526dbf5f

简而言之,当我们删除带有finalizers字段的对象时,该对象仅仅是被更新了,而不是被删除了。 这是因为Kubernetes获取到该对象包含终结器,通过添加deletionTimestamp(删除时间戳)字段将其置于只读状态(删除终结器键更新除外)。 换句话说,在删除该对象终结器之前,删除都不会完成。

接下来我们尝试通过patch命令删除终结器,并观察configmap/mymap是否会被’真正’删除。

1
2
3
$ kubectl patch configmap/mymap \
--type json \
--patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'

再次检索该对象

1
2
$ kubectl get cm mymap
Error from server (NotFound): configmaps "mymap" not found

发现该对象已被真正删除,下图描述了带有finalizers字段的对象删除流程:

总结:当您试图删除一个带有终结器的对象,它将一直处于预删除只读状态, 直到控制器删除了终结器键或使用Kubectl删除了终结器。一旦终结器列表为空,Kubernetes就可以回收该对象,并将其放入要从注册表中删除的队列中

带有finalizers字段的对象无法删除的原因大致如下:

  • 对象存在finalizers,关联的控制器故障未能执行或执行finalizer函数hang住: 比如namespace控制器无法删除完空间内所有的对象, 特别是在使用aggregated apiserver时,第三方apiserver服务故障导致无法删除其对象。 此时,需要会恢复第三方apiserver服务或移除该apiserver的聚合,具体选择哪种方案需根据实际情况而定。
  • 集群内安装的控制器给一些对象增加了自定义finalizers,未删除完fianlizers就下线了该控制器,导致这些fianlizers没有控制器来移除他们。 此时,需要恢复该控制器会手动移除finalizers(多出现于自定义operator),具体选择哪种方案根据实际情况而定。

5. Owner References又是什么?
上面我们提到了两个属性:finalizers与owner references可能会干扰删除操作,导致删除阻塞或失败。 并介绍了Finalizers,接下来我们聊聊Owner References.

Owner References(所有者引用或所有者归属)描述了对象组之间的关系。 指定了资源彼此关联的属性,因此可以级联删除整个资源树。

当存在所有者引用时,将处理终结器规则。所有者引用由名称和UID组成

所有者引用相同名称空间内的链接资源,它还需要UID以使该引用生效(确保唯一)。 Pods通常具有对所属副本集的所有者引用。 因此,当Deloyment或有StatefulSet被删除时,子ReplicaSet和Pod将在流程中被删除。

我们通过下面的例子,来理解Owner References(所有者引用)的工作原理:

1.创建cm/mymap-parent对象

1
2
3
4
5
6
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-parent
EOF

2.获取cm/mymap-parent的UID

1
$ CM_UID=$(kubectl get configmap mymap-parent -o jsonpath="{.metadata.uid}")

3.创建cm/mymap-child对象,并设置ownerReferences字段声明所有者引用(通过kind、name、uid字段确保选择器可以匹配到)

1
2
3
4
5
6
7
8
9
10
11
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-child
ownerReferences:
- apiVersion: v1
kind: ConfigMap
name: mymap-parent
uid: $CM_UID
EOF

即cm/mymap-parent为cm/mymap-child的父对象,此时我们删除cm/mymap-parent对象并观察cm/mymap-child对象状态

1
2
3
4
5
6
7
8
9
10
$ kubectl get cm
NAME DATA AGE
mymap-child 0 2m44s
mymap-parent 0 3m

$ kubectl delete cm mymap-parent
configmap "mymap-parent" deleted

$ kubectl get cm
No resources found in default namespace.

即我们通过删除父对象,间接删除了父对象下的所有子对象。 这种删除k8s中被称为级联删除。我们可不可以只删除父对象,而不删除子对象呢?

答案是: 可以的,删除时通过添加–cascade=false参数实现,我们通过下面的例子来验证:

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
$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-parent
EOF

$ CM_UID=$(kubectl get configmap mymap-parent -o jsonpath="{.metadata.uid}")

$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: mymap-child
ownerReferences:
- apiVersion: v1
kind: ConfigMap
name: mymap-parent
uid: $CM_UID
EOF

$ kubectl delete --cascade=false configmap/mymap-parent
configmap "mymap-parent" deleted

$ kubectl get cm
NAME DATA AGE
mymap-child 0 107s

–cascade=false参数实际改变了父-子资源的删除顺序,k8s中关于父-子资源删除策略有以下三种:

  • Foreground: 子资源在父资源之前被删除(post-order)
  • Background: 父资源在子资源之前被删除 (pre-order)
  • Orphan: 忽略所有者引用进行删除

6. 强制删除命名空间
有一种情况可能需要强制删除命名空间:

如果您已经删除了一个命名空间,并删除了它下面的所有对象,但名称空间仍然存在,一般为Terminating状态。 则可以通过更新名称空间的finalize属性来强制删除该名称空间。

  1. 会话1

    1
    $ kubectl proxy
  2. 会话2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    $ NAMESPACE_NAME=test
    cat <<EOF | curl -X PUT \
    127.0.0.1:8001/api/v1/namespaces/$NAMESPACE_NAME/finalize \
    -H "Content-Type: application/json" \
    --data-binary @-
    {
    "kind": "Namespace",
    "apiVersion": "v1",
    "metadata": {
    "name": "$NAMESPACE_NAME"
    },
    "spec": {
    "finalizers": null
    }
    }
    EOF

我们应该谨慎思考是否强制删除命名空间,因为这样做可能只删除名称空间,命名空间下的其他资源删不完全,最终导致留下孤儿对象。 比如资源对象A存在于ddd命名空间,此时若强制删除ddd命名空间, 且对象A又未被删除,那么对象A便成了孤儿对象。

当出现孤儿对象时,可以手动重新创建名称空间,随后可以手动清理和恢复该对象。

在lua原生语法特性中是不具备面向对象设计的特性。因此,要想在lua上像其他高级语言一样使用面向对象的设计方法,就需要使用原生的元表(metatable)来模拟面向对象设计。

一、元表setmetatable
对指定 table 设置元表(metatable),如果元表(metatable)中存在__metatable键值,setmetatable会失败。

以下实例演示了如何对指定的表设置元表:

1
2
3
mytable = {}                          -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表

二、元表__index 元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的_index 键。如果_index包含一个表格,Lua会在表格中查找相应的键。

1
2
3
4
5
6
other = { foo = 3 }
t = setmetatable({}, { __index = other })
print(t.foo)
3
print(t.bar)
nil

三、使用metatable实现继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local _M = {
version = 'lua 1.0'
}

local parent = {
__index = _M
}

function parent.new()
-- 初始化new,如果没有这句,那么类所建立的对象如果有一个改变,其他对象都会改变
local new = {}
-- 使用setmetatable来实现继承
setmetatable(new, parent)
return new
end

function _M:echo()
print("M:echo "..self.version)
end

四、使用metatable实现重载和多态

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
local _M = {
version = 'lua 1.0'
}

local parent = {
__index = _M
}

function parent.new()
-- 初始化new,如果没有这句,那么类所建立的对象如果有一个改变,其他对象都会改变
local new = {}
-- 使用setmetatable来实现继承
setmetatable(new, parent)
return new
end

function _M:echo()
print("M:echo "..self.version)
end


local _M2 = parent.new()

local child = {
__index = _M2
}

function child.new()
-- 初始化new,如果没有这句,那么类所建立的对象如果有一个改变,其他对象都会改变
local new = {}
-- 使用setmetatable来实现继承
setmetatable(new, child)
return new
end

-- 重载echo方法
function _M2:echo()
print("M2:echo "..self.version)
end


test = parent.new()
test:echo()

test = child.new()
test:echo()

luaosslluacrypto的代替版本,是针对 Lua 5.1、5.2、5.3 和 LuaJIT 的 OpenSSL 综合绑定。是 Lua 世界中最全面的 OpenSSL 绑定。luaosll安装依赖OpenSSL库,所以需要先安装OpenSSL。

一、安装OpenSSL
下载好的OpenSSL源码目录下,编译安装OpenSSL(以下均使用默认路径“/usr/local”进行安装)

1)生成Makefile文件

1
2
# 根据编译平台及环境自动生成Makefile文件,可以通过./config --prefix指定安装路径,-Wl,-rpath参数指定OpenSSL运行时依赖libcrypto、libssl库的路径。
./config -Wl,-rpath,/usr/local/lib

2)安装OpenSSL

1
2
make
make install

3)查看OpenSSL版本

1
2
3
4
openssl version

# 显示如下格式内容说明安装成功(本文以安装OpenSSL 1.1.1q为例)
OpenSSL 1.1.1q 5 Jul 2022 (Library: OpenSSL 1.1.1k FIPS 25 Mar 2021)

二、安装luaosll

1
2
3
4
5
6
7
8
9
10
11
12
luarocks install luaossl

# 显示如下格式内容说明安装成功
Installing https://luarocks.org/luaossl-20220711-0.src.rock

luaossl 20220711-0 depends on lua (5.1-1 provided by VM)
Applying patch config.h.diff...
Hunk 1 found at offset 2...
gcc -O2 -fPIC -I/usr/local/include -c src/openssl.c -o src/openssl.o -D_REENTRANT -D_THREAD_SAFE -DCOMPAT53_PREFIX=luaossl -D_GNU_SOURCE -I/usr/local/include -I/usr/local/include
gcc -O2 -fPIC -I/usr/local/include -c vendor/compat53/c-api/compat-5.3.c -o vendor/compat53/c-api/compat-5.3.o -D_REENTRANT -D_THREAD_SAFE -DCOMPAT53_PREFIX=luaossl -D_GNU_SOURCE -I/usr/local/include -I/usr/local/include
gcc -shared -o _openssl.so src/openssl.o vendor/compat53/c-api/compat-5.3.o -L/usr/local/lib64 -L/usr/local/lib64 -Wl,-rpath,/usr/local/lib64 -Wl,-rpath,/usr/local/lib64 -lssl -lcrypto -lpthread -lm -ldl
luaossl 20220711-0 is now installed in /usr/local (license: MIT/X11)

KEDA 是一个基于 Kubernetes 的事件驱动自动缩放器。使用 KEDA,您可以根据需要处理的事件数量来驱动 Kubernetes 中任何容器的扩展。KEDA可以支持很多自定义事件源,如:Mysql、MongoDB、Redis、ActiveMQ、Kafka、Prometheus、Metrics API等。本文使用nginx中的stub_status数据 + Prometheus为事件源进行KEDA HPA配置。

一、开启nginx(stub_status)
nginx中的stub_status模块主要用于查看Nginx的一些状态信息。

1)查看nginx时候有安装该模块。

1
/usr/local/nginx/sbin/nginx -V

2)安装stub_status模块
(注意:有的话可以忽略此步骤,就不用安装了)
在nginx编译安装的时候加上参数 “–with-http_stub_status_module”,就安装了这个模块。

1
./configure --with-http_stub_status_module

3)开启stub_status

1
2
3
4
5
location /nginx_status {
stub_status on;
allow 127.0.0.1; #only allow requests from localhost
deny all; #deny all other hosts
}

二、在kubernetes中运行nginx-prometheus-exporter
nginx-prometheus-exporter 是将 stub_status 指标转换为 Prometheus 指标类型,最终可以由 Prometheus 进行收集。

在Kubernetes里运行

1)创建一个无状态服务(Deployment)

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-prometheus
labels:
workload.user.cattle.io/workloadselector: apps.deployment-default-nginx-prometheus
namespace: default
spec:
selector:
matchLabels:
workload.user.cattle.io/workloadselector: apps.deployment-default-nginx-prometheus
template:
metadata:
labels:
workload.user.cattle.io/workloadselector: apps.deployment-default-nginx-prometheus
spec:
containers:
- imagePullPolicy: Always
name: nginx-prometheus
image: nginx/nginx-prometheus-exporter:0.10.0
command:
- nginx-prometheus-exporter
args:
- '-nginx.scrape-uri=http://127.0.0.1/nginx_status'
restartPolicy: Always

2)创建一个Service服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
name: nginx-prometheus
labels:
metrics-prometheus-discovery: 'true' # for monitoring discovery
namespace: default
spec:
ports:
- name: prometheus
port: 9113
protocol: TCP
targetPort: 9113
type: ClusterIP

三、配置kubernetes中的Monitor服务

1)创建一个ServiceMonitor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: nginx-monitor
namespace: default
spec:
selector:
matchLabels:
metrics-prometheus-discovery: 'true' # for monitoring discovery
namespaceSelector:
matchNames:
- default
endpoints:
- port: prometheus
interval: 5s

2)创建KEDA(ScaledObject)缩放规则

Prometheus的集群内访问地址是:http://prometheus-operated.cattle-prometheus.svc:9090

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: nginx-prometheus-scaledobject
namespace: default
spec:
scaleTargetRef:
name: nginx
pollingInterval: 5 # Optional. Default: 30 seconds
cooldownPeriod: 60 # Optional. Default: 300 seconds
minReplicaCount: 1 # Optional. Default: 0
maxReplicaCount: 3 # Optional. Default: 100
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-operated.cattle-monitoring-system.svc.cluster.local:9090
metricName: nginx_http_requests_total
threshold: '100'
query: sum(rate(nginx_http_requests_total[10s]))

ps:

在Docker中运行

1
docker run -p 9113:9113 nginx/nginx-prometheus-exporter:0.10.0 -nginx.scrape-uri=http://127.0.0.1/nginx_status

运行后,可以通过9113 端口来访问 Prometheus 收集的数据,可以配合Grafana来实现数据可视化。

1
curl http://localhost:9113/metrics

在开发环境及私有环境下需要使用SSL,于是使用openssl创建自签发证书,支持多域名、泛域名、直接IP访问。

一、使用openssl生成证书自签名

openssl在centos中是标配,所以直接在centos中操作,因为要多个域名和IP,故而需要编辑一个配置文件,如下:

1
$ vim req.cnf
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
# 定义输入用户信息选项的"特征名称"字段名,该扩展字段定义了多项用户信息。
distinguished_name = req_distinguished_name
# 生成自签名证书时要使用的证书扩展项字段名,该扩展字段定义了要加入到证书中的一系列扩展项。
x509_extensions = v3_req

# 如果设为no,那么 req 指令将直接从配置文件中读取证书字段的信息,而不提示用户输入。
prompt = no

[req_distinguished_name]
#国家代码,一般都是CN(大写)
C = CN
#省份
ST = Beijing
#城市
L = Beijing
#企业/单位名称
O = phpkoo
#企业部门
OU = phpkoo
#证书的主域名
CN = phpkoo.com

##### 要加入到证书请求中的一系列扩展项 #####
[v3_req]
keyUsage = critical, digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[ alt_names ]
DNS.1=第一个域名
DNS.2=第二个域名
DNS.N=第N个域名
IP.1=第一个IP
IP.2=第二个IP
IP.N=第N个IP

其中IP配置项,可有可无。

1
2
$ mkdir -p ssl/
$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./ssl/private.key -out ./ssl/ssl.crt -config ./req.cnf -sha256

至此证书生成完成

可以下载ssl目录下的private.key和ssl.crt文件

openssl 命令参数说明:

req 大致有3个功能:生成证书请求文件、验证证书请求文件和创建根CA。
-x509 说明生成自签名证书。
-nodes openssl req在自动创建私钥时,将总是加密该私钥文件,并提示输入加密的密码。可以使用”-nodes”选项禁止加密私钥文件。
-days 指定所颁发的证书有效期。
-key 指定输入的密钥,如果不指定此选项会根据 -newkey 选项的参数生成密钥对。
-newkey 指定生成一个新的密钥对,只有在没有 -key 选项的时候才生效,参数形式为rsa:numbits或者dsa:file,例如:rsa:2048 rsa表示创建rsa私钥,2048表示私钥的长度。
-keyout 指定私钥保存位置。
-out 新的证书请求文件位置。
-config 指定req的配置文件,指定后将忽略所有的其他配置文件。如果不指定则默认使用/etc/pki/tls/openssl.cnf中req段落的值。

二、证书如何使用

此时访问会出现如下问题

你还得在系统上安装一下证书,安装步骤如下
1、在Windows中导入证书

  • 第一步:双击ssl.crt文件打开证书文件,出现如下界面

  • 第二步:

  • 第三步:

  • 第四步:

  • 第五步:

证书安装,完成

重启浏览器,才能生效

2、测试效果

3、火狐浏览器处理

Firefox默认情况不,还是不信任自签名证书

打开火狐浏览器输入about:config进行设置界面

  • 第一步:

  • 第二步:

输入security.enterprise_roots.enabled修改为true

  • 第三步:

设置完成

重启浏览器,才能生效

在一次和国内某云厂商对接对象存储时,因为他们对象存储服务,是直接使用的是第三方开源服务,开源服务默认并不支持chunked编码(而国内其它大厂一般都是支持chunked编码)。导致上传文件一直报HTTP/1.1 411 Length Required错误,出现问题的原因是说,我没有传Content-Length。

而明明我在header里设置了Content-Length,但是抓包发现,实际却变成了chunked

经过反复的测试情况都依旧,直到在github上找到了这个https://github.com/golang/go/issues/16264

然后查看了golang源码

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
65
66
67
68
69
70
71
72
73
74
75
76
func NewRequest(method, url string, body io.Reader) (*Request, error) {
if method == "" {
// We document that "" means "GET" for Request.Method, and people have
// relied on that from NewRequest, so keep that working.
// We still enforce validMethod for non-empty methods.
method = "GET"
}
if !validMethod(method) {
return nil, fmt.Errorf("net/http: invalid method %q", method)
}
u, err := parseURL(url) // Just url.Parse (url is shadowed for godoc).
if err != nil {
return nil, err
}
rc, ok := body.(io.ReadCloser)
if !ok && body != nil {
rc = ioutil.NopCloser(body)
}
// The host's colon:port should be normalized. See Issue 14836.
u.Host = removeEmptyPort(u.Host)
req := &Request{
Method: method,
URL: u,
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(Header),
Body: rc,
Host: u.Host,
}
if body != nil {
switch v := body.(type) {
case *bytes.Buffer:
req.ContentLength = int64(v.Len())
buf := v.Bytes()
req.GetBody = func() (io.ReadCloser, error) {
r := bytes.NewReader(buf)
return ioutil.NopCloser(r), nil
}
case *bytes.Reader:
req.ContentLength = int64(v.Len())
snapshot := *v
req.GetBody = func() (io.ReadCloser, error) {
r := snapshot
return ioutil.NopCloser(&r), nil
}
case *strings.Reader:
req.ContentLength = int64(v.Len())
snapshot := *v
req.GetBody = func() (io.ReadCloser, error) {
r := snapshot
return ioutil.NopCloser(&r), nil
}
default:
// This is where we'd set it to -1 (at least
// if body != NoBody) to mean unknown, but
// that broke people during the Go 1.8 testing
// period. People depend on it being 0 I
// guess. Maybe retry later. See Issue 18117.
}
// For client requests, Request.ContentLength of 0
// means either actually 0, or unknown. The only way
// to explicitly say that the ContentLength is zero is
// to set the Body to nil. But turns out too much code
// depends on NewRequest returning a non-nil Body,
// so we use a well-known ReadCloser variable instead
// and have the http package also treat that sentinel
// variable to mean explicitly zero.
if req.GetBody != nil && req.ContentLength == 0 {
req.Body = NoBody
req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
}
}

return req, nil
}

可以看到,这里面居然有个switch,当你使用bytes.Buffer,bytes.Reader或者strings.Reader作为Body的时候,它会自动给你设置req.ContentLength…

所以,问题不是当你Post一个ReadCloser的时候,就会变成chunked,而是你Post非这三种类型的body进来的时候都没有Content-Length,需要自己显式设置。代码如下:

1
2
req, _ := http.NewRequest(method, url, bodyReader)
req.ContentLength = req.Header.Get("Content-Length")

设置了req.ContentLength之后,抓包如下确实不再是chunked编码了,抓包如下

至此问题解决。

lua-mongo 是对 MongoDB C Driver 1.16 或更高版本的 Lua 的绑定,githup仓库:https://github.com/neoxic/lua-mongo

通过luarocks方式安装lua模块

一、安装luarocks工具,官网:https://luarocks.org/
二、安装lua-mongo模块

1
luarocks install lua-mongo

安装时,报以下错误信息

Error: Could not find expected file libmongoc-1.0/mongoc.h, or libmongoc-1.0/mongoc.h for LIBMONGOC – you may have to install LIBMONGOC in your system and/or pass LIBMONGOC_DIR or LIBMONGOC_INCDIR to the luarocks command. Example: luarocks install mongorover LIBMONGOC_DIR=/usr/local

三、安装cmake

因为mongo-c-driver需要使用cmake进行编译

1
2
3
4
5
6
7
yum install gcc gcc-c++ ncurses-devel
wget wget https://cmake.org/files/v3.3/cmake-3.3.2.tar.gz
tar -xzf cmake-3.3.2.tar.gz
cd cmake-3.3.2/
./bootstrap
gmake
gmake install

四、安装libmongoc库

官方安装参照:http://mongoc.org/

1
2
3
4
5
6
wget https://github.com/mongodb/mongo-c-driver/releases/download/1.17.6/mongo-c-driver-1.17.6.tar.gz
tar -xzf mongo-c-driver-1.17.6.tar.gz
cd mongo-c-driver-1.17.6/
cmake .
make
make install

再执行,安装lua-mongo模块

1
luarocks install lua-mongo

安装成功

nginx想支持openresty的功能,需要安装以下模块及依赖luajit2lua-resty-corelua-resty-lrucachelua-nginx-modulengx_devel_kit以下5个依赖包必须先下载,另外如果需要同时支持SSL的话,还需要下载openssl依赖包(ssl只需要下载nginx编译时使用)。

一、安装openresty依赖包
1、安装luajit2(下载最新版本)

1
2
3
4
5
6
7
8
9
wget https://github.com/openresty/luajit2/archive/v2.1-20201229.tar.gz
tar -zxvf luajit2-2.1-20201229.tar.gz
cd luajit2-2.1-20201229
make
make install

#导入环境变量(编译nginx时需要)
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.1

2、安装lua-resty-core

1
2
3
4
wget https://github.com/openresty/lua-resty-core/archive/v0.1.21.tar.gz
tar -zxvf lua-resty-core-0.1.21.tar.gz
cd lua-resty-core-0.1.21
make install

3、安装lua-resty-lrucache

1
2
3
4
wget https://github.com/openresty/lua-resty-lrucache/archive/v0.10.tar.gz
tar -zxvf lua-resty-lrucache-0.10.tar.gz
cd lua-resty-lrucache-0.10
make install

4、解压lua-nginx-module

1
2
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.19.tar.gz
tar -zxvf lua-nginx-module-0.10.19.tar.gz

5、解压ngx_devel_kit

1
2
wget https://github.com/vision5/ngx_devel_kit/archive/v0.3.1.tar.gz
tar -zxvf ngx_devel_kit-0.3.1.tar.gz

6、解压openssl

1
2
wget https://www.openssl.org/source/openssl-1.1.1k.tar.gz
tar -zxvf openssl-1.1.1k.tar.gz

二、编译及安装nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
wget http://nginx.org/download/nginx-1.18.0.tar.gz
tar -zxvf nginx-1.18.0.tar.gz

./configure --prefix=/usr/local/nginx \
--with-openssl=/usr/local/src/openssl-1.1.1k \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_auth_request_module \
--with-http_gzip_static_module \
--with-threads \
--with-stream \
--with-stream_ssl_module \
--with-stream_ssl_preread_module \
--with-http_slice_module \
--with-compat \
--with-file-aio \
--with-http_v2_module \
--with-ld-opt="-Wl,-rpath,/usr/local/lib" \
--add-module=/usr/local/src/ngx_devel_kit-0.3.1 \
--add-module=/usr/local/src/lua-nginx-module-0.10.19

make && make install

三、nginx配置

1
2
3
4
5
6
#设置openresty模块目录
lua_package_path "/usr/local/lib/lua/?.lua;/usr/local/nginx/conf/lua/?.lua;;";

#指定dns解析服务器,实现动态upstream
resolver 114.114.114.114 223.5.5.5 1.1.1.1 8.8.8.8 valid=30;
resolver_timeout 5;

四、hello openresty(/usr/local/nginx/conf/lua/hello.lua)

1
ngx.print("hello openresty!")

一、lsof相关使用
1、系统所有进程打开对应的句柄数

1
lsof -n|awk '{print $2}'|sort|uniq -c |sort -nr|more

2、查看当前进程实时打开的文件数

1
lsof -p 进程ID |wc -l

3、查看被打开某一文件的相关进程信息

1
lsof /proc

4、查看监听端口25的进程

1
lsof -i:25

5、查看活动的连接

1
lsof -i @192.168.1.2

二、查看当前进程持有文件句柄数量和文件句柄最大限制

1、查看这个PID持有的句柄数

1
ls /proc/进程ID/fd | wc -l

2、查看当前进程句柄数量限制

1
cat /proc/进程ID/limits | grep "files"

三、查看系统总限制

1、查看系统总限制打开文件的最大数量

1
cat /proc/sys/fs/file-max

四、watch命令
watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行。在Linux下,watch是周期性的执行下个程序,并全屏显示执行结果。

1、每隔一秒高亮显示网络链接数的变化情况

1
watch -n 1 -d netstat -ant

2、每隔一秒高亮显示http链接数的变化情况

1
watch -n 1 -d 'pstree|grep http'

3、实时查看模拟攻击客户机建立起来的连接数

1
watch 'netstat -an | grep:21 | \ grep<模拟攻击客户机的IP>| wc -l'

4、10秒一次输出系统的平均负载

1
watch -n 10 'cat /proc/loadavg'

5、监测磁盘inode和block数目变化情况

1
watch -n 1 'df -i;df'

Composer 是 PHP 的一个依赖管理工具。它允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。

运行 Composer 需要 PHP 5.3.2+ 以上版本。一些敏感的 PHP 设置和编译标志也是必须的,但对于任何不兼容项安装程序都会抛出警告。

全局安装 (推荐)

1
2
3
curl -sS https://getcomposer.org/installer | php

mv composer.phar /usr/local/bin/composer

局部安装

1
curl -sS https://getcomposer.org/installer | php

全局配置(推荐)

阿里 Composer 全量镜像

1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

取消配置

1
composer config -g --unset repos.packagist