生产环境Flink的搭建

生产环境用到了Flink,那其实无论腾讯的flink还是aws的flink,都是运行在容器中的 所以我们自建,也用容器,docker-compose.yaml一把梭 services: jobmanager: image: flink:1.17.1 ports: - "8081:8081" command: jobmanager environment: - | FLINK_PROPERTIES= jobmanager.memory.process.size: 4gb jobmanager.rpc.address: jobmanager taskmanager: image: flink:1.17.1 depends_on: - jobmanager command: taskmanager environment: - | FLINK_PROPERTIES= taskmanager.numberOfTaskSlots: 1 taskmanager.memory.process.size: 8gb jobmanager.rpc.address: jobmanager deploy: replicas: 1 注意啊,上面节点是16GB内存,所以job分了4G,然后task给了8G,TaskSlot给了1,副本也是1。内存富裕4G。是这么算的 我们再看一个配置,是测试环境的,节点是32G: services: jobmanager: image: flink:1.17.1 ports: - "8081:8081" command: jobmanager environment: - | FLINK_PROPERTIES= jobmanager.memory.process.size: 2gb jobmanager.rpc.address: jobmanager taskmanager: image: flink:1.17.1 depends_on: - jobmanager command: taskmanager environment: - | FLINK_PROPERTIES= taskmanager.numberOfTaskSlots: 4 taskmanager.memory.process.size: 8gb jobmanager.rpc.address: jobmanager deploy: replicas: 1 测试环境的内存就囧多了,jobmanager只给了2G,但是任务多且大,8G和Slot给了4,这个值是不断失败然后修改得到的 ...

2025年11月21日 · 1 分钟 · 82 字 · 八戒

Java进程cpu高排查

没办法了,被连续问到这个问题,必须讲一下了 java程序写得不好,要么cpu高,要么内存高,内存可以不断扩大,我们的某个pod,已经扩到8G了,node总共才16G,无语啊。 那究竟用啥玩意来排查呢? 那必须推荐async-profiler了 使用也很简单,运行排查即可 mkdir -p /opt/app/profiler wget https://github.com/async-profiler/async-profiler/releases/download/v4.2/async-profiler-4.2-linux-x64.tar.gz tar zxvf async-profiler-4.2-linux-x64.tar.gz -C /opt/app/profiler --strip-components=1 命令很简单,关键是参数-e的选择: 高 CPU 占用 (CPU-Bound)?缺省参数 首选: -e cpu 或 -e cycles 目标: 查看火焰图顶端最宽的方法,即消耗 CPU 最多的计算代码。 I/O 阻塞、锁等待或应用假死 (Latency/Blocking-Bound)? 首选: -e wall 目标: 查找火焰图顶端长时间处于 BLOCKED 或 I/O 相关的代码块,找出阻塞的根源。 频繁 GC 或内存消耗过快? 首选: -e alloc 目标: 找出哪些方法在短时间内创建了大量对象,导致年轻代 GC 频繁。 明确怀疑锁竞争? 首选: -e lock 目标: 准确找出哪个锁是主要的竞争点。 那确定目标,cpu是缺省参数,所以可以直接运行 ./asprof -d 30 -f 1.html 1 解释,-d 分析实践30秒,-f输出的文件,最后的1是java的进程号,这样就生成了一个1.html的文件 打开看看: ...

2025年11月17日 · 1 分钟 · 80 字 · 八戒

Kubernetes下各种资源的每日定期备份

这个问题其实比较有意思,同事早上用k9s修改Deployment的时候误删了一个deployment,这下麻烦了。那其实自己也有过类似操作,迁移的时候误删过influxdb,最后考古才弄回来,也误删过一个ns的secret,都是大麻烦! 其实系统是有velero备份的,但是从那里面去弄出单独一个ns的deploy进行恢复,怎么也来不及,那幸好有每日资源备份,直接拿过来重建即可。分享一下脚本,那注意一下,因为主节点的etcd也是非常要命的东西,也必须备一下,防止主节点脑裂,到时候可以用备份进行恢复。 #!/bin/bash export KUBECONFIG=/root/.kube/config export PATH=/usr/local/bin:$PATH # K8s 资源备份脚本 # 目标:遍历所有命名空间,将 Deployment, Secret, Ingress, 和 Service 导出为 YAML 文件。 # 设置输出目录 OUTPUT_DIR="/root/k8s_deployments_backup/$(date +%Y%m%d)" # 要备份的资源类型及其对应的 kubectl 别名 # 注意:Secret 资源会备份所有类型,包括自动生成的 Service Account Tokens。 RESOURCE_TYPES=("deployments" "secrets" "ingresses" "services") # 确保脚本在遇到错误时立即退出 set -e echo "--- 正在创建输出目录: $OUTPUT_DIR ---" mkdir -p "$OUTPUT_DIR" # ---------------------------------------------------- # 核心函数:备份指定类型的资源 # 参数: $1 = 资源类型 (如 deployment), $2 = 命名空间, $3 = 输出目录 # ---------------------------------------------------- backup_resource() { local TYPE=$1 local NAMESPACE=$2 local DIR=$3 # 获取当前命名空间中所有该类型资源的名称 # 使用 2>/dev/null 隐藏找不到资源时的错误信息 local RESOURCES=$(kubectl get "$TYPE" -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null) if [ -z "$RESOURCES" ]; then echo " [信息] 命名空间 $NAMESPACE 中未找到 $TYPE。" return fi echo " -> 找到 ${#RESOURCES[@]} 个 $TYPE,正在导出..." # 遍历每个资源并保存 YAML for RESOURCE in $RESOURCES; do # 文件名格式: namespace-resource_type-resourcename.yaml local FILE_NAME="${NAMESPACE}-${TYPE}-${RESOURCE}.yaml" local FILE_PATH="$DIR/$FILE_NAME" # 使用 kubectl get 获取 YAML,并通过管道和 sed 清理元数据字段 kubectl get "$TYPE" "$RESOURCE" -n "$NAMESPACE" -o yaml --ignore-not-found | # 清理元数据字段,使 YAML 更干净,便于重新应用 sed '/^ creationTimestamp:/d' | sed '/^ resourceVersion:/d' | sed '/^ uid:/d' | sed '/^ selfLink:/d' | sed '/^ status:/d' \ > "$FILE_PATH" # 检查 Secret 是否为 Service Account Token,如果是,则发出警告 if [ "$TYPE" == "secrets" ] && grep -q 'kubernetes.io/service-account.name' "$FILE_PATH"; then echo " [警告] Secret $RESOURCE 是 Service Account Token,通常不需要备份。" fi done } # 1. 获取所有命名空间 (Namespace) NAMESPACES=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}') if [ -z "$NAMESPACES" ]; then echo "错误:未找到任何命名空间。请检查 kubectl 配置和集群连接。" exit 1 fi echo "找到以下命名空间: $NAMESPACES" echo "----------------------------------------------------" # 2. 遍历每个命名空间和资源类型 for NAMESPACE in $NAMESPACES; do echo "--- 处理命名空间: $NAMESPACE ---" for TYPE in "${RESOURCE_TYPES[@]}"; do backup_resource "$TYPE" "$NAMESPACE" "$OUTPUT_DIR" done echo "--- 命名空间 $NAMESPACE 处理完成 ---" done ETCDCTL_API=3 etcdctl \ --endpoints=https://10.10.240.3:2379 \ --cacert=/etc/ssl/etcd/ca.pem \ --cert=/etc/ssl/etcd/etcd-client.pem \ --key=/etc/ssl/etcd/etcd-client-key.pem \ snapshot save $OUTPUT_DIR/etcd-`date +%Y%m%d`-snapshot.db echo "====================================================" echo "✅ 所有指定资源已成功备份到目录: $OUTPUT_DIR" echo "====================================================" exit 0

2025年11月17日 · 2 分钟 · 286 字 · 八戒

AWS 放错在Private子网的机器如何用公网IP连进去

今天面试了一下,面试官问到AWS的子网问题,来说说AWS的最佳实践: 首先是VPC的划定,然后就是三个public子网,三个private子网,都是一个zone分布在a、b、c三个不同机房 来保证最大冗余性,然后pub子网通过IGW来出公网,那Private子网就通过NAT出公网。 但是,但是,但是: 如果你把一台EC2服务器一开始就放错了子网,放到了private子网里,然后上面又跑了重要的服务,无法迁移,无法重启,而且整个vpc里只有这一台ec2,其它东西都是aws的服务或者市场的服务又或者fargate、lambda之类的无服务器,这时候你想进去调试,那就麻烦大了 那能不能给这台private的机器加上公网ip来当作跳板机直接访问呢? 答案是肯定的,可以。 但是又来了,如果这么配置了,你要对路由非常的熟悉,因为随后发生错乱的情况可能需要你手动添加路由,最麻烦的不是配置网卡,而是配置路由! 做法如下: 一、动态添加弹性ip到这个ec2的第二个网卡 二、配置网络 # vi /etc/netplan/50-cloud-init.yaml # This file is generated from information provided by the datasource. Changes # to it will not persist across an instance reboot. To disable cloud-init's # network configuration capabilities, write a file # /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following: # network: {config: disabled} network: ethernets: enX0: addresses: - 10.0.132.169/20 #The private IP address of primary ENI nameservers: addresses: - 10.0.0.2 routes: - to: 0.0.0.0/0 via: 10.0.128.1 # Default gateway, you can find it using** ip r** command table: 1001 - to: 10.0.132.169 via: 0.0.0.0 scope: link table: 1001 routing-policy: - from: 10.0.132.169 table: 1001 dhcp4: no dhcp6: false match: macaddress: 06:36:82:ef:39:39 set-name: enX0 enX1: addresses: - 10.0.3.176/20 #The private IP address of primary ENI gateway4: 10.0.0.1 nameservers: addresses: - 8.8.8.8 - 1.1.1.1 routes: - to: 0.0.0.0/0 via: 10.0.0.1 # Default gateway, you can find it using** ip r** command table: 1002 - to: 10.0.3.176 via: 0.0.0.0 scope: link table: 1002 routing-policy: - from: 10.0.3.176 table: 1002 - from: 120.116.111.99 table: 1002 dhcp4: no dhcp6: false match: macaddress: 06:29:c3:b2:c5:f9 set-name: enX1 version: 2 netplan apply 详细解释一下,enX0是private子网的网卡,enX1是弹性IP的网卡,注意,即使是弹性IP,也是个内网地址,这两个IP呢,都有各自独立的网关,第二个网卡还有自己的DNS。这就容易发生错乱了,因为缺省路由走第一个网卡,那如果从第二个网卡的公网IP入,出的时候走第一张网卡,那就有意思了。 ...

2025年11月16日 · 2 分钟 · 227 字 · 八戒

安装轻量级的Docker registry:zot

服务器在HK,但是去拉取腾讯广州Docker镜像仓的时候总是失败,还是企业版,大无语了。 没办法,被逼无奈,干脆在HK建一个镜像仓,然后推送到那里,这样就方便了。 刚开始的想法是用Harbor,但是Harbor实在太沉重了,而且Harbor的最大问题是过期镜像清理,如果过期镜像出错了,那是清理不掉的,除非重新打包上传覆盖掉错误的镜像,然后再删除才可以,但问题是如果有700、800个错的镜像,那是真没有那个劲去清理了。 那这次就想建个轻量级的,干脆连WEB界面都不要,只要docker login后能push和pull即可! 这样的话zot是个不错的选择,zot是兼容oci标准的,某些地方跟docker是不兼容的: 下载zot wget https://github.com/project-zot/zot/releases/download/v2.1.10/zot-linux-amd64 准备配置文件 cat >/usr/local/bin/config.json << EOF { "distSpecVersion": "1.0.0-dev", "http": { "address": "127.0.0.1", "port": "5000", "compat": ["docker2s2"], "auth": { "htpasswd": { "path": "/usr/local/bin/htpasswd" } } }, "storage": { "rootDirectory": "/data/registry" } } EOF 注意上面compat,如果不配置,就会出现push的时候出现manifest invalid错误,这是因为docker和oci的标准不同,zot是遵循oci标准的 准备htpasswd,必须用bcrypt的方式,zot不支持其它的! -B Force bcrypt hashing of the password (very secure) htpasswd -B -c /usr/local/bin/htpasswd user01 准备zot.service ...

2025年11月11日 · 1 分钟 · 146 字 · 八戒

Caddy自动签发任意域名证书

公司的SAAS平台,本来多租户的证书管理模式预计是这样的: 给每个租户单独设立一个 xyz.alibaba.com 的子域名,然后用cert-manager管理证书,只要annouce一个 xyz.alibaba.com 的ingress出来,就会自动签发证书。 然而研发觉得这样也是很不爽,连annouce ingress也不愿意,就想客户的域名直接解析一个 xyz.客户.com 的 cname 过来,然后就自动签发证书。 这个需求真的是很有意思,也只有caddy能很轻松的这么实现,方法如下: 一、做好一个caddy的deploy和service,前面直接顶一个Loadbalance 二、配置caddy的配置 /etc/caddy/Caddyfile 的内容 { debug on_demand_tls { ask http://localhost:5555/ } } https:// { tls { on_demand } handle { reverse_proxy https://alibaba.com { header_up Host alibaba.com } } respond "Welcome to dynamic certificate signing!" 200 } http://localhost:5555 { respond 200 } 注意上面,Caddy对这种随便的域名,是要求去问一下后端的服务是否对这个域名进行签发的,这是为了防止滥用。 我们直接做个虚拟服务,回应200即可 这样的做法比较直接了当,当然只适用于国外,国内就麻烦了,未知的域名解析到你的ip上,没备案直接就会被封站。

2025年11月10日 · 1 分钟 · 57 字 · 八戒

AWS的ECS使用Fargate服务器如何开启shell进入容器调试

公司用到了Amazon Elastic Container Service,完全托管的容器服务 服务器用的是Fargate,没有自己的服务器,纯托管的,开始部署的时候,也没有考虑到有一天要进入容器进行调试。 现在麻烦就来了,需要进入容器,查看文件日志 结果就很麻烦,解决的步骤如下: 一、查看集群状态,看看任务定义的版本号,下面是206 二、去到相应的任务定义 注意,现在只有一个任务执行角色,任务角色是个空的!!! 然后我们点开任务执行角色,添加权限 初始化的时候只有AmazonECSTaskExecutionRolePolicy, 我们需要加上AmazonSSMManagedInstanceCore 加完就上面一样。 三、返回到任务定义,使用JSON创建新修订 "taskRoleArn": "arn:xxxx:ecsTaskExecutionRole", "executionRoleArn": "arn:ecsTaskExecutionRole", 新增taskRoleArn,和executionRoleArn保持一致,然后保存 四、按照新版本,部署新任务 从新任务定义更新服务 选择相应集群,相应服务 故障排除配置,务必开启ECS exec,然后更新即可 五、开启容器的shell 我去集群–服务–容器,点击连接 aws ecs execute-command --cluster yoov_work_e_form_prod_cluster --task 23xxxxx --container yontainer --interactive --command '/bin/sh' 实际对应的命令如上。其实也可以用aws的cli客户端直接执行,只不过要拿到集群名和容器名。

2025年11月10日 · 1 分钟 · 40 字 · 八戒

MongoDB的伪副本模式

这个话题比较有意思,公司的测试环境本来是一个mongodb的三节点三副本全集群,升级了2次升到8.0。 结果吧,由于里面数据过于庞大,测试的时候经常把节点打挂,虽说是打挂后又能很快被docker拉起来,但是挂的那个时刻应用程序就掉了,导致测试失败。 这三台节点还都是8 cpu 32G memory的配置,真大无语了。 没办法,干脆弄成单节点,然后内存垒高到128G,cpu加到32,弄好之后,读取数据的Flink又不行了,要求是必须读mongo的副本,真完犊子。 那就强制启动个副本模式吧: mongo启动方式用的是docker compose,启动的时候新增参数 --replSet rs0 # mongo数据目录的属主是 999:999 mkdir -p /data/mongodb/27017/data chown -R 999:999 /data/mongodb/27017/data mkdir -p /data/mongodb/27017/conf/ cd /data/mongodb/27017 #先gen keyfile openssl rand -base64 756 > keyfile chmod 400 keyfile #准备mongod.conf EOF < cat > /data/mongodb/27017/conf/mongod.conf systemLog: destination: file path: "/data/db/mongod.log" logAppend: true net: bindIp: 0.0.0.0 port: 27017 security: keyFile: "/data/configdb/keyfile" EOF EOF < cat > /data/mongodb/docker-compose.yaml services: mongo: container_name: mongo image: mongo:8.0 restart: always environment: TZ: Asia/Shanghai volumes: - /data/mongodb/27017/data:/data/db - /data/mongodb/27017/conf/mongod.conf:/data/configdb/mongod.conf - ./keyfile:/data/configdb/keyfile command: --replSet rs0 --config /data/configdb/mongod.conf ports: - 27017:27017 EOF docker compose up -d 然后不算完啊,启动之后需要进入容器执行rs.initiate(), 放心大胆的执行,不会破坏任何原有的数据 ...

2025年10月29日 · 1 分钟 · 112 字 · 八戒

cert-manager的不同clusterissuer验证方式

cert-manager普通签发证书的时候,通常是DNS的A记录已经解析到相关的Ingress前置的LB 公网IP了。 第一种情况,有公网IP的证书签发,验证方式是http 那准备好cluster-issuer.yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod1 spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: zhangranrui@rendoumi.com privateKeySecretRef: name: letsencrypt-prod1 solvers: - http01: ingress: class: nginx 第二种情况,如果集群是部署在内网,根本没有公网ip,就不能通过80和443的验证来签发了,只能用DNS校验的方式来签发证书 那以cloudflare托管的DNS为例,我们需要拿到CF的dns-api的token,然后声明,再定义ClusterIssuer --- apiVersion: v1 kind: Secret metadata: name: cloudflare-api-token-secret type: Opaque stringData: api-token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod2 spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: zhangranrui@rendoumi.com privateKeySecretRef: name: letsencrypt-prod2 solvers: - dns01: cloudflare: email: zhangranrui@rendoumi.com apiTokenSecretRef: name: cloudflare-api-token-secret key: api-token 那第一种和第二种的区别就是solvers是不同的。 ...

2025年10月24日 · 1 分钟 · 110 字 · 八戒

OPNsense的远程安装并访问WEBGUI

OPNsense安装其实很容易,但是远程装就不那么好玩了。 尤其涉及到内网WEBGUI的访问,缺省安装完成以后,只能从内网口访问管理界面,问题是VPC里只有一台Linux机器,还没有浏览器,怎么办? 记录一下安装过程,备查: 一、下载iso镜像并解压 wget https://pkg.opnsense.org/releases/25.7/OPNsense-25.7-dvd-amd64.iso.bz2 bzip2 -d OPNsense-25.7-dvd-amd64.iso.bz2 二、kvm用virt-install安装iso,注意桥接了2个网卡 #!/bin/bash mkdir -p /export/kvm/opn/ qemu-img create -f qcow2 /export/kvm/opn/opn.qcow2 20G virt-install \ --name=opn \ --vcpu=2 \ --ram=2048 \ --disk path=/export/kvm/opn/opn.qcow2,format=qcow2,size=20 \ --cdrom=/export/kvm/iso/OPNsense-25.7-dvd-amd64.iso \ --network bridge=br0,model=virtio \ --network bridge=br1,model=virtio \ --os-type unix \ --os-variant freebsd13.1 \ --vnc --vnclisten=0.0.0.0 --vncport=5901 三、vnc连到port 5901上进行安装配置 OPNsense公网的网卡、ip、mask、gateway配置 OPNsense内网的网卡、ip、mask配置 四、设置内网访问 这里就非常鬼畜了,首先在内网的那台机器上做port转发,这台机器是192.168.100.2,做个systemd的service: cat /etc/systemd/system/opn-proxy.service [Unit] After=network.target Wants=network.target [Service] WorkingDirectory=/usr/local/bin/ Type=simple ExecStart=/usr/local/bin/go-tcp-proxy_1.0.2_linux_amd64 -l ":4430" -r "192.168.100.1:4430" Restart=on-failure RestartSec=1s [Install] WantedBy=multi-user.target 如上的话,如果web访问http://192.168.100.2:4430,那Host: 192.168.100.1 会被转发给 192.168.100.1 的OPNsense的4430端口 那OPNsense的内网IP是192.168.100.1,登录用户是root,密码是xxxxxxxx 从vnc上登录,然后进入shell,找到webgui的部分,添加port和althostnames的两项 vi /conf/config.xml ...... <webgui> <protocol>https</protocol> <ssl-certref>68adc26101eeb</ssl-certref> <port>4430</port> <althostnames>192.168.100.2</althostnames> </webgui> ...... 然后重启webgui ...

2025年10月23日 · 1 分钟 · 95 字 · 八戒