Eisen's Blog

© 2023. All rights reserved.

压缩 flyway migration

2023 August-24

起因

  2023 08 25 15 05 09

数据库的 migration 随着时间的迁移会越来越多,这会带来一些问题:

  1. 很多老旧代码摆在那里看着很烦,在未来系统升级的时候可能会被标记为 `deprecated`` 成为累赘,比如在新的数据库里挂掉,比如 spring 版本升级了会挂掉
  2. 数据库结构通过一系列 migraiton 才能完成,如果是像 openbayes 这种需要大量部署的情况会导致启动的速度变慢

解决方案

如果确认目前所有的生成环境都已经升级大于 migration 的某一个版本了就可以考虑把之前的 migration 都压缩掉了,这样可以减少一些不必要的麻烦。比如我们确定目前所有的环境的 migration 版本都大于 400 了,那么我们就可以压缩 400 以及之前的 migration。

准备一个 migration 到特定版本的环境

本地启动一个新的 mysql:

docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=mysql -p 3306:3306 -d mysql:8

然后在本地启动一个 springboot 项目,配置好 flyway 的配置,然后把 migration 的版本设置为 400,这样就可以把 migration 生成到 400 了:

./gradlew flywayMigrate -Dflyway.target="400"

这里我发现一个奇怪的问题,如果这里 400 这个版本不是一个 sql 而是一个 java 脚本类型的 migration 则会报错,不知道是不是我配置的问题。

将数据 dump 出来

mysqldump -u<username> -p<password> db > dump.sql

抽取需要的数据导入到 V1 版本的 migration

这里我只保存两部分内容:

  1. create table 的 sql
  2. insert into 的 sql

我会让 chatgpt 分别给我写两个脚本帮我抽取其中的内容然后贴到我的项目里。

"""
抽取 create table 的 sql
"""

import re

def extract_create_table_statements(dump_file):
    create_table_statements = []
    create_table_pattern = r'CREATE TABLE.*?;'

    with open(dump_file, 'r') as file:
        dump_content = file.read()
        create_table_matches = re.findall(create_table_pattern, dump_content, re.DOTALL)

        for match in create_table_matches:
            create_table_statements.append(match.strip())

    return create_table_statements

# Example usage
dump_file = 'dump.sql'
create_table_statements = extract_create_table_statements(dump_file)

for statement in create_table_statements:
    print(statement)
    print()
"""
抽取 insert into 的 sql
"""
import re

def extract_insert_statements(file_path):
    with open(file_path, 'r') as file:
        dump_data = file.read()

    insert_pattern = r'INSERT INTO .*?;'
    insert_statements = re.findall(insert_pattern, dump_data, re.DOTALL)

    return insert_statements

# Usage example
file_path = 'dump.sql'
insert_statements = extract_insert_statements(file_path)

# Print the extracted insert statements
for statement in insert_statements:
    print(statement)
    print()

这里记得手工剔除掉 migration 的 table。

删掉 V400 以及之前的 migration

这样就结束了。

注意

flyway 会有一个 sql 的校验它会阻止你修改已经做过的 migration。这个机制是为了避免手贱不小心修改了老的 sql 不过我们这里是刻意为之,所以就需要把这个关掉了。

application.yaml:

spring:

...

  flyway:
    validate-on-migrate: false

...

k8s pod 无法启动,报错 argument list too long

2023 July-12

事故原因

前天晚上 k8s 集群出现了问题,一开始是有一台 master 的内存不够了,然后导致了一些 k8s 系统的 pod 跪了,在对 master 节点升级之后开始去重启那些受影响的 crd controller,结果发现这个 crd controller 报错:

k8s argument list too long

看到这个报错我很懵逼:我代码都没改,怎么突然就会报错呢,我启动的 argument list 也不长。连续重启两次发现没有什么效果就又去尝试重启其他的 pod 然后发现其他的 pod 也会报同样的错误...导致了问题的扩大。而且这个 crd controller 还是很重要的组件,它启动不起来由它管理的 crd 就都不能更新了,非常头大。

然后就去网上查资料,发现了一个几乎和我们集群一模一样的问题的帖子:事后分析:Kubernetes Pod 无法启动,因为服务太多 ,总结来说就是因为 k8s 默认会把一个 namespace 的 service 创建一组环境变量,然后把这些环境变量注入到 namespace 所有的 pod 里。service 越多,在 pod 里注入的环境变量就越多。

我们的 bayesjob crd 里恰好有几个 service 那么所有启动的 pod 里就会默认多非常多的环境变量,然后恰好那天有个用户跑了非常多的 bayesjob 刚好冲破了这个程序启动注入环境变量的上限就引发了这个问题。

SVC_XXXA_TXWGIWBFC37V_PORT_80_TCP_PORT=80
SVC_BAADF_OE3QGDRQF7OX_PORT_6637_TCP_ADDR=10.97.0.126
SVC_ADMIN_VI6RZ95HMNVY_PORT_7088_TCP_ADDR=10.97.8.254
SVC_ADMIN_VI6RZ95HMNVY_SERVICE_PORT=80
SVC_ADMIN_VI6RZ95HMNVY_PORT=tcp://10.97.8.254:80
...

解决方案

解决的办法就是在 pod 里增加额外的配置:enableServiceLinks: false 告诉 pod 不要主动去注入这些环境变量即可。

总结

之前 bayesjob 的生命周期比较短,并没有遇到这个问题,直到最近调整了它的生命周期(从 72h -> 14d)带来大量存活的 service ,最后在这天又恰好来了一个创建 300+ 任务的用户。这种问题真的是要到一定规模你才能发现的,实现考虑到的难度有点大了。

crd controller 也应该有更好的容错性,不能因为一台 master 出问题就跪了才对,后续要对其进行调整。


解决 TrueNAS Scale HDD 硬盘莫名读写的问题

2022 December-15

最近攒了一台机器作为家里的 NAS,操作系统选择了 TrueNAS Scale ,之所以用这个系统原因有两个:

  1. 它是基于 Debian 的,对我来说比较熟悉,FreeBSD 什么的实在是有点陌生,并且据说那边的驱动跟进也不是很好(当然是据说,我没实操过)
  2. TrueNAS Scale 里面的 App Store 是基于 helm charts 的,TrueNAS Scale 在开启 Apps 这个功能后会跑了一个 k3s 来管理这些 apps 这个操作着实有点激进,不过我还是愿意尝试一下的,也是因为我对这些东西比较熟悉

screenshot of truenas scale applications

基本环境准备好了之后把由 5 块 HDD 组成的 RaidZ1 作为了主存储池就开始使用了。用了一段时间发现系统有一个小小的问题:这些 HDD 差不多每几秒就会卡卡响两下,不是什么异常的声音,就是正常的读写的声音,可我当时并没有使用它做什么事情,而且这种有节奏的噪音让我也有点难受。看了下监控,io 模式就是这个样子:

truenas scale hdd idle read write

首先我就去 google 了,搜来搜去,用关键词「truenas scale idle read write」找到了 TrueNas Scale constant writes while idle 这个问题,下面的回复基本就就是清楚了原因,我这里简单复述下:

TrueNAS 会默认把你建立的第一个 data pool 设定为 System Dataset 然后会在这里塞日志等乱七八糟的东西,尤其是系统日志这种东西它就会持续的对系统进行写入。

既然 HDD 读写声音很讨厌,那我就还是再加一个 nvme 的盘做成存储池来用吧,幸好我的主板 m.2 的插槽管够。插上 nvme 硬盘并设置一个新的存储池后,通过「System Settings」-「Advanced」-「System Dataset Pool」把它修改成心态添加的 nvme 存储即可。

config system dataset

如果要使用上文提到的 Apps 的话,TrueNAS Scale 也会要求你指定存储的位置,考虑到性能和噪声也要把它指向 nvme 的存储。

所以对于 TrueNAS Scale 来说,最好要准备三个部分的存储:

  1. 系统盘,16 GB 就够了(此处应该有傲腾 M10):TrueNAS 就是把系统放这里,其他乱七八糟的东西统统要放到额外的存储池里
  2. 主存储,就是放数据的地方,要做好冗余方案:我这里依然选择了老土 HDD,当然还是考虑到价格比较便宜
  3. 应用存储,可以认为是工作空间:把系统日志,Apps 的一些配置什么的都扔这里,对我来说这些数据不是那么重要,我直接就放了一个 m.2 的 nvme。由于这个空间会因为 Apps 的各种操作和系统日志而频繁读写,使用 ssd 很有必要