用Docker部署kafka且不依赖zookeeper

用redpanda居然还要每隔30天更新一下License文件,叔可忍婶不可忍啊。 换掉换掉,直接用Docker部署kafka,后端不用zookeeper,用kraft,天下苦zk也久矣! 方法很简单,用docker-compose,编辑docker-compose.yml文件 name: 'stream' services: kafka: image: confluentinc/cp-kafka:latest hostname: kafka container_name: kafka ports: - 9092:9092 - 9093:9093 environment: KAFKA_KRAFT_MODE: true # This enables KRaft mode in Kafka. KAFKA_PROCESS_ROLES: controller,broker # Kafka acts as both broker and controller. KAFKA_NODE_ID: 1 # A unique ID for this Kafka instance. KAFKA_CONTROLLER_QUORUM_VOTERS: 1@10.8.1.119:9093 # Defines the controller voters. KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://10.8.1.119:9092 KAFKA_LOG_DIRS: /var/lib/kafka/data # Where Kafka stores its logs. KAFKA_AUTO_CREATE_TOPICS_ENABLE: true # Kafka will automatically create topics if needed. KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 # Since we’re running one broker, one replica is enough. KAFKA_LOG_RETENTION_HOURS: 168 # Keep logs for 7 days. KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 # No delay for consumer rebalancing. CLUSTER_ID: Mk3OEYBSD34fcwNTJENDM2Qk # A unique ID for the Kafka cluster. volumes: - /var/run/docker.sock:/var/run/docker.sock - ./data:/var/lib/kafka/data # Store Kafka logs on your local machine. 注意以上的文件,暴露了2个端口,9092和9093,9092用于客户端连接,9093用于集群通讯;KAFKA_KRAFT_MODE: true用kraft不用zk. ClUSTER_ID可以换个不同的。 ...

2025年07月28日 · 2 分钟 · 245 字 · 八戒

aws的EC2服务器用lego获得免费证书并更新到ACM

交代一下背景: 公司在AWS上用的是EKS+Fargate,还有一个跳板机,证书呢就不太想用aws收费的了,干脆用免费的证书好了。 那问题就来了,Let‘s encrypt的证书90天到期,已经手动更新过3次了,实在太麻烦了,能不能自动申请并更新到ACM呢? 答案是可以的,原理是:利用EC2可以携带IAM角色的原理,就可以无凭证更新免费证书到AWS Certificate Manager了 做法如下: 一、首先去ACM中拿到证书的ARN,要确保IAM权限不外泄,避免更新错证书 二、生成一个IAM的policy,命名为AllowUpdateCertificate,只允许更新这两个特定的证书 具体策略内容如下: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "acm:ImportCertificate" ], "Resource": [ "arn:aws:acm:ap-southeast-1:111111111111:certificate/arn01", "arn:aws:acm:ap-southeast-1:111111111111:certificate/arn02" ] } ] } 三、IAM生成一个role角色,命名为Ec2UpdateCertificateRole,把AllowUpdateCertificate策略给附上 四、到EC2的实例详细信息中,操作–>安全–>修改IAM角色 附上Ec2UpdateCertificateRole这个角色 五、在EC2的机器上,装好AWS CLI,还有lego(申请Let’s encrypt证书的软件) 先写好获得证书的脚本 get_cert.sh,域名DNS解析是托管到cloudflare的,所以也直接用API TOKEN来处理 #!/bin/bash CLOUDFLARE_DNS_API_TOKEN=######## /usr/local/bin/lego --path /usr/local/bin/certs --email zhangranrui@rendoumi.com --dns cloudflare --domains *.rendoumi.com --domains rendoumi.com renew --renew-hook="/usr/local/bin/update_aws_cert.sh" 参数--renew-hook如果有新证书,就调用后面的脚本。如果没有新证书,就无操作,避免无用功。 然后再写好 update_aws_cert.sh,这就是个千字文了,由gemini生成的: 由于lego生成的crt是证书+证书链,所以第一步要把证书部分给单独拆出来 #!/bin/bash cd /usr/local/bin/certs/certificates #首先把证书单独拆出来 awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/ {print} /-----END CERTIFICATE-----/ {exit}' _.rendoumi.com.crt > _.rendoumi.com.only # --- 配置部分 --- # 您要更新的现有 ACM 证书的 ARN # 示例:CERTIFICATE_ARN="arn:aws:acm:ap-southeast-1:123456789012:certificate/abcdefab-1234-5678-abcd-1234567890ab" CERTIFICATE_ARN="arn:aws:acm:ap-southeast-1:111111111111:certificate/11111111-1111-1111-1111-111111111111" # 新证书文件的路径(例如:/opt/certs/new_certificate.crt) # 确保这个路径在您的EC2实例上是正确的 CERTIFICATE_BODY_PATH="_.rendoumi.com.only" # 新私钥文件的路径(例如:/opt/certs/private.key) # 确保这个路径在您的EC2实例上是正确的 PRIVATE_KEY_PATH="_.rendoumi.com.key" # 证书链文件的路径(可选) # 如果没有证书链,请将此变量留空或注释掉 # 示例:CERTIFICATE_CHAIN_PATH="/opt/certs/ca_bundle.crt" CERTIFICATE_CHAIN_PATH="_.rendoumi.com.issuer.crt" # 如果没有链,请将其留空 "" # ACM 证书所在的 AWS 区域 # 这个区域应该与 CERTIFICATE_ARN 中的区域匹配 # 示例:AWS_REGION="ap-southeast-1" AWS_REGION="ap-southeast-1" # --- 配置部分结束 --- echo "--- 开始使用实例角色重新导入 ACM 证书 ---" echo "证书 ARN: ${CERTIFICATE_ARN}" echo "区域: ${AWS_REGION}" # 检查文件是否存在 if [ ! -f "${CERTIFICATE_BODY_PATH}" ]; then echo "错误:证书文件 '${CERTIFICATE_BODY_PATH}' 不存在。" exit 1 fi if [ ! -f "${PRIVATE_KEY_PATH}" ]; then echo "错误:私钥文件 '${PRIVATE_KEY_PATH}' 不存在。" exit 1 fi if [ -n "${CERTIFICATE_CHAIN_PATH}" ] && [ ! -f "${CERTIFICATE_CHAIN_PATH}" ]; then echo "警告:证书链文件 '${CERTIFICATE_CHAIN_PATH}' 已指定但不存在。将不包含证书链进行导入。" CERTIFICATE_CHAIN_PATH="" # 如果文件不存在,则清空路径 fi # 构造 AWS CLI 命令。AWS CLI 会自动使用实例角色提供的凭证。 IMPORT_CMD="aws acm import-certificate \ --certificate-arn ${CERTIFICATE_ARN} \ --certificate fileb://${CERTIFICATE_BODY_PATH} \ --private-key fileb://${PRIVATE_KEY_PATH} \ --region \"${AWS_REGION}\"" # 如果有证书链,则添加到命令中 if [ -n "${CERTIFICATE_CHAIN_PATH}" ]; then IMPORT_CMD="${IMPORT_CMD} --certificate-chain fileb://${CERTIFICATE_CHAIN_PATH}" fi echo "正在执行命令:" echo "$IMPORT_CMD" echo "--------------------------" # 执行命令 # 注意:这里我们不指定 --profile 或任何凭证,AWS CLI 会自动使用实例IAM角色。 eval $IMPORT_CMD # 检查命令执行结果 if [ $? -eq 0 ]; then echo "--- 证书重新导入成功!---" echo "ACM 证书 ${CERTIFICATE_ARN} 已更新。" else echo "--- 证书重新导入失败!---" echo "请检查错误消息,确保:" echo "1. EC2 实例已正确附加了具有 acm:ImportCertificate 权限的 IAM 角色。" echo "2. 证书、私钥和链文件路径正确且可读。" echo "3. 证书/私钥/链的格式正确。" fi echo "--- 脚本执行完毕 ---" 看起来真的是又臭又长,废话连篇。注意:gemini 生成的代码有问题,aws acm import-certificate 的参数中,证书文件的前缀是fileb://,gemini给的是file://,调试了半天,最后去aws看了文档才发现。 ...

2025年07月25日 · 2 分钟 · 285 字 · 八戒

Redpanda到期后如何续License

上回我们在 Kafka的测试替代品-Redpanda 中说了怎么搭建,结果用了一个月,问题就来了 license过期了,擦的,只允许用30天,需要续费了。 方法如下: 一、打开网站,申请30天的免费license https://cloud.redpanda.com/try-enterprise 打开网站,然后填入姓名和邮箱,就会得到一个长字符串xyz,拷贝下来 二、去机器上 我们是用docker compose启动的,所以需要进入容器,导入license docker exec -it redpanda-0 /bin/sh rpk cluster license set xyz 三、重启容器 按道理上一步弄完,就应该好了的,可惜这产品做的稀烂。还必须重启容器才起作用 docker compose restart 就ok了,但是30天就更新一次license,而且必须要重启容器,这谁受得了啊。 下周就抽空装一个不依赖Zookeeper,用Rraft的Kafka来用,彻底去掉这个不伦不类的软件。

2025年07月25日 · 1 分钟 · 28 字 · 八戒

Postgres不同实例、不同用户直接的数据导出导入

公司生产环境在AWS使用了 Aurora Postgres Serveless 的数据库。 测试环境是在内网搭建了一个postgres 13数据库。 那测试和生产的实例不同,数据库不同,用户名也不同,需要把测试环境的A库的schema.public迁移到生产B库的schema.public。 普及一下概念,PG的权限分三层,数据库、schema、schema中的对象。 那么用pg_dump的话,如果不带任何参数,那会把权限也给带上,那就麻烦大了去了! 所以正确的导出、导入步骤如下: 一、首先要观察源库,看看都有什么extention,在schema.public有什么Functions 如上图所示,看到uuid_generate # 看看服务器版本 select version(); PostgreSQL 13.14 那就很奇怪,extension 只有一个plpgsql,functions却有一堆uuid_generate 那十有八九需要用到extension uuid-oosp 先去目的库建一个吧 create extension "uuid-ossp"; 二、dump数据 这里要注意,不要把任何权限的东西带进来 pg_dump -h 172.16.8.1 -U postgres -d source --schema=public --no-owner --no-privileges --file=export.sql # 全备份的命令 # export PGPASSWORD="xxxxxxxx" # pg_dump -U bbc -h localhost -p 5432 bbc > dc_system.sql 然后要编辑看看这个导入的文件 发现有create schema的语句,如果目的数据库已经有了,那需要去掉。 再看下面: 建立了一个FUNCTION,如果已经建立了extension,那也会报错,不过这2种错误我们都可以直接不用管,直接执行试试 三、导入目标库 跑一跑,看一看: psql -h 10.8.0.1 -U dest -d dest -f export.sql 报了不少错 ...

2025年07月23日 · 1 分钟 · 132 字 · 八戒

内网kubernetes+cloudflare+cert-manager自动签发证书

这个场景非常的有意思: 公司内部有若干个kubernetes集群,属于测试环境,研发搭建了一套SAAS的环境,这个环境需要xxx.abc.com的域名来访问,域名解析记录全都是192.168.0.x,供内部访问,域名会动态生成,需要证书也随之生成,且dns域名托管在cloudflare上。 基本环境如上,麻烦的是 cert-manager 不可能签发私网的证书,只能曲线救国,用dns验证的方式来签发证书了,如果配上 external-dns,那dns解析也不用做了;只要发布一个ingress,就直接生成dns解析记录和对应的https证书。 具体做法如下: 一、配置 cloudflare API token 登录cloudflare,去生成API Token,路径是:User Profile > API Tokens > API Tokens. Permissions: Zone - DNS - Edit Zone Resources: Include - All Zones cert-manager的官方文档居然是错的,https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/ 推荐的 Zone - Zone - Read 是加不上的,有Edit就足够了 然后获得Token的字符串xxxxxx 二、安装cert-manager 这个简单,直接用helm安装 现在这个时间节点,2025.07.09,cert-manager 是 v1.18.2,所以用新命令执行安装 helm repo add jetstack https://charts.jetstack.io helm repo update #老版本比如1.0 helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true #新版本v1.18.2 helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set crds.enabled=true 三、准备签发机构Issuer ...

2025年07月09日 · 2 分钟 · 276 字 · 八戒

CORS跨域在不同环境中的配置

Cross-Origin Resource Sharing (CORS) 跨域,一个网站引用了另外一个网站的资源,就需要跨域了。 最通俗点,就是服务器的返回头上要加上三行: Access-Control-Allow-Origin", "*" Access-Control-Allow-Methods", "GET, POST, OPTIONS" Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" 说白点:就是允许的来源站,允许的方法,允许的头信息 那在不同的地方配置又不相同: 一、Nginx server { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS'; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization'; 二、openresty中的lua脚本 location /pg/nginit { rewrite_by_lua_block { ngx.log(ngx.ERR, "Environment configuration update, about to get configuration information from redis") -- 添加跨域 CORS 头 ngx.header["Access-Control-Allow-Origin"] = "*" -- 允许所有域名请求 ngx.header["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS" -- 允许的方法 ngx.header["Access-Control-Allow-Headers"] = "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" -- 允许的请求头 三、AWS的S3 <CORSConfiguration> <CORSRule> <AllowedOrigin>http://example.com</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration> 四、AWS Lambda 或者 API Gateway headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': true, } 最后,AWS的ALB不支持配置CORS!!!,必须在后端的 application 中设置。 ...

2025年06月17日 · 1 分钟 · 91 字 · 八戒

AWS的跨账号访问不同VPC域中的服务

公司有两个AWS账号,有各自不同的 VPC 域。其中一个账号用到了 Aurora Postgres Serveless 的数据库。 现在另一个账号也需要用到posgres数据库,最直接的想法是在本域再搭一套postgres,或者ec2新加一台,上面安装posgres。 先去检查了一下已有的postgres serverless服务,结果使用率才23%,当下就直接想复用它了。 那能不能单独再开一个db,单独给第二个账号用呢,查了半天资料。可以的,但是吧,chatgpt真的是满嘴胡说八道,连带gemini也是。 aws家的东西都是开源经过魔改,跟网上占大多数的资料是不同的,这直接导致 chatgpt 按照网上大多数文档来进行推理,导致错误!!! 还有就是aws家的东西更新修改很快,最新的文档和之前的文档不同,也导致 chatpt 按照网上大多数的旧文档来进行推理,导致错误!!! 这个东西的架构图如上,右边的VPC里面连着 aurora 服务,它实际是target group的一个目标,然后上联到nlb,nlb再上联到endpoint service,对外提供服务. 然后左面VPC中建endpoint,通过PrivateLink连接到右边的EndpointService,这样通过私有的dns name和security group再进行控制,就可以直接连到右边的endpoint service服务资源了。 具体的做法步骤如下: 一、第一步,右边的VPC中 1、建立EndpointService 到VPC中,左边的Endpoint Services,点击: 点击Create endpoint service新建一个 名字:postgresql-serverless-endpoint-service,由于现在还没有nlb,需要再新建一个;点击Create new load balancer 2、建立nlb 点击:Create load balancer新建 这个类型,只能是nlb 我们这个nlb,不放在公网,不公开,所以选私有的Internal,只有IPV4,不提供IPV6. 网络选该VPC,子网选择三个private的子网 Security groups选择一下,这里rds-sg的inbound规则是允许TCP端口5432的进入,如果没有就新建一个SG 到最后了,居然还没有target group,那又要建一个了,点击Create target group建立 3、建立target group 名字叫做:postgres-target-group 我们选IP addresses,因为要连到后面的postgres服务 然后选好TCP,Port 5432, IPV4,VPC,其它保持缺省,然后建立 ...

2025年05月21日 · 1 分钟 · 130 字 · 八戒

kafka的测试替代品-redpanda

公司的一个公网要搬到内网的一个Rancher集群上,很繁琐 原有的架构有kafka,要在内网复现一个出来 那3节点的kafka和3节点的zookeeper,真的是不想搭啊 搜啊搜啊搜啊搜,终于找到若干平替: Redis 的平替 Dragonfly Mongo 的平替 FerretDB Kafka 的平替 Redpanda 那就选定用Redpanda来搞,方法还是有一些曲折的: Redpanda 在 2025年5月16日这个节点,有5个版本 最新的25.1的版本,如果用docker compose来启动,是个压缩包,好多文件。 数据盘有三个了,而且用到了minio做后端的持久化卷,这么复杂,那不如直接搞Kafka了 那只能往前回退,选用24.2的版本,docker compose 就一个文件,不过这个版本 2025年7月31日就终结支持了。 下载的话:https://docs.redpanda.com/24.2/get-started/quick-start/ 我们要的是一个broker的,那直接给出源文件 name: redpanda-quickstart-one-broker networks: redpanda_network: driver: bridge volumes: redpanda-0: null services: redpanda-0: command: - redpanda - start - --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092 # Address the broker advertises to clients that connect to the Kafka API. # Use the internal addresses to connect to the Redpanda brokers' # from inside the same Docker network. # Use the external addresses to connect to the Redpanda brokers' # from outside the Docker network. - --advertise-kafka-addr internal://redpanda-0:9092,external://localhost:19092 - --pandaproxy-addr internal://0.0.0.0:8082,external://0.0.0.0:18082 # Address the broker advertises to clients that connect to the HTTP Proxy. - --advertise-pandaproxy-addr internal://redpanda-0:8082,external://localhost:18082 - --schema-registry-addr internal://0.0.0.0:8081,external://0.0.0.0:18081 # Redpanda brokers use the RPC API to communicate with each other internally. - --rpc-addr redpanda-0:33145 - --advertise-rpc-addr redpanda-0:33145 # Mode dev-container uses well-known configuration properties for development in containers. - --mode dev-container # Tells Seastar (the framework Redpanda uses under the hood) to use 1 core on the system. - --smp 1 - --default-log-level=info image: docker.redpanda.com/redpandadata/redpanda:v25.1.4 container_name: redpanda-0 volumes: - redpanda-0:/var/lib/redpanda/data networks: - redpanda_network ports: - 18081:18081 - 18082:18082 - 19092:19092 - 19644:9644 console: container_name: redpanda-console image: docker.redpanda.com/redpandadata/console:v3.1.0 networks: - redpanda_network entrypoint: /bin/sh command: -c 'echo "$$CONSOLE_CONFIG_FILE" > /tmp/config.yml; /app/console' environment: CONFIG_FILEPATH: /tmp/config.yml CONSOLE_CONFIG_FILE: | kafka: brokers: ["redpanda-0:9092"] schemaRegistry: enabled: true urls: ["http://redpanda-0:8081"] redpanda: adminApi: enabled: true urls: ["http://redpanda-0:9644"] ports: - 8080:8080 depends_on: - redpanda-0 仔细看了一下,这里面有问题,卷是空的,这可不行,必须持久化到本地,改一下 ...

2025年05月16日 · 2 分钟 · 254 字 · 八戒

Ubuntu 如何安装远程桌面RDP

非常郁闷的一件事。公司的网络架构非常有意思,灵活性极大。导致我这个总在公司之外用openvpn连进去的人很痛苦,无法复现同事提出的问题。 没办法,需要在公司网络内部有台测试机,那实际是有很多的,但都是debian或者ubuntu的,而问题偏偏是网页打不开的问题。 那就干脆找一台Ubuntu,安装一下远程桌面,用RDP 3389连进去,启动一个Firefox,进行测试即可。 步骤如下: 一、升级ubuntu到最新 sudo apt update sudo apt upgrade -y 二、安装xrdp sudo apt install xrdp -y 三、安装桌面环境xfce sudo apt install xfce4 xfce4-goodies -y 四、配置xfce使用xrdp echo xfce4-session > ~/.xsession # 将最后一行替换掉 sudo vi /etc/xrdp/startwm.sh #exec /bin/sh /etc/X11/Xsession exec startxfce4 五、设置xrdp服务自启动 sudo systemctl enable xrdp sudo systemctl start xrdp 六、如果有防火墙,记得放开3389的tcp端口 七、把用户加入ssl-cert组 # 这里我的登录用户是debian sudo adduser debian ssl-cert # 加组不用重启,如有其它改动需要重启 sudo systemctl restart xrdp 八、安装Firefox sudo apt install firefox-esr 九、开远程桌面,连上去,开个命令行执行firefox-esr ...

2025年05月16日 · 1 分钟 · 70 字 · 八戒

mongodb 集群的恢复

要做一个mongodb集群的迁移和恢复,真是折腾死人了。记录并顺便吐槽一下: mongodb的tools真的是太简易、粗糙、丑陋了。 线上的源数据库是腾讯的服务,要废弃搬迁到内网,足足折腾了一个星期。 线上源数据库环境如下: 云数据库 TencentDB 4C/8GB/500GB 3节点 于是在线下对等建立了mongo集群,同样配置,同样3节点。 噩梦由此开始啊,注意线下集群的memory配置一定要高: 32GB 用户名和密码一定要和线上完全一致 如果用户名密码不一致,Document恢复完毕,Index无法建立,因为是完全恢复,会覆盖admin这个db 然后开始dump线上数据 mongodump -vvvvv --uri="mongodb://root:xxxxxxxx@10.1.2.3:27017/?authSource=admin" 然后生成一个24G大小得dump文件夹,压缩后2.4G,传输到线下库上准备恢复 # 大量恢复必须扩大文件句柄,否则过不去 ulimit -n 655360 # 一定要记录下来开始的时间戳,后面有用 date Sun May 11 06:34:02 PM CST 2025 # 开始恢复,漫长的一天开始了 # 必须放到screen中执行,否则网络不好,断了也会很悲剧 screen -L ./mongorestore \ --drop \ --host=192.168.111.222 \ --port=27017 \ --username=root \ --authenticationDatabase=admin \ --nsExclude=admin \ /root/dump ctrl+a+d 其实刚开始线下三节点得配置是8G,然后恢复用了一晚,报错,直接把shard3给打崩了,虽然三节点都是docker启动得,有自动恢复机制,但是mongorestore可不管这个,中断期间有1000个Document没有恢复成功。 重试了2次后才发现这问题,2天已经过去了,意识到就立刻把内存提升到32G,再次进行恢复。 但是新建的集群的密码跟源库这时是不一致的,又一天过去了,Document文档是恢复完毕,Index又无法建立,因为用户名和密码不一致,Fuck! 又双叒再次开始恢复,这下天下大吉了,然后这还不算完! restore后,其实又过了一天,那要追平之后的数据,就得用上实时同步工具了,这里用的是阿里的mongoshake 下载: wget https://github.com/alibaba/MongoShake/releases/download/release-v2.8.5-20250403/mongo-shake-v2.8.5.tgz tar zxvf mongo-shake-v2.8.5.tgz cd mongo-shake-v2.8.5 wget https://raw.githubusercontent.com/alibaba/MongoShake/refs/heads/develop/conf/collector.conf 我们需要注意collector.conf的以下地方: ...

2025年05月12日 · 1 分钟 · 143 字 · 八戒