Expect自动化工具简介

公司服务器使用了两层跳板机,外面的一台我们管它叫 server A, 另外一台 叫它 server B。 虽然我不知道这种双保险给公司带来了多少安全感,但是我知道我的运维效率降低了差不多90%吧 :>。 server A被直接暴露在公网上, 我们不能使用 ssh key 只能使用 password认证ssh。 这还不算完,server A每3小时改一次自己的root密码。 后面的server B跳板机器的自我安全感就强多了,server B可以直接免密使用ssh key登陆所有的内网服务器,而且允许server A免密登陆到自己。 我决得这样两次登录很浪费时间,于是写了个脚本从外网一次性登陆到server B服务器上。 expect 脚本 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 #!/usr/bin/expect -f # for anyone not familar with expect # should read this awesome post # https://www.pantz.org/software/expect/expect_examples_and_tips.html set timeout 15 ### CHANGE pwd every 3h set pwd "mySuperSecretpwd123" set nested_ssh "ssh server_B" ## for debug # log_user 0 # exp_internal 1 send_user "going to connected to server A\n" spawn ssh -q -o StrictHostKeyChecking=no server_A expect { timeout { send_user "\ntimeout Failed to get password prompt, is VPN on?\n"; exit 1 } eof { send_user "\nSSH failure for server A\n"; exit 1 } "*assword:" } send "$pwd\r" expect { timeout {send_user "\nSSH failure for server B\n"; exit 1 } "Last login:*" } send "$nested_ssh\r" interact 基本语法 简单说下基本流程如下: ...

March 19, 2020 · datewu

准确的分页

昨天同事调试前端页面分页功能时, 发现了一个分页的问题。 问题简要描述如下: 前端选择一些过滤条件(a&&b&&c||d …)向后端请求数据,过一会发现用同样的过滤条件去查询,数据变少了,前端看上去第一页和最后一页是一样的。 初步怀疑是分页出了问题。 这个分页的问题比较麻烦,不能稳定复现,一会出现一会又不出现。 分析了很长一段时间后,发现是后台的定时任务更新了db数据使得很多数据不再符合前面的过滤条件,后端框架返回的总页码数,和data的数量不符。 也就是说后端返回的总页码数是脏/旧数据。 脏数据 查看后端框架代码时候,发现后端查询db的执行了count 和 select 两条query: 1 2 3 4 5 6 select count(*) form table_name where condition_a -- meamwhile other workers update table_a -- A LOT in short time -- or they(the workers) MIGHT lock -- the WHOLE table for READ select * form table_name where condition_a 这就是是造成脏数据的原因。 解决方案 window function 可以用 window function 做到上述两条query的同时查询(其实就是一条查询): 1 2 3 4 5 6 SELECT *, count(*) OVER() AS full_count FROM tbl WHERE -- /* whatever */ ORDER BY col1 LIMIT ? OFFSET ? Common Table Expressions (cte) However, as Dani pointed out, when OFFSET is at least as great as the number of rows returned from the base query, no rows are returned. So we also don’t get full_count. ...

March 2, 2020 · datewu

细说kubeconfig

今天准备管理某一个kubernetes 集群时发现master主机22端口因为管理的需要被禁用了,无法登陆服务器。 问了一下运维人员,原来是基于安全原因,公司决定禁用所有服务器的root ssh登陆权限, 平时我都是ssh 登陆到master node,在服务器上直接使用kubectl命令 查看/部署/debug deployment/service等资源, 现在只好修改下本地 kubeconfig 文件,用自己本地的 kubectl 管理/操作kubernetes集群。 操作了一段时间后,发现用本地kubectl操作kubernetes体验蛮好的,特别是服务器缺少本地editor(vim) kubectl edit ... 的语法高亮支持。 配置kubeconfig过程分享如下,大体上说过就两步: 添加 context; use context。 1 2 vim .kube/config kubectl config use-context dev-8-admin@kubernetes 除了使用vim 编辑 .kube/config 文件,对于一些简单的配置也可以使用kubectl config command 快速配置kubeconfig: 1 2 3 4 5 6 7 8 9 10 11 12 13 ## create new cluster kubectl config set-cluster NAME [--server=server] [--certificate-authority=path/to/certificate/authority] [--insecure-skip-tls-verify=true] ## create new user kubectl config set-credentials NAME [--client-certificate=path/to/certfile] [--client-key=path/to/keyfile] [--token=bearer_token] [--username=basic_user] [--password=basic_password] [--auth-provider=provider_name] [--auth-provider-arg=key=value] [options] ## create new context kubectl config set-context [NAME | --current] [--cluster=cluster_nickname] [--user=user_nickname] [--namespace=namespace] [options] ## use context kubectl config use-context CONTEXT_NAME [options] 另外,kubectl config set 不支持对 certificate-authority-data字段的设置,只支持指定data文件的路径, 所以推荐用vim 编辑kubeconfig文件。 ...

August 11, 2019 · datewu

Neutron小记

前段时间的花了很多功夫对接k8s和openstack的kuryr-kubernetes网路组件。 学到了很多openstack的知识,今天抽出时间来整理下。 client 首先是 install openstack-cli neutron client: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash [root@deoops ~]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 7.5 (Maipo) #### add openstack yum repo source [root@deoops ~]# vi /etc/yum.repos.d/openstack.repo [root@deoops ~]# yum install -y python2-openstackclient openstack-neutron [root@deoops shells]# cat source export OS_PROJECT_DOMAIN_NAME=default export OS_USER_DOMAIN_NAME=default export OS_PROJECT_NAME=your_project_name export OS_USERNAME=your_use_name export OS_PASSWORD=your_pwd export OS_AUTH_URL=http://10.8.1.3:35357/v3 export OS_IDENTITY_API_VERSION=3 export OS_IMAGE_API_VERSION=2 vip 我们来创建一个virtual IP验证上一步配置的openstack source对不对 : ...

April 21, 2019 · datewu

nopCloser函数

update: ioutil逐渐被io 取代。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package ioutil // import "io/ioutil" func NopCloser(r io.Reader) io.ReadCloser NopCloser returns a ReadCloser with a no-op Close method wrapping the provided Reader r. As of Go 1.16, this function simply calls io.NopCloser. package io // import "io" func NopCloser(r Reader) ReadCloser NopCloser returns a ReadCloser with a no-op Close method wrapping the provided Reader r. 最近使用baloo写集成测试,遇到了个需求, 在unmarshalrespones之后(或者之前)还要再输出一次response的纯文本格式供debug参考。 即需要多次读http.Resp.Body。 问题 response.Body 只能读一次,读完之后再进行read操作就会遇到EOF error。 分析问题 模糊记得baloo在一次请求中能多次(JSON() 和 String())读取response.Body内容。 仔细去看了下baloo的源代码,发现baloo自己在内部 封装了一个对象 http.RawResonse ,使用了 iouti.NopCloser函数重新填充了res.Body: 1 2 3 4 5 6 7 8 9 10 func readBodyJSON(res *http.Response) ([]byte, error) { body, err := ioutil.ReadAll(res.Body) if err != nil { return []byte{}, err } // Re-fill body reader stream after reading it res.Body = ioutil.NopCloser(bytes.NewBuffer(body)) return body, err } 解决方案 有了 ioutil.NopCloser函数,可以很快速的写出debugPlugin: ...

April 17, 2019 · datewu

bc语言

base 通过修改ibase和obase可以实现各种进制的转化,比如十进制和二进制和十六进制之间的转换; If you aren’t familiar with conversion between decimal, binary, and hexadecimal formats, you can use a calculator utility such as bc or dc to convert between different radix representations. For example, in bc, you can run the command obase=2; 240 to print the number 240 in binary (base 2) form. 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 #!/bin/bash ❯ bc -q ibase 10 obase 10 1+ 3 *3 10 obase=2 245 11110101 255 11111111 192 11000000 168 10101000 1 1 172 10101100 obase=16 34 22 172 AC ^D% syntax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 bc An arbitrary precision calculator language. More information: https://manned.org/bc. - Start bc in interactive mode using the standard math library: bc -l - Calculate the result of an expression: bc <<< "(1 + 2) * 2 ^ 2" - Calculate the result of an expression and force the number of decimal places to 10: bc <<< "scale=10; 5 / 3" - Calculate the result of an expression with sine and cosine using mathlib: bc -l <<< "s(1) + c(1)"

October 22, 2018 · datewu

Pod生命周期和Liveness的区别

今天测试给我反馈了一个pod问题,测试给出的 use case 描述如下: 配置一个nginx的web服务; 在生命周期中选择http协议,端口配置80,路径配置/errorpath; 服务中pod能正常启动; 预期在pod的事件中应该有一个“FailedPostStartHook”错误信息。 测试人员发现,第4点的预期没有达到,pod 正常启动了,却没有 FailedPostStarHook事件出现。 简单分析了一下,我发现是测试人员把pod的lifecycle和 pod的liveness/readiness 诺混淆了,从而写出了错的test case。 Lifecycle handlers 首先回顾下pod lifecycle的作用: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 kubectl explain pod.spec.containers.lifecycle.postStart RESOURCE: postStart <Object> DESCRIPTION: PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/ container-lifecycle-hooks/#container-hooks/ Handler defines a specific action that should be taken 简单翻译下就是说, kubernetes 提供的pod start和exit的 lifecycle hooks 方便开发人员hooked到 ...

September 18, 2018 · datewu

子目录父目录

最近开发的遇到一个需求是在判断 两个目录是否相互包含。 想着用正则表达式或者递归去解决,捣鼓一段时间后发现总有些edge case 不能cover到, 后来发现用 python 的pathlib 可以很好的解决。 1 2 3 4 5 6 7 from pathlib import Path def overlapping(a, b): if a == b: return True a_path = Path(a) b_path = Path(b) return a_path in b_path.parents or b_path in a_path.parents

September 10, 2018 · datewu

替换k8s所有证书

客户需要把kubernetes apiserver/etcd/kubelet/kubectl 等所有的证书有效期修改为100年。 很明显这是一个不合理的需求,不过客户说什么就是什么。 于是经几天的调试有了下面的这个 Makefile批量生成所有(FILES变量)的证书。 如果对makefile的语法不熟悉,可以看看Makefile简介 makefile 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 FILES = ca.crt ca.key sa.key sa.pub front-proxy-ca.crt front-proxy-ca.key etcd_ca.crt etcd_ca.key CONFS = admin.conf controller-manager.conf kubelet.conf scheduler.conf SELFS = kubelet.crt.self kubelet.crt.key #KEYs = ca.key front-proxy-ca.key etcd_ca.key sa.key #CAs = ca.crt front-proxy-ca.crt etcd_ca.crt #PUBs = sa.pub ## kubernetes will sign certificate ## automatically, so below ## csr/cert is for test purpose #CSR = apiserver.csr apiserver-kubelet-client.csr CERT_KEYS = apiserver.key apiserver-kubelet-client.key front-proxy-client.key CERTS = apiserver.cert apiserver-kubelet-client.cert front-proxy-client.cert # openssl genrsa -des3 -out rootCA.key 4096 CMD_CREATE_PRIVATE_KEY = openssl genrsa -out $@ 2048 CMD_CREATE_PUBLIC_KEY = openssl rsa -in $< -pubout -out $@ # openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt CMD_CREATE_CA = openssl req -x509 -new -nodes -key $< -sha256 -days 36500 -out $@ -subj '/CN=kubernetes' # openssl req -new -key mydomain.com.key -out mydomain.com.csr CMD_CREATE_CSR = openssl req -new -key $< -out $@ -config $(word 2,$^) # openssl x509 -req -in mydomain.com.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out mydomain.com.crt -days 500 -sha256 CMD_SIGN_CERT = openssl x509 -req -in $< -CA $(word 2,$^) -CAkey $(word 3,$^) -CAcreateserial -out $@ -days 36500 -sha256 -extfile $(word 4,$^) -extensions my_extensions # generata self sign certificate CMD_CREATE_CERT = openssl req -x509 -new -nodes -key $< -sha256 -days 36500 -out $@ -subj '/CN=nodeXXX@timestamp1531732165' CMD_MSG = @echo generating $@ ... MASTER_IP := 192.168.1.200 ## REMEMBER CHANGE ME .PHONY: all clean check self_sign rename all: ${FILES} ${CONFS} ${CERT_KEYS} ${CERTS} clean: -rm ${FILES} ${CONFS} ${CERT_KEYS} ${CERTS} self_sign: ${SELFS} check: for f in *.cert *.crt; do echo $$f; openssl x509 -noout -dates -in $$f; echo '==='; done rename: for f in *.cert; do echo $$f; mv $$f $${f%.*}.crt; echo '====='; done %.key: ${CMD_MSG} ${CMD_CREATE_PRIVATE_KEY} %.pub: %.key ${CMD_MSG} ${CMD_CREATE_PUBLIC_KEY} %.self: %.key ${CMD_MSG} ${CMD_CREATE_CERT} %.crt: %.key ${CMD_MSG} ${CMD_CREATE_CA} %.csr: %.key %.csr.cnf ${CMD_MSG} ${CMD_CREATE_CSR} %.cert: %.csr ca.crt ca.key %.csr.cnf #%.cert: %.csr front-proxy-ca.crt front-proxy-ca.key %.csr.cnf ${CMD_MSG} ${CMD_SIGN_CERT} %.conf: %.cert %-conf.sh sh $(word 2,$^) ${MASTER_IP} 上面的Makefile还需要对应的csr和 conffiles。 ...

August 10, 2018 · datewu

flannel vpc

update: flannel从v0.14.0(2021/05/27)开始已经支持腾讯云的vpc backend了。 客户需要在腾讯云上部署kubernetes集群而且选用的网络插件是flannel,所以我们需要为flannel 添加 腾讯云 vpc 的 backend 适配。 我大致看了下github上 阿里云 和 aws 适配器的代码,发现并不复杂,flannel已经把所有的dirty work flannel 都包装好API了。 稍稍了解一些网络设备或者Linux网络相关的命令(比如route table)就可以比较轻松的写出flannel适配器。 整个适配过程可以分为下面4个步骤: 定义 TxVpcBackend struct, 实现New func 在init func中注册; 调用腾讯云SDK 实现 RegisterNetwork method; 最后在main.go中 注册腾讯云backend 即可; 部署deployment 的时候选择 tx-vpc 的backend 即可. 下面结合部分代码具体的说下实现过程: 开发 定义结构体 只是搭一个架子,方便注册到flannel backend上,不含具体适配器的逻辑: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type TxVpcBackend struct { sm subnet.Manager extIface *backend.ExternalInterface } func New(sm subnet.Manager, extIface *backend.ExternalInterface) (backend.Backend, error) { be := TxVpcBackend{ sm: sm, extIface: extIface, } return &be, nil } func init() { backend.Register("tx-vpc", New) } 实现RegisterNetwork 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 func (be *TxVpcBackend) RegisterNetwork(ctx context.Context, config *subnet.Config) (backend.Network, error) { // 1. Parse our configuration cfg := struct { AccessKeyID string AccessKeySecret string }{} if len(config.Backend) > 0 { if err := json.Unmarshal(config.Backend, &cfg); err != nil { return nil, fmt.Errorf("error decoding VPC backend config: %v", err) } } log.Infof("Unmarshal Configure : %v\n", cfg) // 2. Acquire the lease form subnet manager attrs := subnet.LeaseAttrs{ PublicIP: ip.FromIP(be.extIface.ExtAddr), } l, err := be.sm.AcquireLease(ctx, &attrs) switch err { case nil: case context.Canceled, context.DeadlineExceeded: return nil, err default: return nil, fmt.Errorf("failed to acquire lease: %v", err) } if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" { cfg.AccessKeyID = os.Getenv("ACCESS_KEY_ID") cfg.AccessKeySecret = os.Getenv("ACCESS_KEY_SECRET") if cfg.AccessKeyID == "" || cfg.AccessKeySecret == "" { return nil, fmt.Errorf("ACCESS_KEY_ID and ACCESS_KEY_SECRET must be provided! ") } } err = createRoute(l.Subnet.String(), cfg.AccessKeyID, cfg.AccessKeySecret) if err != nil { log.Errorf("Error DescribeVRouters: %s .\n", err.Error()) } return &backend.SimpleNetwork{ SubnetLease: l, ExtIface: be.extIface, }, nil } 主要逻辑是 使用腾讯云的SDK 在vpc 网络下创建route , 即上面的 ...

August 8, 2018 · datewu