-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 38.6 KB
/
content.json
File metadata and controls
1 lines (1 loc) · 38.6 KB
1
{"meta":{"title":"SchoIsles","subtitle":null,"description":null,"author":"SchoIsles","url":"http://blog.ziruqiren.com"},"pages":[],"posts":[{"title":"flannel 多网络切换","slug":"flannel-multi-networks","date":"2018-01-25T09:20:12.000Z","updated":"2018-01-25T12:27:35.000Z","comments":true,"path":"Kubernetes/flannel-multi-networks/","link":"","permalink":"http://blog.ziruqiren.com/Kubernetes/flannel-multi-networks/","excerpt":"起因最近部署一个实例数为 50 的业务时,在创建 Pod 进行到第 41 个时卡住了,查看 kubelet 日志发现是 CNI 调用失败,原因是当前节点的 flannel 没有可用的 IP 地址来分配了。 原理flannel 以 etcd 为元数据存储,在计算节点上启动 agent,从集群网络中申请一个子网,该节点可用的 IP 范围就跟子网大小有关了。 比如我们配置了集群网络为 10.1.0.0/16,子网掩码长度为27,agent(flanneld)申请到的子网类似 10.1.2.32/27 这样,除去 vxlan 设备和 cni 网桥占用的两个,可用 IP 仅仅为 30 个 与 calico 的 agent 可以申请多个子网不同,flannel 的 agent 同时只关联一个子网,agent 启动时从集群读取配置,写入本地文件,内容格式如下 12345% cat /var/run/flannel/subnet.envFLANNEL_NETWORK=10.1.0.0/16FLANNEL_SUBNET=10.1.2.32/27FLANNEL_MTU=1450FLANNEL_IPMASQ=true 在生产环境中,单个节点只跑 30 个容器是巨大的浪费,尤其这些容器占用的 CPU、内存资源并不多,所以我们必须调整子网掩码长度 常规切换方法重新设置 etcd 中 flannel 的配置 12% etcdctl get /coreos.com/network/config{\"Network\":\"10.1.0.0/16\", \"SubnetLen\": 27, \"Backend\": {\"Type\": \"vxlan\"}} 可以执行set操作重写 config,将 SubnetLen 调为合适的值 修改完成后重启 flanneld,将分配新的子网,过程中必定会发生网络中断,因此在重启 flanneld 前,需从 kubernetes 集群下线该节点,让 Pod 主动迁移到其它节点 为什么会造成中断呢?首先 flanneld 必须重启后才能设置新的子网 第一次重启时发现 etcd 里此节点关联了旧的子网,将删除此子网后退出。 第二次启动时申请并设置新的子网。 而其它节点 flanneld 通过 watch etcd 发现一个子网被删后,会更改本地 vxlan 策略或路由规则,到该节点旧子网的请求将不可达,直到该节点的 flanneld 第二次重启成功。 考虑到直接更改子网掩码长度必然会导致网络中断,或者 Pod 迁移,所以有了下面的平滑切换方案。","text":"起因最近部署一个实例数为 50 的业务时,在创建 Pod 进行到第 41 个时卡住了,查看 kubelet 日志发现是 CNI 调用失败,原因是当前节点的 flannel 没有可用的 IP 地址来分配了。 原理flannel 以 etcd 为元数据存储,在计算节点上启动 agent,从集群网络中申请一个子网,该节点可用的 IP 范围就跟子网大小有关了。 比如我们配置了集群网络为 10.1.0.0/16,子网掩码长度为27,agent(flanneld)申请到的子网类似 10.1.2.32/27 这样,除去 vxlan 设备和 cni 网桥占用的两个,可用 IP 仅仅为 30 个 与 calico 的 agent 可以申请多个子网不同,flannel 的 agent 同时只关联一个子网,agent 启动时从集群读取配置,写入本地文件,内容格式如下 12345% cat /var/run/flannel/subnet.envFLANNEL_NETWORK=10.1.0.0/16FLANNEL_SUBNET=10.1.2.32/27FLANNEL_MTU=1450FLANNEL_IPMASQ=true 在生产环境中,单个节点只跑 30 个容器是巨大的浪费,尤其这些容器占用的 CPU、内存资源并不多,所以我们必须调整子网掩码长度 常规切换方法重新设置 etcd 中 flannel 的配置 12% etcdctl get /coreos.com/network/config{\"Network\":\"10.1.0.0/16\", \"SubnetLen\": 27, \"Backend\": {\"Type\": \"vxlan\"}} 可以执行set操作重写 config,将 SubnetLen 调为合适的值 修改完成后重启 flanneld,将分配新的子网,过程中必定会发生网络中断,因此在重启 flanneld 前,需从 kubernetes 集群下线该节点,让 Pod 主动迁移到其它节点 为什么会造成中断呢?首先 flanneld 必须重启后才能设置新的子网 第一次重启时发现 etcd 里此节点关联了旧的子网,将删除此子网后退出。 第二次启动时申请并设置新的子网。 而其它节点 flanneld 通过 watch etcd 发现一个子网被删后,会更改本地 vxlan 策略或路由规则,到该节点旧子网的请求将不可达,直到该节点的 flanneld 第二次重启成功。 考虑到直接更改子网掩码长度必然会导致网络中断,或者 Pod 迁移,所以有了下面的平滑切换方案。 平滑切换最理想的方案是,创建新的 flannel 网络,新创建的 Pod 使用新的 IP,旧的 Pod 继续工作,直到被销毁。 Service LB 层需同时支持到新旧两个网络的转发。 我们知道,flannel 的数据存储在 etcd 集群,但是路径是能够自己指定的,默认情况下使用的是 /coreos.com/network 所以,可以在 flanneld 的启动参数里可以修改主路径,来使用新的网络配置 1flanneld -etcd-prefix=</prefix/to/new> 在配置新的 flannel network 时,有几个要注意的地方 UDP端口修改当 flannel 传输模式设置为 vxlan 时,将封包后使用 udp 协议发往目标主机,默认情况下,此端口为 8472,由内核模块 vxlan 启动监听,flanneld 的退出不会影响主机间的数据交换。 创建新的 flannel 网络时,可以修改 udp 传输端口。更换新配置路径的 flanneld 在启动后,vxlan 模块将监听新的 udp 端口,但旧的端口监听仍在工作,因此来自旧的 flannel 网络的包仍然能正常封包解包。 如果创建新的 flannel network 时未定义新的端口,和旧的 flannel network 共用了默认端口,会是什么样的结果?经测试,来自两个网络的包可以正常发送给相应的 vxlan 设备。 vxlan设备ID修改flanneld 会生成一块 vxlan 设备,配置其 ip 以及 路由,来处理与其子网相关的网络请求,一般看到的设备名为 flannel.1,这跟 etcd 里的主配置相关,vxlan 属性 VNI 默认为1 想要在同一台主机启动 2 个 flanneld,需保证 VNI 不冲突,否则后起的 flanneld 会重写设备 ip,导致前一个 flannel 网络不可用 在 Service LB 节点启动两个 flanneld,建立 2 个 vxlan 设备 flannel.1 和 flannel.2,互不干扰 CNI设备ID修改kubernetes 通过调用网络插件来给容器设置网络 首先 flanneld 申请到子网后,会写配置到 /run/flannel/subnet.env flannel cni 插件读取配置 /run/flannel/subnet.env,来获取网络、子网、MTU等参数,创建网桥,默认名 cni0,分别绑定 veth pair 到容器以及bridge 创建新的 flannel 网络后,势必需要创建新的网桥,新启动的 Pod 将接入到新的网桥,即新的 flannel 网络。 性能优化flannel 支持 udp、vxlan、host-gw 三种方式在节点间传输数据 udp 模式可靠性、性能都不强,适合用来做测试 vxlan 模式,只要宿主机 IP 互相可达,即可使用,但性能一般 host-gw 模式,学习了 calico 的主机路由,性能最好,但需要所有节点在同一个物理子网内 现在 0.9.0 版本的 vxlan 模式,支持参数 DirectRouting。如果要路由的目标宿主机和自己是同一个物理子网,不再做 vxlan 封包,直接通过主机路由出去。 比如有4个节点 名称 宿主机IP flannel subnet A 172.16.0.1/24 10.1.2.0/25 B 172.16.0.2/24 10.1.8.128/25 C 172.16.8.1/24 10.1.2.128/25 D 172.16.8.2/24 10.1.4.0/25 对于节点 A 来说,节点 B 和自己是二层直达的,节点 B 的 flannel subnet 通过主机路由走即可.节点 A 会在本机添加路由 1route add -net 10.1.8.128 netmask 255.255.255.128 gw 172.16.0.2 而去往 10.1.2.128/25 和 10.1.4.0/25 的,由于其宿主机的 IP 属于另一个网络,无法作为gateway,只能通过 udp 封包传输 切换步骤禁止 arp 老化切换 flannel 网络后,旧的业务仍然能使用设备 flannel.1 与其它节点通讯。但是,当其它节点的 flanneld 停止后,本地记录的 arp 老化后无法更新,就无法将包发给该节点了。 执行下面的命令,将 flannel.1 的 arp 老化时间设为 0 1echo 0 > /proc/sys/net/ipv4/neigh/flannel.1/gc_stale_time 创建新的 flannel 配置定义 etcd 配置路径格式为 /flannel/network{id} 本次以 2 为 id,即 /flannel/network2 vxlan 设备 ID 设置为2,即 VNI = 2 udp 端口自增,即 8473 开启 DirectRouting 设定网络为 192.168.0.0/16,子网掩码长度 26,即每个主机最多可分配 64 - 2 = 62 个 IP 1etcdctl set /flannel/network2/config '{\"Network\":\"192.168.0.0/16\", \"SubnetLen\": 26, \"Backend\": {\"Type\": \"vxlan\", \"DirectRouting\": true, \"VNI\": 2, \"Port\": 8473}}' 下载新版 flanneld所有 flannel 节点 12curl http://metadata.svc.17paipai.cn/k8s/flanneld-v0.9.1 -o /opt/bin/flanneld-v0.9.1chmod +x /opt/bin/flanneld-v0.9.1 Service LB节点启动新的 flanneld 实例复制systemd配置1cp /lib/systemd/system/flanneld.service /lib/systemd/system/flanneld2.service 更改配置,调整启动参数,-subnet-file /run/flannel/subnet2.env -etcd-prefix=/flannel/network2 修改以使用指定的 flanneld 版本启动1systemctl start flanneld2 ####检查 监听了 udp 端口 8472 8473 ip link列表有 flannel.1 flannel.2 路由表,防火墙规则都有添加 检查网络互通是否正常 记得在切换完毕后将 flanneld2 设置为开机启动 计算节点切换暂停 kubernetes 调度避免节点切换网络过程中,创建的新 Pod 失败 1kubectl cordon <node> 更改 flanneld 配置编辑 /lib/systemd/system/flanneld.service 修改 etcd-prefix=/flannel/network2 与 service LB 节点上启动两个 flanneld 不同,计算节点只需启动一个,并且覆盖默认的本地配置文件 /run/flannel/subnet.env,供 cni 插件使用 重启12systemctl daemon-reloadsystemctl restart flanneld 检查 监听了 8472 8473 端口 ip link 有 flannel.1 flannel.2 两设备 更改 cni 配置编辑 /etc/cni/net.d/10-flannel.conf,设置 bridge 名称为 cni2 12345678{ \"name\": \"flannel\", \"type\": \"flannel\", \"delegate\": { \"isDefaultGateway\": true, \"bridge\": \"cni2\" }} 开启 kubernetes 调度1kubectl uncordon <node> 结语如此一来,计算节点上新创建的 Pod 会使用新的新的 flannel 网络进行通讯,之前创建的 Pod 可以继续使用旧的 flannel 网络,当这些旧的 Pod 全部销毁后,旧的设备 flannel.1 和 cni0 都可以删除了,还有 iptables nat 表里的规则。 或者当节点重启后,flanneld 启动后只创建新的网络,Pod 也只会属于新的网络,旧的网络不再需要关心。 当全部计算节点销毁了旧的 Pod 后,Service LB 层的旧 flanneld 进程就可以退出了,与其关联的配置也可以清除了。","categories":[{"name":"Kubernetes","slug":"Kubernetes","permalink":"http://blog.ziruqiren.com/categories/Kubernetes/"}],"tags":[{"name":"network","slug":"network","permalink":"http://blog.ziruqiren.com/tags/network/"},{"name":"flannel","slug":"flannel","permalink":"http://blog.ziruqiren.com/tags/flannel/"}]},{"title":"kubernetes 业务平滑升级","slug":"kubernetes-app-graceful-update","date":"2018-01-18T02:26:23.000Z","updated":"2018-01-22T02:50:08.000Z","comments":true,"path":"Kubernetes/kubernetes-app-graceful-update/","link":"","permalink":"http://blog.ziruqiren.com/Kubernetes/kubernetes-app-graceful-update/","excerpt":"Kubernetes 提供了 Deployment 来完成对应用的滚动升级,升级过程中,会先创建新版本的 replicaSet,初始复制个数设为 0,按一定比例增加新版本 replicaSet 的复制个数,成功后减小旧版本 replicaSet 的复制个数,此消彼长,直到新版本 replicaSet 的复制个数达到此 Deployment 设定值,旧版本 replicaSet 的复制个数变为0。 Service 通过标签选择器同时选中新旧两个版本 replicaSet 里的 Pod,不断更新其 Endpoint 列表,从而保证向外暴露出的服务总是可用的。 与此同时,如果 Pod 设置了健康检查项,将能够保证 Endpoint 列表的可靠性。通过 readiness 检查来决定是否要将节点从 Endpoint 列表移除或添加,而 liveness 检测会决定是否要重启此容器。 回想一下传统的部署升级方式,最原始的是停止程序后更新代码再启动,优雅一些的则会在更新代码后派生出新的子进程来工作,更高级的还会在程序运行过程中动态更新模块,甚至更换自身二进制文件,有时候还需要预先在 LB 层下线节点,升级完成后再上线。 这几种方式有着共同的目标 尽可能保证服务不中断 提供服务的实例,身份不改变(比如 ip:port) 而在进入云时代,一切都变了。 不再原地重起实例,一个实例的状态只有启动和退出 实例是全新的,尤其是 IP,无论是以注册方式,还是 LB 反向代理方式来提供服务,实例的身份已经发生了变化,必须及时更新 因此引出两个需求: 实例退出时需优雅关闭 LB 层需预先下线节点 一些长连接类型的服务,一般支持在优雅退出的同时,让客户端重连其它节点,比如 Dubbo。另外 Dubbo 是通过注册来实现动态服务发现的,服务之间相互调用是通过内部软负载均衡直连,不需要借助 Kubernetes 来实现。 其它一些服务,web 类型,自身只实现了优雅重启,并未考虑优雅退出,如 Python 的 Gunicorn, PHP 的 php-fpm,还有 uWSGI,在关闭时比较粗暴,不顾未完成的任务,直接关闭子进程。php-fpm 的介绍中虽然说能接收 SIGQUIT 来完成优雅关闭,但实测效果并不理想。","text":"Kubernetes 提供了 Deployment 来完成对应用的滚动升级,升级过程中,会先创建新版本的 replicaSet,初始复制个数设为 0,按一定比例增加新版本 replicaSet 的复制个数,成功后减小旧版本 replicaSet 的复制个数,此消彼长,直到新版本 replicaSet 的复制个数达到此 Deployment 设定值,旧版本 replicaSet 的复制个数变为0。 Service 通过标签选择器同时选中新旧两个版本 replicaSet 里的 Pod,不断更新其 Endpoint 列表,从而保证向外暴露出的服务总是可用的。 与此同时,如果 Pod 设置了健康检查项,将能够保证 Endpoint 列表的可靠性。通过 readiness 检查来决定是否要将节点从 Endpoint 列表移除或添加,而 liveness 检测会决定是否要重启此容器。 回想一下传统的部署升级方式,最原始的是停止程序后更新代码再启动,优雅一些的则会在更新代码后派生出新的子进程来工作,更高级的还会在程序运行过程中动态更新模块,甚至更换自身二进制文件,有时候还需要预先在 LB 层下线节点,升级完成后再上线。 这几种方式有着共同的目标 尽可能保证服务不中断 提供服务的实例,身份不改变(比如 ip:port) 而在进入云时代,一切都变了。 不再原地重起实例,一个实例的状态只有启动和退出 实例是全新的,尤其是 IP,无论是以注册方式,还是 LB 反向代理方式来提供服务,实例的身份已经发生了变化,必须及时更新 因此引出两个需求: 实例退出时需优雅关闭 LB 层需预先下线节点 一些长连接类型的服务,一般支持在优雅退出的同时,让客户端重连其它节点,比如 Dubbo。另外 Dubbo 是通过注册来实现动态服务发现的,服务之间相互调用是通过内部软负载均衡直连,不需要借助 Kubernetes 来实现。 其它一些服务,web 类型,自身只实现了优雅重启,并未考虑优雅退出,如 Python 的 Gunicorn, PHP 的 php-fpm,还有 uWSGI,在关闭时比较粗暴,不顾未完成的任务,直接关闭子进程。php-fpm 的介绍中虽然说能接收 SIGQUIT 来完成优雅关闭,但实测效果并不理想。 而 nginx 在这方面做的很好,它也是通过接收 SIGQUIT 信号来处理,但能保证在关闭过程中停止监听,不再接受新的请求,空闲子进程直接关闭,活动子进程在任务完成后再关闭。 为了演示,下面我们创建一个 Django 项目,编写两个 View,一个快速返回,另一个延迟一段时间再返回代码如下 views.py 123456789101112import timefrom django.views.generic import Viewfrom django.http.response import HttpResponseclass NormalView(View): def get(self, request, *args, **kwargs): return HttpResponse(\"OK\")class SleepView(View): def get(self, request, *args, **kwargs): time.sleep(10) return HttpResponse(\"Sleep finished\") urls.py 123456......urlpatterns = [ url(r'^$', NormalView.as_view()), url(r'^sleep$', SleepView.as_view()),] 这样,我们得到了两个测试链接,当访问 / 时会立即响应,而当访问 /sleep 时会等待 10s 后才返回 无论是用 Django 内置 manager、Gunirorn、或是 uWSGI 来启动 webserver,在客户端使用 curl 请求 /sleep 链接时,使用 Control + C 中断,或是发送 SIGINT、SIGTERM、SIGQUIT 等信号,都会使 webserver 退出的同时,客户端 curl 中断。Gunicorn、uWSGI 只在平滑重启时才会关心旧的子进程是否有活动连接。 而 nginx 在这方面做的很完善,支持收到 SIGQUIT 信号(kill -3)后平滑关闭,在此过程中 master 进程关闭了监听,并向所有子进程发送 SIGQUIT 信号 关闭 idle 状态的连接 等待 active 状态的连接关闭 关闭空闲的子进程 待所有子进程退出后,关闭 master 进程 因此,在容器环境中,nginx 是用来保障容器退出时不中断业务的最佳方案,我们需要更改容器运行脚本,优化退出步骤,截断容器停止信号 SIGTERM,向 nginx 发送 SIGQUIT,10 次检查仍未完全关闭时,再强制杀掉 nginx, 测试 demo 脚本如下 123456789101112131415161718192021222324252627282930313233343536373839#!/bin/bashif [ \"$1\" == \"bash\" ];then exec /bin/bashfiGUNICORN_PID='/tmp/gunicorn.pid'graceful_exit() { echo \"graceful stop nginx\" nginx -s quit for ((i=10;i--;i>=0)) { pkill -0 nginx if [ $? -ne 0 ];then break else echo \"Check nginx master is graceful exited. The number of remaining retries: $i\" sleep 1 fi if [ $i -eq 1 ];then echo \"kill nginx master\" pkill -9 nginx fi } echo \"kill gunicorn\" kill -2 `cat $GUNICORN_PID` exit}trap 'echo \"receive SIGINT\";graceful_exit;exit' SIGINT SIGTERMnginx -t && nginxgunicorn p0.wsgi --daemon -b 127.0.0.1:8000 --pid $GUNICORN_PID --max-requests=5000 --workers=4 --log-file - --access-logfile - --error-logfile -mkfifo /tmp/blockexec 3<> /tmp/blockread s <&3 这样一来,发起一个请求 curl <ip:port>/sleep 后, 向容器发送 docker stop 指令,新的请求会失败,报 Connection refused,已发起的请求在 10s 内成功返回后,nginx 停掉了最后一个 worker,关闭主进程,容器退出。demo 里没有去管 Gunicorn 的关闭,而是让操作系统自行处理,生产环境中应该考虑的更严谨一些。 在完成了容器的优雅退出后,还要考虑另外一个问题,如果用了 ipvs 实现 L4 LB,在容器内服务关闭端口监听后,如果 Service 层未及时通知 ipvs 将该节点下线,必然会导致一部分请求继续发给此节点,而一般客户端没有做重连,将会直接返回网络失败,假如还配置了 TCP session affine,那某个用户就悲剧了。 因此,在关闭容器前,最好能先从节点列表里清除,而容器启动后,在健康检查确认后再加入列表。 需要知道的是,ipvs 去掉一个后端节点,并不会造成已建立的连接中断,所以,我们可以放心地通知 ipvs 删除此节点,与此同时,我们需要先在 ipvs 层删除此节点后,再真正地通知程序退出。 Service 是逻辑概念,现在已经有一些项目(如 kube-proxy、kube-router,traefik)通过 watch 集群中 Service、Pod、Endpoint 的变化来实现服务的动态发现。在实施过程中,是否能达到完美的平滑升级? 在 Pod 的生命周期内,有着一系列的事件(event)发生,如创建、更新、删除,但这些事件具体发生在哪个时间点呢?比如: Pod 创建,是发生在创建前还是创建成功后,readiness 在何时检测成功将其加入 Endpoint 列表? Pod 删除,是发生在删除指令发出后,还是 Pod 已经被删除,是否会跳过 readiness 直接将其移出 Endpoint 列表? 因此,在实际环境中使用前需要确认这些问题 Pod 生成时,发出了哪些事件,状态变化经历了哪些阶段,健康检查何时发生,Endpoint 何时添加 Pod 销毁时,发出了哪些事件,是否保证了停止过程中不会有新的请求过来,Endpoint 移除是健康检查的结果还是 Pod 销毁事件直接触发的 readiness检测成功后,Pod 状态发生了怎样的变化,Endpoint 的更新发生在什么时候,kube-dns的缓存如何更新?","categories":[{"name":"Kubernetes","slug":"Kubernetes","permalink":"http://blog.ziruqiren.com/categories/Kubernetes/"}],"tags":[{"name":"Kubernetes","slug":"Kubernetes","permalink":"http://blog.ziruqiren.com/tags/Kubernetes/"}]},{"title":"toa内核模块","slug":"toa-kernel-module","date":"2017-07-23T12:19:28.000Z","updated":"2018-01-25T06:04:34.000Z","comments":true,"path":"lvs/toa-kernel-module/","link":"","permalink":"http://blog.ziruqiren.com/lvs/toa-kernel-module/","excerpt":"安装测试环境 centos 7.3 内核版本 3.10.0-514 安装编译环境 1yum install bc xmlto asciidoc hmaccalc python-devel newt-devel pesignzlib-devel audit-libs-devel numactl-devel pciutils-devel ncurses-devel pesign elfutils-devel binutils-devel perl-ExtUtils-Embed rpm-build 编译内核模块准备内核源码创建builder用户useradd builder 下载并安装当前内核版本的src包以及devel包 123456wget http://vault.centos.org/7.3.1611/os/x86_64/Packages/kernel-devel-3.10.0-514.el7.x86_64.rpmrpm -ivh kernel-devel-3.10.0-514.el7.x86_64.rpm su - builderwget http://vault.centos.org/7.3.1611/updates/Source/SPackages/kernel-3.10.0-514.2.2.el7.src.rpmrpm -ivh kernel-3.10.0-514.2.2.el7.src.rpm 在builder家目录下产生了rpmbuild目录 释放内核源码到rpmbuild/BUILD目录 123rpmbuild -bp rpmbuild/SPECS/kernel.specls ~/rpmbuild/BUILD/kernel-3.10.0-514.2.2.el7/ 打补丁在taobao的toa.patch(1.0.0.0)基础上,去掉ipv6支持,适配centos7内核,见附件","text":"安装测试环境 centos 7.3 内核版本 3.10.0-514 安装编译环境 1yum install bc xmlto asciidoc hmaccalc python-devel newt-devel pesignzlib-devel audit-libs-devel numactl-devel pciutils-devel ncurses-devel pesign elfutils-devel binutils-devel perl-ExtUtils-Embed rpm-build 编译内核模块准备内核源码创建builder用户useradd builder 下载并安装当前内核版本的src包以及devel包 123456wget http://vault.centos.org/7.3.1611/os/x86_64/Packages/kernel-devel-3.10.0-514.el7.x86_64.rpmrpm -ivh kernel-devel-3.10.0-514.el7.x86_64.rpm su - builderwget http://vault.centos.org/7.3.1611/updates/Source/SPackages/kernel-3.10.0-514.2.2.el7.src.rpmrpm -ivh kernel-3.10.0-514.2.2.el7.src.rpm 在builder家目录下产生了rpmbuild目录 释放内核源码到rpmbuild/BUILD目录 123rpmbuild -bp rpmbuild/SPECS/kernel.specls ~/rpmbuild/BUILD/kernel-3.10.0-514.2.2.el7/ 打补丁在taobao的toa.patch(1.0.0.0)基础上,去掉ipv6支持,适配centos7内核,见附件centos 7.3 toa补丁 添加配置12echo -e '\\n# toa\\nCONFIG_TOA=m' >> .configcp /usr/src/kernels/`uname -r`/Module.symvers . 编译12345make oldconfigmake preparemake modules_prepare make M=net/toa 加载toa模块cp net/toa/toa.ko /tmp 切换到root用户, 加载模块123su - root insmod /tmp/toa.ko 持久化123cp /tmp/toa.ko /lib/modules/`uname -r`/extra/depmod -aecho toa > /etc/modules-load.d/toa.conf reboot参考资料 http://blog.51cto.com/shanks/1393434 http://www.hl10502.com/2017/09/14/centos-build-nbd/ http://blog.csdn.net/wwyyxx26/article/details/6325843 https://github.com/alibaba/ali_kernel_rpm/blob/master/patches.taobao/toa.patch 原理分析fullnat 发给 realserver 的 tcp 包添加了一个 OPTION,用于传递客户端真实地址,real server 通过 toa 模块读取此 OPTION,更改 socket 属性, 使应用层能读取真实的客户端ip端口 toa option数据结构定义1234567struct toa_data{ __u8 opcode; __u8 opsize; __u16 port; __u32 ip;} tcpdump抓包后,由wireshark分析,tcp封包细节如图 可以看到,经lvs-fullnat模式封包后, TCP包里新增了一个option, code 为 c8,转十进制后为 200 08 为 size,接下来读取后 6 个字节 前两个字节 c3 f4 为端口号,转码后int(‘0xc3f4’, 16)后为 50164 后4个为ip地址,分别转码后得到 10 211 55 2 于是得到来源客户端为 10.211.55.2:50164 toa模块是如何处理这个option的?当 socket 执行 accept 操作建立连接后,程序可以使用getpeername函数来得到连接对端的 ip 和端口,内核源码里最终是调用了 inet_getname 函数 原本此函数返回的是 L3 层的 source ip,和 L4 层的 source port,但是toa 模块修改了此行为,它从 L4 层取出上游服务写入的tcp option,找到 ip 和 端口,取代了返回结果。 替换 inet_getname 函数首先,toa 定义了一个函数 inet_getname_toa 来替代默认的 inet_getname 函数 1inet_stream_ops_p->getname = inet_getname_toa; inet_getname_toa函数首先调用默认的inet_getname,得到结果后再做额外的处理 检测socket结构体中sk_user_data是否为空,如果不为空,读取其内容 判断是否为toa option,如果是,解析出ip和端口,替换inet_getname函数的返回结果 sk_user_data(RPC layer private data)又是如何设置的?同样的,toa模块定义了函数tcp_v4_syn_recv_sock_toa,替换了默认的tcp_v4_syn_recv_sock 1ipv4_specific_p->syn_recv_sock = tcp_v4_syn_recv_sock_toa; 此函数首先调用默认的tcp_v4_syn_recv_sock,得到 socket,遍历tcp options,如果找到toa option(opcode==200),解析其内容,得到源ip和端口信息,写入此socket结构体的属性sk_user_data中,返回 socket, 然后应用程序通过getpeername方法就能获得此 socket 连接的来源ip和端口了","categories":[{"name":"lvs","slug":"lvs","permalink":"http://blog.ziruqiren.com/categories/lvs/"}],"tags":[]},{"title":"ASCII、Unicode、GBK 和 UTF-8 字符编码的区别联系","slug":"character-difference","date":"2017-05-24T07:07:05.000Z","updated":"2018-01-25T06:04:26.000Z","comments":true,"path":"转载/character-difference/","link":"","permalink":"http://blog.ziruqiren.com/转载/character-difference/","excerpt":"文章转载自 月纷悦ASCII、Unicode、GBK 和 UTF-8 字符编码的区别联系 很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为”字节“。再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机“。 ASCII开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上0×10, 终端就换行,遇上0×07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。他们看到这样很好,于是就把这些0×20以下的字节状态称为”控制码”。他们又把所有的空 格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉 很好,于是大家都把这个方案叫做 ANSI 的”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用 127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128 到255这一页的字符集被称”扩展字符集“。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧! GB2312等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312“。GB2312 是对 ASCII 的中文扩展。","text":"文章转载自 月纷悦ASCII、Unicode、GBK 和 UTF-8 字符编码的区别联系 很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为”字节“。再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为”计算机“。 ASCII开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上0×10, 终端就换行,遇上0×07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。他们看到这样很好,于是就把这些0×20以下的字节状态称为”控制码”。他们又把所有的空 格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉 很好,于是大家都把这个方案叫做 ANSI 的”Ascii”编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用 127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128 到255这一页的字符集被称”扩展字符集“。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧! GB2312等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312“。GB2312 是对 ASCII 的中文扩展。 但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS“(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣 们都要每天念下面这个咒语数百遍: “一个汉字算两个英文字符!一个汉字算两个英文字符……” 因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个”汉字系统”,专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么”倚天汉字系统”才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊! unicode正在这时,大天使加百列及时出现了——一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号 的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “unicode“。 unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些“半角”字符,unicode包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。 这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从unicode开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的”一个字符“!同时,也都是统一的”两个字节“,请注意”字符”和”字节”两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在unicode中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。 unicode同样也不完美,这里就有两个的问题,一个是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍,这是难以接受的。 UTF-8unicode在很长一段时间内无法推广,直到互联网的出现,为解决unicode如何在网络上传输的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8就是在互联网上使用最广的一种unicode的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。 UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度,当字符在ASCII码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是unicode一个中文字符占2个字节,而UTF-8一个中文字符占3个字节)。从unicode到uft-8并不是直接的对应,而是要过一些算法和规则来转换。 Unicode符号范围 UTF-8编码方式 (十六进制) (二进制) 0000 0000-0000 007F 0xxxxxxx 0000 0080-0000 07FF 110xxxxx 10xxxxxx 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx","categories":[{"name":"转载","slug":"转载","permalink":"http://blog.ziruqiren.com/categories/转载/"}],"tags":[]}]}