你好,我是 Hyacinthus
喜欢探索新东西
也曾研究 Python,也曾布道 Golang
现在在 Vibe Coding
这里是我的思考和分享
今天把我几年没写的 Blog 翻出来,准备重新开始写点东西。既然如此,当然要换一个新的模板。 现在时代不同了,我 Clone 好之后,打开 claude code,输入:请把模板换成 PaperMod 放弃使用 git submodule,适配最新版的 hugo,修复任何潜在的错误。很快,一切都搞定了。 很多年前,我还是用 VSCode 或者 Typora,现在用的是 Obsidian,因为 Hugo 的文章需要 Front Matter,我就让 claude 在 content 中创建 template,让我可以方便的插入 Front Matter。然后噩梦就开始了。 这两个月,AI 已经进化到可怕,Claude Code 在手有种天下无敌茫然四顾的感觉,写了或者改了很多项目现在基本上指哪打哪。结果这个 Template,难住了它,Hugo 无论如何都不忽略,改成了隐藏文件夹,hugo 倒是忽略,obsidian 也忽略了,无法用做模板。它开始劝说我,在 obsidian 里打开项目根目录,而不是 content 目录,这怎么能忍。又祭出 Gemini,依然无解,反反复复改很多次,尝试了若干种办法,把 Claude 走过的坑再走一遍。 最后,还是亲自出马吧,我研究了一下 hugo,发现 ignoreFiles 这个配置要放在 hugo.toml 的最前边,就是这么简单,这个 key 如果出现在某个 key 后边就失效了。这应该是 hugo 的一个 bug 吧。我搜索到了很多讨论,都没提到这一点。 这是原始文档,很多人在 reddit 说这个配置是无效的: https://gohugo.io/configuration/all/#ignorefiles
大家好,这是又一个向微信发通知的服务。这样的服务其实不少,一定是有新的特色我才好意思来介绍。 Server饭不仅可以给微信发通知,还能反过来用微信和服务器产生交互。大概像下面这样: 好了,还是先介绍基础功能吧。 主动发送通知 给自己发消息是最常用的功能。 拉到文末扫码关注服务号,或者在微信搜索 “LetServerRun” 这个服务号关注。 在服务号发送 token 命令查看自己的用户 token 。 使用用户 token,您就可以用 API 向公众号发警告消息了: curl "https://api.letserver.run/message/info?token=YOUR-TOKEN&msg=hello" 这个 GET 接口是为了调试和轻量使用场景的,在程序中使用的话有一系列接口和SDK。 反向控制服务器 Server饭的特色功能是用微信控制服务器做简单的事情。就像一开始的图里那样。 放心,不需要你提供ssh密钥,为了安全,命令能做什么完全由你定义。 实现的原理是在服务号中你发的命令会被存储在云端, Agent 每分钟向云端发起请求检查一次,如果有命令则拉回来执行它。 执行完成之后可以返回成功或者失败的结果,你就会在微信服务号上看到。 Agent 哪里来呢?有这么几种选择: 最自由:调用我们的API自己写 省事点:调用 SDK 自己写 够用就好:直接用我们几个开源的方案 这里我们先使用一个开源的通用 Agent来上手。 它可以帮你在服务器执行特定的命令。后面我们可以根据需求,自己通过 API 或 SDK,集成 Agent 或自己编写。 假设你的服务器是 Debian/Ubuntu ,如果是别的请参考 安装通用Agent 如果您本身就是 root 用户,麻烦去掉所有命令中的 sudo # 注册仓库 curl -1sLf \ 'https://dl.cloudsmith.io/public/hackfan/skadi/setup.deb.sh' \ | sudo -E bash # 更新 apt update # 安装 apt install skadi 在安装后,因为还没有 Token,所以并没有自动启动。 Token 哪来的呢?在服务号输入命令: agent add 名字 (名字是要你给它取个简单的名字,以后每次都要用它发命令) 然后将得到的 Token 写入配置文件,像下面这样。 你也可以编辑 /etc/skadi/skadi.yml 这个文件自己写入。 ...
每次有一个新的数据库,都要琢磨着配置一个备份。当然你可能有一个祖传的 shell 脚本,重新配置一下放到 crontab 里就可以了。但是无论是查看和修改配置,还是查看 crontab 都不是那么方便。 今天介绍一个 Docker 镜像,只需要简单的配置启动,就可以。地址在这里:https://hub.docker.com/r/deitch/mysql-backup/ 但你也许不需要点开链接,先看完我的例子吧。 version: '3.7' services: backup: image: databack/mysql-backup volumes: - /root/dbbackup:/db environment: - DB_DUMP_TARGET=/db - DB_USER=root - DB_PASS=root - DB_SERVER=xxx.xxx.xxx.xxx user: root deploy: placement: constraints: - node.id == YOURDOCKERSWARMNODEID 讲要点 Version 我用了最新版的 docker-compose 格式,优点就是不用在装 docker-compose … 使用新版 Docker 自带的 docker stack -c backup.yml backup 这样子启动 stack 就好。这样机器就只用装 docker 一个包。 如果你想用旧版的 docker-compose 点开上边作者的 readme 里有例子。 ...
缘起 事情是这样的,我在知乎受到邀请回答一个问题,主要是问 ID 找不到到底要不要用 Status 404 。我回答的还是比较早的,那时候只有一两个回答。我本来以为这是没啥争议的,在一个学术的地方讨论学术问题,当然是要遵守规范了,结果过了几个小时大跌眼镜。自造 code 党竟然支持率第一,还好平时见的也很多的全 200 党没有受到支持,不然真的吐血了。 为什么要遵守规范 一般那种说特殊情况特殊处理,不要拘泥于规范的人,大多都是自己没搞清楚某些知识,拿这句话当作偷懒的借口。其实一般做项目没那么多特殊情况。 为了更好的适应各种库 大部分完善的 HTTP 请求库,都会依照 RFC 的规范去设计错误处理的流程,虽然处理方式各有不同,但一定会在文档说明错误处理的部分的。使用 RFC 标准能最大限度的兼容各种 HTTP 客户端。你说现在你用的HTTP客户端不处理 Status Code,但是你没法保证将来不重构,重构的时候还是不处理。 一般调用 api 使用 js 或者 python 的概率比较大,我们看看知名的库。在 js 里,最近比较流行的 axios 默认会把 200 系列外的 code 归到异常里。在 python 里,最流行的 http client 是 requests ,它更为详尽的预处理了 status code 。 为了开发者更好上手 另外在管理团队的方面,我们的原则是尽量的减少一个项目的“规范”,这样才能更容易去遵守。能用标准的地方,一定不要自己定一个更复杂的规则。无论是服务端的维护者还是 API 的消费者是会换人流动的,每个进入项目的人熟悉一大堆无谓的自定义项目规范都要成本。 更简单的办法是参考大厂 其实给项目定规范,最不靠谱的是自己拍脑袋,稍好一点的是去知乎或论坛问,更好一点的是去 google 搜,最简单的是直接去看大厂的产品或者规范啊。 API 本来就是个公开暴露的东西,还有比这更好找参考的吗?我们来看看: Google 遵守规范 Github 遵守规范 Microsoft 遵守规范 顺便说一句,微软的 API 规范真的很具有指导意义。 Twitter 遵守规范 阿里云 遵守规范 腾讯云 不遵守规范 全部 200 事实上腾讯的技术比较混乱,每个项目都不一样。但最新要执行的统一规范是全部 返回 200 用返回值中的错误码表明错误。 百度云 遵守规范 我的建议 很多人也许用着很简陋的 Web 框架,导致误以为返回了错误码,就不能返回 Response Body 了。其实你返回 204 外的任何 Status Code,最好都伴随着返回 Body 。 ...
简介 今年夏天又开始新的创业项目,忙得没日没夜,好久没写博客了。 但是也许 Docker 越来越火,知乎的专栏每天都有新的人关注,不抽空写点太对不起大家。 之前写过 API 镜像,今天来说说前端镜像。 本文适用于任何一个需要编译的前端框架,我们利用 Docker 的两段构建,用一个 Dockerfile 一气呵成的产出不含源码的生产镜像。 镜像内用了 Caddy 当作服务器,又经过了半年的发展,虽然版本还没到1,但 Caddy 已经足够强大和健壮了。 Caddyfile 为了能让项目在 Caddy 镜像中被访问,我们在项目根目录建一个叫 Caddyfile 的文件,供后续镜像内启动服务时使用。 0.0.0.0:80 root /www gzip log stdout errors stdout 解释一下,我们未来会把编译好的项目放在容器的 /www 目录。 服务器在容器的80端口。启用gzip,并且将日志输出在stdout–这是 Docker 的推荐做法。 Dockerfile 然后就开始我们的二段构建了: # build FROM node:8 as builder ADD . /src WORKDIR /src RUN npm i && npm run build # product FROM abiosoft/caddy COPY --from=builder /src/dist /www COPY Caddyfile /etc/Caddyfile 我们首先使用 node 的官方镜像对项目进行编译,工作目录为 /src ,编译结果为 /src/dist 。 然后我们再将 dist 文件夹复制为 caddy 镜像的 /www 目录。 最后我们将我们的 Caddyfile 覆盖镜像原版的。 ...
Docker 的口号是 Build, Ship, and Run Any App, Anywhere. 但是我们在应用过程中会遇到一个问题,我们在 build 的时候,把源码也 build 进去了。 然后就继续把源码 Ship 出去吗?这可不行。所有的编译型语言都面临这个困扰。 即使是脚本型语言,build 的时候也会使用很多上线时用不到的构建工具, 而我们希望减小生产镜像的体积,这样我们的小鲸鱼才能多拉一点集装箱嘛。 传统做法 我们最终的目的是要将编译好的可执行文件复制到 alpine 这样的迷你镜像里, 那么该怎么弄到编译好的文件呢?基于 Docker 的思想,我们肯定需要在一个标准容器中编译, 这样这个过程才是标准化的,再说,你在 Ubuntu 编译出一个二进制文件在 alpine 也运行不了。 于是我们先需要准备一个编译用的自定义镜像。一般是用相应语言的 alpine 基础镜像, 把编译项目额外需要的各种工具打包进去,比如 golang 目前没有官方的包管理, 你就需要把你用的包管理工具装进去。 然后我们需要在运行 container 时把主机的一个目录通过 -v 挂载到 container上, 让它把编译的结果输出到这个挂载的目录,这样我们就在主机上拿到这个文件了。 最后,我们用一个最小的 alpine 镜像,把二进制文件复制进去。 可能你还需要设置一下时区之类的。 持续集成 上面的流程,在用持续集成工具时又变成了一个问题。你会发现每一家 CI 提供商都不太一样。 你未必有权限控制 CI 时的宿主机。 比如 Docker Cloud,你需要定义 pre-build 的 hook 去完成这个工作, 在 SEMAPHORE,你发现你有了一台宿主机,这下和我们在本地的做法可以一样了。 在更多的提供商,你会发现他们只是能根据 git 仓库和 Dockerfile 构建镜像, 你用他们的系统甚至没办法做出一个最小镜像…… 中国的 DaoCloud 其实挺先进的,很早就推出了安全镜像的概念,让你的构建通过两步完成。 但是,那个配置的内容太多让不太懂的人看了直接晕掉。 ...
借助移动端的增长,如今 RESTful 风格的 API 已经十分流行, 用各种语言去写后端 API 都有很成熟方便的方案,用 golang 写后端 API 更是生产力的代表, 你可以用不输 python/ruby 这类动态语言的速度,写出性能高出一两个数量级的后端 API 。 ECHO 框架 由于 golang 的标准库在网络方面已经很完善,导致框架发挥余地不大。很多高手都说, 用什么框架,用标准库就写好了,框架只是语法糖而已,还会限制项目的发展。 不过我们并不是高手,语法糖也是糖,用一个趁手的框架还是能提高不少效率的。 要是在半年前,你让我推荐框架,我会说有很多,都各有优缺点,除了 beego 随便选一个就可以。 但是来到2017年,一个叫 Echo 的框架脱颖而出。这是我目前最推荐的框架。 Echo 的宣传语用的是 “高性能,易扩展,极简 Go Web 框架” 。它的一些特性如下图所示: 这些特性里,HTTP/2,Auto HTTPS,听着很熟?这是我之前介绍的 Caddy 也有的特性, 因为 golang 实现这些太容易了。还有 Middleware 里的一大堆功能也差不多。 我们在做微服务的时候,这些通用的东西由 API Gateway 统一实现就好了, 如果你写的是个小的独立应用的后端,这些开箱即用的功能倒是能提供很大的帮助。 其实今天我主要想说说最后一个特性里提到的,“中心化的 HTTP 错误处理”。 RESTful API 错误返回 一个团队应当有一份 RESTful API 的规范,而在规范中应该规范响应格式,包括所有错误响应的格式。 比如微软的规范, jsonapi.org 推荐规范等等。 大部分时候我们不需要实现的那么繁琐,我们规定一个简单的结构: STATUS 400 Bad Request { "error": "InvalidID", "message": "invalid id in your url query parameters" } 传统的错误响应可能只有一个伴随 HTTP Status code 的 string 类型的 message, 如今我们把正常的响应格式变成了 JSON ,那么把错误返回也用 JSON 吧。 除了用 JSON 之外,我们又增加了一个 error 字段, 这个字段是一个比 Status code 要详细一个级别的 Key, 消费端可以用这个约定的 Key 做更为灵活的错误处理。 ...
今天我写一点在 Docker 容器中使用 MYSQL 的 tips. 要不要在生产环境使用 Docker 运行数据库这么深奥的问题,等我踩足够的坑再来写吧。 但是至少在开发和测试环境你可以用 docker 管理数据库啊。 Compose file 先贴一个我常用的 docker-compose 片段,后边进行详细的解释。这是日常使用的状态,此处省略了别的服务。 version: '2' services: mysql: image: mysql:5.7.16 ports: - ${DB_PORT}:3306 environment: - TZ=Asia/Shanghai command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci volumes: - ./mysql_data:/var/lib/mysql restart: unless-stopped 版本 一定要锁定镜像到最小的版本,因为mysql镜像升级后需要你手动在容器中执行命令去 mysql-upgrade,否则很久之后,你才发现有的数据已经损坏了。 实在想升级,升级步骤如下: 如果是用 docker run 启动的,那么停掉再启动个新的。如果是用 docker-compose 启动的,直接改版本号 pull 镜像重新 up。 在新的版本启动后执行 docker exec -it 你的容器名称或id mysql-upgrade 端口 如果想在外部通过工具访问,需要将3306端口映射到host上的一个端口,不准备外部访问的环境则不必。 一般来说开发和测试环境还是经常需要登到数据库上查看的。 时区 添加环境变量 TZ 让mysql用你的默认时区启动。 这是因为 mysql 的基础镜像是 debian, 这个环境变量可以声明 debian 的时区,然后被 mysql 继承。 ...
背景 大家都知道,Docker这些年让IT界产生了深刻的变革, 从开发到测试到运维,处处都有它的身影。 它同时也和微服务架构相互促进,并肩前行。 在最新版的 Docker(CE 17.03) 里,随着 swarm mode 的成熟, 在较简单的场景里已经可以不再需要专门的基础设施管理, 服务编排,服务发现,健康检查,负载均衡等等。 但是API gateway还是需要一个的。或许再加上一个日志收集, 你的微服务架构就五脏俱全了。 我们知道Nginx Plus是可以很好的胜任 API gateway 的工作的, 但它是商业软件。Nginx我们不说认证啊限流啊统计啊之类的功能, 单就请求转发这一点最基本的就出了问题。 我们知道Docker是用DNS的方式,均衡同一名称的服务请求到不同的node, 但是Nginx为了速度,在反向代理的时候会有一个不可取消的 DNS Cache, 这样我们Docker在根据容器的扩展或收缩动态的更新DNS,可Nginx却不为所动, 坚持把请求往固定的IP上发,不说均衡,这个IP甚至可能已经失效了呢。 有一个配置文件上的小Hack可以实现Nginx每次去查询DNS,我本来准备写一篇文章来着, 现在看来不用了,我们找到了更优雅的API gateway, Caddy 。 我上篇文章也写了一个它的简介。 接下来的所有代码,都在这个demo中, 你可以clone下来玩,也能在此基础上做自己的实验。 应用 我们先用golang写一个最简单的HTTP API,你可以用你会的任何语言写出来, 它为GET请求返回 Hello World 加自己的 hostname . package main import ( "io" "log" "net/http" "os" ) // HelloServer the web server func HelloServer(w http.ResponseWriter, req *http.Request) { hostname, _ := os.Hostname() log.Println(hostname) io.WriteString(w, "Hello, world! I am "+hostname+" :)\n") } func main() { http.HandleFunc("/", HelloServer) log.Fatal(http.ListenAndServe(":12345", nil)) } Docker 化 我们需要把上面的应用做成一个docker镜像,暴露端口12345。 接着才有可能使用Docker Swarm启动成集群。 本来做镜像特别简单,但我为了让大家直接拉镜像测试时快一点,用了两步构建, 先编译出应用,然后添加到比较小的alpine镜像中。大家可以不必在意这些细节。 我们还是先来看看最终的docker-compose.yml编排文件吧。 ...
caddy 是一个像 Apache, nginx, 或 lighttpd 的web服务器。 你要问nginx已经很好了,为什么要用caddy呢? 我觉得caddy最大的特点是用起来简单, 然后呢,它还有下面这些开箱即用的特性: HTTP/2 全自动支持HTTP/2协议,无需任何配置。 Auto HTTPS Caddy 使用 Let’s Encrypt 让你的站点全自动变成全站HTTPS,无需任何配置。当然你想使用自己的证书也是可以的。 Multi-core 因为caddy是golang写的,所以当然可以合理使用多核啦。 IPv6 完全支持IPv6环境. WebSockets Caddy 对WebSockets有很好的支持. Markdown 自动把md转成 HTML ,当然,我后续要给大家介绍更强大的hugo来干这个事情. Logging Caddy 对log格式的定义很容易,更好的满足你日志收集的需求。 Easy Deployment 得益于go的特性,caddy只是一个小小的二进制文件,没有依赖,很好部署。 那么在什么场景下适合尝试使用caddy呢,我推荐从以下场景开始: 作为静态页面的webserver 转发 fastcgi 请求到 php-fpm 服务,比如替换apache或nginx作为wordpress的server 反向代理,管理多个站点 微服务的 API gateway ,我会专门写一篇文章。 有些在nginx上难以开发的需求,为caddy写插件太方便了。 入门 安装caddy 下载 caddy 把caddy放到系统的PATH中,让其可以直接执行。比如Linux中一般习惯放到 /usr/local/bin 简单测试 找一个做测试的临时目录,生成一个测试主页。echo "hello world">index.html 执行 caddy 在另一个终端 curl localhost:2015 或在浏览器访问 (http://localhost:2015) Caddyfile caddy的一个特色就是配置简单,nginx的配置文件群已经越看越晕了。我们来试试: 在当前目录创建这样一个叫Caddyfile的文件: localhost:2020 gzip 这次,我们改变了端口,并且启用了gzip自动压缩数据。运行caddy,去你指定的地址看看吧。 ...