替代kubernetes crontab的神器kala

本来 k8s 的 crontab 是启动一个容器来运行的,很简单,如下: apiVersion: batch/v1beta1 kind: CronJob metadata: name: curl-cron spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: curl-cron image: radial/busyboxplus:curl imagePullPolicy: IfNotPresent command: - /bin/sh - -c - date;echo "run crontab";curl http://www.baidu.com restartPolicy: OnFailure successfulJobsHistoryLimit: 2 failedJobsHistoryLimit: 2 上面就跑了一个 busybox 的 pod,每分钟去访问百度,然后在 stdout 输出结果 这个没什么,注意上面文件的最后两行。限制成功以及失败 job 的 History 数量,如果不加限制,kubectl get pods 会看到无穷无尽的completed 状态的 curl-cron pod。 ...

2021年11月25日

kubernetes的hpa和自定义指标hpa

kubernetes 的动态伸缩 HPA 是非常有用的特性。 我们的服务器托管在阿里云的 ACK 上,k8s 根据 cpu 或者 内存的使用情况,会自动伸缩关键 pod 的数量,以应对大流量的情形。而且更妙的是,动态扩展的 pod 并不是使用自己的固定服务器,而是使用阿里动态的 ECI 虚拟节点服务器,这样就真的是即开即用,用完即毁。有多大流量付多少钱,物尽其用。 我们先明确一下概念: k8s 的资源指标获取是通过 api 接口来获得的,有两种 api,一种是核心指标,一种是自定义指标。 核心指标:Core metrics,由metrics-server提供API,即 metrics.k8s.io,仅提供Node和Pod的CPU和内存使用情况。api 是 metrics.k8s.io 自定义指标:Custom Metrics,由Prometheus Adapter提供API,即 custom.metrics.k8s.io,由此可支持任意Prometheus采集到的自定义指标。api 是 custom.metrics.k8s.io 和 external.metrics.k8s.io 一、核心指标metrics-server 阿里的 ACK 缺省是装了 metrics-server 的,看一下,系统里有一个metrics-server kubectl get pods -n kube-system 再看看 api 的核心指标能拿到什么,先看 Node 的指标: kubectl get --raw "/apis/metrics.k8s.io" | jq . kubectl get --raw "/apis/metrics.k8s.io/v1beta1" | jq . kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" | jq . 可以看到阿里 eci 虚拟节点的 cpu 和 memory 资源。 ...

2021年11月25日

ansible vault加密的使用

公司已经由 saltstack 全面转向了 ansible 。 用 ansible-playbook 执行各种任务的时候,需要登录主机,就必然涉及到主机 ssh 密码的输入。 最早我们是在 inventory 里做了定义: [deqin:vars] ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" host_key_checking=False ansible_ssh_user="peadmin" ansible_ssh_pass="Fuck2021!" [deqin] 192.168.1.19 太直白了,所有看到这文件内容的人都会知道密码了。完全没有安全性,这样行不通啊! 好在 ansible-vault 提供了一种方法来解决:那就是生成一个密文放进去,然后解开它必须再输入一个密码。这样看到的人也不知道实际的密码到底是什么 具体的做法如下,首先生成 key –> 加密字符串的键值对: ansible-vault encrypt_string 'Fuck2021!' --name 'ansible_ssh_pass' 输入密码,会得到下面一串字符 ansible_ssh_pass: !vault | $ANSIBLE_VAULT;1.1;AES256 37393235646234613332646366306233346330656666623862313339313861393239646261366237 6663343263363161643634653266343466356634656539650a393834663938636165336431656433 66333761643538623434363334316661653035313166333137373562363436613636366162353239 3661623733323933350a373164626131646235616361356638653733646534616163393362373135 6139 这个就是密文了,必须用输入的密码才能解开。 注意:这里的键值 name 不可改变,如果你想把字符串拷贝下来,改掉 ansible_ssh_pass 的名字,改成别的,想改名引用,是不行的。 这一大长串密文有以下两种用法: 一、ini格式的inventory引用 最原始的 inventory.ini 内容如下: [deqin:vars] ansible_ssh_common_args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" host_key_checking=False ansible_ssh_user="peadmin" [deqin] 192.168.1.19 我们定义 playbook 文件 shenji.yml: - hosts: deqin become: yes vars_files: - pass.yml vars: ansible_ssh_pass: '{{ ansible_ssh_pass }}' tasks: - name: mkdirs file: path="{{ item }}" state=directory with_items: - "OS.05" - "OS.06" 把密文放进 pass.yml 文件 ...

2021年11月24日

使用FreeIPA和FreeRadius搭建双因子认证服务器

审计啊审计,公司使用的华为防火墙需要配置双因子登录认证,这下麻烦了。 查了一下华为手册,支持 Radius 认证,那么没办法,最省钱的办法就是用 FreeIPA 和 FreeRadius 搭一套 OTP 双因子认证了。 系统是 CentOS 7 ,已关闭防火墙服务,方法如下: 一、搭建FreeIPA 首先设置 hostname hostnamectl set-hostname freeipa.rendoumi.local echo "192.168.1.5 freeipa.rendoumi.local" >> /etc/hosts 然后安装 FreeIPA,注意要回答的几个问题 不装bind,无论是 dnsmasq 或 coredns,都比 bind 轻,要装也装那两个。 server hostname 是 freeipa.rendoumi.local domian name 是 rendoumi.local realm name 是大写的 RENDOUMI.LOCAL 有两个密码,第一个是 LDAP 的密码,第二个是 IPA 的密码 yum -y install deltarpm yum update yum -y install freeipa-server sysctl net.ipv6.conf.all.disable_ipv6=0 ipa-server-install This program will set up the IPA Server. This includes: * Configure a stand-alone CA (dogtag) for certificate management * Configure the Network Time Daemon (ntpd) * Create and configure an instance of Directory Server * Create and configure a Kerberos Key Distribution Center (KDC) * Configure Apache (httpd) To accept the default shown in brackets, press the Enter key. WARNING: conflicting time&date synchronization service 'chronyd' will be disabled in favor of ntpd Do you want to configure integrated DNS (BIND)? [no]:no Server host name [freeipa.rendoumi.local]: Please confirm the domain name [rendoumi.local]: Please provide a realm name [RENDOUMI.LOCAL]: Directory Manager password: Password (confirm): ... IPA admin password: Password (confirm): The IPA Master Server will be configured with: Hostname: freeipa.rendoumi.local IP address(es): 192.168.1.5 Domain name: rendoumi.local Realm name: RENDOUMI.LOCAL Continue to configure the system with these values? [no]: yes The following operations may take some minutes to complete. Please wait until the prompt is returned. Configuring NTP daemon (ntpd) ... Setup complete Next steps: 1. You must make sure these network ports are open: TCP Ports: * 80, 443: HTTP/HTTPS * 389, 636: LDAP/LDAPS * 88, 464: kerberos UDP Ports: * 88, 464: kerberos * 123: ntp 2. You can now obtain a kerberos ticket using the command: 'kinit admin' This ticket will allow you to use the IPA tools (e.g., ipa user-add) and the web user interface. Be sure to back up the CA certificate stored in /root/cacert.p12 This file is required to create replicas. The password for this file is the Directory Manager password 以上,就装好了 FreeIPA,配置文件在 /etc/ipa/default.conf ...

2021年11月23日

KVM下附加硬盘的passthrough直通

这个比较有意思,同事要存放 2TB 的数据,但是系统是 1.7TB 的 4 块 600G 盘组成的 Raid10。 很明显盘空间不够了,去库房找两块 10TB 的大盘组成 Raid1 给他用好了。 问题来了,系统是 KVM 虚机,怎样把这个 10TB 的大盘给送进虚机呢? 这里面还真有要注意的问题: Important Guest virtual machines should not be given write access to whole disks or block devices (for example, /dev/sdb). Guest virtual machines with access to whole block devices may be able to modify volume labels, which can be used to compromise the host physical machine system. Use partitions (for example, /dev/sdb1) or LVM volumes to prevent this issue. ...

2021年11月19日

CentOS7的救援模式和紧急模式

说到 CentOS7 的紧急模式与救援模式,网上可以搜到漫天飞的帖子,说一下区别 RESCUE 救援模式: 救援模式启动的系统没有挂载硬盘,可以将硬盘 mount 出然后拷出数据。 EMERGENCY 紧急模式: 紧急模式启动的系统是一个最小的环境。根目录档案系统将会被挂载为仅能读取,而且将不会做任何的设定。 当然进入的方法也很简单,进入系统的时候按 e 修改 grub 菜单参数,就可以进入不同的模式 本文讨论的重点不是怎么进去,而是那两句命令,在紧急状态下反正我是记不住的 systemd.unit=rescue.target systemd.unit=emergency.target 都没有之前的 single 简单,也完全记不住,既然记不住,那就干脆做到菜单里好了,这才是本文的重点。 现在都是使用 grub2 了,而不是 grub,这很重要。grub2的配置文件是 /boot/grub2/grub.cfg。 修改 grub2 有两个工具,grub2-mkconfig 和 grubby,不要同时使用这两个工具修改,会覆盖的 grub2-mkconfig 会去搜索 /boot 目录下的内核文件,有多少个内核就会生成多少个启动项。那么如果是同一个内核,想修改不同的启动参数,做多个启动项就完蛋,他不能自动生成单内核的多个启动项 grubby 很灵活,可以根据当前 grub2 的配置,生成一个内核,多个不同启动参数的多个启动项。 那么我们要加进去两个只是启动参数不同,内核其实一样的启动项,用 grubby 就好了 grubby --add-kernel=\$(ls -1cat /boot/vmlinuz*|grep rescue) --title="RESCUE BOOT" --initrd=\$(ls -1cat /boot/initramfs*|grep rescue) --args="systemd.unit=rescue.target" --copy-default grubby --add-kernel=\$(ls -1cat /boot/vmlinuz*|grep rescue) --title="EMERGENCY BOOT" --initrd=\$(ls -1cat /boot/initramfs*|grep rescue) --args="systemd.unit=emergency.target" --copy-default 切忌我们之后不能运行 ...

2021年11月19日

linux下web pacproxy的用法

说实在话,这个场景非常怪异,客户在 linux 下要动态根据 url 选择代理: 看图,中间的是前端代理,地址是 192.168.1.1:8080,然后客户设置使用这个代理 export http_proxy=http://192.168.1.1:8080 export https_proxy=http://192.168.1.1:8080 然后对应后端有三个代理,两个 http 代理,一个 socks 代理 http 192.168.2.1:3128 socks 192.168.2.2:1080 http 192.168.2.3:3128 我们要根据客户的请求 URL 来决定具体要使用后端的哪个代理 这个如果在浏览器上设置非常容易,设置 PAC 即可。但是偏偏客户端不是浏览器,而是一个程序,那么麻烦就来了。怎么设置呢? 步骤很简单: 一、安装 pacproxy 网址:https://github.com/williambailey/pacproxy wget https://github.com/williambailey/pacproxy/releases/download/v.2.0.4/pacproxy_2.0.4_linux_amd64.tar.gz tar zxvf pacproxy_2.0.4_linux_amd64.tar.gz 拷出来 pacproxy 备用 二、生成配置文件 最主要的就是 PAC 文件的生成 我们给个例子: cat << EOF >> pac function FindProxyForURL(url, host) { if (host == "www.baidu.com") { return "PROXY 192.168.2.1:3128"; } else if (host == "www.sina.com.cn") { return "SOCKS 192.168.2.2:1080"; } else if (host == "www.sohu.com") { return "SOCKS 192.168.2.3:1080"; } else { return "DIRECT"; } } EOF 其实 PAC 文件的内容就是一段 javascript,用来返回代理的地址 ...

2021年11月16日

Tomcat server.xml配置详细解释

Tomcat 是一个 HTTP server。同时,它也是一个 serverlet 容器,可以执行 java 的 Servlet,也可以把 JavaServer Pages(JSP)和 JavaServerFaces(JSF)编译成 Java Servlet。它的模型图如下: 给个生产环境 server.xml 的例子: 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 <?xml version='1.0' encoding='utf-8'?> <Server port="-1" shutdown="SHUTDOWN"> <Listener className="org.apache.catalina.startup.VersionLoggerListener" /> <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" /> <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" /> <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" /> <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" /> <GlobalNamingResources> <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml" /> </GlobalNamingResources> <Service name="Catalina"> <Connector port="8080" server="www.rendoumi.com" protocol="org.apache.coyote.http11.Http11NioProtocol" maxHttpHeaderSize="8192" acceptCount="500" maxThreads="1000" minSpareThreads="200" enableLookups="false" redirectPort="8443" connectionTimeout="20000" relaxedQueryChars="[]|{}@!$*()+'.,;^\`&quot;&lt;&gt;" disableUploadTimeout="true" allowTrace="false" URIEncoding="UTF-8" useBodyEncodingForURI="true" /> <Engine name="Catalina" defaultHost="localhost"> <Realm className="org.apache.catalina.realm.LockOutRealm"> <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/> </Realm> <Host name="localhost" appBase="webapps" unpackWARs="false" autoDeploy="false"> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access_log." suffix=".txt" fileDateFormat="yyyy-MM-dd" pattern="%a|%A|%T|%{X-Forwarded-For}i|%l|%u|%t|%r|%s|%b|%{Referer}i|%{User-Agent}i " resolveHosts="false"/> <Context path="" docBase="/export/servers/tomcat/webapps/web" /> </Host> </Engine> </Service> </Server> 分解开来一部分一部分的看: ...

2021年11月15日

GlusterFS文件系统的优化

公司用了6年的GlusterFS终于到了要调整卷参数的地步了。 小文件已经多到要影响 IO 的地步了。 首先说一下结论,GlusterFS 安装完成后,基本不需要调整任何参数。生产系统千万不可盲目! 然后我们这是有特殊情况,所以调优步骤如下(以卷名为 esign-vol 为例): #必须关掉NFS gluster volume set esign-vol nfs.disable on #必须保留10%的空间,避免塞爆卷空间 gluster volume set esign-vol cluster.min-free-disk 10% #本机有256G的内存,所以设置25G的读缓存 gluster volume set esign-vol performance.cache-size 25GB #读缓存中,单个文件的缓存,最大文件size是128MB,大于128MB的单个文件不缓存 gluster volume set esign-vol performance.cache-max-file-size 128MB #设置每个客户端都允许多线程,缺省是2,多个小文件增加为4 gluster volume set esign-vol client.event-threads 4 #设置服务器端对特定的卷允许多线程,缺省是1,多个小文件增加为4 gluster volume set esign-vol server.event-threads 4 #分割线,以下参数不要调整,除非明确知道后果 #设置 io 线程数量,这个值缺省是16,已经很大了,足够用 gluster volume set esign-vol performance.io-thread-count 16 #设置写缓冲区,这个值缺省是1M,弄大了如果停电什么的,会丢数据 gluster volume set esign-vol performance.write-behind-window-size: 1M 以上就可以了。还有个参数 global-threading ,缺省是 off,不要设置为 on,有使用条件的,弄错了反而会导致性能降低。

2021年11月15日

Python的协程详细解释

在实际中遇到这样一个问题,公司软件发布上线自动化。 说简单点,就是需要去登录一个上线的内部网站,然后爬下所有的上线数据。 然后根据爬下来的数据整理好,可以一起上线的,就并发多线程,其实就是去传参数点击一个链接等返回。 不能并发的就单线程点链接。 那这个事情必须更有效率,单线程的没问题,用 python 的 request 就可以实现了。 我们仔细研究一下协程,先讲一下历史: 使用Python的人往往纠结在多线程、多进程,哪个效率更高?到底用哪个好呢? 其实 Python 的多进程和多线程,相对于别家的协程和异步处理机制,都不行,线程之间切换耗费 CPU 和寄存器,OS 的调度不可控,多进程之间通讯也不便。性能根本不行。 后来呢 Python 改进了语法,出现了 yiled from 充当协程调度,有人就根据这个特性开发了第三方的协程框架,Tornado,Gevent等。 官方也不能坐视不理啊,任凭别人出风头,于是 Python 之父深入简出3年,苦心钻研自家的协程,async/await 和 asyncio 库,并放到 Python3.5 后成为官方原生的协程。 对于 http请求、读写文件、读写数据库这种高延时的 IO 操作,协程是个大杀器,优点非常多;它可以在预料到一个阻塞将发生时,挂起当前协程,跑去执行其它协程,同时把事件注册到循环中,实现了多协程并发,其实这玩意是跟 Nodejs 的回调学的。 看下图,详细解释下,左边我们有100个网页请求,并发100个协程请求(其实也是1个1个发),当需要等待长时间回应回应时,挂起当前协程,并注册一个回调函数到事件循环(Event Loop)中,执行下一个协程,当有协程事件完成再通过回调函数唤醒挂起的协程,然后返回结果。 这个跟 nodejs 的回调函数基本一样,我们必须注意主进程和协程的关系,如果我在一个主进程中,触发协程函数,有100个协程,那么必须等待100个协程都结束后,才能回到正常的那个主进程中。当然,主进程也可能也是一个协程。 那么协程的基本用法 async f(n) 声明一个函数是协程的 await f(n) 挂起当前协程,把控制权交回 event loop,并且执行f(n)和注册之后的f(n)回调。 举个例子:如果在 g() 这个函数中执行了 await f(),那么g()函数会被挂起,并等待 f() 函数有结果结束,然后返回 g() 继续执行。 async def get(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() 最后一行 await 是挂起命令,挂起当前函数 get() ,并执行 response.text() 和注册回调,等待 response.text() 执行完成后重新激活当前函數get()继续执行,返回。 ...

2021年11月12日