CoreDNS替代dnsmasq

CoreDNS是k8s的御用DNS解析软件 支持很多插件,各种DoH、DoT、gRPC ,各种特性,安装配置也十分简单 替代dnsmasq后,基本dnsmasq的功能都支持,修改劫持域名后也不用重启,会自动刷文件更新 下面说一下装法: 安装: wget https://github.com/coredns/coredns/releases/download/v1.7.0/coredns_1.7.0_linux_amd64.tgz 解压后就一个文件,直接放到/usr/local/bin 做个systemd启动文件 cat /etc/systemd/system/coredns.service [Unit] Description=CoreDNS DNS server After=network.target [Service] PermissionsStartOnly=true LimitNOFILE=1048576 LimitNPROC=512 CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE NoNewPrivileges=true ExecStart=/usr/local/bin/coredns -conf=/usr/local/bin/coredns.conf ExecReload=/bin/kill -SIGUSR1 $MAINPID Restart=on-failure [Install] WantedBy=multi-user.target 东西全都在/usr/local/bin/coredns.conf配置文件里: .:5353 https://.:443 { any log debug bind 172.18.30.1 tls /usr/local/bin/full.pem /usr/local/bin/key.pem hosts /etc/hosts { ttl 60 reload 1m no_reverse fallthrough } cache 120 reload 6s loadbalance forward . 114.114.114.114:53 223.5.5.5:53 223.6.6.6:53 { policy sequential } prometheus 172.18.30.1:9253 } 解释一下: ...

2022年12月7日

sed中的正则表达式用法

之前说过sed的高阶用法,其实普通情况下,正则是最常用的,下面就来说一下,用不到的就暂时不说 ^ # 匹配行开始,如:/^sed/匹配所有以sed开头的行。 $ # 匹配行结束,如:/sed$/匹配所有以sed结尾的行。 . # 匹配一个非换行符的任意字符,如:/s.d/匹配s后接一个任意字符,最后是d。 * # 匹配0个或多次字符,如:/a*sed/匹配所有模板是0个或多个a后紧跟sed的行。后紧跟sed的行。 \? # 匹配0次或1次他前面的字符 \+ # 匹配1次或多次他前面的字符,如:空格\+ 或 “\+“匹配至少一个或多个空格 | #管道符号用来匹配两边任意一个子表达式,如:'/101\|102/p' 匹配包含101或者102的行打印 [] # 匹配一个指定范围内的字符,如/[sS]ed/匹配sed和Sed。 [^] # 匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行。 \(..\) # 匹配子串,保存匹配的字符,如s/\(love\)able/\1rs,loveable被替换成lovers。 & # 保存搜索字符用来替换其他字符,如s/love/ **&** /,love这成 **love** 。 \< # 匹配单词的开始,如:/\<love/匹配包含以love开头的单词的行。 \> # 匹配单词的结束,如/love\>/匹配包含以love结尾的单词的行。 \{m\} # 重复字符x,m次,如:/0\{5\}/匹配包含5个0的行。 \{m,\} # 重复字符x,至少m次,如:/0\{5,\}/匹配至少有5个0的行。 \{m,n\} # 重复字符x,至少m次,不多于n次,如:/0\{5,10\}/匹配5~10个0的行。 例子很多: 用户名,后面跟1个或者多个空格,再跟密码;更换成用户名 密码 MAC地址 sed -i "s/${username} \+${password}$/${username} ${password} ${IV_HWADDR}/" ${PASSFILE} 跟 shell 脚本的参数一起记比较好: $0 脚本本身的名字 $1 传递给该shell脚本的第1个参数 $2 传递给该shell脚本的第2个参数 $@ 传给脚本的所有参数的列表 $# 传给脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数,与位置变量不同,>参数可超过9个 $$ 脚本运行的当前进程ID号 $? 命令执行结果反馈,0表示执行成功,其余数字表示执行不成功。 还有一个重要的地方,sed 使用 -i 对文件进行修改时,执行者需要有对文件目录的写权限,因为sed实际是产生了一个临时文件,然后再挪回去得!!!! ...

2022年12月7日

Zabbix下发往钉钉告警

zabbix 是很熟悉的东西,但是实际上博主已经跳过了这个东西,直接蹦到 Prometheus 去了 但是,存在即合理,当下公司用的是这个,那么用就用吧,zabbix发到钉钉告警。 那么我们也研究一下如何发到钉钉告警,而且好看一些 原理: 原理就是用 post 向钉钉机器人的 webhook 地址提交 Markdown 的 json 信息 首先我们要建立个钉钉群,然后在群中添加一个群机器人,这里就会有两个选择,一个是这个机器人只接受特定的词语,二是向机器人发送消息的机器的ip是固定的。 阿里云建议的是关键词:云监控、云服务、监控、Monitor、ECS、报警 当然,这里更加建议IP,IP是死的,报警里带关键词意味着发送内容被部分固定了。 如上,我们会得到一个钉钉机器人的Webhook地址: https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxx 然后我们要准备一个发送的脚本,python很合适 #!/usr/bin/env python #coding:utf-8 #zabbix钉钉报警 import requests,json,sys,os,datetime #说明:这里改为自己创建的机器人的webhook webhook="https://oapi.dingtalk.com/robot/send?access_token=xxxxxx" def log(info): if os.path.isfile("/tmp/dingding.log") == False: f = open(log_file, 'a+') else: f = open(log_file,'w+') f.write(info) f.close() def msg(text,user): json_text= { "msgtype": "markdown", "markdown": { "title": "zabbix monitor", "text": text }, "at": { "atMobiles": [ user ], "isAtAll": False } } headers = {'Content-Type': 'application/json'} r=requests.post(url=webhook,data=json.dumps(json_text),headers=headers).json() code = r["errcode"] time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) if code == 0: log(time + ":消息发送成功 返回码:" + str(code) + "\n") else: log(time + ":消息发送失败 返回码:" + str(code) + "\n") exit(3) if __name__ == '__main__': user = sys.argv[1] #用户 subject = sys.argv[2] #标题 text = sys.argv[3] #消息 msg(text,user) 我们把上面的文件内容放到 /usr/lib/zabbix/alertscripts 目录下,名字就叫做 dingding.py ,只能是这个目录,因为这是 zabbix 缺省外挂脚本文件的目录。 ...

2022年11月30日

kubernetes使用nfs做持久化卷存储

用NFS来做kubernetes的持久化卷虽然不是最佳实践,但存在即合理。在现在的公司就是这么用的,记录一下,可用但不是最佳。 一、搭建NFS服务端和所有工作节点安装客户端 #NFS服务端 (centos7) yum install nfs-utils rpcbind -y mkdir -p /data/nfs/{download,bakup,www} echo "/data/nfs 10.10.0.0/16(rw,all_squash,insecure,sync)" >> /etc/exports exportfs -r systemctl enable rpcbind nfs-server systemctl restart rpcbind nfs-server showmount -e localhost #客户端根本不装也可以,客户端不需要启动任何nfs服务 #只是为了使用showmount工具来验证 #NFS客户端(CentOS,所有WorkNode节点都需要执行) yum install -y nfs-utils #NFS客户端(Ubuntu,所有WorkNode节点都需要执行) apt install -y nfs-common NFS 服务端的参数如下: rw 能读能写 insecure NFS通过1024以上的端口发送 root_squash root会被变成noboby,其他人不变 no_root_squash root身份保持不变,其他人不变。(不能用这个,容易被黑) all_squash 不论登入NFS的使用者身份为何,他的身份都会被压缩成为匿名使用者,通常也就是nobody sync 数据直接落盘,性能略损。 async 数据先落内存,然后落盘,性能略升。 综上,保险的参数就是 /data/nfs 10.10.0.0/16(rw,insecure,all_squash,sync) 注意:修改了/etc/exports后,并不需要重启nfs服务,只要用exportfs重新扫描一次/etc/exports,并且重新加载即可 exports -rv 二、POD内直接使用 由于nfs是内核自带的东西,所以最简单的使用方法就是直接在pod内使用 apiVersion: v1 kind: Pod metadata: name: test labels: app.kubernetes.io/name: alpine spec: containers: - name: alpine image: alpine:latest command: - touch - /data/test volumeMounts: - name: nfs-volume mountPath: /data volumes: - name: nfs-volume nfs: server: 10.10.247.38 path: /data readOnly: no 这个pod是一次性运行的,运行完后就可以看到nfs中多了个test文件 ...

2022年11月24日

钉钉告警发送

#!/usr/bin/env python #coding:utf-8 #zabbix钉钉报警 import requests import json import sys import os import datetime #这里是自己创建的机器人的webhook webhook="https://oapi.dingtalk.com/robot/send?access_token=xxxxxx" user=sys.argv[1] text=sys.argv[3] data={ "msgtype": "text", "text": { "content": text }, "at": { "atMobiles": [ user ], "isAtAll": False } } headers = {'Content-Type': 'application/json'} x=requests.post(url=webhook,data=json.dumps(data),headers=headers) 下面放shell脚本的 #!/bin/bash function ddmsg() { Token="xxxxxxxxxxxx" Weburl="https://oapi.dingtalk.com/robot/send?access_token=" curl -ks -m 2 "${Weburl}${Token}" \ -H 'Content-Type: application/json;charset=utf-8' \ -d "{'msgtype': 'text', 'text': { 'content': '$*'} }" &>/tmp/ddmsg.log if [ `grep "errmsg.*ok" /tmp/ddmsg.log |wc -l` -ne 1 ]; then echo 'send error !';cat /tmp/ddmsg.log;exit 1; fi } #测试内容 echo "@警报 主机:$(hostname) 信息:Node test 时间:$(date +"%F %T") ">.msg cat .msg ddmsg "`cat .msg`"

2022年11月23日

Freelancer之aws

客户发了一个需求如下: 用shell 或者python3 控制aws去获取新IP、绑定IP到实例 这个就非常简单了,因为本身aws就提供python的工具,一切都可以api化的 做法如下: #安装aws的工具 cd /tmp curl -kL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install aws --version #配置aws key aws configure #查看配置 aws configure list ---------------------------- #申请一个新IP并且打tag aws ec2 allocate-address --region $Region |tee /tmp/eip.log eip_id=$(jq -r ".AllocationId" /tmp/eip.log) aws ec2 create-tags --resources ${eip_id} --tags Key=Name,Value=eip-01 eip=$(jq -r ".PublicIp" /tmp/eip.log) #给ec2赋ip,前提是知道ec2_id aws ec2 associate-address --instance-id ${ec2_id} --allocation-id ${eip_id} ---------------------------- #启动新ec2的方法: #启动ec2 aws ec2 run-instances \ --region $Region --count 1 \ --instance-type t2.micro \ --subnet-id ${lan_a_public_id} \ --security-group-ids $gid \ --key-name MySshKey \ --image-id ami-04ff9e9b51c1f62ca \ |tee /tmp/ec2.log ec2_id=$(jq -r ".Instances[0].InstanceId" /tmp/ec2.log) 参考: https://www.cnblogs.com/elvi/p/16542406.html

2022年11月23日

Freelancer之QRCode

客户发了一个需求如下: TODO: 在sortFiles中,获取最新的域名,生成二维码,覆盖QRCodePath图片 TODO:将QRCodePath图片上传到阿里云0S根目录,重复则覆盖 又具体询问了一下细节,是一个 csharp 的程序 var sortFiles = Directory.GetFiles(zipPath, "*.zip").Select(fn => new FileInfo(fn)).OrderBy(f => f.LastAccessTime); // TODO: 在sortFiles中,获取最新的域名(文件名去掉.zip就是域名),结合logoUrl生成二维码,覆盖QRCodePath图片 // TODO: 将QRCodePath图片上传到阿里云OOS根目录,重复则覆盖, 看了一下,觉得思路如下,这是个管道流,无论徒手写代码生成QRCODE(还有个logo图需要放到二维码中央),还是集成OSS的SDK都麻烦 最快速方法是直接调用外部成程序做这两步,第一步用Go写个程序生成二维码,第二步用阿里的ossutil64工具上传 一、Go程序Gen出QRCode go 有很多库,我们直接拿来用好了,但是基本上都是老代码,所以需要下载 go1.13.15 来用 # 下载go 1.13.15,解压到E:\go,并建立一个E:\go\go-work目录,下载的模块会放到这里 https://dl.google.com/go/go1.13.15.windows-amd64.zip # 设置三个变量 GOROOT=E:\go GOPATH=E:\go\go-work PATH中增加好E:\go\bin的目录 # 设置一下代理,有时候模块拉不下来 go env -w GOPROXY=https://goproxy.cn,direct 然后就准备gen qrcode的程序,main.go package main import ( "flag" "image" "image/draw" "image/jpeg" _ "image/png" "os" "path/filepath" "github.com/LyricTian/logger" "github.com/nfnt/resize" "github.com/skip2/go-qrcode" ) var ( text string logo string percent int size int out string ) func init() { flag.StringVar(&text, "t", "", "二维码内容") flag.StringVar(&logo, "l", "", "二维码Logo(png)") flag.IntVar(&percent, "p", 15, "二维码Logo的显示比例(默认15%)") flag.IntVar(&size, "s", 256, "二维码的大小(默认256)") flag.StringVar(&out, "o", "", "输出文件") } func main() { flag.Parse() if text == "" { logger.Fatalf("请指定二维码的生成内容") } if out == "" { logger.Fatalf("请指定输出文件") } if exists, err := checkFile(out); err != nil { logger.Fatalf("检查输出文件发生错误:%s", err.Error()) } else if exists { logger.Fatalf("输出文件已经存在,请重新指定") } code, err := qrcode.New(text, qrcode.Highest) if err != nil { logger.Fatalf("创建二维码发生错误:%s", err.Error()) } srcImage := code.Image(size) if logo != "" { logoSize := float64(size) * float64(percent) / 100 srcImage, err = addLogo(srcImage, logo, int(logoSize)) if err != nil { logger.Fatalf("增加Logo发生错误:%s", err.Error()) } } outAbs, err := filepath.Abs(out) if err != nil { logger.Fatalf("获取输出文件绝对路径发生错误:%s", err.Error()) } os.MkdirAll(filepath.Dir(outAbs), 0777) outFile, err := os.Create(outAbs) if err != nil { logger.Fatalf("创建输出文件发生错误:%s", err.Error()) } defer outFile.Close() jpeg.Encode(outFile, srcImage, &jpeg.Options{Quality: 100}) logger.Infof("二维码生成成功,文件路径:%s", outAbs) } func checkFile(name string) (bool, error) { _, err := os.Stat(name) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } return true, nil } func resizeLogo(logo string, size uint) (image.Image, error) { file, err := os.Open(logo) if err != nil { return nil, err } defer file.Close() img, _, err := image.Decode(file) if err != nil { return nil, err } img = resize.Resize(size, size, img, resize.Lanczos3) return img, nil } func addLogo(srcImage image.Image, logo string, size int) (image.Image, error) { logoImage, err := resizeLogo(logo, uint(size)) if err != nil { return nil, err } offset := image.Pt((srcImage.Bounds().Dx()-logoImage.Bounds().Dx())/2, (srcImage.Bounds().Dy()-logoImage.Bounds().Dy())/2) b := srcImage.Bounds() m := image.NewNRGBA(b) draw.Draw(m, b, srcImage, image.ZP, draw.Src) draw.Draw(m, logoImage.Bounds().Add(offset), logoImage, image.ZP, draw.Over) return m, nil } 上面很简单,用到了别人的三个模块,需要安装一下 ...

2022年11月23日

MySQL数据库的备份和恢复之二

我们来说恢复的第二种情况,就是需要从binlog中指定位置恢复 binlog如何设置不说了,我们假设上次用mysqldump做过一次全量备份。 mysqldump -uroot -p -h 192.168.1.35 -P3306 --opt --triggers -R --hex-blob --single-transaction --flush-logs --master-data=2 -B 库名 > 库名.sql 由于我们在备份中使用了参数–flush-logs –master-data=2,所以 库名.sql 中会有binglog的信息供我们使用。 一、用mysqlbinlog来恢复 我们首先要查到随后的binlog文件是那个,从那时候起又生成多少个binglog文件。 然后去库里查询: show master logs; show master status; 我们要分析一下SQL # show binlog events [IN 'log_name'] [FROM pos] [LIMIT [offset,] row_count]; show binlog events in 'javaboy_logbin.000002' limit 5,10; 我们来翻看日志: 可以看到是从 764–>865 ,发生了删除,那么回放到这个764这个position前即可,764不会执行 mysqlbinlog /var/lib/mysql/mysql-bin.000204 --stop-position=764 --database=bbb | mysql -uroot -p –start-position指定从哪里开始恢复,如果不指定,就会从binlog文件开头的position开始 mysqlbinlog /var/lib/mysql/mysql-bin.000204 --start-position=205 --stop-position=764 --database=bbb | mysql -uroot -p 二、用binglog2sql来恢复 binlog2sql 是大众点评公司的DBA 开发的一款基于通过解析binlog将delete 恢复为insert,update 的值 set 字段和where条件做对调的原理来恢复数据的。 使用限制 MySQL的binlog format 必须是row 安装 ...

2022年11月22日

MySQL数据库的备份和恢复之一

喜大普奔,干了一个月的MySQL DBA的工作,又学到了一部分知识。 记录一下,以备不时只需 mysqldump 的备份其实很麻烦,要考虑很多因素 备份时候不能锁表 恢复的时候要快 有二进制数据的话需要备份二进制数据 有触发器、存储过程的都备份 通常 mysqldump 是做每天的fullbackup,要为之后的 binlog 做好准备,万一要恢复要提前做好准备 我们一点一点来说: 一、备份时不能锁表 --single-transaction 通过将导出操作封装在一个事务内来使得导出的数据是一个一致性快照 该选项在导出数据之前提交一个BEGIN SQL语句,BEGIN不会阻塞任何应用程序且能保证导出时数据库的一致性状态。 它只适用于InnoDB存储引擎。 本选项和--lock-tables选项是互斥的,因为LOCK TABLES会使任何挂起的事务隐含提交。 二、恢复时要尽量快 --opt 等同于--add-drop-table, --add-locks, --create-options, --quick, --extended-insert, --lock-tables, --set-charset, --disable-keys 该选项默认开启, 可以用--skip-opt禁用. --extended-insert, -e --extended-insert=false 使用具有多个VALUES列的INSERT语法。这样使导出文件更小,并加速导入时的速度。 默认为打开状态,使用--skip-extended-insert取消选项。 三、有二进制的按二进制备份 --hex-blob 使用十六进制格式导出二进制字符串字段。如果有二进制数据就必须使用该选项。影响到的字段类型有BINARY、VARBINARY、BLOB。 四、有触发器、存储过程的都备份 --triggers 导出触发器。该选项默认启用,用--skip-triggers禁用它。 --routines, -R 导出存储过程以及自定义函数 五、为之后如果要做binlog恢复提前做好准备 --flush-logs 开始导出之前刷新binlog日志。 请注意:假如一次导出多个数据库(使用选项--databases或者--all-databases),将会逐个数据库刷新日志。 除非使用--lock-all-tables或者--master-data外。在这种情况下,日志将会被刷新一次,相应的所以表同时被锁定。 因此,如果打算同时导出和刷新日志应该使用--lock-all-tables 或者--master-data 和--flush-logs --master-data 在导出的时候同时生成binlog文件名和位置在导出的文件开头。 这个非常重要。binlog 的文件和位置可以从这里拿到。 例如: -- CHANGE MASTER TO MASTER_LOG_FILE='binlog.000001', MASTER_LOG_POS=157; 该选项将binlog的位置和文件名追加到输出文件中。 如果为1,将会输出CHANGE MASTER 命令; 如果为2,输出的CHANGE MASTER命令前添加注释信息。 该选项将打开--lock-all-tables 选项,除非--single-transaction也被指定(在这种情况下,全局读锁在开始导出时获得很短的时间;其他内容参考下面的--single-transaction选项)。 该选项自动关闭--lock-tables选项。 mysqldump -uroot -p --host=localhost --all-databases --master-data=1; mysqldump -uroot -p --host=localhost --all-databases --master-data=2; 那么综上所述,总结一行非常使用的备份语句: ...

2022年11月21日

OpenVPN客户端定制IP和路由

公司用的 OpenVPN ,研发部门和普通员工的权限不一样,技术部门需要访问服务器,普通员工则不需要 这样的话就需要根据用户定制路由了,技术部门推送特定的路由,普通员工不推送。 那么做法如下,在 server.conf 做如下设定: ifconfig-pool-persist /etc/openvpn/conf/ipp.txt client-config-dir /etc/openvpn/conf/static 第一行是指定记录 ip 地址的文件,作用是如果服务器进程重启了,重启后会读取这个文件,客户端重新拨号后获得的 IP 就不会变,还是重启前的 IP。 第二行是指定了客户端的单独配置 我们假设一个用户是 zhangranrui,那么就去 /etc/openvpn/conf/static 下建立一个 zhangranrui 的文本文件,内容如下: # 前面是客户端固定IP地址,后面是网关地址 ifconfig-push 172.18.0.10 172.18.0.9 # 推送路由,后面是掩码 push "route 10.10.0.0 255.255.0.0" # 重定向客户端的所有流量,否则访问服务端内网要像上面一样推送路由 # push "redirect-gateway def1" # 推送给客户端的DNS解析地址 # push "dhcp-option DNS 223.5.5.5" # push "dhcp-option DNS 114.114.114.114" 上面我们固定了IP,也推送了特定路由,那么怎么知道一个客户的 ip 呢,去看 ipp 文件: cat /etc/openvpn/conf/ipp.txt zhangranrui,172.18.0.8 注意,上面文件里的 172.18.0.8 不是 ip ,是 网段 地址 172.18.0.9 是网关地址,172.18.0.10才是真正的 IP 地址,所以不要搞错了 ...

2022年11月19日