思有所皈,绪有所依

给万千思绪,寻一寄存之所

Fix No chain/target/match by that name

最近在Alibaba Cloud Linux 3上使用audo apt update进行系统升级后,某个Docker容器无法重启,错误如下:

1
2
Error response from daemon: Cannot restart container some-app: driver failed programming external connectivity on endpoint some-app (88301987d72d07ba99c1630461e1ade9d1fb53879b1d6f94fc8571ac7db7e2a6):  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9015 -j DNAT --to-destination 172.17.0.3:9015 ! -i docker0: iptables: No chain/target/match by that name.
(exit status 1))

首先排除了iptables的原因,因为我们在做容器化部署的服务器上,都禁用了iptables。

然后怀疑是系统更新的时候,影响到了Docker容器的某些配置,导致容器无法重启。但是重建容器后,还是无法重启,报一样的错误。

最后搜索了一下,找到了解决办法:
参考:docker启动容器报错(iptables failed)

重启docker服务即可:

1
systemctl restart docker

容器的健康检测

如果在Dockerfile或者docker run命令里进行了健康检测,容器启动后查看状态会多出一个信息:healthy或者unhealthy。

1
2
3
# docker ps
... STATUS
... Up 19 minutes (healthy)

在Dockerfile里配置健康检测

Dockerfile
1
HEALTHCHECK --interval=10s --timeout=2s --start-period=30s --retries=3 CMD curl --silent --fail http://localhost:7015/actuator/health || exit 1

在容器启动时指定健康检测参数

1
2
3
4
5
6
7
8
docker run ...
--health-interval=10s \
--health-timeout=2s \
--health-start-period=30s \
--health-retries=3 \
--health-cmd="curl --silent --fail http://localhost:7017/actuator/health || exit 1" \
--restart on-failure \
...

自动重启unhealthy的容器

参考Github项目: Autoheal
大致是说本来docker run有一个issue,或者说应该有那么一个功能,参数--exit-on-unhealthy,默认值true,在容器unhealthy时就退出。再配合--restart=on-failure,来实现容器的自动重启。在exit-on-unhealthy没实现之前,就用这个Autoheal来实现这个功能。

1
2
3
4
5
6
docker run -d \
--name autoheal \
--restart=unless-stopped \
-e AUTOHEAL_CONTAINER_LABEL=all \
-v /var/run/docker.sock:/var/run/docker.sock \
willfarrell/autoheal
  • AUTOHEAL_CONTAINER_LABEL的默认值是autoheal,表示Autoheal会监视带有标签--label autoheal=true的容器。
  • 设置AUTOHEAL_CONTAINER_LABEL=all则是监视所有正在运行的容器。
  • 也可以自定义AUTOHEAL_CONTAINER_LABEL的值,比如AUTOHEAL_CONTAINER_LABEL=myapp,这会监视所有带myapp=true标签的容器。(说实话,官方的这句Set ENV AUTOHEAL_CONTAINER_LABEL to existing label name that has the value true.,最初让我懵了一下。)

莫名其妙的“操作超时”

在用C#的HttpWebRequest写一个HTTP客户端请求时,遇到了一个莫名其妙的问题,就是在发送POST请求时,总是报“操作超时”的错误。核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
HttpWebRequest wbRequest = (HttpWebRequest)WebRequest.Create(url);
wbRequest.Method = "POST";
wbRequest.ContentType = "application/json";

byte[] data = Encoding.UTF8.GetBytes(content);
wbRequest.ContentLength = data.Length;
using (Stream requestStream = wbRequest.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
requestStream.Flush();
}
...
HttpWebResponse wbResponse = (HttpWebResponse)wbRequest.GetResponse();

在执行bRequest.GetResponse()时会卡住,一直等待直到报“操作超时”的错误。

分析与排查

代码问题的几率很小

这段代码没啥好说的,是老系统里千锤百炼留下来的基础代码。从网络上找到的教程什么的,大致也是这样写的,所以代码的问题的几率很小。

也不是服务端处理慢

服务端的响应不涉及复杂的业务处理,这个可以直接排除。

用Postman测试可以正常访问

用Postman测试,没有出现“操作超时”的错误。这个问题就很奇怪了,Postman的请求和代码里的请求应该是一样的,为什么Postman可以正常访问,而代码里就不行呢?

用Fiddler抓包分析

只有用Fiddler抓包分析,来看看代码的request跟Postman的请求有什么差异。发现代码里的请求,主要差异是多了一个Expect: 100-continue的Header。

Postman复现问题

Postman加上100-continue的Header,就可以复现“操作超时”的问题。

罪魁祸首之100-continue

查了一下资料,100-continue是HTTP 1.1里设计的一个状态码,在发送请求体之前,先发送一个Expect: 100-continue,如果服务器响应了100-continue,那么就会继续发送请求体。

这个设计的目的是为了在发送大量数据时,先确认服务器是否接受这个请求,如果不接受,就不用发送大量的数据了,从而减少网络带宽的浪费。

这就跟本文的问题对上了,刚好是带请求体的POST请求,所以先发送Expect: 100-continue,但是服务器没有响应100-continue,就一直等待到“操作超时”。

解决方案

定位到具体的原因后,解决起来就容易了,有两种方案(任选其一):

客户端:禁用100-continue

1
wbRequest.ServicePoint.Expect100Continue = false;

服务端:支持100-continue或者忽略100-continue

这个得看服务端的网关是什么了,可以配置为HTTP协议为1.1,或者把请求头里的Expect: 100-continue过滤掉。

未能创建 SSL/TLS 安全通道

最近将服务器升级到Ubuntu 22.04.2 LTS后,部分终端的.Net framework应用连接时报错:“请求被中止:未能创建SSL/TLS 安全通道”。

这个问题以前也出现过,终端的操作系统是Windows 7,只能安装.Net Framefork 4.0,但是4.0不支持TLS1.2,而Ubuntu升级后不支持TLS1.0和1.1,这就导致无法建立SSL/TLS安全通道。

当时主要参考 ubuntu不支持tls1.0解决,修改openssl的配置/etc/ssl/openssl.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
//在第一行增加:
openssl_conf = default_conf

//在尾部增加:
[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
MinProtocol = TLSv1
CipherString = DEFAULT:@SECLEVEL=1

Ubuntu升级过程中,都是选择的维持现有配置,所以/etc/ssl/openssl.cnf里还是有这些配置,不知道为什么没有生效。

修改Nginx的配置

参考 Enable TLS 1.0 and TLS 1.1 on Ubuntu 20.04

修改nginx的配置:

nginx.conf
1
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH:@SECLEVEL=1";

使用openssl测试

nginx.conf
1
2
openssl s_client -connect xiaoboey.top:443 -tls1_1
openssl s_client -connect xiaoboey.top:443 -tls1

还是没解决。。。

Event Source,即Server-sent events,主要优点是基于HTTP协议进行长连接,除了在网页里用JavaScript可以接收消息,其他的应用只要支持HTTP协议都没问题;而RabbitMQ完整实现了AMQP协议,即Advanced Message Queuing Protocol,非常成熟稳定。本文所谓的“代替”,适合的场景非常有限,如果要追求消息推送的稳定可靠,还是建议直接使用RabbitMQ。

阅读全文 »

弃坑

初步学习下来,跟k8s要解决的问题差不多,而k8s毕竟是容器编排的事实标准,所以还不如学习k8s?

Docker Swarm介绍及使用入门

Docker Swarm 搭建集群环境(高可用)
升级工作节点为管理节点:

1
docker node promote node01

Docker Swarm(四)Volume 数据(挂载)持久化

Docker Swarm(六)Label 节点标签与服务约束

docker swarm如何在指定节点运行service

TODO: 安装基于etcd的高可用k3s集群,部署Spring Boot应用。此文还未整理,暂时只是流水账。

最佳实践

保存离线安装文件

https://get.k3s.io获取安装脚本并保存为install.sh,在github上下载离线文件(主要是k3s执行程序和镜像文件)。特别是install.sh,K3s每一次的版本升级都可能会修改这个文件。笔者暂时没有找到在线获取历史版本的方法,所以如果真要自己部署K3s,把这些文件都保存下来更“靠谱”。

aliyun linux 安装Docker

参考: https://help.aliyun.com/document_detail/264695.html?spm=a2c6h.12873639.article-detail.4.54a5146d4WimM7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 添加docker-ce的dnf源
dnf config-manager --add-repo=https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 安装Alibaba Cloud Linux 3专用的dnf源兼容插件
dnf -y install dnf-plugin-releasever-adapter --repo alinux3-plus

# 安装docker-ce
dnf -y install docker-ce --nobest

# 验证
dnf list docker-ce

# 管理Docker守护进程
systemctl start docker #运行Docker守护进程
systemctl stop docker #停止Docker守护进程
systemctl restart docker #重启Docker守护进程
systemctl enable docker #设置Docker开机自启动
systemctl status docker #查看Docker的运行状态

安装K3s

单机

参考官网文档

国内加速安装
1
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh -

嵌入式etcd高可用

参考: https://docs.rancher.cn/docs/k3s/installation/ha-embedded/_index

–-docker: k3s server组件是以containerd作为容器运行时。k3s server节点默认也会启动一个agent节点,这个agent节点可以用–docker参数指定其用docker作为容器运行时。
–-disable-agent: 指定server节点不启动agent。

1
2
3
4
5
6
7
8
9
10
11
12
13
# k3s-server-1(--docker 表示agent使用docker)
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn K3S_TOKEN=SECRET sh -s - server --docker --cluster-init

systemctl enable k3s

# k3s-server-n
curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn K3S_TOKEN=SECRET sh -s - server --docker --server https://172.24.46.247:6443

systemctl enable k3s

# 移除节点
kubectl delete node 节点名称

Traefik v2

K3s 1.21 及更高版本默认安装 Traefik v2

启动Traefik Dashboard

出于安全考虑,默认没有公开 Dashboard

1
kubectl -n kube-system port-forward $(kubectl -n kube-system get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000

访问地址: http://localhost:9000/dashboard/

LetsEncrypt

参考:k3s使用Let‘s Encrypt配置https入口部署

1
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.1/cert-manager.yaml
letsencrypt.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
namespace: cert-manager
name: letsencrypt
spec:
acme:
email: [email protected]
privateKeySecretRef:
name: prod-issuer-account-key
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- http01:
ingress:
class: traefik
selector: {}

kubectl describe clusterissuer letsencrypt

ingress.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
apiVersion: networking.k8s.io/v1 
kind: Ingress
metadata:
name: myapp-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- secretName: myapp-web-tls
hosts:
- myapp.mydomain.com
ingressClassName: traefik
rules:
- host: myapp.mydomain.com
http:
paths:
- path: /web
pathType: Prefix
backend:
service:
name: myapp-svc
port:
number: 8080

K3s离线安装

尝试用离线方式来安装

部署私有镜像仓库

1
2
3
4
5
6
docker run -d \
-p 5000:5000 \
--restart=always \
--name registry \
-v $PWD/data:/var/lib/registry \
registry:2

修改hosts:

1
vi /etc/hosts

配置私有仓库为insecure(即http)

/etc/docker/daemon.json
1
2
3
4
5
6
7
8
9
{
"insecure-registries":[
"myregistry.com:5000"
],
"registry-mirrors": [
"https://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}

docker tag demo:v1.2 myregistry.com:5000/deom:v1.2
docker push myregistry.com:5000/deom:v1.2

批量推送镜像到私有仓库
docker images | awk ‘{print $1”:”$2}’ | xargs -I {} docker push :/{}

查看私有仓库上的镜像:
http://localhost:5000/v2/_catalog

查看demo镜像的tags
http://localhost:5000/v2/demo/tags/list

docker –debug pull myregistry.com:5000/demo:v1.2

离线安装K3s

  • https://get.k3s.io下载install.sh
  • https://github.com/rancher/k3s/releases下载k3s执行文件,放到/usr/local/bin目录下
1
INSTALL_K3S_SKIP_DOWNLOAD=true ./install.sh

确认k3s是否安装成功

1
systemctl status k3s

配置k3s使用私有镜像仓库

启动时,K3s 会检查/etc/rancher/k3s/中是否存在registries.yaml文件,并指示 containerd 使用文件中定义的镜像仓库。如果你想使用一个私有的镜像仓库,那么你需要在每个使用镜像仓库的节点上以 root 身份创建这个文件。

registries.yaml
1
2
3
4
5
6
mirrors:
docker.io:
endpoint:
- "http://myregistry.com:5000"
rewrite:
"^rancher/(.*)": "mirrorproject/rancher-images/$1"

修改Host,添加myregistry.com的映射:

1
vi /etc/hosts

笔者测试的操作系统是Alibaba Cloud Linux 3.2 104 LTS,修改host就生效了,不需要重启网络。

让Docker支持http, 修改/etc/docker/daemon.json,如果没有则添加一个:

daemon.sh
1
2
3
{
"insecure-registries" : ["myregistry.com:5000"]
}

重启Docker生效:

1
systemctl restart docker

添加镜像到私有镜像仓库#

首先,从https://github.com/rancher/k3s/releases下载获取你正在使用的版本的 k3s-images.txt 文件,然后从 docker.io 中拉取 k3s-images.txt 文件中列出的 K3s 镜像,再将这些镜像重新标记成私有镜像仓库,并推送到私有镜像仓库。

示例:

1
2
3
docker pull docker.io/rancher/coredns-coredns:1.6.3
docker tag coredns-coredns:1.6.3 mycustomreg:5000/coredns-coredns
docker push mycustomreg.com:5000/coredns-coredns

查看私有镜像仓库:

1
curl http://myregistry.com:5000/v2/_catalog

Helm

安装

1
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

添加chart仓库

1
2
3
helm repo add stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo add azure http://mirror.azure.cn/kubernetes/charts
helm repo add bitnami https://charts.bitnami.com/bitnami

常用命令

  • helm repo list 查看chart仓库
  • helm search repo zookeeper 搜索chart

安装zookeeper

1
2
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
helm install zookeeper bitnami/zookeeper --set replicaCount=3 --set auth.enabled=false --set allowAnonymousLogin=true

安装输出:

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
NAME: zookeeper
LAST DEPLOYED: Wed May 31 16:03:48 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: zookeeper
CHART VERSION: 11.4.2
APP VERSION: 3.8.1

** Please be patient while the chart is being deployed **

ZooKeeper can be accessed via port 2181 on the following DNS name from within your cluster:

zookeeper.default.svc.cluster.local

To connect to your ZooKeeper server run the following commands:

export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=zookeeper,app.kubernetes.io/instance=zookeeper,app.kubernetes.io/component=zookeeper" -o jsonpath="{.items[0].metadata.name}")
kubectl exec -it $POD_NAME -- zkCli.sh

To connect to your ZooKeeper server from outside the cluster execute the following commands:

kubectl port-forward --namespace default svc/zookeeper 2181:2181 &
zkCli.sh 127.0.0.1:2181

“手上过”是方言,跟那句“Talk is cheap. Show me the code.”的意思差不多。

本文尝试着用Spring Boot 3.0搭建了一个基本的单体应用,Spring Security结合Json Web Token进行用户认证和授权,Caffeine缓存结合OncePerRequestFilter过滤器来记录访问日志,另外统一了Controller的返回和全局异常处理,并给出了一些扩展点的思路。

希望在后续的学习中可以用这个项目来做验证和测试,也可以作为一些简单项目的脚手架,稍微扩展一下就可以把重点放在业务实现上。

已开源: GitHub-Practice Spring Boot 3.0-Single
上一篇: Spring Boot 3.0 手上过 - GraalVM原生镜像

阅读全文 »

“手上过”是方言,跟那句“Talk is cheap. Show me the code.”的意思差不多。

GraalVM Native Image,原生镜像是独立的可执行镜像,这个独立是指不需要JVM。原生镜像通常具有镜像文件小,启动速度快,内存占用少等特点。这几个特点刚好解决了大规模容器化部署和Serverless应用场景的分发效率、(冷启动)响应速度和资源消耗等问题。

本文使用start.spring.io创建了一个Spring Boot 3.0的项目,并使用GraalVM编译为原生镜像,从构建速度、镜像大小、启动速度和内存占用等方面,跟传统镜像进行了比较。

Spring官方文档: GraalVM Native Image Support

阅读全文 »
0%