
能力说明:
深度理解Python的语法与数据类型知识,对运算符、控制语句、列表、元组、字典的应用等具有清晰的认知。理解Flask、Django等Web开发框架的原理、构建方法,掌握利用Python爬虫技术与常用工具进行数据收集的应用能力。
能力说明:
熟练掌握Linux常用命令、文件及用户管理、文本处理、Vim工具使用等,熟练掌握企业IP规划、子网划分、Linux的路由、网卡、以及其他企业级网络配置技术,可进行Web服务器(Nginx),以及数据库(My SQL)的搭建、配置、应用,可根据需求编写Shell脚本,通过常用工具进行linux服务器自动化运维。
暂时未有相关云产品技术能力~
阿里云技能认证
详细说明探路 前篇列出的都是缓兵之计, 大部分问题还是存在, 在前几个月懵逼和饱受白名单问题煎熬后, 慢慢打起了vpc迁移的算盘. 这个迁移很大的一个问题就是, 如何将VPC和经典资源打通呢? VPC经典互通的第一次尝试 在16年底, 风控准备上第一版服务, 将服务放在了vpc内. 以此为契机尝试打通vpc和经典的服务,但是这个时候阿里云vpc刚上没多久, 基本没有任何对这方面的支持. 第一次尝试方案如下 在vpc内创建了一个openvpn server把端口暴露给经典网络 在经典环境以一台空闲节点作为client保持和openvpn server的连接 vpn server给经典client下发vpc网段路由下一跳指向vpn server 在vpn server上对vpn和经典网段分别进行SNAT vpc路由器上将经典网段的10路由下一跳指向vpn server 经典节点上对vpc和vpn网段SNAT 其他的经典节点由于路由不可控无法主动访问vpc, 经典client节点可以openvpn server的身份访问其他vpc所有节点.这样经典client的节点相当于和vpc完全互通了.而vpc内的节点则可以通过vpn client访问所有经典节点, 由于是在路由器上加的路由这个访问是对于vpc节点来说是无需配置, 无感知的. 不过由于阿里云有一个神奇的100.64.0.0/10网段(RDS,DRDS,redis,部分SLB), 这个路由是无法控制的, 为了让vpc能访问到阿里云的这些资源, 只能不停在将各种不同的资源在经典的vpn client做DNAT.DNAT规则大致如下. firewall { '100 snat for vpn': chain => 'POSTROUTING', jump => 'SNAT', proto => 'all', source => '192.168.0.0/24', table => 'nat', tosource => '10.165.69.144', } firewall { '101 snat for vpn': chain => 'POSTROUTING', jump => 'SNAT', proto => 'all', source => '172.31.255.0/24', table => 'nat', tosource => '10.165.69.144', } firewall { '102 dnat for drds xxx': chain => 'PREROUTING', table => 'nat', jump => 'DNAT', source => '!10.0.0.0/8', destination => '10.165.69.144', proto => 'tcp', dport => '3306', todest => '100.98.69.136', } firewall { '103 dnat for rds xxx.mysql.rds.aliyuncs.com': chain => 'PREROUTING', table => 'nat', jump => 'DNAT', source => '!10.0.0.0/8', destination => '10.165.69.144', dport => '3307', proto => 'tcp', todest => '100.114.46.162:3306', } firewall { '104 dnat for redis xxxx.m.cnbja.kvstore.aliyuncs.com': chain => 'PREROUTING', table => 'nat', jump => 'DNAT', source => '!10.0.0.0/8', destination => '10.165.69.144', proto => 'tcp', dport => '6379', todest => '100.99.94.201', } firewall { '105 dnat for slb xxx': chain => 'PREROUTING', table => 'nat', jump => 'DNAT', source => '!10.0.0.0/8', destination => '10.165.69.144', proto => 'tcp', dport => '80', todest => '100.114.31.148', } 整个结构的简单图示: 这样勉强达到了互通的要求, 但是问题也很明显, vpn client和vpn server都是单点的. 期间分别准备好了备机以防止意外随时切换, 这种状态跑了半年, 除了有时经典节点公网带宽跑满, 并未出现过其他问题, 升级过带宽后也好了. iptables的转发还是很给力的, 不过现在阿里云在互通方面做了很多支持,没必要这样做了. 正式上路 阿里云推出经典与vpc互联支持后 阿里云文档: https://help.aliyun.com/document_detail/55051.html 主要支持了 ClassicLink, 混访混挂, 单ECS迁移, 既有的java应用也是在推出上述实际支持后开始的. 迁移问题 提前规划 迁移到vpc后, 自然不希望再和以前一样看着经典ip一脸懵逼. 希望看到ip就能知道是哪个集群里的, 策略可以直接根据预先设定的网段来设置. 下面是之前规划时整理的部分文档(2017年4月写的, 具体请以阿里云最新文档为准) 限制: 注意事项: VPC 的交换机是一个 3 层交换机,不支持 2 层广播和组播。 每个交换机的第1个和最后3个 IP 地址为系统保留地址。以 192.168.1.0/24 为例,192.168.1.0、 192.168.1.253、 192.168.1.254 和 192.168.1.255 这些地址是系统保留地址。 专有网络内的 ECS 使用安全组防火墙进行三层网络访问控制。 划分原则 每个分组对应至少一个或多个(可用区)交换机, 每个交换机绑定至少一个分组命名的安全组. 每个分组对应至少一个分组命名的容器集群. 每个分组对应单独的SNAT表或者SNAT IP POOL. 每个分组之间默认完全隔离. 由安全组控制访问.访问 每个分组保证至少2个可用区,核心业务三个可用区. 以可用区字母结尾, 例如 common-a 表示common分组, A可用区 生产和测试vpc独立, 完全隔离 其实不太建议用 192.168.0.0/16 网段, 可用ip相对会少一些, 而且后续如果办公网或者经典互联容易出现问题. 路由问题 这里需要注意2个问题 默认情况路由表条目是有限制的, 默认是48, 如果后续容器化(阿里云为了打通容器网段, 会在路由表上自动添加路由条目, 容器网段下一跳指向对应宿主机节点), 很容易达到限制配额. 可以手动控台申请配额, 或者工单申请. 正常情况classiclink后, 经典网络节点可以直接访问vpc节点, 但是如果vpc网段是 192.168.0.0/16 就比较特殊了, 需要手动在经典节点上加下路由. 脚本参考这里 添加路由 新老服务交互 我们应用主要是dubbo + http方式来进行应用间的互相访问, 涉及数十个应用, 不会是一波进去, 就会存在一段时间需要vpc和经典互访的情况. dubbo服务 利用classic将zk集群和provider端链入vpc vpc内新建一个zk集群 consumer端先迁入, 同时注册新老zk集群, 因为后续使用的容器化, 直接在启动entrypoint的脚本里做一些处理即可, 应用无需改动. # add dubbo vpc registry # find /data/ -name "*.xml" -exec grep -Hn 'registry' {} \; | awk -F: '{print $1}' | xargs -i -t sed -i -r '/zookeeper.vpc.cluster/d' {} # find /data/ -name "*.xml" -exec grep -Hn 'registry' {} \; | awk -F: '{print $1}' | xargs -i -t sed -i -r '/registry/{h;s/dubbo:registry/& id="vpc"/;s/(address=")(.*)(")/\1${zookeeper.vpc.cluster}\3/;G}' {} 后续provider迁入, 同理. 直至相关服务迁移完成后将主zk直接指向vpc的zk集群, 老zk集群下线. http服务 这个比较简单了, 因为我们内部服务也通过slb暴露, 这里主要通过slb混挂来做. vpc内起一个新的服务, 将相关节点挂入原有的经典内网slb. 调整权重, 观察服务正常逐渐替换老的节点即可. 一般会在vpc内也再开一个vpc内网slb提供vpc内调用, 后续迁移完成后确认老的经典slb无流量后释放即可. 公网slb其实同理. 不过由于在迁移vpc同时进行了容器化, 这里稍微有些处理(下篇专门介绍容器化会说到) 阿里云相关资源调用 后续迁移过程中阿里云其实都已经提供了相当方便的支持, 在这之前我们都是使用的相当粗暴的走公网链接方式(很不推荐, 受公网带宽限制, 而且有一定风险, 虽然traceroute测试下来公网路径貌似都是经过的阿里云节点,但终究心里没底) oss, ots, ons等等直接换成vpc的endpoint即可 drds,rds, redis稍微麻烦一点, 需要迁移vpc后选择保留经典链接, 最多可以同时维持vpc,经典内网,公网三个链接地址, 而且经典地址保留时间可以工单延长. vpc内的应用直接使用vpc内网地址即可. 基本上在阿里云实际提供支持后, vpc和经典网络这块并没有太多的障碍, 主要根据应用状态, 逐个迁移观察, 就是一些细心的活了. 更多踩到的坑还是在容器化中, 下篇继续介绍vpc迁移同时容器化踩到的一些坑.
前言 距离vpc和容器化过去了快一年, 一直想要完整回顾梳理下整个过程, 最近准备进行swarm->kubernetes的二次迁移, 正好借由这次契机重新回顾下这段历从最初原始时代到vpc,swarm容器化到k8s的经历. 原始时代 16年7月从上家游戏公司离职, 来到了目前的互金公司, 成为唯一的运维, 此时公司java开发人数已经有几十人... 运维的技术栈也由php转移到了java, 刚开始的时候有些不适应和孤独感(只有自己一个运维, 交流主要就是招我进来同时也是前期负责运维这块的CIO, 刚开始下发完任务后交流也不多), 花了一段时间适应和熟悉后, 感觉问题颇多. 现状是开发同学有很多, 但是运维就一个, 初期运维这块内容由CIO兼顾, 同时CIO还是整个架构的主导者和主力程序员. 已有的100多个ECS由puppet下发配置, puppet agent默认半小时刷新一次.应用类型主要为spring + dubbo, tomcat + nginx 比较关键的几个问题如下 经典网络问题: 公司所有计算资源均在阿里云上, 不过阿里云当时并未推出vpc, 所有资源都在经典网络, 直接导致了如下的问题 安全问题: 经典网络各租户ip之间并不能保证真正的隔离, 有一定风险, 不过CIO安全公司出身, 很强烈的安全意识, 所有ECS都开启了iptables, 只开放了自有节点的访问, 规则由puppet统一下发, 但是agent半小时才会自动刷新, 过程并无任何监控, 是否生效完全随缘, 而且puppet主机列表是手动维护的一个list文件, 漏没漏很难说. 网段问题: 节点都是阿里云自动分配A段的随缘ip, 一个节点一个公网ip, 意味着出口ip各不相同, 新增一个节点, 要加一堆白名单(rds,drds,slb,nas等等),内部内网应用等资源还好说, 调用阿里云api还可以自动操作下, 要是涉及到第三方要报备的时间就很难控制了, 这个导致我们新增节点非常痛苦, 加一次白名单就很要命了, 只能提前部署好一些备机以防万一,平时在备机上跑一些不太重要的任务. 项目部署, 有一个专门的编译服务器, 上面分了testbuilder和pordbuilder的用户, 对应测试和线上的构建用户, 开发同学自己登陆到编译服务器用脚本编译和拷贝到NFS目录, 然后到对应的测试或线上服务器一台一台的用NFS中的部署脚本部署 各种冲突: lib冲突: java同学抽出来的lib包很多, 虽然有maven私服, 但是所有java的lib包全部都是SNAPSHOT版本, 而且基本没有上传私服的习惯, 一旦有公共lib改动在测试环境编译了, 很容易就会导致其他开发同学躺枪. 测试环境的资源冲突, 测试环境一共只有有4~5台服务器, 各种tomcat和端口满天飞, 根本无法识别, 该起的没起来, 本来应该停了的跑起来了. 代码冲突: 部署脚本都是自己维护, 没有统一模板, 内容千奇百怪, 分支切来切去, 而且都是用的testbuilder和prodbuilder用户, 完全不知道谁之前到底干了啥, 代码冲突也经常发生, 维护完全靠自觉. 发布太随意: 开发同学自己掌控线上服务器部署能力, 有时候发布略随意, 不吭声的把未充分测试代码发布到线上导致问题, 事后出现异常才体现出来 环境不一致: lib包繁多并且都是SNAPSHOT版本,如果涉及改动的lib太多,测试环境测试正常版本的lib状态,到线上编译的时候不一定和测试一致,就会导致报错. 发布时间太长: web基本项目都使用了slb, 部署的流程则是, slb权重调零->跑NFS里的部署脚本(停止应用, 备份老的代码, 从NFS复制新的代码, 启动应用)->挂回SLB,节点多的部署一次可能半小时就没了, 万一要是还有问题需要回滚, 或者高频次发布, 完全就耗在这种重复的事情上了. 基础服务版本差异: 由于没有标准化模板, tomacat或者nginx之类的基础服务的版本也是千奇百怪, 当中配置文件的差异就更是玄学了 权限控制: 服务器权限: 有一个现成的中央跳板机, 每个开发同学有一个自己的账号, 应用服务器则是统一使用的xxxx用户, 能否登陆应用服务器, 取决于应用服务器上是否加了跳板机上对应开发的key,开发同学登陆到应用节点后基本就不可控了, 都是同一个账号, 无法区分谁干了啥. 而且当时puppet里面配置的分组只有测试和线上, 要是加一下登陆的key整个线上环境节点的登陆等于全开放了 数据库权限: 由CIO直接在数据库开通个人的数据库账号, 通过跳板机创建ssh转发, 用内网ip以跳板机身份去访问RDS, 起了一个简单的tcpdump进程对内网网卡抓包,用perl分析,可以获取发出的明文sql,查起问题来很麻烦, 而且一般是事后了. 配置管理: 应用的文件配置: 各种properties文件, 项目内resource目录下分了common,dev,prod文件夹, mvn编译的时候传入参数类似 -Ddev 参数来获取对应环境的配置文件. 不过dev环境配置包括账密之类的都是直接写死在文件里提交git, 线上配置则是用占位符比如 {DB_PASSWORD},在编译节点部署的时候,通过脚本替换占位符替换为真实的值.不过既然能登陆编译服务器, 稍微机灵点的看下脚本内容就能找到有着所有明文配置的源配置文件....替换后的应用配置文件也依旧是明文的,到应用服务器或者NFS上还是一览无余, 加上服务器权限开得很奔放且无审计, 组合起来真是灾难. 想想万一有个好奇的看到了源文件配置好奇在服务器上连了下里面数据源或其他内容, 你怕不怕 应用内的配置: 这种是应用在启动后读取的实时配置, 现成有个superdiamond,但是是单点的, 整个环境的应用配置都堆在一个页面, 页面加载都要等好久, 没有版本管理,不好回滚, 热加载也是随缘, 有时候改个配置还是要重启. 同样也有明文配置的问题. 监控: 业务监控: 有一个自研的业务监控系统 系统监控: 基本没有. 上面这些问题刚开始的时候用一些粗暴的方式去缓解: 监控: 部署zabbix对系统和进程信息进行监控, 加了发现规则自动获取tomcat进程加入监控(经常性线上应用跑挂了不知道, 这个效果还不错), puppet dashboard获取agent节点配置刷新的状态,至少改完配置后不是一脸懵逼傻傻的等, 有时候想要快速生效, 就直接用ansible去批量刷puppet agent. 权限回收: 线上应用服务器权限回收, 对已有节点分组, 按需根据应用组或者项目组授权. 编译服务器权限线上编译账号prodbuilder权限回收重新分配, 让开发同学权限尽量最小化. 流程和标准化: 线上核心应用禁止私自部署, 统一提工单到运维同学, 周知项目相关同学, 由运维同学部署(导致的结果就很直接了,不少时间耗在这了,不过确实因为私下部署而导致问题的情况好了很多) tomcat,nginx等应用版本和参数确定好作为模板后放到NFS,后续应用都由模板创建. 线上发布的lib统一为master分支, 应用使用online分支.非充分测试的提交禁止合并到部署分支.后续比较重要应用的部署都是运维同学控制,还比较好控制. 配置中心: 换成了spring cloud config, 有点重, gitlab全家桶加上开发同学自己写的客户端. 好处是依赖gitlab有了版本管理, 热加载不再是玄学, 但是依旧有着单点的问题,而且客户端不太稳定, 造成了几次大规模出错. 提前部署: 需要报备三方的应用, 提前部署起来, 平时先不启动, 节点上可以先跑不太重要的服务.需要扩容的时候直接更新最新代码启动后挂载到对应slb提供服务. 探路 当然上面列出的都是缓兵之计, 大部分问题还是存在, 在前几个月懵逼和饱受白名单问题煎熬后, 慢慢打起了vpc迁移的算盘. 这个迁移很大的一个问题就是, 如何将VPC和经典资源打通呢?下篇继续
自动补全: yum -y install bash-completion echo "source <(kubectl completion bash)" >> ~/.bashrc 添加镜像仓库认证 kubectl -n $NAMESPACE create secret docker-registry $KEYNAME \ --docker-server=$DOCKER_REGISTRY_SERVER \ --docker-username=$DOCKER_USER \ --docker-password=$DOCKER_PASSWORD \ --docker-email=$DOCKER_EMAIL echo "------------ create k8s-user secret ----------------" kubectl get secret $KEYNAME --output="jsonpath={.data.\.dockerconfigjson}" | base64 -d echo "------------ add k8s-user serviceaccount ----------------" kubectl -n $NAMESPACE patch serviceaccount default -p '{"imagePullSecrets": [{"name": "k8s-user"}]}' 获取集群内部service&pod网段 ps -ef | grep -Po 'cluster-cidr\S+\s|service-cluster-ip-range\S+\s' 获取集群/组件状态 kubectl get componentstatus kubectl cluster-info 获取kubelet状态 systemctl status kubelet journalctl -xefu kubelet 更新istio流量拦截网段 helm template install/kubernetes/helm/istio --set global.proxy.includeIPRanges="10.1.0.0/16\,10.2.0.0/20" -x templates/sidecar-injector-configmap.yaml | kubectl apply -f - istio 开启自动注入 kubectl label namespace default istio-injection=enabled istio替换现有deploy, 注入sidercar istioctl kube-inject -f <(kubectl get deploy xxx -o yaml) | kubectl replace -f - 重启pod(无法直接重启, 删除或者replace pod可以达到重启效果, 或者进入容器/cashbus/tomcat/restart.sh) kubectl delete po -l app=sc-tag kubectl get pod logtail-ds-698m5 -n kube-system -o yaml | kubectl replace --force -f - 转发pod流量到kubectl运行节点上 # kiali kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=kiali -o jsonpath='{.items[0].metadata.name}') 20001:20001 # grafana kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000:3000 & # prometheus kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=prometheus -o jsonpath='{.items[0].metadata.name}') 9090:9090 & # service Graph kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=servicegraph -o jsonpath='{.items[0].metadata.name}') 8088:8088 & # jaeger-collector kubectl -n istio-system port-forward $(kubectl -n istio-system get po -l app=tracing-on-sls,component=collector -o jsonpath='{.items[0].metadata.name}') 9411:9411& 停止应用所有pod 没法直接停止 可以 scale对应deployment为0达到效果 kubectl scale xx sc-contract-sc-xxx --replicas=0 命令行处理yaml cat <<EOF | kubectl create -f - apiVersion: extensions/v1beta1 kind: Ingress metadata: name: simple annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - http: paths: - path: /svc backend: serviceName: http-svc servicePort: 80 EOF 标记master可调度(不建议) kubectl taint node -l node-role.kubernetes.io/master node-role.kubernetes.io/master=:PreferNoSchedule --overwrite kubectl taint node -l node-role.kubernetes.io/master node-role.kubernetes.io/master- 标记节点不可调度 kubectl cordon node helm手动安装 ######### helm install 需要支持ipv6 /etc/sysctl.conf net.ipv6.conf.all.disable_ipv6 = 0 net.ipv6.conf.default.disable_ipv6 = 0 net.ipv6.conf.lo.disable_ipv6 = 0 $ sysctl -p /etc/sysconfig/network NETWORKING_IPV6=yes $ service network restart curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash helm tiller自行升级 官方源被墙 国内连不上 这里用阿里镜像源 export TILLER_TAG=v2.9.1 kubectl --namespace=kube-system set image deployments/tiller-deploy tiller=registry-vpc.cn-beijing.aliyuncs.com/google_containers/tiller:$TILLER_TAG helm 自动补全 source <(helm completion bash) 获取pod异常终止前的状态 kubectl get pod -o go-template='{{range.status.containerStatuses}}{{"Container Name: "}}{{.name}}{{"\r\nLastState: "}}{{.lastState}}{{end}}' xxxxxxxx 删除node 转移节点正在运行的 resource kubectl get no -o wide | awk '/NotReady/{print $1}' | xargs -i -t kubectl drain {} --ignore-daemonsets 删除 kubectl get no -o wide | awk '/NotReady/{print $1}' | xargs -i -t kubectl delete node {} 逐出节点上所有pod kubectl drain cn-beijing.i-xxxxx --delete-local-data --ignore-daemonsets 批量打ingress controller标签 用作自动调度 kubectl get node | awk '!/master/{print $1}' |xargs -i -t kubectl label node {} node-role.kubernetes.io/ingress=true --overwrite 各种patch patch configmap kubectl patch -n kube-system cm tcp-services --patch '{"data":{"32218": "default/sc-xxx0debug:32219"}}' patch serice kubectl patch -n kube-system svc nginx-ingress-lb --patch '{"spec":{"ports":[{"name":"patch-test","port": 32222, "protocol":"TCP", "targetPort": 32222}]}}' patch nodeSelector kubectl patch deployment sc-demo-sc-demo -p '{"spec":{"template":{"spec":{"nodeSelector":{"env":"testing"}}}}}' patch readiness healthCheck jsonpatch http://jsonpatch.com/ patch healthCheck, JSON Patch, RFC 6902 kubectl patch deployment xxxx --type='json' -p='[ { "op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe", "value": { "failureThreshold": 1, "initialDelaySeconds": 30, "periodSeconds": 3, "successThreshold": 3, "httpGet": { "path": "/rest/healthCheck", "port": "app-port" }, "timeoutSeconds": 1 } } ]' 获取所有运行镜像 go-template示例 kubectl get deployment -o go-template --template='{{range .items}}{{range .spec.template.spec.containers }}{{printf "%s\n" .image}}{{end}}{{end}}' kubectl get deployment -o go-template --template='{{range .items}}{{range .spec.template.spec.containers }}{{printf "%s\n" .image}}{{end}}{{end}}' | awk -F'[:/]' '{printf " [\042%s\042]=\042%s\042\n",$(NF-1),$NF}' 删除istio相关crd(customresourcedefinition) kubectl get crd | awk '/istio/{print $1}' | xargs -i kubectl delete crd {} 清空整个namespace(慎用) kubectl -n istio-system delete all --all 强制更新 helm "no deployed release"状态的release,感觉还是比较鸡肋. 至今没找到好的办法处理 "no deployed release" helm upgrade --force -i -f ack-istio-default.yaml ack-istio-default incubator/ack-istio
起因 我们使用的一直是阿里云的redis, 我们并非高并发应用, 主要也就是拿来做分布式锁和少量的缓存, 基本不怎么需要维护, 昨天下午突然收到一封告警邮件, 线上redis内存使用100%, 瞬间神经绷紧感觉上控台确认.这是一个16G的线上实例, 平时不到50% 排查修复 info信息 基本使用都是默认的db 0, 看到keys已经七千多万, 设置了过期的时间key只有173W.. 查找bigkey, 想办法先释放一部分keys 先挂上bigkeys的命令 $ redis-cli -h xxx.redis.rds.aliyuncs.com -a xxx --bigkeys 发现几个出现频率较高的大string key, 和开发同事确认, 是最近添加的redis缓存, 缓存时间为10天, 通知先关闭开关, 避免影响已经运行的任务, 进redis暂时先删除几个比较大的keys看看, 释放了少量内存 BoundValueOperations<String, String> contactListCache = kvLockTemplate.boundValueOps("athena.cache.contactlist" + user.getId()); 现在key太多, 如果直接keys效率非常低下, 好在redis原生提供了SCAN, 可以迭代遍历, 写个简单的python脚本用scan每次扫描100W,把相关的keys给删掉. import redis def clean_excess(host='xxx.redis.rds.aliyuncs.com', port=6379, db=0, password='xxx', pattern=None): _redis = redis.StrictRedis(host=host, port=port, db=db, password=password) i = None while i != 0: if i is None: i = 0 print('>> scan index', i) _scan = _redis.scan(i, match=pattern, count="1000000") i, l = _scan if l: for _i in l: print("-- delete key {}".format(_i)) _redis.delete(_i) if __name__ == '__main__': clean_excess(pattern="athena.cache.contactlist*") 运行完成后内存使用率降到了 45%, 到这里内存问题算是解决了. 用awk快速抽样统计下keys的比例 好在我们都是keyPrefix + 数字id这样的格式, 这里抽样100W看下比例, redis-cli -h xxx.redis.rds.aliyuncs.com -a xxx scan 0 count 1000000 | awk -F '[0-9]' '{s=NF>0?$1:$0;print s}' | sort | uniq -c | sort -n 找开发同学确认下, 这里 ip.try.counter 原本是某个版本用来锁定用户ip尝试登陆的..居然没设置过期时间, 其余的key多多少少都带了一些神奇的逻辑, 就基本没法动了....内心是崩溃的 那么先把这些清理掉 import redis def clean_excess(host='xxx.redis.rds.aliyuncs.com', port=6379, db=0, password='xxx', pattern=None): _redis = redis.StrictRedis(host=host, port=port, db=db, password=password) i = None while i != 0: if i is None: i = 0 print('>> scan index', i) _scan = _redis.scan(i, match=pattern, count="1000000") i, l = _scan if l: for _i in l: print("-- delete key {}".format(_i)) _redis.delete(_i) if __name__ == '__main__': clean_excess(pattern="ip.try.counter.*") 跑了大概九个小时... 清理完后还剩了三千多万key, 内存倒是没怎么释放 其余的问了开发同学基本不能动. 使用率现在维持在50%以下暂时就不动了 结语 关于redis内存逐出策略的问题, 阿里云默认给出的maxmemory刚好是等于你的内存配置大小的,也就是内存使用率100%了才会触发, 逐出策略默认是valatile-ttl只会逐出设置了过期时间的key, 相对于我们的情况, 大部分都是没设置过期时间基本就是杯水车薪. 如果改变策略为allkeys-xx 进行有效逐出,还是会影响到业务的正常运行 说到底还是需要规范使用者的习惯, 该设置过期时间的不能偷懒, 确实有大内存需求的独立分配资源. 其实有很多不错的现成的工具可以对redis进行诊断 可以参考下 https://scalegrid.io/blog/the-top-6-free-redis-memory-analysis-tools/不过这里rdb只能分析dump.rdb文件, 其他几个工具试用了下对于keys数量级过大效率都略低下, 好在我们keys命名还算有一定规律所以自己写了个简单脚本去抽样统计, 比较喜欢的是redis-memory-for-key(只是用来统计具体key的内存占用情况),也可能我使用姿势不对吧 欢迎一起探讨
0. 起因 在我们测试环境k8s上运行了数十个helm release, 昨天helm ls的时候居然报错了 $ helm ls Error: trying to send message larger than max (23934705 vs. 20971520) 这个问题搜索下, 发现github issue今年一月就已经有人遇到了 https://github.com/helm/helm/issues/3322 , 今年二月份官方的同学也提pr修复了 https://github.com/helm/helm/pull/3514 为啥我这里还会出现呢 1. 探究 看下修复pr的主要内容, 发现只是把限制从4M提升到20M... 这个值还是一个常量, 目前最近的release和master也一样 我们使用的tiller镜像 registry.cn-beijing.aliyuncs.com/acs/tiller:v2.9.1,已经包了修复内容,看来想解决只能自己修改源码编译了 2. 修复 2.1 修改源码 const maxMsgSize = 1024 * 1024 * 100这里改成100M,还能超的话只能说要再来一个集群了.. 2.2 编译 Helm 拉下代码, 发现helm使用make进行构建, 按照官方 https://docs.helm.sh/developers/, 尝试初始化和构建 17:03 $ make bootstrap build glide install --strip-vendor [INFO] Downloading dependencies. Please wait... [INFO] --> Found desired version locally github.com/aokoli/goutils 9c37978a95bd5c709a15883b6242714ea6709e64! [INFO] --> Found desired version locally github.com/asaskevich/govalidator 7664702784775e51966f0885f5cd27435916517b! [INFO] --> Found desired version locally github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109! [INFO] --> Found desired version locally github.com/Azure/go-autorest bca49d5b51a50dc5bb17bbf6204c711c6dbded06! [INFO] --> Found desired version locally github.com/beorn7/perks 3ac7bf7a47d159a033b107610db8a1b6575507a4! [INFO] --> Found desired version locally github.com/BurntSushi/toml 3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005! [INFO] --> Found desired version locally github.com/chai2010/gettext-go c6fed771bfd517099caf0f7a961671fa8ed08723! [INFO] --> Found desired version locally github.com/cpuguy83/go-md2man 71acacd42f85e5e82f70a55327789582a5200a90! [INFO] --> Found desired version locally github.com/cyphar/filepath-securejoin a261ee33d7a517f054effbf451841abaafe3e0fd! [INFO] --> Found desired version locally github.com/davecgh/go-spew 782f4967f2dc4564575ca782fe2d04090b5faca8! [INFO] --> Found desired version locally github.com/dgrijalva/jwt-go 01aeca54ebda6e0fbfafd0a524d234159c05ec20! [INFO] --> Found desired version locally github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c! [INFO] --> Found desired version locally github.com/docker/docker a9fbbdc8dd8794b20af358382ab780559bca589d! [INFO] --> Found desired version locally github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d! [INFO] --> Found desired version locally github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1! [INFO] --> Found desired version locally github.com/docker/spdystream 449fdfce4d962303d702fec724ef0ad181c92528! [INFO] --> Found desired version locally github.com/evanphx/json-patch 36442dbdb585210f8d5a1b45e67aa323c197d5c4! [INFO] --> Found desired version locally github.com/exponent-io/jsonpath d6023ce2651d8eafb5c75bb0c7167536102ec9f5! [INFO] --> Found desired version locally github.com/fatih/camelcase f6a740d52f961c60348ebb109adde9f4635d7540! [INFO] --> Found desired version locally github.com/ghodss/yaml 73d445a93680fa1a78ae23a5839bad48f32ba1ee! [INFO] --> Found desired version locally github.com/go-openapi/jsonpointer 46af16f9f7b149af66e5d1bd010e3574dc06de98! [INFO] --> Found desired version locally github.com/go-openapi/jsonreference 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272! [INFO] --> Found desired version locally github.com/go-openapi/spec 1de3e0542de65ad8d75452a595886fdd0befb363! [INFO] --> Found desired version locally github.com/go-openapi/swag f3f9494671f93fcff853e3c6e9e948b3eb71e590! [INFO] --> Found desired version locally github.com/gobwas/glob 5ccd90ef52e1e632236f7326478d4faa74f99438! [INFO] --> Found desired version locally github.com/gogo/protobuf c0656edd0d9eab7c66d1eb0c568f9039345796f7! [INFO] --> Found desired version locally github.com/golang/glog 44145f04b68cf362d9c4df2182967c2275eaefed! [INFO] --> Found desired version locally github.com/golang/groupcache 02826c3e79038b59d737d3b1c0a1d937f71a4433! [INFO] --> Found desired version locally github.com/golang/protobuf 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9! [INFO] --> Found desired version locally github.com/google/btree 7d79101e329e5a3adf994758c578dab82b90c017! [INFO] --> Found desired version locally github.com/google/gofuzz 44d81051d367757e1c7c6a5a86423ece9afcf63c! [INFO] --> Found desired version locally github.com/google/uuid 064e2069ce9c359c118179501254f67d7d37ba24! [INFO] --> Found desired version locally github.com/googleapis/gnostic 0c5108395e2debce0d731cf0287ddf7242066aba! [INFO] --> Found desired version locally github.com/gophercloud/gophercloud 781450b3c4fcb4f5182bcc5133adb4b2e4a09d1d! [INFO] --> Found desired version locally github.com/gosuri/uitable 36ee7e946282a3fb1cfecd476ddc9b35d8847e42! [INFO] --> Found desired version locally github.com/gregjones/httpcache 787624de3eb7bd915c329cba748687a3b22666a6! [INFO] --> Found desired version locally github.com/grpc-ecosystem/go-grpc-prometheus 0c1b191dbfe51efdabe3c14b9f6f3b96429e0722! [INFO] --> Found desired version locally github.com/hashicorp/golang-lru a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4! [INFO] --> Found desired version locally github.com/huandu/xstrings 3959339b333561bf62a38b424fd41517c2c90f40! [INFO] --> Found desired version locally github.com/imdario/mergo 9316a62528ac99aaecb4e47eadd6dc8aa6533d58! [INFO] --> Found desired version locally github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75! [INFO] --> Found desired version locally github.com/json-iterator/go f2b4162afba35581b6d4a50d3b8f34e33c144682! [INFO] --> Found desired version locally github.com/mailru/easyjson 2f5df55504ebc322e4d52d34df6a1f5b503bf26d! [INFO] --> Found desired version locally github.com/MakeNowJust/heredoc bb23615498cded5e105af4ce27de75b089cbe851! [INFO] --> Found desired version locally github.com/Masterminds/semver 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd! [INFO] --> Found desired version locally github.com/Masterminds/sprig 15f9564e7e9cf0da02a48e0d25f12a7b83559aa6! [WARN] Unable to checkout golang.org/x/text [ERROR] Update failed for golang.org/x/text: Cannot detect VCS [WARN] Unable to checkout golang.org/x/time [ERROR] Update failed for golang.org/x/time: Cannot detect VCS [WARN] Unable to checkout google.golang.org/appengine [ERROR] Update failed for google.golang.org/appengine: Cannot detect VCS [WARN] Unable to checkout google.golang.org/genproto [ERROR] Update failed for google.golang.org/genproto: Cannot detect VCS [WARN] Unable to checkout google.golang.org/grpc [ERROR] Update failed for google.golang.org/grpc: Cannot detect VCS [ERROR] Failed to install: Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS Cannot detect VCS make: *** [bootstrap] Error 1 问题1: 国内golang的依赖包各种被墙 .PHONY: bootstrap bootstrap: ifndef HAS_GLIDE go get -u github.com/Masterminds/glide endif ifndef HAS_GOX go get -u github.com/mitchellh/gox endif ifndef HAS_GIT $(error You must install Git) endif glide install --strip-vendor go build -o bin/protoc-gen-go ./vendor/github.com/golang/protobuf/protoc-gen-go 看一眼Makefile, 这里用的glide管理的依赖包 好在github基本都会有对应的包源码, 有几种解决方式: clone源码然后按照目录结构放到对应$GOPATH/src下 给git设置proxy,小飞机你懂的~ 使用 gopm 使用glide的mirror功能设置映射 glide-mirror 这里用的方法4, mirrors.yaml, 不想一个一个做映射可以参考这里 mirrors.yaml 问题2: 编译路径 17:36 $ make build GOBIN=/home/jokimina/golang/k8s.io/helm/bin go install -tags '' -ldflags '-w -s -X k8s.io/helm/pkg/version.GitCommit=26c88ec815785708cc19c014366ce4bb000ae89c -X k8s.io/helm/pkg/version.GitTreeState=dirty' k8s.io/helm/cmd/... warning: "k8s.io/helm/cmd/..." matched no packages 如果是直接拉下代码放到随意的目录, build的时候会报错, 需要放到$GOPATH/src/k8s.io/helm再执行编译 问题3: golang默认不是静态编译, docker alpine镜像没有运行所需要的库 $ make build GOBIN=/home/xxx/go/src/k8s.io/helm/bin CGO_ENABLED=0 go install -tags '' -ldflags '-w -s -X k8s.io/helm/pkg/version.GitCommit=147c8217c7843b53e98528f6df12890d64c696be -X k8s.io/helm/pkg/version.GitTreeState=dirty' k8s.io/helm/cmd/... ~/go/src/k8s.io/helm $ ls bin/ helm rudder tiller make bootstrap build执行完后会生成二进制文件到 $GOBIN 目录下 , 直接替换掉阿里云tiller容器内的运行发现报错 # /tiller sh: /tiller: not found 但是本地可以运行, 搜索下 https://stackoverflow.com/questions/36279253/go-compiled-binary-wont-run-in-an-alpine-docker-container-on-ubuntu-host 发现是由于默认没有静态编译导致引用了alpine不存在的库链接 # ldd tiller /lib64/ld-linux-x86-64.so.2 (0x55d98fa73000) libpthread.so.0 => /lib64/ld-linux-x86-64.so.2 (0x55d98fa73000) libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x55d98fa73000) 重新静态编译下, 看到Makefile默认支持交叉编译这里偷个懒 make TARGETS="linux/amd64" build-cross 节省时间这里就只编译需要的了, 构建好的二进制文件会在 _dist/下 2.3 构建镜像 这里就直接替换tiller然后commit镜像, 不写Dockerfile了 $ docker pull registry.cn-beijing.aliyuncs.com/acs/tiller:v2.9.1 $ docker run -d --name build registry.cn-beijing.aliyuncs.com/acs/tiller:v2.9.1 $ docker cp tiller build:/tiller $ docker commit build registry-vpc.cn-beijing.aliyuncs.com/xxx-service/helm:v2.11.0 $ docker push !$ 2.4 换掉k8s集群上的tiller $ helm init --tiller-image registry-vpc.cn-beijing.aliyuncs.com/xxx-service/helm:v2.11.0 --upgrade $ helm version Client: &version.Version{SemVer:"v2.9.1", GitCommit:"08c1144f5eb3e3b636d9775617287cc26e53dba4", GitTreeState:"clean"} Server: &version.Version{SemVer:"v2.9.1", GitCommit:"e8c5cf08751dfc7be5c4bedcd400ed78355e3468", GitTreeState:"dirty"} 测试下 ok了 3. 后话 在配置参数不变更只是更新镜像的情况下, 其实可以考虑直接update deplayment开销会小很多, helm主要还是首次初始化的时候自动生成了需要的resource. 正常情况下官方限制的20M基本能满足需求, 只是超了一点点的话可以在helm ls的时候用-m或者--deployed限制下返回数量和过滤状态不 过这里是一个测试集群release会不断增加...所以无法忍受了